Add monthly repeat options

pull/618/head
Alex Baker 7 years ago
parent c0eaa75304
commit 952c607556

@ -28,6 +28,7 @@ import com.todoroo.astrid.data.UserActivity;
import com.todoroo.astrid.files.FilesControlSet; import com.todoroo.astrid.files.FilesControlSet;
import com.todoroo.astrid.gtasks.GtasksList; import com.todoroo.astrid.gtasks.GtasksList;
import com.todoroo.astrid.notes.CommentsController; import com.todoroo.astrid.notes.CommentsController;
import com.todoroo.astrid.repeats.RepeatControlSet;
import com.todoroo.astrid.service.TaskDeleter; import com.todoroo.astrid.service.TaskDeleter;
import com.todoroo.astrid.timers.TimerPlugin; import com.todoroo.astrid.timers.TimerPlugin;
import com.todoroo.astrid.ui.EditTitleControlSet; import com.todoroo.astrid.ui.EditTitleControlSet;
@ -234,6 +235,10 @@ public final class TaskEditFragment extends InjectingFragment implements Toolbar
return getFragment(GoogleTaskListFragment.TAG); return getFragment(GoogleTaskListFragment.TAG);
} }
private RepeatControlSet getRepeatControlSet() {
return getFragment(RepeatControlSet.TAG);
}
private FilesControlSet getFilesControlSet() { private FilesControlSet getFilesControlSet() {
return getFragment(FilesControlSet.TAG); return getFragment(FilesControlSet.TAG);
} }
@ -320,6 +325,10 @@ public final class TaskEditFragment extends InjectingFragment implements Toolbar
getGoogleTaskListFragment().setList(list); getGoogleTaskListFragment().setList(list);
} }
public void onDueDateChanged(long dueDate) {
getRepeatControlSet().onDueDateChanged(dueDate);
}
public void addComment(String message, String actionCode, String picture) { public void addComment(String message, String actionCode, String picture) {
UserActivity userActivity = new UserActivity(); UserActivity userActivity = new UserActivity();
userActivity.setMessage(message); userActivity.setMessage(message);

@ -58,6 +58,7 @@ import org.tasks.tasklist.TagListFragment;
import org.tasks.themes.Theme; import org.tasks.themes.Theme;
import org.tasks.themes.ThemeCache; import org.tasks.themes.ThemeCache;
import org.tasks.themes.ThemeColor; import org.tasks.themes.ThemeColor;
import org.tasks.ui.DeadlineControlSet;
import org.tasks.ui.EmptyTaskEditFragment; import org.tasks.ui.EmptyTaskEditFragment;
import org.tasks.ui.NavigationDrawerFragment; import org.tasks.ui.NavigationDrawerFragment;
import org.tasks.ui.PriorityControlSet; import org.tasks.ui.PriorityControlSet;
@ -79,6 +80,7 @@ public class TaskListActivity extends InjectingAppCompatActivity implements
PriorityControlSet.OnPriorityChanged, PriorityControlSet.OnPriorityChanged,
TimerControlSet.TimerControlSetCallback, TimerControlSet.TimerControlSetCallback,
RepeatControlSet.RepeatChangedListener, RepeatControlSet.RepeatChangedListener,
DeadlineControlSet.DueDateChangeListener,
TaskEditFragment.TaskEditFragmentCallbackHandler, TaskEditFragment.TaskEditFragmentCallbackHandler,
CommentBarFragment.CommentBarFragmentCallback, CommentBarFragment.CommentBarFragmentCallback,
SortDialog.SortDialogCallback, SortDialog.SortDialogCallback,
@ -533,4 +535,9 @@ public class TaskListActivity extends InjectingAppCompatActivity implements
actionMode = null; actionMode = null;
} }
} }
@Override
public void dueDateChanged(long dateTime) {
getTaskEditFragment().onDueDateChanged(dateTime);
}
} }

