desktop: custom time picker (#3741)
* desktop: custom time picker * text color * formatting * changes in UI * optimization * desktop: opening SimpleX links inside the app (#3738) * 5.5: ios 194, android 175, desktop 26 * docs: update downloads page * ui: fix chat preview showing incorrect timestamp when chat is empty (#3739) * ui: align call buttons with calls preference (#3740) * ui: deleted item preview (#3726) --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com> Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
parent
da9a7f4642
commit
bd30b80e15
@ -0,0 +1,106 @@
|
|||||||
|
package chat.simplex.common.views.helpers
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import chat.simplex.common.model.CustomTimeUnit
|
||||||
|
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
||||||
|
import com.sd.lib.compose.wheel_picker.*
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun CustomTimePicker(
|
||||||
|
selection: MutableState<Int>,
|
||||||
|
timeUnitsLimits: List<TimeUnitLimits>
|
||||||
|
) {
|
||||||
|
fun getUnitValues(unit: CustomTimeUnit, selectedValue: Int): List<Int> {
|
||||||
|
val unitLimits = timeUnitsLimits.firstOrNull { it.timeUnit == unit } ?: TimeUnitLimits.defaultUnitLimits(unit)
|
||||||
|
val regularUnitValues = (unitLimits.minValue..unitLimits.maxValue).toList()
|
||||||
|
return regularUnitValues + if (regularUnitValues.contains(selectedValue)) emptyList() else listOf(selectedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
val (unit, duration) = CustomTimeUnit.toTimeUnit(selection.value)
|
||||||
|
val selectedUnit: MutableState<CustomTimeUnit> = remember { mutableStateOf(unit) }
|
||||||
|
val selectedDuration = remember { mutableStateOf(duration) }
|
||||||
|
val selectedUnitValues = remember { mutableStateOf(getUnitValues(selectedUnit.value, selectedDuration.value)) }
|
||||||
|
val isTriggered = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(selectedUnit.value) {
|
||||||
|
// on initial composition, if passed selection doesn't fit into picker bounds, so that selectedDuration is bigger than selectedUnit maxValue
|
||||||
|
// (e.g., for selection = 121 seconds: selectedUnit would be Second, selectedDuration would be 121 > selectedUnit maxValue of 120),
|
||||||
|
// selectedDuration would've been replaced by maxValue - isTriggered check prevents this by skipping LaunchedEffect on initial composition
|
||||||
|
if (isTriggered.value) {
|
||||||
|
val maxValue = timeUnitsLimits.firstOrNull { it.timeUnit == selectedUnit.value }?.maxValue
|
||||||
|
if (maxValue != null && selectedDuration.value > maxValue) {
|
||||||
|
selectedDuration.value = maxValue
|
||||||
|
selectedUnitValues.value = getUnitValues(selectedUnit.value, selectedDuration.value)
|
||||||
|
} else {
|
||||||
|
selectedUnitValues.value = getUnitValues(selectedUnit.value, selectedDuration.value)
|
||||||
|
selection.value = selectedUnit.value.toSeconds * selectedDuration.value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isTriggered.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(selectedDuration.value) {
|
||||||
|
selection.value = selectedUnit.value.toSeconds * selectedDuration.value
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = DEFAULT_PADDING),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(0.dp)
|
||||||
|
) {
|
||||||
|
Column(Modifier.weight(1f)) {
|
||||||
|
val durationPickerState = rememberFWheelPickerState(selectedUnitValues.value.indexOf(selectedDuration.value))
|
||||||
|
FVerticalWheelPicker(
|
||||||
|
count = selectedUnitValues.value.count(),
|
||||||
|
state = durationPickerState,
|
||||||
|
unfocusedCount = 2,
|
||||||
|
focus = {
|
||||||
|
FWheelPickerFocusVertical(dividerColor = MaterialTheme.colors.primary)
|
||||||
|
}
|
||||||
|
) { index ->
|
||||||
|
Text(
|
||||||
|
selectedUnitValues.value[index].toString(),
|
||||||
|
fontSize = 18.sp,
|
||||||
|
color = MaterialTheme.colors.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
LaunchedEffect(durationPickerState) {
|
||||||
|
snapshotFlow { durationPickerState.currentIndex }
|
||||||
|
.collect {
|
||||||
|
selectedDuration.value = selectedUnitValues.value[it]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(Modifier.weight(1f)) {
|
||||||
|
val unitPickerState = rememberFWheelPickerState(timeUnitsLimits.indexOfFirst { it.timeUnit == selectedUnit.value })
|
||||||
|
FVerticalWheelPicker(
|
||||||
|
count = timeUnitsLimits.count(),
|
||||||
|
state = unitPickerState,
|
||||||
|
unfocusedCount = 2,
|
||||||
|
focus = {
|
||||||
|
FWheelPickerFocusVertical(dividerColor = MaterialTheme.colors.primary)
|
||||||
|
}
|
||||||
|
) { index ->
|
||||||
|
Text(
|
||||||
|
timeUnitsLimits[index].timeUnit.text,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
color = MaterialTheme.colors.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
LaunchedEffect(unitPickerState) {
|
||||||
|
snapshotFlow { unitPickerState.currentIndex }
|
||||||
|
.collect {
|
||||||
|
selectedUnit.value = timeUnitsLimits[it].timeUnit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -59,14 +59,6 @@ fun SendMsgView(
|
|||||||
) {
|
) {
|
||||||
val showCustomDisappearingMessageDialog = remember { mutableStateOf(false) }
|
val showCustomDisappearingMessageDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if (showCustomDisappearingMessageDialog.value) {
|
|
||||||
CustomDisappearingMessageDialog(
|
|
||||||
sendMessage = sendMessage,
|
|
||||||
setShowDialog = { showCustomDisappearingMessageDialog.value = it },
|
|
||||||
customDisappearingMessageTimePref = customDisappearingMessageTimePref
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(Modifier.padding(vertical = 8.dp)) {
|
Box(Modifier.padding(vertical = 8.dp)) {
|
||||||
val cs = composeState.value
|
val cs = composeState.value
|
||||||
var progressByTimeout by rememberSaveable { mutableStateOf(false) }
|
var progressByTimeout by rememberSaveable { mutableStateOf(false) }
|
||||||
@ -203,6 +195,11 @@ fun SendMsgView(
|
|||||||
DefaultDropdownMenu(showDropdown) {
|
DefaultDropdownMenu(showDropdown) {
|
||||||
menuItems.forEach { composable -> composable() }
|
menuItems.forEach { composable -> composable() }
|
||||||
}
|
}
|
||||||
|
CustomDisappearingMessageDialog(
|
||||||
|
showCustomDisappearingMessageDialog,
|
||||||
|
sendMessage = sendMessage,
|
||||||
|
customDisappearingMessageTimePref = customDisappearingMessageTimePref
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
SendMsgButton(icon, sendButtonSize, sendButtonAlpha, sendButtonColor, !sendMsgButtonDisabled, sendMessage)
|
SendMsgButton(icon, sendButtonSize, sendButtonAlpha, sendButtonColor, !sendMsgButtonDisabled, sendMessage)
|
||||||
}
|
}
|
||||||
@ -220,93 +217,43 @@ expect fun VoiceButtonWithoutPermissionByPlatform()
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CustomDisappearingMessageDialog(
|
private fun CustomDisappearingMessageDialog(
|
||||||
|
showMenu: MutableState<Boolean>,
|
||||||
sendMessage: (Int?) -> Unit,
|
sendMessage: (Int?) -> Unit,
|
||||||
setShowDialog: (Boolean) -> Unit,
|
|
||||||
customDisappearingMessageTimePref: SharedPreference<Int>?
|
customDisappearingMessageTimePref: SharedPreference<Int>?
|
||||||
) {
|
) {
|
||||||
val showCustomTimePicker = remember { mutableStateOf(false) }
|
DefaultDropdownMenu(showMenu) {
|
||||||
|
Text(
|
||||||
if (showCustomTimePicker.value) {
|
generalGetString(MR.strings.send_disappearing_message),
|
||||||
val selectedDisappearingMessageTime = remember {
|
Modifier.padding(vertical = DEFAULT_PADDING_HALF, horizontal = DEFAULT_PADDING * 1.5f),
|
||||||
mutableStateOf(customDisappearingMessageTimePref?.get?.invoke() ?: 300)
|
fontSize = 16.sp,
|
||||||
}
|
color = MaterialTheme.colors.secondary
|
||||||
CustomTimePickerDialog(
|
|
||||||
selectedDisappearingMessageTime,
|
|
||||||
title = generalGetString(MR.strings.delete_after),
|
|
||||||
confirmButtonText = generalGetString(MR.strings.send_disappearing_message_send),
|
|
||||||
confirmButtonAction = { ttl ->
|
|
||||||
sendMessage(ttl)
|
|
||||||
customDisappearingMessageTimePref?.set?.invoke(ttl)
|
|
||||||
setShowDialog(false)
|
|
||||||
},
|
|
||||||
cancel = { setShowDialog(false) }
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
@Composable
|
|
||||||
fun ChoiceButton(
|
|
||||||
text: String,
|
|
||||||
onClick: () -> Unit
|
|
||||||
) {
|
|
||||||
TextButton(onClick) {
|
|
||||||
Text(
|
|
||||||
text,
|
|
||||||
fontSize = 18.sp,
|
|
||||||
color = MaterialTheme.colors.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DefaultDialog(onDismissRequest = { setShowDialog(false) }) {
|
ItemAction(generalGetString(MR.strings.send_disappearing_message_30_seconds)) {
|
||||||
Surface(
|
sendMessage(30)
|
||||||
shape = RoundedCornerShape(corner = CornerSize(25.dp)),
|
showMenu.value = false
|
||||||
contentColor = LocalContentColor.current
|
}
|
||||||
) {
|
ItemAction(generalGetString(MR.strings.send_disappearing_message_1_minute)) {
|
||||||
Box(
|
sendMessage(60)
|
||||||
contentAlignment = Alignment.Center
|
showMenu.value = false
|
||||||
) {
|
}
|
||||||
Column(
|
ItemAction(generalGetString(MR.strings.send_disappearing_message_5_minutes)) {
|
||||||
modifier = Modifier.padding(DEFAULT_PADDING),
|
sendMessage(300)
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
showMenu.value = false
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
}
|
||||||
) {
|
ItemAction(generalGetString(MR.strings.send_disappearing_message_custom_time)) {
|
||||||
Row(
|
showMenu.value = false
|
||||||
modifier = Modifier.fillMaxWidth(),
|
val selectedDisappearingMessageTime = mutableStateOf(customDisappearingMessageTimePref?.get?.invoke() ?: 300)
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
showCustomTimePickerDialog(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
selectedDisappearingMessageTime,
|
||||||
) {
|
title = generalGetString(MR.strings.delete_after),
|
||||||
Text(" ") // centers title
|
confirmButtonText = generalGetString(MR.strings.send_disappearing_message_send),
|
||||||
Text(
|
confirmButtonAction = { ttl ->
|
||||||
generalGetString(MR.strings.send_disappearing_message),
|
sendMessage(ttl)
|
||||||
fontSize = 16.sp,
|
customDisappearingMessageTimePref?.set?.invoke(ttl)
|
||||||
color = MaterialTheme.colors.secondary
|
},
|
||||||
)
|
cancel = { showMenu.value = false }
|
||||||
Icon(
|
)
|
||||||
painterResource(MR.images.ic_close),
|
|
||||||
generalGetString(MR.strings.icon_descr_close_button),
|
|
||||||
tint = MaterialTheme.colors.secondary,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(25.dp)
|
|
||||||
.clickable { setShowDialog(false) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ChoiceButton(generalGetString(MR.strings.send_disappearing_message_30_seconds)) {
|
|
||||||
sendMessage(30)
|
|
||||||
setShowDialog(false)
|
|
||||||
}
|
|
||||||
ChoiceButton(generalGetString(MR.strings.send_disappearing_message_1_minute)) {
|
|
||||||
sendMessage(60)
|
|
||||||
setShowDialog(false)
|
|
||||||
}
|
|
||||||
ChoiceButton(generalGetString(MR.strings.send_disappearing_message_5_minutes)) {
|
|
||||||
sendMessage(300)
|
|
||||||
setShowDialog(false)
|
|
||||||
}
|
|
||||||
ChoiceButton(generalGetString(MR.strings.send_disappearing_message_custom_time)) {
|
|
||||||
showCustomTimePicker.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -651,6 +651,23 @@ fun ItemAction(text: String, icon: ImageVector, onClick: () -> Unit, color: Colo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ItemAction(text: String, color: Color = Color.Unspecified, onClick: () -> Unit) {
|
||||||
|
val finalColor = if (color == Color.Unspecified) {
|
||||||
|
MenuTextColor
|
||||||
|
} else color
|
||||||
|
DropdownMenuItem(onClick, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 1.5f)) {
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1F)
|
||||||
|
.padding(end = 15.dp),
|
||||||
|
color = finalColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun cancelFileAlertDialog(fileId: Long, cancelFile: (Long) -> Unit, cancelAction: CancelAction) {
|
fun cancelFileAlertDialog(fileId: Long, cancelFile: (Long) -> Unit, cancelAction: CancelAction) {
|
||||||
AlertManager.shared.showAlertDialog(
|
AlertManager.shared.showAlertDialog(
|
||||||
title = generalGetString(cancelAction.alert.titleId),
|
title = generalGetString(cancelAction.alert.titleId),
|
||||||
|
@ -1,116 +1,21 @@
|
|||||||
package chat.simplex.common.views.helpers
|
package chat.simplex.common.views.helpers
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import SectionItemView
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CornerSize
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
|
||||||
import chat.simplex.common.model.CustomTimeUnit
|
import chat.simplex.common.model.CustomTimeUnit
|
||||||
import chat.simplex.common.model.timeText
|
import chat.simplex.common.model.timeText
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import com.sd.lib.compose.wheel_picker.*
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CustomTimePicker(
|
expect fun CustomTimePicker(
|
||||||
selection: MutableState<Int>,
|
selection: MutableState<Int>,
|
||||||
timeUnitsLimits: List<TimeUnitLimits> = TimeUnitLimits.defaultUnitsLimits
|
timeUnitsLimits: List<TimeUnitLimits> = TimeUnitLimits.defaultUnitsLimits
|
||||||
) {
|
)
|
||||||
fun getUnitValues(unit: CustomTimeUnit, selectedValue: Int): List<Int> {
|
|
||||||
val unitLimits = timeUnitsLimits.firstOrNull { it.timeUnit == unit } ?: TimeUnitLimits.defaultUnitLimits(unit)
|
|
||||||
val regularUnitValues = (unitLimits.minValue..unitLimits.maxValue).toList()
|
|
||||||
return regularUnitValues + if (regularUnitValues.contains(selectedValue)) emptyList() else listOf(selectedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
val (unit, duration) = CustomTimeUnit.toTimeUnit(selection.value)
|
|
||||||
val selectedUnit: MutableState<CustomTimeUnit> = remember { mutableStateOf(unit) }
|
|
||||||
val selectedDuration = remember { mutableStateOf(duration) }
|
|
||||||
val selectedUnitValues = remember { mutableStateOf(getUnitValues(selectedUnit.value, selectedDuration.value)) }
|
|
||||||
val isTriggered = remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
LaunchedEffect(selectedUnit.value) {
|
|
||||||
// on initial composition, if passed selection doesn't fit into picker bounds, so that selectedDuration is bigger than selectedUnit maxValue
|
|
||||||
// (e.g., for selection = 121 seconds: selectedUnit would be Second, selectedDuration would be 121 > selectedUnit maxValue of 120),
|
|
||||||
// selectedDuration would've been replaced by maxValue - isTriggered check prevents this by skipping LaunchedEffect on initial composition
|
|
||||||
if (isTriggered.value) {
|
|
||||||
val maxValue = timeUnitsLimits.firstOrNull { it.timeUnit == selectedUnit.value }?.maxValue
|
|
||||||
if (maxValue != null && selectedDuration.value > maxValue) {
|
|
||||||
selectedDuration.value = maxValue
|
|
||||||
selectedUnitValues.value = getUnitValues(selectedUnit.value, selectedDuration.value)
|
|
||||||
} else {
|
|
||||||
selectedUnitValues.value = getUnitValues(selectedUnit.value, selectedDuration.value)
|
|
||||||
selection.value = selectedUnit.value.toSeconds * selectedDuration.value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isTriggered.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(selectedDuration.value) {
|
|
||||||
selection.value = selectedUnit.value.toSeconds * selectedDuration.value
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = DEFAULT_PADDING),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(0.dp)
|
|
||||||
) {
|
|
||||||
Column(Modifier.weight(1f)) {
|
|
||||||
val durationPickerState = rememberFWheelPickerState(selectedUnitValues.value.indexOf(selectedDuration.value))
|
|
||||||
FVerticalWheelPicker(
|
|
||||||
count = selectedUnitValues.value.count(),
|
|
||||||
state = durationPickerState,
|
|
||||||
unfocusedCount = 2,
|
|
||||||
focus = {
|
|
||||||
FWheelPickerFocusVertical(dividerColor = MaterialTheme.colors.primary)
|
|
||||||
}
|
|
||||||
) { index ->
|
|
||||||
Text(
|
|
||||||
selectedUnitValues.value[index].toString(),
|
|
||||||
fontSize = 18.sp,
|
|
||||||
color = MaterialTheme.colors.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
LaunchedEffect(durationPickerState) {
|
|
||||||
snapshotFlow { durationPickerState.currentIndex }
|
|
||||||
.collect {
|
|
||||||
selectedDuration.value = selectedUnitValues.value[it]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column(Modifier.weight(1f)) {
|
|
||||||
val unitPickerState = rememberFWheelPickerState(timeUnitsLimits.indexOfFirst { it.timeUnit == selectedUnit.value })
|
|
||||||
FVerticalWheelPicker(
|
|
||||||
count = timeUnitsLimits.count(),
|
|
||||||
state = unitPickerState,
|
|
||||||
unfocusedCount = 2,
|
|
||||||
focus = {
|
|
||||||
FWheelPickerFocusVertical(dividerColor = MaterialTheme.colors.primary)
|
|
||||||
}
|
|
||||||
) { index ->
|
|
||||||
Text(
|
|
||||||
timeUnitsLimits[index].timeUnit.text,
|
|
||||||
fontSize = 18.sp,
|
|
||||||
color = MaterialTheme.colors.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
LaunchedEffect(unitPickerState) {
|
|
||||||
snapshotFlow { unitPickerState.currentIndex }
|
|
||||||
.collect {
|
|
||||||
selectedUnit.value = timeUnitsLimits[it].timeUnit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class TimeUnitLimits(
|
data class TimeUnitLimits(
|
||||||
val timeUnit: CustomTimeUnit,
|
val timeUnit: CustomTimeUnit,
|
||||||
@ -141,8 +46,7 @@ data class TimeUnitLimits(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
fun showCustomTimePickerDialog(
|
||||||
fun CustomTimePickerDialog(
|
|
||||||
selection: MutableState<Int>,
|
selection: MutableState<Int>,
|
||||||
timeUnitsLimits: List<TimeUnitLimits> = TimeUnitLimits.defaultUnitsLimits,
|
timeUnitsLimits: List<TimeUnitLimits> = TimeUnitLimits.defaultUnitsLimits,
|
||||||
title: String,
|
title: String,
|
||||||
@ -150,53 +54,26 @@ fun CustomTimePickerDialog(
|
|||||||
confirmButtonAction: (Int) -> Unit,
|
confirmButtonAction: (Int) -> Unit,
|
||||||
cancel: () -> Unit
|
cancel: () -> Unit
|
||||||
) {
|
) {
|
||||||
DefaultDialog(onDismissRequest = cancel) {
|
AlertManager.shared.showAlertDialogButtonsColumn(
|
||||||
Surface(
|
title = title,
|
||||||
shape = RoundedCornerShape(corner = CornerSize(25.dp)),
|
onDismissRequest = cancel
|
||||||
contentColor = LocalContentColor.current
|
) {
|
||||||
) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Box(
|
CustomTimePicker(
|
||||||
contentAlignment = Alignment.Center
|
selection,
|
||||||
|
timeUnitsLimits
|
||||||
|
)
|
||||||
|
SectionItemView({
|
||||||
|
AlertManager.shared.hideAlert()
|
||||||
|
confirmButtonAction(selection.value)
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
Column(
|
Text(
|
||||||
modifier = Modifier.padding(DEFAULT_PADDING),
|
confirmButtonText,
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
Modifier.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
textAlign = TextAlign.Center,
|
||||||
) {
|
color = MaterialTheme.colors.primary
|
||||||
Row(
|
)
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(" ") // centers title
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
color = MaterialTheme.colors.secondary
|
|
||||||
)
|
|
||||||
Icon(
|
|
||||||
painterResource(MR.images.ic_close),
|
|
||||||
generalGetString(MR.strings.icon_descr_close_button),
|
|
||||||
tint = MaterialTheme.colors.secondary,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(25.dp)
|
|
||||||
.clickable { cancel() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomTimePicker(
|
|
||||||
selection,
|
|
||||||
timeUnitsLimits
|
|
||||||
)
|
|
||||||
|
|
||||||
TextButton(onClick = { confirmButtonAction(selection.value) }) {
|
|
||||||
Text(
|
|
||||||
confirmButtonText,
|
|
||||||
fontSize = 18.sp,
|
|
||||||
color = MaterialTheme.colors.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,7 +97,6 @@ fun DropdownCustomTimePickerSettingRow(
|
|||||||
|
|
||||||
val dropdownSelection: MutableState<DropdownSelection> = remember { mutableStateOf(DropdownSelection.DropdownValue(selection.value)) }
|
val dropdownSelection: MutableState<DropdownSelection> = remember { mutableStateOf(DropdownSelection.DropdownValue(selection.value)) }
|
||||||
val values: MutableState<List<DropdownSelection>> = remember { mutableStateOf(getValues(selection.value)) }
|
val values: MutableState<List<DropdownSelection>> = remember { mutableStateOf(getValues(selection.value)) }
|
||||||
val showCustomTimePicker = remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
fun updateValue(selectedValue: Int?) {
|
fun updateValue(selectedValue: Int?) {
|
||||||
values.value = getValues(selectedValue)
|
values.value = getValues(selectedValue)
|
||||||
@ -247,28 +123,22 @@ fun DropdownCustomTimePickerSettingRow(
|
|||||||
onSelected = { sel: DropdownSelection ->
|
onSelected = { sel: DropdownSelection ->
|
||||||
when (sel) {
|
when (sel) {
|
||||||
is DropdownSelection.DropdownValue -> updateValue(sel.value)
|
is DropdownSelection.DropdownValue -> updateValue(sel.value)
|
||||||
DropdownSelection.Custom -> showCustomTimePicker.value = true
|
DropdownSelection.Custom -> {
|
||||||
|
val selectedCustomTime = mutableStateOf(selection.value ?: 86400)
|
||||||
|
showCustomTimePickerDialog(
|
||||||
|
selectedCustomTime,
|
||||||
|
timeUnitsLimits = customPickerTimeUnitsLimits,
|
||||||
|
title = customPickerTitle,
|
||||||
|
confirmButtonText = customPickerConfirmButtonText,
|
||||||
|
confirmButtonAction = ::updateValue,
|
||||||
|
cancel = {
|
||||||
|
dropdownSelection.value = DropdownSelection.DropdownValue(selection.value)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (showCustomTimePicker.value) {
|
|
||||||
val selectedCustomTime = remember { mutableStateOf(selection.value ?: 86400) }
|
|
||||||
CustomTimePickerDialog(
|
|
||||||
selectedCustomTime,
|
|
||||||
timeUnitsLimits = customPickerTimeUnitsLimits,
|
|
||||||
title = customPickerTitle,
|
|
||||||
confirmButtonText = customPickerConfirmButtonText,
|
|
||||||
confirmButtonAction = { time ->
|
|
||||||
updateValue(time)
|
|
||||||
showCustomTimePicker.value = false
|
|
||||||
},
|
|
||||||
cancel = {
|
|
||||||
dropdownSelection.value = DropdownSelection.DropdownValue(selection.value)
|
|
||||||
showCustomTimePicker.value = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class DropdownSelection {
|
private sealed class DropdownSelection {
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
package chat.simplex.common.views.helpers
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import chat.simplex.common.model.CustomTimeUnit
|
||||||
|
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun CustomTimePicker(
|
||||||
|
selection: MutableState<Int>,
|
||||||
|
timeUnitsLimits: List<TimeUnitLimits>
|
||||||
|
) {
|
||||||
|
val unit = remember {
|
||||||
|
var res: CustomTimeUnit = CustomTimeUnit.Second
|
||||||
|
val found = timeUnitsLimits.asReversed().any {
|
||||||
|
if (selection.value >= it.minValue * it.timeUnit.toSeconds && selection.value <= it.maxValue * it.timeUnit.toSeconds) {
|
||||||
|
res = it.timeUnit
|
||||||
|
selection.value = (selection.value / it.timeUnit.toSeconds).coerceIn(it.minValue, it.maxValue) * it.timeUnit.toSeconds
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
// If custom interval doesn't fit in any category, set it to 1 second interval
|
||||||
|
selection.value = 1
|
||||||
|
}
|
||||||
|
mutableStateOf(res)
|
||||||
|
}
|
||||||
|
val values = remember(unit.value) {
|
||||||
|
val limit = timeUnitsLimits.first { it.timeUnit == unit.value }
|
||||||
|
val res = ArrayList<Pair<Int, String>>()
|
||||||
|
for (i in limit.minValue..limit.maxValue) {
|
||||||
|
val seconds = i * limit.timeUnit.toSeconds
|
||||||
|
val desc = i.toString()
|
||||||
|
res.add(seconds to desc)
|
||||||
|
}
|
||||||
|
if (res.none { it.first == selection.value }) {
|
||||||
|
// Doesn't fit into min..max, put it equal to the closest value
|
||||||
|
selection.value = selection.value.coerceIn(res.first().first, res.last().first)
|
||||||
|
//selection.value = res.last { it.first <= selection.value }.first
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
val units = remember {
|
||||||
|
val res = ArrayList<Pair<CustomTimeUnit, String>>()
|
||||||
|
for (unit in timeUnitsLimits) {
|
||||||
|
res.add(unit.timeUnit to unit.timeUnit.text)
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
Modifier.padding(bottom = DEFAULT_PADDING),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
|
) {
|
||||||
|
ExposedDropDownSetting(
|
||||||
|
values,
|
||||||
|
selection,
|
||||||
|
textColor = MaterialTheme.colors.onBackground,
|
||||||
|
enabled = remember { mutableStateOf(true) },
|
||||||
|
onSelected = { selection.value = it }
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(DEFAULT_PADDING))
|
||||||
|
ExposedDropDownSetting(
|
||||||
|
units,
|
||||||
|
unit,
|
||||||
|
textColor = MaterialTheme.colors.onBackground,
|
||||||
|
enabled = remember { mutableStateOf(true) },
|
||||||
|
onSelected = {
|
||||||
|
selection.value = selection.value / unit.value.toSeconds * it.toSeconds
|
||||||
|
unit.value = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user