Convert TimerControlSet to compose

pull/1926/head
Alex Baker 2 years ago
parent 908d75909d
commit f5a53e6453

@ -7,29 +7,38 @@ package com.todoroo.astrid.timers
import android.app.Activity
import android.os.Bundle
import android.os.SystemClock
import android.text.format.DateFormat
import android.text.format.DateUtils
import android.view.View
import android.view.ViewGroup
import android.widget.Chronometer
import android.widget.Chronometer.OnChronometerTickListener
import android.widget.ImageView
import android.widget.TextView
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.lifecycle.lifecycleScope
import com.todoroo.andlib.utility.DateUtilities
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.Strings.isNullOrEmpty
import org.tasks.databinding.ControlSetTimersBinding
import org.tasks.compose.DisabledText
import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.dialogs.DialogBuilder
import org.tasks.themes.Theme
import org.tasks.ui.TaskEditControlFragment
import org.tasks.ui.TaskEditControlComposeFragment
import java.lang.System.currentTimeMillis
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
/**
* Control Set for managing repeats
@ -37,15 +46,11 @@ import javax.inject.Inject
* @author Tim Su <tim></tim>@todoroo.com>
*/
@AndroidEntryPoint
class TimerControlSet : TaskEditControlFragment() {
class TimerControlSet : TaskEditControlComposeFragment() {
@Inject lateinit var activity: Activity
@Inject lateinit var dialogBuilder: DialogBuilder
@Inject lateinit var theme: Theme
private lateinit var displayEdit: TextView
private lateinit var chronometer: Chronometer
private lateinit var timerButton: ImageView
private lateinit var estimated: TimeDurationControlSet
private lateinit var elapsed: TimeDurationControlSet
private var dialog: AlertDialog? = null
@ -56,9 +61,8 @@ class TimerControlSet : TaskEditControlFragment() {
dialogView = activity.layoutInflater.inflate(R.layout.control_set_timers_dialog, null)
estimated = TimeDurationControlSet(activity, dialogView, R.id.estimatedDuration, theme)
elapsed = TimeDurationControlSet(activity, dialogView, R.id.elapsedDuration, theme)
estimated.setTimeDuration(viewModel.estimatedSeconds!!)
elapsed.setTimeDuration(viewModel.elapsedSeconds!!)
refresh()
estimated.setTimeDuration(viewModel.estimatedSeconds.value)
elapsed.setTimeDuration(viewModel.elapsedSeconds.value)
}
override fun onAttach(activity: Activity) {
@ -77,8 +81,11 @@ class TimerControlSet : TaskEditControlFragment() {
return dialogBuilder
.newDialog()
.setView(dialogView)
.setPositiveButton(R.string.ok) { _, _ -> refreshDisplayView() }
.setOnCancelListener { refreshDisplayView() }
.setPositiveButton(R.string.ok) { _, _ ->
viewModel.estimatedSeconds.value = estimated.timeDurationInSeconds
viewModel.elapsedSeconds.value = elapsed.timeDurationInSeconds
}
.setOnCancelListener {}
.create()
}
@ -86,27 +93,78 @@ class TimerControlSet : TaskEditControlFragment() {
lifecycleScope.launch {
if (timerActive()) {
val task = callback.stopTimer()
viewModel.elapsedSeconds.value = task.elapsedSeconds
elapsed.setTimeDuration(task.elapsedSeconds)
viewModel.timerStarted = 0
chronometer.stop()
refreshDisplayView()
viewModel.timerStarted.value = 0
} else {
val task = callback.startTimer()
viewModel.timerStarted = task.timerStart
chronometer.start()
viewModel.timerStarted.value = task.timerStart
}
updateChronometer()
}
}
override fun bind(parent: ViewGroup?) =
ControlSetTimersBinding.inflate(layoutInflater, parent, true).let {
displayEdit = it.displayRowEdit
chronometer = it.timer
timerButton = it.timerButton
it.timerContainer.setOnClickListener { timerClicked() }
it.root
@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()))
}
val elapsed =
viewModel.elapsedSeconds.collectAsStateLifecycleAware().value.takeIf { it > 0 }
?.let {
stringResource(id = R.string.TEA_timer_elap, DateUtils.formatElapsedTime(it + newElapsed))
}
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
@ -114,61 +172,7 @@ class TimerControlSet : TaskEditControlFragment() {
override val isClickable = true
private fun refresh() {
refreshDisplayView()
updateChronometer()
}
private fun refreshDisplayView() {
var est: String? = null
viewModel.estimatedSeconds = estimated.timeDurationInSeconds
if (viewModel.estimatedSeconds!! > 0) {
est = getString(
R.string.TEA_timer_est,
DateUtils.formatElapsedTime(viewModel.estimatedSeconds!!.toLong()))
}
var elap: String? = null
viewModel.elapsedSeconds = elapsed.timeDurationInSeconds
if (viewModel.elapsedSeconds!! > 0) {
elap = getString(
R.string.TEA_timer_elap,
DateUtils.formatElapsedTime(viewModel.elapsedSeconds!!.toLong()))
}
val toDisplay: String?
toDisplay = if (!isNullOrEmpty(est) && !isNullOrEmpty(elap)) {
"$est, $elap" // $NON-NLS-1$
} else if (!isNullOrEmpty(est)) {
est
} else if (!isNullOrEmpty(elap)) {
elap
} else {
null
}
displayEdit.text = toDisplay
}
private fun updateChronometer() {
timerButton.setImageResource(
if (timerActive()) R.drawable.ic_outline_pause_24px else R.drawable.ic_outline_play_arrow_24px)
var elapsed = elapsed.timeDurationInSeconds * 1000L
if (timerActive()) {
chronometer.visibility = View.VISIBLE
elapsed += DateUtilities.now() - viewModel.timerStarted
chronometer.base = SystemClock.elapsedRealtime() - elapsed
if (elapsed > DateUtilities.ONE_DAY) {
chronometer.onChronometerTickListener = OnChronometerTickListener { cArg: Chronometer ->
val t = SystemClock.elapsedRealtime() - cArg.base
cArg.text = DateFormat.format("d'd' h:mm", t) // $NON-NLS-1$
}
}
chronometer.start()
} else {
chronometer.visibility = View.GONE
chronometer.stop()
}
}
private fun timerActive() = viewModel.timerStarted > 0
private fun timerActive() = viewModel.timerStarted.value > 0
interface TimerControlSetCallback {
suspend fun stopTimer(): Task

@ -119,6 +119,9 @@ class TaskEditViewModel @Inject constructor(
}
eventUri.value = task.calendarURI
priority.value = task.priority
elapsedSeconds.value = task.elapsedSeconds
estimatedSeconds.value = task.estimatedSeconds
timerStarted.value = task.timerStart
}
lateinit var task: Task
@ -231,17 +234,9 @@ class TaskEditViewModel @Inject constructor(
var isNew: Boolean = false
private set
var timerStarted: Long
get() = task.timerStart
set(value) {
task.timerStart = value
}
var estimatedSeconds: Int? = null
get() = field ?: task.estimatedSeconds
var elapsedSeconds: Int? = null
get() = field ?: task.elapsedSeconds
val timerStarted = MutableStateFlow(0L)
val estimatedSeconds = MutableStateFlow(0)
val elapsedSeconds = MutableStateFlow(0)
private lateinit var originalList: Filter
@ -307,8 +302,8 @@ class TaskEditViewModel @Inject constructor(
} else {
task.calendarURI != eventUri.value
} ||
task.elapsedSeconds != elapsedSeconds ||
task.estimatedSeconds != estimatedSeconds ||
task.elapsedSeconds != elapsedSeconds.value ||
task.estimatedSeconds != estimatedSeconds.value ||
originalList != selectedList.value ||
originalLocation != selectedLocation.value ||
originalTags.toHashSet() != selectedTags.value.toHashSet() ||
@ -337,8 +332,8 @@ class TaskEditViewModel @Inject constructor(
task.hideUntil = startDate.value
task.recurrence = recurrence
task.repeatUntil = repeatUntil!!
task.elapsedSeconds = elapsedSeconds!!
task.estimatedSeconds = estimatedSeconds!!
task.elapsedSeconds = elapsedSeconds.value
task.estimatedSeconds = estimatedSeconds.value
task.ringFlags = getRingFlags()
applyCalendarChanges()

@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
** Copyright (c) 2012 Todoroo Inc
**
** See the file "LICENSE" for the full license governing this code.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:baselineAligned="false"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="100"
android:orientation="vertical">
<TextView
android:id="@+id/display_row_edit"
style="@style/TaskEditTextPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:hint="@string/TEA_timer_controls"
android:textAlignment="viewStart"/>
</LinearLayout>
<LinearLayout
android:id="@+id/timer_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ImageView
android:id="@+id/timer_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="5dip"
android:paddingEnd="5dip"
android:alpha="@dimen/alpha_secondary"
android:gravity="center_vertical|center_horizontal"
android:scaleType="centerInside"
app:tint="@color/icon_tint"/>
<Chronometer
android:id="@+id/timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="center_horizontal"
android:textColor="?android:attr/textColorSecondary"
android:textSize="10sp"
android:visibility="visible"/>
</LinearLayout>
</LinearLayout>
Loading…
Cancel
Save