|
|
|
@ -20,16 +20,27 @@ import com.todoroo.astrid.data.Task.Companion.NOTIFY_MODE_FIVE
|
|
|
|
|
import com.todoroo.astrid.data.Task.Companion.NOTIFY_MODE_NONSTOP
|
|
|
|
|
import com.todoroo.astrid.data.Task.Companion.createDueDate
|
|
|
|
|
import com.todoroo.astrid.data.Task.Companion.hasDueTime
|
|
|
|
|
import com.todoroo.astrid.data.Task.RepeatFrom.Companion.COMPLETION_DATE
|
|
|
|
|
import com.todoroo.astrid.data.Task.RepeatFrom.Companion.DUE_DATE
|
|
|
|
|
import com.todoroo.astrid.gcal.GCalHelper
|
|
|
|
|
import com.todoroo.astrid.service.TaskCompleter
|
|
|
|
|
import com.todoroo.astrid.service.TaskCreator
|
|
|
|
|
import com.todoroo.astrid.service.TaskCreator.Companion.getDefaultAlarms
|
|
|
|
|
import com.todoroo.astrid.service.TaskDeleter
|
|
|
|
|
import com.todoroo.astrid.service.TaskMover
|
|
|
|
|
import com.todoroo.astrid.timers.TimerPlugin
|
|
|
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
|
|
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
|
|
|
import kotlinx.collections.immutable.PersistentList
|
|
|
|
|
import kotlinx.collections.immutable.PersistentSet
|
|
|
|
|
import kotlinx.collections.immutable.persistentListOf
|
|
|
|
|
import kotlinx.collections.immutable.persistentSetOf
|
|
|
|
|
import kotlinx.collections.immutable.toPersistentSet
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
|
import kotlinx.coroutines.NonCancellable
|
|
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
|
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
|
|
|
import kotlinx.coroutines.flow.asStateFlow
|
|
|
|
|
import kotlinx.coroutines.flow.update
|
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
|
import kotlinx.coroutines.runBlocking
|
|
|
|
@ -60,6 +71,7 @@ import org.tasks.files.FileHelper
|
|
|
|
|
import org.tasks.location.GeofenceApi
|
|
|
|
|
import org.tasks.preferences.PermissionChecker
|
|
|
|
|
import org.tasks.preferences.Preferences
|
|
|
|
|
import org.tasks.time.DateTime
|
|
|
|
|
import org.tasks.time.DateTimeUtils.currentTimeMillis
|
|
|
|
|
import org.tasks.time.DateTimeUtils.startOfDay
|
|
|
|
|
import timber.log.Timber
|
|
|
|
@ -67,199 +79,130 @@ import javax.inject.Inject
|
|
|
|
|
|
|
|
|
|
@HiltViewModel
|
|
|
|
|
class TaskEditViewModel @Inject constructor(
|
|
|
|
|
@ApplicationContext private val context: Context,
|
|
|
|
|
savedStateHandle: SavedStateHandle,
|
|
|
|
|
private val taskDao: TaskDao,
|
|
|
|
|
private val taskDeleter: TaskDeleter,
|
|
|
|
|
private val timerPlugin: TimerPlugin,
|
|
|
|
|
private val permissionChecker: PermissionChecker,
|
|
|
|
|
private val calendarEventProvider: CalendarEventProvider,
|
|
|
|
|
private val gCalHelper: GCalHelper,
|
|
|
|
|
private val taskMover: TaskMover,
|
|
|
|
|
private val locationDao: LocationDao,
|
|
|
|
|
private val geofenceApi: GeofenceApi,
|
|
|
|
|
private val tagDao: TagDao,
|
|
|
|
|
private val tagDataDao: TagDataDao,
|
|
|
|
|
private val preferences: Preferences,
|
|
|
|
|
private val googleTaskDao: GoogleTaskDao,
|
|
|
|
|
private val caldavDao: CaldavDao,
|
|
|
|
|
private val taskCompleter: TaskCompleter,
|
|
|
|
|
private val alarmService: AlarmService,
|
|
|
|
|
private val taskListEvents: TaskListEventBus,
|
|
|
|
|
private val mainActivityEvents: MainActivityEventBus,
|
|
|
|
|
private val firebase: Firebase? = null,
|
|
|
|
|
private val userActivityDao: UserActivityDao,
|
|
|
|
|
private val alarmDao: AlarmDao,
|
|
|
|
|
private val taskAttachmentDao: TaskAttachmentDao,
|
|
|
|
|
@ApplicationContext private val applicationContext: Context,
|
|
|
|
|
savedStateHandle: SavedStateHandle,
|
|
|
|
|
private val taskDao: TaskDao,
|
|
|
|
|
private val taskDeleter: TaskDeleter,
|
|
|
|
|
private val timerPlugin: TimerPlugin,
|
|
|
|
|
private val permissionChecker: PermissionChecker,
|
|
|
|
|
private val calendarEventProvider: CalendarEventProvider,
|
|
|
|
|
private val gCalHelper: GCalHelper,
|
|
|
|
|
private val taskMover: TaskMover,
|
|
|
|
|
private val locationDao: LocationDao,
|
|
|
|
|
private val geofenceApi: GeofenceApi,
|
|
|
|
|
private val tagDao: TagDao,
|
|
|
|
|
private val tagDataDao: TagDataDao,
|
|
|
|
|
private val preferences: Preferences,
|
|
|
|
|
private val googleTaskDao: GoogleTaskDao,
|
|
|
|
|
private val caldavDao: CaldavDao,
|
|
|
|
|
private val taskCompleter: TaskCompleter,
|
|
|
|
|
private val alarmService: AlarmService,
|
|
|
|
|
private val taskListEvents: TaskListEventBus,
|
|
|
|
|
private val mainActivityEvents: MainActivityEventBus,
|
|
|
|
|
private val firebase: Firebase? = null,
|
|
|
|
|
private val userActivityDao: UserActivityDao,
|
|
|
|
|
private val alarmDao: AlarmDao,
|
|
|
|
|
private val taskAttachmentDao: TaskAttachmentDao,
|
|
|
|
|
private val taskCreator: TaskCreator,
|
|
|
|
|
) : ViewModel() {
|
|
|
|
|
private val resources = context.resources
|
|
|
|
|
private var cleared = false
|
|
|
|
|
|
|
|
|
|
val task: Task = savedStateHandle[TaskEditFragment.EXTRA_TASK]!!
|
|
|
|
|
|
|
|
|
|
val isNew = task.isNew
|
|
|
|
|
|
|
|
|
|
var creationDate: Long = task.creationDate
|
|
|
|
|
var modificationDate: Long = task.modificationDate
|
|
|
|
|
var completionDate: Long = task.completionDate
|
|
|
|
|
var title: String? = task.title
|
|
|
|
|
var completed: Boolean = task.isCompleted
|
|
|
|
|
var priority = MutableStateFlow(task.priority)
|
|
|
|
|
var description: String? = task.notes.stripCarriageReturns()
|
|
|
|
|
val recurrence = MutableStateFlow(task.recurrence)
|
|
|
|
|
val repeatAfterCompletion = MutableStateFlow(task.repeatAfterCompletion())
|
|
|
|
|
var eventUri = MutableStateFlow(task.calendarURI)
|
|
|
|
|
val timerStarted = MutableStateFlow(task.timerStart)
|
|
|
|
|
val estimatedSeconds = MutableStateFlow(task.estimatedSeconds)
|
|
|
|
|
val elapsedSeconds = MutableStateFlow(task.elapsedSeconds)
|
|
|
|
|
var newSubtasks = MutableStateFlow(emptyList<Task>())
|
|
|
|
|
val hasParent: Boolean
|
|
|
|
|
get() = task.parent > 0
|
|
|
|
|
|
|
|
|
|
val dueDate = MutableStateFlow(task.dueDate)
|
|
|
|
|
|
|
|
|
|
fun setDueDate(value: Long) {
|
|
|
|
|
dueDate.value = when {
|
|
|
|
|
value == 0L -> 0
|
|
|
|
|
hasDueTime(value) -> createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, value)
|
|
|
|
|
else -> createDueDate(Task.URGENCY_SPECIFIC_DAY, value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val startDate = MutableStateFlow(task.hideUntil)
|
|
|
|
|
|
|
|
|
|
fun setStartDate(value: Long) {
|
|
|
|
|
startDate.value = when {
|
|
|
|
|
value == 0L -> 0
|
|
|
|
|
hasDueTime(value) ->
|
|
|
|
|
value.toDateTime().withSecondOfMinute(1).withMillisOfSecond(0).millis
|
|
|
|
|
else -> value.startOfDay()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var originalCalendar: String? = if (isNew && permissionChecker.canAccessCalendars()) {
|
|
|
|
|
preferences.defaultCalendar
|
|
|
|
|
} else {
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
var selectedCalendar = MutableStateFlow(originalCalendar)
|
|
|
|
|
|
|
|
|
|
val originalList: Filter = savedStateHandle[TaskEditFragment.EXTRA_LIST]!!
|
|
|
|
|
var selectedList = MutableStateFlow(originalList)
|
|
|
|
|
data class State(
|
|
|
|
|
val task: Task,
|
|
|
|
|
val filter: Filter,
|
|
|
|
|
val calendar: String? = null,
|
|
|
|
|
val location: Location? = null,
|
|
|
|
|
val tags: PersistentSet<TagData> = persistentSetOf(),
|
|
|
|
|
val attachments: PersistentSet<TaskAttachment> = persistentSetOf(),
|
|
|
|
|
val alarms: PersistentSet<Alarm> = persistentSetOf(),
|
|
|
|
|
val newSubtasks: PersistentList<Task> = persistentListOf(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private val resources = applicationContext.resources
|
|
|
|
|
private var cleared = false
|
|
|
|
|
|
|
|
|
|
private var originalLocation: Location? = savedStateHandle[TaskEditFragment.EXTRA_LOCATION]
|
|
|
|
|
var selectedLocation = MutableStateFlow(originalLocation)
|
|
|
|
|
private var originalState: State
|
|
|
|
|
private val _state: MutableStateFlow<State>
|
|
|
|
|
val state: StateFlow<State>
|
|
|
|
|
|
|
|
|
|
private val originalTags: List<TagData> =
|
|
|
|
|
savedStateHandle.get<ArrayList<TagData>>(TaskEditFragment.EXTRA_TAGS) ?: emptyList()
|
|
|
|
|
val selectedTags = MutableStateFlow(ArrayList(originalTags))
|
|
|
|
|
val originalTask: Task
|
|
|
|
|
get() = originalState.task
|
|
|
|
|
|
|
|
|
|
private lateinit var originalAttachments: List<TaskAttachment>
|
|
|
|
|
val selectedAttachments = MutableStateFlow(emptyList<TaskAttachment>())
|
|
|
|
|
val originalFilter: Filter
|
|
|
|
|
get() = originalState.filter
|
|
|
|
|
|
|
|
|
|
private val originalAlarms: List<Alarm> = if (isNew) {
|
|
|
|
|
ArrayList<Alarm>().apply {
|
|
|
|
|
if (task.isNotifyAtStart) {
|
|
|
|
|
add(Alarm.whenStarted(0))
|
|
|
|
|
}
|
|
|
|
|
if (task.isNotifyAtDeadline) {
|
|
|
|
|
add(Alarm.whenDue(0))
|
|
|
|
|
}
|
|
|
|
|
if (task.isNotifyAfterDeadline) {
|
|
|
|
|
add(Alarm.whenOverdue(0))
|
|
|
|
|
}
|
|
|
|
|
if (task.randomReminder > 0) {
|
|
|
|
|
add(Alarm(0, task.randomReminder, Alarm.TYPE_RANDOM))
|
|
|
|
|
}
|
|
|
|
|
init {
|
|
|
|
|
val task: Task = savedStateHandle[TaskEditFragment.EXTRA_TASK]
|
|
|
|
|
?: throw IllegalStateException("No task")
|
|
|
|
|
val tags: List<TagData> = savedStateHandle[TaskEditFragment.EXTRA_TAGS] ?: emptyList()
|
|
|
|
|
|
|
|
|
|
originalState = State(
|
|
|
|
|
task = task.copy(
|
|
|
|
|
notes = task.notes.stripCarriageReturns(),
|
|
|
|
|
calendarURI = task.calendarURI?.takeIf { it.isNotBlank() },
|
|
|
|
|
),
|
|
|
|
|
filter = savedStateHandle[TaskEditFragment.EXTRA_LIST]
|
|
|
|
|
?: throw IllegalStateException("No list"),
|
|
|
|
|
tags = tags.toPersistentSet(),
|
|
|
|
|
location = savedStateHandle[TaskEditFragment.EXTRA_LOCATION],
|
|
|
|
|
calendar = if (task.isNew && permissionChecker.canAccessCalendars()) {
|
|
|
|
|
preferences.defaultCalendar
|
|
|
|
|
} else {
|
|
|
|
|
null
|
|
|
|
|
},
|
|
|
|
|
alarms = if (task.isNew) {
|
|
|
|
|
ArrayList<Alarm>().apply {
|
|
|
|
|
if (task.isNotifyAtStart) {
|
|
|
|
|
add(Alarm.whenStarted(0))
|
|
|
|
|
}
|
|
|
|
|
if (task.isNotifyAtDeadline) {
|
|
|
|
|
add(Alarm.whenDue(0))
|
|
|
|
|
}
|
|
|
|
|
if (task.isNotifyAfterDeadline) {
|
|
|
|
|
add(Alarm.whenOverdue(0))
|
|
|
|
|
}
|
|
|
|
|
if (task.randomReminder > 0) {
|
|
|
|
|
add(Alarm(0, task.randomReminder, Alarm.TYPE_RANDOM))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
savedStateHandle[TaskEditFragment.EXTRA_ALARMS] ?: emptyList()
|
|
|
|
|
}.toPersistentSet()
|
|
|
|
|
)
|
|
|
|
|
_state = MutableStateFlow(originalState)
|
|
|
|
|
state = _state.asStateFlow()
|
|
|
|
|
|
|
|
|
|
viewModelScope.launch(Dispatchers.Default) {
|
|
|
|
|
val attachments = taskAttachmentDao.getAttachments(task.id).toPersistentSet()
|
|
|
|
|
originalState = originalState.copy(attachments = attachments)
|
|
|
|
|
_state.update { it.copy(attachments = attachments) }
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
savedStateHandle[TaskEditFragment.EXTRA_ALARMS]!!
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var selectedAlarms = MutableStateFlow(originalAlarms)
|
|
|
|
|
@Deprecated("delete me")
|
|
|
|
|
val isNew = originalState.task.isNew
|
|
|
|
|
|
|
|
|
|
var ringNonstop: Boolean = task.isNotifyModeNonstop
|
|
|
|
|
set(value) {
|
|
|
|
|
field = value
|
|
|
|
|
if (value) {
|
|
|
|
|
ringFiveTimes = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ringFiveTimes:Boolean = task.isNotifyModeFive
|
|
|
|
|
set(value) {
|
|
|
|
|
field = value
|
|
|
|
|
if (value) {
|
|
|
|
|
ringNonstop = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val isReadOnly = task.readOnly
|
|
|
|
|
val isReadOnly = originalState.task.readOnly
|
|
|
|
|
|
|
|
|
|
val isWritable = !isReadOnly
|
|
|
|
|
|
|
|
|
|
fun hasChanges(): Boolean =
|
|
|
|
|
(task.title != title || (isNew && title?.isNotBlank() == true)) ||
|
|
|
|
|
task.isCompleted != completed ||
|
|
|
|
|
task.dueDate != dueDate.value ||
|
|
|
|
|
task.priority != priority.value ||
|
|
|
|
|
if (task.notes.isNullOrBlank()) {
|
|
|
|
|
!description.isNullOrBlank()
|
|
|
|
|
} else {
|
|
|
|
|
task.notes != description
|
|
|
|
|
} ||
|
|
|
|
|
task.hideUntil != startDate.value ||
|
|
|
|
|
if (task.recurrence.isNullOrBlank()) {
|
|
|
|
|
!recurrence.value.isNullOrBlank()
|
|
|
|
|
} else {
|
|
|
|
|
task.recurrence != recurrence.value
|
|
|
|
|
} ||
|
|
|
|
|
task.repeatAfterCompletion() != repeatAfterCompletion.value ||
|
|
|
|
|
originalCalendar != selectedCalendar.value ||
|
|
|
|
|
if (task.calendarURI.isNullOrBlank()) {
|
|
|
|
|
!eventUri.value.isNullOrBlank()
|
|
|
|
|
} else {
|
|
|
|
|
task.calendarURI != eventUri.value
|
|
|
|
|
} ||
|
|
|
|
|
task.elapsedSeconds != elapsedSeconds.value ||
|
|
|
|
|
task.estimatedSeconds != estimatedSeconds.value ||
|
|
|
|
|
originalList != selectedList.value ||
|
|
|
|
|
originalLocation != selectedLocation.value ||
|
|
|
|
|
originalTags.toHashSet() != selectedTags.value.toHashSet() ||
|
|
|
|
|
(::originalAttachments.isInitialized &&
|
|
|
|
|
originalAttachments.toHashSet() != selectedAttachments.value.toHashSet()) ||
|
|
|
|
|
newSubtasks.value.isNotEmpty() ||
|
|
|
|
|
getRingFlags() != when {
|
|
|
|
|
task.isNotifyModeFive -> NOTIFY_MODE_FIVE
|
|
|
|
|
task.isNotifyModeNonstop -> NOTIFY_MODE_NONSTOP
|
|
|
|
|
else -> 0
|
|
|
|
|
} ||
|
|
|
|
|
originalAlarms.toHashSet() != selectedAlarms.value.toHashSet()
|
|
|
|
|
fun hasChanges() = originalState != _state.value
|
|
|
|
|
|
|
|
|
|
@MainThread
|
|
|
|
|
suspend fun save(remove: Boolean = true): Boolean = withContext(NonCancellable) {
|
|
|
|
|
suspend fun save(remove: Boolean = true): Task? = withContext(NonCancellable) {
|
|
|
|
|
if (cleared) {
|
|
|
|
|
return@withContext false
|
|
|
|
|
return@withContext null
|
|
|
|
|
}
|
|
|
|
|
if (!hasChanges() || isReadOnly) {
|
|
|
|
|
discard(remove)
|
|
|
|
|
return@withContext false
|
|
|
|
|
return@withContext null
|
|
|
|
|
}
|
|
|
|
|
clear(remove)
|
|
|
|
|
task.title = if (title.isNullOrBlank()) resources.getString(R.string.no_title) else title
|
|
|
|
|
task.dueDate = dueDate.value
|
|
|
|
|
task.priority = priority.value
|
|
|
|
|
task.notes = description
|
|
|
|
|
task.hideUntil = startDate.value
|
|
|
|
|
task.recurrence = recurrence.value
|
|
|
|
|
task.repeatFrom = if (repeatAfterCompletion.value) {
|
|
|
|
|
Task.RepeatFrom.COMPLETION_DATE
|
|
|
|
|
} else {
|
|
|
|
|
Task.RepeatFrom.DUE_DATE
|
|
|
|
|
}
|
|
|
|
|
task.elapsedSeconds = elapsedSeconds.value
|
|
|
|
|
task.estimatedSeconds = estimatedSeconds.value
|
|
|
|
|
task.ringFlags = getRingFlags()
|
|
|
|
|
val originalTask = originalState.task
|
|
|
|
|
val state = _state.value
|
|
|
|
|
var task = state.task
|
|
|
|
|
if (task.title.isNullOrBlank()) {
|
|
|
|
|
task = task.copy(title = resources.getString(R.string.no_title))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
applyCalendarChanges()
|
|
|
|
|
|
|
|
|
@ -267,14 +210,14 @@ class TaskEditViewModel @Inject constructor(
|
|
|
|
|
taskDao.createNew(task)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((isNew && selectedLocation.value != null) || originalLocation != selectedLocation.value) {
|
|
|
|
|
originalLocation?.let { location ->
|
|
|
|
|
if ((isNew && state.location != null) || originalState.location != state.location) {
|
|
|
|
|
originalState.location?.let { location ->
|
|
|
|
|
if (location.geofence.id > 0) {
|
|
|
|
|
locationDao.delete(location.geofence)
|
|
|
|
|
geofenceApi.update(location.place)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
selectedLocation.value?.let { location ->
|
|
|
|
|
state.location?.let { location ->
|
|
|
|
|
val place = location.place
|
|
|
|
|
locationDao.insert(
|
|
|
|
|
location.geofence.copy(
|
|
|
|
@ -288,26 +231,34 @@ class TaskEditViewModel @Inject constructor(
|
|
|
|
|
task.modificationDate = currentTimeMillis()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((isNew && selectedTags.value.isNotEmpty()) || originalTags.toHashSet() != selectedTags.value.toHashSet()) {
|
|
|
|
|
tagDao.applyTags(task, tagDataDao, selectedTags.value)
|
|
|
|
|
if ((isNew && state.tags.isNotEmpty()) || originalState.tags.toHashSet() != state.tags.toHashSet()) {
|
|
|
|
|
tagDao.applyTags(task, tagDataDao, state.tags.toList())
|
|
|
|
|
task.modificationDate = currentTimeMillis()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!task.hasStartDate()) {
|
|
|
|
|
selectedAlarms.value = selectedAlarms.value.filterNot { a -> a.type == TYPE_REL_START }
|
|
|
|
|
_state.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
alarms = it.alarms.removeAll { a -> a.type == TYPE_REL_START }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!task.hasDueDate()) {
|
|
|
|
|
selectedAlarms.value = selectedAlarms.value.filterNot { a -> a.type == TYPE_REL_END }
|
|
|
|
|
_state.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
alarms = it.alarms.removeAll { a -> a.type == TYPE_REL_END }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
taskDao.save(task, null)
|
|
|
|
|
|
|
|
|
|
if (isNew || originalList != selectedList.value) {
|
|
|
|
|
if (isNew || originalState.filter != state.filter) {
|
|
|
|
|
task.parent = 0
|
|
|
|
|
taskMover.move(listOf(task.id), selectedList.value)
|
|
|
|
|
taskMover.move(listOf(task.id), state.filter)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (subtask in newSubtasks.value) {
|
|
|
|
|
for (subtask in state.newSubtasks) {
|
|
|
|
|
if (Strings.isNullOrEmpty(subtask.title)) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
@ -317,7 +268,7 @@ class TaskEditViewModel @Inject constructor(
|
|
|
|
|
taskDao.createNew(subtask)
|
|
|
|
|
alarmDao.insert(subtask.getDefaultAlarms())
|
|
|
|
|
firebase?.addTask("subtasks")
|
|
|
|
|
when (val filter = selectedList.value) {
|
|
|
|
|
when (val filter = state.filter) {
|
|
|
|
|
is GtasksFilter -> {
|
|
|
|
|
val googleTask = CaldavTask(
|
|
|
|
|
task = subtask.id,
|
|
|
|
@ -354,24 +305,23 @@ class TaskEditViewModel @Inject constructor(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
selectedAlarms.value.toHashSet() != originalAlarms.toHashSet() ||
|
|
|
|
|
(isNew && selectedAlarms.value.isNotEmpty())
|
|
|
|
|
originalState.alarms.toHashSet() != state.alarms.toHashSet() ||
|
|
|
|
|
(isNew && state.alarms.isNotEmpty())
|
|
|
|
|
) {
|
|
|
|
|
alarmService.synchronizeAlarms(task.id, selectedAlarms.value.toMutableSet())
|
|
|
|
|
alarmService.synchronizeAlarms(task.id, state.alarms.toMutableSet())
|
|
|
|
|
task.putTransitory(SyncFlags.FORCE_CALDAV_SYNC, true)
|
|
|
|
|
task.modificationDate = now()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
this@TaskEditViewModel::originalAttachments.isInitialized &&
|
|
|
|
|
selectedAttachments.value.toHashSet() != originalAttachments.toHashSet()
|
|
|
|
|
) {
|
|
|
|
|
originalAttachments
|
|
|
|
|
.minus(selectedAttachments.value.toSet())
|
|
|
|
|
if (state.attachments.toHashSet() != originalState.attachments.toHashSet()) {
|
|
|
|
|
originalState
|
|
|
|
|
.attachments
|
|
|
|
|
.minus(state.attachments.toSet())
|
|
|
|
|
.map { it.remoteId }
|
|
|
|
|
.let { taskAttachmentDao.delete(task.id, it) }
|
|
|
|
|
selectedAttachments.value
|
|
|
|
|
.minus(originalAttachments.toSet())
|
|
|
|
|
state
|
|
|
|
|
.attachments
|
|
|
|
|
.minus(originalState.attachments.toSet())
|
|
|
|
|
.map {
|
|
|
|
|
Attachment(
|
|
|
|
|
task = task.id,
|
|
|
|
@ -382,8 +332,8 @@ class TaskEditViewModel @Inject constructor(
|
|
|
|
|
.let { taskAttachmentDao.insert(it) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (task.isCompleted != completed) {
|
|
|
|
|
taskCompleter.setComplete(task, completed)
|
|
|
|
|
if (task.isCompleted != originalTask.isCompleted) {
|
|
|
|
|
taskCompleter.setComplete(task, task.isCompleted)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isNew) {
|
|
|
|
@ -393,44 +343,45 @@ class TaskEditViewModel @Inject constructor(
|
|
|
|
|
taskListEvents.emit(TaskListEvent.CalendarEventCreated(model.title, it))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
task
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun applyCalendarChanges() {
|
|
|
|
|
if (!permissionChecker.canAccessCalendars()) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (eventUri.value == null) {
|
|
|
|
|
val task = state.value.task
|
|
|
|
|
if (task.calendarURI == null) {
|
|
|
|
|
calendarEventProvider.deleteEvent(task)
|
|
|
|
|
}
|
|
|
|
|
if (!task.hasDueDate()) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
selectedCalendar.value?.let {
|
|
|
|
|
_state.value.calendar?.let { selectedCalendar ->
|
|
|
|
|
try {
|
|
|
|
|
task.calendarURI = gCalHelper.createTaskEvent(task, it)?.toString()
|
|
|
|
|
_state.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
task = it.task.copy(
|
|
|
|
|
calendarURI = gCalHelper.createTaskEvent(it.task, selectedCalendar)?.toString()
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
Timber.e(e)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun getRingFlags() = when {
|
|
|
|
|
ringNonstop -> NOTIFY_MODE_NONSTOP
|
|
|
|
|
ringFiveTimes -> NOTIFY_MODE_FIVE
|
|
|
|
|
else -> 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suspend fun delete() {
|
|
|
|
|
taskDeleter.markDeleted(task)
|
|
|
|
|
taskDeleter.markDeleted(_state.value.task.id)
|
|
|
|
|
discard()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suspend fun discard(remove: Boolean = true) {
|
|
|
|
|
if (isNew) {
|
|
|
|
|
timerPlugin.stopTimer(task)
|
|
|
|
|
originalAttachments.plus(selectedAttachments.value).toSet().takeIf { it.isNotEmpty() }
|
|
|
|
|
?.onEach { FileHelper.delete(context, it.uri.toUri()) }
|
|
|
|
|
timerPlugin.stopTimer(_state.value.task)
|
|
|
|
|
originalState.attachments.plus(state.value.attachments).toSet().takeIf { it.isNotEmpty() }
|
|
|
|
|
?.onEach { FileHelper.delete(applicationContext, it.uri.toUri()) }
|
|
|
|
|
?.let { taskAttachmentDao.delete(it.toList()) }
|
|
|
|
|
}
|
|
|
|
|
clear(remove)
|
|
|
|
@ -455,14 +406,87 @@ class TaskEditViewModel @Inject constructor(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun removeAlarm(alarm: Alarm) {
|
|
|
|
|
selectedAlarms.update { it.minus(alarm) }
|
|
|
|
|
}
|
|
|
|
|
fun setTitle(title: String) =
|
|
|
|
|
_state.update { it.copy(task = it.task.copy(title = title)) }
|
|
|
|
|
|
|
|
|
|
fun setCompleted(completed: Boolean) =
|
|
|
|
|
_state.update { it.copy(task = it.task.copy(completionDate = if (completed) now() else 0)) }
|
|
|
|
|
|
|
|
|
|
fun setPriority(@Task.Priority priority: Int) =
|
|
|
|
|
_state.update { it.copy(task = it.task.copy(priority = priority)) }
|
|
|
|
|
|
|
|
|
|
fun setDescription(description: String?) =
|
|
|
|
|
_state.update { it.copy(task = it.task.copy(notes = description)) }
|
|
|
|
|
|
|
|
|
|
fun setRecurrence(recurrence: String?) =
|
|
|
|
|
_state.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
task = it.task.copy(
|
|
|
|
|
recurrence = recurrence,
|
|
|
|
|
dueDate = if (it.task.dueDate == 0L && recurrence?.isNotBlank() == true) {
|
|
|
|
|
DateTime().startOfDay().millis
|
|
|
|
|
} else {
|
|
|
|
|
it.task.dueDate
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun setRepeatAfterCompletion(repeatAfterCompletion: Boolean) =
|
|
|
|
|
_state.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
task = it.task.copy(
|
|
|
|
|
repeatFrom = if (repeatAfterCompletion) COMPLETION_DATE else DUE_DATE
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun clearCalendarUri() =
|
|
|
|
|
_state.update { it.copy(task = it.task.copy(calendarURI = null)) }
|
|
|
|
|
|
|
|
|
|
fun setCalendar(calendar: String?) =
|
|
|
|
|
_state.update { it.copy(calendar = calendar) }
|
|
|
|
|
|
|
|
|
|
fun setDueDate(value: Long) =
|
|
|
|
|
_state.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
task = it.task.copy(
|
|
|
|
|
dueDate = when {
|
|
|
|
|
value == 0L -> 0
|
|
|
|
|
hasDueTime(value) -> createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, value)
|
|
|
|
|
else -> createDueDate(Task.URGENCY_SPECIFIC_DAY, value)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun setStartDate(value: Long) =
|
|
|
|
|
_state.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
task = it.task.copy(
|
|
|
|
|
hideUntil = when {
|
|
|
|
|
value == 0L -> 0
|
|
|
|
|
hasDueTime(value) ->
|
|
|
|
|
value.toDateTime().withSecondOfMinute(1).withMillisOfSecond(0).millis
|
|
|
|
|
else -> value.startOfDay()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun removeAlarm(alarm: Alarm) =
|
|
|
|
|
_state.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
alarms = it.alarms.remove(alarm)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun addAlarm(alarm: Alarm) {
|
|
|
|
|
with (selectedAlarms) {
|
|
|
|
|
if (value.none { it.same(alarm) }) {
|
|
|
|
|
value = value.plus(alarm)
|
|
|
|
|
if (state.value.alarms.none { it.same(alarm) }) {
|
|
|
|
|
_state.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
alarms = it.alarms.add(alarm)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -470,11 +494,11 @@ class TaskEditViewModel @Inject constructor(
|
|
|
|
|
fun addComment(message: String?, picture: Uri?) {
|
|
|
|
|
val userActivity = UserActivity()
|
|
|
|
|
if (picture != null) {
|
|
|
|
|
val output = FileHelper.copyToUri(context, preferences.attachmentsDirectory!!, picture)
|
|
|
|
|
val output = FileHelper.copyToUri(applicationContext, preferences.attachmentsDirectory!!, picture)
|
|
|
|
|
userActivity.setPicture(output)
|
|
|
|
|
}
|
|
|
|
|
userActivity.message = message
|
|
|
|
|
userActivity.targetId = task.uuid
|
|
|
|
|
userActivity.targetId = originalState.task.uuid
|
|
|
|
|
userActivity.created = now()
|
|
|
|
|
viewModelScope.launch {
|
|
|
|
|
withContext(NonCancellable) {
|
|
|
|
@ -483,13 +507,77 @@ class TaskEditViewModel @Inject constructor(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
|
viewModelScope.launch {
|
|
|
|
|
taskAttachmentDao.getAttachments(task.id).let { attachments ->
|
|
|
|
|
selectedAttachments.update { attachments }
|
|
|
|
|
originalAttachments = attachments
|
|
|
|
|
}
|
|
|
|
|
fun setFilter(filter: Filter) =
|
|
|
|
|
_state.update { it.copy(filter = filter) }
|
|
|
|
|
|
|
|
|
|
fun setLocation(location: Location?) =
|
|
|
|
|
_state.update { it.copy(location = location) }
|
|
|
|
|
|
|
|
|
|
fun setTags(tags: List<TagData>) =
|
|
|
|
|
_state.update { it.copy(tags = tags.toPersistentSet()) }
|
|
|
|
|
|
|
|
|
|
fun setTimer(estimated: Int, elapsed: Int) =
|
|
|
|
|
_state.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
task = it.task.copy(
|
|
|
|
|
estimatedSeconds = estimated,
|
|
|
|
|
elapsedSeconds = elapsed,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun setTimerStarted(timerStart: Long) =
|
|
|
|
|
_state.update { it.copy(task = it.task.copy(timerStart = timerStart) ) }
|
|
|
|
|
|
|
|
|
|
fun setElapsed(elapsedSeconds: Int) =
|
|
|
|
|
_state.update { it.copy(task = it.task.copy(elapsedSeconds = elapsedSeconds)) }
|
|
|
|
|
|
|
|
|
|
fun removeNewSubtask(task: Task) =
|
|
|
|
|
_state.update { it.copy(newSubtasks = it.newSubtasks.remove(task)) }
|
|
|
|
|
|
|
|
|
|
fun addSubtask() = viewModelScope.launch {
|
|
|
|
|
_state.update { it.copy(newSubtasks = it.newSubtasks.add(taskCreator.createWithValues(""))) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun toggleNewSubtaskCompleted(task: Task) =
|
|
|
|
|
_state.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
newSubtasks = it.newSubtasks.set(
|
|
|
|
|
it.newSubtasks.indexOf(task),
|
|
|
|
|
task.copy(completionDate = if (task.isCompleted) 0 else now())
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun deleteAttachment(attachment: TaskAttachment) =
|
|
|
|
|
_state.update { it.copy(attachments = it.attachments.remove(attachment)) }
|
|
|
|
|
|
|
|
|
|
fun addAttachment(attachment: TaskAttachment) =
|
|
|
|
|
_state.update { it.copy(attachments = it.attachments.add(attachment)) }
|
|
|
|
|
|
|
|
|
|
fun removeTag(tag: TagData) =
|
|
|
|
|
_state.update { it.copy(tags = it.tags.remove(tag)) }
|
|
|
|
|
|
|
|
|
|
fun setRingMode(ringMode: Int) {
|
|
|
|
|
_state.update {
|
|
|
|
|
it.copy(
|
|
|
|
|
task = it.task.copy(
|
|
|
|
|
ringFlags = when (ringMode) {
|
|
|
|
|
1 -> NOTIFY_MODE_FIVE
|
|
|
|
|
2 -> NOTIFY_MODE_NONSTOP
|
|
|
|
|
else -> 0
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun setCompleted(task: Task, completed: Boolean) = viewModelScope.launch {
|
|
|
|
|
taskCompleter.setComplete(task, completed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun toggleSubtask(taskId: Long, collapsed: Boolean) = viewModelScope.launch {
|
|
|
|
|
taskDao.setCollapsed(taskId, collapsed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|