From 2adb7fe67946a5522b7ea57cab0c2df7ad1d293a Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Sun, 24 Mar 2024 17:56:40 +0530 Subject: [PATCH 1/3] Fix: Alarm Settings layout --- .../com/bnyro/clock/ui/components/AlarmRow.kt | 11 - .../clock/ui/components/AlarmSettingsSheet.kt | 302 ++++++++++-------- .../clock/ui/components/AlarmTimePicker.kt | 4 +- .../com/bnyro/clock/ui/model/AlarmModel.kt | 15 + 4 files changed, 180 insertions(+), 152 deletions(-) diff --git a/app/src/main/java/com/bnyro/clock/ui/components/AlarmRow.kt b/app/src/main/java/com/bnyro/clock/ui/components/AlarmRow.kt index c125b307..ebe999fa 100644 --- a/app/src/main/java/com/bnyro/clock/ui/components/AlarmRow.kt +++ b/app/src/main/java/com/bnyro/clock/ui/components/AlarmRow.kt @@ -141,17 +141,6 @@ fun AlarmRow(alarm: Alarm, alarmModel: AlarmModel) { onCheckedChange = { newValue -> alarm.enabled = newValue isEnabled = newValue - - if (isEnabled) { - val millisRemainingForAlarm = (AlarmHelper.getAlarmTime(alarm) - System.currentTimeMillis()) - val formattedDuration = TimeHelper.durationToFormatted(context, millisRemainingForAlarm.milliseconds) - Toast.makeText( - context, - context.resources.getString(R.string.alarm_will_play, formattedDuration), - Toast.LENGTH_SHORT - ).show() - } - alarmModel.updateAlarm(context, alarm) } ) diff --git a/app/src/main/java/com/bnyro/clock/ui/components/AlarmSettingsSheet.kt b/app/src/main/java/com/bnyro/clock/ui/components/AlarmSettingsSheet.kt index e71ded89..11d4798e 100644 --- a/app/src/main/java/com/bnyro/clock/ui/components/AlarmSettingsSheet.kt +++ b/app/src/main/java/com/bnyro/clock/ui/components/AlarmSettingsSheet.kt @@ -12,21 +12,24 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Label import androidx.compose.material.icons.rounded.Alarm import androidx.compose.material.icons.rounded.EventRepeat import androidx.compose.material.icons.rounded.Snooze import androidx.compose.material.icons.rounded.Vibration -import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -37,9 +40,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.compose.ui.window.DialogWindowProvider import com.bnyro.clock.R import com.bnyro.clock.obj.Alarm import com.bnyro.clock.ui.common.SwitchItem @@ -49,11 +56,9 @@ import com.bnyro.clock.ui.dialog.SnoozeTimePickerDialog import com.bnyro.clock.util.AlarmHelper import com.bnyro.clock.util.TimeHelper -@OptIn(ExperimentalMaterial3Api::class) @Composable fun AlarmSettingsSheet(onDismissRequest: () -> Unit, currentAlarm: Alarm, onSave: (Alarm) -> Unit) { val context = LocalContext.current - val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) var showRingtoneDialog by remember { mutableStateOf(false) } var showSnoozeDialog by remember { mutableStateOf(false) } @@ -72,154 +77,173 @@ fun AlarmSettingsSheet(onDismissRequest: () -> Unit, currentAlarm: Alarm, onSave val initialTime = remember { TimeHelper.millisToTime(currentAlarm.time) } var hours = remember { initialTime.hours } var minutes = remember { initialTime.minutes } - ModalBottomSheet(onDismissRequest, sheetState = sheetState) { - Column( - Modifier - .fillMaxSize() - .padding(horizontal = 8.dp, vertical = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceBetween - ) { - AlarmTimePicker( - hours, - minutes, - onHoursChanged = { - hours = it - }, - onMinutesChanged = { minutes = it } - ) - Column { - SwitchItem( - title = stringResource(R.string.repeat), - isChecked = repeat, - onClick = { newValue -> - repeat = newValue + Dialog( + onDismissRequest, + properties = DialogProperties( + usePlatformDefaultWidth = false, + dismissOnClickOutside = false + ) + ) { + val window = (LocalView.current.parent as DialogWindowProvider).window + SideEffect { + window.setDimAmount(0f) + } + val scrollState = rememberScrollState() + Surface { + Column( + Modifier + .fillMaxSize() + .padding(horizontal = 8.dp, vertical = 16.dp) + .verticalScroll(scrollState), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceEvenly + ) { + AlarmTimePicker( + hours, + minutes, + onHoursChanged = { + hours = it }, - icon = Icons.Rounded.EventRepeat + onMinutesChanged = { minutes = it } ) - AnimatedVisibility(visible = repeat) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - val daysOfWeek = remember { - AlarmHelper.getDaysOfWeekByLocale(context) - } + Column { + SwitchItem( + title = stringResource(R.string.repeat), + isChecked = repeat, + onClick = { newValue -> + repeat = newValue + }, + icon = Icons.Rounded.EventRepeat + ) + AnimatedVisibility(visible = repeat) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + val daysOfWeek = remember { + AlarmHelper.getDaysOfWeekByLocale(context) + } - daysOfWeek.forEach { (day, index) -> - val enabled = chosenDays.contains(index) - Box( - modifier = Modifier - .size(30.dp) - .background( - if (enabled) MaterialTheme.colorScheme.primary else Color.Transparent, - CircleShape - ) - .clip(CircleShape) - .border( - if (enabled) 0.dp else 1.dp, - MaterialTheme.colorScheme.primary, - CircleShape + daysOfWeek.forEach { (day, index) -> + val enabled = chosenDays.contains(index) + Box( + modifier = Modifier + .size(30.dp) + .background( + if (enabled) MaterialTheme.colorScheme.primary else Color.Transparent, + CircleShape + ) + .clip(CircleShape) + .border( + if (enabled) 0.dp else 1.dp, + MaterialTheme.colorScheme.primary, + CircleShape + ) + .clickable { + if (enabled) { + if (chosenDays.size > 1) chosenDays.remove(index) + } else { + chosenDays.add( + index + ) + } + }, + contentAlignment = Alignment.Center + ) { + Text( + text = day, + color = if (enabled) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onPrimaryContainer ) - .clickable { - if (enabled) { - if (chosenDays.size > 1) chosenDays.remove(index) - } else { - chosenDays.add( - index - ) - } - }, - contentAlignment = Alignment.Center - ) { - Text( - text = day, - color = if (enabled) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onPrimaryContainer - ) + } } } } - } - Row( - modifier = Modifier.padding(8.dp, 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - OutlinedTextField( - modifier = Modifier.fillMaxWidth(), - value = label, - onValueChange = { - label = it + Row( + modifier = Modifier.padding(8.dp, 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = label, + onValueChange = { + label = it + }, + label = { + Text(text = stringResource(id = R.string.alarm_name)) + }, + singleLine = true, + leadingIcon = { + Icon(imageVector = Icons.Outlined.Label, contentDescription = null) + } + ) + } + SwitchWithDivider( + title = stringResource(R.string.sound), + description = soundName ?: stringResource(R.string.default_sound), + isChecked = soundEnabled, + icon = Icons.Rounded.Alarm, + onClick = { + showRingtoneDialog = true }, - label = { - Text(text = stringResource(id = R.string.alarm_name)) + onChecked = { + soundEnabled = it + } + ) + SwitchItem( + title = stringResource(R.string.vibrate), + isChecked = vibrationEnabled, + onClick = { newValue -> + vibrationEnabled = newValue + }, + icon = Icons.Rounded.Vibration + ) + SwitchWithDivider( + title = stringResource(R.string.snooze), + description = with(snoozeMinutes) { + pluralStringResource( + id = R.plurals.minutes, + count = this, + this + ) }, - singleLine = true, - leadingIcon = { - Icon(imageVector = Icons.Outlined.Label, contentDescription = null) + isChecked = snoozeEnabled, + icon = Icons.Rounded.Snooze, + onClick = { + showSnoozeDialog = true + }, + onChecked = { + snoozeEnabled = it } ) } - SwitchWithDivider( - title = stringResource(R.string.sound), - description = soundName ?: stringResource(R.string.default_sound), - isChecked = soundEnabled, - icon = Icons.Rounded.Alarm, - onClick = { - showRingtoneDialog = true - }, - onChecked = { - soundEnabled = it + Row( + Modifier.align(Alignment.End), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + OutlinedButton(onClick = { onDismissRequest.invoke() }) { + Text(text = stringResource(id = android.R.string.cancel)) } - ) - SwitchItem( - title = stringResource(R.string.vibrate), - isChecked = vibrationEnabled, - onClick = { newValue -> - vibrationEnabled = newValue - }, - icon = Icons.Rounded.Vibration - ) - SwitchWithDivider( - title = stringResource(R.string.snooze), - description = with(snoozeMinutes) { - pluralStringResource( - id = R.plurals.minutes, - count = this, - this - ) - }, - isChecked = snoozeEnabled, - icon = Icons.Rounded.Snooze, - onClick = { - showSnoozeDialog = true - }, - onChecked = { - snoozeEnabled = it + Button(onClick = { + val alarm = + currentAlarm.copy( + time = (hours * 60 + minutes) * 60 * 1000L, + label = label.takeIf { l -> l.isNotBlank() }, + days = chosenDays.sorted(), + vibrate = vibrationEnabled, + soundName = soundName, + soundUri = soundUri, + repeat = repeat, + snoozeEnabled = snoozeEnabled, + snoozeMinutes = snoozeMinutes, + soundEnabled = soundEnabled + ) + onSave(alarm) + onDismissRequest.invoke() + }) { + Text(text = stringResource(id = android.R.string.ok)) } - ) - } - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { - DialogButton(label = android.R.string.cancel) { - onDismissRequest.invoke() - } - DialogButton(label = android.R.string.ok) { - val alarm = - currentAlarm.copy( - time = (hours * 60 + minutes) * 60 * 1000L, - label = label.takeIf { l -> l.isNotBlank() }, - days = chosenDays.sorted(), - vibrate = vibrationEnabled, - soundName = soundName, - soundUri = soundUri, - repeat = repeat, - snoozeEnabled = snoozeEnabled, - snoozeMinutes = snoozeMinutes, - soundEnabled = soundEnabled - ) - onSave(alarm) - onDismissRequest.invoke() } } } diff --git a/app/src/main/java/com/bnyro/clock/ui/components/AlarmTimePicker.kt b/app/src/main/java/com/bnyro/clock/ui/components/AlarmTimePicker.kt index 68cacb7e..d31ed657 100644 --- a/app/src/main/java/com/bnyro/clock/ui/components/AlarmTimePicker.kt +++ b/app/src/main/java/com/bnyro/clock/ui/components/AlarmTimePicker.kt @@ -29,7 +29,7 @@ import androidx.compose.ui.unit.dp * @param onMinutesChanged New minutes value */ @Composable -fun ColumnScope.AlarmTimePicker( +fun AlarmTimePicker( initialHours: Int, initialMinutes: Int, onHoursChanged: (Int) -> Unit, @@ -41,7 +41,7 @@ fun ColumnScope.AlarmTimePicker( if (initialHours >= 12) Meridiem.PM else Meridiem.AM } Box( - modifier = Modifier.fillMaxWidth().weight(1f), + modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center ) { Row { diff --git a/app/src/main/java/com/bnyro/clock/ui/model/AlarmModel.kt b/app/src/main/java/com/bnyro/clock/ui/model/AlarmModel.kt index 1a1aa8ac..8641e45b 100644 --- a/app/src/main/java/com/bnyro/clock/ui/model/AlarmModel.kt +++ b/app/src/main/java/com/bnyro/clock/ui/model/AlarmModel.kt @@ -1,15 +1,18 @@ package com.bnyro.clock.ui.model import android.content.Context +import android.widget.Toast import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.bnyro.clock.R import com.bnyro.clock.db.DatabaseHolder import com.bnyro.clock.obj.Alarm import com.bnyro.clock.obj.AlarmFilters import com.bnyro.clock.util.AlarmHelper +import com.bnyro.clock.util.TimeHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -19,6 +22,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import java.util.Collections +import kotlin.time.Duration.Companion.milliseconds class AlarmModel : ViewModel() { var selectedAlarm: Alarm? by mutableStateOf(null) @@ -47,6 +51,17 @@ class AlarmModel : ViewModel() { } fun updateAlarm(context: Context, alarm: Alarm) { + if (alarm.enabled) { + val millisRemainingForAlarm = + (AlarmHelper.getAlarmTime(alarm) - System.currentTimeMillis()) + val formattedDuration = + TimeHelper.durationToFormatted(context, millisRemainingForAlarm.milliseconds) + Toast.makeText( + context, + context.resources.getString(R.string.alarm_will_play, formattedDuration), + Toast.LENGTH_SHORT + ).show() + } AlarmHelper.enqueue(context, alarm) viewModelScope.launch(Dispatchers.IO) { DatabaseHolder.instance.alarmsDao().update(alarm) From 8152aca4619465852649748776dae90e97bb925f Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Sun, 24 Mar 2024 18:25:00 +0530 Subject: [PATCH 2/3] Fix: Save button covering preset timers --- .../com/bnyro/clock/ui/screens/TimerScreen.kt | 22 ++++++++++++------- app/src/main/res/values/strings.xml | 1 + 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/bnyro/clock/ui/screens/TimerScreen.kt b/app/src/main/java/com/bnyro/clock/ui/screens/TimerScreen.kt index 2e7bfcaa..311c0aaf 100644 --- a/app/src/main/java/com/bnyro/clock/ui/screens/TimerScreen.kt +++ b/app/src/main/java/com/bnyro/clock/ui/screens/TimerScreen.kt @@ -23,8 +23,11 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Create +import androidx.compose.material.icons.filled.FilterAlt import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Save +import androidx.compose.material.icons.rounded.Add +import androidx.compose.material.icons.rounded.AddAlarm import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -46,6 +49,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.bnyro.clock.R import com.bnyro.clock.obj.NumberKeypadOperation +import com.bnyro.clock.ui.components.ClickableIcon import com.bnyro.clock.ui.components.FormattedTimerTime import com.bnyro.clock.ui.components.NumberKeypad import com.bnyro.clock.ui.components.TimePickerDial @@ -82,12 +86,6 @@ fun TimerScreen(onClickSettings: () -> Unit, timerModel: TimerModel) { } Spacer(Modifier.height(8.dp)) } - FloatingActionButton(onClick = { - timerModel.addPersistentTimer(timerModel.timePickerSeconds) - }) { - Icon(imageVector = Icons.Default.Save, contentDescription = null) - } - Spacer(Modifier.height(16.dp)) FloatingActionButton( onClick = { createNew = false @@ -105,6 +103,15 @@ fun TimerScreen(onClickSettings: () -> Unit, timerModel: TimerModel) { Icon(imageVector = Icons.Default.Create, contentDescription = null) } } + }, actions = { + if (timerModel.scheduledObjects.isEmpty() || createNew) { + ClickableIcon( + imageVector = Icons.Rounded.AddAlarm, + contentDescription = stringResource(R.string.add_preset_timer) + ) { + timerModel.addPersistentTimer(timerModel.timePickerSeconds) + } + } }) { paddingValues -> if (timerModel.scheduledObjects.isEmpty() || createNew) { Column( @@ -151,8 +158,7 @@ fun TimerScreen(onClickSettings: () -> Unit, timerModel: TimerModel) { val haptic = LocalHapticFeedback.current LazyVerticalGrid( modifier = Modifier - .fillMaxWidth() - .weight(1f), + .fillMaxWidth(), columns = GridCells.Adaptive(100.dp), contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b1f1cf7..419c4c46 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -108,4 +108,5 @@ Lap Lap Time Overall Time + Add preset timer \ No newline at end of file From c96d2c513de56d34e2ba6a324638482cc2633270 Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Mon, 25 Mar 2024 18:07:45 +0530 Subject: [PATCH 3/3] Fix: Timer screen layout --- .../bnyro/clock/ui/components/NumberKeypad.kt | 12 +- .../com/bnyro/clock/ui/screens/TimerScreen.kt | 287 ++++++++++-------- app/src/main/res/values/strings.xml | 1 + 3 files changed, 176 insertions(+), 124 deletions(-) diff --git a/app/src/main/java/com/bnyro/clock/ui/components/NumberKeypad.kt b/app/src/main/java/com/bnyro/clock/ui/components/NumberKeypad.kt index 0d472850..62fc25f6 100644 --- a/app/src/main/java/com/bnyro/clock/ui/components/NumberKeypad.kt +++ b/app/src/main/java/com/bnyro/clock/ui/components/NumberKeypad.kt @@ -38,28 +38,32 @@ fun NumberKeypad( verticalArrangement = Arrangement.spacedBy(buttonSpacing) ) { Row( - horizontalArrangement = Arrangement.spacedBy(buttonSpacing) + horizontalArrangement = Arrangement.spacedBy(buttonSpacing), + modifier = Modifier.weight(1f) ) { NumPadButton(number = "1", buttonSize, onOperation) NumPadButton(number = "2", buttonSize, onOperation) NumPadButton(number = "3", buttonSize, onOperation) } Row( - horizontalArrangement = Arrangement.spacedBy(buttonSpacing) + horizontalArrangement = Arrangement.spacedBy(buttonSpacing), + modifier = Modifier.weight(1f) ) { NumPadButton(number = "4", buttonSize, onOperation) NumPadButton(number = "5", buttonSize, onOperation) NumPadButton(number = "6", buttonSize, onOperation) } Row( - horizontalArrangement = Arrangement.spacedBy(buttonSpacing) + horizontalArrangement = Arrangement.spacedBy(buttonSpacing), + modifier = Modifier.weight(1f) ) { NumPadButton(number = "7", buttonSize, onOperation) NumPadButton(number = "8", buttonSize, onOperation) NumPadButton(number = "9", buttonSize, onOperation) } Row( - horizontalArrangement = Arrangement.spacedBy(buttonSpacing) + horizontalArrangement = Arrangement.spacedBy(buttonSpacing), + modifier = Modifier.weight(1f) ) { NumPadButton(number = "00", buttonSize, onOperation) NumPadButton(number = "0", buttonSize, onOperation) diff --git a/app/src/main/java/com/bnyro/clock/ui/screens/TimerScreen.kt b/app/src/main/java/com/bnyro/clock/ui/screens/TimerScreen.kt index 311c0aaf..a4eaaea8 100644 --- a/app/src/main/java/com/bnyro/clock/ui/screens/TimerScreen.kt +++ b/app/src/main/java/com/bnyro/clock/ui/screens/TimerScreen.kt @@ -1,5 +1,6 @@ package com.bnyro.clock.ui.screens +import android.content.Context import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable @@ -8,10 +9,9 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues 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.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn @@ -21,18 +21,17 @@ import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.Create -import androidx.compose.material.icons.filled.FilterAlt import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material.icons.filled.Save import androidx.compose.material.icons.rounded.Add import androidx.compose.material.icons.rounded.AddAlarm +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SmallFloatingActionButton +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -58,7 +57,7 @@ import com.bnyro.clock.ui.model.TimerModel import com.bnyro.clock.ui.nav.TopBarScaffold import com.bnyro.clock.util.Preferences -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun TimerScreen(onClickSettings: () -> Unit, timerModel: TimerModel) { val context = LocalContext.current @@ -72,129 +71,38 @@ fun TimerScreen(onClickSettings: () -> Unit, timerModel: TimerModel) { mutableStateOf(false) } - TopBarScaffold(title = stringResource(R.string.timer), onClickSettings, fab = { - if (timerModel.scheduledObjects.isEmpty() || createNew) { - Column( - horizontalAlignment = Alignment.CenterHorizontally + TopBarScaffold(title = stringResource(R.string.timer), onClickSettings, actions = { + if (timerModel.scheduledObjects.isEmpty()) { + ClickableIcon( + imageVector = Icons.Rounded.AddAlarm, + contentDescription = stringResource(R.string.add_preset_timer) ) { - if (timerModel.scheduledObjects.isNotEmpty()) { - SmallFloatingActionButton( - containerColor = MaterialTheme.colorScheme.tertiaryContainer, - onClick = { createNew = false } - ) { - Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null) - } - Spacer(Modifier.height(8.dp)) - } - FloatingActionButton( - onClick = { - createNew = false - timerModel.startTimer(context) - } - ) { - Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null) - } + timerModel.addPersistentTimer(timerModel.timePickerSeconds) } } else { - FloatingActionButton( - modifier = Modifier, - onClick = { createNew = true } - ) { - Icon(imageVector = Icons.Default.Create, contentDescription = null) - } - } - }, actions = { - if (timerModel.scheduledObjects.isEmpty() || createNew) { ClickableIcon( - imageVector = Icons.Rounded.AddAlarm, + imageVector = Icons.Rounded.Add, contentDescription = stringResource(R.string.add_preset_timer) ) { - timerModel.addPersistentTimer(timerModel.timePickerSeconds) + createNew = true } } }) { paddingValues -> - if (timerModel.scheduledObjects.isEmpty() || createNew) { + if (timerModel.scheduledObjects.isEmpty()) { Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - horizontalAlignment = Alignment.CenterHorizontally + Modifier + .padding(paddingValues) ) { - if (!useScrollPicker) { - Row( - Modifier - .fillMaxWidth() - .weight(2f), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - TimePickerDial(timerModel) - } - } else { - Column( - modifier = Modifier.weight(3f), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - FormattedTimerTime( - seconds = timerModel.timePickerFakeUnits, - modifier = Modifier.padding(bottom = 32.dp) - ) - NumberKeypad( - onOperation = { operation -> - when (operation) { - is NumberKeypadOperation.AddNumber -> timerModel.addNumber( - operation.number - ) - - is NumberKeypadOperation.Delete -> timerModel.deleteLastNumber() - is NumberKeypadOperation.Clear -> timerModel.clear() - } - } - ) - } - } - if (showExampleTimers) { - val haptic = LocalHapticFeedback.current - LazyVerticalGrid( - modifier = Modifier - .fillMaxWidth(), - columns = GridCells.Adaptive(100.dp), - contentPadding = PaddingValues(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - itemsIndexed(items = timerModel.persistentTimers) { index, timer -> - Box( - modifier = Modifier - .clip(RoundedCornerShape(16.dp)) - .combinedClickable( - onClick = { - timerModel.timePickerSeconds = timer.seconds - createNew = false - timerModel.startTimer(context) - }, - onLongClick = { - haptic.performHapticFeedback( - HapticFeedbackType.LongPress - ) - timerModel.removePersistentTimer(index) - } - ) - .width(100.dp) - .background(MaterialTheme.colorScheme.secondaryContainer) - .padding(8.dp), - contentAlignment = Alignment.Center - ) { - Text( - timer.formattedTime, - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSecondaryContainer - ) - } - } - } - } + TimerPicker( + useScrollPicker, + timerModel, + showExampleTimers, + context, + onCreateNew = { + createNew = false + }, + showFAB = true + ) } } else { LazyColumn( @@ -209,4 +117,143 @@ fun TimerScreen(onClickSettings: () -> Unit, timerModel: TimerModel) { } } } + + if (createNew) { + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + ModalBottomSheet( + onDismissRequest = { createNew = false }, + sheetState = sheetState + ) { + TimerPicker( + useScrollPicker, + timerModel, + showExampleTimers, + context, + onCreateNew = { + createNew = false + }, + showFAB = false + ) + } + } +} + +@Composable +@OptIn(ExperimentalFoundationApi::class) +private fun TimerPicker( + useScrollPicker: Boolean, + timerModel: TimerModel, + showExampleTimers: Boolean, + context: Context, + onCreateNew: () -> Unit, + showFAB: Boolean +) { + Column( + modifier = Modifier + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (!useScrollPicker) { + Row( + Modifier + .fillMaxWidth() + .weight(2f), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + TimePickerDial(timerModel) + } + } else { + Column( + modifier = Modifier.weight(3f), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + FormattedTimerTime( + seconds = timerModel.timePickerFakeUnits, + modifier = Modifier.padding(bottom = 32.dp) + ) + NumberKeypad( + onOperation = { operation -> + when (operation) { + is NumberKeypadOperation.AddNumber -> timerModel.addNumber( + operation.number + ) + + is NumberKeypadOperation.Delete -> timerModel.deleteLastNumber() + is NumberKeypadOperation.Clear -> timerModel.clear() + } + } + ) + } + } + if (showExampleTimers) { + val haptic = LocalHapticFeedback.current + LazyVerticalGrid( + modifier = Modifier + .heightIn(0.dp, 200.dp) + .fillMaxWidth(), + columns = GridCells.Adaptive(100.dp), + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + itemsIndexed(items = timerModel.persistentTimers) { index, timer -> + Box( + modifier = Modifier + .clip(RoundedCornerShape(16.dp)) + .combinedClickable( + onClick = { + timerModel.timePickerSeconds = timer.seconds + onCreateNew.invoke() + timerModel.startTimer(context) + }, + onLongClick = { + haptic.performHapticFeedback( + HapticFeedbackType.LongPress + ) + timerModel.removePersistentTimer(index) + } + ) + .width(100.dp) + .background(MaterialTheme.colorScheme.secondaryContainer) + .padding(8.dp), + contentAlignment = Alignment.Center + ) { + Text( + timer.formattedTime, + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + } + } + } + } + if (showFAB) { + FloatingActionButton( + modifier = Modifier + .align(Alignment.End) + .padding(vertical = 16.dp) + .padding(end = 16.dp), + onClick = { + onCreateNew.invoke() + timerModel.startTimer(context) + }) { + Icon( + imageVector = Icons.Default.PlayArrow, + contentDescription = stringResource(R.string.start) + ) + } + } else { + Button(modifier = Modifier.padding(vertical = 16.dp), onClick = { + onCreateNew.invoke() + timerModel.startTimer(context) + }) { + Text( + text = stringResource(R.string.start), + style = MaterialTheme.typography.titleLarge + ) + } + } + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 419c4c46..0186c981 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -109,4 +109,5 @@ Lap Time Overall Time Add preset timer + Start \ No newline at end of file