Convert TimerControlSet to compose

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

@ -7,29 +7,38 @@ package com.todoroo.astrid.timers
import android.app.Activity import android.app.Activity
import android.os.Bundle import android.os.Bundle
import android.os.SystemClock
import android.text.format.DateFormat
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.View 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.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 androidx.lifecycle.lifecycleScope
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import com.todoroo.astrid.ui.TimeDurationControlSet import com.todoroo.astrid.ui.TimeDurationControlSet
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.tasks.R import org.tasks.R
import org.tasks.Strings.isNullOrEmpty import org.tasks.compose.DisabledText
import org.tasks.databinding.ControlSetTimersBinding import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.dialogs.DialogBuilder import org.tasks.dialogs.DialogBuilder
import org.tasks.themes.Theme 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 javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
/** /**
* Control Set for managing repeats * Control Set for managing repeats
@ -37,15 +46,11 @@ import javax.inject.Inject
* @author Tim Su <tim></tim>@todoroo.com> * @author Tim Su <tim></tim>@todoroo.com>
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class TimerControlSet : TaskEditControlFragment() { class TimerControlSet : TaskEditControlComposeFragment() {
@Inject lateinit var activity: Activity @Inject lateinit var activity: Activity
@Inject lateinit var dialogBuilder: DialogBuilder @Inject lateinit var dialogBuilder: DialogBuilder
@Inject lateinit var theme: Theme @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 estimated: TimeDurationControlSet
private lateinit var elapsed: TimeDurationControlSet private lateinit var elapsed: TimeDurationControlSet
private var dialog: AlertDialog? = null private var dialog: AlertDialog? = null
@ -56,9 +61,8 @@ class TimerControlSet : TaskEditControlFragment() {
dialogView = activity.layoutInflater.inflate(R.layout.control_set_timers_dialog, null) dialogView = activity.layoutInflater.inflate(R.layout.control_set_timers_dialog, null)
estimated = TimeDurationControlSet(activity, dialogView, R.id.estimatedDuration, theme) estimated = TimeDurationControlSet(activity, dialogView, R.id.estimatedDuration, theme)
elapsed = TimeDurationControlSet(activity, dialogView, R.id.elapsedDuration, theme) elapsed = TimeDurationControlSet(activity, dialogView, R.id.elapsedDuration, theme)
estimated.setTimeDuration(viewModel.estimatedSeconds!!) estimated.setTimeDuration(viewModel.estimatedSeconds.value)
elapsed.setTimeDuration(viewModel.elapsedSeconds!!) elapsed.setTimeDuration(viewModel.elapsedSeconds.value)
refresh()
} }
override fun onAttach(activity: Activity) { override fun onAttach(activity: Activity) {
@ -77,8 +81,11 @@ class TimerControlSet : TaskEditControlFragment() {
return dialogBuilder return dialogBuilder
.newDialog() .newDialog()
.setView(dialogView) .setView(dialogView)
.setPositiveButton(R.string.ok) { _, _ -> refreshDisplayView() } .setPositiveButton(R.string.ok) { _, _ ->
.setOnCancelListener { refreshDisplayView() } viewModel.estimatedSeconds.value = estimated.timeDurationInSeconds
viewModel.elapsedSeconds.value = elapsed.timeDurationInSeconds
}
.setOnCancelListener {}
.create() .create()
} }
@ -86,26 +93,77 @@ class TimerControlSet : TaskEditControlFragment() {
lifecycleScope.launch { lifecycleScope.launch {
if (timerActive()) { if (timerActive()) {
val task = callback.stopTimer() val task = callback.stopTimer()
viewModel.elapsedSeconds.value = task.elapsedSeconds
elapsed.setTimeDuration(task.elapsedSeconds) elapsed.setTimeDuration(task.elapsedSeconds)
viewModel.timerStarted = 0 viewModel.timerStarted.value = 0
chronometer.stop()
refreshDisplayView()
} else { } else {
val task = callback.startTimer() val task = callback.startTimer()
viewModel.timerStarted = task.timerStart viewModel.timerStarted.value = task.timerStart
chronometer.start() }
}
}
@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
)
} }
updateChronometer() }
LaunchedEffect(key1 = started) {
while (started > 0) {
delay(1.seconds)
now = currentTimeMillis()
} }
} }
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
} }
override val icon = R.drawable.ic_outline_timer_24px override val icon = R.drawable.ic_outline_timer_24px
@ -114,61 +172,7 @@ class TimerControlSet : TaskEditControlFragment() {
override val isClickable = true override val isClickable = true
private fun refresh() { private fun timerActive() = viewModel.timerStarted.value > 0
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
interface TimerControlSetCallback { interface TimerControlSetCallback {
suspend fun stopTimer(): Task suspend fun stopTimer(): Task

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