diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt index 38e079375..2c1ec6bda 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt @@ -652,6 +652,21 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL } true } + R.id.reschedule -> { + lifecycleScope.launch { + taskDao + .fetch(selected) + .takeIf { it.isNotEmpty() } + ?.let { + newDateTimePicker( + preferences.getBoolean(R.string.p_auto_dismiss_datetime_list_screen, false), + *it.toTypedArray()) + .show(parentFragmentManager, FRAG_TAG_DATE_TIME_PICKER) + } + } + finishActionMode() + true + } R.id.menu_select_all -> { lifecycleScope.launch { taskAdapter.setSelected(taskDao.fetchTasks(preferences, filter) @@ -714,9 +729,8 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL val fragmentManager = parentFragmentManager if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_TIME_PICKER) == null) { newDateTimePicker( - task.id, - task.dueDate, - preferences.getBoolean(R.string.p_auto_dismiss_datetime_list_screen, false)) + preferences.getBoolean(R.string.p_auto_dismiss_datetime_list_screen, false), + task.task) .show(fragmentManager, FRAG_TAG_DATE_TIME_PICKER) } } diff --git a/app/src/main/java/org/tasks/dialogs/DateTimePicker.kt b/app/src/main/java/org/tasks/dialogs/DateTimePicker.kt index 35a3cd4c3..e15225f40 100644 --- a/app/src/main/java/org/tasks/dialogs/DateTimePicker.kt +++ b/app/src/main/java/org/tasks/dialogs/DateTimePicker.kt @@ -29,12 +29,15 @@ import kotlinx.coroutines.launch import org.tasks.R import org.tasks.databinding.DialogDateTimePickerBinding import org.tasks.date.DateTimeUtils.newDateTime +import org.tasks.date.DateTimeUtils.toDateTime import org.tasks.dialogs.MyTimePickerDialog.newTimePicker import org.tasks.locale.Locale import org.tasks.notifications.NotificationManager import org.tasks.preferences.Preferences import org.tasks.themes.Theme import org.tasks.time.DateTime +import org.tasks.time.DateTimeUtils.millisOfDay +import org.tasks.time.DateTimeUtils.startOfDay import java.time.format.FormatStyle import javax.inject.Inject @@ -49,12 +52,13 @@ class DateTimePicker : BottomSheetDialogFragment() { @Inject lateinit var theme: Theme lateinit var binding: DialogDateTimePickerBinding - private var customDate: DateTime? = null - private var selected: DateTime? = null + private var customDate = NO_DAY + private var customTime = NO_TIME + private var selectedDay = NO_DAY + private var selectedTime = NO_TIME private val today = newDateTime().startOfDay() private val tomorrow = today.plusDays(1) private val nextWeek = today.plusDays(7) - private var customTime = 0 private var morning = 32401000 private var afternoon = 46801000 private var evening = 61201000 @@ -66,17 +70,25 @@ class DateTimePicker : BottomSheetDialogFragment() { } companion object { + const val EXTRA_DAY = "extra_day" + const val EXTRA_TIME = "extra_time" + const val EXTRA_TASKS = "extra_tasks" const val EXTRA_TIMESTAMP = "extra_timestamp" - const val EXTRA_TASK = "extra_task" private const val EXTRA_AUTO_CLOSE = "extra_auto_close" - private const val EXTRA_SELECTED = "extra_selected" private const val REQUEST_TIME = 10101 private const val FRAG_TAG_TIME_PICKER = "frag_tag_time_picker" + private const val NO_DAY = 0L + private const val NO_TIME = 0 + private const val MULTIPLE_DAYS = -1L + private const val MULTIPLE_TIMES = -1 - fun newDateTimePicker(task: Long, current: Long, autoClose: Boolean): DateTimePicker { + fun newDateTimePicker(autoClose: Boolean, vararg tasks: Task): DateTimePicker { val bundle = Bundle() - bundle.putLong(EXTRA_TASK, task) - bundle.putLong(EXTRA_TIMESTAMP, current) + bundle.putLongArray(EXTRA_TASKS, tasks.map { it.id }.toLongArray()) + val dueDates = tasks.map { it.dueDate.startOfDay() }.toSet() + val dueTimes = tasks.map { it.dueDate.millisOfDay() }.toSet() + bundle.putLong(EXTRA_DAY, if (dueDates.size == 1) dueDates.first() else -1) + bundle.putInt(EXTRA_TIME, if (dueTimes.size == 1) dueTimes.first() else -1) bundle.putBoolean(EXTRA_AUTO_CLOSE, autoClose) val fragment = DateTimePicker() fragment.arguments = bundle @@ -85,7 +97,8 @@ class DateTimePicker : BottomSheetDialogFragment() { fun newDateTimePicker(target: Fragment, rc: Int, current: Long, autoClose: Boolean): DateTimePicker { val bundle = Bundle() - bundle.putLong(EXTRA_TIMESTAMP, current) + bundle.putLong(EXTRA_DAY, current.startOfDay()) + bundle.putInt(EXTRA_TIME, current.millisOfDay()) bundle.putBoolean(EXTRA_AUTO_CLOSE, autoClose) val fragment = DateTimePicker() fragment.arguments = bundle @@ -108,18 +121,15 @@ class DateTimePicker : BottomSheetDialogFragment() { binding.shortcuts.nextWeekButton.text = getString(R.string.next, DateUtilities.getWeekdayShort(newDateTime().plusWeeks(1), locale.locale)) binding.calendarView.setOnDateChangeListener { _, y, m, d -> - selected = DateTime(y, m + 1, d, selected?.hourOfDay ?: 0, selected?.minuteOfHour - ?: 0, selected?.secondOfMinute ?: 0) - returnDate(selected!!.millis) + returnDate(day = DateTime(y, m + 1, d).millis) refreshButtons() } val firstDayOfWeek = preferences.firstDayOfWeek if (firstDayOfWeek in 1..7) { binding.calendarView.firstDayOfWeek = firstDayOfWeek } - val timestamp = savedInstanceState?.getLong(EXTRA_SELECTED) - ?: requireArguments().getLong(EXTRA_TIMESTAMP) - selected = if (timestamp > 0) DateTime(timestamp) else null + selectedDay = savedInstanceState?.getLong(EXTRA_DAY) ?: requireArguments().getLong(EXTRA_DAY) + selectedTime = savedInstanceState?.getInt(EXTRA_TIME) ?: requireArguments().getInt(EXTRA_TIME) return binding.root } @@ -141,54 +151,61 @@ class DateTimePicker : BottomSheetDialogFragment() { } private fun refreshButtons() { - when (selected?.startOfDay()) { - null -> binding.shortcuts.dateGroup.check(R.id.no_date_button) - today -> binding.shortcuts.dateGroup.check(R.id.today_button) - tomorrow -> binding.shortcuts.dateGroup.check(R.id.tomorrow_button) - nextWeek -> binding.shortcuts.dateGroup.check(R.id.next_week_button) + when (selectedDay) { + 0L -> binding.shortcuts.dateGroup.check(R.id.no_date_button) + today.millis -> binding.shortcuts.dateGroup.check(R.id.today_button) + tomorrow.millis -> binding.shortcuts.dateGroup.check(R.id.tomorrow_button) + nextWeek.millis -> binding.shortcuts.dateGroup.check(R.id.next_week_button) else -> { - customDate = selected + customDate = selectedDay binding.shortcuts.dateGroup.check(R.id.current_date_selection) binding.shortcuts.currentDateSelection.visibility = View.VISIBLE - binding.shortcuts.currentDateSelection.text = - DateUtilities.getRelativeDay(context, selected!!.millis, locale.locale, FormatStyle.MEDIUM) + binding.shortcuts.currentDateSelection.text = if (customDate == MULTIPLE_DAYS) { + requireContext().getString(R.string.date_picker_multiple) + } else { + DateUtilities.getRelativeDay(context, selectedDay, locale.locale, FormatStyle.MEDIUM) + } } } - if (Task.hasDueTime(selected?.millis ?: 0)) { - when (selected?.millisOfDay) { + if (selectedTime == MULTIPLE_TIMES || Task.hasDueTime(selectedTime.toLong())) { + when (selectedTime) { morning -> binding.shortcuts.timeGroup.check(R.id.morning_button) afternoon -> binding.shortcuts.timeGroup.check(R.id.afternoon_button) evening -> binding.shortcuts.timeGroup.check(R.id.evening_button) night -> binding.shortcuts.timeGroup.check(R.id.night_button) else -> { - customTime = selected!!.millisOfDay + customTime = selectedTime binding.shortcuts.timeGroup.check(R.id.current_time_selection) binding.shortcuts.currentTimeSelection.visibility = View.VISIBLE - binding.shortcuts.currentTimeSelection.text = DateUtilities.getTimeString(context, selected) + binding.shortcuts.currentTimeSelection.text = if (customTime == MULTIPLE_TIMES) { + requireContext().getString(R.string.date_picker_multiple) + } else { + DateUtilities.getTimeString(context, today.withMillisOfDay(selectedTime)) + } } } } else { binding.shortcuts.timeGroup.check(R.id.no_time) } - if (selected != null) { - binding.calendarView.setDate(selected!!.millis, true, true) + if (selectedDay > 0) { + binding.calendarView.setDate(selectedDay, true, true) } } @OnClick(R.id.no_date_button) - fun clearDate() = returnDate(0) + fun clearDate() = returnDate(day = 0, time = 0) @OnClick(R.id.no_time) - fun clearTime() = returnDate(selected?.startOfDay()?.millis ?: 0) + fun clearTime() = returnDate(time = 0) @OnClick(R.id.today_button) - fun setToday() = returnDate(today.withMillisOfDay(selected?.millisOfDay ?: 0)) + fun setToday() = returnDate(day = today.startOfDay().millis) @OnClick(R.id.tomorrow_button) - fun setTomorrow() = returnDate(tomorrow.withMillisOfDay(selected?.millisOfDay ?: 0)) + fun setTomorrow() = returnDate(day = tomorrow.startOfDay().millis) @OnClick(R.id.next_week_button) - fun setNextWeek() = returnDate(nextWeek.withMillisOfDay(selected?.millisOfDay ?: 0)) + fun setNextWeek() = returnDate(day = nextWeek.startOfDay().millis) @OnClick(R.id.morning_button) fun setMorning() = returnSelectedTime(morning) @@ -203,33 +220,35 @@ class DateTimePicker : BottomSheetDialogFragment() { fun setNight() = returnSelectedTime(night) @OnClick(R.id.current_date_selection) - fun currentDate() = returnDate(customDate) + fun currentDate() = returnDate(day = customDate) @OnClick(R.id.current_time_selection) fun currentTime() = returnSelectedTime(customTime) @OnClick(R.id.pick_time_button) fun pickTime() { - newTimePicker(this, REQUEST_TIME, selected?.millis ?: today.noon().millis) + val time = if (Task.hasDueTime(today.withMillisOfDay(selectedTime).millis)) { + selectedTime + } else { + today.noon().millisOfDay + } + newTimePicker(this, REQUEST_TIME, today.withMillisOfDay(time).millis) .show(parentFragmentManager, FRAG_TAG_TIME_PICKER) } private fun returnSelectedTime(millisOfDay: Int) { - if (selected == null) { - selected = today.withMillisOfDay(millisOfDay) - if (selected!!.isBeforeNow) { - selected = selected!!.plusDays(1) - } - } else { - selected = selected!!.withMillisOfDay(millisOfDay) + val day = when { + selectedDay == MULTIPLE_DAYS -> MULTIPLE_DAYS + selectedDay > 0 -> selectedDay + today.withMillisOfDay(millisOfDay).isAfterNow -> today.millis + else -> today.plusDays(1).millis } - returnDate(selected!!.millis) + returnDate(day = day, time = millisOfDay) } - private fun returnDate(dt: DateTime? = selected) = returnDate(dt?.millis ?: 0) - - private fun returnDate(date: Long? = selected?.millis) { - selected = if (date == null || date <= 0) null else DateTime(date) + private fun returnDate(day: Long = selectedDay, time: Int = selectedTime) { + selectedDay = day + selectedTime = time if (closeAutomatically()) { sendSelected() } else { @@ -237,21 +256,37 @@ class DateTimePicker : BottomSheetDialogFragment() { } } + private val taskIds: LongArray + get() = arguments?.getLongArray(EXTRA_TASKS) ?: longArrayOf() + private fun sendSelected() { - val taskId = arguments?.getLong(EXTRA_TASK) ?: 0 - val dueDate = selected?.millis ?: 0 - if (dueDate != arguments?.getLong(EXTRA_TIMESTAMP)) { - if (taskId > 0) { + if (selectedDay != arguments?.getLong(EXTRA_DAY) + || selectedTime != arguments?.getInt(EXTRA_TIME)) { + if (taskIds.isEmpty()) { + val intent = Intent() + intent.putExtra(EXTRA_TIMESTAMP, when { + selectedDay == NO_DAY -> 0 + selectedTime == NO_TIME -> selectedDay + else -> selectedDay.toDateTime().withMillisOfDay(selectedTime).millis + }) + targetFragment?.onActivityResult(targetRequestCode, RESULT_OK, intent) + } else { lifecycleScope.launch(NonCancellable) { - taskDao.fetch(taskId)?.let { - it.setDueDateAdjustingHideUntil(dueDate) - taskDao.save(it) + taskIds.forEach { taskId -> + taskDao.fetch(taskId)?.let { + it.setDueDateAdjustingHideUntil(when { + selectedDay == MULTIPLE_DAYS -> + it.dueDate.toDateTime().withMillisOfDay(selectedTime).millis + selectedDay == NO_DAY -> 0L + selectedTime == MULTIPLE_TIMES -> + selectedDay.toDateTime().withMillisOfDay(it.dueDate.millisOfDay()).millis + selectedTime == NO_TIME -> selectedDay + else -> selectedDay.toDateTime().withMillisOfDay(selectedTime).millis + }) + taskDao.save(it) + } } } - } else { - val intent = Intent() - intent.putExtra(EXTRA_TIMESTAMP, dueDate) - targetFragment?.onActivityResult(targetRequestCode, RESULT_OK, intent) } } dismiss() @@ -268,7 +303,8 @@ class DateTimePicker : BottomSheetDialogFragment() { override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putSerializable(EXTRA_SELECTED, selected?.millis) + outState.putLong(EXTRA_DAY, selectedDay) + outState.putInt(EXTRA_TIME, selectedTime) } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { diff --git a/app/src/main/java/org/tasks/time/DateTimeUtils.kt b/app/src/main/java/org/tasks/time/DateTimeUtils.kt index 435e2c173..c5c293332 100644 --- a/app/src/main/java/org/tasks/time/DateTimeUtils.kt +++ b/app/src/main/java/org/tasks/time/DateTimeUtils.kt @@ -2,7 +2,7 @@ package org.tasks.time import android.annotation.SuppressLint import org.tasks.BuildConfig -import org.tasks.date.DateTimeUtils.newDateTime +import org.tasks.date.DateTimeUtils.toDateTime import java.util.* object DateTimeUtils { @@ -39,5 +39,7 @@ object DateTimeUtils { } } - fun Long.startOfDay(): Long = newDateTime(this).startOfDay().millis + fun Long.startOfDay(): Long = if (this > 0) toDateTime().startOfDay().millis else 0 + + fun Long.millisOfDay(): Int = if (this > 0) toDateTime().millisOfDay else 0 } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/widget/WidgetClickActivity.kt b/app/src/main/java/org/tasks/widget/WidgetClickActivity.kt index d2e985a25..f7d777f8a 100644 --- a/app/src/main/java/org/tasks/widget/WidgetClickActivity.kt +++ b/app/src/main/java/org/tasks/widget/WidgetClickActivity.kt @@ -57,9 +57,8 @@ class WidgetClickActivity : InjectingAppCompatActivity(), OnDismissHandler { val fragmentManager = supportFragmentManager if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_TIME_PICKER) == null) { newDateTimePicker( - task.id, - task.dueDate, - preferences.getBoolean(R.string.p_auto_dismiss_datetime_widget, false)) + preferences.getBoolean(R.string.p_auto_dismiss_datetime_widget, false), + task) .show(fragmentManager, FRAG_TAG_DATE_TIME_PICKER) } } diff --git a/app/src/main/res/menu/menu_multi_select.xml b/app/src/main/res/menu/menu_multi_select.xml index 81444a36d..244982ad5 100644 --- a/app/src/main/res/menu/menu_multi_select.xml +++ b/app/src/main/res/menu/menu_multi_select.xml @@ -15,10 +15,10 @@ app:showAsAction="always" /> + android:id="@+id/reschedule" + android:icon="@drawable/ic_outline_schedule_24px" + android:title="@string/multi_select_reschedule" + app:showAsAction="always" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 68da6220d..ba98cd6ff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -651,4 +651,6 @@ File %1$s contained %2$s.\n\n Ignore warnings Ignore backup warnings if you do not need backups or have your own backup solution WARNING: Files located in %s will be deleted if Tasks is uninstalled! Please choose a custom location to prevent Android from deleting your files. + Reschedule + Multiple