TP-40039 | Internal permission revamp (#8739)

Co-authored-by: Shaurya Rehan <shaurya.rehan@navi.com>
Co-authored-by: rahul bhat <rahul.bhat@navi.com>
Co-authored-by: shankar yadav <shankar.yadav@navi.com>
Co-authored-by: Aditya Narayan Malik <aditya.narayan@navi.com>
This commit is contained in:
Sidharth Bamba
2023-12-12 13:46:13 +05:30
committed by GitHub
parent baff62fb35
commit 2695b01470
23 changed files with 626 additions and 488 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,3 +14,8 @@ data class PermissionData(
val qualifierList: List<String>,
var state: Int? = null
)
enum class PermissionState {
HARD_DENIED,
SHOW_RATIONALE,
}

View File

@@ -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<PermissionResult>,
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<String> =
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<String> =
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
)
}
}
}

View File

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

View File

@@ -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<Float> = SwipeableDefaults.AnimationSpec
): BottomSheetNavigator {
val sheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
animationSpec = animationSpec,
skipHalfExpanded = false
)
return remember(sheetState) {
BottomSheetNavigator(sheetState = sheetState)
}
}
suspend fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> = runBlocking {
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
}

View File

@@ -1,50 +1,68 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M20,20m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0"
android:fillColor="#F5F5F5"/>
<path
android:pathData="M29.83,27.25C29.83,29.02 28.39,30.46 26.62,30.46H12.01V7.53H26.62C28.39,7.53 29.83,8.97 29.83,10.74V27.25Z"
android:pathData="M19.657,20.017C19.657,20.227 19.492,20.392 19.282,20.392H5.67C5.46,20.392 5.295,20.227 5.295,20.017V2.279C5.295,2.069 5.46,1.904 5.67,1.904H19.282C19.492,1.904 19.657,2.069 19.657,2.279V20.009V20.017Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M28.7,8.98C29.07,8.51 29.82,8.77 29.82,9.36V27.25C29.82,29.02 28.38,30.46 26.61,30.46H12.01L28.7,8.98Z"
android:fillColor="#E3FFEC"/>
android:pathData="M19.657,1.904V20.009C19.657,20.219 19.492,20.384 19.282,20.384H5.295"
android:fillColor="#E4FFED"/>
<path
android:strokeWidth="1"
android:pathData="M10.17,13.77H13.68"
android:pathData="M11.58,20.392H5.67C5.46,20.392 5.295,20.227 5.295,20.017V2.279C5.295,2.069 5.46,1.904 5.67,1.904H19.282C19.492,1.904 19.657,2.069 19.657,2.279V16.132"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M10.17,24.23H13.68"
android:pathData="M3.817,6.938H6.645"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M21.46,17.94C22.631,17.94 23.58,16.991 23.58,15.82C23.58,14.649 22.631,13.7 21.46,13.7C20.289,13.7 19.34,14.649 19.34,15.82C19.34,16.991 20.289,17.94 21.46,17.94Z"
android:strokeLineJoin="round"
android:fillColor="#58ECA2"
android:strokeColor="#21002A"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M16.8,24.3C16.8,21.73 18.89,19.64 21.46,19.64C24.03,19.64 26.12,21.73 26.12,24.3H16.8Z"
android:strokeLineJoin="round"
android:fillColor="#58ECA2"
android:strokeColor="#21002A"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M29.83,27.25C29.83,29.02 28.39,30.46 26.62,30.46H12.01V7.53H26.62C28.39,7.53 29.83,8.97 29.83,10.74V27.25Z"
android:pathData="M3.817,15.359H6.645"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M3.817,11.152H6.645"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M12.915,10.297C13.859,10.297 14.625,9.531 14.625,8.587C14.625,7.643 13.859,6.877 12.915,6.877C11.971,6.877 11.205,7.643 11.205,8.587C11.205,9.531 11.971,10.297 12.915,10.297Z"
android:strokeLineJoin="round"
android:fillColor="#58EDA3"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M9.157,15.42C9.157,13.342 10.837,11.662 12.915,11.662C14.993,11.662 16.673,13.342 16.673,15.42H9.157Z"
android:strokeLineJoin="round"
android:fillColor="#58EDA3"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M18.487,18.69L20.182,20.392L18.487,22.094"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M19.635,20.393H14.25"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
</vector>

