From 952c6075569f06f19758c20871c8593515266cfa Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Wed, 18 Oct 2017 00:00:28 -0500 Subject: [PATCH] Add monthly repeat options --- .../astrid/activity/TaskEditFragment.java | 9 ++ .../astrid/activity/TaskListActivity.java | 7 + .../astrid/repeats/RepeatControlSet.java | 28 +++- .../astrid/repeats/RepeatTaskHelper.java | 4 +- .../tasks/repeats/CustomRecurrenceDialog.java | 153 +++++++++++++----- .../org/tasks/repeats/RepeatRuleToString.java | 63 +++++++- .../main/java/org/tasks/time/DateTime.java | 37 +++++ .../java/org/tasks/ui/DeadlineControlSet.java | 15 +- .../main/res/layout/control_set_repeat.xml | 28 ++++ app/src/main/res/values/strings.xml | 9 ++ 10 files changed, 300 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.java b/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.java index 48bf715fd..b57b6c23b 100755 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.java +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.java @@ -28,6 +28,7 @@ import com.todoroo.astrid.data.UserActivity; import com.todoroo.astrid.files.FilesControlSet; import com.todoroo.astrid.gtasks.GtasksList; import com.todoroo.astrid.notes.CommentsController; +import com.todoroo.astrid.repeats.RepeatControlSet; import com.todoroo.astrid.service.TaskDeleter; import com.todoroo.astrid.timers.TimerPlugin; import com.todoroo.astrid.ui.EditTitleControlSet; @@ -234,6 +235,10 @@ public final class TaskEditFragment extends InjectingFragment implements Toolbar return getFragment(GoogleTaskListFragment.TAG); } + private RepeatControlSet getRepeatControlSet() { + return getFragment(RepeatControlSet.TAG); + } + private FilesControlSet getFilesControlSet() { return getFragment(FilesControlSet.TAG); } @@ -320,6 +325,10 @@ public final class TaskEditFragment extends InjectingFragment implements Toolbar getGoogleTaskListFragment().setList(list); } + public void onDueDateChanged(long dueDate) { + getRepeatControlSet().onDueDateChanged(dueDate); + } + public void addComment(String message, String actionCode, String picture) { UserActivity userActivity = new UserActivity(); userActivity.setMessage(message); diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskListActivity.java b/app/src/main/java/com/todoroo/astrid/activity/TaskListActivity.java index 07ba45f16..1c6215b80 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskListActivity.java +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskListActivity.java @@ -58,6 +58,7 @@ import org.tasks.tasklist.TagListFragment; import org.tasks.themes.Theme; import org.tasks.themes.ThemeCache; import org.tasks.themes.ThemeColor; +import org.tasks.ui.DeadlineControlSet; import org.tasks.ui.EmptyTaskEditFragment; import org.tasks.ui.NavigationDrawerFragment; import org.tasks.ui.PriorityControlSet; @@ -79,6 +80,7 @@ public class TaskListActivity extends InjectingAppCompatActivity implements PriorityControlSet.OnPriorityChanged, TimerControlSet.TimerControlSetCallback, RepeatControlSet.RepeatChangedListener, + DeadlineControlSet.DueDateChangeListener, TaskEditFragment.TaskEditFragmentCallbackHandler, CommentBarFragment.CommentBarFragmentCallback, SortDialog.SortDialogCallback, @@ -533,4 +535,9 @@ public class TaskListActivity extends InjectingAppCompatActivity implements actionMode = null; } } + + @Override + public void dueDateChanged(long dateTime) { + getTaskEditFragment().onDueDateChanged(dateTime); + } } diff --git a/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.java b/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.java index 5f37ec899..c2843c11a 100644 --- a/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.java +++ b/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.java @@ -25,6 +25,7 @@ import android.widget.TextView; import com.google.common.base.Strings; import com.google.ical.values.Frequency; import com.google.ical.values.RRule; +import com.google.ical.values.WeekdayNum; import com.todoroo.astrid.data.Task; import org.tasks.R; @@ -33,7 +34,6 @@ import org.tasks.analytics.Tracking; import org.tasks.dialogs.DialogBuilder; 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; @@ -82,11 +82,29 @@ public class RepeatControlSet extends TaskEditControlFragment refreshDisplayView(); } + public void onDueDateChanged(long dueDate) { + this.dueDate = dueDate; + if (rrule != null && rrule.getFreq() == MONTHLY && !rrule.getByDay().isEmpty()) { + WeekdayNum weekdayNum = rrule.getByDay().get(0); + DateTime dateTime = new DateTime(dueDate); + int num; + int dayOfWeekInMonth = dateTime.getDayOfWeekInMonth(); + if (weekdayNum.num == -1 || dayOfWeekInMonth == 5) { + num = dayOfWeekInMonth == dateTime.getMaxDayOfWeekInMonth() ? -1 : dayOfWeekInMonth; + } else { + num = dayOfWeekInMonth; + } + rrule.setByDay(newArrayList(new WeekdayNum(num, dateTime.getWeekday()))); + refreshDisplayView(); + } + } + public interface RepeatChangedListener { void repeatChanged(boolean repeat); } private static final String EXTRA_RECURRENCE = "extra_recurrence"; + private static final String EXTRA_DUE_DATE = "extra_due_date"; private static final String EXTRA_REPEAT_AFTER_COMPLETION = "extra_repeat_after_completion"; public static final int TYPE_DUE_DATE = 1; @@ -105,6 +123,7 @@ public class RepeatControlSet extends TaskEditControlFragment private final List repeatTypes = new ArrayList<>(); private RRule rrule; private HiddenTopArrayAdapter typeAdapter; + private long dueDate; private RepeatChangedListener callback; @@ -116,6 +135,7 @@ public class RepeatControlSet extends TaskEditControlFragment View view = super.onCreateView(inflater, container, savedInstanceState); if (savedInstanceState != null) { String recurrence = savedInstanceState.getString(EXTRA_RECURRENCE); + dueDate = savedInstanceState.getLong(EXTRA_DUE_DATE); if (Strings.isNullOrEmpty(recurrence)) { rrule = null; } else { @@ -168,6 +188,7 @@ public class RepeatControlSet extends TaskEditControlFragment outState.putString(EXTRA_RECURRENCE, rrule == null ? "" : rrule.toIcal()); outState.putBoolean(EXTRA_REPEAT_AFTER_COMPLETION, repeatAfterCompletion); + outState.putLong(EXTRA_DUE_DATE, dueDate); } @Override @@ -187,7 +208,7 @@ public class RepeatControlSet extends TaskEditControlFragment return false; } Frequency frequency = rrule.getFreq(); - return frequency == WEEKLY && !rrule.getByDay().isEmpty() || + return (frequency == WEEKLY || frequency == MONTHLY) && !rrule.getByDay().isEmpty() || frequency == HOURLY || frequency == MINUTELY || rrule.getUntil() != null || @@ -237,7 +258,7 @@ public class RepeatControlSet extends TaskEditControlFragment if (i == 0) { rrule = null; } else if (i == 5) { - newCustomRecurrenceDialog(this, rrule) + newCustomRecurrenceDialog(this, rrule, dueDate) .show(getFragmentManager(), FRAG_TAG_CUSTOM_RECURRENCE); return; } else { @@ -289,6 +310,7 @@ public class RepeatControlSet extends TaskEditControlFragment @Override public void initialize(boolean isNewTask, Task task) { repeatAfterCompletion = task.repeatAfterCompletion(); + dueDate = task.getDueDate(); try { rrule = new RRule(task.getRecurrenceWithoutFrom()); rrule.setUntil(new DateTime(task.getRepeatUntil()).toDateValue()); diff --git a/app/src/main/java/com/todoroo/astrid/repeats/RepeatTaskHelper.java b/app/src/main/java/com/todoroo/astrid/repeats/RepeatTaskHelper.java index 9f36dc895..66e046971 100644 --- a/app/src/main/java/com/todoroo/astrid/repeats/RepeatTaskHelper.java +++ b/app/src/main/java/com/todoroo/astrid/repeats/RepeatTaskHelper.java @@ -115,7 +115,7 @@ public class RepeatTaskHelper { return handleSubdayRepeat(original, rrule); } else if(rrule.getFreq() == Frequency.WEEKLY && rrule.getByDay().size() > 0 && repeatAfterCompletion) { return handleWeeklyRepeatAfterComplete(rrule, original, task.hasDueTime()); - } else if (rrule.getFreq() == Frequency.MONTHLY) { + } else if (rrule.getFreq() == Frequency.MONTHLY && rrule.getByDay().isEmpty()) { return handleMonthlyRepeat(original, startDateAsDV, task.hasDueTime(), rrule); } else { return invokeRecurrence(rrule, original, startDateAsDV); @@ -227,7 +227,7 @@ public class RepeatTaskHelper { // handle the iCalendar "byDay" field differently depending on if // we are weekly or otherwise - if(rrule.getFreq() != Frequency.WEEKLY) { + if(rrule.getFreq() != Frequency.WEEKLY && rrule.getFreq() != Frequency.MONTHLY) { rrule.setByDay(Collections.emptyList()); } diff --git a/app/src/main/java/org/tasks/repeats/CustomRecurrenceDialog.java b/app/src/main/java/org/tasks/repeats/CustomRecurrenceDialog.java index 02fb23303..7b3e36229 100644 --- a/app/src/main/java/org/tasks/repeats/CustomRecurrenceDialog.java +++ b/app/src/main/java/org/tasks/repeats/CustomRecurrenceDialog.java @@ -16,6 +16,8 @@ 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; @@ -56,6 +58,7 @@ import butterknife.OnTextChanged; import timber.log.Timber; import static android.support.v4.content.ContextCompat.getColor; +import static com.google.common.collect.Lists.newArrayList; import static com.google.ical.values.Frequency.DAILY; import static com.google.ical.values.Frequency.HOURLY; import static com.google.ical.values.Frequency.MINUTELY; @@ -64,16 +67,18 @@ import static com.google.ical.values.Frequency.WEEKLY; import static com.google.ical.values.Frequency.YEARLY; import static java.util.Arrays.asList; import static org.tasks.date.DateTimeUtils.newDateTime; +import static org.tasks.time.DateTimeUtils.currentTimeMillis; public class CustomRecurrenceDialog extends InjectingDialogFragment { - public static CustomRecurrenceDialog newCustomRecurrenceDialog(Fragment target, RRule rrule) { + public static CustomRecurrenceDialog newCustomRecurrenceDialog(Fragment target, RRule rrule, long dueDate) { CustomRecurrenceDialog dialog = new CustomRecurrenceDialog(); dialog.setTargetFragment(target, 0); Bundle arguments = new Bundle(); if (rrule != null) { arguments.putString(EXTRA_RRULE, rrule.toIcal()); } + arguments.putLong(EXTRA_DATE, dueDate); dialog.setArguments(arguments); return dialog; } @@ -85,6 +90,8 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment { private static final List FREQUENCIES = asList(MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY); private static final String EXTRA_RRULE = "extra_rrule"; + private static final String EXTRA_WEEKDAYS = "extra_weekdays"; + private static final String EXTRA_DATE = "extra_date"; private static final int REQUEST_PICK_DATE = 505; @Inject @ForActivity Context context; @@ -102,6 +109,11 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment { @BindView(R.id.week_day_6) WeekButton day6; @BindView(R.id.week_day_7) WeekButton day7; + @BindView(R.id.month_group) RadioGroup monthGroup; + @BindView(R.id.repeat_monthly_same_day) RadioButton repeatMonthlySameDay; + @BindView(R.id.repeat_monthly_day_of_nth_week) RadioButton repeatMonthlyDayOfNthWeek; + @BindView(R.id.repeat_monthly_day_of_last_week) RadioButton repeatMonthlyDayOfLastWeek; + @BindView(R.id.repeat_until) Spinner repeatUntilSpinner; @BindView(R.id.frequency) Spinner frequencySpinner; @BindView(R.id.intervalValue) EditText intervalEditText; @@ -114,6 +126,7 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment { private WeekButton[] weekButtons; private RRule rrule; + private boolean[] weekdaySelected; @NonNull @Override @@ -121,9 +134,13 @@ 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 = savedInstanceState == null - ? getArguments().getString(EXTRA_RRULE) + ? arguments.getString(EXTRA_RRULE) : savedInstanceState.getString(EXTRA_RRULE); + weekdaySelected = savedInstanceState == null + ? new boolean[7] + : savedInstanceState.getBooleanArray(EXTRA_WEEKDAYS); try { if (!Strings.isNullOrEmpty(rule)) { rrule = new RRule(rule); @@ -135,10 +152,62 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment { rrule = new RRule(); rrule.setInterval(1); rrule.setFreq(WEEKLY); + weekdaySelected = new boolean[7]; } + DateFormatSymbols dfs = new DateFormatSymbols(locale.getLocale()); + String[] shortWeekdays = dfs.getShortWeekdays(); + ButterKnife.bind(this, dialogView); + Calendar dayOfMonthCalendar = Calendar.getInstance(locale.getLocale()); + dayOfMonthCalendar.setTimeInMillis(arguments.getLong(EXTRA_DATE, currentTimeMillis())); + 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 WeekdayNum(-1, calendarDayToWeekday(dueDayOfWeek))); + repeatMonthlyDayOfLastWeek.setText(text); + } else { + repeatMonthlyDayOfLastWeek.setVisibility(View.GONE); + } + + if (dayOfWeekInMonth < 5) { + 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 + }; + 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 WeekdayNum(dayOfWeekInMonth, calendarDayToWeekday(dueDayOfWeek))); + repeatMonthlyDayOfNthWeek.setText(text); + } else { + repeatMonthlyDayOfNthWeek.setVisibility(View.GONE); + } + + if (rrule.getFreq() == MONTHLY) { + if (rrule.getByDay().size() == 1) { + WeekdayNum weekdayNum = rrule.getByDay().get(0); + if (weekdayNum.num == -1) { + repeatMonthlyDayOfLastWeek.setChecked(true); + } else if (weekdayNum.num == 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) { + 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); frequencySpinner.setAdapter(frequencyAdapter); @@ -181,13 +250,12 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment { // set up days of week ThemeAccent accent = theme.getThemeAccent(); - DateFormatSymbols dfs = new DateFormatSymbols(locale.getLocale()); - Calendar calendar = Calendar.getInstance(locale.getLocale()); - calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek()); - String[] shortWeekdays = dfs.getShortWeekdays(); + Calendar dayOfWeekCalendar = Calendar.getInstance(locale.getLocale()); + dayOfWeekCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeekCalendar.getFirstDayOfWeek()); for(int i = 0; i < 7; i++) { - int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); + int index = i; + int dayOfWeek = dayOfWeekCalendar.get(Calendar.DAY_OF_WEEK); String text = shortWeekdays[dayOfWeek]; WeekdayNum weekdayNum = new WeekdayNum(0, calendarDayToWeekday(dayOfWeek)); WeekButton weekButton = weekButtons[i]; @@ -197,33 +265,46 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment { weekButton.setTextOff(text); weekButton.setTextOn(text); weekButton.setText(text); - weekButton.setCheckedNoAnimate(rrule.getByDay().contains(weekdayNum)); - calendar.add(Calendar.DATE, 1); + if (savedInstanceState == null) { + weekdaySelected[index] = rrule.getByDay().contains(weekdayNum); + } + weekButton.setOnCheckedChangeListener((compoundButton, checked) -> weekdaySelected[index] = checked); + dayOfWeekCalendar.add(Calendar.DATE, 1); } return dialogBuilder.newDialog() .setView(dialogView) - .setPositiveButton(android.R.string.ok, (dialog12, which) -> { - if (rrule.getFreq() == WEEKLY) { - setByDays(); - } else { - rrule.setByDay(Collections.emptyList()); - } - ((CustomRecurrenceCallback) getTargetFragment()).onSelected(rrule); - }) + .setPositiveButton(android.R.string.ok, this::onRuleSelected) .setNegativeButton(android.R.string.cancel, null) .setOnCancelListener(DialogInterface::dismiss) .show(); } - private void setByDays() { - List checked = new ArrayList<>(); - for (WeekButton button : weekButtons) { - if (button.isChecked()) { - checked.add((WeekdayNum) button.getTag()); + private void onRuleSelected(DialogInterface dialogInterface, int which) { + if (rrule.getFreq() == WEEKLY) { + List checked = new ArrayList<>(); + for (int i = 0 ; i < 7 ; i++) { + if (weekdaySelected[i]) { + checked.add((WeekdayNum) weekButtons[i].getTag()); + } } + rrule.setByDay(checked); + } else if (rrule.getFreq() == MONTHLY) { + switch (monthGroup.getCheckedRadioButtonId()) { + case R.id.repeat_monthly_same_day: + rrule.setByDay(Collections.emptyList()); + break; + case R.id.repeat_monthly_day_of_nth_week: + rrule.setByDay(newArrayList((WeekdayNum) repeatMonthlyDayOfNthWeek.getTag())); + break; + case R.id.repeat_monthly_day_of_last_week: + rrule.setByDay(newArrayList((WeekdayNum) repeatMonthlyDayOfLastWeek.getTag())); + break; + } + } else { + rrule.setByDay(Collections.emptyList()); } - rrule.setByDay(checked); + ((CustomRecurrenceCallback) getTargetFragment()).onSelected(rrule); } private Weekday calendarDayToWeekday(int calendarDay) { @@ -250,8 +331,8 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment { public void onResume() { super.onResume(); - for (WeekButton button : weekButtons) { - button.setCheckedNoAnimate(button.isChecked()); + for (int i = 0 ; i < 7 ; i++) { + weekButtons[i].setCheckedNoAnimate(weekdaySelected[i]); } } @@ -259,20 +340,10 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment { public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - setByDays(); + outState.putBooleanArray(EXTRA_WEEKDAYS, weekdaySelected); outState.putString(EXTRA_RRULE, rrule.toIcal()); } - private void setFrequency(Frequency frequency) { - rrule.setFreq(frequency); - int weekVisibility = frequency == WEEKLY ? View.VISIBLE : View.GONE; - weekGroup1.setVisibility(weekVisibility); - if (weekGroup2 != null) { - weekGroup2.setVisibility(weekVisibility); - } - updateIntervalTextView(); - } - private void setInterval(int interval, boolean updateEditText) { rrule.setInterval(interval); if (updateEditText) { @@ -338,7 +409,15 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment { @OnItemSelected(R.id.frequency) public void onFrequencyChanged(int position) { - setFrequency(FREQUENCIES.get(position)); + Frequency frequency = FREQUENCIES.get(position); + rrule.setFreq(frequency); + int weekVisibility = frequency == WEEKLY ? View.VISIBLE : View.GONE; + weekGroup1.setVisibility(weekVisibility); + if (weekGroup2 != null) { + weekGroup2.setVisibility(weekVisibility); + } + monthGroup.setVisibility(frequency == MONTHLY ? View.VISIBLE : View.GONE); + updateIntervalTextView(); } @OnTextChanged(R.id.intervalValue) diff --git a/app/src/main/java/org/tasks/repeats/RepeatRuleToString.java b/app/src/main/java/org/tasks/repeats/RepeatRuleToString.java index a3380dee4..facf958ff 100644 --- a/app/src/main/java/org/tasks/repeats/RepeatRuleToString.java +++ b/app/src/main/java/org/tasks/repeats/RepeatRuleToString.java @@ -17,10 +17,12 @@ import org.tasks.time.DateTime; import java.text.DateFormatSymbols; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.List; import javax.inject.Inject; +import static com.google.ical.values.Frequency.MONTHLY; import static com.google.ical.values.Frequency.WEEKLY; public class RepeatRuleToString { @@ -43,7 +45,7 @@ public class RepeatRuleToString { 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()) { + if ((frequency == WEEKLY || frequency == MONTHLY) && !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); @@ -62,7 +64,7 @@ public class RepeatRuleToString { } else { int plural = getFrequencyPlural(frequency); String frequencyPlural = context.getResources().getQuantityString(plural, interval, interval); - if (frequency == WEEKLY && !rrule.getByDay().isEmpty()) { + if ((frequency == WEEKLY || frequency == MONTHLY) && !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); @@ -83,12 +85,59 @@ public class RepeatRuleToString { private String getDayString(RRule rrule) { DateFormatSymbols dfs = new DateFormatSymbols(locale.getLocale()); - String[] shortWeekdays = dfs.getShortWeekdays(); - List days = new ArrayList<>(); - for (WeekdayNum weekday : rrule.getByDay()) { - days.add(shortWeekdays[weekdays.indexOf(weekday.wday) + 1]); + if (rrule.getFreq() == WEEKLY) { + String[] shortWeekdays = dfs.getShortWeekdays(); + List 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); + } else if (rrule.getFreq() == MONTHLY) { + String[] longWeekdays = dfs.getWeekdays(); + WeekdayNum weekdayNum = rrule.getByDay().get(0); + String weekday; + Calendar dayOfWeekCalendar = Calendar.getInstance(locale.getLocale()); + dayOfWeekCalendar.set(Calendar.DAY_OF_WEEK, weekdayToCalendarDay(weekdayNum.wday)); + weekday = longWeekdays[dayOfWeekCalendar.get(Calendar.DAY_OF_WEEK)]; + if (weekdayNum.num == -1) { + return context.getString(R.string.repeat_monthly_every_day_of_nth_week, + context.getString(R.string.repeat_monthly_last_week), + weekday); + } else { + 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 + }; + return context.getString(R.string.repeat_monthly_every_day_of_nth_week, + context.getString(resources[weekdayNum.num - 1]), + weekday); + } + } else { + throw new RuntimeException(); + } + } + + private int weekdayToCalendarDay(Weekday weekday) { + switch (weekday) { + case SU: + return Calendar.SUNDAY; + case MO: + return Calendar.MONDAY; + case TU: + return Calendar.TUESDAY; + case WE: + return Calendar.WEDNESDAY; + case TH: + return Calendar.THURSDAY; + case FR: + return Calendar.FRIDAY; + case SA: + return Calendar.SATURDAY; + default: + throw new RuntimeException("Invalid weekday: " + weekday); } - return Joiner.on(context.getString(R.string.list_separator_with_space)).join(days); } private int getSingleFrequencyResource(Frequency frequency) { diff --git a/app/src/main/java/org/tasks/time/DateTime.java b/app/src/main/java/org/tasks/time/DateTime.java index d519b6f23..0f2eee3ef 100644 --- a/app/src/main/java/org/tasks/time/DateTime.java +++ b/app/src/main/java/org/tasks/time/DateTime.java @@ -3,6 +3,7 @@ package org.tasks.time; import com.google.ical.values.DateTimeValue; import com.google.ical.values.DateValue; import com.google.ical.values.DateValueImpl; +import com.google.ical.values.Weekday; import org.tasks.locale.Locale; @@ -12,6 +13,14 @@ import java.util.GregorianCalendar; import java.util.TimeZone; import java.util.concurrent.TimeUnit; +import static java.util.Calendar.FRIDAY; +import static java.util.Calendar.MONDAY; +import static java.util.Calendar.SATURDAY; +import static java.util.Calendar.SUNDAY; +import static java.util.Calendar.THURSDAY; +import static java.util.Calendar.TUESDAY; +import static java.util.Calendar.WEDNESDAY; + public class DateTime { public static final int MAX_MILLIS_PER_DAY = (int) TimeUnit.DAYS.toMillis(1) - 1; @@ -289,6 +298,34 @@ public class DateTime { : new DateValueImpl(getYear(), getMonthOfYear(), getDayOfMonth()); } + public int getDayOfWeekInMonth() { + return getCalendar().get(Calendar.DAY_OF_WEEK_IN_MONTH); + } + + public int getMaxDayOfWeekInMonth() { + return getCalendar().getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH); + } + + public Weekday getWeekday() { + switch (getCalendar().get(Calendar.DAY_OF_WEEK)) { + case SUNDAY: + return Weekday.SU; + case MONDAY: + return Weekday.MO; + case TUESDAY: + return Weekday.TU; + case WEDNESDAY: + return Weekday.WE; + case THURSDAY: + return Weekday.TH; + case FRIDAY: + return Weekday.FR; + case SATURDAY: + return Weekday.SA; + } + throw new RuntimeException(); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/app/src/main/java/org/tasks/ui/DeadlineControlSet.java b/app/src/main/java/org/tasks/ui/DeadlineControlSet.java index 2fa990176..56e5a186b 100644 --- a/app/src/main/java/org/tasks/ui/DeadlineControlSet.java +++ b/app/src/main/java/org/tasks/ui/DeadlineControlSet.java @@ -82,10 +82,18 @@ public class DeadlineControlSet extends TaskEditControlFragment { private long date = 0; private int time = -1; + public interface DueDateChangeListener { + void dueDateChanged(long dateTime); + } + + DueDateChangeListener callback; + @Override public void onAttach(Activity activity) { super.onAttach(activity); + callback = (DueDateChangeListener) activity; + dateShortcutMorning = preferences.getDateShortcutMorning(); dateShortcutAfternoon = preferences.getDateShortcutAfternoon(); dateShortcutEvening = preferences.getDateShortcutEvening(); @@ -193,9 +201,7 @@ public class DeadlineControlSet extends TaskEditControlFragment { @OnClick(R.id.clear) void clearTime(View view) { - date = 0; - time = -1; - refreshDisplayView(); + setDate(0); } @OnTouch({R.id.due_date, R.id.due_time}) @@ -404,6 +410,7 @@ public class DeadlineControlSet extends TaskEditControlFragment { if (date == 0) { time = -1; } + callback.dueDateChanged(getDueDateTime()); refreshDisplayView(); } @@ -417,7 +424,7 @@ public class DeadlineControlSet extends TaskEditControlFragment { } date = dateTime.startOfDay().getMillis(); } - + callback.dueDateChanged(getDueDateTime()); refreshDisplayView(); } } diff --git a/app/src/main/res/layout/control_set_repeat.xml b/app/src/main/res/layout/control_set_repeat.xml index df469312c..3900b676f 100644 --- a/app/src/main/res/layout/control_set_repeat.xml +++ b/app/src/main/res/layout/control_set_repeat.xml @@ -83,6 +83,34 @@ + + + + + + + + + Default calendar Display a task count on Tasks launcher icon. Not all launchers support badges. Combine multiple notifications into a single notification + on the same day each month + + every %1$s %2$s + on every %1$s %2$s + first + second + third + fourth + last