com.todoroo.astrid.repeats.RepeatControlSet.java Source code

Java tutorial

Introduction

Here is the source code for com.todoroo.astrid.repeats.RepeatControlSet.java

Source

/**
 * Copyright (c) 2012 Todoroo Inc
 *
 * See the file "LICENSE" for the full license governing this code.
 */
package com.todoroo.astrid.repeats;

import android.app.Dialog;
import android.content.DialogInterface;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog;
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.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;

import com.fourmob.datetimepicker.date.DatePickerDialog;
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 com.todoroo.astrid.ui.NumberPickerDialog;
import com.todoroo.astrid.ui.NumberPickerDialog.OnNumberPickedListener;
import com.todoroo.astrid.ui.PopupControlSet;

import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tasks.R;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.dialogs.MyDatePickerDialog;
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;
import static org.tasks.date.DateTimeUtils.newDateTime;

/**
 * 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);

    private static final String FRAG_TAG_REPEAT_UNTIL = "frag_tag_repeat_until";

    // --- 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 Spinner repeatUntil;
    private ArrayAdapter<String> repeatUntilAdapter;
    private final List<String> repeatUntilOptions = new ArrayList<>();
    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 {
        void repeatChanged(boolean repeat);
    }

    // --- implementation

    public RepeatControlSet(ActivityPreferences preferences, FragmentActivity activity,
            DialogBuilder dialogBuilder) {
        super(preferences, activity, R.layout.control_set_repeat, R.layout.control_set_repeat_display,
                R.string.repeat_enabled, dialogBuilder);
    }

    /** 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;
        updateRepeatUntilOptions();
    }

    protected void repeatValueClick() {
        int dialogValue = repeatValue;
        if (dialogValue == 0) {
            dialogValue = 1;
        }

        new NumberPickerDialog(activity, preferences.getDialogTheme(), 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() {
        MyDatePickerDialog dialog = new MyDatePickerDialog();
        DateTime initial = newDateTime();
        dialog.initialize(new DatePickerDialog.OnDateSetListener() {
            @Override
            public void onDateSet(DatePickerDialog datePickerDialog, int year, int month, int day) {
                setRepeatUntilValue(new DateTime(year, month + 1, day, 0, 0, 0, 0).getMillis());
            }
        }, initial.getYear(), initial.getMonthOfYear() - 1, initial.getDayOfMonth(), false);
        dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                setRepeatUntilValue(repeatUntilValue);
            }
        });
        dialog.show(activity.getSupportFragmentManager(), FRAG_TAG_REPEAT_UNTIL);
    }

    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
    public int getIcon() {
        return R.attr.ic_action_reload;
    }

    @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() {
        View dialogView = getDialogView();
        value = (Button) dialogView.findViewById(R.id.repeatValue);
        interval = (Spinner) dialogView.findViewById(R.id.repeatInterval);
        interval.setAdapter(new ArrayAdapter<String>(activity, android.R.layout.simple_spinner_item,
                activity.getResources().getStringArray(R.array.repeat_interval)) {
            {
                setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            }
        });
        type = (Spinner) dialogView.findViewById(R.id.repeatType);
        type.setAdapter(new ArrayAdapter<String>(activity, android.R.layout.simple_spinner_item,
                activity.getResources().getStringArray(R.array.repeat_type)) {
            {
                setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            }
        });
        daysOfWeekContainer = (LinearLayout) dialogView.findViewById(R.id.repeatDayOfWeekContainer);
        repeatUntil = (Spinner) dialogView.findViewById(R.id.repeat_until);
        repeatUntilAdapter = new ArrayAdapter<>(activity, android.R.layout.simple_spinner_item, repeatUntilOptions);
        repeatUntilAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        repeatUntil.setAdapter(repeatUntilAdapter);
        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.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                if (repeatUntilOptions.size() == 2) {
                    if (i == 0) {
                        setRepeatUntilValue(0);
                    } else {
                        repeatUntilClick();
                    }
                } else {
                    if (i == 1) {
                        setRepeatUntilValue(0);
                    } else if (i == 2) {
                        repeatUntilClick();
                    }
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {
                //
            }
        });

        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$
        }

        task.setRecurrence(result);
        task.setRepeatUntil(repeatUntilValue);

        if (task.repeatAfterCompletion()) {
            type.setSelection(1);
        }
    }

    @Override
    protected void refreshDisplayView() {
        TextView repeatDisplay = (TextView) getView().findViewById(R.id.display_row_edit);
        if (doRepeat) {
            repeatDisplay.setText(getRepeatString());
            repeatDisplay.setTextColor(themeColor);
        } else {
            repeatDisplay.setTextColor(unsetColor);
            repeatDisplay.setText(R.string.repeat_never);
        }
    }

    private String getRepeatString() {
        int 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, getDisplayString());
        } 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);
            }
        };

        return super.buildDialog(title, doRepeatButton, cancelListener);
    }

    @Override
    protected void additionalDialogSetup(AlertDialog.Builder builder) {
        super.additionalDialogSetup(builder);

        builder.setNeutralButton(R.string.repeat_dont, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                doRepeat = false;
                refreshDisplayView();
                dialog.dismiss();

                for (RepeatChangedListener l : listeners) {
                    l.repeatChanged(doRepeat);
                }
            }
        });
    }

    private void updateRepeatUntilOptions() {
        repeatUntilOptions.clear();
        if (repeatUntilValue > 0) {
            repeatUntilOptions.add(activity.getString(R.string.repeat_until, getDisplayString()));
        }
        repeatUntilOptions.add(activity.getString(R.string.repeat_forever));
        repeatUntilOptions.add(activity.getString(R.string.repeat_until, "").trim());
        repeatUntilAdapter.notifyDataSetChanged();
        repeatUntil.setSelection(0);
    }

    private String getDisplayString() {
        StringBuilder displayString = new StringBuilder();
        Date d = newDate(repeatUntilValue);
        if (d.getTime() > 0) {
            displayString.append(DateUtilities.getDateStringHideYear(d));
            if (Task.hasDueTime(repeatUntilValue)) {
                displayString.append(", "); //$NON-NLS-1$ //$NON-NLS-2$
                displayString.append(DateUtilities.getTimeString(activity, d));
            }
        }
        return displayString.toString();
    }
}