Jetpack Compose permissions bug - android-jetpack-compose

Trying to request READ_EXTERNAL_STORAGE to select an image from the users gallery permission but the permissions launcher automatically hits the denied branch of the if statement without even attempting to open the permission dialog. This code works fine on Android 12 and below, but not on Android 13. Does anyone know what could be causing this issue?
#Composable
fun CreateComposable(
navigateToStorePreview: (String) -> Unit
) {
val viewModel = hiltViewModel<CreateViewModel>()
val state by viewModel.state.collectAsState()
LaunchedEffect(key1 = viewModel.effects) {
viewModel.effects.collect { effect ->
when (effect) {
}
}
}
CreateScreen(state = state, eventHandler = viewModel::postEvent)
}
#OptIn(ExperimentalCoilApi::class)
#Composable
internal fun CreateScreen(
state: CreateState,
eventHandler: (CreateEvent) -> Unit
) {
val context = LocalContext.current
val galleryLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? ->
uri?.let { eventHandler(CreateEvent.SetUriFromCamera(uri = uri)) }
}
val permissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
galleryLauncher.launch("image/*")
} else {
showToast(context, context.getString(R.string.permissions_denied_resolution))
}
}
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(bottom = 24.dp)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(1.dp))
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
modifier = Modifier
.widthIn(200.dp),
value = state.flashCardName,
onValueChange = {
eventHandler(CreateEvent.FlashCardNameUpdated(input = it))
},
colors = TextFieldDefaults.outlinedTextFieldColors(
backgroundColor = Color.Transparent,
focusedBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
cursorColor = WildBlueYonder
),
label = {
Text(
text = stringResource(id = R.string.enter_flash_card_name),
color = Color.LightGray
)
},
leadingIcon = {
Image(
painter = painterResource(id = R.drawable.add),
contentDescription = null,
colorFilter = ColorFilter.tint(WildBlueYonder)
)
},
textStyle = TextStyle(
color = WildBlueYonder,
fontFamily = Baloo2,
fontSize = 16.sp
),
shape = RoundedCornerShape(16.dp),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password
)
)
if (state.imageUri.isEmpty()) {
Box(
modifier = Modifier
.padding(horizontal = 16.dp)
.height(300.dp)
.width(200.dp)
.clip(RoundedCornerShape(16.dp))
.border(4.dp, WildBlueYonder, RoundedCornerShape(16.dp))
.clickable {
when (ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_EXTERNAL_STORAGE
)) {
PackageManager.PERMISSION_GRANTED -> {
galleryLauncher.launch("image/*")
}
else -> {
permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
}
}
}
) {
Image(
modifier = Modifier.align(Alignment.Center),
painter = painterResource(id = R.drawable.add),
contentDescription = null,
colorFilter = ColorFilter.tint(TeaGreen)
)
}
} else {
Image(
modifier = Modifier
.height(300.dp)
.width(200.dp),
painter = rememberImagePainter(data = state.imageUri.toUri()),
contentDescription = null
)
}
}
BouncyButton(
modifier = Modifier.fillMaxWidth(),
enabled = true,
text = stringResource(id = R.string.bottom_bar_create),
onClick = {
eventHandler(CreateEvent.CreateClicked)
}
)
}
}

Related

Jetpack Compose View Models not updating view

