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

@ -36,7 +36,6 @@ import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.ForActivity;
import org.tasks.injection.FragmentComponent;
import org.tasks.locale.Locale;
import org.tasks.preferences.Preferences;
import org.tasks.repeats.CustomRecurrenceDialog;
import org.tasks.themes.Theme;
import org.tasks.time.DateTime;
@ -96,7 +95,6 @@ public class RepeatControlSet extends TaskEditControlFragment
public static final int TYPE_COMPLETION_DATE = 2;
@Inject DialogBuilder dialogBuilder;
@Inject Preferences preferences;
@Inject @ForActivity Context context;
@Inject Theme theme;
@Inject Locale locale;
@ -194,7 +192,8 @@ public class RepeatControlSet extends TaskEditControlFragment
frequency == HOURLY ||
frequency == MINUTELY ||
rrule.getUntil() != null ||
rrule.getInterval() != 1;
rrule.getInterval() != 1 ||
rrule.getCount() != 0;
}
@OnClick(R.id.display_row_edit)
@ -344,15 +343,21 @@ public class RepeatControlSet extends TaskEditControlFragment
int interval = rrule.getInterval();
Frequency frequency = rrule.getFreq();
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) {
String frequencyString = getString(getSingleFrequencyResource(frequency));
if (frequency == WEEKLY && !rrule.getByDay().isEmpty()) {
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);
} else {
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) {
return getString(R.string.repeats_single, frequencyString);
} else {
@ -363,11 +368,15 @@ public class RepeatControlSet extends TaskEditControlFragment
String frequencyPlural = getResources().getQuantityString(plural, interval, interval);
if (frequency == WEEKLY && !rrule.getByDay().isEmpty()) {
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);
} else {
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) {
return getString(R.string.repeats_plural, frequencyPlural);
} else {

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

@ -5,7 +5,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
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.astrid.activity.TaskListActivity;
import com.todoroo.astrid.activity.TaskListFragment;
@ -16,21 +16,14 @@ import com.todoroo.astrid.data.Task;
import org.tasks.R;
import org.tasks.analytics.Tracker;
import java.text.ParseException;
import javax.inject.Inject;
import timber.log.Timber;
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 Tracker tracker;
private final TaskDao taskDao;
@ -57,7 +50,7 @@ public class RepeatConfirmationReceiver extends BroadcastReceiver {
if (taskId > 0) {
long oldDueDate = intent.getLongExtra(AstridApiConstants.EXTRAS_OLD_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 {
showSnackbar(taskListFragment, task, oldDueDate, newDueDate);
@ -74,6 +67,16 @@ public class RepeatConfirmationReceiver extends BroadcastReceiver {
.setAction(R.string.DLG_undo, v -> {
task.setDueDateAdjustingHideUntil(oldDueDate);
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);
})
.show();

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

@ -10,7 +10,6 @@
android:orientation="vertical">
<LinearLayout
android:id="@+id/repeatTypeContainer"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:orientation="vertical">
@ -54,7 +53,7 @@
android:textAppearance="@style/TextAppearance" />
<EditText
android:id="@+id/repeatValue"
android:id="@+id/intervalValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="3"
@ -84,17 +83,53 @@
<include layout="@layout/week_buttons" />
<Spinner
android:id="@+id/repeat_until"
style="Widget.AppCompat.Spinner"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingEnd="@dimen/keyline_first"
android:paddingLeft="@dimen/keyline_first"
android:paddingRight="@dimen/keyline_first"
android:paddingStart="@dimen/keyline_first"
android:textColor="?attr/asTextColor" />
android:orientation="horizontal">
<Spinner
android:id="@+id/repeat_until"
style="Widget.AppCompat.Spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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:paddingLeft="0dp"
android:paddingRight="@dimen/keyline_first"
android:paddingStart="0dp"
android:textAppearance="@style/TextAppearance" />
</LinearLayout>
</LinearLayout>

@ -520,6 +520,10 @@ File %1$s contained %2$s.\n\n
<string name="repeat_monthly">REPEAT MONTHLY</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">
<item quantity="one">minute</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) -->
<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>
@ -835,7 +841,9 @@ File %1$s contained %2$s.\n\n
<string name="repeats_single">Repeats %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_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_number_of_times">Repeats %s on %s, occurs %d %s</string>
<string name="repeats_minutely">minutely</string>
<string name="repeats_hourly">hourly</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_yearly">yearly</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_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_number_of_times">Repeats every %s on %s, occurs %d %s</string>
<string name="list_separator_with_space">,\u0020</string>
<string name="dont_add_to_calendar">Don\'t add to calendar</string>
<string name="default_calendar">Default calendar</string>

Loading…
Cancel
Save