View File

@@ -1,65 +1,62 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M20,20m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0"
android:fillColor="#F5F5F5"/>
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"/>
<path
android:pathData="M11.08,13.75L11.11,12.8C11.11,12.55 11.21,12.32 11.38,12.14C11.55,11.96 11.78,11.86 12.02,11.85H12.91C13.16,11.85 13.41,11.95 13.59,12.13C13.77,12.31 13.89,12.55 13.9,12.81V13.76"
android:fillColor="#58ECA2"/>
<path
android:pathData="M27.64,13.75C27.46,13.75 27.29,13.7 27.14,13.6C26.99,13.5 26.87,13.36 26.79,13.2C26.28,12.09 25.73,10.89 24.7,10.89H20C19.13,10.89 18.69,11.69 17.46,13.36C17.37,13.48 17.26,13.58 17.13,13.65C17,13.72 16.86,13.75 16.71,13.75H11.08C9.67,13.75 9.2,14.4 9.2,15.39V25.3C9.2,26.3 9.67,27.1 11.13,27.1H28.87C30.33,27.1 30.8,26.29 30.8,25.3V15.39C30.8,14.39 30.33,13.75 28.87,13.75H27.64Z"
android:pathData="M20.13,6.734C19.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.485V19.04C0.5,20.1 1,20.96 2.556,20.96H21.444C23,20.96 23.5,20.1 23.5,19.04V8.485C23.5,7.424 23,6.734 21.444,6.734H20.13Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M28.87,17.57C30.33,17.57 30.8,18.22 30.8,19.21V15.39C30.8,14.39 30.33,13.75 28.87,13.75H27.64C27.46,13.75 27.29,13.7 27.14,13.6C26.99,13.5 26.87,13.36 26.79,13.2C26.28,12.09 25.73,10.89 24.7,10.89H20C19.13,10.89 18.69,11.69 17.46,13.36C17.37,13.48 17.26,13.58 17.13,13.65C17,13.72 16.86,13.75 16.71,13.75H11.08C9.67,13.75 9.2,14.4 9.2,15.39V19.21C9.2,18.21 9.67,17.57 11.08,17.57H28.87Z"
android:fillColor="#E3FFEC"/>
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"/>
<path
android:strokeWidth="1"
android:pathData="M27.64,13.75C27.46,13.75 27.29,13.7 27.14,13.6C26.99,13.5 26.87,13.36 26.79,13.2C26.28,12.09 25.73,10.89 24.7,10.89H20C19.13,10.89 18.69,11.69 17.46,13.36C17.37,13.48 17.26,13.58 17.13,13.65C17,13.72 16.86,13.75 16.71,13.75H11.08C9.67,13.75 9.2,14.4 9.2,15.39V25.3C9.2,26.3 9.67,27.1 11.13,27.1H28.87C30.33,27.1 30.8,26.29 30.8,25.3V15.39C30.8,14.39 30.33,13.75 28.87,13.75H27.64Z"
android:pathData="M20.13,6.734C19.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.485V19.04C0.5,20.1 1,20.96 2.556,20.96H21.444C23,20.96 23.5,20.1 23.5,19.04V8.485C23.5,7.424 23,6.734 21.444,6.734H20.13Z"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:pathData="M22.35,25.2C25.2,25.2 27.52,22.85 27.52,19.95C27.52,17.05 25.21,14.7 22.35,14.7C19.49,14.7 17.18,17.05 17.18,19.95C17.18,22.85 19.49,25.2 22.35,25.2Z"
android:pathData="M14.5,18.927C17.538,18.927 20,16.425 20,13.339C20,10.252 17.538,7.75 14.5,7.75C11.462,7.75 9,10.252 9,13.339C9,16.425 11.462,18.927 14.5,18.927Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M22.35,22.82C23.91,22.82 25.17,21.54 25.17,19.96C25.17,18.38 23.91,17.1 22.35,17.1C20.79,17.1 19.53,18.38 19.53,19.96C19.53,21.54 20.79,22.82 22.35,22.82Z"
android:fillColor="#58ECA2"/>
<path
android:pathData="M12.49,18.52C13.27,18.52 13.9,17.88 13.9,17.09C13.9,16.3 13.27,15.66 12.49,15.66C11.71,15.66 11.08,16.3 11.08,17.09C11.08,17.88 11.71,18.52 12.49,18.52Z"
android:pathData="M14.5,16.388C16.157,16.388 17.5,15.023 17.5,13.339C17.5,11.656 16.157,10.291 14.5,10.291C12.843,10.291 11.5,11.656 11.5,13.339C11.5,15.023 12.843,16.388 14.5,16.388Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M20.36,21.98C19.85,21.44 19.56,20.72 19.57,19.97C19.57,19.22 19.87,18.5 20.4,17.97C20.93,17.44 21.63,17.14 22.37,17.13C23.11,17.13 23.82,17.41 24.35,17.93L20.37,21.98H20.36Z"
android:pathData="M4,11.814C4.828,11.814 5.5,11.132 5.5,10.29C5.5,9.448 4.828,8.766 4,8.766C3.172,8.766 2.5,9.448 2.5,10.29C2.5,11.132 3.172,11.814 4,11.814Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M12.379,15.494C11.833,14.919 11.53,14.149 11.537,13.35C11.544,12.551 11.859,11.786 12.415,11.221C12.972,10.656 13.724,10.335 14.511,10.328C15.297,10.321 16.055,10.629 16.621,11.184L12.379,15.494Z"
android:fillColor="#AFECCD"/>
<path
android:strokeWidth="1"
android:pathData="M22.35,22.82C23.91,22.82 25.17,21.54 25.17,19.96C25.17,18.38 23.91,17.1 22.35,17.1C20.79,17.1 19.53,18.38 19.53,19.96C19.53,21.54 20.79,22.82 22.35,22.82Z"
android:pathData="M14.5,16.388C16.157,16.388 17.5,15.023 17.5,13.339C17.5,11.656 16.157,10.291 14.5,10.291C12.843,10.291 11.5,11.656 11.5,13.339C11.5,15.023 12.843,16.388 14.5,16.388Z"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M11.08,13.75L11.11,12.8C11.11,12.55 11.21,12.32 11.38,12.14C11.55,11.96 11.78,11.86 12.02,11.85H12.91C13.16,11.85 13.41,11.95 13.59,12.13C13.77,12.31 13.89,12.55 13.9,12.81V13.76"
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:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M22.35,25.2C25.2,25.2 27.52,22.85 27.52,19.95C27.52,17.05 25.21,14.7 22.35,14.7C19.49,14.7 17.18,17.05 17.18,19.95C17.18,22.85 19.49,25.2 22.35,25.2Z"
android:pathData="M14.5,18.927C17.538,18.927 20,16.425 20,13.339C20,10.252 17.538,7.75 14.5,7.75C11.462,7.75 9,10.252 9,13.339C9,16.425 11.462,18.927 14.5,18.927Z"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M12.49,18.52C13.27,18.52 13.9,17.88 13.9,17.09C13.9,16.3 13.27,15.66 12.49,15.66C11.71,15.66 11.08,16.3 11.08,17.09C11.08,17.88 11.71,18.52 12.49,18.52Z"
android:pathData="M4,11.814C4.828,11.814 5.5,11.132 5.5,10.29C5.5,9.448 4.828,8.766 4,8.766C3.172,8.766 2.5,9.448 2.5,10.29C2.5,11.132 3.172,11.814 4,11.814Z"
android:strokeLineJoin="round"
android:fillColor="#58ECA2"
android:strokeColor="#21002A"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
</vector>

