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.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
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.common.base.Strings;
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 org.tasks.R;
import org.tasks.activities.DatePickerActivity;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.ForActivity;
import org.tasks.injection.FragmentComponent;
import org.tasks.preferences.Preferences;
import org.tasks.themes.Theme;
import org.tasks.time.DateTime;
import org.tasks.ui.TaskEditControlFragment;

import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import javax.inject.Inject;

import butterknife.BindView;
import butterknife.OnClick;
import timber.log.Timber;

import static android.support.v4.content.ContextCompat.getColor;
import static org.tasks.date.DateTimeUtils.newDateTime;

/**
 * Control Set for managing repeats
 *
 * @author Tim Su <tim@todoroo.com>
 *
 */
public class RepeatControlSet extends TaskEditControlFragment {

    public static final int TAG = R.string.TEA_ctrl_repeat_pref;

    public interface RepeatChangedListener {
        void repeatChanged(boolean repeat);
    }

    private static final int REQUEST_PICK_DATE = 505;
    private static final String EXTRA_RECURRENCE = "extra_recurrence";
    private static final String EXTRA_REPEAT_UNTIL = "extra_repeat_until";
    private static final String EXTRA_REPEAT_AFTER_COMPLETION = "extra_repeat_after_completion";

    // --- spinner constants

    private static final int INTERVAL_DAYS = 0;
    private static final int INTERVAL_WEEKS = 1;
    private static final int INTERVAL_MONTHS = 2;
    private static final int INTERVAL_HOURS = 3;
    private static final int INTERVAL_MINUTES = 4;
    private 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 repeatUntil;

    @Inject
    DialogBuilder dialogBuilder;
    @Inject
    Preferences preferences;
    @Inject
    @ForActivity
    Context context;
    @Inject
    Theme theme;

    @BindView(R.id.clear)
    ImageView clear;
    @BindView(R.id.display_row_edit)
    TextView displayView;

    private ArrayAdapter<String> repeatUntilAdapter;
    private final List<String> repeatUntilOptions = new ArrayList<>();
    private LinearLayout daysOfWeekContainer;
    private final Weekday[] weekdays = new Weekday[7];
    private final boolean[] isChecked = new boolean[7];

    private String recurrence;
    private int repeatValue;
    private int intervalValue;
    private long repeatUntilValue;
    private View dialogView;
    private AlertDialog dialog;

    private RepeatChangedListener callback;

    private boolean repeatAfterCompletion;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = super.onCreateView(inflater, container, savedInstanceState);
        if (savedInstanceState != null) {
            recurrence = savedInstanceState.getString(EXTRA_RECURRENCE);
            repeatUntilValue = savedInstanceState.getLong(EXTRA_REPEAT_UNTIL);
            repeatAfterCompletion = savedInstanceState.getBoolean(EXTRA_REPEAT_AFTER_COMPLETION);
        }

