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