View File

@@ -0,0 +1,68 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19.657,20.017C19.657,20.227 19.492,20.392 19.282,20.392H5.67C5.46,20.392 5.295,20.227 5.295,20.017V2.279C5.295,2.069 5.46,1.904 5.67,1.904H19.282C19.492,1.904 19.657,2.069 19.657,2.279V20.009V20.017Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M19.657,1.904V20.009C19.657,20.219 19.492,20.384 19.282,20.384H5.295"
android:fillColor="#E4FFED"/>
<path
android:strokeWidth="1"
android:pathData="M11.58,20.392H5.67C5.46,20.392 5.295,20.227 5.295,20.017V2.279C5.295,2.069 5.46,1.904 5.67,1.904H19.282C19.492,1.904 19.657,2.069 19.657,2.279V16.132"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M3.817,6.938H6.645"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M3.817,15.359H6.645"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M3.817,11.152H6.645"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M12.915,10.297C13.859,10.297 14.625,9.531 14.625,8.587C14.625,7.643 13.859,6.877 12.915,6.877C11.971,6.877 11.205,7.643 11.205,8.587C11.205,9.531 11.971,10.297 12.915,10.297Z"
android:strokeLineJoin="round"
android:fillColor="#58EDA3"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M9.157,15.42C9.157,13.342 10.837,11.662 12.915,11.662C14.993,11.662 16.673,13.342 16.673,15.42H9.157Z"
android:strokeLineJoin="round"
android:fillColor="#58EDA3"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M18.487,18.69L20.182,20.392L18.487,22.094"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M19.635,20.393H14.25"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
</vector>