I've created a view model in Jetpack Compose, but the view doesn't refresh when a variable updates inside it.
I've followed the view model guide here: https://developer.android.com/jetpack/compose/libraries#viewmodel
In the SignInWithGoogleView and SignInWithEmailAddressView, the signInOrSignUpMethod variable updates when I click on the ClickableText, but the SignInOrSignUpView doesn't update.
What am I missing?
enum class SignInOrSignUpMethod {
Google,
EmailOnly
}
class SignInOrSignUpViewModel: ViewModel() {
var signInOrSignUpMethod: SignInOrSignUpMethod = SignInOrSignUpMethod.Google
}
//SignInOrSignUpView
#Composable
fun SignInOrSignUpView(signInOrSignUpViewModel: SignInOrSignUpViewModel = viewModel()) {
Box {
//Background image
Image(
painter = painterResource(id = R.drawable.sign_in_sign_up_background),
contentDescription = null,
contentScale = ContentScale.FillBounds,
alpha = 0.35F,
modifier = Modifier
.background(Color.White)
)
Column(
modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
//App Logo
Image(
painter = painterResource(id = R.drawable.app_logo),
contentDescription = null,
colorFilter = ColorFilter.tint(
(if (isSystemInDarkTheme()) {
Color.White
} else {
Color.Black
})
),
modifier = Modifier
.size(125.dp, 125.dp)
.offset(0.dp, 50.dp)
)
Spacer(modifier = Modifier.height(150.dp))
//Sign In Text
Text(
text = "Sign In",
style = androidx.compose.ui.text.TextStyle(
fontSize = 30.sp,
fontWeight = FontWeight.Bold
)
)
Spacer(modifier = Modifier.weight(1f))
//Sign In Buttons
if (signInOrSignUpViewModel.signInOrSignUpMethod == SignInOrSignUpMethod.Google) {
SignInWithGoogleView()
} else if (signInOrSignUpViewModel.signInOrSignUpMethod == SignInOrSignUpMethod.EmailOnly) {
SignInWithEmailAddressView()
}
}
}
}
#Composable
fun SignInWithGoogleView(signInOrSignUpViewModel: SignInOrSignUpViewModel = viewModel()) {
Text(
text = "Google Sign In",
style = androidx.compose.ui.text.TextStyle(
fontSize = 30.sp,
)
)
ClickableText(
text = AnnotatedString("Continue with email address"),
onClick = {
Log.d("Print", signInOrSignUpViewModel.signInOrSignUpMethod.toString())
signInOrSignUpViewModel.signInOrSignUpMethod = SignInOrSignUpMethod.EmailOnly
}
)
}
#Composable
fun SignInWithEmailAddressView(signInOrSignUpViewModel: SignInOrSignUpViewModel = viewModel()) {
Text(
text = "Email Sign In",
style = androidx.compose.ui.text.TextStyle(
fontSize = 30.sp,
)
)
ClickableText(
text = AnnotatedString("Continue with Google"),
onClick = {
signInOrSignUpViewModel.signInOrSignUpMethod = SignInOrSignUpMethod.Google
}
)
}
This bit I was missing was in my ViewModel to declare mutableStateOf:
class SignInOrSignUpViewModel: ViewModel() {
var signInOrSignUpMethod: SignInOrSignUpMethod by mutableStateOf(SignInOrSignUpMethod.Google)
}

Jetpack Compose animation is jumping

