diff --git a/feature/standing-instruction/.gitignore b/feature/standing-instruction/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/feature/standing-instruction/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/standing-instruction/build.gradle.kts b/feature/standing-instruction/build.gradle.kts
new file mode 100644
index 000000000..6469c2a1c
--- /dev/null
+++ b/feature/standing-instruction/build.gradle.kts
@@ -0,0 +1,12 @@
+plugins {
+ alias(libs.plugins.mifospay.android.feature)
+ alias(libs.plugins.mifospay.android.library.compose)
+}
+
+android {
+ namespace = "org.mifospay.feature.standing.instruction"
+}
+
+dependencies {
+ implementation(projects.core.data)
+}
\ No newline at end of file
diff --git a/feature/standing-instruction/consumer-rules.pro b/feature/standing-instruction/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/feature/standing-instruction/proguard-rules.pro b/feature/standing-instruction/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/feature/standing-instruction/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/feature/standing-instruction/src/androidTest/java/org/mifospay/feature/standing/instruction/ExampleInstrumentedTest.kt b/feature/standing-instruction/src/androidTest/java/org/mifospay/feature/standing/instruction/ExampleInstrumentedTest.kt
new file mode 100644
index 000000000..8d857f5c3
--- /dev/null
+++ b/feature/standing-instruction/src/androidTest/java/org/mifospay/feature/standing/instruction/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package org.mifospay.feature.standing.instruction
+
+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("org.mifospay.feature.standing.instruction.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/feature/standing-instruction/src/main/AndroidManifest.xml b/feature/standing-instruction/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..a5918e68a
--- /dev/null
+++ b/feature/standing-instruction/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/SIContent.kt b/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/SIContent.kt
new file mode 100644
index 000000000..62944986c
--- /dev/null
+++ b/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/SIContent.kt
@@ -0,0 +1,65 @@
+package org.mifospay.feature.standing.instruction
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Divider
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@Composable
+fun SIContent(
+ fromClientName: String,
+ toClientName: String,
+ validTill: String,
+ amount: String
+) {
+ Column(modifier = Modifier.padding(10.dp)) {
+ Text(
+ text = fromClientName,
+ color = Color.Black,
+ fontSize = 16.sp,
+ modifier = Modifier.padding(bottom = 20.dp)
+ )
+
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
+ Text(
+ text = toClientName,
+ color = Color.Black,
+ fontSize = 16.sp,
+ modifier = Modifier.padding(bottom = 4.dp)
+ )
+ Text(
+ text = amount,
+ color = Color.Black,
+ fontSize = 16.sp,
+ modifier = Modifier.padding(end = 8.dp, bottom = 8.dp)
+ )
+ }
+
+ Text(
+ text = validTill,
+ color = Color.Gray,
+ modifier = Modifier.padding(bottom = 4.dp)
+ )
+
+ Divider(
+ color = Color.Black,
+ thickness = 1.dp,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun SIContentPreview() {
+ SIContent("From Client", "To Client", "Date", "Amount")
+}
\ No newline at end of file
diff --git a/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/StandingInstructionScreen.kt b/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/StandingInstructionScreen.kt
new file mode 100644
index 000000000..4d795c78e
--- /dev/null
+++ b/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/StandingInstructionScreen.kt
@@ -0,0 +1,162 @@
+package org.mifospay.feature.standing.instruction
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Info
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.mifospay.core.model.entity.accounts.savings.SavingAccount
+import com.mifospay.core.model.entity.client.Client
+import com.mifospay.core.model.entity.client.Status
+import com.mifospay.core.model.entity.standinginstruction.StandingInstruction
+import org.mifospay.core.designsystem.component.MifosLoadingWheel
+import org.mifospay.core.designsystem.icon.MifosIcons
+import org.mifospay.core.ui.EmptyContentScreen
+
+@Composable
+fun StandingInstructionsScreen(
+ viewModel: StandingInstructionViewModel = hiltViewModel(),
+ onNewSI: () -> Unit
+) {
+ val standingInstructionsUiState by viewModel.standingInstructionsUiState.collectAsStateWithLifecycle()
+ StandingInstructionScreen(
+ standingInstructionsUiState = standingInstructionsUiState,
+ onNewSI = onNewSI
+ )
+}
+
+@Composable
+fun StandingInstructionScreen(
+ standingInstructionsUiState: StandingInstructionsUiState,
+ onNewSI: () -> Unit
+) = when (standingInstructionsUiState) {
+ StandingInstructionsUiState.Empty -> {
+ EmptyContentScreen(
+ modifier = Modifier,
+ title = stringResource(id = R.string.feature_standing_instruction_error_oops),
+ subTitle = stringResource(id = R.string.feature_standing_instruction_empty_standing_instructions),
+ iconTint = Color.Black,
+ iconImageVector = Icons.Rounded.Info
+ )
+ }
+
+ is StandingInstructionsUiState.Error -> {
+ EmptyContentScreen(
+ modifier = Modifier,
+ title = stringResource(id = R.string.feature_standing_instruction_error_oops),
+ subTitle = stringResource(id = R.string.feature_standing_instruction_error_fetching_si_list),
+ iconTint = Color.Black,
+ iconImageVector = Icons.Rounded.Info
+ )
+ }
+
+ StandingInstructionsUiState.Loading -> {
+ MifosLoadingWheel(
+ modifier = Modifier.fillMaxWidth(),
+ contentDesc = stringResource(R.string.feature_standing_instruction_loading)
+ )
+ }
+
+ is StandingInstructionsUiState.StandingInstructionList -> {
+ Scaffold(
+ modifier = Modifier,
+ floatingActionButton = {
+ FloatingActionButton(
+ onClick = { onNewSI.invoke() },
+ ) {
+ Icon(
+ painter = rememberVectorPainter(MifosIcons.Add),
+ contentDescription = null,
+ tint = Color.Black
+ )
+ }
+ }
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(it)
+ ) {
+ LazyColumn(modifier = Modifier.fillMaxSize()) {
+ items(standingInstructionsUiState.standingInstructionList) { items ->
+ SIContent(
+ fromClientName = items.fromClient.displayName.toString(),
+ toClientName = items.toClient.displayName.toString(),
+ validTill = items.validTill.toString(),
+ amount = items.amount.toString(),
+ )
+ }
+ }
+ }
+ }
+
+ }
+}
+
+class StandingInstructionPreviewParameterProvider : PreviewParameterProvider {
+ override val values: Sequence = sequenceOf(
+ StandingInstructionsUiState.Loading,
+ StandingInstructionsUiState.Empty,
+ StandingInstructionsUiState.Error("Error Screen"),
+ StandingInstructionsUiState.StandingInstructionList(
+ standingInstructionList = listOf(
+ StandingInstruction(
+ id = 1,
+ name = "Instruction 1",
+ fromClient = Client(displayName = "Alice"),
+ fromAccount = SavingAccount(),
+ toClient = Client(displayName = "Bob"),
+ toAccount = SavingAccount(),
+ status = Status(),
+ amount = 100.0,
+ validFrom = listOf(2022, 1, 1),
+ validTill = listOf(2024, 12, 31),
+ recurrenceInterval = 30,
+ recurrenceOnMonthDay = listOf(1)
+ ),
+ StandingInstruction(
+ id = 2,
+ name = "Instruction 2",
+ fromClient = Client(displayName = "Charlie"),
+ fromAccount = SavingAccount(),
+ toClient = Client(displayName = "Dave"),
+ toAccount = SavingAccount(),
+ status = Status(),
+ amount = 200.0,
+ validFrom = listOf(2022, 1, 1),
+ validTill = listOf(2024, 12, 31),
+ recurrenceInterval = 30,
+ recurrenceOnMonthDay = listOf(1)
+ )
+ )
+ )
+ )
+}
+
+@Preview(showBackground = true)
+@Composable
+fun StandingInstructionsScreenPreview(
+ @PreviewParameter(StandingInstructionPreviewParameterProvider::class) standingInstructionsUiState: StandingInstructionsUiState
+) {
+ StandingInstructionScreen(
+ standingInstructionsUiState = standingInstructionsUiState,
+ onNewSI = {}
+ )
+}
\ No newline at end of file
diff --git a/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/StandingInstructionViewModel.kt b/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/StandingInstructionViewModel.kt
new file mode 100644
index 000000000..5d9f6c3ca
--- /dev/null
+++ b/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/StandingInstructionViewModel.kt
@@ -0,0 +1,59 @@
+package org.mifospay.feature.standing.instruction
+
+import androidx.lifecycle.ViewModel
+import com.mifospay.core.model.entity.standinginstruction.StandingInstruction
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import org.mifospay.core.data.base.UseCase
+import org.mifospay.core.data.base.UseCaseHandler
+import org.mifospay.core.data.domain.usecase.standinginstruction.GetAllStandingInstructions
+import org.mifospay.core.data.repository.local.LocalRepository
+import javax.inject.Inject
+
+@HiltViewModel
+class StandingInstructionViewModel @Inject constructor(
+ private val mUseCaseHandler: UseCaseHandler,
+ private val localRepository: LocalRepository,
+ private val getAllStandingInstructions: GetAllStandingInstructions
+) : ViewModel() {
+
+ private val _standingInstructionsUiState =
+ MutableStateFlow(StandingInstructionsUiState.Loading)
+ val standingInstructionsUiState: StateFlow =
+ _standingInstructionsUiState
+
+ private fun getAllSI() {
+ val client = localRepository.clientDetails
+ _standingInstructionsUiState.value = StandingInstructionsUiState.Loading
+ mUseCaseHandler.execute(getAllStandingInstructions,
+ GetAllStandingInstructions.RequestValues(client.clientId), object :
+ UseCase.UseCaseCallback {
+
+ override fun onSuccess(response: GetAllStandingInstructions.ResponseValue) {
+ if (response.standingInstructionsList.isEmpty()) {
+ _standingInstructionsUiState.value = StandingInstructionsUiState.Empty
+ } else {
+ _standingInstructionsUiState.value =
+ StandingInstructionsUiState.StandingInstructionList(response.standingInstructionsList)
+ }
+ }
+
+ override fun onError(message: String) {
+ _standingInstructionsUiState.value = StandingInstructionsUiState.Error(message)
+ }
+ })
+ }
+
+ init {
+ getAllSI()
+ }
+}
+
+sealed class StandingInstructionsUiState {
+ data object Loading : StandingInstructionsUiState()
+ data object Empty : StandingInstructionsUiState()
+ data class Error(val message: String) : StandingInstructionsUiState()
+ data class StandingInstructionList(val standingInstructionList: List) :
+ StandingInstructionsUiState()
+}
\ No newline at end of file
diff --git a/feature/standing-instruction/src/main/res/values/strings.xml b/feature/standing-instruction/src/main/res/values/strings.xml
new file mode 100644
index 000000000..970ad8ff6
--- /dev/null
+++ b/feature/standing-instruction/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Oops!
+ No standing instructions to show
+ Couldn\'t fetch Standing Instructions. Please, try again.
+ Loading
+
\ No newline at end of file
diff --git a/feature/standing-instruction/src/test/java/org/mifospay/feature/standing/instruction/ExampleUnitTest.kt b/feature/standing-instruction/src/test/java/org/mifospay/feature/standing/instruction/ExampleUnitTest.kt
new file mode 100644
index 000000000..2527f068b
--- /dev/null
+++ b/feature/standing-instruction/src/test/java/org/mifospay/feature/standing/instruction/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package org.mifospay.feature.standing.instruction
+
+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/settings.gradle.kts b/settings.gradle.kts
index a5d6c6939..454b4fd51 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -48,4 +48,5 @@ include(":feature:invoices")
include(":feature:invoices")
include(":feature:settings")
include(":feature:profile")
+include(":feature:standing-instruction")
include(":feature:payments")