feat[notifications, clientCharges]: DB flow to room database (#2728)

* feat[notifications, clientCharges]: DB flow to room database

* fix: clientChargeRepositoryImpl test

* fix: module name, entity name

* fix: Dependency Guard

* fix: Database Implementation

* fix: Fix Test & Lint Issue

---------

Co-authored-by: Sk Niyaj Ali <niyaj639@gmail.com>
This commit is contained in:
Nagarjuna 2025-01-03 21:44:53 +05:30 committed by GitHub
parent 0297116156
commit efb38f5263
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
83 changed files with 2416 additions and 1990 deletions

View File

@ -9,15 +9,6 @@
*/
import org.mifos.mobile.dynamicVersion
/*
* 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-mobile/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifos.android.application)
alias(libs.plugins.mifos.android.application.compose)
@ -84,7 +75,7 @@ dependencies {
implementation(projects.core.common)
implementation(projects.core.model)
implementation(projects.core.data)
implementation(projects.core.datastore)
implementation(projects.core.database)
implementation(projects.core.ui)
implementation(projects.core.designsystem)
@ -132,7 +123,6 @@ dependencies {
implementation(libs.androidx.profileinstaller)
implementation(libs.google.oss.licenses)
implementation(libs.androidx.multidex)
implementation(libs.dbflow)
testImplementation(projects.core.testing)
testImplementation(libs.hilt.android.testing)

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
:core:common
:core:data
:core:database
:core:datastore
:core:designsystem
:core:logs
@ -30,9 +31,9 @@
:libs:mifos-passcode
:libs:pullrefresh
:shared
androidx.activity:activity-compose:1.9.1
androidx.activity:activity-ktx:1.9.1
androidx.activity:activity:1.9.1
androidx.activity:activity-compose:1.9.3
androidx.activity:activity-ktx:1.9.3
androidx.activity:activity:1.9.3
androidx.annotation:annotation-experimental:1.4.1
androidx.annotation:annotation-jvm:1.8.1
androidx.annotation:annotation:1.8.1
@ -41,55 +42,56 @@ androidx.appcompat:appcompat:1.7.0
androidx.arch.core:core-common:2.2.0
androidx.arch.core:core-runtime:2.2.0
androidx.autofill:autofill:1.0.0
androidx.camera:camera-camera2:1.3.4
androidx.camera:camera-core:1.3.4
androidx.camera:camera-lifecycle:1.3.4
androidx.camera:camera-video:1.3.4
androidx.camera:camera-view:1.3.4
androidx.collection:collection-jvm:1.4.2
androidx.collection:collection-ktx:1.4.2
androidx.collection:collection:1.4.2
androidx.compose.animation:animation-android:1.7.0-rc01
androidx.compose.animation:animation-core-android:1.7.0-rc01
androidx.compose.animation:animation-core:1.7.0-rc01
androidx.compose.animation:animation:1.7.0-rc01
androidx.compose.foundation:foundation-android:1.7.0-rc01
androidx.compose.foundation:foundation-layout-android:1.7.0-rc01
androidx.compose.foundation:foundation-layout:1.7.0-rc01
androidx.compose.foundation:foundation:1.7.0-rc01
androidx.compose.material3:material3-android:1.2.1
androidx.compose.material3:material3:1.2.1
androidx.compose.material:material-android:1.6.8
androidx.compose.material:material-icons-core-android:1.6.8
androidx.compose.material:material-icons-core:1.6.8
androidx.compose.material:material-icons-extended-android:1.6.8
androidx.compose.material:material-icons-extended:1.6.8
androidx.compose.material:material-ripple-android:1.6.8
androidx.compose.material:material-ripple:1.6.8
androidx.compose.material:material:1.6.8
androidx.compose.runtime:runtime-android:1.7.0-rc01
androidx.compose.runtime:runtime-saveable-android:1.7.0-rc01
androidx.compose.runtime:runtime-saveable:1.7.0-rc01
androidx.compose.runtime:runtime:1.7.0-rc01
androidx.compose.ui:ui-android:1.7.0-rc01
androidx.compose.ui:ui-geometry-android:1.7.0-rc01
androidx.compose.ui:ui-geometry:1.7.0-rc01
androidx.compose.ui:ui-graphics-android:1.7.0-rc01
androidx.compose.ui:ui-graphics:1.7.0-rc01
androidx.compose.ui:ui-text-android:1.7.0-rc01
androidx.compose.ui:ui-text:1.7.0-rc01
androidx.compose.ui:ui-tooling-preview-android:1.7.0-rc01
androidx.compose.ui:ui-tooling-preview:1.7.0-rc01
androidx.compose.ui:ui-unit-android:1.7.0-rc01
androidx.compose.ui:ui-unit:1.7.0-rc01
androidx.compose.ui:ui-util-android:1.7.0-rc01
androidx.compose.ui:ui-util:1.7.0-rc01
androidx.compose.ui:ui:1.7.0-rc01
androidx.compose:compose-bom:2024.08.00
androidx.camera:camera-camera2:1.4.1
androidx.camera:camera-core:1.4.1
androidx.camera:camera-lifecycle:1.4.1
androidx.camera:camera-video:1.4.1
androidx.camera:camera-view:1.4.1
androidx.collection:collection-jvm:1.4.4
androidx.collection:collection-ktx:1.4.4
androidx.collection:collection:1.4.4
androidx.compose.animation:animation-android:1.7.6
androidx.compose.animation:animation-core-android:1.7.6
androidx.compose.animation:animation-core:1.7.6
androidx.compose.animation:animation:1.7.6
androidx.compose.foundation:foundation-android:1.7.6
androidx.compose.foundation:foundation-layout-android:1.7.6
androidx.compose.foundation:foundation-layout:1.7.6
androidx.compose.foundation:foundation:1.7.6
androidx.compose.material3:material3-android:1.3.1
androidx.compose.material3:material3:1.3.1
androidx.compose.material:material-android:1.7.6
androidx.compose.material:material-icons-core-android:1.7.6
androidx.compose.material:material-icons-core:1.7.6
androidx.compose.material:material-icons-extended-android:1.7.6
androidx.compose.material:material-icons-extended:1.7.6
androidx.compose.material:material-ripple-android:1.7.6
androidx.compose.material:material-ripple:1.7.6
androidx.compose.material:material:1.7.6
androidx.compose.runtime:runtime-android:1.7.6
androidx.compose.runtime:runtime-saveable-android:1.7.6
androidx.compose.runtime:runtime-saveable:1.7.6
androidx.compose.runtime:runtime:1.7.6
androidx.compose.ui:ui-android:1.7.6
androidx.compose.ui:ui-geometry-android:1.7.6
androidx.compose.ui:ui-geometry:1.7.6
androidx.compose.ui:ui-graphics-android:1.7.6
androidx.compose.ui:ui-graphics:1.7.6
androidx.compose.ui:ui-text-android:1.7.6
androidx.compose.ui:ui-text:1.7.6
androidx.compose.ui:ui-tooling-preview-android:1.7.6
androidx.compose.ui:ui-tooling-preview:1.7.6
androidx.compose.ui:ui-unit-android:1.7.6
androidx.compose.ui:ui-unit:1.7.6
androidx.compose.ui:ui-util-android:1.7.6
androidx.compose.ui:ui-util:1.7.6
androidx.compose.ui:ui:1.7.6
androidx.compose:compose-bom:2024.12.01
androidx.concurrent:concurrent-futures-ktx:1.1.0
androidx.concurrent:concurrent-futures:1.1.0
androidx.core:core-ktx:1.13.1
androidx.core:core-ktx:1.15.0
androidx.core:core-splashscreen:1.0.1
androidx.core:core:1.13.1
androidx.core:core:1.15.0
androidx.cursoradapter:cursoradapter:1.0.0
androidx.customview:customview-poolingcontainer:1.0.0
androidx.customview:customview:1.0.0
@ -103,48 +105,53 @@ androidx.drawerlayout:drawerlayout:1.0.0
androidx.emoji2:emoji2-views-helper:1.3.0
androidx.emoji2:emoji2:1.3.0
androidx.exifinterface:exifinterface:1.3.2
androidx.fragment:fragment-ktx:1.7.1
androidx.fragment:fragment:1.7.1
androidx.fragment:fragment-ktx:1.8.3
androidx.fragment:fragment:1.8.3
androidx.graphics:graphics-path:1.0.1
androidx.hilt:hilt-navigation-compose:1.2.0
androidx.hilt:hilt-navigation:1.2.0
androidx.interpolator:interpolator:1.0.0
androidx.legacy:legacy-support-core-utils:1.0.0
androidx.lifecycle:lifecycle-common-java8:2.8.4
androidx.lifecycle:lifecycle-common-jvm:2.8.4
androidx.lifecycle:lifecycle-common:2.8.4
androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.4
androidx.lifecycle:lifecycle-livedata-core:2.8.4
androidx.lifecycle:lifecycle-livedata:2.8.4
androidx.lifecycle:lifecycle-process:2.8.4
androidx.lifecycle:lifecycle-runtime-android:2.8.4
androidx.lifecycle:lifecycle-runtime-compose-android:2.8.4
androidx.lifecycle:lifecycle-runtime-compose:2.8.4
androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.4
androidx.lifecycle:lifecycle-runtime-ktx:2.8.4
androidx.lifecycle:lifecycle-runtime:2.8.4
androidx.lifecycle:lifecycle-viewmodel-android:2.8.4
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.8.4
androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4
androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.4
androidx.lifecycle:lifecycle-viewmodel:2.8.4
androidx.lifecycle:lifecycle-common-java8:2.8.7
androidx.lifecycle:lifecycle-common-jvm:2.8.7
androidx.lifecycle:lifecycle-common:2.8.7
androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.7
androidx.lifecycle:lifecycle-livedata-core:2.8.7
androidx.lifecycle:lifecycle-livedata:2.8.7
androidx.lifecycle:lifecycle-process:2.8.7
androidx.lifecycle:lifecycle-runtime-android:2.8.7
androidx.lifecycle:lifecycle-runtime-compose-android:2.8.7
androidx.lifecycle:lifecycle-runtime-compose:2.8.7
androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.7
androidx.lifecycle:lifecycle-runtime-ktx:2.8.7
androidx.lifecycle:lifecycle-runtime:2.8.7
androidx.lifecycle:lifecycle-viewmodel-android:2.8.7
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.8.7
androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7
androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.7
androidx.lifecycle:lifecycle-viewmodel:2.8.7
androidx.loader:loader:1.0.0
androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
androidx.metrics:metrics-performance:1.0.0-beta01
androidx.multidex:multidex:2.0.1
androidx.navigation:navigation-common-ktx:2.8.0-rc01
androidx.navigation:navigation-common:2.8.0-rc01
androidx.navigation:navigation-compose:2.8.0-rc01
androidx.navigation:navigation-runtime-ktx:2.8.0-rc01
androidx.navigation:navigation-runtime:2.8.0-rc01
androidx.navigation:navigation-common-ktx:2.8.5
androidx.navigation:navigation-common:2.8.5
androidx.navigation:navigation-compose:2.8.5
androidx.navigation:navigation-runtime-ktx:2.8.5
androidx.navigation:navigation-runtime:2.8.5
androidx.print:print:1.0.0
androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05
androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05
androidx.profileinstaller:profileinstaller:1.3.1
androidx.profileinstaller:profileinstaller:1.4.1
androidx.resourceinspection:resourceinspection-annotation:1.0.1
androidx.room:room-common:2.6.1
androidx.room:room-ktx:2.6.1
androidx.room:room-runtime:2.6.1
androidx.savedstate:savedstate-ktx:1.2.1
androidx.savedstate:savedstate:1.2.1
androidx.sqlite:sqlite-framework:2.4.0
androidx.sqlite:sqlite:2.4.0
androidx.startup:startup-runtime:1.1.1
androidx.tracing:tracing-ktx:1.2.0
androidx.tracing:tracing:1.2.0
@ -154,14 +161,12 @@ androidx.versionedparcelable:versionedparcelable:1.1.1
androidx.viewpager:viewpager:1.0.0
app.cash.turbine:turbine-jvm:1.1.0
app.cash.turbine:turbine:1.1.0
co.touchlab:stately-concurrency-jvm:2.0.6
co.touchlab:stately-concurrency:2.0.6
co.touchlab:stately-concurrent-collections-jvm:2.0.6
co.touchlab:stately-concurrent-collections:2.0.6
co.touchlab:stately-strict-jvm:2.0.6
co.touchlab:stately-strict:2.0.6
com.github.Raizlabs.DBFlow:dbflow-core:4.2.4
com.github.Raizlabs.DBFlow:dbflow:4.2.4
co.touchlab:stately-concurrency-jvm:2.1.0
co.touchlab:stately-concurrency:2.1.0
co.touchlab:stately-concurrent-collections-jvm:2.1.0
co.touchlab:stately-concurrent-collections:2.1.0
co.touchlab:stately-strict-jvm:2.1.0
co.touchlab:stately-strict:2.1.0
com.google.accompanist:accompanist-pager:0.34.0
com.google.accompanist:accompanist-permissions:0.34.0
com.google.android.datatransport:transport-api:3.2.0
@ -172,35 +177,35 @@ com.google.android.gms:play-services-base:18.5.0
com.google.android.gms:play-services-basement:18.4.0
com.google.android.gms:play-services-cloud-messaging:17.2.0
com.google.android.gms:play-services-maps:18.2.0
com.google.android.gms:play-services-measurement-api:22.1.0
com.google.android.gms:play-services-measurement-base:22.1.0
com.google.android.gms:play-services-measurement-impl:22.1.0
com.google.android.gms:play-services-measurement-sdk-api:22.1.0
com.google.android.gms:play-services-measurement-sdk:22.1.0
com.google.android.gms:play-services-measurement:22.1.0
com.google.android.gms:play-services-measurement-api:22.1.2
com.google.android.gms:play-services-measurement-base:22.1.2
com.google.android.gms:play-services-measurement-impl:22.1.2
com.google.android.gms:play-services-measurement-sdk-api:22.1.2
com.google.android.gms:play-services-measurement-sdk:22.1.2
com.google.android.gms:play-services-measurement:22.1.2
com.google.android.gms:play-services-oss-licenses:17.1.0
com.google.android.gms:play-services-stats:17.0.2
com.google.android.gms:play-services-tasks:18.2.0
com.google.auto.value:auto-value-annotations:1.6.3
com.google.code.findbugs:jsr305:3.0.2
com.google.code.gson:gson:2.10.1
com.google.dagger:dagger-lint-aar:2.52
com.google.dagger:dagger:2.52
com.google.dagger:hilt-android:2.52
com.google.dagger:hilt-core:2.52
com.google.dagger:dagger-lint-aar:2.54
com.google.dagger:dagger:2.54
com.google.dagger:hilt-android:2.54
com.google.dagger:hilt-core:2.54
com.google.errorprone:error_prone_annotations:2.26.0
com.google.firebase:firebase-abt:21.1.1
com.google.firebase:firebase-analytics-ktx:22.1.0
com.google.firebase:firebase-analytics:22.1.0
com.google.firebase:firebase-analytics-ktx:22.1.2
com.google.firebase:firebase-analytics:22.1.2
com.google.firebase:firebase-annotations:16.2.0
com.google.firebase:firebase-bom:33.2.0
com.google.firebase:firebase-bom:33.7.0
com.google.firebase:firebase-common-ktx:21.0.0
com.google.firebase:firebase-common:21.0.0
com.google.firebase:firebase-components:18.0.0
com.google.firebase:firebase-config-interop:16.0.1
com.google.firebase:firebase-config:22.0.0
com.google.firebase:firebase-crashlytics-ktx:19.0.3
com.google.firebase:firebase-crashlytics:19.0.3
com.google.firebase:firebase-config:22.0.1
com.google.firebase:firebase-crashlytics-ktx:19.3.0
com.google.firebase:firebase-crashlytics:19.3.0
com.google.firebase:firebase-datatransport:19.0.0
com.google.firebase:firebase-encoders-json:18.0.1
com.google.firebase:firebase-encoders-proto:16.0.0
@ -209,11 +214,11 @@ com.google.firebase:firebase-iid-interop:17.1.0
com.google.firebase:firebase-installations-interop:17.2.0
com.google.firebase:firebase-installations:18.0.0
com.google.firebase:firebase-measurement-connector:20.0.1
com.google.firebase:firebase-messaging-ktx:24.0.1
com.google.firebase:firebase-messaging:24.0.1
com.google.firebase:firebase-perf-ktx:21.0.1
com.google.firebase:firebase-perf:21.0.1
com.google.firebase:firebase-sessions:2.0.3
com.google.firebase:firebase-messaging-ktx:24.1.0
com.google.firebase:firebase-messaging:24.1.0
com.google.firebase:firebase-perf-ktx:21.0.3
com.google.firebase:firebase-perf:21.0.3
com.google.firebase:firebase-sessions:2.0.7
com.google.firebase:protolite-well-known-types:18.0.0
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-android
@ -221,7 +226,7 @@ com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.j2objc:j2objc-annotations:1.3
com.google.maps.android:maps-compose:4.4.1
com.google.maps.android:maps-ktx:5.0.0
com.google.protobuf:protobuf-javalite:3.21.11
com.google.protobuf:protobuf-javalite:3.25.5
com.google.zxing:core:3.5.3
com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0
@ -232,47 +237,55 @@ com.squareup.retrofit2:converter-gson:2.11.0
com.squareup.retrofit2:retrofit:2.11.0
dev.chrisbanes.snapper:snapper:0.2.2
io.github.mr0xf00:easycrop:0.1.1
io.insert-koin:koin-android:3.6.0-Beta4
io.insert-koin:koin-androidx-compose:3.6.0-Beta4
io.insert-koin:koin-compose-jvm:1.2.0-Beta4
io.insert-koin:koin-compose-viewmodel-jvm:1.2.0-Beta4
io.insert-koin:koin-compose-viewmodel:1.2.0-Beta4
io.insert-koin:koin-compose:1.2.0-Beta4
io.insert-koin:koin-core-jvm:3.6.0-Beta4
io.insert-koin:koin-core:3.6.0-Beta4
io.insert-koin:koin-android:4.0.0
io.insert-koin:koin-androidx-compose:4.0.0
io.insert-koin:koin-compose-jvm:4.0.0
io.insert-koin:koin-compose-viewmodel-jvm:4.0.0
io.insert-koin:koin-compose-viewmodel:4.0.0
io.insert-koin:koin-compose:4.0.0
io.insert-koin:koin-core-jvm:4.0.0
io.insert-koin:koin-core-viewmodel-jvm:4.0.0
io.insert-koin:koin-core-viewmodel:4.0.0
io.insert-koin:koin-core:4.0.0
io.michaelrocks:libphonenumber-android:8.13.35
io.reactivex.rxjava2:rxandroid:2.1.1
io.reactivex.rxjava2:rxjava:2.2.21
jakarta.inject:jakarta.inject-api:2.0.1
javax.inject:javax.inject:1
net.bytebuddy:byte-buddy-agent:1.14.5
net.bytebuddy:byte-buddy:1.14.5
net.bytebuddy:byte-buddy-agent:1.14.8
net.bytebuddy:byte-buddy:1.14.8
org.checkerframework:checker-qual:3.12.0
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0-rc03
org.jetbrains.androidx.navigation:navigation-compose:2.7.0-alpha06
org.jetbrains.androidx.core:core-bundle-android:1.0.0
org.jetbrains.androidx.core:core-bundle:1.0.0
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.0
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.0
org.jetbrains.androidx.savedstate:savedstate:1.2.0
org.jetbrains.compose.material3:material3:1.6.11
org.jetbrains.compose.material:material:1.6.11
org.jetbrains.compose.runtime:runtime:1.6.10-rc03
org.jetbrains.compose.runtime:runtime:1.6.11
org.jetbrains.compose.ui:ui-tooling-preview:1.6.11
org.jetbrains.kotlin:kotlin-android-extensions-runtime:2.0.20
org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.20
org.jetbrains.kotlin:kotlin-stdlib-common:2.0.20
org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.20
org.jetbrains.kotlin:kotlin-android-extensions-runtime:2.1.0
org.jetbrains.kotlin:kotlin-parcelize-runtime:2.1.0
org.jetbrains.kotlin:kotlin-stdlib-common:2.1.0
org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.1.0
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10
org.jetbrains.kotlin:kotlin-stdlib:2.0.20
org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.7
org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.3
org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3
org.jetbrains.kotlin:kotlin-stdlib:2.1.0
org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.8
org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.8
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.1
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.8.0-RC
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.8.0-RC
org.jetbrains.kotlinx:kotlinx-serialization-core:1.8.0-RC
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.8.0-RC
org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0-RC
org.jetbrains:annotations:23.0.0
org.mockito:mockito-core:5.4.0
org.jspecify:jspecify:1.0.0
org.mockito:mockito-core:5.6.0
org.objenesis:objenesis:3.3
org.reactivestreams:reactive-streams:1.0.4

View File

@ -12,7 +12,6 @@ package org.mifos.mobile
import androidx.multidex.MultiDex
import androidx.multidex.MultiDexApplication
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.raizlabs.android.dbflow.config.FlowManager
import dagger.hilt.android.HiltAndroidApp
import org.mifos.mobile.core.datastore.PreferencesHelper
import org.mifos.mobile.feature.settings.applySavedTheme
@ -22,13 +21,7 @@ class MifosSelfServiceApp : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
MultiDex.install(this)
FlowManager.init(this)
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true)
FirebaseCrashlytics.getInstance().isCrashlyticsCollectionEnabled = true
PreferencesHelper(this).applySavedTheme()
}
override fun onTerminate() {
super.onTerminate()
FlowManager.destroy()
}
}

View File

@ -654,4 +654,5 @@
<string name="s_no">S.No</string>
<string name="no_transaction_found">No Transaction Found</string>
<string name="not_connected">⚠️ You arent connected to the internet</string>
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">AIzaSyBbeT2BaMWLj-lReCgYoNmXs_TIyRLr9qQ</string>
</resources>

View File

@ -20,7 +20,6 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
apply("com.dropbox.dependency-guard")
apply("mifos.detekt.plugin")
apply("mifos.spotless.plugin")
apply("mifos.ktlint.plugin")
apply("mifos.git.hooks")
}

View File

@ -1,24 +1,30 @@
import org.mifos.mobile.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
import org.mifos.mobile.libs
class AndroidHiltConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("dagger.hilt.android.plugin")
// KAPT must go last to avoid build warnings.
// See: https://stackoverflow.com/questions/70550883/warning-the-following-options-were-not-recognized-by-any-processor-dagger-f
apply("org.jetbrains.kotlin.kapt")
pluginManager.apply("com.google.devtools.ksp")
dependencies {
"ksp"(libs.findLibrary("hilt.compiler").get())
}
dependencies {
"implementation"(libs.findLibrary("hilt.android").get())
"kapt"(libs.findLibrary("hilt.compiler").get())
"kaptAndroidTest"(libs.findLibrary("hilt.compiler").get())
"kaptTest"(libs.findLibrary("hilt.compiler").get())
// Add support for Jvm Module, base on org.jetbrains.kotlin.jvm
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
dependencies {
"implementation"(libs.findLibrary("hilt.core").get())
}
}
/** Add support for Android modules, based on [AndroidBasePlugin] */
pluginManager.withPlugin("com.android.base") {
pluginManager.apply("dagger.hilt.android.plugin")
dependencies {
"implementation"(libs.findLibrary("hilt.android").get())
}
}
}
}

