mirror of https://github.com/tasks/tasks
Convert task edit screen to kotlin
parent
a6d10ed479
commit
47d414f20f
@ -1,185 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.todoroo.astrid.files;
|
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
|
||||||
import static org.tasks.dialogs.AddAttachmentDialog.REQUEST_AUDIO;
|
|
||||||
import static org.tasks.dialogs.AddAttachmentDialog.REQUEST_CAMERA;
|
|
||||||
import static org.tasks.dialogs.AddAttachmentDialog.REQUEST_GALLERY;
|
|
||||||
import static org.tasks.dialogs.AddAttachmentDialog.REQUEST_STORAGE;
|
|
||||||
import static org.tasks.dialogs.AddAttachmentDialog.newAddAttachmentDialog;
|
|
||||||
import static org.tasks.files.FileHelper.copyToUri;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.ClipData;
|
|
||||||
import android.content.ClipData.Item;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.data.TaskAttachment;
|
|
||||||
import org.tasks.data.TaskAttachmentDao;
|
|
||||||
import org.tasks.dialogs.DialogBuilder;
|
|
||||||
import org.tasks.files.FileHelper;
|
|
||||||
import org.tasks.injection.ForActivity;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
import org.tasks.ui.TaskEditControlFragment;
|
|
||||||
|
|
||||||
public class FilesControlSet extends TaskEditControlFragment {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_files_pref;
|
|
||||||
|
|
||||||
private static final String FRAG_TAG_ADD_ATTACHMENT_DIALOG = "frag_tag_add_attachment_dialog";
|
|
||||||
private static final char LEFT_TO_RIGHT_MARK = '\u200e';
|
|
||||||
|
|
||||||
@Inject TaskAttachmentDao taskAttachmentDao;
|
|
||||||
@Inject DialogBuilder dialogBuilder;
|
|
||||||
@Inject @ForActivity Context context;
|
|
||||||
@Inject Preferences preferences;
|
|
||||||
|
|
||||||
@BindView(R.id.attachment_container)
|
|
||||||
LinearLayout attachmentContainer;
|
|
||||||
|
|
||||||
@BindView(R.id.add_attachment)
|
|
||||||
TextView addAttachment;
|
|
||||||
|
|
||||||
private String taskUuid;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
|
|
||||||
taskUuid = task.getUuid();
|
|
||||||
|
|
||||||
for (TaskAttachment attachment : taskAttachmentDao.getAttachments(taskUuid)) {
|
|
||||||
addAttachment(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
if (task.hasTransitory(TaskAttachment.KEY)) {
|
|
||||||
for (Uri uri : (ArrayList<Uri>) task.getTransitory(TaskAttachment.KEY)) {
|
|
||||||
newAttachment(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.add_attachment)
|
|
||||||
void addAttachment() {
|
|
||||||
newAddAttachmentDialog(this).show(getParentFragmentManager(), FRAG_TAG_ADD_ATTACHMENT_DIALOG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.control_set_files;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIcon() {
|
|
||||||
return R.drawable.ic_outline_attachment_24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (requestCode == REQUEST_CAMERA || requestCode == REQUEST_AUDIO) {
|
|
||||||
if (resultCode == RESULT_OK) {
|
|
||||||
Uri uri = data.getData();
|
|
||||||
copyToAttachmentDirectory(uri);
|
|
||||||
FileHelper.delete(context, uri);
|
|
||||||
}
|
|
||||||
} else if (requestCode == REQUEST_STORAGE || requestCode == REQUEST_GALLERY) {
|
|
||||||
if (resultCode == RESULT_OK) {
|
|
||||||
ClipData clip = data.getClipData();
|
|
||||||
if (clip != null) {
|
|
||||||
for (int i = 0; i < clip.getItemCount(); i++) {
|
|
||||||
Item item = clip.getItemAt(i);
|
|
||||||
copyToAttachmentDirectory(item.getUri());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
copyToAttachmentDirectory(data.getData());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAttachment(TaskAttachment taskAttachment) {
|
|
||||||
View fileRow =
|
|
||||||
getActivity().getLayoutInflater().inflate(R.layout.file_row, attachmentContainer, false);
|
|
||||||
fileRow.setTag(taskAttachment);
|
|
||||||
attachmentContainer.addView(fileRow);
|
|
||||||
addAttachment(taskAttachment, fileRow);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAttachment(final TaskAttachment taskAttachment, final View fileRow) {
|
|
||||||
TextView nameView = fileRow.findViewById(R.id.file_text);
|
|
||||||
String name = LEFT_TO_RIGHT_MARK + taskAttachment.getName();
|
|
||||||
nameView.setText(name);
|
|
||||||
nameView.setOnClickListener(v -> showFile(taskAttachment));
|
|
||||||
View clearFile = fileRow.findViewById(R.id.clear);
|
|
||||||
clearFile.setOnClickListener(
|
|
||||||
v ->
|
|
||||||
dialogBuilder
|
|
||||||
.newDialog(R.string.premium_remove_file_confirm)
|
|
||||||
.setPositiveButton(
|
|
||||||
android.R.string.ok,
|
|
||||||
(dialog, which) -> {
|
|
||||||
taskAttachmentDao.delete(taskAttachment);
|
|
||||||
FileHelper.delete(context, taskAttachment.parseUri());
|
|
||||||
attachmentContainer.removeView(fileRow);
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
private void showFile(final TaskAttachment m) {
|
|
||||||
FileHelper.startActionView(getActivity(), m.parseUri());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copyToAttachmentDirectory(Uri input) {
|
|
||||||
newAttachment(copyToUri(context, preferences.getAttachmentsDirectory(), input));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void newAttachment(Uri output) {
|
|
||||||
TaskAttachment attachment =
|
|
||||||
new TaskAttachment(taskUuid, output, FileHelper.getFilename(context, output));
|
|
||||||
taskAttachmentDao.createNew(attachment);
|
|
||||||
addAttachment(attachment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Todoroo Inc
|
||||||
|
*
|
||||||
|
* See the file "LICENSE" for the full license governing this code.
|
||||||
|
*/
|
||||||
|
package com.todoroo.astrid.files
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.OnClick
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.data.TaskAttachment
|
||||||
|
import org.tasks.data.TaskAttachmentDao
|
||||||
|
import org.tasks.dialogs.AddAttachmentDialog
|
||||||
|
import org.tasks.dialogs.DialogBuilder
|
||||||
|
import org.tasks.files.FileHelper
|
||||||
|
import org.tasks.injection.ForActivity
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import org.tasks.ui.TaskEditControlFragment
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class FilesControlSet : TaskEditControlFragment() {
|
||||||
|
@Inject @ForActivity lateinit var activity: Context
|
||||||
|
@Inject lateinit var taskAttachmentDao: TaskAttachmentDao
|
||||||
|
@Inject lateinit var dialogBuilder: DialogBuilder
|
||||||
|
@Inject lateinit var preferences: Preferences
|
||||||
|
|
||||||
|
@BindView(R.id.attachment_container)
|
||||||
|
lateinit var attachmentContainer: LinearLayout
|
||||||
|
|
||||||
|
@BindView(R.id.add_attachment)
|
||||||
|
lateinit var addAttachment: TextView
|
||||||
|
|
||||||
|
private var taskUuid: String? = null
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
taskUuid = task.uuid
|
||||||
|
for (attachment in taskAttachmentDao.getAttachments(taskUuid!!)) {
|
||||||
|
addAttachment(attachment)
|
||||||
|
}
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
if (task.hasTransitory(TaskAttachment.KEY)) {
|
||||||
|
for (uri in (task.getTransitory<ArrayList<Uri>>(TaskAttachment.KEY))!!) {
|
||||||
|
newAttachment(uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.add_attachment)
|
||||||
|
fun addAttachment() {
|
||||||
|
AddAttachmentDialog.newAddAttachmentDialog(this).show(parentFragmentManager, FRAG_TAG_ADD_ATTACHMENT_DIALOG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.control_set_files
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_outline_attachment_24px
|
||||||
|
|
||||||
|
override fun controlId() = TAG
|
||||||
|
|
||||||
|
override fun apply(task: Task) {}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (requestCode == AddAttachmentDialog.REQUEST_CAMERA || requestCode == AddAttachmentDialog.REQUEST_AUDIO) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
val uri = data!!.data
|
||||||
|
copyToAttachmentDirectory(uri)
|
||||||
|
FileHelper.delete(activity, uri)
|
||||||
|
}
|
||||||
|
} else if (requestCode == AddAttachmentDialog.REQUEST_STORAGE || requestCode == AddAttachmentDialog.REQUEST_GALLERY) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
val clip = data!!.clipData
|
||||||
|
if (clip != null) {
|
||||||
|
for (i in 0 until clip.itemCount) {
|
||||||
|
val item = clip.getItemAt(i)
|
||||||
|
copyToAttachmentDirectory(item.uri)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
copyToAttachmentDirectory(data.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addAttachment(taskAttachment: TaskAttachment) {
|
||||||
|
val fileRow = requireActivity().layoutInflater.inflate(R.layout.file_row, attachmentContainer, false)
|
||||||
|
fileRow.tag = taskAttachment
|
||||||
|
attachmentContainer.addView(fileRow)
|
||||||
|
addAttachment(taskAttachment, fileRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addAttachment(taskAttachment: TaskAttachment, fileRow: View) {
|
||||||
|
val nameView = fileRow.findViewById<TextView>(R.id.file_text)
|
||||||
|
val name = LEFT_TO_RIGHT_MARK.toString() + taskAttachment.name
|
||||||
|
nameView.text = name
|
||||||
|
nameView.setOnClickListener { showFile(taskAttachment) }
|
||||||
|
val clearFile = fileRow.findViewById<View>(R.id.clear)
|
||||||
|
clearFile.setOnClickListener {
|
||||||
|
dialogBuilder
|
||||||
|
.newDialog(R.string.premium_remove_file_confirm)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
taskAttachmentDao.delete(taskAttachment)
|
||||||
|
FileHelper.delete(context, taskAttachment.parseUri())
|
||||||
|
attachmentContainer.removeView(fileRow)
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) = component.inject(this)
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
private fun showFile(m: TaskAttachment) {
|
||||||
|
FileHelper.startActionView(requireActivity(), m.parseUri())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyToAttachmentDirectory(input: Uri?) {
|
||||||
|
newAttachment(FileHelper.copyToUri(context, preferences.attachmentsDirectory, input))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun newAttachment(output: Uri) {
|
||||||
|
val attachment = TaskAttachment(taskUuid!!, output, FileHelper.getFilename(context, output)!!)
|
||||||
|
taskAttachmentDao.createNew(attachment)
|
||||||
|
addAttachment(attachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_files_pref
|
||||||
|
private const val FRAG_TAG_ADD_ATTACHMENT_DIALOG = "frag_tag_add_attachment_dialog"
|
||||||
|
private const val LEFT_TO_RIGHT_MARK = '\u200e'
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,259 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.todoroo.astrid.repeats;
|
|
||||||
|
|
||||||
import static com.google.common.collect.Lists.newArrayList;
|
|
||||||
import static com.google.ical.values.Frequency.MONTHLY;
|
|
||||||
import static org.tasks.Strings.isNullOrEmpty;
|
|
||||||
import static org.tasks.repeats.BasicRecurrenceDialog.newBasicRecurrenceDialog;
|
|
||||||
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.Spinner;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.OnItemSelected;
|
|
||||||
import com.google.ical.values.RRule;
|
|
||||||
import com.google.ical.values.WeekdayNum;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.Strings;
|
|
||||||
import org.tasks.analytics.Firebase;
|
|
||||||
import org.tasks.dialogs.DialogBuilder;
|
|
||||||
import org.tasks.injection.ForActivity;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.repeats.RepeatRuleToString;
|
|
||||||
import org.tasks.themes.Theme;
|
|
||||||
import org.tasks.time.DateTime;
|
|
||||||
import org.tasks.ui.HiddenTopArrayAdapter;
|
|
||||||
import org.tasks.ui.TaskEditControlFragment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
private static final int TYPE_DUE_DATE = 1;
|
|
||||||
private static final int TYPE_COMPLETION_DATE = 2;
|
|
||||||
private static final String FRAG_TAG_BASIC_RECURRENCE = "frag_tag_basic_recurrence";
|
|
||||||
private static final String EXTRA_RECURRENCE = "extra_recurrence";
|
|
||||||
private static final String EXTRA_DUE_DATE = "extra_due_date";
|
|
||||||
private static final String EXTRA_REPEAT_AFTER_COMPLETION = "extra_repeat_after_completion";
|
|
||||||
private final List<String> repeatTypes = new ArrayList<>();
|
|
||||||
@Inject DialogBuilder dialogBuilder;
|
|
||||||
@Inject @ForActivity Context context;
|
|
||||||
@Inject Theme theme;
|
|
||||||
@Inject Firebase firebase;
|
|
||||||
@Inject RepeatRuleToString repeatRuleToString;
|
|
||||||
|
|
||||||
@BindView(R.id.display_row_edit)
|
|
||||||
TextView displayView;
|
|
||||||
|
|
||||||
@BindView(R.id.repeatType)
|
|
||||||
Spinner typeSpinner;
|
|
||||||
|
|
||||||
@BindView(R.id.repeatTypeContainer)
|
|
||||||
LinearLayout repeatTypeContainer;
|
|
||||||
|
|
||||||
private RRule rrule;
|
|
||||||
private HiddenTopArrayAdapter<String> typeAdapter;
|
|
||||||
private long dueDate;
|
|
||||||
private boolean repeatAfterCompletion;
|
|
||||||
|
|
||||||
public void onSelected(RRule rrule) {
|
|
||||||
this.rrule = rrule;
|
|
||||||
refreshDisplayView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onDueDateChanged(long dueDate) {
|
|
||||||
this.dueDate = dueDate > 0 ? dueDate : currentTimeMillis();
|
|
||||||
if (rrule != null && rrule.getFreq() == MONTHLY && !rrule.getByDay().isEmpty()) {
|
|
||||||
WeekdayNum weekdayNum = rrule.getByDay().get(0);
|
|
||||||
DateTime dateTime = new DateTime(this.dueDate);
|
|
||||||
int num;
|
|
||||||
int dayOfWeekInMonth = dateTime.getDayOfWeekInMonth();
|
|
||||||
if (weekdayNum.num == -1 || dayOfWeekInMonth == 5) {
|
|
||||||
num = dayOfWeekInMonth == dateTime.getMaxDayOfWeekInMonth() ? -1 : dayOfWeekInMonth;
|
|
||||||
} else {
|
|
||||||
num = dayOfWeekInMonth;
|
|
||||||
}
|
|
||||||
rrule.setByDay(newArrayList(new WeekdayNum(num, dateTime.getWeekday())));
|
|
||||||
refreshDisplayView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
repeatAfterCompletion = task.repeatAfterCompletion();
|
|
||||||
dueDate = task.getDueDate();
|
|
||||||
if (dueDate <= 0) {
|
|
||||||
dueDate = currentTimeMillis();
|
|
||||||
}
|
|
||||||
String recurrenceWithoutFrom = task.getRecurrenceWithoutFrom();
|
|
||||||
if (Strings.isNullOrEmpty(recurrenceWithoutFrom)) {
|
|
||||||
rrule = null;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
rrule = new RRule(recurrenceWithoutFrom);
|
|
||||||
rrule.setUntil(new DateTime(task.getRepeatUntil()).toDateValue());
|
|
||||||
} catch (ParseException e) {
|
|
||||||
rrule = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String recurrence = savedInstanceState.getString(EXTRA_RECURRENCE);
|
|
||||||
dueDate = savedInstanceState.getLong(EXTRA_DUE_DATE);
|
|
||||||
if (isNullOrEmpty(recurrence)) {
|
|
||||||
rrule = null;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
rrule = new RRule(recurrence);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
rrule = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
repeatAfterCompletion = savedInstanceState.getBoolean(EXTRA_REPEAT_AFTER_COMPLETION);
|
|
||||||
}
|
|
||||||
|
|
||||||
repeatTypes.add("");
|
|
||||||
repeatTypes.addAll(Arrays.asList(getResources().getStringArray(R.array.repeat_type)));
|
|
||||||
typeAdapter =
|
|
||||||
new HiddenTopArrayAdapter<String>(context, 0, repeatTypes) {
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
|
||||||
int selectedItemPosition = position;
|
|
||||||
if (parent instanceof AdapterView) {
|
|
||||||
selectedItemPosition = ((AdapterView) parent).getSelectedItemPosition();
|
|
||||||
}
|
|
||||||
TextView tv =
|
|
||||||
(TextView) inflater.inflate(android.R.layout.simple_spinner_item, parent, false);
|
|
||||||
tv.setPadding(0, 0, 0, 0);
|
|
||||||
tv.setText(repeatTypes.get(selectedItemPosition));
|
|
||||||
return tv;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Drawable drawable = context.getDrawable(R.drawable.textfield_underline_black).mutate();
|
|
||||||
drawable.setTint(context.getColor(R.color.text_primary));
|
|
||||||
typeSpinner.setBackgroundDrawable(drawable);
|
|
||||||
typeSpinner.setAdapter(typeAdapter);
|
|
||||||
typeSpinner.setSelection(repeatAfterCompletion ? TYPE_COMPLETION_DATE : TYPE_DUE_DATE);
|
|
||||||
|
|
||||||
refreshDisplayView();
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnItemSelected(R.id.repeatType)
|
|
||||||
public void onRepeatTypeChanged(int position) {
|
|
||||||
repeatAfterCompletion = position == TYPE_COMPLETION_DATE;
|
|
||||||
repeatTypes.set(0, repeatAfterCompletion ? repeatTypes.get(2) : repeatTypes.get(1));
|
|
||||||
typeAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putString(EXTRA_RECURRENCE, rrule == null ? "" : rrule.toIcal());
|
|
||||||
outState.putBoolean(EXTRA_REPEAT_AFTER_COMPLETION, repeatAfterCompletion);
|
|
||||||
outState.putLong(EXTRA_DUE_DATE, dueDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRowClick() {
|
|
||||||
newBasicRecurrenceDialog(this, rrule, dueDate)
|
|
||||||
.show(getParentFragmentManager(), FRAG_TAG_BASIC_RECURRENCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isClickable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.control_set_repeat_display;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIcon() {
|
|
||||||
return R.drawable.ic_outline_repeat_24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasChanges(Task original) {
|
|
||||||
return !getRecurrenceValue().equals(original.getRecurrence())
|
|
||||||
|| original.getRepeatUntil()
|
|
||||||
!= (rrule == null ? 0 : DateTime.from(rrule.getUntil()).getMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {
|
|
||||||
task.setRepeatUntil(rrule == null ? 0 : DateTime.from(rrule.getUntil()).getMillis());
|
|
||||||
task.setRecurrence(getRecurrenceValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getRecurrenceValue() {
|
|
||||||
if (rrule == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
RRule copy;
|
|
||||||
try {
|
|
||||||
copy = new RRule(rrule.toIcal());
|
|
||||||
} catch (ParseException e) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
copy.setUntil(null);
|
|
||||||
String result = copy.toIcal();
|
|
||||||
if (repeatAfterCompletion && !isNullOrEmpty(result)) {
|
|
||||||
result += ";FROM=COMPLETION"; // $NON-NLS-1$
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshDisplayView() {
|
|
||||||
if (rrule == null) {
|
|
||||||
displayView.setText(null);
|
|
||||||
repeatTypeContainer.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
displayView.setText(repeatRuleToString.toString(rrule));
|
|
||||||
repeatTypeContainer.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,227 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Todoroo Inc
|
||||||
|
*
|
||||||
|
* See the file "LICENSE" for the full license governing this code.
|
||||||
|
*/
|
||||||
|
package com.todoroo.astrid.repeats
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.OnItemSelected
|
||||||
|
import com.google.ical.values.Frequency
|
||||||
|
import com.google.ical.values.RRule
|
||||||
|
import com.google.ical.values.WeekdayNum
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.Strings.isNullOrEmpty
|
||||||
|
import org.tasks.analytics.Firebase
|
||||||
|
import org.tasks.dialogs.DialogBuilder
|
||||||
|
import org.tasks.injection.ForActivity
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.repeats.BasicRecurrenceDialog
|
||||||
|
import org.tasks.repeats.RepeatRuleToString
|
||||||
|
import org.tasks.themes.Theme
|
||||||
|
import org.tasks.time.DateTime
|
||||||
|
import org.tasks.time.DateTimeUtils
|
||||||
|
import org.tasks.ui.HiddenTopArrayAdapter
|
||||||
|
import org.tasks.ui.TaskEditControlFragment
|
||||||
|
import java.text.ParseException
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control Set for managing repeats
|
||||||
|
*
|
||||||
|
* @author Tim Su <tim></tim>@todoroo.com>
|
||||||
|
*/
|
||||||
|
class RepeatControlSet : TaskEditControlFragment() {
|
||||||
|
private val repeatTypes: MutableList<String> = ArrayList()
|
||||||
|
|
||||||
|
@Inject @ForActivity lateinit var activity: Context
|
||||||
|
@Inject lateinit var dialogBuilder: DialogBuilder
|
||||||
|
@Inject lateinit var theme: Theme
|
||||||
|
@Inject lateinit var firebase: Firebase
|
||||||
|
@Inject lateinit var repeatRuleToString: RepeatRuleToString
|
||||||
|
|
||||||
|
@BindView(R.id.display_row_edit)
|
||||||
|
lateinit var displayView: TextView
|
||||||
|
|
||||||
|
@BindView(R.id.repeatType)
|
||||||
|
lateinit var typeSpinner: Spinner
|
||||||
|
|
||||||
|
@BindView(R.id.repeatTypeContainer)
|
||||||
|
lateinit var repeatTypeContainer: LinearLayout
|
||||||
|
|
||||||
|
private var rrule: RRule? = null
|
||||||
|
private lateinit var typeAdapter: HiddenTopArrayAdapter<String>
|
||||||
|
private var dueDate: Long = 0
|
||||||
|
private var repeatAfterCompletion = false
|
||||||
|
|
||||||
|
fun onSelected(rrule: RRule?) {
|
||||||
|
this.rrule = rrule
|
||||||
|
refreshDisplayView()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDueDateChanged(dueDate: Long) {
|
||||||
|
this.dueDate = if (dueDate > 0) dueDate else DateTimeUtils.currentTimeMillis()
|
||||||
|
if (rrule != null && rrule!!.freq == Frequency.MONTHLY && rrule!!.byDay.isNotEmpty()) {
|
||||||
|
val weekdayNum = rrule!!.byDay[0]
|
||||||
|
val dateTime = DateTime(this.dueDate)
|
||||||
|
val num: Int
|
||||||
|
val dayOfWeekInMonth = dateTime.dayOfWeekInMonth
|
||||||
|
num = if (weekdayNum.num == -1 || dayOfWeekInMonth == 5) {
|
||||||
|
if (dayOfWeekInMonth == dateTime.maxDayOfWeekInMonth) -1 else dayOfWeekInMonth
|
||||||
|
} else {
|
||||||
|
dayOfWeekInMonth
|
||||||
|
}
|
||||||
|
rrule!!.byDay = listOf((WeekdayNum(num, dateTime.weekday)))
|
||||||
|
refreshDisplayView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
repeatAfterCompletion = task.repeatAfterCompletion()
|
||||||
|
dueDate = task.dueDate
|
||||||
|
if (dueDate <= 0) {
|
||||||
|
dueDate = DateTimeUtils.currentTimeMillis()
|
||||||
|
}
|
||||||
|
val recurrenceWithoutFrom = task.getRecurrenceWithoutFrom()
|
||||||
|
if (isNullOrEmpty(recurrenceWithoutFrom)) {
|
||||||
|
rrule = null
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
rrule = RRule(recurrenceWithoutFrom)
|
||||||
|
rrule!!.until = DateTime(task.repeatUntil).toDateValue()
|
||||||
|
} catch (e: ParseException) {
|
||||||
|
rrule = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val recurrence = savedInstanceState.getString(EXTRA_RECURRENCE)
|
||||||
|
dueDate = savedInstanceState.getLong(EXTRA_DUE_DATE)
|
||||||
|
rrule = if (isNullOrEmpty(recurrence)) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
RRule(recurrence)
|
||||||
|
} catch (e: ParseException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repeatAfterCompletion = savedInstanceState.getBoolean(EXTRA_REPEAT_AFTER_COMPLETION)
|
||||||
|
}
|
||||||
|
repeatTypes.add("")
|
||||||
|
repeatTypes.addAll(listOf(*resources.getStringArray(R.array.repeat_type)))
|
||||||
|
typeAdapter = object : HiddenTopArrayAdapter<String>(activity, 0, repeatTypes) {
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
var selectedItemPosition = position
|
||||||
|
if (parent is AdapterView<*>) {
|
||||||
|
selectedItemPosition = parent.selectedItemPosition
|
||||||
|
}
|
||||||
|
val tv = inflater.inflate(android.R.layout.simple_spinner_item, parent, false) as TextView
|
||||||
|
tv.setPadding(0, 0, 0, 0)
|
||||||
|
tv.text = repeatTypes[selectedItemPosition]
|
||||||
|
return tv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val drawable = activity.getDrawable(R.drawable.textfield_underline_black)!!.mutate()
|
||||||
|
drawable.setTint(activity.getColor(R.color.text_primary))
|
||||||
|
typeSpinner.setBackgroundDrawable(drawable)
|
||||||
|
typeSpinner.adapter = typeAdapter
|
||||||
|
typeSpinner.setSelection(if (repeatAfterCompletion) TYPE_COMPLETION_DATE else TYPE_DUE_DATE)
|
||||||
|
refreshDisplayView()
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnItemSelected(R.id.repeatType)
|
||||||
|
fun onRepeatTypeChanged(position: Int) {
|
||||||
|
repeatAfterCompletion = position == TYPE_COMPLETION_DATE
|
||||||
|
repeatTypes[0] = if (repeatAfterCompletion) repeatTypes[2] else repeatTypes[1]
|
||||||
|
typeAdapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putString(EXTRA_RECURRENCE, if (rrule == null) "" else rrule!!.toIcal())
|
||||||
|
outState.putBoolean(EXTRA_REPEAT_AFTER_COMPLETION, repeatAfterCompletion)
|
||||||
|
outState.putLong(EXTRA_DUE_DATE, dueDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) = component.inject(this)
|
||||||
|
|
||||||
|
override fun onRowClick() {
|
||||||
|
BasicRecurrenceDialog.newBasicRecurrenceDialog(this, rrule, dueDate)
|
||||||
|
.show(parentFragmentManager, FRAG_TAG_BASIC_RECURRENCE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isClickable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.control_set_repeat_display
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_outline_repeat_24px
|
||||||
|
|
||||||
|
override fun controlId() = TAG
|
||||||
|
|
||||||
|
override fun hasChanges(original: Task): Boolean {
|
||||||
|
return (recurrenceValue != original.recurrence
|
||||||
|
|| original.repeatUntil
|
||||||
|
!= if (rrule == null) 0 else DateTime.from(rrule!!.until).millis)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(task: Task) {
|
||||||
|
task.repeatUntil = if (rrule == null) 0 else DateTime.from(rrule!!.until).millis
|
||||||
|
task.recurrence = recurrenceValue
|
||||||
|
}
|
||||||
|
|
||||||
|
private val recurrenceValue: String
|
||||||
|
get() {
|
||||||
|
if (rrule == null) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
val copy: RRule = try {
|
||||||
|
RRule(rrule!!.toIcal())
|
||||||
|
} catch (e: ParseException) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
copy.until = null
|
||||||
|
var result = copy.toIcal()
|
||||||
|
if (repeatAfterCompletion && !isNullOrEmpty(result)) {
|
||||||
|
result += ";FROM=COMPLETION" // $NON-NLS-1$
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshDisplayView() {
|
||||||
|
if (rrule == null) {
|
||||||
|
displayView.text = null
|
||||||
|
repeatTypeContainer.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
displayView.text = repeatRuleToString.toString(rrule)
|
||||||
|
repeatTypeContainer.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_repeat_pref
|
||||||
|
private const val TYPE_DUE_DATE = 1
|
||||||
|
private const val TYPE_COMPLETION_DATE = 2
|
||||||
|
private const val FRAG_TAG_BASIC_RECURRENCE = "frag_tag_basic_recurrence"
|
||||||
|
private const val EXTRA_RECURRENCE = "extra_recurrence"
|
||||||
|
private const val EXTRA_DUE_DATE = "extra_due_date"
|
||||||
|
private const val EXTRA_REPEAT_AFTER_COMPLETION = "extra_repeat_after_completion"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,188 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.todoroo.astrid.tags;
|
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
|
||||||
import static com.google.common.collect.FluentIterable.from;
|
|
||||||
import static com.google.common.collect.Lists.newArrayList;
|
|
||||||
import static com.google.common.collect.Sets.newHashSet;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import com.google.android.material.chip.Chip;
|
|
||||||
import com.google.android.material.chip.ChipGroup;
|
|
||||||
import com.google.common.base.Predicates;
|
|
||||||
import com.google.common.collect.Ordering;
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Set;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.data.TagDao;
|
|
||||||
import org.tasks.data.TagData;
|
|
||||||
import org.tasks.data.TagDataDao;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.tags.TagPickerActivity;
|
|
||||||
import org.tasks.ui.ChipProvider;
|
|
||||||
import org.tasks.ui.TaskEditControlFragment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Control set to manage adding and removing tags
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*/
|
|
||||||
public final class TagsControlSet extends TaskEditControlFragment {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_lists_pref;
|
|
||||||
|
|
||||||
private static final String EXTRA_ORIGINAL_TAGS = "extra_original_tags";
|
|
||||||
private static final String EXTRA_SELECTED_TAGS = "extra_selected_tags";
|
|
||||||
private static final int REQUEST_TAG_PICKER_ACTIVITY = 10582;
|
|
||||||
private final Ordering<TagData> orderByName =
|
|
||||||
new Ordering<TagData>() {
|
|
||||||
@Override
|
|
||||||
public int compare(TagData left, TagData right) {
|
|
||||||
return left.getName().compareTo(right.getName());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@Inject TagDao tagDao;
|
|
||||||
@Inject TagDataDao tagDataDao;
|
|
||||||
@Inject ChipProvider chipProvider;
|
|
||||||
|
|
||||||
@BindView(R.id.no_tags)
|
|
||||||
TextView tagsDisplay;
|
|
||||||
@BindView(R.id.chip_group)
|
|
||||||
ChipGroup chipGroup;
|
|
||||||
|
|
||||||
private ArrayList<TagData> originalTags;
|
|
||||||
private ArrayList<TagData> selectedTags;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
selectedTags = savedInstanceState.getParcelableArrayList(EXTRA_SELECTED_TAGS);
|
|
||||||
originalTags = savedInstanceState.getParcelableArrayList(EXTRA_ORIGINAL_TAGS);
|
|
||||||
} else {
|
|
||||||
originalTags =
|
|
||||||
newArrayList(
|
|
||||||
task.isNew()
|
|
||||||
? from(task.getTags())
|
|
||||||
.transform(tagDataDao::getTagByName)
|
|
||||||
.filter(Predicates.notNull())
|
|
||||||
: tagDataDao.getTagDataForTask(task.getId()));
|
|
||||||
selectedTags = new ArrayList<>(originalTags);
|
|
||||||
}
|
|
||||||
refreshDisplayView();
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putParcelableArrayList(EXTRA_SELECTED_TAGS, selectedTags);
|
|
||||||
outState.putParcelableArrayList(EXTRA_ORIGINAL_TAGS, originalTags);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.control_set_tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {
|
|
||||||
if (tagDao.applyTags(task, tagDataDao, selectedTags)) {
|
|
||||||
task.setModificationDate(DateUtilities.now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRowClick() {
|
|
||||||
Intent intent = new Intent(getContext(), TagPickerActivity.class);
|
|
||||||
intent.putParcelableArrayListExtra(TagPickerActivity.EXTRA_SELECTED, selectedTags);
|
|
||||||
startActivityForResult(intent, REQUEST_TAG_PICKER_ACTIVITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isClickable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIcon() {
|
|
||||||
return R.drawable.ic_outline_label_24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasChanges(Task original) {
|
|
||||||
Set<TagData> originalSet = newHashSet(originalTags);
|
|
||||||
Set<TagData> selectedSet = newHashSet(selectedTags);
|
|
||||||
return !originalSet.equals(selectedSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshDisplayView() {
|
|
||||||
if (selectedTags.isEmpty()) {
|
|
||||||
chipGroup.setVisibility(View.GONE);
|
|
||||||
tagsDisplay.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
tagsDisplay.setVisibility(View.GONE);
|
|
||||||
chipGroup.setVisibility(View.VISIBLE);
|
|
||||||
chipGroup.removeAllViews();
|
|
||||||
for (TagData tagData : orderByName.sortedCopy(selectedTags)) {
|
|
||||||
if (tagData == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Chip chip = chipProvider.newClosableChip(tagData);
|
|
||||||
chipProvider.apply(chip, tagData);
|
|
||||||
chip.setOnClickListener(view -> onRowClick());
|
|
||||||
chip.setOnCloseIconClickListener(
|
|
||||||
view -> {
|
|
||||||
selectedTags.remove(tagData);
|
|
||||||
refreshDisplayView();
|
|
||||||
});
|
|
||||||
chipGroup.addView(chip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
|
||||||
if (requestCode == REQUEST_TAG_PICKER_ACTIVITY) {
|
|
||||||
if (resultCode == RESULT_OK && data != null) {
|
|
||||||
selectedTags = data.getParcelableArrayListExtra(TagPickerActivity.EXTRA_SELECTED);
|
|
||||||
refreshDisplayView();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requiresId() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Todoroo Inc
|
||||||
|
*
|
||||||
|
* See the file "LICENSE" for the full license governing this code.
|
||||||
|
*/
|
||||||
|
package com.todoroo.astrid.tags
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import butterknife.BindView
|
||||||
|
import com.google.android.material.chip.ChipGroup
|
||||||
|
import com.google.common.collect.Ordering
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.data.TagDao
|
||||||
|
import org.tasks.data.TagData
|
||||||
|
import org.tasks.data.TagDataDao
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.tags.TagPickerActivity
|
||||||
|
import org.tasks.ui.ChipProvider
|
||||||
|
import org.tasks.ui.TaskEditControlFragment
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control set to manage adding and removing tags
|
||||||
|
*
|
||||||
|
* @author Tim Su <tim></tim>@todoroo.com>
|
||||||
|
*/
|
||||||
|
class TagsControlSet : TaskEditControlFragment() {
|
||||||
|
private val orderByName: Ordering<TagData> = object : Ordering<TagData>() {
|
||||||
|
override fun compare(left: TagData?, right: TagData?): Int {
|
||||||
|
return left!!.name!!.compareTo(right!!.name!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject lateinit var tagDao: TagDao
|
||||||
|
@Inject lateinit var tagDataDao: TagDataDao
|
||||||
|
@Inject lateinit var chipProvider: ChipProvider
|
||||||
|
|
||||||
|
@BindView(R.id.no_tags)
|
||||||
|
lateinit var tagsDisplay: TextView
|
||||||
|
|
||||||
|
@BindView(R.id.chip_group)
|
||||||
|
lateinit var chipGroup: ChipGroup
|
||||||
|
|
||||||
|
private lateinit var originalTags: ArrayList<TagData>
|
||||||
|
private lateinit var selectedTags: ArrayList<TagData>
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
selectedTags = savedInstanceState.getParcelableArrayList(EXTRA_SELECTED_TAGS)!!
|
||||||
|
originalTags = savedInstanceState.getParcelableArrayList(EXTRA_ORIGINAL_TAGS)!!
|
||||||
|
} else {
|
||||||
|
originalTags = ArrayList(if (task.isNew) {
|
||||||
|
task.tags.mapNotNull(tagDataDao::getTagByName)
|
||||||
|
} else {
|
||||||
|
tagDataDao.getTagDataForTask(task.id)
|
||||||
|
})
|
||||||
|
selectedTags = ArrayList(originalTags)
|
||||||
|
}
|
||||||
|
refreshDisplayView()
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putParcelableArrayList(EXTRA_SELECTED_TAGS, selectedTags)
|
||||||
|
outState.putParcelableArrayList(EXTRA_ORIGINAL_TAGS, originalTags)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.control_set_tags
|
||||||
|
|
||||||
|
override fun apply(task: Task) {
|
||||||
|
if (tagDao.applyTags(task, tagDataDao, selectedTags)) {
|
||||||
|
task.modificationDate = DateUtilities.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRowClick() {
|
||||||
|
val intent = Intent(context, TagPickerActivity::class.java)
|
||||||
|
intent.putParcelableArrayListExtra(TagPickerActivity.EXTRA_SELECTED, selectedTags)
|
||||||
|
startActivityForResult(intent, REQUEST_TAG_PICKER_ACTIVITY)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isClickable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_outline_label_24px
|
||||||
|
|
||||||
|
override fun controlId() = TAG
|
||||||
|
|
||||||
|
override fun hasChanges(original: Task): Boolean {
|
||||||
|
return HashSet(originalTags) != HashSet(selectedTags)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshDisplayView() {
|
||||||
|
if (selectedTags.isEmpty()) {
|
||||||
|
chipGroup.visibility = View.GONE
|
||||||
|
tagsDisplay.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
tagsDisplay.visibility = View.GONE
|
||||||
|
chipGroup.visibility = View.VISIBLE
|
||||||
|
chipGroup.removeAllViews()
|
||||||
|
for (tagData in orderByName.sortedCopy(selectedTags)) {
|
||||||
|
if (tagData == null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val chip = chipProvider.newClosableChip(tagData)
|
||||||
|
chipProvider.apply(chip, tagData)
|
||||||
|
chip.setOnClickListener { onRowClick() }
|
||||||
|
chip.setOnCloseIconClickListener {
|
||||||
|
selectedTags.remove(tagData)
|
||||||
|
refreshDisplayView()
|
||||||
|
}
|
||||||
|
chipGroup.addView(chip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (requestCode == REQUEST_TAG_PICKER_ACTIVITY) {
|
||||||
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
|
selectedTags = data.getParcelableArrayListExtra(TagPickerActivity.EXTRA_SELECTED)!!
|
||||||
|
refreshDisplayView()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) = component.inject(this)
|
||||||
|
|
||||||
|
override fun requiresId() = true
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_lists_pref
|
||||||
|
private const val EXTRA_ORIGINAL_TAGS = "extra_original_tags"
|
||||||
|
private const val EXTRA_SELECTED_TAGS = "extra_selected_tags"
|
||||||
|
private const val REQUEST_TAG_PICKER_ACTIVITY = 10582
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,246 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.todoroo.astrid.timers;
|
|
||||||
|
|
||||||
import static org.tasks.Strings.isNullOrEmpty;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.text.format.DateFormat;
|
|
||||||
import android.text.format.DateUtils;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Chronometer;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import com.todoroo.astrid.ui.TimeDurationControlSet;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.dialogs.DialogBuilder;
|
|
||||||
import org.tasks.injection.ForActivity;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.themes.Theme;
|
|
||||||
import org.tasks.ui.TaskEditControlFragment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Control Set for managing repeats
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*/
|
|
||||||
public class TimerControlSet extends TaskEditControlFragment {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_timer_pref;
|
|
||||||
private static final String EXTRA_STARTED = "extra_started";
|
|
||||||
private static final String EXTRA_ESTIMATED = "extra_estimated";
|
|
||||||
private static final String EXTRA_ELAPSED = "extra_elapsed";
|
|
||||||
@Inject DialogBuilder dialogBuilder;
|
|
||||||
@Inject @ForActivity Context context;
|
|
||||||
@Inject Theme theme;
|
|
||||||
|
|
||||||
@BindView(R.id.display_row_edit)
|
|
||||||
TextView displayEdit;
|
|
||||||
|
|
||||||
@BindView(R.id.timer)
|
|
||||||
Chronometer chronometer;
|
|
||||||
|
|
||||||
@BindView(R.id.timer_button)
|
|
||||||
ImageView timerButton;
|
|
||||||
|
|
||||||
private TimeDurationControlSet estimated;
|
|
||||||
private TimeDurationControlSet elapsed;
|
|
||||||
private long timerStarted;
|
|
||||||
private AlertDialog dialog;
|
|
||||||
private View dialogView;
|
|
||||||
private TimerControlSetCallback callback;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
int elapsedSeconds;
|
|
||||||
int estimatedSeconds;
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
timerStarted = task.getTimerStart();
|
|
||||||
elapsedSeconds = task.getElapsedSeconds();
|
|
||||||
estimatedSeconds = task.getEstimatedSeconds();
|
|
||||||
} else {
|
|
||||||
timerStarted = savedInstanceState.getLong(EXTRA_STARTED);
|
|
||||||
elapsedSeconds = savedInstanceState.getInt(EXTRA_ELAPSED);
|
|
||||||
estimatedSeconds = savedInstanceState.getInt(EXTRA_ESTIMATED);
|
|
||||||
}
|
|
||||||
|
|
||||||
dialogView = inflater.inflate(R.layout.control_set_timers_dialog, null);
|
|
||||||
estimated = new TimeDurationControlSet(context, dialogView, R.id.estimatedDuration, theme);
|
|
||||||
elapsed = new TimeDurationControlSet(context, dialogView, R.id.elapsedDuration, theme);
|
|
||||||
estimated.setTimeDuration(estimatedSeconds);
|
|
||||||
elapsed.setTimeDuration(elapsedSeconds);
|
|
||||||
refresh();
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
|
|
||||||
callback = (TimerControlSetCallback) activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putInt(EXTRA_ELAPSED, elapsed.getTimeDurationInSeconds());
|
|
||||||
outState.putInt(EXTRA_ESTIMATED, estimated.getTimeDurationInSeconds());
|
|
||||||
outState.putLong(EXTRA_STARTED, timerStarted);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRowClick() {
|
|
||||||
if (dialog == null) {
|
|
||||||
dialog = buildDialog();
|
|
||||||
}
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isClickable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AlertDialog buildDialog() {
|
|
||||||
return dialogBuilder
|
|
||||||
.newDialog()
|
|
||||||
.setView(dialogView)
|
|
||||||
.setPositiveButton(android.R.string.ok, (dialog12, which) -> refreshDisplayView())
|
|
||||||
.setOnCancelListener(dialog1 -> refreshDisplayView())
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.timer_container)
|
|
||||||
void timerClicked() {
|
|
||||||
if (timerActive()) {
|
|
||||||
Task task = callback.stopTimer();
|
|
||||||
elapsed.setTimeDuration(task.getElapsedSeconds());
|
|
||||||
timerStarted = 0;
|
|
||||||
chronometer.stop();
|
|
||||||
refreshDisplayView();
|
|
||||||
} else {
|
|
||||||
Task task = callback.startTimer();
|
|
||||||
timerStarted = task.getTimerStart();
|
|
||||||
chronometer.start();
|
|
||||||
}
|
|
||||||
updateChronometer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.control_set_timers;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIcon() {
|
|
||||||
return R.drawable.ic_outline_timer_24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasChanges(Task original) {
|
|
||||||
return elapsed.getTimeDurationInSeconds() != original.getElapsedSeconds()
|
|
||||||
|| estimated.getTimeDurationInSeconds() != original.getEstimatedSeconds();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {
|
|
||||||
task.setElapsedSeconds(elapsed.getTimeDurationInSeconds());
|
|
||||||
task.setEstimatedSeconds(estimated.getTimeDurationInSeconds());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refresh() {
|
|
||||||
refreshDisplayView();
|
|
||||||
updateChronometer();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshDisplayView() {
|
|
||||||
String est = null;
|
|
||||||
int estimatedSeconds = estimated.getTimeDurationInSeconds();
|
|
||||||
if (estimatedSeconds > 0) {
|
|
||||||
est = getString(R.string.TEA_timer_est, DateUtils.formatElapsedTime(estimatedSeconds));
|
|
||||||
}
|
|
||||||
String elap = null;
|
|
||||||
int elapsedSeconds = elapsed.getTimeDurationInSeconds();
|
|
||||||
if (elapsedSeconds > 0) {
|
|
||||||
elap = getString(R.string.TEA_timer_elap, DateUtils.formatElapsedTime(elapsedSeconds));
|
|
||||||
}
|
|
||||||
|
|
||||||
String toDisplay;
|
|
||||||
|
|
||||||
if (!isNullOrEmpty(est) && !isNullOrEmpty(elap)) {
|
|
||||||
toDisplay = est + ", " + elap; // $NON-NLS-1$
|
|
||||||
} else if (!isNullOrEmpty(est)) {
|
|
||||||
toDisplay = est;
|
|
||||||
} else if (!isNullOrEmpty(elap)) {
|
|
||||||
toDisplay = elap;
|
|
||||||
} else {
|
|
||||||
toDisplay = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
displayEdit.setText(toDisplay);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateChronometer() {
|
|
||||||
timerButton.setImageResource(
|
|
||||||
timerActive() ? R.drawable.ic_outline_pause_24px : R.drawable.ic_outline_play_arrow_24px);
|
|
||||||
|
|
||||||
long elapsed = this.elapsed.getTimeDurationInSeconds() * 1000L;
|
|
||||||
if (timerActive()) {
|
|
||||||
chronometer.setVisibility(View.VISIBLE);
|
|
||||||
elapsed += DateUtilities.now() - timerStarted;
|
|
||||||
chronometer.setBase(SystemClock.elapsedRealtime() - elapsed);
|
|
||||||
if (elapsed > DateUtilities.ONE_DAY) {
|
|
||||||
chronometer.setOnChronometerTickListener(
|
|
||||||
cArg -> {
|
|
||||||
long t = SystemClock.elapsedRealtime() - cArg.getBase();
|
|
||||||
cArg.setText(DateFormat.format("d'd' h:mm", t)); // $NON-NLS-1$
|
|
||||||
});
|
|
||||||
}
|
|
||||||
chronometer.start();
|
|
||||||
} else {
|
|
||||||
chronometer.setVisibility(View.GONE);
|
|
||||||
chronometer.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean timerActive() {
|
|
||||||
return timerStarted > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface TimerControlSetCallback {
|
|
||||||
|
|
||||||
Task stopTimer();
|
|
||||||
|
|
||||||
Task startTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Todoroo Inc
|
||||||
|
*
|
||||||
|
* See the file "LICENSE" for the full license governing this code.
|
||||||
|
*/
|
||||||
|
package com.todoroo.astrid.timers
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.text.format.DateFormat
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Chronometer
|
||||||
|
import android.widget.Chronometer.OnChronometerTickListener
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.OnClick
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.ui.TimeDurationControlSet
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.Strings.isNullOrEmpty
|
||||||
|
import org.tasks.dialogs.DialogBuilder
|
||||||
|
import org.tasks.injection.ForActivity
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.themes.Theme
|
||||||
|
import org.tasks.ui.TaskEditControlFragment
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control Set for managing repeats
|
||||||
|
*
|
||||||
|
* @author Tim Su <tim></tim>@todoroo.com>
|
||||||
|
*/
|
||||||
|
class TimerControlSet : TaskEditControlFragment() {
|
||||||
|
@Inject @ForActivity lateinit var activity: Context
|
||||||
|
@Inject lateinit var dialogBuilder: DialogBuilder
|
||||||
|
@Inject lateinit var theme: Theme
|
||||||
|
|
||||||
|
@BindView(R.id.display_row_edit)
|
||||||
|
lateinit var displayEdit: TextView
|
||||||
|
|
||||||
|
@BindView(R.id.timer)
|
||||||
|
lateinit var chronometer: Chronometer
|
||||||
|
|
||||||
|
@BindView(R.id.timer_button)
|
||||||
|
lateinit var timerButton: ImageView
|
||||||
|
|
||||||
|
private lateinit var estimated: TimeDurationControlSet
|
||||||
|
private lateinit var elapsed: TimeDurationControlSet
|
||||||
|
private var timerStarted: Long = 0
|
||||||
|
private var dialog: AlertDialog? = null
|
||||||
|
private lateinit var dialogView: View
|
||||||
|
private var callback: TimerControlSetCallback? = null
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
val elapsedSeconds: Int
|
||||||
|
val estimatedSeconds: Int
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
timerStarted = task.timerStart
|
||||||
|
elapsedSeconds = task.elapsedSeconds
|
||||||
|
estimatedSeconds = task.estimatedSeconds
|
||||||
|
} else {
|
||||||
|
timerStarted = savedInstanceState.getLong(EXTRA_STARTED)
|
||||||
|
elapsedSeconds = savedInstanceState.getInt(EXTRA_ELAPSED)
|
||||||
|
estimatedSeconds = savedInstanceState.getInt(EXTRA_ESTIMATED)
|
||||||
|
}
|
||||||
|
dialogView = inflater.inflate(R.layout.control_set_timers_dialog, null)
|
||||||
|
estimated = TimeDurationControlSet(activity, dialogView, R.id.estimatedDuration, theme)
|
||||||
|
elapsed = TimeDurationControlSet(activity, dialogView, R.id.elapsedDuration, theme)
|
||||||
|
estimated.setTimeDuration(estimatedSeconds)
|
||||||
|
elapsed.setTimeDuration(elapsedSeconds)
|
||||||
|
refresh()
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttach(activity: Activity) {
|
||||||
|
super.onAttach(activity)
|
||||||
|
callback = activity as TimerControlSetCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) = component.inject(this)
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putInt(EXTRA_ELAPSED, elapsed.timeDurationInSeconds)
|
||||||
|
outState.putInt(EXTRA_ESTIMATED, estimated.timeDurationInSeconds)
|
||||||
|
outState.putLong(EXTRA_STARTED, timerStarted)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRowClick() {
|
||||||
|
if (dialog == null) {
|
||||||
|
dialog = buildDialog()
|
||||||
|
}
|
||||||
|
dialog!!.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isClickable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
private fun buildDialog(): AlertDialog {
|
||||||
|
return dialogBuilder
|
||||||
|
.newDialog()
|
||||||
|
.setView(dialogView)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ -> refreshDisplayView() }
|
||||||
|
.setOnCancelListener { refreshDisplayView() }
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.timer_container)
|
||||||
|
fun timerClicked() {
|
||||||
|
if (timerActive()) {
|
||||||
|
val task = callback!!.stopTimer()
|
||||||
|
elapsed.setTimeDuration(task.elapsedSeconds)
|
||||||
|
timerStarted = 0
|
||||||
|
chronometer.stop()
|
||||||
|
refreshDisplayView()
|
||||||
|
} else {
|
||||||
|
val task = callback!!.startTimer()
|
||||||
|
timerStarted = task.timerStart
|
||||||
|
chronometer.start()
|
||||||
|
}
|
||||||
|
updateChronometer()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.control_set_timers
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_outline_timer_24px
|
||||||
|
|
||||||
|
override fun controlId() = TAG
|
||||||
|
|
||||||
|
override fun hasChanges(original: Task): Boolean {
|
||||||
|
return (elapsed.timeDurationInSeconds != original.elapsedSeconds
|
||||||
|
|| estimated.timeDurationInSeconds != original.estimatedSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(task: Task) {
|
||||||
|
task.elapsedSeconds = elapsed.timeDurationInSeconds
|
||||||
|
task.estimatedSeconds = estimated.timeDurationInSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refresh() {
|
||||||
|
refreshDisplayView()
|
||||||
|
updateChronometer()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshDisplayView() {
|
||||||
|
var est: String? = null
|
||||||
|
val estimatedSeconds = estimated.timeDurationInSeconds
|
||||||
|
if (estimatedSeconds > 0) {
|
||||||
|
est = getString(R.string.TEA_timer_est, DateUtils.formatElapsedTime(estimatedSeconds.toLong()))
|
||||||
|
}
|
||||||
|
var elap: String? = null
|
||||||
|
val elapsedSeconds = elapsed.timeDurationInSeconds
|
||||||
|
if (elapsedSeconds > 0) {
|
||||||
|
elap = getString(R.string.TEA_timer_elap, DateUtils.formatElapsedTime(elapsedSeconds.toLong()))
|
||||||
|
}
|
||||||
|
val toDisplay: String?
|
||||||
|
toDisplay = if (!isNullOrEmpty(est) && !isNullOrEmpty(elap)) {
|
||||||
|
"$est, $elap" // $NON-NLS-1$
|
||||||
|
} else if (!isNullOrEmpty(est)) {
|
||||||
|
est
|
||||||
|
} else if (!isNullOrEmpty(elap)) {
|
||||||
|
elap
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
displayEdit.text = toDisplay
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateChronometer() {
|
||||||
|
timerButton.setImageResource(
|
||||||
|
if (timerActive()) R.drawable.ic_outline_pause_24px else R.drawable.ic_outline_play_arrow_24px)
|
||||||
|
var elapsed = elapsed.timeDurationInSeconds * 1000L
|
||||||
|
if (timerActive()) {
|
||||||
|
chronometer.visibility = View.VISIBLE
|
||||||
|
elapsed += DateUtilities.now() - timerStarted
|
||||||
|
chronometer.base = SystemClock.elapsedRealtime() - elapsed
|
||||||
|
if (elapsed > DateUtilities.ONE_DAY) {
|
||||||
|
chronometer.onChronometerTickListener = OnChronometerTickListener { cArg: Chronometer ->
|
||||||
|
val t = SystemClock.elapsedRealtime() - cArg.base
|
||||||
|
cArg.text = DateFormat.format("d'd' h:mm", t) // $NON-NLS-1$
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chronometer.start()
|
||||||
|
} else {
|
||||||
|
chronometer.visibility = View.GONE
|
||||||
|
chronometer.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun timerActive(): Boolean {
|
||||||
|
return timerStarted > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TimerControlSetCallback {
|
||||||
|
fun stopTimer(): Task
|
||||||
|
fun startTimer(): Task
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_timer_pref
|
||||||
|
private const val EXTRA_STARTED = "extra_started"
|
||||||
|
private const val EXTRA_ESTIMATED = "extra_estimated"
|
||||||
|
private const val EXTRA_ELAPSED = "extra_elapsed"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,370 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.todoroo.astrid.ui;
|
|
||||||
|
|
||||||
import static com.todoroo.astrid.data.Task.HIDE_UNTIL_DAY_BEFORE;
|
|
||||||
import static com.todoroo.astrid.data.Task.HIDE_UNTIL_DUE;
|
|
||||||
import static com.todoroo.astrid.data.Task.HIDE_UNTIL_NONE;
|
|
||||||
import static com.todoroo.astrid.data.Task.HIDE_UNTIL_WEEK_BEFORE;
|
|
||||||
import static java.util.Arrays.asList;
|
|
||||||
import static org.tasks.date.DateTimeUtils.newDateTime;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
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.ImageView;
|
|
||||||
import android.widget.Spinner;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.activities.DateAndTimePickerActivity;
|
|
||||||
import org.tasks.dialogs.MyTimePickerDialog;
|
|
||||||
import org.tasks.injection.ForActivity;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.locale.Locale;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
import org.tasks.themes.ThemeBase;
|
|
||||||
import org.tasks.time.DateTime;
|
|
||||||
import org.tasks.ui.HiddenTopArrayAdapter;
|
|
||||||
import org.tasks.ui.TaskEditControlFragment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Control set for specifying when a task should be hidden
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*/
|
|
||||||
public class HideUntilControlSet extends TaskEditControlFragment implements OnItemSelectedListener {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_hide_until_pref;
|
|
||||||
|
|
||||||
private static final String EXTRA_CUSTOM = "extra_custom";
|
|
||||||
private static final String EXTRA_SELECTION = "extra_selection";
|
|
||||||
|
|
||||||
private static final int SPECIFIC_DATE = -1;
|
|
||||||
private static final int EXISTING_TIME_UNSET = -2;
|
|
||||||
private static final int REQUEST_HIDE_UNTIL = 11011;
|
|
||||||
private final List<HideUntilValue> spinnerItems = new ArrayList<>();
|
|
||||||
@Inject @ForActivity Context context;
|
|
||||||
@Inject ThemeBase themeBase;
|
|
||||||
@Inject Preferences preferences;
|
|
||||||
@Inject Locale locale;
|
|
||||||
// private final CheckBox enabled;
|
|
||||||
@BindView(R.id.hideUntil)
|
|
||||||
Spinner spinner;
|
|
||||||
|
|
||||||
@BindView(R.id.clear)
|
|
||||||
ImageView clearButton;
|
|
||||||
|
|
||||||
private ArrayAdapter<HideUntilValue> adapter;
|
|
||||||
private int previousSetting = Task.HIDE_UNTIL_NONE;
|
|
||||||
private int selection;
|
|
||||||
private long existingDate = EXISTING_TIME_UNSET;
|
|
||||||
private HideUntilValue selectedValue;
|
|
||||||
|
|
||||||
@OnClick(R.id.clear)
|
|
||||||
void clearHideUntil() {
|
|
||||||
updateSpinnerOptions(0);
|
|
||||||
selection = 0;
|
|
||||||
spinner.setSelection(selection);
|
|
||||||
refreshDisplayView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRowClick() {
|
|
||||||
spinner.performClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isClickable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
adapter =
|
|
||||||
new HiddenTopArrayAdapter<HideUntilValue>(
|
|
||||||
context, android.R.layout.simple_spinner_item, spinnerItems) {
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
|
|
||||||
int selectedItemPosition = position;
|
|
||||||
if (parent instanceof AdapterView) {
|
|
||||||
selectedItemPosition = ((AdapterView) parent).getSelectedItemPosition();
|
|
||||||
}
|
|
||||||
TextView tv =
|
|
||||||
(TextView) inflater.inflate(android.R.layout.simple_spinner_item, parent, false);
|
|
||||||
tv.setPadding(0, 0, 0, 0);
|
|
||||||
HideUntilValue value = getItem(selectedItemPosition);
|
|
||||||
if (value.setting == Task.HIDE_UNTIL_NONE) {
|
|
||||||
clearButton.setVisibility(View.GONE);
|
|
||||||
tv.setText(value.labelDisplay);
|
|
||||||
tv.setTextColor(context.getColor(R.color.text_tertiary));
|
|
||||||
} else {
|
|
||||||
String display = value.labelDisplay;
|
|
||||||
tv.setText(getString(R.string.TEA_hideUntil_display, display));
|
|
||||||
tv.setTextColor(context.getColor(R.color.text_primary));
|
|
||||||
}
|
|
||||||
return tv;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
long dueDate = task.getDueDate();
|
|
||||||
long hideUntil = task.getHideUntil();
|
|
||||||
|
|
||||||
DateTime dueDay =
|
|
||||||
newDateTime(dueDate)
|
|
||||||
.withHourOfDay(0)
|
|
||||||
.withMinuteOfHour(0)
|
|
||||||
.withSecondOfMinute(0)
|
|
||||||
.withMillisOfSecond(0);
|
|
||||||
|
|
||||||
// For the hide until due case, we need the time component
|
|
||||||
long dueTime = dueDate / 1000L * 1000L;
|
|
||||||
|
|
||||||
if (hideUntil <= 0) {
|
|
||||||
selection = 0;
|
|
||||||
hideUntil = 0;
|
|
||||||
if (task.isNew()) {
|
|
||||||
int defaultHideUntil =
|
|
||||||
preferences.getIntegerFromString(R.string.p_default_hideUntil_key, HIDE_UNTIL_NONE);
|
|
||||||
switch (defaultHideUntil) {
|
|
||||||
case HIDE_UNTIL_DUE:
|
|
||||||
selection = 1;
|
|
||||||
break;
|
|
||||||
case HIDE_UNTIL_DAY_BEFORE:
|
|
||||||
selection = 3;
|
|
||||||
break;
|
|
||||||
case HIDE_UNTIL_WEEK_BEFORE:
|
|
||||||
selection = 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (hideUntil == dueDay.getMillis()) {
|
|
||||||
selection = 1;
|
|
||||||
hideUntil = 0;
|
|
||||||
} else if (hideUntil == dueTime) {
|
|
||||||
selection = 2;
|
|
||||||
hideUntil = 0;
|
|
||||||
} else if (hideUntil + DateUtilities.ONE_DAY == dueDay.getMillis()) {
|
|
||||||
selection = 3;
|
|
||||||
hideUntil = 0;
|
|
||||||
} else if (hideUntil + DateUtilities.ONE_WEEK == dueDay.getMillis()) {
|
|
||||||
selection = 4;
|
|
||||||
hideUntil = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSpinnerOptions(hideUntil);
|
|
||||||
} else {
|
|
||||||
updateSpinnerOptions(savedInstanceState.getLong(EXTRA_CUSTOM));
|
|
||||||
selection = savedInstanceState.getInt(EXTRA_SELECTION);
|
|
||||||
}
|
|
||||||
spinner.setAdapter(adapter);
|
|
||||||
spinner.setSelection(selection);
|
|
||||||
spinner.setOnItemSelectedListener(this);
|
|
||||||
refreshDisplayView();
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.control_set_hide;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getIcon() {
|
|
||||||
return R.drawable.ic_outline_visibility_off_24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (requestCode == REQUEST_HIDE_UNTIL) {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
setCustomDate(data.getLongExtra(MyTimePickerDialog.EXTRA_TIMESTAMP, 0L));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {
|
|
||||||
task.setHideUntil(getHideUntil(task));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasChanges(Task original) {
|
|
||||||
return original.getHideUntil() != getHideUntil(original);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getHideUntil(Task task) {
|
|
||||||
return task.createHideUntil(selectedValue.setting, selectedValue.date);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putLong(EXTRA_CUSTOM, existingDate);
|
|
||||||
outState.putInt(EXTRA_SELECTION, selection);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSpinnerOptions(long specificDate) {
|
|
||||||
spinnerItems.clear();
|
|
||||||
// set up base values
|
|
||||||
String[] labelsSpinner = getResources().getStringArray(R.array.TEA_hideUntil_spinner);
|
|
||||||
String[] labelsDisplay = getResources().getStringArray(R.array.TEA_hideUntil_display);
|
|
||||||
|
|
||||||
spinnerItems.addAll(
|
|
||||||
new ArrayList<>(
|
|
||||||
asList(
|
|
||||||
new HideUntilValue(labelsSpinner[0], labelsDisplay[0], Task.HIDE_UNTIL_DUE),
|
|
||||||
new HideUntilValue(labelsSpinner[1], labelsDisplay[1], Task.HIDE_UNTIL_DUE_TIME),
|
|
||||||
new HideUntilValue(labelsSpinner[2], labelsDisplay[2], Task.HIDE_UNTIL_DAY_BEFORE),
|
|
||||||
new HideUntilValue(labelsSpinner[3], labelsDisplay[3], Task.HIDE_UNTIL_WEEK_BEFORE),
|
|
||||||
new HideUntilValue(
|
|
||||||
labelsSpinner[4],
|
|
||||||
"",
|
|
||||||
Task.HIDE_UNTIL_SPECIFIC_DAY,
|
|
||||||
-1)))); // no need for a string for display here, since the chosen day will be
|
|
||||||
// displayed
|
|
||||||
|
|
||||||
if (specificDate > 0) {
|
|
||||||
spinnerItems.add(0, getHideUntilValue(specificDate));
|
|
||||||
existingDate = specificDate;
|
|
||||||
} else {
|
|
||||||
spinnerItems.add(
|
|
||||||
0, new HideUntilValue(getString(R.string.TEA_hideUntil_label), Task.HIDE_UNTIL_NONE));
|
|
||||||
existingDate = EXISTING_TIME_UNSET;
|
|
||||||
}
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private HideUntilValue getHideUntilValue(long timestamp) {
|
|
||||||
DateTime hideUntilAsDate = newDateTime(timestamp);
|
|
||||||
if (hideUntilAsDate.getHourOfDay() == 0
|
|
||||||
&& hideUntilAsDate.getMinuteOfHour() == 0
|
|
||||||
&& hideUntilAsDate.getSecondOfMinute() == 0) {
|
|
||||||
return new HideUntilValue(
|
|
||||||
DateUtilities.getDateString(context, newDateTime(timestamp)),
|
|
||||||
Task.HIDE_UNTIL_SPECIFIC_DAY,
|
|
||||||
timestamp);
|
|
||||||
} else {
|
|
||||||
return new HideUntilValue(
|
|
||||||
DateUtilities.getLongDateStringWithTime(timestamp, locale.getLocale()),
|
|
||||||
Task.HIDE_UNTIL_SPECIFIC_DAY_TIME,
|
|
||||||
timestamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
// if specific date selected, show dialog
|
|
||||||
// ... at conclusion of dialog, update our list
|
|
||||||
HideUntilValue item = adapter.getItem(position);
|
|
||||||
if (item.date == SPECIFIC_DATE) {
|
|
||||||
final DateTime customDate =
|
|
||||||
newDateTime(existingDate == EXISTING_TIME_UNSET ? DateUtilities.now() : existingDate)
|
|
||||||
.withSecondOfMinute(0);
|
|
||||||
|
|
||||||
final Activity activity = getActivity();
|
|
||||||
Intent intent = new Intent(activity, DateAndTimePickerActivity.class);
|
|
||||||
intent.putExtra(DateAndTimePickerActivity.EXTRA_TIMESTAMP, customDate.getMillis());
|
|
||||||
startActivityForResult(intent, REQUEST_HIDE_UNTIL);
|
|
||||||
spinner.setSelection(previousSetting);
|
|
||||||
} else {
|
|
||||||
previousSetting = position;
|
|
||||||
}
|
|
||||||
selection = spinner.getSelectedItemPosition();
|
|
||||||
refreshDisplayView();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- listening for events
|
|
||||||
|
|
||||||
private void setCustomDate(long timestamp) {
|
|
||||||
updateSpinnerOptions(timestamp);
|
|
||||||
spinner.setSelection(0);
|
|
||||||
refreshDisplayView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNothingSelected(AdapterView<?> arg0) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshDisplayView() {
|
|
||||||
selectedValue = adapter.getItem(selection);
|
|
||||||
clearButton.setVisibility(
|
|
||||||
selectedValue.setting == Task.HIDE_UNTIL_NONE ? View.GONE : View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- setting up values
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Container class for urgencies
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*/
|
|
||||||
private static class HideUntilValue {
|
|
||||||
|
|
||||||
final String labelSpinner;
|
|
||||||
final String labelDisplay;
|
|
||||||
final int setting;
|
|
||||||
final long date;
|
|
||||||
|
|
||||||
HideUntilValue(String label, int setting) {
|
|
||||||
this(label, label, setting, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
HideUntilValue(String labelSpinner, String labelDisplay, int setting) {
|
|
||||||
this(labelSpinner, labelDisplay, setting, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
HideUntilValue(String label, int setting, long date) {
|
|
||||||
this(label, label, setting, date);
|
|
||||||
}
|
|
||||||
|
|
||||||
HideUntilValue(String labelSpinner, String labelDisplay, int setting, long date) {
|
|
||||||
this.labelSpinner = labelSpinner;
|
|
||||||
this.labelDisplay = labelDisplay;
|
|
||||||
this.setting = setting;
|
|
||||||
this.date = date;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return labelSpinner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,285 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Todoroo Inc
|
||||||
|
*
|
||||||
|
* See the file "LICENSE" for the full license governing this code.
|
||||||
|
*/
|
||||||
|
package com.todoroo.astrid.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.*
|
||||||
|
import android.widget.AdapterView.OnItemSelectedListener
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.OnClick
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.activities.DateAndTimePickerActivity
|
||||||
|
import org.tasks.date.DateTimeUtils
|
||||||
|
import org.tasks.dialogs.MyTimePickerDialog
|
||||||
|
import org.tasks.injection.ForActivity
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.locale.Locale
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import org.tasks.themes.ThemeBase
|
||||||
|
import org.tasks.ui.HiddenTopArrayAdapter
|
||||||
|
import org.tasks.ui.TaskEditControlFragment
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control set for specifying when a task should be hidden
|
||||||
|
*
|
||||||
|
* @author Tim Su <tim></tim>@todoroo.com>
|
||||||
|
*/
|
||||||
|
class HideUntilControlSet : TaskEditControlFragment(), OnItemSelectedListener {
|
||||||
|
private val spinnerItems: MutableList<HideUntilValue> = ArrayList()
|
||||||
|
|
||||||
|
@Inject @ForActivity lateinit var activity: Context
|
||||||
|
@Inject lateinit var themeBase: ThemeBase
|
||||||
|
@Inject lateinit var preferences: Preferences
|
||||||
|
@Inject lateinit var locale: Locale
|
||||||
|
|
||||||
|
@BindView(R.id.hideUntil)
|
||||||
|
lateinit var spinner: Spinner
|
||||||
|
|
||||||
|
@BindView(R.id.clear)
|
||||||
|
lateinit var clearButton: ImageView
|
||||||
|
|
||||||
|
private lateinit var adapter: ArrayAdapter<HideUntilValue>
|
||||||
|
private var previousSetting = Task.HIDE_UNTIL_NONE
|
||||||
|
private var selection = 0
|
||||||
|
private var existingDate = EXISTING_TIME_UNSET.toLong()
|
||||||
|
private var selectedValue: HideUntilValue? = null
|
||||||
|
|
||||||
|
@OnClick(R.id.clear)
|
||||||
|
fun clearHideUntil() {
|
||||||
|
updateSpinnerOptions(0)
|
||||||
|
selection = 0
|
||||||
|
spinner.setSelection(selection)
|
||||||
|
refreshDisplayView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRowClick() {
|
||||||
|
spinner.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isClickable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
adapter = object : HiddenTopArrayAdapter<HideUntilValue>(
|
||||||
|
activity, android.R.layout.simple_spinner_item, spinnerItems) {
|
||||||
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
|
var selectedItemPosition = position
|
||||||
|
if (parent is AdapterView<*>) {
|
||||||
|
selectedItemPosition = parent.selectedItemPosition
|
||||||
|
}
|
||||||
|
val tv = inflater.inflate(android.R.layout.simple_spinner_item, parent, false) as TextView
|
||||||
|
tv.setPadding(0, 0, 0, 0)
|
||||||
|
val value = getItem(selectedItemPosition)
|
||||||
|
if (value!!.setting == Task.HIDE_UNTIL_NONE) {
|
||||||
|
clearButton.visibility = View.GONE
|
||||||
|
tv.text = value.labelDisplay
|
||||||
|
tv.setTextColor(activity.getColor(R.color.text_tertiary))
|
||||||
|
} else {
|
||||||
|
val display = value.labelDisplay
|
||||||
|
tv.text = getString(R.string.TEA_hideUntil_display, display)
|
||||||
|
tv.setTextColor(activity.getColor(R.color.text_primary))
|
||||||
|
}
|
||||||
|
return tv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
val dueDate = task.dueDate
|
||||||
|
var hideUntil = task.hideUntil
|
||||||
|
val dueDay = DateTimeUtils.newDateTime(dueDate)
|
||||||
|
.withHourOfDay(0)
|
||||||
|
.withMinuteOfHour(0)
|
||||||
|
.withSecondOfMinute(0)
|
||||||
|
.withMillisOfSecond(0)
|
||||||
|
|
||||||
|
// For the hide until due case, we need the time component
|
||||||
|
val dueTime = dueDate / 1000L * 1000L
|
||||||
|
if (hideUntil <= 0) {
|
||||||
|
selection = 0
|
||||||
|
hideUntil = 0
|
||||||
|
if (task.isNew) {
|
||||||
|
when (preferences.getIntegerFromString(R.string.p_default_hideUntil_key, Task.HIDE_UNTIL_NONE)) {
|
||||||
|
Task.HIDE_UNTIL_DUE -> selection = 1
|
||||||
|
Task.HIDE_UNTIL_DAY_BEFORE -> selection = 3
|
||||||
|
Task.HIDE_UNTIL_WEEK_BEFORE -> selection = 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (hideUntil == dueDay.millis) {
|
||||||
|
selection = 1
|
||||||
|
hideUntil = 0
|
||||||
|
} else if (hideUntil == dueTime) {
|
||||||
|
selection = 2
|
||||||
|
hideUntil = 0
|
||||||
|
} else if (hideUntil + DateUtilities.ONE_DAY == dueDay.millis) {
|
||||||
|
selection = 3
|
||||||
|
hideUntil = 0
|
||||||
|
} else if (hideUntil + DateUtilities.ONE_WEEK == dueDay.millis) {
|
||||||
|
selection = 4
|
||||||
|
hideUntil = 0
|
||||||
|
}
|
||||||
|
updateSpinnerOptions(hideUntil)
|
||||||
|
} else {
|
||||||
|
updateSpinnerOptions(savedInstanceState.getLong(EXTRA_CUSTOM))
|
||||||
|
selection = savedInstanceState.getInt(EXTRA_SELECTION)
|
||||||
|
}
|
||||||
|
spinner.adapter = adapter
|
||||||
|
spinner.setSelection(selection)
|
||||||
|
spinner.onItemSelectedListener = this
|
||||||
|
refreshDisplayView()
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.control_set_hide
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_outline_visibility_off_24px
|
||||||
|
|
||||||
|
override fun controlId(): Int {
|
||||||
|
return TAG
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (requestCode == REQUEST_HIDE_UNTIL) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
setCustomDate(data!!.getLongExtra(MyTimePickerDialog.EXTRA_TIMESTAMP, 0L))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(task: Task) {
|
||||||
|
task.hideUntil = getHideUntil(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasChanges(original: Task): Boolean {
|
||||||
|
return original.hideUntil != getHideUntil(original)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHideUntil(task: Task): Long {
|
||||||
|
return task.createHideUntil(selectedValue!!.setting, selectedValue!!.date)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putLong(EXTRA_CUSTOM, existingDate)
|
||||||
|
outState.putInt(EXTRA_SELECTION, selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) {
|
||||||
|
component.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSpinnerOptions(specificDate: Long) {
|
||||||
|
spinnerItems.clear()
|
||||||
|
// set up base values
|
||||||
|
val labelsSpinner = resources.getStringArray(R.array.TEA_hideUntil_spinner)
|
||||||
|
val labelsDisplay = resources.getStringArray(R.array.TEA_hideUntil_display)
|
||||||
|
spinnerItems.addAll(
|
||||||
|
ArrayList(
|
||||||
|
listOf(
|
||||||
|
HideUntilValue(labelsSpinner[0], labelsDisplay[0], Task.HIDE_UNTIL_DUE),
|
||||||
|
HideUntilValue(labelsSpinner[1], labelsDisplay[1], Task.HIDE_UNTIL_DUE_TIME),
|
||||||
|
HideUntilValue(labelsSpinner[2], labelsDisplay[2], Task.HIDE_UNTIL_DAY_BEFORE),
|
||||||
|
HideUntilValue(labelsSpinner[3], labelsDisplay[3], Task.HIDE_UNTIL_WEEK_BEFORE),
|
||||||
|
HideUntilValue(
|
||||||
|
labelsSpinner[4],
|
||||||
|
"",
|
||||||
|
Task.HIDE_UNTIL_SPECIFIC_DAY,
|
||||||
|
-1)))) // no need for a string for display here, since the chosen day will be
|
||||||
|
// displayed
|
||||||
|
existingDate = if (specificDate > 0) {
|
||||||
|
spinnerItems.add(0, getHideUntilValue(specificDate))
|
||||||
|
specificDate
|
||||||
|
} else {
|
||||||
|
spinnerItems.add(
|
||||||
|
0, HideUntilValue(getString(R.string.TEA_hideUntil_label), Task.HIDE_UNTIL_NONE))
|
||||||
|
EXISTING_TIME_UNSET.toLong()
|
||||||
|
}
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHideUntilValue(timestamp: Long): HideUntilValue {
|
||||||
|
val hideUntilAsDate = DateTimeUtils.newDateTime(timestamp)
|
||||||
|
return if (hideUntilAsDate.hourOfDay == 0 && hideUntilAsDate.minuteOfHour == 0 && hideUntilAsDate.secondOfMinute == 0) {
|
||||||
|
HideUntilValue(
|
||||||
|
DateUtilities.getDateString(context, DateTimeUtils.newDateTime(timestamp)),
|
||||||
|
Task.HIDE_UNTIL_SPECIFIC_DAY,
|
||||||
|
timestamp)
|
||||||
|
} else {
|
||||||
|
HideUntilValue(
|
||||||
|
DateUtilities.getLongDateStringWithTime(timestamp, locale.locale),
|
||||||
|
Task.HIDE_UNTIL_SPECIFIC_DAY_TIME,
|
||||||
|
timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
|
||||||
|
// if specific date selected, show dialog
|
||||||
|
// ... at conclusion of dialog, update our list
|
||||||
|
val item = adapter.getItem(position)
|
||||||
|
if (item!!.date == SPECIFIC_DATE.toLong()) {
|
||||||
|
val customDate = DateTimeUtils.newDateTime(if (existingDate == EXISTING_TIME_UNSET.toLong()) DateUtilities.now() else existingDate)
|
||||||
|
.withSecondOfMinute(0)
|
||||||
|
val intent = Intent(activity, DateAndTimePickerActivity::class.java)
|
||||||
|
intent.putExtra(DateAndTimePickerActivity.EXTRA_TIMESTAMP, customDate.millis)
|
||||||
|
startActivityForResult(intent, REQUEST_HIDE_UNTIL)
|
||||||
|
spinner.setSelection(previousSetting)
|
||||||
|
} else {
|
||||||
|
previousSetting = position
|
||||||
|
}
|
||||||
|
selection = spinner.selectedItemPosition
|
||||||
|
refreshDisplayView()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- listening for events
|
||||||
|
private fun setCustomDate(timestamp: Long) {
|
||||||
|
updateSpinnerOptions(timestamp)
|
||||||
|
spinner.setSelection(0)
|
||||||
|
refreshDisplayView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(arg0: AdapterView<*>?) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshDisplayView() {
|
||||||
|
selectedValue = adapter.getItem(selection)
|
||||||
|
clearButton.visibility = if (selectedValue!!.setting == Task.HIDE_UNTIL_NONE) View.GONE else View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HideUntilValue @JvmOverloads internal constructor(val labelSpinner: String, val labelDisplay: String, val setting: Int, val date: Long = 0) {
|
||||||
|
|
||||||
|
internal constructor(label: String, setting: Int) : this(label, label, setting, 0)
|
||||||
|
|
||||||
|
internal constructor(label: String, setting: Int, date: Long) : this(label, label, setting, date)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return labelSpinner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_hide_until_pref
|
||||||
|
private const val EXTRA_CUSTOM = "extra_custom"
|
||||||
|
private const val EXTRA_SELECTION = "extra_selection"
|
||||||
|
private const val SPECIFIC_DATE = -1
|
||||||
|
private const val EXISTING_TIME_UNSET = -2
|
||||||
|
private const val REQUEST_HIDE_UNTIL = 11011
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.todoroo.astrid.ui;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.Spinner;
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
import org.tasks.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Control set dealing with random reminder settings
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*/
|
|
||||||
class RandomReminderControlSet {
|
|
||||||
|
|
||||||
private final int[] hours;
|
|
||||||
private int selectedIndex;
|
|
||||||
|
|
||||||
public RandomReminderControlSet(Context context, View parentView, long reminderPeriod) {
|
|
||||||
Spinner periodSpinner = parentView.findViewById(R.id.reminder_random_interval);
|
|
||||||
periodSpinner.setVisibility(View.VISIBLE);
|
|
||||||
// create adapter
|
|
||||||
ArrayAdapter<String> adapter =
|
|
||||||
new ArrayAdapter<>(
|
|
||||||
context,
|
|
||||||
android.R.layout.simple_spinner_item,
|
|
||||||
context.getResources().getStringArray(R.array.TEA_reminder_random));
|
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
|
||||||
periodSpinner.setAdapter(adapter);
|
|
||||||
|
|
||||||
periodSpinner.setOnItemSelectedListener(
|
|
||||||
new AdapterView.OnItemSelectedListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
selectedIndex = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNothingSelected(AdapterView<?> parent) {}
|
|
||||||
});
|
|
||||||
|
|
||||||
// create hour array
|
|
||||||
String[] hourStrings = context.getResources().getStringArray(R.array.TEA_reminder_random_hours);
|
|
||||||
hours = new int[hourStrings.length];
|
|
||||||
for (int i = 0; i < hours.length; i++) {
|
|
||||||
hours[i] = Integer.parseInt(hourStrings[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < hours.length - 1; i++) {
|
|
||||||
if (hours[i] * DateUtilities.ONE_HOUR >= reminderPeriod) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
periodSpinner.setSelection(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getReminderPeriod() {
|
|
||||||
int hourValue = hours[selectedIndex];
|
|
||||||
return hourValue * DateUtilities.ONE_HOUR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Todoroo Inc
|
||||||
|
*
|
||||||
|
* See the file "LICENSE" for the full license governing this code.
|
||||||
|
*/
|
||||||
|
package com.todoroo.astrid.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.AdapterView.OnItemSelectedListener
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Spinner
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities
|
||||||
|
import org.tasks.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control set dealing with random reminder settings
|
||||||
|
*
|
||||||
|
* @author Tim Su <tim></tim>@todoroo.com>
|
||||||
|
*/
|
||||||
|
internal class RandomReminderControlSet(context: Context, parentView: View, reminderPeriod: Long) {
|
||||||
|
private val hours: IntArray
|
||||||
|
private var selectedIndex = 0
|
||||||
|
val reminderPeriod: Long
|
||||||
|
get() {
|
||||||
|
val hourValue = hours[selectedIndex]
|
||||||
|
return hourValue * DateUtilities.ONE_HOUR
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val periodSpinner = parentView.findViewById<Spinner>(R.id.reminder_random_interval)
|
||||||
|
periodSpinner.visibility = View.VISIBLE
|
||||||
|
// create adapter
|
||||||
|
val adapter = ArrayAdapter(
|
||||||
|
context,
|
||||||
|
android.R.layout.simple_spinner_item,
|
||||||
|
context.resources.getStringArray(R.array.TEA_reminder_random))
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
periodSpinner.adapter = adapter
|
||||||
|
periodSpinner.onItemSelectedListener = object : OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
|
||||||
|
selectedIndex = position
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create hour array
|
||||||
|
val hourStrings = context.resources.getStringArray(R.array.TEA_reminder_random_hours)
|
||||||
|
hours = IntArray(hourStrings.size)
|
||||||
|
for (i in hours.indices) {
|
||||||
|
hours[i] = hourStrings[i].toInt()
|
||||||
|
}
|
||||||
|
var i = 0
|
||||||
|
while (i < hours.size - 1) {
|
||||||
|
if (hours[i] * DateUtilities.ONE_HOUR >= reminderPeriod) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
periodSpinner.setSelection(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,352 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.todoroo.astrid.ui;
|
|
||||||
|
|
||||||
import static com.google.common.collect.Lists.transform;
|
|
||||||
import static com.google.common.collect.Sets.newHashSet;
|
|
||||||
import static com.todoroo.andlib.utility.DateUtilities.getLongDateStringWithTime;
|
|
||||||
import static com.todoroo.astrid.data.Task.NO_ID;
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static org.tasks.date.DateTimeUtils.newDateTime;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import com.google.common.primitives.Longs;
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
import com.todoroo.astrid.alarms.AlarmService;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.activities.DateAndTimePickerActivity;
|
|
||||||
import org.tasks.data.Alarm;
|
|
||||||
import org.tasks.dialogs.DialogBuilder;
|
|
||||||
import org.tasks.dialogs.MyTimePickerDialog;
|
|
||||||
import org.tasks.injection.ForActivity;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.locale.Locale;
|
|
||||||
import org.tasks.ui.TaskEditControlFragment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Control set dealing with reminder settings
|
|
||||||
*
|
|
||||||
* @author Tim Su <tim@todoroo.com>
|
|
||||||
*/
|
|
||||||
public class ReminderControlSet extends TaskEditControlFragment {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_reminders_pref;
|
|
||||||
|
|
||||||
private static final int REQUEST_NEW_ALARM = 12152;
|
|
||||||
|
|
||||||
private static final String EXTRA_FLAGS = "extra_flags";
|
|
||||||
private static final String EXTRA_RANDOM_REMINDER = "extra_random_reminder";
|
|
||||||
private static final String EXTRA_ALARMS = "extra_alarms";
|
|
||||||
private final Set<Long> alarms = new LinkedHashSet<>();
|
|
||||||
@Inject AlarmService alarmService;
|
|
||||||
@Inject @ForActivity Context context;
|
|
||||||
@Inject Locale locale;
|
|
||||||
@Inject DialogBuilder dialogBuilder;
|
|
||||||
|
|
||||||
@BindView(R.id.alert_container)
|
|
||||||
LinearLayout alertContainer;
|
|
||||||
|
|
||||||
@BindView(R.id.reminder_alarm)
|
|
||||||
TextView mode;
|
|
||||||
|
|
||||||
private long taskId;
|
|
||||||
private int flags;
|
|
||||||
private long randomReminder;
|
|
||||||
private int ringMode;
|
|
||||||
private RandomReminderControlSet randomControlSet;
|
|
||||||
private boolean whenDue;
|
|
||||||
private boolean whenOverdue;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
|
|
||||||
mode.setPaintFlags(mode.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
|
||||||
|
|
||||||
taskId = task.getId();
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
flags = task.getReminderFlags();
|
|
||||||
randomReminder = task.getReminderPeriod();
|
|
||||||
setup(currentAlarms());
|
|
||||||
} else {
|
|
||||||
flags = savedInstanceState.getInt(EXTRA_FLAGS);
|
|
||||||
randomReminder = savedInstanceState.getLong(EXTRA_RANDOM_REMINDER);
|
|
||||||
setup(Longs.asList(savedInstanceState.getLongArray(EXTRA_ALARMS)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Long> currentAlarms() {
|
|
||||||
return taskId == NO_ID
|
|
||||||
? emptyList()
|
|
||||||
: transform(alarmService.getAlarms(taskId), Alarm::getTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.reminder_alarm)
|
|
||||||
void onClickRingType() {
|
|
||||||
String[] modes = getResources().getStringArray(R.array.reminder_ring_modes);
|
|
||||||
dialogBuilder
|
|
||||||
.newDialog()
|
|
||||||
.setSingleChoiceItems(modes, ringMode, (dialog, which) -> {
|
|
||||||
setRingMode(which);
|
|
||||||
dialog.dismiss();
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setRingMode(int ringMode) {
|
|
||||||
this.ringMode = ringMode;
|
|
||||||
mode.setText(getRingModeString(ringMode));
|
|
||||||
}
|
|
||||||
|
|
||||||
private @StringRes int getRingModeString(int ringMode) {
|
|
||||||
switch (ringMode) {
|
|
||||||
case 2:
|
|
||||||
return R.string.ring_nonstop;
|
|
||||||
case 1:
|
|
||||||
return R.string.ring_five_times;
|
|
||||||
default:
|
|
||||||
return R.string.ring_once;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAlarm(String selected) {
|
|
||||||
if (selected.equals(getString(R.string.when_due))) {
|
|
||||||
addDue();
|
|
||||||
} else if (selected.equals(getString(R.string.when_overdue))) {
|
|
||||||
addOverdue();
|
|
||||||
} else if (selected.equals(getString(R.string.randomly))) {
|
|
||||||
addRandomReminder(TimeUnit.DAYS.toMillis(14));
|
|
||||||
} else if (selected.equals(getString(R.string.pick_a_date_and_time))) {
|
|
||||||
addNewAlarm();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.alarms_add)
|
|
||||||
void addAlarm() {
|
|
||||||
List<String> options = getOptions();
|
|
||||||
if (options.size() == 1) {
|
|
||||||
addNewAlarm();
|
|
||||||
} else {
|
|
||||||
dialogBuilder
|
|
||||||
.newDialog()
|
|
||||||
.setItems(
|
|
||||||
options,
|
|
||||||
(dialog, which) -> {
|
|
||||||
addAlarm(options.get(which));
|
|
||||||
dialog.dismiss();
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.control_set_reminders;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIcon() {
|
|
||||||
return R.drawable.ic_outline_notifications_24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setup(List<Long> alarms) {
|
|
||||||
setValue(flags);
|
|
||||||
|
|
||||||
alertContainer.removeAllViews();
|
|
||||||
if (whenDue) {
|
|
||||||
addDue();
|
|
||||||
}
|
|
||||||
if (whenOverdue) {
|
|
||||||
addOverdue();
|
|
||||||
}
|
|
||||||
if (randomReminder > 0) {
|
|
||||||
addRandomReminder(randomReminder);
|
|
||||||
}
|
|
||||||
for (long timestamp : alarms) {
|
|
||||||
addAlarmRow(timestamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasChanges(Task original) {
|
|
||||||
return getFlags() != original.getReminderFlags()
|
|
||||||
|| getRandomReminderPeriod() != original.getReminderPeriod()
|
|
||||||
|| !newHashSet(currentAlarms()).equals(alarms);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requiresId() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {
|
|
||||||
task.setReminderFlags(getFlags());
|
|
||||||
|
|
||||||
task.setReminderPeriod(getRandomReminderPeriod());
|
|
||||||
|
|
||||||
if (alarmService.synchronizeAlarms(task.getId(), alarms)) {
|
|
||||||
task.setModificationDate(DateUtilities.now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putInt(EXTRA_FLAGS, getFlags());
|
|
||||||
outState.putLong(EXTRA_RANDOM_REMINDER, getRandomReminderPeriod());
|
|
||||||
outState.putLongArray(EXTRA_ALARMS, Longs.toArray(alarms));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (requestCode == REQUEST_NEW_ALARM) {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
addAlarmRow(data.getLongExtra(MyTimePickerDialog.EXTRA_TIMESTAMP, 0L));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAlarmRow(final Long timestamp) {
|
|
||||||
if (alarms.add(timestamp)) {
|
|
||||||
addAlarmRow(getLongDateStringWithTime(timestamp, locale.getLocale()), v -> alarms.remove(timestamp));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getFlags() {
|
|
||||||
int value = 0;
|
|
||||||
if (whenDue) {
|
|
||||||
value |= Task.NOTIFY_AT_DEADLINE;
|
|
||||||
}
|
|
||||||
if (whenOverdue) {
|
|
||||||
value |= Task.NOTIFY_AFTER_DEADLINE;
|
|
||||||
}
|
|
||||||
|
|
||||||
value &= ~(Task.NOTIFY_MODE_FIVE | Task.NOTIFY_MODE_NONSTOP);
|
|
||||||
if (ringMode == 2) {
|
|
||||||
value |= Task.NOTIFY_MODE_NONSTOP;
|
|
||||||
} else if (ringMode == 1) {
|
|
||||||
value |= Task.NOTIFY_MODE_FIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getRandomReminderPeriod() {
|
|
||||||
return randomControlSet == null ? 0L : randomControlSet.getReminderPeriod();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addNewAlarm() {
|
|
||||||
Intent intent = new Intent(getActivity(), DateAndTimePickerActivity.class);
|
|
||||||
intent.putExtra(
|
|
||||||
DateAndTimePickerActivity.EXTRA_TIMESTAMP, newDateTime().noon().getMillis());
|
|
||||||
startActivityForResult(intent, REQUEST_NEW_ALARM);
|
|
||||||
}
|
|
||||||
|
|
||||||
private View addAlarmRow(String text, final OnClickListener onRemove) {
|
|
||||||
final View alertItem = getActivity().getLayoutInflater().inflate(R.layout.alarm_edit_row, null);
|
|
||||||
alertContainer.addView(alertItem);
|
|
||||||
addAlarmRow(alertItem, text, onRemove);
|
|
||||||
return alertItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addAlarmRow(final View alertItem, String text, final View.OnClickListener onRemove) {
|
|
||||||
TextView display = alertItem.findViewById(R.id.alarm_string);
|
|
||||||
display.setText(text);
|
|
||||||
alertItem
|
|
||||||
.findViewById(R.id.clear)
|
|
||||||
.setOnClickListener(
|
|
||||||
v -> {
|
|
||||||
alertContainer.removeView(alertItem);
|
|
||||||
if (onRemove != null) {
|
|
||||||
onRemove.onClick(v);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getOptions() {
|
|
||||||
List<String> options = new ArrayList<>();
|
|
||||||
if (!whenDue) {
|
|
||||||
options.add(getString(R.string.when_due));
|
|
||||||
}
|
|
||||||
if (!whenOverdue) {
|
|
||||||
options.add(getString(R.string.when_overdue));
|
|
||||||
}
|
|
||||||
if (randomControlSet == null) {
|
|
||||||
options.add(getString(R.string.randomly));
|
|
||||||
}
|
|
||||||
options.add(getString(R.string.pick_a_date_and_time));
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addDue() {
|
|
||||||
whenDue = true;
|
|
||||||
addAlarmRow(getString(R.string.when_due), v -> whenDue = false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addOverdue() {
|
|
||||||
whenOverdue = true;
|
|
||||||
addAlarmRow(getString(R.string.when_overdue), v -> whenOverdue = false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addRandomReminder(long reminderPeriod) {
|
|
||||||
View alarmRow =
|
|
||||||
addAlarmRow(getString(R.string.randomly_once) + " ", v -> randomControlSet = null);
|
|
||||||
randomControlSet = new RandomReminderControlSet(context, alarmRow, reminderPeriod);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setValue(int flags) {
|
|
||||||
whenDue = (flags & Task.NOTIFY_AT_DEADLINE) > 0;
|
|
||||||
whenOverdue = (flags & Task.NOTIFY_AFTER_DEADLINE) > 0;
|
|
||||||
|
|
||||||
if ((flags & Task.NOTIFY_MODE_NONSTOP) > 0) {
|
|
||||||
setRingMode(2);
|
|
||||||
} else if ((flags & Task.NOTIFY_MODE_FIVE) > 0) {
|
|
||||||
setRingMode(1);
|
|
||||||
} else {
|
|
||||||
setRingMode(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,303 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Todoroo Inc
|
||||||
|
*
|
||||||
|
* See the file "LICENSE" for the full license governing this code.
|
||||||
|
*/
|
||||||
|
package com.todoroo.astrid.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.OnClick
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities
|
||||||
|
import com.todoroo.astrid.alarms.AlarmService
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.activities.DateAndTimePickerActivity
|
||||||
|
import org.tasks.data.Alarm
|
||||||
|
import org.tasks.date.DateTimeUtils
|
||||||
|
import org.tasks.dialogs.DialogBuilder
|
||||||
|
import org.tasks.dialogs.MyTimePickerDialog
|
||||||
|
import org.tasks.injection.ForActivity
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.locale.Locale
|
||||||
|
import org.tasks.ui.TaskEditControlFragment
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control set dealing with reminder settings
|
||||||
|
*
|
||||||
|
* @author Tim Su <tim></tim>@todoroo.com>
|
||||||
|
*/
|
||||||
|
class ReminderControlSet : TaskEditControlFragment() {
|
||||||
|
private val alarms: MutableSet<Long> = LinkedHashSet()
|
||||||
|
|
||||||
|
@Inject @ForActivity lateinit var activity: Context
|
||||||
|
@Inject lateinit var alarmService: AlarmService
|
||||||
|
@Inject lateinit var locale: Locale
|
||||||
|
@Inject lateinit var dialogBuilder: DialogBuilder
|
||||||
|
|
||||||
|
@BindView(R.id.alert_container)
|
||||||
|
lateinit var alertContainer: LinearLayout
|
||||||
|
|
||||||
|
@BindView(R.id.reminder_alarm)
|
||||||
|
lateinit var mode: TextView
|
||||||
|
|
||||||
|
private var taskId: Long = 0
|
||||||
|
private var flags = 0
|
||||||
|
private var randomReminder: Long = 0
|
||||||
|
private var ringMode = 0
|
||||||
|
private var randomControlSet: RandomReminderControlSet? = null
|
||||||
|
private var whenDue = false
|
||||||
|
private var whenOverdue = false
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
mode.paintFlags = mode.paintFlags or Paint.UNDERLINE_TEXT_FLAG
|
||||||
|
taskId = task.id
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
flags = task.reminderFlags
|
||||||
|
randomReminder = task.reminderPeriod
|
||||||
|
setup(currentAlarms())
|
||||||
|
} else {
|
||||||
|
flags = savedInstanceState.getInt(EXTRA_FLAGS)
|
||||||
|
randomReminder = savedInstanceState.getLong(EXTRA_RANDOM_REMINDER)
|
||||||
|
setup(savedInstanceState.getLongArray(EXTRA_ALARMS)!!.toList())
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun currentAlarms(): List<Long> {
|
||||||
|
return if (taskId == Task.NO_ID) {
|
||||||
|
emptyList()
|
||||||
|
} else {
|
||||||
|
alarmService.getAlarms(taskId).map(Alarm::time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.reminder_alarm)
|
||||||
|
fun onClickRingType() {
|
||||||
|
val modes = resources.getStringArray(R.array.reminder_ring_modes)
|
||||||
|
dialogBuilder
|
||||||
|
.newDialog()
|
||||||
|
.setSingleChoiceItems(modes, ringMode) { dialog: DialogInterface, which: Int ->
|
||||||
|
setRingMode(which)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setRingMode(ringMode: Int) {
|
||||||
|
this.ringMode = ringMode
|
||||||
|
mode.setText(getRingModeString(ringMode))
|
||||||
|
}
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
private fun getRingModeString(ringMode: Int): Int {
|
||||||
|
return when (ringMode) {
|
||||||
|
2 -> R.string.ring_nonstop
|
||||||
|
1 -> R.string.ring_five_times
|
||||||
|
else -> R.string.ring_once
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addAlarm(selected: String) {
|
||||||
|
when (selected) {
|
||||||
|
getString(R.string.when_due) -> addDue()
|
||||||
|
getString(R.string.when_overdue) -> addOverdue()
|
||||||
|
getString(R.string.randomly) -> addRandomReminder(TimeUnit.DAYS.toMillis(14))
|
||||||
|
getString(R.string.pick_a_date_and_time) -> addNewAlarm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.alarms_add)
|
||||||
|
fun addAlarm() {
|
||||||
|
val options = options
|
||||||
|
if (options.size == 1) {
|
||||||
|
addNewAlarm()
|
||||||
|
} else {
|
||||||
|
dialogBuilder
|
||||||
|
.newDialog()
|
||||||
|
.setItems(
|
||||||
|
options
|
||||||
|
) { dialog: DialogInterface, which: Int ->
|
||||||
|
addAlarm(options[which])
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.control_set_reminders
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_outline_notifications_24px
|
||||||
|
|
||||||
|
override fun controlId(): Int {
|
||||||
|
return TAG
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setup(alarms: List<Long>) {
|
||||||
|
setValue(flags)
|
||||||
|
alertContainer.removeAllViews()
|
||||||
|
if (whenDue) {
|
||||||
|
addDue()
|
||||||
|
}
|
||||||
|
if (whenOverdue) {
|
||||||
|
addOverdue()
|
||||||
|
}
|
||||||
|
if (randomReminder > 0) {
|
||||||
|
addRandomReminder(randomReminder)
|
||||||
|
}
|
||||||
|
for (timestamp in alarms) {
|
||||||
|
addAlarmRow(timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasChanges(original: Task): Boolean {
|
||||||
|
return getFlags() != original.reminderFlags || randomReminderPeriod != original.reminderPeriod || HashSet(currentAlarms()) != alarms
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun requiresId() = true
|
||||||
|
|
||||||
|
override fun apply(task: Task) {
|
||||||
|
task.reminderFlags = getFlags()
|
||||||
|
task.reminderPeriod = randomReminderPeriod
|
||||||
|
if (alarmService.synchronizeAlarms(task.id, alarms)) {
|
||||||
|
task.modificationDate = DateUtilities.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putInt(EXTRA_FLAGS, getFlags())
|
||||||
|
outState.putLong(EXTRA_RANDOM_REMINDER, randomReminderPeriod)
|
||||||
|
outState.putLongArray(EXTRA_ALARMS, alarms.toLongArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (requestCode == REQUEST_NEW_ALARM) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
addAlarmRow(data!!.getLongExtra(MyTimePickerDialog.EXTRA_TIMESTAMP, 0L))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addAlarmRow(timestamp: Long) {
|
||||||
|
if (alarms.add(timestamp)) {
|
||||||
|
addAlarmRow(DateUtilities.getLongDateStringWithTime(timestamp, locale.locale), View.OnClickListener { alarms.remove(timestamp) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFlags(): Int {
|
||||||
|
var value = 0
|
||||||
|
if (whenDue) {
|
||||||
|
value = value or Task.NOTIFY_AT_DEADLINE
|
||||||
|
}
|
||||||
|
if (whenOverdue) {
|
||||||
|
value = value or Task.NOTIFY_AFTER_DEADLINE
|
||||||
|
}
|
||||||
|
value = value and (Task.NOTIFY_MODE_FIVE or Task.NOTIFY_MODE_NONSTOP).inv()
|
||||||
|
if (ringMode == 2) {
|
||||||
|
value = value or Task.NOTIFY_MODE_NONSTOP
|
||||||
|
} else if (ringMode == 1) {
|
||||||
|
value = value or Task.NOTIFY_MODE_FIVE
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
private val randomReminderPeriod: Long
|
||||||
|
get() = if (randomControlSet == null) 0L else randomControlSet!!.reminderPeriod
|
||||||
|
|
||||||
|
private fun addNewAlarm() {
|
||||||
|
val intent = Intent(activity, DateAndTimePickerActivity::class.java)
|
||||||
|
intent.putExtra(
|
||||||
|
DateAndTimePickerActivity.EXTRA_TIMESTAMP, DateTimeUtils.newDateTime().noon().millis)
|
||||||
|
startActivityForResult(intent, REQUEST_NEW_ALARM)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addAlarmRow(text: String, onRemove: View.OnClickListener): View {
|
||||||
|
val alertItem = requireActivity().layoutInflater.inflate(R.layout.alarm_edit_row, null)
|
||||||
|
alertContainer.addView(alertItem)
|
||||||
|
addAlarmRow(alertItem, text, onRemove)
|
||||||
|
return alertItem
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addAlarmRow(alertItem: View, text: String, onRemove: View.OnClickListener?) {
|
||||||
|
val display = alertItem.findViewById<TextView>(R.id.alarm_string)
|
||||||
|
display.text = text
|
||||||
|
alertItem
|
||||||
|
.findViewById<View>(R.id.clear)
|
||||||
|
.setOnClickListener { v: View? ->
|
||||||
|
alertContainer.removeView(alertItem)
|
||||||
|
onRemove?.onClick(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val options: List<String>
|
||||||
|
get() {
|
||||||
|
val options: MutableList<String> = ArrayList()
|
||||||
|
if (!whenDue) {
|
||||||
|
options.add(getString(R.string.when_due))
|
||||||
|
}
|
||||||
|
if (!whenOverdue) {
|
||||||
|
options.add(getString(R.string.when_overdue))
|
||||||
|
}
|
||||||
|
if (randomControlSet == null) {
|
||||||
|
options.add(getString(R.string.randomly))
|
||||||
|
}
|
||||||
|
options.add(getString(R.string.pick_a_date_and_time))
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addDue() {
|
||||||
|
whenDue = true
|
||||||
|
addAlarmRow(getString(R.string.when_due), View.OnClickListener { whenDue = false })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addOverdue() {
|
||||||
|
whenOverdue = true
|
||||||
|
addAlarmRow(getString(R.string.when_overdue), View.OnClickListener { whenOverdue = false })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addRandomReminder(reminderPeriod: Long) {
|
||||||
|
val alarmRow = addAlarmRow(getString(R.string.randomly_once) + " ", View.OnClickListener { randomControlSet = null })
|
||||||
|
randomControlSet = RandomReminderControlSet(activity, alarmRow, reminderPeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setValue(flags: Int) {
|
||||||
|
whenDue = flags and Task.NOTIFY_AT_DEADLINE > 0
|
||||||
|
whenOverdue = flags and Task.NOTIFY_AFTER_DEADLINE > 0
|
||||||
|
when {
|
||||||
|
flags and Task.NOTIFY_MODE_NONSTOP > 0 -> setRingMode(2)
|
||||||
|
flags and Task.NOTIFY_MODE_FIVE > 0 -> setRingMode(1)
|
||||||
|
else -> setRingMode(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) = component.inject(this)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_reminders_pref
|
||||||
|
private const val REQUEST_NEW_ALARM = 12152
|
||||||
|
private const val EXTRA_FLAGS = "extra_flags"
|
||||||
|
private const val EXTRA_RANDOM_REMINDER = "extra_random_reminder"
|
||||||
|
private const val EXTRA_ALARMS = "extra_alarms"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,102 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2012 Todoroo Inc
|
|
||||||
*
|
|
||||||
* See the file "LICENSE" for the full license governing this code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.todoroo.astrid.ui;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.text.format.DateUtils;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import com.todoroo.astrid.ui.NNumberPickerDialog.OnNNumberPickedListener;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.themes.Theme;
|
|
||||||
|
|
||||||
public class TimeDurationControlSet implements OnNNumberPickedListener, View.OnClickListener {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final Theme theme;
|
|
||||||
private final TextView timeButton;
|
|
||||||
private int timeDuration;
|
|
||||||
private int[] initialValues = null;
|
|
||||||
private NNumberPickerDialog dialog = null;
|
|
||||||
|
|
||||||
public TimeDurationControlSet(Context context, View view, int timeButtonId, Theme theme) {
|
|
||||||
this.context = context;
|
|
||||||
this.theme = theme;
|
|
||||||
|
|
||||||
timeButton = view.findViewById(timeButtonId);
|
|
||||||
((View) timeButton.getParent()).setOnClickListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTimeDurationInSeconds() {
|
|
||||||
return timeDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimeDuration(Integer timeDurationInSeconds) {
|
|
||||||
if (timeDurationInSeconds == null) {
|
|
||||||
timeDurationInSeconds = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
timeDuration = timeDurationInSeconds;
|
|
||||||
|
|
||||||
if (timeDurationInSeconds == 0) {
|
|
||||||
timeButton.setText(context.getString(R.string.WID_dateButtonUnset));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
timeButton.setText(DateUtils.formatElapsedTime(timeDuration));
|
|
||||||
int hours = timeDuration / 3600;
|
|
||||||
int minutes = timeDuration / 60 - 60 * hours;
|
|
||||||
initialValues = new int[] {hours, minutes};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Called when NumberPicker activity is completed */
|
|
||||||
@Override
|
|
||||||
public void onNumbersPicked(int[] values) {
|
|
||||||
setTimeDuration(values[0] * 3600 + values[1] * 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Called when time button is clicked */
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
if (dialog == null) {
|
|
||||||
dialog =
|
|
||||||
new NNumberPickerDialog(
|
|
||||||
context,
|
|
||||||
this,
|
|
||||||
context.getString(R.string.DLG_hour_minutes),
|
|
||||||
new int[] {0, 0},
|
|
||||||
new int[] {1, 5},
|
|
||||||
new int[] {0, 0},
|
|
||||||
new int[] {999, 59},
|
|
||||||
new String[] {":", null});
|
|
||||||
final NumberPicker hourPicker = dialog.getPicker(0);
|
|
||||||
final NumberPicker minutePicker = dialog.getPicker(1);
|
|
||||||
minutePicker.setFormatter(value -> String.format("%02d", value));
|
|
||||||
minutePicker.setOnChangeListener(
|
|
||||||
newVal -> {
|
|
||||||
if (newVal < 0) {
|
|
||||||
if (hourPicker.getCurrent() == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
hourPicker.setCurrent(hourPicker.getCurrent() - 1);
|
|
||||||
return 60 + newVal;
|
|
||||||
} else if (newVal > 59) {
|
|
||||||
hourPicker.setCurrent(hourPicker.getCurrent() + 1);
|
|
||||||
return newVal % 60;
|
|
||||||
}
|
|
||||||
return newVal;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialValues != null) {
|
|
||||||
dialog.setInitialValues(initialValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
theme.applyToContext(dialog.getContext());
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2012 Todoroo Inc
|
||||||
|
*
|
||||||
|
* See the file "LICENSE" for the full license governing this code.
|
||||||
|
*/
|
||||||
|
package com.todoroo.astrid.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.todoroo.astrid.ui.NNumberPickerDialog.OnNNumberPickedListener
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.themes.Theme
|
||||||
|
|
||||||
|
class TimeDurationControlSet(private val context: Context, view: View, timeButtonId: Int, private val theme: Theme) : OnNNumberPickedListener, View.OnClickListener {
|
||||||
|
private val timeButton: TextView = view.findViewById(timeButtonId)
|
||||||
|
var timeDurationInSeconds = 0
|
||||||
|
private set
|
||||||
|
private var initialValues: IntArray? = null
|
||||||
|
private var dialog: NNumberPickerDialog? = null
|
||||||
|
|
||||||
|
fun setTimeDuration(timeDurationInSeconds: Int) {
|
||||||
|
this.timeDurationInSeconds = timeDurationInSeconds
|
||||||
|
if (timeDurationInSeconds == 0) {
|
||||||
|
timeButton.text = context.getString(R.string.WID_dateButtonUnset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timeButton.text = DateUtils.formatElapsedTime(timeDurationInSeconds.toLong())
|
||||||
|
val hours = this.timeDurationInSeconds / 3600
|
||||||
|
val minutes = this.timeDurationInSeconds / 60 - 60 * hours
|
||||||
|
initialValues = intArrayOf(hours, minutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when NumberPicker activity is completed */
|
||||||
|
override fun onNumbersPicked(values: IntArray) {
|
||||||
|
setTimeDuration(values[0] * 3600 + values[1] * 60)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when time button is clicked */
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
if (dialog == null) {
|
||||||
|
dialog = NNumberPickerDialog(
|
||||||
|
context,
|
||||||
|
this,
|
||||||
|
context.getString(R.string.DLG_hour_minutes), intArrayOf(0, 0), intArrayOf(1, 5), intArrayOf(0, 0), intArrayOf(999, 59), arrayOf(":", null))
|
||||||
|
val hourPicker = dialog!!.getPicker(0)
|
||||||
|
val minutePicker = dialog!!.getPicker(1)
|
||||||
|
minutePicker.setFormatter { value: Int -> String.format("%02d", value) }
|
||||||
|
minutePicker.setOnChangeListener { newVal: Int ->
|
||||||
|
if (newVal < 0) {
|
||||||
|
if (hourPicker.current == 0) {
|
||||||
|
return@setOnChangeListener 0
|
||||||
|
}
|
||||||
|
hourPicker.current = hourPicker.current - 1
|
||||||
|
return@setOnChangeListener 60 + newVal
|
||||||
|
} else if (newVal > 59) {
|
||||||
|
hourPicker.current = hourPicker.current + 1
|
||||||
|
return@setOnChangeListener newVal % 60
|
||||||
|
}
|
||||||
|
newVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (initialValues != null) {
|
||||||
|
dialog!!.setInitialValues(initialValues)
|
||||||
|
}
|
||||||
|
theme.applyToContext(dialog!!.context)
|
||||||
|
dialog!!.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
(timeButton.parent as View).setOnClickListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,270 +0,0 @@
|
|||||||
package org.tasks.fragments;
|
|
||||||
|
|
||||||
import static org.tasks.Strings.isNullOrEmpty;
|
|
||||||
import static org.tasks.files.ImageHelper.sampleBitmap;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.TypedValue;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.inputmethod.EditorInfo;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import butterknife.OnEditorAction;
|
|
||||||
import butterknife.OnTextChanged;
|
|
||||||
import com.todoroo.andlib.utility.AndroidUtilities;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.activities.CameraActivity;
|
|
||||||
import org.tasks.dialogs.DialogBuilder;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.preferences.Device;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
import org.tasks.themes.ThemeColor;
|
|
||||||
import org.tasks.ui.TaskEditControlFragment;
|
|
||||||
|
|
||||||
public class CommentBarFragment extends TaskEditControlFragment {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_comments;
|
|
||||||
private static final int REQUEST_CODE_CAMERA = 60;
|
|
||||||
private static final String EXTRA_TEXT = "extra_text";
|
|
||||||
private static final String EXTRA_PICTURE = "extra_picture";
|
|
||||||
@Inject Activity activity;
|
|
||||||
@Inject DialogBuilder dialogBuilder;
|
|
||||||
@Inject Device device;
|
|
||||||
@Inject Preferences preferences;
|
|
||||||
@Inject ThemeColor themeColor;
|
|
||||||
|
|
||||||
@BindView(R.id.commentButton)
|
|
||||||
View commentButton;
|
|
||||||
|
|
||||||
@BindView(R.id.commentField)
|
|
||||||
EditText commentField;
|
|
||||||
|
|
||||||
@BindView(R.id.picture)
|
|
||||||
ImageView pictureButton;
|
|
||||||
|
|
||||||
@BindView(R.id.updatesFooter)
|
|
||||||
LinearLayout commentBar;
|
|
||||||
|
|
||||||
private CommentBarFragmentCallback callback;
|
|
||||||
private Uri pendingCommentPicture = null;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
|
|
||||||
callback = (CommentBarFragmentCallback) activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = inflater.inflate(getLayout(), container, false);
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
String uri = savedInstanceState.getString(EXTRA_PICTURE);
|
|
||||||
if (uri != null) {
|
|
||||||
pendingCommentPicture = Uri.parse(uri);
|
|
||||||
setPictureButtonToPendingPicture();
|
|
||||||
}
|
|
||||||
commentField.setText(savedInstanceState.getString(EXTRA_TEXT));
|
|
||||||
}
|
|
||||||
|
|
||||||
commentField.setHorizontallyScrolling(false);
|
|
||||||
commentField.setMaxLines(Integer.MAX_VALUE);
|
|
||||||
|
|
||||||
if (!preferences.getBoolean(R.string.p_show_task_edit_comments, true)) {
|
|
||||||
commentBar.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
commentBar.setBackgroundColor(themeColor.getPrimaryColor());
|
|
||||||
|
|
||||||
resetPictureButton();
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.fragment_comment_bar;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getIcon() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {}
|
|
||||||
|
|
||||||
@OnTextChanged(R.id.commentField)
|
|
||||||
void onTextChanged(CharSequence s) {
|
|
||||||
commentButton.setVisibility(
|
|
||||||
pendingCommentPicture == null && isNullOrEmpty(s.toString())
|
|
||||||
? View.GONE
|
|
||||||
: View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnEditorAction(R.id.commentField)
|
|
||||||
boolean onEditorAction(KeyEvent key) {
|
|
||||||
int actionId = key != null ? key.getAction() : 0;
|
|
||||||
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) {
|
|
||||||
if (commentField.getText().length() > 0 || pendingCommentPicture != null) {
|
|
||||||
addComment();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.commentButton)
|
|
||||||
void addClicked() {
|
|
||||||
addComment();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putString(EXTRA_TEXT, commentField.getText().toString());
|
|
||||||
if (pendingCommentPicture != null) {
|
|
||||||
outState.putString(EXTRA_PICTURE, pendingCommentPicture.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.picture)
|
|
||||||
void onClickPicture() {
|
|
||||||
if (pendingCommentPicture == null) {
|
|
||||||
showPictureLauncher(null);
|
|
||||||
} else {
|
|
||||||
showPictureLauncher(
|
|
||||||
() -> {
|
|
||||||
pendingCommentPicture = null;
|
|
||||||
resetPictureButton();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (requestCode == REQUEST_CODE_CAMERA) {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
pendingCommentPicture = data.getData();
|
|
||||||
setPictureButtonToPendingPicture();
|
|
||||||
commentField.requestFocus();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addComment() {
|
|
||||||
addComment(commentField.getText().toString());
|
|
||||||
AndroidUtilities.hideSoftInputForViews(activity, commentField);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setPictureButtonToPendingPicture() {
|
|
||||||
Bitmap bitmap =
|
|
||||||
sampleBitmap(
|
|
||||||
activity,
|
|
||||||
pendingCommentPicture,
|
|
||||||
pictureButton.getLayoutParams().width,
|
|
||||||
pictureButton.getLayoutParams().height);
|
|
||||||
pictureButton.setImageBitmap(bitmap);
|
|
||||||
commentButton.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addComment(String message) {
|
|
||||||
// Allow for users to just add picture
|
|
||||||
if (isNullOrEmpty(message)) {
|
|
||||||
message = " ";
|
|
||||||
}
|
|
||||||
Uri picture = pendingCommentPicture;
|
|
||||||
|
|
||||||
if (commentField != null) {
|
|
||||||
commentField.setText(""); // $NON-NLS-1$
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingCommentPicture = null;
|
|
||||||
resetPictureButton();
|
|
||||||
callback.addComment(message, picture);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetPictureButton() {
|
|
||||||
TypedValue typedValue = new TypedValue();
|
|
||||||
getActivity().getTheme().resolveAttribute(R.attr.colorOnPrimary, typedValue, true);
|
|
||||||
Drawable drawable = getContext().getDrawable(R.drawable.ic_outline_photo_camera_24px).mutate();
|
|
||||||
drawable.setTint(typedValue.data);
|
|
||||||
pictureButton.setImageDrawable(drawable);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showPictureLauncher(final ClearImageCallback clearImageOption) {
|
|
||||||
final List<Runnable> runnables = new ArrayList<>();
|
|
||||||
List<String> options = new ArrayList<>();
|
|
||||||
|
|
||||||
final boolean cameraAvailable = device.hasCamera();
|
|
||||||
if (cameraAvailable) {
|
|
||||||
runnables.add(
|
|
||||||
() ->
|
|
||||||
startActivityForResult(
|
|
||||||
new Intent(activity, CameraActivity.class), REQUEST_CODE_CAMERA));
|
|
||||||
options.add(getString(R.string.take_a_picture));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clearImageOption != null) {
|
|
||||||
runnables.add(clearImageOption::clearImage);
|
|
||||||
options.add(getString(R.string.actfm_picture_clear));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runnables.size() == 1) {
|
|
||||||
runnables.get(0).run();
|
|
||||||
} else {
|
|
||||||
DialogInterface.OnClickListener listener =
|
|
||||||
(d, which) -> {
|
|
||||||
runnables.get(which).run();
|
|
||||||
d.dismiss();
|
|
||||||
};
|
|
||||||
|
|
||||||
// show a menu of available options
|
|
||||||
dialogBuilder.newDialog().setItems(options, listener).show().setOwnerActivity(activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface CommentBarFragmentCallback {
|
|
||||||
|
|
||||||
void addComment(String message, Uri picture);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ClearImageCallback {
|
|
||||||
|
|
||||||
void clearImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,217 @@
|
|||||||
|
package org.tasks.fragments
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import butterknife.*
|
||||||
|
import com.todoroo.andlib.utility.AndroidUtilities
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.Strings.isNullOrEmpty
|
||||||
|
import org.tasks.activities.CameraActivity
|
||||||
|
import org.tasks.dialogs.DialogBuilder
|
||||||
|
import org.tasks.files.ImageHelper
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.preferences.Device
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import org.tasks.themes.ThemeColor
|
||||||
|
import org.tasks.ui.TaskEditControlFragment
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CommentBarFragment : TaskEditControlFragment() {
|
||||||
|
@Inject lateinit var activity: Activity
|
||||||
|
@Inject lateinit var dialogBuilder: DialogBuilder
|
||||||
|
@Inject lateinit var device: Device
|
||||||
|
@Inject lateinit var preferences: Preferences
|
||||||
|
@Inject lateinit var themeColor: ThemeColor
|
||||||
|
|
||||||
|
@BindView(R.id.commentButton)
|
||||||
|
lateinit var commentButton: View
|
||||||
|
|
||||||
|
@BindView(R.id.commentField)
|
||||||
|
lateinit var commentField: EditText
|
||||||
|
|
||||||
|
@BindView(R.id.picture)
|
||||||
|
lateinit var pictureButton: ImageView
|
||||||
|
|
||||||
|
@BindView(R.id.updatesFooter)
|
||||||
|
lateinit var commentBar: LinearLayout
|
||||||
|
|
||||||
|
private lateinit var callback: CommentBarFragmentCallback
|
||||||
|
private var pendingCommentPicture: Uri? = null
|
||||||
|
|
||||||
|
override fun onAttach(activity: Activity) {
|
||||||
|
super.onAttach(activity)
|
||||||
|
callback = activity as CommentBarFragmentCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) = component.inject(this)
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = inflater.inflate(layout, container, false)
|
||||||
|
ButterKnife.bind(this, view)
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
val uri = savedInstanceState.getString(EXTRA_PICTURE)
|
||||||
|
if (uri != null) {
|
||||||
|
pendingCommentPicture = Uri.parse(uri)
|
||||||
|
setPictureButtonToPendingPicture()
|
||||||
|
}
|
||||||
|
commentField.setText(savedInstanceState.getString(EXTRA_TEXT))
|
||||||
|
}
|
||||||
|
commentField.setHorizontallyScrolling(false)
|
||||||
|
commentField.maxLines = Int.MAX_VALUE
|
||||||
|
if (!preferences.getBoolean(R.string.p_show_task_edit_comments, true)) {
|
||||||
|
commentBar.visibility = View.GONE
|
||||||
|
}
|
||||||
|
commentBar.setBackgroundColor(themeColor.primaryColor)
|
||||||
|
resetPictureButton()
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.fragment_comment_bar
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = 0
|
||||||
|
|
||||||
|
override fun controlId() = TAG
|
||||||
|
|
||||||
|
override fun apply(task: Task) {}
|
||||||
|
|
||||||
|
@OnTextChanged(R.id.commentField)
|
||||||
|
fun onTextChanged(s: CharSequence) {
|
||||||
|
commentButton.visibility = if (pendingCommentPicture == null && isNullOrEmpty(s.toString())) View.GONE else View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnEditorAction(R.id.commentField)
|
||||||
|
fun onEditorAction(key: KeyEvent?): Boolean {
|
||||||
|
val actionId = key?.action ?: 0
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) {
|
||||||
|
if (commentField.text.isNotEmpty() || pendingCommentPicture != null) {
|
||||||
|
addComment()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.commentButton)
|
||||||
|
fun addClicked() {
|
||||||
|
addComment()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putString(EXTRA_TEXT, commentField.text.toString())
|
||||||
|
if (pendingCommentPicture != null) {
|
||||||
|
outState.putString(EXTRA_PICTURE, pendingCommentPicture.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.picture)
|
||||||
|
fun onClickPicture() {
|
||||||
|
if (pendingCommentPicture == null) {
|
||||||
|
showPictureLauncher(null)
|
||||||
|
} else {
|
||||||
|
showPictureLauncher {
|
||||||
|
pendingCommentPicture = null
|
||||||
|
resetPictureButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (requestCode == REQUEST_CODE_CAMERA) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
pendingCommentPicture = data!!.data
|
||||||
|
setPictureButtonToPendingPicture()
|
||||||
|
commentField.requestFocus()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addComment() {
|
||||||
|
addComment(commentField.text.toString())
|
||||||
|
AndroidUtilities.hideSoftInputForViews(activity, commentField)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setPictureButtonToPendingPicture() {
|
||||||
|
val bitmap = ImageHelper.sampleBitmap(
|
||||||
|
activity,
|
||||||
|
pendingCommentPicture,
|
||||||
|
pictureButton.layoutParams.width,
|
||||||
|
pictureButton.layoutParams.height)
|
||||||
|
pictureButton.setImageBitmap(bitmap)
|
||||||
|
commentButton.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addComment(message: String) {
|
||||||
|
val picture = pendingCommentPicture
|
||||||
|
commentField.setText("")
|
||||||
|
pendingCommentPicture = null
|
||||||
|
resetPictureButton()
|
||||||
|
callback.addComment(if (isNullOrEmpty(message)) " " else message, picture)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetPictureButton() {
|
||||||
|
val typedValue = TypedValue()
|
||||||
|
activity.theme.resolveAttribute(R.attr.colorOnPrimary, typedValue, true)
|
||||||
|
val drawable = activity.getDrawable(R.drawable.ic_outline_photo_camera_24px)!!.mutate()
|
||||||
|
drawable.setTint(typedValue.data)
|
||||||
|
pictureButton.setImageDrawable(drawable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showPictureLauncher(clearImageOption: (() -> Unit)?) {
|
||||||
|
val runnables: MutableList<() -> Unit> = ArrayList()
|
||||||
|
val options: MutableList<String> = ArrayList()
|
||||||
|
val cameraAvailable = device.hasCamera()
|
||||||
|
if (cameraAvailable) {
|
||||||
|
runnables.add {
|
||||||
|
startActivityForResult(
|
||||||
|
Intent(activity, CameraActivity::class.java), REQUEST_CODE_CAMERA)
|
||||||
|
}
|
||||||
|
options.add(getString(R.string.take_a_picture))
|
||||||
|
}
|
||||||
|
if (clearImageOption != null) {
|
||||||
|
runnables.add { clearImageOption.invoke() }
|
||||||
|
options.add(getString(R.string.actfm_picture_clear))
|
||||||
|
}
|
||||||
|
if (runnables.size == 1) {
|
||||||
|
runnables[0].invoke()
|
||||||
|
} else {
|
||||||
|
val listener = DialogInterface.OnClickListener { d: DialogInterface, which: Int ->
|
||||||
|
runnables[which].invoke()
|
||||||
|
d.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
// show a menu of available options
|
||||||
|
dialogBuilder.newDialog().setItems(options, listener).show().setOwnerActivity(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommentBarFragmentCallback {
|
||||||
|
fun addComment(message: String?, picture: Uri?)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_comments
|
||||||
|
private const val REQUEST_CODE_CAMERA = 60
|
||||||
|
private const val EXTRA_TEXT = "extra_text"
|
||||||
|
private const val EXTRA_PICTURE = "extra_picture"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,339 +0,0 @@
|
|||||||
package org.tasks.ui;
|
|
||||||
|
|
||||||
import static org.tasks.PermissionUtil.verifyPermissions;
|
|
||||||
import static org.tasks.Strings.isNullOrEmpty;
|
|
||||||
import static org.tasks.calendars.CalendarPicker.newCalendarPicker;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.provider.CalendarContract;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import com.todoroo.astrid.gcal.GCalHelper;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.analytics.Firebase;
|
|
||||||
import org.tasks.calendars.AndroidCalendar;
|
|
||||||
import org.tasks.calendars.CalendarEventProvider;
|
|
||||||
import org.tasks.calendars.CalendarPicker;
|
|
||||||
import org.tasks.calendars.CalendarProvider;
|
|
||||||
import org.tasks.dialogs.DialogBuilder;
|
|
||||||
import org.tasks.injection.ForActivity;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.preferences.FragmentPermissionRequestor;
|
|
||||||
import org.tasks.preferences.PermissionChecker;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
import org.tasks.themes.ThemeBase;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class CalendarControlSet extends TaskEditControlFragment {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_gcal;
|
|
||||||
|
|
||||||
private static final String FRAG_TAG_CALENDAR_PICKER = "frag_tag_calendar_picker";
|
|
||||||
private static final int REQUEST_CODE_PICK_CALENDAR = 70;
|
|
||||||
private static final int REQUEST_CODE_OPEN_EVENT = 71;
|
|
||||||
private static final int REQUEST_CODE_CLEAR_EVENT = 72;
|
|
||||||
|
|
||||||
private static final String EXTRA_URI = "extra_uri";
|
|
||||||
private static final String EXTRA_ID = "extra_id";
|
|
||||||
|
|
||||||
@BindView(R.id.clear)
|
|
||||||
View cancelButton;
|
|
||||||
|
|
||||||
@BindView(R.id.calendar_display_which)
|
|
||||||
TextView calendar;
|
|
||||||
|
|
||||||
@Inject GCalHelper gcalHelper;
|
|
||||||
@Inject CalendarProvider calendarProvider;
|
|
||||||
@Inject Preferences preferences;
|
|
||||||
@Inject @ForActivity Context context;
|
|
||||||
@Inject PermissionChecker permissionChecker;
|
|
||||||
@Inject FragmentPermissionRequestor permissionRequestor;
|
|
||||||
@Inject Firebase firebase;
|
|
||||||
@Inject DialogBuilder dialogBuilder;
|
|
||||||
@Inject ThemeBase themeBase;
|
|
||||||
@Inject CalendarEventProvider calendarEventProvider;
|
|
||||||
|
|
||||||
private String calendarId;
|
|
||||||
private String eventUri;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
boolean canAccessCalendars = permissionChecker.canAccessCalendars();
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
eventUri = savedInstanceState.getString(EXTRA_URI);
|
|
||||||
calendarId = savedInstanceState.getString(EXTRA_ID);
|
|
||||||
} else if (task.isNew() && canAccessCalendars) {
|
|
||||||
calendarId = preferences.getDefaultCalendar();
|
|
||||||
if (!isNullOrEmpty(calendarId)) {
|
|
||||||
try {
|
|
||||||
AndroidCalendar defaultCalendar = calendarProvider.getCalendar(calendarId);
|
|
||||||
if (defaultCalendar == null) {
|
|
||||||
calendarId = null;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Timber.e(e);
|
|
||||||
firebase.reportException(e);
|
|
||||||
calendarId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eventUri = task.getCalendarURI();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canAccessCalendars && !calendarEntryExists(eventUri)) {
|
|
||||||
eventUri = null;
|
|
||||||
}
|
|
||||||
refreshDisplayView();
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.control_set_gcal_display;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getIcon() {
|
|
||||||
return R.drawable.ic_outline_event_24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("SimplifiableIfStatement")
|
|
||||||
@Override
|
|
||||||
public boolean hasChanges(Task original) {
|
|
||||||
if (!permissionChecker.canAccessCalendars()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!isNullOrEmpty(calendarId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
String originalUri = original.getCalendarURI();
|
|
||||||
if (isNullOrEmpty(eventUri) && isNullOrEmpty(originalUri)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !originalUri.equals(eventUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {
|
|
||||||
if (!permissionChecker.canAccessCalendars()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNullOrEmpty(task.getCalendarURI())) {
|
|
||||||
if (eventUri == null) {
|
|
||||||
calendarEventProvider.deleteEvent(task);
|
|
||||||
} else if (!calendarEntryExists(task.getCalendarURI())) {
|
|
||||||
task.setCalendarURI("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!task.hasDueDate()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (calendarEntryExists(task.getCalendarURI())) {
|
|
||||||
ContentResolver cr = context.getContentResolver();
|
|
||||||
try {
|
|
||||||
ContentValues updateValues = new ContentValues();
|
|
||||||
|
|
||||||
// check if we need to update the item
|
|
||||||
updateValues.put(CalendarContract.Events.TITLE, task.getTitle());
|
|
||||||
updateValues.put(CalendarContract.Events.DESCRIPTION, task.getNotes());
|
|
||||||
gcalHelper.createStartAndEndDate(task, updateValues);
|
|
||||||
|
|
||||||
cr.update(Uri.parse(task.getCalendarURI()), updateValues, null, null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Timber.e(e, "unable-to-update-calendar: %s", task.getCalendarURI());
|
|
||||||
}
|
|
||||||
} else if (!isNullOrEmpty(calendarId)) {
|
|
||||||
try {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(CalendarContract.Events.CALENDAR_ID, calendarId);
|
|
||||||
Uri uri = gcalHelper.createTaskEvent(task, values);
|
|
||||||
if (uri != null) {
|
|
||||||
task.setCalendarURI(uri.toString());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Timber.e(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putString(EXTRA_URI, eventUri);
|
|
||||||
outState.putString(EXTRA_ID, calendarId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.clear)
|
|
||||||
void clearCalendar() {
|
|
||||||
if (isNullOrEmpty(eventUri)) {
|
|
||||||
clear();
|
|
||||||
} else {
|
|
||||||
dialogBuilder
|
|
||||||
.newDialog(R.string.delete_calendar_event_confirmation)
|
|
||||||
.setPositiveButton(
|
|
||||||
R.string.delete,
|
|
||||||
(dialog, which) -> {
|
|
||||||
if (permissionRequestor.requestCalendarPermissions(REQUEST_CODE_CLEAR_EVENT)) {
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clear() {
|
|
||||||
calendarId = null;
|
|
||||||
eventUri = null;
|
|
||||||
refreshDisplayView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRowClick() {
|
|
||||||
if (isNullOrEmpty(eventUri)) {
|
|
||||||
newCalendarPicker(this, REQUEST_CODE_PICK_CALENDAR, getCalendarName())
|
|
||||||
.show(getParentFragmentManager(), FRAG_TAG_CALENDAR_PICKER);
|
|
||||||
} else {
|
|
||||||
if (permissionRequestor.requestCalendarPermissions(REQUEST_CODE_OPEN_EVENT)) {
|
|
||||||
openCalendarEvent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isClickable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openCalendarEvent() {
|
|
||||||
ContentResolver cr = getActivity().getContentResolver();
|
|
||||||
Uri uri = Uri.parse(eventUri);
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
|
||||||
try (Cursor cursor =
|
|
||||||
cr.query(
|
|
||||||
uri,
|
|
||||||
new String[] {CalendarContract.Events.DTSTART, CalendarContract.Events.DTEND},
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null)) {
|
|
||||||
if (cursor.getCount() == 0) {
|
|
||||||
// event no longer exists
|
|
||||||
Toast.makeText(context, R.string.calendar_event_not_found, Toast.LENGTH_SHORT).show();
|
|
||||||
eventUri = null;
|
|
||||||
refreshDisplayView();
|
|
||||||
} else {
|
|
||||||
cursor.moveToFirst();
|
|
||||||
intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, cursor.getLong(0));
|
|
||||||
intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, cursor.getLong(1));
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Timber.e(e);
|
|
||||||
Toast.makeText(getActivity(), R.string.gcal_TEA_error, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (requestCode == REQUEST_CODE_PICK_CALENDAR) {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
calendarId = data.getStringExtra(CalendarPicker.EXTRA_CALENDAR_ID);
|
|
||||||
refreshDisplayView();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCalendarName() {
|
|
||||||
if (calendarId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
AndroidCalendar calendar = calendarProvider.getCalendar(calendarId);
|
|
||||||
return calendar == null ? null : calendar.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(
|
|
||||||
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
|
||||||
if (requestCode == REQUEST_CODE_OPEN_EVENT) {
|
|
||||||
if (verifyPermissions(grantResults)) {
|
|
||||||
openCalendarEvent();
|
|
||||||
}
|
|
||||||
} else if (requestCode == REQUEST_CODE_CLEAR_EVENT) {
|
|
||||||
if (verifyPermissions(grantResults)) {
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshDisplayView() {
|
|
||||||
if (!isNullOrEmpty(eventUri)) {
|
|
||||||
calendar.setText(R.string.gcal_TEA_showCalendar_label);
|
|
||||||
cancelButton.setVisibility(View.VISIBLE);
|
|
||||||
} else if (calendarId != null) {
|
|
||||||
calendar.setText(getCalendarName());
|
|
||||||
cancelButton.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
calendar.setText(null);
|
|
||||||
cancelButton.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean calendarEntryExists(String eventUri) {
|
|
||||||
if (isNullOrEmpty(eventUri)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Uri uri = Uri.parse(eventUri);
|
|
||||||
ContentResolver contentResolver = context.getContentResolver();
|
|
||||||
try (Cursor cursor =
|
|
||||||
contentResolver.query(
|
|
||||||
uri, new String[] {CalendarContract.Events.DTSTART}, null, null, null)) {
|
|
||||||
if (cursor.getCount() != 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Timber.e(e, "%s: %s", eventUri, e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,299 @@
|
|||||||
|
package org.tasks.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.CalendarContract
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.OnClick
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.gcal.GCalHelper
|
||||||
|
import org.tasks.PermissionUtil.verifyPermissions
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.Strings.isNullOrEmpty
|
||||||
|
import org.tasks.analytics.Firebase
|
||||||
|
import org.tasks.calendars.CalendarEventProvider
|
||||||
|
import org.tasks.calendars.CalendarPicker
|
||||||
|
import org.tasks.calendars.CalendarProvider
|
||||||
|
import org.tasks.dialogs.DialogBuilder
|
||||||
|
import org.tasks.injection.ForActivity
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.preferences.FragmentPermissionRequestor
|
||||||
|
import org.tasks.preferences.PermissionChecker
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import org.tasks.themes.ThemeBase
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CalendarControlSet : TaskEditControlFragment() {
|
||||||
|
@BindView(R.id.clear)
|
||||||
|
lateinit var cancelButton: View
|
||||||
|
|
||||||
|
@BindView(R.id.calendar_display_which)
|
||||||
|
lateinit var calendar: TextView
|
||||||
|
|
||||||
|
@Inject @ForActivity lateinit var activity: Context
|
||||||
|
@Inject lateinit var gcalHelper: GCalHelper
|
||||||
|
@Inject lateinit var calendarProvider: CalendarProvider
|
||||||
|
@Inject lateinit var preferences: Preferences
|
||||||
|
@Inject lateinit var permissionChecker: PermissionChecker
|
||||||
|
@Inject lateinit var permissionRequestor: FragmentPermissionRequestor
|
||||||
|
@Inject lateinit var firebase: Firebase
|
||||||
|
@Inject lateinit var dialogBuilder: DialogBuilder
|
||||||
|
@Inject lateinit var themeBase: ThemeBase
|
||||||
|
@Inject lateinit var calendarEventProvider: CalendarEventProvider
|
||||||
|
|
||||||
|
private var calendarId: String? = null
|
||||||
|
private var eventUri: String? = null
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
val canAccessCalendars = permissionChecker.canAccessCalendars()
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
eventUri = savedInstanceState.getString(EXTRA_URI)
|
||||||
|
calendarId = savedInstanceState.getString(EXTRA_ID)
|
||||||
|
} else if (task.isNew && canAccessCalendars) {
|
||||||
|
calendarId = preferences.defaultCalendar
|
||||||
|
if (!isNullOrEmpty(calendarId)) {
|
||||||
|
try {
|
||||||
|
val defaultCalendar = calendarProvider.getCalendar(calendarId)
|
||||||
|
if (defaultCalendar == null) {
|
||||||
|
calendarId = null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
firebase.reportException(e)
|
||||||
|
calendarId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eventUri = task.calendarURI
|
||||||
|
}
|
||||||
|
if (canAccessCalendars && !calendarEntryExists(eventUri)) {
|
||||||
|
eventUri = null
|
||||||
|
}
|
||||||
|
refreshDisplayView()
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.control_set_gcal_display
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_outline_event_24px
|
||||||
|
|
||||||
|
override fun controlId() = TAG
|
||||||
|
|
||||||
|
override fun hasChanges(original: Task): Boolean {
|
||||||
|
if (!permissionChecker.canAccessCalendars()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!isNullOrEmpty(calendarId)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
val originalUri = original.calendarURI
|
||||||
|
return if (isNullOrEmpty(eventUri) && isNullOrEmpty(originalUri)) {
|
||||||
|
false
|
||||||
|
} else originalUri != eventUri
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(task: Task) {
|
||||||
|
if (!permissionChecker.canAccessCalendars()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!isNullOrEmpty(task.calendarURI)) {
|
||||||
|
if (eventUri == null) {
|
||||||
|
calendarEventProvider.deleteEvent(task)
|
||||||
|
} else if (!calendarEntryExists(task.calendarURI)) {
|
||||||
|
task.calendarURI = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!task.hasDueDate()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (calendarEntryExists(task.calendarURI)) {
|
||||||
|
val cr = activity.contentResolver
|
||||||
|
try {
|
||||||
|
val updateValues = ContentValues()
|
||||||
|
|
||||||
|
// check if we need to update the item
|
||||||
|
updateValues.put(CalendarContract.Events.TITLE, task.title)
|
||||||
|
updateValues.put(CalendarContract.Events.DESCRIPTION, task.notes)
|
||||||
|
gcalHelper.createStartAndEndDate(task, updateValues)
|
||||||
|
cr.update(Uri.parse(task.calendarURI), updateValues, null, null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "unable-to-update-calendar: %s", task.calendarURI)
|
||||||
|
}
|
||||||
|
} else if (!isNullOrEmpty(calendarId)) {
|
||||||
|
try {
|
||||||
|
val values = ContentValues()
|
||||||
|
values.put(CalendarContract.Events.CALENDAR_ID, calendarId)
|
||||||
|
val uri = gcalHelper.createTaskEvent(task, values)
|
||||||
|
if (uri != null) {
|
||||||
|
task.calendarURI = uri.toString()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putString(EXTRA_URI, eventUri)
|
||||||
|
outState.putString(EXTRA_ID, calendarId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.clear)
|
||||||
|
fun clearCalendar() {
|
||||||
|
if (isNullOrEmpty(eventUri)) {
|
||||||
|
clear()
|
||||||
|
} else {
|
||||||
|
dialogBuilder
|
||||||
|
.newDialog(R.string.delete_calendar_event_confirmation)
|
||||||
|
.setPositiveButton(R.string.delete) { _, _ ->
|
||||||
|
if (permissionRequestor.requestCalendarPermissions(REQUEST_CODE_CLEAR_EVENT)) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clear() {
|
||||||
|
calendarId = null
|
||||||
|
eventUri = null
|
||||||
|
refreshDisplayView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRowClick() {
|
||||||
|
if (isNullOrEmpty(eventUri)) {
|
||||||
|
CalendarPicker.newCalendarPicker(this, REQUEST_CODE_PICK_CALENDAR, calendarName)
|
||||||
|
.show(parentFragmentManager, FRAG_TAG_CALENDAR_PICKER)
|
||||||
|
} else {
|
||||||
|
if (permissionRequestor.requestCalendarPermissions(REQUEST_CODE_OPEN_EVENT)) {
|
||||||
|
openCalendarEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isClickable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
private fun openCalendarEvent() {
|
||||||
|
val cr = activity.contentResolver
|
||||||
|
val uri = Uri.parse(eventUri)
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||||
|
try {
|
||||||
|
cr.query(
|
||||||
|
uri, arrayOf(CalendarContract.Events.DTSTART, CalendarContract.Events.DTEND),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null).use { cursor ->
|
||||||
|
if (cursor!!.count == 0) {
|
||||||
|
// event no longer exists
|
||||||
|
Toast.makeText(activity, R.string.calendar_event_not_found, Toast.LENGTH_SHORT).show()
|
||||||
|
eventUri = null
|
||||||
|
refreshDisplayView()
|
||||||
|
} else {
|
||||||
|
cursor.moveToFirst()
|
||||||
|
intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, cursor.getLong(0))
|
||||||
|
intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, cursor.getLong(1))
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
Toast.makeText(activity, R.string.gcal_TEA_error, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (requestCode == REQUEST_CODE_PICK_CALENDAR) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
calendarId = data!!.getStringExtra(CalendarPicker.EXTRA_CALENDAR_ID)
|
||||||
|
refreshDisplayView()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val calendarName: String?
|
||||||
|
get() {
|
||||||
|
if (calendarId == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val calendar = calendarProvider.getCalendar(calendarId)
|
||||||
|
return calendar?.name
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
|
if (requestCode == REQUEST_CODE_OPEN_EVENT) {
|
||||||
|
if (verifyPermissions(grantResults)) {
|
||||||
|
openCalendarEvent()
|
||||||
|
}
|
||||||
|
} else if (requestCode == REQUEST_CODE_CLEAR_EVENT) {
|
||||||
|
if (verifyPermissions(grantResults)) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshDisplayView() {
|
||||||
|
if (!isNullOrEmpty(eventUri)) {
|
||||||
|
calendar.setText(R.string.gcal_TEA_showCalendar_label)
|
||||||
|
cancelButton.visibility = View.VISIBLE
|
||||||
|
} else if (calendarId != null) {
|
||||||
|
calendar.text = calendarName
|
||||||
|
cancelButton.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
calendar.text = null
|
||||||
|
cancelButton.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calendarEntryExists(eventUri: String?): Boolean {
|
||||||
|
if (isNullOrEmpty(eventUri)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val uri = Uri.parse(eventUri)
|
||||||
|
val contentResolver = activity.contentResolver
|
||||||
|
contentResolver.query(
|
||||||
|
uri, arrayOf(CalendarContract.Events.DTSTART), null, null, null).use { cursor ->
|
||||||
|
if (cursor!!.count != 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "%s: %s", eventUri, e.message)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) = component.inject(this)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_gcal
|
||||||
|
private const val FRAG_TAG_CALENDAR_PICKER = "frag_tag_calendar_picker"
|
||||||
|
private const val REQUEST_CODE_PICK_CALENDAR = 70
|
||||||
|
private const val REQUEST_CODE_OPEN_EVENT = 71
|
||||||
|
private const val REQUEST_CODE_CLEAR_EVENT = 72
|
||||||
|
private const val EXTRA_URI = "extra_uri"
|
||||||
|
private const val EXTRA_ID = "extra_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,174 +0,0 @@
|
|||||||
package org.tasks.ui;
|
|
||||||
|
|
||||||
import static org.tasks.date.DateTimeUtils.newDateTime;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.dialogs.DateTimePicker;
|
|
||||||
import org.tasks.injection.ForActivity;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.locale.Locale;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
import org.tasks.time.DateTime;
|
|
||||||
import org.threeten.bp.format.FormatStyle;
|
|
||||||
|
|
||||||
public class DeadlineControlSet extends TaskEditControlFragment {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_when_pref;
|
|
||||||
|
|
||||||
private static final int REQUEST_DATE = 504;
|
|
||||||
private static final String EXTRA_DATE = "extra_date";
|
|
||||||
private static final String FRAG_TAG_DATE_PICKER = "frag_tag_date_picker";
|
|
||||||
|
|
||||||
@Inject @ForActivity Context context;
|
|
||||||
@Inject Locale locale;
|
|
||||||
@Inject Preferences preferences;
|
|
||||||
|
|
||||||
@BindView(R.id.due_date)
|
|
||||||
TextView dueDate;
|
|
||||||
|
|
||||||
private DueDateChangeListener callback;
|
|
||||||
private long date = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
|
|
||||||
callback = (DueDateChangeListener) activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
date = task.getDueDate();
|
|
||||||
} else {
|
|
||||||
date = savedInstanceState.getLong(EXTRA_DATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshDisplayView();
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRowClick() {
|
|
||||||
FragmentManager fragmentManager = getParentFragmentManager();
|
|
||||||
if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_PICKER) == null) {
|
|
||||||
DateTimePicker.Companion.newDateTimePicker(
|
|
||||||
this,
|
|
||||||
REQUEST_DATE,
|
|
||||||
getDueDateTime(),
|
|
||||||
preferences.getBoolean(R.string.p_auto_dismiss_datetime_edit_screen, false))
|
|
||||||
.show(fragmentManager, FRAG_TAG_DATE_PICKER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isClickable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.control_set_deadline;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getIcon() {
|
|
||||||
return R.drawable.ic_outline_schedule_24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasChanges(Task original) {
|
|
||||||
return original.getDueDate() != getDueDateTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {
|
|
||||||
long dueDate = getDueDateTime();
|
|
||||||
if (dueDate != task.getDueDate()) {
|
|
||||||
task.setReminderSnooze(0L);
|
|
||||||
}
|
|
||||||
task.setDueDate(dueDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (requestCode == REQUEST_DATE) {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
long timestamp = data.getLongExtra(DateTimePicker.EXTRA_TIMESTAMP, 0L);
|
|
||||||
DateTime dateTime = new DateTime(timestamp);
|
|
||||||
date = dateTime.getMillis();
|
|
||||||
callback.dueDateChanged(getDueDateTime());
|
|
||||||
}
|
|
||||||
refreshDisplayView();
|
|
||||||
} else {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getDueDateTime() {
|
|
||||||
return date == 0
|
|
||||||
? 0
|
|
||||||
: Task.createDueDate(
|
|
||||||
Task.hasDueTime(date) ? Task.URGENCY_SPECIFIC_DAY_TIME : Task.URGENCY_SPECIFIC_DAY,
|
|
||||||
date);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putLong(EXTRA_DATE, date);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshDisplayView() {
|
|
||||||
if (date == 0) {
|
|
||||||
dueDate.setText("");
|
|
||||||
setTextColor(false);
|
|
||||||
} else {
|
|
||||||
dueDate.setText(
|
|
||||||
DateUtilities.getRelativeDateTime(context, date, locale.getLocale(), FormatStyle.FULL));
|
|
||||||
setTextColor(
|
|
||||||
Task.hasDueTime(date)
|
|
||||||
? newDateTime(date).isBeforeNow()
|
|
||||||
: newDateTime(date).endOfDay().isBeforeNow());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setTextColor(boolean overdue) {
|
|
||||||
dueDate.setTextColor(
|
|
||||||
context.getColor(overdue ? R.color.overdue : R.color.text_primary));
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface DueDateChangeListener {
|
|
||||||
|
|
||||||
void dueDateChanged(long dateTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
package org.tasks.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import butterknife.BindView
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.data.Task.Companion.createDueDate
|
||||||
|
import com.todoroo.astrid.data.Task.Companion.hasDueTime
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.date.DateTimeUtils
|
||||||
|
import org.tasks.dialogs.DateTimePicker
|
||||||
|
import org.tasks.dialogs.DateTimePicker.Companion.newDateTimePicker
|
||||||
|
import org.tasks.injection.ForActivity
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.locale.Locale
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import org.tasks.time.DateTime
|
||||||
|
import org.threeten.bp.format.FormatStyle
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DeadlineControlSet : TaskEditControlFragment() {
|
||||||
|
@Inject @ForActivity lateinit var activity: Context
|
||||||
|
@Inject lateinit var locale: Locale
|
||||||
|
@Inject lateinit var preferences: Preferences
|
||||||
|
|
||||||
|
@BindView(R.id.due_date)
|
||||||
|
lateinit var dueDate: TextView
|
||||||
|
|
||||||
|
private lateinit var callback: DueDateChangeListener
|
||||||
|
private var date: Long = 0
|
||||||
|
|
||||||
|
override fun onAttach(activity: Activity) {
|
||||||
|
super.onAttach(activity)
|
||||||
|
callback = activity as DueDateChangeListener
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) = component.inject(this)
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
date = savedInstanceState?.getLong(EXTRA_DATE) ?: task.dueDate
|
||||||
|
refreshDisplayView()
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRowClick() {
|
||||||
|
val fragmentManager = parentFragmentManager
|
||||||
|
if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_PICKER) == null) {
|
||||||
|
newDateTimePicker(
|
||||||
|
this,
|
||||||
|
REQUEST_DATE,
|
||||||
|
dueDateTime,
|
||||||
|
preferences.getBoolean(R.string.p_auto_dismiss_datetime_edit_screen, false))
|
||||||
|
.show(fragmentManager, FRAG_TAG_DATE_PICKER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isClickable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.control_set_deadline
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_outline_schedule_24px
|
||||||
|
|
||||||
|
override fun controlId() = TAG
|
||||||
|
|
||||||
|
override fun hasChanges(original: Task): Boolean {
|
||||||
|
return original.dueDate != dueDateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(task: Task) {
|
||||||
|
val dueDate = dueDateTime
|
||||||
|
if (dueDate != task.dueDate) {
|
||||||
|
task.reminderSnooze = 0L
|
||||||
|
}
|
||||||
|
task.dueDate = dueDate
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (requestCode == REQUEST_DATE) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
val timestamp = data!!.getLongExtra(DateTimePicker.EXTRA_TIMESTAMP, 0L)
|
||||||
|
val dateTime = DateTime(timestamp)
|
||||||
|
date = dateTime.millis
|
||||||
|
callback.dueDateChanged(dueDateTime)
|
||||||
|
}
|
||||||
|
refreshDisplayView()
|
||||||
|
} else {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dueDateTime: Long
|
||||||
|
get() = if (date == 0L) 0 else createDueDate(
|
||||||
|
if (hasDueTime(date)) Task.URGENCY_SPECIFIC_DAY_TIME else Task.URGENCY_SPECIFIC_DAY,
|
||||||
|
date)
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putLong(EXTRA_DATE, date)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshDisplayView() {
|
||||||
|
if (date == 0L) {
|
||||||
|
dueDate.text = ""
|
||||||
|
setTextColor(false)
|
||||||
|
} else {
|
||||||
|
dueDate.text = DateUtilities.getRelativeDateTime(activity, date, locale.locale, FormatStyle.FULL)
|
||||||
|
setTextColor(
|
||||||
|
if (hasDueTime(date)) DateTimeUtils.newDateTime(date).isBeforeNow else DateTimeUtils.newDateTime(date).endOfDay().isBeforeNow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTextColor(overdue: Boolean) {
|
||||||
|
dueDate.setTextColor(
|
||||||
|
activity.getColor(if (overdue) R.color.overdue else R.color.text_primary))
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DueDateChangeListener {
|
||||||
|
fun dueDateChanged(dateTime: Long)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_when_pref
|
||||||
|
private const val REQUEST_DATE = 504
|
||||||
|
private const val EXTRA_DATE = "extra_date"
|
||||||
|
private const val FRAG_TAG_DATE_PICKER = "frag_tag_date_picker"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,101 +0,0 @@
|
|||||||
package org.tasks.ui;
|
|
||||||
|
|
||||||
import static org.tasks.Strings.isNullOrEmpty;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.OnTextChanged;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.dialogs.Linkify;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
|
|
||||||
public class DescriptionControlSet extends TaskEditControlFragment {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_notes_pref;
|
|
||||||
private static final String EXTRA_DESCRIPTION = "extra_description";
|
|
||||||
|
|
||||||
@Inject Linkify linkify;
|
|
||||||
@Inject Preferences preferences;
|
|
||||||
|
|
||||||
@BindView(R.id.notes)
|
|
||||||
EditText editText;
|
|
||||||
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
static String stripCarriageReturns(@Nullable String original) {
|
|
||||||
return original == null ? null : original.replaceAll("\\r\\n?", "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
description = stripCarriageReturns(task.getNotes());
|
|
||||||
} else {
|
|
||||||
description = savedInstanceState.getString(EXTRA_DESCRIPTION);
|
|
||||||
}
|
|
||||||
if (!isNullOrEmpty(description)) {
|
|
||||||
editText.setTextKeepState(description);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preferences.getBoolean(R.string.p_linkify_task_edit, false)) {
|
|
||||||
linkify.linkify(editText);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putString(EXTRA_DESCRIPTION, description);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.control_set_description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getIcon() {
|
|
||||||
return R.drawable.ic_outline_notes_24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnTextChanged(R.id.notes)
|
|
||||||
void textChanged(CharSequence text) {
|
|
||||||
description = text.toString().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {
|
|
||||||
task.setNotes(description);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasChanges(Task original) {
|
|
||||||
return !(isNullOrEmpty(description)
|
|
||||||
? isNullOrEmpty(original.getNotes())
|
|
||||||
: description.equals(stripCarriageReturns(original.getNotes())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
package org.tasks.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.EditText
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.OnTextChanged
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.Strings.isNullOrEmpty
|
||||||
|
import org.tasks.dialogs.Linkify
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DescriptionControlSet : TaskEditControlFragment() {
|
||||||
|
@Inject lateinit var linkify: Linkify
|
||||||
|
@Inject lateinit var preferences: Preferences
|
||||||
|
|
||||||
|
@BindView(R.id.notes)
|
||||||
|
lateinit var editText: EditText
|
||||||
|
|
||||||
|
private var description: String? = null
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
description = if (savedInstanceState == null) {
|
||||||
|
stripCarriageReturns(task.notes)
|
||||||
|
} else {
|
||||||
|
savedInstanceState.getString(EXTRA_DESCRIPTION)
|
||||||
|
}
|
||||||
|
if (!isNullOrEmpty(description)) {
|
||||||
|
editText.setTextKeepState(description)
|
||||||
|
}
|
||||||
|
if (preferences.getBoolean(R.string.p_linkify_task_edit, false)) {
|
||||||
|
linkify.linkify(editText)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putString(EXTRA_DESCRIPTION, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.control_set_description
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_outline_notes_24px
|
||||||
|
|
||||||
|
override fun controlId() = TAG
|
||||||
|
|
||||||
|
@OnTextChanged(R.id.notes)
|
||||||
|
fun textChanged(text: CharSequence) {
|
||||||
|
description = text.toString().trim { it <= ' ' }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(task: Task) {
|
||||||
|
task.notes = description
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasChanges(original: Task): Boolean {
|
||||||
|
return !if (isNullOrEmpty(description)) isNullOrEmpty(original.notes) else description == stripCarriageReturns(original.notes)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) = component.inject(this)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_notes_pref
|
||||||
|
private const val EXTRA_DESCRIPTION = "extra_description"
|
||||||
|
fun stripCarriageReturns(original: String?): String? {
|
||||||
|
return original?.replace("\\r\\n?".toRegex(), "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,325 +0,0 @@
|
|||||||
package org.tasks.ui;
|
|
||||||
|
|
||||||
import static com.google.common.collect.Lists.transform;
|
|
||||||
import static com.todoroo.astrid.data.SyncFlags.FORCE_CALDAV_SYNC;
|
|
||||||
import static org.tasks.PermissionUtil.verifyPermissions;
|
|
||||||
import static org.tasks.Strings.isNullOrEmpty;
|
|
||||||
import static org.tasks.dialogs.GeofenceDialog.newGeofenceDialog;
|
|
||||||
import static org.tasks.location.LocationPickerActivity.EXTRA_PLACE;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.style.ClickableSpan;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.util.Pair;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import com.todoroo.andlib.utility.DateUtilities;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.data.Geofence;
|
|
||||||
import org.tasks.data.Location;
|
|
||||||
import org.tasks.data.LocationDao;
|
|
||||||
import org.tasks.data.Place;
|
|
||||||
import org.tasks.dialogs.DialogBuilder;
|
|
||||||
import org.tasks.dialogs.GeofenceDialog;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.location.GeofenceApi;
|
|
||||||
import org.tasks.location.LocationPickerActivity;
|
|
||||||
import org.tasks.preferences.Device;
|
|
||||||
import org.tasks.preferences.FragmentPermissionRequestor;
|
|
||||||
import org.tasks.preferences.PermissionChecker;
|
|
||||||
import org.tasks.preferences.PermissionRequestor;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
|
|
||||||
public class LocationControlSet extends TaskEditControlFragment {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_locations_pref;
|
|
||||||
private static final int REQUEST_LOCATION_REMINDER = 12153;
|
|
||||||
private static final int REQUEST_GEOFENCE_DETAILS = 12154;
|
|
||||||
private static final String FRAG_TAG_LOCATION_DIALOG = "location_dialog";
|
|
||||||
private static final String EXTRA_ORIGINAL = "extra_original_location";
|
|
||||||
private static final String EXTRA_LOCATION = "extra_new_location";
|
|
||||||
|
|
||||||
@Inject Preferences preferences;
|
|
||||||
@Inject DialogBuilder dialogBuilder;
|
|
||||||
@Inject GeofenceApi geofenceApi;
|
|
||||||
@Inject LocationDao locationDao;
|
|
||||||
@Inject Device device;
|
|
||||||
@Inject FragmentPermissionRequestor permissionRequestor;
|
|
||||||
@Inject PermissionChecker permissionChecker;
|
|
||||||
|
|
||||||
@BindView(R.id.location_name)
|
|
||||||
TextView locationName;
|
|
||||||
|
|
||||||
@BindView(R.id.location_address)
|
|
||||||
TextView locationAddress;
|
|
||||||
|
|
||||||
@BindView(R.id.geofence_options)
|
|
||||||
ImageView geofenceOptions;
|
|
||||||
|
|
||||||
private Location original;
|
|
||||||
private Location location;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
if (task.isNew()) {
|
|
||||||
if (task.hasTransitory(Place.KEY)) {
|
|
||||||
Place place = locationDao.getPlace(task.getTransitory(Place.KEY));
|
|
||||||
if (place != null) {
|
|
||||||
original = new Location(new Geofence(place.getUid(), preferences), place);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
original = locationDao.getGeofences(task.getId());
|
|
||||||
}
|
|
||||||
if (original != null) {
|
|
||||||
location = new Location(original.geofence, original.place);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
original = savedInstanceState.getParcelable(EXTRA_ORIGINAL);
|
|
||||||
location = savedInstanceState.getParcelable(EXTRA_LOCATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
updateUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setLocation(@Nullable Location location) {
|
|
||||||
this.location = location;
|
|
||||||
updateUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUi() {
|
|
||||||
if (this.location == null) {
|
|
||||||
locationName.setText("");
|
|
||||||
geofenceOptions.setVisibility(View.GONE);
|
|
||||||
locationAddress.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
geofenceOptions.setVisibility(device.supportsGeofences() ? View.VISIBLE : View.GONE);
|
|
||||||
geofenceOptions.setImageResource(
|
|
||||||
permissionChecker.canAccessLocation()
|
|
||||||
&& (this.location.isArrival() || this.location.isDeparture())
|
|
||||||
? R.drawable.ic_outline_notifications_24px
|
|
||||||
: R.drawable.ic_outline_notifications_off_24px);
|
|
||||||
String name = this.location.getDisplayName();
|
|
||||||
String address = this.location.getDisplayAddress();
|
|
||||||
if (!isNullOrEmpty(address) && !address.equals(name)) {
|
|
||||||
locationAddress.setText(address);
|
|
||||||
locationAddress.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
locationAddress.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
SpannableString spannableString = new SpannableString(name);
|
|
||||||
spannableString.setSpan(
|
|
||||||
new ClickableSpan() {
|
|
||||||
@Override
|
|
||||||
public void onClick(@NonNull View view) {}
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
name.length(),
|
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
locationName.setText(spannableString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRowClick() {
|
|
||||||
if (location == null) {
|
|
||||||
chooseLocation();
|
|
||||||
} else {
|
|
||||||
List<Pair<Integer, Runnable>> options = new ArrayList<>();
|
|
||||||
options.add(Pair.create(R.string.open_map, () -> location.open(getActivity())));
|
|
||||||
if (!isNullOrEmpty(location.getPhone())) {
|
|
||||||
options.add(Pair.create(R.string.action_call, this::call));
|
|
||||||
}
|
|
||||||
if (!isNullOrEmpty(location.getUrl())) {
|
|
||||||
options.add(Pair.create(R.string.visit_website, this::openWebsite));
|
|
||||||
}
|
|
||||||
options.add(Pair.create(R.string.choose_new_location, this::chooseLocation));
|
|
||||||
options.add(Pair.create(R.string.delete, () -> setLocation(null)));
|
|
||||||
dialogBuilder
|
|
||||||
.newDialog(location.getDisplayName())
|
|
||||||
.setItems(
|
|
||||||
new ArrayList<>(transform(options, o -> getString(o.first))),
|
|
||||||
(dialog, which) -> options.get(which).second.run())
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isClickable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void chooseLocation() {
|
|
||||||
Intent intent = new Intent(getActivity(), LocationPickerActivity.class);
|
|
||||||
if (location != null) {
|
|
||||||
intent.putExtra(EXTRA_PLACE, (Parcelable) location.place);
|
|
||||||
}
|
|
||||||
startActivityForResult(intent, REQUEST_LOCATION_REMINDER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.geofence_options)
|
|
||||||
void geofenceOptions() {
|
|
||||||
if (permissionRequestor.requestFineLocation()) {
|
|
||||||
showGeofenceOptions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(
|
|
||||||
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
|
||||||
if (requestCode == PermissionRequestor.REQUEST_LOCATION) {
|
|
||||||
if (verifyPermissions(grantResults)) {
|
|
||||||
showGeofenceOptions();
|
|
||||||
} else {
|
|
||||||
dialogBuilder
|
|
||||||
.newDialog(R.string.missing_permissions)
|
|
||||||
.setMessage(R.string.location_permission_required_geofence)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showGeofenceOptions() {
|
|
||||||
GeofenceDialog dialog = newGeofenceDialog(location);
|
|
||||||
dialog.setTargetFragment(this, REQUEST_GEOFENCE_DETAILS);
|
|
||||||
dialog.show(getParentFragmentManager(), FRAG_TAG_LOCATION_DIALOG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.location_row;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIcon() {
|
|
||||||
return R.drawable.ic_outline_place_24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openWebsite() {
|
|
||||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(location.getUrl())));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void call() {
|
|
||||||
startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + location.getPhone())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasChanges(Task task) {
|
|
||||||
if (original == null) {
|
|
||||||
return location != null;
|
|
||||||
}
|
|
||||||
if (location == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!original.place.equals(location.place)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return original.isDeparture() != location.isDeparture()
|
|
||||||
|| original.isArrival() != location.isArrival()
|
|
||||||
|| original.getRadius() != location.getRadius();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requiresId() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {
|
|
||||||
if ((original == null || location == null || !original.place.equals(location.place))) {
|
|
||||||
task.putTransitory(FORCE_CALDAV_SYNC, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (original != null) {
|
|
||||||
locationDao.delete(original.geofence);
|
|
||||||
geofenceApi.update(original.place);
|
|
||||||
}
|
|
||||||
if (location != null) {
|
|
||||||
Place place = location.place;
|
|
||||||
Geofence geofence = location.geofence;
|
|
||||||
geofence.setTask(task.getId());
|
|
||||||
geofence.setPlace(place.getUid());
|
|
||||||
geofence.setId(locationDao.insert(geofence));
|
|
||||||
geofenceApi.update(place);
|
|
||||||
}
|
|
||||||
task.setModificationDate(DateUtilities.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putParcelable(EXTRA_ORIGINAL, original);
|
|
||||||
outState.putParcelable(EXTRA_LOCATION, location);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (requestCode == REQUEST_LOCATION_REMINDER) {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
Place place = data.getParcelableExtra(EXTRA_PLACE);
|
|
||||||
Geofence geofence;
|
|
||||||
if (location == null) {
|
|
||||||
geofence = new Geofence(place.getUid(), preferences);
|
|
||||||
} else {
|
|
||||||
Geofence existing = location.geofence;
|
|
||||||
geofence =
|
|
||||||
new Geofence(
|
|
||||||
place.getUid(),
|
|
||||||
existing.isArrival(),
|
|
||||||
existing.isDeparture(),
|
|
||||||
existing.getRadius());
|
|
||||||
}
|
|
||||||
setLocation(new Location(geofence, place));
|
|
||||||
}
|
|
||||||
} else if (requestCode == REQUEST_GEOFENCE_DETAILS) {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
location.geofence = data.getParcelableExtra(GeofenceDialog.EXTRA_GEOFENCE);
|
|
||||||
updateUi();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,283 @@
|
|||||||
|
package org.tasks.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.text.SpannableString
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.style.ClickableSpan
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.util.Pair
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.OnClick
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities
|
||||||
|
import com.todoroo.astrid.data.SyncFlags
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import org.tasks.PermissionUtil.verifyPermissions
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.Strings.isNullOrEmpty
|
||||||
|
import org.tasks.data.Geofence
|
||||||
|
import org.tasks.data.Location
|
||||||
|
import org.tasks.data.LocationDao
|
||||||
|
import org.tasks.data.Place
|
||||||
|
import org.tasks.dialogs.DialogBuilder
|
||||||
|
import org.tasks.dialogs.GeofenceDialog
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.location.GeofenceApi
|
||||||
|
import org.tasks.location.LocationPickerActivity
|
||||||
|
import org.tasks.preferences.*
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class LocationControlSet : TaskEditControlFragment() {
|
||||||
|
@Inject lateinit var preferences: Preferences
|
||||||
|
@Inject lateinit var dialogBuilder: DialogBuilder
|
||||||
|
@Inject lateinit var geofenceApi: GeofenceApi
|
||||||
|
@Inject lateinit var locationDao: LocationDao
|
||||||
|
@Inject lateinit var device: Device
|
||||||
|
@Inject lateinit var permissionRequestor: FragmentPermissionRequestor
|
||||||
|
@Inject lateinit var permissionChecker: PermissionChecker
|
||||||
|
|
||||||
|
@BindView(R.id.location_name)
|
||||||
|
lateinit var locationName: TextView
|
||||||
|
|
||||||
|
@BindView(R.id.location_address)
|
||||||
|
lateinit var locationAddress: TextView
|
||||||
|
|
||||||
|
@BindView(R.id.geofence_options)
|
||||||
|
lateinit var geofenceOptions: ImageView
|
||||||
|
|
||||||
|
private var original: Location? = null
|
||||||
|
private var location: Location? = null
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
if (task.isNew) {
|
||||||
|
if (task.hasTransitory(Place.KEY)) {
|
||||||
|
val place = locationDao.getPlace(task.getTransitory<String>(Place.KEY)!!)
|
||||||
|
if (place != null) {
|
||||||
|
original = Location(Geofence(place.uid, preferences), place)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
original = locationDao.getGeofences(task.id)
|
||||||
|
}
|
||||||
|
if (original != null) {
|
||||||
|
location = Location(original!!.geofence, original!!.place)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
original = savedInstanceState.getParcelable(EXTRA_ORIGINAL)
|
||||||
|
location = savedInstanceState.getParcelable(EXTRA_LOCATION)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
updateUi()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setLocation(location: Location?) {
|
||||||
|
this.location = location
|
||||||
|
updateUi()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUi() {
|
||||||
|
if (location == null) {
|
||||||
|
locationName.text = ""
|
||||||
|
geofenceOptions.visibility = View.GONE
|
||||||
|
locationAddress.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
geofenceOptions.visibility = if (device.supportsGeofences()) View.VISIBLE else View.GONE
|
||||||
|
geofenceOptions.setImageResource(
|
||||||
|
if (permissionChecker.canAccessLocation()
|
||||||
|
&& (location!!.isArrival || location!!.isDeparture)) R.drawable.ic_outline_notifications_24px else R.drawable.ic_outline_notifications_off_24px)
|
||||||
|
val name = location!!.displayName
|
||||||
|
val address = location!!.displayAddress
|
||||||
|
if (!isNullOrEmpty(address) && address != name) {
|
||||||
|
locationAddress.text = address
|
||||||
|
locationAddress.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
locationAddress.visibility = View.GONE
|
||||||
|
}
|
||||||
|
val spannableString = SpannableString(name)
|
||||||
|
spannableString.setSpan(
|
||||||
|
object : ClickableSpan() {
|
||||||
|
override fun onClick(view: View) {}
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
name.length,
|
||||||
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
locationName.text = spannableString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRowClick() {
|
||||||
|
if (location == null) {
|
||||||
|
chooseLocation()
|
||||||
|
} else {
|
||||||
|
val options: MutableList<Pair<Int, () -> Unit>> = ArrayList()
|
||||||
|
options.add(Pair.create(R.string.open_map, { location!!.open(activity) }))
|
||||||
|
if (!isNullOrEmpty(location!!.phone)) {
|
||||||
|
options.add(Pair.create(R.string.action_call, { call() }))
|
||||||
|
}
|
||||||
|
if (!isNullOrEmpty(location!!.url)) {
|
||||||
|
options.add(Pair.create(R.string.visit_website, { openWebsite() }))
|
||||||
|
}
|
||||||
|
options.add(Pair.create(R.string.choose_new_location, { chooseLocation() }))
|
||||||
|
options.add(Pair.create(R.string.delete, { setLocation(null) }))
|
||||||
|
val items = options.map { requireContext().getString(it.first!!) }
|
||||||
|
dialogBuilder
|
||||||
|
.newDialog(location!!.displayName)
|
||||||
|
.setItems(items) { _, which: Int ->
|
||||||
|
options[which].second!!.invoke()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isClickable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
private fun chooseLocation() {
|
||||||
|
val intent = Intent(activity, LocationPickerActivity::class.java)
|
||||||
|
if (location != null) {
|
||||||
|
intent.putExtra(LocationPickerActivity.EXTRA_PLACE, location!!.place as Parcelable)
|
||||||
|
}
|
||||||
|
startActivityForResult(intent, REQUEST_LOCATION_REMINDER)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.geofence_options)
|
||||||
|
fun geofenceOptions() {
|
||||||
|
if (permissionRequestor.requestFineLocation()) {
|
||||||
|
showGeofenceOptions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
|
if (requestCode == PermissionRequestor.REQUEST_LOCATION) {
|
||||||
|
if (verifyPermissions(grantResults)) {
|
||||||
|
showGeofenceOptions()
|
||||||
|
} else {
|
||||||
|
dialogBuilder
|
||||||
|
.newDialog(R.string.missing_permissions)
|
||||||
|
.setMessage(R.string.location_permission_required_geofence)
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showGeofenceOptions() {
|
||||||
|
val dialog = GeofenceDialog.newGeofenceDialog(location)
|
||||||
|
dialog.setTargetFragment(this, REQUEST_GEOFENCE_DETAILS)
|
||||||
|
dialog.show(parentFragmentManager, FRAG_TAG_LOCATION_DIALOG)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.location_row
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_outline_place_24px
|
||||||
|
|
||||||
|
override fun controlId() = TAG
|
||||||
|
|
||||||
|
private fun openWebsite() {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(location!!.url)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun call() {
|
||||||
|
startActivity(Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + location!!.phone)))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasChanges(task: Task): Boolean {
|
||||||
|
if (original == null) {
|
||||||
|
return location != null
|
||||||
|
}
|
||||||
|
if (location == null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return if (original!!.place != location!!.place) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
original!!.isDeparture != location!!.isDeparture
|
||||||
|
|| original!!.isArrival != location!!.isArrival
|
||||||
|
|| original!!.radius != location!!.radius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun requiresId() = true
|
||||||
|
|
||||||
|
override fun apply(task: Task) {
|
||||||
|
if (original == null || location == null || original!!.place != location!!.place) {
|
||||||
|
task.putTransitory(SyncFlags.FORCE_CALDAV_SYNC, true)
|
||||||
|
}
|
||||||
|
if (original != null) {
|
||||||
|
locationDao.delete(original!!.geofence)
|
||||||
|
geofenceApi.update(original!!.place)
|
||||||
|
}
|
||||||
|
if (location != null) {
|
||||||
|
val place = location!!.place
|
||||||
|
val geofence = location!!.geofence
|
||||||
|
geofence.task = task.id
|
||||||
|
geofence.place = place.uid
|
||||||
|
geofence.id = locationDao.insert(geofence)
|
||||||
|
geofenceApi.update(place)
|
||||||
|
}
|
||||||
|
task.modificationDate = DateUtilities.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putParcelable(EXTRA_ORIGINAL, original)
|
||||||
|
outState.putParcelable(EXTRA_LOCATION, location)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (requestCode == REQUEST_LOCATION_REMINDER) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
val place: Place = data!!.getParcelableExtra(LocationPickerActivity.EXTRA_PLACE)!!
|
||||||
|
val geofence = if (location == null) {
|
||||||
|
Geofence(place.uid, preferences)
|
||||||
|
} else {
|
||||||
|
val existing = location!!.geofence
|
||||||
|
Geofence(
|
||||||
|
place.uid,
|
||||||
|
existing.isArrival,
|
||||||
|
existing.isDeparture,
|
||||||
|
existing.radius)
|
||||||
|
}
|
||||||
|
setLocation(Location(geofence, place))
|
||||||
|
}
|
||||||
|
} else if (requestCode == REQUEST_GEOFENCE_DETAILS) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
location!!.geofence = data!!.getParcelableExtra(GeofenceDialog.EXTRA_GEOFENCE)!!
|
||||||
|
updateUi()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) = component.inject(this)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_locations_pref
|
||||||
|
private const val REQUEST_LOCATION_REMINDER = 12153
|
||||||
|
private const val REQUEST_GEOFENCE_DETAILS = 12154
|
||||||
|
private const val FRAG_TAG_LOCATION_DIALOG = "location_dialog"
|
||||||
|
private const val EXTRA_ORIGINAL = "extra_original_location"
|
||||||
|
private const val EXTRA_LOCATION = "extra_new_location"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,133 +0,0 @@
|
|||||||
package org.tasks.ui;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.ColorStateList;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.widget.AppCompatRadioButton;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import com.todoroo.astrid.data.Task.Priority;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.injection.ForApplication;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.themes.ColorProvider;
|
|
||||||
|
|
||||||
public class PriorityControlSet extends TaskEditControlFragment {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_importance_pref;
|
|
||||||
private static final String EXTRA_PRIORITY = "extra_priority";
|
|
||||||
|
|
||||||
@Inject @ForApplication Context context;
|
|
||||||
@Inject ColorProvider colorProvider;
|
|
||||||
|
|
||||||
@BindView(R.id.priority_high)
|
|
||||||
AppCompatRadioButton priorityHigh;
|
|
||||||
|
|
||||||
@BindView(R.id.priority_medium)
|
|
||||||
AppCompatRadioButton priorityMedium;
|
|
||||||
|
|
||||||
@BindView(R.id.priority_low)
|
|
||||||
AppCompatRadioButton priorityLow;
|
|
||||||
|
|
||||||
@BindView(R.id.priority_none)
|
|
||||||
AppCompatRadioButton priorityNone;
|
|
||||||
|
|
||||||
private @Priority int priority;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick({R.id.priority_high, R.id.priority_medium, R.id.priority_low, R.id.priority_none})
|
|
||||||
void onPriorityChanged() {
|
|
||||||
priority = getPriority();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
final View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
priority = task.getPriority();
|
|
||||||
} else {
|
|
||||||
priority = savedInstanceState.getInt(EXTRA_PRIORITY);
|
|
||||||
}
|
|
||||||
if (priority == 0) {
|
|
||||||
priorityHigh.setChecked(true);
|
|
||||||
} else if (priority == 1) {
|
|
||||||
priorityMedium.setChecked(true);
|
|
||||||
} else if (priority == 2) {
|
|
||||||
priorityLow.setChecked(true);
|
|
||||||
} else {
|
|
||||||
priorityNone.setChecked(true);
|
|
||||||
}
|
|
||||||
tintRadioButton(priorityHigh, 0);
|
|
||||||
tintRadioButton(priorityMedium, 1);
|
|
||||||
tintRadioButton(priorityLow, 2);
|
|
||||||
tintRadioButton(priorityNone, 3);
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putInt(EXTRA_PRIORITY, priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.control_set_priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getIcon() {
|
|
||||||
return R.drawable.ic_outline_flag_24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {
|
|
||||||
task.setPriority(priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasChanges(Task original) {
|
|
||||||
return original.getPriority() != priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tintRadioButton(AppCompatRadioButton radioButton, int priority) {
|
|
||||||
int color = colorProvider.getPriorityColor(priority, true);
|
|
||||||
radioButton.setButtonTintList(
|
|
||||||
new ColorStateList(
|
|
||||||
new int[][] {
|
|
||||||
new int[] {-android.R.attr.state_checked}, new int[] {android.R.attr.state_checked}
|
|
||||||
},
|
|
||||||
new int[] {color, color}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Priority int getPriority() {
|
|
||||||
if (priorityHigh.isChecked()) {
|
|
||||||
return Priority.HIGH;
|
|
||||||
}
|
|
||||||
if (priorityMedium.isChecked()) {
|
|
||||||
return Priority.MEDIUM;
|
|
||||||
}
|
|
||||||
if (priorityLow.isChecked()) {
|
|
||||||
return Priority.LOW;
|
|
||||||
}
|
|
||||||
return Priority.NONE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
package org.tasks.ui
|
||||||
|
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.OnClick
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.themes.ColorProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class PriorityControlSet : TaskEditControlFragment() {
|
||||||
|
@Inject lateinit var colorProvider: ColorProvider
|
||||||
|
|
||||||
|
@BindView(R.id.priority_high)
|
||||||
|
lateinit var priorityHigh: AppCompatRadioButton
|
||||||
|
|
||||||
|
@BindView(R.id.priority_medium)
|
||||||
|
lateinit var priorityMedium: AppCompatRadioButton
|
||||||
|
|
||||||
|
@BindView(R.id.priority_low)
|
||||||
|
lateinit var priorityLow: AppCompatRadioButton
|
||||||
|
|
||||||
|
@BindView(R.id.priority_none)
|
||||||
|
lateinit var priorityNone: AppCompatRadioButton
|
||||||
|
|
||||||
|
@Task.Priority
|
||||||
|
private var priority = 0
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) = component.inject(this)
|
||||||
|
|
||||||
|
@OnClick(R.id.priority_high, R.id.priority_medium, R.id.priority_low, R.id.priority_none)
|
||||||
|
fun onPriorityChanged() {
|
||||||
|
priority = getPriority()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
priority = savedInstanceState?.getInt(EXTRA_PRIORITY) ?: task.priority
|
||||||
|
when (priority) {
|
||||||
|
0 -> priorityHigh.isChecked = true
|
||||||
|
1 -> priorityMedium.isChecked = true
|
||||||
|
2 -> priorityLow.isChecked = true
|
||||||
|
else -> priorityNone.isChecked = true
|
||||||
|
}
|
||||||
|
tintRadioButton(priorityHigh, 0)
|
||||||
|
tintRadioButton(priorityMedium, 1)
|
||||||
|
tintRadioButton(priorityLow, 2)
|
||||||
|
tintRadioButton(priorityNone, 3)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putInt(EXTRA_PRIORITY, priority)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.control_set_priority
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_outline_flag_24px
|
||||||
|
|
||||||
|
override fun controlId() = TAG
|
||||||
|
|
||||||
|
override fun apply(task: Task) {
|
||||||
|
task.priority = priority
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasChanges(original: Task): Boolean {
|
||||||
|
return original.priority != priority
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tintRadioButton(radioButton: AppCompatRadioButton, priority: Int) {
|
||||||
|
val color = colorProvider.getPriorityColor(priority, true)
|
||||||
|
radioButton.buttonTintList = ColorStateList(arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked)), intArrayOf(color, color))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Task.Priority
|
||||||
|
private fun getPriority(): Int {
|
||||||
|
if (priorityHigh.isChecked) {
|
||||||
|
return Task.Priority.HIGH
|
||||||
|
}
|
||||||
|
if (priorityMedium.isChecked) {
|
||||||
|
return Task.Priority.MEDIUM
|
||||||
|
}
|
||||||
|
return if (priorityLow.isChecked) {
|
||||||
|
Task.Priority.LOW
|
||||||
|
} else {
|
||||||
|
Task.Priority.NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_importance_pref
|
||||||
|
private const val EXTRA_PRIORITY = "extra_priority"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,233 +0,0 @@
|
|||||||
package org.tasks.ui;
|
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
|
||||||
import static org.tasks.activities.RemoteListPicker.newRemoteListSupportPicker;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import com.google.android.material.chip.Chip;
|
|
||||||
import com.google.android.material.chip.ChipGroup;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.todoroo.astrid.api.CaldavFilter;
|
|
||||||
import com.todoroo.astrid.api.Filter;
|
|
||||||
import com.todoroo.astrid.api.GtasksFilter;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import com.todoroo.astrid.gtasks.GtasksListService;
|
|
||||||
import com.todoroo.astrid.service.TaskMover;
|
|
||||||
import java.util.Objects;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.activities.RemoteListPicker;
|
|
||||||
import org.tasks.data.CaldavCalendar;
|
|
||||||
import org.tasks.data.CaldavDao;
|
|
||||||
import org.tasks.data.CaldavTask;
|
|
||||||
import org.tasks.data.GoogleTask;
|
|
||||||
import org.tasks.data.GoogleTaskDao;
|
|
||||||
import org.tasks.data.GoogleTaskList;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.preferences.DefaultFilterProvider;
|
|
||||||
|
|
||||||
public class RemoteListFragment extends TaskEditControlFragment {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_google_task_list;
|
|
||||||
private static final String FRAG_TAG_GOOGLE_TASK_LIST_SELECTION =
|
|
||||||
"frag_tag_google_task_list_selection";
|
|
||||||
private static final String EXTRA_ORIGINAL_LIST = "extra_original_list";
|
|
||||||
private static final String EXTRA_SELECTED_LIST = "extra_selected_list";
|
|
||||||
private static final int REQUEST_CODE_SELECT_LIST = 10101;
|
|
||||||
|
|
||||||
@BindView(R.id.dont_sync)
|
|
||||||
TextView textView;
|
|
||||||
|
|
||||||
@BindView(R.id.chip_group)
|
|
||||||
ChipGroup chipGroup;
|
|
||||||
|
|
||||||
@Inject GtasksListService gtasksListService;
|
|
||||||
@Inject GoogleTaskDao googleTaskDao;
|
|
||||||
@Inject CaldavDao caldavDao;
|
|
||||||
@Inject DefaultFilterProvider defaultFilterProvider;
|
|
||||||
@Inject TaskMover taskMover;
|
|
||||||
@Inject ChipProvider chipProvider;
|
|
||||||
|
|
||||||
@Nullable private Filter originalList;
|
|
||||||
@Nullable private Filter selectedList;
|
|
||||||
private OnListChanged callback;
|
|
||||||
|
|
||||||
public interface OnListChanged {
|
|
||||||
void onListchanged(@Nullable Filter filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
|
|
||||||
callback = (OnListChanged) activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
originalList = savedInstanceState.getParcelable(EXTRA_ORIGINAL_LIST);
|
|
||||||
setSelected(savedInstanceState.getParcelable(EXTRA_SELECTED_LIST));
|
|
||||||
} else {
|
|
||||||
if (task.isNew()) {
|
|
||||||
if (task.hasTransitory(GoogleTask.KEY)) {
|
|
||||||
GoogleTaskList googleTaskList =
|
|
||||||
gtasksListService.getList(task.getTransitory(GoogleTask.KEY));
|
|
||||||
if (googleTaskList != null) {
|
|
||||||
originalList = new GtasksFilter(googleTaskList);
|
|
||||||
}
|
|
||||||
} else if (task.hasTransitory(CaldavTask.KEY)) {
|
|
||||||
CaldavCalendar caldav = caldavDao.getCalendarByUuid(task.getTransitory(CaldavTask.KEY));
|
|
||||||
if (caldav != null) {
|
|
||||||
originalList = new CaldavFilter(caldav);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
originalList = defaultFilterProvider.getDefaultRemoteList();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
GoogleTask googleTask = googleTaskDao.getByTaskId(task.getId());
|
|
||||||
CaldavTask caldavTask = caldavDao.getTask(task.getId());
|
|
||||||
if (googleTask != null) {
|
|
||||||
GoogleTaskList googleTaskList = gtasksListService.getList(googleTask.getListId());
|
|
||||||
if (googleTaskList != null) {
|
|
||||||
originalList = new GtasksFilter(googleTaskList);
|
|
||||||
}
|
|
||||||
} else if (caldavTask != null) {
|
|
||||||
CaldavCalendar calendarByUuid = caldavDao.getCalendarByUuid(caldavTask.getCalendar());
|
|
||||||
if (calendarByUuid != null) {
|
|
||||||
originalList = new CaldavFilter(calendarByUuid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelected(originalList);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setSelected(@Nullable Filter filter) {
|
|
||||||
selectedList = filter;
|
|
||||||
refreshView();
|
|
||||||
callback.onListchanged(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
if (originalList != null) {
|
|
||||||
outState.putParcelable(EXTRA_ORIGINAL_LIST, originalList);
|
|
||||||
}
|
|
||||||
if (selectedList != null) {
|
|
||||||
outState.putParcelable(EXTRA_SELECTED_LIST, selectedList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.control_set_remote_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getIcon() {
|
|
||||||
return R.drawable.ic_outline_cloud_24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRowClick() {
|
|
||||||
openPicker();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isClickable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openPicker() {
|
|
||||||
newRemoteListSupportPicker(selectedList, this, REQUEST_CODE_SELECT_LIST)
|
|
||||||
.show(getParentFragmentManager(), FRAG_TAG_GOOGLE_TASK_LIST_SELECTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requiresId() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {
|
|
||||||
if (isNew() || hasChanges()) {
|
|
||||||
task.setParent(0);
|
|
||||||
task.setParentUuid(null);
|
|
||||||
taskMover.move(ImmutableList.of(task.getId()), selectedList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasChanges(Task original) {
|
|
||||||
return hasChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasChanges() {
|
|
||||||
return !Objects.equals(selectedList, originalList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (requestCode == REQUEST_CODE_SELECT_LIST) {
|
|
||||||
if (resultCode == RESULT_OK) {
|
|
||||||
setList(data.getParcelableExtra(RemoteListPicker.EXTRA_SELECTED_FILTER));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setList(Filter list) {
|
|
||||||
if (list == null) {
|
|
||||||
setSelected(null);
|
|
||||||
} else if (list instanceof GtasksFilter || list instanceof CaldavFilter) {
|
|
||||||
setSelected(list);
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Unhandled filter type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshView() {
|
|
||||||
if (selectedList == null) {
|
|
||||||
textView.setVisibility(View.VISIBLE);
|
|
||||||
chipGroup.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
textView.setVisibility(View.GONE);
|
|
||||||
chipGroup.setVisibility(View.VISIBLE);
|
|
||||||
chipGroup.removeAllViews();
|
|
||||||
Chip chip =
|
|
||||||
chipProvider.newChip(selectedList, R.drawable.ic_outline_cloud_24px, true, true);
|
|
||||||
chip.setCloseIconVisible(true);
|
|
||||||
chip.setOnClickListener(v -> openPicker());
|
|
||||||
chip.setOnCloseIconClickListener(v -> setSelected(null));
|
|
||||||
chipGroup.addView(chip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,196 @@
|
|||||||
|
package org.tasks.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import butterknife.BindView
|
||||||
|
import com.google.android.material.chip.ChipGroup
|
||||||
|
import com.todoroo.astrid.api.CaldavFilter
|
||||||
|
import com.todoroo.astrid.api.Filter
|
||||||
|
import com.todoroo.astrid.api.GtasksFilter
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.gtasks.GtasksListService
|
||||||
|
import com.todoroo.astrid.service.TaskMover
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.activities.RemoteListPicker
|
||||||
|
import org.tasks.data.CaldavDao
|
||||||
|
import org.tasks.data.CaldavTask
|
||||||
|
import org.tasks.data.GoogleTask
|
||||||
|
import org.tasks.data.GoogleTaskDao
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.preferences.DefaultFilterProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RemoteListFragment : TaskEditControlFragment() {
|
||||||
|
@BindView(R.id.dont_sync)
|
||||||
|
lateinit var textView: TextView
|
||||||
|
|
||||||
|
@BindView(R.id.chip_group)
|
||||||
|
lateinit var chipGroup: ChipGroup
|
||||||
|
|
||||||
|
@Inject lateinit var gtasksListService: GtasksListService
|
||||||
|
@Inject lateinit var googleTaskDao: GoogleTaskDao
|
||||||
|
@Inject lateinit var caldavDao: CaldavDao
|
||||||
|
@Inject lateinit var defaultFilterProvider: DefaultFilterProvider
|
||||||
|
@Inject lateinit var taskMover: TaskMover
|
||||||
|
@Inject lateinit var chipProvider: ChipProvider
|
||||||
|
|
||||||
|
private var originalList: Filter? = null
|
||||||
|
private var selectedList: Filter? = null
|
||||||
|
private lateinit var callback: OnListChanged
|
||||||
|
|
||||||
|
interface OnListChanged {
|
||||||
|
fun onListchanged(filter: Filter?)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttach(activity: Activity) {
|
||||||
|
super.onAttach(activity)
|
||||||
|
callback = activity as OnListChanged
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
originalList = savedInstanceState.getParcelable(EXTRA_ORIGINAL_LIST)
|
||||||
|
setSelected(savedInstanceState.getParcelable(EXTRA_SELECTED_LIST))
|
||||||
|
} else {
|
||||||
|
if (task.isNew) {
|
||||||
|
if (task.hasTransitory(GoogleTask.KEY)) {
|
||||||
|
val listId = task.getTransitory<String>(GoogleTask.KEY)!!
|
||||||
|
val googleTaskList = gtasksListService.getList(listId)
|
||||||
|
if (googleTaskList != null) {
|
||||||
|
originalList = GtasksFilter(googleTaskList)
|
||||||
|
}
|
||||||
|
} else if (task.hasTransitory(CaldavTask.KEY)) {
|
||||||
|
val caldav = caldavDao.getCalendarByUuid(task.getTransitory(CaldavTask.KEY)!!)
|
||||||
|
if (caldav != null) {
|
||||||
|
originalList = CaldavFilter(caldav)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
originalList = defaultFilterProvider.defaultRemoteList
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val googleTask = googleTaskDao.getByTaskId(task.id)
|
||||||
|
val caldavTask = caldavDao.getTask(task.id)
|
||||||
|
if (googleTask != null) {
|
||||||
|
val googleTaskList = gtasksListService.getList(googleTask.listId)
|
||||||
|
if (googleTaskList != null) {
|
||||||
|
originalList = GtasksFilter(googleTaskList)
|
||||||
|
}
|
||||||
|
} else if (caldavTask != null) {
|
||||||
|
val calendarByUuid = caldavDao.getCalendarByUuid(caldavTask.calendar!!)
|
||||||
|
if (calendarByUuid != null) {
|
||||||
|
originalList = CaldavFilter(calendarByUuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSelected(originalList)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setSelected(filter: Filter?) {
|
||||||
|
selectedList = filter
|
||||||
|
refreshView()
|
||||||
|
callback.onListchanged(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
if (originalList != null) {
|
||||||
|
outState.putParcelable(EXTRA_ORIGINAL_LIST, originalList)
|
||||||
|
}
|
||||||
|
if (selectedList != null) {
|
||||||
|
outState.putParcelable(EXTRA_SELECTED_LIST, selectedList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.control_set_remote_list
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_outline_cloud_24px
|
||||||
|
|
||||||
|
override fun controlId() = TAG
|
||||||
|
|
||||||
|
override fun onRowClick() {
|
||||||
|
openPicker()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isClickable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
private fun openPicker() {
|
||||||
|
RemoteListPicker.newRemoteListSupportPicker(selectedList, this, REQUEST_CODE_SELECT_LIST)
|
||||||
|
.show(parentFragmentManager, FRAG_TAG_GOOGLE_TASK_LIST_SELECTION)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun requiresId() = true
|
||||||
|
|
||||||
|
override fun apply(task: Task) {
|
||||||
|
if (isNew || hasChanges()) {
|
||||||
|
task.parent = 0
|
||||||
|
task.parentUuid = null
|
||||||
|
taskMover.move(listOf(task.id), selectedList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasChanges(original: Task): Boolean {
|
||||||
|
return hasChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasChanges(): Boolean {
|
||||||
|
return selectedList != originalList
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) = component.inject(this)
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (requestCode == REQUEST_CODE_SELECT_LIST) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
setList(data?.getParcelableExtra(RemoteListPicker.EXTRA_SELECTED_FILTER))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setList(list: Filter?) {
|
||||||
|
if (list == null) {
|
||||||
|
setSelected(null)
|
||||||
|
} else if (list is GtasksFilter || list is CaldavFilter) {
|
||||||
|
setSelected(list)
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("Unhandled filter type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshView() {
|
||||||
|
if (selectedList == null) {
|
||||||
|
textView.visibility = View.VISIBLE
|
||||||
|
chipGroup.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
textView.visibility = View.GONE
|
||||||
|
chipGroup.visibility = View.VISIBLE
|
||||||
|
chipGroup.removeAllViews()
|
||||||
|
val chip = chipProvider.newChip(selectedList, R.drawable.ic_outline_cloud_24px, true, true)
|
||||||
|
chip.isCloseIconVisible = true
|
||||||
|
chip.setOnClickListener { openPicker() }
|
||||||
|
chip.setOnCloseIconClickListener { setSelected(null) }
|
||||||
|
chipGroup.addView(chip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_google_task_list
|
||||||
|
private const val FRAG_TAG_GOOGLE_TASK_LIST_SELECTION = "frag_tag_google_task_list_selection"
|
||||||
|
private const val EXTRA_ORIGINAL_LIST = "extra_original_list"
|
||||||
|
private const val EXTRA_SELECTED_LIST = "extra_selected_list"
|
||||||
|
private const val REQUEST_CODE_SELECT_LIST = 10101
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,335 +0,0 @@
|
|||||||
package org.tasks.ui;
|
|
||||||
|
|
||||||
import static com.todoroo.andlib.utility.DateUtilities.now;
|
|
||||||
import static org.tasks.Strings.isNullOrEmpty;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.inputmethod.EditorInfo;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import com.todoroo.andlib.sql.Criterion;
|
|
||||||
import com.todoroo.andlib.sql.Join;
|
|
||||||
import com.todoroo.andlib.sql.QueryTemplate;
|
|
||||||
import com.todoroo.astrid.activity.MainActivity;
|
|
||||||
import com.todoroo.astrid.api.CaldavFilter;
|
|
||||||
import com.todoroo.astrid.api.Filter;
|
|
||||||
import com.todoroo.astrid.api.GtasksFilter;
|
|
||||||
import com.todoroo.astrid.dao.TaskDao;
|
|
||||||
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import com.todoroo.astrid.service.TaskCompleter;
|
|
||||||
import com.todoroo.astrid.service.TaskCreator;
|
|
||||||
import com.todoroo.astrid.ui.CheckableImageView;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.tasks.LocalBroadcastManager;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.data.CaldavDao;
|
|
||||||
import org.tasks.data.CaldavTask;
|
|
||||||
import org.tasks.data.GoogleTask;
|
|
||||||
import org.tasks.data.GoogleTaskDao;
|
|
||||||
import org.tasks.injection.FragmentComponent;
|
|
||||||
import org.tasks.locale.Locale;
|
|
||||||
import org.tasks.preferences.Preferences;
|
|
||||||
import org.tasks.tasklist.SubtaskViewHolder.Callbacks;
|
|
||||||
import org.tasks.tasklist.SubtasksRecyclerAdapter;
|
|
||||||
|
|
||||||
public class SubtaskControlSet extends TaskEditControlFragment implements Callbacks {
|
|
||||||
|
|
||||||
public static final int TAG = R.string.TEA_ctrl_subtask_pref;
|
|
||||||
private static final String EXTRA_NEW_SUBTASKS = "extra_new_subtasks";
|
|
||||||
|
|
||||||
@BindView(R.id.recycler_view)
|
|
||||||
RecyclerView recyclerView;
|
|
||||||
|
|
||||||
@BindView(R.id.new_subtasks)
|
|
||||||
LinearLayout newSubtaskContainer;
|
|
||||||
|
|
||||||
@Inject Activity activity;
|
|
||||||
@Inject TaskCompleter taskCompleter;
|
|
||||||
@Inject LocalBroadcastManager localBroadcastManager;
|
|
||||||
@Inject GoogleTaskDao googleTaskDao;
|
|
||||||
@Inject Toaster toaster;
|
|
||||||
@Inject Preferences preferences;
|
|
||||||
@Inject TaskCreator taskCreator;
|
|
||||||
@Inject CaldavDao caldavDao;
|
|
||||||
@Inject TaskDao taskDao;
|
|
||||||
@Inject Locale locale;
|
|
||||||
@Inject CheckBoxProvider checkBoxProvider;
|
|
||||||
@Inject ChipProvider chipProvider;
|
|
||||||
|
|
||||||
private TaskListViewModel viewModel;
|
|
||||||
private final RefreshReceiver refreshReceiver = new RefreshReceiver();
|
|
||||||
private Filter remoteList;
|
|
||||||
private GoogleTask googleTask;
|
|
||||||
private SubtasksRecyclerAdapter recyclerAdapter;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(FragmentComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
viewModel = new ViewModelProvider(this).get(TaskListViewModel.class);
|
|
||||||
component.inject(viewModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
|
|
||||||
outState.putParcelableArrayList(EXTRA_NEW_SUBTASKS, getNewSubtasks());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
final View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
for (Task task : savedInstanceState.<Task>getParcelableArrayList(EXTRA_NEW_SUBTASKS)) {
|
|
||||||
addSubtask(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recyclerAdapter = new SubtasksRecyclerAdapter(activity, chipProvider, checkBoxProvider, this);
|
|
||||||
if (task.getId() > 0) {
|
|
||||||
recyclerAdapter.submitList(viewModel.getValue());
|
|
||||||
viewModel.setFilter(new Filter("subtasks", getQueryTemplate(task)));
|
|
||||||
((DefaultItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
|
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(activity));
|
|
||||||
recyclerView.setNestedScrollingEnabled(false);
|
|
||||||
viewModel.observe(this, recyclerAdapter::submitList);
|
|
||||||
recyclerView.setAdapter(recyclerAdapter);
|
|
||||||
}
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static QueryTemplate getQueryTemplate(Task task) {
|
|
||||||
return new QueryTemplate()
|
|
||||||
.join(
|
|
||||||
Join.left(
|
|
||||||
GoogleTask.TABLE,
|
|
||||||
Criterion.and(
|
|
||||||
GoogleTask.PARENT.eq(task.getId()),
|
|
||||||
GoogleTask.TASK.eq(Task.ID),
|
|
||||||
GoogleTask.DELETED.eq(0))))
|
|
||||||
.where(
|
|
||||||
Criterion.and(
|
|
||||||
TaskCriteria.activeAndVisible(),
|
|
||||||
Criterion.or(Task.PARENT.eq(task.getId()), GoogleTask.TASK.gt(0))));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getLayout() {
|
|
||||||
return R.layout.control_set_subtasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getIcon() {
|
|
||||||
return R.drawable.ic_subdirectory_arrow_right_black_24dp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int controlId() {
|
|
||||||
return TAG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requiresId() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Task task) {
|
|
||||||
for (Task subtask: getNewSubtasks()) {
|
|
||||||
if (isNullOrEmpty(subtask.getTitle())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
subtask.setCompletionDate(task.getCompletionDate());
|
|
||||||
taskDao.createNew(subtask);
|
|
||||||
if (remoteList instanceof GtasksFilter) {
|
|
||||||
GoogleTask googleTask =
|
|
||||||
new GoogleTask(subtask.getId(), ((GtasksFilter) remoteList).getRemoteId());
|
|
||||||
googleTask.setParent(task.getId());
|
|
||||||
googleTask.setMoved(true);
|
|
||||||
googleTaskDao.insertAndShift(googleTask, preferences.addGoogleTasksToTop());
|
|
||||||
} else if (remoteList instanceof CaldavFilter) {
|
|
||||||
CaldavTask caldavTask =
|
|
||||||
new CaldavTask(subtask.getId(), ((CaldavFilter) remoteList).getUuid());
|
|
||||||
subtask.setParent(task.getId());
|
|
||||||
caldavTask.setRemoteParent(caldavDao.getRemoteIdForTask(task.getId()));
|
|
||||||
taskDao.save(subtask);
|
|
||||||
caldavDao.insert(caldavTask);
|
|
||||||
} else {
|
|
||||||
subtask.setParent(task.getId());
|
|
||||||
subtask.setParentUuid(task.getUuid());
|
|
||||||
taskDao.save(subtask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasChanges(Task original) {
|
|
||||||
return !getNewSubtasks().isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArrayList<Task> getNewSubtasks() {
|
|
||||||
ArrayList<Task> subtasks = new ArrayList<>();
|
|
||||||
int children = newSubtaskContainer.getChildCount();
|
|
||||||
for (int i = 0 ; i < children ; i++) {
|
|
||||||
View view = newSubtaskContainer.getChildAt(i);
|
|
||||||
EditText title = view.findViewById(R.id.title);
|
|
||||||
CheckableImageView completed = view.findViewById(R.id.completeBox);
|
|
||||||
Task subtask = taskCreator.createWithValues(title.getText().toString());
|
|
||||||
if (completed.isChecked()) {
|
|
||||||
subtask.setCompletionDate(now());
|
|
||||||
}
|
|
||||||
subtasks.add(subtask);
|
|
||||||
}
|
|
||||||
return subtasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
localBroadcastManager.registerRefreshReceiver(refreshReceiver);
|
|
||||||
|
|
||||||
googleTask = googleTaskDao.getByTaskId(task.getId());
|
|
||||||
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
|
|
||||||
localBroadcastManager.unregisterReceiver(refreshReceiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.add_subtask)
|
|
||||||
void addSubtask() {
|
|
||||||
if (isGoogleTaskChild()) {
|
|
||||||
toaster.longToast(R.string.subtasks_multilevel_google_task);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
EditText editText = addSubtask(taskCreator.createWithValues(""));
|
|
||||||
editText.requestFocus();
|
|
||||||
InputMethodManager imm =
|
|
||||||
(InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private EditText addSubtask(Task task) {
|
|
||||||
ViewGroup view =
|
|
||||||
(ViewGroup)
|
|
||||||
LayoutInflater.from(activity)
|
|
||||||
.inflate(R.layout.editable_subtask_adapter_row_body, newSubtaskContainer, false);
|
|
||||||
view.findViewById(R.id.clear).setOnClickListener(v -> newSubtaskContainer.removeView(view));
|
|
||||||
EditText editText = view.findViewById(R.id.title);
|
|
||||||
editText.setTextKeepState(task.getTitle());
|
|
||||||
editText.setHorizontallyScrolling(false);
|
|
||||||
editText.setLines(1);
|
|
||||||
editText.setMaxLines(Integer.MAX_VALUE);
|
|
||||||
editText.setFocusable(true);
|
|
||||||
editText.setEnabled(true);
|
|
||||||
editText.setOnEditorActionListener(
|
|
||||||
(arg0, actionId, arg2) -> {
|
|
||||||
if (actionId == EditorInfo.IME_ACTION_NEXT) {
|
|
||||||
if (editText.getText().length() != 0) {
|
|
||||||
addSubtask();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
CheckableImageView completeBox = view.findViewById(R.id.completeBox);
|
|
||||||
completeBox.setChecked(task.isCompleted());
|
|
||||||
updateCompleteBox(task, completeBox, editText);
|
|
||||||
completeBox.setOnClickListener(v -> updateCompleteBox(task, completeBox, editText));
|
|
||||||
newSubtaskContainer.addView(view);
|
|
||||||
return editText;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateCompleteBox(Task task, CheckableImageView completeBox, EditText editText) {
|
|
||||||
boolean isComplete = completeBox.isChecked();
|
|
||||||
completeBox.setImageDrawable(
|
|
||||||
checkBoxProvider.getCheckBox(isComplete, false, task.getPriority()));
|
|
||||||
if (isComplete) {
|
|
||||||
editText.setPaintFlags(editText.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
|
|
||||||
} else {
|
|
||||||
editText.setPaintFlags(editText.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isGoogleTaskChild() {
|
|
||||||
return remoteList instanceof GtasksFilter
|
|
||||||
&& googleTask != null
|
|
||||||
&& googleTask.getParent() > 0
|
|
||||||
&& googleTask.getListId().equals(((GtasksFilter) remoteList).getRemoteId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUI() {
|
|
||||||
if (isGoogleTaskChild()) {
|
|
||||||
recyclerView.setVisibility(View.GONE);
|
|
||||||
newSubtaskContainer.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
recyclerView.setVisibility(View.VISIBLE);
|
|
||||||
newSubtaskContainer.setVisibility(View.VISIBLE);
|
|
||||||
recyclerAdapter.setMultiLevelSubtasksEnabled(!(remoteList instanceof GtasksFilter));
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onRemoteListChanged(@Nullable Filter filter) {
|
|
||||||
this.remoteList = filter;
|
|
||||||
|
|
||||||
if (recyclerView != null) {
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refresh() {
|
|
||||||
viewModel.invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void openSubtask(Task task) {
|
|
||||||
((MainActivity) getActivity()).getTaskListFragment().onTaskListItemClicked(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toggleSubtask(long taskId, boolean collapsed) {
|
|
||||||
taskDao.setCollapsed(taskId, collapsed);
|
|
||||||
localBroadcastManager.broadcastRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void complete(Task task, boolean completed) {
|
|
||||||
taskCompleter.setComplete(task, completed);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class RefreshReceiver extends BroadcastReceiver {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,296 @@
|
|||||||
|
package org.tasks.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import butterknife.BindView
|
||||||
|
import butterknife.OnClick
|
||||||
|
import com.todoroo.andlib.sql.Criterion
|
||||||
|
import com.todoroo.andlib.sql.Join
|
||||||
|
import com.todoroo.andlib.sql.QueryTemplate
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities
|
||||||
|
import com.todoroo.astrid.activity.MainActivity
|
||||||
|
import com.todoroo.astrid.api.CaldavFilter
|
||||||
|
import com.todoroo.astrid.api.Filter
|
||||||
|
import com.todoroo.astrid.api.GtasksFilter
|
||||||
|
import com.todoroo.astrid.dao.TaskDao
|
||||||
|
import com.todoroo.astrid.dao.TaskDao.TaskCriteria.activeAndVisible
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import com.todoroo.astrid.service.TaskCompleter
|
||||||
|
import com.todoroo.astrid.service.TaskCreator
|
||||||
|
import com.todoroo.astrid.ui.CheckableImageView
|
||||||
|
import org.tasks.LocalBroadcastManager
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.Strings.isNullOrEmpty
|
||||||
|
import org.tasks.data.*
|
||||||
|
import org.tasks.injection.FragmentComponent
|
||||||
|
import org.tasks.locale.Locale
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import org.tasks.tasklist.SubtaskViewHolder
|
||||||
|
import org.tasks.tasklist.SubtasksRecyclerAdapter
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SubtaskControlSet : TaskEditControlFragment(), SubtaskViewHolder.Callbacks {
|
||||||
|
@JvmField
|
||||||
|
@BindView(R.id.recycler_view)
|
||||||
|
var recyclerView: RecyclerView? = null
|
||||||
|
|
||||||
|
@BindView(R.id.new_subtasks)
|
||||||
|
lateinit var newSubtaskContainer: LinearLayout
|
||||||
|
|
||||||
|
@Inject lateinit var activity: Activity
|
||||||
|
@Inject lateinit var taskCompleter: TaskCompleter
|
||||||
|
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
|
||||||
|
@Inject lateinit var googleTaskDao: GoogleTaskDao
|
||||||
|
@Inject lateinit var toaster: Toaster
|
||||||
|
@Inject lateinit var preferences: Preferences
|
||||||
|
@Inject lateinit var taskCreator: TaskCreator
|
||||||
|
@Inject lateinit var caldavDao: CaldavDao
|
||||||
|
@Inject lateinit var taskDao: TaskDao
|
||||||
|
@Inject lateinit var locale: Locale
|
||||||
|
@Inject lateinit var checkBoxProvider: CheckBoxProvider
|
||||||
|
@Inject lateinit var chipProvider: ChipProvider
|
||||||
|
|
||||||
|
private lateinit var viewModel: TaskListViewModel
|
||||||
|
private val refreshReceiver = RefreshReceiver()
|
||||||
|
private var remoteList: Filter? = null
|
||||||
|
private var googleTask: GoogleTask? = null
|
||||||
|
private lateinit var recyclerAdapter: SubtasksRecyclerAdapter
|
||||||
|
|
||||||
|
override fun inject(component: FragmentComponent) {
|
||||||
|
component.inject(this)
|
||||||
|
viewModel = ViewModelProvider(this).get(TaskListViewModel::class.java)
|
||||||
|
component.inject(viewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
outState.putParcelableArrayList(EXTRA_NEW_SUBTASKS, newSubtasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
for (task in savedInstanceState.getParcelableArrayList<Task>(EXTRA_NEW_SUBTASKS)!!) {
|
||||||
|
addSubtask(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recyclerAdapter = SubtasksRecyclerAdapter(activity, chipProvider, checkBoxProvider, this)
|
||||||
|
if (task.id > 0) {
|
||||||
|
recyclerAdapter.submitList(viewModel.value)
|
||||||
|
viewModel.setFilter(Filter("subtasks", getQueryTemplate(task)))
|
||||||
|
(recyclerView!!.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
|
||||||
|
recyclerView!!.layoutManager = LinearLayoutManager(activity)
|
||||||
|
recyclerView!!.isNestedScrollingEnabled = false
|
||||||
|
viewModel.observe(this, Observer { list: List<TaskContainer?>? -> recyclerAdapter.submitList(list) })
|
||||||
|
recyclerView!!.adapter = recyclerAdapter
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override val layout: Int
|
||||||
|
get() = R.layout.control_set_subtasks
|
||||||
|
|
||||||
|
override val icon: Int
|
||||||
|
get() = R.drawable.ic_subdirectory_arrow_right_black_24dp
|
||||||
|
|
||||||
|
override fun controlId() = TAG
|
||||||
|
|
||||||
|
override fun requiresId() = true
|
||||||
|
|
||||||
|
override fun apply(task: Task) {
|
||||||
|
for (subtask in newSubtasks) {
|
||||||
|
if (isNullOrEmpty(subtask.title)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
subtask.completionDate = task.completionDate
|
||||||
|
taskDao.createNew(subtask)
|
||||||
|
when (remoteList) {
|
||||||
|
is GtasksFilter -> {
|
||||||
|
val googleTask = GoogleTask(subtask.id, (remoteList as GtasksFilter).remoteId)
|
||||||
|
googleTask.parent = task.id
|
||||||
|
googleTask.isMoved = true
|
||||||
|
googleTaskDao.insertAndShift(googleTask, preferences.addGoogleTasksToTop())
|
||||||
|
}
|
||||||
|
is CaldavFilter -> {
|
||||||
|
val caldavTask = CaldavTask(subtask.id, (remoteList as CaldavFilter).uuid)
|
||||||
|
subtask.parent = task.id
|
||||||
|
caldavTask.remoteParent = caldavDao.getRemoteIdForTask(task.id)
|
||||||
|
taskDao.save(subtask)
|
||||||
|
caldavDao.insert(caldavTask)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
subtask.parent = task.id
|
||||||
|
subtask.parentUuid = task.uuid
|
||||||
|
taskDao.save(subtask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasChanges(original: Task): Boolean {
|
||||||
|
return newSubtasks.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val newSubtasks: ArrayList<Task>
|
||||||
|
get() {
|
||||||
|
val subtasks = ArrayList<Task>()
|
||||||
|
val children = newSubtaskContainer.childCount
|
||||||
|
for (i in 0 until children) {
|
||||||
|
val view = newSubtaskContainer.getChildAt(i)
|
||||||
|
val title = view.findViewById<EditText>(R.id.title)
|
||||||
|
val completed: CheckableImageView = view.findViewById(R.id.completeBox)
|
||||||
|
val subtask = taskCreator.createWithValues(title.text.toString())
|
||||||
|
if (completed.isChecked) {
|
||||||
|
subtask.completionDate = DateUtilities.now()
|
||||||
|
}
|
||||||
|
subtasks.add(subtask)
|
||||||
|
}
|
||||||
|
return subtasks
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
localBroadcastManager.registerRefreshReceiver(refreshReceiver)
|
||||||
|
googleTask = googleTaskDao.getByTaskId(task.id)
|
||||||
|
updateUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
localBroadcastManager.unregisterReceiver(refreshReceiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.add_subtask)
|
||||||
|
fun addSubtask() {
|
||||||
|
if (isGoogleTaskChild) {
|
||||||
|
toaster.longToast(R.string.subtasks_multilevel_google_task)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val editText = addSubtask(taskCreator.createWithValues(""))
|
||||||
|
editText.requestFocus()
|
||||||
|
val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addSubtask(task: Task): EditText {
|
||||||
|
val view = LayoutInflater.from(activity)
|
||||||
|
.inflate(R.layout.editable_subtask_adapter_row_body, newSubtaskContainer, false) as ViewGroup
|
||||||
|
view.findViewById<View>(R.id.clear).setOnClickListener { newSubtaskContainer.removeView(view) }
|
||||||
|
val editText = view.findViewById<EditText>(R.id.title)
|
||||||
|
editText.setTextKeepState(task.title)
|
||||||
|
editText.setHorizontallyScrolling(false)
|
||||||
|
editText.setLines(1)
|
||||||
|
editText.maxLines = Int.MAX_VALUE
|
||||||
|
editText.isFocusable = true
|
||||||
|
editText.isEnabled = true
|
||||||
|
editText.setOnEditorActionListener { _, actionId: Int, _ ->
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_NEXT) {
|
||||||
|
if (editText.text.isNotEmpty()) {
|
||||||
|
addSubtask()
|
||||||
|
}
|
||||||
|
return@setOnEditorActionListener true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
val completeBox: CheckableImageView = view.findViewById(R.id.completeBox)
|
||||||
|
completeBox.isChecked = task.isCompleted
|
||||||
|
updateCompleteBox(task, completeBox, editText)
|
||||||
|
completeBox.setOnClickListener { updateCompleteBox(task, completeBox, editText) }
|
||||||
|
newSubtaskContainer.addView(view)
|
||||||
|
return editText
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCompleteBox(task: Task, completeBox: CheckableImageView, editText: EditText) {
|
||||||
|
val isComplete = completeBox.isChecked
|
||||||
|
completeBox.setImageDrawable(
|
||||||
|
checkBoxProvider.getCheckBox(isComplete, false, task.priority))
|
||||||
|
editText.paintFlags = if (isComplete) {
|
||||||
|
editText.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
|
||||||
|
} else {
|
||||||
|
editText.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val isGoogleTaskChild: Boolean
|
||||||
|
get() = (remoteList is GtasksFilter
|
||||||
|
&& googleTask != null && googleTask!!.parent > 0 && googleTask!!.listId == (remoteList as GtasksFilter).remoteId)
|
||||||
|
|
||||||
|
private fun updateUI() {
|
||||||
|
if (isGoogleTaskChild) {
|
||||||
|
recyclerView!!.visibility = View.GONE
|
||||||
|
newSubtaskContainer.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
recyclerView!!.visibility = View.VISIBLE
|
||||||
|
newSubtaskContainer.visibility = View.VISIBLE
|
||||||
|
recyclerAdapter.setMultiLevelSubtasksEnabled(remoteList !is GtasksFilter)
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onRemoteListChanged(filter: Filter?) {
|
||||||
|
remoteList = filter
|
||||||
|
if (recyclerView != null) {
|
||||||
|
updateUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refresh() {
|
||||||
|
viewModel.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openSubtask(task: Task) {
|
||||||
|
(activity as MainActivity).taskListFragment.onTaskListItemClicked(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toggleSubtask(taskId: Long, collapsed: Boolean) {
|
||||||
|
taskDao.setCollapsed(taskId, collapsed)
|
||||||
|
localBroadcastManager.broadcastRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun complete(task: Task, completed: Boolean) {
|
||||||
|
taskCompleter.setComplete(task, completed)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class RefreshReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = R.string.TEA_ctrl_subtask_pref
|
||||||
|
private const val EXTRA_NEW_SUBTASKS = "extra_new_subtasks"
|
||||||
|
private fun getQueryTemplate(task: Task): QueryTemplate {
|
||||||
|
return QueryTemplate()
|
||||||
|
.join(
|
||||||
|
Join.left(
|
||||||
|
GoogleTask.TABLE,
|
||||||
|
Criterion.and(
|
||||||
|
GoogleTask.PARENT.eq(task.id),
|
||||||
|
GoogleTask.TASK.eq(Task.ID),
|
||||||
|
GoogleTask.DELETED.eq(0))))
|
||||||
|
.where(
|
||||||
|
Criterion.and(
|
||||||
|
activeAndVisible(),
|
||||||
|
Criterion.or(Task.PARENT.eq(task.id), GoogleTask.TASK.gt(0))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,76 +0,0 @@
|
|||||||
package org.tasks.ui;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
import org.tasks.R;
|
|
||||||
import org.tasks.injection.InjectingFragment;
|
|
||||||
|
|
||||||
public abstract class TaskEditControlFragment extends InjectingFragment {
|
|
||||||
|
|
||||||
public static final String EXTRA_TASK = "extra_task";
|
|
||||||
public static final String EXTRA_IS_NEW = "extra_is_new";
|
|
||||||
|
|
||||||
protected Task task;
|
|
||||||
private boolean isNew;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
final View view = inflater.inflate(R.layout.control_set_template, null);
|
|
||||||
LinearLayout content = view.findViewById(R.id.content);
|
|
||||||
inflater.inflate(getLayout(), content);
|
|
||||||
ImageView icon = view.findViewById(R.id.icon);
|
|
||||||
icon.setImageResource(getIcon());
|
|
||||||
if (isClickable()) {
|
|
||||||
content.setOnClickListener(v -> onRowClick());
|
|
||||||
}
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
|
|
||||||
Bundle arguments = getArguments();
|
|
||||||
if (arguments != null) {
|
|
||||||
task = arguments.getParcelable(EXTRA_TASK);
|
|
||||||
isNew = arguments.getBoolean(EXTRA_IS_NEW);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onRowClick() {}
|
|
||||||
|
|
||||||
protected boolean isClickable() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract int getLayout();
|
|
||||||
|
|
||||||
protected abstract int getIcon();
|
|
||||||
|
|
||||||
public abstract int controlId();
|
|
||||||
|
|
||||||
public boolean requiresId() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void apply(Task task);
|
|
||||||
|
|
||||||
public boolean hasChanges(Task original) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isNew() {
|
|
||||||
return isNew;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package org.tasks.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import butterknife.ButterKnife
|
||||||
|
import com.todoroo.astrid.data.Task
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.injection.InjectingFragment
|
||||||
|
|
||||||
|
abstract class TaskEditControlFragment : InjectingFragment() {
|
||||||
|
protected lateinit var task: Task
|
||||||
|
|
||||||
|
var isNew = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
val view = inflater.inflate(R.layout.control_set_template, null)
|
||||||
|
val content = view.findViewById<LinearLayout>(R.id.content)
|
||||||
|
inflater.inflate(layout, content)
|
||||||
|
val icon = view.findViewById<ImageView>(R.id.icon)
|
||||||
|
icon.setImageResource(this.icon)
|
||||||
|
if (isClickable) {
|
||||||
|
content.setOnClickListener { onRowClick() }
|
||||||
|
}
|
||||||
|
ButterKnife.bind(this, view)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttach(activity: Activity) {
|
||||||
|
super.onAttach(activity)
|
||||||
|
val args = requireArguments()
|
||||||
|
task = args.getParcelable(EXTRA_TASK)!!
|
||||||
|
isNew = args.getBoolean(EXTRA_IS_NEW)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun onRowClick() {}
|
||||||
|
protected open val isClickable: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
protected abstract val layout: Int
|
||||||
|
protected abstract val icon: Int
|
||||||
|
abstract fun controlId(): Int
|
||||||
|
open fun requiresId(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun apply(task: Task)
|
||||||
|
|
||||||
|
open fun hasChanges(original: Task): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_TASK = "extra_task"
|
||||||
|
const val EXTRA_IS_NEW = "extra_is_new"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue