Click on alarm row to replace it

pull/1957/head
Alex Baker 3 years ago
parent 8ed2880953
commit 20ab106e12

@ -2,16 +2,18 @@ package com.todoroo.astrid.ui
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.viewModels
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus import com.google.accompanist.permissions.PermissionStatus
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
@ -24,17 +26,11 @@ import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.compose.edit.AlarmRow import org.tasks.compose.edit.AlarmRow
import org.tasks.data.Alarm import org.tasks.data.Alarm
import org.tasks.data.Alarm.Companion.TYPE_DATE_TIME import org.tasks.data.Alarm.Companion.TYPE_DATE_TIME
import org.tasks.data.Alarm.Companion.TYPE_REL_END
import org.tasks.data.Alarm.Companion.TYPE_REL_START
import org.tasks.data.Alarm.Companion.whenDue
import org.tasks.data.Alarm.Companion.whenOverdue
import org.tasks.data.Alarm.Companion.whenStarted
import org.tasks.date.DateTimeUtils import org.tasks.date.DateTimeUtils
import org.tasks.dialogs.DialogBuilder import org.tasks.dialogs.DialogBuilder
import org.tasks.dialogs.MyTimePickerDialog import org.tasks.dialogs.MyTimePickerDialog
import org.tasks.ui.TaskEditControlFragment import org.tasks.ui.TaskEditControlFragment
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -44,7 +40,6 @@ class ReminderControlSet : TaskEditControlFragment() {
@Inject lateinit var locale: Locale @Inject lateinit var locale: Locale
private val ringMode = mutableStateOf(0) private val ringMode = mutableStateOf(0)
private val vm: ReminderControlSetViewModel by viewModels()
override fun createView(savedInstanceState: Bundle?) { override fun createView(savedInstanceState: Bundle?) {
when { when {
@ -76,39 +71,6 @@ class ReminderControlSet : TaskEditControlFragment() {
this.ringMode.value = ringMode this.ringMode.value = ringMode
} }
private fun addAlarm(selected: String) {
val id = viewModel.task.id
when (selected) {
getString(R.string.when_started) ->
viewModel.addAlarm(whenStarted(id))
getString(R.string.when_due) ->
viewModel.addAlarm(whenDue(id))
getString(R.string.when_overdue) ->
viewModel.addAlarm(whenOverdue(id))
getString(R.string.randomly) ->
vm.showRandomDialog(visible = true)
getString(R.string.pick_a_date_and_time) ->
addNewAlarm()
getString(R.string.repeat_option_custom) ->
vm.showCustomDialog(visible = true)
}
}
private fun addAlarm() {
val options = options
if (options.size == 1) {
addNewAlarm()
} else {
dialogBuilder
.newDialog()
.setItems(options) { dialog: DialogInterface, which: Int ->
addAlarm(options[which])
dialog.dismiss()
}
.show()
}
}
@OptIn(ExperimentalPermissionsApi::class) @OptIn(ExperimentalPermissionsApi::class)
override fun bind(parent: ViewGroup?): View = override fun bind(parent: ViewGroup?): View =
(parent as ComposeView).apply { (parent as ComposeView).apply {
@ -122,6 +84,16 @@ class ReminderControlSet : TaskEditControlFragment() {
} else { } else {
null null
} }
val pickDateAndTime =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode != RESULT_OK) return@rememberLauncherForActivityResult
val data = result.data ?: return@rememberLauncherForActivityResult
val timestamp =
data.getLongExtra(MyTimePickerDialog.EXTRA_TIMESTAMP, 0L)
val replace: Alarm? = data.getParcelableExtra(EXTRA_REPLACE)
replace?.let { viewModel.removeAlarm(it) }
viewModel.addAlarm(Alarm(0, timestamp, TYPE_DATE_TIME))
}
AlarmRow( AlarmRow(
locale = locale, locale = locale,
alarms = viewModel.selectedAlarms.collectAsStateLifecycleAware().value, alarms = viewModel.selectedAlarms.collectAsStateLifecycleAware().value,
@ -131,11 +103,18 @@ class ReminderControlSet : TaskEditControlFragment() {
notificationPermissions?.launchPermissionRequest() notificationPermissions?.launchPermissionRequest()
}, },
ringMode = ringMode, ringMode = ringMode,
newAlarm = this@ReminderControlSet::addAlarm,
addAlarm = viewModel::addAlarm, addAlarm = viewModel::addAlarm,
openRingType = this@ReminderControlSet::onClickRingType, openRingType = this@ReminderControlSet::onClickRingType,
deleteAlarm = { deleteAlarm = viewModel::removeAlarm,
viewModel.selectedAlarms.value = viewModel.selectedAlarms.value.minus(it) pickDateAndTime = { replace ->
pickDateAndTime.launch(
Intent(activity, DateAndTimePickerActivity::class.java)
.putExtra(
DateAndTimePickerActivity.EXTRA_TIMESTAMP,
DateTimeUtils.newDateTime().noon().millis
)
.putExtra(EXTRA_REPLACE, replace)
)
} }
) )
} }
@ -144,46 +123,8 @@ class ReminderControlSet : TaskEditControlFragment() {
override fun controlId() = TAG override fun controlId() = TAG
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_NEW_ALARM) {
if (resultCode == Activity.RESULT_OK) {
val timestamp = data!!.getLongExtra(MyTimePickerDialog.EXTRA_TIMESTAMP, 0L)
viewModel.addAlarm(Alarm(0, timestamp, TYPE_DATE_TIME))
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun addNewAlarm() {
val intent = Intent(activity, DateAndTimePickerActivity::class.java)
.putExtra(
DateAndTimePickerActivity.EXTRA_TIMESTAMP,
DateTimeUtils.newDateTime().noon().millis
)
startActivityForResult(intent, REQUEST_NEW_ALARM)
}
private val options: List<String>
get() {
val options: MutableList<String> = ArrayList()
if (viewModel.selectedAlarms.value.find { it.type == TYPE_REL_START && it.time == 0L } == null) {
options.add(getString(R.string.when_started))
}
if (viewModel.selectedAlarms.value.find { it.type == TYPE_REL_END && it.time == 0L } == null) {
options.add(getString(R.string.when_due))
}
if (viewModel.selectedAlarms.value.find { it.type == TYPE_REL_END && it.time == TimeUnit.HOURS.toMillis(24) } == null) {
options.add(getString(R.string.when_overdue))
}
options.add(getString(R.string.randomly))
options.add(getString(R.string.pick_a_date_and_time))
options.add(getString(R.string.repeat_option_custom))
return options
}
companion object { companion object {
const val TAG = R.string.TEA_ctrl_reminders_pref const val TAG = R.string.TEA_ctrl_reminders_pref
private const val REQUEST_NEW_ALARM = 12152 private const val EXTRA_REPLACE = "extra_replace"
} }
} }

