diff --git a/.idea/ChatHistory_schema_v2.xml b/.idea/ChatHistory_schema_v2.xml index 3253196..b8bdf60 100644 --- a/.idea/ChatHistory_schema_v2.xml +++ b/.idea/ChatHistory_schema_v2.xml @@ -15,6 +15,7 @@ + @@ -33,9 +34,11 @@ + + diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml index 51fb332..447cbf0 100644 --- a/.idea/androidTestResultsUserPreferences.xml +++ b/.idea/androidTestResultsUserPreferences.xml @@ -117,6 +117,7 @@ + diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index 4ff54ea..7ae67ff 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -15,6 +15,17 @@ + + diff --git a/.pr_agent.toml b/.pr_agent.toml index 2d6483f..f04dd14 100644 --- a/.pr_agent.toml +++ b/.pr_agent.toml @@ -1,6 +1,6 @@ [config] model="openrouter/qwen/qwen-2.5-coder-32b-instruct:free" -custom_model_max_tokens=9000 +custom_model_max_tokens=4000 [github_action_config] auto_review = true auto_describe = true diff --git a/core/domain/src/main/java/com/core/domain/model/Note.kt b/core/domain/src/main/java/com/core/domain/model/Note.kt index d7d4da5..f6f2855 100644 --- a/core/domain/src/main/java/com/core/domain/model/Note.kt +++ b/core/domain/src/main/java/com/core/domain/model/Note.kt @@ -8,4 +8,5 @@ data class Note( val done: Boolean, val date: String, val time: String, + val optionRevealed: Boolean = false, ) diff --git a/feature/tags/ui/src/androidTest/java/com/feature/tags/ui/screen/TagsViewModelTest.kt b/feature/tags/ui/src/androidTest/java/com/feature/tags/ui/screen/TagsViewModelTest.kt index 4cf5633..3ad208b 100644 --- a/feature/tags/ui/src/androidTest/java/com/feature/tags/ui/screen/TagsViewModelTest.kt +++ b/feature/tags/ui/src/androidTest/java/com/feature/tags/ui/screen/TagsViewModelTest.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test import com.core.domain.model.Note import com.core.domain.model.TagWithNotes +import com.core.domain.usecase.UpdateNoteDeletedUseCase import com.core.domain.usecase.UpdateNoteDoneUseCase import com.feature.tags.domain.usecase.GetTagWithNotesUseCase import com.feature.tags.domain.usecase.UpdateTagNameUseCase @@ -33,6 +34,7 @@ class TagsViewModelTest { private lateinit var getTagWithNotesUseCase: GetTagWithNotesUseCase private lateinit var updateTagNameUseCase: UpdateTagNameUseCase private lateinit var updateNoteDoneUseCase: UpdateNoteDoneUseCase + private lateinit var updateNoteDeletedUseCase: UpdateNoteDeletedUseCase private lateinit var tagsViewModel: TagsViewModel private val testDispatcher = StandardTestDispatcher() @@ -70,6 +72,7 @@ class TagsViewModelTest { getTagWithNotesUseCase = mockk() updateTagNameUseCase = mockk() updateNoteDoneUseCase = mockk() + updateNoteDeletedUseCase = mockk() } @Test @@ -84,8 +87,9 @@ class TagsViewModelTest { TagsViewModel( savedStateHandle = savedStateHandle, getTagWithNotesUseCase = getTagWithNotesUseCase, - updateTagNameUseCase = updateTagNameUseCase, - updateNoteDoneUseCase = updateNoteDoneUseCase, + updateTagNameUseCase = { updateTagNameUseCase }, + updateNoteDoneUseCase = { updateNoteDoneUseCase }, + updateNoteDeletedUseCase = { updateNoteDeletedUseCase }, ) // Assert @@ -109,8 +113,9 @@ class TagsViewModelTest { TagsViewModel( savedStateHandle = savedStateHandle, getTagWithNotesUseCase = getTagWithNotesUseCase, - updateTagNameUseCase = updateTagNameUseCase, - updateNoteDoneUseCase = updateNoteDoneUseCase, + updateTagNameUseCase = { updateTagNameUseCase }, + updateNoteDoneUseCase = { updateNoteDoneUseCase }, + updateNoteDeletedUseCase = { updateNoteDeletedUseCase }, ) // Assert @@ -133,8 +138,9 @@ class TagsViewModelTest { TagsViewModel( savedStateHandle = savedStateHandle, getTagWithNotesUseCase = getTagWithNotesUseCase, - updateTagNameUseCase = updateTagNameUseCase, - updateNoteDoneUseCase = updateNoteDoneUseCase, + updateTagNameUseCase = { updateTagNameUseCase }, + updateNoteDoneUseCase = { updateNoteDoneUseCase }, + updateNoteDeletedUseCase = { updateNoteDeletedUseCase }, ) // Act & Assert @@ -161,8 +167,9 @@ class TagsViewModelTest { TagsViewModel( savedStateHandle = savedStateHandle, getTagWithNotesUseCase = getTagWithNotesUseCase, - updateTagNameUseCase = updateTagNameUseCase, - updateNoteDoneUseCase = updateNoteDoneUseCase, + updateTagNameUseCase = { updateTagNameUseCase }, + updateNoteDoneUseCase = { updateNoteDoneUseCase }, + updateNoteDeletedUseCase = { updateNoteDeletedUseCase }, ) // Assert @@ -185,7 +192,7 @@ class TagsViewModelTest { } @Test - fun markNoteAsDone_shouldMarkANoteAsDone() = + fun markNoteAsDone_shouldMarkNoteAsDone() = runTest { // Arrange val note = @@ -207,8 +214,9 @@ class TagsViewModelTest { TagsViewModel( savedStateHandle = savedStateHandle, getTagWithNotesUseCase = getTagWithNotesUseCase, - updateTagNameUseCase = updateTagNameUseCase, - updateNoteDoneUseCase = updateNoteDoneUseCase, + updateTagNameUseCase = { updateTagNameUseCase }, + updateNoteDoneUseCase = { updateNoteDoneUseCase }, + updateNoteDeletedUseCase = { updateNoteDeletedUseCase }, ) // Assert @@ -223,6 +231,46 @@ class TagsViewModelTest { } } + @Test + fun markNoteAsDeleted_shouldMarkNoteAsDeleted() = + runTest { + // Arrange + val note = + Note( + id = 1L, + content = "Test note 1", + timestamp = 123456789, + tagId = 1L, + done = false, + date = "2025-06-25", + time = "00:00:00", + ) + every { getTagWithNotesUseCase(any()) } returns flowOf(tagWithNotes) + savedStateHandle = SavedStateHandle(mapOf("tagId" to 1L)) + coEvery { updateNoteDeletedUseCase(any(), any()) } returns 1 + + // Act + tagsViewModel = + TagsViewModel( + savedStateHandle = savedStateHandle, + getTagWithNotesUseCase = getTagWithNotesUseCase, + updateTagNameUseCase = { updateTagNameUseCase }, + updateNoteDoneUseCase = { updateNoteDoneUseCase }, + updateNoteDeletedUseCase = { updateNoteDeletedUseCase }, + ) + + // Assert + tagsViewModel.tagsUiState.test { + assertEquals(TagsUiState.Idle, awaitItem()) + assertEquals(TagsUiState.Loading, awaitItem()) + assertEquals(TagsUiState.TagLoaded(tagWithNotes), awaitItem()) + tagsViewModel.markNoteAsDeleted(note = note) + advanceUntilIdle() + coVerify { updateNoteDeletedUseCase(note.id, true) } + cancelAndIgnoreRemainingEvents() + } + } + @After fun tearDown() { Dispatchers.resetMain() diff --git a/feature/tags/ui/src/main/java/com/feature/tags/ui/navigation/InternalTagsFeatureApi.kt b/feature/tags/ui/src/main/java/com/feature/tags/ui/navigation/InternalTagsFeatureApi.kt index ae3c373..0ba3b7d 100644 --- a/feature/tags/ui/src/main/java/com/feature/tags/ui/navigation/InternalTagsFeatureApi.kt +++ b/feature/tags/ui/src/main/java/com/feature/tags/ui/navigation/InternalTagsFeatureApi.kt @@ -76,6 +76,7 @@ internal class InternalTagsFeatureApi navHostController.navigate(AddEditNoteScreen(noteId = note.id)) }, onNoteDoneClick = tagsViewModel::markNoteAsDone, + onNoteDeleteClick = tagsViewModel::markNoteAsDeleted, ) } } diff --git a/feature/tags/ui/src/main/java/com/feature/tags/ui/screen/TagScreenUi.kt b/feature/tags/ui/src/main/java/com/feature/tags/ui/screen/TagScreenUi.kt index 684e91c..53e80ec 100644 --- a/feature/tags/ui/src/main/java/com/feature/tags/ui/screen/TagScreenUi.kt +++ b/feature/tags/ui/src/main/java/com/feature/tags/ui/screen/TagScreenUi.kt @@ -25,6 +25,7 @@ fun TagScreenUi( onSaveTagNameClick: (Long, String) -> Unit, onNoteClick: (Note) -> Unit, onNoteDoneClick: (Note) -> Unit, + onNoteDeleteClick: (Note) -> Unit, modifier: Modifier = Modifier, ) { val state = tagsUiState @@ -62,6 +63,7 @@ fun TagScreenUi( onNoteClick = onNoteClick, onEditTagClick = onEditTagClick, onNoteDoneClick = onNoteDoneClick, + onNoteDeleteClick = onNoteDeleteClick, ) if (state.tagsUiBottomSheet is TagsUiBottomSheet.RenameTagBottomSheet) { @@ -93,6 +95,7 @@ private fun TagScreenPreview( hideEditTagBottomSheet = {}, onNoteClick = {}, onNoteDoneClick = {}, + onNoteDeleteClick = {}, ) } } diff --git a/feature/tags/ui/src/main/java/com/feature/tags/ui/screen/TagsViewModel.kt b/feature/tags/ui/src/main/java/com/feature/tags/ui/screen/TagsViewModel.kt index 621f163..660a6f6 100644 --- a/feature/tags/ui/src/main/java/com/feature/tags/ui/screen/TagsViewModel.kt +++ b/feature/tags/ui/src/main/java/com/feature/tags/ui/screen/TagsViewModel.kt @@ -9,9 +9,11 @@ import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute import com.core.common.navigation.TagScreen import com.core.domain.model.Note +import com.core.domain.usecase.UpdateNoteDeletedUseCase import com.core.domain.usecase.UpdateNoteDoneUseCase import com.feature.tags.domain.usecase.GetTagWithNotesUseCase import com.feature.tags.domain.usecase.UpdateTagNameUseCase +import dagger.Lazy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -26,8 +28,9 @@ class TagsViewModel constructor( @Assisted private val savedStateHandle: SavedStateHandle, private val getTagWithNotesUseCase: GetTagWithNotesUseCase, - private val updateTagNameUseCase: UpdateTagNameUseCase, - private val updateNoteDoneUseCase: UpdateNoteDoneUseCase, + private val updateTagNameUseCase: Lazy, + private val updateNoteDoneUseCase: Lazy, + private val updateNoteDeletedUseCase: Lazy, ) : ViewModel() { companion object { private val TAG = TagsViewModel::class.simpleName @@ -65,7 +68,7 @@ class TagsViewModel @Suppress("TooGenericExceptionCaught") viewModelScope.launch { try { - updateTagNameUseCase(tagId = tagId, newName = newName) + updateTagNameUseCase.get()(tagId = tagId, newName = newName) } catch (ex: Exception) { Log.e(TAG, "Error updating tag name: ${ex.message}", ex) } @@ -84,7 +87,13 @@ class TagsViewModel fun markNoteAsDone(note: Note) { viewModelScope.launch { - updateNoteDoneUseCase(id = note.id, done = true) + updateNoteDoneUseCase.get()(id = note.id, done = true) + } + } + + fun markNoteAsDeleted(note: Note) { + viewModelScope.launch { + updateNoteDeletedUseCase.get()(id = note.id, deleted = true) } } } diff --git a/feature/tags/ui/src/main/java/com/feature/tags/ui/screen/composables/TagContent.kt b/feature/tags/ui/src/main/java/com/feature/tags/ui/screen/composables/TagContent.kt index 711b813..d6f8dfd 100644 --- a/feature/tags/ui/src/main/java/com/feature/tags/ui/screen/composables/TagContent.kt +++ b/feature/tags/ui/src/main/java/com/feature/tags/ui/screen/composables/TagContent.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -18,14 +19,19 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.core.common.theme.LightGray +import com.core.common.theme.Red import com.core.common.theme.TaskerTheme +import com.core.common.ui.ActionIcon +import com.core.common.ui.SwipeableItemWithActions import com.core.common.utils.toColorSafely import com.core.domain.model.Note import com.core.domain.model.TagWithNotes @@ -39,6 +45,7 @@ fun TagContent( onNoteClick: (Note) -> Unit, onEditTagClick: (TagWithNotes) -> Unit, onNoteDoneClick: (Note) -> Unit, + onNoteDeleteClick: (Note) -> Unit, modifier: Modifier = Modifier, ) { Box(modifier = modifier) { @@ -96,12 +103,27 @@ fun TagContent( .fillMaxSize(), ) { items(tagsUiState.tag.notes, key = { it.id }) { note -> - TagNoteItem( - modifier = Modifier.fillMaxWidth(), - note = note, - onNoteClick = onNoteClick, - onNoteDoneClick = onNoteDoneClick, - ) + + SwipeableItemWithActions( + modifier = Modifier.animateItem(), + isRevealed = note.optionRevealed, + actions = { + ActionIcon( + onClick = { onNoteDeleteClick(note) }, + backgroundColor = Red, + icon = ImageVector.vectorResource(id = com.core.common.R.drawable.ic_delete), + modifier = Modifier.fillMaxHeight(), + contentDescription = "Delete Icon", + ) + }, + ) { + TagNoteItem( + modifier = Modifier.fillMaxWidth().background(tagsUiState.tag.color.toColorSafely()), + note = note, + onNoteClick = onNoteClick, + onNoteDoneClick = onNoteDoneClick, + ) + } HorizontalDivider( modifier = Modifier.padding(start = 60.dp), color = Color.White, @@ -119,10 +141,15 @@ private fun TagContentPreview() { TaskerTheme { TagContent( modifier = Modifier.fillMaxSize(), - tagsUiState = TagsUiState.TagLoaded(tag = tagWithNotes, tagsUiBottomSheet = TagsUiBottomSheet.None), + tagsUiState = + TagsUiState.TagLoaded( + tag = tagWithNotes, + tagsUiBottomSheet = TagsUiBottomSheet.None, + ), onEditTagClick = {}, onNoteClick = {}, onNoteDoneClick = {}, + onNoteDeleteClick = {}, ) } }