Fix weekly repeat bugs

pull/618/head
Alex Baker 8 years ago
parent e839d0b870
commit 8ea915a9f5

@ -39,13 +39,6 @@ import static org.tasks.makers.TaskMaker.newTask;
@RunWith(AndroidJUnit4.class)
public class RepeatTaskHelperTest extends DatabaseTestCase {
private final Task task = new Task();
{
task.setDueDate(new DateTime(2013, 12, 31, 17, 17, 32, 900).getMillis());
task.setCompletionDate(new DateTime(2014, 1, 7, 17, 17, 32, 900).getMillis());
}
@Inject TaskDao taskDao;
private LocalBroadcastManager localBroadcastManager;

@ -0,0 +1,57 @@
package org.tasks.repeats;
import android.support.test.runner.AndroidJUnit4;
import com.google.ical.values.RRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.locale.Locale;
import java.text.ParseException;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static junit.framework.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
public class RepeatRuleToStringTest {
@Test
public void weekly() {
assertEquals("Repeats weekly", toString("RRULE:FREQ=WEEKLY;INTERVAL=1"));
}
@Test
public void weeklyPlural() {
assertEquals("Repeats every 2 weeks", toString("RRULE:FREQ=WEEKLY;INTERVAL=2"));
}
@Test
public void weeklyByDay() {
assertEquals("Repeats weekly on Mon, Tue, Wed, Thu, Fri", toString("RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR"));
}
@Test
public void printDaysInRepeatRuleOrder() {
assertEquals("Repeats weekly on Fri, Thu, Wed, Tue, Mon", toString("RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=FR,TH,WE,TU,MO"));
}
@Test
public void useLocaleForDays() {
assertEquals("Wiederhole wöchentlich am Sa., So.", toString("de", "RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SA,SU"));
}
private String toString(String rrule) {
return toString(null, rrule);
}
private String toString(String language, String rrule) {
try {
Locale locale = new Locale(java.util.Locale.getDefault(), language, -1);
return new RepeatRuleToString(locale.createConfigurationContext(getTargetContext()), locale)
.toString(new RRule(rrule));
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}

@ -82,7 +82,7 @@ public class WeekButton extends ToggleButton {
// Reset text color for animation
// The correct state color will be
// set when animation is done or cancelled
setTextColor(isChecked() ? mCheckedTextColor : mDefaultTextColor);
setTextColor(mCheckedTextColor);
mDrawable.setCheckedOnClick(isChecked(), mCallback);
}
}

@ -22,13 +22,9 @@ import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.ical.values.Frequency;
import com.google.ical.values.RRule;
import com.google.ical.values.Weekday;
import com.google.ical.values.WeekdayNum;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.data.Task;
import org.tasks.R;
@ -39,13 +35,13 @@ import org.tasks.injection.ForActivity;
import org.tasks.injection.FragmentComponent;
import org.tasks.locale.Locale;
import org.tasks.repeats.CustomRecurrenceDialog;
import org.tasks.repeats.RepeatRuleToString;
import org.tasks.themes.Theme;
import org.tasks.time.DateTime;
import org.tasks.ui.HiddenTopArrayAdapter;
import org.tasks.ui.SingleCheckedArrayAdapter;
import org.tasks.ui.TaskEditControlFragment;
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
@ -77,7 +73,6 @@ public class RepeatControlSet extends TaskEditControlFragment
implements CustomRecurrenceDialog.CustomRecurrenceCallback {
public static final int TAG = R.string.TEA_ctrl_repeat_pref;
public static final List<Weekday> WEEKDAYS = Arrays.asList(Weekday.values());
private static final String FRAG_TAG_CUSTOM_RECURRENCE = "frag_tag_custom_recurrence";
@Override
@ -100,15 +95,15 @@ public class RepeatControlSet extends TaskEditControlFragment
@Inject DialogBuilder dialogBuilder;
@Inject @ForActivity Context context;
@Inject Theme theme;
@Inject Locale locale;
@Inject Tracker tracker;
@Inject RepeatRuleToString repeatRuleToString;
@BindView(R.id.display_row_edit) TextView displayView;
@BindView(R.id.repeatType) Spinner typeSpinner;
@BindView(R.id.repeatTypeContainer) LinearLayout repeatTypeContainer;
private RRule rrule;
private final List<String> repeatTypes = new ArrayList<>();
private RRule rrule;
private HiddenTopArrayAdapter<String> typeAdapter;
private RepeatChangedListener callback;
@ -206,7 +201,7 @@ public class RepeatControlSet extends TaskEditControlFragment
List<String> repeatOptions = newArrayList(context.getResources().getStringArray(R.array.repeat_options));
SingleCheckedArrayAdapter adapter = new SingleCheckedArrayAdapter(context, repeatOptions, theme.getThemeAccent());
if (customPicked) {
adapter.insert(getRepeatString(), 0);
adapter.insert(repeatRuleToString.toString(rrule), 0);
adapter.setChecked(0);
} else if (rrule == null) {
adapter.setChecked(0);
@ -339,103 +334,9 @@ public class RepeatControlSet extends TaskEditControlFragment
displayView.setTextColor(getColor(context, R.color.text_tertiary));
repeatTypeContainer.setVisibility(View.GONE);
} else {
displayView.setText(getRepeatString());
displayView.setText(repeatRuleToString.toString(rrule));
displayView.setTextColor(getColor(context, R.color.text_primary));
repeatTypeContainer.setVisibility(View.VISIBLE);
}
}
private String getRepeatString() {
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 (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 {
return getString(R.string.repeats_single_until, frequencyString, DateUtilities.getLongDateString(repeatUntil));
}
} else {
int plural = getFrequencyPlural(frequency);
String frequencyPlural = getResources().getQuantityString(plural, interval, interval);
if (frequency == WEEKLY && !rrule.getByDay().isEmpty()) {
String dayString = getDayString();
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 {
return getString(R.string.repeats_plural_until, frequencyPlural, DateUtilities.getLongDateString(repeatUntil));
}
}
}
private String getDayString() {
DateFormatSymbols dfs = new DateFormatSymbols(locale.getLocale());
String[] shortWeekdays = dfs.getShortWeekdays();
List<String> days = new ArrayList<>();
for (WeekdayNum weekday : rrule.getByDay()) {
days.add(shortWeekdays[WEEKDAYS.indexOf(weekday.wday) + 1]);
}
return Joiner.on(getString(R.string.list_separator_with_space)).join(days);
}
private int getSingleFrequencyResource(Frequency frequency) {
switch (frequency) {
case MINUTELY:
return R.string.repeats_minutely;
case HOURLY:
return R.string.repeats_hourly;
case DAILY:
return R.string.repeats_daily;
case WEEKLY:
return R.string.repeats_weekly;
case MONTHLY:
return R.string.repeats_monthly;
case YEARLY:
return R.string.repeats_yearly;
default:
throw new RuntimeException("Invalid frequency: " + frequency);
}
}
private int getFrequencyPlural(Frequency frequency) {
switch (frequency) {
case MINUTELY:
return R.plurals.repeat_n_minutes;
case HOURLY:
return R.plurals.repeat_n_hours;
case DAILY:
return R.plurals.repeat_n_days;
case WEEKLY:
return R.plurals.repeat_n_weeks;
case MONTHLY:
return R.plurals.repeat_n_months;
case YEARLY:
return R.plurals.repeat_n_years;
default:
throw new RuntimeException("Invalid frequency: " + frequency);
}
}
}

@ -76,7 +76,7 @@ public class Locale {
private final int directionOverride;
private final boolean hasUserOverrides;
private Locale(java.util.Locale deviceLocale, String languageOverride, int directionOverride) {
public Locale(java.util.Locale deviceLocale, String languageOverride, int directionOverride) {
this.deviceLocale = deviceLocale;
this.languageOverride = languageOverride;
this.directionOverride = directionOverride;

@ -24,6 +24,7 @@ import com.appeaser.sublimepickerlibrary.recurrencepicker.WeekButton;
import com.google.common.base.Strings;
import com.google.ical.values.Frequency;
import com.google.ical.values.RRule;
import com.google.ical.values.Weekday;
import com.google.ical.values.WeekdayNum;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.data.Task;
@ -52,6 +53,7 @@ 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;
@ -60,7 +62,6 @@ import static com.google.ical.values.Frequency.MINUTELY;
import static com.google.ical.values.Frequency.MONTHLY;
import static com.google.ical.values.Frequency.WEEKLY;
import static com.google.ical.values.Frequency.YEARLY;
import static com.todoroo.astrid.repeats.RepeatControlSet.WEEKDAYS;
import static java.util.Arrays.asList;
import static org.tasks.date.DateTimeUtils.newDateTime;
@ -108,8 +109,9 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
@BindView(R.id.repeatTimesValue) EditText repeatTimes;
@BindView(R.id.repeatTimesText) TextView repeatTimesText;
private ArrayAdapter<String> repeatUntilAdapter;
private final List<String> repeatUntilOptions = new ArrayList<>();
private ArrayAdapter<String> repeatUntilAdapter;
private WeekButton[] weekButtons;
private RRule rrule;
@ -119,13 +121,15 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
LayoutInflater inflater = LayoutInflater.from(getActivity());
View dialogView = inflater.inflate(R.layout.control_set_repeat, null);
Bundle arguments = getArguments();
String rule = arguments.getString(EXTRA_RRULE);
if (!Strings.isNullOrEmpty(rule)) {
try {
String rule = savedInstanceState == null
? getArguments().getString(EXTRA_RRULE)
: savedInstanceState.getString(EXTRA_RRULE);
try {
if (!Strings.isNullOrEmpty(rule)) {
rrule = new RRule(rule);
} catch (Exception ignored) {
}
} catch (Exception e) {
Timber.e(e, e.getMessage());
}
if (rrule == null) {
rrule = new RRule();
@ -167,7 +171,7 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
repeatUntilSpinner.setAdapter(repeatUntilAdapter);
updateRepeatUntilOptions();
WeekButton[] weekButtons = new WeekButton[] { day1, day2, day3, day4, day5, day6, day7 };
weekButtons = new WeekButton[] { day1, day2, day3, day4, day5, day6, day7 };
int expandedWidthHeight = getResources()
.getDimensionPixelSize(R.dimen.week_button_state_on_circle_size);
@ -178,37 +182,31 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
// set up days of week
ThemeAccent accent = theme.getThemeAccent();
DateFormatSymbols dfs = new DateFormatSymbols(locale.getLocale());
Calendar calendar = Calendar.getInstance();
Calendar calendar = Calendar.getInstance(locale.getLocale());
calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());
String[] shortWeekdays = dfs.getShortWeekdays();
for(int i = 0; i < 7; i++) {
String text = shortWeekdays[calendar.get(Calendar.DAY_OF_WEEK)];
WeekdayNum weekdayNum = new WeekdayNum(0, WEEKDAYS.get(i));
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
String text = shortWeekdays[dayOfWeek];
WeekdayNum weekdayNum = new WeekdayNum(0, calendarDayToWeekday(dayOfWeek));
WeekButton weekButton = weekButtons[i];
weekButton.setTag(weekdayNum);
weekButton.setBackgroundDrawable(new CheckableDrawable(accent.getAccentColor(), false, expandedWidthHeight));
weekButton.setTextColor(weekButtonUnselectedTextColor);
weekButton.setTextOff(text);
weekButton.setTextOn(text);
weekButton.setText(text);
if (rrule.getByDay().contains(weekdayNum)) {
weekButton.setChecked(true);
}
weekButton.setOnCheckedChangeListener((compoundButton, b) -> {
List<WeekdayNum> days = rrule.getByDay();
if (b) {
days.add(weekdayNum);
} else {
days.remove(weekdayNum);
}
});
weekButton.setCheckedNoAnimate(rrule.getByDay().contains(weekdayNum));
calendar.add(Calendar.DATE, 1);
}
return dialogBuilder.newDialog()
.setView(dialogView)
.setPositiveButton(android.R.string.ok, (dialog12, which) -> {
if (rrule.getFreq() != WEEKLY) {
if (rrule.getFreq() == WEEKLY) {
setByDays();
} else {
rrule.setByDay(Collections.emptyList());
}
((CustomRecurrenceCallback) getTargetFragment()).onSelected(rrule);
@ -218,6 +216,53 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
.show();
}
private void setByDays() {
List<WeekdayNum> checked = new ArrayList<>();
for (WeekButton button : weekButtons) {
if (button.isChecked()) {
checked.add((WeekdayNum) button.getTag());
}
}
rrule.setByDay(checked);
}
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 onResume() {
super.onResume();
for (WeekButton button : weekButtons) {
button.setCheckedNoAnimate(button.isChecked());
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
setByDays();
outState.putString(EXTRA_RRULE, rrule.toIcal());
}
private void setFrequency(Frequency frequency) {
rrule.setFreq(frequency);
int weekVisibility = frequency == WEEKLY ? View.VISIBLE : View.GONE;

@ -0,0 +1,131 @@
package org.tasks.repeats;
import android.content.Context;
import com.google.common.base.Joiner;
import com.google.ical.values.Frequency;
import com.google.ical.values.RRule;
import com.google.ical.values.Weekday;
import com.google.ical.values.WeekdayNum;
import com.todoroo.andlib.utility.DateUtilities;
import org.tasks.R;
import org.tasks.injection.ForApplication;
import org.tasks.locale.Locale;
import org.tasks.time.DateTime;
import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import static com.google.ical.values.Frequency.WEEKLY;
public class RepeatRuleToString {
private final Context context;
private final Locale locale;
private final List<Weekday> weekdays = Arrays.asList(Weekday.values());
@Inject
public RepeatRuleToString(@ForApplication Context context, Locale locale) {
this.context = context;
this.locale = locale;
}
public String toString(RRule rrule) {
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 ? context.getResources().getQuantityString(R.plurals.repeat_times, count) : "";
if (interval == 1) {
String frequencyString = context.getString(getSingleFrequencyResource(frequency));
if (frequency == WEEKLY && !rrule.getByDay().isEmpty()) {
String dayString = getDayString(rrule);
if (count > 0) {
return context.getString(R.string.repeats_single_on_number_of_times, frequencyString, dayString, count, countString);
} else if (repeatUntil == null) {
return context.getString(R.string.repeats_single_on, frequencyString, dayString);
} else {
return context.getString(R.string.repeats_single_on_until, frequencyString, dayString, DateUtilities.getLongDateString(repeatUntil));
}
} else if (count > 0) {
return context.getString(R.string.repeats_single_number_of_times, frequencyString, count, countString);
} else if (repeatUntil == null) {
return context.getString(R.string.repeats_single, frequencyString);
} else {
return context.getString(R.string.repeats_single_until, frequencyString, DateUtilities.getLongDateString(repeatUntil));
}
} else {
int plural = getFrequencyPlural(frequency);
String frequencyPlural = context.getResources().getQuantityString(plural, interval, interval);
if (frequency == WEEKLY && !rrule.getByDay().isEmpty()) {
String dayString = getDayString(rrule);
if (count > 0) {
return context.getString(R.string.repeats_plural_on_number_of_times, frequencyPlural, dayString, count, countString);
} else if (repeatUntil == null) {
return context.getString(R.string.repeats_plural_on, frequencyPlural, dayString);
} else {
return context.getString(R.string.repeats_plural_on_until, frequencyPlural, dayString, DateUtilities.getLongDateString(repeatUntil));
}
} else if (count > 0) {
return context.getString(R.string.repeats_plural_number_of_times, frequencyPlural, count, countString);
} else if (repeatUntil == null) {
return context.getString(R.string.repeats_plural, frequencyPlural);
} else {
return context.getString(R.string.repeats_plural_until, frequencyPlural, DateUtilities.getLongDateString(repeatUntil));
}
}
}
private String getDayString(RRule rrule) {
DateFormatSymbols dfs = new DateFormatSymbols(locale.getLocale());
String[] shortWeekdays = dfs.getShortWeekdays();
List<String> days = new ArrayList<>();
for (WeekdayNum weekday : rrule.getByDay()) {
days.add(shortWeekdays[weekdays.indexOf(weekday.wday) + 1]);
}
return Joiner.on(context.getString(R.string.list_separator_with_space)).join(days);
}
private int getSingleFrequencyResource(Frequency frequency) {
switch (frequency) {
case MINUTELY:
return R.string.repeats_minutely;
case HOURLY:
return R.string.repeats_hourly;
case DAILY:
return R.string.repeats_daily;
case WEEKLY:
return R.string.repeats_weekly;
case MONTHLY:
return R.string.repeats_monthly;
case YEARLY:
return R.string.repeats_yearly;
default:
throw new RuntimeException("Invalid frequency: " + frequency);
}
}
private int getFrequencyPlural(Frequency frequency) {
switch (frequency) {
case MINUTELY:
return R.plurals.repeat_n_minutes;
case HOURLY:
return R.plurals.repeat_n_hours;
case DAILY:
return R.plurals.repeat_n_days;
case WEEKLY:
return R.plurals.repeat_n_weeks;
case MONTHLY:
return R.plurals.repeat_n_months;
case YEARLY:
return R.plurals.repeat_n_years;
default:
throw new RuntimeException("Invalid frequency: " + frequency);
}
}
}
Loading…
Cancel
Save