From 3e3de3c1d6cd6898d63ad3e028e4b7bdc98d99b8 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Thu, 21 Jul 2022 00:38:55 -0500 Subject: [PATCH] Moving logic into composables, adding previews --- .../astrid/repeats/RepeatControlSet.kt | 122 ++++--------- .../todoroo/astrid/ui/StartDateControlSet.kt | 165 ++---------------- .../java/org/tasks/compose/DisabledText.kt | 6 +- .../java/org/tasks/compose/TaskEditRow.kt | 19 +- .../java/org/tasks/compose/edit/DueDateRow.kt | 81 +++++++++ .../org/tasks/compose/edit/LocationRow.kt | 123 +++++++++++++ .../org/tasks/compose/edit/PriorityRow.kt | 96 ++++++++++ .../java/org/tasks/compose/edit/RepeatRow.kt | 127 ++++++++++++++ .../org/tasks/compose/edit/StartDateRow.kt | 129 ++++++++++++++ .../java/org/tasks/ui/DeadlineControlSet.kt | 87 ++------- .../java/org/tasks/ui/LocationControlSet.kt | 116 ++++-------- .../java/org/tasks/ui/PriorityControlSet.kt | 99 ++--------- 12 files changed, 685 insertions(+), 485 deletions(-) create mode 100644 app/src/main/java/org/tasks/compose/edit/DueDateRow.kt create mode 100644 app/src/main/java/org/tasks/compose/edit/LocationRow.kt create mode 100644 app/src/main/java/org/tasks/compose/edit/PriorityRow.kt create mode 100644 app/src/main/java/org/tasks/compose/edit/RepeatRow.kt create mode 100644 app/src/main/java/org/tasks/compose/edit/StartDateRow.kt diff --git a/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt b/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt index 7ecc09a19..56099c84e 100644 --- a/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt @@ -8,24 +8,17 @@ package com.todoroo.astrid.repeats import android.app.Activity.RESULT_OK import android.content.Intent import android.os.Bundle -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material.DropdownMenu -import androidx.compose.material.DropdownMenuItem -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.unit.dp +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView import androidx.lifecycle.lifecycleScope +import com.google.android.material.composethemeadapter.MdcTheme import dagger.hilt.android.AndroidEntryPoint import net.fortuna.ical4j.model.Recur import net.fortuna.ical4j.model.WeekDay import org.tasks.R -import org.tasks.compose.DisabledText import org.tasks.compose.collectAsStateLifecycleAware +import org.tasks.compose.edit.RepeatRow import org.tasks.repeats.BasicRecurrenceDialog import org.tasks.repeats.RecurrenceUtils.newRecur import org.tasks.repeats.RepeatRuleToString @@ -53,7 +46,8 @@ class RepeatControlSet : TaskEditControlComposeFragment() { val recur = newRecur(recurrence) if (recur.frequency == Recur.Frequency.MONTHLY && recur.dayList.isNotEmpty()) { val weekdayNum = recur.dayList[0] - val dateTime = DateTime(this.dueDate) + val dateTime = + DateTime(this.viewModel.dueDate.value.let { if (it > 0) it else currentTimeMillis() }) val num: Int val dayOfWeekInMonth = dateTime.dayOfWeekInMonth num = if (weekdayNum.offset == -1 || dayOfWeekInMonth == 5) { @@ -78,29 +72,29 @@ class RepeatControlSet : TaskEditControlComposeFragment() { } } - private val dueDate: Long - get() = viewModel.dueDate.value.let { if (it > 0) it else currentTimeMillis() } - - override fun onRowClick() { - BasicRecurrenceDialog.newBasicRecurrenceDialog( - this, REQUEST_RECURRENCE, viewModel.recurrence.value, dueDate) - .show(parentFragmentManager, FRAG_TAG_BASIC_RECURRENCE) - } - - override val isClickable = true - - @Composable - override fun Body() { - RepeatRow( - recurrence = viewModel.recurrence.collectAsStateLifecycleAware().value?.let { - repeatRuleToString.toString(it) - }, - repeatFromCompletion = viewModel.repeatAfterCompletion.collectAsStateLifecycleAware().value, - onRepeatFromChanged = { viewModel.repeatAfterCompletion.value = it } - ) - } - - override val icon = R.drawable.ic_outline_repeat_24px + override fun bind(parent: ViewGroup?): View = + (parent as ComposeView).apply { + setContent { + MdcTheme { + RepeatRow( + recurrence = viewModel.recurrence.collectAsStateLifecycleAware().value?.let { + repeatRuleToString.toString(it) + }, + repeatAfterCompletion = viewModel.repeatAfterCompletion.collectAsStateLifecycleAware().value, + onClick = { + BasicRecurrenceDialog.newBasicRecurrenceDialog( + this@RepeatControlSet, + REQUEST_RECURRENCE, + viewModel.recurrence.value, + viewModel.dueDate.value.let { if (it > 0) it else currentTimeMillis() } + ) + .show(parentFragmentManager, FRAG_TAG_BASIC_RECURRENCE) + }, + onRepeatFromChanged = { viewModel.repeatAfterCompletion.value = it } + ) + } + } + } override fun controlId() = TAG @@ -110,59 +104,3 @@ class RepeatControlSet : TaskEditControlComposeFragment() { private const val REQUEST_RECURRENCE = 10000 } } - -@Composable -fun RepeatRow( - recurrence: String?, - repeatFromCompletion: Boolean, - onRepeatFromChanged: (Boolean) -> Unit, -) { - Column { - Spacer(modifier = Modifier.height(20.dp)) - if (recurrence.isNullOrBlank()) { - DisabledText(text = stringResource(id = R.string.repeat_option_does_not_repeat)) - } else { - Text( - text = recurrence, - modifier = Modifier.height(24.dp) - ) - Spacer(modifier = Modifier.height(8.dp)) - Row { - Text(text = stringResource(id = R.string.repeats_from)) - Spacer(modifier = Modifier.width(4.dp)) - var expanded by remember { mutableStateOf(false) } - Text( - text = stringResource( - id = if (repeatFromCompletion) - R.string.repeat_type_completion - else - R.string.repeat_type_due - ), - style = MaterialTheme.typography.body1.copy( - textDecoration = TextDecoration.Underline, - ), - modifier = Modifier.clickable { expanded = true } - ) - DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { - DropdownMenuItem( - onClick = { - expanded = false - onRepeatFromChanged(false) - } - ) { - Text(text = stringResource(id = R.string.repeat_type_due)) - } - DropdownMenuItem( - onClick = { - expanded = false - onRepeatFromChanged(true) - } - ) { - Text(text = stringResource(id = R.string.repeat_type_completion)) - } - } - } - } - Spacer(modifier = Modifier.height(20.dp)) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/ui/StartDateControlSet.kt b/app/src/main/java/com/todoroo/astrid/ui/StartDateControlSet.kt index d454d8512..0a5edd259 100644 --- a/app/src/main/java/com/todoroo/astrid/ui/StartDateControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/ui/StartDateControlSet.kt @@ -3,47 +3,26 @@ package com.todoroo.astrid.ui import android.app.Activity import android.content.Context import android.content.Intent -import android.content.res.Configuration import android.os.Bundle import android.view.ViewGroup -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material.ContentAlpha -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import com.google.android.material.composethemeadapter.MdcTheme import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities.getTimeString -import com.todoroo.andlib.utility.DateUtilities.now -import com.todoroo.astrid.ui.StartDateControlSet.Companion.getRelativeDateString import dagger.hilt.android.AndroidEntryPoint import org.tasks.R -import org.tasks.compose.TaskEditIcon -import org.tasks.compose.TaskEditRow import org.tasks.compose.collectAsStateLifecycleAware +import org.tasks.compose.edit.StartDateRow import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.dialogs.StartDatePicker -import org.tasks.dialogs.StartDatePicker.Companion.DAY_BEFORE_DUE -import org.tasks.dialogs.StartDatePicker.Companion.DUE_DATE -import org.tasks.dialogs.StartDatePicker.Companion.DUE_TIME import org.tasks.dialogs.StartDatePicker.Companion.EXTRA_DAY import org.tasks.dialogs.StartDatePicker.Companion.EXTRA_TIME import org.tasks.dialogs.StartDatePicker.Companion.NO_DAY import org.tasks.dialogs.StartDatePicker.Companion.NO_TIME -import org.tasks.dialogs.StartDatePicker.Companion.WEEK_BEFORE_DUE import org.tasks.preferences.Preferences import org.tasks.ui.TaskEditControlComposeFragment -import org.tasks.ui.TaskEditViewModel import java.time.format.FormatStyle import java.util.* import javax.inject.Inject @@ -70,11 +49,23 @@ class StartDateControlSet : TaskEditControlComposeFragment() { (parent as ComposeView).apply { setContent { MdcTheme { + val selectedDay = vm.selectedDay.collectAsStateLifecycleAware().value + val selectedTime = vm.selectedTime.collectAsStateLifecycleAware().value StartDateRow( - viewModel = viewModel, - vm = vm, - preferences = preferences, - locale = locale, + startDate = viewModel.startDate.collectAsStateLifecycleAware().value, + selectedDay = selectedDay, + selectedTime = selectedTime, + hasDueDate = viewModel.dueDate.collectAsStateLifecycleAware().value > 0, + printDate = { + DateUtilities.getRelativeDateTime( + context, + selectedDay + selectedTime, + locale, + FormatStyle.FULL, + preferences.alwaysDisplayFullDate, + false + ) + }, onClick = { val fragmentManager = parentFragmentManager if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_PICKER) == null) { @@ -130,125 +121,3 @@ class StartDateControlSet : TaskEditControlComposeFragment() { } } } - -@Composable -fun StartDateRow( - viewModel: TaskEditViewModel, - vm: StartDateViewModel, - preferences: Preferences, - locale: Locale, - onClick: () -> Unit, -) { - TaskEditRow( - icon = { - TaskEditIcon( - id = R.drawable.ic_pending_actions_24px, - modifier = Modifier - .padding( - start = 16.dp, - top = 20.dp, - end = 32.dp, - bottom = 20.dp - ) - ) - }, - content = { - StartDate( - startDate = viewModel.startDate.collectAsStateLifecycleAware().value, - selectedDay = vm.selectedDay.collectAsStateLifecycleAware().value, - selectedTime = vm.selectedTime.collectAsStateLifecycleAware().value, - displayFullDate = preferences.alwaysDisplayFullDate, - locale = locale, - hasDueDate = viewModel.dueDate.collectAsStateLifecycleAware().value > 0 - ) - }, - onClick = onClick - ) -} - -@Composable -fun StartDate( - startDate: Long, - selectedDay: Long, - selectedTime: Int, - displayFullDate: Boolean, - locale: Locale = Locale.getDefault(), - currentTime: Long = now(), - hasDueDate: Boolean, -) { - val context = LocalContext.current - Text( - text = when (selectedDay) { - DUE_DATE -> context.getRelativeDateString(R.string.due_date, selectedTime) - DUE_TIME -> context.getString(R.string.due_time) - DAY_BEFORE_DUE -> context.getRelativeDateString(R.string.day_before_due, selectedTime) - WEEK_BEFORE_DUE -> context.getRelativeDateString(R.string.week_before_due, selectedTime) - in 1..Long.MAX_VALUE -> DateUtilities.getRelativeDateTime( - LocalContext.current, - selectedDay + selectedTime, - locale, - FormatStyle.FULL, - displayFullDate, - false - ) - else -> stringResource(id = R.string.no_start_date) - }, - color = when { - selectedDay < 0 && !hasDueDate -> colorResource(id = R.color.overdue) - startDate == 0L -> MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) - startDate < currentTime -> colorResource(id = R.color.overdue) - else -> MaterialTheme.colors.onSurface - }, - modifier = Modifier - .padding(vertical = 20.dp) - .height(24.dp), - ) -} - -@Preview(showBackground = true) -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -fun NoStartDate() { - MdcTheme { - StartDate( - startDate = 0L, - selectedDay = NO_DAY, - selectedTime = NO_TIME, - displayFullDate = false, - currentTime = 1657080392000L, - hasDueDate = false, - ) - } -} - -@Preview(showBackground = true) -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -fun FutureStartDate() { - MdcTheme { - StartDate( - startDate = 1657080392000L, - selectedDay = DUE_DATE, - selectedTime = NO_TIME, - displayFullDate = false, - currentTime = 1657080392000L, - hasDueDate = false, - ) - } -} - -@Preview(showBackground = true) -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -fun PastStartDate() { - MdcTheme { - StartDate( - startDate = 1657080392000L, - selectedDay = DUE_TIME, - selectedTime = NO_TIME, - displayFullDate = false, - currentTime = 1657080392001L, - hasDueDate = false, - ) - } -} diff --git a/app/src/main/java/org/tasks/compose/DisabledText.kt b/app/src/main/java/org/tasks/compose/DisabledText.kt index d0f7916ed..5e8e4450a 100644 --- a/app/src/main/java/org/tasks/compose/DisabledText.kt +++ b/app/src/main/java/org/tasks/compose/DisabledText.kt @@ -1,6 +1,7 @@ package org.tasks.compose -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.padding import androidx.compose.material.ContentAlpha import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -19,6 +20,7 @@ fun DisabledText( style = MaterialTheme.typography.body1, modifier = modifier .alpha(alpha = ContentAlpha.disabled) - .height(24.dp), + .padding(end = 16.dp) + .defaultMinSize(minHeight = 24.dp), ) } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/compose/TaskEditRow.kt b/app/src/main/java/org/tasks/compose/TaskEditRow.kt index 700ee23d6..bbeb66912 100644 --- a/app/src/main/java/org/tasks/compose/TaskEditRow.kt +++ b/app/src/main/java/org/tasks/compose/TaskEditRow.kt @@ -2,12 +2,29 @@ package org.tasks.compose import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.ContentAlpha import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.unit.dp @Composable fun TaskEditRow( - icon: @Composable () -> Unit, + iconRes: Int = 0, + icon: @Composable () -> Unit = { + TaskEditIcon( + id = iconRes, + modifier = Modifier + .alpha(ContentAlpha.medium) + .padding( + start = 16.dp, + top = 20.dp, + end = 32.dp, + bottom = 20.dp + ) + ) + }, content: @Composable () -> Unit, onClick: (() -> Unit)? = null, ) { diff --git a/app/src/main/java/org/tasks/compose/edit/DueDateRow.kt b/app/src/main/java/org/tasks/compose/edit/DueDateRow.kt new file mode 100644 index 000000000..7f8b354e9 --- /dev/null +++ b/app/src/main/java/org/tasks/compose/edit/DueDateRow.kt @@ -0,0 +1,81 @@ +package org.tasks.compose.edit + +import android.content.res.Configuration +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.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.google.android.material.composethemeadapter.MdcTheme +import org.tasks.R +import org.tasks.compose.DisabledText +import org.tasks.compose.TaskEditRow + +@Composable +fun DueDateRow( + dueDate: String?, + overdue: Boolean, + onClick: () -> Unit, +) { + TaskEditRow( + iconRes = R.drawable.ic_outline_schedule_24px, + content = { + DueDate( + dueDate = dueDate, + overdue = overdue, + ) + }, + onClick = onClick, + ) +} + +@Composable +fun DueDate(dueDate: String?, overdue: Boolean) { + if (dueDate.isNullOrBlank()) { + DisabledText( + text = stringResource(id = R.string.no_due_date), + modifier = Modifier.padding(top = 20.dp, bottom = 20.dp, end = 16.dp) + ) + } else { + Text( + text = dueDate, + color = if (overdue) { + colorResource(id = R.color.overdue) + } else { + MaterialTheme.colors.onSurface + }, + modifier = Modifier.padding(top = 20.dp, bottom = 20.dp, end = 16.dp) + ) + } +} + +@ExperimentalComposeUiApi +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun DueDatePreview() { + MdcTheme { + DueDateRow( + dueDate = "Today", + overdue = false, + ) {} + } +} + +@ExperimentalComposeUiApi +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun NoDueDatePreview() { + MdcTheme { + DueDateRow( + dueDate = null, + overdue = false, + ) {} + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/compose/edit/LocationRow.kt b/app/src/main/java/org/tasks/compose/edit/LocationRow.kt new file mode 100644 index 000000000..8a13e9678 --- /dev/null +++ b/app/src/main/java/org/tasks/compose/edit/LocationRow.kt @@ -0,0 +1,123 @@ +package org.tasks.compose.edit + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.ContentAlpha +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Notifications +import androidx.compose.material.icons.outlined.NotificationsOff +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.google.android.material.composethemeadapter.MdcTheme +import org.tasks.R +import org.tasks.compose.DisabledText +import org.tasks.compose.TaskEditRow +import org.tasks.data.Geofence +import org.tasks.data.Location +import org.tasks.data.Place + +@Composable +fun LocationRow( + location: Location?, + hasPermissions: Boolean, + onClick: () -> Unit, + openGeofenceOptions: () -> Unit, +) { + TaskEditRow( + iconRes = R.drawable.ic_outline_place_24px, + content = { + if (location == null) { + DisabledText( + text = stringResource(id = R.string.add_location), + modifier = Modifier.padding(vertical = 20.dp) + ) + } else { + Location( + name = location.displayName, + address = location.displayAddress, + openGeofenceOptions = openGeofenceOptions, + geofenceOn = hasPermissions && (location.isArrival || location.isDeparture) + ) + } + }, + onClick = onClick + ) +} + +@Composable +fun Location( + name: String, + address: String?, + geofenceOn: Boolean, + openGeofenceOptions: () -> Unit, +) { + Row { + Column( + modifier = Modifier + .weight(1f) + .padding(vertical = 20.dp) + ) { + Text(text = name) + address?.takeIf { it.isNotBlank() && it != name }?.let { + Text(text = address) + } + } + IconButton( + onClick = openGeofenceOptions, + modifier = Modifier.padding(top = 8.dp /* + 12dp from icon */) + ) { + Icon( + imageVector = if (geofenceOn) { + Icons.Outlined.Notifications + } else { + Icons.Outlined.NotificationsOff + }, + contentDescription = null, + modifier = Modifier.alpha(ContentAlpha.medium), + ) + } + } +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun NoLocation() { + MdcTheme { + LocationRow( + location = null, + hasPermissions = true, + onClick = {}, + openGeofenceOptions = {}, + ) + } +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun SampleLocation() { + MdcTheme { + LocationRow( + location = Location( + Geofence(), + Place().apply { + name = "Googleplex" + address = "1600 Amphitheatre Pkwy, Mountain View, CA 94043" + }, + ), + hasPermissions = true, + onClick = {}, + openGeofenceOptions = {}, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/compose/edit/PriorityRow.kt b/app/src/main/java/org/tasks/compose/edit/PriorityRow.kt new file mode 100644 index 000000000..e66d86d80 --- /dev/null +++ b/app/src/main/java/org/tasks/compose/edit/PriorityRow.kt @@ -0,0 +1,96 @@ +package org.tasks.compose.edit + +import android.content.res.Configuration +import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.RadioButton +import androidx.compose.material.RadioButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.google.android.material.composethemeadapter.MdcTheme +import com.todoroo.astrid.data.Task +import org.tasks.R +import org.tasks.compose.TaskEditRow + +@Composable +fun PriorityRow( + priority: Int, + onChangePriority: (Int) -> Unit, +) { + TaskEditRow( + iconRes = R.drawable.ic_outline_flag_24px, + content = { + Priority( + selected = priority, + onClick = { onChangePriority(it) } + ) + }, + ) +} + +@Composable +fun Priority( + selected: Int, + onClick: (Int) -> Unit = {} +) { + Row( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding( + top = dimensionResource(id = R.dimen.half_keyline_first), + bottom = dimensionResource(id = R.dimen.half_keyline_first), + end = 16.dp + ), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(id = R.string.TEA_importance_label), + style = MaterialTheme.typography.body1, + ) + Spacer(modifier = Modifier.weight(1f)) + for (i in Task.Priority.HIGH..Task.Priority.NONE) { + PriorityButton(priority = i, selected = selected, onClick = onClick) + } + } +} + +@Composable +fun PriorityButton( + @Task.Priority priority: Int, + selected: Int, + onClick: (Int) -> Unit, +) { + val color = when (priority) { + in Int.MIN_VALUE..Task.Priority.HIGH -> colorResource(id = R.color.red_500) + Task.Priority.MEDIUM -> colorResource(id = R.color.amber_500) + Task.Priority.LOW -> colorResource(id = R.color.blue_500) + else -> colorResource(R.color.grey_500) + } + RadioButton( + selected = priority == selected, + onClick = { onClick(priority) }, + colors = RadioButtonDefaults.colors( + selectedColor = color, + unselectedColor = color, + ), + ) +} + +@ExperimentalComposeUiApi +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun PriorityPreview() { + MdcTheme { + PriorityRow(priority = Task.Priority.MEDIUM) {} + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/compose/edit/RepeatRow.kt b/app/src/main/java/org/tasks/compose/edit/RepeatRow.kt new file mode 100644 index 000000000..390888c19 --- /dev/null +++ b/app/src/main/java/org/tasks/compose/edit/RepeatRow.kt @@ -0,0 +1,127 @@ +package org.tasks.compose.edit + +import android.content.res.Configuration +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.google.android.material.composethemeadapter.MdcTheme +import org.tasks.R +import org.tasks.compose.DisabledText +import org.tasks.compose.TaskEditRow + +@Composable +fun RepeatRow( + recurrence: String?, + repeatAfterCompletion: Boolean, + onClick: () -> Unit, + onRepeatFromChanged: (Boolean) -> Unit, +) { + TaskEditRow( + iconRes = R.drawable.ic_outline_repeat_24px, + content = { + Repeat( + recurrence = recurrence, + repeatFromCompletion = repeatAfterCompletion, + onRepeatFromChanged = onRepeatFromChanged, + ) + }, + onClick = onClick, + ) +} + +@Composable +fun Repeat( + recurrence: String?, + repeatFromCompletion: Boolean, + onRepeatFromChanged: (Boolean) -> Unit, +) { + Column { + Spacer(modifier = Modifier.height(20.dp)) + if (recurrence.isNullOrBlank()) { + DisabledText(text = stringResource(id = R.string.repeat_option_does_not_repeat)) + } else { + Text( + text = recurrence, + modifier = Modifier.defaultMinSize(minHeight = 24.dp).padding(end = 16.dp).fillMaxWidth(), + maxLines = Int.MAX_VALUE, + ) + Spacer(modifier = Modifier.height(8.dp)) + Row { + Text(text = stringResource(id = R.string.repeats_from)) + Spacer(modifier = Modifier.width(4.dp)) + var expanded by remember { mutableStateOf(false) } + Text( + text = stringResource( + id = if (repeatFromCompletion) + R.string.repeat_type_completion + else + R.string.repeat_type_due + ), + style = MaterialTheme.typography.body1.copy( + textDecoration = TextDecoration.Underline, + ), + modifier = Modifier.clickable { expanded = true } + ) + DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { + DropdownMenuItem( + onClick = { + expanded = false + onRepeatFromChanged(false) + } + ) { + Text(text = stringResource(id = R.string.repeat_type_due)) + } + DropdownMenuItem( + onClick = { + expanded = false + onRepeatFromChanged(true) + } + ) { + Text(text = stringResource(id = R.string.repeat_type_completion)) + } + } + } + } + Spacer(modifier = Modifier.height(20.dp)) + } +} + +@ExperimentalComposeUiApi +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun RepeatPreview() { + MdcTheme { + RepeatRow( + recurrence = "Repeats weekly on Mon, Tue, Wed, Thu, Fri", + repeatAfterCompletion = false, + onClick = {}, + onRepeatFromChanged = {}, + ) + } +} + +@ExperimentalComposeUiApi +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun NoRepeatPreview() { + MdcTheme { + RepeatRow( + recurrence = null, + repeatAfterCompletion = false, + onClick = {}, + onRepeatFromChanged = {}, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/compose/edit/StartDateRow.kt b/app/src/main/java/org/tasks/compose/edit/StartDateRow.kt new file mode 100644 index 000000000..1cd940c6c --- /dev/null +++ b/app/src/main/java/org/tasks/compose/edit/StartDateRow.kt @@ -0,0 +1,129 @@ +package org.tasks.compose.edit + +import android.content.res.Configuration +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.ContentAlpha +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.google.android.material.composethemeadapter.MdcTheme +import com.todoroo.andlib.utility.DateUtilities +import com.todoroo.astrid.ui.StartDateControlSet.Companion.getRelativeDateString +import org.tasks.R +import org.tasks.compose.TaskEditRow +import org.tasks.dialogs.StartDatePicker + +@Composable +fun StartDateRow( + startDate: Long, + selectedDay: Long, + selectedTime: Int, + currentTime: Long = DateUtilities.now(), + hasDueDate: Boolean, + printDate: () -> String, + onClick: () -> Unit, +) { + TaskEditRow( + iconRes = R.drawable.ic_pending_actions_24px, + content = { + StartDate( + startDate = startDate, + selectedDay = selectedDay, + selectedTime = selectedTime, + currentTime = currentTime, + hasDueDate = hasDueDate, + printDate = printDate, + ) + }, + onClick = onClick + ) +} + +@Composable +fun StartDate( + startDate: Long, + selectedDay: Long, + selectedTime: Int, + currentTime: Long, + hasDueDate: Boolean, + printDate: () -> String, +) { + val context = LocalContext.current + Text( + text = when (selectedDay) { + StartDatePicker.DUE_DATE -> context.getRelativeDateString(R.string.due_date, selectedTime) + StartDatePicker.DUE_TIME -> context.getString(R.string.due_time) + StartDatePicker.DAY_BEFORE_DUE -> context.getRelativeDateString(R.string.day_before_due, selectedTime) + StartDatePicker.WEEK_BEFORE_DUE -> context.getRelativeDateString(R.string.week_before_due, selectedTime) + in 1..Long.MAX_VALUE -> printDate() + else -> stringResource(id = R.string.no_start_date) + }, + color = when { + selectedDay < 0 && !hasDueDate -> colorResource(id = R.color.overdue) + startDate == 0L -> MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) + startDate < currentTime -> colorResource(id = R.color.overdue) + else -> MaterialTheme.colors.onSurface + }, + modifier = Modifier + .padding(vertical = 20.dp) + .height(24.dp), + ) +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun NoStartDate() { + MdcTheme { + StartDateRow( + startDate = 0L, + selectedDay = StartDatePicker.NO_DAY, + selectedTime = StartDatePicker.NO_TIME, + currentTime = 1657080392000L, + hasDueDate = false, + printDate = { "" }, + onClick = {}, + ) + } +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun FutureStartDate() { + MdcTheme { + StartDateRow( + startDate = 1657080392000L, + selectedDay = StartDatePicker.DUE_DATE, + selectedTime = StartDatePicker.NO_TIME, + currentTime = 1657080392000L, + hasDueDate = false, + printDate = { "" }, + onClick = {}, + ) + } +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun PastStartDate() { + MdcTheme { + StartDateRow( + startDate = 1657080392000L, + selectedDay = StartDatePicker.DUE_TIME, + selectedTime = StartDatePicker.NO_TIME, + currentTime = 1657080392001L, + hasDueDate = false, + printDate = { "" }, + onClick = {}, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/DeadlineControlSet.kt b/app/src/main/java/org/tasks/ui/DeadlineControlSet.kt index 038cc7d4a..19a491e14 100644 --- a/app/src/main/java/org/tasks/ui/DeadlineControlSet.kt +++ b/app/src/main/java/org/tasks/ui/DeadlineControlSet.kt @@ -3,29 +3,18 @@ package org.tasks.ui import android.app.Activity import android.content.Intent import android.view.ViewGroup -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.platform.ComposeView import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import com.google.android.material.composethemeadapter.MdcTheme import com.todoroo.andlib.utility.DateUtilities import com.todoroo.astrid.data.Task.Companion.hasDueTime import dagger.hilt.android.AndroidEntryPoint import org.tasks.R -import org.tasks.compose.DisabledText -import org.tasks.compose.TaskEditIcon -import org.tasks.compose.TaskEditRow import org.tasks.compose.collectAsStateLifecycleAware +import org.tasks.compose.edit.DueDateRow import org.tasks.date.DateTimeUtils import org.tasks.dialogs.DateTimePicker import org.tasks.preferences.Preferences -import org.tasks.ui.DeadlineControlSet.Companion.isOverdue import java.time.format.FormatStyle import java.util.* import javax.inject.Inject @@ -39,10 +28,21 @@ class DeadlineControlSet : TaskEditControlComposeFragment() { (parent as ComposeView).apply { setContent { MdcTheme { + val dueDate = viewModel.dueDate.collectAsStateLifecycleAware().value DueDateRow( - viewModel = viewModel, - locale = locale, - displayFullDate = preferences.alwaysDisplayFullDate, + dueDate = if (dueDate == 0L) { + null + } else { + DateUtilities.getRelativeDateTime( + LocalContext.current, + dueDate, + locale, + FormatStyle.FULL, + preferences.alwaysDisplayFullDate, + false + ) + }, + overdue = dueDate.isOverdue, onClick = { val fragmentManager = parentFragmentManager if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_PICKER) == null) { @@ -84,60 +84,3 @@ class DeadlineControlSet : TaskEditControlComposeFragment() { } } } - -@Composable -fun DueDateRow( - viewModel: TaskEditViewModel, - locale: Locale, - displayFullDate: Boolean, - onClick: () -> Unit, -) { - TaskEditRow( - icon = { - TaskEditIcon( - id = R.drawable.ic_outline_schedule_24px, - modifier = Modifier.padding( - start = 16.dp, - top = 20.dp, - end = 32.dp, - bottom = 20.dp - ) - ) - }, - content = { - DueDate( - dueDate = viewModel.dueDate.collectAsStateLifecycleAware().value, - locale = locale, - displayFullDate = displayFullDate, - ) - }, - onClick = onClick, - ) -} - -@Composable -fun DueDate(dueDate: Long, locale: Locale, displayFullDate: Boolean) { - if (dueDate == 0L) { - DisabledText( - text = stringResource(id = R.string.no_due_date), - modifier = Modifier.padding(vertical = 20.dp) - ) - } else { - Text( - text = DateUtilities.getRelativeDateTime( - LocalContext.current, - dueDate, - locale, - FormatStyle.FULL, - displayFullDate, - false - ), - color = if (dueDate.isOverdue) { - colorResource(id = R.color.overdue) - } else { - MaterialTheme.colors.onSurface - }, - modifier = Modifier.padding(vertical = 20.dp) - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/LocationControlSet.kt b/app/src/main/java/org/tasks/ui/LocationControlSet.kt index 7314a15f4..d67dce282 100644 --- a/app/src/main/java/org/tasks/ui/LocationControlSet.kt +++ b/app/src/main/java/org/tasks/ui/LocationControlSet.kt @@ -4,29 +4,18 @@ import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Parcelable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.material.ContentAlpha -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Notifications -import androidx.compose.material.icons.outlined.NotificationsOff -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView import androidx.core.util.Pair import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberMultiplePermissionsState +import com.google.android.material.composethemeadapter.MdcTheme import dagger.hilt.android.AndroidEntryPoint import org.tasks.R import org.tasks.Strings.isNullOrEmpty -import org.tasks.compose.DisabledText import org.tasks.compose.collectAsStateLifecycleAware +import org.tasks.compose.edit.LocationRow import org.tasks.data.Geofence import org.tasks.data.Location import org.tasks.data.Place @@ -35,8 +24,6 @@ import org.tasks.dialogs.GeofenceDialog import org.tasks.extensions.Context.openUri import org.tasks.location.LocationPermissionDialog.Companion.newLocationPermissionDialog import org.tasks.location.LocationPickerActivity -import org.tasks.preferences.Device -import org.tasks.preferences.FragmentPermissionRequestor import org.tasks.preferences.PermissionChecker import org.tasks.preferences.PermissionChecker.backgroundPermissions import org.tasks.preferences.Preferences @@ -46,8 +33,6 @@ import javax.inject.Inject class LocationControlSet : TaskEditControlComposeFragment() { @Inject lateinit var preferences: Preferences @Inject lateinit var dialogBuilder: DialogBuilder - @Inject lateinit var device: Device - @Inject lateinit var permissionRequestor: FragmentPermissionRequestor @Inject lateinit var permissionChecker: PermissionChecker private fun setLocation(location: Location?) { @@ -94,40 +79,35 @@ class LocationControlSet : TaskEditControlComposeFragment() { } @OptIn(ExperimentalPermissionsApi::class) - @Composable - override fun Body() { - val location = viewModel.selectedLocation.collectAsStateLifecycleAware().value - val hasPermissions = - rememberMultiplePermissionsState(permissions = backgroundPermissions()) - .allPermissionsGranted - if (location == null) { - DisabledText( - text = stringResource(id = R.string.add_location), - modifier = Modifier.padding(vertical = 20.dp) - ) - } else { - LocationRow( - name = location.displayName, - address = location.displayAddress, - onClick = { - if (hasPermissions) { - showGeofenceOptions() - } else { - newLocationPermissionDialog(this, REQUEST_LOCATION_PERMISSIONS) - .show(parentFragmentManager, FRAG_TAG_REQUEST_LOCATION) - } - }, - geofenceOn = hasPermissions && (location.isArrival || location.isDeparture) - ) + override fun bind(parent: ViewGroup?): View = + (parent as ComposeView).apply { + setContent { + MdcTheme { + val hasPermissions = + rememberMultiplePermissionsState(permissions = backgroundPermissions()) + .allPermissionsGranted + LocationRow( + location = viewModel.selectedLocation.collectAsStateLifecycleAware().value, + hasPermissions = hasPermissions, + onClick = this@LocationControlSet::onRowClick, + openGeofenceOptions = { + if (hasPermissions) { + showGeofenceOptions() + } else { + newLocationPermissionDialog( + this@LocationControlSet, + REQUEST_LOCATION_PERMISSIONS + ) + .show(parentFragmentManager, FRAG_TAG_REQUEST_LOCATION) + } + } + ) + } + } } - } - - override val icon = R.drawable.ic_outline_place_24px override fun controlId() = TAG - override val isClickable = true - private fun openWebsite() { viewModel.selectedLocation.value?.let { context?.openUri(it.url) } } @@ -180,39 +160,3 @@ class LocationControlSet : TaskEditControlComposeFragment() { private const val FRAG_TAG_REQUEST_LOCATION = "request_location" } } - -@Composable -fun LocationRow( - name: String, - address: String?, - geofenceOn: Boolean, - onClick: () -> Unit, -) { - Row { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 20.dp) - ) { - Text(text = name) - address?.takeIf { it.isNotBlank() && it != name }?.let { - Text(text = address) - } - } - IconButton( - onClick = onClick, - modifier = Modifier.padding(top = 8.dp /* + 12dp from icon */) - ) { - Icon( - imageVector = if (geofenceOn) { - Icons.Outlined.Notifications - } else { - Icons.Outlined.NotificationsOff - }, - contentDescription = null, - modifier = Modifier.alpha(ContentAlpha.medium), - ) - } - } -} - diff --git a/app/src/main/java/org/tasks/ui/PriorityControlSet.kt b/app/src/main/java/org/tasks/ui/PriorityControlSet.kt index 81cc04907..a17a40595 100644 --- a/app/src/main/java/org/tasks/ui/PriorityControlSet.kt +++ b/app/src/main/java/org/tasks/ui/PriorityControlSet.kt @@ -1,38 +1,28 @@ package org.tasks.ui -import android.content.res.Configuration -import androidx.compose.foundation.layout.* -import androidx.compose.material.MaterialTheme -import androidx.compose.material.RadioButton -import androidx.compose.material.RadioButtonDefaults -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView import com.google.android.material.composethemeadapter.MdcTheme -import com.todoroo.astrid.data.Task import dagger.hilt.android.AndroidEntryPoint import org.tasks.R import org.tasks.compose.collectAsStateLifecycleAware +import org.tasks.compose.edit.PriorityRow @AndroidEntryPoint class PriorityControlSet : TaskEditControlComposeFragment() { - @Composable - override fun Body() { - val priority = viewModel.priority.collectAsStateLifecycleAware() - PriorityRow( - selected = priority.value, - onClick = { viewModel.priority.value = it }) - } - - override val icon = R.drawable.ic_outline_flag_24px + override fun bind(parent: ViewGroup?): View = + (parent as ComposeView).apply { + setContent { + MdcTheme { + PriorityRow( + priority = viewModel.priority.collectAsStateLifecycleAware().value, + onChangePriority = { viewModel.priority.value = it } + ) + } + } + } override fun controlId() = TAG @@ -40,62 +30,3 @@ class PriorityControlSet : TaskEditControlComposeFragment() { const val TAG = R.string.TEA_ctrl_importance_pref } } - -@Composable -fun PriorityRow( - selected: Int, - onClick: (Int) -> Unit = {} -) { - Row( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding( - top = dimensionResource(id = R.dimen.half_keyline_first), - bottom = dimensionResource(id = R.dimen.half_keyline_first), - end = 16.dp - ), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(id = R.string.TEA_importance_label), - style = MaterialTheme.typography.body1, - ) - Spacer(modifier = Modifier.weight(1f)) - for (i in Task.Priority.HIGH..Task.Priority.NONE) { - PriorityButton(priority = i, selected = selected, onClick = onClick) - } - } -} - -@Composable -fun PriorityButton( - @Task.Priority priority: Int, - selected: Int, - onClick: (Int) -> Unit, -) { - val color = when (priority) { - in Int.MIN_VALUE..Task.Priority.HIGH -> colorResource(id = R.color.red_500) - Task.Priority.MEDIUM -> colorResource(id = R.color.amber_500) - Task.Priority.LOW -> colorResource(id = R.color.blue_500) - else -> colorResource(R.color.grey_500) - } - RadioButton( - selected = priority == selected, - onClick = { onClick(priority) }, - colors = RadioButtonDefaults.colors( - selectedColor = color, - unselectedColor = color, - ), - ) -} - -@ExperimentalComposeUiApi -@Preview(showBackground = true) -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -fun PriorityPreview() { - MdcTheme { - PriorityRow(selected = Task.Priority.MEDIUM) - } -} \ No newline at end of file