        dialogView = inflater.inflate(R.layout.control_set_repeat, null);
        value = (Button) dialogView.findViewById(R.id.repeatValue);
        Spinner interval = (Spinner) dialogView.findViewById(R.id.repeatInterval);
        ArrayAdapter<String> intervalAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item,
                getResources().getStringArray(R.array.repeat_interval));
        intervalAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        interval.setAdapter(intervalAdapter);
        Spinner type = (Spinner) dialogView.findViewById(R.id.repeatType);
        ArrayAdapter<String> typeAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item,
                getResources().getStringArray(R.array.repeat_type));
        typeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        type.setAdapter(typeAdapter);
        type.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                repeatAfterCompletion = position == TYPE_COMPLETION_DATE;
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });
        daysOfWeekContainer = (LinearLayout) dialogView.findViewById(R.id.repeatDayOfWeekContainer);
        repeatUntil = (Spinner) dialogView.findViewById(R.id.repeat_until);
        repeatUntilAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, repeatUntilOptions);
        repeatUntilAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        repeatUntil.setAdapter(repeatUntilAdapter);
        // set up days of week
        DateFormatSymbols dfs = new DateFormatSymbols();
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());
        CompoundButton[] daysOfWeek = new CompoundButton[7];
        for (int i = 0; i < 7; i++) {
            final int index = i;
            CheckBox checkBox = (CheckBox) daysOfWeekContainer.getChildAt(i);
            checkBox.setOnCheckedChangeListener(
                    (buttonView, isChecked1) -> RepeatControlSet.this.isChecked[index] = isChecked1);
            int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
            checkBox.setText(dfs.getShortWeekdays()[dayOfWeek].substring(0, 1));
            daysOfWeek[i] = checkBox;
            weekdays[i] = Weekday.values()[dayOfWeek - 1];
            calendar.add(Calendar.DATE, 1);
        }

        // set up listeners
        value.setOnClickListener(v -> repeatValueClick());

        setRepeatValue(1);
        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) {
                //
            }
        });

        setRepeatUntilValue(repeatUntilValue);
        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);
        type.setSelection(repeatAfterCompletion ? TYPE_COMPLETION_DATE : TYPE_DUE_DATE);
        doRepeat = !Strings.isNullOrEmpty(recurrence);
        if (doRepeat) {
            // read recurrence rule
            try {
                RRule rrule = new RRule(recurrence);

                setRepeatValue(rrule.getInterval());

                for (WeekdayNum day : rrule.getByDay()) {
                    for (int i = 0; i < 7; i++) {
                        if (weekdays[i].equals(day.wday)) {
                            daysOfWeek[i].setChecked(true);
                        }
                    }
                }

                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:
                    Timber.e(new Exception("Unhandled rrule frequency: " + recurrence), "repeat-unhandled-rule");
                }
                interval.setSelection(intervalValue);

            } catch (Exception e) {
                // invalid RRULE
                recurrence = ""; //$NON-NLS-1$
                Timber.e(e, e.getMessage());
            }
        }
        refreshDisplayView();
        return view;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putString(EXTRA_RECURRENCE, getRecurrence());
        outState.putLong(EXTRA_REPEAT_UNTIL, repeatUntilValue);
        outState.putBoolean(EXTRA_REPEAT_AFTER_COMPLETION, repeatAfterCompletion);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        callback = (RepeatChangedListener) activity;
    }

    @Override
    protected void inject(FragmentComponent component) {
        component.inject(this);
    }

    @OnClick(R.id.clear)
    void clearRepeat(View view) {
        doRepeat = false;
        refreshDisplayView();
        callback.repeatChanged(doRepeat);
    }

    @OnClick(R.id.display_row_edit)
    void openPopup(View view) {
        if (dialog == null) {
            dialog = buildDialog();
        }
        dialog.show();
    }

    private AlertDialog buildDialog() {
        return dialogBuilder.newDialog().setView(dialogView)
                .setPositiveButton(android.R.string.ok, (dialog12, which) -> {
                    doRepeat = true;

                    callback.repeatChanged(doRepeat);

                    refreshDisplayView();
                }).setOnCancelListener(dialog1 -> refreshDisplayView()).create();
    }

    @Override
    protected int getLayout() {
        return R.layout.control_set_repeat_display;
    }

    @Override
    public int getIcon() {
        return R.drawable.ic_repeat_24dp;
    }

    @Override
    public int controlId() {
        return TAG;
    }

    @Override
    public void initialize(boolean isNewTask, Task task) {
        repeatAfterCompletion = task.repeatAfterCompletion();
        recurrence = task.sanitizedRecurrence();
        repeatUntilValue = task.getRepeatUntil();
    }

    @Override
    public boolean hasChanges(Task original) {
        return !getRecurrenceValue().equals(original.getRecurrence())
                || repeatUntilValue != original.getRepeatUntil();
    }

    @Override
    public void apply(Task task) {
        task.setRecurrence(getRecurrenceValue());
        task.setRepeatUntil(repeatUntilValue);
    }

    private String getRecurrenceValue() {
        String result = getRecurrence();

        if (repeatAfterCompletion && !TextUtils.isEmpty(result)) {
            result += ";FROM=COMPLETION"; //$NON-NLS-1$
        }

        return result;
    }

    private String getRecurrence() {
        String result;
        if (!doRepeat) {
            result = ""; //$NON-NLS-1$
        } else {
            RRule rrule = new RRule();
            rrule.setInterval(repeatValue);
            switch (intervalValue) {
            case INTERVAL_DAYS:
                rrule.setFreq(Frequency.DAILY);
                break;
            case INTERVAL_WEEKS: {
                rrule.setFreq(Frequency.WEEKLY);

                ArrayList<WeekdayNum> days = new ArrayList<>();
                for (int i = 0; i < isChecked.length; i++) {
                    if (isChecked[i]) {
                        days.add(new WeekdayNum(0, weekdays[i]));
                    }
                }
                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();
        }

        return result;
    }

    /** Set up the repeat value button */
    private void setRepeatValue(int newValue) {
        repeatValue = newValue;
        value.setText(getString(R.string.repeat_every, newValue));
    }

    private void setRepeatUntilValue(long newValue) {
        repeatUntilValue = newValue;
        updateRepeatUntilOptions();
    }

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

        NumberPickerDialog dialog = new NumberPickerDialog(theme.getThemedDialog(getActivity()),
                this::setRepeatValue, getResources().getString(R.string.repeat_interval_prompt), dialogValue, 1, 1,
                365);
        theme.applyToContext(dialog.getContext());
        dialog.show();
    }

    private void repeatUntilClick() {
        Intent intent = new Intent(context, DatePickerActivity.class);
        intent.putExtra(DatePickerActivity.EXTRA_TIMESTAMP, repeatUntilValue > 0 ? repeatUntilValue : 0L);
        startActivityForResult(intent, REQUEST_PICK_DATE);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_PICK_DATE) {
            if (resultCode == Activity.RESULT_OK) {
                setRepeatUntilValue(data.getLongExtra(DatePickerActivity.EXTRA_TIMESTAMP, 0L));
            } else {
                setRepeatUntilValue(repeatUntilValue);
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    private void refreshDisplayView() {
        if (doRepeat) {
            displayView.setText(getRepeatString());
            displayView.setTextColor(getColor(context, R.color.text_primary));
            clear.setVisibility(View.VISIBLE);
        } else {
            displayView.setText(R.string.repeat_never);
            displayView.setTextColor(getColor(context, R.color.text_tertiary));
            clear.setVisibility(View.GONE);
        }
    }

    private String getRepeatString() {
        int arrayResource = R.array.repeat_interval;

        String[] dates = getResources().getStringArray(arrayResource);
        String date = String.format("%s %s", repeatValue, dates[intervalValue]); //$NON-NLS-1$
        if (repeatUntilValue > 0) {
            return getString(R.string.repeat_detail_duedate_until, date, getDisplayString());
        } else {
            return getString(R.string.repeat_detail_duedate, date); // Every freq int
        }
    }

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

    private String getDisplayString() {
        StringBuilder displayString = new StringBuilder();
        DateTime d = newDateTime(repeatUntilValue);
        if (d.getMillis() > 0) {
            displayString.append(DateUtilities.getDateString(d));
            if (Task.hasDueTime(repeatUntilValue)) {
                displayString.append(", "); //$NON-NLS-1$ //$NON-NLS-2$
                displayString.append(DateUtilities.getTimeString(context, repeatUntilValue));
            }
        }
        return displayString.toString();
    }
}