I'm trying to add a fade in/fade out animation to switch between 2 views but the animation is jumping when I run it on the simulator.
I've added x2 AnimatedVisibility to switch between each of the views in my SignInOrSignUpMasterView.
How can I fix this?
class SignInOrSignUp : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
Surface(modifier = Modifier.fillMaxSize()) {
SignInOrSignUpMasterView()
}
}
}
}
}
//--------------------------------------------------------------------------------------------------
//SignInOrSignUpMethod enum
enum class SignInOrSignUpMethod {
Google,
EmailOnly
}
//--------------------------------------------------------------------------------------------------
//SignInOrSignUpViewModel
class SignInOrSignUpViewModel : ViewModel() {
var signInOrSignUpMethod: SignInOrSignUpMethod by mutableStateOf(SignInOrSignUpMethod.EmailOnly)
}
//--------------------------------------------------------------------------------------------------
//SignInOrSignUpMasterView
#Composable
fun SignInOrSignUpMasterView(
signInOrSignUpViewModel: SignInOrSignUpViewModel = viewModel()
) {
Box {
//Background image
Image(
painter = painterResource(id = R.drawable.sign_in_sign_up_background),
contentDescription = null,
contentScale = ContentScale.Crop,
alpha = 0.50F,
modifier = Modifier
.background(Color.White)
.fillMaxSize()
)
Column(
modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
//App Logo
Image(
painter = painterResource(id = R.drawable.app_logo),
contentDescription = null,
colorFilter = ColorFilter.tint(
(if (isSystemInDarkTheme()) {
Color.Black
} else {
Color.White
})
),
modifier = Modifier
.size(125.dp, 125.dp)
.offset(0.dp, 50.dp)
)
//Spacer to fill the rest of screen to position the buttons at the bottom of the screen
Spacer(modifier = Modifier.weight(1f))
//Sign In/Up Views
AnimatedVisibility(
visible = signInOrSignUpViewModel.signInOrSignUpMethod == SignInOrSignUpMethod.Google,
enter = fadeIn(),
exit = fadeOut()
) {
SignInOrSignUpWithGoogleView()
}
AnimatedVisibility(
visible = signInOrSignUpViewModel.signInOrSignUpMethod == SignInOrSignUpMethod.EmailOnly,
enter = fadeIn(),
exit = fadeOut()
) {
SignInOrSignUpWithEmailAddressView()
}
Spacer(Modifier.height(10.dp))
Text("Legal information")
Spacer(Modifier.height(40.dp))
}
}
}
//--------------------------------------------------------------------------------------------------
//SignInOrSignUpWithGoogleView
#Composable
fun SignInOrSignUpWithGoogleView(
signInOrSignUpViewModel: SignInOrSignUpViewModel = viewModel()
) {
Column(
modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
Text("Sign in with Google view")
ClickableText(
text = AnnotatedString("Continue with email address"),
onClick = {
signInOrSignUpViewModel.signInOrSignUpMethod = SignInOrSignUpMethod.EmailOnly
},
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Medium,
color = if (isSystemInDarkTheme()) {
Color.Black
} else {
Color.White
}
)
)
}
}
//--------------------------------------------------------------------------------------------------
//SignInOrSignUpWithEmailAddressView
#Composable
fun SignInOrSignUpWithEmailAddressView(
signInOrSignUpViewModel: SignInOrSignUpViewModel = viewModel()
) {
Column(
modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
Text("Sign in with email address view")
ClickableText(
text = AnnotatedString("Continue with Google"),
onClick = {
signInOrSignUpViewModel.signInOrSignUpMethod = SignInOrSignUpMethod.Google
},
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Medium,
color = if (isSystemInDarkTheme()) {
Color.Black
} else {
Color.White
}
)
)
}
}
You Should Wrap your animated visibility in a box because if they are in a column the column will allocate space for the showing view while the other one is fading away resulting in a janky animation.
Box {
// Wrap your Animated Visibility in a Box
// Sign In/Up Views
androidx.compose.animation.AnimatedVisibility(
visible = signInOrSignUpViewModel.signInOrSignUpMethod == SignInOrSignUpMethod.Google,
enter = fadeIn(),
exit = fadeOut()
) {
SignInOrSignUpWithGoogleView()
}
androidx.compose.animation.AnimatedVisibility(
visible = signInOrSignUpViewModel.signInOrSignUpMethod == SignInOrSignUpMethod.EmailOnly,
enter = fadeIn(),
exit = fadeOut()
) {
SignInOrSignUpWithEmailAddressView()
}
}
Update
Box {
val googleAlpha by animateFloatAsState(
if (signInOrSignUpViewModel.signInOrSignUpMethod == SignInOrSignUpMethod.Google) 1f
else 0f
)
val emailAlpha by animateFloatAsState(
if (signInOrSignUpViewModel.signInOrSignUpMethod == SignInOrSignUpMethod.EmailOnly) 1f
else 0f
)
SignInOrSignUpWithGoogleView(
modifier = Modifier.alpha(googleAlpha)
)
SignInOrSignUpWithEmailAddressView(
modifier = Modifier.alpha(emailAlpha)
)
}

Alarm runs immediately instead of specified time with jetpack compose

