diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts
index 12ad40ba1a9..69f2a6e84a5 100644
--- a/core/common/build.gradle.kts
+++ b/core/common/build.gradle.kts
@@ -24,11 +24,11 @@ android {
}
}
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = "1.8"
+ jvmTarget = "17"
}
}
diff --git a/core/common/src/main/AndroidManifest.xml b/core/common/src/main/AndroidManifest.xml
index a5918e68abc..cbb96c15613 100644
--- a/core/common/src/main/AndroidManifest.xml
+++ b/core/common/src/main/AndroidManifest.xml
@@ -1,4 +1,6 @@
+
+
\ No newline at end of file
diff --git a/core/common/src/main/java/com/mifos/core/common/utils/BaseUrl.kt b/core/common/src/main/java/com/mifos/core/common/utils/BaseUrl.kt
new file mode 100644
index 00000000000..669263eb3d5
--- /dev/null
+++ b/core/common/src/main/java/com/mifos/core/common/utils/BaseUrl.kt
@@ -0,0 +1,16 @@
+package com.mifos.core.common.utils
+
+object BaseUrl {
+
+ // "/" in the last of the base url always
+
+ const val PROTOCOL_HTTPS = "https://"
+
+ const val API_ENDPOINT = "gsoc.mifos.community"
+
+ const val API_PATH = "/fineract-provider/api/v1/"
+
+ const val PORT = "80"
+
+ const val TENANT = "default"
+}
\ No newline at end of file
diff --git a/core/common/src/main/java/com/mifos/core/common/utils/Network.kt b/core/common/src/main/java/com/mifos/core/common/utils/Network.kt
new file mode 100644
index 00000000000..e88dd2cb796
--- /dev/null
+++ b/core/common/src/main/java/com/mifos/core/common/utils/Network.kt
@@ -0,0 +1,33 @@
+/*
+ * This project is licensed under the open source MPL V2.
+ * See https://github.com/openMF/android-client/blob/master/LICENSE.md
+ */
+package com.mifos.core.common.utils
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
+
+/**
+ * Created by Aditya Gupta on 11/02/24.
+ */
+
+object Network {
+
+ fun isOnline(context: Context): Boolean {
+ val connectivityManager =
+ context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val capabilities =
+ connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
+ if (capabilities != null) {
+ if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ return true
+ } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+ return true
+ } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
+ return true
+ }
+ }
+ return false
+ }
+}
\ No newline at end of file
diff --git a/core/common/src/main/java/com/mifos/core/common/utils/Resource.kt b/core/common/src/main/java/com/mifos/core/common/utils/Resource.kt
new file mode 100644
index 00000000000..0e63c6d1111
--- /dev/null
+++ b/core/common/src/main/java/com/mifos/core/common/utils/Resource.kt
@@ -0,0 +1,14 @@
+package com.mifos.core.common.utils
+
+/**
+ * Created by Aditya Gupta on 11/02/24.
+ */
+
+sealed class Resource(val data: T? = null, val message: String? = null) {
+
+ class Success(data: T?) : Resource(data)
+
+ class Error(message: String, data: T? = null) : Resource(data, message)
+
+ class Loading(data: T? = null) : Resource(data)
+}
\ No newline at end of file
diff --git a/core/data/.gitignore b/core/data/.gitignore
new file mode 100644
index 00000000000..42afabfd2ab
--- /dev/null
+++ b/core/data/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts
new file mode 100644
index 00000000000..01c85df162b
--- /dev/null
+++ b/core/data/build.gradle.kts
@@ -0,0 +1,43 @@
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "com.mifos.core.data"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 26
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = "17"
+ }
+}
+
+dependencies {
+
+ implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.appcompat:appcompat:1.6.1")
+ implementation("com.google.android.material:material:1.11.0")
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+}
\ No newline at end of file
diff --git a/core/data/consumer-rules.pro b/core/data/consumer-rules.pro
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/core/data/proguard-rules.pro b/core/data/proguard-rules.pro
new file mode 100644
index 00000000000..481bb434814
--- /dev/null
+++ b/core/data/proguard-rules.pro
@@ -0,0 +1,21 @@
+# 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
\ No newline at end of file
diff --git a/core/data/src/androidTest/java/com/mifos/core/data/ExampleInstrumentedTest.kt b/core/data/src/androidTest/java/com/mifos/core/data/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000000..fe519f7e834
--- /dev/null
+++ b/core/data/src/androidTest/java/com/mifos/core/data/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.mifos.core.data
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.mifos.core.data.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/core/data/src/main/AndroidManifest.xml b/core/data/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..a5918e68abc
--- /dev/null
+++ b/core/data/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/data/src/main/java/com/mifos/core/data/model/Role.kt b/core/data/src/main/java/com/mifos/core/data/model/Role.kt
new file mode 100644
index 00000000000..20e21a38432
--- /dev/null
+++ b/core/data/src/main/java/com/mifos/core/data/model/Role.kt
@@ -0,0 +1,16 @@
+/*
+ * This project is licensed under the open source MPL V2.
+ * See https://github.com/openMF/android-client/blob/master/LICENSE.md
+ */
+package com.mifos.core.data.model
+
+/**
+ * Created by ishankhanna on 09/02/14.
+ */
+data class Role(
+ var id: Int = 0,
+
+ var name: String? = null,
+
+ var description: String? = null
+)
\ No newline at end of file
diff --git a/core/data/src/main/java/com/mifos/core/data/model/User.kt b/core/data/src/main/java/com/mifos/core/data/model/User.kt
new file mode 100644
index 00000000000..d1568b880c5
--- /dev/null
+++ b/core/data/src/main/java/com/mifos/core/data/model/User.kt
@@ -0,0 +1,23 @@
+/*
+ * This project is licensed under the open source MPL V2.
+ * See https://github.com/openMF/android-client/blob/master/LICENSE.md
+ */
+package com.mifos.core.data.model
+
+class User {
+ var username: String? = null
+
+ var userId = 0
+
+ var base64EncodedAuthenticationKey: String? = null
+
+ var isAuthenticated = false
+
+ var officeId = 0
+
+ var officeName: String? = null
+
+ var roles: List = ArrayList()
+
+ var permissions: List = ArrayList()
+}
\ No newline at end of file
diff --git a/core/data/src/test/java/com/mifos/core/data/ExampleUnitTest.kt b/core/data/src/test/java/com/mifos/core/data/ExampleUnitTest.kt
new file mode 100644
index 00000000000..bd60383b85b
--- /dev/null
+++ b/core/data/src/test/java/com/mifos/core/data/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.mifos.core.data
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/core/datastore/build.gradle.kts b/core/datastore/build.gradle.kts
index 663e7ba3d47..810535c77ee 100644
--- a/core/datastore/build.gradle.kts
+++ b/core/datastore/build.gradle.kts
@@ -1,6 +1,8 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
+ id("kotlin-kapt")
+ id("com.google.dagger.hilt.android")
}
android {
@@ -34,10 +36,24 @@ android {
dependencies {
+ implementation(project(":core:data"))
+
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ implementation("com.squareup.retrofit2:converter-gson:2.9.0")
+
+ // Hilt dependency
+ implementation("com.google.dagger:hilt-android:2.50")
+ kapt("com.google.dagger:hilt-android-compiler:2.50")
+
+
+ // fineract sdk dependencies
+ implementation("com.github.openMF:mifos-android-sdk-arch:1.06")
+
+ // sdk client
+ implementation("com.github.openMF:fineract-client:2.0.3")
}
\ No newline at end of file
diff --git a/core/datastore/src/main/java/com/mifos/core/datastore/PrefManager.kt b/core/datastore/src/main/java/com/mifos/core/datastore/PrefManager.kt
new file mode 100644
index 00000000000..59219b6463e
--- /dev/null
+++ b/core/datastore/src/main/java/com/mifos/core/datastore/PrefManager.kt
@@ -0,0 +1,54 @@
+package com.mifos.core.datastore
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.preference.PreferenceManager
+import com.mifos.core.data.model.User
+import dagger.hilt.android.qualifiers.ApplicationContext
+import org.apache.fineract.client.models.PostAuthenticationResponse
+import org.mifos.core.sharedpreference.Key
+import org.mifos.core.sharedpreference.UserPreferences
+import javax.inject.Inject
+
+/**
+ * Created by Aditya Gupta on 19/08/23.
+ */
+
+class PrefManager @Inject constructor(@ApplicationContext context: Context) :
+ UserPreferences() {
+
+ private val USER_DETAILS = "user_details"
+ private val AUTH_USERNAME = "auth_username"
+ private val AUTH_PASSWORD = "auth_password"
+
+ override val preference: SharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(context)
+
+ override fun getUser(): User {
+ return get(Key.Custom(USER_DETAILS))
+ }
+
+ override fun saveUser(user: User) {
+ put(Key.Custom(USER_DETAILS), user)
+ }
+
+ // Created this to store userDetails
+ fun savePostAuthenticationResponse(user: PostAuthenticationResponse) {
+ put(Key.Custom(USER_DETAILS), user)
+ }
+
+ fun setPermissionDeniedStatus(permissionDeniedStatus: String, status: Boolean) {
+ put(Key.Custom(permissionDeniedStatus), status)
+ }
+
+ fun getPermissionDeniedStatus(permissionDeniedStatus: String): Boolean {
+ return get(Key.Custom(permissionDeniedStatus), true)
+ }
+
+ var usernamePassword: Pair
+ get() = Pair(get(Key.Custom(AUTH_USERNAME)), get(Key.Custom(AUTH_PASSWORD)))
+ set(value) {
+ put(Key.Custom(AUTH_USERNAME), value.first)
+ put(Key.Custom(AUTH_PASSWORD), value.second)
+ }
+}
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosAndroidClientIcon.kt b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosAndroidClientIcon.kt
index 548d36170e6..96889d9ee25 100644
--- a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosAndroidClientIcon.kt
+++ b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosAndroidClientIcon.kt
@@ -1,12 +1,8 @@
package com.mifos.core.designsystem.component
import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
@@ -18,6 +14,6 @@ fun MifosAndroidClientIcon(id: Int) {
painter = painterResource(id = id),
contentDescription = null,
modifier = Modifier
- .size(200.dp,100.dp)
+ .size(200.dp, 100.dp)
)
}
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosEditTextField.kt b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosEditTextField.kt
index af5ef0633a7..74c2ca1aa06 100644
--- a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosEditTextField.kt
+++ b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosEditTextField.kt
@@ -35,8 +35,7 @@ fun MifosOutlinedTextField(
label: Int,
visualTransformation: VisualTransformation = VisualTransformation.None,
trailingIcon: @Composable (() -> Unit)? = null,
- error: Boolean = false,
- supportingText: String?
+ error: Int?
) {
OutlinedTextField(
@@ -66,16 +65,14 @@ fun MifosOutlinedTextField(
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
visualTransformation = visualTransformation,
- isError = error,
+ isError = error != null,
supportingText = {
- if (error) {
- if (supportingText != null) {
- Text(
- modifier = Modifier.fillMaxWidth(),
- text = supportingText,
- color = MaterialTheme.colorScheme.error
- )
- }
+ if (error != null) {
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ text = stringResource(id = error),
+ color = MaterialTheme.colorScheme.error
+ )
}
}
)
diff --git a/core/network/.gitignore b/core/network/.gitignore
new file mode 100644
index 00000000000..42afabfd2ab
--- /dev/null
+++ b/core/network/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts
new file mode 100644
index 00000000000..758f01c526b
--- /dev/null
+++ b/core/network/build.gradle.kts
@@ -0,0 +1,61 @@
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+ id("kotlin-kapt")
+ id("com.google.dagger.hilt.android")
+}
+
+android {
+ namespace = "com.mifos.core.network"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 26
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = "17"
+ }
+}
+
+dependencies {
+
+ implementation(project(":core:datastore"))
+
+ implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.appcompat:appcompat:1.6.1")
+ implementation("com.google.android.material:material:1.11.0")
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+
+ //rxjava dependencies
+ implementation("io.reactivex:rxandroid:1.1.0")
+ implementation("io.reactivex:rxjava:1.3.8")
+
+ // Hilt dependency
+ implementation("com.google.dagger:hilt-android:2.50")
+ kapt("com.google.dagger:hilt-android-compiler:2.50")
+
+ // fineract sdk dependencies
+ implementation("com.github.openMF:mifos-android-sdk-arch:1.06")
+
+ // sdk client
+ implementation("com.github.openMF:fineract-client:2.0.3")
+}
\ No newline at end of file
diff --git a/core/network/consumer-rules.pro b/core/network/consumer-rules.pro
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/core/network/proguard-rules.pro b/core/network/proguard-rules.pro
new file mode 100644
index 00000000000..481bb434814
--- /dev/null
+++ b/core/network/proguard-rules.pro
@@ -0,0 +1,21 @@
+# 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
\ No newline at end of file
diff --git a/core/network/src/androidTest/java/com/mifos/core/network/ExampleInstrumentedTest.kt b/core/network/src/androidTest/java/com/mifos/core/network/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000000..4740686abba
--- /dev/null
+++ b/core/network/src/androidTest/java/com/mifos/core/network/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.mifos.core.network
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.mifos.core.network.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/core/network/src/main/AndroidManifest.xml b/core/network/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..a5918e68abc
--- /dev/null
+++ b/core/network/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/network/src/main/java/com/mifos/core/network/datamanger/DataManagerAuth.kt b/core/network/src/main/java/com/mifos/core/network/datamanger/DataManagerAuth.kt
new file mode 100644
index 00000000000..0a1ca8a8696
--- /dev/null
+++ b/core/network/src/main/java/com/mifos/core/network/datamanger/DataManagerAuth.kt
@@ -0,0 +1,28 @@
+package com.mifos.core.network.datamanger
+
+import com.mifos.core.network.di.BaseApiManagerQualifier
+import org.apache.fineract.client.models.PostAuthenticationRequest
+import org.apache.fineract.client.models.PostAuthenticationResponse
+import org.mifos.core.apimanager.BaseApiManager
+import rx.Observable
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Created by Rajan Maurya on 19/02/17.
+ */
+@Singleton
+class DataManagerAuth @Inject constructor(@BaseApiManagerQualifier private val baseApiManager: BaseApiManager) {
+ /**
+ * @param username Username
+ * @param password Password
+ * @return Basic OAuth
+ */
+ fun login(username: String, password: String): Observable {
+ val body = PostAuthenticationRequest().apply {
+ this.username = username
+ this.password = password
+ }
+ return baseApiManager.getAuthApi().authenticate(body, true)
+ }
+}
\ No newline at end of file
diff --git a/core/network/src/main/java/com/mifos/core/network/di/BaseApiManagerQualifier.kt b/core/network/src/main/java/com/mifos/core/network/di/BaseApiManagerQualifier.kt
new file mode 100644
index 00000000000..2326c0bf2b7
--- /dev/null
+++ b/core/network/src/main/java/com/mifos/core/network/di/BaseApiManagerQualifier.kt
@@ -0,0 +1,7 @@
+package com.mifos.core.network.di
+
+import javax.inject.Qualifier
+
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+annotation class BaseApiManagerQualifier
\ No newline at end of file
diff --git a/core/network/src/main/java/com/mifos/core/network/di/NetworkModule.kt b/core/network/src/main/java/com/mifos/core/network/di/NetworkModule.kt
new file mode 100644
index 00000000000..1e6d27168f0
--- /dev/null
+++ b/core/network/src/main/java/com/mifos/core/network/di/NetworkModule.kt
@@ -0,0 +1,31 @@
+package com.mifos.core.network.di
+
+import com.mifos.core.datastore.PrefManager
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import org.mifos.core.apimanager.BaseApiManager
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object NetworkModule {
+
+ @Provides
+ @Singleton
+ @BaseApiManagerQualifier
+ fun provideSdkBaseApiManager(prefManager: PrefManager): BaseApiManager {
+ val usernamePassword: Pair = prefManager.usernamePassword
+ val baseManager = BaseApiManager.getInstance()
+ baseManager.createService(
+ usernamePassword.first,
+ usernamePassword.second,
+ prefManager.getInstanceUrl(),
+ prefManager.getTenant(),
+ false
+ )
+ return baseManager
+ }
+
+}
\ No newline at end of file
diff --git a/core/network/src/test/java/com/mifos/core/network/ExampleUnitTest.kt b/core/network/src/test/java/com/mifos/core/network/ExampleUnitTest.kt
new file mode 100644
index 00000000000..dc3064e936e
--- /dev/null
+++ b/core/network/src/test/java/com/mifos/core/network/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.mifos.core.network
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/feature/auth/build.gradle.kts b/feature/auth/build.gradle.kts
index f523933e8a4..473f90ae36c 100644
--- a/feature/auth/build.gradle.kts
+++ b/feature/auth/build.gradle.kts
@@ -47,8 +47,11 @@ android {
dependencies {
-// implementation(project(":mifosng-android"))
implementation(project(":core:designsystem"))
+ implementation(project(":core:network"))
+ implementation(project(":core:datastore"))
+ implementation(project(":core:common"))
+
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
@@ -61,6 +64,10 @@ dependencies {
implementation("com.google.dagger:hilt-android:2.50")
kapt("com.google.dagger:hilt-android-compiler:2.50")
+ //rxjava dependencies
+ implementation("io.reactivex:rxandroid:1.1.0")
+ implementation("io.reactivex:rxjava:1.3.8")
+
// Jetpack Compose
implementation("androidx.compose.material:material:1.6.0")
implementation("androidx.compose.compiler:compiler:1.5.8")
@@ -70,4 +77,14 @@ dependencies {
implementation("androidx.compose.material3:material3:1.1.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
implementation("androidx.compose.material:material-icons-extended:1.6.1")
+
+ // ViewModel utilities for Compose
+ implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
+ implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
+
+ // fineract sdk dependencies
+ implementation("com.github.openMF:mifos-android-sdk-arch:1.06")
+
+ // sdk client
+ implementation("com.github.openMF:fineract-client:2.0.3")
}
\ No newline at end of file
diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/data/repository_imp/LoginRepositoryImp.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/data/repository_imp/LoginRepositoryImp.kt
new file mode 100644
index 00000000000..d864d13e39e
--- /dev/null
+++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/data/repository_imp/LoginRepositoryImp.kt
@@ -0,0 +1,19 @@
+package com.mifos.feature.auth.login.data.repository_imp
+
+import com.mifos.core.network.datamanger.DataManagerAuth
+import com.mifos.feature.auth.login.domain.repository.LoginRepository
+import org.apache.fineract.client.models.PostAuthenticationResponse
+import rx.Observable
+import javax.inject.Inject
+
+/**
+ * Created by Aditya Gupta on 06/08/23.
+ */
+
+class LoginRepositoryImp @Inject constructor(private val dataManagerAuth: DataManagerAuth) :
+ LoginRepository {
+
+ override fun login(username: String, password: String): Observable {
+ return dataManagerAuth.login(username, password)
+ }
+}
\ No newline at end of file
diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/di/ApplicationModule.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/di/ApplicationModule.kt
new file mode 100644
index 00000000000..68ae7f551b8
--- /dev/null
+++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/di/ApplicationModule.kt
@@ -0,0 +1,19 @@
+package com.mifos.feature.auth.login.di
+
+import com.mifos.core.network.datamanger.DataManagerAuth
+import com.mifos.feature.auth.login.data.repository_imp.LoginRepositoryImp
+import com.mifos.feature.auth.login.domain.repository.LoginRepository
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@InstallIn(SingletonComponent::class)
+@Module
+object ApplicationModule {
+
+ @Provides
+ fun providesLoginRepository(dataManagerAuth: DataManagerAuth): LoginRepository =
+ LoginRepositoryImp(dataManagerAuth)
+
+}
\ No newline at end of file
diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/di/UseCaseModule.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/di/UseCaseModule.kt
new file mode 100644
index 00000000000..223f629992e
--- /dev/null
+++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/di/UseCaseModule.kt
@@ -0,0 +1,28 @@
+package com.mifos.feature.auth.login.di
+
+import com.mifos.feature.auth.login.domain.repository.LoginRepository
+import com.mifos.feature.auth.login.domain.use_case.LoginUseCase
+import com.mifos.feature.auth.login.domain.use_case.PasswordValidationUseCase
+import com.mifos.feature.auth.login.domain.use_case.UsernameValidationUseCase
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+object UseCaseModule {
+
+ @Provides
+ fun provideUsernameValidationUseCase(): UsernameValidationUseCase =
+ UsernameValidationUseCase()
+
+ @Provides
+ fun providePasswordValidationUseCase(): PasswordValidationUseCase =
+ PasswordValidationUseCase()
+
+ @Provides
+ fun provideLoginUseCase(loginRepository: LoginRepository): LoginUseCase =
+ LoginUseCase(loginRepository)
+
+}
\ No newline at end of file
diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/model/ValidationResult.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/model/ValidationResult.kt
new file mode 100644
index 00000000000..933cd1801be
--- /dev/null
+++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/model/ValidationResult.kt
@@ -0,0 +1,12 @@
+package com.mifos.feature.auth.login.domain.model
+
+/**
+ * Created by Aditya Gupta on 11/02/24.
+ */
+
+data class ValidationResult(
+
+ val success: Boolean,
+
+ val message: Int? = null
+)
\ No newline at end of file
diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/repository/LoginRepository.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/repository/LoginRepository.kt
new file mode 100644
index 00000000000..d870d114a5d
--- /dev/null
+++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/repository/LoginRepository.kt
@@ -0,0 +1,14 @@
+package com.mifos.feature.auth.login.domain.repository
+
+import org.apache.fineract.client.models.PostAuthenticationResponse
+import rx.Observable
+
+/**
+ * Created by Aditya Gupta on 06/08/23.
+ */
+
+interface LoginRepository {
+
+ fun login(username: String, password: String): Observable
+
+}
\ No newline at end of file
diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/LoginUseCase.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/LoginUseCase.kt
new file mode 100644
index 00000000000..4d2b7414a46
--- /dev/null
+++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/LoginUseCase.kt
@@ -0,0 +1,54 @@
+package com.mifos.feature.auth.login.domain.use_case
+
+import com.mifos.core.common.utils.Resource
+import com.mifos.feature.auth.login.domain.repository.LoginRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.suspendCancellableCoroutine
+import org.apache.fineract.client.models.PostAuthenticationResponse
+import rx.Subscriber
+import rx.android.schedulers.AndroidSchedulers
+import rx.schedulers.Schedulers
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+
+/**
+ * Created by Aditya Gupta on 11/02/24.
+ */
+
+class LoginUseCase(private val loginRepository: LoginRepository) {
+
+ operator fun invoke(
+ username: String,
+ password: String
+ ): Flow> = flow {
+ try {
+ emit(Resource.Loading())
+ val result = login(username, password)
+ emit(result)
+ } catch (e: Exception) {
+ emit(Resource.Error(e.message.toString()))
+ }
+ }
+
+ suspend fun login(username: String, password: String): Resource {
+ return suspendCancellableCoroutine { continuation ->
+ loginRepository.login(username, password)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(Schedulers.io())
+ .subscribe(object : Subscriber() {
+ override fun onNext(user: PostAuthenticationResponse) {
+ continuation.resume(Resource.Success(user))
+ }
+
+ override fun onCompleted() {
+ // No operation needed
+ }
+
+ override fun onError(e: Throwable) {
+ continuation.resumeWithException(e)
+ }
+ })
+ }
+ }
+}
diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/PasswordValidationUseCase.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/PasswordValidationUseCase.kt
new file mode 100644
index 00000000000..a3815c300f5
--- /dev/null
+++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/PasswordValidationUseCase.kt
@@ -0,0 +1,28 @@
+package com.mifos.feature.auth.login.domain.use_case
+
+import com.mifos.feature.auth.R
+import com.mifos.feature.auth.login.domain.model.ValidationResult
+
+/**
+ * Created by Aditya Gupta on 11/02/24.
+ */
+
+class PasswordValidationUseCase {
+
+ operator fun invoke(password: String): ValidationResult {
+
+ if (password.isEmpty()) {
+ return ValidationResult(
+ success = false,
+ R.string.feature_auth_enter_credentials
+ )
+ } else if (password.length < 6) {
+ return ValidationResult(
+ success = false,
+ R.string.feature_error_password_length
+ )
+ }
+ return ValidationResult(success = true)
+ }
+
+}
\ No newline at end of file
diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/UsernameValidationUseCase.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/UsernameValidationUseCase.kt
new file mode 100644
index 00000000000..3f846bac05c
--- /dev/null
+++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/domain/use_case/UsernameValidationUseCase.kt
@@ -0,0 +1,27 @@
+package com.mifos.feature.auth.login.domain.use_case
+
+import com.mifos.feature.auth.R
+import com.mifos.feature.auth.login.domain.model.ValidationResult
+
+/**
+ * Created by Aditya Gupta on 11/02/24.
+ */
+
+class UsernameValidationUseCase {
+
+ operator fun invoke(username: String): ValidationResult {
+ if (username.isEmpty()) {
+ return ValidationResult(
+ success = false,
+ R.string.feature_auth_enter_credentials
+ )
+ } else if (username.length < 5) {
+ return ValidationResult(
+ success = false,
+ R.string.feature_error_username_length
+ )
+ }
+ return ValidationResult(success = true)
+ }
+
+}
\ No newline at end of file
diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/LoginScreen.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginScreen.kt
similarity index 60%
rename from feature/auth/src/main/java/com/mifos/feature/auth/login/LoginScreen.kt
rename to feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginScreen.kt
index 21c79160ceb..585277d26bb 100644
--- a/feature/auth/src/main/java/com/mifos/feature/auth/login/LoginScreen.kt
+++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginScreen.kt
@@ -1,4 +1,4 @@
-package com.mifos.feature.auth.login
+package com.mifos.feature.auth.login.presentation
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
@@ -12,17 +12,24 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Error
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -31,6 +38,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontStyle
@@ -42,15 +50,34 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import androidx.hilt.navigation.compose.hiltViewModel
import com.mifos.core.designsystem.component.MifosAndroidClientIcon
import com.mifos.core.designsystem.component.MifosOutlinedTextField
import com.mifos.core.designsystem.theme.BluePrimary
import com.mifos.core.designsystem.theme.BluePrimaryDark
import com.mifos.core.designsystem.theme.DarkGrey
+import com.mifos.core.designsystem.theme.White
import com.mifos.feature.auth.R
+/**
+ * Created by Aditya Gupta on 11/02/24.
+ */
+
@Composable
-fun LoginScreen() {
+fun LoginScreen(
+ homeIntent: () -> Unit,
+ passcodeIntent: () -> Unit
+) {
+
+ val loginViewModel: LoginViewModel = hiltViewModel()
+ val state = loginViewModel.loginUiState.collectAsState().value
+ val context = LocalContext.current
+
+ val snackbarHostState = remember { SnackbarHostState() }
+
+ val showDialog = rememberSaveable { mutableStateOf(false) }
var userName by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(
@@ -64,12 +91,46 @@ fun LoginScreen() {
}
var passwordVisibility: Boolean by remember { mutableStateOf(false) }
+ val usernameError: MutableState = remember { mutableStateOf(null) }
+ val passwordError: MutableState = remember { mutableStateOf(null) }
+
+ when (state) {
+ is LoginUiState.Empty -> {}
+
+ is LoginUiState.ShowError -> {
+ showDialog.value = false
+ LaunchedEffect(key1 = state.message) {
+ snackbarHostState.showSnackbar(message = context.getString(state.message))
+ }
+ }
+
+ is LoginUiState.ShowProgress -> {
+ showDialog.value = true
+ }
+
+ is LoginUiState.ShowValidationError -> {
+ usernameError.value = state.usernameError
+ passwordError.value = state.passwordError
+ }
+
+ LoginUiState.HomeActivityIntent -> {
+ showDialog.value = false
+ homeIntent()
+ }
+
+ LoginUiState.PassCodeActivityIntent -> {
+ showDialog.value = false
+ passcodeIntent()
+ }
+ }
+
Scaffold(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
- containerColor = Color.White
+ containerColor = Color.White,
+ snackbarHost = { SnackbarHost(snackbarHostState) }
) {
Column(
modifier = Modifier
@@ -106,7 +167,12 @@ fun LoginScreen() {
},
icon = Icons.Filled.Person,
label = R.string.feature_auth_username,
- supportingText = null
+ error = usernameError.value,
+ trailingIcon = {
+ if (usernameError.value != null) {
+ Icon(imageVector = Icons.Filled.Error, contentDescription = null)
+ }
+ }
)
Spacer(modifier = Modifier.height(6.dp))
@@ -117,23 +183,27 @@ fun LoginScreen() {
password = value
},
visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
- trailingIcon = {
- val image = if (passwordVisibility)
- Icons.Filled.Visibility
- else Icons.Filled.VisibilityOff
- IconButton(onClick = { passwordVisibility = !passwordVisibility }) {
- Icon(imageVector = image, null)
- }
- },
icon = Icons.Filled.Lock,
label = R.string.feature_auth_password,
- supportingText = null
+ error = passwordError.value,
+ trailingIcon = {
+ if (passwordError.value == null) {
+ val image = if (passwordVisibility)
+ Icons.Filled.Visibility
+ else Icons.Filled.VisibilityOff
+ IconButton(onClick = { passwordVisibility = !passwordVisibility }) {
+ Icon(imageVector = image, null)
+ }
+ } else {
+ Icon(imageVector = Icons.Filled.Error, contentDescription = null)
+ }
+ }
)
Spacer(modifier = Modifier.height(8.dp))
Button(
- onClick = { },
+ onClick = { loginViewModel.validateUserInputs(userName.text, password.text) },
modifier = Modifier
.fillMaxWidth()
.heightIn(44.dp)
@@ -146,11 +216,22 @@ fun LoginScreen() {
Text(text = "Login", fontSize = 16.sp)
}
}
+ if (showDialog.value) {
+ Dialog(
+ onDismissRequest = { showDialog.value },
+ properties = DialogProperties(
+ dismissOnBackPress = false,
+ dismissOnClickOutside = false
+ )
+ ) {
+ CircularProgressIndicator(color = White)
+ }
+ }
}
}
@Preview(showSystemUi = true, device = "id:pixel_7")
@Composable
fun LoginScreenPreview() {
- LoginScreen()
+ LoginScreen({}, {})
}
\ No newline at end of file
diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginUiState.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginUiState.kt
new file mode 100644
index 00000000000..fdb409b0c37
--- /dev/null
+++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginUiState.kt
@@ -0,0 +1,22 @@
+package com.mifos.feature.auth.login.presentation
+
+/**
+ * Created by Aditya Gupta on 06/08/23.
+ */
+
+sealed class LoginUiState {
+
+ data object Empty : LoginUiState()
+
+ data object ShowProgress : LoginUiState()
+
+ data class ShowError(val message: Int) : LoginUiState()
+
+ data class ShowValidationError(val usernameError: Int? = null, val passwordError: Int? = null) :
+ LoginUiState()
+
+ data object HomeActivityIntent : LoginUiState()
+
+ data object PassCodeActivityIntent : LoginUiState()
+
+}
\ No newline at end of file
diff --git a/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginViewModel.kt b/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginViewModel.kt
new file mode 100644
index 00000000000..6a84a078f67
--- /dev/null
+++ b/feature/auth/src/main/java/com/mifos/feature/auth/login/presentation/LoginViewModel.kt
@@ -0,0 +1,133 @@
+package com.mifos.feature.auth.login.presentation
+
+import android.content.Context
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.mifos.core.common.utils.BaseUrl
+import com.mifos.core.common.utils.Network
+import com.mifos.core.common.utils.Resource
+import com.mifos.core.datastore.PrefManager
+import com.mifos.core.network.di.BaseApiManagerQualifier
+import com.mifos.feature.auth.R
+import com.mifos.feature.auth.login.domain.use_case.LoginUseCase
+import com.mifos.feature.auth.login.domain.use_case.PasswordValidationUseCase
+import com.mifos.feature.auth.login.domain.use_case.UsernameValidationUseCase
+import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import org.apache.fineract.client.models.PostAuthenticationResponse
+import org.mifos.core.apimanager.BaseApiManager
+import javax.inject.Inject
+
+/**
+ * Created by Aditya Gupta on 06/08/23.
+ */
+
+@HiltViewModel
+class LoginViewModel @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val prefManager: PrefManager,
+ private val usernameValidationUseCase: UsernameValidationUseCase,
+ private val passwordValidationUseCase: PasswordValidationUseCase,
+ @BaseApiManagerQualifier private val baseApiManager: BaseApiManager,
+ private val loginUseCase: LoginUseCase
+) :
+ ViewModel() {
+
+ private val _loginUiState = MutableStateFlow(LoginUiState.Empty)
+ val loginUiState = _loginUiState.asStateFlow()
+
+
+ fun validateUserInputs(username: String, password: String) {
+
+ val usernameValidationResult = usernameValidationUseCase(username)
+ val passwordValidationResult = passwordValidationUseCase(password)
+
+ val hasError =
+ listOf(usernameValidationResult, passwordValidationResult).any { !it.success }
+
+ if (hasError) {
+ _loginUiState.value = LoginUiState.ShowValidationError(
+ usernameValidationResult.message,
+ passwordValidationResult.message
+ )
+ return
+ }
+ viewModelScope.launch {
+ setupPrefManger(username, password)
+ }
+ }
+
+ private fun setupPrefManger(username: String, password: String) {
+
+ prefManager.setTenant(BaseUrl.TENANT)
+ // Saving InstanceURL for next usages
+ prefManager.setInstanceUrl(BaseUrl.PROTOCOL_HTTPS + BaseUrl.API_ENDPOINT + BaseUrl.API_PATH)
+ // Saving domain name
+ prefManager.setInstanceDomain(BaseUrl.API_ENDPOINT)
+ // Saving port
+ prefManager.setPort(BaseUrl.PORT)
+ // Updating Services
+ baseApiManager.createService(
+ username,
+ password,
+ prefManager.getInstanceUrl(),
+ prefManager.getTenant(),
+ true
+ )
+ if (Network.isOnline(context)) {
+ login(username, password)
+ } else {
+ _loginUiState.value =
+ LoginUiState.ShowError(R.string.feature_error_not_connected_internet)
+ }
+ }
+
+
+ fun login(username: String, password: String) {
+ viewModelScope.launch(Dispatchers.IO) {
+ loginUseCase(username, password).collect { result ->
+ when (result) {
+ is Resource.Error -> {
+ _loginUiState.value =
+ LoginUiState.ShowError(R.string.feature_error_login_failed)
+ }
+
+ is Resource.Loading -> {
+ _loginUiState.value = LoginUiState.ShowProgress
+ }
+
+ is Resource.Success -> {
+ result.data?.let { onLoginSuccessful(it, username, password) }
+ }
+ }
+ }
+ }
+ }
+
+
+ private fun onLoginSuccessful(
+ user: PostAuthenticationResponse,
+ username: String,
+ password: String
+ ) {
+ // Saving username password
+ prefManager.usernamePassword = Pair(username, password)
+ // Saving userID
+ prefManager.setUserId(user.userId!!.toInt())
+ // Saving user's token
+ prefManager.saveToken("Basic " + user.base64EncodedAuthenticationKey)
+ // Saving user
+ prefManager.savePostAuthenticationResponse(user)
+
+ if (prefManager.getPassCodeStatus()) {
+ _loginUiState.value = LoginUiState.HomeActivityIntent
+ } else {
+ _loginUiState.value = LoginUiState.PassCodeActivityIntent
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/feature/auth/src/main/res/values/strings.xml b/feature/auth/src/main/res/values/strings.xml
index 456306a9a50..510eeb5230f 100644
--- a/feature/auth/src/main/res/values/strings.xml
+++ b/feature/auth/src/main/res/values/strings.xml
@@ -4,4 +4,9 @@
Please enter your credentials
Password
Username
+ Please Enter the Credentials
+ Invalid username length
+ Invalid password length
+ No Internet Connection
+ Login Failed, Please Try Again Later.
\ No newline at end of file
diff --git a/mifosng-android/build.gradle.kts b/mifosng-android/build.gradle.kts
index 5d149042bc2..9e5378b9a44 100644
--- a/mifosng-android/build.gradle.kts
+++ b/mifosng-android/build.gradle.kts
@@ -246,4 +246,8 @@ dependencies {
implementation("androidx.compose.material3:material3:1.1.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
implementation("androidx.compose.material:material-icons-extended:1.6.1")
+
+ // ViewModel utilities for Compose
+ implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
+ implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
}
\ No newline at end of file
diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/activity/login/LoginActivity.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/activity/login/LoginActivity.kt
index abf277afa39..eca155bbb71 100644
--- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/activity/login/LoginActivity.kt
+++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/activity/login/LoginActivity.kt
@@ -6,224 +6,33 @@ package com.mifos.mifosxdroid.activity.login
import android.content.Intent
import android.os.Bundle
-import android.text.Editable
-import android.text.InputType
-import android.text.TextWatcher
-import android.view.KeyEvent
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import android.view.inputmethod.EditorInfo
-import android.widget.Toast
-import androidx.core.content.ContextCompat
-import androidx.lifecycle.ViewModelProvider
-import com.mifos.api.BaseApiManager
+import androidx.activity.compose.setContent
+import com.mifos.feature.auth.login.presentation.LoginScreen
import com.mifos.mifosxdroid.activity.home.HomeActivity
-import com.mifos.mifosxdroid.R
import com.mifos.mifosxdroid.core.MifosBaseActivity
-import com.mifos.mifosxdroid.core.util.Toaster
-import com.mifos.mifosxdroid.databinding.ActivityLoginBinding
import com.mifos.mifosxdroid.passcode.PassCodeActivity
import com.mifos.utils.Constants
-import com.mifos.utils.Network
-import com.mifos.utils.PrefManager
-import com.mifos.utils.PrefManager.savePostAuthenticationResponse
-import com.mifos.utils.PrefManager.saveToken
-import com.mifos.utils.ValidationUtil
import dagger.hilt.android.AndroidEntryPoint
-import org.apache.fineract.client.models.PostAuthenticationResponse
-import javax.inject.Inject
/**
* Created by ishankhanna on 08/02/14.
*/
@AndroidEntryPoint
-class LoginActivity : MifosBaseActivity(){
-
- private lateinit var binding: ActivityLoginBinding
-
- private lateinit var viewModel: LoginViewModel
-
- @Inject
- lateinit var baseApiManager: org.mifos.core.apimanager.BaseApiManager
-
- private lateinit var username: String
- private lateinit var instanceURL: String
- private lateinit var password: String
- private lateinit var domain: String
- private var isValidUrl = false
- private val urlWatcher: TextWatcher = object : TextWatcher {
- override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun afterTextChanged(editable: Editable) {
- val port = if (binding.etInstancePort.editableText.toString()
- .isEmpty()
- ) null else Integer.valueOf(binding.etInstancePort.editableText.toString())
- instanceURL = ValidationUtil.getInstanceUrl(binding.etInstanceURL.text.toString(), port)
- isValidUrl = ValidationUtil.isValidUrl(instanceURL)
- binding.tvConstructedInstanceUrl.text = instanceURL
- domain = binding.etInstanceURL.editableText.toString()
- if (domain.isEmpty() || domain.contains(" ")) {
- isValidUrl = false
- }
- binding.tvConstructedInstanceUrl.setTextColor(
- if (isValidUrl) ContextCompat.getColor(
- applicationContext, R.color.green_light
- ) else ContextCompat.getColor(
- applicationContext, R.color.red_light
- )
- )
- }
- }
+class LoginActivity : MifosBaseActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = ActivityLoginBinding.inflate(layoutInflater)
- title = null
- setContentView(binding.root)
- binding.etInstancePort.inputType = InputType.TYPE_CLASS_NUMBER
- if (PrefManager.getPort() != "80") binding.etInstancePort.setText(PrefManager.getPort())
- binding.etInstanceURL.setText(PrefManager.getInstanceDomain())
- binding.etInstanceURL.addTextChangedListener(urlWatcher)
- binding.etInstancePort.addTextChangedListener(urlWatcher)
- urlWatcher.afterTextChanged(Editable.Factory.getInstance().newEditable(""))
-
- viewModel = ViewModelProvider(this)[LoginViewModel::class.java]
-
- binding.btLogin.setOnClickListener {
- hideKeyboard(binding.btLogin)
- login()
- }
-
- binding.etPassword.setOnEditorActionListener { _, actionId, keyEvent ->
- if (actionId == EditorInfo.IME_ACTION_DONE || (keyEvent != null && keyEvent.keyCode == KeyEvent.KEYCODE_ENTER && keyEvent.action == KeyEvent.ACTION_DOWN)) {
- login()
- return@setOnEditorActionListener true
- }
- return@setOnEditorActionListener false
- }
- viewModel.loginUiState.observe(this){
- when(it) {
- is LoginUiState.ShowProgress -> showProgressbar(it.state)
- is LoginUiState.ShowLoginSuccessful -> {
- hideProgress()
- onLoginSuccessful(it.user)
- }
- is LoginUiState.ShowError -> {
- hideProgress()
- onLoginError(it.message)
- }
- }
- }
- }
-
- private fun validateUserInputs(): Boolean {
- domain = binding.etInstanceURL.editableText.toString()
- if (domain.isEmpty() || domain.contains(" ")) {
- showToastMessage(getString(R.string.error_invalid_url))
- return false
- }
- if (!isValidUrl) {
- showToastMessage(getString(R.string.error_invalid_connection))
- return false
+ setContent {
+ LoginScreen(homeIntent = {
+ startActivity(Intent(this, HomeActivity::class.java))
+ finish()
+ }, passcodeIntent = {
+ val intent = Intent(this, PassCodeActivity::class.java)
+ intent.putExtra(Constants.INTIAL_LOGIN, true)
+ startActivity(intent)
+ finish()
+ })
}
- username = binding.etUsername.editableText.toString()
- password = binding.etPassword.editableText.toString()
- if (username.isEmpty() || password.isEmpty()) {
- showToastMessage(getString(R.string.error_enter_credentials))
- return false
- } else {
- if (username.length < 5) {
- showToastMessage(getString(R.string.error_username_length))
- return false
- }
- if (password.length < 6) {
- showToastMessage(getString(R.string.error_password_length))
- return false
- }
- }
- return true
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- val inflater = menuInflater
- inflater.inflate(R.menu.menu_login, menu)
- return true
}
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- // Handle item selection
- return when (item.itemId) {
- R.id.mItem_connection_settings -> {
- binding.llConnectionSettings.visibility =
- if (binding.llConnectionSettings.visibility == View.VISIBLE) View.GONE else View.VISIBLE
- true
- }
-
- else -> super.onOptionsItemSelected(item)
- }
- }
-
- private fun showToastMessage(message: String) {
- Toaster.show(findViewById(android.R.id.content), message, Toaster.LONG)
- }
-
- private fun onLoginSuccessful(user: PostAuthenticationResponse) {
-
- PrefManager.usernamePassword = Pair(username,password)
- // Saving userID
- PrefManager.setUserId(user.userId!!.toInt())
- // Saving user's token
- saveToken("Basic " + user.base64EncodedAuthenticationKey)
- // Saving user
- savePostAuthenticationResponse(user)
- Toast.makeText(
- this, getString(R.string.toast_welcome) + " " + user.username, Toast.LENGTH_SHORT
- ).show()
- if (PrefManager.getPassCodeStatus()) {
- startActivity(Intent(this, HomeActivity::class.java))
- } else {
- val intent = Intent(this, PassCodeActivity::class.java)
- intent.putExtra(Constants.INTIAL_LOGIN, true)
- startActivity(intent)
- }
- finish()
- }
-
- private fun onLoginError(errorMessage: String) {
- showToastMessage(errorMessage)
- }
-
- private fun showProgressbar(show: Boolean) {
- if (show) {
- showProgress(getString(R.string.logging_in))
- } else {
- hideProgress()
- }
- }
-
-
- private fun login() {
- if (!validateUserInputs()) {
- return
- }
- // Saving tenant
- PrefManager.setTenant(binding.etTenantIdentifier.editableText.toString())
- // Saving InstanceURL for next usages
- PrefManager.setInstanceUrl(instanceURL)
- // Saving domain name
- PrefManager.setInstanceDomain(binding.etInstanceURL.editableText.toString())
- // Saving port
- PrefManager.setPort(binding.etInstancePort.editableText.toString())
- // Updating Services
- BaseApiManager.createService()
- baseApiManager.createService(username,password,PrefManager.getInstanceUrl(),PrefManager.getTenant(),true)
- if (Network.isOnline(this)) {
- viewModel.login(username, password)
- } else {
- showToastMessage(getString(R.string.error_not_connected_internet))
- }
- }
-
}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 0e23a5076a1..e69a31e6f03 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -23,3 +23,5 @@ include(":core:designsystem")
include(":core:datastore")
include(":core:common")
include(":feature:auth")
+include(":core:network")
+include(":core:data")