diff --git a/build.gradle b/build.gradle index 9d6640d7..d11f41a2 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { - grapes_version = '1.5.1' + grapes_version = '1.5.2' kotlin_version = '1.7.10' firebase_app_distribution_version = '2.1.2' diff --git a/library-compose/src/main/java/com/spendesk/grapes/compose/textfield/GrapesBaseTextField.kt b/library-compose/src/main/java/com/spendesk/grapes/compose/textfield/GrapesBaseTextField.kt index 8f93c22f..21a3cd05 100644 --- a/library-compose/src/main/java/com/spendesk/grapes/compose/textfield/GrapesBaseTextField.kt +++ b/library-compose/src/main/java/com/spendesk/grapes/compose/textfield/GrapesBaseTextField.kt @@ -2,6 +2,7 @@ package com.spendesk.grapes.compose.textfield import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize @@ -48,8 +49,10 @@ internal fun GrapesBaseTextField( modifier: Modifier = Modifier, helperText: String? = null, enabled: Boolean = true, + readOnly: Boolean = false, textStyle: TextStyle = GrapesTheme.typography.bodyRegular, isError: Boolean = false, + onClick: (() -> Unit)? = null, keyboardActions: KeyboardActions = KeyboardActions.Default, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, singleLine: Boolean = false, @@ -100,6 +103,7 @@ internal fun GrapesBaseTextField( modifier = modifier, helperText = helperText, enabled = enabled, + readOnly = readOnly, textStyle = textStyle, isError = isError, keyboardActions = keyboardActions, @@ -122,8 +126,10 @@ internal fun GrapesBaseTextField( modifier: Modifier = Modifier, helperText: String? = null, enabled: Boolean = true, + readOnly: Boolean = false, textStyle: TextStyle = GrapesTheme.typography.bodyRegular, isError: Boolean = false, + onClick: (() -> Unit)? = null, keyboardActions: KeyboardActions = KeyboardActions.Default, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, singleLine: Boolean = false, @@ -137,6 +143,11 @@ internal fun GrapesBaseTextField( val textColor = colors.textColor(enabled).value val mergedTextStyle = textStyle.merge(TextStyle(color = textColor)) + val isClickable = onClick != null && readOnly + if (isClickable && interactionSource.collectIsPressedAsState().value) { + onClick?.invoke() + } + Column( modifier = modifier.width(IntrinsicSize.Min) ) { @@ -146,6 +157,7 @@ internal fun GrapesBaseTextField( modifier = Modifier, onValueChange = onValueChange, enabled = enabled, + readOnly = readOnly, textStyle = mergedTextStyle, isError = isError, keyboardActions = keyboardActions, @@ -210,6 +222,7 @@ private fun GrapesCoreTextField( onValueChange: (TextFieldValue) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + readOnly: Boolean = false, textStyle: TextStyle = TextStyle.Default, isError: Boolean = false, keyboardActions: KeyboardActions = KeyboardActions.Default, @@ -237,6 +250,7 @@ private fun GrapesCoreTextField( shape = GrapesTextFieldDefaults.TextFieldShape ), enabled = enabled, + readOnly = readOnly, value = value, onValueChange = onValueChange, textStyle = textStyle, diff --git a/library-compose/src/main/java/com/spendesk/grapes/compose/textfield/GrapesTextInput.kt b/library-compose/src/main/java/com/spendesk/grapes/compose/textfield/GrapesTextInput.kt index a46e939f..4c0a3683 100644 --- a/library-compose/src/main/java/com/spendesk/grapes/compose/textfield/GrapesTextInput.kt +++ b/library-compose/src/main/java/com/spendesk/grapes/compose/textfield/GrapesTextInput.kt @@ -16,6 +16,10 @@ import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Scaffold +import androidx.compose.material.ScaffoldState +import androidx.compose.material.SnackbarDuration +import androidx.compose.material.rememberScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Switch import androidx.compose.material3.Text @@ -23,6 +27,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -35,6 +40,9 @@ import androidx.compose.ui.unit.dp import com.spendesk.grapes.compose.R import com.spendesk.grapes.compose.icons.GrapesIcon import com.spendesk.grapes.compose.theme.GrapesTheme +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch /** * @author jean-philippe @@ -49,9 +57,11 @@ fun GrapesTextInput( modifier: Modifier = Modifier, helperText: String? = null, enabled: Boolean = true, + readOnly: Boolean = false, textStyle: TextStyle = GrapesTheme.typography.bodyRegular, colors: GrapesTextFieldColors = GrapesTextFieldDefaults.textFieldColors(), isError: Boolean = false, + onClick: (() -> Unit)? = null, keyboardActions: KeyboardActions = KeyboardActions.Default, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, visualTransformation: VisualTransformation = VisualTransformation.None, @@ -59,15 +69,17 @@ fun GrapesTextInput( trailingIcon: @Composable (() -> Unit)? = null, ) { GrapesBaseTextField( + modifier = modifier, value = value, placeholderValue = placeholderValue, onValueChange = onValueChange, - modifier = modifier, helperText = helperText, enabled = enabled, + readOnly = readOnly, textStyle = textStyle, colors = colors, isError = isError, + onClick = onClick, keyboardActions = keyboardActions, keyboardOptions = keyboardOptions, singleLine = true, @@ -86,9 +98,11 @@ fun GrapesTextInput( modifier: Modifier = Modifier, helperText: String? = null, enabled: Boolean = true, + readOnly: Boolean = false, textStyle: TextStyle = GrapesTheme.typography.bodyRegular, colors: GrapesTextFieldColors = GrapesTextFieldDefaults.textFieldColors(), isError: Boolean = false, + onClick: (() -> Unit)? = null, keyboardActions: KeyboardActions = KeyboardActions.Default, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, visualTransformation: VisualTransformation = VisualTransformation.None, @@ -96,15 +110,17 @@ fun GrapesTextInput( trailingIcon: @Composable (() -> Unit)? = null, ) { GrapesBaseTextField( + modifier = modifier, value = value, placeholderValue = placeholderValue, onValueChange = onValueChange, - modifier = modifier, helperText = helperText, enabled = enabled, + readOnly = readOnly, textStyle = textStyle, colors = colors, isError = isError, + onClick = onClick, keyboardActions = keyboardActions, keyboardOptions = keyboardOptions, singleLine = true, @@ -122,7 +138,7 @@ fun GrapesTextInput( fun PreviewGrapesTextField() { var isEnabled by remember { mutableStateOf(true) } - + var isReadOnly by remember { mutableStateOf(false) } var showLeadingIcon by remember { mutableStateOf(false) } var showTrailingIcon by remember { mutableStateOf(false) } var isError by remember { mutableStateOf(false) } @@ -140,107 +156,147 @@ fun PreviewGrapesTextField() { var hasHelperText by remember { mutableStateOf(false) } var canToggleError by remember { mutableStateOf(isEnabled) } + val leadingIcon = @Composable { + GrapesIcon(icon = R.drawable.ic_neutral, Modifier.size(18.dp)) + } + + val trailingIcon = @Composable { + GrapesIcon(icon = R.drawable.ic_success, Modifier.size(18.dp)) + } + + val scaffoldState = rememberScaffoldState() + val coroutineScope = rememberCoroutineScope() + GrapesTheme { - Column( - modifier = Modifier - .fillMaxSize() - .background(GrapesTheme.colors.mainBackground) - .verticalScroll(rememberScrollState()) - .padding(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalAlignment = Alignment.Start, + Scaffold( + modifier = Modifier.padding(8.dp), + scaffoldState = scaffoldState ) { - Column( modifier = Modifier - .padding(bottom = 12.dp) - .fillMaxWidth() - .background(GrapesTheme.colors.mainNeutralLight) - .padding(8.dp) + .fillMaxSize() + .background(GrapesTheme.colors.mainBackground) + .verticalScroll(rememberScrollState()) + .padding(it), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.Start, + ) { + Column( + modifier = Modifier + .padding(bottom = 12.dp) + .fillMaxWidth() + .background(GrapesTheme.colors.mainNeutralLight) + .padding(8.dp) - ) { - PreviewOptionsRow( - options = { - PreviewRowOptionSwitch( - label = "Show text input value", - isChecked = hasTextValue, - onCheckedChange = { isChecked -> - hasTextValue = isChecked - textFieldValue = TextFieldValue( - text = "This is a text input value".takeIf { isChecked }.orEmpty() - ) - } - ) - PreviewRowOptionSwitch( - label = "Show helper text", - isChecked = hasHelperText, - onCheckedChange = { isChecked -> - hasHelperText = isChecked - helperText = "Helper text".takeIf { isChecked }.orEmpty() - } - ) - } - ) + ) { + PreviewOptionsRow( + options = { + PreviewRowOptionSwitch( + label = "Show text input value", + isChecked = hasTextValue, + onCheckedChange = { isChecked -> + hasTextValue = isChecked + textFieldValue = TextFieldValue( + text = "This is a text input value".takeIf { isChecked }.orEmpty() + ) + } + ) - PreviewOptionsRow( - options = { - PreviewRowOptionSwitch( - label = "Is Enabled", - isChecked = isEnabled, - onCheckedChange = { isChecked -> - canToggleError = isChecked - if (isChecked.not() && isError) { - isError = false + PreviewRowOptionSwitch( + label = "Show helper text", + isChecked = hasHelperText, + onCheckedChange = { isChecked -> + hasHelperText = isChecked + helperText = "Helper text".takeIf { isChecked }.orEmpty() } + ) + } + ) - isEnabled = isChecked - } - ) + PreviewOptionsRow( + options = { + PreviewRowOptionSwitch( + label = "Is Enabled", + isChecked = isEnabled, + onCheckedChange = { isChecked -> + canToggleError = isChecked + if (isChecked.not() && isError) { + isError = false + } - PreviewRowOptionSwitch( - label = "Is Error", - isEnable = canToggleError, - isChecked = isError, - onCheckedChange = { isChecked -> isError = isChecked } - ) - } - ) + isEnabled = isChecked + } + ) + + PreviewRowOptionSwitch( + label = "Is Error", + isEnable = canToggleError, + isChecked = isError, + onCheckedChange = { isChecked -> isError = isChecked } + ) + } + ) + + PreviewOptionsRow( + options = { + PreviewRowOptionSwitch( + label = "Leading Icon", + isChecked = showLeadingIcon, + onCheckedChange = { isChecked -> showLeadingIcon = isChecked } + ) - PreviewOptionsRow( - options = { - PreviewRowOptionSwitch( - label = "Leading Icon", - isChecked = showLeadingIcon, - onCheckedChange = { isChecked -> showLeadingIcon = isChecked } - ) + PreviewRowOptionSwitch( + label = "Trailing Icon", + isChecked = showTrailingIcon, + onCheckedChange = { isChecked -> showTrailingIcon = isChecked } + ) + } + ) - PreviewRowOptionSwitch( - label = "Trailing Icon", - isChecked = showTrailingIcon, - onCheckedChange = { isChecked -> showTrailingIcon = isChecked } - ) + PreviewOptionsRow( + options = { + PreviewRowOptionSwitch( + label = "Read Only", + isChecked = isReadOnly, + onCheckedChange = { isChecked -> isReadOnly = isChecked } + ) + } + ) + } + + // ---- + GrapesTextInput( + modifier = Modifier.fillMaxWidth(), + value = textFieldValue, + placeholderValue = "This is a placeholder", + enabled = isEnabled, + readOnly = isReadOnly, + helperText = helperText, + isError = isError, + onValueChange = { textFieldValue = it }, + leadingIcon = leadingIcon.takeIf { showLeadingIcon }, + trailingIcon = trailingIcon.takeIf { showTrailingIcon }, + onClick = { + showOnClickSnackBar(coroutineScope, scaffoldState) } ) } - - // ---- - GrapesTextInput( - modifier = Modifier.fillMaxWidth(), - value = textFieldValue, - placeholderValue = "This is a placeholder", - enabled = isEnabled, - helperText = helperText, - isError = isError, - onValueChange = { textFieldValue = it }, - leadingIcon = { if (showLeadingIcon) GrapesIcon(icon = R.drawable.ic_block, Modifier.size(18.dp)) }, - trailingIcon = { if (showTrailingIcon) GrapesIcon(icon = R.drawable.ic_block, Modifier.size(18.dp)) } - ) } } } +private fun showOnClickSnackBar(coroutineScope: CoroutineScope, scaffoldState: ScaffoldState) { + coroutineScope.launch(Dispatchers.Default) { + scaffoldState.snackbarHostState.showSnackbar( + message = "Click Click (pan pan pan)", + duration = SnackbarDuration.Short, + actionLabel = "Dismiss" + ) + } +} + @Composable private fun PreviewOptionsRow( options: @Composable RowScope.() -> Unit, diff --git a/library/src/main/java/com/spendesk/grapes/bottomsheet/searchable/SearchableBottomSheetDialogFragment.kt b/library/src/main/java/com/spendesk/grapes/bottomsheet/searchable/SearchableBottomSheetDialogFragment.kt index cd1a7b8a..fcd5ee31 100644 --- a/library/src/main/java/com/spendesk/grapes/bottomsheet/searchable/SearchableBottomSheetDialogFragment.kt +++ b/library/src/main/java/com/spendesk/grapes/bottomsheet/searchable/SearchableBottomSheetDialogFragment.kt @@ -9,7 +9,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import androidx.fragment.app.FragmentActivity +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment @@ -96,6 +97,7 @@ class SearchableBottomSheetDialogFragment : BottomSheetDialogFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { binding = FragmentBottomSheetSearchableBinding.inflate(inflater, container, false) + return binding!!.root } @@ -103,11 +105,26 @@ class SearchableBottomSheetDialogFragment : BottomSheetDialogFragment() { super.onViewCreated(view, savedInstanceState) setupView(view) + setupInsets() bindView() Handler(Looper.getMainLooper()).postDelayed({ view.forceHideKeyboard() }, 50) } + private fun setupInsets() { + dialog?.window?.decorView?.setOnApplyWindowInsetsListener { _, windowInsets -> + val bottomPaddingConsideringBottomInsets = WindowInsetsCompat + .toWindowInsetsCompat(windowInsets) + .getInsets(WindowInsetsCompat.Type.ime()) + .bottom + + binding?.root?.updatePadding( + bottom = bottomPaddingConsideringBottomInsets + ) + windowInsets + } + } + override fun onDestroyView() { super.onDestroyView() diff --git a/sample/src/main/java/com/spendesk/grapes/samples/home/fragments/BottomSheetsFragment.kt b/sample/src/main/java/com/spendesk/grapes/samples/home/fragments/BottomSheetsFragment.kt index bdecbb4d..ed114536 100644 --- a/sample/src/main/java/com/spendesk/grapes/samples/home/fragments/BottomSheetsFragment.kt +++ b/sample/src/main/java/com/spendesk/grapes/samples/home/fragments/BottomSheetsFragment.kt @@ -56,7 +56,19 @@ class BottomSheetsFragment : Fragment(R.layout.fragment_home_bottom_sheets) { SimpleListModel.Item(id = "1", configuration = item1Configuration), SimpleListModel.Item(id = "2", configuration = item2Configuration), SimpleListModel.Item(id = "3", configuration = item3Configuration), - SimpleListModel.Item(id = "4", configuration = item4Configuration) + SimpleListModel.Item(id = "4", configuration = item4Configuration), + SimpleListModel.Item(id = "5", configuration = item1Configuration), + SimpleListModel.Item(id = "6", configuration = item2Configuration), + SimpleListModel.Item(id = "7", configuration = item3Configuration), + SimpleListModel.Item(id = "8", configuration = item4Configuration), + SimpleListModel.Item(id = "9", configuration = item1Configuration), + SimpleListModel.Item(id = "10", configuration = item2Configuration), + SimpleListModel.Item(id = "11", configuration = item3Configuration), + SimpleListModel.Item(id = "12", configuration = item4Configuration), + SimpleListModel.Item(id = "13", configuration = item1Configuration), + SimpleListModel.Item(id = "14", configuration = item2Configuration), + SimpleListModel.Item(id = "15", configuration = item3Configuration), + SimpleListModel.Item(id = "16", configuration = item4Configuration) ) ) )