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