@ -25,6 +25,7 @@ import android.widget.TextView;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.ical.values.Frequency; import com.google.ical.values.Frequency;
import com.google.ical.values.RRule; import com.google.ical.values.RRule;
import com.google.ical.values.WeekdayNum;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import org.tasks.R; import org.tasks.R;
@ -33,7 +34,6 @@ import org.tasks.analytics.Tracking;
import org.tasks.dialogs.DialogBuilder; 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.repeats.CustomRecurrenceDialog; import org.tasks.repeats.CustomRecurrenceDialog;
import org.tasks.repeats.RepeatRuleToString; import org.tasks.repeats.RepeatRuleToString;
import org.tasks.themes.Theme; import org.tasks.themes.Theme;
@ -82,11 +82,29 @@ public class RepeatControlSet extends TaskEditControlFragment
refreshDisplayView(); 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 { public interface RepeatChangedListener {
void repeatChanged(boolean repeat); void repeatChanged(boolean repeat);
} }
private static final String EXTRA_RECURRENCE = "extra_recurrence"; 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"; private static final String EXTRA_REPEAT_AFTER_COMPLETION = "extra_repeat_after_completion";
public static final int TYPE_DUE_DATE = 1; public static final int TYPE_DUE_DATE = 1;
@ -105,6 +123,7 @@ public class RepeatControlSet extends TaskEditControlFragment
private final List<String> repeatTypes = new ArrayList<>(); private final List<String> repeatTypes = new ArrayList<>();
private RRule rrule; private RRule rrule;
private HiddenTopArrayAdapter<String> typeAdapter; private HiddenTopArrayAdapter<String> typeAdapter;
private long dueDate;
private RepeatChangedListener callback; private RepeatChangedListener callback;
@ -116,6 +135,7 @@ public class RepeatControlSet extends TaskEditControlFragment
View view = super.onCreateView(inflater, container, savedInstanceState); View view = super.onCreateView(inflater, container, savedInstanceState);
if (savedInstanceState != null) { if (savedInstanceState != null) {
String recurrence = savedInstanceState.getString(EXTRA_RECURRENCE); String recurrence = savedInstanceState.getString(EXTRA_RECURRENCE);
dueDate = savedInstanceState.getLong(EXTRA_DUE_DATE);
if (Strings.isNullOrEmpty(recurrence)) { if (Strings.isNullOrEmpty(recurrence)) {
rrule = null; rrule = null;
} else { } else {
@ -168,6 +188,7 @@ public class RepeatControlSet extends TaskEditControlFragment
outState.putString(EXTRA_RECURRENCE, rrule == null ? "" : rrule.toIcal()); outState.putString(EXTRA_RECURRENCE, rrule == null ? "" : rrule.toIcal());
outState.putBoolean(EXTRA_REPEAT_AFTER_COMPLETION, repeatAfterCompletion); outState.putBoolean(EXTRA_REPEAT_AFTER_COMPLETION, repeatAfterCompletion);
outState.putLong(EXTRA_DUE_DATE, dueDate);
} }
@Override @Override
@ -187,7 +208,7 @@ public class RepeatControlSet extends TaskEditControlFragment
return false; return false;
} }
Frequency frequency = rrule.getFreq(); Frequency frequency = rrule.getFreq();
return frequency == WEEKLY && !rrule.getByDay().isEmpty() || return (frequency == WEEKLY || frequency == MONTHLY) && !rrule.getByDay().isEmpty() ||
frequency == HOURLY || frequency == HOURLY ||
frequency == MINUTELY || frequency == MINUTELY ||
rrule.getUntil() != null || rrule.getUntil() != null ||
@ -237,7 +258,7 @@ public class RepeatControlSet extends TaskEditControlFragment
if (i == 0) { if (i == 0) {
rrule = null; rrule = null;
} else if (i == 5) { } else if (i == 5) {
newCustomRecurrenceDialog(this, rrule) newCustomRecurrenceDialog(this, rrule, dueDate)
.show(getFragmentManager(), FRAG_TAG_CUSTOM_RECURRENCE); .show(getFragmentManager(), FRAG_TAG_CUSTOM_RECURRENCE);
return; return;
} else { } else {
@ -289,6 +310,7 @@ public class RepeatControlSet extends TaskEditControlFragment
@Override @Override
public void initialize(boolean isNewTask, Task task) { public void initialize(boolean isNewTask, Task task) {
repeatAfterCompletion = task.repeatAfterCompletion(); repeatAfterCompletion = task.repeatAfterCompletion();
dueDate = task.getDueDate();
try { try {
rrule = new RRule(task.getRecurrenceWithoutFrom()); rrule = new RRule(task.getRecurrenceWithoutFrom());
rrule.setUntil(new DateTime(task.getRepeatUntil()).toDateValue()); rrule.setUntil(new DateTime(task.getRepeatUntil()).toDateValue());

@ -115,7 +115,7 @@ public class RepeatTaskHelper {
return handleSubdayRepeat(original, rrule); return handleSubdayRepeat(original, rrule);
} else if(rrule.getFreq() == Frequency.WEEKLY && rrule.getByDay().size() > 0 && repeatAfterCompletion) { } else if(rrule.getFreq() == Frequency.WEEKLY && rrule.getByDay().size() > 0 && repeatAfterCompletion) {
return handleWeeklyRepeatAfterComplete(rrule, original, task.hasDueTime()); 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); return handleMonthlyRepeat(original, startDateAsDV, task.hasDueTime(), rrule);
} else { } else {
return invokeRecurrence(rrule, original, startDateAsDV); return invokeRecurrence(rrule, original, startDateAsDV);
@ -227,7 +227,7 @@ public class RepeatTaskHelper {
// handle the iCalendar "byDay" field differently depending on if // handle the iCalendar "byDay" field differently depending on if
// we are weekly or otherwise // we are weekly or otherwise
if(rrule.getFreq() != Frequency.WEEKLY) { if(rrule.getFreq() != Frequency.WEEKLY && rrule.getFreq() != Frequency.MONTHLY) {
rrule.setByDay(Collections.emptyList()); rrule.setByDay(Collections.emptyList());
} }

@ -16,6 +16,8 @@ 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;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
@ -56,6 +58,7 @@ import butterknife.OnTextChanged;
import timber.log.Timber; import timber.log.Timber;
import static android.support.v4.content.ContextCompat.getColor; 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.DAILY;
import static com.google.ical.values.Frequency.HOURLY; import static com.google.ical.values.Frequency.HOURLY;
import static com.google.ical.values.Frequency.MINUTELY; 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 com.google.ical.values.Frequency.YEARLY;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import static org.tasks.date.DateTimeUtils.newDateTime; import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class CustomRecurrenceDialog extends InjectingDialogFragment { 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(); CustomRecurrenceDialog dialog = new CustomRecurrenceDialog();
dialog.setTargetFragment(target, 0); dialog.setTargetFragment(target, 0);
Bundle arguments = new Bundle(); Bundle arguments = new Bundle();
if (rrule != null) { if (rrule != null) {
arguments.putString(EXTRA_RRULE, rrule.toIcal()); arguments.putString(EXTRA_RRULE, rrule.toIcal());
} }
arguments.putLong(EXTRA_DATE, dueDate);
dialog.setArguments(arguments); dialog.setArguments(arguments);
return dialog; return dialog;
} }
@ -85,6 +90,8 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
private static final List<Frequency> FREQUENCIES = asList(MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY); private static final List<Frequency> FREQUENCIES = asList(MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY);
private static final String EXTRA_RRULE = "extra_rrule"; 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; private static final int REQUEST_PICK_DATE = 505;
@Inject @ForActivity Context context; @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_6) WeekButton day6;
@BindView(R.id.week_day_7) WeekButton day7; @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.repeat_until) Spinner repeatUntilSpinner;
@BindView(R.id.frequency) Spinner frequencySpinner; @BindView(R.id.frequency) Spinner frequencySpinner;
@BindView(R.id.intervalValue) EditText intervalEditText; @BindView(R.id.intervalValue) EditText intervalEditText;
@ -114,6 +126,7 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
private WeekButton[] weekButtons; private WeekButton[] weekButtons;
private RRule rrule; private RRule rrule;
private boolean[] weekdaySelected;
@NonNull @NonNull
@Override @Override
@ -121,9 +134,13 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
LayoutInflater inflater = LayoutInflater.from(getActivity()); LayoutInflater inflater = LayoutInflater.from(getActivity());
View dialogView = inflater.inflate(R.layout.control_set_repeat, null); View dialogView = inflater.inflate(R.layout.control_set_repeat, null);
Bundle arguments = getArguments();
String rule = savedInstanceState == null String rule = savedInstanceState == null
? getArguments().getString(EXTRA_RRULE) ? arguments.getString(EXTRA_RRULE)
: savedInstanceState.getString(EXTRA_RRULE); : savedInstanceState.getString(EXTRA_RRULE);
weekdaySelected = savedInstanceState == null
? new boolean[7]
: savedInstanceState.getBooleanArray(EXTRA_WEEKDAYS);
try { try {
if (!Strings.isNullOrEmpty(rule)) { if (!Strings.isNullOrEmpty(rule)) {
rrule = new RRule(rule); rrule = new RRule(rule);
@ -135,10 +152,62 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
rrule = new RRule(); rrule = new RRule();
rrule.setInterval(1); rrule.setInterval(1);
rrule.setFreq(WEEKLY); rrule.setFreq(WEEKLY);
weekdaySelected = new boolean[7];
} }
DateFormatSymbols dfs = new DateFormatSymbols(locale.getLocale());
String[] shortWeekdays = dfs.getShortWeekdays();
ButterKnife.bind(this, dialogView); 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<CharSequence> frequencyAdapter = ArrayAdapter.createFromResource(context, R.array.repeat_frequency, R.layout.frequency_item); ArrayAdapter<CharSequence> frequencyAdapter = ArrayAdapter.createFromResource(context, R.array.repeat_frequency, R.layout.frequency_item);
frequencyAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); frequencyAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
frequencySpinner.setAdapter(frequencyAdapter); frequencySpinner.setAdapter(frequencyAdapter);
@ -181,13 +250,12 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
// set up days of week // set up days of week
ThemeAccent accent = theme.getThemeAccent(); ThemeAccent accent = theme.getThemeAccent();
DateFormatSymbols dfs = new DateFormatSymbols(locale.getLocale()); Calendar dayOfWeekCalendar = Calendar.getInstance(locale.getLocale());
Calendar calendar = Calendar.getInstance(locale.getLocale()); dayOfWeekCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeekCalendar.getFirstDayOfWeek());
calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());
String[] shortWeekdays = dfs.getShortWeekdays();
for(int i = 0; i < 7; i++) { 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]; String text = shortWeekdays[dayOfWeek];
WeekdayNum weekdayNum = new WeekdayNum(0, calendarDayToWeekday(dayOfWeek)); WeekdayNum weekdayNum = new WeekdayNum(0, calendarDayToWeekday(dayOfWeek));
WeekButton weekButton = weekButtons[i]; WeekButton weekButton = weekButtons[i];
@ -197,33 +265,46 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
weekButton.setTextOff(text); weekButton.setTextOff(text);
weekButton.setTextOn(text); weekButton.setTextOn(text);
weekButton.setText(text); weekButton.setText(text);
weekButton.setCheckedNoAnimate(rrule.getByDay().contains(weekdayNum)); if (savedInstanceState == null) {
calendar.add(Calendar.DATE, 1); weekdaySelected[index] = rrule.getByDay().contains(weekdayNum);
}
weekButton.setOnCheckedChangeListener((compoundButton, checked) -> weekdaySelected[index] = checked);
dayOfWeekCalendar.add(Calendar.DATE, 1);
} }
return dialogBuilder.newDialog() return dialogBuilder.newDialog()
.setView(dialogView) .setView(dialogView)
.setPositiveButton(android.R.string.ok, (dialog12, which) -> { .setPositiveButton(android.R.string.ok, this::onRuleSelected)
if (rrule.getFreq() == WEEKLY) {
setByDays();
} else {
rrule.setByDay(Collections.emptyList());
}
((CustomRecurrenceCallback) getTargetFragment()).onSelected(rrule);
})
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setOnCancelListener(DialogInterface::dismiss) .setOnCancelListener(DialogInterface::dismiss)
.show(); .show();
} }
private void setByDays() { private void onRuleSelected(DialogInterface dialogInterface, int which) {
List<WeekdayNum> checked = new ArrayList<>(); if (rrule.getFreq() == WEEKLY) {
for (WeekButton button : weekButtons) { List<WeekdayNum> checked = new ArrayList<>();
if (button.isChecked()) { for (int i = 0 ; i < 7 ; i++) {
checked.add((WeekdayNum) button.getTag()); 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) { private Weekday calendarDayToWeekday(int calendarDay) {
@ -250,8 +331,8 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
for (WeekButton button : weekButtons) { for (int i = 0 ; i < 7 ; i++) {
button.setCheckedNoAnimate(button.isChecked()); weekButtons[i].setCheckedNoAnimate(weekdaySelected[i]);
} }
} }
@ -259,20 +340,10 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
setByDays(); outState.putBooleanArray(EXTRA_WEEKDAYS, weekdaySelected);
outState.putString(EXTRA_RRULE, rrule.toIcal()); 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) { private void setInterval(int interval, boolean updateEditText) {
rrule.setInterval(interval); rrule.setInterval(interval);
if (updateEditText) { if (updateEditText) {
@ -338,7 +409,15 @@ public class CustomRecurrenceDialog extends InjectingDialogFragment {
@OnItemSelected(R.id.frequency) @OnItemSelected(R.id.frequency)
public void onFrequencyChanged(int position) { 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) @OnTextChanged(R.id.intervalValue)

@ -17,10 +17,12 @@ import org.tasks.time.DateTime;
import java.text.DateFormatSymbols; import java.text.DateFormatSymbols;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import static com.google.ical.values.Frequency.MONTHLY;
import static com.google.ical.values.Frequency.WEEKLY; import static com.google.ical.values.Frequency.WEEKLY;
public class RepeatRuleToString { public class RepeatRuleToString {
@ -43,7 +45,7 @@ public class RepeatRuleToString {
String countString = count > 0 ? context.getResources().getQuantityString(R.plurals.repeat_times, count) : ""; String countString = count > 0 ? context.getResources().getQuantityString(R.plurals.repeat_times, count) : "";
if (interval == 1) { if (interval == 1) {
String frequencyString = context.getString(getSingleFrequencyResource(frequency)); String frequencyString = context.getString(getSingleFrequencyResource(frequency));
if (frequency == WEEKLY && !rrule.getByDay().isEmpty()) { if ((frequency == WEEKLY || frequency == MONTHLY) && !rrule.getByDay().isEmpty()) {
String dayString = getDayString(rrule); String dayString = getDayString(rrule);
if (count > 0) { if (count > 0) {
return context.getString(R.string.repeats_single_on_number_of_times, frequencyString, dayString, count, countString); return context.getString(R.string.repeats_single_on_number_of_times, frequencyString, dayString, count, countString);
@ -62,7 +64,7 @@ public class RepeatRuleToString {
} else { } else {
int plural = getFrequencyPlural(frequency); int plural = getFrequencyPlural(frequency);
String frequencyPlural = context.getResources().getQuantityString(plural, interval, interval); 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); String dayString = getDayString(rrule);
if (count > 0) { if (count > 0) {
return context.getString(R.string.repeats_plural_on_number_of_times, frequencyPlural, dayString, count, countString); 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) { private String getDayString(RRule rrule) {
DateFormatSymbols dfs = new DateFormatSymbols(locale.getLocale()); DateFormatSymbols dfs = new DateFormatSymbols(locale.getLocale());
String[] shortWeekdays = dfs.getShortWeekdays(); if (rrule.getFreq() == WEEKLY) {
List<String> days = new ArrayList<>(); String[] shortWeekdays = dfs.getShortWeekdays();
for (WeekdayNum weekday : rrule.getByDay()) { List<String> days = new ArrayList<>();
days.add(shortWeekdays[weekdays.indexOf(weekday.wday) + 1]); 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) { private int getSingleFrequencyResource(Frequency frequency) {

@ -3,6 +3,7 @@ package org.tasks.time;
import com.google.ical.values.DateTimeValue; import com.google.ical.values.DateTimeValue;
import com.google.ical.values.DateValue; import com.google.ical.values.DateValue;
import com.google.ical.values.DateValueImpl; import com.google.ical.values.DateValueImpl;
import com.google.ical.values.Weekday;
import org.tasks.locale.Locale; import org.tasks.locale.Locale;
@ -12,6 +13,14 @@ import java.util.GregorianCalendar;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.TimeUnit; 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 class DateTime {
public static final int MAX_MILLIS_PER_DAY = (int) TimeUnit.DAYS.toMillis(1) - 1; 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()); : 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 @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

@ -82,10 +82,18 @@ public class DeadlineControlSet extends TaskEditControlFragment {
private long date = 0; private long date = 0;
private int time = -1; private int time = -1;
public interface DueDateChangeListener {
void dueDateChanged(long dateTime);
}
DueDateChangeListener callback;
@Override @Override
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
super.onAttach(activity); super.onAttach(activity);
callback = (DueDateChangeListener) activity;
dateShortcutMorning = preferences.getDateShortcutMorning(); dateShortcutMorning = preferences.getDateShortcutMorning();
dateShortcutAfternoon = preferences.getDateShortcutAfternoon(); dateShortcutAfternoon = preferences.getDateShortcutAfternoon();
dateShortcutEvening = preferences.getDateShortcutEvening(); dateShortcutEvening = preferences.getDateShortcutEvening();
@ -193,9 +201,7 @@ public class DeadlineControlSet extends TaskEditControlFragment {
@OnClick(R.id.clear) @OnClick(R.id.clear)
void clearTime(View view) { void clearTime(View view) {
date = 0; setDate(0);
time = -1;
refreshDisplayView();
} }
@OnTouch({R.id.due_date, R.id.due_time}) @OnTouch({R.id.due_date, R.id.due_time})
@ -404,6 +410,7 @@ public class DeadlineControlSet extends TaskEditControlFragment {
if (date == 0) { if (date == 0) {
time = -1; time = -1;
} }
callback.dueDateChanged(getDueDateTime());
refreshDisplayView(); refreshDisplayView();
} }
@ -417,7 +424,7 @@ public class DeadlineControlSet extends TaskEditControlFragment {
} }
date = dateTime.startOfDay().getMillis(); date = dateTime.startOfDay().getMillis();
} }
callback.dueDateChanged(getDueDateTime());
refreshDisplayView(); refreshDisplayView();
} }
} }

@ -83,6 +83,34 @@
<include layout="@layout/week_buttons" /> <include layout="@layout/week_buttons" />
<RadioGroup
android:id="@+id/month_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:paddingEnd="@dimen/keyline_first"
android:paddingLeft="@dimen/keyline_first"
android:paddingRight="@dimen/keyline_first"
android:paddingStart="@dimen/keyline_first">
<RadioButton
android:id="@+id/repeat_monthly_same_day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/repeat_monthly_same_day_each_month" />
<RadioButton
android:id="@+id/repeat_monthly_day_of_nth_week"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<RadioButton
android:id="@+id/repeat_monthly_day_of_last_week"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RadioGroup>
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

@ -863,4 +863,13 @@ File %1$s contained %2$s.\n\n
<string name="default_calendar">Default calendar</string> <string name="default_calendar">Default calendar</string>
<string name="badges_description">Display a task count on Tasks launcher icon. Not all launchers support badges.</string> <string name="badges_description">Display a task count on Tasks launcher icon. Not all launchers support badges.</string>
<string name="bundle_notifications_summary">Combine multiple notifications into a single notification</string> <string name="bundle_notifications_summary">Combine multiple notifications into a single notification</string>
<string name="repeat_monthly_same_day_each_month">on the same day each month</string>
<string name="repeat_monthly_every_day_of_nth_week">every %1$s %2$s</string>
<string name="repeat_monthly_on_every_day_of_nth_week">on every %1$s %2$s</string>
<string name="repeat_monthly_first_week">first</string>
<string name="repeat_monthly_second_week">second</string>
<string name="repeat_monthly_third_week">third</string>
<string name="repeat_monthly_fourth_week">fourth</string>
<string name="repeat_monthly_last_week">last</string>
</resources> </resources>

Loading…
Cancel
Save