diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index dd566523b..343375d31 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -663,7 +663,11 @@
android:name="org.tasks.dialogs.SortSettingsActivity"
android:theme="@style/TranslucentWindow" />
-
+
+
+
Unit,
+) {
+ Box(
+ modifier = modifier
+ .height(45.dp)
+ .border(1.dp, color = border(), RoundedCornerShape(4.dp))
+ .padding(start = 8.dp, end = 8.dp),
+ contentAlignment = Alignment.Center,
+ content = content,
+ )
+}
diff --git a/app/src/main/java/org/tasks/compose/OutlinedSpinner.kt b/app/src/main/java/org/tasks/compose/OutlinedSpinner.kt
new file mode 100644
index 000000000..e06d768cb
--- /dev/null
+++ b/app/src/main/java/org/tasks/compose/OutlinedSpinner.kt
@@ -0,0 +1,65 @@
+package org.tasks.compose
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.DropdownMenu
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.ArrowDropDown
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun OutlinedSpinner(
+ text: String,
+ options: List,
+ onSelected: (Int) -> Unit,
+) {
+ var expanded by remember { mutableStateOf(false) }
+ OutlinedBox(
+ modifier = Modifier.clickable { expanded = !expanded }
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.padding(start = 8.dp),
+ ) {
+ Text(text = text)
+ Icon(
+ imageVector = Icons.Outlined.ArrowDropDown,
+ contentDescription = null,
+ tint = MaterialTheme.colors.onSurface,
+ )
+ }
+ DropdownMenu(
+ expanded = expanded,
+ onDismissRequest = { expanded = false },
+ offset = DpOffset(-8.dp, 0.dp),
+ ) {
+ options.forEachIndexed { index, item ->
+ DropdownMenuItem(
+ onClick = {
+ expanded = false
+ onSelected(index)
+ },
+ content = {
+ Text(
+ text = item,
+ style = MaterialTheme.typography.body2,
+ )
+ },
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/tasks/compose/OutlinedTextInput.kt b/app/src/main/java/org/tasks/compose/OutlinedTextInput.kt
new file mode 100644
index 000000000..f895037f5
--- /dev/null
+++ b/app/src/main/java/org/tasks/compose/OutlinedTextInput.kt
@@ -0,0 +1,91 @@
+package org.tasks.compose
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.core.os.ConfigurationCompat
+import org.tasks.extensions.formatNumber
+import org.tasks.extensions.parseInteger
+import java.util.Locale
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun OutlinedNumberInput(
+ number: Int,
+ onTextChanged: (Int) -> Unit,
+ onFocus: () -> Unit = {},
+) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val context = LocalContext.current
+ val locale = remember {
+ ConfigurationCompat
+ .getLocales(context.resources.configuration)
+ .get(0)
+ ?: Locale.getDefault()
+ }
+ val numberString = remember(number) {
+ number.takeIf { it > 0 }?.let { locale.formatNumber(it) } ?: ""
+ }
+ BasicTextField(
+ value = numberString,
+ onValueChange = {
+ val newValue = locale
+ .parseInteger(it)
+ ?: 0
+ onTextChanged(newValue)
+ },
+ textStyle = MaterialTheme.typography.body1.copy(
+ color = MaterialTheme.colors.onSurface,
+ textAlign = TextAlign.Center,
+ ),
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = border(),
+ shape = RoundedCornerShape(4.dp),
+ )
+ .onFocusChanged {
+ if (it.hasFocus) {
+ onFocus()
+ }
+ }
+ .width(60.dp)
+ .height(45.dp)
+ .fillMaxWidth(),
+ cursorBrush = SolidColor(MaterialTheme.colors.onBackground),
+ interactionSource = interactionSource,
+ singleLine = true,
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
+ ) {
+ TextFieldDefaults.TextFieldDecorationBox(
+ value = number.toString(),
+ innerTextField = it,
+ singleLine = true,
+ enabled = true,
+ visualTransformation = VisualTransformation.None,
+ interactionSource = interactionSource,
+ // keep horizontal paddings but change the vertical
+ contentPadding = TextFieldDefaults.textFieldWithoutLabelPadding(
+ top = 0.dp, bottom = 0.dp
+ )
+ )
+ }
+}
diff --git a/app/src/main/java/org/tasks/compose/pickers/CustomRecurrence.kt b/app/src/main/java/org/tasks/compose/pickers/CustomRecurrence.kt
new file mode 100644
index 000000000..5df8d3162
--- /dev/null
+++ b/app/src/main/java/org/tasks/compose/pickers/CustomRecurrence.kt
@@ -0,0 +1,526 @@
+package org.tasks.compose.pickers
+
+import android.content.res.Configuration
+import android.os.LocaleList
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.RadioButton
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.ArrowBack
+import androidx.compose.material3.Divider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.pluralStringResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.core.os.ConfigurationCompat
+import com.google.android.material.composethemeadapter.MdcTheme
+import com.todoroo.andlib.utility.DateUtilities
+import net.fortuna.ical4j.model.Recur
+import net.fortuna.ical4j.model.WeekDay
+import org.tasks.R
+import org.tasks.compose.OutlinedBox
+import org.tasks.compose.OutlinedNumberInput
+import org.tasks.compose.OutlinedSpinner
+import org.tasks.compose.border
+import org.tasks.repeats.CustomRecurrenceViewModel
+import java.time.DayOfWeek
+import java.time.format.FormatStyle
+import java.time.format.TextStyle
+import java.util.Locale
+
+@Composable
+fun CustomRecurrence(
+ state: CustomRecurrenceViewModel.ViewState,
+ save: () -> Unit,
+ discard: () -> Unit,
+ setInterval: (Int) -> Unit,
+ setSelectedFrequency: (Recur.Frequency) -> Unit,
+ setEndDate: (Long) -> Unit,
+ setSelectedEndType: (Int) -> Unit,
+ setOccurrences: (Int) -> Unit,
+ toggleDay: (DayOfWeek) -> Unit,
+ setMonthSelection: (Int) -> Unit,
+) {
+ BackHandler {
+ save()
+ }
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = {
+ Text(
+ text = stringResource(id = R.string.repeats_custom_recurrence),
+ )
+ },
+ navigationIcon = {
+ IconButton(onClick = save) {
+ Icon(
+ imageVector = Icons.Outlined.ArrowBack,
+ contentDescription = stringResource(id = R.string.save),
+ tint = MaterialTheme.colors.onSurface,
+ )
+ }
+ },
+ actions = {
+ TextButton(onClick = discard) {
+ Text(
+ text = stringResource(id = R.string.cancel),
+ style = MaterialTheme.typography.body1.copy(
+ fontFeatureSettings = "c2sc, smcp"
+ )
+ )
+ }
+ },
+ backgroundColor = MaterialTheme.colors.surface,
+ contentColor = MaterialTheme.colors.onSurface,
+ )
+ }
+ ) { padding ->
+ Surface(
+ color = MaterialTheme.colors.surface,
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(padding),
+ ) {
+ Column {
+ Spacer(modifier = Modifier.height(16.dp))
+ Header(R.string.repeats_every)
+ Spacer(modifier = Modifier.height(16.dp))
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = Modifier.padding(horizontal = 16.dp),
+ ) {
+ OutlinedNumberInput(
+ number = state.interval,
+ onTextChanged = setInterval,
+ )
+ val context = LocalContext.current
+ val options by remember(state.interval, state.frequency) {
+ derivedStateOf {
+ state.frequencyOptions.map {
+ context.resources.getQuantityString(
+ it.plural,
+ state.interval,
+ state.interval,
+ )
+ }
+ }
+ }
+ OutlinedSpinner(
+ text = pluralStringResource(
+ id = state.frequency.plural,
+ count = state.interval
+ ),
+ options = options,
+ onSelected = { setSelectedFrequency(state.frequencyOptions[it]) },
+ )
+ }
+ if (state.frequency == Recur.Frequency.WEEKLY) {
+ WeekdayPicker(
+ daysOfWeek = state.daysOfWeek,
+ selected = state.selectedDays,
+ toggle = toggleDay,
+ )
+ } else if (state.frequency == Recur.Frequency.MONTHLY) {
+ MonthlyPicker(
+ monthDay = state.monthDay,
+ dayNumber = state.dueDayOfMonth,
+ dayOfWeek = state.dueDayOfWeek,
+ nthWeek = state.nthWeek,
+ isLastWeek = state.lastWeekDayOfMonth,
+ locale = state.locale,
+ onSelected = setMonthSelection,
+ )
+ }
+ Divider(
+ modifier = Modifier.padding(vertical = if (state.frequency == Recur.Frequency.WEEKLY) 11.dp else 16.dp),
+ color = border()
+ )
+ EndsPicker(
+ selection = state.endSelection,
+ endDate = state.endDate,
+ endOccurrences = state.endCount,
+ setEndDate = setEndDate,
+ setSelection = setSelectedEndType,
+ setOccurrences = setOccurrences,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun Header(resId: Int) {
+ Text(
+ text = stringResource(id = resId),
+ style = MaterialTheme.typography.caption,
+ color = MaterialTheme.colors.onSurface,
+ modifier = Modifier.padding(horizontal = 16.dp),
+ )
+}
+
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+private fun WeekdayPicker(
+ daysOfWeek: List,
+ selected: List,
+ toggle: (DayOfWeek) -> Unit,
+) {
+ val context = LocalContext.current
+ val locale = remember {
+ ConfigurationCompat
+ .getLocales(context.resources.configuration)
+ .get(0)
+ ?: Locale.getDefault()
+ }
+ Divider(
+ modifier = Modifier.padding(vertical = 16.dp),
+ color = border()
+ )
+ Header(R.string.repeats_weekly_on)
+ Spacer(modifier = Modifier.height(16.dp))
+ FlowRow(
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ modifier = Modifier.padding(horizontal = 16.dp),
+ ) {
+ daysOfWeek.forEach { dayOfWeek ->
+ val string = remember(dayOfWeek) {
+ dayOfWeek.getDisplayName(TextStyle.NARROW, locale)
+ }
+ Box(
+ modifier = Modifier
+ .padding(bottom = 5.dp) // hack until compose 1.5
+ .size(36.dp)
+ .let {
+ if (selected.contains(dayOfWeek)) {
+ it.background(MaterialTheme.colors.secondary, shape = CircleShape)
+ } else {
+ it.border(1.dp, border(), shape = CircleShape)
+ }
+ }
+ .clickable { toggle(dayOfWeek) },
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = string,
+ style = MaterialTheme.typography.body2,
+ color = if (selected.contains(dayOfWeek)) MaterialTheme.colors.onSecondary else MaterialTheme.colors.onSurface
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun MonthlyPicker(
+ monthDay: WeekDay?,
+ dayNumber: Int,
+ dayOfWeek: DayOfWeek,
+ nthWeek: Int,
+ isLastWeek: Boolean,
+ locale: Locale,
+ onSelected: (Int) -> Unit,
+) {
+ val selection = remember(monthDay) {
+ when (monthDay?.offset) {
+ null -> 0
+ -1 -> 2
+ else -> 1
+ }
+ }
+ Divider(
+ modifier = Modifier.padding(vertical = 16.dp),
+ color = border()
+ )
+ val context = LocalContext.current
+ val options = remember(dayNumber, dayOfWeek, nthWeek, isLastWeek, locale) {
+ ArrayList().apply {
+ add(context.getString(R.string.repeat_monthly_on_day_number, dayNumber))
+ val nth = context.getString(
+ when (nthWeek - 1) {
+ 0 -> R.string.repeat_monthly_first_week
+ 1 -> R.string.repeat_monthly_second_week
+ 2 -> R.string.repeat_monthly_third_week
+ 3 -> R.string.repeat_monthly_fourth_week
+ 4 -> R.string.repeat_monthly_fifth_week
+ else -> throw IllegalArgumentException()
+ }
+ )
+ val dayOfWeekDisplayName = dayOfWeek.getDisplayName(TextStyle.FULL, locale)
+ add(
+ context.getString(
+ R.string.repeat_monthly_on_the_nth_weekday,
+ nth,
+ dayOfWeekDisplayName
+ )
+ )
+ if (isLastWeek) {
+ add(
+ context.getString(
+ R.string.repeat_monthly_on_the_nth_weekday,
+ context.getString(R.string.repeat_monthly_last_week),
+ dayOfWeekDisplayName
+ )
+ )
+ }
+ }
+ }
+ Row(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ ) {
+ OutlinedSpinner(
+ text = options[selection],
+ options = options,
+ onSelected = onSelected,
+ )
+ }
+}
+
+@Composable
+private fun EndsPicker(
+ selection: Int,
+ endDate: Long,
+ endOccurrences: Int,
+ setOccurrences: (Int) -> Unit,
+ setEndDate: (Long) -> Unit,
+ setSelection: (Int) -> Unit,
+) {
+ Header(R.string.repeats_ends)
+ Spacer(modifier = Modifier.height(8.dp))
+ RadioRow(selected = selection == 0, onClick = { setSelection(0) }) {
+ Text(text = stringResource(id = R.string.repeats_never))
+ }
+ Divider(
+ modifier = Modifier.padding(start = 50.dp, end = 16.dp, top = 8.dp, bottom = 8.dp),
+ color = border()
+ )
+ RadioRow(selected = selection == 1, onClick = { setSelection(1) }) {
+ Text(text = stringResource(id = R.string.repeats_on))
+ Spacer(modifier = Modifier.width(8.dp))
+ val context = LocalContext.current
+ val locale = remember { LocaleList.getDefault()[0] }
+ val endDateString by remember(context, endDate) {
+ derivedStateOf {
+ DateUtilities.getRelativeDay(context, endDate, locale, FormatStyle.MEDIUM)
+ }
+ }
+ var showDatePicker by remember { mutableStateOf(false) }
+ if (showDatePicker) {
+ DatePickerDialog(
+ initialDate = endDate,
+ selected = { setEndDate(it) },
+ dismiss = { showDatePicker = false },
+ )
+ }
+ OutlinedBox(
+ modifier = Modifier.clickable {
+ setSelection(1)
+ showDatePicker = true
+ }
+ ) {
+ Text(text = endDateString)
+ Spacer(modifier = Modifier.width(4.dp))
+ }
+ }
+ Divider(
+ modifier = Modifier.padding(start = 50.dp, end = 16.dp, top = 8.dp, bottom = 8.dp),
+ color = border()
+ )
+ RadioRow(selected = selection == 2, onClick = { setSelection(2) }) {
+ Text(text = stringResource(id = R.string.repeats_after))
+ Spacer(modifier = Modifier.width(8.dp))
+ OutlinedNumberInput(
+ number = endOccurrences,
+ onTextChanged = setOccurrences,
+ onFocus = { setSelection(2) },
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(text = pluralStringResource(id = R.plurals.repeat_occurrence, endOccurrences))
+ }
+}
+
+@Composable
+fun RadioRow(
+ selected: Boolean,
+ onClick: () -> Unit,
+ content: @Composable RowScope.() -> Unit
+) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { onClick() },
+ ) {
+ RadioButton(selected = selected, onClick = onClick)
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ content = content,
+ )
+ }
+}
+
+private val Recur.Frequency.plural: Int
+ get() = when (this) {
+ Recur.Frequency.MINUTELY -> R.plurals.repeat_minutes
+ Recur.Frequency.HOURLY -> R.plurals.repeat_hours
+ Recur.Frequency.DAILY -> R.plurals.repeat_days
+ Recur.Frequency.WEEKLY -> R.plurals.repeat_weeks
+ Recur.Frequency.MONTHLY -> R.plurals.repeat_months
+ Recur.Frequency.YEARLY -> R.plurals.repeat_years
+ else -> throw RuntimeException()
+ }
+
+@Preview(showBackground = true)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+fun WeeklyPreview() {
+ MdcTheme {
+ CustomRecurrence(
+ state = CustomRecurrenceViewModel.ViewState(frequency = Recur.Frequency.WEEKLY),
+ save = {},
+ discard = {},
+ setSelectedFrequency = {},
+ setSelectedEndType = {},
+ setEndDate = {},
+ setInterval = {},
+ setOccurrences = {},
+ toggleDay = {},
+ setMonthSelection = {},
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+fun MonthlyPreview() {
+ MdcTheme {
+ CustomRecurrence(
+ state = CustomRecurrenceViewModel.ViewState(frequency = Recur.Frequency.MONTHLY),
+ save = {},
+ discard = {},
+ setSelectedFrequency = {},
+ setSelectedEndType = {},
+ setEndDate = {},
+ setInterval = {},
+ setOccurrences = {},
+ toggleDay = {},
+ setMonthSelection = {},
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+fun MinutelyPreview() {
+ MdcTheme {
+ CustomRecurrence(
+ state = CustomRecurrenceViewModel.ViewState(frequency = Recur.Frequency.MINUTELY),
+ save = {},
+ discard = {},
+ setSelectedFrequency = {},
+ setSelectedEndType = {},
+ setEndDate = {},
+ setInterval = {},
+ setOccurrences = {},
+ toggleDay = {},
+ setMonthSelection = {},
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+fun HourlyPreview() {
+ MdcTheme {
+ CustomRecurrence(
+ state = CustomRecurrenceViewModel.ViewState(frequency = Recur.Frequency.HOURLY),
+ save = {},
+ discard = {},
+ setSelectedFrequency = {},
+ setSelectedEndType = {},
+ setEndDate = {},
+ setInterval = {},
+ setOccurrences = {},
+ toggleDay = {},
+ setMonthSelection = {},
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+fun DailyPreview() {
+ MdcTheme {
+ CustomRecurrence(
+ state = CustomRecurrenceViewModel.ViewState(frequency = Recur.Frequency.DAILY),
+ save = {},
+ discard = {},
+ setSelectedFrequency = {},
+ setSelectedEndType = {},
+ setEndDate = {},
+ setInterval = {},
+ setOccurrences = {},
+ toggleDay = {},
+ setMonthSelection = {},
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+fun YearlyPreview() {
+ MdcTheme {
+ CustomRecurrence(
+ state = CustomRecurrenceViewModel.ViewState(frequency = Recur.Frequency.YEARLY),
+ save = {},
+ discard = {},
+ setSelectedFrequency = {},
+ setSelectedEndType = {},
+ setEndDate = {},
+ setInterval = {},
+ setOccurrences = {},
+ toggleDay = {},
+ setMonthSelection = {},
+ )
+ }
+}
diff --git a/app/src/main/java/org/tasks/compose/pickers/DatePickerDialog.kt b/app/src/main/java/org/tasks/compose/pickers/DatePickerDialog.kt
new file mode 100644
index 000000000..052b43a30
--- /dev/null
+++ b/app/src/main/java/org/tasks/compose/pickers/DatePickerDialog.kt
@@ -0,0 +1,77 @@
+package org.tasks.compose.pickers
+
+import android.content.res.Configuration
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.Text
+import androidx.compose.material3.DatePicker
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.material3.rememberDatePickerState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import com.google.android.material.composethemeadapter.MdcTheme
+import org.tasks.R
+import org.tasks.time.DateTime
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun DatePickerDialog(
+ initialDate: Long,
+ selected: (Long) -> Unit,
+ dismiss: () -> Unit,
+) {
+ MaterialTheme(
+ colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()
+ ) {
+ val initialDateUTC by remember(initialDate) {
+ derivedStateOf {
+ DateTime(initialDate).toUTC().millis
+ }
+ }
+ val datePickerState = rememberDatePickerState(
+ initialSelectedDateMillis = initialDateUTC,
+ )
+ androidx.compose.material3.DatePickerDialog(
+ onDismissRequest = { dismiss() },
+ dismissButton = {
+ TextButton(onClick = dismiss) {
+ Text(text = stringResource(id = R.string.cancel))
+ }
+ },
+ confirmButton = {
+ TextButton(
+ onClick = {
+ datePickerState
+ .selectedDateMillis
+ ?.let { selected(it - DateTime(it).offset) }
+ dismiss()
+ }
+ ) {
+ Text(text = stringResource(id = R.string.ok))
+ }
+ }
+ ) {
+ DatePicker(state = datePickerState)
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+fun DatePickerPreview() {
+ MdcTheme {
+ DatePickerDialog(
+ initialDate = DateTime().plusDays(1).millis,
+ selected = {},
+ dismiss = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/dialogs/MyDatePickerDialog.kt b/app/src/main/java/org/tasks/dialogs/MyDatePickerDialog.kt
index b076b0f65..ca6d61e8f 100644
--- a/app/src/main/java/org/tasks/dialogs/MyDatePickerDialog.kt
+++ b/app/src/main/java/org/tasks/dialogs/MyDatePickerDialog.kt
@@ -5,7 +5,6 @@ import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.DialogFragment
-import androidx.fragment.app.Fragment
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.datepicker.MaterialDatePicker.INPUT_MODE_CALENDAR
import com.google.android.material.datepicker.MaterialDatePicker.INPUT_MODE_TEXT
@@ -66,15 +65,6 @@ class MyDatePickerDialog : DialogFragment() {
const val FRAG_TAG_DATE_PICKER = "frag_date_picker"
const val EXTRA_TIMESTAMP = "extra_timestamp"
- @JvmStatic
- fun newDatePicker(target: Fragment, rc: Int, initial: Long) =
- MyDatePickerDialog().apply {
- arguments = Bundle().apply {
- putLong(EXTRA_TIMESTAMP, initial)
- }
- setTargetFragment(target, rc)
- }
-
@JvmStatic
fun newDatePicker(initial: Long, inputMode: Int) =
MaterialDatePicker.Builder.datePicker()
diff --git a/app/src/main/java/org/tasks/preferences/fragments/TaskDefaults.kt b/app/src/main/java/org/tasks/preferences/fragments/TaskDefaults.kt
index fd3573e33..a7de78717 100644
--- a/app/src/main/java/org/tasks/preferences/fragments/TaskDefaults.kt
+++ b/app/src/main/java/org/tasks/preferences/fragments/TaskDefaults.kt
@@ -28,7 +28,7 @@ import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.IconPreference
import org.tasks.preferences.Preferences
import org.tasks.repeats.BasicRecurrenceDialog
-import org.tasks.repeats.BasicRecurrenceDialog.EXTRA_RRULE
+import org.tasks.repeats.BasicRecurrenceDialog.Companion.EXTRA_RRULE
import org.tasks.repeats.RepeatRuleToString
import org.tasks.tags.TagPickerActivity
import org.tasks.tags.TagPickerActivity.Companion.EXTRA_SELECTED
diff --git a/app/src/main/java/org/tasks/repeats/BasicRecurrenceDialog.java b/app/src/main/java/org/tasks/repeats/BasicRecurrenceDialog.java
deleted file mode 100644
index 7dfb65337..000000000
--- a/app/src/main/java/org/tasks/repeats/BasicRecurrenceDialog.java
+++ /dev/null
@@ -1,163 +0,0 @@
-package org.tasks.repeats;
-
-import static android.app.Activity.RESULT_OK;
-import static com.google.common.collect.Lists.newArrayList;
-import static net.fortuna.ical4j.model.Recur.Frequency.DAILY;
-import static net.fortuna.ical4j.model.Recur.Frequency.HOURLY;
-import static net.fortuna.ical4j.model.Recur.Frequency.MINUTELY;
-import static net.fortuna.ical4j.model.Recur.Frequency.MONTHLY;
-import static net.fortuna.ical4j.model.Recur.Frequency.WEEKLY;
-import static net.fortuna.ical4j.model.Recur.Frequency.YEARLY;
-import static org.tasks.Strings.isNullOrEmpty;
-import static org.tasks.repeats.CustomRecurrenceDialog.newCustomRecurrenceDialog;
-import static org.tasks.repeats.RecurrenceUtils.newRecur;
-import static org.tasks.time.DateTimeUtils.currentTimeMillis;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.Intent;
-import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
-import dagger.hilt.android.AndroidEntryPoint;
-import java.util.List;
-import javax.inject.Inject;
-import net.fortuna.ical4j.model.Recur;
-import net.fortuna.ical4j.model.Recur.Frequency;
-import org.tasks.R;
-import org.tasks.dialogs.DialogBuilder;
-import org.tasks.ui.SingleCheckedArrayAdapter;
-import timber.log.Timber;
-
-@AndroidEntryPoint
-public class BasicRecurrenceDialog extends DialogFragment {
-
- public static final String EXTRA_RRULE = "extra_rrule";
- private static final String EXTRA_DATE = "extra_date";
- private static final String FRAG_TAG_CUSTOM_RECURRENCE = "frag_tag_custom_recurrence";
-
- @Inject Activity context;
- @Inject DialogBuilder dialogBuilder;
- @Inject RepeatRuleToString repeatRuleToString;
-
- public static BasicRecurrenceDialog newBasicRecurrenceDialog(
- Fragment target, int rc, String rrule, long dueDate) {
- BasicRecurrenceDialog dialog = new BasicRecurrenceDialog();
- dialog.setTargetFragment(target, rc);
- Bundle arguments = new Bundle();
- if (rrule != null) {
- arguments.putString(EXTRA_RRULE, rrule);
- }
- arguments.putLong(EXTRA_DATE, dueDate);
- dialog.setArguments(arguments);
- return dialog;
- }
-
- @NonNull
- @Override
- public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
- Bundle arguments = getArguments();
- long dueDate = arguments.getLong(EXTRA_DATE, currentTimeMillis());
- String rule = arguments.getString(EXTRA_RRULE);
- Recur rrule = null;
- try {
- if (!isNullOrEmpty(rule)) {
- rrule = newRecur(rule);
- }
- } catch (Exception e) {
- Timber.e(e);
- }
-
- boolean customPicked = isCustomValue(rrule);
- List repeatOptions =
- newArrayList(context.getResources().getStringArray(R.array.repeat_options));
- SingleCheckedArrayAdapter adapter =
- new SingleCheckedArrayAdapter(context, repeatOptions);
- int selected = 0;
- if (customPicked) {
- adapter.insert(repeatRuleToString.toString(rule), 0);
- } else if (rrule != null) {
- switch (rrule.getFrequency()) {
- case DAILY:
- selected = 1;
- break;
- case WEEKLY:
- selected = 2;
- break;
- case MONTHLY:
- selected = 3;
- break;
- case YEARLY:
- selected = 4;
- break;
- default:
- selected = 0;
- break;
- }
- }
- return dialogBuilder
- .newDialog()
- .setSingleChoiceItems(
- adapter,
- selected,
- (dialogInterface, i) -> {
- if (customPicked) {
- if (i == 0) {
- dialogInterface.dismiss();
- return;
- }
- i--;
- }
- Recur result;
- if (i == 0) {
- result = null;
- } else if (i == 5) {
- newCustomRecurrenceDialog(
- getTargetFragment(), getTargetRequestCode(), rule, dueDate)
- .show(getParentFragmentManager(), FRAG_TAG_CUSTOM_RECURRENCE);
- dialogInterface.dismiss();
- return;
- } else {
- result = newRecur();
- result.setInterval(1);
-
- switch (i) {
- case 1:
- result.setFrequency(DAILY.name());
- break;
- case 2:
- result.setFrequency(WEEKLY.name());
- break;
- case 3:
- result.setFrequency(MONTHLY.name());
- break;
- case 4:
- result.setFrequency(YEARLY.name());
- break;
- }
- }
-
- Intent intent = new Intent();
- intent.putExtra(EXTRA_RRULE, result == null ? null : result.toString());
- getTargetFragment().onActivityResult(getTargetRequestCode(), RESULT_OK, intent);
- dialogInterface.dismiss();
- })
- .setOnCancelListener(null)
- .show();
- }
-
- private boolean isCustomValue(Recur rrule) {
- if (rrule == null) {
- return false;
- }
- Frequency frequency = rrule.getFrequency();
- return (frequency == WEEKLY || frequency == MONTHLY) && !rrule.getDayList().isEmpty()
- || frequency == HOURLY
- || frequency == MINUTELY
- || rrule.getUntil() != null
- || rrule.getInterval() > 1
- || rrule.getCount() > 0;
- }
-}
diff --git a/app/src/main/java/org/tasks/repeats/BasicRecurrenceDialog.kt b/app/src/main/java/org/tasks/repeats/BasicRecurrenceDialog.kt
new file mode 100644
index 000000000..d3c61983d
--- /dev/null
+++ b/app/src/main/java/org/tasks/repeats/BasicRecurrenceDialog.kt
@@ -0,0 +1,130 @@
+package org.tasks.repeats
+
+import android.app.Activity
+import android.app.Activity.RESULT_OK
+import android.app.Dialog
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.Fragment
+import com.google.common.collect.Lists
+import dagger.hilt.android.AndroidEntryPoint
+import net.fortuna.ical4j.model.Recur
+import org.tasks.R
+import org.tasks.Strings.isNullOrEmpty
+import org.tasks.dialogs.DialogBuilder
+import org.tasks.repeats.CustomRecurrenceActivity.Companion.newIntent
+import org.tasks.repeats.RecurrenceUtils.newRecur
+import org.tasks.time.DateTimeUtils.currentTimeMillis
+import org.tasks.ui.SingleCheckedArrayAdapter
+import timber.log.Timber
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class BasicRecurrenceDialog : DialogFragment() {
+ @Inject lateinit var context: Activity
+ @Inject lateinit var dialogBuilder: DialogBuilder
+ @Inject lateinit var repeatRuleToString: RepeatRuleToString
+
+ private val customRecurrence =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ targetFragment?.onActivityResult(targetRequestCode, it.resultCode, it.data)
+ dismiss()
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val arguments = arguments
+ val dueDate = arguments!!.getLong(EXTRA_DATE, currentTimeMillis())
+ val rule = arguments.getString(EXTRA_RRULE)
+ var rrule: Recur? = null
+ try {
+ if (!isNullOrEmpty(rule)) {
+ rrule = newRecur(rule!!)
+ }
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
+ val customPicked = isCustomValue(rrule)
+ val repeatOptions: List =
+ Lists.newArrayList(*requireContext().resources.getStringArray(R.array.repeat_options))
+ val adapter = SingleCheckedArrayAdapter(requireContext(), repeatOptions)
+ var selected = 0
+ if (customPicked) {
+ adapter.insert(repeatRuleToString!!.toString(rule), 0)
+ } else if (rrule != null) {
+ selected = when (rrule.frequency) {
+ Recur.Frequency.DAILY -> 1
+ Recur.Frequency.WEEKLY -> 2
+ Recur.Frequency.MONTHLY -> 3
+ Recur.Frequency.YEARLY -> 4
+ else -> 0
+ }
+ }
+ return dialogBuilder
+ .newDialog()
+ .setSingleChoiceItems(
+ adapter,
+ selected
+ ) { dialogInterface: DialogInterface, i: Int ->
+ var i = i
+ if (customPicked) {
+ if (i == 0) {
+ dialogInterface.dismiss()
+ return@setSingleChoiceItems
+ }
+ i--
+ }
+ val result: Recur?
+ when (i) {
+ 0 -> result = null
+ 5 -> {
+ customRecurrence.launch(newIntent(requireContext(), rule, dueDate))
+ return@setSingleChoiceItems
+ }
+ else -> {
+ result = newRecur()
+ result.interval = 1
+ when (i) {
+ 1 -> result.setFrequency(Recur.Frequency.DAILY.name)
+ 2 -> result.setFrequency(Recur.Frequency.WEEKLY.name)
+ 3 -> result.setFrequency(Recur.Frequency.MONTHLY.name)
+ 4 -> result.setFrequency(Recur.Frequency.YEARLY.name)
+ }
+ }
+ }
+ val intent = Intent()
+ intent.putExtra(EXTRA_RRULE, result?.toString())
+ targetFragment!!.onActivityResult(targetRequestCode, RESULT_OK, intent)
+ dialogInterface.dismiss()
+ }
+ .show()
+ }
+
+ private fun isCustomValue(rrule: Recur?): Boolean {
+ if (rrule == null) {
+ return false
+ }
+ val frequency = rrule.frequency
+ return (frequency == Recur.Frequency.WEEKLY || frequency == Recur.Frequency.MONTHLY) && !rrule.dayList.isEmpty() || frequency == Recur.Frequency.HOURLY || frequency == Recur.Frequency.MINUTELY || rrule.until != null || rrule.interval > 1 || rrule.count > 0
+ }
+
+ companion object {
+ const val EXTRA_RRULE = "extra_rrule"
+ private const val EXTRA_DATE = "extra_date"
+ fun newBasicRecurrenceDialog(
+ target: Fragment?, rc: Int, rrule: String?, dueDate: Long
+ ): BasicRecurrenceDialog {
+ val dialog = BasicRecurrenceDialog()
+ dialog.setTargetFragment(target, rc)
+ val arguments = Bundle()
+ if (rrule != null) {
+ arguments.putString(EXTRA_RRULE, rrule)
+ }
+ arguments.putLong(EXTRA_DATE, dueDate)
+ dialog.arguments = arguments
+ return dialog
+ }
+ }
+}
diff --git a/app/src/main/java/org/tasks/repeats/CustomRecurrenceActivity.kt b/app/src/main/java/org/tasks/repeats/CustomRecurrenceActivity.kt
new file mode 100644
index 000000000..1547f8d29
--- /dev/null
+++ b/app/src/main/java/org/tasks/repeats/CustomRecurrenceActivity.kt
@@ -0,0 +1,53 @@
+package org.tasks.repeats
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.activity.viewModels
+import androidx.compose.material.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.fragment.app.FragmentActivity
+import com.google.android.material.composethemeadapter.MdcTheme
+import dagger.hilt.android.AndroidEntryPoint
+import org.tasks.compose.collectAsStateLifecycleAware
+import org.tasks.compose.pickers.CustomRecurrence
+
+@AndroidEntryPoint
+class CustomRecurrenceActivity : FragmentActivity() {
+ val viewModel: CustomRecurrenceViewModel by viewModels()
+
+ override fun onPostCreate(savedInstanceState: Bundle?) {
+ super.onPostCreate(savedInstanceState)
+ setContent {
+ MdcTheme {
+ CustomRecurrence(
+ state = viewModel.state.collectAsStateLifecycleAware().value,
+ save = {
+ setResult(RESULT_OK, Intent().putExtra(EXTRA_RRULE, viewModel.getRecur()))
+ finish()
+ },
+ discard = { finish() },
+ setSelectedFrequency = { viewModel.setFrequency(it) },
+ setEndDate = { viewModel.setEndDate(it) },
+ setSelectedEndType = { viewModel.setEndType(it) },
+ setInterval = { viewModel.setInterval(it) },
+ setOccurrences = { viewModel.setOccurrences(it) },
+ toggleDay = { viewModel.toggleDay(it) },
+ setMonthSelection = { viewModel.setMonthSelection(it) },
+ )
+ }
+ }
+ }
+
+ companion object {
+ const val EXTRA_RRULE = "extra_rrule"
+ const val EXTRA_DATE = "extra_date"
+
+ @JvmStatic
+ fun newIntent(context: Context, rrule: String?, dueDate: Long) =
+ Intent(context, CustomRecurrenceActivity::class.java)
+ .putExtra(EXTRA_DATE, dueDate)
+ .putExtra(EXTRA_RRULE, rrule)
+ }
+}
diff --git a/app/src/main/java/org/tasks/repeats/CustomRecurrenceDialog.java b/app/src/main/java/org/tasks/repeats/CustomRecurrenceDialog.java
deleted file mode 100644
index cbc96fe8c..000000000
--- a/app/src/main/java/org/tasks/repeats/CustomRecurrenceDialog.java
+++ /dev/null
@@ -1,557 +0,0 @@
-package org.tasks.repeats;
-
-import static android.app.Activity.RESULT_OK;
-import static com.google.common.collect.Lists.newArrayList;
-import static net.fortuna.ical4j.model.Recur.Frequency.DAILY;
-import static net.fortuna.ical4j.model.Recur.Frequency.HOURLY;
-import static net.fortuna.ical4j.model.Recur.Frequency.MINUTELY;
-import static net.fortuna.ical4j.model.Recur.Frequency.MONTHLY;
-import static net.fortuna.ical4j.model.Recur.Frequency.WEEKLY;
-import static net.fortuna.ical4j.model.Recur.Frequency.YEARLY;
-import static org.tasks.Strings.isNullOrEmpty;
-import static org.tasks.dialogs.MyDatePickerDialog.newDatePicker;
-import static org.tasks.repeats.BasicRecurrenceDialog.EXTRA_RRULE;
-import static org.tasks.repeats.RecurrenceUtils.newRecur;
-import static org.tasks.time.DateTimeUtils.currentTimeMillis;
-import static java.util.Arrays.asList;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.StateListDrawable;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.Spinner;
-import android.widget.TextView;
-import android.widget.ToggleButton;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.widget.AppCompatSpinner;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
-
-import com.todoroo.andlib.utility.DateUtilities;
-
-import net.fortuna.ical4j.model.Recur;
-import net.fortuna.ical4j.model.Recur.Frequency;
-import net.fortuna.ical4j.model.WeekDay;
-
-import org.tasks.R;
-import org.tasks.databinding.ControlSetRepeatBinding;
-import org.tasks.databinding.WeekButtonsBinding;
-import org.tasks.dialogs.DialogBuilder;
-import org.tasks.dialogs.MyDatePickerDialog;
-import org.tasks.extensions.LocaleKt;
-import org.tasks.preferences.ResourceResolver;
-import org.tasks.time.DateTime;
-import org.tasks.ui.OnItemSelected;
-import org.tasks.ui.OnTextChanged;
-
-import java.text.DateFormatSymbols;
-import java.time.format.FormatStyle;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.List;
-import java.util.Locale;
-
-import javax.inject.Inject;
-
-import dagger.hilt.android.AndroidEntryPoint;
-import timber.log.Timber;
-
-@AndroidEntryPoint
-public class CustomRecurrenceDialog extends DialogFragment {
-
- private static final List FREQUENCIES =
- asList(MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY);
- private static final String EXTRA_DATE = "extra_date";
- private static final String FRAG_TAG_DATE_PICKER = "frag_tag_date_picker";
- private static final int REQUEST_PICK_DATE = 505;
- private final List repeatUntilOptions = new ArrayList<>();
- @Inject Activity context;
- @Inject DialogBuilder dialogBuilder;
- @Inject Locale locale;
-
- private LinearLayout weekGroup1;
- @Nullable private LinearLayout weekGroup2;
- private RadioGroup monthGroup;
- private RadioButton repeatMonthlyDayOfNthWeek;
- private RadioButton repeatMonthlyDayOfLastWeek;
- private Spinner repeatUntilSpinner;
- private EditText intervalEditText;
- private TextView intervalTextView;
- private EditText repeatTimes;
- private TextView repeatTimesText;
-
- private ArrayAdapter repeatUntilAdapter;
- private ToggleButton[] weekButtons;
- private Recur rrule;
- private long dueDate;
-
- public static CustomRecurrenceDialog newCustomRecurrenceDialog(
- Fragment target, int rc, String rrule, long dueDate) {
- CustomRecurrenceDialog dialog = new CustomRecurrenceDialog();
- dialog.setTargetFragment(target, rc);
- Bundle arguments = new Bundle();
- if (rrule != null) {
- arguments.putString(EXTRA_RRULE, rrule);
- }
- arguments.putLong(EXTRA_DATE, dueDate);
- dialog.setArguments(arguments);
- return dialog;
- }
-
- @NonNull
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- LayoutInflater inflater = LayoutInflater.from(context);
- ControlSetRepeatBinding binding = ControlSetRepeatBinding.inflate(inflater);
- WeekButtonsBinding weekBinding = WeekButtonsBinding.bind(binding.getRoot());
-
- Bundle arguments = getArguments();
- dueDate = arguments.getLong(EXTRA_DATE, currentTimeMillis());
- String rule =
- savedInstanceState == null
- ? arguments.getString(EXTRA_RRULE)
- : savedInstanceState.getString(EXTRA_RRULE);
- try {
- if (!isNullOrEmpty(rule)) {
- rrule = newRecur(rule);
- }
- } catch (Exception e) {
- Timber.e(e);
- }
- if (rrule == null) {
- rrule = newRecur();
- rrule.setInterval(1);
- rrule.setFrequency(WEEKLY.name());
- }
-
- DateFormatSymbols dfs = new DateFormatSymbols(locale);
- String[] shortWeekdays = dfs.getShortWeekdays();
-
- weekGroup1 = weekBinding.weekGroup;
- weekGroup2 = weekBinding.weekGroup2;
- monthGroup = binding.monthGroup;
- repeatMonthlyDayOfNthWeek = binding.repeatMonthlyDayOfNthWeek;
- repeatMonthlyDayOfLastWeek = binding.repeatMonthlyDayOfLastWeek;
- repeatUntilSpinner = binding.repeatUntil;
- repeatUntilSpinner.setOnItemSelectedListener(new OnItemSelected() {
- @Override
- public void onItemSelected(int position) {
- onRepeatUntilChanged(position);
- }
- });
- intervalEditText = binding.intervalValue;
- intervalEditText.addTextChangedListener(new OnTextChanged() {
- @Override
- public void onTextChanged(@Nullable CharSequence text) {
- if (text != null) {
- onRepeatValueChanged(text);
- }
- }
- });
- intervalTextView = binding.intervalText;
- repeatTimes = binding.repeatTimesValue;
- repeatTimes.addTextChangedListener(new OnTextChanged() {
- @Override
- public void onTextChanged(@Nullable CharSequence text) {
- if (text != null) {
- onRepeatTimesValueChanged(text);
- }
- }
- });
- repeatTimesText = binding.repeatTimesText;
- AppCompatSpinner frequency = binding.frequency;
- frequency.setOnItemSelectedListener(new OnItemSelected() {
- @Override
- public void onItemSelected(int position) {
- onFrequencyChanged(position);
- }
- });
-
- Calendar dayOfMonthCalendar = Calendar.getInstance(locale);
- dayOfMonthCalendar.setTimeInMillis(dueDate);
- int dayOfWeekInMonth = dayOfMonthCalendar.get(Calendar.DAY_OF_WEEK_IN_MONTH);
- int maxDayOfWeekInMonth = dayOfMonthCalendar.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH);
-
- int dueDayOfWeek = dayOfMonthCalendar.get(Calendar.DAY_OF_WEEK);
- String today = dfs.getWeekdays()[dueDayOfWeek];
- if (dayOfWeekInMonth == maxDayOfWeekInMonth) {
- repeatMonthlyDayOfLastWeek.setVisibility(View.VISIBLE);
- String last = getString(R.string.repeat_monthly_last_week);
- String text = getString(R.string.repeat_monthly_on_every_day_of_nth_week, last, today);
- repeatMonthlyDayOfLastWeek.setTag(new WeekDay(calendarDayToWeekday(dueDayOfWeek), -1));
- repeatMonthlyDayOfLastWeek.setText(text);
- } else {
- repeatMonthlyDayOfLastWeek.setVisibility(View.GONE);
- }
-
- if (dayOfWeekInMonth < 6) {
- int[] resources =
- new int[] {
- R.string.repeat_monthly_first_week,
- R.string.repeat_monthly_second_week,
- R.string.repeat_monthly_third_week,
- R.string.repeat_monthly_fourth_week,
- R.string.repeat_monthly_fifth_week,
- };
- repeatMonthlyDayOfNthWeek.setVisibility(View.VISIBLE);
- String nth = getString(resources[dayOfWeekInMonth - 1]);
- String text = getString(R.string.repeat_monthly_on_every_day_of_nth_week, nth, today);
- repeatMonthlyDayOfNthWeek.setTag(
- new WeekDay(calendarDayToWeekday(dueDayOfWeek), dayOfWeekInMonth));
- repeatMonthlyDayOfNthWeek.setText(text);
- } else {
- repeatMonthlyDayOfNthWeek.setVisibility(View.GONE);
- }
-
- if (rrule.getFrequency() == MONTHLY) {
- if (rrule.getDayList().size() == 1) {
- WeekDay weekday = rrule.getDayList().get(0);
- if (weekday.getOffset() == -1) {
- repeatMonthlyDayOfLastWeek.setChecked(true);
- } else if (weekday.getOffset() == dayOfWeekInMonth) {
- repeatMonthlyDayOfNthWeek.setChecked(true);
- }
- }
- }
- if (monthGroup.getCheckedRadioButtonId() != R.id.repeat_monthly_day_of_last_week
- && monthGroup.getCheckedRadioButtonId() != R.id.repeat_monthly_day_of_nth_week) {
- binding.repeatMonthlySameDay.setChecked(true);
- }
-
- ArrayAdapter frequencyAdapter =
- ArrayAdapter.createFromResource(context, R.array.repeat_frequency, R.layout.frequency_item);
- frequencyAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- frequency.setAdapter(frequencyAdapter);
- frequency.setSelection(FREQUENCIES.indexOf(rrule.getFrequency()));
-
- intervalEditText.setText(LocaleKt.formatNumber(locale, rrule.getInterval()));
-
- repeatUntilAdapter =
- new ArrayAdapter(context, 0, repeatUntilOptions) {
- @Override
- public View getDropDownView(
- int position, @Nullable View convertView, @NonNull ViewGroup parent) {
- ViewGroup vg =
- (ViewGroup) inflater.inflate(R.layout.simple_spinner_dropdown_item, parent, false);
- ((TextView) vg.findViewById(R.id.text1)).setText(getItem(position));
- return vg;
- }
-
- @NonNull
- @Override
- public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
- int selectedItemPosition = position;
- if (parent instanceof AdapterView) {
- selectedItemPosition = ((AdapterView) parent).getSelectedItemPosition();
- }
- TextView tv =
- (TextView) inflater.inflate(android.R.layout.simple_spinner_item, parent, false);
- tv.setPadding(0, 0, 0, 0);
- tv.setText(repeatUntilOptions.get(selectedItemPosition));
- return tv;
- }
- };
- repeatUntilAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- repeatUntilSpinner.setAdapter(repeatUntilAdapter);
- updateRepeatUntilOptions();
-
- weekButtons = new ToggleButton[]{
- weekBinding.weekDay1.button,
- weekBinding.weekDay2.button,
- weekBinding.weekDay3.button,
- weekBinding.weekDay4.button,
- weekBinding.weekDay5.button,
- weekBinding.weekDay6.button,
- weekBinding.weekDay7.button
- };
-
- // set up days of week
- Calendar dayOfWeekCalendar = Calendar.getInstance(locale);
- dayOfWeekCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeekCalendar.getFirstDayOfWeek());
-
- WeekDay todayWeekday = new WeekDay(new DateTime(dueDate).getWeekDay(), 0);
-
- ColorStateList colorStateList =
- new ColorStateList(
- new int[][] {
- new int[] {android.R.attr.state_checked}, new int[] {-android.R.attr.state_checked}
- },
- new int[] {
- ResourceResolver.getData(context, com.google.android.material.R.attr.colorOnSecondary),
- context.getColor(R.color.text_primary)
- });
- int inset = (int) context.getResources().getDimension(R.dimen.week_button_inset);
- int accentColor = ResourceResolver.getData(context, androidx.appcompat.R.attr.colorAccent);
- int animationDuration =
- context.getResources().getInteger(android.R.integer.config_shortAnimTime);
-
- for (int i = 0; i < 7; i++) {
- ToggleButton weekButton = weekButtons[i];
-
- GradientDrawable ovalDrawable =
- (GradientDrawable)
- context.getDrawable(R.drawable.week_day_button_oval).mutate();
- ovalDrawable.setColor(accentColor);
- LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] {ovalDrawable});
- layerDrawable.setLayerInset(0, inset, inset, inset, inset);
- StateListDrawable stateListDrawable = new StateListDrawable();
- stateListDrawable.setEnterFadeDuration(animationDuration);
- stateListDrawable.setExitFadeDuration(animationDuration);
- stateListDrawable.addState(
- new int[] {-android.R.attr.state_checked}, new ColorDrawable(Color.TRANSPARENT));
- stateListDrawable.addState(new int[] {android.R.attr.state_checked}, layerDrawable);
- int paddingBottom = weekButton.getPaddingBottom();
- int paddingTop = weekButton.getPaddingTop();
- int paddingLeft = weekButton.getPaddingLeft();
- int paddingRight = weekButton.getPaddingRight();
- weekButton.setBackground(stateListDrawable);
- weekButton.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
-
- int dayOfWeek = dayOfWeekCalendar.get(Calendar.DAY_OF_WEEK);
- String text = shortWeekdays[dayOfWeek];
- weekButton.setTextColor(colorStateList);
- weekButton.setTextOn(text);
- weekButton.setTextOff(text);
- weekButton.setTag(new WeekDay(calendarDayToWeekday(dayOfWeek), 0));
- if (savedInstanceState == null) {
- weekButton.setChecked(
- rrule.getFrequency() != WEEKLY || rrule.getDayList().isEmpty()
- ? todayWeekday.equals(weekButton.getTag())
- : rrule.getDayList().contains(weekButton.getTag()));
- }
- dayOfWeekCalendar.add(Calendar.DATE, 1);
- }
-
- setCancelable(false);
-
- return dialogBuilder
- .newDialog()
- .setView(binding.getRoot())
- .setPositiveButton(R.string.ok, this::onRuleSelected)
- .setNegativeButton(R.string.cancel, null)
- .show();
- }
-
- private void onRuleSelected(DialogInterface dialogInterface, int which) {
- if (rrule.getFrequency() == WEEKLY) {
- List checked = new ArrayList<>();
- for (ToggleButton weekButton : weekButtons) {
- if (weekButton.isChecked()) {
- checked.add((WeekDay) weekButton.getTag());
- }
- }
- rrule.getDayList().clear();
- rrule.getDayList().addAll(checked);
- } else if (rrule.getFrequency() == MONTHLY) {
- int checkedRadioButtonId = monthGroup.getCheckedRadioButtonId();
- if (checkedRadioButtonId == R.id.repeat_monthly_same_day) {
- rrule.getDayList().clear();
- } else if (checkedRadioButtonId == R.id.repeat_monthly_day_of_nth_week) {
- rrule.getDayList().clear();
- rrule.getDayList().addAll(newArrayList((WeekDay) repeatMonthlyDayOfNthWeek.getTag()));
- } else if (checkedRadioButtonId == R.id.repeat_monthly_day_of_last_week) {
- rrule.getDayList().clear();
- rrule.getDayList().addAll(newArrayList((WeekDay) repeatMonthlyDayOfLastWeek.getTag()));
- }
- } else {
- rrule.getDayList().clear();
- }
- Intent intent = new Intent();
- intent.putExtra(EXTRA_RRULE, rrule.toString());
- getTargetFragment().onActivityResult(getTargetRequestCode(), RESULT_OK, intent);
- dismiss();
- }
-
- private WeekDay calendarDayToWeekday(int calendarDay) {
- switch (calendarDay) {
- case Calendar.SUNDAY:
- return WeekDay.SU;
- case Calendar.MONDAY:
- return WeekDay.MO;
- case Calendar.TUESDAY:
- return WeekDay.TU;
- case Calendar.WEDNESDAY:
- return WeekDay.WE;
- case Calendar.THURSDAY:
- return WeekDay.TH;
- case Calendar.FRIDAY:
- return WeekDay.FR;
- case Calendar.SATURDAY:
- return WeekDay.SA;
- }
- throw new RuntimeException("Invalid calendar day: " + calendarDay);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
- outState.putString(EXTRA_RRULE, rrule.toString());
- }
-
- private void setInterval(int interval, boolean updateEditText) {
- rrule.setInterval(interval);
- if (updateEditText) {
- intervalEditText.setText(LocaleKt.formatNumber(locale, interval));
- }
- updateIntervalTextView();
- }
-
- private void updateIntervalTextView() {
- int resource = getFrequencyPlural();
- String quantityString = getResources().getQuantityString(resource, rrule.getInterval());
- intervalTextView.setText(quantityString);
- }
-
- private void setCount(int count, boolean updateEditText) {
- rrule.setCount(count);
- if (updateEditText) {
- intervalEditText.setText(LocaleKt.formatNumber(locale, count));
- }
- updateCountText();
- }
-
- private void updateCountText() {
- repeatTimesText.setText(
- getResources().getQuantityString(R.plurals.repeat_times, rrule.getCount()));
- }
-
- private int getFrequencyPlural() {
- switch (rrule.getFrequency()) {
- case MINUTELY:
- return R.plurals.repeat_minutes;
- case HOURLY:
- return R.plurals.repeat_hours;
- case DAILY:
- return R.plurals.repeat_days;
- case WEEKLY:
- return R.plurals.repeat_weeks;
- case MONTHLY:
- return R.plurals.repeat_months;
- case YEARLY:
- return R.plurals.repeat_years;
- default:
- throw new RuntimeException("Invalid frequency: " + rrule.getFrequency());
- }
- }
-
- private void onRepeatUntilChanged(int position) {
- if (repeatUntilOptions.size() == 4) {
- position--;
- }
- if (position == 0) {
- rrule.setUntil(null);
- updateRepeatUntilOptions();
- } else if (position == 1) {
- repeatUntilClick();
- } else if (position == 2) {
- rrule.setUntil(null);
- rrule.setCount(Math.max(rrule.getCount(), 1));
- updateRepeatUntilOptions();
- }
- }
-
- private void onFrequencyChanged(int position) {
- Frequency frequency = FREQUENCIES.get(position);
- rrule.setFrequency(frequency.name());
- int weekVisibility = frequency == WEEKLY ? View.VISIBLE : View.GONE;
- weekGroup1.setVisibility(weekVisibility);
- if (weekGroup2 != null) {
- weekGroup2.setVisibility(weekVisibility);
- }
- monthGroup.setVisibility(frequency == MONTHLY && dueDate >= 0 ? View.VISIBLE : View.GONE);
- updateIntervalTextView();
- }
-
- private void onRepeatValueChanged(CharSequence text) {
- Integer value = LocaleKt.parseInteger(locale, text.toString());
- if (value == null) {
- return;
- }
- if (value < 1) {
- setInterval(1, true);
- } else {
- setInterval(value, false);
- }
- }
-
- private void onRepeatTimesValueChanged(CharSequence text) {
- Integer value = LocaleKt.parseInteger(locale, text.toString());
- if (value == null) {
- return;
- }
- if (value < 1) {
- setCount(1, true);
- } else {
- setCount(value, false);
- }
- }
-
- private void repeatUntilClick() {
- if (getParentFragmentManager().findFragmentByTag(FRAG_TAG_DATE_PICKER) == null) {
- long repeatUntil = DateTime.from(rrule.getUntil()).getMillis();
- newDatePicker(this, REQUEST_PICK_DATE, repeatUntil > 0 ? repeatUntil : 0L)
- .show(getParentFragmentManager(), FRAG_TAG_DATE_PICKER);
- }
- }
-
- private void updateRepeatUntilOptions() {
- repeatUntilOptions.clear();
- long repeatUntil = DateTime.from(rrule.getUntil()).getMillis();
- int count = rrule.getCount();
- if (repeatUntil > 0) {
- repeatUntilOptions.add(
- getString(
- R.string.repeat_until,
- DateUtilities.getRelativeDateTime(
- context, repeatUntil, locale, FormatStyle.MEDIUM, true)));
- repeatTimes.setVisibility(View.GONE);
- repeatTimesText.setVisibility(View.GONE);
- } else if (count > 0) {
- repeatUntilOptions.add(getString(R.string.repeat_occurs));
- repeatTimes.setText(LocaleKt.formatNumber(locale, count));
- repeatTimes.setVisibility(View.VISIBLE);
- updateCountText();
- repeatTimesText.setVisibility(View.VISIBLE);
- } else {
- repeatTimes.setVisibility(View.GONE);
- repeatTimesText.setVisibility(View.GONE);
- }
- repeatUntilOptions.add(getString(R.string.repeat_forever));
- repeatUntilOptions.add(getString(R.string.repeat_until, "").trim());
- repeatUntilOptions.add(getString(R.string.repeat_number_of_times));
- repeatUntilAdapter.notifyDataSetChanged();
- repeatUntilSpinner.setSelection(0);
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == REQUEST_PICK_DATE) {
- if (resultCode == RESULT_OK) {
- rrule.setUntil(
- new DateTime(data.getLongExtra(MyDatePickerDialog.EXTRA_TIMESTAMP, 0L)).toDate());
- }
- updateRepeatUntilOptions();
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
-}
diff --git a/app/src/main/java/org/tasks/repeats/CustomRecurrenceViewModel.kt b/app/src/main/java/org/tasks/repeats/CustomRecurrenceViewModel.kt
new file mode 100644
index 000000000..59e9b3664
--- /dev/null
+++ b/app/src/main/java/org/tasks/repeats/CustomRecurrenceViewModel.kt
@@ -0,0 +1,224 @@
+package org.tasks.repeats
+
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import net.fortuna.ical4j.model.Date
+import net.fortuna.ical4j.model.Recur
+import net.fortuna.ical4j.model.Recur.Frequency.DAILY
+import net.fortuna.ical4j.model.Recur.Frequency.HOURLY
+import net.fortuna.ical4j.model.Recur.Frequency.MINUTELY
+import net.fortuna.ical4j.model.Recur.Frequency.MONTHLY
+import net.fortuna.ical4j.model.Recur.Frequency.WEEKLY
+import net.fortuna.ical4j.model.Recur.Frequency.YEARLY
+import net.fortuna.ical4j.model.WeekDay
+import net.fortuna.ical4j.model.WeekDayList
+import net.fortuna.ical4j.model.property.RRule
+import org.tasks.date.DateTimeUtils.toDateTime
+import org.tasks.repeats.CustomRecurrenceActivity.Companion.EXTRA_DATE
+import org.tasks.repeats.CustomRecurrenceActivity.Companion.EXTRA_RRULE
+import org.tasks.time.DateTime
+import org.tasks.time.DateTimeUtils.startOfDay
+import java.time.DayOfWeek
+import java.time.Instant
+import java.time.ZoneId
+import java.time.temporal.WeekFields
+import java.util.Calendar
+import java.util.Calendar.DAY_OF_WEEK_IN_MONTH
+import java.util.Locale
+import javax.inject.Inject
+
+@HiltViewModel
+class CustomRecurrenceViewModel @Inject constructor(
+ savedStateHandle: SavedStateHandle,
+ locale: Locale,
+) : ViewModel() {
+ data class ViewState(
+ val interval: Int = 1,
+ val frequency: Recur.Frequency = WEEKLY,
+ val dueDate: Long = DateTime().startOfDay().millis,
+ val endSelection: Int = 0,
+ val endDate: Long = dueDate.toDateTime().plusMonths(1).startOfDay().millis,
+ val endCount: Int = 1,
+ val frequencyOptions: List = DEFAULT_FREQUENCIES,
+ val daysOfWeek: List = Locale.getDefault().daysOfWeek(),
+ val selectedDays: List = emptyList(),
+ val locale: Locale = Locale.getDefault(),
+ val monthDay: WeekDay? = null,
+ ) {
+ val dueDayOfWeek: DayOfWeek
+ get() = Instant.ofEpochMilli(dueDate).atZone(ZoneId.systemDefault()).dayOfWeek
+
+ val dueDayOfMonth: Int
+ get() = DateTime(dueDate).dayOfMonth
+
+ val nthWeek: Int
+ get() =
+ Calendar.getInstance(locale)
+ .apply { timeInMillis = dueDate }
+ .get(DAY_OF_WEEK_IN_MONTH)
+
+ val lastWeekDayOfMonth: Boolean
+ get() =
+ Calendar.getInstance(locale)
+ .apply { timeInMillis = dueDate }
+ .let { it[DAY_OF_WEEK_IN_MONTH] == it.getActualMaximum(DAY_OF_WEEK_IN_MONTH) }
+ }
+
+ private val _state = MutableStateFlow(ViewState())
+ val state = _state.asStateFlow()
+
+ init {
+ val daysOfWeek = locale.daysOfWeek()
+ val recur = savedStateHandle.get(EXTRA_RRULE)?.let { RRule(it) }?.recur
+ val dueDate = savedStateHandle
+ .get(EXTRA_DATE)
+ ?.takeIf { it > 0 }
+ ?: System.currentTimeMillis().startOfDay()
+ val selectedDays = if (recur?.frequency == WEEKLY) {
+ recur.dayList?.toDaysOfWeek()
+ } else {
+ emptyList()
+ }
+ _state.update { state ->
+ state.copy(
+ interval = recur?.interval?.takeIf { it > 0 } ?: 1,
+ frequency = recur?.frequency ?: WEEKLY,
+ dueDate = dueDate,
+ endSelection = when {
+ recur == null -> 0
+ recur.until != null -> 1
+ recur.count >= 0 -> 2
+ else -> 0
+ },
+ endDate = DateTime(dueDate).plusMonths(1).startOfDay().millis,
+ endCount = recur?.count?.takeIf { it >= 0 } ?: 1,
+ daysOfWeek = daysOfWeek,
+ selectedDays = recur
+ ?.dayList
+ ?.takeIf { recur.frequency == WEEKLY }
+ ?.toDaysOfWeek()
+ ?: emptyList(),
+ locale = locale,
+ monthDay = recur?.dayList?.takeIf { recur.frequency == MONTHLY }?.firstOrNull(),
+ )
+ }
+ }
+
+ fun setEndType(endType: Int) {
+ _state.update {
+ it.copy(endSelection = endType)
+ }
+ }
+
+ fun setFrequency(frequency: Recur.Frequency) {
+ _state.update {
+ it.copy(frequency = frequency)
+ }
+ }
+
+ fun setEndDate(endDate: Long) {
+ _state.update {
+ it.copy(endDate = endDate)
+ }
+ }
+
+ fun setInterval(interval: Int) {
+ _state.update {
+ it.copy(interval = interval)
+ }
+ }
+
+ fun setOccurrences(occurrences: Int) {
+ _state.update {
+ it.copy(endCount = occurrences)
+ }
+ }
+
+ fun toggleDay(dayOfWeek: DayOfWeek) {
+ _state.update { state ->
+ state.copy(
+ selectedDays = state.selectedDays.toMutableList().also {
+ if (!it.remove(dayOfWeek)) {
+ it.add(dayOfWeek)
+ }
+ }
+ )
+ }
+ }
+
+ fun getRecur(): String {
+ val state = _state.value
+ val builder = Recur.Builder().frequency(state.frequency)
+ if (state.frequency == WEEKLY) {
+ builder.dayList(state.selectedDays.toWeekDayList())
+ } else if (state.frequency == MONTHLY) {
+ state.monthDay?.let { builder.dayList(WeekDayList(it)) }
+ }
+ if (state.interval > 1) {
+ builder.interval(state.interval)
+ }
+ when (state.endSelection) {
+ 1 -> builder.until(Date(state.endDate))
+ 2 -> builder.count(state.endCount.coerceAtLeast(1))
+ }
+ return builder.build().toString()
+ }
+
+ fun setMonthSelection(selection: Int) {
+ _state.update {
+ it.copy(
+ monthDay = when (selection) {
+ 0 -> null
+ 1 -> WeekDay(it.dueDayOfWeek.weekDay, it.nthWeek)
+ 2 -> WeekDay(it.dueDayOfWeek.weekDay, -1)
+ else -> throw IllegalArgumentException()
+ },
+ )
+ }
+ }
+
+ companion object {
+ val DEFAULT_FREQUENCIES = listOf(MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY)
+
+ private fun Locale.daysOfWeek(): List {
+ val values = DayOfWeek.values()
+ val weekFields = WeekFields.of(this)
+ var index = values.indexOf(weekFields.firstDayOfWeek)
+ return (0..6).map {
+ values[index].also { index = (index + 1) % 7 }
+ }
+ }
+
+ private fun WeekDayList.toDaysOfWeek(): List = map {
+ when (it) {
+ WeekDay.SU -> DayOfWeek.SUNDAY
+ WeekDay.MO -> DayOfWeek.MONDAY
+ WeekDay.TU -> DayOfWeek.TUESDAY
+ WeekDay.WE -> DayOfWeek.WEDNESDAY
+ WeekDay.TH -> DayOfWeek.THURSDAY
+ WeekDay.FR -> DayOfWeek.FRIDAY
+ WeekDay.SA -> DayOfWeek.SATURDAY
+ else -> throw IllegalArgumentException()
+ }
+ }
+
+ private fun List.toWeekDayList(): WeekDayList =
+ WeekDayList(*sortedBy { it.value }.map { it.weekDay }.toTypedArray())
+
+ private val DayOfWeek.weekDay: WeekDay
+ get() = when (this) {
+ DayOfWeek.SUNDAY -> WeekDay.SU
+ DayOfWeek.MONDAY -> WeekDay.MO
+ DayOfWeek.TUESDAY -> WeekDay.TU
+ DayOfWeek.WEDNESDAY -> WeekDay.WE
+ DayOfWeek.THURSDAY -> WeekDay.TH
+ DayOfWeek.FRIDAY -> WeekDay.FR
+ DayOfWeek.SATURDAY -> WeekDay.SA
+ else -> throw IllegalArgumentException()
+ }
+ }
+}
diff --git a/app/src/main/res/drawable/week_day_button_oval.xml b/app/src/main/res/drawable/week_day_button_oval.xml
deleted file mode 100644
index 34be63161..000000000
--- a/app/src/main/res/drawable/week_day_button_oval.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/layout-w820dp/week_buttons.xml b/app/src/main/res/layout-w820dp/week_buttons.xml
deleted file mode 100644
index e59cf1fa2..000000000
--- a/app/src/main/res/layout-w820dp/week_buttons.xml
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/control_set_repeat.xml b/app/src/main/res/layout/control_set_repeat.xml
deleted file mode 100644
index 291b23c52..000000000
--- a/app/src/main/res/layout/control_set_repeat.xml
+++ /dev/null
@@ -1,152 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/frequency_item.xml b/app/src/main/res/layout/frequency_item.xml
deleted file mode 100644
index 316f54161..000000000
--- a/app/src/main/res/layout/frequency_item.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
diff --git a/app/src/main/res/layout/simple_spinner_dropdown_item.xml b/app/src/main/res/layout/simple_spinner_dropdown_item.xml
deleted file mode 100644
index a83f88a4e..000000000
--- a/app/src/main/res/layout/simple_spinner_dropdown_item.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/week_buttons.xml b/app/src/main/res/layout/week_buttons.xml
deleted file mode 100644
index 098cfc79b..000000000
--- a/app/src/main/res/layout/week_buttons.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/week_day_button.xml b/app/src/main/res/layout/week_day_button.xml
deleted file mode 100644
index cfe57969a..000000000
--- a/app/src/main/res/layout/week_day_button.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 34776d148..2ec5773d8 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -79,7 +79,6 @@
يومي
أسبوعي
مرتين في الأسبوع
- كرِّر للأبد
مؤقت
بدء هذه المهمة:
الوقت المنقضي:
@@ -196,7 +195,6 @@
متوسط
عال
التاريخ والوقت
- كرِّر حتَّى %s
- لا مهام
- مهمة واحدة
@@ -207,12 +205,6 @@
تاريخ الإكمال
تاريخ الإكمال
- كرِّر كل دقيقة
- كرِّر سنويًّا
- كرِّر شهريًّا
- كرِّر أسبوعيًّا
- كرِّر يوميًّا
- كرِّر كلّ ساعة
فوق المتوسط
الأيقونة
نسخ
@@ -222,8 +214,6 @@
ليلًا
مساءً
صباحًا
- كرّر عددًا من المرات
- كلّ
كل ساعة
لا منبهات
أظهر التاريخ الكامل
@@ -410,7 +400,6 @@
هذه الميزة تحتاج إلى اشتراك
لم يمكن الاتصال
أنشأ مهمّة
- في نفس اليوم شهريًّا
التقويم الافتراضي
لا تضف إلى التقويم
تكرار من
@@ -574,7 +563,6 @@
طور لاصدار متقدم
مساعدة وتبليغات
قائمة الاشعارات
- في كل %1$s %2$s
كل %1$s %2$s
كرره كل %1$s في %2$s حتى %3$s
كرره كل %1$s، يحدث %2$d %3$s
@@ -691,7 +679,6 @@
- %d ساعة
- %d ساعة
- يحدث
موقع الخلفية
موقع المقدمة
مستحق %s
diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml
index fa430ffcf..c074af721 100644
--- a/app/src/main/res/values-bg-rBG/strings.xml
+++ b/app/src/main/res/values-bg-rBG/strings.xml
@@ -148,19 +148,12 @@
ежедневно
ежеседмично
на две седмици
- Всеки(а)
Не се повтаря
всеки ден
всяка седмица
всеки месец
всяка година
По избор…
- Да се повтаря всяка минута
- Да се повтаря всеки час
- Да се повтаря всеки ден
- Да се повтаря всяка седмица
- Да се повтаря всеки месец
- Да се повтаря всяка година
- %d задача
- %d задачи
@@ -219,10 +212,6 @@
крайна дата
дата на завършване
- Да се повтаря винаги
- Да се повтаря до %s
- Да се повтаря няколко пъти
- Да се повтаря
%1$s е преместена за %2$s
Създаване на етикет
Създаване на списък
@@ -375,9 +364,7 @@
Календар по подразбиране
Броят на задачите е видим върху иконата на Tasks. Не всички начални екрани го поддържат.
Няколко известия се обединяват в едно
- на един и същи ден всеки месец
всеки %1$s %2$s
- на всеки %1$s %2$s
първа
втора
трета
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 36380efe8..3d9023f4c 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -67,8 +67,6 @@
Inici de Silenci
Final de Silenci
Notificacions al Atzar
- Repeteix indefinidament
- Repeteix fins %s
Temporitzadors actius per %s!
Tasques sent cronometrades
inici d\'aquesta tasca:
@@ -177,7 +175,6 @@
Cada dues setmanes
Setmanalment
Anualment
- REPETEIX CADA DIA
Comença per: \?
Comença per…
El nom de l\'etiqueta conté: \?
@@ -189,9 +186,6 @@
A diari
Setmanalment
Personalitza…
- REPETEIX CADA MINUT
- REPETEIX CADA HORA
- REPETEIX CADA SETMANA
Sense data d\'inici
Etiqueta: \?
El nom de l\'etiqueta conté…
@@ -202,8 +196,6 @@
- %d tasca
- %d tasques
- REPETEIX MENSUALMENT
- REPETEIX ANUALMENT
Ho sentim, hem tingut problemes de comunicació amb els servidors de Google. Per favor torna-ho a intentar.
Google Tasks
Compte %s no trobat-si us plau desconnecta i torna a connectar-te des de la configuració de Google Tasks.
@@ -211,7 +203,6 @@
Sona cinc cops
Sona ininterrompudament
Posposa tot
- Cada
- Hora
- Hores
@@ -292,9 +283,7 @@
Per data de finalització
%1$s reprogramat pel %2$s
Data de finalització
- Repeteix un número de vegades
data de venciment
data de finalització
Data de creació
- Succeeix
\ No newline at end of file
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index f1f10b656..df805ab7d 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -117,8 +117,6 @@
Denně
Týdně
Každých čtrnáct dní
- Opakovat stále
- Opakovat až do %s
Vytvořit nový štítek
Smazat %s?
Časovače aktivní pro %s!
@@ -221,18 +219,12 @@
Odložit vše
Trvalá upozornění
Trvalá upozornění nemohou být odstraněna
- Každý
Neopakuje se
Každý den
Každý týden
Každý měsíc
Každý rok
Vlastní…
- OPAKOVAT PO HODINĚ
- OPAKOVAT DENNĚ
- OPAKOVAT TÝDNĚ
- OPAKOVAT MĚSÍČNĚ
- OPAKOVAT KAŽDOROČNĚ
- %d úkol
- %d úkoly
@@ -310,7 +302,6 @@
termínu úkolu
okamžiku dokončení
- Výskytů
Úkol %1$s byl znovu naplánován na %2$s
Vytvořit nový seznam
Tasks bude při upomínce nahlas číst název úkolu
@@ -381,7 +372,6 @@
Výchozí kalendář
Zobrazit počet úkolů u ikony Tasks. Některé spouštěče tuto funkci nepodporují.
Seskupit několik upozornění do jednoho
- ve stejný den každý měsíc
každý %1$s %2$s
první
druhý
@@ -405,8 +395,6 @@
Vyžaduje PRO předplatné
Odhlásit se
Tato funkce vyžaduje předplatné
- OPAKOVAT PO MINUTĚ
- Opakovat několikrát
Po termínu
Všechna data daného účtu budou z tohoto zařízení odstraněna
Nepodařilo se připojit k účtu
@@ -421,7 +409,6 @@
Nastavení seznamu
Opakuje se každý %1$s v %2$s, ještě %3$d %4$s
Podle systému
- každý %1$s %2$s
Nebo vyberte místo
Chybí oprávnění
Oprávnění k přístupu k poloze je nutné pro zjištění vaší aktuální polohy
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 04d6a7265..228b945ed 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -59,8 +59,6 @@
Hviletid begynder
Hviletid slutter
Tilfældige påmindelser
- Gentag uendeligt
- Gentag indtil %s
Slet opgave
- 1 opgave
@@ -84,7 +82,6 @@
Hviletid
Vis topbjælke
Tilføjede en opgave
- Stopper efter
På Google Huskelisten…
Kontoen %s ikke fundet. Prøv at logge ud og logge ind igen gennem Google Huskeliste-indstillingerne.
Vis kommentarer i opgaveredigering
@@ -220,7 +217,6 @@
tredje
anden
første
- på samme dag hver måned
Saml flere notifikationer i én
Vis en opgavetæller på Tasks’ app-ikon. Ikke alle platforme understøtter badges.
Standardkalender
@@ -362,7 +358,6 @@
Opgrader til pro
Opdater køb
Administrer abonnement
- hver %1$s i %2$s
hver %1$s i %2$s
Gentages %2$s hver %1$s, stopper efter %3$d %4$s
Gentages %2$s hver %1$s indtil %3$s
@@ -384,7 +379,6 @@
Opret ny liste
Opret nyt tag
%1$s er planlagt næste gang til %2$s
- Gentag et bestemt antal gange
udført-dato
frist-dato
@@ -447,19 +441,12 @@
- %d opgave
- %d opgaver
- Gentag årligt
- Gentag månedligt
- Gentag ugentligt
- Gentag dagligt
- Gentag hver time
- Gentag hvert minut
Tilpas…
Hvert år
Hver måned
Hver uge
Hver dag
Gentages ikke
- Hver
Hver anden uge
Ugentligt
Dagligt
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 109b499c5..d168fb25a 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -142,19 +142,12 @@
täglich
wöchentlich
vierzehntägig
- Jede(n, -s)
Wiederholt sich nicht
Jeden Tag
Jede Woche
Jeden Monat
Jedes Jahr
Eigene…
- Minütlich wiederholen
- Stündlich wiederholen
- Täglich wiederholen
- Wöchentlich wiederholen
- Monatlich wiederholen
- Jährlich wiederholen
- %d Aufgabe
- %d Aufgaben
@@ -213,10 +206,6 @@
Fälligkeitsdatum
Erledigung
- Endlos wiederholen
- Wiederholen bis %s
- N-mal wiederholen
- Erscheint
%1$s neu geplant für %2$s
Neues Schlagwort erstellen
Neue Liste erstellen
@@ -364,9 +353,7 @@
Standardkalender
Anzeige eines Aufgabenzählers am Tasks Launcher Icon. Nicht alle Launcher unterstützen Badges.
Mehrere Benachrichtigungen in einer zusammenfassen
- am selben Tag des Monats
jeden %1$s %2$s
- an jedem %1$s %2$s
ersten
zweiten
dritten
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 2d6fa1fc1..f1ae62471 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -111,8 +111,6 @@
Ημερήσια
Εβδομαδιαία
κάθε δυο εβδομάδες
- Επανάληψη επ\'αορίστου
- Επανάληψη μέχρι %s
Χρονοδιακόπτες ενεργοί για %s!
Καθήκοντα με χρονικό όριο
Χρονοδιακόπτης
@@ -136,7 +134,6 @@
Προσθήκη υπενθύμισης
Ηλε. ταχυδρομείο
Δεν επαναλαμβάνεται
- Κάθε
Μη αυτόματη ταξινόμηση Astrid
Η παραγγελία μου
Ανοίξτε το
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index f2c181b45..12abb6de5 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -113,19 +113,10 @@
Ĉiumonate
Ĉiutage
Ĉiusemajne
- Ĉiu
Ne ripetas
- je ĉia %1$s %2$s
- je la sama tago ĉiumonate
ĉia %1$s %2$s
Nur hodiaŭ
Nur teksto
- RIPETI ĈIUJARE
- RIPETI ĈIUMONATE
- RIPETI ĈIUSEMAJNE
- RIPETI ĈIUTAGE
- RIPETI ĈIUHORE
- RIPETI ĈIUMINUTE
lasta
kvina
kvara
@@ -308,7 +299,6 @@
Krei novan liston
Krei novan etikedon
%1$s tempe replanis al %2$s
- Ripeti ĝis %s
- %d jaro
- %d jaroj
@@ -371,7 +361,6 @@
Elekti ĉi tiun lokon
Kiam malfrua
Aŭtomate fermi kiam elekti per tasklisto
- Ripeti ĉiame
Montri nekomencitajn
Modifita %s
Aŭtomate fermi kiam elekti per taska redakto
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 33aa1d1a4..6d0cdb12b 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -142,19 +142,12 @@
Diariamente
Semanalmente
Quincenalmente
- Cada
No repetir
Cada día
Cada semana
Cada mes
Cada año
Personalizado…
- REPETIR CADA MINUTO
- REPETIR CADA HORA
- REPETIR DIARIAMENTE
- REPETIR SEMANALMENTE
- REPETIR MENSUALMENTE
- REPETIR ANUALMENTE
- %d tarea
- %d tareas
@@ -227,10 +220,6 @@
fecha límite
fecha de finalización
- Repetir por siempre
- Repetir hasta %s
- Repetir un número de veces
- Número de repeticiones
%1$s He reprogramado esta tarea recurrente para %2$s
Crear nueva etiqueta
Crea una nueva lista
@@ -382,9 +371,7 @@
Calendario predeterminado
Muestre un recuento de tareas en el icono de iniciador de Tasks. No todos los iniciadores admiten insignias de notificación.
Combinar múltiples notificaciones en una sola
- el mismo día cada mes
cada %1$s %2$s
- cada %1$s %2$s
primer
segundo
tercer
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index 42184ab86..95e383588 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -109,7 +109,6 @@
Vaikimisi meeldetuletus
Püsiv meeldetuletus
Välja lülitatud
- Iga
Ei kordu
Iga päev
Iga nädal
@@ -117,9 +116,6 @@
Iga aasta
Kohandatud…
lõpetamise kuupäev
- Korda igavesti
- Korda kuni %s
- Korda määratud arv kordi
Loo uus nimekiri
Taimer
Kustuta ülesanne
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index ccba78e9e..668932c7c 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -144,19 +144,12 @@
Egunero
Astero
Bi astean behin
- Errepikapena
Ez errepikatu
Egunero
Astero
Hilabetero
Urtero
Pertsonalizatua…
- ERREPIKATU MINUTUKO
- ERREPIKATU ORDUKO
- ERREPIKATU EGUNEKO
- ERREPIKATU ASTEKO
- ERREPIKATU HILABETEKO
- ERREPIKATU URTEKO
- Zeregin %d
- %d zeregin
@@ -215,10 +208,6 @@
epemuga-data
burutze-data
- Errepikatu betirako
- Errepikatu %s arte
- Errepikatu aldi kopuru bat
- Errepikapenak
%1$s %2$s-rako programatuta
Sortu etiketa berria
Sortu zerrenda berria
@@ -373,9 +362,7 @@
Lehenetsitako egutegia
Bistaratu zeregin kopurua abiarazleko Tasks ikonoan. Abiarazle guztiek ez dituzte dominak onartzen.
Taldekatu hainbat jakinarazpen batean
- egun berean hilero
%1$s %2$s bakoitzean
- %1$s %2$s bakoitzean
lehen
bigarren
hirugarren
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index f5aee34ea..f8187a033 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -124,8 +124,6 @@
تاریخ موعود
تاریخ اتمام
- تکرار بینهایت
- تکرار تا %s
ایجاد تگ جدید
ایجاد لیست جدید
وظایف زمانبندی شده
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index 37a4353bb..f08e769d2 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -134,19 +134,12 @@
Päivittäin
Viikoittain
Jokatoinen viikko
- Joka
Älä toista
Joka päivä
Joka viikko
Joka kuukausi
Joka vuosi
Valinnainen…
- TOISTA MINUUTEITTAIN
- TOISTA TUNNEITTAIN
- TOISTA PÄIVITTÄIN
- TOISTA VIIKOTTAIN
- TOISTA KUUKAUSITTAIN
- TOISTA VUOSITTAIN
- %d tehtävä
- %d tehtävät
@@ -205,10 +198,6 @@
Määräpäivä
valmistumispäivä
- Toista loputtomiin
- Toista kunnes %s
- Toista useita kertoja
- Tapahtuu
%1$s uudelleenajastettu %2$s
Luo uusi tunniste
Luo uusi lista
@@ -347,9 +336,7 @@
Oletus kalenteri
Näytä tehtävien lukumäärä Tasksin käynnistyskuvakkeessa. Kaikki käynnistimet eivät tue merkkejä.
Yhdistä useita ilmoituksia yhdeksi
- sama päivä joka kuukausi
joka %1$s %2$s
- jokaisella %1$s %2$s
ensimmäinen
toinen
kolmas
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 30af267be..ee3c1c4fc 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -135,19 +135,12 @@
chaque jour
hebdomadaire
bi-hebdomadaire
- Tou(te)s les
Ne pas répéter
Tous les jours
Toutes les semaines
Tous les mois
Tous les ans
Personnalisé…
- Répéter toutes les minutes
- Répéter toutes les heures
- Répéter tous les jours
- Répéter toutes les semaines
- Répéter tous les mois
- Répéter tous les ans
- %d tâche
- %d tâches
@@ -210,10 +203,6 @@
date d\'échéance
date d\'achèvement
- Répéter indéfiniment
- Répéter jusqu\'à %s
- Nombre de répétitions
- Se répète
%1$s replanifiée à %2$s
Créer une nouvelle étiquette
Créer une nouvelle liste
@@ -362,9 +351,7 @@
Calendrier par défaut
Afficher un compteur de tâche sur l\'icône du lanceur de Tasks. Certains lanceurs ne supportent pas les badges.
Combiner plusieurs notifications en une seule
- le même jour chaque mois
tous les %1$s %2$s
- tous les %1$s %2$s
première
seconde
troisième
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 032b6d32b..ac6f84f05 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -138,8 +138,6 @@
Diariamente
Semanalmente
Quincenalmente
- Repetir de por sempre
- Repetir ata o %s
%1$s reprogramou esta tarefa para %2$s
Crear nova etiqueta
Crea unha nova lista
@@ -327,8 +325,6 @@
Todas as semanas
Todos os meses
Personalizado…
- REPETIR POR MINUTOS
- REPETIR POR DÍAS
- %d tarefa
- %d tarefas
@@ -341,7 +337,6 @@
- tempo
- tempos
- Repetir un número de veces
mañá
Próx Mar
Próx Mér
@@ -354,7 +349,6 @@
Crear nova tarefa
Amosar descrición
Repetir cada %1$s en %2$s
- en cada %1$s %2$s
Non se pode acceder á conta
Recordatorio de chegada
Recordatorio de saída
@@ -424,7 +418,6 @@
por hora
Repetir cada %1$s en %2$s ata %3$s
Repetir cada %1$s o %2$s, ocorre que %3$d %4$s
- no mesmo día de cada mes
Tasks é software de código aberto, con licenza baixo a GNU General Public License v3.0
Precísase dunha suscrición pro
Esta característica necesita dunha suscrición
@@ -511,12 +504,7 @@
Á chegada e á saída
Sen data de inicio
Pospoñer todo
- Todo
Todos os anos
- REPETIR POR HORAS
- REPETIR POR SEMANAS
- REPETIR POR MESES
- REPETIR POR ANOS
Seguranza en Google Drive
Precísase a URL
Opacidade da fila
@@ -599,7 +587,6 @@
%s despois do inicio
%s antes do límite
%s despois do límite
- Ocorre
Comeza con: \?
Comezar por…
Calquera data de inicio
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 28e242dcf..80a73e7e5 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -312,9 +312,7 @@
treći
drugi
prvi
- na svaki %1$s %2$s
svaki %1$s %2$s
- na isti dan svaki mjesec
Spoji više obavijesti u jednu
Prikaži broj zadataka na ikoni pokretača programa Tasks. Neki pokretači ne podržavaju značke.
Standardni kalendar
@@ -452,13 +450,6 @@
- %d zadatka
- %d zadataka
- PONAVLJAJ GODIŠNJE
- PONAVLJAJ MJESEČNO
- PONAVLJAJ TJEDNO
- PONAVLJAJ DNEVNO
- PONAVLJAJ SVAKI SAT
- PONAVLJAJ SVAKE MINUTE
- Svakih
Dvotjedno
Tjedno
Dnevno
@@ -611,10 +602,6 @@
Mjerači vremena aktivni za %s!
Izbrisati %s\?
%1$s preraspoređen za %2$s
- Pojavljivanja
- Ponavljaj x-puta
- Ponavljaj do %s
- Ponavljaj zauvijek
Datum obavljanja
datum obavljanja
datum roka
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index e95a30a7b..94b9033e6 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -141,19 +141,12 @@
Naponta
Hetente
Kéthetente
- Minden
Nincs ismétlés
Minden nap
Minden héten
Minden hónapban
Minden évben
Egyéni…
- ISMÉTLÉS PERCENKÉNT
- ISMÉTLÉS ÓRÁNKÉNT
- ISMÉTLÉS NAPONTA
- ISMÉTLÉS HETENTE
- ISMÉTLÉS HAVONTA
- ISMÉTLÉS ÉVENTE
- %d feladat
- %d feladat
@@ -212,10 +205,6 @@
határidő
befejezés dátuma
- Ismétlés örökké
- Ismétlés eddig: %s
- Ismétlés meghatározott alkalommal
- Előfordul
%1$s újraütemezve ekkorra: %2$s
Új címke létrehozása
Új lista létrehozása
@@ -368,9 +357,7 @@
Alapértelmezett naptár
Feladatok számának megjelenítése a Tasks ikonon. Nem minden launcher által támogatott funkció.
Több értesítés összevonása eggyé
- minden hónap egyazon napján
minden %1$s %2$s
- minden %1$s %2$s
első
második
harmadik
diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml
index 5c33d1fdd..bb47ef568 100644
--- a/app/src/main/res/values-id/strings.xml
+++ b/app/src/main/res/values-id/strings.xml
@@ -101,19 +101,12 @@
Harian
Mingguan
Dua mingguan
- Setiap
Tidak berulang
Setiap hari
Setiap minggu
Setiap bulan
Setiap tahun
Khusus…
- ULANGI PER MENIT
- ULANGI PER JAM
- ULANGI HARIAN
- ULANGI MINGGUAN
- ULANGI BULANAN
- ULANGI TAHUNAN
- %d tugas
@@ -158,9 +151,6 @@
tanggal jatuh tempo
tanggal selesai
- Ulangi selamanya
- Ulangi sampai %s
- Ulangi beberapa kali
Buat tag baru
Buat daftar baru
Hapus %s\?
@@ -309,7 +299,6 @@
Tasks akan mengucapkan nama tugas selama pengingat tugas berjalan
Tampilkan jumlah tugas pada ikon peluncur Tasks. Tidak semua peluncur mendukung fitur lencana ini.
Gabung beberapa notifikasi menjadi satu
- pada hari yang sama setiap bulan
pertama
kedua
ketiga
@@ -566,7 +555,6 @@
Langganan
Set beranda tidak ditemukan
kelima
- pada setiap %1$s %2$s
setiap %1$s %2$s
Ulangi setiap %1$s pada %2$s sampai %3$s
Ulangi setiap %1$s pada %2$s
@@ -589,7 +577,6 @@
Tugas yang Dicatat Waktu
Pencatat Waktu Aktif untuk %s!
%1$s dijadwalkan ulang untuk %2$s
- Terjadi
tanggal selesai
Notifikasi tetap tidak dapat dihapus
Notifikasi untuk tugas tanpa waktu jatuh tempo akan muncul pada %s
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 9face56c0..6a7a8a03f 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -144,19 +144,12 @@
Quotidianamente
Settimanale
Bisettimanale
- Ogni
Non si ripete
Ogni giorno
Ogni settimana
Ogni mese
Ogni anno
Personalizzato…
- RIPETI OGNI MINUTO
- RIPETI OGNI ORA
- RIPETI OGNI GIORNO
- RIPETI OGNI SETTIMANA
- RIPETI OGNI MESE
- RIPETI OGNI ANNO
- %d attività
- %d attività
@@ -229,10 +222,6 @@
data di scadenza
data di completamento
- Ripeti all\'infinito
- Ripeti fino a %s
- Ripeti un dato numero di volte
- Si ripete
%1$s ripianificata per %2$s
Crea nuova etichetta
Crea nuova lista
@@ -383,9 +372,7 @@
Calendario predefinito
Mostra il contatore delle notifiche sull\'icona di Tasks. Non tutti i launcher supportano questa funzionalità.
Combina diverse notifiche in una sola
- lo stesso giorno di ogni mese
ogni %1$s %2$s
- ogni %1$s %2$s
primo
secondo
terzo
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index cf6fdf85e..bdaa1c75e 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -144,19 +144,12 @@
יומי
שבועי
דו שבועי
- כל
לא חוזר
כל יום
כל שבוע
כל חודש
כל שנה
התאמה אישית…
- חזרה בדקות
- חזרה בשעות
- חזרה בימים
- חזרה בשבועות
- חזרה בחודשים
- חזרה בשנים
- %d משימה
- %d משימות
@@ -243,10 +236,6 @@
תאריך יעד
תאריך סיום
- חזור לנצח
- חזרה עד %s
- חוזר מספר פעמים
- קורה
%1$s תזמן מחדש משימה חוזרת זו ל־%2$s
יצירת תגית חדשה
יצירת רשימה חדשה
@@ -402,9 +391,7 @@
יומן ברירת מחדל
הצגת כמות המשימות על הסמל של Tasks במשגר. לא כל המשגרים תומכים בעיטורים.
שילוב מספר התראות באמת
- באותו היום בכל חודש
בכל %1$s %2$s
- בכל %1$s %2$s
ראשון
שני
שלישי
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 47974f973..23fed6bf6 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -149,19 +149,12 @@
毎日
毎週
1 週間おき
- 毎
繰り返ししない
毎日
毎週
毎月
毎年
カスタム…
- 毎分繰り返す
- 毎時間繰り返す
- 毎日繰り返す
- 毎週繰り返す
- 毎月繰り返す
- 毎年繰り返す
- タスク %d 件
@@ -217,10 +210,6 @@
締切日時
完了日
- 永久に繰り返す
- %s まで繰り返す
- 繰り返し回数
- 回数
%1$s を %2$s にスケジュール変更しました
新しいタグを作成
新しいリストを作成
@@ -371,9 +360,7 @@
デフォルトのカレンダー
Tasksランチャーアイコンにタスク数を表示します。すべてのランチャーがバッジをサポートしているわけではありません。
複数の通知を1つの通知にまとめる
- 毎月同じ日に
%1$s %2$s ごと
- %1$s %2$s ごとに
第一週
第二週
第三週
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index c643ae06b..bc008d8ca 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -144,19 +144,12 @@
매일
매주
격주로
- 매
반복하지 않기
매일
매주
매월
매년
사용자설정…
- 매분 반복
- 매시 반복
- 매일 반복
- 매주 반복
- 매월 반복
- 매년 반복
- %d 할일
- %d 할일들
@@ -215,10 +208,6 @@
마감일
완료일
- 계속 반복
- %s 까지 반복
- 여러회 반복
- 반복횟수
%1$s 이 %2$s 로 변경되었습니다
새 태그 만들기
새 목록 만들기
@@ -368,9 +357,7 @@
기본 달력
Tasks 앱 아이콘 위에 할일 개수를 표시합니다. 모든 런처가 배지 기능을 지원하지는 않습니다.
여러 상태바 알림을 하나로 묶어서 표시합니다
- 매월 같은 날에
매 %1$s %2$s
- 매 %1$s %2$s 마다
첫번째
두번째
세번째
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index 7f09dbd2e..bd7fb3bad 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -148,19 +148,12 @@
Kas dieną
Kas savaitę
Du kart savaitėje
- Kiekvieną
Nekartoti
Kiekvieną dieną
Kiekvieną savaitę
Kiekvieną mėnesį
Kiekvienais metais
Individualizuotas…
- KARTOTI KAS MINUTĘ
- KARTOTI KAS VALANDĄ
- KARTOTI KAS DIENĄ
- KARTOTI KAS SAVAITĘ
- KARTOTI KAS MĖNESĮ
- KARTOTI KAS METUS
- %d užduotis
- %d užduotys
@@ -233,10 +226,6 @@
termino data
Užbaigimo data
- Kartoti amžinai
- Kartoti iki %s
- Kartoti nustatytą kiekį kartų
- Įvyksta
%1$s perplanuotas šiai datai: %2$s
Sukurti naują etiketę
Sukurti naują sąrašą
@@ -389,9 +378,7 @@
Numatytas kalendorius
Rodyti užduočių kiekį Tasks paleidimo piktogramoje. Ne visos paleidimo programos palaiko šią funkciją.
Sujungti kelis pranešimus į vieną
- Tą pačią mėnesio dieną
Kiekvieną %1$s %2$s
- Kiekvieną %1$s %2$s
pirmą
antrą
trečią
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index 6398fc58d..fe18009b8 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -89,8 +89,6 @@
- år
- år
- Gjenta for alltid
- Gjenta til %s
%s har aktive tidtakere!
Gjøremål med tidtaker
Nedtelling
@@ -229,19 +227,12 @@
Daglig
Ukentlig
Annenhver uke
- Hver
Gjentas ikke
Hver dag
Hver uke
Månedlig
Årlig
Egendefinert…
- GJENTA HVERT MINUTT
- GJENTA HVER TIME
- GJENTA DAGLIG
- GJENTA UKENTLIG
- GJENTA MÅNEDLIG
- GJENTA ÅRLIG
- %d gjøremål
- %d gjøremål
@@ -272,8 +263,6 @@
tidsfristsdato
fullføringsdato
- Gjenta et antall ganger
- Inntreffer
%1$s flyttet til %2$s
Opprett en ny etikett
Opprett en ny liste
@@ -373,9 +362,7 @@
Ikke legg til i kalender
Forvalgt kalender
Vis gjøremålsantall i Task-oppstarterikonet. Ikke alle oppstartere støtter merker.
- på samme dag hver måned
hver %1$s %2$s
- hver %1$s %2$s
første
andre
tredje
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index bc031b8f4..626eae4bb 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -143,19 +143,12 @@
Dagelijks
Wekelijks
Elke twee weken
- Elke
Niet herhalen
Elke dag
Elke week
Elke maand
Elk jaar
Aangepast…
- HERHAAL ELKE MINUUT
- HERHAAL ELK UUR
- HERHAAL DAGELIJKS
- HERHAAL WEKELIJKS
- HERHAAL MAANDELIJKS
- HERHAAL JAARLIJKS
- %d taak
- %d taken
@@ -214,10 +207,6 @@
vervaldatum
voltooiingsdatum
- Eindeloos herhalen
- Herhalen tot %s
- N-keer herhalen
- Gebeurt
%1$s verplaatst naar %2$s
Nieuw label aanmaken
Nieuwe lijst maken
@@ -363,9 +352,7 @@
Standaardkalender
Toon taken teller bij pictogram van Tasks. Niet alle launchers ondersteunen badges.
Combineer meerdere meldingen samen in één
- elke maand op dezelfde dag
elke %1$s %2$s
- op elke %1$s %2$s
eerste
tweede
derde
diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml
index 1ba9e3ca5..284d0ca9d 100644
--- a/app/src/main/res/values-or/strings.xml
+++ b/app/src/main/res/values-or/strings.xml
@@ -126,7 +126,6 @@
ଵର୍ଣ୍ଣନା
ପ୍ରାଥମିକତା
ପ୍ରତି ଘଣ୍ଟା
- ପ୍ରତ୍ୟେକ
ପ୍ରତିଵର୍ଷ
ପ୍ରତି ମାସ
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 388bb31fa..0b601b6d3 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -138,19 +138,12 @@
raz dziennie
raz w tygodniu
raz na dwa tygodnie
- Każdy
Nie powtarza się
Każdego dnia
Każdego tygodnia
Każdego miesiąca
Każdego roku
Własne ustawienie…
- Powtarzaj co ileś minut
- Powtarzaj co ileś godzin
- Powtarzaj co ileś dni
- Powtarzaj co ileś tygodni
- Powtarzaj co ileś miesięcy
- Powtarzaj co ileś lat
- %d zadań
- %d zadania
@@ -223,10 +216,6 @@
w terminie
data zakończenia
- Powtarzaj bez końca
- Powtarzaj do %s
- Powtarzaj podaną ilość razy
- Występuje
%1$s przełożone na %2$s
Stwórz nowy tag
Utwórz nową listę
@@ -379,9 +368,7 @@
Domyślny kalendarz
Wyświetl liczbę zadań na ikonie Tasks. Nie wszystkie launchery to wspierają.
Wyświetlaj wiele powiadomień jako jedno
- tego samego dnia każdego miesiąca
każdy %1$s %2$s
- co każde %1$s %2$s
pierwszy
drugi
trzeci
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 043944efb..64193370b 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -139,19 +139,12 @@
Diariamente
Semanalmente
A cada duas semanas
- Todo
Não repete
Todos os dias
Todas as semanas
Todos os meses
Todos os anos
Personalizado…
- REPETIR A CADA MINUTO
- REPETIR A CADA HORA
- REPETIR DIARIAMENTE
- REPETIR SEMANALMENTE
- REPETIR MENSALMENTE
- REPETIR ANUALMENTE
- %d tarefa
- %d tarefas
@@ -210,10 +203,6 @@
data de vencimento
data de conclusão
- Repetir para sempre
- Repetir até %s
- Repetir um número de vezes
- Ocorre
%1$s remarcada para %2$s
Criar nova etiqueta
Criar nova lista
@@ -365,9 +354,7 @@
Calendário padrão
Mostrar a contagem de tarefas no ícone do Tasks no launcher. Nem todos os launchers suportam emblemas.
Combinar múltiplas notificações em uma única
- no mesmo dia cada mês
todo %1$s %2$s
- em todo %1$s %2$s
primeiro
segundo
terceiro
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 509c6884a..23eeb6edd 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -133,19 +133,12 @@
Diariamente
Semanalmente
A cada 2 semanas
- A cada
Não repetir
Todos os dias
Todas as semanas
Todos os meses
Todos os anos
Personalizado…
- Repetir a cada minuto
- Repetir a cada hora
- Repetir todos os dias
- Repetir todas as semanada
- Repetir todos os meses
- Repetir todos os anos
- %d tarefa
- %d tarefas
@@ -204,10 +197,6 @@
data de fim
Data de conclusão
- Repetir eternamente
- Repetir até %s
- Repetir um número de vezes
- Ocorre
%1$s agendada para %2$s
Criar nova etiqueta
Criar nova lista
@@ -343,9 +332,7 @@
Não adicionar ao calendário
Calendário predefinido
Combinar várias notificações numa só notificação
- cada mês no mesmo dia
cada %1$s %2$s
- em cada %1$s %2$s
primeiro
segundo
terceiro
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 3dc002684..2f4ce689f 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -114,9 +114,7 @@
al treilea
al doilea
prima
- la fiecare %1$s %2$s
în fiecare %1$s %2$s
- în aceeași zi în fiecare lună
Combinați mai multe notificări într-una singură
Afișarea unui număr de sarcini pe pictograma de lansare a aplicației Tasks. Nu toate lansatoarele acceptă insigne.
Calendar implicit
@@ -366,19 +364,12 @@
- %d sarcini
- %d sarcini
- SE REPETĂ ANUAL
- REPETIȚIE LUNARĂ
- REPETAȚI SĂPTĂMÂNAL
- REPETIȚI ZILNIC
- REPETIȚIE ORARĂ
- REPETĂ CU MINUȚIOZITATE
Personalizat…
În fiecare an
In fiecare luna
În fiecare săptămână
Zilnic
Nu se repetă
- Fiecare
Bi-săptămânal
Săptămânal
Zilnic
@@ -658,10 +649,6 @@
Creați o nouă listă
Creați o nouă etichetă
%1$s reprogramat pentru %2$s
- Se întâmplă
- Se repetă de mai multe ori
- Se repetă până la %s
- Repetă pentru totdeauna
Data de finalizare
data finalizării
data scadentă
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index fec14f037..2f4ada08e 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -143,19 +143,12 @@
Ежедневно
Еженедельно
Каждые две недели
- Каждую(ые)
Не повторять
Ежедневно
Еженедельно
Ежемесячно
Ежегодно
Другой…
- ПОВТОРЯТЬ ЕЖЕМИНУТНО
- ПОВТОРЯТЬ ЕЖЕЧАСНО
- ПОВТОРЯТЬ ЕЖЕДНЕВНО
- ПОВТОРЯТЬ ЕЖЕНЕДЕЛЬНО
- ПОВТОРЯТЬ ЕЖЕМЕСЯЧНО
- ПОВТОРЯТЬ ЕЖЕГОДНО
- %d задача
- %d задачи
@@ -228,10 +221,6 @@
намеченной даты
даты завершения
- Повторять всегда
- Повторять до %s
- Повторять несколько раз
- Повторять
%1$s перенесено на %2$s
Создать новый тег
Создать новый список
@@ -385,9 +374,7 @@
Календарь по умолчанию
Отображать количество задач на иконке приложения. Не все лаунчеры это поддерживают.
Группировать несколько уведомлений в одно
- в тот же день ежемесячно
каждый %1$s %2$s
- в каждый %1$s %2$s
первая
вторая
третья
diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml
index 2b1d0a8a8..a8d696eba 100644
--- a/app/src/main/res/values-si/strings.xml
+++ b/app/src/main/res/values-si/strings.xml
@@ -45,9 +45,6 @@
අරය
කාර්යය එකතු කරන ලදි
මෙම කාර්යය ආරම්භ කරන ලදි:
- සිදු වේ
- කිහිප වතාවක් නැවත කරන්න
- %s තෙක් නැවත කරන්න
- විනාඩි
- විනාඩි
@@ -234,7 +231,6 @@
DAVx⁵ යෙදුම සමඟ ඔබගේ කාර්යයන් සමමුහුර්ත කරන්න
ගොනු මත පදනම් වූ සමමුහුර්තකරණය
Tasks.org සමඟ ඔබේ කාර්යයන් සමමුහුර්ත කරන්න
- සෑම
පිරික්සුම් කොටු පෙන්වන්න
ඊයේ
ඊයේ
@@ -269,7 +265,6 @@
%s මකාදමනවාද\?
නව ලැයිස්තුවක් සාදන්න
නව ටැගයක් සාදන්න
- දිගටම නැවත කරන්න
සම්පූර්ණ කරන දිනය
සම්පූර්ණ කරන දිනය
@@ -329,12 +324,6 @@
- කාර්යයන් %d
නියමිත වේලාවක් නොමැති කාර්යයන් සඳහා දැනුම්දීම් %s හි දිස්වනු ඇත
- වසරකට වතාවක් නැවත
- මාසයකට වතාවක් නැවත
- සතියකට වතාවක් නැවත
- සෑම දිනකම නැවත
- පැයකට වතාවක් නැවත
- මිනිත්තුවකට වතාවක් නැවත
අභිරුචි…
සෑම වසරකම
සෑම මාසයකම
@@ -412,7 +401,6 @@
Dashclock දිගුව
Pro වෙත උත්ශ්රේණි කරන්න
CalDAV Home Set හමු නොවීය
- සෑම %1$s %2$s
සෑම %1$s %2$s
", "
පුනරාවර්තනය සෑම %1$s %2$s, %3$d %4$s සිදු වේ
@@ -572,7 +560,6 @@
තෙවන
දෙවැනි
පළමුවන
- සෑම මසකම එකම දිනයක
බහුවිධ දැනුම්දීම් එකකට ඒකාබද්ධ කරන්න
Tasks දියත් කිරීමේ නිරූපකයේ කාර්ය ගණන පෙන්වන්න. සියලුම දියත් කරන්නන් (launchers) ලාංඡන සඳහා සහය නොදක්වයි.
පෙරනිමි දින දර්ශනය
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index e98a60527..5179b4e4e 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -144,19 +144,12 @@
denne
Týždenne
Každý druhý týždeň
- Každý
Neopakovať
Každý deň
Každý týždeň
Každý mesiac
Každý rok
Vlastné…
- OPAKOVAŤ KAŽDÚ MINÚTU
- OPAKOVAŤ KAŽDÚ HODINU
- OPAKOVAŤ KAŽDÝ DEŇ
- OPAKOVAŤ KAŽDÝ TÝŽDEŇ
- OPAKOVAŤ KAŽDÝ MESIAC
- OPAKOVAŤ KAŽDÝ ROK
- %d úloha
- %d úloh
@@ -215,10 +208,6 @@
termín
deň dokončenia
- Opakovať donekonečna
- Opakovať do %s
- Opakovať určitý počet krát
- Vyskytuje sa
%1$s preložené na %2$s
Vytvoriť nový štítok
Vytvoriť nový zoznam
@@ -369,9 +358,7 @@
Východzí kalendár
Zobraziť počet úloh na ikone spustenia Úloh. Niektoré spúšťače nepodporujú odznaky.
Zlúč viaceré upozornenia do jedného
- v ten istý deň v každom mesiaci
každý %1$s %2$s
- na každý %1$s %2$s
prvý
druhý
tretí
diff --git a/app/src/main/res/values-sl-rSI/strings.xml b/app/src/main/res/values-sl-rSI/strings.xml
index c3371900b..f68d13464 100644
--- a/app/src/main/res/values-sl-rSI/strings.xml
+++ b/app/src/main/res/values-sl-rSI/strings.xml
@@ -111,8 +111,6 @@
dnevno
tedensko
vsake dva tedna
- Ponavljaj do preklica
- Ponavljaj do %s
Merilniki časa aktivni za %s!
Opravki katerim se meri čas
Merilnik časa
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 9e527bcfc..2e7ac2618 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -14,8 +14,6 @@
Po datumu: ?
%s (završeno)
Nasumice posetnik
- Ponavljaj zauvek
- Interval ponavljanja %s
Obriši zadatak
Vibracije
Kontaktiraj razvojni tim
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index e7d49977c..8f3e5f1dc 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -133,8 +133,6 @@
varje dag
varje vecka
varannan vecka
- Upprepa för alltid
- Repetera till %s
%1$s omplanerat till %2$s
Skapa en ny ettikett
Skapa en ny lista
@@ -241,19 +239,12 @@
Vid avgång
Vid ankomst och avgång
Snooza alla
- Varje
Repetera inte
Varje dag
Varje vecka
Varje månad
Varje år
Anpassad…
- REPETERA VARJE MINUT
- REPETERA VARJE TIMME
- REPETERA VARJE DAG
- REPETERA VARJE VECKA
- REPETERA VARJE MÅNAD
- REPETERA VARJE ÅR
- %d uppgift
- %d uppgifter
@@ -312,8 +303,6 @@
förfallodatum
datum färdigställt
- Upprepa ett antal gånger
- Inträffar
Tidtagare
Google Drive-säkerhetskopia
Radavstånd
@@ -371,9 +360,7 @@
Standardkalender
Visa en uppgiftsräkning på Tasks-startikon. Inte alla startprogram stöder märken.
Kombinera flera aviseringar till en
- samma dag varje månad
varje %1$s %2$s
- på varje %1$s %2$s
första
andra
Tredje
diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml
index 5983e86d8..fff184057 100644
--- a/app/src/main/res/values-ta/strings.xml
+++ b/app/src/main/res/values-ta/strings.xml
@@ -84,18 +84,12 @@
இப்போதே நகல் எடு
நகலை இறக்குமதி செய்
பெயர்
- வருடந்தோறும் மீண்டும் செய்யவும்
- மாதந்தோறும் செய்யவும்
- வாரந்தோறும் செய்யவும்
- தினமும் செய்யவும்
- மணிநேரம் மீண்டும் செய்யவும்
தனிப்பயன்…
ஒவ்வொரு வருடமும்
ஒவ்வொரு மாதமும்
ஒவ்வொரு வாரமும்
தினமும்
மீண்டும் செய்யாது
- ஒவ்வொரு
இரு வாராந்திர
வாராந்திர
தினசரி
@@ -271,9 +265,7 @@
மூன்றாவது
இரண்டாவது
முதல்
- ஒவ்வொரு %1$s %2$s இல்
ஒவ்வொரு %1$s %2$s
- ஒவ்வொரு மாதமும் ஒரே நாளில்
பல அறிவிப்புகளை ஒன்றில் இணைக்கவும்
இயல்புநிலை காலண்டர்
நாள்காட்டியில் சேர்க்க வேண்டாம்
@@ -437,10 +429,6 @@
%s ஐ நீக்கவா\?
புதிய பட்டியலை உருவாக்கவும்
புதிய குறிச்சொல்லை உருவாக்கவும்
- நிகழ்கிறது
- பல முறை செய்யவும்
- %s வரை மீண்டும் செய்யவும்
- என்றென்றும் செய்யவும்
நிறைவு தேதி
உரிய தேதி
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
index 0bcbe62d4..e2731a611 100644
--- a/app/src/main/res/values-th/strings.xml
+++ b/app/src/main/res/values-th/strings.xml
@@ -234,9 +234,7 @@
ที่สาม
ที่สอง
ก่อน
- ทุกๆ %1$s %2$s
ทุกๆ %1$s %2$s
- ในวันเดียวกันของทุกเดือน
รวมการแจ้งเตือนหลายรายการไว้ในรายการเดียว
แสดงจํานวนงานบนไอคอนตัวเปิดใช้งาน ตัวเรียกใช้งานไม่รองรับป้ายทั้งหมด
ปฏิทินเริ่มต้น
@@ -416,10 +414,6 @@
สร้างรายการใหม่
สร้างแท็กใหม่
%1$s จัดกําหนดการใหม่สําหรับ %2$s
- เกิดขึ้น
- ทําซ้ําหลายครั้ง
- ทําซ้ําจนถึง %s
- ทําซ้ําตลอดไป
วันที่เสร็จสมบูรณ์
วันที่เสร็จสมบูรณ์
วันที่ครบกําหนด
@@ -471,18 +465,11 @@
- %d งาน
- ทําซ้ําทุกปี
- ทําซ้ําทุกเดือน
- ทําซ้ําทุกสัปดาห์
- ทำซ้ำทุกวัน
- ทําซ้ํารายชั่วโมง
- ทําซ้ําเป็นนาที
ทุกปี
ทุกเดือน
ทุกสัปดาห์
ทุกวัน
ไม่ซ้ำ
- ทุกๆ
รายปักษ์
รายสัปดาห์
ทุกวัน
diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml
index 9fa6aa171..9afa4cd96 100644
--- a/app/src/main/res/values-tl/strings.xml
+++ b/app/src/main/res/values-tl/strings.xml
@@ -39,8 +39,6 @@
- taon
- mga taon
- Ulitin habambuhay
- Ulitin hanggang %s
Tanggalin gawain
Mga taginting
Pinagmulang code
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index ab1f80402..0c91fb76f 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -144,19 +144,12 @@
her gün
her hafta
haftada 2 kez
- Her
Yinelemez
Her gün
Her hafta
Her ay
Her yıl
Özel…
- DAKİKALIK YİNELE
- SAATLİK YİNELE
- GÜNLÜK YİNELE
- HAFTALIK YİNELE
- AYLIK YİNELE
- YILLIK YİNELE
- %d görev
- %d görev
@@ -215,10 +208,6 @@
bitiş tarihi
tamamlanma tarihi
- Sürekli yinele
- %s\'e kadar yinele
- Birkaç kez yinele
- Şu kadar gerçekleşir
%1$s, %2$s için yeniden zamanlandı
Yeni etiket oluştur
Yeni liste oluştur
@@ -370,9 +359,7 @@
Öntanımlı takvim
Tasks başlatıcı simgesinde görev sayısı göster. Tüm başlatıcılar rozetleri desteklemez.
Birden çok bildirimi tek bildirime birleştir
- her ayın aynı gününde
her %1$s %2$s
- her %1$s %2$s
birinci
ikinci
üçüncü
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 0e984e7a6..d45f17e6a 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -143,19 +143,12 @@
Щоденно
Щотижнево
Кожні два тижні
- Кожні
Не повторювати
Щодня
Щотижня
Щомісяця
Щороку
Інше…
- ПОВТОРЮВАТИ ПОХВИЛИННО
- ПОВТОРЮВАТИ ПОГОДИННО
- ПОВТОРЮВАТИ ПОДЕННО
- ПОВТОРЮВАТИ ПОТИЖНЕВО
- ПОВТОРЮВАТИ ПОМІСЯЧНО
- ПОВТОРЮВАТИ ПОРІЧНО
- %d завдання
- %d завдання
@@ -228,10 +221,6 @@
призначеної дати
дати виконання
- Повторювати завжди
- Повторювати до %s
- Повторити кількість разів
- Станеться
%1$s перенесено на %2$s
Створити нову мітку
Новий список
@@ -386,9 +375,7 @@
Типовий календар
Показувати кількість завдань на піктограмі запуску Tasks. Не всі лаунчери підтримують такі значки.
Групувати кілька сповіщень в одне
- того ж дня щомісяця
що %1$s %2$s
- що %1$s %2$s
перший
другий
третій
diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml
index eaaceae93..f9fe927f3 100644
--- a/app/src/main/res/values-ur/strings.xml
+++ b/app/src/main/res/values-ur/strings.xml
@@ -140,7 +140,6 @@
کیلنڈر ایونٹ نہیں مل سکا
کیلنڈر ایونٹ کھولیں
کیلنڈر میں کام شامل کرنے میں خرابی!
- ہر ایک
ہفتے میں دو بار
ہفتہ وار
روزانہ
@@ -160,10 +159,6 @@
پانچ بار رنگ بجنا
ایک بار رنگ بجنا
ایک نوٹ ریکارڈ کریں
- ہفتہ وار دہرانا
- روزانہ دہرانا
- گھنٹہ وار دہرانا
- منٹ وار دہرانا
کسٹم…
ہر سال
ہر مہینے
@@ -183,10 +178,6 @@
ڈیفالٹ تاریخ آغاز
تاریخ آغاز
بلحاظ تاریخ آغاز
- واقع ہونا
- چند مرتبہ دہرانا
- %s تک دہرانا
- ہمیشہ دہرانا
تاریخ تکمیل
تاریخ تکمیل
مقررہ تاریخ
@@ -254,8 +245,6 @@
- %d ٹاسک
- %d ٹاسکس
- سالانہ دہرانا
- مہینہ وار دہرانا
گزشتہ کل
گزشتہ کل
آنے والا کل
@@ -420,9 +409,7 @@
تیسرا
دوسرا
پہلا
- ہر %1$s%2$s کو
ہر %1$s%2$s
- ہر مہینے کو ایک ہی دن
متعدد نوٹیفیکیشنز کو ایک میں کر دیں
ٹاسکس ایپ کے آئیکن پر ٹاسک کی تعداد ظاہر کریں۔ تمام لانچرز نشانات کو سپوڑٹ نہیں کرتے۔
ڈیفالٹ کیلںنڈر
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 82263771d..83fb51acc 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -291,11 +291,9 @@
cuối cùng
thứ năm
thứ tư
- vào ngày đó mỗi tháng
thứ ba
thứ hai
đầu tiên
- vào mỗi %1$s %2$s
mỗi %1$s %2$s
Kết hợp nhiều thông báo thành một
Hiển thị số lượng công việc trên biểu tượng trên launcher của Tasks. Không phải tất cả launcher đều hỗ trợ huy hiệu.
@@ -490,10 +488,6 @@
Tạo danh sách mới
Tạo thẻ mới
%1$s đã được lên lịch lại cho %2$s
- Xảy ra
- Lặp lại một số lần
- Lặp lại đến %s
- Lặp lại mãi mãi
Ngày hoàn thành
ngày hoàn thành
ngày hạn
@@ -545,19 +539,12 @@
- %d công việc
- LẶP LẠI HÀNG NĂM
- LẶP LẠI HÀNG THÁNG
- LẶP LẠI HÀNG TUẦN
- LẶP LẠI HÀNG NGÀY
- LẶP LẠI HÀNG TIẾNG
- LẶP LẠI HÀNG PHÚT
Tuỳ chỉnh…
Mỗi năm
Mỗi tháng
Mỗi tuần
Mỗi ngày
Không lặp lại
- Mỗi
Mỗi hai tuần
Hàng tuần
Hàng ngày
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index a1c29f00b..8a39ba747 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -132,19 +132,12 @@
每天
每周
每两周
- 每
不要重复
每天
每周
每月
每年
自定义…
- 按分钟重复
- 按小时重复
- 按天重复
- 按周重复
- 按月重复
- 按年重复
- %d 个任务
@@ -200,10 +193,6 @@
截止日期
完成日期
- 永远重复
- 重复到 %s
- 重复多次
- 发生
%1$s 重新安排在 %2$s
新建标签
新建列表
@@ -352,9 +341,7 @@
默认日历
在 Tasks 启动图标上显示任务计数。不是所有的启动器都支持角标。
将多个通知合并为一个通知
- 在每月的同一天
每 %1$s %2$s
- 在每 %1$s 个 %2$s
第一个
第二个
第三个
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index ff97f3d75..63fb4f1cd 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -114,8 +114,6 @@
- %d 分鐘
- %d 分鐘
- 永遠重複
- 重複到 %s
新增標籤
新增清單
%s 的碼錶啟動了!
@@ -263,8 +261,6 @@
已新增的任務
要刪除 %s 嗎 \?
%1$s 重新安排至 %2$s
- 發生
- 重複次數
完成日期
完成日期
截止日期
@@ -278,7 +274,6 @@
每星期
每天
不重複
- 每
固定通知將無法被消除
固定通知
全部延遲提醒
@@ -430,9 +425,7 @@
第三週
第二週
第一週
- 在每 %1$s %2$s
每 %1$s %2$s
- 每月的同一天
結合多項通知成一項通知
預設日曆
不要新增至日曆
@@ -529,12 +522,6 @@
- 時間
- 每年重覆
- 每月重複
- 每週重複
- 每天重複
- 每小時重複
- 每分鐘重複
自訂…
每年
任何起始日期
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 7c1e02598..800a5ef98 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -82,15 +82,6 @@
- @string/repeat_option_custom
-
- - @string/repeat_minutely
- - @string/repeat_hourly
- - @string/repeat_daily
- - @string/repeat_weekly
- - @string/repeat_monthly
- - @string/repeat_yearly
-
-
- @string/due_date
- @string/repeat_type_completion_capitalized
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index d4dbda73c..0e6fa2eee 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -23,8 +23,6 @@
48dp
36dp
24dp
- 6dp
- 48dp
14sp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0a2bb569b..b62c1ce30 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -167,19 +167,16 @@ File %1$s contained %2$s.\n\n
Daily
Weekly
Bi-weekly
- Every
Does not repeat
Every day
Every week
Every month
Every year
Custom…
- REPEAT MINUTELY
- REPEAT HOURLY
- REPEAT DAILY
- REPEAT WEEKLY
- REPEAT MONTHLY
- REPEAT YEARLY
+
+ - occurrence
+ - occurrences
+
- %d task
- %d tasks
@@ -263,10 +260,6 @@ File %1$s contained %2$s.\n\n
due date
completion date
Completion date
- Repeat forever
- Repeat until %s
- Repeat a number of times
- Occurs
%1$s rescheduled for %2$s
Create new tag
Create new list
@@ -455,6 +448,13 @@ File %1$s contained %2$s.\n\n
weekly
monthly
yearly
+ Custom recurrence
+ Repeats every
+ Repeats on
+ Never
+ On
+ After
+ Ends
Repeats every %s
Repeats every %1$s on %2$s
Repeats every %1$s until %2$s
@@ -467,9 +467,9 @@ File %1$s contained %2$s.\n\n
Show notifications for tasks without due times
Display a task count on Tasks\' launcher icon. Not all launchers support badges.
Combine multiple notifications into one
- on the same day each month
every %1$s %2$s
- on every %1$s %2$s
+ Monthly on day %1d
+ Monthly on the %1$s %2$s
first
second
third
diff --git a/app/src/test/java/org/tasks/repeats/CustomRecurrenceViewModelTest.kt b/app/src/test/java/org/tasks/repeats/CustomRecurrenceViewModelTest.kt
new file mode 100644
index 000000000..572f8f8a6
--- /dev/null
+++ b/app/src/test/java/org/tasks/repeats/CustomRecurrenceViewModelTest.kt
@@ -0,0 +1,173 @@
+package org.tasks.repeats
+
+import androidx.lifecycle.SavedStateHandle
+import net.fortuna.ical4j.model.Recur.Frequency.DAILY
+import net.fortuna.ical4j.model.Recur.Frequency.HOURLY
+import net.fortuna.ical4j.model.Recur.Frequency.MINUTELY
+import net.fortuna.ical4j.model.Recur.Frequency.MONTHLY
+import net.fortuna.ical4j.model.Recur.Frequency.SECONDLY
+import net.fortuna.ical4j.model.Recur.Frequency.YEARLY
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.tasks.repeats.CustomRecurrenceActivity.Companion.EXTRA_DATE
+import org.tasks.repeats.CustomRecurrenceActivity.Companion.EXTRA_RRULE
+import org.tasks.time.DateTime
+import java.time.DayOfWeek
+import java.util.Locale
+
+class CustomRecurrenceViewModelTest {
+ @Test
+ fun defaultStateValue() {
+ val state = newVM().state.value
+ assertEquals(CustomRecurrenceViewModel.ViewState(), state)
+ }
+
+ @Test
+ fun setFrequencies() {
+ assertEquals("FREQ=SECONDLY", newVM { setFrequency(SECONDLY) }.getRecur())
+ assertEquals("FREQ=MINUTELY", newVM { setFrequency(MINUTELY) }.getRecur())
+ assertEquals("FREQ=HOURLY", newVM { setFrequency(HOURLY) }.getRecur())
+ assertEquals("FREQ=DAILY", newVM { setFrequency(DAILY) }.getRecur())
+ assertEquals("FREQ=WEEKLY", newVM().getRecur())
+ assertEquals("FREQ=MONTHLY", newVM { setFrequency(MONTHLY) }.getRecur())
+ assertEquals("FREQ=YEARLY", newVM { setFrequency(YEARLY) }.getRecur())
+ }
+
+ @Test
+ fun setInterval() {
+ assertEquals("FREQ=WEEKLY;INTERVAL=4", newVM { setInterval(4) }.getRecur())
+ }
+
+ @Test
+ fun ignoreCountWhenChangingToNever() {
+ assertEquals(
+ "FREQ=WEEKLY",
+ newVM("FREQ=WEEKLY;COUNT=2") { setEndType(0) }.getRecur()
+ )
+ }
+
+ @Test
+ fun setEndDate() {
+ assertEquals(
+ "FREQ=WEEKLY;UNTIL=20230726",
+ newVM {
+ setEndDate(DateTime(2023, 7, 26).millis)
+ setEndType(1)
+ }.getRecur()
+ )
+ }
+
+ @Test
+ fun ignoreEndDateWhenChangingToNever() {
+ assertEquals(
+ "FREQ=WEEKLY",
+ newVM("FREQ=WEEKLY;UNTIL=20230726") { setEndType(0) }.getRecur()
+ )
+ }
+
+ @Test
+ fun setDaysInOrder() {
+ assertEquals(
+ "FREQ=WEEKLY;BYDAY=MO,TU,WE",
+ newVM {
+ toggleDay(DayOfWeek.MONDAY)
+ toggleDay(DayOfWeek.WEDNESDAY)
+ toggleDay(DayOfWeek.TUESDAY)
+ }
+ .getRecur()
+ )
+ }
+
+ @Test
+ fun ignoreDaysForNonWeekly() {
+ assertEquals(
+ "FREQ=MONTHLY",
+ newVM {
+ setFrequency(MONTHLY)
+ toggleDay(DayOfWeek.MONDAY)
+ }
+ .getRecur()
+ )
+ }
+
+ @Test
+ fun setCount() {
+ assertEquals(
+ "FREQ=WEEKLY;COUNT=3",
+ newVM {
+ setEndType(2)
+ setOccurrences(3)
+ }
+ .getRecur()
+ )
+ }
+
+ @Test
+ fun toggleDayOff() {
+ assertEquals(
+ "FREQ=WEEKLY;BYDAY=MO",
+ newVM("FREQ=WEEKLY;BYDAY=MO,TU") { toggleDay(DayOfWeek.TUESDAY) }.getRecur()
+ )
+ }
+
+ @Test
+ fun nthDayOfMonth() {
+ assertEquals(
+ "FREQ=MONTHLY;BYDAY=4TH",
+ newVM(dueDate = DateTime(2023, 7, 27)) {
+ setFrequency(MONTHLY)
+ setMonthSelection(1)
+ }.getRecur()
+ )
+ }
+
+ @Test
+ fun lastDayOfMonth() {
+ assertEquals(
+ "FREQ=MONTHLY;BYDAY=-1TH",
+ newVM(dueDate = DateTime(2023, 7, 27)) {
+ setFrequency(MONTHLY)
+ setMonthSelection(2)
+ }.getRecur()
+ )
+ }
+
+ @Test
+ fun restoreMonthDay() {
+ assertEquals(
+ "FREQ=MONTHLY;BYDAY=-1TH",
+ newVM(
+ recur = "FREQ=MONTHLY;BYDAY=-1TH",
+ dueDate = DateTime(2023, 7, 27)
+ ).getRecur()
+ )
+ }
+
+ @Test
+ fun changeMonthDay() {
+ assertEquals(
+ "FREQ=MONTHLY;BYDAY=4TH",
+ newVM(
+ recur = "FREQ=MONTHLY;BYDAY=-1TH",
+ dueDate = DateTime(2023, 7, 27)
+ ) {
+ setMonthSelection(1)
+ }.getRecur()
+ )
+ }
+
+ private fun newVM(
+ recur: String? = null,
+ dueDate: DateTime = DateTime(0),
+ block: CustomRecurrenceViewModel.() -> Unit = {}
+ ) =
+ CustomRecurrenceViewModel(
+ savedStateHandle = SavedStateHandle(
+ mapOf(
+ EXTRA_RRULE to recur,
+ EXTRA_DATE to dueDate.millis,
+ )
+ ),
+ locale = Locale.US
+ ).also(block)
+}