View File

@ -19,7 +19,6 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
apply("mifos.android.lint")
apply("mifos.detekt.plugin")
apply("mifos.spotless.plugin")
apply("mifos.ktlint.plugin")
}
extensions.configure<LibraryExtension> {

View File

@ -1,4 +1,7 @@
import androidx.room.gradle.RoomExtension
import com.google.devtools.ksp.gradle.KspExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
@ -6,12 +9,15 @@ import org.gradle.kotlin.dsl.dependencies
import org.mifos.mobile.libs
class AndroidRoomConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("androidx.room")
pluginManager.apply("com.google.devtools.ksp")
extensions.configure<KspExtension> {
arg("room.generateKotlin", "true")
}
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.
@ -20,9 +26,9 @@ class AndroidRoomConventionPlugin : Plugin<Project> {
}
dependencies {
add("implementation", libs.findLibrary("room.runtime").get())
add("implementation", libs.findLibrary("room.ktx").get())
add("ksp", libs.findLibrary("room.compiler").get())
"implementation"(libs.findLibrary("androidx.room.runtime").get())
"implementation"(libs.findLibrary("androidx.room.ktx").get())
"ksp"(libs.findLibrary("androidx.room.compiler").get())
}
}
}

View File

@ -3,7 +3,6 @@ package org.mifos.mobile
import com.android.build.api.dsl.CommonExtension
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeCompilerGradlePluginExtension
@ -50,8 +49,7 @@ internal fun Project.configureAndroidCompose(
.relativeToRootProject("compose-reports")
.let(reportsDestination::set)
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("compose_compiler_config.conf")
enableStrongSkippingMode = true
stabilityConfigurationFiles
.add(isolated.rootProject.projectDirectory.file("compose_compiler_config.conf"))
}
}