I am trying to set an alarm using a TimePicker but the alarm ignores the time sent by parameter and instead it is executed immediately !!
This is my code:
class MainActivity : ComponentActivity() {
lateinit var navController: NavHostController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
navController = rememberNavController()
NotePadReminderTheme {
SetupNavGraph(navController = navController)
}
}
}
}
#RequiresApi(Build.VERSION_CODES.M)
fun createAlarm(context: Context,time:Long){
val alarmManager =context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
val intent =Intent(context, AlarmReceiver::class.java)
Log.d("receivedtime","time $time")
val pendingIntent = PendingIntent.getBroadcast(context, Date().seconds, intent, PendingIntent.FLAG_IMMUTABLE)
alarmManager?.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pendingIntent)
Toast.makeText(context,"Alarma creada",Toast.LENGTH_LONG).show()
}
#RequiresApi(Build.VERSION_CODES.M)
#Composable
fun AddPage(navController: NavController, vm: TaskViewModel = viewModel()) {
val scope= rememberCoroutineScope()
val title = remember {
mutableStateOf("")
}
val description = remember {
mutableStateOf("")
}
val date = remember {
mutableStateOf("")
}
val time = Calendar.getInstance()
val iscompleted = remember {
mutableStateOf(false)
}
val context = LocalContext.current
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = 30.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Agregar Tarea",
style = NoteTheme.typography.h1
)
RowOfThis(title, "Título", true)
RowOfThis(description, "Descripción", false)
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
DatePick(date)
}
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
TimePick(time)
}
Button(
onClick = {
if (title.value.isNullOrBlank() || description.value.isNullOrBlank() ||
date.value.isNullOrBlank()
) {
Log.d("date","date:${date.value}")
Toast.makeText(context, "Por favor ingrese todos los datos", Toast.LENGTH_LONG)
.show()
return#Button
}
vm.task.value.title = title.value
vm.task.value.description = description.value
vm.task.value.date = date.value
vm.task.value.time = time.timeInMillis
Log.d("tmepickerreceived","date:${time.timeInMillis}")
scope.launch {
vm.createTask(context, vm.task.value)
createAlarm(context, time=time.timeInMillis). //here set the alarm ****************
Toast.makeText(context, "Tarea creada correctamente", Toast.LENGTH_LONG).show()
vm.resetTask()
}
},
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Blue)
) {
Text(text = "Guardar", color = Color.White, style = NoteTheme.typography.subtitle)
}
}
}
#Composable
fun RowOfThis(value: MutableState<String>, label: String, single: Boolean) {
val focusManager = LocalFocusManager.current
Row(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
TextField(
modifier = Modifier
.fillMaxWidth()
.border(BorderStroke(width = 1.dp, color = Color.Black)),
singleLine = single,
maxLines = 3,
textStyle = NoteTheme.typography.body,
value = value.value, onValueChange = {
value.value = it
}, label = {
Text(
text = label, textAlign = TextAlign.Start,
color = Color.DarkGray,
style = NoteTheme.typography.subtitle
)
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White),
keyboardActions = KeyboardActions(onNext =
{ focusManager.moveFocus((FocusDirection.Next)) })
)
}
}
#Composable
fun DatePick(mDate:MutableState<String>) {
val mContext = LocalContext.current
val mYear: Int
val mMonth: Int
val mDay: Int
val mCalendar = Calendar.getInstance()
mYear = mCalendar.get(Calendar.YEAR)
mMonth = mCalendar.get(Calendar.MONTH)
mDay = mCalendar.get(Calendar.DAY_OF_MONTH)
mCalendar.time = Date()
val mDatePickerDialog = DatePickerDialog(
mContext,
{ _: DatePicker, mYear: Int, mMonth: Int, mDayOfMonth: Int ->
mDate.value = "$mDayOfMonth/${mMonth + 1}/$mYear"
}, mYear, mMonth, mDay
)
mDatePickerDialog.datePicker.minDate=Date().time
Log.d("datepicker","dateset:${mDate.value}")
Button(modifier = Modifier
.padding(7.dp)
.fillMaxWidth(0.5f), onClick = {
mDatePickerDialog.show()
}, colors = ButtonDefaults.buttonColors(backgroundColor = Color(0XFF227093))) {
Text(text = "Seleccionar fecha",
color = Color.White, style = NoteTheme.typography.subtitle)
}
Text(text = "${mDate.value}",style = NoteTheme.typography.caption, fontSize = 22.sp, color = Color(0XFF227093))
}
#Composable
fun TimePick(mCalendar:Calendar) {
val mContext = LocalContext.current
val mHour = mCalendar[Calendar.HOUR_OF_DAY]
val mMinute = mCalendar[Calendar.MINUTE]
val mTime = remember { mutableStateOf("") }
val mTimePickerDialog = TimePickerDialog(
mContext,
{_, mHour : Int, mMinute: Int ->
mTime.value = "$mHour:$mMinute"
}, mHour, mMinute, false
)
Log.d("tmepicker","time:${mCalendar.timeInMillis}")
Button(modifier = Modifier
.padding(7.dp)
.fillMaxWidth(0.5f), onClick = {
mTimePickerDialog.show()
}, colors = ButtonDefaults.buttonColors(backgroundColor = Color(0XFF2c2c54))) {
Text(text = "Seleccionar hora",
color = Color.White, style = NoteTheme.typography.subtitle)
}
Text(text = "${mTime.value}",style = NoteTheme.typography.caption, fontSize = 22.sp, color = Color(0XFF2c2c54))
}
The problem I have is that I want the alarm to run at the scheduled time of the DatePicker ad TimePicker but it is running immediately !!
I don't know if this has something to do with the context, but how can I make the alarm be "scheduled" at the time and date of the DatePicker and Timepicker instead of firing at once
Well this was because when you get the calendar month you have to add one for some reason, so that was what caused an inaccurate time when setting the alarm. Final code:
class MainActivity : ComponentActivity() {
#RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val context= LocalContext.current
AlarmTestTheme {
mainPage(context)
}
}
}
}
#RequiresApi(Build.VERSION_CODES.M)
#Composable
fun mainPage(context: Context) {
val scope= rememberCoroutineScope()
val calendar=Calendar.getInstance().apply {
timeInMillis=System.currentTimeMillis()
}
val title = remember {
mutableStateOf("")
}
val description = remember {
mutableStateOf("")
}
val year= remember {
mutableStateOf(calendar.get(Calendar.YEAR))
}
val month= remember {
mutableStateOf(calendar.get(Calendar.MONTH)+1)
}
val day= remember {
mutableStateOf(calendar.get(Calendar.DAY_OF_MONTH))
}
val hour= remember {
mutableStateOf(calendar.get(Calendar.HOUR_OF_DAY))
}
val minutes= remember {
mutableStateOf(calendar.get(Calendar.MINUTE))
}
val iscompleted = remember {
mutableStateOf(false)
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = 30.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Agregar Tarea"
)
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
DatePick(context = context, year = year, month = month, day = day)
}
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
TimePick(context = context, hours = hour, minutes = minutes)
}
Button(
onClick = {
Log.d("tmepickerreceived","date and time :${year.value}/${month.value}/${day.value} : ${hour.value}:${minutes.value}")
calendar.apply {
set(Calendar.YEAR,year.value)
set(Calendar.MONTH,(month.value-1))
set(Calendar.DAY_OF_MONTH,day.value)
set(Calendar.HOUR_OF_DAY,hour.value)
set(Calendar.MINUTE,minutes.value)
}
val alarmMgr= context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val shuff=(1..99999).shuffled().first()
val intent= Intent(context,AlarmReceiver::class.java)
intent.action="alarma"
intent.putExtra("ALARMA_STRING","alarm number: $shuff")
val pending=
PendingIntent.getBroadcast(context,shuff,intent, PendingIntent.FLAG_IMMUTABLE)
Log.e("time final","compare ${System.currentTimeMillis()}" +
" mytime: ${calendar.timeInMillis} " +
"hour: ${calendar.get(Calendar.HOUR_OF_DAY)} " +
"minute: ${calendar.get(Calendar.MINUTE)} " +
"year: ${calendar.get(Calendar.YEAR)} " +
"month: ${calendar.get(Calendar.MONTH)+1} " +
"day: ${calendar.get(Calendar.DAY_OF_MONTH)}")
alarmMgr?.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,calendar.timeInMillis,pending)
Toast.makeText(context, "Alarm created", Toast.LENGTH_LONG)
.show()
},
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Blue)
) {
Text(text = "Guardar", color = Color.White)
}
}
}
#Composable
fun DatePick(context: Context, year: MutableState<Int>,month: MutableState<Int>,day: MutableState<Int>) {
val mDatePickerDialog = DatePickerDialog(
context,{d,mYear,mMonth,mDay ->
year.value=mYear
month.value=mMonth+1
day.value=mDay
}, year.value, month.value, day.value
)
mDatePickerDialog.datePicker.minDate=System.currentTimeMillis() - 1000
Log.d("datepicker","${day.value}/${month.value}/${year.value}")
Button(modifier = Modifier
.padding(7.dp)
.fillMaxWidth(0.5f), onClick = {
mDatePickerDialog.show()
}, colors = ButtonDefaults.buttonColors(backgroundColor = Color(0XFF227093))) {
Text(text = "Seleccionar fecha",
color = Color.White)
}
Text(text = "${day.value}/${month.value}/${year.value}", fontSize = 22.sp, color = Color(0XFF227093))
}
#Composable
fun TimePick(context: Context,hours:MutableState<Int>,minutes:MutableState<Int>) {
val mTimePickerDialog = TimePickerDialog(
context,
{_, mHour : Int, mMinute: Int ->
hours.value=mHour
minutes.value=mMinute
}, hours.value, minutes.value, false
)
Log.d("tmepicker","hour selected :${hours.value}: munute selected: ${minutes.value}")
Button(modifier = Modifier
.padding(7.dp)
.fillMaxWidth(0.5f), onClick = {
mTimePickerDialog.show()
}, colors = ButtonDefaults.buttonColors(backgroundColor = Color(0XFF2c2c54))) {
Text(text = "Seleccionar hora",
color = Color.White)
}
Text(text = "${hours.value}:${minutes.value}", fontSize = 22.sp, color = Color(0XFF2c2c54))
}

