mirror of https://github.com/tasks/tasks
Add TaskEditViewModel
parent
3cb802a801
commit
a73b716419
@ -1,30 +0,0 @@
|
||||
package org.tasks.ui
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DescriptionControlSetTest {
|
||||
@Test
|
||||
fun replaceCRLF() {
|
||||
assertEquals("aaa\nbbb", DescriptionControlSet.stripCarriageReturns("aaa\r\nbbb"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceCR() {
|
||||
assertEquals("aaa\nbbb", DescriptionControlSet.stripCarriageReturns("aaa\rbbb"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dontReplaceLF() {
|
||||
assertEquals("aaa\nbbb", DescriptionControlSet.stripCarriageReturns("aaa\nbbb"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkIfNull() {
|
||||
assertNull(DescriptionControlSet.stripCarriageReturns(null))
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package org.tasks.ui.editviewmodel
|
||||
|
||||
import android.content.Context
|
||||
import com.todoroo.astrid.alarms.AlarmService
|
||||
import com.todoroo.astrid.dao.Database
|
||||
import com.todoroo.astrid.dao.TaskDao
|
||||
import com.todoroo.astrid.gcal.GCalHelper
|
||||
import com.todoroo.astrid.service.TaskCompleter
|
||||
import com.todoroo.astrid.service.TaskDeleter
|
||||
import com.todoroo.astrid.service.TaskMover
|
||||
import com.todoroo.astrid.timers.TimerPlugin
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.junit.Before
|
||||
import org.tasks.calendars.CalendarEventProvider
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
import org.tasks.location.GeofenceApi
|
||||
import org.tasks.preferences.DefaultFilterProvider
|
||||
import org.tasks.preferences.PermissivePermissionChecker
|
||||
import org.tasks.preferences.Preferences
|
||||
import org.tasks.ui.TaskEditViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
open class BaseTaskEditViewModelTest : InjectingTestCase() {
|
||||
@ApplicationContext @Inject lateinit var context: Context
|
||||
@Inject lateinit var db: Database
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@Inject lateinit var taskDeleter: TaskDeleter
|
||||
@Inject lateinit var timerPlugin: TimerPlugin
|
||||
@Inject lateinit var calendarEventProvider: CalendarEventProvider
|
||||
@Inject lateinit var gCalHelper: GCalHelper
|
||||
@Inject lateinit var taskMover: TaskMover
|
||||
@Inject lateinit var geofenceApi: GeofenceApi
|
||||
@Inject lateinit var preferences: Preferences
|
||||
@Inject lateinit var defaultFilterProvider: DefaultFilterProvider
|
||||
@Inject lateinit var taskCompleter: TaskCompleter
|
||||
@Inject lateinit var alarmService: AlarmService
|
||||
|
||||
protected lateinit var viewModel: TaskEditViewModel
|
||||
|
||||
@Before
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
viewModel = TaskEditViewModel(
|
||||
context,
|
||||
taskDao,
|
||||
taskDeleter,
|
||||
timerPlugin,
|
||||
PermissivePermissionChecker(context),
|
||||
calendarEventProvider,
|
||||
gCalHelper,
|
||||
taskMover,
|
||||
db.locationDao,
|
||||
geofenceApi,
|
||||
db.tagDao,
|
||||
db.tagDataDao,
|
||||
preferences,
|
||||
defaultFilterProvider,
|
||||
db.googleTaskDao,
|
||||
db.caldavDao,
|
||||
taskCompleter,
|
||||
db.alarmDao,
|
||||
alarmService)
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package org.tasks.ui.editviewmodel
|
||||
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import com.natpryce.makeiteasy.MakeItEasy
|
||||
import com.todoroo.astrid.data.Task
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import dagger.hilt.android.testing.UninstallModules
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import org.tasks.injection.ProductionModule
|
||||
import org.tasks.makers.TaskMaker
|
||||
|
||||
@UninstallModules(ProductionModule::class)
|
||||
@HiltAndroidTest
|
||||
class PriorityTests : BaseTaskEditViewModelTest() {
|
||||
@Test
|
||||
fun changePriorityCausesChange() {
|
||||
viewModel.setup(TaskMaker.newTask(MakeItEasy.with(TaskMaker.PRIORITY, Task.Priority.HIGH)))
|
||||
|
||||
viewModel.priority = Task.Priority.MEDIUM
|
||||
|
||||
Assert.assertTrue(viewModel.hasChanges())
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun applyPriorityChange() = runBlocking {
|
||||
val task = TaskMaker.newTask(MakeItEasy.with(TaskMaker.PRIORITY, Task.Priority.HIGH))
|
||||
viewModel.setup(task)
|
||||
viewModel.priority = Task.Priority.MEDIUM
|
||||
|
||||
viewModel.save()
|
||||
|
||||
Assert.assertEquals(Task.Priority.MEDIUM, task.priority)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noChangeWhenRevertingPriority() {
|
||||
viewModel.setup(TaskMaker.newTask(MakeItEasy.with(TaskMaker.PRIORITY, Task.Priority.HIGH)))
|
||||
|
||||
viewModel.priority = Task.Priority.MEDIUM
|
||||
viewModel.priority = Task.Priority.HIGH
|
||||
|
||||
Assert.assertFalse(viewModel.hasChanges())
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package org.tasks.ui.editviewmodel
|
||||
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import dagger.hilt.android.testing.UninstallModules
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.tasks.injection.ProductionModule
|
||||
import org.tasks.makers.TaskMaker.newTask
|
||||
|
||||
@UninstallModules(ProductionModule::class)
|
||||
@HiltAndroidTest
|
||||
class ReminderTests : BaseTaskEditViewModelTest() {
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun whenDueReminder() = runBlocking {
|
||||
val task = newTask()
|
||||
viewModel.setup(task)
|
||||
|
||||
viewModel.whenDue = true
|
||||
viewModel.save()
|
||||
|
||||
assertTrue(taskDao.fetch(task.id)!!.isNotifyAtDeadline)
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun whenOverDueReminder() = runBlocking {
|
||||
val task = newTask()
|
||||
viewModel.setup(task)
|
||||
|
||||
viewModel.whenOverdue = true
|
||||
viewModel.save()
|
||||
|
||||
assertTrue(taskDao.fetch(task.id)!!.isNotifyAfterDeadline)
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun ringFiveTimes() = runBlocking {
|
||||
val task = newTask()
|
||||
viewModel.setup(task)
|
||||
|
||||
viewModel.ringFiveTimes = true
|
||||
viewModel.save()
|
||||
|
||||
assertTrue(taskDao.fetch(task.id)!!.isNotifyModeFive)
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun ringNonstop() = runBlocking {
|
||||
val task = newTask()
|
||||
viewModel.setup(task)
|
||||
|
||||
viewModel.ringNonstop = true
|
||||
viewModel.save()
|
||||
|
||||
assertTrue(taskDao.fetch(task.id)!!.isNotifyModeNonstop)
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun ringFiveTimesCantRingNonstop() = runBlocking {
|
||||
val task = newTask()
|
||||
viewModel.setup(task)
|
||||
|
||||
viewModel.ringNonstop = true
|
||||
viewModel.ringFiveTimes = true
|
||||
viewModel.save()
|
||||
|
||||
assertFalse(taskDao.fetch(task.id)!!.isNotifyModeNonstop)
|
||||
assertTrue(taskDao.fetch(task.id)!!.isNotifyModeFive)
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun ringNonStopCantRingFiveTimes() = runBlocking {
|
||||
val task = newTask()
|
||||
viewModel.setup(task)
|
||||
|
||||
viewModel.ringFiveTimes = true
|
||||
viewModel.ringNonstop = true
|
||||
viewModel.save()
|
||||
|
||||
assertFalse(taskDao.fetch(task.id)!!.isNotifyModeFive)
|
||||
assertTrue(taskDao.fetch(task.id)!!.isNotifyModeNonstop)
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package org.tasks.ui.editviewmodel
|
||||
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import com.google.ical.values.RRule
|
||||
import com.natpryce.makeiteasy.MakeItEasy.with
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import dagger.hilt.android.testing.UninstallModules
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.tasks.injection.ProductionModule
|
||||
import org.tasks.makers.TaskMaker
|
||||
import org.tasks.makers.TaskMaker.newTask
|
||||
|
||||
@UninstallModules(ProductionModule::class)
|
||||
@HiltAndroidTest
|
||||
class RepeatTests : BaseTaskEditViewModelTest() {
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun changeRepeatAfterCompletion() = runBlocking {
|
||||
val task = newTask(with(TaskMaker.RRULE, RRule("RRULE:FREQ=DAILY;INTERVAL=1")))
|
||||
viewModel.setup(task)
|
||||
|
||||
viewModel.repeatAfterCompletion = true
|
||||
|
||||
viewModel.save()
|
||||
|
||||
assertEquals(
|
||||
"RRULE:FREQ=DAILY;INTERVAL=1;FROM=COMPLETION",
|
||||
taskDao.fetch(task.id)!!.recurrence)
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun removeRepeatAfterCompletion() = runBlocking {
|
||||
val task = newTask()
|
||||
task.recurrence = "RRULE:FREQ=DAILY;INTERVAL=1;FROM=COMPLETION"
|
||||
viewModel.setup(task)
|
||||
|
||||
viewModel.repeatAfterCompletion = false
|
||||
|
||||
viewModel.save()
|
||||
|
||||
assertEquals(
|
||||
"RRULE:FREQ=DAILY;INTERVAL=1",
|
||||
taskDao.fetch(task.id)!!.recurrence)
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package org.tasks.ui.editviewmodel
|
||||
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import com.todoroo.astrid.data.Task
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import dagger.hilt.android.testing.UninstallModules
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.tasks.injection.ProductionModule
|
||||
import org.tasks.makers.TaskMaker.newTask
|
||||
|
||||
@UninstallModules(ProductionModule::class)
|
||||
@HiltAndroidTest
|
||||
class TaskEditViewModelTest : BaseTaskEditViewModelTest() {
|
||||
@Test
|
||||
fun noChangesForNewTask() {
|
||||
viewModel.setup(newTask())
|
||||
|
||||
assertFalse(viewModel.hasChanges())
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun dontSaveTaskWithoutChanges() = runBlocking {
|
||||
viewModel.setup(newTask())
|
||||
|
||||
assertFalse(viewModel.save())
|
||||
|
||||
assertTrue(taskDao.getAll().isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun dontSaveTaskTwice() = runBlocking {
|
||||
viewModel.setup(newTask())
|
||||
|
||||
viewModel.priority = Task.Priority.HIGH
|
||||
|
||||
assertTrue(viewModel.save())
|
||||
|
||||
assertFalse(viewModel.save())
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package org.tasks.ui.editviewmodel
|
||||
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import com.natpryce.makeiteasy.MakeItEasy.with
|
||||
import com.todoroo.astrid.data.Task.Priority.Companion.HIGH
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import dagger.hilt.android.testing.UninstallModules
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.tasks.injection.ProductionModule
|
||||
import org.tasks.makers.TaskMaker
|
||||
import org.tasks.makers.TaskMaker.newTask
|
||||
|
||||
@UninstallModules(ProductionModule::class)
|
||||
@HiltAndroidTest
|
||||
class TitleTests : BaseTaskEditViewModelTest() {
|
||||
@Test
|
||||
fun changeTitleCausesChange() {
|
||||
viewModel.setup(newTask())
|
||||
|
||||
viewModel.title = "Test"
|
||||
|
||||
assertTrue(viewModel.hasChanges())
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun saveWithEmptyTitle() = runBlocking {
|
||||
val task = newTask()
|
||||
viewModel.setup(task)
|
||||
|
||||
viewModel.priority = HIGH
|
||||
|
||||
viewModel.save()
|
||||
|
||||
assertEquals("(No title)", taskDao.fetch(task.id)!!.title)
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun newTaskPrepopulatedWithTitleHasChanges() {
|
||||
viewModel.setup(newTask(with(TaskMaker.TITLE, "some title")))
|
||||
|
||||
assertTrue(viewModel.hasChanges())
|
||||
}
|
||||
}
|
@ -0,0 +1,464 @@
|
||||
package org.tasks.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.hilt.lifecycle.ViewModelInject
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.google.ical.values.RRule
|
||||
import com.todoroo.andlib.utility.DateUtilities.now
|
||||
import com.todoroo.astrid.alarms.AlarmService
|
||||
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.data.SyncFlags
|
||||
import com.todoroo.astrid.data.Task
|
||||
import com.todoroo.astrid.data.Task.Companion.createDueDate
|
||||
import com.todoroo.astrid.data.Task.Companion.hasDueTime
|
||||
import com.todoroo.astrid.data.Task.Companion.isRepeatAfterCompletion
|
||||
import com.todoroo.astrid.data.Task.Companion.withoutFrom
|
||||
import com.todoroo.astrid.gcal.GCalHelper
|
||||
import com.todoroo.astrid.service.TaskCompleter
|
||||
import com.todoroo.astrid.service.TaskDeleter
|
||||
import com.todoroo.astrid.service.TaskMover
|
||||
import com.todoroo.astrid.timers.TimerPlugin
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.tasks.Event
|
||||
import org.tasks.R
|
||||
import org.tasks.Strings
|
||||
import org.tasks.calendars.CalendarEventProvider
|
||||
import org.tasks.data.*
|
||||
import org.tasks.location.GeofenceApi
|
||||
import org.tasks.preferences.DefaultFilterProvider
|
||||
import org.tasks.preferences.PermissionChecker
|
||||
import org.tasks.preferences.Preferences
|
||||
import org.tasks.time.DateTime
|
||||
import org.tasks.time.DateTimeUtils.currentTimeMillis
|
||||
import timber.log.Timber
|
||||
import java.text.ParseException
|
||||
|
||||
class TaskEditViewModel @ViewModelInject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
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 defaultFilterProvider: DefaultFilterProvider,
|
||||
private val googleTaskDao: GoogleTaskDao,
|
||||
private val caldavDao: CaldavDao,
|
||||
private val taskCompleter: TaskCompleter,
|
||||
private val alarmDao: AlarmDao,
|
||||
private val alarmService: AlarmService) : ViewModel() {
|
||||
|
||||
val cleared = MutableLiveData<Event<Boolean>>()
|
||||
|
||||
fun setup(task: Task) {
|
||||
this.task = task
|
||||
isNew = task.isNew
|
||||
runBlocking {
|
||||
val list = async { defaultFilterProvider.getList(task) }
|
||||
val location = async { locationDao.getLocation(task, preferences) }
|
||||
val tags = async { tagDataDao.getTags(task) }
|
||||
val alarms = async { alarmDao.getAlarms(task) }
|
||||
originalList = list.await()
|
||||
originalLocation = location.await()
|
||||
originalTags = tags.await().toImmutableList()
|
||||
originalAlarms = alarms.await().map { it.time }.toImmutableSet()
|
||||
if (isNew && permissionChecker.canAccessCalendars()) {
|
||||
originalCalendar = preferences.defaultCalendar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var task: Task? = null
|
||||
private set
|
||||
|
||||
var title: String? = null
|
||||
get() = field ?: task?.title
|
||||
|
||||
var completed: Boolean? = null
|
||||
get() = field ?: task?.isCompleted ?: false
|
||||
|
||||
var dueDate: Long? = null
|
||||
get() = field ?: task?.dueDate ?: 0
|
||||
set(value) {
|
||||
field = when {
|
||||
value == null -> null
|
||||
value == 0L -> 0
|
||||
hasDueTime(value) -> createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, value)
|
||||
else -> createDueDate(Task.URGENCY_SPECIFIC_DAY, value)
|
||||
}
|
||||
}
|
||||
|
||||
var priority: Int? = null
|
||||
get() = field ?: task?.priority ?: 0
|
||||
|
||||
var description: String? = null
|
||||
get() = field ?: task?.notes.stripCarriageReturns()
|
||||
|
||||
var hideUntil: Long? = null
|
||||
get() = field ?: task?.hideUntil ?: 0
|
||||
|
||||
var recurrence: String? = null
|
||||
get() = field ?: task?.recurrence
|
||||
|
||||
var repeatUntil: Long? = null
|
||||
get() = field ?: task?.repeatUntil ?: 0
|
||||
|
||||
var repeatAfterCompletion: Boolean? = null
|
||||
get() = field ?: task?.repeatAfterCompletion() ?: false
|
||||
set(value) {
|
||||
field = value
|
||||
if (value == true) {
|
||||
if (!recurrence.isRepeatAfterCompletion()) {
|
||||
recurrence += ";FROM=COMPLETION"
|
||||
}
|
||||
} else if (recurrence.isRepeatAfterCompletion()) {
|
||||
recurrence = recurrence.withoutFrom()
|
||||
}
|
||||
}
|
||||
|
||||
var rrule: RRule?
|
||||
get() = if (recurrence.isNullOrBlank()) {
|
||||
null
|
||||
} else {
|
||||
val rrule = RRule(recurrence.withoutFrom())
|
||||
rrule.until = DateTime(repeatUntil!!).toDateValue()
|
||||
rrule
|
||||
}
|
||||
set(value) {
|
||||
if (value == null) {
|
||||
recurrence = ""
|
||||
repeatUntil = 0
|
||||
return
|
||||
}
|
||||
val copy: RRule = try {
|
||||
RRule(value.toIcal())
|
||||
} catch (e: ParseException) {
|
||||
recurrence = ""
|
||||
repeatUntil = 0
|
||||
return
|
||||
}
|
||||
repeatUntil = DateTime.from(copy.until).millis
|
||||
copy.until = null
|
||||
var result = copy.toIcal()
|
||||
if (repeatAfterCompletion!! && !result.isNullOrBlank()) {
|
||||
result += ";FROM=COMPLETION"
|
||||
}
|
||||
recurrence = result
|
||||
}
|
||||
|
||||
var originalCalendar: String? = null
|
||||
private set(value) {
|
||||
field = value
|
||||
selectedCalendar = value
|
||||
}
|
||||
|
||||
var selectedCalendar: String? = null
|
||||
|
||||
var eventUri: String?
|
||||
get() = task?.calendarURI
|
||||
set(value) {
|
||||
task?.calendarURI = value
|
||||
}
|
||||
|
||||
var isNew: Boolean = false
|
||||
private set
|
||||
|
||||
var timerStarted: Long
|
||||
get() = task?.timerStart ?: 0
|
||||
set(value) {
|
||||
task?.timerStart = value
|
||||
}
|
||||
|
||||
var estimatedSeconds: Int? = null
|
||||
get() = field ?: task?.estimatedSeconds ?: 0
|
||||
|
||||
var elapsedSeconds: Int? = null
|
||||
get() = field ?: task?.elapsedSeconds ?: 0
|
||||
|
||||
var originalList: Filter? = null
|
||||
private set(value) {
|
||||
field = value
|
||||
selectedList = value
|
||||
}
|
||||
|
||||
var selectedList: Filter? = null
|
||||
|
||||
var originalLocation: Location? = null
|
||||
private set(value) {
|
||||
field = value
|
||||
selectedLocation = value
|
||||
}
|
||||
|
||||
var selectedLocation: Location? = null
|
||||
|
||||
var originalTags: ImmutableList<TagData>? = null
|
||||
private set(value) {
|
||||
field = value
|
||||
selectedTags = value?.let { ArrayList(it) }
|
||||
}
|
||||
|
||||
var selectedTags: ArrayList<TagData>? = null
|
||||
|
||||
var newSubtasks = ArrayList<Task>()
|
||||
|
||||
var reminderPeriod: Long? = null
|
||||
get() = field ?: task?.reminderPeriod ?: 0
|
||||
|
||||
var originalAlarms: ImmutableSet<Long>? = null
|
||||
private set(value) {
|
||||
field = value
|
||||
selectedAlarms = value?.let { HashSet(it) }
|
||||
}
|
||||
|
||||
var selectedAlarms: HashSet<Long>? = null
|
||||
|
||||
var whenDue: Boolean? = null
|
||||
get() = field ?: (task?.reminderFlags?.and(Task.NOTIFY_AT_DEADLINE) ?: 0 > 0)
|
||||
|
||||
var whenOverdue: Boolean? = null
|
||||
get() = field ?: (task?.reminderFlags?.and(Task.NOTIFY_AFTER_DEADLINE) ?: 0 > 0)
|
||||
|
||||
var ringNonstop: Boolean? = null
|
||||
get() = field ?: (task?.reminderFlags?.and(Task.NOTIFY_MODE_NONSTOP) ?: 0 > 0)
|
||||
set(value) {
|
||||
field = value
|
||||
if (value == true) {
|
||||
ringFiveTimes = false
|
||||
}
|
||||
}
|
||||
|
||||
var ringFiveTimes:Boolean? = null
|
||||
get() = field ?: (task?.reminderFlags?.and(Task.NOTIFY_MODE_FIVE) ?: 0 > 0)
|
||||
set(value) {
|
||||
field = value
|
||||
if (value == true) {
|
||||
ringNonstop = false
|
||||
}
|
||||
}
|
||||
|
||||
fun hasChanges(): Boolean = task?.let {
|
||||
(it.title != title || (isNew && title?.isNotBlank() == true)) ||
|
||||
it.isCompleted != completed ||
|
||||
it.dueDate != dueDate ||
|
||||
it.priority != priority ||
|
||||
it.notes != description ||
|
||||
it.hideUntil != hideUntil ||
|
||||
if (it.recurrence.isNullOrBlank()) {
|
||||
!recurrence.isNullOrBlank()
|
||||
} else {
|
||||
it.recurrence != recurrence
|
||||
} ||
|
||||
it.repeatAfterCompletion() != repeatAfterCompletion ||
|
||||
it.repeatUntil != repeatUntil ||
|
||||
originalCalendar != selectedCalendar ||
|
||||
it.calendarURI != eventUri ||
|
||||
it.elapsedSeconds != elapsedSeconds ||
|
||||
it.estimatedSeconds != estimatedSeconds ||
|
||||
originalList != selectedList ||
|
||||
originalLocation != selectedLocation ||
|
||||
originalTags?.toHashSet() != selectedTags?.toHashSet() ||
|
||||
newSubtasks.isNotEmpty() ||
|
||||
it.reminderPeriod != reminderPeriod ||
|
||||
it.reminderFlags != getReminderFlags() ||
|
||||
originalAlarms != selectedAlarms
|
||||
} ?: false
|
||||
|
||||
fun cleared() = cleared.value?.value == true
|
||||
|
||||
@MainThread
|
||||
suspend fun save(): Boolean = task?.let {
|
||||
if (cleared()) {
|
||||
return false
|
||||
}
|
||||
if (!hasChanges()) {
|
||||
discard()
|
||||
return false
|
||||
}
|
||||
clear()
|
||||
it.title = if (title.isNullOrBlank()) context.getString(R.string.no_title) else title
|
||||
it.dueDate = dueDate!!
|
||||
it.priority = priority!!
|
||||
it.notes = description
|
||||
it.hideUntil = hideUntil!!
|
||||
it.recurrence = recurrence
|
||||
it.repeatUntil = repeatUntil!!
|
||||
it.elapsedSeconds = elapsedSeconds!!
|
||||
it.estimatedSeconds = estimatedSeconds!!
|
||||
it.reminderFlags = getReminderFlags()
|
||||
it.reminderPeriod = reminderPeriod!!
|
||||
|
||||
applyCalendarChanges()
|
||||
|
||||
val isNew = it.isNew
|
||||
|
||||
if (isNew) {
|
||||
taskDao.createNew(it)
|
||||
}
|
||||
|
||||
if (isNew || originalList != selectedList) {
|
||||
it.parent = 0
|
||||
taskMover.move(listOf(it.id), selectedList!!)
|
||||
}
|
||||
|
||||
if ((isNew && selectedLocation != null) || originalLocation != selectedLocation) {
|
||||
originalLocation?.let { location ->
|
||||
if (location.geofence.id > 0) {
|
||||
locationDao.delete(location.geofence)
|
||||
geofenceApi.update(location.place)
|
||||
}
|
||||
}
|
||||
selectedLocation?.let { location ->
|
||||
val place = location.place
|
||||
val geofence = location.geofence
|
||||
geofence.task = it.id
|
||||
geofence.place = place.uid
|
||||
geofence.id = locationDao.insert(geofence)
|
||||
geofenceApi.update(place)
|
||||
}
|
||||
it.putTransitory(SyncFlags.FORCE_CALDAV_SYNC, true)
|
||||
it.modificationDate = currentTimeMillis()
|
||||
}
|
||||
|
||||
if (originalTags?.toHashSet() != selectedTags?.toHashSet()) {
|
||||
tagDao.applyTags(it, tagDataDao, selectedTags!!)
|
||||
it.modificationDate = currentTimeMillis()
|
||||
}
|
||||
|
||||
for (subtask in newSubtasks) {
|
||||
if (Strings.isNullOrEmpty(subtask.title)) {
|
||||
continue
|
||||
}
|
||||
if (!subtask.isCompleted) {
|
||||
subtask.completionDate = it.completionDate
|
||||
}
|
||||
taskDao.createNew(subtask)
|
||||
when (selectedList) {
|
||||
is GtasksFilter -> {
|
||||
val googleTask = GoogleTask(subtask.id, (selectedList as GtasksFilter).remoteId)
|
||||
googleTask.parent = it.id
|
||||
googleTask.isMoved = true
|
||||
googleTaskDao.insertAndShift(googleTask, false)
|
||||
}
|
||||
is CaldavFilter -> {
|
||||
val caldavTask = CaldavTask(subtask.id, (selectedList as CaldavFilter).uuid)
|
||||
subtask.parent = it.id
|
||||
caldavTask.remoteParent = caldavDao.getRemoteIdForTask(it.id)
|
||||
taskDao.save(subtask)
|
||||
caldavDao.insert(subtask, caldavTask, false)
|
||||
}
|
||||
else -> {
|
||||
subtask.parent = it.id
|
||||
taskDao.save(subtask)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedAlarms != originalAlarms) {
|
||||
alarmService.synchronizeAlarms(it.id, selectedAlarms!!)
|
||||
it.modificationDate = now()
|
||||
}
|
||||
|
||||
taskDao.save(it, null)
|
||||
|
||||
if (it.isCompleted != completed!!) {
|
||||
taskCompleter.setComplete(it, completed!!)
|
||||
}
|
||||
|
||||
true
|
||||
} ?: false
|
||||
|
||||
private fun applyCalendarChanges() {
|
||||
if (!permissionChecker.canAccessCalendars()) {
|
||||
return
|
||||
}
|
||||
if (eventUri == null || task?.hasDueDate() != true) {
|
||||
if (!task?.calendarURI.isNullOrBlank()) {
|
||||
calendarEventProvider.deleteEvent(task)
|
||||
}
|
||||
}
|
||||
eventUri?.let {
|
||||
if (!it.isBlank()) {
|
||||
gCalHelper.updateEvent(it, task!!)
|
||||
}
|
||||
}
|
||||
selectedCalendar?.let {
|
||||
try {
|
||||
task?.calendarURI = gCalHelper.createTaskEvent(task, it)?.toString()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getReminderFlags(): Int {
|
||||
var value = 0
|
||||
if (whenDue == true) {
|
||||
value = value or Task.NOTIFY_AT_DEADLINE
|
||||
}
|
||||
if (whenOverdue == true) {
|
||||
value = value or Task.NOTIFY_AFTER_DEADLINE
|
||||
}
|
||||
value = value and (Task.NOTIFY_MODE_FIVE or Task.NOTIFY_MODE_NONSTOP).inv()
|
||||
if (ringNonstop == true) {
|
||||
value = value or Task.NOTIFY_MODE_NONSTOP
|
||||
} else if (ringFiveTimes == true) {
|
||||
value = value or Task.NOTIFY_MODE_FIVE
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
fun delete() {
|
||||
task?.let(taskDeleter::markDeleted)
|
||||
discard()
|
||||
}
|
||||
|
||||
fun discard() {
|
||||
task?.let {
|
||||
if (it.isNew) {
|
||||
timerPlugin.stopTimer(it)
|
||||
}
|
||||
}
|
||||
clear()
|
||||
}
|
||||
|
||||
@MainThread
|
||||
fun clear() {
|
||||
if (!cleared()) {
|
||||
cleared.value = Event(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
cleared.value.let {
|
||||
if (it == null || !it.value) {
|
||||
runBlocking(NonCancellable) {
|
||||
save()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun String?.stripCarriageReturns(): String? {
|
||||
return this?.replace("\\r\\n?".toRegex(), "\n")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package org.tasks.ui
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import org.tasks.ui.TaskEditViewModel.Companion.stripCarriageReturns
|
||||
|
||||
class TaskEditViewModelTest {
|
||||
@Test
|
||||
fun replaceCRLF() {
|
||||
assertEquals("aaa\nbbb", "aaa\r\nbbb".stripCarriageReturns())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceCR() {
|
||||
assertEquals("aaa\nbbb", "aaa\rbbb".stripCarriageReturns())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dontReplaceLF() {
|
||||
assertEquals("aaa\nbbb", "aaa\nbbb".stripCarriageReturns())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stripCarriageReturnOnNull() {
|
||||
assertNull((null as String?).stripCarriageReturns())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue