Add repeat for a number of occurrences

pull/574/head
Alex Baker 7 years ago
parent f894715fd8
commit d8a676217c

@ -10,6 +10,7 @@ import android.content.ContentValues;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import com.google.ical.values.RRule;
import com.todoroo.andlib.data.Property; import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.Property.IntegerProperty; import com.todoroo.andlib.data.Property.IntegerProperty;
import com.todoroo.andlib.data.Property.LongProperty; import com.todoroo.andlib.data.Property.LongProperty;
@ -427,6 +428,10 @@ public class Task extends RemoteModel {
setValue(RECURRENCE, recurrence); setValue(RECURRENCE, recurrence);
} }
public void setRecurrence(RRule rrule, boolean afterCompletion) {
setRecurrence(rrule.toIcal() + (afterCompletion ? ";FROM=COMPLETION" : ""));
}
public Long getCreationDate() { public Long getCreationDate() {
return getValue(CREATION_DATE); return getValue(CREATION_DATE);
} }

@ -36,7 +36,6 @@ import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.ForActivity; import org.tasks.injection.ForActivity;
import org.tasks.injection.FragmentComponent; import org.tasks.injection.FragmentComponent;
import org.tasks.locale.Locale; import org.tasks.locale.Locale;
import org.tasks.preferences.Preferences;
import org.tasks.repeats.CustomRecurrenceDialog; import org.tasks.repeats.CustomRecurrenceDialog;
import org.tasks.themes.Theme; import org.tasks.themes.Theme;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
@ -96,7 +95,6 @@ public class RepeatControlSet extends TaskEditControlFragment
public static final int TYPE_COMPLETION_DATE = 2; public static final int TYPE_COMPLETION_DATE = 2;
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject Preferences preferences;
@Inject @ForActivity Context context; @Inject @ForActivity Context context;
@Inject Theme theme; @Inject Theme theme;
@Inject Locale locale; @Inject Locale locale;
@ -194,7 +192,8 @@ public class RepeatControlSet extends TaskEditControlFragment
frequency == HOURLY || frequency == HOURLY ||
frequency == MINUTELY || frequency == MINUTELY ||
rrule.getUntil() != null || rrule.getUntil() != null ||
rrule.getInterval() != 1; rrule.getInterval() != 1 ||
rrule.getCount() != 0;
} }
@OnClick(R.id.display_row_edit) @OnClick(R.id.display_row_edit)
@ -344,15 +343,21 @@ public class RepeatControlSet extends TaskEditControlFragment
int interval = rrule.getInterval(); int interval = rrule.getInterval();
Frequency frequency = rrule.getFreq(); Frequency frequency = rrule.getFreq();
DateTime repeatUntil = rrule.getUntil() == null ? null : DateTime.from(rrule.getUntil()); DateTime repeatUntil = rrule.getUntil() == null ? null : DateTime.from(rrule.getUntil());
int count = rrule.getCount();
String countString = count > 0 ? getContext().getResources().getQuantityString(R.plurals.repeat_times, count) : "";
if (interval == 1) { if (interval == 1) {
String frequencyString = getString(getSingleFrequencyResource(frequency)); String frequencyString = getString(getSingleFrequencyResource(frequency));
if (frequency == WEEKLY && !rrule.getByDay().isEmpty()) { if (frequency == WEEKLY && !rrule.getByDay().isEmpty()) {
String dayString = getDayString(); String dayString = getDayString();
if (repeatUntil == null) { if (count > 0) {
return getString(R.string.repeats_single_on_number_of_times, frequencyString, dayString, count, countString);
} else if (repeatUntil == null) {
return getString(R.string.repeats_single_on, frequencyString, dayString); return getString(R.string.repeats_single_on, frequencyString, dayString);
} else { } else {
return getString(R.string.repeats_single_on_until, frequencyString, dayString, DateUtilities.getLongDateString(repeatUntil)); return getString(R.string.repeats_single_on_until, frequencyString, dayString, DateUtilities.getLongDateString(repeatUntil));
} }
} else if (count > 0) {
return getString(R.string.repeats_single_number_of_times, frequencyString, count, countString);
} else if (repeatUntil == null) { } else if (repeatUntil == null) {
return getString(R.string.repeats_single, frequencyString); return getString(R.string.repeats_single, frequencyString);
} else { } else {
@ -363,11 +368,15 @@ public class RepeatControlSet extends TaskEditControlFragment
String frequencyPlural = getResources().getQuantityString(plural, interval, interval); String frequencyPlural = getResources().getQuantityString(plural, interval, interval);
if (frequency == WEEKLY && !rrule.getByDay().isEmpty()) { if (frequency == WEEKLY && !rrule.getByDay().isEmpty()) {
String dayString = getDayString(); String dayString = getDayString();
if (repeatUntil == null) { if (count > 0) {
return getString(R.string.repeats_plural_on_number_of_times, frequencyPlural, dayString, count, countString);
} else if (repeatUntil == null) {
return getString(R.string.repeats_plural_on, frequencyPlural, dayString); return getString(R.string.repeats_plural_on, frequencyPlural, dayString);
} else { } else {
return getString(R.string.repeats_plural_on_until, frequencyPlural, dayString, DateUtilities.getLongDateString(repeatUntil)); return getString(R.string.repeats_plural_on_until, frequencyPlural, dayString, DateUtilities.getLongDateString(repeatUntil));
} }
} else if (count > 0) {
return getString(R.string.repeats_plural_number_of_times, frequencyPlural, count, countString);
} else if (repeatUntil == null) { } else if (repeatUntil == null) {
return getString(R.string.repeats_plural, frequencyPlural); return getString(R.string.repeats_plural, frequencyPlural);
} else { } else {

@ -61,7 +61,9 @@ public class RepeatTaskHelper {
if(recurrence != null && recurrence.length() > 0) { if(recurrence != null && recurrence.length() > 0) {
long newDueDate; long newDueDate;
RRule rrule;
try { try {
rrule = initRRule(task.getRecurrenceWithoutFrom());
newDueDate = computeNextDueDate(task, recurrence, repeatAfterCompletion); newDueDate = computeNextDueDate(task, recurrence, repeatAfterCompletion);
if(newDueDate == -1) { if(newDueDate == -1) {
return; return;
@ -78,6 +80,12 @@ public class RepeatTaskHelper {
return; return;
} }
int count = rrule.getCount();
if (count > 1) {
rrule.setCount(count - 1);
task.setRecurrence(rrule, repeatAfterCompletion);
}
rescheduleTask(task, newDueDate); rescheduleTask(task, newDueDate);
rescheduleAlarms(task.getId(), oldDueDate, newDueDate); rescheduleAlarms(task.getId(), oldDueDate, newDueDate);

@ -5,7 +5,7 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import com.todoroo.andlib.data.Property; import com.google.ical.values.RRule;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.activity.TaskListActivity; import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.activity.TaskListFragment; import com.todoroo.astrid.activity.TaskListFragment;
@ -16,21 +16,14 @@ import com.todoroo.astrid.data.Task;
import org.tasks.R; import org.tasks.R;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
import java.text.ParseException;
import javax.inject.Inject; import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
public class RepeatConfirmationReceiver extends BroadcastReceiver { public class RepeatConfirmationReceiver extends BroadcastReceiver {
private final Property<?>[] REPEAT_RESCHEDULED_PROPERTIES =
new Property<?>[]{
Task.ID,
Task.TITLE,
Task.DUE_DATE,
Task.HIDE_UNTIL,
Task.REPEAT_UNTIL
};
private final Activity activity; private final Activity activity;
private final Tracker tracker; private final Tracker tracker;
private final TaskDao taskDao; private final TaskDao taskDao;
@ -57,7 +50,7 @@ public class RepeatConfirmationReceiver extends BroadcastReceiver {
if (taskId > 0) { if (taskId > 0) {
long oldDueDate = intent.getLongExtra(AstridApiConstants.EXTRAS_OLD_DUE_DATE, 0); long oldDueDate = intent.getLongExtra(AstridApiConstants.EXTRAS_OLD_DUE_DATE, 0);
long newDueDate = intent.getLongExtra(AstridApiConstants.EXTRAS_NEW_DUE_DATE, 0); long newDueDate = intent.getLongExtra(AstridApiConstants.EXTRAS_NEW_DUE_DATE, 0);
Task task = taskDao.fetch(taskId, REPEAT_RESCHEDULED_PROPERTIES); Task task = taskDao.fetch(taskId);
try { try {
showSnackbar(taskListFragment, task, oldDueDate, newDueDate); showSnackbar(taskListFragment, task, oldDueDate, newDueDate);
@ -74,6 +67,16 @@ public class RepeatConfirmationReceiver extends BroadcastReceiver {
.setAction(R.string.DLG_undo, v -> { .setAction(R.string.DLG_undo, v -> {
task.setDueDateAdjustingHideUntil(oldDueDate); task.setDueDateAdjustingHideUntil(oldDueDate);
task.setCompletionDate(0L); task.setCompletionDate(0L);
try {
RRule rrule = new RRule(task.getRecurrenceWithoutFrom());
int count = rrule.getCount();
if (count > 0) {
rrule.setCount(count + 1);
}
task.setRecurrence(rrule, task.repeatAfterCompletion());
} catch (ParseException e) {
Timber.e(e, e.getMessage());
}
taskDao.save(task); taskDao.save(task);
}) })
.show(); .show();

@ -11,6 +11,8 @@ import android.support.annotation.Nullable;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.LinearLayout;
@ -39,7 +41,6 @@ import org.tasks.themes.ThemeAccent;
import org.tasks.time.DateTime; import org.tasks.time.DateTime;
import java.text.DateFormatSymbols; import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
@ -51,7 +52,6 @@ import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import butterknife.OnItemSelected; import butterknife.OnItemSelected;
import butterknife.OnTextChanged; import butterknife.OnTextChanged;
import timber.log.Timber;
import static android.support.v4.content.ContextCompat.getColor; import static android.support.v4.content.ContextCompat.getColor;
import static com.google.ical.values.Frequency.DAILY; import static com.google.ical.values.Frequency.DAILY;
@ -103,8 +103,10 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
@BindView(R.id.repeat_until) Spinner repeatUntilSpinner; @BindView(R.id.repeat_until) Spinner repeatUntilSpinner;
@BindView(R.id.frequency) Spinner frequencySpinner; @BindView(R.id.frequency) Spinner frequencySpinner;
@BindView(R.id.repeatValue) EditText intervalEditText; @BindView(R.id.intervalValue) EditText intervalEditText;
@BindView(R.id.intervalText) TextView intervalTextView; @BindView(R.id.intervalText) TextView intervalTextView;
@BindView(R.id.repeatTimesValue) EditText repeatTimes;
@BindView(R.id.repeatTimesText) TextView repeatTimesText;
private ArrayAdapter<String> repeatUntilAdapter; private ArrayAdapter<String> repeatUntilAdapter;
private final List<String> repeatUntilOptions = new ArrayList<>(); private final List<String> repeatUntilOptions = new ArrayList<>();
@ -140,7 +142,27 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
intervalEditText.setText(locale.formatNumber(rrule.getInterval())); intervalEditText.setText(locale.formatNumber(rrule.getInterval()));
repeatUntilAdapter = new ArrayAdapter<>(context, R.layout.simple_spinner_item, repeatUntilOptions); repeatUntilAdapter = new ArrayAdapter<String>(context, R.layout.simple_spinner_item, 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); repeatUntilAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
repeatUntilSpinner.setAdapter(repeatUntilAdapter); repeatUntilSpinner.setAdapter(repeatUntilAdapter);
updateRepeatUntilOptions(); updateRepeatUntilOptions();
@ -220,6 +242,18 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
intervalTextView.setText(quantityString); intervalTextView.setText(quantityString);
} }
private void setCount(int count, boolean updateEditText) {
rrule.setCount(count);
if (updateEditText) {
intervalEditText.setText(locale.formatNumber(count));
}
updateCountText();
}
private void updateCountText() {
repeatTimesText.setText(getResources().getQuantityString(R.plurals.repeat_times, rrule.getCount()));
}
private int getFrequencyPlural() { private int getFrequencyPlural() {
switch (rrule.getFreq()) { switch (rrule.getFreq()) {
case MINUTELY: case MINUTELY:
@ -241,18 +275,19 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
@OnItemSelected(R.id.repeat_until) @OnItemSelected(R.id.repeat_until)
public void onRepeatUntilChanged(int position) { public void onRepeatUntilChanged(int position) {
if (repeatUntilOptions.size() == 2) { if (repeatUntilOptions.size() == 4) {
position--;
}
if (position == 0) { if (position == 0) {
setRepeatUntilValue(0); rrule.setUntil(null);
} else { rrule.setCount(0);
updateRepeatUntilOptions();
} else if (position == 1) {
repeatUntilClick(); repeatUntilClick();
}
} else {
if (position == 1) {
setRepeatUntilValue(0);
} else if (position == 2) { } else if (position == 2) {
repeatUntilClick(); rrule.setUntil(null);
} rrule.setCount(Math.max(rrule.getCount(), 1));
updateRepeatUntilOptions();
} }
} }
@ -261,7 +296,7 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
setFrequency(FREQUENCIES.get(position)); setFrequency(FREQUENCIES.get(position));
} }
@OnTextChanged(R.id.repeatValue) @OnTextChanged(R.id.intervalValue)
public void onRepeatValueChanged(CharSequence text) { public void onRepeatValueChanged(CharSequence text) {
Integer value = locale.parseInteger(text.toString()); Integer value = locale.parseInteger(text.toString());
if (value == null) { if (value == null) {
@ -274,9 +309,17 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
} }
} }
private void setRepeatUntilValue(long newValue) { @OnTextChanged(R.id.repeatTimesValue)
rrule.setUntil(new DateTime(newValue).toDateValue()); public void onRepeatTimesValueChanged(CharSequence text) {
updateRepeatUntilOptions(); Integer value = locale.parseInteger(text.toString());
if (value == null) {
return;
}
if (value < 1) {
setCount(1, true);
} else {
setCount(value, false);
}
} }
private void repeatUntilClick() { private void repeatUntilClick() {
@ -289,11 +332,24 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
private void updateRepeatUntilOptions() { private void updateRepeatUntilOptions() {
repeatUntilOptions.clear(); repeatUntilOptions.clear();
long repeatUntil = DateTime.from(rrule.getUntil()).getMillis(); long repeatUntil = DateTime.from(rrule.getUntil()).getMillis();
int count = rrule.getCount();
if (repeatUntil > 0) { if (repeatUntil > 0) {
repeatUntilOptions.add(getString(R.string.repeat_until, getDisplayString(context, repeatUntil))); repeatUntilOptions.add(getString(R.string.repeat_until, getDisplayString(context, repeatUntil)));
repeatTimes.setVisibility(View.GONE);
repeatTimesText.setVisibility(View.GONE);
} else if (count > 0) {
repeatUntilOptions.add(getString(R.string.repeat_occurs));
repeatTimes.setText(locale.formatNumber(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_forever));
repeatUntilOptions.add(getString(R.string.repeat_until, "").trim()); repeatUntilOptions.add(getString(R.string.repeat_until, "").trim());
repeatUntilOptions.add(getString(R.string.repeat_number_of_times));
repeatUntilAdapter.notifyDataSetChanged(); repeatUntilAdapter.notifyDataSetChanged();
repeatUntilSpinner.setSelection(0); repeatUntilSpinner.setSelection(0);
} }
@ -302,10 +358,10 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_PICK_DATE) { if (requestCode == REQUEST_PICK_DATE) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
setRepeatUntilValue(data.getLongExtra(DatePickerActivity.EXTRA_TIMESTAMP, 0L)); rrule.setUntil(new DateTime(data.getLongExtra(DatePickerActivity.EXTRA_TIMESTAMP, 0L)).toDateValue());
} else { rrule.setCount(0);
setRepeatUntilValue(DateTime.from(rrule.getUntil()).getMillis());
} }
updateRepeatUntilOptions();
} }
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
} }

@ -10,7 +10,6 @@
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
android:id="@+id/repeatTypeContainer"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:orientation="vertical"> android:orientation="vertical">
@ -54,7 +53,7 @@
android:textAppearance="@style/TextAppearance" /> android:textAppearance="@style/TextAppearance" />
<EditText <EditText
android:id="@+id/repeatValue" android:id="@+id/intervalValue"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ems="3" android:ems="3"
@ -84,17 +83,53 @@
<include layout="@layout/week_buttons" /> <include layout="@layout/week_buttons" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<Spinner <Spinner
android:id="@+id/repeat_until" android:id="@+id/repeat_until"
style="Widget.AppCompat.Spinner" style="Widget.AppCompat.Spinner"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_gravity="center"
android:layout_marginLeft="@dimen/keyline_first"
android:layout_marginStart="@dimen/keyline_first"
android:layout_marginRight="0dp"
android:layout_marginEnd="0dp"
android:textColor="?attr/asTextColor" />
<EditText
android:id="@+id/repeatTimesValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="3"
android:focusable="true"
android:imeOptions="flagNoExtractUi"
android:focusableInTouchMode="true"
android:gravity="center"
android:inputType="number"
android:maxLength="3"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:selectAllOnFocus="true"
android:singleLine="true"
android:textSize="15sp" />
<TextView
android:id="@+id/repeatTimesText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingEnd="@dimen/keyline_first" android:paddingEnd="@dimen/keyline_first"
android:paddingLeft="@dimen/keyline_first" android:paddingLeft="0dp"
android:paddingRight="@dimen/keyline_first" android:paddingRight="@dimen/keyline_first"
android:paddingStart="@dimen/keyline_first" android:paddingStart="0dp"
android:textColor="?attr/asTextColor" /> android:textAppearance="@style/TextAppearance" />
</LinearLayout>
</LinearLayout> </LinearLayout>

@ -520,6 +520,10 @@ File %1$s contained %2$s.\n\n
<string name="repeat_monthly">REPEAT MONTHLY</string> <string name="repeat_monthly">REPEAT MONTHLY</string>
<string name="repeat_yearly">REPEAT YEARLY</string> <string name="repeat_yearly">REPEAT YEARLY</string>
<plurals name="repeat_times">
<item quantity="one">time</item>
<item quantity="other">times</item>
</plurals>
<plurals name="repeat_minutes"> <plurals name="repeat_minutes">
<item quantity="one">minute</item> <item quantity="one">minute</item>
<item quantity="other">minutes</item> <item quantity="other">minutes</item>
@ -582,6 +586,8 @@ File %1$s contained %2$s.\n\n
<!-- text for button when repeating task until specified date (%s -> date string) --> <!-- text for button when repeating task until specified date (%s -> date string) -->
<string name="repeat_until">Repeat until %s</string> <string name="repeat_until">Repeat until %s</string>
<string name="repeat_number_of_times">Occurs a number of times</string>
<string name="repeat_occurs">Occurs</string>
<string name="repeat_snackbar">%1$s rescheduled for %2$s</string> <string name="repeat_snackbar">%1$s rescheduled for %2$s</string>
@ -835,7 +841,9 @@ File %1$s contained %2$s.\n\n
<string name="repeats_single">Repeats %s</string> <string name="repeats_single">Repeats %s</string>
<string name="repeats_single_on">Repeats %s on %s</string> <string name="repeats_single_on">Repeats %s on %s</string>
<string name="repeats_single_until">Repeats %s until %s</string> <string name="repeats_single_until">Repeats %s until %s</string>
<string name="repeats_single_number_of_times">Repeats %s, occurs %d %s</string>
<string name="repeats_single_on_until">Repeats %s on %s until %s</string> <string name="repeats_single_on_until">Repeats %s on %s until %s</string>
<string name="repeats_single_on_number_of_times">Repeats %s on %s, occurs %d %s</string>
<string name="repeats_minutely">minutely</string> <string name="repeats_minutely">minutely</string>
<string name="repeats_hourly">hourly</string> <string name="repeats_hourly">hourly</string>
<string name="repeats_daily">daily</string> <string name="repeats_daily">daily</string>
@ -843,9 +851,11 @@ File %1$s contained %2$s.\n\n
<string name="repeats_monthly">monthly</string> <string name="repeats_monthly">monthly</string>
<string name="repeats_yearly">yearly</string> <string name="repeats_yearly">yearly</string>
<string name="repeats_plural">Repeats every %s</string> <string name="repeats_plural">Repeats every %s</string>
<string name="repeats_plural_until">Repeats every %s until %s</string>
<string name="repeats_plural_on">Repeats every %s on %s</string> <string name="repeats_plural_on">Repeats every %s on %s</string>
<string name="repeats_plural_until">Repeats every %s until %s</string>
<string name="repeats_plural_number_of_times">Repeats every %s, occurs %d %s</string>
<string name="repeats_plural_on_until">Repeats every %s on %s until %s</string> <string name="repeats_plural_on_until">Repeats every %s on %s until %s</string>
<string name="repeats_plural_on_number_of_times">Repeats every %s on %s, occurs %d %s</string>
<string name="list_separator_with_space">,\u0020</string> <string name="list_separator_with_space">,\u0020</string>
<string name="dont_add_to_calendar">Don\'t add to calendar</string> <string name="dont_add_to_calendar">Don\'t add to calendar</string>
<string name="default_calendar">Default calendar</string> <string name="default_calendar">Default calendar</string>

Loading…
Cancel
Save