@ -4,12 +4,16 @@ import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import org.tasks.data.Alarm
class ReminderControlSetViewModel : ViewModel() { class ReminderControlSetViewModel : ViewModel() {
data class ViewState( data class ViewState(
val showAddAlarm: Boolean = false,
val showCustomDialog: Boolean = false, val showCustomDialog: Boolean = false,
val showRandomDialog: Boolean = false, val showRandomDialog: Boolean = false,
val replace: Alarm? = null,
) )
private val _viewState = MutableStateFlow(ViewState()) private val _viewState = MutableStateFlow(ViewState())
@ -17,15 +21,26 @@ class ReminderControlSetViewModel : ViewModel() {
val viewState: StateFlow<ViewState> val viewState: StateFlow<ViewState>
get() = _viewState.asStateFlow() get() = _viewState.asStateFlow()
fun setReplace(alarm: Alarm?) {
_viewState.update { it.copy(replace = alarm) }
}
fun showAddAlarm(visible: Boolean) {
_viewState.update { state ->
state.copy(
showAddAlarm = visible,
replace = state.replace?.takeIf {
visible || state.showCustomDialog || state.showRandomDialog
},
)
}
}
fun showCustomDialog(visible: Boolean) { fun showCustomDialog(visible: Boolean) {
_viewState.value = _viewState.value.copy( _viewState.update { it.copy(showCustomDialog = visible) }
showCustomDialog = visible
)
} }
fun showRandomDialog(visible: Boolean) { fun showRandomDialog(visible: Boolean) {
_viewState.value = _viewState.value.copy( _viewState.update { it.copy(showRandomDialog = visible) }
showRandomDialog = visible
)
} }
} }

@ -79,6 +79,7 @@ class DateAndTimePickerActivity : InjectingAppCompatActivity() {
} }
addOnPositiveButtonClickListener { addOnPositiveButtonClickListener {
val data = Intent() val data = Intent()
data.putExtras(intent)
data.putExtra( data.putExtra(
MyTimePickerDialog.EXTRA_TIMESTAMP, MyTimePickerDialog.EXTRA_TIMESTAMP,
DateTime( DateTime(

@ -32,6 +32,9 @@ import com.google.android.material.composethemeadapter.MdcTheme
import kotlinx.coroutines.android.awaitFrame import kotlinx.coroutines.android.awaitFrame
import org.tasks.R import org.tasks.R
import org.tasks.data.Alarm import org.tasks.data.Alarm
import org.tasks.data.Alarm.Companion.TYPE_REL_END
import org.tasks.data.Alarm.Companion.TYPE_REL_START
import org.tasks.data.Alarm.Companion.whenStarted
import org.tasks.reminders.AlarmToString.Companion.getRepeatString import org.tasks.reminders.AlarmToString.Companion.getRepeatString
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -104,11 +107,11 @@ object AddReminderDialog {
time.value.takeIf { it >= 0 }?.let { i -> time.value.takeIf { it >= 0 }?.let { i ->
addAlarm( addAlarm(
Alarm( Alarm(
0, task = 0,
-1 * i * units.millis, time = -1 * i * units.millis,
Alarm.TYPE_REL_END, type = TYPE_REL_END,
repeat.value, repeat = repeat.value,
interval.value * recurringUnits.millis interval = interval.value * recurringUnits.millis
) )
) )
closeDialog() closeDialog()
@ -460,6 +463,54 @@ fun BodyText(modifier: Modifier = Modifier, text: String) {
) )
} }
@Composable
fun AlarmDropDown(
visible: Boolean,
existingAlarms: List<Alarm>,
addAlarm: (Alarm) -> Unit,
addRandom: () -> Unit,
addCustom: () -> Unit,
pickDateAndTime: () -> Unit,
dismiss: () -> Unit,
) {
CustomDialog(visible = visible, onDismiss = dismiss) {
Column(modifier = Modifier.padding(vertical = 4.dp)) {
if (existingAlarms.none { it.type == TYPE_REL_START && it.time == 0L }) {
DialogRow(text = R.string.when_started) {
addAlarm(whenStarted(0))
dismiss()
}
}
if (existingAlarms.none { it.type == TYPE_REL_END && it.time == 0L }) {
DialogRow(text = R.string.when_due) {
addAlarm(Alarm.whenDue(0))
dismiss()
}
}
if (existingAlarms.none {
it.type == TYPE_REL_END && it.time == TimeUnit.HOURS.toMillis(24)
}) {
DialogRow(text = R.string.when_overdue) {
addAlarm(Alarm.whenOverdue(0))
dismiss()
}
}
DialogRow(text = R.string.randomly) {
addRandom()
dismiss()
}
DialogRow(text = R.string.pick_a_date_and_time) {
pickDateAndTime()
dismiss()
}
DialogRow(text = R.string.repeat_option_custom) {
addCustom()
dismiss()
}
}
}
}
@ExperimentalComposeUiApi @ExperimentalComposeUiApi
@Preview(showBackground = true) @Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@ -547,3 +598,19 @@ fun AddRandomReminder() =
units = remember { mutableStateOf(1) } units = remember { mutableStateOf(1) }
) )
} }
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun AddReminderDialog() =
MdcTheme {
AlarmDropDown(
visible = true,
existingAlarms = emptyList(),
addAlarm = {},
addRandom = {},
addCustom = {},
pickDateAndTime = {},
dismiss = {},
)
}

@ -0,0 +1,29 @@
package org.tasks.compose
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
@Composable
fun CustomDialog(
visible: Boolean,
onDismiss: () -> Unit,
content: @Composable () -> Unit,
) {
if (!visible) {
return
}
Dialog(onDismissRequest = onDismiss) {
Card(
shape = MaterialTheme.shapes.medium,
modifier = Modifier.padding(16.dp),
elevation = 8.dp
) {
content()
}
}
}

@ -0,0 +1,25 @@
package org.tasks.compose
import androidx.annotation.IntegerRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@Composable
fun DialogRow(@IntegerRes text: Int, onClick: () -> Unit) {
Text(
text = stringResource(id = text),
modifier = Modifier
.clickable { onClick() }
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp),
color = MaterialTheme.colors.onSurface,
style = MaterialTheme.typography.body1,
)
}

@ -37,10 +37,10 @@ fun AlarmRow(
alarms: List<Alarm>, alarms: List<Alarm>,
ringMode: Int, ringMode: Int,
locale: Locale, locale: Locale,
newAlarm: () -> Unit,
addAlarm: (Alarm) -> Unit, addAlarm: (Alarm) -> Unit,
deleteAlarm: (Alarm) -> Unit, deleteAlarm: (Alarm) -> Unit,
openRingType: () -> Unit, openRingType: () -> Unit,
pickDateAndTime: (replace: Alarm?) -> Unit,
) { ) {
TaskEditRow( TaskEditRow(
iconRes = R.drawable.ic_outline_notifications_24px, iconRes = R.drawable.ic_outline_notifications_24px,
@ -52,7 +52,11 @@ fun AlarmRow(
alarms = alarms, alarms = alarms,
ringMode = ringMode, ringMode = ringMode,
locale = locale, locale = locale,
addAlarm = newAlarm, replaceAlarm = {
vm.setReplace(it)
vm.showAddAlarm(visible = true)
},
addAlarm = { vm.showAddAlarm(visible = true) },
deleteAlarm = deleteAlarm, deleteAlarm = deleteAlarm,
openRingType = openRingType, openRingType = openRingType,
) )
@ -62,8 +66,8 @@ fun AlarmRow(
modifier = Modifier modifier = Modifier
.padding(end = 16.dp) .padding(end = 16.dp)
.clickable { .clickable {
launchPermissionRequest() launchPermissionRequest()
} }
) { ) {
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
Text( Text(
@ -80,15 +84,34 @@ fun AlarmRow(
} }
} }
AlarmDropDown(
visible = viewState.showAddAlarm,
existingAlarms = alarms,
addAlarm = {
viewState.replace?.let(deleteAlarm)
addAlarm(it)
},
addRandom = { vm.showRandomDialog(visible = true) },
addCustom = { vm.showCustomDialog(visible = true) },
pickDateAndTime = { pickDateAndTime(viewState.replace) },
dismiss = { vm.showAddAlarm(visible = false) },
)
AddReminderDialog.AddCustomReminderDialog( AddReminderDialog.AddCustomReminderDialog(
openDialog = viewState.showCustomDialog, openDialog = viewState.showCustomDialog,
addAlarm = addAlarm, addAlarm = {
viewState.replace?.let(deleteAlarm)
addAlarm(it)
},
closeDialog = { vm.showCustomDialog(visible = false) } closeDialog = { vm.showCustomDialog(visible = false) }
) )
AddReminderDialog.AddRandomReminderDialog( AddReminderDialog.AddRandomReminderDialog(
openDialog = viewState.showRandomDialog, openDialog = viewState.showRandomDialog,
addAlarm = addAlarm, addAlarm = {
viewState.replace?.let(deleteAlarm)
addAlarm(it)
},
closeDialog = { vm.showRandomDialog(visible = false) } closeDialog = { vm.showRandomDialog(visible = false) }
) )
}, },
@ -100,6 +123,7 @@ fun Alarms(
alarms: List<Alarm>, alarms: List<Alarm>,
ringMode: Int, ringMode: Int,
locale: Locale, locale: Locale,
replaceAlarm: (Alarm) -> Unit,
addAlarm: () -> Unit, addAlarm: () -> Unit,
deleteAlarm: (Alarm) -> Unit, deleteAlarm: (Alarm) -> Unit,
openRingType: () -> Unit, openRingType: () -> Unit,
@ -107,9 +131,11 @@ fun Alarms(
Column { Column {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
alarms.forEach { alarm -> alarms.forEach { alarm ->
AlarmRow(AlarmToString(LocalContext.current, locale).toString(alarm)) { AlarmRow(
deleteAlarm(alarm) text = AlarmToString(LocalContext.current, locale).toString(alarm),
} onClick = { replaceAlarm(alarm) },
remove = { deleteAlarm(alarm) }
)
} }
Row(modifier = Modifier.fillMaxWidth()) { Row(modifier = Modifier.fillMaxWidth()) {
DisabledText( DisabledText(
@ -119,7 +145,7 @@ fun Alarms(
.clickable( .clickable(
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false), indication = rememberRipple(bounded = false),
onClick = { addAlarm() } onClick = addAlarm,
) )
) )
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
@ -150,10 +176,15 @@ fun Alarms(
} }
@Composable @Composable
private fun AlarmRow(text: String, remove: () -> Unit = {}) { private fun AlarmRow(
text: String,
onClick: () -> Unit,
remove: () -> Unit,
) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { onClick() }
) { ) {
Text( Text(
text = text, text = text,
@ -175,12 +206,12 @@ fun NoAlarms() {
alarms = emptyList(), alarms = emptyList(),
ringMode = 0, ringMode = 0,
locale = Locale.getDefault(), locale = Locale.getDefault(),
newAlarm = {},
addAlarm = {}, addAlarm = {},
deleteAlarm = {}, deleteAlarm = {},
openRingType = {}, openRingType = {},
permissionStatus = PermissionStatus.Granted, permissionStatus = PermissionStatus.Granted,
launchPermissionRequest = {} launchPermissionRequest = {},
pickDateAndTime = {},
) )
} }
} }
@ -195,12 +226,12 @@ fun PermissionDenied() {
alarms = emptyList(), alarms = emptyList(),
ringMode = 0, ringMode = 0,
locale = Locale.getDefault(), locale = Locale.getDefault(),
newAlarm = {},
addAlarm = {}, addAlarm = {},
deleteAlarm = {}, deleteAlarm = {},
openRingType = {}, openRingType = {},
permissionStatus = PermissionStatus.Denied(true), permissionStatus = PermissionStatus.Denied(true),
launchPermissionRequest = {} launchPermissionRequest = {},
pickDateAndTime = {},
) )
} }
} }

@ -28,6 +28,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -385,6 +386,10 @@ class TaskEditViewModel @Inject constructor(
} }
} }
fun removeAlarm(alarm: Alarm) {
selectedAlarms.update { it.minus(alarm) }
}
fun addAlarm(alarm: Alarm) { fun addAlarm(alarm: Alarm) {
with (selectedAlarms) { with (selectedAlarms) {
if (value.none { it.same(alarm) }) { if (value.none { it.same(alarm) }) {

Loading…
Cancel
Save