diff --git a/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.kt b/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.kt index a6e5e5c5f..bc9404f7f 100644 --- a/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.kt @@ -5,45 +5,33 @@ */ package com.todoroo.astrid.files -import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Bundle -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -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.Delete -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment.Companion.CenterVertically -import androidx.compose.ui.Modifier -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.lifecycle.lifecycleScope +import com.google.android.material.composethemeadapter.MdcTheme import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.tasks.R -import org.tasks.compose.DisabledText import org.tasks.compose.collectAsStateLifecycleAware +import org.tasks.compose.edit.AttachmentRow import org.tasks.data.TaskAttachment import org.tasks.data.TaskAttachmentDao import org.tasks.dialogs.AddAttachmentDialog import org.tasks.dialogs.DialogBuilder import org.tasks.files.FileHelper import org.tasks.preferences.Preferences -import org.tasks.ui.TaskEditControlComposeFragment +import org.tasks.ui.TaskEditControlFragment import javax.inject.Inject @AndroidEntryPoint -class FilesControlSet : TaskEditControlComposeFragment() { +class FilesControlSet : TaskEditControlFragment() { @Inject lateinit var taskAttachmentDao: TaskAttachmentDao @Inject lateinit var dialogBuilder: DialogBuilder @Inject lateinit var preferences: Preferences @@ -59,53 +47,25 @@ class FilesControlSet : TaskEditControlComposeFragment() { } } - private fun addAttachment() { - AddAttachmentDialog.newAddAttachmentDialog(this) - .show(parentFragmentManager, FRAG_TAG_ADD_ATTACHMENT_DIALOG) - } - - @Composable - override fun Body() { - val attachments = - taskAttachmentDao.watchAttachments(viewModel.task.uuid) - .collectAsStateLifecycleAware(initial = emptyList()).value - Column( - modifier = Modifier.padding(top = if (attachments.isEmpty()) 0.dp else 8.dp), - ) { - attachments.forEach { - Row( - modifier = Modifier - .clickable { showFile(it) }, - verticalAlignment = CenterVertically, - ) { - Text( - text = it.name!!, - modifier = Modifier.weight(1f), + override fun bind(parent: ViewGroup?): View = + (parent as ComposeView).apply { + setContent { + MdcTheme { + AttachmentRow( + attachments = taskAttachmentDao.watchAttachments(viewModel.task.uuid) + .collectAsStateLifecycleAware(initial = emptyList()).value, + openAttachment = { + FileHelper.startActionView(requireActivity(), it.parseUri()) + }, + deleteAttachment = this@FilesControlSet::deleteAttachment, + addAttachment = { + AddAttachmentDialog.newAddAttachmentDialog(this@FilesControlSet) + .show(parentFragmentManager, FRAG_TAG_ADD_ATTACHMENT_DIALOG) + }, ) - IconButton(onClick = { deleteAttachment(it) }) { - Icon( - imageVector = Icons.Outlined.Delete, - contentDescription = stringResource( - id = R.string.delete - ) - ) - } } } - DisabledText( - text = stringResource(id = R.string.add_attachment), - modifier = Modifier - .fillMaxWidth() - .clickable { addAttachment() } - .padding( - top = if (attachments.isEmpty()) 20.dp else 8.dp, - bottom = 20.dp, - ) - ) } - } - - override val icon = R.drawable.ic_outline_attachment_24px override fun controlId() = TAG @@ -148,11 +108,6 @@ class FilesControlSet : TaskEditControlComposeFragment() { .show() } - @SuppressLint("NewApi") - private fun showFile(m: TaskAttachment) { - FileHelper.startActionView(requireActivity(), m.parseUri()) - } - private fun copyToAttachmentDirectory(input: Uri?) { newAttachment(FileHelper.copyToUri(requireContext(), preferences.attachmentsDirectory!!, input!!)) } @@ -171,4 +126,4 @@ class FilesControlSet : TaskEditControlComposeFragment() { const val TAG = R.string.TEA_ctrl_files_pref private const val FRAG_TAG_ADD_ATTACHMENT_DIALOG = "frag_tag_add_attachment_dialog" } -} \ No newline at end of file +} 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 56099c84e..6bb876012 100644 --- a/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt @@ -24,11 +24,11 @@ import org.tasks.repeats.RecurrenceUtils.newRecur import org.tasks.repeats.RepeatRuleToString import org.tasks.time.DateTime import org.tasks.time.DateTimeUtils.currentTimeMillis -import org.tasks.ui.TaskEditControlComposeFragment +import org.tasks.ui.TaskEditControlFragment import javax.inject.Inject @AndroidEntryPoint -class RepeatControlSet : TaskEditControlComposeFragment() { +class RepeatControlSet : TaskEditControlFragment() { @Inject lateinit var repeatRuleToString: RepeatRuleToString override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.kt b/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.kt index 1c23ed8cb..6e53ed7f8 100644 --- a/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.kt @@ -12,11 +12,11 @@ import org.tasks.compose.collectAsStateLifecycleAware import org.tasks.compose.edit.TagsRow import org.tasks.tags.TagPickerActivity import org.tasks.ui.ChipProvider -import org.tasks.ui.TaskEditControlComposeFragment +import org.tasks.ui.TaskEditControlFragment import javax.inject.Inject @AndroidEntryPoint -class TagsControlSet : TaskEditControlComposeFragment() { +class TagsControlSet : TaskEditControlFragment() { @Inject lateinit var chipProvider: ChipProvider override fun onRowClick() { diff --git a/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.kt b/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.kt index 3e186f70a..ceea27663 100644 --- a/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.kt @@ -7,38 +7,23 @@ package com.todoroo.astrid.timers import android.app.Activity import android.os.Bundle -import android.text.format.DateUtils import android.view.View +import android.view.ViewGroup import androidx.appcompat.app.AlertDialog -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.Pause -import androidx.compose.material.icons.outlined.PlayArrow -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp +import androidx.compose.ui.platform.ComposeView import androidx.lifecycle.lifecycleScope +import com.google.android.material.composethemeadapter.MdcTheme import com.todoroo.astrid.data.Task import com.todoroo.astrid.ui.TimeDurationControlSet import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.tasks.R -import org.tasks.compose.DisabledText import org.tasks.compose.collectAsStateLifecycleAware +import org.tasks.compose.edit.TimerRow import org.tasks.dialogs.DialogBuilder import org.tasks.themes.Theme -import org.tasks.ui.TaskEditControlComposeFragment -import java.lang.System.currentTimeMillis +import org.tasks.ui.TaskEditControlFragment import javax.inject.Inject -import kotlin.time.Duration.Companion.seconds /** * Control Set for managing repeats @@ -46,7 +31,7 @@ import kotlin.time.Duration.Companion.seconds * @author Tim Su @todoroo.com> */ @AndroidEntryPoint -class TimerControlSet : TaskEditControlComposeFragment() { +class TimerControlSet : TaskEditControlFragment() { @Inject lateinit var activity: Activity @Inject lateinit var dialogBuilder: DialogBuilder @Inject lateinit var theme: Theme @@ -103,76 +88,23 @@ class TimerControlSet : TaskEditControlComposeFragment() { } } - @Composable - override fun Body() { - var now by remember { mutableStateOf(currentTimeMillis()) } - val started = viewModel.timerStarted.collectAsStateLifecycleAware().value - val newElapsed = if (started > 0) (now - started) / 1000L else 0 - val estimated = - viewModel.estimatedSeconds.collectAsStateLifecycleAware().value.takeIf { it > 0 } - ?.let { - stringResource(id = R.string.TEA_timer_est, DateUtils.formatElapsedTime(it.toLong())) + override fun bind(parent: ViewGroup?): View = + (parent as ComposeView).apply { + setContent { + MdcTheme { + TimerRow( + started = viewModel.timerStarted.collectAsStateLifecycleAware().value, + estimated = viewModel.estimatedSeconds.collectAsStateLifecycleAware().value, + elapsed = viewModel.elapsedSeconds.collectAsStateLifecycleAware().value, + timerClicked = this@TimerControlSet::timerClicked, + onClick = this@TimerControlSet::onRowClick, + ) } - val elapsed = - (newElapsed + viewModel.elapsedSeconds.collectAsStateLifecycleAware().value) - .takeIf { it > 0 } - ?.let { - stringResource(id = R.string.TEA_timer_elap, DateUtils.formatElapsedTime(it)) - } - val text = when { - estimated != null && elapsed != null -> "$estimated, $elapsed" - estimated != null -> estimated - elapsed != null -> elapsed - else -> null - } - Row { - if (text == null) { - DisabledText( - text = stringResource(id = R.string.TEA_timer_controls), - modifier = Modifier - .weight(1f) - .padding(vertical = 20.dp), - ) - } else { - Text( - text = text, - modifier = Modifier - .weight(1f) - .padding(vertical = 20.dp), - ) - } - IconButton( - onClick = { - now = currentTimeMillis() - timerClicked() - }, - modifier = Modifier.padding(vertical = 8.dp), - ) { - Icon( - imageVector = if (started > 0) { - Icons.Outlined.Pause - } else { - Icons.Outlined.PlayArrow - }, - modifier = Modifier.alpha(ContentAlpha.medium), - contentDescription = null - ) } } - LaunchedEffect(key1 = started) { - while (started > 0) { - delay(1.seconds) - now = currentTimeMillis() - } - } - } - - override val icon = R.drawable.ic_outline_timer_24px override fun controlId() = TAG - override val isClickable = true - private fun timerActive() = viewModel.timerStarted.value > 0 interface TimerControlSetCallback { @@ -183,4 +115,4 @@ class TimerControlSet : TaskEditControlComposeFragment() { companion object { const val TAG = R.string.TEA_ctrl_timer_pref } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt b/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt index 3ad434a8f..3088bee43 100644 --- a/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt @@ -43,12 +43,12 @@ import org.tasks.date.DateTimeUtils import org.tasks.dialogs.DialogBuilder import org.tasks.dialogs.MyTimePickerDialog import org.tasks.reminders.AlarmToString -import org.tasks.ui.TaskEditControlComposeFragment +import org.tasks.ui.TaskEditControlFragment import java.util.concurrent.TimeUnit import javax.inject.Inject @AndroidEntryPoint -class ReminderControlSet : TaskEditControlComposeFragment() { +class ReminderControlSet : TaskEditControlFragment() { @Inject lateinit var activity: Activity @Inject lateinit var dialogBuilder: DialogBuilder @Inject lateinit var alarmToString: AlarmToString 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 0a5edd259..bfc4e8df4 100644 --- a/app/src/main/java/com/todoroo/astrid/ui/StartDateControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/ui/StartDateControlSet.kt @@ -22,13 +22,13 @@ 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.preferences.Preferences -import org.tasks.ui.TaskEditControlComposeFragment +import org.tasks.ui.TaskEditControlFragment import java.time.format.FormatStyle import java.util.* import javax.inject.Inject @AndroidEntryPoint -class StartDateControlSet : TaskEditControlComposeFragment() { +class StartDateControlSet : TaskEditControlFragment() { @Inject lateinit var preferences: Preferences @Inject lateinit var locale: Locale diff --git a/app/src/main/java/org/tasks/compose/edit/AttachmentRow.kt b/app/src/main/java/org/tasks/compose/edit/AttachmentRow.kt new file mode 100644 index 000000000..bd591cd6e --- /dev/null +++ b/app/src/main/java/org/tasks/compose/edit/AttachmentRow.kt @@ -0,0 +1,103 @@ +package org.tasks.compose.edit + +import android.content.res.Configuration +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +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.Delete +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.net.toUri +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.TaskAttachment + +@Composable +fun AttachmentRow( + attachments: List, + openAttachment: (TaskAttachment) -> Unit, + deleteAttachment: (TaskAttachment) -> Unit, + addAttachment: () -> Unit, +) { + TaskEditRow( + iconRes = R.drawable.ic_outline_attachment_24px, + content = { + Column( + modifier = Modifier.padding(top = if (attachments.isEmpty()) 0.dp else 8.dp), + ) { + attachments.forEach { + Row( + modifier = Modifier + .clickable { openAttachment(it) }, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = it.name!!, + modifier = Modifier.weight(1f), + ) + IconButton(onClick = { deleteAttachment(it) }) { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = stringResource( + id = R.string.delete + ) + ) + } + } + } + DisabledText( + text = stringResource(id = R.string.add_attachment), + modifier = Modifier + .fillMaxWidth() + .clickable { addAttachment() } + .padding( + top = if (attachments.isEmpty()) 20.dp else 8.dp, + bottom = 20.dp, + ) + ) + } + }, + ) +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun NoAttachments() { + MdcTheme { + AttachmentRow( + attachments = emptyList(), + openAttachment = {}, + deleteAttachment = {}, + addAttachment = {}, + ) + } +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun AttachmentPreview() { + MdcTheme { + AttachmentRow( + attachments = listOf( + TaskAttachment("", "file://attachment.txt".toUri(), "attachment.txt") + ), + openAttachment = {}, + deleteAttachment = {}, + addAttachment = {}, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/compose/edit/CalendarRow.kt b/app/src/main/java/org/tasks/compose/edit/CalendarRow.kt new file mode 100644 index 000000000..8dcb73d03 --- /dev/null +++ b/app/src/main/java/org/tasks/compose/edit/CalendarRow.kt @@ -0,0 +1,93 @@ +package org.tasks.compose.edit + +import android.content.res.Configuration +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.Delete +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 + +@Composable +fun CalendarRow( + eventUri: String?, + selectedCalendar: String?, + onClick: () -> Unit, + clear: () -> Unit, +) { + TaskEditRow( + iconRes = R.drawable.ic_outline_event_24px, + content = { + if (eventUri?.isNotBlank() == true) { + Row { + Text( + text = stringResource(id = R.string.gcal_TEA_showCalendar_label), + modifier = Modifier + .weight(1f) + .padding(vertical = 20.dp) + ) + IconButton( + onClick = { clear() }, + Modifier.padding(vertical = 8.dp), + ) { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = stringResource(id = R.string.delete), + modifier = Modifier.alpha(ContentAlpha.medium), + ) + } + } + } else if (selectedCalendar?.isNotBlank() == true) { + Text( + text = selectedCalendar, + modifier = Modifier.padding(vertical = 20.dp), + ) + } else { + DisabledText( + text = stringResource(id = R.string.dont_add_to_calendar), + modifier = Modifier.padding(vertical = 20.dp), + ) + } + }, + onClick = onClick + ) +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun NoCalendar() { + MdcTheme { + CalendarRow(eventUri = null, selectedCalendar = null, onClick = {}, clear = {}) + } +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun NewCalendar() { + MdcTheme { + CalendarRow(eventUri = null, selectedCalendar = "Personal", onClick = {}, clear = {}) + } +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun ExistingCalendar() { + MdcTheme { + CalendarRow(eventUri = "abcd", selectedCalendar = null, onClick = {}, clear = {}) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/compose/edit/InfoRow.kt b/app/src/main/java/org/tasks/compose/edit/InfoRow.kt new file mode 100644 index 000000000..80e39668d --- /dev/null +++ b/app/src/main/java/org/tasks/compose/edit/InfoRow.kt @@ -0,0 +1,73 @@ +package org.tasks.compose.edit + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +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.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.TaskEditRow +import java.text.SimpleDateFormat +import java.util.* + +@Composable +fun InfoRow( + creationDate: Long?, + modificationDate: Long?, + completionDate: Long?, + locale: Locale = Locale.getDefault(), +) { + TaskEditRow( + iconRes = R.drawable.ic_outline_info_24px, + content = { + Column(modifier = Modifier.padding(vertical = 20.dp)) { + val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm", locale) + creationDate?.let { + Text( + text = stringResource( + id = R.string.sort_created_group, + formatter.format(it) + ) + ) + } + modificationDate?.let { + Text( + text = stringResource( + id = R.string.sort_modified_group, + formatter.format(it) + ) + ) + } + completionDate?.takeIf { it > 0 }?.let { + Text( + text = stringResource( + id = R.string.sort_completion_group, + formatter.format(it) + ) + ) + } + } + }, + ) +} + +@ExperimentalComposeUiApi +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun InfoPreview() { + MdcTheme { + InfoRow( + creationDate = 1658727180000, + modificationDate = 1658813557000, + completionDate = 1658813557000, + locale = Locale.US, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/compose/edit/ListRow.kt b/app/src/main/java/org/tasks/compose/edit/ListRow.kt index 22b9b700b..898b87c04 100644 --- a/app/src/main/java/org/tasks/compose/edit/ListRow.kt +++ b/app/src/main/java/org/tasks/compose/edit/ListRow.kt @@ -1,9 +1,14 @@ package org.tasks.compose.edit +import android.content.res.Configuration import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.google.android.material.composethemeadapter.MdcTheme +import com.todoroo.andlib.sql.QueryTemplate import com.todoroo.astrid.api.Filter import org.tasks.R import org.tasks.compose.ChipGroup @@ -34,4 +39,18 @@ fun ListRow( }, onClick = onClick ) +} + +@ExperimentalComposeUiApi +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun ListPreview() { + MdcTheme { + ListRow( + list = Filter("Default list", QueryTemplate()), + colorProvider = { -769226 }, + onClick = {}, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/compose/edit/TagsRow.kt b/app/src/main/java/org/tasks/compose/edit/TagsRow.kt index b2060ea89..065905e4b 100644 --- a/app/src/main/java/org/tasks/compose/edit/TagsRow.kt +++ b/app/src/main/java/org/tasks/compose/edit/TagsRow.kt @@ -1,16 +1,20 @@ package org.tasks.compose.edit +import android.content.res.Configuration import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier 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.Chip import org.tasks.compose.ChipGroup import org.tasks.compose.DisabledText import org.tasks.compose.TaskEditRow import org.tasks.data.TagData +import org.tasks.themes.ColorProvider import org.tasks.themes.CustomIcons @Composable @@ -46,3 +50,69 @@ fun TagsRow( onClick = onClick ) } + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun NoTags() { + MdcTheme { + TagsRow( + tags = emptyList(), + colorProvider = { 0 }, + onClick = {}, + ) + } +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun SingleTag() { + MdcTheme { + TagsRow( + tags = listOf( + TagData("Home").apply { + setIcon(1062) + setColor(ColorProvider.BLUE_500) + } + ), + colorProvider = { it }, + onClick = {}, + ) + } +} + +@Preview(showBackground = true, widthDp = 320) +@Composable +fun BunchOfTags() { + MdcTheme { + TagsRow( + tags = listOf( + TagData("One"), + TagData("Two"), + TagData("Three"), + TagData("Four"), + TagData("Five"), + ), + colorProvider = { it }, + onClick = {} + ) + } +} + +@Preview(showBackground = true, widthDp = 320) +@Composable +fun TagWithReallyLongName() { + MdcTheme { + TagsRow( + tags = listOf( + TagData("This is a tag with a really really long name").apply { + setIcon(1062) + setColor(ColorProvider.BLUE_500) + } + ), + colorProvider = { it }, + onClick = {}, + ) + } +} diff --git a/app/src/main/java/org/tasks/compose/edit/TimerRow.kt b/app/src/main/java/org/tasks/compose/edit/TimerRow.kt new file mode 100644 index 000000000..b0e31f0b9 --- /dev/null +++ b/app/src/main/java/org/tasks/compose/edit/TimerRow.kt @@ -0,0 +1,125 @@ +package org.tasks.compose.edit + +import android.content.res.Configuration +import android.text.format.DateUtils +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.Pause +import androidx.compose.material.icons.outlined.PlayArrow +import androidx.compose.runtime.* +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 kotlinx.coroutines.delay +import org.tasks.R +import org.tasks.compose.DisabledText +import org.tasks.compose.TaskEditRow +import kotlin.time.Duration.Companion.seconds + +@Composable +fun TimerRow( + started: Long, + estimated: Int, + elapsed: Int, + timerClicked: () -> Unit, + onClick: () -> Unit, +) { + TaskEditRow( + iconRes = R.drawable.ic_outline_timer_24px, + content = { + var now by remember { mutableStateOf(System.currentTimeMillis()) } + + val newElapsed = if (started > 0) (now - started) / 1000L else 0 + val estimatedString = estimated + .takeIf { it > 0 } + ?.let { + stringResource( + id = R.string.TEA_timer_est, + DateUtils.formatElapsedTime(it.toLong()) + ) + } + val elapsedString = + (newElapsed + elapsed) + .takeIf { it > 0 } + ?.let { + stringResource( + id = R.string.TEA_timer_elap, + DateUtils.formatElapsedTime(it) + ) + } + val text = when { + estimatedString != null && elapsedString != null -> "$estimatedString, $elapsedString" + estimatedString != null -> estimatedString + elapsedString != null -> elapsedString + else -> null + } + Row { + if (text == null) { + DisabledText( + text = stringResource(id = R.string.TEA_timer_controls), + modifier = Modifier + .weight(1f) + .padding(vertical = 20.dp), + ) + } else { + Text( + text = text, + modifier = Modifier + .weight(1f) + .padding(vertical = 20.dp), + ) + } + IconButton( + onClick = { + now = System.currentTimeMillis() + timerClicked() + }, + modifier = Modifier.padding(vertical = 8.dp), + ) { + Icon( + imageVector = if (started > 0) { + Icons.Outlined.Pause + } else { + Icons.Outlined.PlayArrow + }, + modifier = Modifier.alpha(ContentAlpha.medium), + contentDescription = null + ) + } + } + LaunchedEffect(key1 = started) { + while (started > 0) { + delay(1.seconds) + now = System.currentTimeMillis() + } + } + }, + onClick = onClick + ) +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun NoTimer() { + MdcTheme { + TimerRow(started = 0, estimated = 0, elapsed = 0, timerClicked = {}, onClick = {}) + } +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun RunningTimer() { + MdcTheme { + TimerRow(started = System.currentTimeMillis(), estimated = 900, elapsed = 400, timerClicked = {}, onClick = {}) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/CalendarControlSet.kt b/app/src/main/java/org/tasks/ui/CalendarControlSet.kt index 1e9239c9c..afcc8cd81 100644 --- a/app/src/main/java/org/tasks/ui/CalendarControlSet.kt +++ b/app/src/main/java/org/tasks/ui/CalendarControlSet.kt @@ -4,34 +4,25 @@ import android.app.Activity import android.content.Intent import android.net.Uri import android.provider.CalendarContract +import android.view.View +import android.view.ViewGroup import android.widget.Toast.LENGTH_SHORT -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.Delete -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 androidx.compose.ui.platform.ComposeView +import com.google.android.material.composethemeadapter.MdcTheme import dagger.hilt.android.AndroidEntryPoint import org.tasks.R import org.tasks.Strings.isNullOrEmpty import org.tasks.calendars.CalendarPicker import org.tasks.calendars.CalendarProvider -import org.tasks.compose.DisabledText import org.tasks.compose.collectAsStateLifecycleAware +import org.tasks.compose.edit.CalendarRow import org.tasks.extensions.Context.toast import org.tasks.preferences.PermissionChecker import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint -class CalendarControlSet : TaskEditControlComposeFragment() { +class CalendarControlSet : TaskEditControlFragment() { @Inject lateinit var activity: Activity @Inject lateinit var calendarProvider: CalendarProvider @Inject lateinit var permissionChecker: PermissionChecker @@ -50,65 +41,34 @@ class CalendarControlSet : TaskEditControlComposeFragment() { } } - @Composable - override fun Body() { - val eventUri = viewModel.eventUri.collectAsStateLifecycleAware().value - val selectedCalendar = - viewModel.selectedCalendar.collectAsStateLifecycleAware().value?.let { - calendarProvider.getCalendar(it)?.name - } - if (eventUri?.isNotBlank() == true) { - Row { - Text( - text = stringResource(id = R.string.gcal_TEA_showCalendar_label), - modifier = Modifier - .weight(1f) - .padding(vertical = 20.dp) - ) - IconButton( - onClick = { clear() }, - Modifier.padding(vertical = 8.dp), - ) { - Icon( - imageVector = Icons.Outlined.Delete, - contentDescription = stringResource(id = R.string.delete), - modifier = Modifier.alpha(ContentAlpha.medium), + override fun bind(parent: ViewGroup?): View = + (parent as ComposeView).apply { + setContent { + MdcTheme { + CalendarRow( + eventUri = viewModel.eventUri.collectAsStateLifecycleAware().value, + selectedCalendar = viewModel.selectedCalendar.collectAsStateLifecycleAware().value?.let { + calendarProvider.getCalendar(it)?.name + }, + onClick = { + if (viewModel.eventUri.value.isNullOrBlank()) { + CalendarPicker.newCalendarPicker(this@CalendarControlSet, REQUEST_CODE_PICK_CALENDAR, calendarName) + .show(parentFragmentManager, FRAG_TAG_CALENDAR_PICKER) + } else { + openCalendarEvent() + } + }, + clear = { + viewModel.selectedCalendar.value = null + viewModel.eventUri.value = null + } ) } } - } else if (selectedCalendar?.isNotBlank() == true) { - Text( - text = selectedCalendar, - modifier = Modifier.padding(vertical = 20.dp), - ) - } else { - DisabledText( - text = stringResource(id = R.string.dont_add_to_calendar), - modifier = Modifier.padding(vertical = 20.dp), - ) } - } - - override val icon = R.drawable.ic_outline_event_24px override fun controlId() = TAG - override val isClickable = true - - private fun clear() { - viewModel.selectedCalendar.value = null - viewModel.eventUri.value = null - } - - override fun onRowClick() { - if (viewModel.eventUri.value.isNullOrBlank()) { - CalendarPicker.newCalendarPicker(this, REQUEST_CODE_PICK_CALENDAR, calendarName) - .show(parentFragmentManager, FRAG_TAG_CALENDAR_PICKER) - } else { - openCalendarEvent() - } - } - private fun openCalendarEvent() { val cr = activity.contentResolver val uri = Uri.parse(viewModel.eventUri.value) @@ -172,4 +132,4 @@ class CalendarControlSet : TaskEditControlComposeFragment() { private const val FRAG_TAG_CALENDAR_PICKER = "frag_tag_calendar_picker" private const val REQUEST_CODE_PICK_CALENDAR = 70 } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/tasks/ui/CreationDateControlSet.kt b/app/src/main/java/org/tasks/ui/CreationDateControlSet.kt index f62b87796..707ffb48d 100644 --- a/app/src/main/java/org/tasks/ui/CreationDateControlSet.kt +++ b/app/src/main/java/org/tasks/ui/CreationDateControlSet.kt @@ -1,50 +1,36 @@ package org.tasks.ui -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -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 +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import com.google.android.material.composethemeadapter.MdcTheme import dagger.hilt.android.AndroidEntryPoint import org.tasks.R -import org.tasks.preferences.Preferences -import java.text.SimpleDateFormat +import org.tasks.compose.edit.InfoRow import java.util.* import javax.inject.Inject @AndroidEntryPoint -class CreationDateControlSet : TaskEditControlComposeFragment() { +class CreationDateControlSet : TaskEditControlFragment() { @Inject lateinit var locale: Locale - @Composable - override fun Body() { - Column(modifier = Modifier.padding(vertical = 20.dp)) { - val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm", locale) - viewModel.creationDate?.let { - Text( - text = stringResource(id = R.string.sort_created_group, formatter.format(it)) - ) - } - viewModel.modificationDate?.let { - Text( - text = stringResource(id = R.string.sort_modified_group, formatter.format(it)) - ) - } - viewModel.completionDate?.takeIf { it > 0 }?.let { - Text( - text = stringResource(id = R.string.sort_completion_group, formatter.format(it)) - ) + override fun bind(parent: ViewGroup?): View = + (parent as ComposeView).apply { + setContent { + MdcTheme { + InfoRow( + creationDate = viewModel.creationDate, + modificationDate = viewModel.modificationDate, + completionDate = viewModel.completionDate, + locale = locale, + ) + } } } - } - - override val icon = R.drawable.ic_outline_info_24px override fun controlId() = TAG companion object { const val TAG = R.string.TEA_ctrl_creation_date } -} \ 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 19a491e14..72a6575a1 100644 --- a/app/src/main/java/org/tasks/ui/DeadlineControlSet.kt +++ b/app/src/main/java/org/tasks/ui/DeadlineControlSet.kt @@ -20,7 +20,7 @@ import java.util.* import javax.inject.Inject @AndroidEntryPoint -class DeadlineControlSet : TaskEditControlComposeFragment() { +class DeadlineControlSet : TaskEditControlFragment() { @Inject lateinit var locale: Locale @Inject lateinit var preferences: Preferences diff --git a/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt b/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt index 2a4dfedc1..172a37470 100644 --- a/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt +++ b/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt @@ -14,7 +14,7 @@ import org.tasks.ui.TaskEditViewModel.Companion.stripCarriageReturns import javax.inject.Inject @AndroidEntryPoint -class DescriptionControlSet : TaskEditControlComposeFragment() { +class DescriptionControlSet : TaskEditControlFragment() { @Inject lateinit var linkify: Linkify @Inject lateinit var preferences: Preferences @Inject lateinit var markdownProvider: MarkdownProvider diff --git a/app/src/main/java/org/tasks/ui/ListFragment.kt b/app/src/main/java/org/tasks/ui/ListFragment.kt index 29e2040e4..c4e772dd7 100644 --- a/app/src/main/java/org/tasks/ui/ListFragment.kt +++ b/app/src/main/java/org/tasks/ui/ListFragment.kt @@ -17,7 +17,7 @@ import org.tasks.compose.edit.ListRow import javax.inject.Inject @AndroidEntryPoint -class ListFragment : TaskEditControlComposeFragment() { +class ListFragment : TaskEditControlFragment() { @Inject lateinit var chipProvider: ChipProvider override fun bind(parent: ViewGroup?): View = diff --git a/app/src/main/java/org/tasks/ui/LocationControlSet.kt b/app/src/main/java/org/tasks/ui/LocationControlSet.kt index d67dce282..fafbe098b 100644 --- a/app/src/main/java/org/tasks/ui/LocationControlSet.kt +++ b/app/src/main/java/org/tasks/ui/LocationControlSet.kt @@ -30,7 +30,7 @@ import org.tasks.preferences.Preferences import javax.inject.Inject @AndroidEntryPoint -class LocationControlSet : TaskEditControlComposeFragment() { +class LocationControlSet : TaskEditControlFragment() { @Inject lateinit var preferences: Preferences @Inject lateinit var dialogBuilder: DialogBuilder @Inject lateinit var permissionChecker: PermissionChecker diff --git a/app/src/main/java/org/tasks/ui/PriorityControlSet.kt b/app/src/main/java/org/tasks/ui/PriorityControlSet.kt index cd46cabd6..c19111b1f 100644 --- a/app/src/main/java/org/tasks/ui/PriorityControlSet.kt +++ b/app/src/main/java/org/tasks/ui/PriorityControlSet.kt @@ -12,7 +12,7 @@ import org.tasks.preferences.Preferences import javax.inject.Inject @AndroidEntryPoint -class PriorityControlSet : TaskEditControlComposeFragment() { +class PriorityControlSet : TaskEditControlFragment() { @Inject lateinit var preferences: Preferences override fun bind(parent: ViewGroup?): View = diff --git a/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt b/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt index 37989c36a..c367f9b6d 100644 --- a/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt +++ b/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt @@ -54,7 +54,7 @@ import org.tasks.themes.ColorProvider import javax.inject.Inject @AndroidEntryPoint -class SubtaskControlSet : TaskEditControlComposeFragment() { +class SubtaskControlSet : TaskEditControlFragment() { @Inject lateinit var activity: Activity @Inject lateinit var taskCompleter: TaskCompleter @Inject lateinit var localBroadcastManager: LocalBroadcastManager diff --git a/app/src/main/java/org/tasks/ui/TaskEditControlComposeFragment.kt b/app/src/main/java/org/tasks/ui/TaskEditControlComposeFragment.kt deleted file mode 100644 index ba14b303c..000000000 --- a/app/src/main/java/org/tasks/ui/TaskEditControlComposeFragment.kt +++ /dev/null @@ -1,61 +0,0 @@ -package org.tasks.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -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.platform.ComposeView -import androidx.compose.ui.unit.dp -import androidx.lifecycle.ViewModelProvider -import com.google.android.material.composethemeadapter.MdcTheme -import org.tasks.compose.TaskEditIcon -import org.tasks.compose.TaskEditRow - -abstract class TaskEditControlComposeFragment : TaskEditControlFragment() { - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val composeView = ComposeView(requireActivity()) - viewModel = ViewModelProvider(requireParentFragment())[TaskEditViewModel::class.java] - bind(composeView) - createView(savedInstanceState) - return composeView - } - - override fun bind(parent: ViewGroup?): View = - (parent as ComposeView).apply { - setContent { - MdcTheme { - TaskEditRow( - icon = { Icon() }, - content = { Body() }, - onClick = if (this@TaskEditControlComposeFragment.isClickable) - this@TaskEditControlComposeFragment::onRowClick - else - null - ) - } - } - } - - @Composable - protected open fun Icon() { - TaskEditIcon( - id = icon, - modifier = Modifier - .padding(start = 16.dp, top = 20.dp, end = 32.dp, bottom = 20.dp) - .alpha(ContentAlpha.medium), - ) - } - - @Composable - protected open fun Body() {} -} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/TaskEditControlFragment.kt b/app/src/main/java/org/tasks/ui/TaskEditControlFragment.kt index 5b8f6cd77..b4d72afb6 100644 --- a/app/src/main/java/org/tasks/ui/TaskEditControlFragment.kt +++ b/app/src/main/java/org/tasks/ui/TaskEditControlFragment.kt @@ -1,9 +1,21 @@ package org.tasks.ui import android.os.Bundle +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +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.platform.ComposeView +import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import com.google.android.material.composethemeadapter.MdcTheme +import org.tasks.compose.TaskEditIcon +import org.tasks.compose.TaskEditRow abstract class TaskEditControlFragment : Fragment() { lateinit var viewModel: TaskEditViewModel @@ -17,5 +29,45 @@ abstract class TaskEditControlFragment : Fragment() { protected open val icon = 0 abstract fun controlId(): Int - protected abstract fun bind(parent: ViewGroup?): View + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val composeView = ComposeView(requireActivity()) + viewModel = ViewModelProvider(requireParentFragment())[TaskEditViewModel::class.java] + bind(composeView) + createView(savedInstanceState) + return composeView + } + + open fun bind(parent: ViewGroup?): View = + (parent as ComposeView).apply { + setContent { + MdcTheme { + TaskEditRow( + icon = { Icon() }, + content = { Body() }, + onClick = if (this@TaskEditControlFragment.isClickable) + this@TaskEditControlFragment::onRowClick + else + null + ) + } + } + } + + @Composable + protected open fun Icon() { + TaskEditIcon( + id = icon, + modifier = Modifier + .padding(start = 16.dp, top = 20.dp, end = 32.dp, bottom = 20.dp) + .alpha(ContentAlpha.medium), + ) + } + + @Composable + protected open fun Body() {} } \ No newline at end of file