View File

@@ -1,85 +1,43 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M20,20m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0"
android:fillColor="#F5F5F5"/>
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"/>
<path
android:pathData="M26.262,29.109H13.737C13.377,29.109 13.085,28.817 13.085,28.457V14.687C13.085,14.282 13.25,13.899 13.535,13.614L17.899,9.317C18.184,9.039 18.559,8.882 18.957,8.882H26.262C26.622,8.882 26.914,9.174 26.914,9.534V28.457C26.914,28.817 26.622,29.109 26.262,29.109Z"
android:pathData="M8.471,10.446C8.616,10.301 8.732,10.128 8.81,9.938C8.889,9.748 8.93,9.544 8.93,9.339C8.93,9.133 8.889,8.93 8.81,8.74C8.732,8.55 8.616,8.377 8.471,8.232L6.81,6.572C6.516,6.278 6.118,6.113 5.703,6.113C5.288,6.113 4.89,6.278 4.596,6.572L3.685,7.483C3.304,7.866 3.066,8.369 3.012,8.907C2.958,9.445 3.091,9.985 3.389,10.436C6.07,14.471 9.53,17.93 13.565,20.611C14.016,20.909 14.556,21.042 15.094,20.988C15.632,20.934 16.135,20.696 16.518,20.314L17.43,19.403C17.723,19.11 17.888,18.712 17.888,18.296C17.888,17.881 17.723,17.483 17.43,17.19L15.769,15.53C15.475,15.236 15.077,15.071 14.662,15.071C14.247,15.071 13.849,15.236 13.555,15.53L13.002,16.083C11.145,14.556 9.441,12.852 7.914,10.995L8.471,10.446Z"
android:fillColor="#AFECCD"/>
<path
android:pathData="M7.917,10.999L8.471,10.446C8.616,10.301 8.732,10.128 8.81,9.938C8.889,9.748 8.93,9.544 8.93,9.339C8.93,9.133 8.889,8.93 8.81,8.74C8.732,8.55 8.616,8.377 8.471,8.232L6.81,6.572C6.516,6.278 6.118,6.113 5.703,6.113C5.288,6.113 4.89,6.278 4.596,6.572L3.685,7.483C3.304,7.866 3.066,8.369 3.012,8.907C2.958,9.445 3.091,9.985 3.389,10.436C4.619,12.285 6.017,14.016 7.565,15.607L9.932,13.24C9.225,12.512 8.543,11.766 7.917,10.999Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M26.914,9.542V28.465C26.914,28.824 26.622,29.117 26.262,29.117H13.737C13.377,29.117 13.085,28.824 13.085,28.465"
android:fillColor="#DDFFE6"/>
<path
android:pathData="M26.262,29.109H13.737C13.377,29.109 13.085,28.817 13.085,28.457V14.687C13.085,14.282 13.25,13.899 13.535,13.614L17.899,9.317C18.184,9.039 18.559,8.882 18.957,8.882H26.262C26.622,8.882 26.914,9.174 26.914,9.534V28.457C26.914,28.817 26.622,29.109 26.262,29.109Z"
android:strokeWidth="1"
android:pathData="M8.471,10.446C8.616,10.301 8.732,10.128 8.81,9.938C8.889,9.748 8.93,9.544 8.93,9.339C8.93,9.133 8.889,8.93 8.81,8.74C8.732,8.55 8.616,8.377 8.471,8.232L6.81,6.572C6.516,6.278 6.118,6.113 5.703,6.113C5.288,6.113 4.89,6.278 4.596,6.572L3.685,7.483C3.304,7.866 3.066,8.369 3.012,8.907C2.958,9.445 3.091,9.985 3.389,10.436C6.07,14.471 9.53,17.93 13.565,20.611C14.016,20.909 14.556,21.042 15.094,20.988C15.632,20.934 16.135,20.696 16.518,20.314L17.43,19.403C17.723,19.11 17.888,18.712 17.888,18.296C17.888,17.881 17.723,17.483 17.43,17.19L15.769,15.53C15.475,15.236 15.077,15.071 14.662,15.071C14.247,15.071 13.849,15.236 13.555,15.53L13.002,16.083C11.145,14.556 9.441,12.852 7.914,10.995L8.471,10.446Z"
android:strokeLineJoin="round"
android:strokeWidth="0.8"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:pathData="M23.09,17.462H16.827C16.467,17.462 16.175,17.754 16.175,18.114V26.207C16.175,26.619 16.505,26.957 16.932,26.957H23.187C23.547,26.957 23.84,26.664 23.84,26.304V18.212C23.84,17.792 23.502,17.462 23.09,17.462Z"
android:strokeWidth="1"
android:pathData="M12,3C14.387,3 16.677,3.948 18.365,5.636C20.053,7.324 21.001,9.614 21.001,12.001"
android:strokeLineJoin="round"
android:strokeWidth="0.8"
android:fillColor="#58E69C"
android:strokeColor="#21002A"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:pathData="M17.465,20.896H22.542"
android:strokeWidth="1"
android:pathData="M12,6.131C13.557,6.131 15.05,6.749 16.151,7.85C17.252,8.951 17.87,10.444 17.87,12.001"
android:strokeLineJoin="round"
android:strokeWidth="0.8"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:pathData="M20.007,20.897V18.932"
android:strokeWidth="1"
android:pathData="M12,9.262C12.726,9.262 13.423,9.55 13.937,10.064C14.451,10.578 14.74,11.275 14.74,12.001"
android:strokeLineJoin="round"
android:strokeWidth="0.8"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeLineCap="round"/>
<path
android:pathData="M17.472,20.897V20.095"
android:strokeLineJoin="round"
android:strokeWidth="0.8"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeLineCap="round"/>
<path
android:pathData="M22.542,20.897V20.095"
android:strokeLineJoin="round"
android:strokeWidth="0.8"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeLineCap="round"/>
<path
android:pathData="M22.542,23.521H17.465"
android:strokeLineJoin="round"
android:strokeWidth="0.8"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeLineCap="round"/>
<path
android:pathData="M20.007,23.521V25.486"
android:strokeLineJoin="round"
android:strokeWidth="0.8"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeLineCap="round"/>
<path
android:pathData="M22.542,23.521V24.324"
android:strokeLineJoin="round"
android:strokeWidth="0.8"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeLineCap="round"/>
<path
android:pathData="M17.472,23.521V24.324"
android:strokeLineJoin="round"
android:strokeWidth="0.8"
android:fillColor="#00000000"
android:strokeColor="#21002A"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
</vector>

