Multi-select rescheduling

pull/1178/head
Alex Baker 5 years ago
parent e2071b58e3
commit 2a47ee321c

@ -652,6 +652,21 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
} }
true 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 -> { R.id.menu_select_all -> {
lifecycleScope.launch { lifecycleScope.launch {
taskAdapter.setSelected(taskDao.fetchTasks(preferences, filter) taskAdapter.setSelected(taskDao.fetchTasks(preferences, filter)
@ -714,9 +729,8 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
val fragmentManager = parentFragmentManager val fragmentManager = parentFragmentManager
if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_TIME_PICKER) == null) { if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_TIME_PICKER) == null) {
newDateTimePicker( newDateTimePicker(
task.id, preferences.getBoolean(R.string.p_auto_dismiss_datetime_list_screen, false),
task.dueDate, task.task)
preferences.getBoolean(R.string.p_auto_dismiss_datetime_list_screen, false))
.show(fragmentManager, FRAG_TAG_DATE_TIME_PICKER) .show(fragmentManager, FRAG_TAG_DATE_TIME_PICKER)
} }
} }

@ -29,12 +29,15 @@ import kotlinx.coroutines.launch
import org.tasks.R import org.tasks.R
import org.tasks.databinding.DialogDateTimePickerBinding import org.tasks.databinding.DialogDateTimePickerBinding
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.dialogs.MyTimePickerDialog.newTimePicker import org.tasks.dialogs.MyTimePickerDialog.newTimePicker
import org.tasks.locale.Locale import org.tasks.locale.Locale
import org.tasks.notifications.NotificationManager import org.tasks.notifications.NotificationManager
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.themes.Theme import org.tasks.themes.Theme
import org.tasks.time.DateTime import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils.millisOfDay
import org.tasks.time.DateTimeUtils.startOfDay
import java.time.format.FormatStyle import java.time.format.FormatStyle
import javax.inject.Inject import javax.inject.Inject
@ -49,12 +52,13 @@ class DateTimePicker : BottomSheetDialogFragment() {
@Inject lateinit var theme: Theme @Inject lateinit var theme: Theme
lateinit var binding: DialogDateTimePickerBinding lateinit var binding: DialogDateTimePickerBinding
private var customDate: DateTime? = null private var customDate = NO_DAY
private var selected: DateTime? = null private var customTime = NO_TIME
private var selectedDay = NO_DAY
private var selectedTime = NO_TIME
private val today = newDateTime().startOfDay() private val today = newDateTime().startOfDay()
private val tomorrow = today.plusDays(1) private val tomorrow = today.plusDays(1)
private val nextWeek = today.plusDays(7) private val nextWeek = today.plusDays(7)
private var customTime = 0
private var morning = 32401000 private var morning = 32401000
private var afternoon = 46801000 private var afternoon = 46801000
private var evening = 61201000 private var evening = 61201000
@ -66,17 +70,25 @@ class DateTimePicker : BottomSheetDialogFragment() {
} }
companion object { 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_TIMESTAMP = "extra_timestamp"
const val EXTRA_TASK = "extra_task"
private const val EXTRA_AUTO_CLOSE = "extra_auto_close" 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 REQUEST_TIME = 10101
private const val FRAG_TAG_TIME_PICKER = "frag_tag_time_picker" 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() val bundle = Bundle()
bundle.putLong(EXTRA_TASK, task) bundle.putLongArray(EXTRA_TASKS, tasks.map { it.id }.toLongArray())
bundle.putLong(EXTRA_TIMESTAMP, current) 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) bundle.putBoolean(EXTRA_AUTO_CLOSE, autoClose)
val fragment = DateTimePicker() val fragment = DateTimePicker()
fragment.arguments = bundle fragment.arguments = bundle
@ -85,7 +97,8 @@ class DateTimePicker : BottomSheetDialogFragment() {
fun newDateTimePicker(target: Fragment, rc: Int, current: Long, autoClose: Boolean): DateTimePicker { fun newDateTimePicker(target: Fragment, rc: Int, current: Long, autoClose: Boolean): DateTimePicker {
val bundle = Bundle() 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) bundle.putBoolean(EXTRA_AUTO_CLOSE, autoClose)
val fragment = DateTimePicker() val fragment = DateTimePicker()
fragment.arguments = bundle fragment.arguments = bundle
@ -108,18 +121,15 @@ class DateTimePicker : BottomSheetDialogFragment() {
binding.shortcuts.nextWeekButton.text = binding.shortcuts.nextWeekButton.text =
getString(R.string.next, DateUtilities.getWeekdayShort(newDateTime().plusWeeks(1), locale.locale)) getString(R.string.next, DateUtilities.getWeekdayShort(newDateTime().plusWeeks(1), locale.locale))
binding.calendarView.setOnDateChangeListener { _, y, m, d -> binding.calendarView.setOnDateChangeListener { _, y, m, d ->
selected = DateTime(y, m + 1, d, selected?.hourOfDay ?: 0, selected?.minuteOfHour returnDate(day = DateTime(y, m + 1, d).millis)
?: 0, selected?.secondOfMinute ?: 0)
returnDate(selected!!.millis)
refreshButtons() refreshButtons()
} }
val firstDayOfWeek = preferences.firstDayOfWeek val firstDayOfWeek = preferences.firstDayOfWeek
if (firstDayOfWeek in 1..7) { if (firstDayOfWeek in 1..7) {
binding.calendarView.firstDayOfWeek = firstDayOfWeek binding.calendarView.firstDayOfWeek = firstDayOfWeek
} }
val timestamp = savedInstanceState?.getLong(EXTRA_SELECTED) selectedDay = savedInstanceState?.getLong(EXTRA_DAY) ?: requireArguments().getLong(EXTRA_DAY)
?: requireArguments().getLong(EXTRA_TIMESTAMP) selectedTime = savedInstanceState?.getInt(EXTRA_TIME) ?: requireArguments().getInt(EXTRA_TIME)
selected = if (timestamp > 0) DateTime(timestamp) else null
return binding.root return binding.root
} }
@ -141,54 +151,61 @@ class DateTimePicker : BottomSheetDialogFragment() {
} }
private fun refreshButtons() { private fun refreshButtons() {
when (selected?.startOfDay()) { when (selectedDay) {
null -> binding.shortcuts.dateGroup.check(R.id.no_date_button) 0L -> binding.shortcuts.dateGroup.check(R.id.no_date_button)
today -> binding.shortcuts.dateGroup.check(R.id.today_button) today.millis -> binding.shortcuts.dateGroup.check(R.id.today_button)
tomorrow -> binding.shortcuts.dateGroup.check(R.id.tomorrow_button) tomorrow.millis -> binding.shortcuts.dateGroup.check(R.id.tomorrow_button)
nextWeek -> binding.shortcuts.dateGroup.check(R.id.next_week_button) nextWeek.millis -> binding.shortcuts.dateGroup.check(R.id.next_week_button)
else -> { else -> {
customDate = selected customDate = selectedDay
binding.shortcuts.dateGroup.check(R.id.current_date_selection) binding.shortcuts.dateGroup.check(R.id.current_date_selection)
binding.shortcuts.currentDateSelection.visibility = View.VISIBLE binding.shortcuts.currentDateSelection.visibility = View.VISIBLE
binding.shortcuts.currentDateSelection.text = binding.shortcuts.currentDateSelection.text = if (customDate == MULTIPLE_DAYS) {
DateUtilities.getRelativeDay(context, selected!!.millis, locale.locale, FormatStyle.MEDIUM) requireContext().getString(R.string.date_picker_multiple)
} else {
DateUtilities.getRelativeDay(context, selectedDay, locale.locale, FormatStyle.MEDIUM)
}
} }
} }
if (Task.hasDueTime(selected?.millis ?: 0)) { if (selectedTime == MULTIPLE_TIMES || Task.hasDueTime(selectedTime.toLong())) {
when (selected?.millisOfDay) { when (selectedTime) {
morning -> binding.shortcuts.timeGroup.check(R.id.morning_button) morning -> binding.shortcuts.timeGroup.check(R.id.morning_button)
afternoon -> binding.shortcuts.timeGroup.check(R.id.afternoon_button) afternoon -> binding.shortcuts.timeGroup.check(R.id.afternoon_button)
evening -> binding.shortcuts.timeGroup.check(R.id.evening_button) evening -> binding.shortcuts.timeGroup.check(R.id.evening_button)
night -> binding.shortcuts.timeGroup.check(R.id.night_button) night -> binding.shortcuts.timeGroup.check(R.id.night_button)
else -> { else -> {
customTime = selected!!.millisOfDay customTime = selectedTime
binding.shortcuts.timeGroup.check(R.id.current_time_selection) binding.shortcuts.timeGroup.check(R.id.current_time_selection)
binding.shortcuts.currentTimeSelection.visibility = View.VISIBLE 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 { } else {
binding.shortcuts.timeGroup.check(R.id.no_time) binding.shortcuts.timeGroup.check(R.id.no_time)
} }
if (selected != null) { if (selectedDay > 0) {
binding.calendarView.setDate(selected!!.millis, true, true) binding.calendarView.setDate(selectedDay, true, true)
} }
} }
@OnClick(R.id.no_date_button) @OnClick(R.id.no_date_button)
fun clearDate() = returnDate(0) fun clearDate() = returnDate(day = 0, time = 0)
@OnClick(R.id.no_time) @OnClick(R.id.no_time)
fun clearTime() = returnDate(selected?.startOfDay()?.millis ?: 0) fun clearTime() = returnDate(time = 0)
@OnClick(R.id.today_button) @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) @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) @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) @OnClick(R.id.morning_button)
fun setMorning() = returnSelectedTime(morning) fun setMorning() = returnSelectedTime(morning)
@ -203,33 +220,35 @@ class DateTimePicker : BottomSheetDialogFragment() {
fun setNight() = returnSelectedTime(night) fun setNight() = returnSelectedTime(night)
@OnClick(R.id.current_date_selection) @OnClick(R.id.current_date_selection)
fun currentDate() = returnDate(customDate) fun currentDate() = returnDate(day = customDate)
@OnClick(R.id.current_time_selection) @OnClick(R.id.current_time_selection)
fun currentTime() = returnSelectedTime(customTime) fun currentTime() = returnSelectedTime(customTime)
@OnClick(R.id.pick_time_button) @OnClick(R.id.pick_time_button)
fun pickTime() { 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) .show(parentFragmentManager, FRAG_TAG_TIME_PICKER)
} }
private fun returnSelectedTime(millisOfDay: Int) { private fun returnSelectedTime(millisOfDay: Int) {
if (selected == null) { val day = when {
selected = today.withMillisOfDay(millisOfDay) selectedDay == MULTIPLE_DAYS -> MULTIPLE_DAYS
if (selected!!.isBeforeNow) { selectedDay > 0 -> selectedDay
selected = selected!!.plusDays(1) today.withMillisOfDay(millisOfDay).isAfterNow -> today.millis
} else -> today.plusDays(1).millis
} else {
selected = selected!!.withMillisOfDay(millisOfDay)
} }
returnDate(selected!!.millis) returnDate(day = day, time = millisOfDay)
} }
private fun returnDate(dt: DateTime? = selected) = returnDate(dt?.millis ?: 0) private fun returnDate(day: Long = selectedDay, time: Int = selectedTime) {
selectedDay = day
private fun returnDate(date: Long? = selected?.millis) { selectedTime = time
selected = if (date == null || date <= 0) null else DateTime(date)
if (closeAutomatically()) { if (closeAutomatically()) {
sendSelected() sendSelected()
} else { } else {
@ -237,21 +256,37 @@ class DateTimePicker : BottomSheetDialogFragment() {
} }
} }
private val taskIds: LongArray
get() = arguments?.getLongArray(EXTRA_TASKS) ?: longArrayOf()
private fun sendSelected() { private fun sendSelected() {
val taskId = arguments?.getLong(EXTRA_TASK) ?: 0 if (selectedDay != arguments?.getLong(EXTRA_DAY)
val dueDate = selected?.millis ?: 0 || selectedTime != arguments?.getInt(EXTRA_TIME)) {
if (dueDate != arguments?.getLong(EXTRA_TIMESTAMP)) { if (taskIds.isEmpty()) {
if (taskId > 0) { 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) { lifecycleScope.launch(NonCancellable) {
taskIds.forEach { taskId ->
taskDao.fetch(taskId)?.let { taskDao.fetch(taskId)?.let {
it.setDueDateAdjustingHideUntil(dueDate) 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) taskDao.save(it)
} }
} }
} else { }
val intent = Intent()
intent.putExtra(EXTRA_TIMESTAMP, dueDate)
targetFragment?.onActivityResult(targetRequestCode, RESULT_OK, intent)
} }
} }
dismiss() dismiss()
@ -268,7 +303,8 @@ class DateTimePicker : BottomSheetDialogFragment() {
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) 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 { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

@ -2,7 +2,7 @@ package org.tasks.time
import android.annotation.SuppressLint import android.annotation.SuppressLint
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.toDateTime
import java.util.* import java.util.*
object DateTimeUtils { 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
} }

@ -57,9 +57,8 @@ class WidgetClickActivity : InjectingAppCompatActivity(), OnDismissHandler {
val fragmentManager = supportFragmentManager val fragmentManager = supportFragmentManager
if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_TIME_PICKER) == null) { if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_TIME_PICKER) == null) {
newDateTimePicker( newDateTimePicker(
task.id, preferences.getBoolean(R.string.p_auto_dismiss_datetime_widget, false),
task.dueDate, task)
preferences.getBoolean(R.string.p_auto_dismiss_datetime_widget, false))
.show(fragmentManager, FRAG_TAG_DATE_TIME_PICKER) .show(fragmentManager, FRAG_TAG_DATE_TIME_PICKER)
} }
} }

