You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.java

464 lines
16 KiB
Java

/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.repeats;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
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.DialogUtilities;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.ui.DateAndTimeDialog;
import com.todoroo.astrid.ui.DateAndTimeDialog.DateAndTimeDialogListener;
import com.todoroo.astrid.ui.DateAndTimePicker;
import com.todoroo.astrid.ui.NumberPickerDialog;
import com.todoroo.astrid.ui.NumberPickerDialog.OnNumberPickedListener;
import com.todoroo.astrid.ui.PopupControlSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tasks.R;
import org.tasks.preferences.ActivityPreferences;
import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import static org.tasks.date.DateTimeUtils.newDate;
/**
* Control Set for managing repeats
*
* @author Tim Su <tim@todoroo.com>
*
*/
public class RepeatControlSet extends PopupControlSet {
private static final Logger log = LoggerFactory.getLogger(RepeatControlSet.class);
// --- spinner constants
public static final int INTERVAL_DAYS = 0;
public static final int INTERVAL_WEEKS = 1;
public static final int INTERVAL_MONTHS = 2;
public static final int INTERVAL_HOURS = 3;
public static final int INTERVAL_MINUTES = 4;
public static final int INTERVAL_YEARS = 5;
private static final int TYPE_DUE_DATE = 0;
private static final int TYPE_COMPLETION_DATE = 1;
//private final CheckBox enabled;
private boolean doRepeat = false;
private Button value;
private Spinner interval;
private Spinner type;
private Button repeatUntil;
private LinearLayout daysOfWeekContainer;
private final CompoundButton[] daysOfWeek = new CompoundButton[7];
private String recurrence;
private int repeatValue;
private int intervalValue;
private long repeatUntilValue;
private final List<RepeatChangedListener> listeners = new LinkedList<>();
public interface RepeatChangedListener {
public void repeatChanged(boolean repeat);
}
// --- implementation
public RepeatControlSet(ActivityPreferences preferences, Activity activity) {
super(preferences, activity, R.layout.control_set_repeat, R.layout.control_set_repeat_display, R.string.repeat_enabled);
}
/** Set up the repeat value button */
private void setRepeatValue(int newValue) {
repeatValue = newValue;
value.setText(activity.getString(R.string.repeat_every, newValue));
}
private void setRepeatUntilValue(long newValue) {
repeatUntilValue = newValue;
if (newValue == 0) {
repeatUntil.setText(activity.getString(R.string.repeat_forever));
} else {
repeatUntil.setText(activity.getString(R.string.repeat_until, DateAndTimePicker.getDisplayString(activity, newValue)));
}
}
protected void repeatValueClick() {
int dialogValue = repeatValue;
if(dialogValue == 0) {
dialogValue = 1;
}
new NumberPickerDialog(activity, new OnNumberPickedListener() {
@Override
public void onNumberPicked(int number) {
setRepeatValue(number);
}
}, activity.getResources().getString(R.string.repeat_interval_prompt),
dialogValue, 1, 1, 365).show();
}
private void repeatUntilClick() {
DateAndTimeDialog d = new DateAndTimeDialog(preferences, activity, repeatUntilValue,
R.layout.repeat_until_dialog, R.string.repeat_until_title);
d.setDateAndTimeDialogListener(new DateAndTimeDialogListener() {
@Override
public void onDateAndTimeSelected(long date) {
setRepeatUntilValue(date);
}
@Override
public void onDateAndTimeCancelled() {
//
}
});
d.show();
}
public void addListener(RepeatChangedListener listener) {
listeners.add(listener);
}
@Override
public void readFromTask(Task task) {
super.readFromTask(task);
recurrence = model.sanitizedRecurrence();
if(recurrence == null) {
recurrence = "";
}
repeatUntilValue = model.getRepeatUntil();
if(recurrence.length() > 0) {
try {
RRule rrule = new RRule(recurrence);
repeatValue = rrule.getInterval();
switch(rrule.getFreq()) {
case DAILY:
intervalValue = INTERVAL_DAYS;
break;
case WEEKLY: {
intervalValue = INTERVAL_WEEKS;
break;
}
case MONTHLY:
intervalValue = INTERVAL_MONTHS;
break;
case HOURLY:
intervalValue = INTERVAL_HOURS;
break;
case MINUTELY:
intervalValue = INTERVAL_MINUTES;
break;
case YEARLY:
intervalValue = INTERVAL_YEARS;
break;
default:
log.error("repeat-unhandled-rule", new Exception("Unhandled rrule frequency: " + recurrence));
}
} catch (Exception e) {
// invalid RRULE
recurrence = ""; //$NON-NLS-1$
log.error(e.getMessage(), e);
}
}
doRepeat = recurrence.length() > 0;
refreshDisplayView();
}
@Override
protected void readFromTaskOnInitialize() {
Date date;
if(model.getDueDate() != 0) {
date = newDate(model.getDueDate());
int dayOfWeek = date.getDay();
for(int i = 0; i < 7; i++) {
daysOfWeek[i].setChecked(i == dayOfWeek);
}
}
// read recurrence rule
if(recurrence.length() > 0) {
try {
RRule rrule = new RRule(recurrence);
setRepeatValue(rrule.getInterval());
setRepeatUntilValue(model.getRepeatUntil());
interval.setSelection(intervalValue);
// clear all day of week checks, then update them
for(int i = 0; i < 7; i++) {
daysOfWeek[i].setChecked(false);
}
for(WeekdayNum day : rrule.getByDay()) {
for(int i = 0; i < 7; i++) {
if (daysOfWeek[i].getTag().equals(day.wday)) {
daysOfWeek[i].setChecked(true);
}
}
}
} catch (Exception e) {
// invalid RRULE
recurrence = ""; //$NON-NLS-1$
log.error(e.getMessage(), e);
}
}
doRepeat = recurrence.length() > 0;
// read flag
if(model.repeatAfterCompletion()) {
type.setSelection(TYPE_COMPLETION_DATE);
} else {
type.setSelection(TYPE_DUE_DATE);
}
refreshDisplayView();
}
@Override
protected void afterInflate() {
value = (Button) getView().findViewById(R.id.repeatValue);
interval = (Spinner) getView().findViewById(R.id.repeatInterval);
interval.setAdapter(new ArrayAdapter<String>(activity, R.layout.simple_spinner_item, activity.getResources().getStringArray(R.array.repeat_interval)) {{
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
}});
type = (Spinner) getView().findViewById(R.id.repeatType);
type.setAdapter(new ArrayAdapter<String>(activity, R.layout.simple_spinner_item, activity.getResources().getStringArray(R.array.repeat_type)) {{
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
}});
daysOfWeekContainer = (LinearLayout) getView().findViewById(R.id.repeatDayOfWeekContainer);
repeatUntil = (Button) getView().findViewById(R.id.repeatUntil);
setRepeatValue(1);
setRepeatUntilValue(0);
// set up days of week
DateFormatSymbols dfs = new DateFormatSymbols();
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT, 1.0f/14);
LinearLayout.LayoutParams textLp = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT, 1.0f/14);
for(int i = 0; i < 7; i++) {
CheckBox checkBox = new CheckBox(activity);
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
checkBox.setPadding(0, 0, 0, 0);
checkBox.setLayoutParams(lp);
checkBox.setTag(Weekday.values()[dayOfWeek - 1]);
checkBox.setButtonDrawable(R.drawable.btn_check_small);
TextView label = new TextView(activity);
label.setTextAppearance(activity, R.style.TextAppearance);
label.setLayoutParams(textLp);
label.setTextSize(14);
label.setText(dfs.getShortWeekdays()[dayOfWeek].substring(0, 1));
daysOfWeek[i] = checkBox;
calendar.add(Calendar.DATE, 1);
daysOfWeekContainer.addView(checkBox);
daysOfWeekContainer.addView(label);
}
// set up listeners
value.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
repeatValueClick();
}
});
interval.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parentView, View view, int position, long id) {
daysOfWeekContainer.setVisibility(position == INTERVAL_WEEKS ? View.VISIBLE : View.GONE);
intervalValue = position;
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
//
}
});
repeatUntil.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
repeatUntilClick();
}
});
daysOfWeekContainer.setVisibility(View.GONE);
}
@Override
protected void writeToModelAfterInitialized(Task task) {
String result;
if(!doRepeat) {
result = ""; //$NON-NLS-1$
} else {
RRule rrule = new RRule();
rrule.setInterval(repeatValue);
switch(interval.getSelectedItemPosition()) {
case INTERVAL_DAYS:
rrule.setFreq(Frequency.DAILY);
break;
case INTERVAL_WEEKS: {
rrule.setFreq(Frequency.WEEKLY);
ArrayList<WeekdayNum> days = new ArrayList<>();
for (CompoundButton dayOfWeek : daysOfWeek) {
if (dayOfWeek.isChecked()) {
days.add(new WeekdayNum(0, (Weekday) dayOfWeek.getTag()));
}
}
rrule.setByDay(days);
break;
}
case INTERVAL_MONTHS:
rrule.setFreq(Frequency.MONTHLY);
break;
case INTERVAL_HOURS:
rrule.setFreq(Frequency.HOURLY);
break;
case INTERVAL_MINUTES:
rrule.setFreq(Frequency.MINUTELY);
break;
case INTERVAL_YEARS:
rrule.setFreq(Frequency.YEARLY);
break;
}
result = rrule.toIcal();
}
if (type.getSelectedItemPosition() == TYPE_COMPLETION_DATE && !TextUtils.isEmpty(result)) {
result = result + ";FROM=COMPLETION"; //$NON-NLS-1$
}
if (!result.equals(task.getRecurrence().replaceAll("BYDAY=;", ""))) //$NON-NLS-1$//$NON-NLS-2$
{
task.putTransitory(TaskService.TRANS_REPEAT_CHANGED, true);
}
task.setRecurrence(result);
task.setRepeatUntil(repeatUntilValue);
if(task.repeatAfterCompletion()) {
type.setSelection(1);
}
}
public boolean isRecurrenceSet() {
return doRepeat;
}
/**
* @return the recurrence display string if set, null
* if not set
*/
public String getStringForExternalDisplay() {
if (isRecurrenceSet()) {
return getRepeatString(false);
}
return null;
}
@Override
protected void refreshDisplayView() {
TextView repeatDisplay = (TextView) getDisplayView().findViewById(R.id.display_row_edit);
ImageView repeatImage = (ImageView) getDisplayView().findViewById(R.id.display_row_icon);
if (doRepeat) {
repeatDisplay.setText(getRepeatString(true));
repeatDisplay.setTextColor(themeColor);
repeatImage.setImageResource(R.drawable.tea_icn_repeat);
} else {
repeatDisplay.setTextColor(unsetColor);
repeatDisplay.setText(R.string.repeat_never);
repeatImage.setImageResource(R.drawable.tea_icn_repeat_gray);
}
}
private String getRepeatString(boolean useAbbrev) {
int arrayResource;
if (useAbbrev) {
arrayResource = R.array.repeat_interval_short;
} else {
arrayResource = R.array.repeat_interval;
}
String[] dates = activity.getResources().getStringArray(
arrayResource);
String date = String.format("%s %s", repeatValue, dates[intervalValue]); //$NON-NLS-1$
if (repeatUntilValue > 0) {
return activity.getString(R.string.repeat_detail_duedate_until, date, DateAndTimePicker.getDisplayString(activity, repeatUntilValue, useAbbrev, useAbbrev));
} else {
return activity.getString(R.string.repeat_detail_duedate, date); // Every freq int
}
}
@Override
protected Dialog buildDialog(String title, final PopupDialogClickListener okListener, final DialogInterface.OnCancelListener cancelListener) {
PopupDialogClickListener doRepeatButton = new PopupDialogClickListener() {
@Override
public boolean onClick(DialogInterface d, int which) {
doRepeat = true;
for (RepeatChangedListener l : listeners) {
l.repeatChanged(doRepeat);
}
return okListener.onClick(d, which);
}
};
final Dialog d = super.buildDialog(title, doRepeatButton, cancelListener);
View.OnClickListener dontRepeatButton = new View.OnClickListener() {
@Override
public void onClick(View view) {
doRepeat = false;
refreshDisplayView();
DialogUtilities.dismissDialog(activity, d);
for (RepeatChangedListener l : listeners) {
l.repeatChanged(doRepeat);
}
}
};
getView().findViewById(R.id.edit_dont_repeat).setOnClickListener(dontRepeatButton);
return d;
}
}