Merge kmm-impl into dev (#1813)

* Feat: KMP Library Setup (#1766)

* Migrating from hilt to koin (This) (#1764)

* Migrating from hilt to koin

* Fixed Instance creation error

* refactor: Removed Hilt and migrated to Koin

This commit removes Hilt and migrates the project to Koin for dependency injection.

The following
 changes were made:

- Removed the `AndroidHiltConventionPlugin`.
- Added the `AndroidKoinConventionPlugin`.
- Updated dependencies to use Koin.
- Updated KSP configuration for Koin.
- Updated feature modules to use Koin.
- Updated common modules to use Koin
.
- Removed Hilt annotations and replaced them with Koin annotations.
- Updated ViewModels to use Koin for dependency injection.
- Updated modules to use Koin for dependency injection.

* Formatted Dependencies

* migrating from hilt to koin clean up commit

* Revert "migrating from hilt to koin clean up commit"

This reverts commit bb63058e49.

---------

Co-authored-by: Sk Niyaj Ali <niyaj639@gmail.com>

* Feat: KMP Library Setup

---------

Co-authored-by: Nagarjuna <99315689+Nagarjuna0033@users.noreply.github.com>

* Feat: [:core:model] - Migrated to KMM (#1770)

* Feat: [:core:common] KMP Migration (#1768)

* Feat: [:core:common] KMP Migration

* Updated Usage Declaration

* Feat: [:core:datastore] - Migrated to KMP (#1769)

* Feat: [:core:network] - Migrated to KMP (#1772)

* Feat: [:core:network] - Migrated to KMP

* Feat: [:core:data] - Migrated to KMP Library

* Feat: [:core:designsystem] - Migrated to KMP with CMP Library (#1774)

* Feat: [:core:ui] - Migrated to KMP with CMP Library (#1775)

* Feat: [:feature:auth] - Migrated to Kotlin Multiplatform (#1782)

* Feat: [:feature:auth] - Migrated to Kotlin Multiplatform

* Added Support For Web

* Feat: Migrated Passcode Module to KMP (#1783)

* Feat: Migrated Home Module to KMP (#1784)

* Feat: Migrated Edit Password Module to KMP (#1787)

* Feat: Migrated FAQ module to kmp (#1786)

* Feat: Migrated Settings Module to KMP (#1785)

* Feat: Migrated Profile Module to KMP (#1788)

* Feat: Migrated History Module to KMP (#1790)

* Feat: Migrated Payments Module to KMP (#1791)

* Feat: Migrated Finance Module to KMP (#1792)

* Feat: Migrated Accounts Module to KMP (#1793)

* Feat: Migrated Accounts Module to KMP

* Updated README.md

* Update README.md

* Feat: Migrated Invoices Module to KMP (#1794)

* Feat: Migrated KYC Module to KMP (#1798)

* Migrated Notification Module to KMP (#1799)

* Feat: Migrated KYC Module to KMP

* Feat: Migrated Notification Module to KMP

* Feat: Migrated Saved Card Module to KMP (#1800)

* Feat: Migrated Receipt Module to KMP (#1801)

* fix: Ios Build (#1802)

* Feat: Migrated SI Module to KMP (#1803)

* Feat: Migrated Request Money Module to KMP (#1807)

* Feat: Migrated Send Money Module to KMP (#1808)

* Feat: Migrated Make Transfer Module to KMP (#1809)

* Feat: Migrated QR Module to KMP (#1810)

* Feat: Migrated UPI Setup Module to KMP (#1811)

* Feat: Final Clean-up For KMP (#1812)

* Feat: Final Cleanup For KMP

* Update README.md

* Update EditPassword README.md

* Update FAQ README.md

* Update Finance README.md

* Update History README.md

* Update Home README.md

* Update Invoice README.md

* Update KYC README.md

* Update Make Transfer README.md

* Update Notification README.md

* Update Payments README.md

* Update Profile README.md

* Update Saved Card README.md

* Update Send Money README.md

* Update Settings README.md

* Update SI README.md

* Migrating from hilt to koin (This) (#1764)

* Migrating from hilt to koin

* Fixed Instance creation error

* refactor: Removed Hilt and migrated to Koin

This commit removes Hilt and migrates the project to Koin for dependency injection.

The following
 changes were made:

- Removed the `AndroidHiltConventionPlugin`.
- Added the `AndroidKoinConventionPlugin`.
- Updated dependencies to use Koin.
- Updated KSP configuration for Koin.
- Updated feature modules to use Koin.
- Updated common modules to use Koin
.
- Removed Hilt annotations and replaced them with Koin annotations.
- Updated ViewModels to use Koin for dependency injection.
- Updated modules to use Koin for dependency injection.

* Formatted Dependencies

* migrating from hilt to koin clean up commit

* Revert "migrating from hilt to koin clean up commit"

This reverts commit bb63058e49.

---------

Co-authored-by: Sk Niyaj Ali <niyaj639@gmail.com>

* Profile UI redesign  (#1767)

* Refactor Profile UI

* Fixed Build Issue

* Profile UI Bug Fix & Improvement

* added placeholder for  when uri is null

cleanup

fix spotless test failure

* Fix - CI Build Issue

---------

Co-authored-by: Sk Niyaj Ali <niyaj639@gmail.com>

* refactor: Redesign payment screen (#1773)

* refactor: Redesign payment screen

* resolved detekt error

* refactor : changed current theme instead of using NewUi

* resolved spotless errors

* refactor: Redesign finance screen (#1777)

* refactor: Redesign payment screen

* resolved detekt error

* refactor : changed current theme instead of using NewUi

* resolved spotless errors

* refactor: Redesign finance screen UI

* resolved spotless errors

* resolved conflicts

* changed structure of accounts screen

* refactor padding values in finance screen

* fix: dark theme colors (#1789)

* fix: dark theme colors

* fixed edit icon tint

* Fix invoice api (#1797)

* Redesign requeset screen UI

* fix MissingKoinDefinitionException

* removed comments and fixed share qr code bug

* fix: Invoice APIs

* update readme file (#1804)

* update readme file

* update readme file

* update readme file with how to contribute

* Add branch policy (#1805)

* update readme file

* update readme file

* update readme file with how to contribute

* update readme file with how to contribute

* update readme file with branch Policy

---------

Co-authored-by: Rajan Maurya <therajanmaurya@users.noreply.github.com>

* Migrating from hilt to koin (This) (#1764)

* Migrating from hilt to koin

* Fixed Instance creation error

* refactor: Removed Hilt and migrated to Koin

This commit removes Hilt and migrates the project to Koin for dependency injection.

The following
 changes were made:

- Removed the `AndroidHiltConventionPlugin`.
- Added the `AndroidKoinConventionPlugin`.
- Updated dependencies to use Koin.
- Updated KSP configuration for Koin.
- Updated feature modules to use Koin.
- Updated common modules to use Koin
.
- Removed Hilt annotations and replaced them with Koin annotations.
- Updated ViewModels to use Koin for dependency injection.
- Updated modules to use Koin for dependency injection.

* Formatted Dependencies

* migrating from hilt to koin clean up commit

* Revert "migrating from hilt to koin clean up commit"

This reverts commit bb63058e49.

---------

Co-authored-by: Sk Niyaj Ali <niyaj639@gmail.com>

* Merge kmm-impl to dev

---------

Co-authored-by: Nagarjuna <99315689+Nagarjuna0033@users.noreply.github.com>
Co-authored-by: Rajan Maurya <therajanmaurya@users.noreply.github.com>
Co-authored-by: Pronay Sarker <pronaycoding@gmail.com>
Co-authored-by: kapmaurya <152150716+kapmaurya@users.noreply.github.com>
This commit is contained in:
Sk Niyaj Ali 2024-11-06 20:52:46 +05:30 committed by GitHub
parent 49101cde9a
commit 3782fd0d6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1432 changed files with 57697 additions and 49510 deletions

View File

@ -1,32 +0,0 @@
name: Mobile-Wallet CI[Feature]
on:
push:
branches:
- '*'
- '!dev'
- '!master'
jobs:
build:
name: Build APK
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Set up JDK
- name: Set Up JDK 17
uses: actions/setup-java@v1
with:
java-version: 17
# Install NDK
- name: Install NDK
run: echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;20.0.5594570" --sdk_root=${ANDROID_SDK_ROOT}
# Update Gradle Permission
- name: Change gradlew Permission
run: chmod +x gradlew
# Build App
- name: Build with Gradle
run: ./gradlew assemble

View File

@ -75,7 +75,7 @@ jobs:
- name: Check Dependency Guard - name: Check Dependency Guard
id: dependencyguard_verify id: dependencyguard_verify
continue-on-error: true continue-on-error: true
run: ./gradlew dependencyGuard run: ./gradlew :mifospay-android:dependencyGuard
- name: Prevent updating Dependency Guard baselines if this is a fork - name: Prevent updating Dependency Guard baselines if this is a fork
id: checkfork_dependencyguard id: checkfork_dependencyguard
@ -88,7 +88,7 @@ jobs:
id: dependencyguard_baseline id: dependencyguard_baseline
if: steps.dependencyguard_verify.outcome == 'failure' && github.event_name == 'pull_request' if: steps.dependencyguard_verify.outcome == 'failure' && github.event_name == 'pull_request'
run: | run: |
./gradlew dependencyGuardBaseline ./gradlew :mifospay-android:dependencyGuardBaseline
- name: Push new Dependency Guard baselines if available - name: Push new Dependency Guard baselines if available
uses: stefanzweifel/git-auto-commit-action@v5 uses: stefanzweifel/git-auto-commit-action@v5
@ -109,7 +109,8 @@ jobs:
java-version: 17 java-version: 17
- name: Run tests - name: Run tests
run: | run: |
./gradlew testDemoDebug :lint:test :mifospay:lintProdRelease :lint:lint ./gradlew :mifospay-android:testDemoDebug
# ./gradlew testDemoDebug :lint:test :mifospay-android:lintProdRelease :lint:lint
- name: Upload reports - name: Upload reports
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@ -130,12 +131,12 @@ jobs:
java-version: 17 java-version: 17
- name: Build APKs - name: Build APKs
run: ./gradlew :mifospay:assembleDemoDebug run: ./gradlew :mifospay-android:assembleDemoDebug
- name: Check badging - name: Check badging
# This step is allowed to fail, as it's not critical for the build # This step is allowed to fail, as it's not critical for the build
continue-on-error: true continue-on-error: true
run: ./gradlew :mifospay:checkProdReleaseBadging run: ./gradlew :mifospay-android:checkProdReleaseBadging
- name: Upload APKs - name: Upload APKs
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

View File

@ -0,0 +1,70 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="mifospay-android" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
<module name="mobile-wallet.mifospay-android.main" />
<option name="DEPLOY" value="true" />
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
<option name="DEPLOY_AS_INSTANT" value="false" />
<option name="ARTIFACT_NAME" value="" />
<option name="PM_INSTALL_OPTIONS" value="" />
<option name="ALL_USERS" value="false" />
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
<option name="CLEAR_APP_STORAGE" value="false" />
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
<option name="MODE" value="default_activity" />
<option name="RESTORE_ENABLED" value="false" />
<option name="RESTORE_FILE" value="" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<option name="DEEP_LINK" value="" />
<option name="ACTIVITY_CLASS" value="" />
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="mifospay-desktop" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="-DmainClass=MainKt --quiet" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":mifospay-desktop:desktopRun" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="mifospay-web-js" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":mifospay-web:jsBrowserRun" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="mifospay-web-wasm" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":mifospay-web:wasmJsBrowserRun" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -6,24 +6,103 @@ Mobile Wallet is an Android-based framework for mobile wallets based on top of <
<a href='https://github.com/openMF/mobile-wallet/wiki/Architecture'>clean architecture</a> and contains a core library module <a href='https://github.com/openMF/mobile-wallet/wiki/Architecture'>clean architecture</a> and contains a core library module
that can be used as a dependency in any other wallet based project. It is developed at <a href='https://mifos.org/'>MIFOS</a> together with a global community. that can be used as a dependency in any other wallet based project. It is developed at <a href='https://mifos.org/'>MIFOS</a> together with a global community.
# Run the project
![Screenshot 2024-10-19 005524](https://github.com/user-attachments/assets/8023c529-1215-4c4b-b212-630f0233223f)
- To run the android-app select the `mifospay-android` run configuration and click run.
- To run the desktop-app select the `mifospay-desktop` run configuration and click run.
- To run the web-app-js select the `mifospay-web-js` run configuration and click run.
## KMP Status for modules
| Module | Progress | Desktop supported | Android supported | iOS supported | Web supported(JS) | Web supported(WASM-JS) |
|-------------------------------|----------|-------------------|-------------------|---------------|-------------------|-------------------------|
| mifospay-android | Done | ✅ | ✅ | ❔ | ✅ | ❔ |
| mifospay-desktop | Done | ✅ | ✅ | ❔ | ✅ | ❔ |
| mifospay-web | Done | ✅ | ✅ | ❔ | ✅ | ❔ |
| mifospay-ios | NO OP | ❌ | ❌ | ❌ | ❌ | ❌ |
| :core:analytics | Done | ❌ | ✔️ | ❔ | ❌ | ❔ |
| :core:common | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :core:data | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :core:datastore | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :core:datastore-proto | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :core:designsystem | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :core:domain | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :core:model | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :core:network | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :core:ui | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:auth | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:editpassword | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:faq | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:history | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:home | Done | ✅ | ✅ | ❔ | ✅ | ❌ |
| :feature:profile | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:settings | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:payments | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:finance | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:account | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:invoices | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:kyc | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:make-transfer | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:merchants | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:notification | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:qr | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:receipt | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:request-money | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:saved-cards | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:send-money | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:standing-instruction | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
| :feature:upi-setup | Done | ✅ | ✅ | ❔ | ✅ | ✅ |
✅: Functioning properly
❔: Not yet tested, but expected to work
✔️: Successfully compiled
❌: Not functioning, requires further attention
## Notice ## Notice
:warning: We are fully committed to implement [Jetpack Compose](https://developer.android.com/jetpack/compose) and moving ourself to support :warning: We are fully committed to implement [Jetpack Compose](https://developer.android.com/jetpack/compose) and moving ourself to support
`kotlin multi-platform`. **If you are sending any PR regarding `XML changes` we will `not` consider at this moment but converting XML to jetpack compose are most welcome.** If you sending any PR regarding logical changes in Activity/Fragment you are most welcome. `kotlin multi-platform`. **If you are sending any PR regarding `XML changes` we will `not` consider at this moment but converting XML to jetpack compose are most welcome.** If you sending any PR regarding logical changes in Activity/Fragment you are most welcome.
| Development | Chat |
Development | Chat | |--------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|
|-----------------|-----------------| | ![Mobile-Wallet CI[Master/Dev]](https://github.com/openMF/mobile-wallet/workflows/Mobile-Wallet%20CI%5BMaster/Dev%5D/badge.svg?branch=dev) | [![Join the chat at https://mifos.slack.com/](https://img.shields.io/badge/Join%20Our%20Community-Slack-blue)](https://mifos.slack.com/) |
![Mobile-Wallet CI[Master/Dev]](https://github.com/openMF/mobile-wallet/workflows/Mobile-Wallet%20CI%5BMaster/Dev%5D/badge.svg?branch=dev) | [![Join the chat at https://mifos.slack.com/](https://img.shields.io/badge/Join%20Our%20Community-Slack-blue)](https://mifos.slack.com/) |
## Join Us on Slack ## Join Us on Slack
Mifos boasts an active and vibrant contributor community, Please join us on [slack](https://join.slack.com/t/mifos/shared_invite/zt-2f4nr6tk3-ZJlHMi1lc0R19FFEHxdvng). Once you've joined the mifos slack community, please join the `#mobile-wallet` channel to engage with mobile-wallet development. If you encounter any difficulties joining our Slack channel, please don't hesitate to open an issue. This will allow us to assist you promptly or send you an invitation. Mifos boasts an active and vibrant contributor community, Please join us on [slack](https://join.slack.com/t/mifos/shared_invite/zt-2f4nr6tk3-ZJlHMi1lc0R19FFEHxdvng). Once you've joined the mifos slack community, please join the `#mobile-wallet` channel to engage with mobile-wallet development. If you encounter any difficulties joining our Slack channel, please don't hesitate to open an issue. This will allow us to assist you promptly or send you an invitation.
### [How to Contribute](https://github.com/openMF/mobile-wallet/wiki/How-to-Contribute)
### [Branch Policy](https://github.com/openMF/mobile-wallet/wiki/Branch-Policy) ## How to Contribute
Thank you for your interest in contributing to the Mobile Wallet project by Mifos! We welcome all contributions and encourage you to follow these guidelines to ensure a smooth and efficient collaboration process.
The issues should be raised via the GitHub issue tracker. For Issue tracker guidelines please click <a href="https://github.com/openMF/mobile-wallet/blob/master/.github/CONTRIBUTING.md#issue-tracker">here</a>. All fixes should be proposed via pull requests. For pull request guidelines please click <a href="https://github.com/openMF/mobile-wallet/blob/master/.github/CONTRIBUTING.md#pull-requests">here</a>. For commit style guidelines please click <a href="https://github.com/openMF/mobile-wallet/wiki/Commit-style-guide">here</a>.
### Branch Policy
We have the following branches :
* **dev**
All the contributions should be pushed to this branch. If you're making a contribution,
you are supposed to make a pull request to _dev_.
Please make sure it passes a build check on Travis.
It is advisable to clone only the development branch using the following command:
```
git clone -b <branch> <remote_repo>
```
With Git 1.7.10 and later, add --single-branch to prevent fetching of all branches. Example, with development branch:
```
git clone -b dev --single-branch https://github.com/username/mobile-wallet.git`
```
* **master**
The master branch contains all the stable and bug-free working code. The development branch once complete will be merged with this branch.
### Demo credentials ### Demo credentials
Fineract Instance: demo.mifos.io Fineract Instance: demo.mifos.io

View File

@ -23,11 +23,8 @@ dependencies {
compileOnly(libs.android.gradlePlugin) compileOnly(libs.android.gradlePlugin)
compileOnly(libs.android.tools.common) compileOnly(libs.android.tools.common)
compileOnly(libs.compose.gradlePlugin) compileOnly(libs.compose.gradlePlugin)
compileOnly(libs.firebase.crashlytics.gradlePlugin)
compileOnly(libs.firebase.performance.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin) compileOnly(libs.kotlin.gradlePlugin)
compileOnly(libs.ksp.gradlePlugin) compileOnly(libs.ksp.gradlePlugin)
compileOnly(libs.room.gradlePlugin)
compileOnly(libs.detekt.gradlePlugin) compileOnly(libs.detekt.gradlePlugin)
compileOnly(libs.ktlint.gradlePlugin) compileOnly(libs.ktlint.gradlePlugin)
compileOnly(libs.spotless.gradle) compileOnly(libs.spotless.gradle)
@ -43,6 +40,7 @@ tasks {
gradlePlugin { gradlePlugin {
plugins { plugins {
// Android Plugins
register("androidApplicationCompose") { register("androidApplicationCompose") {
id = "mifospay.android.application.compose" id = "mifospay.android.application.compose"
implementationClass = "AndroidApplicationComposeConventionPlugin" implementationClass = "AndroidApplicationComposeConventionPlugin"
@ -51,46 +49,28 @@ gradlePlugin {
id = "mifospay.android.application" id = "mifospay.android.application"
implementationClass = "AndroidApplicationConventionPlugin" implementationClass = "AndroidApplicationConventionPlugin"
} }
register("androidLibraryCompose") {
id = "mifospay.android.library.compose"
implementationClass = "AndroidLibraryComposeConventionPlugin"
}
register("androidLibrary") {
id = "mifospay.android.library"
implementationClass = "AndroidLibraryConventionPlugin"
}
register("androidFeature") {
id = "mifospay.android.feature"
implementationClass = "AndroidFeatureConventionPlugin"
}
register("androidTest") {
id = "mifospay.android.test"
implementationClass = "AndroidTestConventionPlugin"
}
register("androidRoom") {
id = "mifospay.android.room"
implementationClass = "AndroidRoomConventionPlugin"
}
register("androidFirebase") {
id = "mifospay.android.application.firebase"
implementationClass = "AndroidApplicationFirebaseConventionPlugin"
}
register("androidLint") {
id = "mifospay.android.lint"
implementationClass = "AndroidLintConventionPlugin"
}
register("jvmLibrary") {
id = "mifospay.jvm.library"
implementationClass = "JvmLibraryConventionPlugin"
}
register("androidFlavors") { register("androidFlavors") {
id = "mifospay.android.application.flavors" id = "mifospay.android.application.flavors"
implementationClass = "AndroidApplicationFlavorsConventionPlugin" implementationClass = "AndroidApplicationFlavorsConventionPlugin"
} }
register("androidKoin") {
id = "mifospay.android.koin" // KMP & CMP Plugins
implementationClass = "AndroidKoinConventionPlugin" register("cmpFeature") {
id = "mifospay.cmp.feature"
implementationClass = "CMPFeatureConventionPlugin"
} }
register("kmpKoin") {
id = "mifospay.kmp.koin"
implementationClass = "KMPKoinConventionPlugin"
}
register("kmpLibrary") {
id = "mifospay.kmp.library"
implementationClass = "KMPLibraryConventionPlugin"
}
// Static Analysis & Formatting Plugins
register("detekt") { register("detekt") {
id = "mifos.detekt.plugin" id = "mifos.detekt.plugin"
implementationClass = "MifosDetektConventionPlugin" implementationClass = "MifosDetektConventionPlugin"

View File

@ -15,11 +15,9 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
with(pluginManager) { with(pluginManager) {
apply("com.android.application") apply("com.android.application")
apply("org.jetbrains.kotlin.android") apply("org.jetbrains.kotlin.android")
apply("mifospay.android.lint")
apply("com.dropbox.dependency-guard") apply("com.dropbox.dependency-guard")
apply("mifos.detekt.plugin") apply("mifos.detekt.plugin")
apply("mifos.spotless.plugin") apply("mifos.spotless.plugin")
apply("mifos.ktlint.plugin")
apply("mifos.git.hooks") apply("mifos.git.hooks")
} }

View File

@ -1,39 +0,0 @@
import com.android.build.api.dsl.ApplicationExtension
import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.mifospay.libs
class AndroidApplicationFirebaseConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.google.gms.google-services")
apply("com.google.firebase.firebase-perf")
apply("com.google.firebase.crashlytics")
}
dependencies {
val bom = libs.findLibrary("firebase-bom").get()
add("implementation", platform(bom))
"implementation"(libs.findLibrary("firebase.analytics").get())
"implementation"(libs.findLibrary("firebase.performance").get())
"implementation"(libs.findLibrary("firebase.crashlytics").get())
"implementation"(libs.findLibrary("firebase.cloud.messaging").get())
}
extensions.configure<ApplicationExtension> {
buildTypes.configureEach {
// Disable the Crashlytics mapping file upload. This feature should only be
// enabled if a Firebase backend is available and configured in
// google-services.json.
configure<CrashlyticsExtension> {
mappingFileUploadEnabled = false
}
}
}
}
}
}

View File

@ -1,66 +0,0 @@
import com.android.build.gradle.LibraryExtension
import com.google.devtools.ksp.gradle.KspExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.kotlin
import org.mifospay.libs
class AndroidFeatureConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply {
apply("mifospay.android.library")
apply("mifospay.android.koin")
}
extensions.configure<LibraryExtension> {
defaultConfig {
// set custom test runner
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
testOptions.animationsDisabled = true
}
extensions.configure<KspExtension> {
arg("KOIN_USE_COMPOSE_VIEWMODEL","true")
}
dependencies {
add("implementation", project(":core:ui"))
add("implementation", project(":core:designsystem"))
add("implementation", project(":core:data"))
add("implementation", project(":libs:material3-navigation"))
add("implementation", libs.findLibrary("androidx.navigation.compose").get())
add("implementation", libs.findLibrary("kotlinx.collections.immutable").get())
add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
add("implementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get())
add("implementation", libs.findLibrary("androidx.tracing.ktx").get())
add("implementation", platform(libs.findLibrary("koin-bom").get()))
add("implementation", libs.findLibrary("koin-android").get())
add("implementation", libs.findLibrary("koin.androidx.compose").get())
add("implementation", libs.findLibrary("koin.android").get())
add("implementation", libs.findLibrary("koin.androidx.navigation").get())
add("implementation", libs.findLibrary("koin.androidx.compose").get())
add("implementation", libs.findLibrary("koin.core.viewmodel").get())
add("androidTestImplementation", libs.findLibrary("androidx.lifecycle.runtimeTesting").get())
add("testImplementation", kotlin("test"))
add("testImplementation", libs.findLibrary("koin.test").get())
add("testImplementation", libs.findLibrary("koin.test.junit4").get())
add("debugImplementation", libs.findLibrary("androidx.compose.ui.test.manifest").get())
add("androidTestImplementation", libs.findLibrary("androidx.navigation.testing").get())
add("androidTestImplementation", libs.findLibrary("androidx.compose.ui.test").get())
}
}
}
}

View File

@ -1,36 +0,0 @@
import com.google.devtools.ksp.gradle.KspExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.mifospay.libs
class AndroidKoinConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.google.devtools.ksp")
}
dependencies {
val bom = libs.findLibrary("koin-bom").get()
add("implementation", platform(bom))
add("implementation", libs.findLibrary("koin.core").get())
add("implementation", libs.findLibrary("koin.annotations").get())
add("ksp", libs.findLibrary("koin.ksp.compiler").get())
add("testImplementation", libs.findLibrary("koin.test").get())
add("testImplementation", libs.findLibrary("koin.test.junit4").get())
}
extensions.configure<KspExtension> {
arg("KOIN_CONFIG_CHECK","true")
arg("USE_COMPOSE_VIEWMODEL", "false")
arg("KOIN_USE_COMPOSE_VIEWMODEL", "true")
}
}
}
}

View File

@ -1,19 +0,0 @@
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.getByType
import org.mifospay.configureAndroidCompose
class AndroidLibraryComposeConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("com.android.library")
apply(plugin = "org.jetbrains.kotlin.plugin.compose")
val extension = extensions.getByType<LibraryExtension>()
configureAndroidCompose(extension)
}
}
}

View File

@ -1,48 +0,0 @@
import com.android.build.api.variant.LibraryAndroidComponentsExtension
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.kotlin
import org.mifospay.configureFlavors
import org.mifospay.configureKotlinAndroid
import org.mifospay.configurePrintApksTask
import org.mifospay.disableUnnecessaryAndroidTests
import org.mifospay.libs
class AndroidLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.android.library")
apply("org.jetbrains.kotlin.android")
apply("mifospay.android.lint")
apply("mifos.detekt.plugin")
apply("mifos.spotless.plugin")
apply("mifos.ktlint.plugin")
apply("mifospay.android.koin")
}
extensions.configure<LibraryExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = 34
testOptions.animationsDisabled = true
configureFlavors(this)
// The resource prefix is derived from the module name,
// so resources inside ":core:module1" must be prefixed with "core_module1_"
resourcePrefix = path.split("""\W""".toRegex()).drop(1).distinct().joinToString(separator = "_").lowercase() + "_"
}
extensions.configure<LibraryAndroidComponentsExtension> {
configurePrintApksTask(this)
disableUnnecessaryAndroidTests(target)
}
dependencies {
add("testImplementation", kotlin("test"))
add("implementation", libs.findLibrary("androidx.tracing.ktx").get())
}
}
}
}

View File

@ -1,37 +0,0 @@
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.LibraryExtension
import com.android.build.api.dsl.Lint
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import java.io.File
class AndroidLintConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
when {
pluginManager.hasPlugin("com.android.application") ->
configure<ApplicationExtension> { lint(Lint::configure) }
pluginManager.hasPlugin("com.android.library") ->
configure<LibraryExtension> { lint(Lint::configure) }
else -> {
pluginManager.apply("com.android.lint")
configure<Lint>(Lint::configure)
}
}
}
}
}
private fun Lint.configure() {
xmlReport = true
checkDependencies = true
abortOnError = false
// Disable this rule until we ship the libraries to some maven.
disable += "ResourceName"
baseline = File("lint-baseline.xml")
explainIssues = true
htmlReport = true
}

View File

@ -1,29 +0,0 @@
import androidx.room.gradle.RoomExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.mifospay.libs
class AndroidRoomConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("androidx.room")
pluginManager.apply("com.google.devtools.ksp")
extensions.configure<RoomExtension> {
// The schemas directory contains a schema file for each version of the Room database.
// This is required to enable Room auto migrations.
// See https://developer.android.com/reference/kotlin/androidx/room/AutoMigration.
schemaDirectory("$projectDir/schemas")
}
dependencies {
add("implementation", libs.findLibrary("room.runtime").get())
add("implementation", libs.findLibrary("room.ktx").get())
add("ksp", libs.findLibrary("room.compiler").get())
}
}
}
}

View File

@ -1,21 +0,0 @@
import com.android.build.gradle.TestExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.mifospay.configureKotlinAndroid
class AndroidTestConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.android.test")
apply("org.jetbrains.kotlin.android")
}
extensions.configure<TestExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = 34
}
}
}
}

View File

@ -0,0 +1,55 @@
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
import org.mifospay.libs
class CMPFeatureConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply {
apply("mifospay.kmp.library")
apply("mifospay.kmp.koin")
apply("org.jetbrains.kotlin.plugin.compose")
apply("org.jetbrains.compose")
}
dependencies {
add("commonMainImplementation", project(":core:ui"))
add("commonMainImplementation", project(":core:designsystem"))
add("commonMainImplementation", project(":core:data"))
add("commonMainImplementation", libs.findLibrary("koin.compose").get())
add("commonMainImplementation", libs.findLibrary("koin.compose.viewmodel").get())
add("commonMainImplementation", libs.findLibrary("jb.composeRuntime").get())
add("commonMainImplementation", libs.findLibrary("jb.composeViewmodel").get())
add("commonMainImplementation", libs.findLibrary("jb.lifecycleViewmodel").get())
add("commonMainImplementation", libs.findLibrary("jb.lifecycleViewmodelSavedState").get())
add("commonMainImplementation", libs.findLibrary("jb.savedstate").get())
add("commonMainImplementation", libs.findLibrary("jb.bundle").get())
add("commonMainImplementation", libs.findLibrary("jb.composeNavigation").get())
add("commonMainImplementation", libs.findLibrary("kotlinx.collections.immutable").get())
add("androidMainImplementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
add("androidMainImplementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get())
add("androidMainImplementation", libs.findLibrary("androidx.tracing.ktx").get())
add("androidMainImplementation", platform(libs.findLibrary("koin-bom").get()))
add("androidMainImplementation", libs.findLibrary("koin-android").get())
add("androidMainImplementation", libs.findLibrary("koin.androidx.compose").get())
add("androidMainImplementation", libs.findLibrary("koin.android").get())
add("androidMainImplementation", libs.findLibrary("koin.androidx.navigation").get())
add("androidMainImplementation", libs.findLibrary("koin.androidx.compose").get())
add("androidMainImplementation", libs.findLibrary("koin.core.viewmodel").get())
add("androidTestImplementation", libs.findLibrary("koin.test.junit4").get())
add("androidInstrumentedTestImplementation", libs.findLibrary("androidx.navigation.testing").get())
add("androidInstrumentedTestImplementation", libs.findLibrary("androidx.compose.ui.test").get())
add("androidInstrumentedTestImplementation", libs.findLibrary("androidx.lifecycle.runtimeTesting").get())
}
}
}
}

View File

@ -1,15 +0,0 @@
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.mifospay.configureKotlinJvm
class JvmLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("org.jetbrains.kotlin.jvm")
apply("mifospay.android.lint")
}
configureKotlinJvm()
}
}
}

View File

@ -0,0 +1,32 @@
import com.google.devtools.ksp.gradle.KspExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.mifospay.libs
class KMPKoinConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.google.devtools.ksp")
}
dependencies {
val bom = libs.findLibrary("koin-bom").get()
add("commonMainImplementation", platform(bom))
add("commonMainImplementation", libs.findLibrary("koin.core").get())
add("commonMainImplementation", libs.findLibrary("koin.annotations").get())
add("kspCommonMainMetadata", libs.findLibrary("koin.ksp.compiler").get())
add("commonTestImplementation", libs.findLibrary("koin.test").get())
}
extensions.configure<KspExtension> {
arg("KOIN_CONFIG_CHECK","true")
}
}
}
}

View File

@ -0,0 +1,44 @@
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.mifospay.configureFlavors
import org.mifospay.configureKotlinAndroid
import org.mifospay.configureKotlinMultiplatform
import org.mifospay.libs
class KMPLibraryConventionPlugin: Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.android.library")
apply("org.jetbrains.kotlin.multiplatform")
apply("mifospay.kmp.koin")
apply("mifos.detekt.plugin")
apply("mifos.spotless.plugin")
}
configureKotlinMultiplatform()
extensions.configure<LibraryExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = 34
configureFlavors(this)
// The resource prefix is derived from the module name,
// so resources inside ":core:module1" must be prefixed with "core_module1_"
resourcePrefix = path
.split("""\W""".toRegex())
.drop(1).distinct()
.joinToString(separator = "_")
.lowercase() + "_"
}
dependencies {
add("commonTestImplementation", libs.findLibrary("kotlin.test").get())
add("commonTestImplementation", libs.findLibrary("kotlinx.coroutines.test").get())
}
}
}
}

View File

@ -5,11 +5,9 @@ import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.named
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
internal fun Project.configureDetekt(extension: DetektExtension) = extension.apply { internal fun Project.configureDetekt(extension: DetektExtension) = extension.apply {
tasks.named<Detekt>("detekt") { tasks.named<Detekt>("detekt") {
jvmTarget = "17"
reports { reports {
xml.required.set(true) xml.required.set(true)
html.required.set(true) html.required.set(true)

View File

@ -10,9 +10,6 @@ import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.provideDelegate import org.gradle.kotlin.dsl.provideDelegate
import org.gradle.kotlin.dsl.withType import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
/** /**
@ -37,7 +34,7 @@ internal fun Project.configureKotlinAndroid(
} }
} }
configureKotlin<KotlinAndroidProjectExtension>() configureKotlin()
dependencies { dependencies {
add("coreLibraryDesugaring", libs.findLibrary("android.desugarJdkLibs").get()) add("coreLibraryDesugaring", libs.findLibrary("android.desugarJdkLibs").get())
@ -55,29 +52,26 @@ internal fun Project.configureKotlinJvm() {
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
} }
configureKotlin<KotlinJvmProjectExtension>() configureKotlin()
} }
/** /**
* Configure base Kotlin options * Configure base Kotlin options
*/ */
private inline fun <reified T : KotlinTopLevelExtension> Project.configureKotlin() = configure<T> { private fun Project.configureKotlin() {
// Use withType to workaround https://youtrack.jetbrains.com/issue/KT-55947
tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
// Set JVM target to 17
jvmTarget = JvmTarget.JVM_17
// Treat all Kotlin warnings as errors (disabled by default) // Treat all Kotlin warnings as errors (disabled by default)
// Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties // Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties
val warningsAsErrors: String? by project val warningsAsErrors: String? by project
when (this) {
is KotlinAndroidProjectExtension -> compilerOptions
is KotlinJvmProjectExtension -> compilerOptions
else -> TODO("Unsupported project extension $this ${T::class}")
}.apply {
jvmTarget = JvmTarget.JVM_17
allWarningsAsErrors = warningsAsErrors.toBoolean() allWarningsAsErrors = warningsAsErrors.toBoolean()
freeCompilerArgs.add( freeCompilerArgs.add(
// Enable experimental coroutines APIs, including Flow // Enable experimental coroutines APIs, including Flow
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
) )
freeCompilerArgs.add( }
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api"
)
} }
} }

View File

@ -0,0 +1,40 @@
package org.mifospay
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
internal fun Project.configureKotlinMultiplatform() {
extensions.configure<KotlinMultiplatformExtension> {
applyDefaultHierarchyTemplate()
jvm("desktop")
androidTarget()
iosSimulatorArm64()
iosX64()
iosArm64()
js(IR) {
this.nodejs()
binaries.executable()
}
wasmJs() {
browser()
nodejs()
}
// Suppress 'expect'/'actual' classes are in Beta.
targets.configureEach {
compilations.configureEach {
compilerOptions.configure {
freeCompilerArgs.addAll("-Xexpect-actual-classes")
}
}
}
// Fixes Cannot locate tasks that match ':core:model:testClasses' as task 'testClasses'
// not found in project ':core:model'. Some candidates are: 'jsTestClasses', 'jvmTestClasses'.
project.tasks.create("testClasses") {
dependsOn("allTests")
}
}
}

View File

@ -21,7 +21,6 @@ plugins {
alias(libs.plugins.ksp) apply false alias(libs.plugins.ksp) apply false
alias(libs.plugins.roborazzi) apply false alias(libs.plugins.roborazzi) apply false
alias(libs.plugins.secrets) apply false alias(libs.plugins.secrets) apply false
alias(libs.plugins.room) apply false
alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.module.graph) apply true alias(libs.plugins.module.graph) apply true
alias(libs.plugins.detekt) apply false alias(libs.plugins.detekt) apply false
@ -33,6 +32,7 @@ plugins {
alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.wire) apply false alias(libs.plugins.wire) apply false
alias(libs.plugins.ktrofit) apply false
} }
object DynamicVersion { object DynamicVersion {

View File

@ -12,11 +12,9 @@ echo Starting all checks and tests...
call :run_gradle_task "check -p build-logic" call :run_gradle_task "check -p build-logic"
call :run_gradle_task "spotlessApply --no-configuration-cache" call :run_gradle_task "spotlessApply --no-configuration-cache"
call :run_gradle_task "dependencyGuardBaseline" call :run_gradle_task "dependencyGuardBaseline"
call :run_gradle_task "formatVersionCatalog"
call :run_gradle_task "detekt" call :run_gradle_task "detekt"
call :run_gradle_task "testDemoDebug :lint:test :mifospay:lintProdRelease :lint:lint" call :run_gradle_task ":mifospay-android:build"
call :run_gradle_task "build" call :run_gradle_task ":mifospay-android:updateProdReleaseBadging"
call :run_gradle_task "updateProdReleaseBadging"
echo All checks and tests completed successfully. echo All checks and tests completed successfully.
exit /b 0 exit /b 0

View File

@ -28,10 +28,8 @@ tasks=(
"spotlessApply --no-configuration-cache" "spotlessApply --no-configuration-cache"
"dependencyGuardBaseline" "dependencyGuardBaseline"
"detekt" "detekt"
"formatVersionCatalog" ":mifospay-android:build"
"testDemoDebug :lint:test :lint:lint :mifospay:lintProdRelease" ":mifospay-android:updateProdReleaseBadging"
"build"
"updateProdReleaseBadging"
) )
for task in "${tasks[@]}"; do for task in "${tasks[@]}"; do

View File

@ -8,17 +8,23 @@
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/ */
plugins { plugins {
alias(libs.plugins.mifospay.android.library) alias(libs.plugins.mifospay.kmp.library)
alias(libs.plugins.mifospay.android.library.compose) alias(libs.plugins.jetbrainsCompose)
alias(libs.plugins.compose.compiler)
} }
android { android {
namespace = "org.mifospay.core.analytics" namespace = "org.mifospay.core.analytics"
} }
dependencies { kotlin {
implementation(libs.androidx.compose.runtime) sourceSets {
commonMain.dependencies {
implementation(platform(libs.firebase.bom)) implementation(compose.runtime)
}
androidMain.dependencies {
implementation(project.dependencies.platform(libs.firebase.bom))
implementation(libs.firebase.analytics) implementation(libs.firebase.analytics)
} }
}
}

View File

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -8,14 +8,56 @@
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/ */
plugins { plugins {
alias(libs.plugins.mifospay.android.library) alias(libs.plugins.mifospay.kmp.library)
alias(libs.plugins.kotlin.parcelize)
} }
android { android {
namespace = "org.mifospay.common" namespace = "org.mifospay.common"
} }
dependencies { kotlin {
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64(),
).forEach {
it.binaries.framework {
isStatic = false
export(libs.kermit.simple)
}
}
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.core)
api(libs.coil.kt)
api(libs.coil.core)
api(libs.coil.svg)
api(libs.coil.network.ktor)
api(libs.kermit.logging)
api(libs.squareup.okio)
api(libs.jb.kotlin.stdlib)
api(libs.kotlinx.datetime)
}
androidMain.dependencies {
implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.coroutines.android)
} }
commonTest.dependencies {
implementation(libs.kotlinx.coroutines.test)
}
iosMain.dependencies {
api(libs.kermit.simple)
}
desktopMain.dependencies {
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.kotlin.reflect)
}
jsMain.dependencies {
api(libs.jb.kotlin.stdlib.js)
api(libs.jb.kotlin.dom)
}
}
}

View File

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,26 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
import java.text.NumberFormat
import java.util.Currency
actual object CurrencyFormatter {
actual fun format(
balance: Double?,
currencyCode: String?,
maximumFractionDigits: Int?,
): String {
val balanceFormatter = NumberFormat.getCurrencyInstance()
balanceFormatter.maximumFractionDigits = maximumFractionDigits ?: 0
balanceFormatter.currency = Currency.getInstance(currencyCode)
return balanceFormatter.format(balance)
}
}

View File

@ -0,0 +1,13 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
// JVM and Android implementation
actual fun createPlatformFileUtils(): FileUtils = CommonFileUtils()

View File

@ -0,0 +1,29 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
import android.os.Parcel
import android.os.Parcelable
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parceler
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.TypeParceler
actual typealias Parcelize = Parcelize
actual typealias Parcelable = Parcelable
actual typealias IgnoredOnParcel = IgnoredOnParcel
actual typealias Parceler<P> = Parceler<P>
actual typealias TypeParceler<T, P> = TypeParceler<T, P>
actual typealias Parcel = Parcel

View File

@ -0,0 +1,22 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common.di
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import org.koin.core.module.Module
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.mifospay.core.common.MifosDispatchers
actual val ioDispatcherModule: Module
get() = module {
single<CoroutineDispatcher>(named(MifosDispatchers.IO.name)) { Dispatchers.IO }
}

View File

@ -0,0 +1,192 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
/**
* Created by naman on 17/6/17.
*/
object Constants {
const val BASIC = "Basic "
const val CLIENT_ID = "client_id"
const val ACCOUNT_ID = "account_id"
const val ACCOUNT = "account"
const val TO_EXTERNAL_ID = "to_external_id"
const val RUPEE = ""
const val QR_DATA = "qr_data"
const val MERCHANT_NAME = "merchant_name"
const val MERCHANT_VPA = "merchant_vpa"
const val MERCHANT_ACCOUNT_NO = "merchant_account_no"
const val FILE = "file"
const val MULTIPART_FORM_DATA = "multipart/form-data"
const val CHOOSE_A_FILE_TO_UPLOAD = "Choose a file to upload."
const val ERROR_UPLOADING_DOCS = "Error uploading docs."
const val KYC_LEVEL_2_DOCUMENTS_ADDED_SUCCESSFULLY = "KYC Level 2 documents added successfully."
const val IMAGE = "image/*"
const val APPLICATION_PDF = "application/pdf"
const val ERROR_ADDING_KYC_LEVEL_1_DETAILS = "Error adding KYC Level 1 details."
const val KYC_LEVEL_1_DETAILS_ADDED_SUCCESSFULLY = "KYC Level 1 details added successfully."
const val PLEASE_WAIT = "Please wait.."
const val COMPLETE_KYC = "Complete KYC"
const val NEED_EXTERNAL_STORAGE_PERMISSION_TO_BROWSE_DOCUMENTS =
"Need external storage permission to browse documents."
const val CHOOSE_FILE = "Choose File"
const val KYC_REGISTRATION_LEVEL_2 = "KYC Registration Level 2"
const val KYC_REGISTRATION_LEVEL_1 = "KYC Registration Level 1"
const val DD_MM_YY = "dd/MM/yy"
const val ERROR_DELETING_CARD = "Error deleting card."
const val CARD_DELETED_SUCCESSFULLY = "Card deleted successfully."
const val DELETING_CARD = "Deleting Card.."
const val ERROR_UPDATING_CARD = "Error updating card."
const val CARD_UPDATED_SUCCESSFULLY = "Card updated successfully."
const val INVALID_CREDIT_CARD_NUMBER = "Invalid Credit Card Number"
const val UPDATING_CARD = "Updating Card.."
const val ERROR_ADDING_CARD = "Error adding card."
const val CARD_ADDED_SUCCESSFULLY = "Card added successfully."
const val ADDING_CARD = "Adding Card.."
const val ADD_CARD_DIALOG = "Add Card Dialog"
const val EDIT_CARD_DIALOG = "Edit Card Dialog"
const val SAVED_CARDS = "Saved Cards"
const val UPDATE = "Update"
const val KYC_REGISTRATION_LEVEL_3 = "KYC Registration Level 3"
const val OK = "OK"
const val SCAN_CODE = "Scan code"
const val QR_CODE = "QR code"
const val FAILED_TO_WRITE_DATA_TO_QR = "Failed to write data to qr"
const val ERROR_OCCURRED = "Error occurred"
const val LOGGING_IN = "Logging in.."
const val HOME = "Home"
const val PROFILE = "Profile"
const val ERROR_FETCHING_BALANCE = "Error fetching balance"
const val UNABLE_TO_PROCESS_TRANSFER = "Unable to process transfer"
const val TRANSACTION_SUCCESSFUL = "Transaction successful"
const val SENDING_MONEY = "Sending money..."
const val INSUFFICIENT_BALANCE = "Insufficient balance"
const val ERROR_FINDING_VPA = "Error finding Virtual Payment Address"
const val ERROR_FINDING_MOBILE_NUMBER = "Error finding Mobile Number"
const val PLEASE_ENTER_VALID_AMOUNT = "Please enter a valid amount"
const val VPA_VALIDATION_REGEX = "^\\w.+@\\w+$"
const val SELF_ACCOUNT_ERROR = "Self Account transfer is not allowed"
const val PLEASE_ENTER_AMOUNT = "Please enter a valid amount before making the transfer"
const val NEED_READ_CONTACTS_PERMISSION = "Need read contacts permission"
const val NEED_CAMERA_PERMISSION_TO_SCAN_QR_CODE = "Need camera permission to scan qr code."
const val ERROR_CHOOSING_CONTACT = "Error choosing contact"
const val MAKE_TRANSFER_FRAGMENT = "Make Transfer Fragment"
const val PLEASE_ENTER_ALL_THE_FIELDS = "Please enter all the fields"
const val TRANSFER = "Transfer"
const val TRANSACTION_DETAILS = "Transaction Details"
const val ACCOUNT_NUMBER = "Account Number : "
const val TRANSACTION = "transaction"
const val TRANSACTIONS_HISTORY = "Transactions History History"
const val SPECIFIC_TRANSACTIONS = "Specific Transactions History"
const val HISTORY_NOT_AVAILABLE = "No Transaction History Available"
const val RECEIPT_DOMAIN = "https://receipt.mifospay.com/"
const val OTHER = "Other"
const val CREDIT = "Credit"
const val DEBIT = "Debit"
const val RECEIPT_ID = "Receipt ID"
const val DATE = "Date"
const val TRANSACTION_ID = "Transaction ID"
const val TRANSACTIONS = "transactions"
const val ERROR_FETCHING_TRANSACTIONS = "Error fetching transactions"
const val TRANSFER_DETAILS = "transfer details"
const val UNIQUE_PAYMENT_LINK_COPIED_TO_CLIPBOARD = "Unique Payment Link copied to clipboard"
const val UNIQUE_PAYMENT_LINK = "Unique Payment Link"
const val STATUS = "Status"
const val UNIQUE_RECEIPT_LINK_COPIED_TO_CLIPBOARD = "Unique Receipt Link copied to clipboard"
const val UNIQUE_RECEIPT_LINK = "Unique Receipt Link"
const val DONE = "Done"
const val PENDING = "Pending"
const val ITEMS = "Item(s)"
const val AMOUNT = "Amount"
const val INR = "INR"
const val CONSUMER = "Consumer"
const val MERCHANT = "Merchant"
const val INVOICE = "Invoice"
const val NEED_EXTERNAL_STORAGE_PERMISSION_TO_DOWNLOAD_RECEIPT =
"Need external storage permission to download receipt"
const val RECEIPT_DOWNLOADED_SUCCESSFULLY = "Receipt Downloaded Successfully"
const val ERROR_DOWNLOADING_RECEIPT = "Error downloading receipt"
const val MIFOSPAY = "mifospay"
const val RECEIPT = "Receipt"
const val PDF = ".pdf"
const val ERROR_FETCHING_RECEIPT = "Error fetching receipt"
const val INVOICE_DOMAIN = "https://invoice.mifospay.com/"
const val SENDING_OTP_TO_YOUR_MOBILE_NUMBER = "Sending OTP to your mobile number.."
const val MOBILE_NUMBER = "Mobile Number"
const val COUNTRY = "Country"
const val GOOGLE_SIGN_IN_FAILED = "Google Sign in failed."
const val GOOGLE_GIVEN_NAME = "GOOGLE_GIVEN_NAME"
const val GOOGLE_FAMILY_NAME = "GOOGLE_FAMILY_NAME"
const val GOOGLE_EMAIL = "GOOGLE_EMAIL"
const val GOOGLE_DISPLAY_NAME = "GOOGLE_DISPLAY_NAME"
const val GOOGLE_PHOTO_URI = "GOOGLE_PHOTO_URI"
const val CHOOSE_SIGNUP_METHOD = "Choose Signup Method"
const val LOGGING_OUT = "Logging out..."
const val NEED_EXTERNAL_STORAGE_PERMISSION_TO_BROWSE_IMAGES =
"Need external storage permission to browse images"
const val SETTINGS = "Settings"
const val EDIT_PROFILE = "Edit Profile"
const val FAQ = "Frequent Asked Questions"
const val LINKED_BANK_ACCOUNTS = "Linked Bank Accounts"
const val BANK_ACCOUNT_DETAILS = "Bank Account Details"
const val NEW_BANK_ACCOUNT = "newBankAccount"
const val VERIFYING_MOBILE_NUMBER = "Verifying mobile number.."
const val VERIFYING_OTP = "Verifying Otp.."
const val MIFOS_SAVINGS_PRODUCT_ID = "Mifos Savings Product Id"
const val MERCHANTS = "Merchants"
const val UPI_PIN = "UPI PIN"
const val STEP = "step"
const val TYPE = "Type"
const val SETUP = "Setup"
const val BANK_DELETED_SUCCESSFULLY = "Bank deleted successfully"
const val ERROR_OCCURRED_WHILE_CHANGING_UPI_PIN = "Error occurred while changing UPI PIN"
const val SETUP_UPI = "Setup UPI"
const val ERROR_WHILE_SETTING_UP_UPI_PIN = "Error while setting up UPI PIN"
const val UPI_PIN_SETUP_COMPLETED_SUCCESSFULLY = "UPI PIN Setup Completed Successfully"
const val FORGOT = "Forgot"
const val CHANGE = "Change"
const val OTP = "otp"
const val SETUP_UPI_PIN = "Setup UPI PIN"
const val SETTING_UP_UPI_PIN = "Setting up UPI PIN.."
const val UPDATED_BANK_ACCOUNT = "Updated Bank Account"
const val INDEX = "Index"
const val CHANGE_UPI_PIN = "Change UPI PIN"
const val FORGOT_UPI_PIN = "Forgot UPI PIN"
// broadcast receiver intent filters
const val REGISTRATION_COMPLETE = "registrationComplete"
const val PUSH_NOTIFICATION = "pushNotification"
// id to handle the notification in the notification tray
const val NOTIFICATION_ID = 100
const val NOTIFICATION_ID_BIG_IMAGE = 101
const val ERROR_FIELDS_CANNOT_BE_EMPTY = "Fields cannot be empty"
const val ERROR_VALIDATING_PASSWORD = "Passwords are not the same"
const val ERROR_PASSWORDS_CANT_BE_SAME = "New password can't be the same as old password."
const val CHANGE_PROFILE_IMAGE_KEY = "CHANGE_PROFILE_IMAGE_KEY"
const val CHANGE_PROFILE_IMAGE_VALUE = "CHANGE_PROFILE_IMAGE_VALUE"
const val TAP_TO_REVEAL = "Tap to Reveal"
const val NAME = "Name : "
const val ERROR_FETCHING_TRANSACTION_DETAILS = "Error fetching details"
// const val WHITE_BACK_BUTTON = R.drawable.ic_arrow_back_white_24dp
// const val BLACK_BACK_BUTTON = R.drawable.ic_arrow_back_black_24dp
const val VIEW = "View"
const val CURRENT_PASSCODE = "current passcode"
const val UPDATE_PASSCODE = "update passcode"
const val SELECT_DATE = "SELECT DATE"
const val SI_ID = "standing_instruction_id"
const val UNAUTHORIZED_ERROR = "401 Unauthorized"
const val RECEIPT_SHARING_MESSAGE = "Receipt link for the transaction "
const val TO = " to "
const val COLON = " : "
const val REQUEST_CAMERA = 0
const val SCAN_QR_REQUEST_CODE = 666
}

View File

@ -0,0 +1,18 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
expect object CurrencyFormatter {
fun format(balance: Double?, currencyCode: String?, maximumFractionDigits: Int?): String
}
fun <T> List<T>.toArrayList(): ArrayList<T> {
return ArrayList(this)
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
sealed class DataState<out T> {
abstract val data: T?
data object Loading : DataState<Nothing>() {
override val data: Nothing? get() = null
}
data class Success<T>(
override val data: T,
) : DataState<T>()
data class Error<T>(
val exception: Throwable,
override val data: T? = null,
) : DataState<T>() {
val message = exception.message.toString()
}
}
fun <T> Flow<T>.asDataStateFlow(): Flow<DataState<T>> =
map<T, DataState<T>> { DataState.Success(it) }
.onStart { emit(DataState.Loading) }
.catch { emit(DataState.Error(it, null)) }

View File

@ -0,0 +1,105 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.transformWhile
inline fun <T : Any?, R : Any?> DataState<T>.map(
transform: (T) -> R,
): DataState<R> = when (this) {
is DataState.Success -> DataState.Success(transform(data))
is DataState.Loading -> DataState.Loading
is DataState.Error -> DataState.Error(exception, data?.let(transform))
}
inline fun <T : Any?, R : Any?> DataState<T>.mapNullable(
transform: (T?) -> R,
): DataState<R> = when (this) {
is DataState.Success -> DataState.Success(data = transform(data))
is DataState.Loading -> DataState.Loading
is DataState.Error -> DataState.Error(exception = exception, data = transform(data))
}
fun <T : Any?> Flow<DataState<T>>.takeUntilResultSuccess(): Flow<DataState<T>> = transformWhile {
emit(it)
it !is DataState.Success
}
fun <T1, T2, R> combineResults(
dataState1: DataState<T1>,
dataState2: DataState<T2>,
transform: (t1: T1, t2: T2) -> R,
): DataState<R> {
val nullableTransform: (T1?, T2?) -> R? = { t1, t2 ->
if (t1 != null && t2 != null) transform(t1, t2) else null
}
return when {
// Error states have highest priority, fail fast.
dataState1 is DataState.Error -> {
DataState.Error(
exception = dataState1.exception,
data = nullableTransform(dataState1.data, dataState2.data),
)
}
dataState2 is DataState.Error -> {
DataState.Error(
exception = dataState2.exception,
data = nullableTransform(dataState1.data, dataState2.data),
)
}
// Something is still loading, we will wait for all the data.
dataState1 is DataState.Loading || dataState2 is DataState.Loading -> DataState.Loading
// Pending state for everything while any one piece of data is updating.
// Both states are _root_ide_package_.org.mifospay.core.common.Result.Success and have data
else -> {
@Suppress("UNCHECKED_CAST")
DataState.Success(transform(dataState1.data as T1, dataState2.data as T2))
}
}
}
fun <T1, T2, T3, R> combineResults(
dataState1: DataState<T1>,
dataState2: DataState<T2>,
dataState3: DataState<T3>,
transform: (t1: T1, t2: T2, t3: T3) -> R,
): DataState<R> =
dataState1
.combineResultsWith(dataState2) { t1, t2 -> t1 to t2 }
.combineResultsWith(dataState3) { t1t2Pair, t3 ->
transform(t1t2Pair.first, t1t2Pair.second, t3)
}
fun <T1, T2, T3, T4, R> combineResults(
dataState1: DataState<T1>,
dataState2: DataState<T2>,
dataState3: DataState<T3>,
dataState4: DataState<T4>,
transform: (t1: T1, t2: T2, t3: T3, t4: T4) -> R,
): DataState<R> =
dataState1
.combineResultsWith(dataState2) { t1, t2 -> t1 to t2 }
.combineResultsWith(dataState3) { t1t2Pair, t3 ->
Triple(t1t2Pair.first, t1t2Pair.second, t3)
}
.combineResultsWith(dataState4) { t1t2t3Triple, t3 ->
transform(t1t2t3Triple.first, t1t2t3Triple.second, t1t2t3Triple.third, t3)
}
fun <T1, T2, R> DataState<T1>.combineResultsWith(
dataState2: DataState<T2>,
transform: (t1: T1, t2: T2) -> R,
): DataState<R> =
combineResults(this, dataState2, transform)

View File

@ -0,0 +1,353 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.Month
import kotlinx.datetime.TimeZone
import kotlinx.datetime.format
import kotlinx.datetime.format.FormatStringsInDatetimeFormats
import kotlinx.datetime.format.byUnicodePattern
import kotlinx.datetime.toLocalDateTime
@OptIn(FormatStringsInDatetimeFormats::class)
object DateHelper {
/*
* This is the full month format for the date picker.
* "dd MM yyyy" is the format of the date picker.
*/
const val FULL_MONTH = "dd MM yyyy"
/*
* This is the short month format for the date picker.
* "dd-MM-yyyy" is the format of the date picker.
*/
const val SHORT_MONTH = "dd-MM-yyyy"
const val MONTH_FORMAT = "dd MMMM"
private val fullMonthFormat = LocalDateTime.Format {
byUnicodePattern(FULL_MONTH)
}
private val shortMonthFormat = LocalDateTime.Format {
byUnicodePattern(SHORT_MONTH)
}
/**
* the result string uses the list given in a reverse order ([x, y, z] results in "z y x")
*
* @param integersOfDate [year-month-day] (ex [2016, 4, 14])
* @return date in the format day month year (ex 14 Apr 2016)
*/
fun getDateAsString(integersOfDate: List<Int>): String {
val stringBuilder = StringBuilder()
stringBuilder.append(integersOfDate[2])
.append(' ')
.append(getMonthName(integersOfDate[1]))
.append(' ')
.append(integersOfDate[0])
return stringBuilder.toString()
}
fun getDateAsString(integersOfDate: List<Long>, pattern: String): String {
return getFormatConverter(
currentFormat = FULL_MONTH,
requiredFormat = pattern,
dateString = getDateAsString(integersOfDate.map { it.toInt() }),
)
}
/**
* This Method converting the dd-MM-yyyy format type date string into dd MMMM yyyy
*
* @param format Final Format of date string
* @param dateString date string
* @return dd MMMM yyyy format date string.
*/
fun getSpecificFormat(format: String, dateString: String): String {
val pickerFormat = shortMonthFormat
val finalFormat = LocalDateTime.Format { byUnicodePattern(format) }
return finalFormat.format(pickerFormat.parse(dateString))
}
private fun getFormatConverter(
currentFormat: String,
requiredFormat: String,
dateString: String,
): String {
val pickerFormat = LocalDateTime.Format { byUnicodePattern(currentFormat) }
val finalFormat = LocalDateTime.Format { byUnicodePattern(requiredFormat) }
return pickerFormat.parse(dateString).format(finalFormat)
}
/**
* Gets the date string in the format "dd-MM-yyyy" from an array of integers representing the year, month, and day.
*
* @param dateComponents An array of three integers representing the year, month, and day, e.g. [2024, 11, 10]
* @return The date string in the format "dd-MM-yyyy", e.g. "10-11-2024"
*/
fun formatTransferDate(dateComponents: List<Int>, pattern: String = SHORT_MONTH): String {
require(dateComponents.size == 3) { "dateComponents must have exactly 3 elements" }
val (year, month, day) = dateComponents
val localDate = LocalDate(year, Month(month), day)
return localDate.format(pattern)
}
// Extension function to format LocalDate
fun LocalDate.format(pattern: String): String {
val year = this.year.toString().padStart(4, '0')
val month = this.monthNumber.toString().padStart(2, '0')
val day = this.dayOfMonth.toString().padStart(2, '0')
return pattern
.replace("yyyy", year)
.replace("MM", month)
.replace("dd", day)
}
/**
* @param month an integer from 1 to 12
* @return string representation of the month like Jan or Feb..etc
*/
private fun getMonthName(month: Int): String {
return when (month) {
1 -> "Jan"
2 -> "Feb"
3 -> "Mar"
4 -> "Apr"
5 -> "May"
6 -> "Jun"
7 -> "Jul"
8 -> "Aug"
9 -> "Sep"
10 -> "Oct"
11 -> "Nov"
12 -> "Dec"
else -> throw IllegalArgumentException("Month should be between 1 and 12")
}
}
/**
* Input timestamp string in milliseconds
* Example timestamp "1698278400000"
* Output examples: "dd-MM-yyyy" - "14-04-2016"
*/
fun getDateAsStringFromLong(timeInMillis: Long): String {
val instant = Instant.fromEpochMilliseconds(timeInMillis)
.toLocalDateTime(TimeZone.currentSystemDefault())
return instant.format(shortMonthFormat)
}
/**
* Input timestamp string in milliseconds
* Example timestamp "1698278400000"
* Output examples: "14 April"
*/
fun getMonthAsStringFromLong(timeInMillis: Long): String {
val instant = Instant.fromEpochMilliseconds(timeInMillis)
.toLocalDateTime(TimeZone.currentSystemDefault())
val monthName = instant.month.name.lowercase().capitalize()
return "${instant.dayOfMonth} $monthName"
}
/**
* Gets the date string in the format "day month year" from an array of integers representing the day and month.
*
* @param integersOfDate An array of two integers representing the day and month, e.g. [11, 10]
* @return The date string in the format "day month year", e.g. "11 October"
*/
fun getDateMonthString(integersOfDate: List<Int>): String {
require(integersOfDate.size == 2) { "integersOfDate must have exactly 2 elements" }
val (day, month) = integersOfDate
val monthName = when (month) {
1 -> "January"
2 -> "February"
3 -> "March"
4 -> "April"
5 -> "May"
6 -> "June"
7 -> "July"
8 -> "August"
9 -> "September"
10 -> "October"
11 -> "November"
12 -> "December"
else -> throw IllegalArgumentException("Invalid month value: $month")
}
return "$day $monthName"
}
/**
* Handles the specific format "yyyy-MM-dd HH:mm:ss.SSSSSS"
* For example "2024-09-19 05:41:18.558995"
* Possible outputs depending on current date:
* "Today at 05:41"
* "Tomorrow at 05:41"
*/
fun String.toFormattedDateTime(): String {
// Parse the datetime string
val dateTime = try {
// Split into date and time parts
val (datePart, timePart) = this.split(" ")
// Remove microseconds from time part
val simplifiedTime = timePart.split(".")[0]
// Combine date and simplified time
val isoString = "${datePart}T$simplifiedTime"
// Parse to LocalDateTime
LocalDateTime.parse(isoString)
} catch (e: Exception) {
return this // Return original string if parsing fails
}
val timeZone = TimeZone.currentSystemDefault()
val now = Clock.System.now()
val nowDateTime = now.toLocalDateTime(timeZone)
return when {
// Same year
nowDateTime.year == dateTime.year -> {
when {
// Same month
nowDateTime.monthNumber == dateTime.monthNumber -> {
when {
// Tomorrow
dateTime.dayOfMonth - nowDateTime.dayOfMonth == 1 -> {
"Tomorrow at ${dateTime.format()}"
}
// Today
dateTime.dayOfMonth == nowDateTime.dayOfMonth -> {
"Today at ${dateTime.format()}"
}
// Yesterday
nowDateTime.dayOfMonth - dateTime.dayOfMonth == 1 -> {
"Yesterday at ${dateTime.format()}"
}
// Same month but different day
else -> {
"${
dateTime.month.name.lowercase().capitalize()
} ${dateTime.dayOfMonth}, ${dateTime.format()}"
}
}
}
// Different month, same year
else -> {
"${
dateTime.month.name.lowercase().capitalize()
} ${dateTime.dayOfMonth}, ${dateTime.format()}"
}
}
}
// Different year
else -> {
"${
dateTime.month.name.lowercase().capitalize()
} ${dateTime.dayOfMonth} ${dateTime.year}, ${dateTime.format()}"
}
}
}
/**
* Input timestamp string in milliseconds
* Example timestamp "1698278400000"
* Output examples:
* "Today at 12:00"
* "Tomorrow at 15:30"
*/
fun String.toPrettyDate(): String {
val timestamp = this.toLong()
val instant = Instant.fromEpochMilliseconds(timestamp)
val timeZone = TimeZone.currentSystemDefault()
val nowDateTime = Clock.System.now().toLocalDateTime(timeZone)
val neededDateTime = instant.toLocalDateTime(timeZone)
return when {
// Same year
nowDateTime.year == neededDateTime.year -> {
when {
// Same month
nowDateTime.monthNumber == neededDateTime.monthNumber -> {
when {
// Tomorrow
neededDateTime.dayOfMonth - nowDateTime.dayOfMonth == 1 -> {
val time = neededDateTime.format()
"Tomorrow at $time"
}
// Today
neededDateTime.dayOfMonth == nowDateTime.dayOfMonth -> {
val time = neededDateTime.format()
"Today at $time"
}
// Yesterday
nowDateTime.dayOfMonth - neededDateTime.dayOfMonth == 1 -> {
val time = neededDateTime.format()
"Yesterday at $time"
}
// Same month but different day
else -> {
"${
neededDateTime.month.name.lowercase().capitalize()
} ${neededDateTime.dayOfMonth}, ${neededDateTime.format()}"
}
}
}
// Different month, same year
else -> {
"${
neededDateTime.month.name.lowercase().capitalize()
} ${neededDateTime.dayOfMonth}, ${neededDateTime.format()}"
}
}
}
// Different year
else -> {
"${
neededDateTime.month.name.lowercase().capitalize()
} ${neededDateTime.dayOfMonth} ${neededDateTime.year}, ${neededDateTime.format()}"
}
}
}
// Helper function to format time
private fun LocalDateTime.format(): String {
return "${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}"
}
// Extension to capitalize first letter
private fun String.capitalize() = replaceFirstChar {
if (it.isLowerCase()) it.titlecase() else it.toString()
}
val currentDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
/**
* This is the full date format for the date picker.
* "dd MM yyyy" is the format of the date picker.
*/
val formattedFullDate = currentDate.format(fullMonthFormat)
/**
* This is the short date format for the date picker.
* "dd-MM-yyyy" is the format of the date picker.
*/
val formattedShortDate = currentDate.format(shortMonthFormat)
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
import co.touchlab.kermit.Logger
object DebugUtil {
private val logger = Logger.withTag("QXZ")
fun log(vararg objects: Any): Array<out Any> {
val stringToPrint = objects.joinToString(", ")
logger.d { stringToPrint }
return objects
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
import co.touchlab.kermit.Logger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
interface FileUtils {
suspend fun writeInputStreamDataToFile(inputStream: ByteArray, filePath: String): Boolean
companion object {
val logger = Logger.withTag("FileUtils")
}
}
expect fun createPlatformFileUtils(): FileUtils
class CommonFileUtils : FileUtils {
override suspend fun writeInputStreamDataToFile(
inputStream: ByteArray,
filePath: String,
): Boolean =
withContext(Dispatchers.Default) {
try {
true
} catch (e: Exception) {
FileUtils.logger.e { "Error writing file: ${e.message}" }
false
}
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
import org.koin.core.annotation.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class Dispatcher(val mifosDispatcher: MifosDispatchers)
enum class MifosDispatchers {
Default,
IO,
Unconfined,
}
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class ApplicationScope

View File

@ -0,0 +1,13 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
const val PAYEE_EXTERNAL_ID_ARG = "payeeExternalId"
const val TRANSFER_AMOUNT_ARG = "transferAmount"

View File

@ -0,0 +1,41 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
expect annotation class Parcelize()
expect interface Parcelable
expect annotation class IgnoredOnParcel()
expect interface Parceler<P> {
fun create(parcel: Parcel): P
fun P.write(parcel: Parcel, flags: Int)
}
expect annotation class TypeParceler<T, P : Parceler<in T>>()
expect class Parcel {
fun readByte(): Byte
fun readInt(): Int
fun readFloat(): Float
fun readDouble(): Double
fun readString(): String?
fun writeByte(value: Byte)
fun writeInt(value: Int)
fun writeFloat(value: Float)
fun writeDouble(value: Double)
fun writeString(value: String?)
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common.di
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import org.koin.core.module.Module
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.mifospay.core.common.MifosDispatchers
val DispatchersModule = module {
includes(ioDispatcherModule)
single<CoroutineDispatcher>(named(MifosDispatchers.Default.name)) { Dispatchers.Default }
single<CoroutineDispatcher>(named(MifosDispatchers.Unconfined.name)) { Dispatchers.Unconfined }
single<CoroutineScope>(named("ApplicationScope")) {
CoroutineScope(SupervisorJob() + Dispatchers.Default)
}
}
expect val ioDispatcherModule: Module

View File

@ -0,0 +1,33 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common.utils
/**
* Whether or not string is a valid email address.
*
* This just checks if the string contains the "@" symbol.
*/
fun String.isValidEmail(): Boolean = contains("@")
fun maskString(input: String, maskChar: Char = '*'): String {
if (input.length <= 3) return input
val visibleCount = 3
val maskLength = input.length - visibleCount
return buildString {
append(maskChar.toString().repeat(maskLength))
append(input.takeLast(visibleCount))
}
}
fun String.capitalizeWords(): String = split(" ").joinToString(" ") { it ->
it.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
import java.text.NumberFormat
import java.util.Currency
actual object CurrencyFormatter {
actual fun format(
balance: Double?,
currencyCode: String?,
maximumFractionDigits: Int?,
): String {
val numberFormat = NumberFormat.getCurrencyInstance()
numberFormat.maximumFractionDigits = maximumFractionDigits ?: 0
numberFormat.currency = Currency.getInstance(currencyCode)
return numberFormat.format(balance)
}
}

View File

@ -0,0 +1,13 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
// JVM and Android implementation
actual fun createPlatformFileUtils(): FileUtils = CommonFileUtils()

View File

@ -0,0 +1,46 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
actual interface Parcelable
actual annotation class IgnoredOnParcel
actual annotation class Parcelize
actual interface Parceler<P> {
actual fun create(parcel: Parcel): P
actual fun P.write(parcel: Parcel, flags: Int)
}
actual annotation class TypeParceler<T, P : Parceler<in T>>
actual class Parcel {
actual fun readString(): String? = null
actual fun readByte(): Byte = 1
actual fun readInt(): Int = 1
actual fun readFloat(): Float = 1f
actual fun readDouble(): Double = 1.0
actual fun writeByte(value: Byte) {
}
actual fun writeInt(value: Int) {
}
actual fun writeFloat(value: Float) {
}
actual fun writeDouble(value: Double) {
}
actual fun writeString(value: String?) {
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common.di
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import org.koin.core.module.Module
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.mifospay.core.common.MifosDispatchers
actual val ioDispatcherModule: Module
get() = module {
single<CoroutineDispatcher>(named(MifosDispatchers.IO.name)) { Dispatchers.Default }
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
actual object CurrencyFormatter {
actual fun format(
balance: Double?,
currencyCode: String?,
maximumFractionDigits: Int?,
): String {
if (balance == null || currencyCode == null) {
return ""
}
val options = js("{}").unsafeCast<dynamic>()
options.style = "currency"
options.currency = currencyCode
if (maximumFractionDigits != null) {
options.maximumFractionDigits = maximumFractionDigits
}
return try {
js("new Intl.NumberFormat('en-US', options).format(balance)").toString()
} catch (e: Exception) {
console.error("Error formatting currency: ${e.message}")
balance.toString()
}
}
}

View File

@ -0,0 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
actual fun createPlatformFileUtils(): FileUtils = CommonFileUtils()

View File

@ -0,0 +1,46 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
actual interface Parcelable
actual annotation class IgnoredOnParcel
actual annotation class Parcelize
actual interface Parceler<P> {
actual fun create(parcel: Parcel): P
actual fun P.write(parcel: Parcel, flags: Int)
}
actual annotation class TypeParceler<T, P : Parceler<in T>>
actual class Parcel {
actual fun readString(): String? = null
actual fun readByte(): Byte = 1
actual fun readInt(): Int = 1
actual fun readFloat(): Float = 1f
actual fun readDouble(): Double = 1.0
actual fun writeByte(value: Byte) {
}
actual fun writeInt(value: Int) {
}
actual fun writeFloat(value: Float) {
}
actual fun writeDouble(value: Double) {
}
actual fun writeString(value: String?) {
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common.di
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import org.koin.core.module.Module
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.mifospay.core.common.MifosDispatchers
actual val ioDispatcherModule: Module
get() = module {
single<CoroutineDispatcher>(named(MifosDispatchers.IO.name)) { Dispatchers.Default }
}

View File

@ -1,192 +0,0 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.common
/**
* Created by naman on 17/6/17.
*/
object Constants {
const val BASIC = "Basic "
const val CLIENT_ID = "client_id"
const val ACCOUNT_ID = "account_id"
const val ACCOUNT = "account"
const val TO_EXTERNAL_ID = "to_external_id"
const val RUPEE = ""
const val QR_DATA = "qr_data"
const val MERCHANT_NAME = "merchant_name"
const val MERCHANT_VPA = "merchant_vpa"
const val MERCHANT_ACCOUNT_NO = "merchant_account_no"
const val FILE = "file"
const val MULTIPART_FORM_DATA = "multipart/form-data"
const val CHOOSE_A_FILE_TO_UPLOAD = "Choose a file to upload."
const val ERROR_UPLOADING_DOCS = "Error uploading docs."
const val KYC_LEVEL_2_DOCUMENTS_ADDED_SUCCESSFULLY = "KYC Level 2 documents added successfully."
const val IMAGE = "image/*"
const val APPLICATION_PDF = "application/pdf"
const val ERROR_ADDING_KYC_LEVEL_1_DETAILS = "Error adding KYC Level 1 details."
const val KYC_LEVEL_1_DETAILS_ADDED_SUCCESSFULLY = "KYC Level 1 details added successfully."
const val PLEASE_WAIT = "Please wait.."
const val COMPLETE_KYC = "Complete KYC"
const val NEED_EXTERNAL_STORAGE_PERMISSION_TO_BROWSE_DOCUMENTS =
"Need external storage permission to browse documents."
const val CHOOSE_FILE = "Choose File"
const val KYC_REGISTRATION_LEVEL_2 = "KYC Registration Level 2"
const val KYC_REGISTRATION_LEVEL_1 = "KYC Registration Level 1"
const val DD_MM_YY = "dd/MM/yy"
const val ERROR_DELETING_CARD = "Error deleting card."
const val CARD_DELETED_SUCCESSFULLY = "Card deleted successfully."
const val DELETING_CARD = "Deleting Card.."
const val ERROR_UPDATING_CARD = "Error updating card."
const val CARD_UPDATED_SUCCESSFULLY = "Card updated successfully."
const val INVALID_CREDIT_CARD_NUMBER = "Invalid Credit Card Number"
const val UPDATING_CARD = "Updating Card.."
const val ERROR_ADDING_CARD = "Error adding card."
const val CARD_ADDED_SUCCESSFULLY = "Card added successfully."
const val ADDING_CARD = "Adding Card.."
const val ADD_CARD_DIALOG = "Add Card Dialog"
const val EDIT_CARD_DIALOG = "Edit Card Dialog"
const val SAVED_CARDS = "Saved Cards"
const val UPDATE = "Update"
const val KYC_REGISTRATION_LEVEL_3 = "KYC Registration Level 3"
const val OK = "OK"
const val SCAN_CODE = "Scan code"
const val QR_CODE = "QR code"
const val FAILED_TO_WRITE_DATA_TO_QR = "Failed to write data to qr"
const val ERROR_OCCURRED = "Error occurred"
const val LOGGING_IN = "Logging in.."
const val HOME = "Home"
const val PROFILE = "Profile"
const val ERROR_FETCHING_BALANCE = "Error fetching balance"
const val UNABLE_TO_PROCESS_TRANSFER = "Unable to process transfer"
const val TRANSACTION_SUCCESSFUL = "Transaction successful"
const val SENDING_MONEY = "Sending money..."
const val INSUFFICIENT_BALANCE = "Insufficient balance"
const val ERROR_FINDING_VPA = "Error finding Virtual Payment Address"
const val ERROR_FINDING_MOBILE_NUMBER = "Error finding Mobile Number"
const val PLEASE_ENTER_VALID_AMOUNT = "Please enter a valid amount"
const val VPA_VALIDATION_REGEX = "^\\w.+@\\w+$"
const val SELF_ACCOUNT_ERROR = "Self Account transfer is not allowed"
const val PLEASE_ENTER_AMOUNT = "Please enter a valid amount before making the transfer"
const val NEED_READ_CONTACTS_PERMISSION = "Need read contacts permission"
const val NEED_CAMERA_PERMISSION_TO_SCAN_QR_CODE = "Need camera permission to scan qr code."
const val ERROR_CHOOSING_CONTACT = "Error choosing contact"
const val MAKE_TRANSFER_FRAGMENT = "Make Transfer Fragment"
const val PLEASE_ENTER_ALL_THE_FIELDS = "Please enter all the fields"
const val TRANSFER = "Transfer"
const val TRANSACTION_DETAILS = "Transaction Details"
const val ACCOUNT_NUMBER = "Account Number : "
const val TRANSACTION = "transaction"
const val TRANSACTIONS_HISTORY = "Transactions History History"
const val SPECIFIC_TRANSACTIONS = "Specific Transactions History"
const val HISTORY_NOT_AVAILABLE = "No Transaction History Available"
const val RECEIPT_DOMAIN = "https://receipt.mifospay.com/"
const val OTHER = "Other"
const val CREDIT = "Credit"
const val DEBIT = "Debit"
const val RECEIPT_ID = "Receipt ID"
const val DATE = "Date"
const val TRANSACTION_ID = "Transaction ID"
const val TRANSACTIONS = "transactions"
const val ERROR_FETCHING_TRANSACTIONS = "Error fetching transactions"
const val TRANSFER_DETAILS = "transfer details"
const val UNIQUE_PAYMENT_LINK_COPIED_TO_CLIPBOARD = "Unique Payment Link copied to clipboard"
const val UNIQUE_PAYMENT_LINK = "Unique Payment Link"
const val STATUS = "Status"
const val UNIQUE_RECEIPT_LINK_COPIED_TO_CLIPBOARD = "Unique Receipt Link copied to clipboard"
const val UNIQUE_RECEIPT_LINK = "Unique Receipt Link"
const val DONE = "Done"
const val PENDING = "Pending"
const val ITEMS = "Item(s)"
const val AMOUNT = "Amount"
const val INR = "INR"
const val CONSUMER = "Consumer"
const val MERCHANT = "Merchant"
const val INVOICE = "Invoice"
const val NEED_EXTERNAL_STORAGE_PERMISSION_TO_DOWNLOAD_RECEIPT =
"Need external storage permission to download receipt"
const val RECEIPT_DOWNLOADED_SUCCESSFULLY = "Receipt Downloaded Successfully"
const val ERROR_DOWNLOADING_RECEIPT = "Error downloading receipt"
const val MIFOSPAY = "mifospay"
const val RECEIPT = "Receipt"
const val PDF = ".pdf"
const val ERROR_FETCHING_RECEIPT = "Error fetching receipt"
const val INVOICE_DOMAIN = "https://invoice.mifospay.com/"
const val SENDING_OTP_TO_YOUR_MOBILE_NUMBER = "Sending OTP to your mobile number.."
const val MOBILE_NUMBER = "Mobile Number"
const val COUNTRY = "Country"
const val GOOGLE_SIGN_IN_FAILED = "Google Sign in failed."
const val GOOGLE_GIVEN_NAME = "GOOGLE_GIVEN_NAME"
const val GOOGLE_FAMILY_NAME = "GOOGLE_FAMILY_NAME"
const val GOOGLE_EMAIL = "GOOGLE_EMAIL"
const val GOOGLE_DISPLAY_NAME = "GOOGLE_DISPLAY_NAME"
const val GOOGLE_PHOTO_URI = "GOOGLE_PHOTO_URI"
const val CHOOSE_SIGNUP_METHOD = "Choose Signup Method"
const val LOGGING_OUT = "Logging out..."
const val NEED_EXTERNAL_STORAGE_PERMISSION_TO_BROWSE_IMAGES =
"Need external storage permission to browse images"
const val SETTINGS = "Settings"
const val EDIT_PROFILE = "Edit Profile"
const val FAQ = "Frequent Asked Questions"
const val LINKED_BANK_ACCOUNTS = "Linked Bank Accounts"
const val BANK_ACCOUNT_DETAILS = "Bank Account Details"
const val NEW_BANK_ACCOUNT = "newBankAccount"
const val VERIFYING_MOBILE_NUMBER = "Verifying mobile number.."
const val VERIFYING_OTP = "Verifying Otp.."
const val MIFOS_SAVINGS_PRODUCT_ID = "Mifos Savings Product Id"
const val MERCHANTS = "Merchants"
const val UPI_PIN = "UPI PIN"
const val STEP = "step"
const val TYPE = "Type"
const val SETUP = "Setup"
const val BANK_DELETED_SUCCESSFULLY = "Bank deleted successfully"
const val ERROR_OCCURRED_WHILE_CHANGING_UPI_PIN = "Error occurred while changing UPI PIN"
const val SETUP_UPI = "Setup UPI"
const val ERROR_WHILE_SETTING_UP_UPI_PIN = "Error while setting up UPI PIN"
const val UPI_PIN_SETUP_COMPLETED_SUCCESSFULLY = "UPI PIN Setup Completed Successfully"
const val FORGOT = "Forgot"
const val CHANGE = "Change"
const val OTP = "otp"
const val SETUP_UPI_PIN = "Setup UPI PIN"
const val SETTING_UP_UPI_PIN = "Setting up UPI PIN.."
const val UPDATED_BANK_ACCOUNT = "Updated Bank Account"
const val INDEX = "Index"
const val CHANGE_UPI_PIN = "Change UPI PIN"
const val FORGOT_UPI_PIN = "Forgot UPI PIN"
// broadcast receiver intent filters
const val REGISTRATION_COMPLETE = "registrationComplete"
const val PUSH_NOTIFICATION = "pushNotification"
// id to handle the notification in the notification tray
const val NOTIFICATION_ID = 100
const val NOTIFICATION_ID_BIG_IMAGE = 101
const val ERROR_FIELDS_CANNOT_BE_EMPTY = "Fields cannot be empty"
const val ERROR_VALIDATING_PASSWORD = "Passwords are not the same"
const val ERROR_PASSWORDS_CANT_BE_SAME = "New password can't be the same as old password."
const val CHANGE_PROFILE_IMAGE_KEY = "CHANGE_PROFILE_IMAGE_KEY"
const val CHANGE_PROFILE_IMAGE_VALUE = "CHANGE_PROFILE_IMAGE_VALUE"
const val TAP_TO_REVEAL = "Tap to Reveal"
const val NAME = "Name : "
const val ERROR_FETCHING_TRANSACTION_DETAILS = "Error fetching details"
// const val WHITE_BACK_BUTTON = R.drawable.ic_arrow_back_white_24dp
// const val BLACK_BACK_BUTTON = R.drawable.ic_arrow_back_black_24dp
const val VIEW = "View"
const val CURRENT_PASSCODE = "current passcode"
const val UPDATE_PASSCODE = "update passcode"
const val SELECT_DATE = "SELECT DATE"
const val SI_ID = "standing_instruction_id"
const val UNAUTHORIZED_ERROR = "401 Unauthorized"
const val RECEIPT_SHARING_MESSAGE = "Receipt link for the transaction "
const val TO = " to "
const val COLON = " : "
const val REQUEST_CAMERA = 0
const val SCAN_QR_REQUEST_CODE = 666
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.common
object CreditCardUtils {
fun validateCreditCardNumber(str: String): Boolean {
if (str.isEmpty()) {
return false
}
val ints = IntArray(str.length)
for (i in str.indices) {
ints[i] = str.substring(i, i + 1).toInt()
}
run {
var i = ints.size - 2
while (i >= 0) {
var j = ints[i]
j *= 2
if (j > 9) {
j = j % 10 + 1
}
ints[i] = j
i -= 2
}
}
var sum = 0
for (i in ints.indices) {
sum += ints[i]
}
return sum % 10 == 0
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.common
import android.util.Log
// TODO Move into separate module
object DebugUtil {
fun log(vararg objects: Any): Array<Any> {
var stringToPrint = ""
for (`object` in objects) {
stringToPrint += "$`object`, "
}
stringToPrint = stringToPrint.substring(0, stringToPrint.lastIndexOf(','))
Log.d("QXZ:: ", stringToPrint)
return objects as Array<Any>
}
}

View File

@ -1,36 +0,0 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.common
import android.util.Log
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
object FileUtils {
fun writeInputStreamDataToFile(inputStream: InputStream, file: File?): Boolean {
return try {
val out: OutputStream = FileOutputStream(file)
val buf = ByteArray(1024)
var len: Int
while (inputStream.read(buf).also { len = it } > 0) {
out.write(buf, 0, len)
}
out.close()
inputStream.close()
true
} catch (e: Exception) {
Log.e("Message", e.message.toString())
false
}
}
}

View File

@ -1,13 +0,0 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.common
const val PAYEE_EXTERNAL_ID_ARG = "payeeExternalId"
const val TRANSFER_AMOUNT_ARG = "transferAmount"

View File

@ -1,47 +0,0 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.common
import java.text.NumberFormat
import java.util.Currency
object Utils {
@JvmStatic
fun getFormattedAccountBalance(
balance: Double?,
currencyCode: String?,
maximumFractionDigits: Int? = 0,
): String {
val accountBalanceFormatter = NumberFormat.getCurrencyInstance()
accountBalanceFormatter.maximumFractionDigits = maximumFractionDigits ?: 0
accountBalanceFormatter.currency = Currency.getInstance(currencyCode)
return accountBalanceFormatter.format(balance)
}
// returns in "$ 10,000.00" format
fun getNewCurrencyFormatter(
balance: Double,
currencySymbol: String,
minimumFractionDigit: Int = 0,
): String {
val accountBalanceFormatter = NumberFormat.getNumberInstance().apply {
maximumFractionDigits = 2
minimumFractionDigits = minimumFractionDigit
}
return currencySymbol + " " + accountBalanceFormatter.format(balance)
}
fun <T> List<T>.toArrayList(): ArrayList<T> {
val array: ArrayList<T> = ArrayList()
for (index in this) array.add(index)
return array
}
}

View File

@ -1,25 +0,0 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.network
import org.koin.core.annotation.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class Dispatcher(val mifosDispatcher: MifosDispatchers)
enum class MifosDispatchers {
Default,
IO,
}
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class ApplicationScope

View File

@ -1,23 +0,0 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.network.di
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import org.koin.core.qualifier.named
import org.koin.dsl.module
val CoroutineScopesModule = module {
single<CoroutineScope>(named("ApplicationScope")) {
CoroutineScope(SupervisorJob() + Dispatchers.Default)
}
}

View File

@ -1,21 +0,0 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.network.di
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.mifospay.core.network.MifosDispatchers
val DispatchersModule = module {
single<CoroutineDispatcher>(named(MifosDispatchers.IO.name)) { Dispatchers.IO }
single<CoroutineDispatcher>(named(MifosDispatchers.Default.name)) { Dispatchers.Default }
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
import platform.Foundation.NSNumber
import platform.Foundation.NSNumberFormatter
import platform.Foundation.NSNumberFormatterCurrencyStyle
actual object CurrencyFormatter {
actual fun format(
balance: Double?,
currencyCode: String?,
maximumFractionDigits: Int?,
): String {
val numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle
numberFormatter.currencyCode = currencyCode ?: "$"
numberFormatter.maximumFractionDigits = (maximumFractionDigits ?: 0).toULong()
return numberFormatter.stringFromNumber(NSNumber(balance ?: 0.0)) ?: ""
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
import kotlinx.cinterop.BetaInteropApi
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.allocArrayOf
import kotlinx.cinterop.memScoped
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import platform.Foundation.NSData
import platform.Foundation.create
import platform.Foundation.writeToFile
// iOS implementation
@BetaInteropApi
@OptIn(ExperimentalForeignApi::class)
actual fun createPlatformFileUtils(): FileUtils = object : FileUtils {
override suspend fun writeInputStreamDataToFile(
inputStream: ByteArray,
filePath: String,
): Boolean =
withContext(Dispatchers.Default) {
try {
val nsData = inputStream.toNSData()
nsData.writeToFile(filePath, true)
true
} catch (e: Exception) {
FileUtils.logger.e { "Error writing file: ${e.message}" }
false
}
}
@BetaInteropApi
fun ByteArray.toNSData(): NSData = memScoped {
NSData.create(bytes = allocArrayOf(this@toNSData), length = this@toNSData.size.toULong())
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
actual interface Parcelable
actual annotation class IgnoredOnParcel
actual annotation class Parcelize
actual interface Parceler<P> {
actual fun create(parcel: Parcel): P
actual fun P.write(parcel: Parcel, flags: Int)
}
actual annotation class TypeParceler<T, P : Parceler<in T>>
actual class Parcel {
actual fun readString(): String? = null
actual fun readByte(): Byte = 1
actual fun readInt(): Int = 1
actual fun readFloat(): Float = 1f
actual fun readDouble(): Double = 1.0
actual fun writeByte(value: Byte) {
}
actual fun writeInt(value: Int) {
}
actual fun writeFloat(value: Float) {
}
actual fun writeDouble(value: Double) {
}
actual fun writeString(value: String?) {
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common.di
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import org.koin.core.module.Module
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.mifospay.core.common.MifosDispatchers
actual val ioDispatcherModule: Module
get() = module {
single<CoroutineDispatcher>(named(MifosDispatchers.IO.name)) { Dispatchers.Default }
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
actual object CurrencyFormatter {
actual fun format(
balance: Double?,
currencyCode: String?,
maximumFractionDigits: Int?,
): String {
return "$currencyCode ${
balance?.let {
val formattedBalance = balance.toString()
val fractionDigits = formattedBalance.substringAfterLast(".")
val fractionDigitsLength = fractionDigits.length
val fractionDigitsToDisplay = if (fractionDigitsLength > maximumFractionDigits!!) {
fractionDigits.substring(0, maximumFractionDigits)
} else {
fractionDigits
}
val integerDigits = formattedBalance.substringBeforeLast(".")
val integerDigitsWithCommas =
integerDigits.reversed().chunked(3).joinToString(",").reversed()
"$integerDigitsWithCommas.$fractionDigitsToDisplay"
} ?: "0.00"
}"
}
}

View File

@ -0,0 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
actual fun createPlatformFileUtils(): FileUtils = CommonFileUtils()

View File

@ -0,0 +1,46 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common
actual interface Parcelable
actual annotation class IgnoredOnParcel
actual annotation class Parcelize
actual interface Parceler<P> {
actual fun create(parcel: Parcel): P
actual fun P.write(parcel: Parcel, flags: Int)
}
actual annotation class TypeParceler<T, P : Parceler<in T>>
actual class Parcel {
actual fun readString(): String? = null
actual fun readByte(): Byte = 1
actual fun readInt(): Int = 1
actual fun readFloat(): Float = 1f
actual fun readDouble(): Double = 1.0
actual fun writeByte(value: Byte) {
}
actual fun writeInt(value: Int) {
}
actual fun writeFloat(value: Float) {
}
actual fun writeDouble(value: Double) {
}
actual fun writeString(value: String?) {
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.common.di
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import org.koin.core.module.Module
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.mifospay.core.common.MifosDispatchers
actual val ioDispatcherModule: Module
get() = module {
single<CoroutineDispatcher>(named(MifosDispatchers.IO.name)) { Dispatchers.Default }
}

View File

@ -8,7 +8,7 @@
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md * See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/ */
plugins { plugins {
alias(libs.plugins.mifospay.android.library) alias(libs.plugins.mifospay.kmp.library)
alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.kotlin.parcelize)
id("kotlinx-serialization") id("kotlinx-serialization")
} }
@ -23,26 +23,26 @@ android {
} }
} }
dependencies { kotlin {
sourceSets {
commonMain.dependencies {
api(projects.core.common) api(projects.core.common)
api(projects.core.datastore)
api(projects.core.model) api(projects.core.model)
api(projects.core.network) implementation(projects.core.network)
implementation(projects.core.analytics)
implementation(libs.squareup.retrofit2) {
// exclude Retrofits OkHttp peer-dependency module and define your own module import
exclude(module = "okhttp")
}
implementation(libs.squareup.retrofit.adapter.rxjava)
implementation(libs.squareup.retrofit.converter.gson)
implementation(libs.squareup.okhttp)
implementation(libs.squareup.logging.interceptor)
implementation(libs.reactivex.rxjava.android)
implementation(libs.reactivex.rxjava)
testImplementation(libs.junit)
androidTestImplementation(libs.espresso.core)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
}
commonTest.dependencies {
implementation(libs.multiplatform.settings)
implementation(libs.multiplatform.settings.test)
}
androidMain.dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.tracing.ktx)
implementation(libs.koin.android) implementation(libs.koin.android)
} }
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.di
import android.content.Context
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import org.mifospay.core.data.util.ConnectivityManagerNetworkMonitor
import org.mifospay.core.data.util.NetworkMonitor
import org.mifospay.core.data.util.TimeZoneBroadcastMonitor
import org.mifospay.core.data.util.TimeZoneMonitor
class AndroidPlatformDependentDataModule(
private val context: Context,
private val dispatcher: CoroutineDispatcher,
private val scope: CoroutineScope,
) : PlatformDependentDataModule {
override val networkMonitor: NetworkMonitor by lazy {
ConnectivityManagerNetworkMonitor(context, dispatcher)
}
override val timeZoneMonitor: TimeZoneMonitor by lazy {
TimeZoneBroadcastMonitor(context, scope, dispatcher)
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.di
import org.koin.android.ext.koin.androidContext
import org.koin.core.module.Module
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.mifospay.core.common.MifosDispatchers
import org.mifospay.core.data.util.ConnectivityManagerNetworkMonitor
import org.mifospay.core.data.util.NetworkMonitor
import org.mifospay.core.data.util.TimeZoneBroadcastMonitor
import org.mifospay.core.data.util.TimeZoneMonitor
val AndroidDataModule = module {
single<NetworkMonitor> {
ConnectivityManagerNetworkMonitor(androidContext(), get(named(MifosDispatchers.IO.name)))
}
single<TimeZoneMonitor> {
TimeZoneBroadcastMonitor(
context = androidContext(),
appScope = get(named("ApplicationScope")),
ioDispatcher = get(named(MifosDispatchers.IO.name)),
)
}
single {
AndroidPlatformDependentDataModule(
context = androidContext(),
dispatcher = get(named(MifosDispatchers.IO.name)),
scope = get(named("ApplicationScope")),
)
}
}
actual val platformModule: Module = AndroidDataModule
actual val getPlatformDataModule: PlatformDependentDataModule
get() = org.koin.core.context.GlobalContext.get().get()

View File

@ -0,0 +1,76 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.util
import android.content.Context
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.net.NetworkRequest.Builder
import androidx.core.content.getSystemService
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
internal class ConnectivityManagerNetworkMonitor(
private val context: Context,
ioDispatcher: CoroutineDispatcher,
) : NetworkMonitor {
override val isOnline: Flow<Boolean> = callbackFlow {
val connectivityManager = context.getSystemService<ConnectivityManager>()
if (connectivityManager == null) {
channel.trySend(false)
channel.close()
return@callbackFlow
}
/**
* The callback's methods are invoked on changes to *any* network matching the [NetworkRequest],
* not just the active network. So we can simply track the presence (or absence) of such [Network].
*/
val callback = object : NetworkCallback() {
private val networks = mutableSetOf<Network>()
override fun onAvailable(network: Network) {
networks += network
channel.trySend(true)
}
override fun onLost(network: Network) {
networks -= network
channel.trySend(networks.isNotEmpty())
}
}
val request = Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, callback)
/**
* Sends the latest connectivity status to the underlying channel.
*/
channel.trySend(connectivityManager.isCurrentlyConnected())
awaitClose {
connectivityManager.unregisterNetworkCallback(callback)
}
}
.flowOn(ioDispatcher)
.conflate()
private fun ConnectivityManager.isCurrentlyConnected() = activeNetwork
?.let(::getNetworkCapabilities)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) ?: false
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.util
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.tracing.trace
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.shareIn
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toKotlinTimeZone
import java.time.ZoneId
internal class TimeZoneBroadcastMonitor(
private val context: Context,
appScope: CoroutineScope,
ioDispatcher: CoroutineDispatcher,
) : TimeZoneMonitor {
override val currentTimeZone: SharedFlow<TimeZone> =
callbackFlow {
// Send the default time zone first.
trySend(TimeZone.currentSystemDefault())
// Registers BroadcastReceiver for the TimeZone changes
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != Intent.ACTION_TIMEZONE_CHANGED) return
val zoneIdFromIntent = if (VERSION.SDK_INT < VERSION_CODES.R) {
null
} else {
// Starting Android R we also get the new TimeZone.
intent.getStringExtra(Intent.EXTRA_TIMEZONE)?.let { timeZoneId ->
// We need to convert it from java.util.Timezone to java.time.ZoneId
val zoneId = ZoneId.of(timeZoneId, ZoneId.SHORT_IDS)
// Convert to kotlinx.datetime.TimeZone
zoneId.toKotlinTimeZone()
}
}
/* If there isn't a zoneId in the intent, fallback to the systemDefault,
which should also reflect the change */
trySend(zoneIdFromIntent ?: TimeZone.currentSystemDefault())
}
}
trace("TimeZoneBroadcastReceiver.register") {
context.registerReceiver(receiver, IntentFilter(Intent.ACTION_TIMEZONE_CHANGED))
}
// Send here again, because registering the Broadcast Receiver can take up to several milliseconds.
// This way, we can reduce the likelihood that a TZ change wouldn't be caught with the Broadcast Receiver.
trySend(TimeZone.currentSystemDefault())
awaitClose {
context.unregisterReceiver(receiver)
}
}
// We use to prevent multiple emissions of the same type, because we use trySend multiple times.
.distinctUntilChanged()
.conflate()
.flowOn(ioDispatcher)
// Sharing the callback to prevent multiple BroadcastReceivers being registered
.shareIn(appScope, SharingStarted.WhileSubscribed(5_000), 1)
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.di
import org.koin.core.module.Module
import org.mifospay.core.data.util.NetworkMonitor
import org.mifospay.core.data.util.TimeZoneMonitor
interface PlatformDependentDataModule {
val networkMonitor: NetworkMonitor
val timeZoneMonitor: TimeZoneMonitor
}
expect val platformModule: Module
expect val getPlatformDataModule: PlatformDependentDataModule

View File

@ -0,0 +1,105 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.di
import kotlinx.serialization.json.Json
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.mifospay.core.common.MifosDispatchers
import org.mifospay.core.data.repository.AccountRepository
import org.mifospay.core.data.repository.AuthenticationRepository
import org.mifospay.core.data.repository.BeneficiaryRepository
import org.mifospay.core.data.repository.ClientRepository
import org.mifospay.core.data.repository.DocumentRepository
import org.mifospay.core.data.repository.InvoiceRepository
import org.mifospay.core.data.repository.KycLevelRepository
import org.mifospay.core.data.repository.LocalAssetRepository
import org.mifospay.core.data.repository.NotificationRepository
import org.mifospay.core.data.repository.RegistrationRepository
import org.mifospay.core.data.repository.RunReportRepository
import org.mifospay.core.data.repository.SavedCardRepository
import org.mifospay.core.data.repository.SavingsAccountRepository
import org.mifospay.core.data.repository.SearchRepository
import org.mifospay.core.data.repository.SelfServiceRepository
import org.mifospay.core.data.repository.StandingInstructionRepository
import org.mifospay.core.data.repository.ThirdPartyTransferRepository
import org.mifospay.core.data.repository.TwoFactorAuthRepository
import org.mifospay.core.data.repository.UserRepository
import org.mifospay.core.data.repositoryImp.AccountRepositoryImpl
import org.mifospay.core.data.repositoryImp.AuthenticationRepositoryImpl
import org.mifospay.core.data.repositoryImp.BeneficiaryRepositoryImpl
import org.mifospay.core.data.repositoryImp.ClientRepositoryImpl
import org.mifospay.core.data.repositoryImp.DocumentRepositoryImpl
import org.mifospay.core.data.repositoryImp.InvoiceRepositoryImpl
import org.mifospay.core.data.repositoryImp.KycLevelRepositoryImpl
import org.mifospay.core.data.repositoryImp.LocalAssetRepositoryImpl
import org.mifospay.core.data.repositoryImp.NotificationRepositoryImpl
import org.mifospay.core.data.repositoryImp.RegistrationRepositoryImpl
import org.mifospay.core.data.repositoryImp.RunReportRepositoryImpl
import org.mifospay.core.data.repositoryImp.SavedCardRepositoryImpl
import org.mifospay.core.data.repositoryImp.SavingsAccountRepositoryImpl
import org.mifospay.core.data.repositoryImp.SearchRepositoryImpl
import org.mifospay.core.data.repositoryImp.SelfServiceRepositoryImpl
import org.mifospay.core.data.repositoryImp.StandingInstructionRepositoryImpl
import org.mifospay.core.data.repositoryImp.ThirdPartyTransferRepositoryImpl
import org.mifospay.core.data.repositoryImp.TwoFactorAuthRepositoryImpl
import org.mifospay.core.data.repositoryImp.UserRepositoryImpl
import org.mifospay.core.data.util.NetworkMonitor
import org.mifospay.core.data.util.TimeZoneMonitor
private val ioDispatcher = named(MifosDispatchers.IO.name)
private val unconfined = named(MifosDispatchers.Unconfined.name)
val RepositoryModule = module {
single<Json> { Json { ignoreUnknownKeys = true } }
single<AccountRepository> { AccountRepositoryImpl(get(), get(ioDispatcher)) }
single<AuthenticationRepository> {
AuthenticationRepositoryImpl(get(), get(ioDispatcher))
}
single<BeneficiaryRepository> { BeneficiaryRepositoryImpl(get(), get(ioDispatcher)) }
single<ClientRepository> {
ClientRepositoryImpl(
apiManager = get(),
fineractApiManager = get(),
ioDispatcher = get(ioDispatcher),
)
}
single<DocumentRepository> { DocumentRepositoryImpl(get(), get(ioDispatcher)) }
single<InvoiceRepository> { InvoiceRepositoryImpl(get(), get(ioDispatcher)) }
single<KycLevelRepository> { KycLevelRepositoryImpl(get(), get(ioDispatcher)) }
single<NotificationRepository> { NotificationRepositoryImpl(get(), get(ioDispatcher)) }
single<RegistrationRepository> { RegistrationRepositoryImpl(get(), get(ioDispatcher)) }
single<RunReportRepository> { RunReportRepositoryImpl(get(), get(ioDispatcher)) }
single<SavedCardRepository> { SavedCardRepositoryImpl(get(), get(ioDispatcher)) }
single<SavingsAccountRepository> { SavingsAccountRepositoryImpl(get(), get(ioDispatcher)) }
single<SearchRepository> { SearchRepositoryImpl(get(), get(ioDispatcher)) }
single<SelfServiceRepository> { SelfServiceRepositoryImpl(get(), get(ioDispatcher)) }
single<StandingInstructionRepository> {
StandingInstructionRepositoryImpl(get(), get(ioDispatcher))
}
single<ThirdPartyTransferRepository> {
ThirdPartyTransferRepositoryImpl(get(), get(ioDispatcher))
}
single<TwoFactorAuthRepository> { TwoFactorAuthRepositoryImpl(get(), get(ioDispatcher)) }
single<UserRepository> { UserRepositoryImpl(get(), get(ioDispatcher)) }
includes(platformModule)
single<PlatformDependentDataModule> { getPlatformDataModule }
single<NetworkMonitor> { getPlatformDataModule.networkMonitor }
single<TimeZoneMonitor> { getPlatformDataModule.timeZoneMonitor }
single<LocalAssetRepository> {
LocalAssetRepositoryImpl(
ioDispatcher = get(qualifier = ioDispatcher),
unconfinedDispatcher = get(unconfined),
networkJson = get(),
)
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.mapper
import org.mifospay.core.model.account.Account
import org.mifospay.core.model.savingsaccount.SavingAccountEntity
import org.mifospay.core.network.model.entity.client.ClientAccountsEntity
fun ClientAccountsEntity.toAccount(): List<Account> {
return this.savingsAccounts.toAccount()
}
fun List<SavingAccountEntity>.toAccount(): List<Account> {
return map {
Account(
name = it.productName,
number = it.accountNo,
id = it.id,
balance = it.accountBalance,
currency = it.currency,
productId = it.productId,
status = it.status,
)
}
}

View File

@ -0,0 +1,106 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.mapper
import org.mifospay.core.model.client.Client
import org.mifospay.core.model.client.ClientAddress
import org.mifospay.core.model.client.ClientStatus
import org.mifospay.core.model.client.ClientTimeline
import org.mifospay.core.model.client.NewClient
import org.mifospay.core.model.client.UpdatedClient
import org.mifospay.core.network.model.entity.Page
import org.mifospay.core.network.model.entity.client.Address
import org.mifospay.core.network.model.entity.client.ClientEntity
import org.mifospay.core.network.model.entity.client.ClientTimelineEntity
import org.mifospay.core.network.model.entity.client.NewClientEntity
import org.mifospay.core.network.model.entity.client.Status
import org.mifospay.core.network.model.entity.client.UpdateClientEntity
fun ClientEntity.toModel(): Client {
return Client(
id = id ?: 0,
accountNo = accountNo ?: "",
externalId = externalId ?: "",
active = active,
activationDate = activationDate,
firstname = firstname ?: "",
lastname = lastname ?: "",
displayName = displayName ?: "",
mobileNo = mobileNo ?: "",
emailAddress = emailAddress ?: "",
dateOfBirth = dateOfBirth,
isStaff = isStaff ?: false,
officeId = officeId ?: 0,
officeName = officeName ?: "",
savingsProductName = savingsProductName ?: "",
status = status?.toModel() ?: ClientStatus(),
timeline = timeline?.toModel() ?: ClientTimeline(),
legalForm = legalForm?.toModel() ?: ClientStatus(),
)
}
fun List<ClientEntity>.toModel(): List<Client> = map { it.toModel() }
fun Page<ClientEntity>.toModel(): Page<Client> {
return Page(
totalFilteredRecords = this.totalFilteredRecords,
pageItems = this.pageItems.map { it.toModel() }.toMutableList(),
)
}
fun NewClient.toEntity(): NewClientEntity {
return NewClientEntity(
firstname = firstname,
lastname = lastname,
externalId = externalId,
mobileNo = mobileNo,
address = address.toEntity(),
savingsProductId = savingsProductId,
)
}
fun ClientAddress.toEntity(): Address {
return Address(
addressLine1 = addressLine1,
addressLine2 = addressLine2,
postalCode = postalCode,
stateProvinceId = stateProvinceId,
countryId = countryId,
addressTypeId = addressTypeId,
)
}
fun Status.toModel(): ClientStatus {
return ClientStatus(
id = id ?: 0,
code = code ?: "",
value = value ?: "",
)
}
fun ClientTimelineEntity.toModel(): ClientTimeline {
return ClientTimeline(
submittedOnDate = submittedOnDate,
activatedOnDate = activatedOnDate,
activatedByUsername = activatedByUsername,
activatedByFirstname = activatedByFirstname,
activatedByLastname = activatedByLastname,
)
}
fun UpdatedClient.toEntity(): UpdateClientEntity {
return UpdateClientEntity(
firstname = firstname,
lastname = lastname,
externalId = externalId,
mobileNo = mobileNo,
emailAddress = emailAddress,
)
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.mapper
import org.mifospay.core.model.savingsaccount.SavingAccount
import org.mifospay.core.model.savingsaccount.SavingAccountDetail
import org.mifospay.core.model.savingsaccount.SavingAccountEntity
import org.mifospay.core.model.savingsaccount.SavingsWithAssociationsEntity
fun SavingAccountEntity.toModel(): SavingAccount {
return SavingAccount(
id = id,
accountNo = accountNo,
productId = productId,
productName = productName,
shortProductName = shortProductName,
status = status,
currency = currency,
accountBalance = accountBalance,
accountType = accountType,
timeline = timeline,
subStatus = subStatus,
lastActiveTransactionDate = lastActiveTransactionDate,
depositType = depositType,
externalId = externalId,
)
}
fun SavingsWithAssociationsEntity.toSavingDetail(): SavingAccountDetail {
return SavingAccountDetail(
id = id,
accountNo = accountNo,
depositType = depositType,
clientId = clientId,
clientName = clientName,
savingsProductId = savingsProductId,
savingsProductName = savingsProductName,
fieldOfficerId = fieldOfficerId,
status = status,
timeline = timeline,
currency = currency,
nominalAnnualInterestRate = nominalAnnualInterestRate,
withdrawalFeeForTransfers = withdrawalFeeForTransfers,
allowOverdraft = allowOverdraft,
enforceMinRequiredBalance = enforceMinRequiredBalance,
lienAllowed = lienAllowed,
withHoldTax = withHoldTax,
lastActiveTransactionDate = lastActiveTransactionDate,
isDormancyTrackingActive = isDormancyTrackingActive,
summary = summary,
transactions = transactions.map { it.toModel() },
)
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.mapper
import org.mifospay.core.model.search.SearchResult
import org.mifospay.core.network.model.entity.SearchedEntity
fun SearchedEntity.toModel(): SearchResult {
return SearchResult(
entityId = entityId,
entityAccountNo = entityAccountNo,
entityName = entityName,
entityType = entityType,
parentId = parentId,
parentName = parentName,
)
}
fun List<SearchedEntity>.toSearchResult(): List<SearchResult> = map { it.toModel() }

View File

@ -0,0 +1,39 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.mapper
import org.mifospay.core.common.DateHelper
import org.mifospay.core.model.savingsaccount.SavingsWithAssociationsEntity
import org.mifospay.core.model.savingsaccount.Transaction
import org.mifospay.core.model.savingsaccount.TransactionType
import org.mifospay.core.model.savingsaccount.TransactionsEntity
fun SavingsWithAssociationsEntity.toTransactionList(): List<Transaction> {
return this.transactions.map { it.toModel() }
}
fun TransactionsEntity.toModel(): Transaction {
return Transaction(
transactionId = this.id,
amount = this.amount,
date = DateHelper.getDateAsString(this.submittedOnDate),
currency = this.currency,
transactionType = when {
this.transactionType.deposit -> TransactionType.CREDIT
this.transactionType.withdrawal -> TransactionType.DEBIT
else -> TransactionType.OTHER
},
transferId = this.transfer?.id,
accountId = this.accountId,
accountNo = this.accountNo,
originalTransactionId = this.originalTransactionId,
paymentDetailId = this.paymentDetailData?.id,
)
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.mapper
import org.mifospay.core.model.user.NewUser
import org.mifospay.core.model.user.RoleInfo
import org.mifospay.core.model.user.UserInfo
import org.mifospay.core.network.model.entity.Role
import org.mifospay.core.network.model.entity.user.NewUserEntity
import org.mifospay.core.network.model.entity.user.User
private const val OFFICE_ID = 1
private const val MOBILE_WALLET_ROLE_ID = 2
private const val SUPER_USER_ROLE_ID = 1
val NEW_USER_ROLE_IDS: ArrayList<Int> = arrayListOf(MOBILE_WALLET_ROLE_ID, SUPER_USER_ROLE_ID)
fun NewUser.toEntity(): NewUserEntity {
return NewUserEntity(
username = username,
firstname = firstname,
lastname = lastname,
email = email,
password = password,
officeId = OFFICE_ID,
roles = NEW_USER_ROLE_IDS,
sendPasswordToEmail = false,
isSelfServiceUser = true,
repeatPassword = password,
)
}
fun User.toUserInfo() = UserInfo(
username = username,
userId = userId,
base64EncodedAuthenticationKey = base64EncodedAuthenticationKey,
authenticated = authenticated,
officeId = officeId,
officeName = officeName,
roles = roles.map { it.toRoleInfo() },
permissions = permissions,
clients = clients,
shouldRenewPassword = shouldRenewPassword,
isTwoFactorAuthenticationRequired = isTwoFactorAuthenticationRequired,
)
fun Role.toRoleInfo() = RoleInfo(
id = id ?: "",
name = name ?: "",
description = description ?: "",
disabled = disabled,
)

View File

@ -0,0 +1,30 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.repository
import kotlinx.coroutines.flow.Flow
import org.mifospay.core.common.DataState
import org.mifospay.core.model.account.Account
import org.mifospay.core.model.account.AccountTransferPayload
import org.mifospay.core.model.savingsaccount.Transaction
import org.mifospay.core.model.savingsaccount.TransferDetail
import org.mifospay.core.model.search.AccountResult
interface AccountRepository {
fun getTransaction(accountId: Long, transactionId: Long): Flow<DataState<Transaction>>
fun getAccountTransfer(transferId: Long): Flow<DataState<TransferDetail>>
fun searchAccounts(query: String): Flow<DataState<List<AccountResult>>>
fun getSelfAccounts(clientId: Long): Flow<DataState<List<Account>>>
suspend fun makeTransfer(payload: AccountTransferPayload): DataState<String>
}

Some files were not shown because too many files have changed in this diff Show More