2

I'm having trouble understanding how imePadding() and BringIntoViewRequester() work.

I have made a signup view and I believe I have applied imePadding correctly to the text field modifiers.

But for the view requesters I read in this medium article that you need to have multiple view requesters for multiple textfields. When I click on a textfield that's under the keyboard it does not come into view.

Am I meant to apply the view requesters to the AppSurface Composable or the overarching Column in SignupView?

Is there a better way to structure the view so that the textfields snaps just above the keyboard and comes into view?

I've also looked at focus requester which I don't think is the right use case for this.

@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun SignupView(
    navController: NavHostController,
    viewModel: SignupViewModel = viewModel()
) {
    val focusManager = LocalFocusManager.current
    val context = LocalContext.current

    // Variables to show the user error messages
    var showFieldErrorDialog by remember { mutableStateOf(false) }
    var showEULAToast by remember { mutableStateOf(false) }
    var eulaItem by remember { mutableStateOf("") }

    // Variables for EULA bottom sheet
    val skipPartiallyExpanded by rememberSaveable { mutableStateOf(false) }
    val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = skipPartiallyExpanded)
    val scope = rememberCoroutineScope()

    // Variables used for IME handling
    // TODO text fields not coming into view when clicked
    val scrollState = rememberScrollState()
    val fNameViewRequester = remember { BringIntoViewRequester() }
    val lNameViewRequester = remember { BringIntoViewRequester() }
    val emailViewRequester = remember { BringIntoViewRequester() }
    val passViewRequester = remember { BringIntoViewRequester() }
    val passCheckRequester = remember { BringIntoViewRequester() }

    // Handles bottom sheet state
    LaunchedEffect(eulaItem) {
        if (eulaItem.isNotEmpty()) {
            sheetState.show()
        } else {
            sheetState.hide()
        }
    }

    AppSurface(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(scrollState),
        content = {
            // Top Component
            Box(modifier = Modifier.fillMaxWidth()) {
                Column(
                    modifier = Modifier.align(Alignment.Center),
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    PrimaryText(text = stringResource(R.string.hello_there))
                    PageTitleText(text = stringResource(R.string.create_an_account))
                }
                LightDarkSwitcher(
                    modifier = Modifier
                        .size(50.dp)
                        .align(Alignment.TopEnd)
                )
            }
            Spacer(modifier = Modifier.weight(1f))
            Column(horizontalAlignment = Alignment.CenterHorizontally) {
                Row {
                    CustomOutlinedTextField(
                        // First Name Field
                        modifier = Modifier
                            .weight(1f)
                            .imePadding()
                            .bringIntoViewRequester(fNameViewRequester)
                            .onFocusEvent { focusState ->
                                if (focusState.isFocused) {
                                    scope.launch {
                                        fNameViewRequester.bringIntoView()
                                    }
                                }
                            },
                        label = stringResource(R.string.first_name),
                        icon = Icons.Outlined.Person,
                        onValueChange = {
                            viewModel.onEvent(SignupUIEvent.FirstNameChanged(it.trim()))
                        },
                        keyboardOptions = KeyboardOptions.Default.copy(
                            capitalization = KeyboardCapitalization.Words,
                            imeAction = ImeAction.Next
                        ),
                        passStatus = viewModel.uiState.firstNamePass
                    )
                    Spacer(modifier = Modifier.width(10.dp))
                    CustomOutlinedTextField(
                        // Last Name Field
                        modifier = Modifier
                            .weight(1f)
                            .imePadding()
                            .bringIntoViewRequester(lNameViewRequester)
                            .onFocusEvent { focusState ->
                                if (focusState.isFocused) {
                                    scope.launch {
                                        lNameViewRequester.bringIntoView()
                                    }
                                }
                            },
                        label = stringResource(R.string.last_name),
                        icon = Icons.Outlined.Person,
                        onValueChange = {
                            viewModel.onEvent(SignupUIEvent.LastNameChanged(it.trim()))
                        },
                        keyboardOptions = KeyboardOptions.Default.copy(
                            capitalization = KeyboardCapitalization.Words,
                            imeAction = ImeAction.Next
                        ),
                        passStatus = viewModel.uiState.lastNamePass
                    )
                }
                Spacer(modifier = Modifier.height(10.dp))
                CustomOutlinedTextField(
                    // Email Field
                    modifier = Modifier
                        .imePadding()
                        .bringIntoViewRequester(emailViewRequester)
                        .onFocusEvent { focusState ->
                            if (focusState.isFocused) {
                                scope.launch {
                                    emailViewRequester.bringIntoView()
                                }
                            }
                        },
                    label = stringResource(R.string.email),
                    icon = Icons.Outlined.Email,
                    onValueChange = {
                        viewModel.onEvent(SignupUIEvent.EmailChanged(it.trim()))
                    },
                    keyboardOptions = KeyboardOptions(
                        keyboardType = KeyboardType.Email,
                        imeAction = ImeAction.Next
                    ),
                    passStatus = viewModel.uiState.emailPass
                )
                Spacer(modifier = Modifier.height(10.dp))
                CustomOutlinedTextField(
                    // Password Field
                    modifier = Modifier
                        .imePadding()
                        .bringIntoViewRequester(passViewRequester)
                        .onFocusEvent { focusState ->
                            if (focusState.isFocused) {
                                scope.launch {
                                    passViewRequester.bringIntoView()
                                }
                            }
                        },
                    isPassword = true,
                    label = stringResource(R.string.password),
                    icon = Icons.Outlined.Lock,
                    onValueChange = {
                        // Do not trim password, passes through `Validator`
                        viewModel.onEvent(SignupUIEvent.PasswordChanged(it))
                    },
                    keyboardOptions = KeyboardOptions(
                        keyboardType = KeyboardType.Password,
                        imeAction = ImeAction.Next
                    ),
                    passStatus = viewModel.uiState.passwordPass
                )
                Spacer(modifier = Modifier.padding(10.dp))
                Column {
                    // Current Password Requirements
                    PasswordCondition(
                        passStatus = viewModel.uiState.passwordLengthPass,
                        label = "At least 6 characters long"
                    )
                    PasswordCondition(
                        passStatus = viewModel.uiState.passwordSpecialCharPass,
                        label = "Includes a special character"
                    )
                    PasswordCondition(
                        passStatus = viewModel.uiState.passwordUppercasePass,
                        label = "Includes an uppercase character"
                    )
                    PasswordCondition(
                        passStatus = viewModel.uiState.passwordNumberPass,
                        label = "Includes a number"
                    )
                    PasswordCondition(
                        passStatus = viewModel.uiState.passwordWhitespacePass,
                        label = "No whitespace characters"
                    )
                }
                Spacer(modifier = Modifier.padding(10.dp))
                CustomOutlinedTextField(
                    // Confirm Password Field
                    modifier = Modifier
                        .imePadding()
                        .bringIntoViewRequester(passCheckRequester)
                        .onFocusEvent { focusState ->
                            if (focusState.isFocused) {
                                scope.launch {
                                    passCheckRequester.bringIntoView()
                                }
                            }
                        },
                    isPassword = true,
                    label = stringResource(R.string.confirm_password),
                    onValueChange = {
                        // Do not trim password, passes through `Validator`
                        viewModel.onEvent(SignupUIEvent.ConfirmPasswordChanged(it))
                    },
                    icon = Icons.Outlined.Lock,
                    keyboardOptions = KeyboardOptions(
                        keyboardType = KeyboardType.Password,
                        imeAction = ImeAction.Done
                    ),
                    passStatus = viewModel.uiState.confirmPasswordPass
                )
                AuthCheckboxComponent(
                    // EULA Component
                    onCheckedChange = {
                        // Clear focus when clicked on EULA
                        viewModel.onEvent(SignupUIEvent.EULACheckboxClicked(it))
                        focusManager.clearFocus()
                    },
                    onTextClicked = { eulaItem = it },
                    passStatus = viewModel.uiState.eulaPass
                )
            }
            Spacer(modifier = Modifier.weight(1f))
            // Bottom Component
            BottomComponent(
                textQuery = "Already have an account? ",
                textClickable = "Login",
                action = "Register",
                navController = navController,
                onButtonClicked = {
                    // Pass the NavHostController for signup handling
                    viewModel.onEvent(SignupUIEvent.RegisterButtonClicked(navController))
                    if (!viewModel.isFormValid()) {
                        showFieldErrorDialog = true
                    }
                    if (!viewModel.isEulaAccepted() && viewModel.isFormValid()) {
                        showEULAToast = true
                    }
                },
                // Button is disabled when sign process is happening
                isEnabled = !viewModel.signupProgress
            )

            // Bottom Sheet for EULA
            if (eulaItem.isNotEmpty()) {
                ModalBottomSheet(
                    onDismissRequest = {
                        eulaItem = ""
                    },
                    sheetState = sheetState,
                    modifier = Modifier.fillMaxHeight(0.9f),
                ) {
                    EULADisplay(eulaItem = eulaItem, closeButtonClicked = { eulaItem = "" })
                }
            }
            // Dialog when there are errors in the user fields
            if (showFieldErrorDialog) {
                AuthDialog(
                    onDismiss = { showFieldErrorDialog = false },
                    titleMessage = "Sign Up Error",
                    statusMessage = "Some information is incorrect"
                )
            }
            // Dialog when there is a signup error
            if (viewModel.errorMessage != null) {
                AuthDialog(
                    onDismiss = { viewModel.clearErrorMessage() },
                    titleMessage = "Signup Failed",
                    statusMessage = viewModel.errorMessage ?: "An unknown error has occurred"
                )
            }
            // Toast message when EULA is not checked
            if (showEULAToast) {
                Toast.makeText(context, "Please accept the Terms and Conditions", Toast.LENGTH_SHORT).show()
                showEULAToast = false
            }
        }
    )
}
@Composable
fun AppSurface(
    modifier: Modifier = Modifier,
    content: @Composable ColumnScope.() -> Unit,
    overlayingContent: @Composable () -> Unit = { }
) {
    val focusManager = LocalFocusManager.current

    Surface(
        modifier = Modifier
            .fillMaxSize()
            .pointerInput(Unit) {
                detectTapGestures(onTap = {
                    focusManager.clearFocus()
                })
            }
            .windowInsetsPadding(WindowInsets.statusBars)
    ) {
        Box(modifier = Modifier.fillMaxSize()) {
            Column(modifier = modifier
                .fillMaxSize()
                .padding(28.dp)
                .imePadding()
            ) {
                content()
            }
            overlayingContent()
        }
        Spacer(
            modifier = Modifier
                .fillMaxWidth()
                .windowInsetsBottomHeight(WindowInsets.navigationBars)
        )
    }
}

Normal View

Confirm Password Field Clicked

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.