Convert repeat control set to compose

pull/1938/head
Alex Baker 3 years ago
parent 0711176ae2
commit e450f51f60

@ -5,63 +5,43 @@
*/ */
package com.todoroo.astrid.repeats package com.todoroo.astrid.repeats
import android.app.Activity
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import androidx.compose.foundation.clickable
import android.view.ViewGroup import androidx.compose.foundation.layout.*
import android.widget.AdapterView import androidx.compose.material.DropdownMenu
import android.widget.LinearLayout import androidx.compose.material.DropdownMenuItem
import android.widget.Spinner import androidx.compose.material.MaterialTheme
import android.widget.TextView 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 androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import net.fortuna.ical4j.model.Recur import net.fortuna.ical4j.model.Recur
import net.fortuna.ical4j.model.WeekDay import net.fortuna.ical4j.model.WeekDay
import org.tasks.R import org.tasks.R
import org.tasks.analytics.Firebase import org.tasks.compose.DisabledText
import org.tasks.databinding.ControlSetRepeatDisplayBinding import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.dialogs.DialogBuilder
import org.tasks.repeats.BasicRecurrenceDialog import org.tasks.repeats.BasicRecurrenceDialog
import org.tasks.repeats.RecurrenceUtils.newRecur import org.tasks.repeats.RecurrenceUtils.newRecur
import org.tasks.repeats.RepeatRuleToString import org.tasks.repeats.RepeatRuleToString
import org.tasks.themes.Theme
import org.tasks.time.DateTime import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils.currentTimeMillis import org.tasks.time.DateTimeUtils.currentTimeMillis
import org.tasks.ui.HiddenTopArrayAdapter import org.tasks.ui.TaskEditControlComposeFragment
import org.tasks.ui.OnItemSelected
import org.tasks.ui.TaskEditControlViewFragment
import javax.inject.Inject import javax.inject.Inject
/**
* Control Set for managing repeats
*
* @author Tim Su <tim></tim>@todoroo.com>
*/
@AndroidEntryPoint @AndroidEntryPoint
class RepeatControlSet : TaskEditControlViewFragment() { class RepeatControlSet : TaskEditControlComposeFragment() {
private val repeatTypes: MutableList<String> = ArrayList()
@Inject lateinit var activity: Activity
@Inject lateinit var dialogBuilder: DialogBuilder
@Inject lateinit var theme: Theme
@Inject lateinit var firebase: Firebase
@Inject lateinit var repeatRuleToString: RepeatRuleToString @Inject lateinit var repeatRuleToString: RepeatRuleToString
private lateinit var displayView: TextView
private lateinit var typeSpinner: Spinner
private lateinit var repeatTypeContainer: LinearLayout
private lateinit var typeAdapter: HiddenTopArrayAdapter<String>
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_RECURRENCE) { if (requestCode == REQUEST_RECURRENCE) {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
viewModel.recur = data viewModel.recurrence.value = data?.getStringExtra(BasicRecurrenceDialog.EXTRA_RRULE)
?.getStringExtra(BasicRecurrenceDialog.EXTRA_RRULE)
?.let { newRecur(it) }
refreshDisplayView()
} }
} else { } else {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
@ -69,7 +49,8 @@ class RepeatControlSet : TaskEditControlViewFragment() {
} }
private fun onDueDateChanged() { private fun onDueDateChanged() {
viewModel.recur?.let { recur -> viewModel.recurrence.value?.takeIf { it.isNotBlank() }?.let { recurrence ->
val recur = newRecur(recurrence)
if (recur.frequency == Recur.Frequency.MONTHLY && recur.dayList.isNotEmpty()) { if (recur.frequency == Recur.Frequency.MONTHLY && recur.dayList.isNotEmpty()) {
val weekdayNum = recur.dayList[0] val weekdayNum = recur.dayList[0]
val dateTime = DateTime(this.dueDate) val dateTime = DateTime(this.dueDate)
@ -84,34 +65,12 @@ class RepeatControlSet : TaskEditControlViewFragment() {
it.clear() it.clear()
it.add(WeekDay(dateTime.weekDay, num)) it.add(WeekDay(dateTime.weekDay, num))
} }
viewModel.recur = recur viewModel.recurrence.value = recur.toString()
refreshDisplayView()
} }
} }
} }
override fun createView(savedInstanceState: Bundle?) { override fun createView(savedInstanceState: Bundle?) {
repeatTypes.add("")
repeatTypes.addAll(listOf(*resources.getStringArray(R.array.repeat_type)))
typeAdapter = object : HiddenTopArrayAdapter<String>(activity, 0, repeatTypes) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var selectedItemPosition = position
if (parent is AdapterView<*>) {
selectedItemPosition = parent.selectedItemPosition
}
val tv = activity.layoutInflater.inflate(android.R.layout.simple_spinner_item, parent, false) as TextView
tv.setPadding(0, 0, 0, 0)
tv.text = repeatTypes[selectedItemPosition]
return tv
}
}
val drawable = activity.getDrawable(R.drawable.textfield_underline_black)!!.mutate()
drawable.setTint(activity.getColor(R.color.text_primary))
typeSpinner.setBackgroundDrawable(drawable)
typeSpinner.adapter = typeAdapter
typeSpinner.setSelection(if (viewModel.repeatAfterCompletion!!) TYPE_COMPLETION_DATE else TYPE_DUE_DATE)
refreshDisplayView()
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
viewModel.dueDate.collect { viewModel.dueDate.collect {
onDueDateChanged() onDueDateChanged()
@ -119,58 +78,91 @@ class RepeatControlSet : TaskEditControlViewFragment() {
} }
} }
private fun onRepeatTypeChanged(position: Int) {
viewModel.repeatAfterCompletion = position == TYPE_COMPLETION_DATE
repeatTypes[0] = if (viewModel.repeatAfterCompletion!!) repeatTypes[2] else repeatTypes[1]
typeAdapter.notifyDataSetChanged()
}
private val dueDate: Long private val dueDate: Long
get() = viewModel.dueDate.value.let { if (it > 0) it else currentTimeMillis() } get() = viewModel.dueDate.value.let { if (it > 0) it else currentTimeMillis() }
override fun onRowClick() { override fun onRowClick() {
BasicRecurrenceDialog.newBasicRecurrenceDialog( BasicRecurrenceDialog.newBasicRecurrenceDialog(
this, REQUEST_RECURRENCE, viewModel.recur?.toString(), dueDate) this, REQUEST_RECURRENCE, viewModel.recurrence.value, dueDate)
.show(parentFragmentManager, FRAG_TAG_BASIC_RECURRENCE) .show(parentFragmentManager, FRAG_TAG_BASIC_RECURRENCE)
} }
override val isClickable = true override val isClickable = true
override fun bind(parent: ViewGroup?) = @Composable
ControlSetRepeatDisplayBinding.inflate(layoutInflater, parent, true).let { override fun Body() {
displayView = it.displayRowEdit RepeatRow(
typeSpinner = it.repeatType.apply { recurrence = viewModel.recurrence.collectAsStateLifecycleAware().value?.let {
onItemSelectedListener = object : OnItemSelected() { repeatRuleToString.toString(it)
override fun onItemSelected(position: Int) { },
onRepeatTypeChanged(position) repeatFromCompletion = viewModel.repeatAfterCompletion.collectAsStateLifecycleAware().value,
} onRepeatFromChanged = { viewModel.repeatAfterCompletion.value = it }
} )
} }
repeatTypeContainer = it.repeatTypeContainer
it.root
}
override val icon = R.drawable.ic_outline_repeat_24px override val icon = R.drawable.ic_outline_repeat_24px
override fun controlId() = TAG override fun controlId() = TAG
private fun refreshDisplayView() {
viewModel.recur.let {
if (it == null) {
displayView.text = null
repeatTypeContainer.visibility = View.GONE
} else {
displayView.text = repeatRuleToString.toString(it)
repeatTypeContainer.visibility = View.VISIBLE
}
}
}
companion object { companion object {
const val TAG = R.string.TEA_ctrl_repeat_pref const val TAG = R.string.TEA_ctrl_repeat_pref
private const val TYPE_DUE_DATE = 1
private const val TYPE_COMPLETION_DATE = 2
private const val FRAG_TAG_BASIC_RECURRENCE = "frag_tag_basic_recurrence" private const val FRAG_TAG_BASIC_RECURRENCE = "frag_tag_basic_recurrence"
private const val REQUEST_RECURRENCE = 10000 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))
}
} }

