From 47d414f20fa1c8d31867b2e4666f58bee4aefd50 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Mon, 11 May 2020 12:25:18 -0500 Subject: [PATCH] Convert task edit screen to kotlin --- .../todoroo/astrid/files/FilesControlSet.java | 185 --------- .../todoroo/astrid/files/FilesControlSet.kt | 153 ++++++++ .../astrid/repeats/RepeatControlSet.java | 259 ------------ .../astrid/repeats/RepeatControlSet.kt | 227 +++++++++++ .../todoroo/astrid/tags/TagsControlSet.java | 188 --------- .../com/todoroo/astrid/tags/TagsControlSet.kt | 152 +++++++ .../astrid/timers/TimerControlSet.java | 246 ------------ .../todoroo/astrid/timers/TimerControlSet.kt | 217 ++++++++++ .../astrid/ui/HideUntilControlSet.java | 370 ------------------ .../todoroo/astrid/ui/HideUntilControlSet.kt | 285 ++++++++++++++ .../astrid/ui/RandomReminderControlSet.java | 70 ---- .../astrid/ui/RandomReminderControlSet.kt | 64 +++ .../todoroo/astrid/ui/ReminderControlSet.java | 352 ----------------- .../todoroo/astrid/ui/ReminderControlSet.kt | 303 ++++++++++++++ .../astrid/ui/TimeDurationControlSet.java | 102 ----- .../astrid/ui/TimeDurationControlSet.kt | 74 ++++ app/src/main/java/org/tasks/data/Location.kt | 2 +- app/src/main/java/org/tasks/data/Place.kt | 6 +- .../tasks/fragments/CommentBarFragment.java | 270 ------------- .../org/tasks/fragments/CommentBarFragment.kt | 217 ++++++++++ .../java/org/tasks/ui/CalendarControlSet.java | 339 ---------------- .../java/org/tasks/ui/CalendarControlSet.kt | 299 ++++++++++++++ .../java/org/tasks/ui/DeadlineControlSet.java | 174 -------- .../java/org/tasks/ui/DeadlineControlSet.kt | 139 +++++++ .../org/tasks/ui/DescriptionControlSet.java | 101 ----- .../org/tasks/ui/DescriptionControlSet.kt | 79 ++++ .../java/org/tasks/ui/LocationControlSet.java | 325 --------------- .../java/org/tasks/ui/LocationControlSet.kt | 283 ++++++++++++++ .../java/org/tasks/ui/PriorityControlSet.java | 133 ------- .../java/org/tasks/ui/PriorityControlSet.kt | 104 +++++ .../java/org/tasks/ui/RemoteListFragment.java | 233 ----------- .../java/org/tasks/ui/RemoteListFragment.kt | 196 ++++++++++ .../java/org/tasks/ui/SubtaskControlSet.java | 335 ---------------- .../java/org/tasks/ui/SubtaskControlSet.kt | 296 ++++++++++++++ .../org/tasks/ui/TaskEditControlFragment.java | 76 ---- .../org/tasks/ui/TaskEditControlFragment.kt | 63 +++ 36 files changed, 3155 insertions(+), 3762 deletions(-) delete mode 100644 app/src/main/java/com/todoroo/astrid/files/FilesControlSet.java create mode 100644 app/src/main/java/com/todoroo/astrid/files/FilesControlSet.kt delete mode 100644 app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.java create mode 100644 app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt delete mode 100644 app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.java create mode 100644 app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.kt delete mode 100644 app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.java create mode 100644 app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.kt delete mode 100644 app/src/main/java/com/todoroo/astrid/ui/HideUntilControlSet.java create mode 100644 app/src/main/java/com/todoroo/astrid/ui/HideUntilControlSet.kt delete mode 100644 app/src/main/java/com/todoroo/astrid/ui/RandomReminderControlSet.java create mode 100644 app/src/main/java/com/todoroo/astrid/ui/RandomReminderControlSet.kt delete mode 100644 app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.java create mode 100644 app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt delete mode 100644 app/src/main/java/com/todoroo/astrid/ui/TimeDurationControlSet.java create mode 100644 app/src/main/java/com/todoroo/astrid/ui/TimeDurationControlSet.kt delete mode 100644 app/src/main/java/org/tasks/fragments/CommentBarFragment.java create mode 100644 app/src/main/java/org/tasks/fragments/CommentBarFragment.kt delete mode 100644 app/src/main/java/org/tasks/ui/CalendarControlSet.java create mode 100644 app/src/main/java/org/tasks/ui/CalendarControlSet.kt delete mode 100644 app/src/main/java/org/tasks/ui/DeadlineControlSet.java create mode 100644 app/src/main/java/org/tasks/ui/DeadlineControlSet.kt delete mode 100644 app/src/main/java/org/tasks/ui/DescriptionControlSet.java create mode 100644 app/src/main/java/org/tasks/ui/DescriptionControlSet.kt delete mode 100644 app/src/main/java/org/tasks/ui/LocationControlSet.java create mode 100644 app/src/main/java/org/tasks/ui/LocationControlSet.kt delete mode 100644 app/src/main/java/org/tasks/ui/PriorityControlSet.java create mode 100644 app/src/main/java/org/tasks/ui/PriorityControlSet.kt delete mode 100644 app/src/main/java/org/tasks/ui/RemoteListFragment.java create mode 100644 app/src/main/java/org/tasks/ui/RemoteListFragment.kt delete mode 100644 app/src/main/java/org/tasks/ui/SubtaskControlSet.java create mode 100644 app/src/main/java/org/tasks/ui/SubtaskControlSet.kt delete mode 100644 app/src/main/java/org/tasks/ui/TaskEditControlFragment.java create mode 100644 app/src/main/java/org/tasks/ui/TaskEditControlFragment.kt diff --git a/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.java b/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.java deleted file mode 100644 index 9208f9777..000000000 --- a/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.java +++ /dev/null @@ -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) 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); - } -} diff --git a/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.kt b/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.kt new file mode 100644 index 000000000..7e0993efd --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.kt @@ -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>(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(R.id.file_text) + val name = LEFT_TO_RIGHT_MARK.toString() + taskAttachment.name + nameView.text = name + nameView.setOnClickListener { showFile(taskAttachment) } + val clearFile = fileRow.findViewById(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' + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.java b/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.java deleted file mode 100644 index 407329319..000000000 --- a/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.java +++ /dev/null @@ -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 - */ -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 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 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(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); - } - } -} diff --git a/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt b/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt new file mode 100644 index 000000000..6f1560541 --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt @@ -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 @todoroo.com> + */ +class RepeatControlSet : TaskEditControlFragment() { + private val repeatTypes: MutableList = 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 + 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(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" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.java b/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.java deleted file mode 100644 index 965bca8d7..000000000 --- a/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.java +++ /dev/null @@ -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 - */ -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 orderByName = - new Ordering() { - @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 originalTags; - private ArrayList 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 originalSet = newHashSet(originalTags); - Set 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; - } -} diff --git a/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.kt b/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.kt new file mode 100644 index 000000000..5356c091b --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.kt @@ -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 @todoroo.com> + */ +class TagsControlSet : TaskEditControlFragment() { + private val orderByName: Ordering = object : Ordering() { + 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 + private lateinit var selectedTags: ArrayList + + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.java b/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.java deleted file mode 100644 index 8d9179c4a..000000000 --- a/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.java +++ /dev/null @@ -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 - */ -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(); - } -} diff --git a/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.kt b/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.kt new file mode 100644 index 000000000..5e2e655f1 --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.kt @@ -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 @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" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/ui/HideUntilControlSet.java b/app/src/main/java/com/todoroo/astrid/ui/HideUntilControlSet.java deleted file mode 100644 index 388e2a5ba..000000000 --- a/app/src/main/java/com/todoroo/astrid/ui/HideUntilControlSet.java +++ /dev/null @@ -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 - */ -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 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 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( - 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 - */ - 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; - } - } -} diff --git a/app/src/main/java/com/todoroo/astrid/ui/HideUntilControlSet.kt b/app/src/main/java/com/todoroo/astrid/ui/HideUntilControlSet.kt new file mode 100644 index 000000000..77483fb8b --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/ui/HideUntilControlSet.kt @@ -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 @todoroo.com> + */ +class HideUntilControlSet : TaskEditControlFragment(), OnItemSelectedListener { + private val spinnerItems: MutableList = 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 + 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( + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/ui/RandomReminderControlSet.java b/app/src/main/java/com/todoroo/astrid/ui/RandomReminderControlSet.java deleted file mode 100644 index 82ef595b2..000000000 --- a/app/src/main/java/com/todoroo/astrid/ui/RandomReminderControlSet.java +++ /dev/null @@ -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 - */ -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 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; - } -} diff --git a/app/src/main/java/com/todoroo/astrid/ui/RandomReminderControlSet.kt b/app/src/main/java/com/todoroo/astrid/ui/RandomReminderControlSet.kt new file mode 100644 index 000000000..437c69dc9 --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/ui/RandomReminderControlSet.kt @@ -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 @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(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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.java b/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.java deleted file mode 100644 index 33e36d5c7..000000000 --- a/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.java +++ /dev/null @@ -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 - */ -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 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 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 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 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 getOptions() { - List 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); - } -} diff --git a/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt b/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt new file mode 100644 index 000000000..c76576125 --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt @@ -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 @todoroo.com> + */ +class ReminderControlSet : TaskEditControlFragment() { + private val alarms: MutableSet = 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 { + 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) { + 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(R.id.alarm_string) + display.text = text + alertItem + .findViewById(R.id.clear) + .setOnClickListener { v: View? -> + alertContainer.removeView(alertItem) + onRemove?.onClick(v) + } + } + + private val options: List + get() { + val options: MutableList = 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" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/ui/TimeDurationControlSet.java b/app/src/main/java/com/todoroo/astrid/ui/TimeDurationControlSet.java deleted file mode 100644 index c615802fb..000000000 --- a/app/src/main/java/com/todoroo/astrid/ui/TimeDurationControlSet.java +++ /dev/null @@ -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(); - } -} diff --git a/app/src/main/java/com/todoroo/astrid/ui/TimeDurationControlSet.kt b/app/src/main/java/com/todoroo/astrid/ui/TimeDurationControlSet.kt new file mode 100644 index 000000000..76ab7b3ed --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/ui/TimeDurationControlSet.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/data/Location.kt b/app/src/main/java/org/tasks/data/Location.kt index 8466306dd..d6307f5db 100644 --- a/app/src/main/java/org/tasks/data/Location.kt +++ b/app/src/main/java/org/tasks/data/Location.kt @@ -49,7 +49,7 @@ class Location : Serializable, Parcelable { val isDeparture: Boolean get() = geofence.isDeparture - val displayName: String? + val displayName: String get() = place.displayName val displayAddress: String? diff --git a/app/src/main/java/org/tasks/data/Place.kt b/app/src/main/java/org/tasks/data/Place.kt index f8bfd8799..07627cdd2 100644 --- a/app/src/main/java/org/tasks/data/Place.kt +++ b/app/src/main/java/org/tasks/data/Place.kt @@ -94,13 +94,13 @@ class Place : Serializable, Parcelable { this.icon = icon } - val displayName: String? + val displayName: String get() { if (!Strings.isNullOrEmpty(name) && !COORDS.matcher(name!!).matches()) { - return name + return name!! } return if (!Strings.isNullOrEmpty(address)) { - address + address!! } else { "${formatCoordinate(latitude, true)} ${formatCoordinate(longitude, false)}" } diff --git a/app/src/main/java/org/tasks/fragments/CommentBarFragment.java b/app/src/main/java/org/tasks/fragments/CommentBarFragment.java deleted file mode 100644 index 906c86490..000000000 --- a/app/src/main/java/org/tasks/fragments/CommentBarFragment.java +++ /dev/null @@ -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 runnables = new ArrayList<>(); - List 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(); - } -} diff --git a/app/src/main/java/org/tasks/fragments/CommentBarFragment.kt b/app/src/main/java/org/tasks/fragments/CommentBarFragment.kt new file mode 100644 index 000000000..e79c07d11 --- /dev/null +++ b/app/src/main/java/org/tasks/fragments/CommentBarFragment.kt @@ -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 = 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" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/CalendarControlSet.java b/app/src/main/java/org/tasks/ui/CalendarControlSet.java deleted file mode 100644 index 2e5cbebbc..000000000 --- a/app/src/main/java/org/tasks/ui/CalendarControlSet.java +++ /dev/null @@ -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); - } -} diff --git a/app/src/main/java/org/tasks/ui/CalendarControlSet.kt b/app/src/main/java/org/tasks/ui/CalendarControlSet.kt new file mode 100644 index 000000000..a205e5a0e --- /dev/null +++ b/app/src/main/java/org/tasks/ui/CalendarControlSet.kt @@ -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, 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" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/DeadlineControlSet.java b/app/src/main/java/org/tasks/ui/DeadlineControlSet.java deleted file mode 100644 index cee13c673..000000000 --- a/app/src/main/java/org/tasks/ui/DeadlineControlSet.java +++ /dev/null @@ -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); - } -} diff --git a/app/src/main/java/org/tasks/ui/DeadlineControlSet.kt b/app/src/main/java/org/tasks/ui/DeadlineControlSet.kt new file mode 100644 index 000000000..43a16ee51 --- /dev/null +++ b/app/src/main/java/org/tasks/ui/DeadlineControlSet.kt @@ -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" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/DescriptionControlSet.java b/app/src/main/java/org/tasks/ui/DescriptionControlSet.java deleted file mode 100644 index 5f1686c36..000000000 --- a/app/src/main/java/org/tasks/ui/DescriptionControlSet.java +++ /dev/null @@ -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); - } -} diff --git a/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt b/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt new file mode 100644 index 000000000..def969668 --- /dev/null +++ b/app/src/main/java/org/tasks/ui/DescriptionControlSet.kt @@ -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") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/LocationControlSet.java b/app/src/main/java/org/tasks/ui/LocationControlSet.java deleted file mode 100644 index 60ef9b28d..000000000 --- a/app/src/main/java/org/tasks/ui/LocationControlSet.java +++ /dev/null @@ -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> 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); - } -} diff --git a/app/src/main/java/org/tasks/ui/LocationControlSet.kt b/app/src/main/java/org/tasks/ui/LocationControlSet.kt new file mode 100644 index 000000000..96d11ae24 --- /dev/null +++ b/app/src/main/java/org/tasks/ui/LocationControlSet.kt @@ -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(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 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, 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" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/PriorityControlSet.java b/app/src/main/java/org/tasks/ui/PriorityControlSet.java deleted file mode 100644 index 59df2b912..000000000 --- a/app/src/main/java/org/tasks/ui/PriorityControlSet.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/org/tasks/ui/PriorityControlSet.kt b/app/src/main/java/org/tasks/ui/PriorityControlSet.kt new file mode 100644 index 000000000..253ff0b47 --- /dev/null +++ b/app/src/main/java/org/tasks/ui/PriorityControlSet.kt @@ -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" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/RemoteListFragment.java b/app/src/main/java/org/tasks/ui/RemoteListFragment.java deleted file mode 100644 index 89bfa410a..000000000 --- a/app/src/main/java/org/tasks/ui/RemoteListFragment.java +++ /dev/null @@ -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); - } - } -} diff --git a/app/src/main/java/org/tasks/ui/RemoteListFragment.kt b/app/src/main/java/org/tasks/ui/RemoteListFragment.kt new file mode 100644 index 000000000..b7fc98945 --- /dev/null +++ b/app/src/main/java/org/tasks/ui/RemoteListFragment.kt @@ -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(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/SubtaskControlSet.java b/app/src/main/java/org/tasks/ui/SubtaskControlSet.java deleted file mode 100644 index ad8f90bec..000000000 --- a/app/src/main/java/org/tasks/ui/SubtaskControlSet.java +++ /dev/null @@ -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.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 getNewSubtasks() { - ArrayList 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(); - } - } -} diff --git a/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt b/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt new file mode 100644 index 000000000..f395005c9 --- /dev/null +++ b/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt @@ -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(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? -> 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 + get() { + val subtasks = ArrayList() + val children = newSubtaskContainer.childCount + for (i in 0 until children) { + val view = newSubtaskContainer.getChildAt(i) + val title = view.findViewById(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(R.id.clear).setOnClickListener { newSubtaskContainer.removeView(view) } + val editText = view.findViewById(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)))) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/TaskEditControlFragment.java b/app/src/main/java/org/tasks/ui/TaskEditControlFragment.java deleted file mode 100644 index 2b4d51e81..000000000 --- a/app/src/main/java/org/tasks/ui/TaskEditControlFragment.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/org/tasks/ui/TaskEditControlFragment.kt b/app/src/main/java/org/tasks/ui/TaskEditControlFragment.kt new file mode 100644 index 000000000..cfa3e5be9 --- /dev/null +++ b/app/src/main/java/org/tasks/ui/TaskEditControlFragment.kt @@ -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(R.id.content) + inflater.inflate(layout, content) + val icon = view.findViewById(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" + } +} \ No newline at end of file