diff --git a/core/data/src/main/java/com/mifos/core/data/di/DataModule.kt b/core/data/src/main/java/com/mifos/core/data/di/DataModule.kt index 969dbc0898e..dfb3cf70e7c 100644 --- a/core/data/src/main/java/com/mifos/core/data/di/DataModule.kt +++ b/core/data/src/main/java/com/mifos/core/data/di/DataModule.kt @@ -10,6 +10,7 @@ import com.mifos.core.data.repository.CheckerInboxTasksRepository import com.mifos.core.data.repository.ClientChargeRepository import com.mifos.core.data.repository.ClientIdentifiersRepository import com.mifos.core.data.repository.CreateNewCenterRepository +import com.mifos.core.data.repository.DataTableDataRepository import com.mifos.core.data.repository.DocumentListRepository import com.mifos.core.data.repository.GroupDetailsRepository import com.mifos.core.data.repository.GroupListRepository @@ -33,6 +34,7 @@ import com.mifos.core.data.repository_imp.CheckerInboxTasksRepositoryImp import com.mifos.core.data.repository_imp.ClientChargeRepositoryImp import com.mifos.core.data.repository_imp.ClientIdentifiersRepositoryImp import com.mifos.core.data.repository_imp.CreateNewCenterRepositoryImp +import com.mifos.core.data.repository_imp.DataTableDataRepositoryImp import com.mifos.core.data.repository_imp.DocumentListRepositoryImp import com.mifos.core.data.repository_imp.GroupDetailsRepositoryImp import com.mifos.core.data.repository_imp.GroupListRepositoryImp @@ -126,4 +128,7 @@ abstract class DataModule { @Binds internal abstract fun bindChargeDialogRepository(impl: ChargeDialogRepositoryImp): ChargeDialogRepository + + @Binds + internal abstract fun bindDataTableDataRepository(impl: DataTableDataRepositoryImp): DataTableDataRepository } \ No newline at end of file diff --git a/core/data/src/main/java/com/mifos/core/data/repository/DataTableDataRepository.kt b/core/data/src/main/java/com/mifos/core/data/repository/DataTableDataRepository.kt new file mode 100644 index 00000000000..34298fef0c1 --- /dev/null +++ b/core/data/src/main/java/com/mifos/core/data/repository/DataTableDataRepository.kt @@ -0,0 +1,20 @@ +package com.mifos.core.data.repository + +import com.google.gson.JsonArray +import org.apache.fineract.client.models.DeleteDataTablesDatatableAppTableIdDatatableIdResponse +import rx.Observable + +/** + * Created by Aditya Gupta on 10/08/23. + */ +interface DataTableDataRepository { + + suspend fun getDataTableInfo(table: String, entityId: Int): JsonArray + + fun deleteDataTableEntry( + table: String?, + entity: Int, + rowId: Int + ): Observable + +} \ No newline at end of file diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataRepositoryImp.kt b/core/data/src/main/java/com/mifos/core/data/repository_imp/DataTableDataRepositoryImp.kt similarity index 80% rename from mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataRepositoryImp.kt rename to core/data/src/main/java/com/mifos/core/data/repository_imp/DataTableDataRepositoryImp.kt index 7df95d297d0..d8fa49098d0 100644 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataRepositoryImp.kt +++ b/core/data/src/main/java/com/mifos/core/data/repository_imp/DataTableDataRepositoryImp.kt @@ -1,6 +1,7 @@ -package com.mifos.mifosxdroid.online.datatabledata +package com.mifos.core.data.repository_imp import com.google.gson.JsonArray +import com.mifos.core.data.repository.DataTableDataRepository import com.mifos.core.network.datamanager.DataManagerDataTable import org.apache.fineract.client.models.DeleteDataTablesDatatableAppTableIdDatatableIdResponse import rx.Observable @@ -12,7 +13,7 @@ import javax.inject.Inject class DataTableDataRepositoryImp @Inject constructor(private val dataManagerDataTable: DataManagerDataTable) : DataTableDataRepository { - override fun getDataTableInfo(table: String?, entityId: Int): Observable { + override suspend fun getDataTableInfo(table: String, entityId: Int): JsonArray { return dataManagerDataTable.getDataTableInfo(table, entityId) } @@ -24,5 +25,4 @@ class DataTableDataRepositoryImp @Inject constructor(private val dataManagerData return dataManagerDataTable.deleteDataTableEntry(table, entity, rowId) } - } \ No newline at end of file diff --git a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosScaffold.kt b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosScaffold.kt index f54dc368256..d2b7906c17f 100644 --- a/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosScaffold.kt +++ b/core/designsystem/src/main/java/com/mifos/core/designsystem/component/MifosScaffold.kt @@ -61,7 +61,7 @@ fun MifosScaffold( Text( text = it, style = TextStyle( - fontSize = 12.sp, + fontSize = 24.sp, fontWeight = FontWeight.Medium, fontStyle = FontStyle.Normal ), diff --git a/core/domain/src/main/java/com/mifos/core/domain/use_cases/DeleteDataTableEntryUseCase.kt b/core/domain/src/main/java/com/mifos/core/domain/use_cases/DeleteDataTableEntryUseCase.kt new file mode 100644 index 00000000000..3a993fe5c37 --- /dev/null +++ b/core/domain/src/main/java/com/mifos/core/domain/use_cases/DeleteDataTableEntryUseCase.kt @@ -0,0 +1,45 @@ +package com.mifos.core.domain.use_cases + +import com.mifos.core.common.utils.Resource +import com.mifos.core.data.repository.DataTableDataRepository +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import org.apache.fineract.client.models.DeleteDataTablesDatatableAppTableIdDatatableIdResponse +import rx.Subscriber +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import javax.inject.Inject + +class DeleteDataTableEntryUseCase @Inject constructor(private val repository: DataTableDataRepository) { + + suspend operator fun invoke( + table: String?, + entity: Int, + rowId: Int + ): Flow> = callbackFlow { + try { + trySend(Resource.Loading()) + repository.deleteDataTableEntry(table, entity, rowId) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe(object : + Subscriber() { + override fun onCompleted() {} + + override fun onError(exception: Throwable) { + trySend(Resource.Error(exception.message.toString())) + } + + override fun onNext(response: DeleteDataTablesDatatableAppTableIdDatatableIdResponse) { + trySend(Resource.Success(response)) + } + }) + awaitClose { channel.close() } + + } catch (exception: Exception) { + trySend(Resource.Error(exception.message.toString())) + } + } + +} \ No newline at end of file diff --git a/core/domain/src/main/java/com/mifos/core/domain/use_cases/GetDataTableInfoUseCase.kt b/core/domain/src/main/java/com/mifos/core/domain/use_cases/GetDataTableInfoUseCase.kt new file mode 100644 index 00000000000..b8dc9e5ffba --- /dev/null +++ b/core/domain/src/main/java/com/mifos/core/domain/use_cases/GetDataTableInfoUseCase.kt @@ -0,0 +1,22 @@ +package com.mifos.core.domain.use_cases + +import com.google.gson.JsonArray +import com.mifos.core.common.utils.Resource +import com.mifos.core.data.repository.DataTableDataRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject + +class GetDataTableInfoUseCase @Inject constructor(private val repository: DataTableDataRepository) { + + suspend operator fun invoke(table: String, entityId: Int): Flow> = flow { + try { + emit(Resource.Loading()) + val data = repository.getDataTableInfo(table, entityId) + emit(Resource.Success(data)) + + } catch (exception: Exception) { + emit(Resource.Error(exception.message.toString())) + } + } +} \ No newline at end of file diff --git a/core/network/src/main/java/com/mifos/core/network/datamanager/DataManagerDataTable.kt b/core/network/src/main/java/com/mifos/core/network/datamanager/DataManagerDataTable.kt index f67c684031f..9c9b142aed6 100644 --- a/core/network/src/main/java/com/mifos/core/network/datamanager/DataManagerDataTable.kt +++ b/core/network/src/main/java/com/mifos/core/network/datamanager/DataManagerDataTable.kt @@ -39,7 +39,7 @@ class DataManagerDataTable @Inject constructor( ) } - fun getDataTableInfo(table: String?, entityId: Int): Observable { + suspend fun getDataTableInfo(table: String, entityId: Int): JsonArray { return mBaseApiManager.dataTableApi.getDataOfDataTable(table, entityId) } diff --git a/core/network/src/main/java/com/mifos/core/network/services/DataTableService.kt b/core/network/src/main/java/com/mifos/core/network/services/DataTableService.kt index d578996fe3b..f54b09e1b98 100644 --- a/core/network/src/main/java/com/mifos/core/network/services/DataTableService.kt +++ b/core/network/src/main/java/com/mifos/core/network/services/DataTableService.kt @@ -25,10 +25,10 @@ interface DataTableService { fun getTableOf(@Query("apptable") table: String?): Observable> @GET(APIEndPoint.DATATABLES + "/{dataTableName}/{entityId}/") - fun getDataOfDataTable( - @Path("dataTableName") dataTableName: String?, + suspend fun getDataOfDataTable( + @Path("dataTableName") dataTableName: String, @Path("entityId") entityId: Int - ): Observable + ): JsonArray //TODO Improve Body Implementation with Payload @POST(APIEndPoint.DATATABLES + "/{dataTableName}/{entityId}/") diff --git a/feature/data-table/.gitignore b/feature/data-table/.gitignore new file mode 100644 index 00000000000..42afabfd2ab --- /dev/null +++ b/feature/data-table/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/data-table/build.gradle.kts b/feature/data-table/build.gradle.kts new file mode 100644 index 00000000000..a59c75d6c3c --- /dev/null +++ b/feature/data-table/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + alias(libs.plugins.mifos.android.feature) + alias(libs.plugins.mifos.android.library.compose) + alias(libs.plugins.mifos.android.library.jacoco) +} + +android { + namespace = "com.mifos.feature.data_table" +} + +dependencies { + + implementation(projects.core.data) + implementation(projects.core.domain) + implementation(projects.core.datastore) + + // swipe refresh + implementation(libs.accompanist.permission) + implementation(libs.accompanist.swiperefresh) + + implementation(libs.coil.kt.compose) + implementation(libs.androidx.paging.compose) + + testImplementation(libs.hilt.android.testing) + testImplementation(projects.core.testing) + + androidTestImplementation(projects.core.testing) + + implementation(libs.androidx.material) +} \ No newline at end of file diff --git a/feature/data-table/consumer-rules.pro b/feature/data-table/consumer-rules.pro new file mode 100644 index 00000000000..e69de29bb2d diff --git a/feature/data-table/proguard-rules.pro b/feature/data-table/proguard-rules.pro new file mode 100644 index 00000000000..481bb434814 --- /dev/null +++ b/feature/data-table/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/data-table/src/androidTest/java/com/mifos/feature/data_table/ExampleInstrumentedTest.kt b/feature/data-table/src/androidTest/java/com/mifos/feature/data_table/ExampleInstrumentedTest.kt new file mode 100644 index 00000000000..a92c745615a --- /dev/null +++ b/feature/data-table/src/androidTest/java/com/mifos/feature/data_table/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.mifos.feature.data_table + +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.feature.data_table.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/feature/data-table/src/main/AndroidManifest.xml b/feature/data-table/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..a5918e68abc --- /dev/null +++ b/feature/data-table/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/data-table/src/main/java/com/mifos/feature/data_table/DataTableDataScreen.kt b/feature/data-table/src/main/java/com/mifos/feature/data_table/DataTableDataScreen.kt new file mode 100644 index 00000000000..3a2a99c0bec --- /dev/null +++ b/feature/data-table/src/main/java/com/mifos/feature/data_table/DataTableDataScreen.kt @@ -0,0 +1,398 @@ +@file:OptIn(ExperimentalMaterialApi::class) + +package com.mifos.feature.data_table + +import android.widget.Toast +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +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 +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +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 androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.mifos.core.designsystem.component.MifosCircularProgress +import com.mifos.core.designsystem.component.MifosScaffold +import com.mifos.core.designsystem.component.MifosSweetError +import com.mifos.core.designsystem.icon.MifosIcons +import com.mifos.core.designsystem.theme.Black +import com.mifos.core.designsystem.theme.BlueSecondary +import com.mifos.core.designsystem.theme.DarkGray +import com.mifos.core.designsystem.theme.White +import com.mifos.core.ui.components.MifosEmptyUi + +@Composable +fun DataTableDataScreen( + table: String, + entityId: Int, + onBackPressed: () -> Unit +) { + + val viewmodel: DataTableDataViewModel = hiltViewModel() + val state by viewmodel.tableDataUiState.collectAsStateWithLifecycle() + val isRefreshing by viewmodel.isRefreshing.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + viewmodel.loadDataTableInfo(table, entityId) + } + + DataTableDataScreen( + state = state, + onBackPressed = onBackPressed, + onRetry = { + viewmodel.loadDataTableInfo(table, entityId) + }, + onRefresh = { + viewmodel.refreshDataTableData(table, entityId) + }, + refreshState = isRefreshing, + deleteDataTable = { + viewmodel.deleteDataTableEntry(table, entityId, it) + } + ) + +} + +@Composable +fun DataTableDataScreen( + state: DataTableDataUiState, + onBackPressed: () -> Unit, + onRetry: () -> Unit, + onRefresh: () -> Unit, + refreshState: Boolean, + deleteDataTable: (Int) -> Unit +) { + val snackbarHostState = remember { SnackbarHostState() } + val pullRefreshState = rememberPullRefreshState( + refreshing = refreshState, + onRefresh = onRefresh + ) + var showOptionDialog by rememberSaveable { mutableStateOf(false) } + var deleteDataTableId by rememberSaveable { mutableIntStateOf(0) } + + + if (showOptionDialog) { + SelectOptionsDialog( + onDismissRequest = { showOptionDialog = false }, + deleteDataTable = { + deleteDataTable(deleteDataTableId) + showOptionDialog = false + onRetry() + } + ) + } + + + MifosScaffold( + icon = MifosIcons.arrowBack, + title = stringResource(id = R.string.feature_data_table_title), + onBackPressed = onBackPressed, + actions = { + IconButton(onClick = { + // TODO Implement Create Data Table + }) { + Icon(imageVector = MifosIcons.Add, contentDescription = null) + } + }, + snackbarHostState = snackbarHostState + ) { paddingValues -> + Column(modifier = Modifier.padding(paddingValues)) { + Box(modifier = Modifier.pullRefresh(pullRefreshState)) { + + when (state) { + is DataTableDataUiState.DataTableDeletedSuccessfully -> { + Toast.makeText( + LocalContext.current, + stringResource(id = R.string.feature_data_table_data_table_created_successfully), + Toast.LENGTH_SHORT + ).show() + onBackPressed() + } + + is DataTableDataUiState.DataTableInfo -> { + DataTableDataContent( + jsonElements = state.jsonElements, + onDataClicked = { + showOptionDialog = true + deleteDataTableId = it + } + ) + } + + is DataTableDataUiState.Error -> MifosSweetError(message = stringResource(id = state.message)) { + onRetry() + } + + is DataTableDataUiState.Loading -> MifosCircularProgress() + } + + PullRefreshIndicator( + refreshing = refreshState, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter) + ) + } + } + } +} + +@Composable +fun DataTableDataContent( + jsonElements: JsonArray, + onDataClicked: (Int) -> Unit +) { + + val jsonElementIterator: Iterator = jsonElements.iterator() + var dataList by rememberSaveable { mutableStateOf>(emptyList()) } + + + if (jsonElements.size() == 0) { + MifosEmptyUi( + text = stringResource(id = R.string.feature_data_table_no_data_table_details_to_show), + ) + } else { + + while (jsonElementIterator.hasNext()) { + + val dataTableDataItem = DataTableDataItem() + + val jsonElement: JsonElement = jsonElementIterator.next() + + if (jsonElement.asJsonObject.has("client_id")) { + dataTableDataItem.clientId = jsonElement.asJsonObject.get("client_id").toString() + } + if (jsonElement.asJsonObject.has("id")) { + dataTableDataItem.id = jsonElement.asJsonObject.get("id").toString() + } + dataList = dataList + dataTableDataItem + } + + + + LazyColumn { + items(dataList) { dataItem -> + DataTableDataCardItem( + dataItem = dataItem, + onDataClicked = onDataClicked + ) + } + } + } +} + +@Composable +fun DataTableDataCardItem( + dataItem: DataTableDataItem, + onDataClicked: (Int) -> Unit +) { + OutlinedCard( + modifier = Modifier + .padding(8.dp) + .clickable { + onDataClicked(dataItem.clientId?.toInt() ?: dataItem.id?.toInt() ?: 0) + }, + colors = CardDefaults.cardColors(White) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding( + 16.dp + ), + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(start = 16.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.feature_data_table_client_id), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Normal, + fontStyle = FontStyle.Normal, + color = Black + ) + ) + Text( + modifier = Modifier.weight(1f), + text = dataItem.clientId ?: "-", + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Normal, + fontStyle = FontStyle.Normal, + color = DarkGray + ) + ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp) + ) { + Text( + modifier = Modifier.weight(1f), + text = stringResource(id = R.string.feature_data_table_data_id), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Normal, + fontStyle = FontStyle.Normal, + color = Black + ) + ) + Text( + modifier = Modifier.weight(1f), + text = dataItem.id ?: "-", + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Normal, + fontStyle = FontStyle.Normal, + color = DarkGray + ) + ) + } + } + } + } +} + + +data class DataTableDataItem( + var id: String? = null, + var clientId: String? = null, +) + +class DataTableDataUiStateProvider : PreviewParameterProvider { + + override val values: Sequence + get() = sequenceOf( + DataTableDataUiState.Loading, + DataTableDataUiState.Error(R.string.feature_data_table_failed_to_delete_data_table), + DataTableDataUiState.DataTableInfo(JsonArray()) + ) +} + + +@Composable +fun SelectOptionsDialog( + onDismissRequest: () -> Unit, + deleteDataTable: () -> Unit, +) { + + Dialog( + onDismissRequest = { onDismissRequest() }, + properties = DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = true + ) + ) { + Card( + colors = CardDefaults.cardColors(White), + shape = RoundedCornerShape(20.dp) + ) { + Column( + modifier = Modifier + .padding(30.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(id = R.string.feature_data_table_select_options), + modifier = Modifier.fillMaxWidth(), + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Normal, + fontStyle = FontStyle.Normal + ), + color = Color.Black, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(20.dp)) + + Button( + onClick = { deleteDataTable() }, + colors = ButtonDefaults.buttonColors(BlueSecondary) + ) { + Text( + text = stringResource(id = R.string.feature_data_table_delete_data_table), + modifier = Modifier.fillMaxWidth(), + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Normal, + fontStyle = FontStyle.Normal + ), + color = Color.Black, + textAlign = TextAlign.Center + ) + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun DataTableDataScreenPreview( + @PreviewParameter(DataTableDataUiStateProvider::class) state: DataTableDataUiState +) { + DataTableDataScreen( + state = state, + onBackPressed = {}, + onRetry = {}, + onRefresh = {}, + refreshState = false, + deleteDataTable = {} + ) +} \ No newline at end of file diff --git a/feature/data-table/src/main/java/com/mifos/feature/data_table/DataTableDataUiState.kt b/feature/data-table/src/main/java/com/mifos/feature/data_table/DataTableDataUiState.kt new file mode 100644 index 00000000000..ba2620e971e --- /dev/null +++ b/feature/data-table/src/main/java/com/mifos/feature/data_table/DataTableDataUiState.kt @@ -0,0 +1,17 @@ +package com.mifos.feature.data_table + +import com.google.gson.JsonArray + +/** + * Created by Aditya Gupta on 10/08/23. + */ +sealed class DataTableDataUiState { + + data object Loading : DataTableDataUiState() + + data class Error(val message: Int) : DataTableDataUiState() + + data class DataTableInfo(val jsonElements: JsonArray) : DataTableDataUiState() + + data object DataTableDeletedSuccessfully : DataTableDataUiState() +} \ No newline at end of file diff --git a/feature/data-table/src/main/java/com/mifos/feature/data_table/DataTableDataViewModel.kt b/feature/data-table/src/main/java/com/mifos/feature/data_table/DataTableDataViewModel.kt new file mode 100644 index 00000000000..d0f0b7fa52e --- /dev/null +++ b/feature/data-table/src/main/java/com/mifos/feature/data_table/DataTableDataViewModel.kt @@ -0,0 +1,67 @@ +package com.mifos.feature.data_table + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.gson.JsonArray +import com.mifos.core.common.utils.Resource +import com.mifos.core.domain.use_cases.DeleteDataTableEntryUseCase +import com.mifos.core.domain.use_cases.GetDataTableInfoUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class DataTableDataViewModel @Inject constructor( + private val getDataTableInfoUseCase: GetDataTableInfoUseCase, + private val deleteDataTableEntryUseCase: DeleteDataTableEntryUseCase +) : ViewModel() { + + private val _dataTableDataUiState = + MutableStateFlow(DataTableDataUiState.Loading) + val tableDataUiState = _dataTableDataUiState.asStateFlow() + + // for refresh feature + private val _isRefreshing = MutableStateFlow(false) + val isRefreshing = _isRefreshing.asStateFlow() + + fun refreshDataTableData(table: String, entityId: Int) { + _isRefreshing.value = true + loadDataTableInfo(table = table, entityId = entityId) + _isRefreshing.value = false + } + + fun loadDataTableInfo(table: String, entityId: Int) = + viewModelScope.launch(Dispatchers.IO) { + getDataTableInfoUseCase(table, entityId).collect { result -> + when (result) { + is Resource.Error -> _dataTableDataUiState.value = + DataTableDataUiState.Error(R.string.feature_data_table_failed_to_load_data_table_details) + + is Resource.Loading -> _dataTableDataUiState.value = + DataTableDataUiState.Loading + + is Resource.Success -> _dataTableDataUiState.value = + DataTableDataUiState.DataTableInfo(result.data ?: JsonArray()) + } + } + } + + fun deleteDataTableEntry(table: String, entity: Int, rowId: Int) = + viewModelScope.launch(Dispatchers.IO) { + deleteDataTableEntryUseCase(table, entity, rowId).collect { result -> + when (result) { + is Resource.Error -> _dataTableDataUiState.value = + DataTableDataUiState.Error(R.string.feature_data_table_failed_to_delete_data_table) + + is Resource.Loading -> _dataTableDataUiState.value = + DataTableDataUiState.Loading + + is Resource.Success -> _dataTableDataUiState.value = + DataTableDataUiState.DataTableDeletedSuccessfully + } + } + } +} \ No newline at end of file diff --git a/feature/data-table/src/main/res/values/strings.xml b/feature/data-table/src/main/res/values/strings.xml new file mode 100644 index 00000000000..3c2cd0aa8d0 --- /dev/null +++ b/feature/data-table/src/main/res/values/strings.xml @@ -0,0 +1,13 @@ + + + Data Table + Client ID + Data ID + Select Options + Delete Data Table + Failed to load data table details + Failed to delete data table + No data table details to show + Data table created successfully + + \ No newline at end of file diff --git a/feature/data-table/src/test/java/com/mifos/feature/data_table/ExampleUnitTest.kt b/feature/data-table/src/test/java/com/mifos/feature/data_table/ExampleUnitTest.kt new file mode 100644 index 00000000000..f92c67ec88c --- /dev/null +++ b/feature/data-table/src/test/java/com/mifos/feature/data_table/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.mifos.feature.data_table + +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/mifosng-android/build.gradle.kts b/mifosng-android/build.gradle.kts index 10c79c285e1..394c8d766f7 100644 --- a/mifosng-android/build.gradle.kts +++ b/mifosng-android/build.gradle.kts @@ -141,6 +141,7 @@ dependencies { implementation(projects.feature.activate) implementation(projects.feature.loan) implementation(projects.feature.document) + implementation(projects.feature.dataTable) implementation(projects.core.common) implementation(projects.core.ui) diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/injection/module/RepositoryModule.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/injection/module/RepositoryModule.kt index f3d106432eb..4fb34363ff7 100644 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/injection/module/RepositoryModule.kt +++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/injection/module/RepositoryModule.kt @@ -64,8 +64,6 @@ import com.mifos.mifosxdroid.online.createnewgroup.CreateNewGroupRepository import com.mifos.mifosxdroid.online.createnewgroup.CreateNewGroupRepositoryImp import com.mifos.mifosxdroid.online.datatable.DataTableRepository import com.mifos.mifosxdroid.online.datatable.DataTableRepositoryImp -import com.mifos.mifosxdroid.online.datatabledata.DataTableDataRepository -import com.mifos.mifosxdroid.online.datatabledata.DataTableDataRepositoryImp import com.mifos.mifosxdroid.online.datatablelistfragment.DataTableListRepository import com.mifos.mifosxdroid.online.datatablelistfragment.DataTableListRepositoryImp import com.mifos.mifosxdroid.online.generatecollectionsheet.GenerateCollectionSheetRepository @@ -238,11 +236,6 @@ class RepositoryModule { return CreateNewGroupRepositoryImp(dataManagerOffices, dataManagerGroups) } - @Provides - fun providesDataTableDataRepository(dataManagerDataTable: DataManagerDataTable): DataTableDataRepository { - return DataTableDataRepositoryImp(dataManagerDataTable) - } - @Provides fun providesDataTableListRepository( dataManagerLoan: DataManagerLoan, diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataFragment.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataFragment.kt index e77ab1cb055..eb694a0bf75 100755 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataFragment.kt +++ b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataFragment.kt @@ -4,48 +4,32 @@ */ package com.mifos.mifosxdroid.online.datatabledata -import android.app.Activity -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup -import android.widget.Toast -import androidx.lifecycle.ViewModelProvider +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener -import com.google.gson.JsonArray -import com.mifos.core.common.utils.Constants import com.mifos.core.objects.noncore.DataTable -import com.mifos.mifosxdroid.R -import com.mifos.mifosxdroid.core.MaterialDialog -import com.mifos.mifosxdroid.core.MifosBaseFragment -import com.mifos.mifosxdroid.core.util.Toaster -import com.mifos.mifosxdroid.databinding.FragmentDatatableBinding -import com.mifos.mifosxdroid.dialogfragments.datatablerowdialog.DataTableRowDialogFragment -import com.mifos.utils.DataTableUIBuilder -import com.mifos.utils.FragmentConstants +import com.mifos.feature.data_table.DataTableDataScreen import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint -class DataTableDataFragment : MifosBaseFragment(), DataTableUIBuilder.DataTableActionListener, - OnRefreshListener { +class DataTableDataFragment : Fragment() { - private lateinit var binding: FragmentDatatableBinding private val arg: DataTableDataFragmentArgs by navArgs() - private lateinit var viewModel: DataTableDataViewModel - - private var dataTable: DataTable? = null + private lateinit var dataTable: DataTable private var entityId = 0 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) dataTable = arg.dataTable entityId = arg.entityId - setHasOptionsMenu(true) } override fun onCreateView( @@ -53,143 +37,27 @@ class DataTableDataFragment : MifosBaseFragment(), DataTableUIBuilder.DataTableA container: ViewGroup?, savedInstanceState: Bundle? ): View { - // Inflate the layout for this fragment - binding = FragmentDatatableBinding.inflate(inflater, container, false) - viewModel = ViewModelProvider(this)[DataTableDataViewModel::class.java] - dataTable!!.registeredTableName?.let { setToolbarTitle(it) } - binding.swipeContainer.setColorSchemeColors( - *activity - ?.resources!!.getIntArray(R.array.swipeRefreshColors) - ) - binding.swipeContainer.setOnRefreshListener(this) - viewModel.loadDataTableInfo(dataTable?.registeredTableName, entityId) - - viewModel.dataTableDataUiState.observe(viewLifecycleOwner) { - when (it) { - is DataTableDataUiState.ShowDataTableDeletedSuccessfully -> { - showProgressbar(false) - showDataTableDeletedSuccessfully() - } - - is DataTableDataUiState.ShowDataTableInfo -> { - showProgressbar(false) - showDataTableInfo(it.jsonElements) - } - - is DataTableDataUiState.ShowEmptyDataTable -> { - showProgressbar(false) - showEmptyDataTable() - } - - is DataTableDataUiState.ShowFetchingError -> { - showProgressbar(false) - showFetchingError(it.message) - } - - is DataTableDataUiState.ShowFetchingErrorString -> { - showProgressbar(false) - showFetchingError(it.message) - } - - is DataTableDataUiState.ShowProgressbar -> showProgressbar(true) - } - } - - return binding.root - } - - override fun onRefresh() { - binding.linearLayoutDatatables.visibility = View.GONE - viewModel.loadDataTableInfo(dataTable?.registeredTableName, entityId) - } - - override fun showDataTableOptions(table: String, entity: Int, rowId: Int) { - MaterialDialog.Builder().init(activity) - .setItems(R.array.datatable_options) { dialog, which -> - when (which) { - 0 -> viewModel.deleteDataTableEntry(table, entity, rowId) - else -> { + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + DataTableDataScreen( + table = dataTable.registeredTableName.toString(), + entityId = entityId, + onBackPressed = { + findNavController().popBackStack() } - } + ) } - .createMaterialDialog() - .show() - } - - private fun showDataTableInfo(jsonElements: JsonArray?) { - if (jsonElements != null) { - binding.linearLayoutDatatables.visibility = View.VISIBLE - binding.llError.visibility = View.GONE - binding.linearLayoutDatatables.removeAllViews() - binding.linearLayoutDatatables.invalidate() - val mListener = activity - ?.supportFragmentManager - ?.findFragmentByTag(FragmentConstants.FRAG_DATA_TABLE) as DataTableUIBuilder.DataTableActionListener? - DataTableUIBuilder().getDataTableLayout( - dataTable!!, - jsonElements, binding.linearLayoutDatatables, requireContext(), entityId, - mListener - ) } } - private fun showDataTableDeletedSuccessfully() { - viewModel.loadDataTableInfo(dataTable?.registeredTableName, entityId) + override fun onResume() { + super.onResume() + (requireActivity() as AppCompatActivity).supportActionBar?.hide() } - private fun showEmptyDataTable() { - binding.linearLayoutDatatables.visibility = View.GONE - binding.llError.visibility = View.VISIBLE - binding.tvError.setText(R.string.empty_data_table) - Toaster.show(binding.root, R.string.empty_data_table) - } - - private fun showFetchingError(message: Int) { - showFetchingError(getString(message)) - } - - private fun showFetchingError(errorMessage: String?) { - binding.linearLayoutDatatables.visibility = View.GONE - binding.llError.visibility = View.VISIBLE - binding.tvError.text = errorMessage - Toast.makeText(activity, errorMessage, Toast.LENGTH_SHORT).show() - } - - private fun showProgressbar(show: Boolean) { - binding.swipeContainer.isRefreshing = false - if (show) { - binding.linearLayoutDatatables.visibility = View.GONE - binding.progressbarDataTable.visibility = View.VISIBLE - } else { - binding.linearLayoutDatatables.visibility = View.VISIBLE - binding.progressbarDataTable.visibility = View.GONE - } - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.menu_add, menu) - super.onCreateOptionsMenu(menu, inflater) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == R.id.menu_add) { - val dataTableRowDialogFragment = DataTableRowDialogFragment - .newInstance(dataTable, entityId) - dataTableRowDialogFragment.setTargetFragment(this, Constants.DIALOG_FRAGMENT) - val fragmentTransaction = requireActivity().supportFragmentManager - .beginTransaction() - fragmentTransaction.addToBackStack(FragmentConstants.DFRAG_DATATABLE_ENTRY_FORM) - dataTableRowDialogFragment.show(fragmentTransaction, "Document Dialog Fragment") - } - return super.onOptionsItemSelected(item) - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - when (requestCode) { - Constants.DIALOG_FRAGMENT -> if (resultCode == Activity.RESULT_OK) { - viewModel - .loadDataTableInfo(dataTable?.registeredTableName, entityId) - } - } + override fun onStop() { + super.onStop() + (requireActivity() as AppCompatActivity).supportActionBar?.show() } } \ No newline at end of file diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataRepository.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataRepository.kt deleted file mode 100644 index ce6f683abf2..00000000000 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataRepository.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.mifos.mifosxdroid.online.datatabledata - -import com.google.gson.JsonArray -import org.apache.fineract.client.models.DeleteDataTablesDatatableAppTableIdDatatableIdResponse -import rx.Observable - -/** - * Created by Aditya Gupta on 10/08/23. - */ -interface DataTableDataRepository { - - fun getDataTableInfo(table: String?, entityId: Int): Observable - - fun deleteDataTableEntry(table: String?, entity: Int, rowId: Int): Observable - -} \ No newline at end of file diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataUiState.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataUiState.kt deleted file mode 100644 index ac32ca89de7..00000000000 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataUiState.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.mifos.mifosxdroid.online.datatabledata - -import com.google.gson.JsonArray - -/** - * Created by Aditya Gupta on 10/08/23. - */ -sealed class DataTableDataUiState { - - object ShowProgressbar : DataTableDataUiState() - - data class ShowFetchingError(val message: Int) : DataTableDataUiState() - - object ShowEmptyDataTable : DataTableDataUiState() - - data class ShowDataTableInfo(val jsonElements: JsonArray) : DataTableDataUiState() - - data class ShowFetchingErrorString(val message: String) : DataTableDataUiState() - - object ShowDataTableDeletedSuccessfully : DataTableDataUiState() -} diff --git a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataViewModel.kt b/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataViewModel.kt deleted file mode 100644 index ceed9173e78..00000000000 --- a/mifosng-android/src/main/java/com/mifos/mifosxdroid/online/datatabledata/DataTableDataViewModel.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.mifos.mifosxdroid.online.datatabledata - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.google.gson.JsonArray -import com.mifos.mifosxdroid.R -import dagger.hilt.android.lifecycle.HiltViewModel -import org.apache.fineract.client.models.DeleteDataTablesDatatableAppTableIdDatatableIdResponse -import rx.Subscriber -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers -import javax.inject.Inject - -/** - * Created by Aditya Gupta on 10/08/23. - */ -@HiltViewModel -class DataTableDataViewModel @Inject constructor(private val repository: DataTableDataRepository) : - ViewModel() { - - private val _dataTableDataUiState = MutableLiveData() - - val dataTableDataUiState: LiveData - get() = _dataTableDataUiState - - fun loadDataTableInfo(table: String?, entityId: Int) { - _dataTableDataUiState.value = DataTableDataUiState.ShowProgressbar - repository.getDataTableInfo(table, entityId) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeOn(Schedulers.io()) - .subscribe(object : Subscriber() { - override fun onCompleted() { - } - - override fun onError(e: Throwable) { - _dataTableDataUiState.value = - DataTableDataUiState.ShowFetchingError(R.string.failed_to_fetch_datatable_info) - } - - override fun onNext(jsonElements: JsonArray) { - if (jsonElements.size() == 0) { - _dataTableDataUiState.value = DataTableDataUiState.ShowEmptyDataTable - } else { - _dataTableDataUiState.value = - DataTableDataUiState.ShowDataTableInfo(jsonElements) - } - } - }) - } - - fun deleteDataTableEntry(table: String?, entity: Int, rowId: Int) { - _dataTableDataUiState.value = DataTableDataUiState.ShowProgressbar - repository.deleteDataTableEntry(table, entity, rowId) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeOn(Schedulers.io()) - .subscribe(object : Subscriber() { - override fun onCompleted() {} - override fun onError(e: Throwable) { - _dataTableDataUiState.value = - DataTableDataUiState.ShowFetchingErrorString(e.message.toString()) - } - - override fun onNext(genericResponse: DeleteDataTablesDatatableAppTableIdDatatableIdResponse) { - _dataTableDataUiState.value = - DataTableDataUiState.ShowDataTableDeletedSuccessfully - } - }) - - } -} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f8587e45257..1473543ea9a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -47,3 +47,4 @@ include(":feature:note") include(":feature:activate") include(":feature:loan") include(":feature:document") +include(":feature:data-table")