@ -25,7 +25,7 @@ class RepeatRuleToString @Inject constructor(
fun toString(rrule: String?): String? = rrule?.let { toString(newRecur(it)) } fun toString(rrule: String?): String? = rrule?.let { toString(newRecur(it)) }
fun toString(rrule: Recur): String = try { private fun toString(rrule: Recur): String = try {
val interval = rrule.interval val interval = rrule.interval
val frequency = rrule.frequency val frequency = rrule.frequency
val repeatUntil = if (rrule.until == null) null else DateTime.from(rrule.until) val repeatUntil = if (rrule.until == null) null else DateTime.from(rrule.until)

@ -1,41 +0,0 @@
package org.tasks.ui;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import java.util.List;
import org.tasks.R;
public class HiddenTopArrayAdapter<T> extends ArrayAdapter<T> {
protected HiddenTopArrayAdapter(Context context, int resources, List<T> objects) {
super(context, resources, objects);
}
@Override
public View getDropDownView(
final int position, final View convertView, @NonNull final ViewGroup parent) {
View v;
if (position == 0) {
TextView tv = new TextView(getContext());
tv.setHeight(0);
tv.setVisibility(View.GONE);
v = tv;
} else {
ViewGroup vg =
(ViewGroup)
LayoutInflater.from(getContext())
.inflate(R.layout.simple_spinner_dropdown_item, parent, false);
((TextView) vg.findViewById(R.id.text1)).setText(getItem(position).toString());
v = vg;
}
parent.setVerticalScrollBarEnabled(false);
return v;
}
}

@ -26,7 +26,6 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.fortuna.ical4j.model.Recur
import org.tasks.R import org.tasks.R
import org.tasks.Strings import org.tasks.Strings
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
@ -42,11 +41,9 @@ import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.location.GeofenceApi import org.tasks.location.GeofenceApi
import org.tasks.preferences.PermissionChecker import org.tasks.preferences.PermissionChecker
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.repeats.RecurrenceUtils.newRecur
import org.tasks.time.DateTimeUtils.currentTimeMillis import org.tasks.time.DateTimeUtils.currentTimeMillis
import org.tasks.time.DateTimeUtils.startOfDay import org.tasks.time.DateTimeUtils.startOfDay
import timber.log.Timber import timber.log.Timber
import java.text.ParseException
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -119,6 +116,8 @@ class TaskEditViewModel @Inject constructor(
elapsedSeconds.value = task.elapsedSeconds elapsedSeconds.value = task.elapsedSeconds
estimatedSeconds.value = task.estimatedSeconds estimatedSeconds.value = task.estimatedSeconds
timerStarted.value = task.timerStart timerStarted.value = task.timerStart
recurrence.value = task.recurrence
repeatAfterCompletion.value = task.repeatAfterCompletion()
} }
lateinit var task: Task lateinit var task: Task
@ -165,33 +164,10 @@ class TaskEditViewModel @Inject constructor(
} }
} }
var recurrence: String? = null val recurrence = MutableStateFlow<String?>(null)
get() = field ?: task.recurrence val repeatAfterCompletion = MutableStateFlow(false)
var repeatAfterCompletion: Boolean? = null private var originalCalendar: String? = null
get() = field ?: task.repeatAfterCompletion()
var recur: Recur?
get() = if (recurrence.isNullOrBlank()) {
null
} else {
newRecur(recurrence!!)
}
set(value) {
if (value == null) {
recurrence = ""
return
}
val copy = try {
newRecur(value.toString())
} catch (e: ParseException) {
recurrence = ""
return
}
recurrence = copy.toString()
}
var originalCalendar: String? = null
private set(value) { private set(value) {
field = value field = value
selectedCalendar.value = value selectedCalendar.value = value
@ -260,11 +236,11 @@ class TaskEditViewModel @Inject constructor(
} || } ||
task.hideUntil != startDate.value || task.hideUntil != startDate.value ||
if (task.recurrence.isNullOrBlank()) { if (task.recurrence.isNullOrBlank()) {
!recurrence.isNullOrBlank() !recurrence.value.isNullOrBlank()
} else { } else {
task.recurrence != recurrence task.recurrence != recurrence.value
} || } ||
task.repeatAfterCompletion() != repeatAfterCompletion || task.repeatAfterCompletion() != repeatAfterCompletion.value ||
originalCalendar != selectedCalendar.value || originalCalendar != selectedCalendar.value ||
if (task.calendarURI.isNullOrBlank()) { if (task.calendarURI.isNullOrBlank()) {
!eventUri.value.isNullOrBlank() !eventUri.value.isNullOrBlank()
@ -299,8 +275,8 @@ class TaskEditViewModel @Inject constructor(
task.priority = priority.value task.priority = priority.value
task.notes = description task.notes = description
task.hideUntil = startDate.value task.hideUntil = startDate.value
task.recurrence = recurrence task.recurrence = recurrence.value
task.repeatFrom = if (repeatAfterCompletion == true) { task.repeatFrom = if (repeatAfterCompletion.value) {
Task.RepeatFrom.COMPLETION_DATE Task.RepeatFrom.COMPLETION_DATE
} else { } else {
Task.RepeatFrom.DUE_DATE Task.RepeatFrom.DUE_DATE

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 B

@ -1,47 +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"
android:baselineAligned="false"
android:orientation="vertical">
<TextView
android:id="@+id/display_row_edit"
style="@style/TaskEditTextPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:gravity="start"
android:hint="@string/repeat_option_does_not_repeat"
android:textAlignment="viewStart"/>
<LinearLayout
android:id="@+id/repeatTypeContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/keyline_first"
android:orientation="horizontal"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="0dp"
android:paddingEnd="5dp"
android:text="@string/repeats_from"
android:textAppearance="@style/TextAppearance"/>
<Spinner
android:id="@+id/repeatType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:textColor="@color/text_primary"/>
</LinearLayout>
</LinearLayout>

@ -91,11 +91,6 @@
<item>@string/repeat_yearly</item> <item>@string/repeat_yearly</item>
</string-array> </string-array>
<string-array name="repeat_type">
<item>@string/repeat_type_due</item>
<item>@string/repeat_type_completion</item>
</string-array>
<string-array name="repeat_type_capitalized"> <string-array name="repeat_type_capitalized">
<item>@string/due_date</item> <item>@string/due_date</item>
<item>@string/repeat_type_completion_capitalized</item> <item>@string/repeat_type_completion_capitalized</item>

Loading…
Cancel
Save