You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/app/src/main/java/org/tasks/dialogs/DateTimePicker.kt

308 lines
13 KiB
Kotlin

package org.tasks.dialogs
import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isGone
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
4 years ago
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.NonCancellable
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.Companion.newTimePicker
import org.tasks.notifications.NotificationManager
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils.millisOfDay
import org.tasks.time.DateTimeUtils.startOfDay
import java.time.format.FormatStyle
import java.util.Calendar.FRIDAY
import java.util.Calendar.MONDAY
import java.util.Calendar.SATURDAY
import java.util.Calendar.SUNDAY
import java.util.Calendar.THURSDAY
import java.util.Calendar.TUESDAY
import java.util.Calendar.WEDNESDAY
import java.util.Locale
import javax.inject.Inject
4 years ago
@AndroidEntryPoint
class DateTimePicker : BaseDateTimePicker() {
@Inject lateinit var activity: Activity
@Inject lateinit var locale: Locale
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var notificationManager: NotificationManager
lateinit var binding: DialogDateTimePickerBinding
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)
override val calendarView get() = binding.calendarView
override val morningButton get() = binding.shortcuts.morningButton
override val afternoonButton get() = binding.shortcuts.afternoonButton
override val eveningButton get() = binding.shortcuts.eveningButton
override val nightButton get() = binding.shortcuts.nightButton
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_HIDE_NO_DATE = "extra_hide_no_date"
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(
autoClose: Boolean,
vararg tasks: Task
): DateTimePicker {
val fragment = DateTimePicker()
val dueDates = tasks.map { it.dueDate.startOfDay() }.toSet()
val dueTimes = tasks.map { it.dueDate.millisOfDay() }.toSet()
fragment.arguments = Bundle().apply {
putLongArray(EXTRA_TASKS, tasks.map { it.id }.toLongArray())
putLong(EXTRA_DAY, if (dueDates.size == 1) dueDates.first() else MULTIPLE_DAYS)
putInt(EXTRA_TIME, if (dueTimes.size == 1) dueTimes.first() else MULTIPLE_TIMES)
putBoolean(EXTRA_HIDE_NO_DATE, tasks.any { it.isRecurring })
putBoolean(EXTRA_AUTO_CLOSE, autoClose)
}
return fragment
}
fun newDateTimePicker(
target: Fragment,
rc: Int,
current: Long,
autoClose: Boolean,
hideNoDate: Boolean,
): DateTimePicker {
val fragment = DateTimePicker()
fragment.arguments = Bundle().apply {
putLong(EXTRA_DAY, current.startOfDay())
putInt(EXTRA_TIME, current.millisOfDay())
putBoolean(EXTRA_AUTO_CLOSE, autoClose)
putBoolean(EXTRA_HIDE_NO_DATE, hideNoDate)
}
fragment.setTargetFragment(target, rc)
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DialogDateTimePickerBinding.inflate(theme.getLayoutInflater(requireContext()))
setupShortcutsAndCalendar()
with (binding.shortcuts) {
nextWeekButton.text =
getString(
when (newDateTime().plusWeeks(1).dayOfWeek) {
SUNDAY -> R.string.next_sunday
MONDAY -> R.string.next_monday
TUESDAY -> R.string.next_tuesday
WEDNESDAY -> R.string.next_wednesday
THURSDAY -> R.string.next_thursday
FRIDAY -> R.string.next_friday
SATURDAY -> R.string.next_saturday
else -> throw IllegalArgumentException()
}
)
noDateButton.isGone = requireArguments().getBoolean(EXTRA_HIDE_NO_DATE, false)
noDateButton.setOnClickListener { clearDate() }
noTime.setOnClickListener { clearTime() }
todayButton.setOnClickListener { setToday() }
tomorrowButton.setOnClickListener { setTomorrow() }
nextWeekButton.setOnClickListener { setNextWeek() }
morningButton.setOnClickListener { setMorning() }
afternoonButton.setOnClickListener { setAfternoon() }
eveningButton.setOnClickListener { setEvening() }
nightButton.setOnClickListener { setNight() }
currentDateSelection.setOnClickListener { currentDate() }
currentTimeSelection.setOnClickListener { currentTime() }
pickTimeButton.setOnClickListener { pickTime() }
}
binding.calendarView.setOnDateChangeListener { _, y, m, d ->
returnDate(day = DateTime(y, m + 1, d).millis)
refreshButtons()
}
selectedDay = savedInstanceState?.getLong(EXTRA_DAY) ?: requireArguments().getLong(EXTRA_DAY)
selectedTime =
savedInstanceState?.getInt(EXTRA_TIME)
?: requireArguments().getInt(EXTRA_TIME)
.takeIf { it == MULTIPLE_TIMES || Task.hasDueTime(it.toLong()) }
?: NO_TIME
return binding.root
}
override fun refreshButtons() {
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 = selectedDay
binding.shortcuts.dateGroup.check(R.id.current_date_selection)
binding.shortcuts.currentDateSelection.visibility = View.VISIBLE
binding.shortcuts.currentDateSelection.text = if (customDate == MULTIPLE_DAYS) {
requireContext().getString(R.string.date_picker_multiple)
} else {
DateUtilities.getRelativeDay(context, selectedDay, locale, FormatStyle.MEDIUM)
}
}
}
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 = selectedTime
binding.shortcuts.timeGroup.check(R.id.current_time_selection)
binding.shortcuts.currentTimeSelection.visibility = View.VISIBLE
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 (selectedDay > 0) {
binding.calendarView.setDate(selectedDay, true, true)
}
}
private fun clearDate() = returnDate(day = 0, time = 0)
private fun clearTime() = returnDate(time = 0)
private fun setToday() = returnDate(day = today.startOfDay().millis)
private fun setTomorrow() = returnDate(day = tomorrow.startOfDay().millis)
private fun setNextWeek() = returnDate(day = nextWeek.startOfDay().millis)
private fun setMorning() = returnSelectedTime(morning)
private fun setAfternoon() = returnSelectedTime(afternoon)
private fun setEvening() = returnSelectedTime(evening)
private fun setNight() = returnSelectedTime(night)
private fun currentDate() = returnDate(day = customDate)
private fun currentTime() = returnSelectedTime(customTime)
fun pickTime() {
val time = if (selectedTime == MULTIPLE_TIMES
|| !Task.hasDueTime(today.withMillisOfDay(selectedTime).millis)) {
today.noon().millisOfDay
} else {
selectedTime
}
newTimePicker(this, REQUEST_TIME, today.withMillisOfDay(time).millis)
.show(parentFragmentManager, FRAG_TAG_TIME_PICKER)
}
private fun returnSelectedTime(millisOfDay: Int) {
val day = when {
selectedDay == MULTIPLE_DAYS -> MULTIPLE_DAYS
selectedDay > 0 -> selectedDay
today.withMillisOfDay(millisOfDay).isAfterNow -> today.millis
else -> today.plusDays(1).millis
}
returnDate(day = day, time = millisOfDay)
}
private fun returnDate(day: Long = selectedDay, time: Int = selectedTime) {
selectedDay = day
selectedTime = time
if (closeAutomatically()) {
sendSelected()
} else {
refreshButtons()
}
}
private val taskIds: LongArray
get() = arguments?.getLongArray(EXTRA_TASKS) ?: longArrayOf()
override fun sendSelected() {
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(taskIds.toList())
.forEach {
val day = if (selectedDay == MULTIPLE_DAYS) {
if (it.hasDueDate()) it.dueDate else today.millis
} else {
selectedDay
}
val time = if (selectedTime == MULTIPLE_TIMES) {
if (it.hasDueTime()) it.dueDate.millisOfDay() else NO_TIME
} else {
selectedTime
}
it.setDueDateAdjustingHideUntil(when {
day == NO_DAY -> 0L
time == NO_TIME -> Task.createDueDate(
Task.URGENCY_SPECIFIC_DAY,
day
)
else -> Task.createDueDate(
Task.URGENCY_SPECIFIC_DAY_TIME,
day.toDateTime().withMillisOfDay(time).millis
)
})
taskDao.save(it)
}
}
}
}
dismiss()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(EXTRA_DAY, selectedDay)
outState.putInt(EXTRA_TIME, selectedTime)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_TIME) {
if (resultCode == RESULT_OK) {
val timestamp = data!!.getLongExtra(MyTimePickerDialog.EXTRA_TIMESTAMP, today.millis)
returnSelectedTime(newDateTime(timestamp).millisOfDay + 1000)
} else {
refreshButtons()
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
}