From 2695b01470c9de4e1b5b1ef9a654dff5c028ea5a Mon Sep 17 00:00:00 2001 From: Sidharth Bamba Date: Tue, 12 Dec 2023 13:46:13 +0530 Subject: [PATCH] TP-40039 | Internal permission revamp (#8739) Co-authored-by: Shaurya Rehan Co-authored-by: rahul bhat Co-authored-by: shankar yadav Co-authored-by: Aditya Narayan Malik --- gradle/libs.versions.toml | 2 +- .../common/model/view/NaviPermissionResult.kt | 13 +- .../pay/common/model/view/PermissionResult.kt | 2 +- .../com/navi/pay/entry/NaviPayActivity.kt | 6 +- .../navi/pay/entry/ui/NaviPayMainScreen.kt | 95 +++-- .../scanpay/ui/QrScannerScreen.kt | 45 +- .../scanpay/viewmodel/QrScannerViewModel.kt | 12 +- .../paytocontacts/ui/PayToContactsScreen.kt | 63 ++- .../viewmodel/PayToContactsViewModel.kt | 11 +- .../binding/ui/NaviPayOnboardingActivity.kt | 14 +- .../binding/ui/NaviPayOnboardingMainScreen.kt | 54 +++ .../binding/ui/NaviPayOnboardingScreen.kt | 31 +- .../viewmodel/NaviPayOnboardingViewModel.kt | 6 + .../permission/model/view/PermissionData.kt | 5 + .../pay/permission/ui/PermissionScreen.kt | 383 +++++++----------- .../pay/permission/utils/PermissionUtils.kt | 2 +- .../kotlin/com/navi/pay/utils/NaviPayExt.kt | 23 ++ .../res/drawable/ic_contact_permission.xml | 78 ++-- .../res/drawable/ic_permission_camera.xml | 55 ++- .../res/drawable/ic_permission_contact.xml | 68 ++++ .../drawable/ic_permission_phone_state.xml | 88 ++-- .../res/drawable/ic_permission_send_sms.xml | 38 +- navi-pay/src/main/res/values/strings.xml | 20 +- 23 files changed, 626 insertions(+), 488 deletions(-) create mode 100644 navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/ui/NaviPayOnboardingMainScreen.kt create mode 100644 navi-pay/src/main/res/drawable/ic_permission_contact.xml diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1b148526c4..efe33feb9f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] accompanist-pager = "0.28.0" -accompanist-permissions = "0.25.1" +accompanist-permissions = "0.32.0" accompanist-systemuicontroller = "0.17.0" androidGradlePlugin = "8.1.2" android-exoplayer = "2.18.1" diff --git a/navi-pay/src/main/kotlin/com/navi/pay/common/model/view/NaviPermissionResult.kt b/navi-pay/src/main/kotlin/com/navi/pay/common/model/view/NaviPermissionResult.kt index 3f6e76a24e..50fd828819 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/common/model/view/NaviPermissionResult.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/common/model/view/NaviPermissionResult.kt @@ -1,6 +1,7 @@ package com.navi.pay.common.model.view import android.app.Activity +import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext import androidx.core.app.ActivityCompat @@ -9,9 +10,12 @@ import com.google.accompanist.permissions.MultiplePermissionsState import com.google.accompanist.permissions.rememberMultiplePermissionsState sealed interface NaviPermissionResult { - object AllGranted : NaviPermissionResult - object Denied : NaviPermissionResult - object ShowRationale : NaviPermissionResult + data object AllGranted : NaviPermissionResult + //Here user has hard denied permission. App cannot ask permisison again.. + + data object HardDenied : NaviPermissionResult + //Here user has denied permission so app can show a UI before asking for permission again. + data object ShowRationale : NaviPermissionResult } fun handlePermissionResult( @@ -27,6 +31,7 @@ fun handlePermissionResult( permissionsResultMap.forEach { shouldShowRequestPermissionRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity, it.key) + if (shouldShowRequestPermissionRationale) { return@forEach } @@ -35,7 +40,7 @@ fun handlePermissionResult( if (shouldShowRequestPermissionRationale) { onResult(NaviPermissionResult.ShowRationale) } else { - onResult(NaviPermissionResult.Denied) + onResult(NaviPermissionResult.HardDenied) } } } diff --git a/navi-pay/src/main/kotlin/com/navi/pay/common/model/view/PermissionResult.kt b/navi-pay/src/main/kotlin/com/navi/pay/common/model/view/PermissionResult.kt index 7ed393ed66..5a5dd2f0a1 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/common/model/view/PermissionResult.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/common/model/view/PermissionResult.kt @@ -5,5 +5,5 @@ import kotlinx.parcelize.Parcelize @Parcelize data class PermissionResult( - val permissionKey: String, val isGranted: Boolean + val permissionKey: String, val isGranted: Boolean, val permissionRevoked: Boolean ) : Parcelable \ No newline at end of file diff --git a/navi-pay/src/main/kotlin/com/navi/pay/entry/NaviPayActivity.kt b/navi-pay/src/main/kotlin/com/navi/pay/entry/NaviPayActivity.kt index 4eaa20d25c..ad6acffc91 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/entry/NaviPayActivity.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/entry/NaviPayActivity.kt @@ -38,6 +38,7 @@ import com.navi.pay.entry.ui.NaviPayAccessDeniedScreen import com.navi.pay.entry.ui.NaviPayMainScreen import com.navi.pay.npcicl.NpciClService import com.navi.pay.onboarding.binding.ui.NaviPayOnboardingActivity +import com.navi.pay.permission.model.view.PermissionState import com.navi.pay.permission.utils.PermissionKeys import com.navi.pay.utils.GenericErrorCtaHandler import com.navi.pay.utils.NEEDS_RESULT @@ -184,9 +185,8 @@ class NaviPayActivity : BaseActivity() { genericErrorCtaHandler.destinationsNavigator?.navigate( NaviPayPermissionScreenDestination( permissionKey = PermissionKeys.NON_FIRST_TIME_SCREEN_PERMISSION_KEY, - titleText = R.string.intro_permission_title, - subTitleText = R.string.intro_permission_subtitle, - ctaText = R.string.allow + ctaText = R.string.go_to_setting, + permissionState = PermissionState.HARD_DENIED ) ) } diff --git a/navi-pay/src/main/kotlin/com/navi/pay/entry/ui/NaviPayMainScreen.kt b/navi-pay/src/main/kotlin/com/navi/pay/entry/ui/NaviPayMainScreen.kt index 67bf5b9bf8..830b044dc7 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/entry/ui/NaviPayMainScreen.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/entry/ui/NaviPayMainScreen.kt @@ -10,9 +10,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.snapshotFlow import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.rememberNavController +import androidx.navigation.plusAssign import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi +import com.google.accompanist.navigation.material.ModalBottomSheetLayout import com.navi.base.utils.orFalse import com.navi.pay.NavGraphs import com.navi.pay.common.model.view.ErrorVisibilityEvent @@ -22,6 +25,7 @@ import com.navi.pay.common.ui.NaviPayModalBottomSheetLayout import com.navi.pay.common.utils.ErrorEventHandler import com.navi.pay.entry.NaviPayActivity import com.navi.pay.utils.GenericErrorCtaHandler +import com.navi.pay.utils.rememberBottomSheetNavigator import com.ramcosta.composedestinations.DestinationsNavHost import com.ramcosta.composedestinations.animations.defaults.RootNavGraphDefaultAnimations import com.ramcosta.composedestinations.animations.rememberAnimatedNavHostEngine @@ -81,46 +85,57 @@ fun NaviPayMainScreen( } } - NaviPayModalBottomSheetLayout( - sheetState = state, - sheetContent = { - GenericErrorBottomSheetContent(errorEvent, onErrorCtaClick) - } - ) { - naviPayActivity.navController = rememberNavController() - DestinationsNavHost( - startRoute = customerStatusRoute ?: NavGraphs.root.startRoute, - navGraph = NavGraphs.root, - engine = rememberAnimatedNavHostEngine(rootDefaultAnimations = RootNavGraphDefaultAnimations( - enterTransition = { - slideIntoContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Companion.Left, - animationSpec = tween(400) - ) - }, - exitTransition = { - slideOutOfContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Companion.Left, - animationSpec = tween(400) - ) - }, - popEnterTransition = { - slideIntoContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Companion.Right, - animationSpec = tween(400) - ) - }, - popExitTransition = { - slideOutOfContainer( - towards = AnimatedContentTransitionScope.SlideDirection.Companion.Right, - animationSpec = tween(400) + val bottomSheetNavigator = rememberBottomSheetNavigator() + val navController = rememberNavController().apply { + this.navigatorProvider += bottomSheetNavigator + } + + ModalBottomSheetLayout(bottomSheetNavigator = bottomSheetNavigator, + content = { + NaviPayModalBottomSheetLayout( + sheetState = state, + sheetContent = { + GenericErrorBottomSheetContent( + errorEvent = errorEvent, + onErrorCtaClick = onErrorCtaClick ) } - )), - navController = naviPayActivity.navController, - dependenciesContainerBuilder = { - dependency(naviPayActivity) - genericErrorCtaHandler.destinationsNavigator = this.destinationsNavigator - }) - } + ) { + naviPayActivity.navController = navController + DestinationsNavHost( + startRoute = customerStatusRoute ?: NavGraphs.root.startRoute, + navGraph = NavGraphs.root, + engine = rememberAnimatedNavHostEngine(rootDefaultAnimations = RootNavGraphDefaultAnimations( + enterTransition = { + slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Companion.Left, + animationSpec = tween(400) + ) + }, + exitTransition = { + slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Companion.Left, + animationSpec = tween(400) + ) + }, + popEnterTransition = { + slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Companion.Right, + animationSpec = tween(400) + ) + }, + popExitTransition = { + slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Companion.Right, + animationSpec = tween(400) + ) + } + )), + navController = naviPayActivity.navController, + dependenciesContainerBuilder = { + dependency(naviPayActivity) + genericErrorCtaHandler.destinationsNavigator = this.destinationsNavigator + }) + } + }) } \ No newline at end of file diff --git a/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/ui/QrScannerScreen.kt b/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/ui/QrScannerScreen.kt index a6532de6aa..a004909831 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/ui/QrScannerScreen.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/ui/QrScannerScreen.kt @@ -103,6 +103,7 @@ import com.navi.pay.management.moneytransfer.scanpay.model.view.QrData import com.navi.pay.management.moneytransfer.scanpay.model.view.QrScanState import com.navi.pay.management.moneytransfer.scanpay.model.view.UriType import com.navi.pay.management.moneytransfer.scanpay.viewmodel.QrScannerViewModel +import com.navi.pay.permission.model.view.PermissionState import com.navi.pay.permission.utils.PermissionKeys import com.navi.pay.permission.utils.PermissionUtils import com.navi.pay.utils.DEFAULT_INITIATION_MODE_QR_MANDATE @@ -246,11 +247,14 @@ fun QrScannerScreen( resultRecipient.onNavResult { result -> when (result) { - is NavResult.Canceled -> navigator.navigateUp() + is NavResult.Canceled -> naviPayActivity.finish() is NavResult.Value -> { - if (result.value.permissionKey == PermissionKeys.QR_SCAN_PERMISSION_KEY && !result.value.isGranted) { + if (result.value.permissionKey == PermissionKeys.QR_SCAN_PERMISSION_KEY && !result.value.isGranted && !result.value.permissionRevoked) { onBackClick() } + if (result.value.permissionKey == PermissionKeys.QR_SCAN_PERMISSION_KEY && result.value.permissionRevoked) { + qrScannerViewModel.updateShowPopUpPermission(true) + } } } } @@ -258,6 +262,7 @@ fun QrScannerScreen( val qrScanResult by qrScannerViewModel.qrScanResult.collectAsStateWithLifecycle() val isTorchEnabled by qrScannerViewModel.isTorchEnabled.collectAsStateWithLifecycle() val bottomSheetStateHolder by qrScannerViewModel.bottomSheetStateHolder.collectAsStateWithLifecycle() + val showPopUpPermission by qrScannerViewModel.showPopUpPermission.collectAsStateWithLifecycle() val cameraPermissionsState = rememberMultiplePermissions( permissions = PermissionUtils.getPermissionListFromPermissionKey(permissionKey = PermissionKeys.QR_SCAN_PERMISSION_KEY) @@ -268,14 +273,32 @@ fun QrScannerScreen( // Camera screen will be shown on recomposition } - NaviPermissionResult.Denied, NaviPermissionResult.ShowRationale -> { - naviPayAnalytics.onCameraPermissionDenied(showRationale = it is NaviPermissionResult.ShowRationale) + NaviPermissionResult.HardDenied -> { - qrScannerViewModel.showPermissionPopup = false - if (!qrScannerViewModel.isPermissionScreenLaunched) { - qrScannerViewModel.isPermissionScreenLaunched = true - navigator.navigate(NaviPayPermissionScreenDestination(permissionKey = PermissionKeys.QR_SCAN_PERMISSION_KEY)) - } + naviPayAnalytics.onCameraPermissionDenied(showRationale = false) + qrScannerViewModel.updateShowPopUpPermission(false) + navigator.navigate( + NaviPayPermissionScreenDestination( + permissionKey = PermissionKeys.QR_SCAN_PERMISSION_KEY, + ctaText = R.string.go_to_setting, + permissionState = PermissionState.HARD_DENIED + ) + ) + } + + NaviPermissionResult.ShowRationale -> { + naviPayAnalytics.onCameraPermissionDenied(showRationale = true) + + qrScannerViewModel.updateShowPopUpPermission(false) + + navigator.navigate( + NaviPayPermissionScreenDestination( + permissionKey = PermissionKeys.QR_SCAN_PERMISSION_KEY, + titleText = R.string.allow_camera_permission, + ctaText = R.string.allow, + permissionState = PermissionState.SHOW_RATIONALE + ) + ) } } } @@ -391,7 +414,9 @@ fun QrScannerScreen( onScanSuccess = qrScannerViewModel::processQrContent, barcodeScanner = barcodeScanner, ) - } else if (qrScannerViewModel.showPermissionPopup) { + } + + if (showPopUpPermission) { LaunchedEffect(Unit) { cameraPermissionsState.launchMultiplePermissionRequest() } diff --git a/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/viewmodel/QrScannerViewModel.kt b/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/viewmodel/QrScannerViewModel.kt index f208fc4ee0..a0b5f399c8 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/viewmodel/QrScannerViewModel.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/management/moneytransfer/scanpay/viewmodel/QrScannerViewModel.kt @@ -74,11 +74,13 @@ class QrScannerViewModel @Inject constructor( var isQrCodeProcessing: AtomicBoolean = AtomicBoolean(false) - var isPermissionScreenLaunched = false - var showPermissionPopup = true var lightSensorValue = 0f - val isUserOnboarded = naviPayCustomerStatusHandler.getCustomerStatusOnMainThread() == NaviPayCustomerStatus.LINKED_VPA + private val _showPopUpPermission = MutableStateFlow(true) + val showPopUpPermission = _showPopUpPermission.asStateFlow() + + val isUserOnboarded = + naviPayCustomerStatusHandler.getCustomerStatusOnMainThread() == NaviPayCustomerStatus.LINKED_VPA fun processQrContent(qrContent: String, sendQrFromGallery: Boolean = false) { if (isQrCodeProcessing.get()) return @@ -132,6 +134,10 @@ class QrScannerViewModel @Inject constructor( } } + fun updateShowPopUpPermission(showPopUpPermission: Boolean) { + _showPopUpPermission.update { showPopUpPermission } + } + private fun parseUpiQrCode(qrContent: String, sendQrFromGallery: Boolean) { when (val upiResult = upiUriParser.parseForUpiUri(uri = qrContent)) { is UpiUriResult.Success -> { diff --git a/navi-pay/src/main/kotlin/com/navi/pay/management/paytocontacts/ui/PayToContactsScreen.kt b/navi-pay/src/main/kotlin/com/navi/pay/management/paytocontacts/ui/PayToContactsScreen.kt index 44adea5d47..4dcd5c2fa1 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/management/paytocontacts/ui/PayToContactsScreen.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/management/paytocontacts/ui/PayToContactsScreen.kt @@ -57,6 +57,7 @@ import com.navi.pay.management.paytocontacts.model.view.FrequentTransactionEntit import com.navi.pay.management.paytocontacts.model.view.PhoneContactEntity import com.navi.pay.management.paytocontacts.viewmodel.PayToContactsUIState import com.navi.pay.management.paytocontacts.viewmodel.PayToContactsViewModel +import com.navi.pay.permission.model.view.PermissionState import com.navi.pay.permission.utils.PermissionKeys import com.navi.pay.permission.utils.PermissionUtils import com.navi.pay.utils.NAVI_PAY_LOADER @@ -97,6 +98,7 @@ fun PayToContactsScreen( val uiState by payToContactsViewModel.uiState.collectAsStateWithLifecycle() val isNewContactVisible by payToContactsViewModel.isNewContactVisible.collectAsStateWithLifecycle() val isTrailingIconEnabled by payToContactsViewModel.isTrailingIconEnabled.collectAsStateWithLifecycle() + val showPopUpPermission by payToContactsViewModel.showPopUpPermission.collectAsStateWithLifecycle() val frequentTransactions by payToContactsViewModel.frequentTransactions.collectAsStateWithLifecycle() val onBackClick = { @@ -160,12 +162,12 @@ fun PayToContactsScreen( resultRecipient.onNavResult { result -> when (result) { is NavResult.Canceled -> { - navigator.navigateUp() + naviPayActivity.finish() } is NavResult.Value -> { - if (!result.value.isGranted) { - navigator.navigateUp() + if (result.value.permissionKey == PermissionKeys.QR_SCAN_PERMISSION_KEY && result.value.permissionRevoked) { + payToContactsViewModel.updateShowPopUpPermission(true) } } } @@ -179,12 +181,27 @@ fun PayToContactsScreen( // Camera screen will be shown on recomposition } - NaviPermissionResult.Denied, NaviPermissionResult.ShowRationale -> { - payToContactsViewModel.showPermissionPopup = false - if (!payToContactsViewModel.isPermissionScreenLaunched) { - payToContactsViewModel.isPermissionScreenLaunched = true - navigator.navigate(NaviPayPermissionScreenDestination(permissionKey = PermissionKeys.CONTACT_PERMISSION_KEY)) - } + NaviPermissionResult.HardDenied -> { + payToContactsViewModel.updateShowPopUpPermission(false) + navigator.navigate( + NaviPayPermissionScreenDestination( + permissionKey = PermissionKeys.CONTACT_PERMISSION_KEY, + ctaText = R.string.go_to_setting, + permissionState = PermissionState.HARD_DENIED + ) + ) + + } + + NaviPermissionResult.ShowRationale -> { + payToContactsViewModel.updateShowPopUpPermission(false) + navigator.navigate( + NaviPayPermissionScreenDestination( + permissionKey = PermissionKeys.CONTACT_PERMISSION_KEY, + ctaText = R.string.allow, + permissionState = PermissionState.SHOW_RATIONALE + ) + ) } } } @@ -193,12 +210,21 @@ fun PayToContactsScreen( LaunchedEffect(Unit) { payToContactsViewModel.fetchContacts() } - when (uiState) { - PayToContactsUIState.Loading -> LoadingScreen( - lottieFileName = NAVI_PAY_LOADER, - titleId = null, - showFooter = false - ) + } else { + // Update UI state to loaded so that loading screen is not shown for permission not granted case + payToContactsViewModel.updateUiState(uiState = PayToContactsUIState.Loaded) + if (showPopUpPermission) { + LaunchedEffect(Unit) { + readContactsPermissionsState.launchMultiplePermissionRequest() + } + } + } + when (uiState) { + PayToContactsUIState.Loading -> LoadingScreen( + lottieFileName = NAVI_PAY_LOADER, + titleId = null, + showFooter = false + ) PayToContactsUIState.Loaded -> RenderPayToContactsScreen( naviPayActivity = naviPayActivity, @@ -235,15 +261,8 @@ fun PayToContactsScreen( } ) } - } else { - if (payToContactsViewModel.showPermissionPopup) { - LaunchedEffect(Unit) { - readContactsPermissionsState.launchMultiplePermissionRequest() - } - } } -} @OptIn(ExperimentalMaterialApi::class) @Composable diff --git a/navi-pay/src/main/kotlin/com/navi/pay/management/paytocontacts/viewmodel/PayToContactsViewModel.kt b/navi-pay/src/main/kotlin/com/navi/pay/management/paytocontacts/viewmodel/PayToContactsViewModel.kt index 49dc7c5678..304a8f3aa5 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/management/paytocontacts/viewmodel/PayToContactsViewModel.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/management/paytocontacts/viewmodel/PayToContactsViewModel.kt @@ -90,6 +90,9 @@ class PayToContactsViewModel @Inject constructor( initialValue = false ) + private val _showPopUpPermission = MutableStateFlow(true) + val showPopUpPermission = _showPopUpPermission.asStateFlow() + init { updateNaviPayDefaultConfig() } @@ -105,7 +108,7 @@ class PayToContactsViewModel @Inject constructor( } var isPermissionScreenLaunched = false - var showPermissionPopup = true + val filteredContactList = combine(_searchQuery, _allContactList) { searchQuery, allContactList -> @@ -142,7 +145,7 @@ class PayToContactsViewModel @Inject constructor( } } - private fun updateUiState(uiState: PayToContactsUIState) { + fun updateUiState(uiState: PayToContactsUIState) { _uiState.update { uiState } } @@ -230,6 +233,10 @@ class PayToContactsViewModel @Inject constructor( } } + fun updateShowPopUpPermission(showPopUpPermission: Boolean) { + _showPopUpPermission.update { showPopUpPermission } + } + } enum class PayToContactsUIState { diff --git a/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/ui/NaviPayOnboardingActivity.kt b/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/ui/NaviPayOnboardingActivity.kt index d03cd3a1f5..5fdfe098e1 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/ui/NaviPayOnboardingActivity.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/ui/NaviPayOnboardingActivity.kt @@ -6,15 +6,12 @@ import android.os.Build import android.os.Bundle import android.util.Log import androidx.activity.compose.setContent -import androidx.navigation.compose.rememberNavController +import androidx.navigation.NavHostController import com.navi.common.model.ModuleNameV2 import com.navi.common.ui.activity.BaseActivity import com.navi.pay.analytics.NaviPayAnalytics import com.navi.pay.common.theme.NaviPayMaterialTheme -import com.navi.pay.onboarding.binding.OnboardingNavGraph import com.navi.pay.utils.NAVI_PAY_DEBUG_LOG -import com.ramcosta.composedestinations.DestinationsNavHost -import com.ramcosta.composedestinations.navigation.dependency import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -24,6 +21,8 @@ class NaviPayOnboardingActivity : BaseActivity() { override val moduleName: ModuleNameV2 get() = ModuleNameV2.NAVIPAY + lateinit var navController: NavHostController + @SuppressLint("SourceLockedOrientationActivity") override fun onCreate(savedInstanceState: Bundle?) { if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O) { @@ -32,12 +31,7 @@ class NaviPayOnboardingActivity : BaseActivity() { super.onCreate(savedInstanceState) setContent { NaviPayMaterialTheme { - DestinationsNavHost( - navGraph = OnboardingNavGraph.root, - navController = rememberNavController(), - dependenciesContainerBuilder = { - dependency(this@NaviPayOnboardingActivity) - }) + NaviPayOnboardingMainScreen(naviPayOnboardingActivity = this) } } Log.d(NAVI_PAY_DEBUG_LOG, "NaviPayOnboardingActivity") diff --git a/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/ui/NaviPayOnboardingMainScreen.kt b/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/ui/NaviPayOnboardingMainScreen.kt new file mode 100644 index 0000000000..09e0d930e9 --- /dev/null +++ b/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/ui/NaviPayOnboardingMainScreen.kt @@ -0,0 +1,54 @@ +package com.navi.pay.onboarding.binding.ui + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.compose.rememberNavController +import androidx.navigation.plusAssign +import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi +import com.google.accompanist.navigation.material.ModalBottomSheetLayout +import com.navi.pay.common.ui.NaviPayModalBottomSheetLayout +import com.navi.pay.common.utils.ErrorEventHandler +import com.navi.pay.onboarding.binding.OnboardingNavGraph +import com.navi.pay.utils.rememberBottomSheetNavigator +import com.ramcosta.composedestinations.DestinationsNavHost +import com.ramcosta.composedestinations.animations.rememberAnimatedNavHostEngine +import com.ramcosta.composedestinations.navigation.dependency + +@OptIn( + ExperimentalMaterialApi::class, ExperimentalMaterialNavigationApi::class, + ExperimentalAnimationApi::class +) +@Composable +fun NaviPayOnboardingMainScreen( + naviPayOnboardingActivity: NaviPayOnboardingActivity +) { + + val bottomSheetNavigator = rememberBottomSheetNavigator() + val navController = rememberNavController().apply { + this.navigatorProvider += bottomSheetNavigator + } + + ModalBottomSheetLayout(bottomSheetNavigator = bottomSheetNavigator, + content = { + NaviPayModalBottomSheetLayout( + sheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden), + sheetContent = { + + } + ) { + naviPayOnboardingActivity.navController = navController + DestinationsNavHost( + navGraph = OnboardingNavGraph.root, + engine = rememberAnimatedNavHostEngine(), + navController = naviPayOnboardingActivity.navController, + dependenciesContainerBuilder = { + dependency(naviPayOnboardingActivity) + }) + } + }) +} diff --git a/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/ui/NaviPayOnboardingScreen.kt b/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/ui/NaviPayOnboardingScreen.kt index 6bba3ed6c6..60c4e97dcc 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/ui/NaviPayOnboardingScreen.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/ui/NaviPayOnboardingScreen.kt @@ -42,6 +42,7 @@ import com.navi.pay.onboarding.binding.model.view.NaviPayOnboardingBottomSheetTy import com.navi.pay.onboarding.binding.model.view.OnboardingDeviceData import com.navi.pay.onboarding.binding.model.view.SmsVerificationState import com.navi.pay.onboarding.binding.viewmodel.NaviPayOnboardingViewModel +import com.navi.pay.permission.model.view.PermissionState import com.navi.pay.permission.utils.PermissionKeys import com.navi.pay.permission.utils.PermissionUtils import com.navi.pay.utils.CUSTOMER_STATUS_AFTER_ONBOARDING @@ -87,20 +88,28 @@ fun NaviPayOnboardingScreen( } } - NaviPermissionResult.Denied, NaviPermissionResult.ShowRationale -> { + NaviPermissionResult.HardDenied -> { naviPayAnalytics.onPermissionDenied() - naviPayOnboardingViewModel.showPermissionPopup = false - if (!naviPayOnboardingViewModel.isPermissionScreenLaunched) { - naviPayOnboardingViewModel.isPermissionScreenLaunched = true navigator.navigate( NaviPayPermissionScreenDestination( permissionKey = PermissionKeys.FIRST_TIME_SCREEN_PERMISSION_KEY, - titleText = R.string.intro_permission_title, - subTitleText = R.string.intro_permission_subtitle, - ctaText = R.string.allow + ctaText = R.string.go_to_setting, + permissionState = PermissionState.HARD_DENIED ) ) - } + + } + + NaviPermissionResult.ShowRationale -> { + naviPayAnalytics.onPermissionDenied() + navigator.navigate( + NaviPayPermissionScreenDestination( + permissionKey = PermissionKeys.FIRST_TIME_SCREEN_PERMISSION_KEY, + ctaText = R.string.allow, + permissionState = PermissionState.SHOW_RATIONALE + ) + ) + } } } @@ -163,6 +172,12 @@ fun NaviPayOnboardingScreen( naviPayAnalytics = naviPayAnalytics, isFirstTimeUserExperience = isFirstTimeUserExperience ) + } + if (result.value.permissionKey == PermissionKeys.FIRST_TIME_SCREEN_PERMISSION_KEY && result.value.permissionRevoked) { + coroutineScope.launch { + naviPayOnboardingViewModel.updateMultiplePermissionScreenLaunched() + } + } else { naviPayAnalytics.onPermissionDenied() finishWithoutResult(naviPayOnboardingActivity) diff --git a/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/viewmodel/NaviPayOnboardingViewModel.kt b/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/viewmodel/NaviPayOnboardingViewModel.kt index 3d42b88854..506028d376 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/viewmodel/NaviPayOnboardingViewModel.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/onboarding/binding/viewmodel/NaviPayOnboardingViewModel.kt @@ -154,6 +154,7 @@ class NaviPayOnboardingViewModel @Inject constructor( private val SMS_SENT_CHECK_TIMEOUT = 45 * 1000L // 45 seconds var showPermissionPopup = true + var isPermissionScreenLaunched = false private val naviPayPoller: NaviPayPoller by lazy { @@ -855,4 +856,9 @@ class NaviPayOnboardingViewModel @Inject constructor( updateBottomSheetUIState(showBottomSheet = false) } } + + + suspend fun updateMultiplePermissionScreenLaunched() { + _onboardingAction.emit(NaviPayOnboardingAction.StartOnboarding) + } } \ No newline at end of file diff --git a/navi-pay/src/main/kotlin/com/navi/pay/permission/model/view/PermissionData.kt b/navi-pay/src/main/kotlin/com/navi/pay/permission/model/view/PermissionData.kt index a001e2f6b5..7baaefd4f8 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/permission/model/view/PermissionData.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/permission/model/view/PermissionData.kt @@ -14,3 +14,8 @@ data class PermissionData( val qualifierList: List, var state: Int? = null ) + +enum class PermissionState { + HARD_DENIED, + SHOW_RATIONALE, +} diff --git a/navi-pay/src/main/kotlin/com/navi/pay/permission/ui/PermissionScreen.kt b/navi-pay/src/main/kotlin/com/navi/pay/permission/ui/PermissionScreen.kt index 59e53d43ea..fd7fe729dd 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/permission/ui/PermissionScreen.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/permission/ui/PermissionScreen.kt @@ -7,8 +7,6 @@ package com.navi.pay.permission.ui -import com.navi.common.R as CommonR -import com.navi.design.R as DesignR import android.app.Activity import android.content.Intent import android.content.pm.PackageManager @@ -23,18 +21,15 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.Text @@ -47,16 +42,12 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.layoutId import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension import androidx.core.app.ActivityCompat import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.Lifecycle @@ -68,26 +59,25 @@ import com.navi.pay.R import com.navi.pay.analytics.NaviPayAnalytics import com.navi.pay.common.model.view.PermissionResult import com.navi.pay.common.theme.color.NaviPayColor -import com.navi.pay.common.ui.BottomSheetContentWithIconHeaderDescButton -import com.navi.pay.common.ui.NaviPayCard -import com.navi.pay.common.ui.NaviPayHeader -import com.navi.pay.common.ui.NaviPayModalBottomSheetLayout import com.navi.pay.common.ui.ThemeRoundedButton import com.navi.pay.permission.model.view.PermissionData +import com.navi.pay.permission.model.view.PermissionState import com.navi.pay.permission.utils.PermissionKeys import com.navi.pay.permission.utils.PermissionUtils.getPermissionListFromPermissionKey import com.navi.pay.permission.viewmodel.PermissionViewModel import com.navi.pay.utils.OnLifecycleEvent -import com.navi.pay.utils.clickableDebounce import com.navi.pay.utils.hasPermissions import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.result.ResultBackNavigator +import com.ramcosta.composedestinations.spec.DestinationStyleBottomSheet import kotlinx.coroutines.delay import kotlinx.coroutines.launch -@Destination +@Destination(style = DestinationStyleBottomSheet::class) @Composable fun NaviPayPermissionScreen( + navigator: DestinationsNavigator, naviPayActivity: Activity, resultNavigator: ResultBackNavigator, permissionKey: String, @@ -95,6 +85,7 @@ fun NaviPayPermissionScreen( subTitleText: Int? = null, ctaText: Int = R.string.allow, showPermissionDeniedView: Boolean = true, + permissionState: PermissionState? = null, permissionViewModel: PermissionViewModel = hiltViewModel(), naviPayAnalytics: NaviPayAnalytics.NaviPayPermission = NaviPayAnalytics.INSTANCE.NaviPayPermission() @@ -111,7 +102,8 @@ fun NaviPayPermissionScreen( permissionKey = permissionKey, titleText = titleText, subTitleText = subTitleText, - ctaText = ctaText + ctaText = ctaText, + permissionState = permissionState ) } @@ -126,6 +118,7 @@ fun RenderNaviPayPermissionScreen( titleText: Int, subTitleText: Int?, ctaText: Int, + permissionState: PermissionState? ) { val showPermissionDeniedViewState by @@ -143,7 +136,8 @@ fun RenderNaviPayPermissionScreen( resultNavigator.navigateBack( result = PermissionResult( permissionKey = permissionKey, - isGranted = false + isGranted = false, + permissionRevoked = false ) ) } @@ -159,7 +153,12 @@ fun RenderNaviPayPermissionScreen( val permissions = permissionsResultMap.map { it.key } if (allPermissionsGranted) { naviPayAnalytics.onAllPermissionGranted(permissions) - resultNavigator.navigateBack(result = PermissionResult(permissionKey, true)) + resultNavigator.navigateBack( + result = PermissionResult( + permissionKey, true, + permissionRevoked = false + ) + ) } else { permissionDetailData.forEach { it.qualifierList.forEach { qualifier -> @@ -206,7 +205,8 @@ fun RenderNaviPayPermissionScreen( resultNavigator.navigateBack( result = PermissionResult( permissionKey, - true + isGranted = true, + permissionRevoked = false ) ) } @@ -254,134 +254,79 @@ fun RenderNaviPayPermissionScreen( } } - NaviPayModalBottomSheetLayout( - sheetState = state, - sheetContent = { - BottomSheetContentWithIconHeaderDescButton( - iconId = DesignR.drawable.ic_info_icon_black, - headerTextId = R.string.know_more_navi_pay, - descriptionTextId = R.string.permission_settings_know_more, - buttonTextId = R.string.np_okay_got_it, - onButtonClicked = { permissionViewModel.updateBottomSheetUIState(showBottomSheet = false) } + Column( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min), + ) { + TitleText( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 16.dp), + textResId = titleText, + permissionKey = permissionKey + ) + if (subTitleText != null) { + Spacer(modifier = Modifier.height(4.dp)) + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(id = subTitleText), + fontFamily = ttComposeFontFamily, + fontSize = 12.sp, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + color = NaviPayColor.textTertiary ) } - ) { - Column( - modifier = Modifier - .fillMaxSize() - .background(color = Color.White), - ) { - NaviPayHeader( - title = stringResource(id = R.string.app_permission_header), - navigationIcon = R.drawable.ic_np_close_black_16, - onNavigationIconClick = { - onBackPress() - }, - onActionClick = { permissionViewModel.updateBottomSheetUIState(showBottomSheet = true) }, - modifier = Modifier.fillMaxWidth() - ) - Spacer(modifier = Modifier.height(16.dp)) - TitleText( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - textResId = titleText, - permissionKey = permissionKey - ) - if (subTitleText != null) { - Spacer(modifier = Modifier.height(4.dp)) - Text( - modifier = Modifier.padding(horizontal = 16.dp), - text = stringResource(id = subTitleText), - fontFamily = ttComposeFontFamily, - fontSize = 12.sp, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - color = NaviPayColor.textTertiary - ) - } - if (showPermissionDeniedViewState) { - Spacer(modifier = Modifier.height(16.dp)) - PermissionsDeniedView( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - onClick = { - launchPermissionSettingsScreen( - naviPayActivity = naviPayActivity, - naviPayAnalytics = naviPayAnalytics - ) - } - ) - } - - Spacer(modifier = Modifier.height(16.dp)) + if (permissionState != null) { PermissionTilesView( permissionViewModel, - modifier = Modifier.padding(horizontal = 16.dp) + modifier = Modifier.padding(horizontal = 16.dp), + permissionProvided = permissionState ) + } - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(32.dp)) - Row( + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + ThemeRoundedButton( + onClick = { + val permissions: List = + permissionDetailData.flatMap { it.qualifierList } + naviPayAnalytics.onPermissionNextClicked() + if (naviPayActivity.hasPermissions(permissions = permissions)) { + resultNavigator.navigateBack( + result = PermissionResult( + permissionKey, + isGranted = true, + permissionRevoked = false + ) + ) + } + if (ctaText == R.string.allow) { + resultNavigator.navigateBack( + result = PermissionResult( + permissionKey, + isGranted = false, + permissionRevoked = true + ) + ) + } else if (permissionViewModel.shouldGoToSettings()) { + launchPermissionSettingsScreen(naviPayActivity, naviPayAnalytics) + } else { + launcher.launch(permissions.toTypedArray()) + } + }, + text = stringResource(id = ctaText), modifier = Modifier .fillMaxWidth() - .padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Image( - painter = painterResource(id = CommonR.drawable.ic_security_checked_green_svg), - contentDescription = null - ) - Spacer(modifier = Modifier.width(4.dp)) - Text( - text = stringResource(id = R.string.your_data_is_secure), - fontSize = 8.sp, - fontFamily = ttComposeFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - color = NaviPayColor.textTertiary - ) - } - - Spacer(modifier = Modifier.weight(1f)) - - Column( - modifier = Modifier - .background( - color = Color.White, - shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) - ), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Spacer(modifier = Modifier.height(18.dp)) - ThemeRoundedButton( - onClick = { - val permissions: List = - permissionDetailData.flatMap { it.qualifierList } - naviPayAnalytics.onPermissionNextClicked() - if (naviPayActivity.hasPermissions(permissions = permissions)) { - resultNavigator.navigateBack( - result = PermissionResult( - permissionKey, - true - ) - ) - } else if (permissionViewModel.shouldGoToSettings()) { - launchPermissionSettingsScreen(naviPayActivity, naviPayAnalytics) - } else { - launcher.launch(permissions.toTypedArray()) - } - }, - text = stringResource(id = ctaText), - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp) - ) - Spacer(modifier = Modifier.height(32.dp)) - } + .padding(start = 16.dp, end = 16.dp) + ) + Spacer(modifier = Modifier.height(24.dp)) } } } @@ -433,18 +378,24 @@ fun TitleText(modifier: Modifier, textResId: Int, permissionKey: String) { @Composable fun PermissionTilesView( permissionViewModel: PermissionViewModel, - modifier: Modifier + modifier: Modifier, + permissionProvided: PermissionState ) { - NaviPayCard(modifier = modifier) { - val permissionState = permissionViewModel.permissionState - LazyColumn( - modifier = Modifier.padding(vertical = 20.dp, horizontal = 12.dp), - verticalArrangement = Arrangement.spacedBy(24.dp) - ) { - items(permissionState) { PermissionTileView(permissionData = it) } + val permissionState = permissionViewModel.permissionState + Column( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(vertical = 12.dp, horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + permissionState.forEach { + PermissionTileView( + permissionData = it, + permissionState = permissionProvided + ) } } - } fun launchPermissionSettingsScreen( @@ -460,71 +411,11 @@ fun launchPermissionSettingsScreen( naviPayAnalytics.onAppSettingsScreenLaunched() } -@Composable -fun PermissionsDeniedView(modifier: Modifier, onClick: (() -> Unit)) { - Box( - modifier = modifier - .clickableDebounce { onClick.invoke() } - .then( - Modifier.background( - color = NaviPayColor.bgNonEditable, shape = RoundedCornerShape(4.dp) - ) - ), - ) { - ConstraintLayout( - modifier = Modifier - .padding(bottom = 16.dp) - .fillMaxWidth() - ) { - val (title, subtitle, icon) = createRefs() - Text( - text = stringResource(id = R.string.permission_settings_desc), - fontSize = 12.sp, - fontFamily = ttComposeFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR), - color = NaviPayColor.textPrimary, - modifier = Modifier - .constrainAs(title) { - top.linkTo(parent.top, 16.dp) - start.linkTo(parent.start, 16.dp) - end.linkTo(icon.start, 24.dp) - width = Dimension.fillToConstraints - } - .layoutId(title) - ) - Text( - text = stringResource(id = R.string.permission_settings_path), - fontSize = 10.sp, - fontFamily = ttComposeFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR), - color = NaviPayColor.textTertiary, - modifier = Modifier - .constrainAs(subtitle) { - top.linkTo(title.bottom, 8.dp) - start.linkTo(parent.start, 16.dp) - end.linkTo(icon.start, 24.dp) - width = Dimension.fillToConstraints - } - .layoutId(subtitle) - ) - Image( - painter = painterResource(id = R.drawable.ic_green_arrow_right_white_circle_bg), - contentDescription = null, - modifier = Modifier - .constrainAs(icon) { - end.linkTo(parent.end, 16.dp) - top.linkTo(parent.top, 16.dp) - } - .layoutId(icon) - ) - } - } -} @Composable -fun PermissionTileView(permissionData: PermissionData) { +fun PermissionTileView(permissionData: PermissionData, permissionState: PermissionState) { Row( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.Top ) { @@ -542,38 +433,56 @@ fun PermissionTileView(permissionData: PermissionData) { ) } - Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) { - Text( - text = stringResource(id = permissionData.titleId), - fontSize = 14.sp, - fontFamily = ttComposeFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR), - color = NaviPayColor.textPrimary - ) - Text( - text = stringResource(id = permissionData.descriptionId), - fontSize = 12.sp, - fontFamily = ttComposeFontFamily, - fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), - color = NaviPayColor.textTertiary - ) - } + if (permissionState == PermissionState.SHOW_RATIONALE) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), - Spacer(modifier = Modifier.width(16.dp)) - - when (permissionData.state) { - PackageManager.PERMISSION_GRANTED, PackageManager.PERMISSION_DENIED -> { - Image( - painter = painterResource(id = if (permissionData.state == PackageManager.PERMISSION_GRANTED) R.drawable.ic_success_green else R.drawable.ic_circular_red_exclamation), - contentDescription = null, - modifier = Modifier - .size(20.dp) - .align(Alignment.CenterVertically) + ) { + Text( + text = stringResource(id = permissionData.titleId), + fontSize = 16.sp, + fontFamily = ttComposeFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR), + color = NaviPayColor.textPrimary + ) + Text( + text = stringResource(id = permissionData.descriptionId), + fontSize = 14.sp, + fontFamily = ttComposeFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + color = NaviPayColor.textTertiary ) } + } - else -> { - Spacer(modifier = Modifier.width(20.dp)) + if (permissionState == PermissionState.HARD_DENIED) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = stringResource(id = permissionData.titleId), + fontSize = 16.sp, + fontFamily = ttComposeFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR), + color = NaviPayColor.textPrimary + ) + Text( + text = stringResource(id = permissionData.descriptionId), + fontSize = 14.sp, + fontFamily = ttComposeFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + color = NaviPayColor.textTertiary + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = stringResource(id = R.string.permission_settings_path), + fontSize = 12.sp, + fontFamily = ttComposeFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR), + color = NaviPayColor.textTertiary + ) } } } diff --git a/navi-pay/src/main/kotlin/com/navi/pay/permission/utils/PermissionUtils.kt b/navi-pay/src/main/kotlin/com/navi/pay/permission/utils/PermissionUtils.kt index dfe79165cb..c6eae6ca03 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/permission/utils/PermissionUtils.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/permission/utils/PermissionUtils.kt @@ -89,7 +89,7 @@ object PermissionKeyData { val CONTACT_PERMISSION_DATA = listOf( PermissionData( - iconId = R.drawable.ic_contact_permission, + iconId = R.drawable.ic_permission_contact, titleId = R.string.contact, descriptionId = R.string.contact_permission_description, qualifierList = listOf(Manifest.permission.READ_CONTACTS) diff --git a/navi-pay/src/main/kotlin/com/navi/pay/utils/NaviPayExt.kt b/navi-pay/src/main/kotlin/com/navi/pay/utils/NaviPayExt.kt index af45f72d1d..f823c28073 100644 --- a/navi-pay/src/main/kotlin/com/navi/pay/utils/NaviPayExt.kt +++ b/navi-pay/src/main/kotlin/com/navi/pay/utils/NaviPayExt.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.view.View import android.view.ViewTreeObserver import android.view.inputmethod.InputMethodManager +import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable @@ -31,6 +32,9 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetState +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.SwipeableDefaults +import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue @@ -53,6 +57,8 @@ import androidx.core.content.getSystemService import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import coil.request.ImageRequest +import com.google.accompanist.navigation.material.BottomSheetNavigator +import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi import com.google.zxing.BarcodeFormat import com.google.zxing.EncodeHintType import com.google.zxing.qrcode.QRCodeWriter @@ -564,6 +570,23 @@ fun isNaviPayOriginatedIntent(uri: Uri): Boolean { fun Context.getImageRequestBuilder(data: Any?) = ImageRequest.Builder(context = this).data(data = data).allowHardware(enable = false).build() +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterialNavigationApi::class, + ExperimentalMaterialNavigationApi::class +) +@Composable +fun rememberBottomSheetNavigator( + animationSpec: AnimationSpec = SwipeableDefaults.AnimationSpec +): BottomSheetNavigator { + val sheetState = rememberModalBottomSheetState( + initialValue = ModalBottomSheetValue.Hidden, + animationSpec = animationSpec, + skipHalfExpanded = false + ) + return remember(sheetState) { + BottomSheetNavigator(sheetState = sheetState) + } +} + suspend fun Iterable.parallelMap(f: suspend (A) -> B): List = runBlocking { map { async(Dispatchers.Default) { f(it) } }.awaitAll() } diff --git a/navi-pay/src/main/res/drawable/ic_contact_permission.xml b/navi-pay/src/main/res/drawable/ic_contact_permission.xml index 230120b5ab..2afd262945 100644 --- a/navi-pay/src/main/res/drawable/ic_contact_permission.xml +++ b/navi-pay/src/main/res/drawable/ic_contact_permission.xml @@ -1,50 +1,68 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> - + android:pathData="M19.657,1.904V20.009C19.657,20.219 19.492,20.384 19.282,20.384H5.295" + android:fillColor="#E4FFED"/> - - + + + + + diff --git a/navi-pay/src/main/res/drawable/ic_permission_camera.xml b/navi-pay/src/main/res/drawable/ic_permission_camera.xml index 170e06855d..8087b7d4ed 100644 --- a/navi-pay/src/main/res/drawable/ic_permission_camera.xml +++ b/navi-pay/src/main/res/drawable/ic_permission_camera.xml @@ -1,65 +1,62 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + android:pathData="M2.5,6.733L2.528,5.717C2.532,5.454 2.635,5.202 2.815,5.013C2.996,4.824 3.241,4.713 3.5,4.701H4.444C4.714,4.704 4.973,4.81 5.169,4.998C5.364,5.187 5.483,5.444 5.5,5.717V6.733" + android:fillColor="#AFECCD"/> - + android:pathData="M21.444,10.798C23,10.798 23.5,11.488 23.5,12.549V8.485C23.5,7.424 23,6.734 21.444,6.734H20.13C19.94,6.734 19.753,6.679 19.593,6.575C19.433,6.471 19.305,6.323 19.224,6.148C18.682,4.968 18.1,3.686 17,3.686H12C11.069,3.686 10.6,4.533 9.3,6.319C9.208,6.447 9.087,6.551 8.948,6.623C8.809,6.695 8.656,6.733 8.5,6.734H2.5C1,6.734 0.5,7.424 0.5,8.485V12.549C0.5,11.488 1,10.798 2.5,10.798H21.444Z" + android:fillColor="#E4FFED"/> - + diff --git a/navi-pay/src/main/res/drawable/ic_permission_contact.xml b/navi-pay/src/main/res/drawable/ic_permission_contact.xml new file mode 100644 index 0000000000..2afd262945 --- /dev/null +++ b/navi-pay/src/main/res/drawable/ic_permission_contact.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + diff --git a/navi-pay/src/main/res/drawable/ic_permission_phone_state.xml b/navi-pay/src/main/res/drawable/ic_permission_phone_state.xml index 2c7709156f..1dbdf4801d 100644 --- a/navi-pay/src/main/res/drawable/ic_permission_phone_state.xml +++ b/navi-pay/src/main/res/drawable/ic_permission_phone_state.xml @@ -1,85 +1,43 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + android:pathData="M15.912,12.393C18.29,12.393 20.217,10.466 20.217,8.088C20.217,5.711 18.29,3.783 15.912,3.783C13.535,3.783 11.607,5.711 11.607,8.088C11.607,10.466 13.535,12.393 15.912,12.393Z" + android:fillColor="#FFF3F0"/> + - - - - - - - diff --git a/navi-pay/src/main/res/drawable/ic_permission_send_sms.xml b/navi-pay/src/main/res/drawable/ic_permission_send_sms.xml index a68bd44b52..6f4ea7814a 100644 --- a/navi-pay/src/main/res/drawable/ic_permission_send_sms.xml +++ b/navi-pay/src/main/res/drawable/ic_permission_send_sms.xml @@ -1,30 +1,40 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + diff --git a/navi-pay/src/main/res/values/strings.xml b/navi-pay/src/main/res/values/strings.xml index 8e01a20607..11580d9750 100644 --- a/navi-pay/src/main/res/values/strings.xml +++ b/navi-pay/src/main/res/values/strings.xml @@ -11,10 +11,10 @@ Simplifying payments on Navi Powered by SMS verification - SMS permission - To verify your bank accounts - SIM card permission - To verify your SIM provider + SMS + To verify your phone number for UPI payments. + Phone + To verify your SIM card with your registered mobile number. *Standard SMS charges will apply Proceed Select bank @@ -172,9 +172,10 @@ Money transfer Manage Settings - App permission + Allow app permission Allow app permissions Allow + Go to settings Hassle-free & fast payments Check your bank balance anytime, anywhere Get started @@ -208,10 +209,13 @@ You do not have any pending requests Create autopay UPI number + Allow camera permission Camera - To scan QR code - Contact - To make contact finding easier + Camera permission is mandatory + Grant camera permission to scan QR codes for merchants, family, and friends. + Required so that you can scan the image of the scanner to pay + Contacts + To allow UPI payments to your contacts. Invalid QR code The QR code you scanned is not a valid UPI QR code Complaint closed