Sat | <Ch-12343> | Hyperverge Selfie (#864)

* started...

* release 1.1.1

* Shashidhara | Update find ifsc visibility based on bank name validation

* back press issue

* minor changes

* pre populate loan data from edit bank account

* added contract

* selfie hyperverge

* error handlding

* added git sttaus

* added keys into gradle

* added keys into docker file

* intro screen hide after 1st time

* removed instrauction

Co-authored-by: Shashidhara Gopal <shashidhara.gopal@navi.com>
This commit is contained in:
Satish Prasad
2020-06-18 18:18:57 +05:30
committed by GitHub Enterprise
parent ecd6e1c043
commit 97573e6288
22 changed files with 445 additions and 67 deletions

View File

@@ -13,6 +13,8 @@ ARG RELEASE_KEY_ALIAS
ARG NEXUS_URL
ARG NEXUS_USERNAME
ARG NEXUS_PASSWORD
ARG HYPERVERGE_APP_KEY
ARG HYPERVERGE_APP_ID
ENV WORK_DIR="/android/navi" \
ANDROID_APK_DIR="app/build/outputs/apk"
@@ -39,7 +41,7 @@ RUN bash -c " \
\
elif [ $FLAVOR = PROD ] ; then \
./gradlew clean \
assembleProd -PBASE_URL=${BASE_URL} -PUXCAM_KEY=${UXCAM_KEY} -PRAZORPAY_KEY=${RAZORPAY_KEY} -PMOENGAGE_KEY=${MOENGAGE_KEY} -PAPPSFLYER_KEY=${APPSFLYER_KEY} -PFLAVOR=${FLAVOR} -PRELEASE_STORE_PASSWORD=${RELEASE_STORE_PASSWORD} -PRELEASE_KEY_PASSWORD=${RELEASE_KEY_PASSWORD} -PRELEASE_KEY_ALIAS=${RELEASE_KEY_ALIAS} ; \
assembleProd -PBASE_URL=${BASE_URL} -PUXCAM_KEY=${UXCAM_KEY} -PRAZORPAY_KEY=${RAZORPAY_KEY} -PMOENGAGE_KEY=${MOENGAGE_KEY} -PAPPSFLYER_KEY=${APPSFLYER_KEY} -PFLAVOR=${FLAVOR} -PRELEASE_STORE_PASSWORD=${RELEASE_STORE_PASSWORD} -PRELEASE_KEY_PASSWORD=${RELEASE_KEY_PASSWORD} -PRELEASE_KEY_ALIAS=${RELEASE_KEY_ALIAS} -PHYPERVERGE_APP_KEY=${HYPERVERGE_APP_KEY} -PHYPERVERGE_APP_ID=${HYPERVERGE_APP_ID} ; \
\
else echo 'ERROR: Flavor not mentioned' ; \
fi ;"

View File

@@ -83,6 +83,8 @@ android {
buildConfigField 'String', 'MOENGAGE_KEY', formatString('2PDJ4M6TDY7ADQ5N5LU48H9Y')
buildConfigField 'String', 'APPSFLYER_KEY', formatString('SwphSVn7YVzwCFFoAQNDKX')
buildConfigField 'String', 'UXCAM_KEY', formatString('ddo6o2imqy6y3so')
buildConfigField 'String', 'HYPERVERGE_APP_KEY', formatString('c9b1e034f7c8961a3f5b')
buildConfigField 'String', 'HYPERVERGE_APP_ID', formatString('2c007b')
}
dev {
applicationId "com.naviapp.dev"
@@ -92,15 +94,19 @@ android {
buildConfigField "String", "MOENGAGE_KEY", formatString('2PDJ4M6TDY7ADQ5N5LU48H9Y')
buildConfigField "String", "APPSFLYER_KEY", formatString('SwphSVn7YVzwCFFoAQNDKX')
buildConfigField 'String', 'UXCAM_KEY', formatString('ddo6o2imqy6y3so')
buildConfigField 'String', 'HYPERVERGE_APP_KEY', formatString('c9b1e034f7c8961a3f5b')
buildConfigField 'String', 'HYPERVERGE_APP_ID', formatString('2c007b')
}
prod {
dimension "app"
if (project.hasProperty('BASE_URL') && project.hasProperty('RAZORPAY_KEY') && project.hasProperty('MOENGAGE_KEY') && project.hasProperty('APPSFLYER_KEY') && project.hasProperty('UXCAM_KEY')) {
if (project.hasProperty('BASE_URL') && project.hasProperty('RAZORPAY_KEY') && project.hasProperty('MOENGAGE_KEY') && project.hasProperty('APPSFLYER_KEY') && project.hasProperty('UXCAM_KEY') && project.hasProperty('HYPERVERGE_APP_KEY') && project.hasProperty('HYPERVERGE_APP_ID')) {
buildConfigField 'String', 'BASE_URL', formatString("$BASE_URL")
buildConfigField 'String', 'RAZORPAY_KEY', formatString("$RAZORPAY_KEY")
buildConfigField 'String', 'MOENGAGE_KEY', formatString("$MOENGAGE_KEY")
buildConfigField 'String', 'APPSFLYER_KEY', formatString("$APPSFLYER_KEY")
buildConfigField 'String', 'UXCAM_KEY', formatString("$UXCAM_KEY")
buildConfigField 'String', 'HYPERVERGE_APP_KEY', formatString("$HYPERVERGE_APP_KEY")
buildConfigField 'String', 'HYPERVERGE_APP_ID', formatString("$HYPERVERGE_APP_ID")
}
}
mockServer {
@@ -111,6 +117,8 @@ android {
buildConfigField "String", "MOENGAGE_KEY", formatString('2PDJ4M6TDY7ADQ5N5LU48H9Y')
buildConfigField "String", "APPSFLYER_KEY", formatString('SwphSVn7YVzwCFFoAQNDKX')
buildConfigField "String", "UXCAM_KEY", formatString('ddo6o2imqy6y3so')
buildConfigField 'String', 'HYPERVERGE_APP_KEY', formatString('c9b1e034f7c8961a3f5b')
buildConfigField 'String', 'HYPERVERGE_APP_ID', formatString('2c007b')
}
}
packagingOptions {
@@ -288,6 +296,14 @@ dependencies {
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.71"
implementation('co.hyperverge:hypersnapsdk:3.1.3@aar', {
transitive = true
exclude group: 'com.google.android.gms'
exclude group: 'co.hyperverge', module: 'hypersnapsdk-instructions'
exclude group: 'co.hyperverge', module: 'hypersnapsdk-qrscanner'
exclude group: 'co.hyperverge', module: 'hypersnap-pdfconverter'
})
}
task executeTestsOnBrowserStack {
@@ -371,15 +387,21 @@ task executeTestsOnBrowserStack {
sleep(Duration.ofSeconds(10).toMillis())
} else {
def anyErrors = buildStatuses.any { it.status == "error" }
if(anyErrors) throw GradleException("Error executing Tests")
if (anyErrors) throw GradleException("Error executing Tests")
def anyTestStatusMissing = buildStatuses.any { it.devices["${device}"].test_status == null }
if(anyTestStatusMissing) throw GradleException("Error getting Test Status. Test Status is null")
def anyTestStatusMissing = buildStatuses.any {
it.devices["${device}"].test_status == null
}
if (anyTestStatusMissing) throw GradleException("Error getting Test Status. Test Status is null")
def anyTestFailed = buildStatuses.any { it.devices["${device}"].test_status.FAILED > 0 }
def failedBuilds = buildStatuses.findAll { it.devices["${device}"].test_status.FAILED > 0 }.collect { it.build_id }
def anyTestFailed = buildStatuses.any {
it.devices["${device}"].test_status.FAILED > 0
}
def failedBuilds = buildStatuses.findAll {
it.devices["${device}"].test_status.FAILED > 0
}.collect { it.build_id }
println(failedBuilds)
if(anyTestFailed) throw new GradleException("Tests failing")
if (anyTestFailed) throw new GradleException("Tests failing")
waitForResult = false
}

View File

@@ -237,3 +237,9 @@
#for UXCam
-keep class com.uxcam.** { *; }
-dontwarn com.uxcam.**
#for selfie : hyperverge
-dontwarn co.hyperverge.**
-keepclassmembers class * implements javax.net.ssl.SSLSocketFactory {
private javax.net.ssl.SSLSocketFactory delegate;
}

View File

@@ -19,7 +19,7 @@
<application
android:name=".app.NaviApplication"
android:allowBackup="false"
android:allowBackup="true"
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"

View File

@@ -6,6 +6,8 @@
package com.naviapp.analytics
import co.hyperverge.hypersnapsdk.HyperSnapSDK
import co.hyperverge.hypersnapsdk.objects.HyperSnapParams
import com.naviapp.BuildConfig
import com.naviapp.analytics.moengage.MoengageUtil
import com.naviapp.app.NaviApplication
@@ -20,6 +22,12 @@ object NaviTrackEvent {
FcmAnalyticsUtil.analytics.init(appContext)
MoengageUtil.init(appContext)
AppsFlyerUtil.instance.init(appContext)
HyperSnapSDK.init(
appContext,
BuildConfig.HYPERVERGE_APP_ID,
BuildConfig.HYPERVERGE_APP_KEY,
HyperSnapParams.Region.India
)
}
fun startScreen(screenName: String?, isNeededForAppsflyer: Boolean = false) {
@@ -59,7 +67,7 @@ object NaviTrackEvent {
fun setLatLongAttribute(location: UserLocation?) = MoengageUtil.setLatLongAttribute(location)
fun trackAppInstall(){
fun trackAppInstall() {
MoengageUtil.appInstallOrUpdate(true)
}

View File

@@ -24,6 +24,7 @@ import com.naviapp.getloan.bankdetailsautodebit.fragments.BankDetailsAutoDebitFr
import com.naviapp.getloan.bankdetailsautodebit.viewmodels.EnachSharedVM
import com.naviapp.getloan.common.CustomerSupportFragment
import com.naviapp.getloan.kyc.fragments.KycFragment
import com.naviapp.getloan.kyc.listeners.SelfieCaptureListener
import com.naviapp.getloan.kyc.models.AadhaarDetails
import com.naviapp.getloan.kyc.models.AadhaarVerificationData
import com.naviapp.getloan.kyc.viewmodels.KycSharedVM
@@ -31,8 +32,12 @@ import com.naviapp.getloan.loanagreement.fragments.LoanAgreementFragment
import com.naviapp.getloan.loandetails.fragments.LoanDetailsFragment
import com.naviapp.manager.PermissionsManager
import com.naviapp.models.SubPageStatusType
import com.naviapp.models.request.SelfieSetting
import com.naviapp.models.request.SelfieUploadRequestData
import com.naviapp.models.response.DisbursementDetailsResponse
import com.naviapp.network.ApiConstants
import com.naviapp.selfiecapture.SelfieErrorData
import com.naviapp.selfiecapture.SelfieVerificationHelper
import com.naviapp.useridentification.activities.LoanEligibilityLoaderActivity.Companion.OFFER
import com.naviapp.useridentification.common.FormProgressBar
import com.naviapp.utils.Constants
@@ -43,7 +48,9 @@ import org.json.JSONObject
import timber.log.Timber
class GetLoanActivity : BaseActivity(), FragmentInteractionListener,
FormProgressBar.FormProgressListener, NaviPermissionsListener, View.OnClickListener {
FormProgressBar.FormProgressListener, NaviPermissionsListener, View.OnClickListener,
SelfieCaptureListener {
private lateinit var binding: ActivityGetLoanBinding
private var disbursementDetailsData: DisbursementDetailsResponse? = null
private val kycSharedVM by lazy { ViewModelProvider(this).get(KycSharedVM::class.java) }
@@ -54,10 +61,11 @@ class GetLoanActivity : BaseActivity(), FragmentInteractionListener,
Manifest.permission.READ_EXTERNAL_STORAGE
)
private var currentScreen: String = ""
private var selfieVerificationHelper: SelfieVerificationHelper? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
selfieVerificationHelper = SelfieVerificationHelper()
initializeData(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_get_loan)
initUi()
@@ -319,6 +327,20 @@ class GetLoanActivity : BaseActivity(), FragmentInteractionListener,
.show(supportFragmentManager, CustomerSupportFragment.TAG)
}
override fun onSelfiCaptureClick(data: SelfieSetting?) {
selfieVerificationHelper?.startFaceCapture(this, this, data)
}
override fun onSelfieSuccess(data: SelfieUploadRequestData, imageUri: String?) {
kycSharedVM.setSelfieRquestData(data)
}
override fun onSelfieError(data: SelfieErrorData?) {
data?.let {
kycSharedVM.setSelfieErrorData(data)
}
}
override fun onClick(view: View?) {
when (view?.id) {
R.id.help_iv -> openHelpInfo()

View File

@@ -12,6 +12,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RadioGroup
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
@@ -29,17 +30,12 @@ import com.naviapp.databinding.KycFragmentBinding
import com.naviapp.digio.AadhaarVerificationHelper
import com.naviapp.errors.activities.ErrorActivity
import com.naviapp.errors.utils.getGetAadhaarCancelOrFailData
import com.naviapp.firebasedb.AADHAR_VERIFICATION
import com.naviapp.firebasedb.FirebaseDataHelper
import com.naviapp.firebasedb.FirebaseDataReceiveListener
import com.naviapp.firebasedb.FirebaseResponse
import com.naviapp.firebasedb.FirebaseStatusType
import com.naviapp.firebasedb.*
import com.naviapp.firebasedb.FirebaseStatusType.SUCCESS
import com.naviapp.firebasedb.SELFIE
import com.naviapp.firebasedb.UPDATE_KYC_DETAILS
import com.naviapp.getloan.activities.GetLoanActivity.Companion.BANK_DETAILS_SCREEN
import com.naviapp.getloan.kyc.activities.SelfieCaptureActivity
import com.naviapp.getloan.kyc.listeners.DocumentUploadListener
import com.naviapp.getloan.kyc.listeners.SelfieCaptureListener
import com.naviapp.getloan.kyc.models.AadhaarDetails
import com.naviapp.getloan.kyc.models.AadhaarVerificationData
import com.naviapp.getloan.kyc.utils.getKycDocuments
@@ -49,18 +45,21 @@ import com.naviapp.models.RedirectPageStatus
import com.naviapp.models.UiStatusValue
import com.naviapp.models.request.Address
import com.naviapp.models.request.KycRequest
import com.naviapp.models.request.SelfieSetting
import com.naviapp.models.request.SelfieUploadRequestData
import com.naviapp.network.ApiConstants
import com.naviapp.network.ApiErrorTagType
import com.naviapp.network.models.GenericErrorResponse
import com.naviapp.utils.*
import com.naviapp.utils.Constants.MIN_ADDRESS_SIZE
import com.naviapp.utils.Constants.SHOULD_SET_PROGRESS
import kotlinx.android.synthetic.main.kyc_address_layout.view.no_rb
import kotlinx.android.synthetic.main.kyc_address_layout.view.yes_rb
import kotlinx.android.synthetic.main.kyc_address_layout.view.*
import timber.log.Timber
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
class KycFragment : BaseFragment(), RadioGroup.OnCheckedChangeListener, View.OnClickListener,
View.OnFocusChangeListener,
DocumentUploadListener {
@@ -75,6 +74,8 @@ class KycFragment : BaseFragment(), RadioGroup.OnCheckedChangeListener, View.OnC
private var listener: FragmentInteractionListener? = null
private var permissionsListener: NaviPermissionsListener? = null
private val analyticsEventTracker = NaviAnalytics.naviAnalytics.Kyc()
private var selfieCaptureListener: SelfieCaptureListener? = null
override fun onCreateView(
inflater: LayoutInflater,
@@ -182,6 +183,14 @@ class KycFragment : BaseFragment(), RadioGroup.OnCheckedChangeListener, View.OnC
observerKycDetailUpdateStatus()
observeSelfieUploadStatus()
observeAdhaarVerificationData()
observeSelfieSetting()
}
private fun observeSelfieSetting() {
viewModel.selfieSetting.observeNullable(this) {
hideLoader()
handleSelectSelfie(it)
}
}
private fun asyncErrorObserver() {
@@ -292,6 +301,20 @@ class KycFragment : BaseFragment(), RadioGroup.OnCheckedChangeListener, View.OnC
}
}
}
sharedVM.selfieRequestData.observeNullable(this) { data ->
data?.let {
if (it.imageUrl.isNullOrBlank().not())
handleSelfieImage(it.imageUrl.orEmpty(), it)
}
}
sharedVM.selfieErrorData.observeNonNull(this) {
hideLoader()
if (it.isCameraPermission.orFalse()) {
activity?.toast(R.string.selfie_permission_error, Toast.LENGTH_LONG)
}
}
}
private fun observerAdhaarUploadStatus() {
@@ -362,19 +385,40 @@ class KycFragment : BaseFragment(), RadioGroup.OnCheckedChangeListener, View.OnC
private fun handleSelfieCapture(bundle: Bundle) {
if (isDead(activity)) return
bundle.getString(Constants.DATA)?.let {
try {
val inputStream: FileInputStream? = activity?.openFileInput(it)
val bitmapImage = BitmapFactory.decodeStream(inputStream)
inputStream?.close()
val outputStream = ByteArrayOutputStream()
bitmapImage.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
handleSelfieImage(it)
}
}
private fun handleSelfieImage(
url: String,
requestData: SelfieUploadRequestData? = null
) {
try {
val outputStream = ByteArrayOutputStream()
var bitmapImage: Bitmap? = null
when (viewModel.selfieSetting.value?.provider) {
HYPERVERGE -> {
val imgFile = File(url)
if (imgFile.exists()) {
val bmOptions = BitmapFactory.Options()
bitmapImage = BitmapFactory.decodeFile(imgFile.absolutePath, bmOptions)
}
}
else -> {
val inputStream: FileInputStream? = activity?.openFileInput(url)
bitmapImage = BitmapFactory.decodeStream(inputStream)
inputStream?.close()
}
}
bitmapImage?.let {
it.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
deInitializeFirebaseListener()
showLoader()
viewModel.submitSelfie(outputStream.toByteArray())
Glide.with(this).asBitmap().load(bitmapImage).circleCrop().into(binding.previewIv)
} catch (e: java.lang.Exception) {
e.printStackTrace()
viewModel.submitSelfie(outputStream.toByteArray(), requestData)
Glide.with(this).asBitmap().load(it).circleCrop().into(binding.previewIv)
}
} catch (e: Exception) {
e.log()
}
}
@@ -560,12 +604,24 @@ class KycFragment : BaseFragment(), RadioGroup.OnCheckedChangeListener, View.OnC
}
private fun openSelfieScreen() {
analyticsEventTracker.onSelfieButtonTap()
try {
val intent = Intent(activity, SelfieCaptureActivity::class.java)
startActivityForResult(intent, SELFIE_REQUEST_CODE)
} catch (e: Exception) {
e.log()
showLoader()
viewModel.fetchSelfieSetting()
}
private fun handleSelectSelfie(selfieSetting: SelfieSetting?) {
when (selfieSetting?.provider?.toUpperCase()) {
HYPERVERGE -> {
selfieCaptureListener?.onSelfiCaptureClick(selfieSetting)
}
else -> {
try {
analyticsEventTracker.onSelfieButtonTap()
val intent = Intent(activity, SelfieCaptureActivity::class.java)
startActivityForResult(intent, SELFIE_REQUEST_CODE)
} catch (e: Exception) {
e.log()
}
}
}
}
@@ -741,6 +797,7 @@ class KycFragment : BaseFragment(), RadioGroup.OnCheckedChangeListener, View.OnC
try {
listener = context as? FragmentInteractionListener
permissionsListener = context as? NaviPermissionsListener
selfieCaptureListener = context as? SelfieCaptureListener
} catch (e: Exception) {
Timber.e(e, "Exception on attach")
}
@@ -757,5 +814,7 @@ class KycFragment : BaseFragment(), RadioGroup.OnCheckedChangeListener, View.OnC
private const val COMPLETED = "COMPLETED"
private const val PERMANENT = "PERMANENT"
private const val CORRESPONDENCE = "CORRESPONDENCE"
private const val HYPERVERGE = "HYPERVERGE"
}
}

View File

@@ -0,0 +1,20 @@
/*
* *
* * Copyright (c) 2020 . All rights reserved @Navi
*
*/
package com.naviapp.getloan.kyc.listeners
import com.naviapp.models.request.SelfieSetting
import com.naviapp.models.request.SelfieUploadRequestData
import com.naviapp.selfiecapture.SelfieErrorData
interface SelfieCaptureListener {
fun onSelfiCaptureClick(data: SelfieSetting?)
fun onSelfieSuccess(data: SelfieUploadRequestData, imageUri: String?)
fun onSelfieError(data: SelfieErrorData?)
}

View File

@@ -2,6 +2,8 @@ package com.naviapp.getloan.kyc.repositories
import com.naviapp.getloan.kyc.models.AadhaarVerificationData
import com.naviapp.models.request.KycRequest
import com.naviapp.models.request.SelfieSetting
import com.naviapp.models.request.SelfieUploadRequestData
import com.naviapp.models.response.UploadDataAsyncResponse
import com.naviapp.network.models.RepoResult
import com.naviapp.network.retrofit.ResponseCallback
@@ -12,7 +14,10 @@ import retrofit2.http.Body
import retrofit2.http.Part
class KycRepository : ResponseCallback() {
suspend fun submitSelfie(@Part type: RequestBody, @Part file: MultipartBody.Part) =
suspend fun submitSelfie(
@Part type: RequestBody? = null,
@Part file: MultipartBody.Part
) =
apiResponseCallback(retrofitService().submitSelfie(type, file))
suspend fun submitKYCDocument(@Part type: RequestBody, @Part file: MultipartBody.Part) =
@@ -38,4 +43,7 @@ class KycRepository : ResponseCallback() {
suspend fun postAadhaarVerificationData(data: AadhaarVerificationData) =
apiResponseCallback(retrofitService().postAadharVerificationData(data))
suspend fun fetchSelfieSettingRequestData(): RepoResult<SelfieSetting> =
apiResponseCallback(retrofitService().fetchSelfieSettings())
}

View File

@@ -10,6 +10,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.naviapp.getloan.kyc.models.AadhaarVerificationData
import com.naviapp.models.request.SelfieUploadRequestData
import com.naviapp.selfiecapture.SelfieErrorData
class KycSharedVM : ViewModel() {
private val _aadhaarPermission = MutableLiveData<Boolean>()
@@ -24,6 +26,14 @@ class KycSharedVM : ViewModel() {
val hasUserCancelled: LiveData<Boolean>
get() = _hasUserCancelled
private val _selfieRequestData = MutableLiveData<SelfieUploadRequestData>()
val selfieRequestData: LiveData<SelfieUploadRequestData>
get() = _selfieRequestData
private val _selfieErrorData = MutableLiveData<SelfieErrorData>()
val selfieErrorData: LiveData<SelfieErrorData>
get() = _selfieErrorData
fun setAadhaarPermission(permissions: Boolean?) {
_aadhaarPermission.value = permissions
}
@@ -36,8 +46,18 @@ class KycSharedVM : ViewModel() {
_hasUserCancelled.value = hasUserCancelled
}
fun setSelfieRquestData(data: SelfieUploadRequestData) {
_selfieRequestData.value = data
}
fun setSelfieErrorData(data: SelfieErrorData) {
_selfieErrorData.value = data
}
fun deInitData() {
_aadhaarPermission.value = null
_aadhaarVerificationData.value = null
_selfieRequestData.value = null
_selfieErrorData.value = null
}
}

View File

@@ -14,15 +14,16 @@ import com.naviapp.getloan.kyc.models.KycStatus
import com.naviapp.getloan.kyc.repositories.KycRepository
import com.naviapp.models.RedirectPageStatus
import com.naviapp.models.request.KycRequest
import com.naviapp.models.request.SelfieSetting
import com.naviapp.models.request.SelfieUploadRequestData
import com.naviapp.models.response.KycDetailsResponse
import com.naviapp.models.response.UploadDataAsyncResponse
import com.naviapp.network.ApiErrorTagType
import com.naviapp.network.models.ErrorMessage
import com.naviapp.network.models.GenericErrorResponse
import com.naviapp.utils.getMultipartBody
import com.naviapp.utils.getMultipartRequestBody
import com.naviapp.utils.orTrue
import com.naviapp.utils.*
import kotlinx.coroutines.launch
import okhttp3.RequestBody
class KycVM : BaseVM() {
private val repository by lazy { KycRepository() }
@@ -75,9 +76,15 @@ class KycVM : BaseVM() {
val asyncError: LiveData<Boolean>
get() = _asyncError
private val _selfieSetting = MutableLiveData<SelfieSetting>()
val selfieSetting: LiveData<SelfieSetting>
get() = _selfieSetting
fun submitSelfie(bytes: ByteArray) {
val requestBody = getMultipartRequestBody(SELFIE_IMAGE)
fun submitSelfie(bytes: ByteArray, requestData: SelfieUploadRequestData? = null) {
var requestBody: RequestBody? = null
convertObjectToJsonString(requestData)?.let {
requestBody = getMultipartRequestJsonBody(it)
}
val multipartBody = getMultipartBody(bytes, FILE_NAME)
coroutineScope.launch {
val response = repository.submitSelfie(
@@ -227,6 +234,21 @@ class KycVM : BaseVM() {
}
}
fun fetchSelfieSetting() {
coroutineScope.launch {
val response = repository.fetchSelfieSettingRequestData()
if (response.error == null) {
_selfieSetting.value = response.data
} else {
setErrorData(
response.errors,
response.error,
ApiErrorTagType.SELFIE_SETTING_API_ERROR
)
}
}
}
companion object {
private const val KYC = "kyc"
private const val KYC_FILE_NAME = "kyc.jpg"

View File

@@ -0,0 +1,22 @@
/*
* *
* * Copyright (c) 2020 . All rights reserved @Navi
*
*/
package com.naviapp.models.request
import com.google.gson.annotations.SerializedName
data class SelfieSetting(
@SerializedName("provider") val provider: String? = null,
@SerializedName("livenessMode") val livenessMode: Boolean? = null,
@SerializedName("showInstructionsPage") val showInstructionsPage: Boolean? = null,
@SerializedName("faceCaptureTitle") val faceCaptureTitle: String? = null,
@SerializedName("livenessAPIParameters") val livenessAPIParameters: ArrayList<livenessParamData>? = null
)
data class livenessParamData(
@SerializedName("value") val value: String? = null,
@SerializedName("key") val key: String? = null
)

View File

@@ -0,0 +1,16 @@
/*
* *
* * Copyright (c) 2020 . All rights reserved @Navi
*
*/
package com.naviapp.models.request
import com.google.gson.annotations.SerializedName
data class SelfieUploadRequestData(
@SerializedName("live") val live: String? = null,
@SerializedName("livenessScore") val livenessScore: Float? = null,
@SerializedName("toBeReviewed") val toBeReviewed: String? = null,
val imageUrl: String? = null
)

View File

@@ -45,6 +45,7 @@ object ApiErrorTagType {
const val SUBMIT_KYC_API_ERROR = "SUBMIT_KYC_API_ERROR"
const val FAQS_FETCH_ERROR = "FAQS_FETCH_ERROR"
const val UI_STATUS_API_ERROR = "UI_STATUS_API_ERROR"
const val SELFIE_SETTING_API_ERROR = "SELFIE_SETTING_API_ERROR"
@StringDef(
SELFIE_UPLOAD,
@@ -80,7 +81,8 @@ object ApiErrorTagType {
FAQS_FETCH_ERROR,
FETCH_USER_DETAILS_ERROR,
UI_STATUS_API_ERROR,
NO_INTERNET_ERROR
NO_INTERNET_ERROR,
SELFIE_SETTING_API_ERROR
)
@Retention(AnnotationRetention.SOURCE)
annotation class ApiErrorTagTypeDef

View File

@@ -28,25 +28,7 @@ import com.naviapp.models.UserInstalledApp
import com.naviapp.models.UserLocation
import com.naviapp.models.UserProfile
import com.naviapp.models.UserSms
import com.naviapp.models.request.BankDetail
import com.naviapp.models.request.CkycRequest
import com.naviapp.models.request.DeviceDetail
import com.naviapp.models.request.GenerateOfferRequest
import com.naviapp.models.request.GenerateQuoteRequest
import com.naviapp.models.request.InputNameRequest
import com.naviapp.models.request.KycRequest
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
import com.naviapp.models.request.RateDataRequest
import com.naviapp.models.request.RegisterRequest
import com.naviapp.models.request.UserBasicDetailsRequest
import com.naviapp.models.request.WorkRequest
import com.naviapp.models.request.*
import com.naviapp.models.response.AdditionalDataAsyncResponse
import com.naviapp.models.response.AppUpgradeResponse
import com.naviapp.models.response.ApplicationIdResponse
@@ -221,8 +203,9 @@ interface RetrofitService {
@Multipart
@POST("/customer-service/customers/me/selfie")
suspend fun submitSelfie(
@Part("type") type: RequestBody,
@Part("details") type: RequestBody? = null,
@Part file: MultipartBody.Part
): Response<GenericResponse<UploadDataAsyncResponse>>
@Multipart
@@ -420,4 +403,7 @@ interface RetrofitService {
@POST("customer-service/customers/me/ratings")
suspend fun postRatingReview(@Body request: RateDataRequest): Response<GenericResponse<RatingData>>
@GET("customer-service/customers/selfie-settings")
suspend fun fetchSelfieSettings(): Response<GenericResponse<SelfieSetting>>
}

View File

@@ -0,0 +1,12 @@
/*
* *
* * Copyright (c) 2020 . All rights reserved @Navi
*
*/
package com.naviapp.selfiecapture
data class SelfieErrorData(
val isError: Boolean? = null,
val isCameraPermission: Boolean? = null
)

View File

@@ -0,0 +1,118 @@
/*
* *
* * Copyright (c) 2020 . All rights reserved @Navi
*
*/
package com.naviapp.selfiecapture
import android.app.Activity
import co.hyperverge.hypersnapsdk.activities.HVFaceActivity
import co.hyperverge.hypersnapsdk.listeners.FaceCaptureCompletionHandler
import co.hyperverge.hypersnapsdk.objects.HVError
import co.hyperverge.hypersnapsdk.objects.HVFaceConfig
import com.naviapp.getloan.kyc.listeners.SelfieCaptureListener
import com.naviapp.models.request.SelfieSetting
import com.naviapp.models.request.SelfieUploadRequestData
import com.naviapp.sharedpref.PreferenceManager
import com.naviapp.utils.*
import org.json.JSONObject
class SelfieVerificationHelper {
private var listener: SelfieCaptureListener? = null
fun startFaceCapture(
activity: Activity,
listener: SelfieCaptureListener?,
data: SelfieSetting?
) {
try {
val introShownCount =
PreferenceManager.getIntPreference(SELFIE_INTRO_SHOWN_COUNT).orZero()
this.listener = listener
val hvFaceConfig = HVFaceConfig()
data?.let {
hvFaceConfig.setLivenessMode(
if (it.livenessMode.orTrue())
HVFaceConfig.LivenessMode.TEXTURELIVENESS
else
HVFaceConfig.LivenessMode.NONE
)
if (introShownCount < MAX_NO_SLEFIE_INTRO)
hvFaceConfig.isShouldShowInstructionPage = it.showInstructionsPage.orFalse()
else
hvFaceConfig.isShouldShowInstructionPage = false
if (it.faceCaptureTitle.isNullOrBlank().not())
hvFaceConfig.faceCaptureTitle = it.faceCaptureTitle
if (it.livenessAPIParameters?.size.orZero() > 0) {
val params = JSONObject()
it.livenessAPIParameters?.forEach { data ->
data.key?.let { key ->
params.put(key, data.value)
}
}
hvFaceConfig.setLivenessAPIParameters(params)
}
} ?: run {
hvFaceConfig.setLivenessMode(HVFaceConfig.LivenessMode.TEXTURELIVENESS)
hvFaceConfig.isShouldShowInstructionPage = false
}
val completionCallback =
FaceCaptureCompletionHandler { error, result, headers ->
error?.let {
when (it.errorCode) {
HVError.OPERATION_CANCELLED_BY_USER_ERROR -> {
listener?.onSelfieError(SelfieErrorData(isError = true))
}
HVError.PERMISSIONS_NOT_GRANTED_ERROR -> {
listener?.onSelfieError(SelfieErrorData(isCameraPermission = true))
}
else -> {
listener?.onSelfieError(SelfieErrorData(isError = true))
}
}
}
result?.let { data ->
listener?.onSelfieSuccess(
populateSelfieResponse(data),
data.getString("imageUri")
)
}
if (introShownCount < MAX_NO_SLEFIE_INTRO)
PreferenceManager.setIntPreference(
SELFIE_INTRO_SHOWN_COUNT,
introShownCount + 1
)
}
HVFaceActivity.start(activity, hvFaceConfig, completionCallback)
} catch (e: Exception) {
e.log()
}
}
private fun populateSelfieResponse(jsonData: JSONObject): SelfieUploadRequestData {
try {
val data = jsonData.getJSONObject("result")
val selfieUploadRequestData = SelfieUploadRequestData(
live = data.getString("live"),
toBeReviewed = data.getString("to-be-reviewed"),
livenessScore = data.getString("liveness-score").toFloat(),
imageUrl = jsonData.getString("imageUri")
)
return selfieUploadRequestData
} catch (e: Exception) {
e.log()
}
return SelfieUploadRequestData(live = "no")
}
companion object {
private const val MAX_NO_SLEFIE_INTRO = 1
}
}

View File

@@ -26,8 +26,8 @@ import com.naviapp.network.retrofit.RetrofitService
import com.naviapp.utils.Constants.INR
import java.text.DecimalFormat
fun Context.toast(@StringRes message: Int) =
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
fun Context.toast(@StringRes message: Int, toastLength: Int = Toast.LENGTH_SHORT) =
Toast.makeText(this, message, toastLength).show()
fun Context.toast(
message: CharSequence?,

View File

@@ -23,3 +23,4 @@ const val TERMS_AND_CONDITIONS_CHECKED = "TERMS_AND_CONDITIONS_CHECKED"
const val PERMISSION_SCREEN_SHOWN = "PERMISSION_SCREEN_SHOWN"
const val LOCATION = "LOCATION"
const val APP_INSTALLED = "APP_INSTALLED"
const val SELFIE_INTRO_SHOWN_COUNT = "SELFIE_INTRO_SHOWN_COUNT"

View File

@@ -19,7 +19,6 @@ import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat.startActivity
import com.google.firebase.auth.FirebaseAuth
import com.google.gson.Gson
import com.naviapp.analytics.moengage.MoengageUtil
@@ -168,6 +167,12 @@ fun getMultipartBody(bytes: ByteArray, fileName: String): MultipartBody.Part {
return MultipartBody.Part.createFormData("file", fileName, reqFile)
}
fun getMultipartRequestJsonBody(content: String): RequestBody {
return RequestBody.create(
"application/json".toMediaTypeOrNull(), content
)
}
fun <T> convertObjectToJson(type: T): JSONObject? {
val gson = Gson()
val jsonString = gson.toJson(type)
@@ -224,4 +229,14 @@ private fun getPackgName(): String {
} catch (e: Exception) {
return "com.naviapp"
}
}
fun <T> convertObjectToJsonString(type: T): String? {
val gson = Gson()
val jsonString = gson.toJson(type)
try {
return jsonString
} catch (e: JSONException) {
}
return null
}

View File

@@ -391,4 +391,14 @@
<string name="supported_banks">Supported banks</string>
<string name="this_bank_is_not_supported">This bank is not supported. Kindly select some other bank.</string>
<!-- Customisation strings in SDK -->
<!-- Face capture screen -->
<string name="selfie_title">Selfie Capture</string>
<string name="faceCaptureFaceFound">Capture Now</string>
<string name="faceCaptureFaceNotFound">Place your face within circle</string>
<string name="faceCaptureMoveAway"> Move away from the phone</string>
<string name="faceCaptureActivity">Processing</string>
<string name="selfie_permission_error">Please give camera permissions from phone settings</string>
</resources>

View File

@@ -32,6 +32,13 @@ allprojects {
}
url "https://nexus.cmd.navi-tech.in/repository/maven-snapshots"
}
maven {
url "s3://hvsdk/android/releases"
credentials(AwsCredentials) {
accessKey "AKIAIIQJNVDGKVQWN62Q"
secretKey "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"
}
}
}
}