Merge pull request #802 from medici/ch11739
Shashidhara | [ch11739] | moratorium consent screen
This commit is contained in:
@@ -44,6 +44,8 @@ data class ApiRequest(
|
||||
const val REQUEST_AADHAAR_DETAILS =
|
||||
"/customer-service/customers/me/kyc/aadhaar/request-details"
|
||||
const val CREATE_LOAN_AGREEMENT = "create-loan-agreement"
|
||||
const val LOGIN_SETTINGS_API = "/login-settings"
|
||||
const val MORATORIUM_CONSENT_API = "/loan-origination-manager/offers/moratorium"
|
||||
|
||||
fun addBankAccount(id: String) = "/loan-applications/$id/$ADD_BANK_ACCOUNT"
|
||||
fun getBankAccounts(id: String, type: String = "DISBURSEMENT") =
|
||||
|
||||
@@ -184,7 +184,7 @@ data class Offer(
|
||||
val referenceId: String, val tenure: Tenure,
|
||||
val loanAmount: AmountRange, val emiAmount: AmountRange,
|
||||
val rateOfInterest: BigDecimal, val processingFee: MoneyRequest,
|
||||
val gst: MoneyRequest, val offerExpired: Boolean = false
|
||||
val gst: MoneyRequest, val offerExpired: Boolean = false, val moratoriumConfig: MoratoriumConfig
|
||||
) {
|
||||
constructor(
|
||||
referenceId: String? = null,
|
||||
@@ -194,7 +194,8 @@ data class Offer(
|
||||
rateOfInterest: BigDecimal? = null,
|
||||
processingFee: MoneyRequest? = null,
|
||||
gst: MoneyRequest? = null,
|
||||
offerExpired: Boolean? = null
|
||||
offerExpired: Boolean? = null,
|
||||
moratoriumConfig: MoratoriumConfig? = null
|
||||
) : this(
|
||||
referenceId ?: "offer-id",
|
||||
tenure ?: Tenure(TenureDetail(2), TenureDetail(18)),
|
||||
@@ -209,7 +210,8 @@ data class Offer(
|
||||
rateOfInterest ?: BigDecimal.valueOf(12),
|
||||
processingFee ?: MoneyRequest(BigDecimal.valueOf(2000)),
|
||||
gst ?: MoneyRequest(BigDecimal.valueOf(100)),
|
||||
offerExpired ?: false
|
||||
offerExpired ?: false,
|
||||
moratoriumConfig ?: MoratoriumConfig()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -536,7 +538,47 @@ data class LoanAccountDueDetailsCard(
|
||||
)
|
||||
)
|
||||
|
||||
data class LoginSettings(
|
||||
val warning: Warning = Warning()
|
||||
)
|
||||
|
||||
data class Warning(
|
||||
val enabled: Boolean,
|
||||
val title: String,
|
||||
val message: String
|
||||
) {
|
||||
constructor(
|
||||
enabled: Boolean? = null,
|
||||
title: String? = null,
|
||||
message: String? = null
|
||||
) : this(
|
||||
enabled ?: true,
|
||||
title ?: "Important",
|
||||
message
|
||||
?: "Please note that for any new loans, we will not be able to offer the moratorium on the EMIs till 31st August 2020."
|
||||
)
|
||||
}
|
||||
|
||||
enum class LoanAccountStatus {
|
||||
MARKED_FOR_CLOSURE, CLOSED, ACTIVE
|
||||
}
|
||||
|
||||
data class MoratoriumConfig(
|
||||
val referenceId: String,
|
||||
val isEnabled: Boolean,
|
||||
val message: String,
|
||||
val customerName: String
|
||||
) {
|
||||
constructor(
|
||||
referenceId: String? = null,
|
||||
isEnabled: Boolean? = null,
|
||||
message: String? = null,
|
||||
customerName: String? = null
|
||||
) : this(
|
||||
referenceId ?: "moratorium-reference-id",
|
||||
isEnabled ?: false,
|
||||
message
|
||||
?: "Please note that for this loan, we will not be able to offer the moratorium on EMIs till 31st August 2020. The EMIs will be scheduled as per the due dates shown in the EMI calendar and cannot be changed later.",
|
||||
customerName ?: "John Doe"
|
||||
)
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import com.naviappmockserver.contract.ApiRequest.Companion.FETCH_KYC_DETAILS
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.FETCH_LOAN_FEE_DETAILS
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.GENERATE_OFFER
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.LOAN_PRODUCTS
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.MORATORIUM_CONSENT_API
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.OFFERS
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.PATCH_CUSTOMER_BASIC_DETAILS
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.PATCH_CUSTOMER_PAN_DETAILS
|
||||
@@ -97,6 +98,7 @@ class AsyncDispatcher(
|
||||
BANKS -> validateBanksRequest(request)
|
||||
FETCH_KYC_DETAILS -> validateKycDetails(request)
|
||||
REQUEST_AADHAAR_DETAILS -> validateRequestAadhaarDetails(request)
|
||||
MORATORIUM_CONSENT_API -> validateMoratoriumConsent(request)
|
||||
else -> handleOtherRequests(request)
|
||||
}
|
||||
return response.also {
|
||||
@@ -121,6 +123,14 @@ class AsyncDispatcher(
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateMoratoriumConsent(request: RecordedRequest): MockResponse {
|
||||
assertEquals("POST", request.method)
|
||||
val bodyMap = bodyFrom(request)
|
||||
assertNotNull(bodyMap["offerReferenceId"])
|
||||
assertNotNull(bodyMap["moratoriumId"])
|
||||
return respondWith(ImmutableMap.of("success", "true"))
|
||||
}
|
||||
|
||||
private fun validateBanksRequest(request: RecordedRequest): MockResponse {
|
||||
assertEquals("GET", request.method)
|
||||
val banksJson: String = (this.javaClass.getResourceAsStream("/banks.json")?.bufferedReader()
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.naviappmockserver.dispatchers
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.Gson
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.LOGIN_SETTINGS_API
|
||||
import com.naviappmockserver.contract.ErrorResponse
|
||||
import com.naviappmockserver.contract.GenericServerResponse
|
||||
import com.naviappmockserver.contract.LoginSettings
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.RecordedRequest
|
||||
|
||||
class RegistrationDispatcher : ServerDispatcher() {
|
||||
private val gson by lazy { Gson() }
|
||||
private var loginSettings = LoginSettings()
|
||||
private var errorResponses: List<ErrorResponse>? = null
|
||||
|
||||
override fun handleRequest(request: RecordedRequest, api: String) = when (api) {
|
||||
LOGIN_SETTINGS_API -> validateLoginSettingsApi()
|
||||
else -> pathNotFound(request)
|
||||
}
|
||||
|
||||
fun setLoginSettings(loginSettings: LoginSettings) {
|
||||
this.loginSettings = loginSettings
|
||||
}
|
||||
|
||||
fun setErrors(errors: List<ErrorResponse>) {
|
||||
errorResponses = errors
|
||||
}
|
||||
|
||||
private fun validateLoginSettingsApi(): MockResponse {
|
||||
return errorResponses?.let { respondWithErrors(it) } ?: respondWith(loginSettings)
|
||||
}
|
||||
|
||||
private fun respondWithErrors(errors: List<ErrorResponse>, code: Int = 404, body: Any = Any()) =
|
||||
MockResponse()
|
||||
.setResponseCode(code)
|
||||
.setBody(gson.toJson(GenericServerResponse(body, code, errors = errors)))
|
||||
|
||||
private fun pathNotFound(request: RecordedRequest) = MockResponse()
|
||||
.setResponseCode(404)
|
||||
.setBody(gson.toJson(ImmutableMap.of("Path not found", request.path ?: "")))
|
||||
}
|
||||
@@ -1,15 +1,25 @@
|
||||
package com.naviappmockserver.instrumentedTests.loanapplication
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.pressBack
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import com.naviapp.R
|
||||
import com.naviapp.getloan.activities.GetLoanActivity
|
||||
import com.naviappmockserver.contract.AmountRange
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.ACTIVE_APPLICATION
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.GENERATE_OFFER
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.LOAN_PRODUCTS
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.MORATORIUM_CONSENT_API
|
||||
import com.naviappmockserver.contract.ApiResult
|
||||
import com.naviappmockserver.contract.AsyncResponse.API_POLL_SUCCESS
|
||||
import com.naviappmockserver.contract.MoneyRequest
|
||||
import com.naviappmockserver.contract.MoratoriumConfig
|
||||
import com.naviappmockserver.contract.Offer
|
||||
import com.naviappmockserver.contract.OfferId
|
||||
import com.naviappmockserver.contract.RequestType
|
||||
@@ -76,6 +86,45 @@ class LoanDetails : AbstractCustomerAppTestCase() {
|
||||
blink(3000)
|
||||
}
|
||||
|
||||
@Test(timeout = DEFAULT_TEST_TIMEOUT)
|
||||
fun expectMoratoriumMessageOnLoanDetailsScreen() {
|
||||
val offer = Offer(moratoriumConfig = MoratoriumConfig(isEnabled = true))
|
||||
updateAsyncDispatcher(
|
||||
dispatcher,
|
||||
uuid,
|
||||
API_POLL_SUCCESS,
|
||||
RequestType.GENERATE_OFFER,
|
||||
OfferId(uuid),
|
||||
offer,
|
||||
false
|
||||
) {
|
||||
activityRule.launchActivity(null)
|
||||
}
|
||||
|
||||
dispatcher.await(
|
||||
ApiResult(LOAN_PRODUCTS),
|
||||
ApiResult(GENERATE_OFFER),
|
||||
ApiResult(requestStatusApiForId(uuid)),
|
||||
ApiResult(requestApiForId(uuid)),
|
||||
ApiResult(offersApiForId(uuid)),
|
||||
ApiResult(ACTIVE_APPLICATION)
|
||||
)
|
||||
|
||||
pressBack()
|
||||
|
||||
onView(withId(R.id.moratorium_title_tv)).check(matches(withText("Moratorium Not Available")))
|
||||
onView(withId(R.id.moratorium_message_tv)).check(matches(withText("Please note that for this loan, we will not be able to offer the moratorium on EMIs till 31st August 2020. The EMIs will be scheduled as per the due dates shown in the EMI calendar and cannot be changed later.")))
|
||||
onView(withId(R.id.moratorium_consent_cb)).check(matches(withText("I, John Doe, agree to not availing the moratorium")))
|
||||
onView(withId(R.id.confirm_btn)).check(matches(isDisplayed()))
|
||||
|
||||
pressBack()
|
||||
|
||||
onView(withId(R.id.moratorium_consent_cb)).perform(click())
|
||||
onView(withId(R.id.confirm_btn)).perform(click())
|
||||
dispatcher.await(ApiResult(MORATORIUM_CONSENT_API))
|
||||
onView(withId(R.id.loan_config_lay)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private lateinit var uuid: String
|
||||
private lateinit var dispatcher: AsyncDispatcher
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.naviappmockserver.instrumentedTests.registration
|
||||
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.filters.LargeTest
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import com.naviapp.R
|
||||
import com.naviapp.registration.RegistrationActivity
|
||||
import com.naviappmockserver.MockServerTest
|
||||
import com.naviappmockserver.contract.ApiRequest.Companion.LOGIN_SETTINGS_API
|
||||
import com.naviappmockserver.contract.ErrorResponse
|
||||
import com.naviappmockserver.contract.LoginSettings
|
||||
import com.naviappmockserver.contract.Warning
|
||||
import com.naviappmockserver.dispatchers.RegistrationDispatcher
|
||||
import com.naviappmockserver.instrumentedTests.AbstractCustomerAppTestCase
|
||||
import org.hamcrest.Matchers.not
|
||||
import org.junit.AfterClass
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
@MockServerTest
|
||||
@LargeTest
|
||||
class Login : AbstractCustomerAppTestCase() {
|
||||
@Rule
|
||||
@JvmField
|
||||
val activityRule = ActivityTestRule(RegistrationActivity::class.java, false, false)
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
dispatcher = RegistrationDispatcher()
|
||||
initDispatcher(dispatcher)
|
||||
}
|
||||
|
||||
@Test(timeout = DEFAULT_TEST_TIMEOUT)
|
||||
fun expectMoratoriumMessageToRenderOnPhoneNumberScreen() {
|
||||
activityRule.launchActivity(null)
|
||||
|
||||
dispatcher.await(LOGIN_SETTINGS_API)
|
||||
|
||||
onView(withId(R.id.moratorium_title_tv)).check(matches(withText("Important")))
|
||||
onView(withId(R.id.moratorium_description_tv)).check(matches(withText("Please note that for any new loans, we will not be able to offer the moratorium on the EMIs till 31st August 2020.")))
|
||||
}
|
||||
|
||||
@Test(timeout = DEFAULT_TEST_TIMEOUT)
|
||||
fun expectMoratoriumMessageToNotRenderOnPhoneNumberScreen() {
|
||||
dispatcher.setLoginSettings(LoginSettings(Warning(false)))
|
||||
activityRule.launchActivity(null)
|
||||
|
||||
dispatcher.await(LOGIN_SETTINGS_API)
|
||||
blink(1000)
|
||||
|
||||
onView(withId(R.id.moratorium_title_tv)).check(matches(not(isDisplayed())))
|
||||
onView(withId(R.id.moratorium_description_tv)).check(matches(not(isDisplayed())))
|
||||
}
|
||||
|
||||
@Test(timeout = DEFAULT_TEST_TIMEOUT)
|
||||
fun expectLoginSettingsApiErrorToNotRenderSomethingWentWrong() {
|
||||
dispatcher.setErrors(listOf(ErrorResponse()))
|
||||
activityRule.launchActivity(null)
|
||||
|
||||
blink(1000)
|
||||
|
||||
onView(withId(R.id.moratorium_title_tv)).check(matches(not(isDisplayed())))
|
||||
onView(withId(R.id.moratorium_description_tv)).check(matches(not(isDisplayed())))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private lateinit var dispatcher: RegistrationDispatcher
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun setUp() {
|
||||
initMockServer()
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@JvmStatic
|
||||
fun tearDown() {
|
||||
shutdownServer()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,6 +363,11 @@ class NaviAnalytics private constructor() {
|
||||
fun onDenyGpsAccess() = NaviTrackEvent.trackEvent("gps_modelbox_no")
|
||||
}
|
||||
|
||||
inner class MoratoriumConsent {
|
||||
fun onCheckConsent() = NaviTrackEvent.trackEvent("moratorium_checkbox")
|
||||
fun onConfirmConsent() = NaviTrackEvent.trackEvent("moratorium_confirm_button_tap")
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SPLASH = "splash"
|
||||
const val TUTORIAL = "tutorial"
|
||||
@@ -411,6 +416,7 @@ class NaviAnalytics private constructor() {
|
||||
const val LOAN_RCS_SCREEN = "loan_rcs_screen"
|
||||
const val GPS_ACCESS_SCREEN = "gps_access_screen"
|
||||
const val SYSTEM_UNDER_MAINTENANCE_SCREEN = "system_under_maintenance_screen"
|
||||
const val MORATORIUM_BOTTOM_SHEET = "moratorium_bottomsheet"
|
||||
|
||||
const val TRUE = "true"
|
||||
const val FALSE = "false"
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.naviapp.getloan.loandetails.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -25,6 +26,7 @@ import com.naviapp.getloan.activities.GetLoanActivity.Companion.LOAN_DETAILS_SCR
|
||||
import com.naviapp.getloan.common.FormSliderView
|
||||
import com.naviapp.getloan.loandetails.listeners.LoanDetailsListener
|
||||
import com.naviapp.getloan.loandetails.viewmodels.LoanDetailsVM
|
||||
import com.naviapp.getloan.moratorium.MoratoriumConsentFragment
|
||||
import com.naviapp.models.LoanFeeDetails
|
||||
import com.naviapp.models.LoanSummary
|
||||
import com.naviapp.models.Money
|
||||
@@ -41,8 +43,10 @@ import com.naviapp.utils.OFFER_ID
|
||||
import com.naviapp.utils.PRODUCT_CODE
|
||||
import com.naviapp.utils.SPACE
|
||||
import com.naviapp.utils.formatCurrency
|
||||
import com.naviapp.utils.isDead
|
||||
import com.naviapp.utils.observeNonNull
|
||||
import com.naviapp.utils.observeNullable
|
||||
import com.naviapp.utils.orFalse
|
||||
import com.naviapp.utils.orTrue
|
||||
import org.joda.money.BigMoney
|
||||
import org.joda.money.CurrencyUnit
|
||||
@@ -71,8 +75,9 @@ class LoanDetailsFragment : BaseFragment(), View.OnClickListener, LoanDetailsLis
|
||||
initError(loanDetailsVM)
|
||||
initUI()
|
||||
initListeners()
|
||||
if (isOfferAvailable()) initData(arguments?.getParcelable(OFFER))
|
||||
else {
|
||||
if (isOfferAvailable()) {
|
||||
initData(arguments?.getParcelable(OFFER))
|
||||
} else {
|
||||
showLoader()
|
||||
loanDetailsVM.fetchLoanProducts()
|
||||
}
|
||||
@@ -367,6 +372,19 @@ class LoanDetailsFragment : BaseFragment(), View.OnClickListener, LoanDetailsLis
|
||||
|
||||
updateEmiSlider()
|
||||
toggleAmountDetails()
|
||||
|
||||
//Consent for Moratorium not being available
|
||||
if (!offer.moratoriumConfig?.isEnabled.orFalse()) return
|
||||
Handler().postDelayed(
|
||||
{
|
||||
if (!isAdded || isDead(activity)) return@postDelayed
|
||||
MoratoriumConsentFragment.newInstance(offer).show(
|
||||
childFragmentManager,
|
||||
MoratoriumConsentFragment.TAG
|
||||
)
|
||||
},
|
||||
MORATORIUM_CONSENT_DELAY
|
||||
)
|
||||
}
|
||||
|
||||
private fun initSlider(
|
||||
@@ -545,5 +563,6 @@ class LoanDetailsFragment : BaseFragment(), View.OnClickListener, LoanDetailsLis
|
||||
const val TAG = "LOAN_DETAILS_FRAGMENT"
|
||||
private const val LOAN_AMOUNT_SLIDER = "LOAN_AMOUNT_SLIDER"
|
||||
private const val EMI_SLIDER = "LOAN_SLIDER"
|
||||
private const val MORATORIUM_CONSENT_DELAY = 250L
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.naviapp.getloan.loandetails.repositories
|
||||
import com.naviapp.models.request.GenerateOfferRequest
|
||||
import com.naviapp.models.request.LoanFeeDetailsRequest
|
||||
import com.naviapp.models.request.LoanRequest
|
||||
import com.naviapp.models.request.MoratoriumRequest
|
||||
import com.naviapp.network.retrofit.ResponseCallback
|
||||
import com.naviapp.utils.retrofitService
|
||||
|
||||
@@ -44,4 +45,7 @@ class LoanDetailsRepository : ResponseCallback() {
|
||||
|
||||
suspend fun fetchOfferRejectionData(requestId: String) =
|
||||
apiResponseCallback(retrofitService().fetchAsyncRequestWithError(requestId))
|
||||
|
||||
suspend fun confirmMoratoriumConsent(request: MoratoriumRequest) =
|
||||
apiResponseCallback(retrofitService().confirmMoratoriumConsent(request))
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.naviapp.models.Tenure
|
||||
import com.naviapp.models.request.GenerateOfferRequest
|
||||
import com.naviapp.models.request.LoanFeeDetailsRequest
|
||||
import com.naviapp.models.request.LoanRequest
|
||||
import com.naviapp.models.request.MoratoriumRequest
|
||||
import com.naviapp.models.response.LoanBasicDetailsResponse
|
||||
import com.naviapp.models.response.OfferIdResponse
|
||||
import com.naviapp.models.response.SuccessResponse
|
||||
@@ -90,6 +91,10 @@ class LoanDetailsVM : BaseVM() {
|
||||
val genericOfferError: LiveData<GenericErrorResponse?>
|
||||
get() = _generateOfferError
|
||||
|
||||
private val _moratoriumConsent = MutableLiveData<Boolean>()
|
||||
val moratoriumConsent: LiveData<Boolean>
|
||||
get() = _moratoriumConsent
|
||||
|
||||
fun fetchLoanProducts() {
|
||||
coroutineScope.launch {
|
||||
val response = repository.fetchLoanProduct()
|
||||
@@ -251,4 +256,16 @@ class LoanDetailsVM : BaseVM() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun confirmMoratoriumConsent(id: String, offerId: String) {
|
||||
coroutineScope.launch {
|
||||
val request = MoratoriumRequest(id, offerId)
|
||||
val response = repository.confirmMoratoriumConsent(request)
|
||||
if (response.error == null) {
|
||||
_moratoriumConsent.value = response.data?.success
|
||||
} else {
|
||||
updateErrorMessage(response.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.naviapp.getloan.moratorium
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewStub
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.naviapp.R
|
||||
import com.naviapp.analytics.NaviAnalytics
|
||||
import com.naviapp.common.customview.BaseBottomSheet
|
||||
import com.naviapp.databinding.MoratoriumConsentFragmentBinding
|
||||
import com.naviapp.getloan.loandetails.viewmodels.LoanDetailsVM
|
||||
import com.naviapp.models.Offer
|
||||
import com.naviapp.utils.observeNonNull
|
||||
|
||||
class MoratoriumConsentFragment : BaseBottomSheet(), View.OnClickListener {
|
||||
private lateinit var binding: MoratoriumConsentFragmentBinding
|
||||
private val loanDetailsVM by lazy { ViewModelProvider(this).get(LoanDetailsVM::class.java) }
|
||||
private val eventTracker by lazy { NaviAnalytics.naviAnalytics.MoratoriumConsent() }
|
||||
private val offer by lazy { arguments?.getParcelable<Offer>(OFFER) }
|
||||
|
||||
override fun setContainerView(viewStub: ViewStub) {
|
||||
viewStub.layoutResource = R.layout.moratorium_consent_fragment
|
||||
binding = DataBindingUtil.getBinding(viewStub.inflate())!!
|
||||
initUI()
|
||||
initListeners()
|
||||
}
|
||||
|
||||
private fun initListeners() {
|
||||
binding.confirmBtn.setOnClickListener(this)
|
||||
binding.moratoriumConsentCb.setOnClickListener(this)
|
||||
|
||||
loanDetailsVM.moratoriumConsent.observeNonNull(this) {
|
||||
if (it) dialog?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initUI() {
|
||||
binding.confirmBtn.setProperties(getString(R.string.confirm))
|
||||
binding.moratoriumMessageTv.text = offer?.moratoriumConfig?.message
|
||||
binding.moratoriumConsentCb.text = getString(
|
||||
R.string.i_agree_to_not_availing_the_moratorium,
|
||||
offer?.moratoriumConfig?.customerName
|
||||
)
|
||||
toggleConfirmButton()
|
||||
}
|
||||
|
||||
private fun toggleConfirmButton() {
|
||||
val isChecked = binding.moratoriumConsentCb.isChecked
|
||||
if (isChecked) eventTracker.onCheckConsent()
|
||||
binding.confirmBtn.setStateABV(
|
||||
isChecked,
|
||||
if (isChecked) R.color.red else R.color.disabled_button_color
|
||||
)
|
||||
}
|
||||
|
||||
private fun confirmMoratoriumNotAvailable() {
|
||||
if (!binding.moratoriumConsentCb.isChecked) return
|
||||
eventTracker.onConfirmConsent()
|
||||
|
||||
offer?.id?.let { offerId ->
|
||||
offer?.moratoriumConfig?.referenceId?.let { referenceId ->
|
||||
loanDetailsVM.confirmMoratoriumConsent(referenceId, offerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
when (v?.id) {
|
||||
R.id.moratorium_consent_cb -> toggleConfirmButton()
|
||||
R.id.confirm_btn -> confirmMoratoriumNotAvailable()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
dialog?.setCancelable(false)
|
||||
}
|
||||
|
||||
override val screenName: String
|
||||
get() = NaviAnalytics.MORATORIUM_BOTTOM_SHEET
|
||||
|
||||
companion object {
|
||||
const val TAG = "MORATORIUM_CONSENT_FRAGMENT"
|
||||
private const val OFFER = "OFFER"
|
||||
|
||||
fun newInstance(offer: Offer) = MoratoriumConsentFragment().apply {
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(OFFER, offer)
|
||||
arguments = bundle
|
||||
}
|
||||
}
|
||||
}
|
||||
13
app/src/main/java/com/naviapp/models/LoginSettings.kt
Normal file
13
app/src/main/java/com/naviapp/models/LoginSettings.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.naviapp.models
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class LoginSettings(
|
||||
@SerializedName("warning") val warning: Warning? = null
|
||||
)
|
||||
|
||||
data class Warning(
|
||||
@SerializedName("enabled") val isEnabled: Boolean? = null,
|
||||
@SerializedName("title") val title: String? = null,
|
||||
@SerializedName("message") val message: String? = null
|
||||
)
|
||||
@@ -6,11 +6,13 @@ import kotlinx.android.parcel.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class Offer(
|
||||
@SerializedName("referenceId") val id: String? = null,
|
||||
@SerializedName("loanAmount") val loanAmount: AmountRange? = null,
|
||||
@SerializedName("emiAmount") val emiAmount: AmountRange? = null,
|
||||
@SerializedName("tenure") val tenure: TenureRange? = null,
|
||||
@SerializedName("rateOfInterest") val rateOfInterest: Double? = null,
|
||||
@SerializedName("offerExpired") val offerExpired: Boolean? = null
|
||||
@SerializedName("offerExpired") val offerExpired: Boolean? = null,
|
||||
@SerializedName("moratoriumConfig") val moratoriumConfig: MoratoriumConfig? = null
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
@@ -30,3 +32,11 @@ data class Tenure(
|
||||
@SerializedName("unit") val unit: String? = null,
|
||||
@SerializedName("value") val value: Int? = null
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class MoratoriumConfig(
|
||||
@SerializedName("referenceId") val referenceId: String? = null,
|
||||
@SerializedName("isEnabled") val isEnabled: Boolean? = null,
|
||||
@SerializedName("message") val message: String? = null,
|
||||
@SerializedName("customerName") val customerName: String? = null
|
||||
) : Parcelable
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.naviapp.models.request
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class MoratoriumRequest(
|
||||
@SerializedName("moratoriumId") val referenceId: String? = null,
|
||||
@SerializedName("offerReferenceId") val offerId: String? = null
|
||||
)
|
||||
@@ -15,6 +15,7 @@ import com.naviapp.models.ApplicationSummary
|
||||
import com.naviapp.models.CustomerSupport
|
||||
import com.naviapp.models.LoanFeeDetails
|
||||
import com.naviapp.models.LoanSummary
|
||||
import com.naviapp.models.LoginSettings
|
||||
import com.naviapp.models.Offer
|
||||
import com.naviapp.models.OfferResponse
|
||||
import com.naviapp.models.RedirectPageStatus
|
||||
@@ -37,6 +38,7 @@ import com.naviapp.models.request.LoanApplicationRequest
|
||||
import com.naviapp.models.request.LoanFeeDetailsRequest
|
||||
import com.naviapp.models.request.LoanRequest
|
||||
import com.naviapp.models.request.LoginRequest
|
||||
import com.naviapp.models.request.MoratoriumRequest
|
||||
import com.naviapp.models.request.OtpRequest
|
||||
import com.naviapp.models.request.PanRequest
|
||||
import com.naviapp.models.request.ProfileRequest
|
||||
@@ -407,4 +409,10 @@ interface RetrofitService {
|
||||
//Logout
|
||||
@POST("/auth/logout")
|
||||
suspend fun logout(): Response<GenericResponse<SuccessResponse>>
|
||||
|
||||
@GET("/login-settings")
|
||||
suspend fun fetchLoginSettings(): Response<GenericResponse<LoginSettings>>
|
||||
|
||||
@POST("/loan-origination-manager/offers/moratorium")
|
||||
suspend fun confirmMoratoriumConsent(@Body request: MoratoriumRequest): Response<GenericResponse<SuccessResponse>>
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import com.naviapp.utils.hideKeyboard
|
||||
import com.naviapp.utils.isValidPhoneNumber
|
||||
import com.naviapp.utils.observeNonNull
|
||||
import com.naviapp.utils.openKeyboard
|
||||
import com.naviapp.utils.orFalse
|
||||
|
||||
class LoginFragment : BaseFragment() {
|
||||
|
||||
@@ -50,6 +51,8 @@ class LoginFragment : BaseFragment() {
|
||||
initError(registrationVM)
|
||||
initUi()
|
||||
initData()
|
||||
initLoginSettingsListener()
|
||||
registrationVM.fetchLoginSettings()
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@@ -68,6 +71,15 @@ class LoginFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun initLoginSettingsListener() {
|
||||
registrationVM.loginSettings.observeNonNull(this) {
|
||||
binding.moratoriumTitleTv.text = it.warning?.title
|
||||
binding.moratoriumDescriptionTv.text = it.warning?.message
|
||||
if (it.warning?.isEnabled.orFalse())
|
||||
binding.moratoriumMessageCv.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun initData() {
|
||||
arguments?.getString(PHONE_NUMBER)?.let {
|
||||
binding.phoneEdit.setText(it)
|
||||
|
||||
@@ -29,4 +29,7 @@ class RegisterRepository : ResponseCallback() {
|
||||
|
||||
suspend fun fetchPrivacyPolicy() =
|
||||
apiResponseCallback(retrofitService().fetchPrivacyPolicy())
|
||||
|
||||
suspend fun fetchLoginSettings() =
|
||||
apiResponseCallback(retrofitService().fetchLoginSettings())
|
||||
}
|
||||
@@ -9,6 +9,7 @@ package com.naviapp.registration.viewmodel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.naviapp.common.BaseVM
|
||||
import com.naviapp.models.LoginSettings
|
||||
import com.naviapp.models.RedirectPageStatus
|
||||
import com.naviapp.models.request.LoginRequest
|
||||
import com.naviapp.models.request.OtpRequest
|
||||
@@ -50,6 +51,10 @@ class RegistrationVM : BaseVM() {
|
||||
val privacyPolicy: LiveData<String>
|
||||
get() = _privacyPolicy
|
||||
|
||||
private val _loginSettings = MutableLiveData<LoginSettings>()
|
||||
val loginSettings: LiveData<LoginSettings>
|
||||
get() = _loginSettings
|
||||
|
||||
fun getRedirectStatus() {
|
||||
coroutineScope.launch {
|
||||
val response = registerRepository.getRedirectStatus()
|
||||
@@ -129,4 +134,14 @@ class RegistrationVM : BaseVM() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchLoginSettings() {
|
||||
coroutineScope.launch {
|
||||
val response = registerRepository.fetchLoginSettings()
|
||||
Timber.tag("LOGIN SETTINGS").i("$response")
|
||||
if (response.error == null) {
|
||||
_loginSettings.value = response.data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
app/src/main/res/drawable/ic_important_svg.xml
Normal file
9
app/src/main/res/drawable/ic_important_svg.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="12dp"
|
||||
android:height="12dp"
|
||||
android:viewportWidth="12"
|
||||
android:viewportHeight="12">
|
||||
<path
|
||||
android:pathData="M6,0c3.314,0 6,2.686 6,6s-2.686,6 -6,6 -6,-2.686 -6,-6 2.686,-6 6,-6zM6,9c0.331,0 0.6,-0.269 0.6,-0.6L6.6,6c0,-0.331 -0.269,-0.6 -0.6,-0.6 -0.331,0 -0.6,0.269 -0.6,0.6v2.4c0,0.331 0.269,0.6 0.6,0.6zM5.7,4.2h0.6c0.166,0 0.3,-0.134 0.3,-0.3v-0.6c0,-0.166 -0.134,-0.3 -0.3,-0.3h-0.6c-0.166,0 -0.3,0.134 -0.3,0.3v0.6c0,0.166 0.134,0.3 0.3,0.3z"
|
||||
android:fillColor="#F33"/>
|
||||
</vector>
|
||||
@@ -12,7 +12,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
android:background="@color/view_background_color_two"
|
||||
android:background="@color/white"
|
||||
android:padding="@dimen/container_padding">
|
||||
|
||||
<TextView
|
||||
@@ -53,8 +53,8 @@
|
||||
<View
|
||||
android:id="@+id/phone_bottom_line_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginTop="@dimen/layout_dp_8"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="@dimen/layout_dp_8"
|
||||
android:background="@color/colorDeepBlueGray"
|
||||
app:layout_constraintEnd_toEndOf="@id/phone_edit"
|
||||
app:layout_constraintStart_toStartOf="@id/country_code_text"
|
||||
@@ -81,5 +81,50 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/enter_valid_number_tv" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/moratorium_message_cv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/container_margin_extra"
|
||||
android:backgroundTint="@color/view_background_color_two"
|
||||
android:visibility="invisible"
|
||||
app:cardCornerRadius="@dimen/card_corner_half_radius"
|
||||
app:cardElevation="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/get_otp_btn"
|
||||
tools:visibility="visible">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/layout_dp_12">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/important_iv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@id/moratorium_title_tv"
|
||||
android:layout_alignBottom="@id/moratorium_title_tv"
|
||||
app:srcCompat="@drawable/ic_important_svg" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/moratorium_title_tv"
|
||||
style="@style/AgreementFontStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/layout_dp_6"
|
||||
android:layout_toEndOf="@id/important_iv" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/moratorium_description_tv"
|
||||
style="@style/TextDescExtraSmallFontStyleThree"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/important_iv"
|
||||
android:layout_alignStart="@id/important_iv"
|
||||
android:layout_marginTop="@dimen/layout_dp_8" />
|
||||
|
||||
</RelativeLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
</layout>
|
||||
|
||||
48
app/src/main/res/layout/moratorium_consent_fragment.xml
Normal file
48
app/src/main/res/layout/moratorium_consent_fragment.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="@dimen/container_padding"
|
||||
android:paddingEnd="@dimen/container_padding"
|
||||
android:paddingBottom="@dimen/container_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/moratorium_title_tv"
|
||||
style="@style/TitleFontStyle2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/moratorium_not_available" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/moratorium_message_tv"
|
||||
style="@style/AgreementTwoFontStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/moratorium_title_tv"
|
||||
android:layout_marginTop="@dimen/layout_dp_12"
|
||||
android:lineSpacingExtra="@dimen/spacing_6"
|
||||
tools:text="Please note that for this loan, we will not be able to offer the moratorium on EMIs till 31st August 2020. The EMIs will be scheduled as per the due dates shown in the EMI calendar and cannot be changed later." />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/moratorium_consent_cb"
|
||||
style="@style/TextDescExtraSmallFontStyleThree"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/moratorium_message_tv"
|
||||
android:layout_marginTop="@dimen/layout_dp_24"
|
||||
android:button="@drawable/checkbox"
|
||||
android:padding="@dimen/layout_dp_8" />
|
||||
|
||||
<com.naviapp.common.customview.ActionButtonView
|
||||
android:id="@+id/confirm_btn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/moratorium_consent_cb"
|
||||
android:layout_marginTop="@dimen/layout_dp_16" />
|
||||
</RelativeLayout>
|
||||
</layout>
|
||||
@@ -375,7 +375,8 @@
|
||||
<string name="about_us">About Us</string>
|
||||
<string name="about_us_menu_item">About us</string>
|
||||
<string name="copyright_text">© 2020 Navi Technologies Pvt Ltd. All rights are reserved.</string>
|
||||
<string name="moratorium_not_available">Moratorium Not Available</string>
|
||||
<string name="i_agree_to_not_availing_the_moratorium">I, %s, agree to not availing the moratorium</string>
|
||||
<string name="about_us_details">Navi is an Instant Personal Loan App where you can avail Personal Loans up to \u20B92 Lakh as direct cash transfer to your bank account. At Navi, our mission is to build financial services that are simple, affordable and accessible. With that view, our focus is on making the loan process quick and easy.\n\nThe loans are provided by Navi Finserv Pvt. Ltd. (formerly known as Chaitanya Rural Intermediation Development Services) as the registered Non-Banking Financial Company (NBFC), approved by the Reserve Bank of India (RBI).</string>
|
||||
<string name="customer_call">Call (Mon-Fri, 10am-5pm)</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -399,6 +399,10 @@
|
||||
<item name="android:textColor">@color/description_color_two</item>
|
||||
</style>
|
||||
|
||||
<style name="TextDescExtraSmallFontStyleThree" parent="TextDescExtraSmallFontStyleTwo">
|
||||
<item name="android:textColor">@color/description_color_one</item>
|
||||
</style>
|
||||
|
||||
<style name="TextDescExtraSmallFontStyleTwo">
|
||||
<item name="android:fontFamily">@font/navi_semi_bold</item>
|
||||
<item name="android:textSize">@dimen/font_extra_small</item>
|
||||
|
||||
Reference in New Issue
Block a user