Is there a better way to wrap bottom sheet height for ModalBottomSheetLayout/BottomSheetScaffold

My solution works, but sometimes when switching between boxes using openSheet() height of content cannot be calculated in time and it's jumps when opens.
Working solution using ModalBottomSheetLayout:
val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
val density = LocalDensity.current
var topAppBarHeight = Dp.Unspecified
var sumHeight by remember { mutableStateOf(Dp.Unspecified) }
var currentSheet by remember { mutableStateOf(0) }
val openSheet: (sheet: Int) -> Unit = {
currentSheet = it
sumHeight = topAppBarHeight + (currentSheet * 50).dp
scope.launch { sheetState.show() }
}
val closeSheet: () -> Unit = {
scope.launch { sheetState.hide() }
sumHeight = Dp.Unspecified
}
ModalBottomSheetLayout(
modifier = Modifier.fillMaxSize(),
sheetState = sheetState,
sheetContent = {
Scaffold(
modifier = Modifier.height(sumHeight),
topBar = {
TopAppBar(
modifier = Modifier
.clickable(onClick = closeSheet)
.onGloballyPositioned { lc ->
topAppBarHeight = with(density) { lc.size.height.toDp() }
},
title = { Text("TopAppBarSheetContent") }
)
},
content = {
Box(
modifier = Modifier
.height((currentSheet * 50).dp)
.fillMaxWidth()
.background(Color.Blue)
)
},
)
},
content = {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(title = { Text("TopAppBarContent") })
},
content = { innerPadding ->
LazyColumn(
modifier = Modifier.padding(innerPadding),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(50) { item ->
Box(
modifier = Modifier
.clip(CircleShape)
.fillMaxWidth()
.height(75.dp)
.background(Color.Red)
.clickable { openSheet(item) }
)
}
}
}
)
}
)
Maybe there is a better way to solve this problem?

