diff --git a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/MainActivity.kt b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/MainActivity.kt index bb7ba4070..6ca22cfdb 100644 --- a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/MainActivity.kt +++ b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/MainActivity.kt @@ -17,7 +17,6 @@ import net.gini.android.health.sdk.exampleapp.databinding.ActivityMainBinding import net.gini.android.health.sdk.exampleapp.invoices.ui.AppCompatThemeInvoicesActivity import net.gini.android.health.sdk.exampleapp.invoices.ui.InvoicesActivity import net.gini.android.health.sdk.exampleapp.pager.PagerAdapter -import net.gini.android.health.sdk.exampleapp.review.ReviewActivity import net.gini.android.health.sdk.exampleapp.upload.UploadActivity import org.koin.androidx.viewmodel.ext.android.viewModel import org.slf4j.LoggerFactory @@ -44,7 +43,7 @@ class MainActivity : AppCompatActivity() { binding.importFile.setOnClickListener { if (useTestDocument) { viewModel.setDocumentForReview(testDocumentId) - startActivity(ReviewActivity.getStartIntent(this, paymentComponentConfiguration = viewModel.getPaymentComponentConfiguration())) +// startActivity(ReviewActivity.getStartIntent(this, paymentComponentConfiguration = viewModel.getPaymentComponentConfiguration())) } else { importFile() } diff --git a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/di/GiniModule.kt b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/di/GiniModule.kt index ce00795b2..ca879934a 100644 --- a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/di/GiniModule.kt +++ b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/di/GiniModule.kt @@ -2,9 +2,11 @@ package net.gini.android.health.sdk.exampleapp.di import net.gini.android.health.sdk.GiniHealth import net.gini.android.health.sdk.util.getGiniApi +import net.gini.android.internal.payment.GiniInternalPaymentModule import org.koin.dsl.module val giniModule = module { single { getGiniApi(get(), getProperty("clientId"), getProperty("clientSecret"), "example.com") } single { GiniHealth(get(), get()) } + single { GiniInternalPaymentModule(context = get(), giniHealthAPI = get())} } \ No newline at end of file diff --git a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/di/ViewModel.kt b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/di/ViewModel.kt index ae6d6bc7c..acb8d2449 100644 --- a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/di/ViewModel.kt +++ b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/di/ViewModel.kt @@ -7,7 +7,6 @@ import net.gini.android.health.sdk.exampleapp.invoices.data.InvoicesRepository import net.gini.android.health.sdk.exampleapp.invoices.ui.InvoicesViewModel import net.gini.android.health.sdk.exampleapp.review.ReviewViewModel import net.gini.android.health.sdk.exampleapp.upload.UploadViewModel -import net.gini.android.internal.payment.paymentComponent.PaymentComponent import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module @@ -19,5 +18,4 @@ val viewModelModule = module { factory { InvoicesRepository(get(), get(), get(), get()) } factory { InvoicesLocalDataSource(get()) } factory { HardcodedInvoicesLocalDataSource(get()) } - factory { PaymentComponent(get(), get()) } } \ No newline at end of file diff --git a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/invoices/ui/InvoicesActivity.kt b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/invoices/ui/InvoicesActivity.kt index 0fdb40cfe..4db9f6fab 100644 --- a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/invoices/ui/InvoicesActivity.kt +++ b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/invoices/ui/InvoicesActivity.kt @@ -33,6 +33,7 @@ import net.gini.android.internal.payment.moreinformation.MoreInformationFragment import net.gini.android.internal.payment.paymentComponent.PaymentComponent import net.gini.android.internal.payment.paymentComponent.PaymentComponentConfiguration import net.gini.android.internal.payment.paymentComponent.PaymentComponentView +import net.gini.android.internal.payment.paymentComponent.PaymentProviderAppsState import org.koin.androidx.viewmodel.ext.android.viewModel import org.slf4j.LoggerFactory @@ -69,7 +70,7 @@ open class InvoicesActivity : AppCompatActivity() { launch { viewModel.uploadHardcodedInvoicesStateFlow.combine(viewModel.paymentProviderAppsFlow) { a, b -> a to b } .collect { (uploadState, bankAppsState) -> - if (uploadState == Loading || bankAppsState == LoadingBankApp) { + if (uploadState == Loading || bankAppsState == PaymentProviderAppsState.Loading) { showLoadingIndicator(binding) } else { hideLoadingIndicator(binding) @@ -90,7 +91,7 @@ open class InvoicesActivity : AppCompatActivity() { } launch { viewModel.paymentProviderAppsFlow.collect { paymentProviderAppsState -> - if (paymentProviderAppsState is Error) { + if (paymentProviderAppsState is PaymentProviderAppsState.Error) { AlertDialog.Builder(this@InvoicesActivity) .setTitle(R.string.failed_to_load_bank_apps) .setMessage(paymentProviderAppsState.throwable.message) @@ -124,12 +125,12 @@ open class InvoicesActivity : AppCompatActivity() { viewModel.loadPaymentProviderApps() binding.invoicesList.layoutManager = LinearLayoutManager(this) - binding.invoicesList.adapter = InvoicesAdapter(emptyList(), viewModel.paymentComponent) + binding.invoicesList.adapter = InvoicesAdapter(emptyList(), viewModel.giniPaymentModule.paymentComponent) binding.invoicesList.addItemDecoration(DividerItemDecoration(this, LinearLayout.VERTICAL)) - viewModel.paymentComponent.listener = object: PaymentComponent.Listener { + viewModel.giniPaymentModule.paymentComponent.listener = object: PaymentComponent.Listener { override fun onMoreInformationClicked() { - MoreInformationFragment.newInstance(viewModel.paymentComponent).apply { + MoreInformationFragment.newInstance(viewModel.giniPaymentModule.paymentComponent).apply { supportFragmentManager.beginTransaction() .add(R.id.fragment_container,this, this::class.java.simpleName) .addToBackStack(this::class.java.simpleName) @@ -138,12 +139,12 @@ open class InvoicesActivity : AppCompatActivity() { } override fun onBankPickerClicked() { - BankSelectionBottomSheet.newInstance(viewModel.paymentComponent).apply { + BankSelectionBottomSheet.newInstance(viewModel.giniPaymentModule.paymentComponent).apply { show(supportFragmentManager, BankSelectionBottomSheet::class.simpleName) } } - override fun onPayInvoiceClicked(documentId: String) { + override fun onPayInvoiceClicked(documentId: String?) { LOG.debug("Pay invoice clicked") viewModel.getPaymentReviewFragment(documentId) diff --git a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/invoices/ui/InvoicesViewModel.kt b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/invoices/ui/InvoicesViewModel.kt index dae755805..ec25c5c39 100644 --- a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/invoices/ui/InvoicesViewModel.kt +++ b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/invoices/ui/InvoicesViewModel.kt @@ -7,15 +7,16 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import net.gini.android.health.sdk.exampleapp.invoices.data.InvoicesRepository import net.gini.android.health.sdk.exampleapp.invoices.ui.model.InvoiceItem -import net.gini.android.health.sdk.paymentcomponent.PaymentComponent -import net.gini.android.health.sdk.review.ReviewConfiguration import net.gini.android.health.sdk.review.ReviewFragment import net.gini.android.health.sdk.review.model.ResultWrapper +import net.gini.android.internal.payment.GiniInternalPaymentModule +import net.gini.android.internal.payment.paymentComponent.PaymentComponentConfiguration +import net.gini.android.internal.payment.review.ReviewConfiguration import org.slf4j.LoggerFactory class InvoicesViewModel( private val invoicesRepository: InvoicesRepository, - val paymentComponent: PaymentComponent + val giniPaymentModule: GiniInternalPaymentModule ) : ViewModel() { val uploadHardcodedInvoicesStateFlow = invoicesRepository.uploadHardcodedInvoicesStateFlow @@ -24,7 +25,7 @@ class InvoicesViewModel( InvoiceItem.fromInvoice(invoice) } } - val paymentProviderAppsFlow = paymentComponent.paymentProviderAppsFlow + val paymentProviderAppsFlow = giniPaymentModule.paymentComponent.paymentProviderAppsFlow val openBankState = invoicesRepository.giniHealth.openBankState @@ -51,18 +52,19 @@ class InvoicesViewModel( fun loadPaymentProviderApps() { viewModelScope.launch { - paymentComponent.loadPaymentProviderApps() + giniPaymentModule.paymentComponent.loadPaymentProviderApps() } } - fun getPaymentReviewFragment(documentId: String): Result { + fun getPaymentReviewFragment(documentId: String?): Result { val documentWithExtractions = invoicesRepository.invoicesFlow.value.find { it.documentId == documentId } return if (documentWithExtractions != null) { return try { - val paymentReviewFragment = paymentComponent.getPaymentReviewFragment( + val paymentReviewFragment = invoicesRepository.giniHealth.getPaymentReviewFragment( documentWithExtractions.documentId, + giniPaymentModule.paymentComponent, ReviewConfiguration(showCloseButton = true) ) Result.success(paymentReviewFragment) @@ -77,7 +79,7 @@ class InvoicesViewModel( } fun setPaymentComponentConfig(paymentComponentConfiguration: PaymentComponentConfiguration) { - paymentComponent.paymentComponentConfiguration = paymentComponentConfiguration + giniPaymentModule.paymentComponent.paymentComponentConfiguration = paymentComponentConfiguration } companion object { diff --git a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/review/ReviewActivity.kt b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/review/ReviewActivity.kt index 8fb9f8fd9..f1764d79b 100644 --- a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/review/ReviewActivity.kt +++ b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/review/ReviewActivity.kt @@ -1,231 +1,233 @@ -package net.gini.android.health.sdk.exampleapp.review - -import android.content.Context -import android.content.DialogInterface -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.IntentCompat -import androidx.core.view.WindowCompat -import androidx.core.view.isGone -import androidx.fragment.app.commit -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.lifecycle.viewModelScope -import dev.chrisbanes.insetter.applyInsetter -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch -import net.gini.android.core.api.models.Document -import net.gini.android.health.sdk.GiniHealth -import net.gini.android.health.sdk.exampleapp.MainActivity -import net.gini.android.health.sdk.exampleapp.MainActivity.Companion.PAYMENT_COMPONENT_CONFIG -import net.gini.android.health.sdk.exampleapp.R -import net.gini.android.health.sdk.exampleapp.databinding.ActivityReviewBinding -import net.gini.android.health.sdk.review.ReviewConfiguration -import net.gini.android.health.sdk.review.ReviewFragment -import net.gini.android.health.sdk.review.ReviewFragmentListener -import net.gini.android.health.sdk.review.model.ResultWrapper -import net.gini.android.internal.payment.bankselection.BankSelectionBottomSheet -import net.gini.android.internal.payment.paymentComponent.PaymentComponent -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.slf4j.LoggerFactory - -class ReviewActivity : AppCompatActivity() { - - private val viewModel: ReviewViewModel by viewModel() - - private val reviewFragmentListener = object : ReviewFragmentListener { - override fun onCloseReview() { - LOG.debug("on close clicked") - finish() - } - - override fun onToTheBankButtonClicked(paymentProviderName: String) { - LOG.debug("to the bank button clicked with payment provider: {}", paymentProviderName) - lifecycleScope.launch { - viewModel.giniHealth.openBankState.collect { paymentState -> - when (paymentState) { - GiniHealth.PaymentState.Loading -> { - LOG.debug("opening bank app") - } - - is GiniHealth.PaymentState.Success -> { - LOG.debug("launching bank app: {}", paymentState.paymentRequest.bankApp.name) - cancel() - } - - is GiniHealth.PaymentState.Error -> { - LOG.error( "failed to open bank app:", paymentState.throwable) - cancel() - } - - GiniHealth.PaymentState.NoAction -> {} - } - } - } - } - } - - /** - * Set it to `true` to show the close button instead of the toolbar. - * - * When 'false' then also set the dimen resource `gpb_page_padding_top` to equal - * `toolbar_height` to prevent the toolbar overlapping the top of the page. - */ - private val showCloseButton = true - - override fun onCreate(savedInstanceState: Bundle?) { - viewModel.giniHealth.setSavedStateRegistryOwner(this, viewModel.viewModelScope) - - WindowCompat.setDecorFitsSystemWindows(window, false) - - super.onCreate(savedInstanceState) - - supportActionBar?.hide() - - val binding = ActivityReviewBinding.inflate(LayoutInflater.from(baseContext)) - IntentCompat.getParcelableExtra(intent, MainActivity.PAYMENT_COMPONENT_CONFIG, PaymentComponentConfiguration::class.java)?.let { - viewModel.setPaymentComponentConfig(it) - } - - setContentView(binding.root) - - binding.toolbar.isGone = showCloseButton - - binding.toolbar.applyInsetter { - type(statusBars = true) { - padding(top = true) - } - } - - binding.reviewFragment.applyInsetter { - type(statusBars = true, navigationBars = true) { - padding(top = true, bottom = true) - } - } - - // Set a listener on the PaymentComponent to receive events from the PaymentComponentView - viewModel.paymentComponent.listener = object : PaymentComponent.Listener { - override fun onMoreInformationClicked() {} - - override fun onBankPickerClicked() { - // Show the BankSelectionBottomSheet to allow the user to select a bank app - BankSelectionBottomSheet.newInstance(viewModel.paymentComponent).apply { - show(supportFragmentManager, BankSelectionBottomSheet::class.simpleName) - } - } - - override fun onPayInvoiceClicked(documentId: String) { - // Get and show the payment ReviewFragment for the document id - lifecycleScope.launch { - binding.progress.visibility = View.VISIBLE - - try { - val reviewFragment = viewModel.paymentComponent.getPaymentReviewFragment( - documentId = documentId, - configuration = ReviewConfiguration(showCloseButton = showCloseButton) - ) - - reviewFragment.listener = reviewFragmentListener - - supportFragmentManager.commit { - replace(R.id.review_fragment, reviewFragment, REVIEW_FRAGMENT_TAG) - } - } catch (e: Exception) { - AlertDialog.Builder(this@ReviewActivity) - .setTitle(getString(R.string.could_not_start_payment_review)) - .setMessage(e.message) - .setPositiveButton(android.R.string.ok, null) - .show() - } finally { - binding.progress.visibility = View.INVISIBLE - } - } - } - } - - // The PaymentComponentView needs the PaymentComponent to be set before it is shown - binding.paymentComponentView.paymentComponent = viewModel.paymentComponent - - lifecycleScope.launch { - val documentId = (viewModel.giniHealth.documentFlow.value as ResultWrapper.Success).value.id - - val isDocumentPayable = viewModel.giniHealth.checkIfDocumentIsPayable(documentId) - - if (!isDocumentPayable) { - AlertDialog.Builder(this@ReviewActivity) - .setTitle(R.string.document_not_payable_title) - .setMessage(R.string.document_not_payable_message) - .setPositiveButton(android.R.string.ok, object : DialogInterface.OnClickListener { - override fun onClick(dialog: DialogInterface, which: Int) { - finish() - } - }) - .show() - return@launch - } - - // Configure the PaymentComponentView - binding.paymentComponentView.isPayable = true - binding.paymentComponentView.documentId = documentId - - // Load the payment provider apps and show an alert dialog for errors - viewModel.paymentComponent.loadPaymentProviderApps() - - repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.paymentComponent.paymentProviderAppsFlow.collect { paymentProviderAppsState -> - when (paymentProviderAppsState) { - is PaymentProviderAppsState.Error -> { - binding.progress.visibility = View.INVISIBLE - - AlertDialog.Builder(this@ReviewActivity) - .setTitle(R.string.failed_to_load_bank_apps) - .setMessage(paymentProviderAppsState.throwable.message) - .setPositiveButton(android.R.string.ok, null) - .show() - } - - PaymentProviderAppsState.Loading -> { - binding.progress.visibility = View.VISIBLE - } - - is PaymentProviderAppsState.Success -> { - binding.progress.visibility = View.INVISIBLE - } - } - } - } - } - - binding.close.setOnClickListener { finish() } - - // Reattach the listener to the ReviewFragment if it is being shown (in case of configuration changes) - supportFragmentManager.findFragmentByTag(REVIEW_FRAGMENT_TAG)?.let { - (it as? ReviewFragment)?.listener = reviewFragmentListener - } - } - - companion object { - private val LOG = LoggerFactory.getLogger(ReviewActivity::class.java) - - private const val EXTRA_URIS = "EXTRA_URIS" - - fun getStartIntent(context: Context, pages: List = emptyList(), paymentComponentConfiguration: PaymentComponentConfiguration?): Intent = - Intent(context, ReviewActivity::class.java).apply { - putParcelableArrayListExtra( - EXTRA_URIS, - if (pages is ArrayList) pages else ArrayList().apply { addAll(pages) }) - putExtra(PAYMENT_COMPONENT_CONFIG, paymentComponentConfiguration) - } - - private val Intent.pageUris: List - get() = getParcelableArrayListExtra(EXTRA_URIS)?.toList() ?: emptyList() - } -} - -private const val REVIEW_FRAGMENT_TAG = "payment_review_fragment" +//package net.gini.android.health.sdk.exampleapp.review +// +//import android.content.Context +//import android.content.DialogInterface +//import android.content.Intent +//import android.net.Uri +//import android.os.Bundle +//import android.view.LayoutInflater +//import android.view.View +//import androidx.appcompat.app.AlertDialog +//import androidx.appcompat.app.AppCompatActivity +//import androidx.core.content.IntentCompat +//import androidx.core.view.WindowCompat +//import androidx.core.view.isGone +//import androidx.fragment.app.commit +//import androidx.lifecycle.Lifecycle +//import androidx.lifecycle.lifecycleScope +//import androidx.lifecycle.repeatOnLifecycle +//import androidx.lifecycle.viewModelScope +//import dev.chrisbanes.insetter.applyInsetter +//import kotlinx.coroutines.cancel +//import kotlinx.coroutines.launch +//import net.gini.android.core.api.models.Document +//import net.gini.android.health.sdk.GiniHealth +//import net.gini.android.health.sdk.exampleapp.MainActivity +//import net.gini.android.health.sdk.exampleapp.MainActivity.Companion.PAYMENT_COMPONENT_CONFIG +//import net.gini.android.health.sdk.exampleapp.R +//import net.gini.android.health.sdk.exampleapp.databinding.ActivityReviewBinding +//import net.gini.android.health.sdk.review.ReviewFragment +//import net.gini.android.health.sdk.review.ReviewFragmentListener +//import net.gini.android.health.sdk.review.model.ResultWrapper +//import net.gini.android.internal.payment.bankselection.BankSelectionBottomSheet +//import net.gini.android.internal.payment.paymentComponent.PaymentComponent +//import net.gini.android.internal.payment.paymentComponent.PaymentComponentConfiguration +//import net.gini.android.internal.payment.paymentComponent.PaymentProviderAppsState +//import net.gini.android.internal.payment.review.ReviewConfiguration +//import org.koin.androidx.viewmodel.ext.android.viewModel +//import org.slf4j.LoggerFactory +// +//class ReviewActivity : AppCompatActivity() { +// +// private val viewModel: ReviewViewModel by viewModel() +// +// private val reviewFragmentListener = object : ReviewFragmentListener { +// override fun onCloseReview() { +// LOG.debug("on close clicked") +// finish() +// } +// +// override fun onToTheBankButtonClicked(paymentProviderName: String) { +// LOG.debug("to the bank button clicked with payment provider: {}", paymentProviderName) +// lifecycleScope.launch { +// viewModel.giniHealth.openBankState.collect { paymentState -> +// when (paymentState) { +// GiniHealth.PaymentState.Loading -> { +// LOG.debug("opening bank app") +// } +// +// is GiniHealth.PaymentState.Success -> { +// LOG.debug("launching bank app: {}", paymentState.paymentRequest.bankApp.name) +// cancel() +// } +// +// is GiniHealth.PaymentState.Error -> { +// LOG.error( "failed to open bank app:", paymentState.throwable) +// cancel() +// } +// +// GiniHealth.PaymentState.NoAction -> {} +// } +// } +// } +// } +// } +// +// /** +// * Set it to `true` to show the close button instead of the toolbar. +// * +// * When 'false' then also set the dimen resource `gpb_page_padding_top` to equal +// * `toolbar_height` to prevent the toolbar overlapping the top of the page. +// */ +// private val showCloseButton = true +// +// override fun onCreate(savedInstanceState: Bundle?) { +// viewModel.giniHealth.setSavedStateRegistryOwner(this, viewModel.viewModelScope) +// +// WindowCompat.setDecorFitsSystemWindows(window, false) +// +// super.onCreate(savedInstanceState) +// +// supportActionBar?.hide() +// +// val binding = ActivityReviewBinding.inflate(LayoutInflater.from(baseContext)) +// IntentCompat.getParcelableExtra(intent, MainActivity.PAYMENT_COMPONENT_CONFIG, PaymentComponentConfiguration::class.java)?.let { +// viewModel.setPaymentComponentConfig(it) +// } +// +// setContentView(binding.root) +// +// binding.toolbar.isGone = showCloseButton +// +// binding.toolbar.applyInsetter { +// type(statusBars = true) { +// padding(top = true) +// } +// } +// +// binding.reviewFragment.applyInsetter { +// type(statusBars = true, navigationBars = true) { +// padding(top = true, bottom = true) +// } +// } +// +// // Set a listener on the PaymentComponent to receive events from the PaymentComponentView +// viewModel.paymentComponent.listener = object : PaymentComponent.Listener { +// override fun onMoreInformationClicked() {} +// +// override fun onBankPickerClicked() { +// // Show the BankSelectionBottomSheet to allow the user to select a bank app +// BankSelectionBottomSheet.newInstance(viewModel.paymentComponent).apply { +// show(supportFragmentManager, BankSelectionBottomSheet::class.simpleName) +// } +// } +// +// override fun onPayInvoiceClicked(documentId: String?) { +// // Get and show the payment ReviewFragment for the document id +// lifecycleScope.launch { +// binding.progress.visibility = View.VISIBLE +// +// try { +// val reviewFragment = viewModel.paymentComponent.getPaymentReviewFragment( +// documentId = documentId, +// configuration = ReviewConfiguration(showCloseButton = showCloseButton) +// ) +// +// reviewFragment.listener = reviewFragmentListener +// +// supportFragmentManager.commit { +// replace(R.id.review_fragment, reviewFragment, REVIEW_FRAGMENT_TAG) +// } +// } catch (e: Exception) { +// AlertDialog.Builder(this@ReviewActivity) +// .setTitle(getString(R.string.could_not_start_payment_review)) +// .setMessage(e.message) +// .setPositiveButton(android.R.string.ok, null) +// .show() +// } finally { +// binding.progress.visibility = View.INVISIBLE +// } +// } +// } +// } +// +// // The PaymentComponentView needs the PaymentComponent to be set before it is shown +// binding.paymentComponentView.paymentComponent = viewModel.paymentComponent +// +// lifecycleScope.launch { +// val documentId = (viewModel.giniHealth.documentFlow.value as ResultWrapper.Success).value.id +// +// val isDocumentPayable = viewModel.giniHealth.checkIfDocumentIsPayable(documentId) +// +// if (!isDocumentPayable) { +// AlertDialog.Builder(this@ReviewActivity) +// .setTitle(R.string.document_not_payable_title) +// .setMessage(R.string.document_not_payable_message) +// .setPositiveButton(android.R.string.ok, object : DialogInterface.OnClickListener { +// override fun onClick(dialog: DialogInterface, which: Int) { +// finish() +// } +// }) +// .show() +// return@launch +// } +// +// // Configure the PaymentComponentView +// binding.paymentComponentView.isPayable = true +// binding.paymentComponentView.documentId = documentId +// +// // Load the payment provider apps and show an alert dialog for errors +// viewModel.paymentComponent.loadPaymentProviderApps() +// +// repeatOnLifecycle(Lifecycle.State.STARTED) { +// viewModel.paymentComponent.paymentProviderAppsFlow.collect { paymentProviderAppsState -> +// when (paymentProviderAppsState) { +// is PaymentProviderAppsState.Error -> { +// binding.progress.visibility = View.INVISIBLE +// +// AlertDialog.Builder(this@ReviewActivity) +// .setTitle(R.string.failed_to_load_bank_apps) +// .setMessage(paymentProviderAppsState.throwable.message) +// .setPositiveButton(android.R.string.ok, null) +// .show() +// } +// +// PaymentProviderAppsState.Loading -> { +// binding.progress.visibility = View.VISIBLE +// } +// +// is PaymentProviderAppsState.Success -> { +// binding.progress.visibility = View.INVISIBLE +// } +// } +// } +// } +// } +// +// binding.close.setOnClickListener { finish() } +// +// // Reattach the listener to the ReviewFragment if it is being shown (in case of configuration changes) +// supportFragmentManager.findFragmentByTag(REVIEW_FRAGMENT_TAG)?.let { +// (it as? ReviewFragment)?.listener = reviewFragmentListener +// } +// } +// +// companion object { +// private val LOG = LoggerFactory.getLogger(ReviewActivity::class.java) +// +// private const val EXTRA_URIS = "EXTRA_URIS" +// +// fun getStartIntent(context: Context, pages: List = emptyList(), paymentComponentConfiguration: PaymentComponentConfiguration?): Intent = +// Intent(context, ReviewActivity::class.java).apply { +// putParcelableArrayListExtra( +// EXTRA_URIS, +// if (pages is ArrayList) pages else ArrayList().apply { addAll(pages) }) +// putExtra(PAYMENT_COMPONENT_CONFIG, paymentComponentConfiguration) +// } +// +// private val Intent.pageUris: List +// get() = getParcelableArrayListExtra(EXTRA_URIS)?.toList() ?: emptyList() +// } +//} +// +//private const val REVIEW_FRAGMENT_TAG = "payment_review_fragment" diff --git a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/review/ReviewViewModel.kt b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/review/ReviewViewModel.kt index 33bbc4ad8..40c2a6423 100644 --- a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/review/ReviewViewModel.kt +++ b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/review/ReviewViewModel.kt @@ -2,7 +2,8 @@ package net.gini.android.health.sdk.exampleapp.review import androidx.lifecycle.ViewModel import net.gini.android.health.sdk.GiniHealth -import net.gini.android.health.sdk.paymentcomponent.PaymentComponent +import net.gini.android.internal.payment.paymentComponent.PaymentComponent +import net.gini.android.internal.payment.paymentComponent.PaymentComponentConfiguration class ReviewViewModel(val giniHealth: GiniHealth, val paymentComponent: PaymentComponent) : ViewModel() { fun setPaymentComponentConfig(paymentComponentConfiguration: PaymentComponentConfiguration) { diff --git a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/upload/UploadActivity.kt b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/upload/UploadActivity.kt index d80cc281f..546631847 100644 --- a/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/upload/UploadActivity.kt +++ b/health-sdk/example-app/src/main/java/net/gini/android/health/sdk/exampleapp/upload/UploadActivity.kt @@ -5,16 +5,14 @@ import android.content.Intent import android.net.Uri import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.IntentCompat import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import net.gini.android.health.sdk.exampleapp.MainActivity.Companion.PAYMENT_COMPONENT_CONFIG import net.gini.android.health.sdk.exampleapp.R import net.gini.android.health.sdk.exampleapp.databinding.ActivityUploadBinding -import net.gini.android.health.sdk.exampleapp.review.ReviewActivity import net.gini.android.health.sdk.exampleapp.upload.UploadViewModel.UploadState -import org.koin.androidx.viewmodel.ext.android.viewModel import net.gini.android.internal.payment.paymentComponent.PaymentComponentConfiguration +import org.koin.androidx.viewmodel.ext.android.viewModel class UploadActivity : AppCompatActivity() { @@ -35,7 +33,7 @@ class UploadActivity : AppCompatActivity() { } binding.payment.setOnClickListener { - startActivity(ReviewActivity.getStartIntent(this, intent.pageUris, IntentCompat.getParcelableExtra(intent, PAYMENT_COMPONENT_CONFIG, PaymentComponentConfiguration::class.java))) +// startActivity(ReviewActivity.getStartIntent(this, intent.pageUris, IntentCompat.getParcelableExtra(intent, PAYMENT_COMPONENT_CONFIG, PaymentComponentConfiguration::class.java))) } } diff --git a/health-sdk/example-app/src/main/res/layout/activity_review.xml b/health-sdk/example-app/src/main/res/layout/activity_review.xml index ffdfbac86..29f65cf9e 100644 --- a/health-sdk/example-app/src/main/res/layout/activity_review.xml +++ b/health-sdk/example-app/src/main/res/layout/activity_review.xml @@ -45,14 +45,14 @@ tools:ignore="ContentDescription" /> - + + + + + + + + - { + LOG.debug("Creating ReviewFragment for selected payment provider app: {}", selectedPaymentProviderAppState.paymentProviderApp.name) + + return ReviewFragment.newInstance( + this, + configuration = configuration, + paymentComponent = paymentComponent, + documentId = documentId + ) + } + + SelectedPaymentProviderAppState.NothingSelected -> { + LOG.error("Cannot create ReviewFragment: No selected payment provider app") + + val exception = + IllegalStateException("Cannot create ReviewFragment: No selected payment provider app") + throw exception + } + } + } + + private val savedStateProvider = SavedStateRegistry.SavedStateProvider { Bundle().apply { when (capturedArguments) { diff --git a/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewFragment.kt b/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewFragment.kt index a708376b0..bf7e8e8f4 100644 --- a/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewFragment.kt +++ b/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewFragment.kt @@ -14,7 +14,6 @@ import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.FileProvider -import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsCompat @@ -23,13 +22,13 @@ import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams -import androidx.core.widget.addTextChangedListener import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.lifecycle.viewModelScope import androidx.transition.ChangeBounds import androidx.transition.Transition import androidx.transition.TransitionListenerAdapter @@ -49,46 +48,23 @@ import net.gini.android.health.sdk.databinding.GhsFragmentReviewBinding import net.gini.android.health.sdk.preferences.UserPreferences import net.gini.android.health.sdk.review.model.PaymentDetails import net.gini.android.health.sdk.review.model.ResultWrapper +import net.gini.android.health.sdk.review.model.toCommonPaymentDetails import net.gini.android.health.sdk.review.pager.DocumentPageAdapter -import net.gini.android.health.sdk.util.amountWatcher -import net.gini.android.health.sdk.util.clearErrorMessage import net.gini.android.health.sdk.util.hideErrorMessage import net.gini.android.health.sdk.util.hideKeyboard -import net.gini.android.health.sdk.util.setBackgroundTint -import net.gini.android.health.sdk.util.setErrorMessage -import net.gini.android.health.sdk.util.setTextIfDifferent import net.gini.android.health.sdk.util.showErrorMessage import net.gini.android.internal.payment.paymentComponent.PaymentComponent import net.gini.android.internal.payment.paymentProvider.PaymentProviderApp -import net.gini.android.internal.payment.review.PaymentField -import net.gini.android.internal.payment.review.ValidationMessage +import net.gini.android.internal.payment.review.ReviewConfiguration +import net.gini.android.internal.payment.review.reviewComponent.ReviewComponent +import net.gini.android.internal.payment.review.reviewComponent.ReviewFields import net.gini.android.internal.payment.utils.autoCleared import net.gini.android.internal.payment.utils.extensions.getFontScale import net.gini.android.internal.payment.utils.extensions.getLayoutInflaterWithGiniPaymentThemeAndLocale +import net.gini.android.internal.payment.utils.extensions.getLocaleStringResource import net.gini.android.internal.payment.utils.extensions.wrappedWithGiniPaymentThemeAndLocale import java.io.File -/** - * Configuration for the [ReviewFragment]. - */ -data class ReviewConfiguration( - /** - * If set to `true`, the [ReviewFragment] will handle errors internally and show snackbars for errors. - * If set to `false`, errors will be ignored by the [ReviewFragment]. In this case the flows exposed by [GiniHealth] should be observed for errors. - * - * Default value is `true`. - */ - val handleErrorsInternally: Boolean = true, - - /** - * Set to `true` to show a close button. Set a [ReviewFragmentListener] to be informed when the - * button is pressed. - * - * Default value is `false`. - */ - val showCloseButton: Boolean = false -) - /** * Listener for [ReviewFragment] events. */ @@ -158,8 +134,18 @@ class ReviewFragment private constructor( viewModel.loadPaymentDetails() with(binding) { + ghsPaymentDetails.reviewComponent = ReviewComponent( + viewModel.paymentComponent, + net.gini.android.internal.payment.review.ReviewConfiguration( + handleErrorsInternally = true, + showCloseButton = true, + editableFields = listOf(ReviewFields.RECIPIENT, ReviewFields.IBAN, ReviewFields.AMOUNT, ReviewFields.PURPOSE) + ), + viewModel.paymentComponent.paymentModule, + viewModel.viewModelScope + ) setStateListeners() - setInputListeners() +// setInputListeners() setKeyboardAnimation() removePagerConstraintAndSetPreviousHeightIfNeeded(documentPagerHeight) } @@ -187,18 +173,18 @@ class ReviewFragment private constructor( launch { viewModel.paymentDetails.collect { setPaymentDetails(it) } } - launch { - viewModel.paymentValidation.collect { handleValidationResult(it) } - } +// launch { +// viewModel.paymentValidation.collect { handleValidationResult(it) } +// } launch { viewModel.giniHealth.openBankState.collect { handlePaymentState(it) } } - launch { - viewModel.isPaymentButtonEnabled.collect { isEnabled -> - payment.isEnabled = isEnabled - payment.alpha = if (isEnabled) 1f else 0.4f - } - } +// launch { +// viewModel.isPaymentButtonEnabled.collect { isEnabled -> +// payment.isEnabled = isEnabled +// payment.alpha = if (isEnabled) 1f else 0.4f +// } +// } launch { viewModel.isInfoBarVisible.collect { visible -> if (visible) showInfoBar() else hideInfoBarAnimated() @@ -207,7 +193,7 @@ class ReviewFragment private constructor( launch { viewModel.paymentProviderApp.collect { paymentProviderApp -> if (paymentProviderApp != null) { - showSelectedPaymentProviderApp(paymentProviderApp) +// showSelectedPaymentProviderApp(paymentProviderApp) setActionListeners(paymentProviderApp) } } @@ -225,24 +211,6 @@ class ReviewFragment private constructor( } } - private fun GhsFragmentReviewBinding.showSelectedPaymentProviderApp(paymentProviderApp: PaymentProviderApp) { - paymentProviderApp.icon?.let { appIcon -> - val roundedDrawable = - RoundedBitmapDrawableFactory.create(requireContext().resources, appIcon.bitmap).apply { - cornerRadius = resources.getDimension(R.dimen.ghs_small_2) - } - - payment.setCompoundDrawablesWithIntrinsicBounds( - roundedDrawable, - null, - null, - null - ) - } - payment.setBackgroundTint(paymentProviderApp.colors.backgroundColor, 255) - payment.setTextColor(paymentProviderApp.colors.textColor) - } - private fun GhsFragmentReviewBinding.handleDocumentResult(documentResult: ResultWrapper) { when (documentResult) { is ResultWrapper.Success -> { @@ -276,72 +244,73 @@ class ReviewFragment private constructor( } private fun GhsFragmentReviewBinding.setPaymentDetails(paymentDetails: PaymentDetails) { - recipient.setTextIfDifferent(paymentDetails.recipient) - iban.setTextIfDifferent(paymentDetails.iban) - amount.setTextIfDifferent(paymentDetails.amount) - purpose.setTextIfDifferent(paymentDetails.purpose) + binding.ghsPaymentDetails.paymentDetails = paymentDetails.toCommonPaymentDetails() +// recipient.setTextIfDifferent(paymentDetails.recipient) +// iban.setTextIfDifferent(paymentDetails.iban) +// amount.setTextIfDifferent(paymentDetails.amount) +// purpose.setTextIfDifferent(paymentDetails.purpose) } //TODO this should be refactored to use [ReviewView] in the layout, // instead of the current layout. And it should use the [PaymentDetailsValidation] and [GiniPaymentManager] classes to handle validation and payments. - private fun GhsFragmentReviewBinding.setInputListeners() { - recipient.addTextChangedListener(onTextChanged = { text, _, _, _ -> viewModel.setRecipient(text.toString()) }) - iban.addTextChangedListener(onTextChanged = { text, _, _, _ -> viewModel.setIban(text.toString()) }) - amount.addTextChangedListener(onTextChanged = { text, _, _, _ -> viewModel.setAmount(text.toString()) }) - amount.addTextChangedListener(amountWatcher) - purpose.addTextChangedListener(onTextChanged = { text, _, _, _ -> viewModel.setPurpose(text.toString()) }) - recipient.setOnFocusChangeListener { _, hasFocus -> handleInputFocusChange(hasFocus, recipientLayout) } - iban.setOnFocusChangeListener { _, hasFocus -> handleInputFocusChange(hasFocus, ibanLayout) } - amount.setOnFocusChangeListener { _, hasFocus -> handleInputFocusChange(hasFocus, amountLayout) } - purpose.setOnFocusChangeListener { _, hasFocus -> handleInputFocusChange(hasFocus, purposeLayout) } - } +// private fun GhsFragmentReviewBinding.setInputListeners() { +// recipient.addTextChangedListener(onTextChanged = { text, _, _, _ -> viewModel.setRecipient(text.toString()) }) +// iban.addTextChangedListener(onTextChanged = { text, _, _, _ -> viewModel.setIban(text.toString()) }) +// amount.addTextChangedListener(onTextChanged = { text, _, _, _ -> viewModel.setAmount(text.toString()) }) +// amount.addTextChangedListener(amountWatcher) +// purpose.addTextChangedListener(onTextChanged = { text, _, _, _ -> viewModel.setPurpose(text.toString()) }) +// recipient.setOnFocusChangeListener { _, hasFocus -> handleInputFocusChange(hasFocus, recipientLayout) } +// iban.setOnFocusChangeListener { _, hasFocus -> handleInputFocusChange(hasFocus, ibanLayout) } +// amount.setOnFocusChangeListener { _, hasFocus -> handleInputFocusChange(hasFocus, amountLayout) } +// purpose.setOnFocusChangeListener { _, hasFocus -> handleInputFocusChange(hasFocus, purposeLayout) } +// } private fun handleInputFocusChange(hasFocus: Boolean, textInputLayout: TextInputLayout) { if (hasFocus) textInputLayout.hideErrorMessage() else textInputLayout.showErrorMessage() } - private fun GhsFragmentReviewBinding.handleValidationResult(messages: List) { - val (fieldsWithError, fieldsWithoutError) = PaymentField.values() - .map { field -> field to messages.firstOrNull { it.field == field } } - .partition { (_, message) -> message != null } - fieldsWithError.forEach { (field, validationMessage) -> - validationMessage?.let { message -> - getTextInputLayout(field).apply { - if (error.isNullOrEmpty() || getTag(R.id.text_input_layout_tag_is_error_enabled) == null) { - setErrorMessage( - when (message) { - is ValidationMessage.Empty -> when (field) { - PaymentField.Recipient -> R.string.ghs_error_input_recipient_empty - PaymentField.Iban -> R.string.ghs_error_input_iban_empty - PaymentField.Amount -> R.string.ghs_error_input_amount_empty - PaymentField.Purpose -> R.string.ghs_error_input_purpose_empty - } - - ValidationMessage.InvalidIban -> R.string.ghs_error_input_invalid_iban - ValidationMessage.AmountFormat -> R.string.ghs_error_input_amount_format - } - ) - if (editText?.isFocused == true) { - hideErrorMessage() - } - } - } - } - } - - fieldsWithoutError.forEach { (field, _) -> - getTextInputLayout(field).apply { - clearErrorMessage() - } - } - } +// private fun GhsFragmentReviewBinding.handleValidationResult(messages: List) { +// val (fieldsWithError, fieldsWithoutError) = PaymentField.values() +// .map { field -> field to messages.firstOrNull { it.field == field } } +// .partition { (_, message) -> message != null } +// fieldsWithError.forEach { (field, validationMessage) -> +// validationMessage?.let { message -> +// getTextInputLayout(field).apply { +// if (error.isNullOrEmpty() || getTag(R.id.text_input_layout_tag_is_error_enabled) == null) { +// setErrorMessage( +// when (message) { +// is ValidationMessage.Empty -> when (field) { +// PaymentField.Recipient -> R.string.ghs_error_input_recipient_empty +// PaymentField.Iban -> R.string.ghs_error_input_iban_empty +// PaymentField.Amount -> R.string.ghs_error_input_amount_empty +// PaymentField.Purpose -> R.string.ghs_error_input_purpose_empty +// } +// +// ValidationMessage.InvalidIban -> R.string.ghs_error_input_invalid_iban +// ValidationMessage.AmountFormat -> R.string.ghs_error_input_amount_format +// } +// ) +// if (editText?.isFocused == true) { +// hideErrorMessage() +// } +// } +// } +// } +// } +// +// fieldsWithoutError.forEach { (field, _) -> +// getTextInputLayout(field).apply { +// clearErrorMessage() +// } +// } +// } - private fun GhsFragmentReviewBinding.getTextInputLayout(field: PaymentField) = when (field) { - PaymentField.Recipient -> recipientLayout - PaymentField.Iban -> ibanLayout - PaymentField.Amount -> amountLayout - PaymentField.Purpose -> purposeLayout - } +// private fun GhsFragmentReviewBinding.getTextInputLayout(field: PaymentField) = when (field) { +// PaymentField.Recipient -> recipientLayout +// PaymentField.Iban -> ibanLayout +// PaymentField.Amount -> amountLayout +// PaymentField.Purpose -> purposeLayout +// } private fun GhsFragmentReviewBinding.handlePaymentState(paymentState: GiniHealth.PaymentState) { (paymentState is GiniHealth.PaymentState.Loading).let { isLoading -> @@ -371,13 +340,14 @@ class ReviewFragment private constructor( } } + //TODO private fun GhsFragmentReviewBinding.handleLoading(isLoading: Boolean) { paymentProgress.isVisible = isLoading - recipientLayout.isEnabled = !isLoading - ibanLayout.isEnabled = !isLoading - amountLayout.isEnabled = !isLoading - purposeLayout.isEnabled = !isLoading - payment.text = if (isLoading) "" else getLocaleStringResource(R.string.ghs_pay_button) +// recipientLayout.isEnabled = !isLoading +// ibanLayout.isEnabled = !isLoading +// amountLayout.isEnabled = !isLoading +// purposeLayout.isEnabled = !isLoading +// payment.text = if (isLoading) "" else getLocaleStringResource(R.string.ghs_pay_button) } private fun GhsFragmentReviewBinding.handleError(text: String, onRetry: () -> Unit) { @@ -403,12 +373,12 @@ class ReviewFragment private constructor( } private fun GhsFragmentReviewBinding.setActionListeners(paymentProviderApp: PaymentProviderApp) { - paymentDetails.setOnClickListener { it.hideKeyboard() } - payment.setOnClickListener { - requireActivity().currentFocus?.clearFocus() - it.hideKeyboard() - viewModel.onPaymentButtonTapped(requireContext().externalCacheDir) - } + ghsPaymentDetails.setOnClickListener { it.hideKeyboard() } +// payment.setOnClickListener { +// requireActivity().currentFocus?.clearFocus() +// it.hideKeyboard() +// viewModel.onPaymentButtonTapped(requireContext().externalCacheDir) +// } close.setOnClickListener { view -> if (isKeyboardShown) { view.hideKeyboard() @@ -419,11 +389,11 @@ class ReviewFragment private constructor( } private fun GhsFragmentReviewBinding.applyInsets() { - paymentDetails.applyInsetter { - type(navigationBars = true, ime = true) { - padding(bottom = true) - } - } +// paymentDetails.applyInsetter { +// type(navigationBars = true, ime = true) { +// padding(bottom = true) +// } +// } close.applyInsetter { type(statusBars = true) { margin(top = true) @@ -449,13 +419,13 @@ class ReviewFragment private constructor( private fun GhsFragmentReviewBinding.setKeyboardAnimation() { ViewCompat.setWindowInsetsAnimationCallback( - paymentDetails, + ghsPaymentDetails, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) { var startBottom = 0 var endBottom = 0 override fun onPrepare(animation: WindowInsetsAnimationCompat) { - startBottom = paymentDetails.paddingBottom + startBottom = ghsPaymentDetails.paddingBottom } override fun onStart( @@ -463,9 +433,9 @@ class ReviewFragment private constructor( bounds: WindowInsetsAnimationCompat.BoundsCompat ): WindowInsetsAnimationCompat.BoundsCompat { if (Build.VERSION.SDK_INT >= 30) { - endBottom = paymentDetails.paddingBottom - paymentDetails.translationY = (endBottom - startBottom).toFloat() - paymentDetailsInfoBar.translationY = paymentDetails.translationY + endBottom = ghsPaymentDetails.paddingBottom + ghsPaymentDetails.translationY = (endBottom - startBottom).toFloat() + paymentDetailsInfoBar.translationY = ghsPaymentDetails.translationY } if (startBottom < endBottom) { indicator.isVisible = false @@ -479,9 +449,9 @@ class ReviewFragment private constructor( ): WindowInsetsCompat { if (Build.VERSION.SDK_INT >= 30) { runningAnimations.find { it.typeMask == windowInsetTypesOf(ime = true) }?.let { animation -> - paymentDetails.translationY = + ghsPaymentDetails.translationY = lerp((endBottom - startBottom).toFloat(), 0f, animation.interpolatedFraction) - paymentDetailsInfoBar.translationY = paymentDetails.translationY + paymentDetailsInfoBar.translationY = ghsPaymentDetails.translationY } } return insets @@ -490,15 +460,15 @@ class ReviewFragment private constructor( override fun onEnd(animation: WindowInsetsAnimationCompat) { super.onEnd(animation) if (Build.VERSION.SDK_INT >= 30) { - paymentDetails.translationY = 0f - paymentDetailsInfoBar.translationY = paymentDetails.translationY + ghsPaymentDetails.translationY = 0f + paymentDetailsInfoBar.translationY = ghsPaymentDetails.translationY } // Was it a closing animation? if (startBottom > endBottom) { if (pager.isUserInputEnabled) { indicator.isVisible = true } - binding.clearFocus() + binding.ghsPaymentDetails.clearFocus() isKeyboardShown = false } else { isKeyboardShown = true @@ -507,12 +477,9 @@ class ReviewFragment private constructor( }) } - private fun GhsFragmentReviewBinding.clearFocus() { - recipient.clearFocus() - iban.clearFocus() - amount.clearFocus() - purpose.clearFocus() - } +// private fun GhsFragmentReviewBinding.clearFocus() { +// +// } private fun GhsFragmentReviewBinding.showInfoBar() { root.doOnLayout { @@ -601,31 +568,31 @@ class ReviewFragment private constructor( //TODO this should be handled via [FlowBottomSheetsManager] private fun handlePaymentNextStep(paymentNextStep: ReviewViewModel.PaymentNextStep) { - when (paymentNextStep) { - is ReviewViewModel.PaymentNextStep.SetLoadingVisibility -> { - binding.handleLoading(paymentNextStep.isVisible) - errorSnackbar?.dismiss() - } - ReviewViewModel.PaymentNextStep.RedirectToBank -> { - viewModel.paymentProviderApp.value?.name?.let { - listener?.onToTheBankButtonClicked(it) - viewModel.onPayment() - } - } - ReviewViewModel.PaymentNextStep.ShowOpenWithSheet -> { - if (viewModel.validatePaymentDetails()) { - viewModel.paymentProviderApp.value?.let { showOpenWithDialog(it) } - } - } - ReviewViewModel.PaymentNextStep.ShowInstallApp -> - if (viewModel.validatePaymentDetails()) { - viewModel.paymentProviderApp.value?.let { showInstallAppDialog(it) } - } - is ReviewViewModel.PaymentNextStep.OpenSharePdf -> { - binding.loading.isVisible = false - startSharePdfIntent(paymentNextStep.file) - } - } +// when (paymentNextStep) { +// is ReviewViewModel.PaymentNextStep.SetLoadingVisibility -> { +// binding.handleLoading(paymentNextStep.isVisible) +// errorSnackbar?.dismiss() +// } +// ReviewViewModel.PaymentNextStep.RedirectToBank -> { +// viewModel.paymentProviderApp.value?.name?.let { +// listener?.onToTheBankButtonClicked(it) +// viewModel.onPayment() +// } +// } +// ReviewViewModel.PaymentNextStep.ShowOpenWithSheet -> { +// if (viewModel.validatePaymentDetails()) { +// viewModel.paymentProviderApp.value?.let { showOpenWithDialog(it) } +// } +// } +// ReviewViewModel.PaymentNextStep.ShowInstallApp -> +// if (viewModel.validatePaymentDetails()) { +// viewModel.paymentProviderApp.value?.let { showInstallAppDialog(it) } +// } +// is ReviewViewModel.PaymentNextStep.OpenSharePdf -> { +// binding.loading.isVisible = false +// startSharePdfIntent(paymentNextStep.file) +// } +// } } private fun createSharePendingIntent() = PendingIntent.getBroadcast( @@ -635,7 +602,7 @@ class ReviewFragment private constructor( ) private fun getLocaleStringResource(resourceId: Int): String { - return getLocaleStringResource(resourceId, viewModel.paymentComponent.giniHealth) + return getLocaleStringResource(resourceId, viewModel.paymentComponent.paymentModule) } override fun onSaveInstanceState(outState: Bundle) { diff --git a/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewViewModel.kt b/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewViewModel.kt index 883b5ccd6..2737c65dd 100644 --- a/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewViewModel.kt +++ b/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewViewModel.kt @@ -8,32 +8,27 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import net.gini.android.core.api.Resource import net.gini.android.core.api.models.Document -import net.gini.android.health.api.models.PaymentRequestInput import net.gini.android.health.sdk.GiniHealth import net.gini.android.health.sdk.preferences.UserPreferences import net.gini.android.health.sdk.review.model.PaymentDetails import net.gini.android.health.sdk.review.model.PaymentRequest import net.gini.android.health.sdk.review.model.ResultWrapper +import net.gini.android.health.sdk.review.model.toCommonPaymentDetails import net.gini.android.health.sdk.review.model.withFeedback import net.gini.android.health.sdk.review.pager.DocumentPageAdapter import net.gini.android.health.sdk.util.adjustToLocalDecimalSeparation -import net.gini.android.health.sdk.util.toBackendFormat -import net.gini.android.health.sdk.util.withPrev import net.gini.android.internal.payment.paymentComponent.PaymentComponent import net.gini.android.internal.payment.paymentComponent.SelectedPaymentProviderAppState import net.gini.android.internal.payment.paymentProvider.PaymentProviderApp +import net.gini.android.internal.payment.review.ReviewConfiguration import net.gini.android.internal.payment.review.ValidationMessage import net.gini.android.internal.payment.utils.FlowBottomSheetsManager import net.gini.android.internal.payment.utils.createTempPdfFile @@ -74,18 +69,18 @@ internal class ReviewViewModel( override val paymentNextStepFlow= MutableSharedFlow( extraBufferCapacity = 1, ) - override val paymentRequestFlow = MutableStateFlow(null) + override val paymentRequestFlow = MutableStateFlow(null) override val shareWithFlowStarted: MutableStateFlow = MutableStateFlow(false) private val _paymentRequestFlow = MutableStateFlow(null) - val isPaymentButtonEnabled: Flow = - combine(giniHealth.openBankState, paymentDetails) { paymentState, paymentDetails -> - val noEmptyFields = paymentDetails.recipient.isNotEmpty() && paymentDetails.iban.isNotEmpty() && - paymentDetails.amount.isNotEmpty() && paymentDetails.purpose.isNotEmpty() - val isLoading = (paymentState is GiniHealth.PaymentState.Loading) - !isLoading && noEmptyFields - } +// val isPaymentButtonEnabled: Flow = +// combine(giniHealth.openBankState, paymentDetails) { paymentState, paymentDetails -> +// val noEmptyFields = paymentDetails.recipient.isNotEmpty() && paymentDetails.iban.isNotEmpty() && +// paymentDetails.amount.isNotEmpty() && paymentDetails.purpose.isNotEmpty() +// val isLoading = (paymentState is GiniHealth.PaymentState.Loading) +// !isLoading && noEmptyFields +// } init { viewModelScope.launch { @@ -104,36 +99,36 @@ internal class ReviewViewModel( delay(SHOW_INFO_BAR_MS) _isInfoBarVisible.value = false } - viewModelScope.launch { - // Validate payment details only if extracted payment details are not being loaded - _paymentDetails - .combine(giniHealth.paymentFlow.filter { it !is ResultWrapper.Loading }) { paymentDetails, _ -> paymentDetails } - .withPrev() - .collect { (prevPaymentDetails, paymentDetails) -> - // Get all validation messages except emptiness validations - val nonEmptyValidationMessages = _paymentValidation.value - .filter { it !is ValidationMessage.Empty } - .toMutableList() - - // Check payment details for emptiness - val newEmptyValidationMessages = - paymentDetails.validate().filterIsInstance() - - // Clear IBAN error, if IBAN changed - if (prevPaymentDetails != null && prevPaymentDetails.iban != paymentDetails.iban) { - nonEmptyValidationMessages.remove(ValidationMessage.InvalidIban) - } - - // If the IBAN is the same as the last validated one, then revalidate it to restore the validation - // message if needed - if (_lastFullyValidatedPaymentDetails.value?.iban == paymentDetails.iban) { - nonEmptyValidationMessages.addAll(validateIban(paymentDetails.iban).filterIsInstance()) - } - - // Emit all new empty validation messages along with other existing validation messages - _paymentValidation.tryEmit(newEmptyValidationMessages + nonEmptyValidationMessages) - } - } +// viewModelScope.launch { +// // Validate payment details only if extracted payment details are not being loaded +// _paymentDetails +// .combine(giniHealth.paymentFlow.filter { it !is ResultWrapper.Loading }) { paymentDetails, _ -> paymentDetails } +// .withPrev() +// .collect { (prevPaymentDetails, paymentDetails) -> +// // Get all validation messages except emptiness validations +// val nonEmptyValidationMessages = _paymentValidation.value +// .filter { it !is ValidationMessage.Empty } +// .toMutableList() +// +// // Check payment details for emptiness +// val newEmptyValidationMessages = +// paymentDetails.validate().filterIsInstance() +// +// // Clear IBAN error, if IBAN changed +// if (prevPaymentDetails != null && prevPaymentDetails.iban != paymentDetails.iban) { +// nonEmptyValidationMessages.remove(ValidationMessage.InvalidIban) +// } +// +// // If the IBAN is the same as the last validated one, then revalidate it to restore the validation +// // message if needed +// if (_lastFullyValidatedPaymentDetails.value?.iban == paymentDetails.iban) { +// nonEmptyValidationMessages.addAll(validateIban(paymentDetails.iban).filterIsInstance()) +// } +// +// // Emit all new empty validation messages along with other existing validation messages +// _paymentValidation.tryEmit(newEmptyValidationMessages + nonEmptyValidationMessages) +// } +// } viewModelScope.launch { paymentComponent.selectedPaymentProviderAppFlow.collect { selectedPaymentProviderAppState -> when (selectedPaymentProviderAppState) { @@ -175,41 +170,45 @@ internal class ReviewViewModel( _paymentDetails.value = paymentDetails.value.copy(purpose = purpose) } - fun validatePaymentDetails(): Boolean = validatePaymentDetails(paymentDetails.value) - - private fun validatePaymentDetails(paymentDetails: PaymentDetails): Boolean { - val items = paymentDetails.validate() - _paymentValidation.tryEmit(items) - _lastFullyValidatedPaymentDetails.tryEmit(paymentDetails) - return items.isEmpty() - } - - override suspend fun getPaymentRequest(): PaymentRequest { - val paymentProviderApp = paymentProviderApp.value - if (paymentProviderApp == null) { - LOG.error("Cannot create PaymentRequest: No selected payment provider app") - throw Exception("Cannot create PaymentRequest: No selected payment provider app") - } +// fun validatePaymentDetails(): Boolean = validatePaymentDetails(paymentDetails.value) - return when (val createPaymentRequestResource = giniHealth.documentManager.createPaymentRequest( - PaymentRequestInput( - paymentProvider = paymentProviderApp.paymentProvider.id, - recipient = paymentDetails.value.recipient, - iban = paymentDetails.value.iban, - amount = "${paymentDetails.value.amount.toBackendFormat()}:EUR", - bic = null, - purpose = paymentDetails.value.purpose, - ) - )) { - is Resource.Cancelled -> throw Exception("Cancelled") - is Resource.Error -> throw Exception(createPaymentRequestResource.exception) - is Resource.Success -> PaymentRequest(id = createPaymentRequestResource.data, paymentProviderApp.name) - } - } +// private fun validatePaymentDetails(paymentDetails: PaymentDetails): Boolean { +// val items = paymentDetails.validate() +// _paymentValidation.tryEmit(items) +// _lastFullyValidatedPaymentDetails.tryEmit(paymentDetails) +// return items.isEmpty() +// } - override suspend fun getPaymentRequestDocument(paymentRequest: PaymentRequest): Resource { + override suspend fun getPaymentRequest(): net.gini.android.internal.payment.api.model.PaymentRequest = paymentComponent.paymentModule.getPaymentRequest(paymentProviderApp.value, paymentDetails.value.toCommonPaymentDetails()) + override suspend fun getPaymentRequestDocument(paymentRequest: net.gini.android.internal.payment.api.model.PaymentRequest): Resource { TODO("Not yet implemented") } +// override suspend fun getPaymentRequest(): PaymentRequest { +// val paymentProviderApp = paymentProviderApp.value +// if (paymentProviderApp == null) { +// LOG.error("Cannot create PaymentRequest: No selected payment provider app") +// throw Exception("Cannot create PaymentRequest: No selected payment provider app") +// } +// +// return when (val createPaymentRequestResource = giniHealth.documentManager.createPaymentRequest( +// PaymentRequestInput( +// paymentProvider = paymentProviderApp.paymentProvider.id, +// recipient = paymentDetails.value.recipient, +// iban = paymentDetails.value.iban, +// amount = "${paymentDetails.value.amount.toBackendFormat()}:EUR", +// bic = null, +// purpose = paymentDetails.value.purpose, +// ) +// )) { +// is Resource.Cancelled -> throw Exception("Cancelled") +// is Resource.Error -> throw Exception(createPaymentRequestResource.exception) +// is Resource.Success -> PaymentRequest(id = createPaymentRequestResource.data, paymentProviderApp.name) +// } +// } +// +// override suspend fun getPaymentRequestDocument(paymentRequest: PaymentRequest): Resource { +// TODO("Not yet implemented") +// } fun onPayment() { val paymentProviderApp = paymentProviderApp.value @@ -226,18 +225,18 @@ internal class ReviewViewModel( } viewModelScope.launch { - val valid = validatePaymentDetails(paymentDetails.value) - if (valid) { - sendFeedbackAndStartLoading() - giniHealth.setOpenBankState( - try { - GiniHealth.PaymentState.Success(getPaymentRequest()) - } catch (throwable: Throwable) { - GiniHealth.PaymentState.Error(throwable) - }, - viewModelScope - ) - } +// val valid = validatePaymentDetails(paymentDetails.value) +// if (valid) { +// sendFeedbackAndStartLoading() +// giniHealth.setOpenBankState( +// try { +// GiniHealth.PaymentState.Success(getPaymentRequest()) +// } catch (throwable: Throwable) { +// GiniHealth.PaymentState.Error(throwable) +// }, +// viewModelScope +// ) +// } } } @@ -306,7 +305,7 @@ internal class ReviewViewModel( giniHealth.setOpenBankState(GiniHealth.PaymentState.Error(throwable), viewModelScope) return@withContext } - _paymentRequestFlow.value = paymentRequest +// _paymentRequestFlow.value = paymentRequest val byteArrayResource = async { giniHealth.documentManager.getPaymentRequestDocument(paymentRequest.id) }.await() when (byteArrayResource) { is Resource.Cancelled -> { diff --git a/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/model/PaymentDetails.kt b/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/model/PaymentDetails.kt index b75b60930..e199c2862 100644 --- a/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/model/PaymentDetails.kt +++ b/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/model/PaymentDetails.kt @@ -96,4 +96,11 @@ internal fun MutableMap.withFeedback(paymentDetails: return this } -internal fun MutableMap.getPaymentExtraction(name: String) = this["payment"]?.specificExtractionMaps?.get(0)?.get(name) \ No newline at end of file +internal fun MutableMap.getPaymentExtraction(name: String) = this["payment"]?.specificExtractionMaps?.get(0)?.get(name) + +internal fun PaymentDetails.toCommonPaymentDetails() = net.gini.android.internal.payment.api.model.PaymentDetails( + recipient = this.recipient, + iban = this.iban, + amount = this.amount, + purpose = this.purpose +) \ No newline at end of file diff --git a/health-sdk/sdk/src/main/res/layout/ghs_fragment_review.xml b/health-sdk/sdk/src/main/res/layout/ghs_fragment_review.xml index ff6d33bde..ef5f9d60c 100644 --- a/health-sdk/sdk/src/main/res/layout/ghs_fragment_review.xml +++ b/health-sdk/sdk/src/main/res/layout/ghs_fragment_review.xml @@ -81,151 +81,170 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + android:id="@+id/ghs_payment_details" + app:layout_constraintBottom_toBottomOf="parent"/> diff --git a/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/GiniInternalPaymentModule.kt b/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/GiniInternalPaymentModule.kt index 1d45029bf..c78efbea6 100644 --- a/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/GiniInternalPaymentModule.kt +++ b/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/GiniInternalPaymentModule.kt @@ -86,7 +86,7 @@ class GiniInternalPaymentModule(private val context: Context, } } - var paymentComponent = PaymentComponent(context, this) + var paymentComponent = PaymentComponent(context).also { it.paymentModule = this } private val openWithPreferences = OpenWithPreferences(context) suspend fun getPaymentRequest(paymentProviderApp: PaymentProviderApp?, paymentDetails: PaymentDetails) = giniPaymentManager.getPaymentRequest(paymentProviderApp, paymentDetails) diff --git a/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/paymentComponent/PaymentComponent.kt b/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/paymentComponent/PaymentComponent.kt index ce1b1ccde..d28245174 100644 --- a/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/paymentComponent/PaymentComponent.kt +++ b/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/paymentComponent/PaymentComponent.kt @@ -19,7 +19,7 @@ import org.slf4j.LoggerFactory * * It requires a [GiniMerchant] instance and a [Context] (application or activity) to be created. */ -class PaymentComponent(@get:VisibleForTesting internal val context: Context, @get:VisibleForTesting internal val paymentModule: GiniInternalPaymentModule, private var configuration: PaymentComponentConfiguration = PaymentComponentConfiguration()) { +class PaymentComponent(@get:VisibleForTesting internal val context: Context, private var configuration: PaymentComponentConfiguration = PaymentComponentConfiguration()) { // Holds the state of the Payment Provider apps as received from the server - no processing is done on this list, to serve as a point of truth private val _initialStatePaymentProviderAppsFlow = MutableStateFlow(PaymentProviderAppsState.Loading) @@ -46,6 +46,9 @@ class PaymentComponent(@get:VisibleForTesting internal val context: Context, @ge */ val returningUserFlow: StateFlow = _returningUserFlow + + lateinit var paymentModule: GiniInternalPaymentModule + @VisibleForTesting internal val paymentComponentPreferences = PaymentComponentPreferences(context) @@ -239,7 +242,7 @@ class PaymentComponent(@get:VisibleForTesting internal val context: Context, @ge * * @param documentId The value in the clicked PaymentComponentView's [PaymentComponentView.documentId] property */ - fun onPayInvoiceClicked(documentId: String) + fun onPayInvoiceClicked(documentId: String?) } } diff --git a/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/paymentComponent/PaymentComponentView.kt b/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/paymentComponent/PaymentComponentView.kt index fe3a8ee33..105109178 100644 --- a/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/paymentComponent/PaymentComponentView.kt +++ b/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/paymentComponent/PaymentComponentView.kt @@ -4,7 +4,6 @@ import android.content.Context import android.util.AttributeSet import android.view.View import android.widget.Button -import android.widget.TextView import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat @@ -325,7 +324,6 @@ class PaymentComponentView(context: Context, attrs: AttributeSet?) : ConstraintL coroutineScope?.launch { paymentComponent?.onPayInvoiceClicked(documentId) } - dismissListener?.onButtonClick(Buttons.PAY_INVOICE) } binding.gpsMoreInformation.setIntervalClickListener { if (paymentComponent == null) { diff --git a/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/review/ReviewConfiguration.kt b/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/review/ReviewConfiguration.kt index 399618fbb..ebadc46d4 100644 --- a/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/review/ReviewConfiguration.kt +++ b/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/review/ReviewConfiguration.kt @@ -1,5 +1,7 @@ package net.gini.android.internal.payment.review +import net.gini.android.internal.payment.review.reviewComponent.ReviewFields + /** * Configuration for the [ReviewBottomSheet]. @@ -13,7 +15,6 @@ data class ReviewConfiguration( */ val handleErrorsInternally: Boolean = true, - //todo take this out - only applicable in health /** * Set to `true` to show a close button. Set a [ReviewFragmentListener] to be informed when the * button is pressed. @@ -22,12 +23,11 @@ data class ReviewConfiguration( */ val showCloseButton: Boolean = false, - //todo refactor to include other fields /** * If set to `true`, the [Amount] field will be editable. * If set to `false` the [Amount] field will be read-only. * * Default value is `true` */ - internal val isAmountFieldEditable: Boolean = true + internal val editableFields: List = listOf(ReviewFields.IBAN, ReviewFields.AMOUNT, ReviewFields.RECIPIENT, ReviewFields.PURPOSE) ) \ No newline at end of file diff --git a/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/review/reviewComponent/ReviewView.kt b/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/review/reviewComponent/ReviewView.kt index e27099fbb..3829dfad1 100644 --- a/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/review/reviewComponent/ReviewView.kt +++ b/internal-payment-sdk/sdk/src/main/java/net/gini/android/internal/payment/review/reviewComponent/ReviewView.kt @@ -94,7 +94,7 @@ class ReviewView(private val context: Context, attrs: AttributeSet?) : launch { reviewComponent?.loadingFlow?.collect { isLoading -> binding.paymentProgress.isVisible = isLoading - binding.amountLayout.isEnabled = !isLoading && reviewComponent?.reviewConfig?.isAmountFieldEditable ?: false + binding.amountLayout.isEnabled = !isLoading && reviewComponent?.reviewConfig?.editableFields?.contains(ReviewFields.AMOUNT) ?: false } } } @@ -202,10 +202,10 @@ class ReviewView(private val context: Context, attrs: AttributeSet?) : } private fun setEditableFields() { - binding.iban.focusable = View.NOT_FOCUSABLE - binding.recipient.focusable = View.NOT_FOCUSABLE - binding.purpose.focusable = View.NOT_FOCUSABLE - binding.amount.focusable = if (reviewComponent?.reviewConfig?.isAmountFieldEditable == true) View.FOCUSABLE else View.NOT_FOCUSABLE + binding.iban.focusable = if (reviewComponent?.reviewConfig?.editableFields?.contains(ReviewFields.IBAN) == true) View.FOCUSABLE else View.NOT_FOCUSABLE + binding.recipient.focusable = if (reviewComponent?.reviewConfig?.editableFields?.contains(ReviewFields.RECIPIENT) == true) View.FOCUSABLE else View.NOT_FOCUSABLE + binding.purpose.focusable = if (reviewComponent?.reviewConfig?.editableFields?.contains(ReviewFields.PURPOSE) == true) View.FOCUSABLE else View.NOT_FOCUSABLE + binding.amount.focusable = if (reviewComponent?.reviewConfig?.editableFields?.contains(ReviewFields.AMOUNT) == true) View.FOCUSABLE else View.NOT_FOCUSABLE } private fun setDisabledIcon(text: String, textView:TextInputLayout) { @@ -217,17 +217,34 @@ class ReviewView(private val context: Context, attrs: AttributeSet?) : } private fun setDisabledIcons() { - setDisabledIcon(context.getString(R.string.gps_iban_hint), binding.ibanLayout) - setDisabledIcon(context.getString(R.string.gps_recipient_hint), binding.recipientLayout) - setDisabledIcon(context.getString(R.string.gps_purpose_hint), binding.purposeLayout) + if (reviewComponent?.reviewConfig?.editableFields?.contains(ReviewFields.IBAN) == false) { + setDisabledIcon(context.getString(R.string.gps_iban_hint), binding.ibanLayout) + binding.iban.isEnabled = false + } + if (reviewComponent?.reviewConfig?.editableFields?.contains(ReviewFields.RECIPIENT) == false) { + setDisabledIcon(context.getString(R.string.gps_recipient_hint), binding.recipientLayout) + binding.recipient.isEnabled = false + } + if (reviewComponent?.reviewConfig?.editableFields?.contains(ReviewFields.PURPOSE) == false) { + setDisabledIcon(context.getString(R.string.gps_purpose_hint), binding.purposeLayout) + binding.purpose.isEnabled = false + } } private fun handleInputFocusChange(hasFocus: Boolean, textInputLayout: TextInputLayout) { if (hasFocus) textInputLayout.hideErrorMessage() else textInputLayout.showErrorMessage() } +// fun clearFocus() { +// binding.recipient.clearFocus() +// binding.iban.clearFocus() +// amount.clearFocus() +// purpose.clearFocus() +// } + companion object { private val LOG = LoggerFactory.getLogger(ReviewView::class.java) } -} \ No newline at end of file +} +enum class ReviewFields { RECIPIENT, IBAN, AMOUNT, PURPOSE } \ No newline at end of file diff --git a/internal-payment-sdk/sdk/src/main/res/color/gps_edit_text_color.xml b/internal-payment-sdk/sdk/src/main/res/color/gps_edit_text_color.xml new file mode 100644 index 000000000..402aba56d --- /dev/null +++ b/internal-payment-sdk/sdk/src/main/res/color/gps_edit_text_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/internal-payment-sdk/sdk/src/main/res/layout/gps_review.xml b/internal-payment-sdk/sdk/src/main/res/layout/gps_review.xml index 146425114..f41e03cc6 100644 --- a/internal-payment-sdk/sdk/src/main/res/layout/gps_review.xml +++ b/internal-payment-sdk/sdk/src/main/res/layout/gps_review.xml @@ -19,6 +19,7 @@ android:layout_height="wrap_content" android:paddingTop="@dimen/gps_medium" android:nextFocusForward="@id/iban_layout" + android:hint="@string/gps_recipient_hint" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -26,7 +27,7 @@ true @drawable/gps_payment_input_edit_text_background @style/GiniPaymentTheme.Typography.Body1 - ?attr/colorOnBackground + @color/gps_edit_text_color - diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFragment.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFragment.kt index d76542756..4ff2b8d0e 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFragment.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFragment.kt @@ -33,6 +33,7 @@ import net.gini.android.internal.payment.paymentComponentBottomSheet.PaymentComp import net.gini.android.internal.payment.paymentProvider.PaymentProviderApp import net.gini.android.internal.payment.review.ReviewConfiguration import net.gini.android.internal.payment.review.reviewBottomSheet.ReviewBottomSheet +import net.gini.android.internal.payment.review.reviewComponent.ReviewFields import net.gini.android.internal.payment.review.reviewComponent.ReviewViewListener import net.gini.android.internal.payment.utils.PaymentNextStep import net.gini.android.internal.payment.utils.autoCleared @@ -142,7 +143,7 @@ class PaymentFragment private constructor( } } - override fun onPayInvoiceClicked(documentId: String) { + override fun onPayInvoiceClicked(documentId: String?) { handlePayFlow() } } @@ -307,7 +308,7 @@ class PaymentFragment private constructor( configuration = ReviewConfiguration( handleErrorsInternally = viewModel.paymentFlowConfiguration?.shouldHandleErrorsInternally == true, showCloseButton = true, - isAmountFieldEditable = viewModel.paymentFlowConfiguration?.isAmountFieldEditable == true + editableFields = if (viewModel.paymentFlowConfiguration?.isAmountFieldEditable == true) listOf(ReviewFields.AMOUNT) else listOf(), ), listener = reviewViewListener, giniInternalPaymentModule = viewModel.giniInternalPaymentModule,