diff --git a/Dockerfile b/Dockerfile index e4d3af2f23..c1ae8ab464 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 ;" diff --git a/app/build.gradle b/app/build.gradle index 5da52f64b3..9a4c67701a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 6ef2bdad1e..470f9772d4 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -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; + } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ed90b762bb..db54645199 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,7 +19,7 @@ openHelpInfo() diff --git a/app/src/main/java/com/naviapp/getloan/kyc/fragments/KycFragment.kt b/app/src/main/java/com/naviapp/getloan/kyc/fragments/KycFragment.kt index 4c9715744e..d1ca8b76bc 100644 --- a/app/src/main/java/com/naviapp/getloan/kyc/fragments/KycFragment.kt +++ b/app/src/main/java/com/naviapp/getloan/kyc/fragments/KycFragment.kt @@ -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" + } } \ No newline at end of file diff --git a/app/src/main/java/com/naviapp/getloan/kyc/listeners/SelfieCaptureListener.kt b/app/src/main/java/com/naviapp/getloan/kyc/listeners/SelfieCaptureListener.kt new file mode 100644 index 0000000000..fadde09068 --- /dev/null +++ b/app/src/main/java/com/naviapp/getloan/kyc/listeners/SelfieCaptureListener.kt @@ -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?) +} \ No newline at end of file diff --git a/app/src/main/java/com/naviapp/getloan/kyc/repositories/KycRepository.kt b/app/src/main/java/com/naviapp/getloan/kyc/repositories/KycRepository.kt index 5ac7d83dcb..0230d63922 100644 --- a/app/src/main/java/com/naviapp/getloan/kyc/repositories/KycRepository.kt +++ b/app/src/main/java/com/naviapp/getloan/kyc/repositories/KycRepository.kt @@ -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 = + apiResponseCallback(retrofitService().fetchSelfieSettings()) } \ No newline at end of file diff --git a/app/src/main/java/com/naviapp/getloan/kyc/viewmodels/KycSharedVM.kt b/app/src/main/java/com/naviapp/getloan/kyc/viewmodels/KycSharedVM.kt index f8c53d63ba..de8305a4e9 100644 --- a/app/src/main/java/com/naviapp/getloan/kyc/viewmodels/KycSharedVM.kt +++ b/app/src/main/java/com/naviapp/getloan/kyc/viewmodels/KycSharedVM.kt @@ -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() @@ -24,6 +26,14 @@ class KycSharedVM : ViewModel() { val hasUserCancelled: LiveData get() = _hasUserCancelled + private val _selfieRequestData = MutableLiveData() + val selfieRequestData: LiveData + get() = _selfieRequestData + + private val _selfieErrorData = MutableLiveData() + val selfieErrorData: LiveData + 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 } } \ No newline at end of file diff --git a/app/src/main/java/com/naviapp/getloan/kyc/viewmodels/KycVM.kt b/app/src/main/java/com/naviapp/getloan/kyc/viewmodels/KycVM.kt index afadd9ff3f..830b21954d 100644 --- a/app/src/main/java/com/naviapp/getloan/kyc/viewmodels/KycVM.kt +++ b/app/src/main/java/com/naviapp/getloan/kyc/viewmodels/KycVM.kt @@ -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 get() = _asyncError + private val _selfieSetting = MutableLiveData() + val selfieSetting: LiveData + 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" diff --git a/app/src/main/java/com/naviapp/models/request/SelfieSetting.kt b/app/src/main/java/com/naviapp/models/request/SelfieSetting.kt new file mode 100644 index 0000000000..65ff9ee044 --- /dev/null +++ b/app/src/main/java/com/naviapp/models/request/SelfieSetting.kt @@ -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? = null +) + +data class livenessParamData( + @SerializedName("value") val value: String? = null, + @SerializedName("key") val key: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/naviapp/models/request/SelfieUploadRequestData.kt b/app/src/main/java/com/naviapp/models/request/SelfieUploadRequestData.kt new file mode 100644 index 0000000000..dbb64c12d7 --- /dev/null +++ b/app/src/main/java/com/naviapp/models/request/SelfieUploadRequestData.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/com/naviapp/network/ApiErrorTagType.kt b/app/src/main/java/com/naviapp/network/ApiErrorTagType.kt index 3cfc8ff4fe..f36b69ebe2 100644 --- a/app/src/main/java/com/naviapp/network/ApiErrorTagType.kt +++ b/app/src/main/java/com/naviapp/network/ApiErrorTagType.kt @@ -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 diff --git a/app/src/main/java/com/naviapp/network/retrofit/RetrofitService.kt b/app/src/main/java/com/naviapp/network/retrofit/RetrofitService.kt index 0dcec78dc9..c271f20db6 100644 --- a/app/src/main/java/com/naviapp/network/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/naviapp/network/retrofit/RetrofitService.kt @@ -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> @Multipart @@ -420,4 +403,7 @@ interface RetrofitService { @POST("customer-service/customers/me/ratings") suspend fun postRatingReview(@Body request: RateDataRequest): Response> + @GET("customer-service/customers/selfie-settings") + suspend fun fetchSelfieSettings(): Response> + } \ No newline at end of file diff --git a/app/src/main/java/com/naviapp/selfiecapture/SelfieErrorData.kt b/app/src/main/java/com/naviapp/selfiecapture/SelfieErrorData.kt new file mode 100644 index 0000000000..4402dacdb4 --- /dev/null +++ b/app/src/main/java/com/naviapp/selfiecapture/SelfieErrorData.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/com/naviapp/selfiecapture/SelfieVerificationHelper.kt b/app/src/main/java/com/naviapp/selfiecapture/SelfieVerificationHelper.kt new file mode 100644 index 0000000000..d513923099 --- /dev/null +++ b/app/src/main/java/com/naviapp/selfiecapture/SelfieVerificationHelper.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/naviapp/utils/Ext.kt b/app/src/main/java/com/naviapp/utils/Ext.kt index 0b8ce10745..b9a42dba29 100644 --- a/app/src/main/java/com/naviapp/utils/Ext.kt +++ b/app/src/main/java/com/naviapp/utils/Ext.kt @@ -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?, diff --git a/app/src/main/java/com/naviapp/utils/PrefConstants.kt b/app/src/main/java/com/naviapp/utils/PrefConstants.kt index e022236e99..98b454d976 100644 --- a/app/src/main/java/com/naviapp/utils/PrefConstants.kt +++ b/app/src/main/java/com/naviapp/utils/PrefConstants.kt @@ -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" diff --git a/app/src/main/java/com/naviapp/utils/Utility.kt b/app/src/main/java/com/naviapp/utils/Utility.kt index 6e96b49097..aac086f1da 100644 --- a/app/src/main/java/com/naviapp/utils/Utility.kt +++ b/app/src/main/java/com/naviapp/utils/Utility.kt @@ -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 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 convertObjectToJsonString(type: T): String? { + val gson = Gson() + val jsonString = gson.toJson(type) + try { + return jsonString + } catch (e: JSONException) { + } + return null } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7244689268..8dd56382c4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -391,4 +391,14 @@ Supported banks This bank is not supported. Kindly select some other bank. + + + Selfie Capture + Capture Now + Place your face within circle + Move away from the phone + Processing + + Please give camera permissions from phone settings + diff --git a/build.gradle b/build.gradle index 0dc4f3e6e5..73393ff6f7 100644 --- a/build.gradle +++ b/build.gradle @@ -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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" + } + } } }