diff --git a/mifospay/build.gradle.kts b/mifospay/build.gradle.kts index dc9f18f8b..38f832268 100644 --- a/mifospay/build.gradle.kts +++ b/mifospay/build.gradle.kts @@ -143,6 +143,8 @@ dependencies { implementation("de.hdodenhof:circleimageview:3.1.0") implementation("com.github.yalantis:ucrop:2.2.2") + implementation("com.google.accompanist:accompanist-swiperefresh:0.27.0") + kspTest(libs.hilt.compiler) testImplementation(libs.junit) diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/bank/presenter/AccountViewModel.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/bank/presenter/AccountViewModel.kt index 0c4835211..1ad1fda8d 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/bank/presenter/AccountViewModel.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/bank/presenter/AccountViewModel.kt @@ -20,6 +20,7 @@ class AccountViewModel @Inject constructor() : ViewModel() { val accountsUiState: StateFlow = _accountUiState private val mRandom = Random() + val isRefreshing = MutableStateFlow(false) init { fetchLinkedAccount() @@ -82,6 +83,11 @@ class AccountViewModel @Inject constructor() : ViewModel() { ) return bankAccountDetailsList } + fun refreshAccountList(){ + isRefreshing.value = true + fetchLinkedAccount() + isRefreshing.value = false + } } diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/bank/ui/AccountsScreen.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/bank/ui/AccountsScreen.kt index e7f0b1ff7..a1c925601 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/bank/ui/AccountsScreen.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/bank/ui/AccountsScreen.kt @@ -15,6 +15,9 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +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 @@ -26,6 +29,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.accompanist.swiperefresh.SwipeRefresh +import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.mifos.mobilewallet.model.domain.BankAccountDetails import org.mifos.mobilewallet.mifospay.R import org.mifos.mobilewallet.mifospay.bank.presenter.AccountViewModel @@ -42,11 +47,18 @@ fun AccountsScreen( ) { val accountsUiState by viewModel.accountsUiState.collectAsStateWithLifecycle() val sampleList = viewModel.bankAccountDetailsList - AccountScreen( - accountsUiState = accountsUiState, - onAddAccount = onAddAccount, - sampleList = sampleList, - ) + val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle() + val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isRefreshing) + SwipeRefresh( + state = swipeRefreshState, + onRefresh = viewModel::refreshAccountList + ){ + AccountScreen( + accountsUiState = accountsUiState, + onAddAccount = onAddAccount, + sampleList = sampleList, + ) + } } @Composable diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/history/presenter/HistoryViewModel.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/history/presenter/HistoryViewModel.kt index d9d72ef85..639a4d592 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/history/presenter/HistoryViewModel.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/history/presenter/HistoryViewModel.kt @@ -23,6 +23,8 @@ class HistoryViewModel @Inject constructor( private val _historyUiState = MutableStateFlow(HistoryUiState.Loading) val historyUiState: StateFlow = _historyUiState + val isRefreshing = MutableStateFlow(false) + fun fetchTransactions() { _historyUiState.value = HistoryUiState.Loading mUseCaseHandler.execute(mFetchAccountUseCase, @@ -59,6 +61,11 @@ class HistoryViewModel @Inject constructor( init { fetchTransactions() } + fun refreshTransactionHistory(){ + isRefreshing.value = true + fetchTransactions() + isRefreshing.value = false + } } sealed class HistoryUiState { diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/history/ui/HistoryScreen.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/history/ui/HistoryScreen.kt index 1db864718..09ee6d90c 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/history/ui/HistoryScreen.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/history/ui/HistoryScreen.kt @@ -2,6 +2,7 @@ package org.mifos.mobilewallet.mifospay.history.ui import android.widget.Toast 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.fillMaxSize @@ -9,6 +10,8 @@ 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.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Info import androidx.compose.material3.Button @@ -19,7 +22,9 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue 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 @@ -28,6 +33,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.accompanist.swiperefresh.SwipeRefresh +import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.mifos.mobilewallet.model.domain.Currency import com.mifos.mobilewallet.model.domain.Transaction import com.mifos.mobilewallet.model.domain.TransactionType @@ -45,9 +52,18 @@ fun HistoryScreen( viewModel: HistoryViewModel = hiltViewModel() ) { val historyUiState by viewModel.historyUiState.collectAsStateWithLifecycle() - HistoryScreen( - historyUiState = historyUiState - ) + + val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle() + val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isRefreshing) + + SwipeRefresh( + state = swipeRefreshState, + onRefresh = viewModel::refreshTransactionHistory + ){ + HistoryScreen( + historyUiState = historyUiState + ) + } } @Composable @@ -57,75 +73,83 @@ fun HistoryScreen( var selectedChip by remember { mutableStateOf(TransactionType.OTHER) } var filteredTransactions by remember { mutableStateOf(emptyList()) } - when (historyUiState) { - HistoryUiState.Empty -> { - EmptyContentScreen( - modifier = Modifier, - title = stringResource(id = R.string.error_oops), - subTitle = stringResource(id = R.string.empty_no_transaction_history_title), - iconTint = Color.Black, - iconImageVector = Icons.Rounded.Info - ) - } + Box(modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + contentAlignment = Alignment.Center + ){ + when (historyUiState) { + HistoryUiState.Empty -> { + EmptyContentScreen( + modifier = Modifier, + title = stringResource(id = R.string.error_oops), + subTitle = stringResource(id = R.string.empty_no_transaction_history_title), + iconTint = Color.Black, + iconImageVector = Icons.Rounded.Info + ) + } - is HistoryUiState.Error -> { - EmptyContentScreen( - modifier = Modifier, - title = stringResource(id = R.string.error_oops), - subTitle = stringResource(id = R.string.unexpected_error_subtitle), - iconTint = Color.Black, - iconImageVector = Icons.Rounded.Info - ) - } + is HistoryUiState.Error -> { + EmptyContentScreen( + modifier = Modifier, + title = stringResource(id = R.string.error_oops), + subTitle = stringResource(id = R.string.unexpected_error_subtitle), + iconTint = Color.Black, + iconImageVector = Icons.Rounded.Info + ) + } - is HistoryUiState.HistoryList -> { - LaunchedEffect(selectedChip) { - filteredTransactions = when (selectedChip) { - TransactionType.OTHER -> historyUiState.list!! - else -> historyUiState.list!!.filter { it.transactionType == selectedChip } + is HistoryUiState.HistoryList -> { + LaunchedEffect(selectedChip) { + filteredTransactions = when (selectedChip) { + TransactionType.OTHER -> historyUiState.list!! + else -> historyUiState.list!!.filter { it.transactionType == selectedChip } + } } - } - Column(modifier = Modifier.fillMaxSize()) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - horizontalArrangement = Arrangement.SpaceBetween + Column( + modifier = Modifier.fillMaxSize() ) { - Chip( - selected = selectedChip == TransactionType.OTHER, - onClick = { selectedChip = TransactionType.OTHER }, - label = stringResource(R.string.all) - ) - Chip( - selected = selectedChip == TransactionType.CREDIT, - onClick = { selectedChip = TransactionType.CREDIT }, - label = stringResource(R.string.credits) - ) - Chip( - selected = selectedChip == TransactionType.DEBIT, - onClick = { selectedChip = TransactionType.DEBIT }, - label = stringResource(R.string.debits) - ) - } - LazyColumn(modifier = Modifier.fillMaxSize()) { - items(filteredTransactions) { - HistoryItem( - date = it.date.toString(), - amount = it.amount.toString(), - transactionType = it.transactionType + Row( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Chip( + selected = selectedChip == TransactionType.OTHER, + onClick = { selectedChip = TransactionType.OTHER }, + label = stringResource(R.string.all) + ) + Chip( + selected = selectedChip == TransactionType.CREDIT, + onClick = { selectedChip = TransactionType.CREDIT }, + label = stringResource(R.string.credits) ) + Chip( + selected = selectedChip == TransactionType.DEBIT, + onClick = { selectedChip = TransactionType.DEBIT }, + label = stringResource(R.string.debits) + ) + } + LazyColumn(modifier = Modifier.fillMaxSize()) { + items(filteredTransactions) { + HistoryItem( + date = it.date.toString(), + amount = it.amount.toString(), + transactionType = it.transactionType + ) + } } } - } - } + } - HistoryUiState.Loading -> { - MifosLoadingWheel( - modifier = Modifier.fillMaxWidth(), - contentDesc = stringResource(R.string.loading) - ) + HistoryUiState.Loading -> { + MifosLoadingWheel( + modifier = Modifier.fillMaxWidth(), + contentDesc = stringResource(R.string.loading) + ) + } } } } diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/invoice/presenter/InvoicesViewModel.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/invoice/presenter/InvoicesViewModel.kt index 769fb109d..4fb1e17dc 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/invoice/presenter/InvoicesViewModel.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/invoice/presenter/InvoicesViewModel.kt @@ -24,6 +24,8 @@ class InvoicesViewModel @Inject constructor( private val _invoiceUiState = MutableStateFlow(InvoiceUiState.Loading) val invoiceUiState: StateFlow = _invoiceUiState + val isRefreshing = MutableStateFlow(false) + fun fetchInvoices() { _invoiceUiState.value = InvoiceUiState.Loading mUseCaseHandler.execute(fetchInvoicesUseCase, @@ -50,6 +52,11 @@ class InvoicesViewModel @Inject constructor( init { fetchInvoices() } + fun refreshInvoices(){ + isRefreshing.value = true + fetchInvoices() + isRefreshing.value = false + } } sealed class InvoiceUiState { diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/invoice/ui/InvoiceScreen.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/invoice/ui/InvoiceScreen.kt index c1b45bac3..eb964e620 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/invoice/ui/InvoiceScreen.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/invoice/ui/InvoiceScreen.kt @@ -2,15 +2,22 @@ package org.mifos.mobilewallet.mifospay.invoice.ui import android.content.Intent import android.net.Uri +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Info import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +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 @@ -18,6 +25,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.accompanist.swiperefresh.SwipeRefresh +import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.mifos.mobilewallet.model.entity.Invoice import org.mifos.mobilewallet.mifospay.R import org.mifos.mobilewallet.mifospay.designsystem.component.MifosLoadingWheel @@ -30,10 +39,17 @@ fun InvoiceScreen( viewModel: InvoicesViewModel = hiltViewModel() ) { val invoiceUiState by viewModel.invoiceUiState.collectAsStateWithLifecycle() - InvoiceScreen( - invoiceUiState = invoiceUiState, - getUniqueInvoiceLink = { viewModel.getUniqueInvoiceLink(it) } - ) + val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle() + val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isRefreshing) + SwipeRefresh( + state = swipeRefreshState, + onRefresh = viewModel::refreshInvoices + ){ + InvoiceScreen( + invoiceUiState = invoiceUiState, + getUniqueInvoiceLink = { viewModel.getUniqueInvoiceLink(it) } + ) + } } @Composable @@ -42,54 +58,60 @@ fun InvoiceScreen( getUniqueInvoiceLink: (Long) -> Uri? ) { val context = LocalContext.current - when (invoiceUiState) { - is InvoiceUiState.Error -> { - EmptyContentScreen( - modifier = Modifier, - title = stringResource(id = R.string.error_oops), - subTitle = stringResource(id = R.string.unexpected_error_subtitle), - iconTint = Color.Black, - iconImageVector = Icons.Rounded.Info - ) - } + Box(modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + contentAlignment = Alignment.Center + ){ + when (invoiceUiState) { + is InvoiceUiState.Error -> { + EmptyContentScreen( + modifier = Modifier, + title = stringResource(id = R.string.error_oops), + subTitle = stringResource(id = R.string.unexpected_error_subtitle), + iconTint = Color.Black, + iconImageVector = Icons.Rounded.Info + ) + } - is InvoiceUiState.InvoiceList -> { - Column(modifier = Modifier.fillMaxSize()) { - LazyColumn(modifier = Modifier.fillMaxSize()) { - items(invoiceUiState.list) { - InvoiceItem( - invoiceTitle = it?.title.toString(), - invoiceAmount = it?.amount.toString(), - invoiceStatus = it?.status.toString(), - invoiceDate = it?.date.toString(), - invoiceId = it?.id.toString(), - invoiceStatusIcon = it?.status!!, - onClick = { invoiceId -> - val uniqueLink = getUniqueInvoiceLink(invoiceId.toLong()) - val intent = Intent(Intent.ACTION_VIEW, uniqueLink) - context.startActivity(intent) - } - ) + is InvoiceUiState.InvoiceList -> { + Column(modifier = Modifier.fillMaxSize()) { + LazyColumn(modifier = Modifier.fillMaxSize()) { + items(invoiceUiState.list) { + InvoiceItem( + invoiceTitle = it?.title.toString(), + invoiceAmount = it?.amount.toString(), + invoiceStatus = it?.status.toString(), + invoiceDate = it?.date.toString(), + invoiceId = it?.id.toString(), + invoiceStatusIcon = it?.status!!, + onClick = { invoiceId -> + val uniqueLink = getUniqueInvoiceLink(invoiceId.toLong()) + val intent = Intent(Intent.ACTION_VIEW, uniqueLink) + context.startActivity(intent) + } + ) + } } } } - } - InvoiceUiState.Empty -> { - EmptyContentScreen( - modifier = Modifier, - title = stringResource(id = R.string.error_oops), - subTitle = stringResource(id = R.string.error_no_invoices_found), - iconTint = Color.Black, - iconImageVector = Icons.Rounded.Info - ) - } + InvoiceUiState.Empty -> { + EmptyContentScreen( + modifier = Modifier, + title = stringResource(id = R.string.error_oops), + subTitle = stringResource(id = R.string.error_no_invoices_found), + iconTint = Color.Black, + iconImageVector = Icons.Rounded.Info + ) + } - InvoiceUiState.Loading -> { - MifosLoadingWheel( - modifier = Modifier.fillMaxWidth(), - contentDesc = stringResource(R.string.loading) - ) + InvoiceUiState.Loading -> { + MifosLoadingWheel( + modifier = Modifier.fillMaxWidth(), + contentDesc = stringResource(R.string.loading) + ) + } } } } diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/kyc/presenter/KYCDescriptionViewModel.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/kyc/presenter/KYCDescriptionViewModel.kt index 967898138..a2ac5a498 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/kyc/presenter/KYCDescriptionViewModel.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/kyc/presenter/KYCDescriptionViewModel.kt @@ -8,9 +8,7 @@ import kotlinx.coroutines.flow.StateFlow import org.mifos.mobilewallet.core.base.UseCase import org.mifos.mobilewallet.core.base.UseCaseHandler import org.mifos.mobilewallet.core.domain.usecase.kyc.FetchKYCLevel1Details -import org.mifos.mobilewallet.mifospay.R import org.mifos.mobilewallet.mifospay.data.local.LocalRepository -import org.mifos.mobilewallet.mifospay.kyc.KYCContract import javax.inject.Inject @HiltViewModel @@ -23,12 +21,13 @@ class KYCDescriptionViewModel @Inject constructor( MutableStateFlow(KYCDescriptionUiState.Loading) val kycdescriptionState: StateFlow = _kycdescriptionState + val isRefreshing = MutableStateFlow(false) init { fetchCurrentLevel() } - private fun fetchCurrentLevel() { + fun fetchCurrentLevel() { fetchKYCLevel1DetailsUseCase.walletRequestValues = FetchKYCLevel1Details.RequestValues(mLocalRepository.clientDetails.clientId.toInt()) val requestValues = fetchKYCLevel1DetailsUseCase.walletRequestValues @@ -49,6 +48,11 @@ class KYCDescriptionViewModel @Inject constructor( } }) } + fun refreshKYCLevel(){ + isRefreshing.value = true + fetchCurrentLevel() + isRefreshing.value = false + } } diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/kyc/ui/KYCDescriptionScreen.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/kyc/ui/KYCDescriptionScreen.kt index bbf92d9fd..f357e2843 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/kyc/ui/KYCDescriptionScreen.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/kyc/ui/KYCDescriptionScreen.kt @@ -1,7 +1,6 @@ package org.mifos.mobilewallet.mifospay.kyc.ui import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -15,7 +14,9 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material3.Button @@ -26,6 +27,9 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +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.draw.scale @@ -39,6 +43,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.accompanist.swiperefresh.SwipeRefresh +import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.mifos.mobilewallet.model.entity.kyc.KYCLevel1Details import org.mifos.mobilewallet.mifospay.R import org.mifos.mobilewallet.mifospay.designsystem.component.MifosOverlayLoadingWheel @@ -53,31 +60,45 @@ fun KYCDescriptionScreen( onLevel3Clicked: () -> Unit ) { val kUiState by viewModel.kycdescriptionState.collectAsState() + val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle() + val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isRefreshing) - when (val state = kUiState) { - KYCDescriptionUiState.Loading -> { - MifosOverlayLoadingWheel(contentDesc = stringResource(R.string.loading)) - } + SwipeRefresh( + state = swipeRefreshState, + onRefresh = viewModel::refreshKYCLevel + ){ + Box( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + contentAlignment = Alignment.Center + ) { + when (val state = kUiState) { + KYCDescriptionUiState.Loading -> { + MifosOverlayLoadingWheel(contentDesc = stringResource(R.string.loading)) + } - is KYCDescriptionUiState.Error -> { - PlaceholderScreen() - } + is KYCDescriptionUiState.Error -> { + PlaceholderScreen() + } - is KYCDescriptionUiState.KYCDescription -> { - val kyc = state.kycLevel1Details - if (kyc != null) { - KYCDescriptionScreen( - kUiState = state, - kyc, - onLevel1Clicked, - onLevel2Clicked, - onLevel3Clicked - ) + is KYCDescriptionUiState.KYCDescription -> { + val kyc = state.kycLevel1Details + if (kyc != null) { + KYCDescriptionScreen( + kUiState = state, + kyc, + onLevel1Clicked, + onLevel2Clicked, + onLevel3Clicked + ) + } + } + + else -> {} + } } } - - else -> {} - } } @Composable diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/merchants/presenter/MerchantViewModel.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/merchants/presenter/MerchantViewModel.kt index ffd05e7fd..49f8e6c79 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/merchants/presenter/MerchantViewModel.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/merchants/presenter/MerchantViewModel.kt @@ -36,6 +36,8 @@ class MerchantViewModel @Inject constructor( private val _merchantUiState = MutableStateFlow(MerchantUiState.Loading) val merchantUiState: StateFlow = _merchantUiState + val isRefreshing = MutableStateFlow(false) + init { fetchMerchants() } @@ -120,6 +122,12 @@ class MerchantViewModel @Inject constructor( } }) } + + fun refreshMerchantsList(){ + isRefreshing.value = true + fetchMerchants() + isRefreshing.value = false + } } sealed class MerchantUiState { diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/merchants/ui/MerchantScreen.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/merchants/ui/MerchantScreen.kt index a71f35862..b6f6dee54 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/merchants/ui/MerchantScreen.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/merchants/ui/MerchantScreen.kt @@ -7,6 +7,8 @@ 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.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Search @@ -17,9 +19,11 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.SearchBar import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf 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 @@ -29,6 +33,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.accompanist.swiperefresh.SwipeRefresh +import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.mifos.mobilewallet.model.entity.accounts.savings.SavingsWithAssociations import org.mifos.mobilewallet.mifospay.R import org.mifos.mobilewallet.mifospay.common.Constants @@ -44,11 +50,19 @@ fun MerchantScreen( ) { val merchantUiState by viewModel.merchantUiState.collectAsStateWithLifecycle() val merchantsListUiState by viewModel.merchantsListUiState.collectAsStateWithLifecycle() - MerchantScreen( - merchantUiState = merchantUiState, - merchantListUiState = merchantsListUiState, - updateQuery = { viewModel.updateSearchQuery(it) } - ) + val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle() + val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isRefreshing) + + SwipeRefresh( + state = swipeRefreshState, + onRefresh = viewModel::refreshMerchantsList + ){ + MerchantScreen( + merchantUiState = merchantUiState, + merchantListUiState = merchantsListUiState, + updateQuery = { viewModel.updateSearchQuery(it) } + ) + } } @Composable @@ -59,7 +73,8 @@ fun MerchantScreen( ) { Box( modifier = Modifier - .fillMaxSize(), + .fillMaxSize() + .verticalScroll(rememberScrollState()), contentAlignment = Alignment.Center, ) { when (merchantUiState) { diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/savedcards/presenter/CardsScreenViewModel.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/savedcards/presenter/CardsScreenViewModel.kt index d3e293cdc..c0a462e5e 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/savedcards/presenter/CardsScreenViewModel.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/savedcards/presenter/CardsScreenViewModel.kt @@ -38,6 +38,8 @@ class CardsScreenViewModel @Inject constructor( private val _cardState = MutableStateFlow(CardsUiState.Loading) val cardState: StateFlow = _cardState.asStateFlow() + val isRefreshing = MutableStateFlow(false) + init { fetchSavedCards() } @@ -163,6 +165,11 @@ class CardsScreenViewModel @Inject constructor( } }) } + fun refreshSavedCards(){ + isRefreshing.value = true + fetchSavedCards() + isRefreshing.value = false + } } diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/savedcards/ui/CardsScreen.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/savedcards/ui/CardsScreen.kt index 4a2aa0e2f..317c8749c 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/savedcards/ui/CardsScreen.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/savedcards/ui/CardsScreen.kt @@ -10,16 +10,14 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.rounded.Info -import androidx.compose.material3.AssistChip -import androidx.compose.material3.AssistChipDefaults import androidx.compose.material3.Card import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -39,11 +37,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.accompanist.swiperefresh.SwipeRefresh +import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.mifos.mobilewallet.model.entity.savedcards.Card import org.mifos.mobilewallet.mifospay.R import org.mifos.mobilewallet.mifospay.designsystem.component.MfLoadingWheel @@ -65,19 +64,27 @@ fun CardsScreen( ) { val cardState by viewModel.cardState.collectAsStateWithLifecycle() val cardListUiState by viewModel.cardListUiState.collectAsStateWithLifecycle() - CardsScreen( - cardState = cardState, - cardListUiState = cardListUiState, - onEditCard = onEditCard, - onDeleteCard = { - // TODO implement Delete card by implementing a delete confirm dialog and call - // TODO viewModel.deleteCard - }, - onAddBtn = onAddBtn, - updateQuery = { - viewModel.updateSearchQuery(it) - } - ) + val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle() + val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isRefreshing) + + SwipeRefresh( + state = swipeRefreshState, + onRefresh = viewModel::refreshSavedCards + ){ + CardsScreen( + cardState = cardState, + cardListUiState = cardListUiState, + onEditCard = onEditCard, + onDeleteCard = { + // TODO implement Delete card by implementing a delete confirm dialog and call + // TODO viewModel.deleteCard + }, + onAddBtn = onAddBtn, + updateQuery = { + viewModel.updateSearchQuery(it) + } + ) + } } @Composable @@ -87,11 +94,12 @@ fun CardsScreen( onEditCard: (Card) -> Unit, onDeleteCard: (Card) -> Unit, onAddBtn: () -> Unit, - updateQuery: (String) -> Unit + updateQuery: (String) -> Unit, ) { Box( modifier = Modifier - .fillMaxSize(), + .fillMaxSize() + .verticalScroll(rememberScrollState()), contentAlignment = Alignment.Center, ) { when (cardState) { @@ -223,7 +231,9 @@ fun CardsList( onMenuItemClick: (Card, CardMenuAction) -> Unit ) { LazyColumn( - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp) + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) ) { items(cards) { card -> CardItem(card = card) { clickedCard, menuItem -> diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/standinginstruction/presenter/StandingInstructionViewModel.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/standinginstruction/presenter/StandingInstructionViewModel.kt index 47e685c60..17f10c1a4 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/standinginstruction/presenter/StandingInstructionViewModel.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/standinginstruction/presenter/StandingInstructionViewModel.kt @@ -23,6 +23,8 @@ class StandingInstructionViewModel @Inject constructor( val standingInstructionsUiState: StateFlow = _standingInstructionsUiState + val isRefreshing = MutableStateFlow(false) + fun getAllSI() { val client = localRepository.clientDetails _standingInstructionsUiState.value = StandingInstructionsUiState.Loading @@ -48,6 +50,11 @@ class StandingInstructionViewModel @Inject constructor( init { getAllSI() } + fun refreshSI(){ + isRefreshing.value = true + getAllSI() + isRefreshing.value = false + } } sealed class StandingInstructionsUiState { diff --git a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/standinginstruction/ui/StandingInstructionScreen.kt b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/standinginstruction/ui/StandingInstructionScreen.kt index 2993a2d25..9bd6c8d00 100644 --- a/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/standinginstruction/ui/StandingInstructionScreen.kt +++ b/mifospay/src/main/java/org/mifos/mobilewallet/mifospay/standinginstruction/ui/StandingInstructionScreen.kt @@ -1,11 +1,14 @@ package org.mifos.mobilewallet.mifospay.standinginstruction.ui +import androidx.compose.foundation.layout.Box 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.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Info import androidx.compose.material3.FloatingActionButton @@ -13,6 +16,10 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +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.res.painterResource @@ -20,6 +27,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.accompanist.swiperefresh.SwipeRefresh +import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import org.mifos.mobilewallet.mifospay.R import org.mifos.mobilewallet.mifospay.designsystem.component.MifosLoadingWheel import org.mifos.mobilewallet.mifospay.standinginstruction.presenter.StandingInstructionViewModel @@ -32,79 +41,97 @@ fun StandingInstructionsScreen( onNewSI: () -> Unit ) { val standingInstructionsUiState by viewModel.standingInstructionsUiState.collectAsStateWithLifecycle() - StandingInstructionScreen( - standingInstructionsUiState = standingInstructionsUiState, - onNewSI = onNewSI - ) + val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle() + val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isRefreshing) + SwipeRefresh( + state = swipeRefreshState, + onRefresh = viewModel::refreshSI + ){ + StandingInstructionScreen( + standingInstructionsUiState = standingInstructionsUiState, + onNewSI = onNewSI + ) + } + } @Composable fun StandingInstructionScreen( standingInstructionsUiState: StandingInstructionsUiState, - onNewSI: () -> Unit -) = when (standingInstructionsUiState) { - StandingInstructionsUiState.Empty -> { - EmptyContentScreen( - modifier = Modifier, - title = stringResource(id = R.string.error_oops), - subTitle = stringResource(id = R.string.empty_standing_instructions), - iconTint = Color.Black, - iconImageVector = Icons.Rounded.Info - ) - } - - is StandingInstructionsUiState.Error -> { - EmptyContentScreen( - modifier = Modifier, - title = stringResource(id = R.string.error_oops), - subTitle = stringResource(id = R.string.error_fetching_si_list), - iconTint = Color.Black, - iconImageVector = Icons.Rounded.Info - ) - } + onNewSI: () -> Unit, +) { + Box( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()), + contentAlignment = Alignment.Center + ) { + when (standingInstructionsUiState) { + StandingInstructionsUiState.Empty -> { + EmptyContentScreen( + modifier = Modifier, + title = stringResource(id = R.string.error_oops), + subTitle = stringResource(id = R.string.empty_standing_instructions), + iconTint = Color.Black, + iconImageVector = Icons.Rounded.Info + ) + } - StandingInstructionsUiState.Loading -> { - MifosLoadingWheel( - modifier = Modifier.fillMaxWidth(), - contentDesc = stringResource(R.string.loading) - ) - } + is StandingInstructionsUiState.Error -> { + EmptyContentScreen( + modifier = Modifier, + title = stringResource(id = R.string.error_oops), + subTitle = stringResource(id = R.string.error_fetching_si_list), + iconTint = Color.Black, + iconImageVector = Icons.Rounded.Info + ) + } - is StandingInstructionsUiState.StandingInstructionList -> { - Scaffold( - modifier = Modifier, - floatingActionButton = { - FloatingActionButton( - onClick = { onNewSI.invoke() }, - ) { - Icon( - painter = painterResource(id = R.drawable.ic_add_white), - contentDescription = null, - tint = Color.Black + StandingInstructionsUiState.Loading -> { + MifosLoadingWheel( + modifier = Modifier.fillMaxWidth(), + contentDesc = stringResource(R.string.loading) ) } - } - ) { - 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(), - ) + + is StandingInstructionsUiState.StandingInstructionList -> { + Scaffold( + modifier = Modifier, + floatingActionButton = { + FloatingActionButton( + onClick = { onNewSI.invoke() }, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_add_white), + 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(), + ) + } + } + } } + } } } - } -} + @Preview(showBackground = true) @Composable