@ -15,10 +15,10 @@
app:showAsAction="always" /> app:showAsAction="always" />
<item <item
android:id="@+id/delete" android:id="@+id/reschedule"
android:icon="@drawable/ic_outline_delete_24px" android:icon="@drawable/ic_outline_schedule_24px"
android:title="@string/delete" android:title="@string/multi_select_reschedule"
app:showAsAction="always"/> app:showAsAction="always" />
<item <item
android:id="@+id/menu_select_all" android:id="@+id/menu_select_all"
@ -38,4 +38,10 @@
android:title="@string/copy" android:title="@string/copy"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<item
android:id="@+id/delete"
android:icon="@drawable/ic_outline_delete_24px"
android:title="@string/delete"
app:showAsAction="ifRoom"/>
</menu> </menu>

@ -651,4 +651,6 @@ File %1$s contained %2$s.\n\n
<string name="backups_ignore_warnings">Ignore warnings</string> <string name="backups_ignore_warnings">Ignore warnings</string>
<string name="backups_ignore_warnings_summary">Ignore backup warnings if you do not need backups or have your own backup solution</string> <string name="backups_ignore_warnings_summary">Ignore backup warnings if you do not need backups or have your own backup solution</string>
<string name="backup_location_warning">WARNING: Files located in %s will be deleted if Tasks is uninstalled! Please choose a custom location to prevent Android from deleting your files.</string> <string name="backup_location_warning">WARNING: Files located in %s will be deleted if Tasks is uninstalled! Please choose a custom location to prevent Android from deleting your files.</string>
<string name="multi_select_reschedule">Reschedule</string>
<string name="date_picker_multiple">Multiple</string>
</resources> </resources>

Loading…
Cancel
Save