Skip to content

Commit

Permalink
TIQR-453: Add push permission dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
dzolnai committed Aug 29, 2024
1 parent 0859d12 commit 566ab1e
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 25 deletions.
31 changes: 22 additions & 9 deletions app/src/main/kotlin/nl/eduid/graphs/MainGraph.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ fun MainGraph(
onSecurityClicked = { navController.navigate(Security.Settings.route) },
onEnrollWithQR = { navController.navigate(Account.ScanQR.routeForEnrol) },
launchOAuth = { navController.navigate(Graph.OAUTH) },
goToRegistrationPinSetup = { challenge ->
goToRegistrationPinSetup = { challenge, isScanFlow ->
val encodeChallenge = viewModel.encodeChallenge(challenge)
navController.navigate(
"${Account.EnrollPinSetup.route}/$encodeChallenge"
"${Account.EnrollPinSetup.route}/$encodeChallenge/$isScanFlow"
)
},
goToConfirmDeactivation = { phoneNumber ->
Expand All @@ -106,9 +106,10 @@ fun MainGraph(
val isEnrolment = entry.arguments?.getBoolean(Account.ScanQR.isEnrolment, false) ?: false
ScanScreen(viewModel = viewModel, isEnrolment = isEnrolment, goBack = { navController.popBackStack() }, goToNext = { challenge ->
val encodedChallenge = viewModel.encodeChallenge(challenge)
val isScanFlow = true
if (challenge is EnrollmentChallenge) {
navController.goToWithPopCurrent(
"${Account.EnrollPinSetup.route}/$encodedChallenge"
"${Account.EnrollPinSetup.route}/$encodedChallenge/$isScanFlow"
)
} else {
navController.goToWithPopCurrent(
Expand All @@ -124,7 +125,8 @@ fun MainGraph(
arguments = Account.EnrollPinSetup.arguments,
) { entry ->
val viewModel = hiltViewModel<RegistrationPinSetupViewModel>(entry)
RegistrationPinSetupScreen(viewModel = viewModel, closePinSetupFlow = { navController.popBackStack() }) { nextStep ->
val isScanFlow = entry.arguments?.getBoolean(Account.EnrollPinSetup.isScanFlow, false) ?: false
RegistrationPinSetupScreen(viewModel = viewModel, isScanFlow = isScanFlow, closePinSetupFlow = { navController.popBackStack() }) { nextStep ->
when (nextStep) {
NextStep.RecoveryInBrowser -> {
navController.navigate(Graph.CONTINUE_RECOVERY_IN_BROWSER)
Expand All @@ -133,12 +135,17 @@ fun MainGraph(
is NextStep.PromptBiometric -> {
navController.navigate(
WithChallenge.EnableBiometric.buildRouteForEnrolment(
encodedChallenge = viewModel.encodeChallenge(nextStep.challenge), pin = nextStep.pin
encodedChallenge = viewModel.encodeChallenge(nextStep.challenge),
pin = nextStep.pin,
isScanFlow = isScanFlow
)
) {
popUpTo(Graph.HOME_PAGE)
}
}
is NextStep.Welcome -> {
navController.navigate(WelcomeStart.routeWithScanFlowArg(isScanFlow))
}

NextStep.Recovery -> navController.navigate(PhoneNumberRecovery.RequestCode.route) {
popUpTo(Graph.HOME_PAGE)
Expand Down Expand Up @@ -177,11 +184,14 @@ fun MainGraph(
route = WithChallenge.EnableBiometric.routeWithArgs, arguments = WithChallenge.arguments
) { entry ->
val viewModel = hiltViewModel<EnableBiometricViewModel>(entry)
val isScanFlow = entry.arguments?.getBoolean(WithChallenge.isScanFlowArg, false) ?: false
EnableBiometricScreen(viewModel = viewModel, goToNext = { shouldAskForRecovery ->
if (shouldAskForRecovery) {
navController.navigate(PhoneNumberRecovery.RequestCode.route) {
popUpTo(Graph.HOME_PAGE)
}
} else if (isScanFlow) {
navController.navigate(WelcomeStart.routeWithScanFlowArg(true))
} else {
//Continue recovery via web
navController.navigate(Graph.CONTINUE_RECOVERY_IN_BROWSER)
Expand Down Expand Up @@ -275,7 +285,7 @@ fun MainGraph(
if (isDeactivation) {
navController.popBackStack()
} else {
navController.navigate(Graph.WELCOME_START) {
navController.navigate(WelcomeStart.routeWithScanFlowArg(false)) {
//Flow for phone number recovery completed, remove from stack entirely
popUpTo(PhoneNumberRecovery.RequestCode.route) { inclusive = true }
}
Expand All @@ -285,14 +295,17 @@ fun MainGraph(
//endregion

//region Welcome-FirstTime
composable(Graph.WELCOME_START) { entry ->
composable(
route = WelcomeStart.routeWithArgs, arguments = WelcomeStart.arguments
) { entry ->
val viewModel = hiltViewModel<WelcomeStartViewModel>(entry)
val isScanFlow = WelcomeStart.decodeScanFlowArg(entry)
WelcomeStartScreen(
viewModel,
) { accountIsAlreadyLinked ->
if (accountIsAlreadyLinked) {
if (accountIsAlreadyLinked || isScanFlow) {
navController.navigate(Graph.HOME_PAGE) {
//Clear existing home page that has no account
// Clear existing home page that has no account
popUpTo(Graph.HOME_PAGE) {
inclusive = true
}
Expand Down
43 changes: 36 additions & 7 deletions app/src/main/kotlin/nl/eduid/graphs/Routes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ object Graph {
const val REQUEST_EDU_ID_ACCOUNT = "request_edu_id_account"
const val REQUEST_EDU_ID_FORM = "request_edu_id_details"

const val WELCOME_START = "start"
const val FIRST_TIME_DIALOG = "first_time_dialog"
const val CONTINUE_RECOVERY_IN_BROWSER = "continue_recovery_in_browser"
const val PERSONAL_INFO = "personal_info"
Expand Down Expand Up @@ -126,6 +125,26 @@ sealed class PhoneNumberRecovery(val route: String) {
}
}

object WelcomeStart {
private const val route = "welcome_start"
const val isScanFlowArg = "is_scan_flow"
const val routeWithArgs = "$route/{$isScanFlowArg}"
val arguments = listOf(navArgument(isScanFlowArg) {
type = NavType.BoolType
nullable = false
defaultValue = false
})

fun routeWithScanFlowArg(isScanFlow: Boolean) =
"${route}/$isScanFlow"

fun decodeScanFlowArg(entry: NavBackStackEntry): Boolean {
return entry.arguments?.getBoolean(isScanFlowArg, false) ?: false
}

}


sealed class Account(val route: String) {

object ScanQR : Account("scan") {
Expand All @@ -143,12 +162,17 @@ sealed class Account(val route: String) {

object EnrollPinSetup : Account("enroll_pin_setup") {
const val enrollChallenge = "enroll_challenge_arg"
const val isScanFlow = "is_scan_flow"

val routeWithArgs = "$route/{$enrollChallenge}"
val routeWithArgs = "$route/{$enrollChallenge}/{$isScanFlow}"
val arguments = listOf(navArgument(enrollChallenge) {
type = NavType.StringType
nullable = false
defaultValue = ""
}, navArgument(isScanFlow) {
type = NavType.BoolType
nullable = false
defaultValue = false
})
}

Expand Down Expand Up @@ -226,8 +250,9 @@ sealed class WithChallenge(val route: String) {
const val challengeArg = "challenge_arg"
const val pinArg = "pin_arg"
const val isEnrolmentArg = "is_enrolment_arg"
const val isScanFlowArg = "is_scan_flow_arg"

const val args = "{$challengeArg}/{$pinArg}/{$isEnrolmentArg}"
const val args = "{$challengeArg}/{$pinArg}/{$isEnrolmentArg}/{$isScanFlowArg}"
val arguments = listOf(navArgument(challengeArg) {
type = NavType.StringType
nullable = false
Expand All @@ -240,17 +265,21 @@ sealed class WithChallenge(val route: String) {
type = NavType.BoolType
nullable = false
defaultValue = true
}, navArgument(isScanFlowArg) {
type = NavType.BoolType
nullable = false
defaultValue = false
})
}

object EnableBiometric : WithChallenge("enable_biometric") {
val routeWithArgs = "$route/$args"
fun buildRouteForEnrolment(encodedChallenge: String, pin: String): String =
"$route/$encodedChallenge/$pin/true"
fun buildRouteForEnrolment(encodedChallenge: String, pin: String, isScanFlow: Boolean): String =
"$route/$encodedChallenge/$pin/true/$isScanFlow"

@SuppressWarnings("unused")
fun buildRouteForAuthentication(encodedChallenge: String, pin: String): String =
"$route/$encodedChallenge/$pin/false"
fun buildRouteForAuthentication(encodedChallenge: String, pin: String, isScanFlow: Boolean): String =
"$route/$encodedChallenge/$pin/false/$isScanFlow"

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ fun HomePageNoAccountContent(
onGoToScan: () -> Unit = {},
onGoToRequestEduId: () -> Unit = {},
onGoToSignIn: () -> Unit = {},
onGoToRegistrationPinSetup: (EnrollmentChallenge) -> Unit = {},
onGoToRegistrationPinSetup: (EnrollmentChallenge, /* isScanFlow: */ Boolean) -> Unit = { _, _ -> },
onGoToConfirmDeactivation: (String) -> Unit = {},
) {
val sheetState = rememberModalBottomSheetState()
Expand Down Expand Up @@ -181,7 +181,7 @@ fun HomePageNoAccountContent(
onGoToRegistrationPinSetup
)
LaunchedEffect(viewModel.uiState) {
currentGoToRegistrationPinSetup(viewModel.uiState.currentChallenge as EnrollmentChallenge)
currentGoToRegistrationPinSetup(viewModel.uiState.currentChallenge as EnrollmentChallenge, /* isScanFlow = */ false)
viewModel.clearCurrentChallenge()
waitingForVmEvent = false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fun HomePageScreen(
onSecurityClicked: () -> Unit,
onEnrollWithQR: () -> Unit,
launchOAuth: () -> Unit,
goToRegistrationPinSetup: (EnrollmentChallenge) -> Unit,
goToRegistrationPinSetup: (EnrollmentChallenge, Boolean) -> Unit,
goToConfirmDeactivation: (String) -> Unit,
onGoToRequestEduIdAccount: () -> Unit,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ import nl.eduid.ui.EduIdTopAppBar
@Composable
fun RegistrationPinSetupScreen(
viewModel: RegistrationPinSetupViewModel,
isScanFlow: Boolean,
closePinSetupFlow: () -> Unit,
goToNextStep: (NextStep) -> Unit,
goToNextStep: (NextStep) -> Unit
) {
BackHandler { viewModel.handleBackNavigation(closePinSetupFlow) }
//Because the same screen is being used for creating the PIN as well as confirming the PIN
Expand All @@ -41,6 +42,7 @@ fun RegistrationPinSetupScreen(
uiState = viewModel.uiState,
padding = it,
goToNextStep = goToNextStep,
isScanFlow = isScanFlow,
viewModel = viewModel,
)
}
Expand All @@ -50,6 +52,7 @@ fun RegistrationPinSetupScreen(
private fun RegistrationPinSetupContent(
uiState: UiState,
padding: PaddingValues = PaddingValues(),
isScanFlow: Boolean,
goToNextStep: (NextStep) -> Unit = {},
viewModel: RegistrationPinSetupViewModel,
) {
Expand Down Expand Up @@ -126,7 +129,7 @@ private fun RegistrationPinSetupContent(
viewModel.onPinChange(pin, step)
},
onClick = {
viewModel.submitPin(context, uiState.pinStep)
viewModel.submitPin(context, isScanFlow, uiState.pinStep)
enrollmentInProgress = uiState.pinStep == PinStep.PinConfirm
},
isProcessing = uiState.isProcessing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class RegistrationPinSetupViewModel @Inject constructor(
}
}

fun submitPin(context: Context, currentStep: PinStep) {
fun submitPin(context: Context, isScanFlow: Boolean, currentStep: PinStep) {
uiState = uiState.copy(isProcessing = true)
if (currentStep is PinStep.PinCreate) {
val createdPin = uiState.pinValue
Expand All @@ -86,14 +86,14 @@ class RegistrationPinSetupViewModel @Inject constructor(
val createdPin = uiState.pinValue
val pinConfirmed = confirmPin == createdPin
if (pinConfirmed) {
enroll(context, createdPin)
enroll(context, isScanFlow, createdPin)
} else {
uiState = uiState.copy(isPinInvalid = true, isProcessing = false)
}
}
}

private fun enroll(context: Context, password: String) = viewModelScope.launch {
private fun enroll(context: Context, isScanFlow: Boolean, password: String) = viewModelScope.launch {
val currentChallenge = challenge ?: return@launch
val result =
enrollRepository.completeChallenge(
Expand All @@ -113,7 +113,7 @@ class RegistrationPinSetupViewModel @Inject constructor(
}

ChallengeCompleteResult.Success -> {
val nextStep = calculateNextStep(context, currentChallenge)
val nextStep = calculateNextStep(context, isScanFlow, currentChallenge)
uiState =
uiState.copy(
nextStep = nextStep,
Expand All @@ -125,6 +125,7 @@ class RegistrationPinSetupViewModel @Inject constructor(

private suspend fun calculateNextStep(
context: Context,
isScanFlow: Boolean,
currentChallenge: EnrollmentChallenge,
): NextStep {
return if (context.biometricUsable() && currentChallenge.identity.biometricOfferUpgrade) {
Expand All @@ -134,6 +135,8 @@ class RegistrationPinSetupViewModel @Inject constructor(
checkRecovery.shouldAppDoRecoveryForIdentity(currentChallenge.identity.identifier)
if (shouldAppDoRecovery) {
NextStep.Recovery
} else if (isScanFlow) {
NextStep.Welcome
} else {
NextStep.RecoveryInBrowser
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/kotlin/nl/eduid/screens/pinsetup/UiState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ sealed class NextStep {
data class PromptBiometric(val challenge: Challenge, val pin: String) : NextStep()
object Recovery : NextStep()
object RecoveryInBrowser : NextStep()
object Welcome: NextStep()
}
31 changes: 31 additions & 0 deletions app/src/main/kotlin/nl/eduid/screens/start/WelcomeStartScreen.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package nl.eduid.screens.start

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
Expand All @@ -16,10 +21,13 @@ import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
Expand All @@ -30,10 +38,12 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.core.content.ContextCompat
import nl.eduid.R
import nl.eduid.ui.EduIdTopAppBar
import nl.eduid.ui.PrimaryButton
import nl.eduid.ui.theme.EduidAppAndroidTheme
import timber.log.Timber

@Composable
fun WelcomeStartScreen(
Expand All @@ -55,6 +65,27 @@ private fun WelcomeStartContent(
padding: PaddingValues = PaddingValues(),
onNext: () -> Unit = {},
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val lifecycle = LocalLifecycleOwner.current.lifecycle
val context = LocalContext.current
val notificationPermission = Manifest.permission.POST_NOTIFICATIONS
val permissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
Timber.i("Notification permission granted")
} else {
Timber.d("Showing notification permission dialog")
}
}
LaunchedEffect(lifecycle){
if (ContextCompat.checkSelfPermission(context, notificationPermission) != PackageManager.PERMISSION_GRANTED) {
permissionLauncher.launch(notificationPermission)
}
}
}


ConstraintLayout(
modifier = Modifier
.fillMaxSize()
Expand Down

0 comments on commit 566ab1e

Please sign in to comment.