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 6f22ca789..7ecc09a19 100644
--- a/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt
+++ b/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt
@@ -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 @todoroo.com>
- */
@AndroidEntryPoint
-class RepeatControlSet : TaskEditControlViewFragment() {
- private val repeatTypes: MutableList = 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
-
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(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))
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/repeats/RepeatRuleToString.kt b/app/src/main/java/org/tasks/repeats/RepeatRuleToString.kt
index fc46bcaee..ee37b2b3b 100644
--- a/app/src/main/java/org/tasks/repeats/RepeatRuleToString.kt
+++ b/app/src/main/java/org/tasks/repeats/RepeatRuleToString.kt
@@ -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)
diff --git a/app/src/main/java/org/tasks/ui/HiddenTopArrayAdapter.java b/app/src/main/java/org/tasks/ui/HiddenTopArrayAdapter.java
deleted file mode 100644
index b8ee0b00b..000000000
--- a/app/src/main/java/org/tasks/ui/HiddenTopArrayAdapter.java
+++ /dev/null
@@ -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 extends ArrayAdapter {
-
- protected HiddenTopArrayAdapter(Context context, int resources, List 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;
- }
-}
diff --git a/app/src/main/java/org/tasks/ui/TaskEditViewModel.kt b/app/src/main/java/org/tasks/ui/TaskEditViewModel.kt
index de0798744..158e707cf 100644
--- a/app/src/main/java/org/tasks/ui/TaskEditViewModel.kt
+++ b/app/src/main/java/org/tasks/ui/TaskEditViewModel.kt
@@ -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(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
diff --git a/app/src/main/res/drawable-hdpi/textfield_underline_black.9.png b/app/src/main/res/drawable-hdpi/textfield_underline_black.9.png
deleted file mode 100644
index 0078bf6b6..000000000
Binary files a/app/src/main/res/drawable-hdpi/textfield_underline_black.9.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/textfield_underline_black.9.png b/app/src/main/res/drawable-mdpi/textfield_underline_black.9.png
deleted file mode 100644
index 8111fcbe7..000000000
Binary files a/app/src/main/res/drawable-mdpi/textfield_underline_black.9.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/textfield_underline_black.9.png b/app/src/main/res/drawable-xhdpi/textfield_underline_black.9.png
deleted file mode 100644
index e7e693a7b..000000000
Binary files a/app/src/main/res/drawable-xhdpi/textfield_underline_black.9.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/textfield_underline_black.9.png b/app/src/main/res/drawable-xxhdpi/textfield_underline_black.9.png
deleted file mode 100644
index c5acb84f0..000000000
Binary files a/app/src/main/res/drawable-xxhdpi/textfield_underline_black.9.png and /dev/null differ
diff --git a/app/src/main/res/layout/control_set_repeat_display.xml b/app/src/main/res/layout/control_set_repeat_display.xml
deleted file mode 100644
index 5078619bd..000000000
--- a/app/src/main/res/layout/control_set_repeat_display.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index e5174dca0..e9658d28a 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -91,11 +91,6 @@
- @string/repeat_yearly
-
- - @string/repeat_type_due
- - @string/repeat_type_completion
-
-
- @string/due_date
- @string/repeat_type_completion_capitalized