View File

@@ -1,30 +1,40 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M20,20m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0"
android:pathData="M12,3C7.03,3 3,6.328 3,10.435C3.021,11.457 3.271,12.462 3.731,13.375C4.192,14.288 4.851,15.086 5.661,15.71L3.783,20.217L8.769,17.368C9.814,17.7 10.904,17.87 12,17.87C16.97,17.87 21,14.541 21,10.435C21,6.328 16.97,3 12,3Z"
android:fillColor="#AFECCD"/>
<path
android:pathData="M12,6.13C13.715,6.085 15.418,6.433 16.978,7.147C18.538,7.861 19.913,8.923 21,10.251C20.879,6.23 16.896,3 12,3C7.104,3 3.121,6.23 3,10.251C4.087,8.923 5.462,7.861 7.022,7.147C8.582,6.433 10.285,6.085 12,6.13Z"
android:fillColor="#F5F5F5"/>
<path
android:strokeWidth="1"
android:pathData="M29.128,11.678H10.902C10.42,11.678 10.03,12.068 10.03,12.549V25.45C10.03,25.931 10.42,26.322 10.902,26.322H29.128C29.61,26.322 30,25.931 30,25.45V12.549C30,12.068 29.61,11.678 29.128,11.678Z"
android:pathData="M12,3C7.03,3 3,6.328 3,10.435C3.021,11.457 3.271,12.462 3.731,13.375C4.192,14.288 4.851,15.086 5.661,15.71L3.783,20.217L8.769,17.368C9.814,17.7 10.904,17.87 12,17.87C16.97,17.87 21,14.541 21,10.435C21,6.328 16.97,3 12,3Z"
android:strokeLineJoin="round"
android:fillColor="#E3FFEC"
android:strokeColor="#21002A"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M20.028,17.763L29.337,25.756C29.564,25.948 29.424,26.323 29.128,26.323H10.901C10.605,26.323 10.466,25.948 10.692,25.756L20.028,17.763Z"
android:pathData="M7.696,7.695H13.956"
android:strokeLineJoin="round"
android:fillColor="#58EDA3"
android:strokeColor="#21002A"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M19.993,20.237L10.692,12.244C10.466,12.053 10.605,11.678 10.901,11.678H29.128C29.424,11.678 29.564,12.053 29.337,12.244L20.002,20.237H19.993Z"
android:pathData="M7.696,10.043H16.304"
android:strokeLineJoin="round"
android:fillColor="#ffffff"
android:strokeColor="#21002A"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M7.696,12.391H16.304"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#00303E"
android:strokeLineCap="round"/>
</vector>

