Merge pull request #802 from medici/ch11739

Shashidhara | [ch11739] | moratorium consent screen
This commit is contained in:
Shashidhara Gopal
2020-05-28 22:18:37 +05:30
committed by GitHub Enterprise
23 changed files with 557 additions and 10 deletions

View File

@@ -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") =

View File

@@ -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"
)
}

View File

@@ -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()

View File

@@ -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 ?: "")))
}

View File

@@ -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

View File

@@ -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()
}
}
}

View File

@@ -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"

View File

@@ -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
}
}

View File

@@ -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))
}

View File

@@ -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)
}
}
}
}

View File

@@ -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
}
}
}

View 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
)

View File

@@ -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

View File

@@ -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
)

View File

@@ -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>>
}

View File

@@ -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)

View File

@@ -29,4 +29,7 @@ class RegisterRepository : ResponseCallback() {
suspend fun fetchPrivacyPolicy() =
apiResponseCallback(retrofitService().fetchPrivacyPolicy())
suspend fun fetchLoginSettings() =
apiResponseCallback(retrofitService().fetchLoginSettings())
}

View File

@@ -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
}
}
}
}

View 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>

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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>