Convert repeat control set to compose

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

@ -5,63 +5,43 @@
*/
package com.todoroo.astrid.repeats
import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.LinearLayout
import android.widget.Spinner
import android.widget.TextView
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 androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import net.fortuna.ical4j.model.Recur
import net.fortuna.ical4j.model.WeekDay
import org.tasks.R
import org.tasks.analytics.Firebase
import org.tasks.databinding.ControlSetRepeatDisplayBinding
import org.tasks.dialogs.DialogBuilder
import org.tasks.compose.DisabledText
import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.repeats.BasicRecurrenceDialog
import org.tasks.repeats.RecurrenceUtils.newRecur
import org.tasks.repeats.RepeatRuleToString
import org.tasks.themes.Theme
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils.currentTimeMillis
import org.tasks.ui.HiddenTopArrayAdapter
import org.tasks.ui.OnItemSelected
import org.tasks.ui.TaskEditControlViewFragment
import org.tasks.ui.TaskEditControlComposeFragment
import javax.inject.Inject
/**
* Control Set for managing repeats
*
* @author Tim Su <tim></tim>@todoroo.com>
*/
@AndroidEntryPoint
class RepeatControlSet : TaskEditControlViewFragment() {
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
class RepeatControlSet : TaskEditControlComposeFragment() {
@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?) {
if (requestCode == REQUEST_RECURRENCE) {
if (resultCode == RESULT_OK) {
viewModel.recur = data
?.getStringExtra(BasicRecurrenceDialog.EXTRA_RRULE)
?.let { newRecur(it) }
refreshDisplayView()
viewModel.recurrence.value = data?.getStringExtra(BasicRecurrenceDialog.EXTRA_RRULE)
}
} else {
super.onActivityResult(requestCode, resultCode, data)
@ -69,7 +49,8 @@ class RepeatControlSet : TaskEditControlViewFragment() {
}
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()) {
val weekdayNum = recur.dayList[0]
val dateTime = DateTime(this.dueDate)
@ -84,34 +65,12 @@ class RepeatControlSet : TaskEditControlViewFragment() {
it.clear()
it.add(WeekDay(dateTime.weekDay, num))
}
viewModel.recur = recur
refreshDisplayView()
viewModel.recurrence.value = recur.toString()
}
}
}
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 {
viewModel.dueDate.collect {
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
get() = viewModel.dueDate.value.let { if (it > 0) it else currentTimeMillis() }
override fun onRowClick() {
BasicRecurrenceDialog.newBasicRecurrenceDialog(
this, REQUEST_RECURRENCE, viewModel.recur?.toString(), dueDate)
this, REQUEST_RECURRENCE, viewModel.recurrence.value, dueDate)
.show(parentFragmentManager, FRAG_TAG_BASIC_RECURRENCE)
}
override val isClickable = true
override fun bind(parent: ViewGroup?) =
ControlSetRepeatDisplayBinding.inflate(layoutInflater, parent, true).let {
displayView = it.displayRowEdit
typeSpinner = it.repeatType.apply {
onItemSelectedListener = object : OnItemSelected() {
override fun onItemSelected(position: Int) {
onRepeatTypeChanged(position)
}
}
}
repeatTypeContainer = it.repeatTypeContainer
it.root
}
@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 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 {
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 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: Recur): String = try {
private fun toString(rrule: Recur): String = try {
val interval = rrule.interval
val frequency = rrule.frequency
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.runBlocking
import kotlinx.coroutines.withContext
import net.fortuna.ical4j.model.Recur
import org.tasks.R
import org.tasks.Strings
import org.tasks.analytics.Firebase
@ -42,11 +41,9 @@ import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.location.GeofenceApi
import org.tasks.preferences.PermissionChecker
import org.tasks.preferences.Preferences
import org.tasks.repeats.RecurrenceUtils.newRecur
import org.tasks.time.DateTimeUtils.currentTimeMillis
import org.tasks.time.DateTimeUtils.startOfDay
import timber.log.Timber
import java.text.ParseException
import javax.inject.Inject
@HiltViewModel
@ -119,6 +116,8 @@ class TaskEditViewModel @Inject constructor(
elapsedSeconds.value = task.elapsedSeconds
estimatedSeconds.value = task.estimatedSeconds
timerStarted.value = task.timerStart
recurrence.value = task.recurrence
repeatAfterCompletion.value = task.repeatAfterCompletion()
}
lateinit var task: Task
@ -165,33 +164,10 @@ class TaskEditViewModel @Inject constructor(
}
}
var recurrence: String? = null
get() = field ?: task.recurrence
val recurrence = MutableStateFlow<String?>(null)
val repeatAfterCompletion = MutableStateFlow(false)
var repeatAfterCompletion: Boolean? = 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 var originalCalendar: String? = null
private set(value) {
field = value
selectedCalendar.value = value
@ -260,11 +236,11 @@ class TaskEditViewModel @Inject constructor(
} ||
task.hideUntil != startDate.value ||
if (task.recurrence.isNullOrBlank()) {
!recurrence.isNullOrBlank()
!recurrence.value.isNullOrBlank()
} else {
task.recurrence != recurrence
task.recurrence != recurrence.value
} ||
task.repeatAfterCompletion() != repeatAfterCompletion ||
task.repeatAfterCompletion() != repeatAfterCompletion.value ||
originalCalendar != selectedCalendar.value ||
if (task.calendarURI.isNullOrBlank()) {
!eventUri.value.isNullOrBlank()
@ -299,8 +275,8 @@ class TaskEditViewModel @Inject constructor(
task.priority = priority.value
task.notes = description
task.hideUntil = startDate.value
task.recurrence = recurrence
task.repeatFrom = if (repeatAfterCompletion == true) {
task.recurrence = recurrence.value
task.repeatFrom = if (repeatAfterCompletion.value) {
Task.RepeatFrom.COMPLETION_DATE
} else {
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>
</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">
<item>@string/due_date</item>
<item>@string/repeat_type_completion_capitalized</item>

Loading…
Cancel
Save