diff --git a/core/domain/src/main/java/easydone/core/domain/RemoteDataSource.kt b/core/domain/src/main/java/easydone/core/domain/RemoteDataSource.kt index 44cac4c..b79f2ea 100644 --- a/core/domain/src/main/java/easydone/core/domain/RemoteDataSource.kt +++ b/core/domain/src/main/java/easydone/core/domain/RemoteDataSource.kt @@ -7,7 +7,7 @@ interface RemoteDataSource { suspend fun isConnected(): Boolean suspend fun getAllTasks(): List suspend fun isTaskKnownOnRemote(id: String): Boolean - suspend fun updateTask(delta: TaskDelta) - suspend fun createTask(delta: TaskDelta) + suspend fun updateTask(delta: TaskDelta): Task + suspend fun createTask(delta: TaskDelta): Task suspend fun disconnect() } diff --git a/core/domain/src/main/java/easydone/core/domain/Synchronizer.kt b/core/domain/src/main/java/easydone/core/domain/Synchronizer.kt index 5a25eb2..d0b5ac3 100644 --- a/core/domain/src/main/java/easydone/core/domain/Synchronizer.kt +++ b/core/domain/src/main/java/easydone/core/domain/Synchronizer.kt @@ -10,6 +10,10 @@ class Synchronizer( ) { suspend fun sync() { + // get remote data + // sync change and apply it to the remote data + // also sync waiting if needed (this renders flushing new delta unneeded) + // flush remote data to local uploadChanges() refreshLocalData() } diff --git a/core/domain/src/test/java/easydone/core/domain/SynchronizerTest.kt b/core/domain/src/test/java/easydone/core/domain/SynchronizerTest.kt index 4627ead..f6cd97b 100644 --- a/core/domain/src/test/java/easydone/core/domain/SynchronizerTest.kt +++ b/core/domain/src/test/java/easydone/core/domain/SynchronizerTest.kt @@ -2,6 +2,7 @@ package easydone.core.domain import assertk.assertThat import assertk.assertions.containsOnly +import easydone.core.domain.model.Markers import easydone.core.domain.model.Task import easydone.core.domain.model.TaskDelta import easydone.core.domain.model.TaskTemplate @@ -14,7 +15,7 @@ import kotlin.reflect.KClass class SynchronizerTest { @Test - fun `Test saving task`() = runTest { + fun `Delta for unknown task should create a card on remote`() = runTest { val delta = taskDelta() val remoteDataSource = remoteDataSource(isTaskKnown = false) val localDataSource = localDataSource(listOf(delta)) @@ -26,7 +27,13 @@ class SynchronizerTest { } @Test - fun `Test updating task`() = runTest { + fun `Created card should be saved locally`() { + //ignore original server card with this id + + } + + @Test + fun `Delta for unknown task should update create a card on remote`() = runTest { val delta = taskDelta() val remoteDataSource = remoteDataSource(isTaskKnown = true) val localDataSource = localDataSource(listOf(delta)) @@ -37,6 +44,38 @@ class SynchronizerTest { assertThat(remoteDataSource.getUpdatedDeltas()).containsOnly(delta) } + @Test + fun `Updated card should be saved locally`() { + //ignore original server card with this id + + } + + @Test + fun `Task from remote source should be saved locally`() = runTest { + val task = task("remote_task") + val remoteDataSource = remoteDataSource(remoteTasks = listOf(task)) + val localDataSource = localDataSource() + val synchronizer = Synchronizer(remoteDataSource, localDataSource) + + synchronizer.sync() + + assertThat(localDataSource.refreshedTasks()).containsOnly(task) + } + + @Test + fun `Waiting task with reached date should be updated`() { + + } + + private fun task(id: String) = Task( + id = id, + type = Task.Type.Inbox, + title = "Task $id", + description = "", + markers = Markers(isUrgent = false, isImportant = false), + isDone = false + ) + private fun taskDelta() = TaskDelta( id = 1, taskId = "taskId", @@ -47,22 +86,28 @@ class SynchronizerTest { isDone = null ) - private fun remoteDataSource(isTaskKnown: Boolean) = object : RemoteDataSource { + private fun remoteDataSource( + isTaskKnown: Boolean = false, + remoteTasks: List = emptyList(), + returnedTask: Task = task("returned_task") + ) = object : RemoteDataSource { private val createdDeltas: MutableList = mutableListOf() private val updatedDeltas: MutableList = mutableListOf() override suspend fun isConnected(): Boolean = true - override suspend fun getAllTasks(): List = emptyList() + override suspend fun getAllTasks(): List = remoteTasks override suspend fun isTaskKnownOnRemote(id: String): Boolean = isTaskKnown - override suspend fun updateTask(delta: TaskDelta) { + override suspend fun updateTask(delta: TaskDelta): Task { updatedDeltas.add(delta) + return returnedTask } - override suspend fun createTask(delta: TaskDelta) { + override suspend fun createTask(delta: TaskDelta): Task { createdDeltas.add(delta) + return returnedTask } override suspend fun disconnect() {} @@ -74,6 +119,8 @@ class SynchronizerTest { private fun localDataSource(changes: List = emptyList()) = object : LocalDataSource { + private var refreshedTasks: List = emptyList() + override suspend fun getChanges(): List = changes override fun observeChangesCount(): Flow = emptyFlow() @@ -85,18 +132,22 @@ class SynchronizerTest { override fun observeTask(id: String): Flow = emptyFlow() override suspend fun getTask(id: String): Task { - TODO("Not yet implemented") + throw NotImplementedError() } override suspend fun createTask(taskTemplate: TaskTemplate) {} override suspend fun updateTask(task: Task) {} - override suspend fun refreshData(tasks: List, updatedTasks: List) {} + override suspend fun refreshData(tasks: List, updatedTasks: List) { + refreshedTasks = tasks + } override suspend fun deleteChange(id: Long) { } + fun refreshedTasks(): List = refreshedTasks + override suspend fun removeAllData() {} } } \ No newline at end of file diff --git a/core/sandbox/src/main/java/easydone/core/sandbox/SandboxRemoteDataSource.kt b/core/sandbox/src/main/java/easydone/core/sandbox/SandboxRemoteDataSource.kt index 2d18113..2e43051 100644 --- a/core/sandbox/src/main/java/easydone/core/sandbox/SandboxRemoteDataSource.kt +++ b/core/sandbox/src/main/java/easydone/core/sandbox/SandboxRemoteDataSource.kt @@ -13,9 +13,13 @@ class SandboxRemoteDataSource : RemoteDataSource { override suspend fun isTaskKnownOnRemote(id: String): Boolean = false - override suspend fun updateTask(delta: TaskDelta) {} + override suspend fun updateTask(delta: TaskDelta): Task { + throw NotImplementedError() + } - override suspend fun createTask(delta: TaskDelta) {} + override suspend fun createTask(delta: TaskDelta): Task { + throw NotImplementedError() + } override suspend fun disconnect() {} } diff --git a/core/service/trello/src/main/java/easydone/service/trello/TrelloRemoteDataSource.kt b/core/service/trello/src/main/java/easydone/service/trello/TrelloRemoteDataSource.kt index 1c5b31e..d243420 100644 --- a/core/service/trello/src/main/java/easydone/service/trello/TrelloRemoteDataSource.kt +++ b/core/service/trello/src/main/java/easydone/service/trello/TrelloRemoteDataSource.kt @@ -31,7 +31,7 @@ class TrelloRemoteDataSource( val token = authInfoHolder.getToken()!! val board = api.boardData(boardId, apiKey, token) rememberDataAnchors(board) - return@withContext board.cards + board.cards .filter { it.idList == authInfoHolder.getTodoListId() || it.idList == authInfoHolder.getInboxListId() || @@ -39,39 +39,17 @@ class TrelloRemoteDataSource( it.idList == authInfoHolder.getProjectsListId() || it.idList == authInfoHolder.getMaybeListId() } - .map { card -> - val localId = idMappings.getString(card.id, UUID.randomUUID().toString()) - if (!idMappings.contains(card.id)) { - idMappings.putString(localId, card.id) - idMappings.putString(card.id, localId) - } - - val type = when (card.idList) { - authInfoHolder.getInboxListId() -> Task.Type.Inbox - authInfoHolder.getWaitingListId() -> Task.Type.Waiting( - //todo: handle waiting items with no date - requireNotNull(card.due) - .let { LocalDate.parse(it, DateTimeFormatter.ISO_DATE_TIME) } - ) - - authInfoHolder.getProjectsListId() -> Task.Type.Project - authInfoHolder.getMaybeListId() -> Task.Type.Maybe - else -> Task.Type.ToDo - } - val isUrgent = card.idLabels.contains(authInfoHolder.getUrgentLabelId()!!) - val isImportant = card.idLabels.contains(authInfoHolder.getImportantLabelId()!!) - card.toTask(localId, type, isUrgent, isImportant) - } + .map { card -> card.toTask() } } override suspend fun isTaskKnownOnRemote(id: String): Boolean = idMappings.contains(id) - override suspend fun updateTask(delta: TaskDelta) { - withContext(Dispatchers.IO) { + override suspend fun updateTask(delta: TaskDelta): Task { + return withContext(Dispatchers.IO) { val serverId: String = requireNotNull( idMappings.getString(delta.taskId) ) { "Try to update task with $delta but server id was not found" } - api.editCard( + val card = api.editCard( serverId, apiKey, authInfoHolder.getToken()!!, @@ -82,11 +60,12 @@ class TrelloRemoteDataSource( listId = delta.type?.let { getListId(it) }, idLabels = delta.convertMarkersToLabels() ) + card.toTask() } } - override suspend fun createTask(delta: TaskDelta) { - withContext(Dispatchers.IO) { + override suspend fun createTask(delta: TaskDelta): Task { + return withContext(Dispatchers.IO) { val card = api.postCard( listId = getListId(delta.type!!), name = delta.title!!, @@ -98,7 +77,42 @@ class TrelloRemoteDataSource( ) idMappings.putString(delta.taskId, card.id) idMappings.putString(card.id, delta.taskId) + card.toTask() + } + } + + private suspend fun Card.toTask(): Task { + val localId = idMappings.getString(id, UUID.randomUUID().toString()) + if (!idMappings.contains(id)) { + idMappings.putString(localId, id) + idMappings.putString(id, localId) + } + + val type = when (idList) { + authInfoHolder.getInboxListId() -> Task.Type.Inbox + authInfoHolder.getWaitingListId() -> Task.Type.Waiting( + //todo: handle waiting items with no date + requireNotNull(due) + .let { LocalDate.parse(it, DateTimeFormatter.ISO_DATE_TIME) } + ) + + authInfoHolder.getProjectsListId() -> Task.Type.Project + authInfoHolder.getMaybeListId() -> Task.Type.Maybe + else -> Task.Type.ToDo } + val isUrgent = idLabels.contains(authInfoHolder.getUrgentLabelId()!!) + val isImportant = idLabels.contains(authInfoHolder.getImportantLabelId()!!) + return Task( + id = localId, + type = type, + title = name, + description = desc, + markers = Markers( + isUrgent = isUrgent, + isImportant = isImportant + ), + isDone = false + ) } private suspend fun TaskDelta.convertMarkersToLabels(): String? { @@ -163,23 +177,4 @@ class TrelloRemoteDataSource( } } - private fun Card.toTask( - id: String, - type: Task.Type, - isUrgent: Boolean, - isImportant: Boolean - ): Task { - return Task( - id = id, - type = type, - title = name, - description = desc, - markers = Markers( - isUrgent = isUrgent, - isImportant = isImportant - ), - isDone = false - ) - } - } diff --git a/core/service/trello/src/main/java/easydone/service/trello/api/TrelloApi.kt b/core/service/trello/src/main/java/easydone/service/trello/api/TrelloApi.kt index d24ab3c..de3930b 100644 --- a/core/service/trello/src/main/java/easydone/service/trello/api/TrelloApi.kt +++ b/core/service/trello/src/main/java/easydone/service/trello/api/TrelloApi.kt @@ -4,7 +4,6 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact import easydone.service.trello.api.model.Card import easydone.service.trello.api.model.NestedBoard import easydone.service.trello.api.model.NestedBoards -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType @@ -60,7 +59,6 @@ interface TrelloApi { ignoreUnknownKeys = true } - @OptIn(ExperimentalSerializationApi::class) fun build(debugInterceptor: Interceptor?): TrelloApi = Retrofit.Builder() .baseUrl("https://trello.com/1/") .client(