View File

@ -1,9 +1,9 @@
package org.mifos.mobile
import com.android.SdkConstants
import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.gradle.BaseExtension
import com.android.SdkConstants
import com.google.common.truth.Truth.assertWithMessage
import org.gradle.api.DefaultTask
import org.gradle.api.Project
@ -19,7 +19,6 @@ import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.configurationcache.extensions.capitalized
import org.gradle.kotlin.dsl.register
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.gradle.process.ExecOperations
@ -83,7 +82,7 @@ abstract class CheckBadgingTask : DefaultTask() {
fun taskAction() {
assertWithMessage(
"Generated badging is different from golden badging! " +
"If this change is intended, run ./gradlew ${updateBadgingTaskName.get()}",
"If this change is intended, run ./gradlew ${updateBadgingTaskName.get()}",
)
.that(generatedBadging.get().asFile.readText())
.isEqualTo(goldenBadging.get().asFile.readText())
@ -97,7 +96,8 @@ fun Project.configureBadgingTasks(
// Registers a callback to be called, when a new variant is configured
componentsExtension.onVariants { variant ->
// Registers a new task to verify the app bundle.
val capitalizedVariantName = variant.name.capitalized()
val capitalizedVariantName = variant.name.toString()
.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
val generateBadgingTaskName = "generate${capitalizedVariantName}Badging"
val generateBadging =
tasks.register<GenerateBadgingTask>(generateBadgingTaskName) {
@ -108,8 +108,8 @@ fun Project.configureBadgingTasks(
File(
baseExtension.sdkDirectory,
"${SdkConstants.FD_BUILD_TOOLS}/" +
"${baseExtension.buildToolsVersion}/" +
SdkConstants.FN_AAPT2,
"${baseExtension.buildToolsVersion}/" +
SdkConstants.FN_AAPT2,
),
)

View File

@ -10,8 +10,8 @@ import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.provideDelegate
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
/**
* Configure base Kotlin with Android options
@ -20,7 +20,7 @@ internal fun Project.configureKotlinAndroid(
commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
commonExtension.apply {
compileSdk = 34
compileSdk = 35
defaultConfig {
minSdk = 26
@ -59,7 +59,7 @@ internal fun Project.configureKotlinJvm() {
/**
* Configure base Kotlin options
*/
private inline fun <reified T : KotlinTopLevelExtension> Project.configureKotlin() = configure<T> {
private inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() = configure<T> {
// Treat all Kotlin warnings as errors (disabled by default)
// Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties
val warningsAsErrors: String? by project
@ -75,7 +75,7 @@ private inline fun <reified T : KotlinTopLevelExtension> Project.configureKotlin
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
)
freeCompilerArgs.add(
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api"
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
)
}
}

View File

@ -26,8 +26,6 @@ plugins {
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.detekt) apply false
alias(libs.plugins.spotless) apply false
alias(libs.plugins.ktlint) apply false
alias(libs.plugins.version.catalog.linter) apply true
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.jetbrainsCompose) apply false

View File

@ -28,7 +28,7 @@ tasks=(
"spotlessApply --no-configuration-cache"
"dependencyGuardBaseline"
"detekt"
"testDebug :lint:test :lint:lint :androidApp:lintRelease"
# "testDebug :lint:test :lint:lint :androidApp:lintRelease"
"build"
"updateReleaseBadging"
)

View File

@ -33,6 +33,8 @@ dependencies {
api(projects.core.common)
api(projects.core.model)
api(projects.core.network)
api(projects.core.database)
api(projects.core.datastore)
implementation(libs.squareup.retrofit2)
implementation(libs.squareup.okhttp)

View File

@ -0,0 +1 @@
mock-maker-inline

View File

@ -0,0 +1,95 @@
/*
* Copyright 2025 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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.data.model
import org.mifos.mobile.core.database.entity.ChargeCalculationTypeEntity
import org.mifos.mobile.core.database.entity.ChargeEntity
import org.mifos.mobile.core.database.entity.ChargeTimeTypeEntity
import org.mifos.mobile.core.database.entity.CurrencyEntity
import org.mifos.mobile.core.model.entity.Charge
import org.mifos.mobile.core.model.entity.ChargeCalculationType
import org.mifos.mobile.core.model.entity.ChargeTimeType
import org.mifos.mobile.core.model.entity.Currency
fun ChargeEntity.toCharge(): Charge {
return Charge(
clientId = clientId,
chargeId = chargeId,
name = name,
dueDate = dueDate,
chargeTimeType = ChargeTimeType(
id = chargeTimeType?.id ?: 0,
code = chargeTimeType?.code,
value = chargeTimeType?.value,
),
chargeCalculationType = ChargeCalculationType(
id = chargeCalculationType?.id ?: 0,
code = chargeCalculationType?.code,
value = chargeCalculationType?.value,
),
currency = Currency(
code = currency?.code,
name = currency?.name,
decimalPlaces = currency?.decimalPlaces ?: 0,
displaySymbol = currency?.displaySymbol,
nameCode = currency?.nameCode,
displayLabel = currency?.displayLabel,
),
amount = amount,
amountPaid = amountPaid,
amountWaived = amountWaived,
amountWrittenOff = amountWrittenOff,
amountOutstanding = amountOutstanding,
penalty = penalty,
isActive = isActive,
isChargePaid = isChargePaid,
isChargeWaived = isChargeWaived,
paid = paid,
waived = waived,
)
}
fun Charge.toChargeEntity(): ChargeEntity {
return ChargeEntity(
clientId = clientId,
chargeId = chargeId,
name = name,
dueDate = dueDate,
chargeTimeType = ChargeTimeTypeEntity(
id = chargeTimeType?.id ?: 0,
code = chargeTimeType?.code,
value = chargeTimeType?.value,
),
chargeCalculationType = ChargeCalculationTypeEntity(
id = chargeCalculationType?.id ?: 0,
code = chargeCalculationType?.code,
value = chargeCalculationType?.value,
),
currency = CurrencyEntity(
code = currency?.code,
name = currency?.name,
decimalPlaces = currency?.decimalPlaces ?: 0,
displaySymbol = currency?.displaySymbol,
nameCode = currency?.nameCode,
displayLabel = currency?.displayLabel,
),
amount = amount,
amountPaid = amountPaid,
amountWaived = amountWaived,
amountWrittenOff = amountWrittenOff,
amountOutstanding = amountOutstanding,
penalty = penalty,
isActive = isActive,
isChargePaid = isChargePaid,
isChargeWaived = isChargeWaived,
paid = paid,
waived = waived,
)
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2025 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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.data.model
import org.mifos.mobile.core.database.entity.MifosNotificationEntity
import org.mifos.mobile.core.model.entity.MifosNotification
fun MifosNotification.toEntity(): MifosNotificationEntity {
return MifosNotificationEntity(
timeStamp = timeStamp,
msg = msg,
read = read,
)
}
fun MifosNotificationEntity.toModel(): MifosNotification {
return MifosNotification(
timeStamp = timeStamp,
msg = msg,
read = read,
)
}

View File

@ -10,13 +10,17 @@
package org.mifos.mobile.core.data.repository
import kotlinx.coroutines.flow.Flow
import org.mifos.mobile.core.datastore.model.Charge
import org.mifos.mobile.core.model.entity.Charge
import org.mifos.mobile.core.model.entity.Page
interface ClientChargeRepository {
fun getClientCharges(clientId: Long): Flow<Page<Charge>>
suspend fun getClientCharges(clientId: Long): Flow<Page<Charge>>
suspend fun getLoanCharges(loanId: Long): Flow<List<Charge>>
suspend fun getSavingsCharges(savingsId: Long): Flow<List<Charge>>
suspend fun clientLocalCharges(): Flow<Page<Charge?>>
fun getLoanCharges(loanId: Long): Flow<List<Charge>>
fun getSavingsCharges(savingsId: Long): Flow<List<Charge>>
fun clientLocalCharges(): Flow<Page<Charge>>
suspend fun syncCharges(charges: Page<Charge>?): Page<Charge>?
}

View File

@ -22,5 +22,5 @@ interface HomeRepository {
fun clientImage(): Flow<ResponseBody>
fun unreadNotificationsCount(): Flow<Int>
suspend fun unreadNotificationsCount(): Flow<Int>
}

View File

@ -10,9 +10,17 @@
package org.mifos.mobile.core.data.repository
import kotlinx.coroutines.flow.Flow
import org.mifos.mobile.core.datastore.model.MifosNotification
import org.mifos.mobile.core.model.entity.MifosNotification
interface NotificationRepository {
suspend fun loadNotifications(): Flow<List<MifosNotification>>
fun loadNotifications(): Flow<List<MifosNotification>>
fun getUnReadNotificationCount(): Flow<Int>
suspend fun saveNotification(notification: MifosNotification)
suspend fun deleteOldNotifications()
suspend fun updateReadStatus(notification: MifosNotification, isRead: Boolean)
}

View File

@ -9,39 +9,61 @@
*/
package org.mifos.mobile.core.data.repositoryImpl
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.mifos.mobile.core.common.network.Dispatcher
import org.mifos.mobile.core.common.network.MifosDispatchers
import org.mifos.mobile.core.data.model.toCharge
import org.mifos.mobile.core.data.model.toChargeEntity
import org.mifos.mobile.core.data.repository.ClientChargeRepository
import org.mifos.mobile.core.datastore.model.Charge
import org.mifos.mobile.core.database.dao.ChargeDao
import org.mifos.mobile.core.model.entity.Charge
import org.mifos.mobile.core.model.entity.Page
import org.mifos.mobile.core.network.DataManager
import javax.inject.Inject
class ClientChargeRepositoryImp @Inject constructor(
private val dataManager: DataManager,
private val chargeDao: ChargeDao,
@Dispatcher(MifosDispatchers.IO)
private val ioDispatcher: CoroutineDispatcher,
) : ClientChargeRepository {
override suspend fun getClientCharges(clientId: Long): Flow<Page<Charge>> {
override fun getClientCharges(clientId: Long): Flow<Page<Charge>> {
return flow {
emit(dataManager.getClientCharges(clientId))
}
}
override suspend fun getLoanCharges(loanId: Long): Flow<List<Charge>> {
override fun getLoanCharges(loanId: Long): Flow<List<Charge>> {
return flow {
emit(dataManager.getLoanCharges(loanId))
}
}
override suspend fun getSavingsCharges(savingsId: Long): Flow<List<Charge>> {
override fun getSavingsCharges(savingsId: Long): Flow<List<Charge>> {
return flow {
emit(dataManager.getSavingsCharges(savingsId))
}
}
override suspend fun clientLocalCharges(): Flow<Page<Charge?>> {
return flow {
emit(dataManager.clientLocalCharges())
override fun clientLocalCharges(): Flow<Page<Charge>> {
return chargeDao.getAllLocalCharges().map { chargeList ->
Page(chargeList.size, chargeList.map { it.toCharge() })
}.flowOn(ioDispatcher)
}
override suspend fun syncCharges(charges: Page<Charge>?): Page<Charge>? {
return withContext(ioDispatcher) {
charges?.pageItems?.let {
chargeDao.syncCharges(it.map { it.toChargeEntity() })
}
charges?.copy(pageItems = charges.pageItems)
}
}
}

View File

@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import okhttp3.ResponseBody
import org.mifos.mobile.core.data.repository.HomeRepository
import org.mifos.mobile.core.data.repository.NotificationRepository
import org.mifos.mobile.core.model.entity.client.Client
import org.mifos.mobile.core.model.entity.client.ClientAccounts
import org.mifos.mobile.core.network.DataManager
@ -20,6 +21,7 @@ import javax.inject.Inject
class HomeRepositoryImp @Inject constructor(
private val dataManager: DataManager,
private val notificationRepository: NotificationRepository,
) : HomeRepository {
override fun clientAccounts(): Flow<ClientAccounts> {
@ -40,9 +42,7 @@ class HomeRepositoryImp @Inject constructor(
}
}
override fun unreadNotificationsCount(): Flow<Int> {
return flow {
emit(dataManager.unreadNotificationsCount())
}
override suspend fun unreadNotificationsCount(): Flow<Int> {
return notificationRepository.getUnReadNotificationCount()
}
}

View File

@ -9,20 +9,53 @@
*/
package org.mifos.mobile.core.data.repositoryImpl
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.mifos.mobile.core.common.network.Dispatcher
import org.mifos.mobile.core.common.network.MifosDispatchers
import org.mifos.mobile.core.data.model.toEntity
import org.mifos.mobile.core.data.model.toModel
import org.mifos.mobile.core.data.repository.NotificationRepository
import org.mifos.mobile.core.datastore.model.MifosNotification
import org.mifos.mobile.core.network.DataManager
import org.mifos.mobile.core.database.dao.MifosNotificationDao
import org.mifos.mobile.core.model.entity.MifosNotification
import javax.inject.Inject
class NotificationRepositoryImp @Inject constructor(
private val dataManager: DataManager,
private val notificationDao: MifosNotificationDao,
@Dispatcher(MifosDispatchers.IO)
private val ioDispatcher: CoroutineDispatcher,
) : NotificationRepository {
override suspend fun loadNotifications(): Flow<List<MifosNotification>> {
return flow {
emit(dataManager.notifications())
override fun loadNotifications(): Flow<List<MifosNotification>> {
return notificationDao.getNotifications()
.map { it.map { it.toModel() } }
.flowOn(ioDispatcher)
}
override fun getUnReadNotificationCount(): Flow<Int> {
return notificationDao.getUnreadNotificationsCount().flowOn(ioDispatcher)
}
override suspend fun saveNotification(notification: MifosNotification) {
withContext(ioDispatcher) {
notificationDao.saveNotification(notification.toEntity())
}
}
override suspend fun deleteOldNotifications() {
return withContext(ioDispatcher) {
val thirtyDaysInMillis = 2592000000L
val cutoffTime = System.currentTimeMillis() - thirtyDaysInMillis
notificationDao.deleteOldNotifications(cutoffTime)
}
}
override suspend fun updateReadStatus(notification: MifosNotification, isRead: Boolean) {
withContext(ioDispatcher) {
notificationDao.updateReadStatus(notification.timeStamp, isRead)
}
}
}

View File

@ -12,13 +12,18 @@ package org.mifos.mobile.core.data.repositories
import app.cash.turbine.test
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mifos.mobile.core.data.model.toCharge
import org.mifos.mobile.core.data.repositoryImpl.ClientChargeRepositoryImp
import org.mifos.mobile.core.datastore.model.Charge
import org.mifos.mobile.core.database.dao.ChargeDao
import org.mifos.mobile.core.database.entity.ChargeEntity
import org.mifos.mobile.core.model.entity.Charge
import org.mifos.mobile.core.model.entity.Page
import org.mifos.mobile.core.network.DataManager
import org.mifos.mobile.core.testing.util.MainDispatcherRule
@ -38,12 +43,19 @@ class ClientChargeRepositoryImpTest {
@Mock
lateinit var dataManager: DataManager
@Mock
lateinit var chargeDao: ChargeDao
private lateinit var clientChargeRepositoryImp: ClientChargeRepositoryImp
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
clientChargeRepositoryImp = ClientChargeRepositoryImp(dataManager)
clientChargeRepositoryImp = ClientChargeRepositoryImp(
dataManager = dataManager,
chargeDao = chargeDao,
ioDispatcher = UnconfinedTestDispatcher(),
)
}
@Test
@ -116,10 +128,12 @@ class ClientChargeRepositoryImpTest {
@Test
fun testClientLocalCharges_Successful() = runTest {
val clientLocalChargeMock = List(5) { mock(Charge::class.java) }
val chargeList = clientLocalChargeMock.toList()
val success = Page<Charge?>(5, chargeList)
`when`(dataManager.clientLocalCharges()).thenReturn(success)
val clientLocalChargeMock = List(5) { mock(ChargeEntity::class.java) }
val success = Page<Charge?>(
clientLocalChargeMock.size,
clientLocalChargeMock.map { it.toCharge() },
)
`when`(chargeDao.getAllLocalCharges()).thenReturn(flowOf(clientLocalChargeMock))
val resultFlow = clientChargeRepositoryImp.clientLocalCharges()
resultFlow.test {
assertEquals(success, awaitItem())
@ -129,7 +143,7 @@ class ClientChargeRepositoryImpTest {
@Test(expected = Exception::class)
fun testClientLocalCharges_Unsuccessful() = runTest {
`when`(dataManager.clientLocalCharges())
`when`(clientChargeRepositoryImp.clientLocalCharges())
.thenThrow(Exception("Error occurred"))
val result = clientChargeRepositoryImp.clientLocalCharges()
result.test {

View File

@ -12,12 +12,14 @@ package org.mifos.mobile.core.data.repositories
import app.cash.turbine.test
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import okhttp3.ResponseBody
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mifos.mobile.core.data.repository.NotificationRepository
import org.mifos.mobile.core.data.repositoryImpl.HomeRepositoryImp
import org.mifos.mobile.core.model.entity.client.Client
import org.mifos.mobile.core.model.entity.client.ClientAccounts
@ -39,12 +41,15 @@ class HomeRepositoryImpTest {
@Mock
lateinit var dataManager: DataManager
@Mock
lateinit var notificationRepository: NotificationRepository
private lateinit var homeRepositoryImp: HomeRepositoryImp
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
homeRepositoryImp = HomeRepositoryImp(dataManager)
homeRepositoryImp = HomeRepositoryImp(dataManager, notificationRepository)
}
@Test
@ -93,7 +98,7 @@ class HomeRepositoryImpTest {
fun testUnreadNotificationsCount_Successful() = runTest {
val mockUnreadCount = 5
`when`(dataManager.unreadNotificationsCount()).thenReturn(mockUnreadCount)
`when`(notificationRepository.getUnReadNotificationCount()).thenReturn(flowOf(mockUnreadCount))
val flow = homeRepositoryImp.unreadNotificationsCount()
@ -147,7 +152,7 @@ class HomeRepositoryImpTest {
fun testUnreadNotificationsCount_Error() = runTest {
val errorMessage = "Failed to fetch unread notifications count"
`when`(dataManager.unreadNotificationsCount())
`when`(notificationRepository.getUnReadNotificationCount())
.thenThrow(Exception(errorMessage))
val flow = homeRepositoryImp.unreadNotificationsCount()

View File

@ -12,14 +12,18 @@ package org.mifos.mobile.core.data.repositories
import app.cash.turbine.test
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mifos.mobile.core.data.model.toModel
import org.mifos.mobile.core.data.repository.NotificationRepository
import org.mifos.mobile.core.data.repositoryImpl.NotificationRepositoryImp
import org.mifos.mobile.core.datastore.model.MifosNotification
import org.mifos.mobile.core.database.dao.MifosNotificationDao
import org.mifos.mobile.core.database.entity.MifosNotificationEntity
import org.mifos.mobile.core.network.DataManager
import org.mifos.mobile.core.testing.util.MainDispatcherRule
import org.mockito.Mock
@ -38,26 +42,32 @@ class NotificationRepositoryImpTest {
@Mock
lateinit var dataManager: DataManager
@Mock
lateinit var mifosNotificationDao: MifosNotificationDao
private lateinit var notificationRepositoryImp: NotificationRepository
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
notificationRepositoryImp = NotificationRepositoryImp(dataManager)
notificationRepositoryImp = NotificationRepositoryImp(
notificationDao = mifosNotificationDao,
ioDispatcher = UnconfinedTestDispatcher(),
)
}
@Test
fun testLoadNotifications_SuccessResponseReceivedFromDataManager_ReturnsSuccess() = runTest {
val notification = mock(MifosNotification::class.java)
val notification = mock(MifosNotificationEntity::class.java)
val notificationList = List(5) { notification }
`when`(
dataManager.notifications(),
).thenReturn(notificationList)
mifosNotificationDao.getNotifications(),
).thenReturn(flowOf(notificationList))
val notifications = notificationRepositoryImp.loadNotifications()
notifications.test {
assertEquals(notificationList, awaitItem())
assertEquals(notificationList.map { it.toModel() }, awaitItem())
cancelAndIgnoreRemainingEvents()
}
}
@ -65,7 +75,7 @@ class NotificationRepositoryImpTest {
@Test(expected = Exception::class)
fun testLoadNotifications_ErrorResponseReceivedFromDataManager_ReturnsError() = runTest {
val dummyError = Exception("Dummy error")
`when`(dataManager.notifications()).thenThrow(dummyError)
`when`(mifosNotificationDao.getNotifications()).thenThrow(dummyError)
val notifications = notificationRepositoryImp.loadNotifications()

View File

@ -0,0 +1 @@
mock-maker-inline

1
core/database/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

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-mobile/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifos.android.library)
alias(libs.plugins.mifos.android.room)
alias(libs.plugins.mifos.android.hilt)
alias(libs.plugins.kotlin.serialization)
}
android {
namespace = "org.mifos.mobile.core.database"
defaultConfig {
consumerProguardFiles("consumer-rules.pro")
}
}
dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
}

View File

View File

@ -0,0 +1,174 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "0ef6ac9f8492c3e3e0026cafe51bd414",
"entities": [
{
"tableName": "charges",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `clientId` INTEGER, `chargeId` INTEGER, `name` TEXT, `dueDate` TEXT NOT NULL, `chargeTimeType` TEXT, `chargeCalculationType` TEXT, `currency` TEXT, `amount` REAL NOT NULL, `amountPaid` REAL NOT NULL, `amountWaived` REAL NOT NULL, `amountWrittenOff` REAL NOT NULL, `amountOutstanding` REAL NOT NULL, `penalty` INTEGER NOT NULL, `isActive` INTEGER NOT NULL, `isChargePaid` INTEGER NOT NULL, `isChargeWaived` INTEGER NOT NULL, `paid` INTEGER NOT NULL, `waived` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "clientId",
"columnName": "clientId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "chargeId",
"columnName": "chargeId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "dueDate",
"columnName": "dueDate",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "chargeTimeType",
"columnName": "chargeTimeType",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "chargeCalculationType",
"columnName": "chargeCalculationType",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "currency",
"columnName": "currency",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "amount",
"columnName": "amount",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "amountPaid",
"columnName": "amountPaid",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "amountWaived",
"columnName": "amountWaived",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "amountWrittenOff",
"columnName": "amountWrittenOff",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "amountOutstanding",
"columnName": "amountOutstanding",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "penalty",
"columnName": "penalty",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isActive",
"columnName": "isActive",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isChargePaid",
"columnName": "isChargePaid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isChargeWaived",
"columnName": "isChargeWaived",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "paid",
"columnName": "paid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "waived",
"columnName": "waived",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "mifos_notifications",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timeStamp` INTEGER NOT NULL, `msg` TEXT, `read` INTEGER, PRIMARY KEY(`timeStamp`))",
"fields": [
{
"fieldPath": "timeStamp",
"columnName": "timeStamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "msg",
"columnName": "msg",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "read",
"columnName": "read",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"timeStamp"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0ef6ac9f8492c3e3e0026cafe51bd414')"
]
}
}

View File

@ -8,8 +8,6 @@
See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
-->
<resources>
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">
YOUR_KEY_HERE
</string>
</resources>
<manifest>
</manifest>

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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.database
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import org.mifos.mobile.core.database.dao.ChargeDao
import org.mifos.mobile.core.database.dao.MifosNotificationDao
import org.mifos.mobile.core.database.entity.ChargeEntity
import org.mifos.mobile.core.database.entity.MifosNotificationEntity
import org.mifos.mobile.core.database.utils.ChargeTypeConverters
@Database(
entities = [
ChargeEntity::class,
MifosNotificationEntity::class,
],
version = SelfServiceDatabase.VERSION,
exportSchema = true,
autoMigrations = [],
)
@TypeConverters(ChargeTypeConverters::class)
abstract class SelfServiceDatabase : RoomDatabase() {
abstract fun mifosNotificationDao(): MifosNotificationDao
abstract fun chargeDao(): ChargeDao
companion object {
// Update the version number if you change the schema
const val VERSION = 1
}
}

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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
import org.mifos.mobile.core.database.entity.ChargeEntity
@Dao
interface ChargeDao {
@Query("SELECT * FROM charges")
fun getAllLocalCharges(): Flow<List<ChargeEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertCharge(charge: List<ChargeEntity>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun syncCharges(charges: List<ChargeEntity>)
}

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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.database.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
import org.mifos.mobile.core.database.entity.MifosNotificationEntity
@Dao
interface MifosNotificationDao {
@Query("SELECT * FROM mifos_notifications ORDER BY timeStamp DESC")
fun getNotifications(): Flow<List<MifosNotificationEntity>>
@Query("SELECT COUNT(*) FROM mifos_notifications WHERE read = 0")
fun getUnreadNotificationsCount(): Flow<Int>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveNotification(notification: MifosNotificationEntity)
@Query("DELETE FROM mifos_notifications WHERE timeStamp < :cutoffTime")
suspend fun deleteOldNotifications(cutoffTime: Long)
@Query("UPDATE mifos_notifications SET read = :isRead WHERE timeStamp = :timeStamp")
suspend fun updateReadStatus(timeStamp: Long, isRead: Boolean)
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2025 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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.database.di
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import org.mifos.mobile.core.database.SelfServiceDatabase
import org.mifos.mobile.core.database.dao.ChargeDao
import org.mifos.mobile.core.database.dao.MifosNotificationDao
@Module
@InstallIn(SingletonComponent::class)
internal object DaoModule {
@Provides
fun provideMifosNotificationDao(database: SelfServiceDatabase): MifosNotificationDao {
return database.mifosNotificationDao()
}
@Provides
fun provideChargeDao(database: SelfServiceDatabase): ChargeDao {
return database.chargeDao()
}
}

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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.database.di
import android.content.Context
import androidx.room.Room
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import org.mifos.mobile.core.database.SelfServiceDatabase
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object DatabaseModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): SelfServiceDatabase {
return Room.databaseBuilder(
context = context,
klass = SelfServiceDatabase::class.java,
name = "self-service-database",
).build()
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright 2025 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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.database.entity
import kotlinx.serialization.Serializable
@Serializable
data class ChargeCalculationTypeEntity(
val id: Int = 0,
val code: String? = null,
val value: String? = null,
)

View File

@ -0,0 +1,37 @@
/*
* 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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.database.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "charges")
data class ChargeEntity(
@PrimaryKey(autoGenerate = true)
val id: Int? = null,
val clientId: Int? = null,
val chargeId: Int? = null,
val name: String? = null,
val dueDate: ArrayList<Int?> = ArrayList(),
val chargeTimeType: ChargeTimeTypeEntity? = null,
val chargeCalculationType: ChargeCalculationTypeEntity? = null,
val currency: CurrencyEntity? = null,
val amount: Double = 0.0,
val amountPaid: Double = 0.0,
val amountWaived: Double = 0.0,
val amountWrittenOff: Double = 0.0,
val amountOutstanding: Double = 0.0,
val penalty: Boolean = false,
val isActive: Boolean = false,
val isChargePaid: Boolean = false,
val isChargeWaived: Boolean = false,
val paid: Boolean = false,
val waived: Boolean = false,
)

View File

@ -7,12 +7,12 @@
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore.model
package org.mifos.mobile.core.database.entity
/**
* @author Vishwajeet
* @since 16/8/16.
*/
data class ChargeListResponse(
var pageItems: List<Charge> = ArrayList(),
var pageItems: List<ChargeEntity> = ArrayList(),
)

View File

@ -0,0 +1,19 @@
/*
* Copyright 2025 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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.database.entity
import kotlinx.serialization.Serializable
@Serializable
data class ChargeTimeTypeEntity(
val id: Int = 0,
val code: String? = null,
val value: String? = null,
)

View File

@ -0,0 +1,22 @@
/*
* Copyright 2025 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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.database.entity
import kotlinx.serialization.Serializable
@Serializable
data class CurrencyEntity(
val code: String? = null,
val name: String? = null,
val decimalPlaces: Int = 0,
val displaySymbol: String? = null,
val nameCode: String? = null,
val displayLabel: String? = null,
)

View File

@ -7,12 +7,14 @@
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore
package org.mifos.mobile.core.database.entity
import com.raizlabs.android.dbflow.annotation.Database
import androidx.room.Entity
import androidx.room.PrimaryKey
@Database(name = SelfServiceDatabase.NAME, version = SelfServiceDatabase.VERSION)
object SelfServiceDatabase {
const val NAME: String = "SelfService" // we will add the .db extension
const val VERSION: Int = 1
}
@Entity(tableName = "mifos_notifications")
data class MifosNotificationEntity(
@PrimaryKey val timeStamp: Long,
val msg: String? = null,
val read: Boolean? = null,
)

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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.database.utils
import androidx.room.TypeConverter
import kotlinx.serialization.json.Json
import org.mifos.mobile.core.database.entity.ChargeCalculationTypeEntity
import org.mifos.mobile.core.database.entity.ChargeTimeTypeEntity
import org.mifos.mobile.core.database.entity.CurrencyEntity
class ChargeTypeConverters {
@TypeConverter
fun fromIntList(value: String): ArrayList<Int?> {
return Json.decodeFromString(value)
}
@TypeConverter
fun toIntList(list: ArrayList<Int?>): String {
return Json.encodeToString(list)
}
@TypeConverter
fun fromChargeTimeType(value: ChargeTimeTypeEntity?): String? {
return value?.let { Json.encodeToString(it) }
}
@TypeConverter
fun toChargeTimeType(value: String?): ChargeTimeTypeEntity? {
return value?.let { Json.decodeFromString(ChargeTimeTypeEntity.serializer(), it) }
}
@TypeConverter
fun fromChargeCalculationType(value: ChargeCalculationTypeEntity?): String? {
return value?.let { Json.encodeToString(it) }
}
@TypeConverter
fun toChargeCalculationType(value: String?): ChargeCalculationTypeEntity? {
return value?.let { Json.decodeFromString(ChargeCalculationTypeEntity.serializer(), it) }
}
@TypeConverter
fun fromCurrency(value: CurrencyEntity?): String? {
return value?.let { Json.encodeToString(it) }
}
@TypeConverter
fun toCurrency(value: String?): CurrencyEntity? {
return value?.let { Json.decodeFromString(CurrencyEntity.serializer(), it) }
}
}

View File

@ -10,6 +10,7 @@
plugins {
alias(libs.plugins.mifos.android.library)
alias(libs.plugins.mifos.android.hilt)
alias(libs.plugins.kotlin.serialization)
}
android {
@ -22,20 +23,8 @@ android {
}
dependencies {
api(projects.core.common)
api(projects.core.model)
implementation(libs.squareup.retrofit.converter.gson)
implementation(projects.core.common)
implementation(projects.core.model)
// DBFlow
implementation(libs.dbflow)
kapt(libs.dbflow.processor)
implementation(libs.dbflow.core)
//rxjava Dependencies
implementation(libs.reactivex.rxjava2.android)
implementation(libs.reactivex.rxjava2)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.espresso.core)
}

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

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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-mobile/blob/master/LICENSE.md
-->
<resources>
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">
AIzaSyBbeT2BaMWLj-lReCgYoNmXs_TIyRLr9qQ
</string>
</resources>

View File

@ -1,76 +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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore
import android.util.Log
import com.google.gson.Gson
import com.raizlabs.android.dbflow.sql.language.SQLite
import io.reactivex.Observable
import org.mifos.mobile.core.datastore.model.Charge
import org.mifos.mobile.core.datastore.model.MifosNotification
import org.mifos.mobile.core.datastore.model.MifosNotification_Table
import org.mifos.mobile.core.datastore.utils.NotificationComparator
import org.mifos.mobile.core.model.entity.Page
import java.util.Collections
import javax.inject.Inject
import javax.inject.Singleton
/**
* Created by Rajan Maurya on 02/03/17.
*/
@Singleton
class DatabaseHelper @Inject constructor() {
fun syncCharges(charges: Page<Charge>?): Page<Charge>? {
if (charges != null) {
for (charge in charges.pageItems)
charge.save()
}
return charges
}
fun clientCharges(): Page<Charge?> {
val charges = SQLite.select()
.from(Charge::class.java)
.queryList()
val chargePage = Page<Charge?>()
chargePage.pageItems = charges
return chargePage
}
fun notifications(): List<MifosNotification> {
deleteOldNotifications()
val notifications = SQLite.select()
.from(MifosNotification::class.java)
.queryList()
Collections.sort(notifications, NotificationComparator())
Log.d("Notifications@@@", Gson().toJson(notifications))
return notifications
}
fun unreadNotificationsCount(): Int {
deleteOldNotifications()
return SQLite.select()
.from(MifosNotification::class.java)
.where(MifosNotification_Table.read.eq(false))
.queryList().size
}
private fun deleteOldNotifications() {
Observable.defer<Void> {
val thirtyDaysInSeconds: Long = 2592000
val thirtyDaysFromCurrentTimeInSeconds = System.currentTimeMillis() / 1000 -
thirtyDaysInSeconds
SQLite.delete(MifosNotification::class.java)
.where(MifosNotification_Table.timeStamp.lessThan(thirtyDaysFromCurrentTimeInSeconds * 1000))
.execute()
null
}
}
}

View File

@ -1,132 +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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore.model
import android.os.Parcel
import android.os.Parcelable
import com.raizlabs.android.dbflow.annotation.Column
import com.raizlabs.android.dbflow.annotation.PrimaryKey
import com.raizlabs.android.dbflow.annotation.Table
import com.raizlabs.android.dbflow.structure.BaseModel
import org.mifos.mobile.core.datastore.SelfServiceDatabase
import org.mifos.mobile.core.model.entity.ChargeCalculationType
import org.mifos.mobile.core.model.entity.ChargeTimeType
import org.mifos.mobile.core.model.entity.Currency
/**
* @author Vishwajeet
* @since 16/8/16.
*/
@Table(database = SelfServiceDatabase::class)
class Charge : BaseModel, Parcelable {
@JvmField
@PrimaryKey
var id: Int? = null
var clientId: Int? = null
private var chargeId: Int? = null
@Column
var name: String? = null
var dueDate: List<Int?> = ArrayList()
private var chargeTimeType: ChargeTimeType? = null
private var chargeCalculationType: ChargeCalculationType? = null
var currency: Currency? = null
@Column
var amount = 0.0
@Column
var amountPaid = 0.0
@Column
var amountWaived = 0.0
@Column
var amountWrittenOff = 0.0
@Column
var amountOutstanding = 0.0
var penalty = false
@Column
var isActive = false
var isChargePaid: Boolean = false
var isChargeWaived: Boolean = false
var paid = false
var waived = false
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeValue(id)
dest.writeValue(clientId)
dest.writeValue(chargeId)
dest.writeString(name)
dest.writeList(dueDate)
dest.writeParcelable(chargeTimeType, flags)
dest.writeParcelable(chargeCalculationType, flags)
dest.writeParcelable(currency, flags)
dest.writeValue(amount)
dest.writeValue(amountPaid)
dest.writeValue(amountWaived)
dest.writeValue(amountWrittenOff)
dest.writeValue(amountOutstanding)
dest.writeValue(penalty)
dest.writeValue(isActive)
dest.writeValue(isChargePaid)
dest.writeValue(isChargeWaived)
dest.writeValue(paid)
dest.writeValue(waived)
}
constructor()
private constructor(`in`: Parcel) {
id = `in`.readValue(Int::class.java.classLoader) as Int
clientId = `in`.readValue(Int::class.java.classLoader) as Int
chargeId = `in`.readValue(Int::class.java.classLoader) as Int
name = `in`.readString()
dueDate = ArrayList()
`in`.readList(dueDate, Int::class.java.classLoader)
chargeTimeType = `in`.readParcelable(ChargeTimeType::class.java.classLoader)
chargeCalculationType = `in`.readParcelable(ChargeCalculationType::class.java.classLoader)
currency = `in`.readParcelable(Currency::class.java.classLoader)
amount = `in`.readValue(Double::class.java.classLoader) as Double
amountPaid = `in`.readValue(Double::class.java.classLoader) as Double
amountWaived = `in`.readValue(Double::class.java.classLoader) as Double
amountWrittenOff = `in`.readValue(Double::class.java.classLoader) as Double
amountOutstanding = `in`.readValue(Double::class.java.classLoader) as Double
penalty = `in`.readValue(Boolean::class.java.classLoader) as Boolean
isActive = `in`.readValue(Boolean::class.java.classLoader) as Boolean
isChargePaid = `in`.readValue(Boolean::class.java.classLoader) as Boolean
isChargeWaived = `in`.readValue(Boolean::class.java.classLoader) as Boolean
isChargePaid = `in`.readValue(Boolean::class.java.classLoader) as Boolean
isChargeWaived = `in`.readValue(Boolean::class.java.classLoader) as Boolean
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<Charge?> = object : Parcelable.Creator<Charge?> {
override fun createFromParcel(source: Parcel): Charge {
return Charge(source)
}
override fun newArray(size: Int): Array<Charge?> {
return arrayOfNulls(size)
}
}
}
}

View File

@ -1,50 +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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore.model
import com.raizlabs.android.dbflow.annotation.Column
import com.raizlabs.android.dbflow.annotation.PrimaryKey
import com.raizlabs.android.dbflow.annotation.Table
import com.raizlabs.android.dbflow.structure.BaseModel
import org.mifos.mobile.core.datastore.SelfServiceDatabase
/**
* Created by dilpreet on 13/9/17.
*/
@Table(database = SelfServiceDatabase::class)
class MifosNotification : BaseModel() {
@JvmField
@PrimaryKey
var timeStamp: Long = 0
@JvmField
@Column
var msg: String? = null
@JvmField
@Column
var read: Boolean? = null
fun getTimeStamp(): Long {
return timeStamp
}
fun setTimeStamp(timeStamp: Long) {
this.timeStamp = timeStamp
}
fun isRead(): Boolean {
return read ?: false
}
fun setRead(read: Boolean?) {
this.read = read
}
}

View File

@ -1,29 +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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore.utils
import org.mifos.mobile.core.datastore.model.MifosNotification
/**
* Created by dilpreet on 14/9/17.
*/
class NotificationComparator : Comparator<MifosNotification> {
override fun compare(
firstNotification: MifosNotification,
secondNotification: MifosNotification,
): Int {
return when {
// comparator function logic to sort notifications in the descending order of their timeStamp :
firstNotification.timeStamp > secondNotification.timeStamp -> -1
firstNotification.timeStamp < secondNotification.timeStamp -> 1
else -> 0
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2024 Mifos Initiative
* Copyright 2025 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
@ -23,10 +23,6 @@ import org.mifos.mobile.core.model.enums.MifosAppLanguage
import javax.inject.Inject
import javax.inject.Singleton
/**
* Author: Vishwajeet
* Since: 07/06/16
*/
@Singleton
class PreferencesHelper @Inject constructor(@ApplicationContext context: Context?) {
private val sharedPreferences: SharedPreferences? =
@ -98,7 +94,7 @@ class PreferencesHelper @Inject constructor(@ApplicationContext context: Context
}
var tenant: String?
get() = getString(TENANT, SelfServiceInterceptor.DEFAULT_TENANT)
get() = getString(TENANT, DEFAULT_TENANT)
set(tenant) {
putString(TENANT, tenant)
}
@ -172,7 +168,7 @@ class PreferencesHelper @Inject constructor(@ApplicationContext context: Context
}
val baseUrl: String?
get() = getString(BASE_URL, BaseURL().defaultBaseUrl)
get() = getString(BASE_URL, DEFAULT_BASE_URL)
var appTheme
get() = getInt(APPLICATION_THEME, AppTheme.SYSTEM.ordinal) ?: AppTheme.SYSTEM.ordinal
@ -181,13 +177,14 @@ class PreferencesHelper @Inject constructor(@ApplicationContext context: Context
}
var language
get() = getString(LANGUAGE_TYPE, MifosAppLanguage.ENGLISH.code) ?: MifosAppLanguage.SYSTEM_LANGUAGE.code
get() = getString(LANGUAGE_TYPE, MifosAppLanguage.ENGLISH.code)
?: MifosAppLanguage.SYSTEM_LANGUAGE.code
set(language) {
putString(LANGUAGE_TYPE, language)
}
var isDefaultSystemLanguage
get() = getBoolean(DEFAULT_SYSTEM_LANGUAGE, false) ?: false
get() = getBoolean(DEFAULT_SYSTEM_LANGUAGE, false) == true
set(value) {
putBoolean(DEFAULT_SYSTEM_LANGUAGE, value)
}
@ -209,6 +206,9 @@ class PreferencesHelper @Inject constructor(@ApplicationContext context: Context
const val APPLICATION_THEME = "application_theme"
const val LANGUAGE_TYPE = "language_type"
const val DEFAULT_SYSTEM_LANGUAGE = "default_system_language"
private const val DEFAULT_TENANT = "gsoc"
private const val DEFAULT_BASE_URL = "https://gsoc.mifos.community"
}
fun getStringFlowForKey(keyForString: String) = callbackFlow<String?> {
@ -220,7 +220,7 @@ class PreferencesHelper @Inject constructor(@ApplicationContext context: Context
sharedPreferences?.registerOnSharedPreferenceChangeListener(listener)
send(getString(keyForString, null))
awaitClose { sharedPreferences?.unregisterOnSharedPreferenceChangeListener(listener) }
}.buffer(Channel.UNLIMITED)
}.buffer(Channel.Factory.UNLIMITED)
fun getIntFlowForKey(keyForInt: String) = callbackFlow<Int?> {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
@ -231,5 +231,5 @@ class PreferencesHelper @Inject constructor(@ApplicationContext context: Context
sharedPreferences?.registerOnSharedPreferenceChangeListener(listener)
send(getInt(keyForInt, -1))
awaitClose { sharedPreferences?.unregisterOnSharedPreferenceChangeListener(listener) }
}.buffer(Channel.UNLIMITED)
}.buffer(Channel.Factory.UNLIMITED)
}

View File

@ -1,28 +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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore
class BaseURL {
val url: String? = null
get() = field
?: (PROTOCOL_HTTPS + API_ENDPOINT + API_PATH)
val defaultBaseUrl: String
get() = PROTOCOL_HTTPS + API_ENDPOINT
fun getUrl(endpoint: String): String {
return endpoint + API_PATH
}
companion object {
const val API_ENDPOINT = "gsoc.mifos.community"
const val API_PATH = "/fineract-provider/api/v1/"
const val PROTOCOL_HTTPS = "https://"
}
}

View File

@ -1,45 +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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Request.Builder
import okhttp3.Response
import java.io.IOException
class SelfServiceInterceptor(private val preferencesHelper: PreferencesHelper) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val chainRequest: Request = chain.request()
val builder: Builder = chainRequest.newBuilder()
builder.header("Content-Type", "application/json")
builder.header("Accept", "application/json")
preferencesHelper.tenant?.let {
builder.header(HEADER_TENANT, it)
}
preferencesHelper.token?.let {
if (it.isNotEmpty()) {
builder.header(HEADER_AUTH, it)
}
}
val request: Request = builder.build()
return chain.proceed(request)
}
companion object {
const val HEADER_TENANT = "Fineract-Platform-TenantId"
const val HEADER_AUTH = "Authorization"
const val DEFAULT_TENANT = "gsoc"
}
}

View File

@ -26,6 +26,7 @@ dependencies {
api(projects.core.common)
implementation(libs.jetbrains.kotlin.jdk7)
implementation(libs.kotlinx.serialization.json)
// For Serialized name
implementation(libs.squareup.retrofit.converter.gson)

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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.model.entity
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class Charge(
val clientId: Int? = null,
val chargeId: Int? = null,
val name: String? = null,
val dueDate: ArrayList<Int?> = ArrayList(),
val chargeTimeType: ChargeTimeType? = null,
val chargeCalculationType: ChargeCalculationType? = null,
val currency: Currency? = null,
val amount: Double = 0.0,
val amountPaid: Double = 0.0,
val amountWaived: Double = 0.0,
val amountWrittenOff: Double = 0.0,
val amountOutstanding: Double = 0.0,
val penalty: Boolean = false,
val isActive: Boolean = false,
val isChargePaid: Boolean = false,
val isChargeWaived: Boolean = false,
val paid: Boolean = false,
val waived: Boolean = false,
) : Parcelable

View File

@ -11,8 +11,10 @@ package org.mifos.mobile.core.model.entity
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
@Parcelize
@Serializable
data class ChargeCalculationType(
var id: Int = 0,
var code: String? = null,

View File

@ -24,5 +24,4 @@ data class Currency(
var displaySymbol: String? = null,
var nameCode: String? = null,
var displayLabel: String? = null,
) : Parcelable

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-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.model.entity
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
@Parcelize
@Serializable
data class MifosNotification(
var timeStamp: Long,
var msg: String? = null,
var read: Boolean? = null,
) : Parcelable {
fun isRead(): Boolean {
return read == true
}
}

View File

@ -7,7 +7,7 @@
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore
package org.mifos.mobile.core.network
class BaseURL {
val url: String? = null

View File

@ -11,10 +11,8 @@ package org.mifos.mobile.core.network
import io.reactivex.Observable
import okhttp3.ResponseBody
import org.mifos.mobile.core.datastore.DatabaseHelper
import org.mifos.mobile.core.datastore.PreferencesHelper
import org.mifos.mobile.core.datastore.model.Charge
import org.mifos.mobile.core.datastore.model.MifosNotification
import org.mifos.mobile.core.model.entity.Charge
import org.mifos.mobile.core.model.entity.Page
import org.mifos.mobile.core.model.entity.Transaction
import org.mifos.mobile.core.model.entity.UpdatePasswordPayload
@ -53,7 +51,6 @@ import javax.inject.Singleton
class DataManager @Inject constructor(
val preferencesHelper: PreferencesHelper,
private val baseApiManager: BaseApiManager,
private val databaseHelper: DatabaseHelper,
) {
var clientId: Long? = preferencesHelper.clientId
@ -88,9 +85,7 @@ class DataManager @Inject constructor(
}
suspend fun getClientCharges(clientId: Long): Page<Charge> {
return baseApiManager.clientChargeApi.getClientChargeList(clientId).apply {
databaseHelper.syncCharges(this)
}
return baseApiManager.clientChargeApi.getClientChargeList(clientId)
}
suspend fun getLoanCharges(loanId: Long): List<Charge> {
@ -209,14 +204,6 @@ class DataManager @Inject constructor(
return baseApiManager.registrationApi.verifyUser(userVerify)
}
fun clientLocalCharges(): Page<Charge?> = databaseHelper.clientCharges()
fun notifications(): List<MifosNotification> = databaseHelper.notifications()
fun unreadNotificationsCount(): Int {
return databaseHelper.unreadNotificationsCount()
}
suspend fun registerNotification(payload: NotificationRegisterPayload?): ResponseBody {
return baseApiManager.notificationApi.registerNotification(payload)
}

View File

@ -7,17 +7,14 @@
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore
package org.mifos.mobile.core.network
import android.text.TextUtils
import okhttp3.Interceptor
import okhttp3.Response
import org.mifos.mobile.core.datastore.PreferencesHelper
import java.io.IOException
/**
* @author Vishwajeet
* @since 21/06/16
*/
class SelfServiceInterceptor(private val preferencesHelper: PreferencesHelper) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {

View File

@ -12,7 +12,6 @@ package org.mifos.mobile.core.network
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.mifos.mobile.core.datastore.PreferencesHelper
import org.mifos.mobile.core.datastore.SelfServiceInterceptor
import java.security.SecureRandom
import java.security.cert.CertificateException
import java.security.cert.X509Certificate

View File

@ -13,8 +13,8 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import org.mifos.mobile.core.datastore.BaseURL
import org.mifos.mobile.core.datastore.PreferencesHelper
import org.mifos.mobile.core.network.BaseURL
import org.mifos.mobile.core.network.SelfServiceOkHttpClient
import org.mifos.mobile.core.network.services.AuthenticationService
import org.mifos.mobile.core.network.services.BeneficiaryService

View File

@ -10,15 +10,11 @@
package org.mifos.mobile.core.network.services
import org.mifos.mobile.core.common.ApiEndPoints
import org.mifos.mobile.core.datastore.model.Charge
import org.mifos.mobile.core.model.entity.Charge
import org.mifos.mobile.core.model.entity.Page
import retrofit2.http.GET
import retrofit2.http.Path
/**
* @author Vishwajeet
* @since 17/8/16.
*/
interface ClientChargeService {
@GET(ApiEndPoints.CLIENTS + "/{clientId}/charges")

View File

@ -10,7 +10,7 @@
package org.mifos.mobile.core.testing.util
import com.google.gson.reflect.TypeToken
import org.mifos.mobile.core.datastore.model.Charge
import org.mifos.mobile.core.database.entity.ChargeEntity
import org.mifos.mobile.core.model.entity.Page
import org.mifos.mobile.core.model.entity.Transaction
import org.mifos.mobile.core.model.entity.UpdatePasswordPayload
@ -177,22 +177,22 @@ object FakeRemoteDataSource {
LoginPayload::class.java,
FakeJsonName.LOGIN,
)
val charge: Page<Charge?>?
get() = mTestDataFactory.getListTypePojo<Page<Charge?>?>(
val charge: Page<ChargeEntity?>?
get() = mTestDataFactory.getListTypePojo<Page<ChargeEntity?>?>(
object :
TypeToken<Page<Charge?>?>() {},
TypeToken<Page<ChargeEntity?>?>() {},
FakeJsonName.CHARGE,
)
val savingsCharge: List<Charge>
val savingsCharge: List<ChargeEntity>
get() = mTestDataFactory.getListTypePojo(
object :
TypeToken<List<Charge>?>() {},
TypeToken<List<ChargeEntity>?>() {},
FakeJsonName.SAVING_CHARGE,
)
val loanCharge: List<Charge>
val loanCharge: List<ChargeEntity>
get() = mTestDataFactory.getListTypePojo(
object :
TypeToken<List<Charge>?>() {},
TypeToken<List<ChargeEntity>?>() {},
FakeJsonName.LOAN_CHARGE,
)
val userVerify: UserVerify

View File

@ -9,21 +9,17 @@
*/
package org.mifos.mobile.feature.beneficiary.beneficiaryList
import android.widget.Toast
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.pulltorefresh.PullToRefreshContainer
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -99,62 +95,43 @@ private fun BeneficiaryListScreen(
Box(
modifier = Modifier
.fillMaxSize()
.padding(it)
.nestedScroll(pullRefreshState.nestedScrollConnection),
.padding(it),
) {
when (uiState) {
BeneficiaryListUiState.Loading -> {
MifosProgressIndicatorOverlay()
}
PullToRefreshBox(
state = pullRefreshState,
onRefresh = refresh,
isRefreshing = isRefreshing,
) {
when (uiState) {
BeneficiaryListUiState.Loading -> {
MifosProgressIndicatorOverlay()
}
is BeneficiaryListUiState.Error -> {
MifosErrorComponent(
isNetworkConnected = Network.isConnected(context),
isRetryEnabled = true,
onRetry = retryLoadingBeneficiary,
message = stringResource(R.string.error_fetching_beneficiaries),
)
}
is BeneficiaryListUiState.Success -> {
if (uiState.beneficiaries.isEmpty()) {
EmptyDataView(
modifier = Modifier.fillMaxSize(),
icon = R.drawable.ic_error_black_24dp,
error = R.string.no_beneficiary_found_please_add,
)
} else {
ShowBeneficiary(
beneficiaryList = uiState.beneficiaries,
onClick = onBeneficiaryItemClick,
is BeneficiaryListUiState.Error -> {
MifosErrorComponent(
isNetworkConnected = Network.isConnected(context),
isRetryEnabled = true,
onRetry = retryLoadingBeneficiary,
message = stringResource(R.string.error_fetching_beneficiaries),
)
}
is BeneficiaryListUiState.Success -> {
if (uiState.beneficiaries.isEmpty()) {
EmptyDataView(
modifier = Modifier.fillMaxSize(),
icon = R.drawable.ic_error_black_24dp,
error = R.string.no_beneficiary_found_please_add,
)
} else {
ShowBeneficiary(
beneficiaryList = uiState.beneficiaries,
onClick = onBeneficiaryItemClick,
)
}
}
}
}
PullToRefreshContainer(
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
)
}
LaunchedEffect(key1 = isRefreshing) {
if (isRefreshing) pullRefreshState.startRefresh()
}
LaunchedEffect(key1 = pullRefreshState.isRefreshing) {
if (pullRefreshState.isRefreshing) {
if (Network.isConnected(context)) {
refresh()
} else {
Toast.makeText(
context,
context.resources.getText(R.string.internet_not_connected),
Toast.LENGTH_SHORT,
).show()
}
pullRefreshState.endRefresh()
}
}
}
}

View File

@ -17,5 +17,5 @@ android {
}
dependencies {
implementation(libs.dbflow)
}

View File

@ -38,11 +38,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.mifos.mobile.core.common.Network
import org.mifos.mobile.core.common.utils.CurrencyUtil
import org.mifos.mobile.core.common.utils.DateHelper
import org.mifos.mobile.core.datastore.model.Charge
import org.mifos.mobile.core.designsystem.components.MifosScaffold
import org.mifos.mobile.core.designsystem.theme.GreenSuccess
import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
import org.mifos.mobile.core.designsystem.theme.RedErrorDark
import org.mifos.mobile.core.model.entity.Charge
import org.mifos.mobile.core.ui.component.EmptyDataView
import org.mifos.mobile.core.ui.component.MifosErrorComponent
import org.mifos.mobile.core.ui.component.MifosProgressIndicator

View File

@ -9,7 +9,7 @@
*/
package org.mifos.mobile.feature.charge.utils
import org.mifos.mobile.core.datastore.model.Charge
import org.mifos.mobile.core.model.entity.Charge
internal sealed class ClientChargeState {
data object Loading : ClientChargeState()

View File

@ -9,6 +9,7 @@
*/
package org.mifos.mobile.feature.charge.viewmodel
import android.util.Log
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -28,8 +29,8 @@ import javax.inject.Inject
@HiltViewModel
internal class ClientChargeViewModel @Inject constructor(
private val clientChargeRepositoryImp: ClientChargeRepository,
val preferencesHelper: PreferencesHelper,
private val savedStateHandle: SavedStateHandle,
preferencesHelper: PreferencesHelper,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val _clientChargeUiState = MutableStateFlow<ClientChargeState>(Loading)
@ -63,6 +64,8 @@ internal class ClientChargeViewModel @Inject constructor(
clientChargeRepositoryImp.getClientCharges(clientId).catch {
_clientChargeUiState.value = ClientChargeState.Error(it.message)
}.collect {
Log.e("selfServiceDatabase", it.toString())
clientChargeRepositoryImp.syncCharges(it)
_clientChargeUiState.value = ClientChargeState.Success(it.pageItems)
}
}
@ -96,8 +99,7 @@ internal class ClientChargeViewModel @Inject constructor(
clientChargeRepositoryImp.clientLocalCharges().catch {
_clientChargeUiState.value = ClientChargeState.Error(it.message)
}.collect {
_clientChargeUiState.value =
ClientChargeState.Success(it.pageItems.filterNotNull())
_clientChargeUiState.value = ClientChargeState.Success(it.pageItems)
}
}
}

View File

@ -17,6 +17,5 @@ android {
}
dependencies {
implementation(projects.core.datastore)
implementation(libs.dbflow)
}

View File

@ -24,17 +24,15 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshContainer
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -45,10 +43,10 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.mifos.mobile.core.common.Network
import org.mifos.mobile.core.common.utils.DateHelper
import org.mifos.mobile.core.datastore.model.MifosNotification
import org.mifos.mobile.core.designsystem.components.MifosScaffold
import org.mifos.mobile.core.designsystem.components.MifosTextButton
import org.mifos.mobile.core.designsystem.theme.MifosMobileTheme
import org.mifos.mobile.core.model.entity.MifosNotification
import org.mifos.mobile.core.ui.component.EmptyDataView
import org.mifos.mobile.core.ui.component.MifosErrorComponent
import org.mifos.mobile.core.ui.component.MifosProgressIndicatorOverlay
@ -137,24 +135,11 @@ private fun NotificationContent(
) {
val pullRefreshState = rememberPullToRefreshState()
if (pullRefreshState.isRefreshing) {
LaunchedEffect(key1 = true) {
onRefresh()
}
}
LaunchedEffect(key1 = isRefreshing) {
if (isRefreshing) {
pullRefreshState.startRefresh()
} else {
pullRefreshState.endRefresh()
}
}
Box(
modifier = modifier
.fillMaxSize()
.nestedScroll(pullRefreshState.nestedScrollConnection),
PullToRefreshBox(
state = pullRefreshState,
onRefresh = onRefresh,
isRefreshing = isRefreshing,
modifier = modifier,
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
itemsIndexed(items = notifications) { index, notification ->
@ -167,10 +152,6 @@ private fun NotificationContent(
}
}
}
PullToRefreshContainer(
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
)
}
}
@ -227,8 +208,16 @@ internal class NotificationUiStatePreviews : PreviewParameterProvider<Notificati
get() = sequenceOf(
NotificationUiState.Success(
notifications = listOf(
MifosNotification(),
MifosNotification(),
MifosNotification(
timeStamp = 13231331L,
msg = "Your payment is successful",
read = false,
),
MifosNotification(
timeStamp = 13231331L,
msg = "Your payment is successful",
read = true,
),
),
),
NotificationUiState.Error(errorMessage = ""),

View File

@ -9,6 +9,7 @@
*/
package org.mifos.mobile.feature.notification
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
@ -17,7 +18,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.launch
import org.mifos.mobile.core.data.repository.NotificationRepository
import org.mifos.mobile.core.datastore.model.MifosNotification
import org.mifos.mobile.core.model.entity.MifosNotification
import org.mifos.mobile.feature.notification.NotificationUiState.Loading
import javax.inject.Inject
@ -33,7 +34,10 @@ internal class NotificationViewModel @Inject constructor(
val isRefreshing: StateFlow<Boolean> get() = _isRefreshing
init {
loadNotifications()
viewModelScope.launch {
notificationRepositoryImp.deleteOldNotifications()
loadNotifications()
}
}
fun loadNotifications() {
@ -41,12 +45,15 @@ internal class NotificationViewModel @Inject constructor(
viewModelScope.launch {
notificationRepositoryImp.loadNotifications()
.catch {
Log.e("selfServiceDatabase", it.toString())
_notificationUiState.value =
NotificationUiState.Error(errorMessage = it.message)
}.collect { notifications ->
Log.e("selfServiceDatabase", notifications.toString())
val sortedNotifications = sortNotifications(notifications)
_isRefreshing.emit(false)
_notificationUiState.value =
NotificationUiState.Success(notifications = notifications)
NotificationUiState.Success(notifications = sortedNotifications)
}
}
}
@ -57,8 +64,18 @@ internal class NotificationViewModel @Inject constructor(
}
fun dismissNotification(notification: MifosNotification) {
notification.setRead(true)
notification.save()
notification.read = true
viewModelScope.launch {
notificationRepositoryImp.saveNotification(notification.copy(read = true))
notificationRepositoryImp.updateReadStatus(notification, true)
}
}
private fun sortNotifications(notifications: List<MifosNotification>): List<MifosNotification> {
return notifications.sortedWith(
compareByDescending<MifosNotification> { !it.isRead() }
.thenByDescending { it.timeStamp },
)
}
}

View File

@ -13,12 +13,10 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
@ -27,11 +25,11 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.rememberPermissionState
@ -110,7 +108,7 @@ internal fun PermissionBox(
contract = ActivityResultContracts.RequestMultiplePermissions(),
onResult = { permissionResults ->
val isGranted =
requiredPermissions.all { permissionResults[it] ?: false }
requiredPermissions.all { permissionResults[it] == true }
permissionGranted = isGranted
@ -179,7 +177,6 @@ internal fun PermissionBox(
}
}
@RequiresApi(Build.VERSION_CODES.O)
internal fun convertToMutableBitmap(bitmap: Bitmap): Bitmap {
return if (bitmap.config == Bitmap.Config.HARDWARE) {
bitmap.copy(Bitmap.Config.ARGB_8888, true)

View File

@ -26,15 +26,13 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshContainer
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -104,64 +102,50 @@ private fun RecentTransactionScreen(
Box(
Modifier
.fillMaxSize()
.padding(paddingValues)
.nestedScroll(pullRefreshState.nestedScrollConnection),
.padding(paddingValues),
) {
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
PullToRefreshBox(
state = pullRefreshState,
onRefresh = onRefresh,
isRefreshing = isRefreshing,
) {
when (uiState) {
is RecentTransactionState.Error -> {
MifosErrorComponent(
isNetworkConnected = Network.isConnected(context),
isRetryEnabled = true,
onRetry = onRetry,
)
}
is RecentTransactionState.Loading -> {
MifosProgressIndicatorOverlay()
}
is RecentTransactionState.Success -> {
if (uiState.transactions.isEmpty()) {
EmptyDataView(
icon = R.drawable.ic_error_black_24dp,
error = R.string.no_transaction,
modifier = Modifier.fillMaxSize(),
)
} else {
RecentTransactionsContent(
transactions = uiState.transactions,
isPaginating = isPaginating,
loadMore = loadMore,
canPaginate = uiState.canPaginate,
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
) {
when (uiState) {
is RecentTransactionState.Error -> {
MifosErrorComponent(
isNetworkConnected = Network.isConnected(context),
isRetryEnabled = true,
onRetry = onRetry,
)
}
is RecentTransactionState.Loading -> {
MifosProgressIndicatorOverlay()
}
is RecentTransactionState.Success -> {
if (uiState.transactions.isEmpty()) {
EmptyDataView(
icon = R.drawable.ic_error_black_24dp,
error = R.string.no_transaction,
modifier = Modifier.fillMaxSize(),
)
} else {
RecentTransactionsContent(
transactions = uiState.transactions,
isPaginating = isPaginating,
loadMore = loadMore,
canPaginate = uiState.canPaginate,
)
}
}
}
}
}
if (pullRefreshState.isRefreshing) {
LaunchedEffect(key1 = true) {
onRefresh()
}
}
LaunchedEffect(key1 = isRefreshing) {
if (isRefreshing) {
pullRefreshState.startRefresh()
} else {
pullRefreshState.endRefresh()
}
}
PullToRefreshContainer(
state = pullRefreshState,
modifier = Modifier
.padding(top = 24.dp)
.align(Alignment.TopCenter),
)
}
},
)

View File

@ -1,37 +1,36 @@
[versions]
accompanistVersion = "0.34.0"
activityVersion = "1.9.1"
androidDesugarJdkLibs = "2.1.1"
androidGradlePlugin = "8.5.2"
androidTools = "31.5.2"
activityVersion = "1.9.3"
androidDesugarJdkLibs = "2.1.4"
androidGradlePlugin = "8.7.0"
androidTools = "31.7.0"
androidx-test-ext-junit = "1.2.1"
androidxActivity = "1.9.1"
androidxComposeBom = "2024.08.00"
androidxActivity = "1.9.3"
androidxComposeBom = "2024.12.01"
androidxComposeCompiler = "1.5.15"
androidxCoreSplashscreen = "1.0.1"
androidxEspresso = "3.6.1"
androidxHilt = "1.2.0"
androidxLifecycle = "2.8.4"
androidxLifecycle = "2.8.7"
androidxMetrics = "1.0.0-beta01"
androidxNavigation = "2.8.0-rc01"
androidxProfileinstaller = "1.3.1"
androidxNavigation = "2.8.5"
androidxProfileinstaller = "1.4.1"
androidxTestExt = "1.2.1"
androidxTestRules = "1.6.1"
androidxTestRunner = "1.6.2"
androidxTracing = "1.2.0"
androidxUiAutomator = "2.3.0"
annotationVersion = "1.8.2"
annotation = "1.9.1"
appcompatVersion = "1.7.0"
cameraCoreVersion = "1.3.4"
cameraxVersion = "1.3.4"
compose-material = "1.6.8"
cameraxVersion = "1.4.1"
compose-material = "1.7.6"
compose-plugin = "1.6.11"
coreKtxVersion = "1.13.1"
dbflowVersion = "4.2.4"
coreKtx = "1.15.0"
dependencyGuard = "0.5.0"
detekt = "1.23.5"
detekt = "1.23.7"
easycropVersion = "0.1.1"
firebaseBom = "33.2.0"
firebaseBom = "33.7.0"
firebaseCrashlyticsPlugin = "3.0.2"
firebasePerfPlugin = "1.4.2"
gmsPlugin = "4.4.2"
@ -39,23 +38,25 @@ googleAppCodeScanner = "17.3.0"
googleMaps = "4.4.1"
googleOss = "17.1.0"
googleOssPlugin = "0.10.6"
hilt = "2.52"
gsonVersion = "2.10.1"
hilt = "2.54"
hiltExt = "1.2.0"
jacoco = "0.8.7"
junitVersion = "4.13.2"
koin = "3.6.0-Beta4"
koinComposeMultiplatform = "1.2.0-Beta4"
kotlin = "2.0.20"
kotlinxCoroutinesAndroidVersion = "1.8.1"
kotlinxCoroutinesTestVersion = "1.8.1"
kotlinxImmutable = "0.3.7"
ksp = "2.0.20-1.0.24"
koin = "4.0.0"
koinComposeMultiplatform = "4.0.0"
kotlin = "2.1.0"
kotlinxCoroutines = "1.10.1"
kotlinxImmutable = "0.3.8"
kotlinxSerializationJson = "1.8.0-RC"
ksp = "2.1.0-1.0.29"
ktlint = "12.1.1"
libphonenumberAndroidVersion = "8.13.35"
lifecycleExtensionsVersion = "2.2.0"
lifecycleVersion = "2.8.4"
material3 = "1.2.1"
mockitoAndroidVersion = "5.4.0"
mockitoCoreVersion = "5.4.0"
lifecycleLivedataKtxVersion = "2.8.7"
lifecycleVersion = "2.8.7"
material3 = "1.3.1"
mockitoCoreVersion = "5.6.0"
multidexVersion = "2.0.1"
okHttp3Version = "4.12.0"
playServicesCodeScanner = "16.1.0"
@ -63,12 +64,15 @@ playServicesVersion = "19.0.0"
retrofitVersion = "2.11.0"
roborazzi = "1.26.0"
room = "2.6.1"
roomKtxVersion = "2.6.1"
roomRuntimeVersion = "2.6.1"
roomTestingVersion = "2.6.1"
rxandroidVersion = "2.1.1"
rxjavaVersion = "2.2.21"
secrets = "2.0.1"
slackComposeLint = "1.3.1"
spotlessVersion = "6.23.3"
truth = "1.4.2"
spotlessVersion = "6.25.0"
truth = "1.4.4"
turbineVersion = "1.1.0"
twitter-detekt-compose = "0.0.26"
versionCatalogLinterVersion = "1.0.3"
@ -82,7 +86,7 @@ android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", ver
android-tools-common = { group = "com.android.tools", name = "common", version.ref = "androidTools" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityVersion" }
androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotationVersion" }
androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompatVersion" }
androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "cameraxVersion" }
androidx-camera-core = { group = "androidx.camera", name = "camera-core", version.ref = "cameraxVersion" }
@ -103,11 +107,12 @@ androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-compose-ui-util = { group = "androidx.compose.ui", name = "ui-util" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtxVersion" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "androidxCoreSplashscreen" }
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidxHilt" }
androidx-lifecycle-extensions = { group = "androidx.lifecycle", name = "lifecycle-extensions", version.ref = "lifecycleExtensionsVersion" }
androidx-lifecycle-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleVersion" }
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtxVersion" }
androidx-lifecycle-runtimeCompose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
androidx-lifecycle-runtimeTesting = { group = "androidx.lifecycle", name = "lifecycle-runtime-testing", version.ref = "androidxLifecycle" }
androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
@ -116,6 +121,10 @@ androidx-multidex = { group = "androidx.multidex", name = "multidex", version.re
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxNavigation" }
androidx-navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "androidxNavigation" }
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "androidxProfileinstaller" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "roomKtxVersion" }
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "roomRuntimeVersion" }
androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "roomTestingVersion" }
androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidxEspresso" }
androidx-test-ext = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidxTestExt" }
androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
@ -124,9 +133,6 @@ androidx-test-runner = { group = "androidx.test", name = "runner", version.ref =
androidx-test-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "androidxUiAutomator" }
androidx-tracing-ktx = { group = "androidx.tracing", name = "tracing-ktx", version.ref = "androidxTracing" }
compose-gradlePlugin = { group = "org.jetbrains.kotlin", name = "compose-compiler-gradle-plugin", version.ref = "kotlin" }
dbflow = { group = "com.github.Raizlabs.DBFlow", name = "dbflow", version.ref = "dbflowVersion" }
dbflow-core = { group = "com.github.Raizlabs.DBFlow", name = "dbflow-core", version.ref = "dbflowVersion" }
dbflow-processor = { group = "com.github.Raizlabs.DBFlow", name = "dbflow-processor", version.ref = "dbflowVersion" }
detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" }
detekt-gradlePlugin = { group = "io.gitlab.arturbosch.detekt", name = "detekt-gradle-plugin", version.ref = "detekt" }
easycrop-compose = { group = "io.github.mr0xf00", name = "easycrop", version.ref = "easycropVersion" }
@ -142,10 +148,15 @@ google-map-compose = { group = "com.google.maps.android", name = "maps-compose",
google-oss-licenses = { group = "com.google.android.gms", name = "play-services-oss-licenses", version.ref = "googleOss" }
google-oss-licenses-plugin = { group = "com.google.android.gms", name = "oss-licenses-plugin", version.ref = "googleOssPlugin" }
google-play-services-code-scanner = { group = "com.google.android.gms", name = "play-services-code-scanner", version.ref = "playServicesCodeScanner" }
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gsonVersion" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
hilt-core = { group = "com.google.dagger", name = "hilt-core", version.ref = "hilt" }
hilt-ext-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hiltExt" }
hilt-ext-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hiltExt" }
jetbrains-kotlin-jdk7 = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk7", version.ref = "kotlin" }
jetbrains-kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" }
junit = { group = "junit", name = "junit", version.ref = "junitVersion" }
koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" }
koin-androidx-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = "koin" }
@ -155,15 +166,16 @@ koin-core = { group = "io.insert-koin", name = "koin-core", version.ref = "koin"
kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroidVersion" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTestVersion" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
ktlint-gradlePlugin = { group = "org.jlleitschuh.gradle", name = "ktlint-gradle", version.ref = "ktlint" }
libphonenumber-android = { group = "io.michaelrocks", name = "libphonenumber-android", version.ref = "libphonenumberAndroidVersion" }
lint-api = { group = "com.android.tools.lint", name = "lint-api", version.ref = "androidTools" }
lint-checks = { group = "com.android.tools.lint", name = "lint-checks", version.ref = "androidTools" }
lint-tests = { group = "com.android.tools.lint", name = "lint-tests", version.ref = "androidTools" }
mockito-android = { group = "org.mockito", name = "mockito-android", version.ref = "mockitoAndroidVersion" }
mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockitoCoreVersion" }
play-services-maps = { group = "com.google.android.gms", name = "play-services-maps", version.ref = "playServicesVersion" }
reactivex-rxjava2 = { group = "io.reactivex.rxjava2", name = "rxjava", version.ref = "rxjavaVersion" }

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip

View File

@ -14,6 +14,10 @@ plugins {
android {
namespace = "com.mifos.library.countrycodepicker"
lint {
disable += "SuspiciousModifierThen"
}
}
dependencies {

View File

@ -262,7 +262,7 @@ fun CountryCodePicker(
visualTransformation = phoneNumberTransformation,
keyboardOptions = keyboardOptions ?: KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Phone,
autoCorrect = true,
autoCorrectEnabled = true,
imeAction = ImeAction.Done,
),
keyboardActions = keyboardActions ?: KeyboardActions(

View File

@ -7,7 +7,7 @@
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
@file:Suppress("InvalidPackageDeclaration")
@file:Suppress("InvalidPackageDeclaration", "DEPRECATION")
package com.mifos.library.pullrefresh

View File

@ -46,6 +46,7 @@ include(":core:model")
include(":core:common")
include(":core:data")
include(":core:network")
include(":core:database")
include(":core:datastore")
include(":core:qrcode")
include(":core:testing")