View File

@@ -11,10 +11,10 @@
<string name="navi_pay_moto">Simplifying payments on Navi</string>
<string name="powered_by">Powered by</string>
<string name="sms_verification">SMS verification</string>
<string name="sms_permission_header">SMS permission</string>
<string name="sms_permission_rationale_description">To verify your bank accounts</string>
<string name="phone_state_permission_header">SIM card permission</string>
<string name="phone_state_permission_description">To verify your SIM provider</string>
<string name="sms_permission_header">SMS</string>
<string name="sms_permission_rationale_description">To verify your phone number for UPI payments.</string>
<string name="phone_state_permission_header">Phone</string>
<string name="phone_state_permission_description">To verify your SIM card with your registered mobile number.</string>
<string name="sms_verification_charges_info">*Standard SMS charges will apply</string>
<string name="proceed">Proceed</string>
<string name="select_bank">Select bank</string>
@@ -172,9 +172,10 @@
<string name="money_transfer">Money transfer</string>
<string name="manage">Manage</string>
<string name="settings">Settings</string>
<string name="app_permission_header">App permission</string>
<string name="app_permission_header">Allow app permission</string>
<string name="allow_app_permissions">Allow app permissions</string>
<string name="allow">Allow </string>
<string name="go_to_setting">Go to settings</string>
<string name="hassle_free_fast_payments">Hassle-free &amp; fast payments</string>
<string name="check_your_balance_anytime_anywhere">Check your bank balance anytime, anywhere</string>
<string name="get_started">Get started</string>
@@ -208,10 +209,13 @@
<string name="no_pending_request">You do not have any pending requests</string>
<string name="create_autopay">Create autopay</string>
<string name="upi_number_tab_header">UPI number</string>
<string name="allow_camera_permission">Allow camera permission</string>
<string name="camera">Camera</string>
<string name="navi_pay_camera_permission_description">To scan QR code</string>
<string name="contact">Contact</string>
<string name="contact_permission_description">To make contact finding easier</string>
<string name="camera_show_rational">Camera permission is mandatory</string>
<string name="navi_pay_camera_permission_description">Grant camera permission to scan QR codes for merchants, family, and friends.</string>
<string name="navi_pay_camera_permission_show_rational_description">Required so that you can scan the image of the scanner to pay</string>
<string name="contact">Contacts</string>
<string name="contact_permission_description">To allow UPI payments to your contacts.</string>
<string name="navi_pay_qr_error_title">Invalid QR code</string>
<string name="navi_pay_qr_error_description">The QR code you scanned is not a valid UPI QR code</string>
<string name="complaint_status_complaint_closed">Complaint closed</string>