Call popup composable function inside onclick of Button in jetpack compose

Hi i want to show popup when something happens . I have this popup:
#Composable
fun popup(message:String,height:Dp,width:Dp,icon:String=""){
Column() {
val openDialog = remember { mutableStateOf(true) }
val dialogWidth = width/(1.3F)
val dialogHeight = height/2
if (openDialog.value) {
Dialog(onDismissRequest = { openDialog.value = false }) {
// Draw a rectangle shape with rounded corners inside the dialog
Box(
Modifier
.size(dialogWidth, dialogHeight)
.background(Color.White)){
Column(modifier = Modifier.fillMaxWidth().padding()) {
Text(text = message)
}
}
}
}
Button(onClick = {
openDialog.value=!openDialog.value
}) {
}
}
}
But i am trying to call him inside onclick Button event :
Button(modifier = Modifier
.padding(start = 6.dp, end = 6.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = Azul99),
onClick = {
if (vm.validateCredentials()=="ok"){
vm.createUser()
}else{
popup(vm.validateCredentials(),200.dp,200.dp,"fill")
}
},
shape = RoundedCornerShape(percent = 28)
) {
Text(text = "Registrarme",
modifier= Modifier.fillMaxWidth(),
style= TextStyle(fontWeight = FontWeight.Bold),
color= Color.White,
textAlign = TextAlign.Center)
}
and Android Studio says: "#Composable invocations can only happen from the context of a #Composable function" How can i call the popup ??
Store showPopUp boolean as state and show popUp by that state;
val showPopUp by remember { mutableStateOf(false)} // -> STATE
Button(
modifier = Modifier
.padding(start = 6.dp, end = 6.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = Azul99),
onClick = {
if (vm.validateCredentials()=="ok"){
vm.createUser()
}else{
showPopUp = !showPopUp // -> CHANGE IN HERE
}
},
shape = RoundedCornerShape(percent = 28)
) {
Text(
text = "Registrarme",
modifier= Modifier.fillMaxWidth(),
style= TextStyle(fontWeight = FontWeight.Bold),
color= Color.White,
textAlign = TextAlign.Center
)
}
if(showPopUp){
popup(vm.validateCredentials(),200.dp,200.dp,"fill") // -> SHOW HERE
}
Change

Resources