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

495 lines
17 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.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();
}
}