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) +}