/** * Copyright (c) 2012 Todoroo Inc * * See the file "LICENSE" for the full license governing this code. */ package com.todoroo.astrid.repeats; 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 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.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.timsu.astrid.R; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.service.StatisticsConstants; import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.service.ThemeService; import com.todoroo.astrid.ui.DateAndTimeDialog; import com.todoroo.astrid.ui.DateAndTimeDialog.DateAndTimeDialogListener; import com.todoroo.astrid.ui.DateAndTimePicker; import com.todoroo.astrid.ui.NumberPicker; import com.todoroo.astrid.ui.NumberPickerDialog; import com.todoroo.astrid.ui.NumberPickerDialog.OnNumberPickedListener; import com.todoroo.astrid.ui.PopupControlSet; /** * Control Set for managing repeats * * @author Tim Su <tim@todoroo.com> * */ public class RepeatControlSet extends PopupControlSet { // --- 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<RepeatChangedListener>(); public interface RepeatChangedListener { public void repeatChanged(boolean repeat); } @Autowired ExceptionService exceptionService; boolean setInterval = false; // --- implementation public RepeatControlSet(Activity activity, int viewLayout, int displayViewLayout, int title) { super(activity, viewLayout, displayViewLayout, title); DependencyInjectionService.getInstance().inject(this); } /** 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(NumberPicker view, int number) { setRepeatValue(number); } }, activity.getResources().getString(R.string.repeat_interval_prompt), dialogValue, 1, 1, 365).show(); } private void repeatUntilClick() { DateAndTimeDialog d = new DateAndTimeDialog(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); } public void removeListener(RepeatChangedListener listener) { if (listeners.contains(listener)) listeners.remove(listener); } @SuppressWarnings("nls") @Override public void readFromTask(Task task) { super.readFromTask(task); recurrence = model.sanitizedRecurrence(); if(recurrence == null) recurrence = ""; repeatUntilValue = model.getValue(Task.REPEAT_UNTIL); 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: // an unhandled recurrence exceptionService.reportError("repeat-unhandled-rule", //$NON-NLS-1$ new Exception("Unhandled rrule frequency: " + recurrence)); } } catch (Exception e) { // invalid RRULE recurrence = ""; //$NON-NLS-1$ exceptionService.reportError("repeat-parse-exception", e); } } doRepeat = recurrence.length() > 0; refreshDisplayView(); } @SuppressWarnings("nls") @Override protected void readFromTaskOnInitialize() { Date date; if(model.getValue(Task.DUE_DATE) != 0) { date = new Date(model.getValue(Task.DUE_DATE)); 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.getValue(Task.REPEAT_UNTIL)); 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); } // suppress first call to interval.onItemSelected setInterval = true; } catch (Exception e) { // invalid RRULE recurrence = ""; //$NON-NLS-1$ exceptionService.reportError("repeat-parse-exception", 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); type = (Spinner) getView().findViewById(R.id.repeatType); 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_GEN_EditLabel); 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() { 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 String writeToModelAfterInitialized(Task task) { String result; if(!doRepeat) result = ""; //$NON-NLS-1$ else { if(TextUtils.isEmpty(task.getValue(Task.RECURRENCE))) { StatisticsService.reportEvent(StatisticsConstants.REPEAT_TASK_CREATE); } 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<WeekdayNum>(); for(int i = 0; i < daysOfWeek.length; i++) if(daysOfWeek[i].isChecked()) days.add(new WeekdayNum(0, (Weekday)daysOfWeek[i].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.getValue(Task.RECURRENCE).replaceAll("BYDAY=;", ""))) //$NON-NLS-1$//$NON-NLS-2$ task.putTransitory(TaskService.TRANS_REPEAT_CHANGED, true); task.setValue(Task.RECURRENCE, result); task.setValue(Task.REPEAT_UNTIL, repeatUntilValue); if(task.repeatAfterCompletion()) type.setSelection(1); return null; } 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(ThemeService.getTaskEditDrawable(R.drawable.tea_icn_repeat, 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, false, 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; } }