Skip to content

Commit

Permalink
Merge pull request #620 from PatilShreyas/fix-619
Browse files Browse the repository at this point in the history
Fix #619: Show recently added notes first
  • Loading branch information
PatilShreyas authored Mar 5, 2023
2 parents fa4aeb0 + bf0c124 commit 04387c8
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 18 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ If you want to discuss on something then feel free to present your opinions, vie
- New code addition/deletion should not break existing flow of a system.
- All tests should be passed.
- Verify `./gradlew build` is passing before raising a PR.
- Run UI tests using `./gradlew connectedCheck` to make sure UI tests are passing.
- Reformat code with KtLint `./gradlew ktlintFormat` before raising a PR.
4 changes: 2 additions & 2 deletions docs/pages/noty-android/compose_report.html
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ <h1>Compose Compiler Report - NotyKT</h1>
</tr>
<tr>
<th>Static Arguments</th>
<td>218</td>
<td>217</td>
</tr>
<tr>
<th>Certain Arguments</th>
Expand Down Expand Up @@ -279,7 +279,7 @@ <h1>Compose Compiler Report - NotyKT</h1>
<td>0</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>1</td>
</tr>
<tr>
<td>dev.shreyaspatil.noty.composeapp.component.action.DeleteAction</td>
Expand Down
2 changes: 1 addition & 1 deletion noty-android/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,7 @@ ij_xml_line_comment_at_first_column = true
ij_xml_use_custom_settings = true

[{*.kt, *.kts}]
disabled_rules = no-wildcard-imports, import-ordering
ktlint_disabled_rules = no-wildcard-imports, import-ordering
ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = false
ij_kotlin_align_multiline_extends_list = false
Expand Down
8 changes: 8 additions & 0 deletions noty-android/app/composeapp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ android {
lint {
abortOnError false
}
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/LICENSE.md'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/DEPENDENCIES'
}
namespace 'dev.shreyaspatil.noty.composeapp'

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package dev.shreyaspatil.noty.composeapp.fake.service
import dev.shreyaspatil.noty.composeapp.testUtil.successResponse
import dev.shreyaspatil.noty.data.remote.api.NotyService
import dev.shreyaspatil.noty.data.remote.model.request.NoteRequest
import dev.shreyaspatil.noty.data.remote.model.request.NoteUpdatePinRequest
import dev.shreyaspatil.noty.data.remote.model.response.NoteResponse
import dev.shreyaspatil.noty.data.remote.model.response.NotesResponse
import dev.shreyaspatil.noty.data.remote.model.response.State
Expand Down Expand Up @@ -53,4 +54,11 @@ class FakeNotyService @Inject constructor() : NotyService {
// Do nothing, just return success
return successResponse(NoteResponse(State.SUCCESS, "", ""))
}

override suspend fun updateNotePin(
noteId: String,
noteRequest: NoteUpdatePinRequest
): Response<NoteResponse> {
return successResponse(NoteResponse(State.SUCCESS, "", ""))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package dev.shreyaspatil.noty.composeapp.ui.screens

import androidx.compose.runtime.Composable
import androidx.compose.ui.test.IdlingResource
import androidx.compose.ui.test.assertContentDescriptionEquals
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextClearance
Expand All @@ -32,6 +34,7 @@ import dev.shreyaspatil.noty.core.repository.NotyNoteRepository
import dev.shreyaspatil.noty.di.LocalRepository
import dev.shreyaspatil.noty.view.viewmodel.NoteDetailViewModel
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.junit.Assert.assertTrue
import org.junit.Before
Expand Down Expand Up @@ -131,6 +134,26 @@ class NoteDetailsScreenTest : NotyScreenTest() {
assertTrue(navigatingUp)
}

@Test
fun showActionToUnpinNote_whenNoteIsAlreadyPinned() = runTest {
registerIdlingResource(setNoteIsPinned(true))
setNotyContent { NoteDetailScreen() }
waitForIdle()

onNodeWithTag("actionTogglePin", useUnmergedTree = true)
.assertContentDescriptionEquals("Pinned")
}

@Test
fun showActionToPinNote_whenNoteIsNotPinned() = runTest {
registerIdlingResource(setNoteIsPinned(false))
setNotyContent { NoteDetailScreen() }
waitForIdle()

onNodeWithTag("actionTogglePin", useUnmergedTree = true)
.assertContentDescriptionEquals("Not Pinned")
}

@Composable
private fun NoteDetailScreen(onNavigateUp: () -> Unit = {}) {
NoteDetailsScreen(
Expand All @@ -157,4 +180,16 @@ class NoteDetailsScreenTest : NotyScreenTest() {
}
}
}

private fun setNoteIsPinned(isPinned: Boolean) = object : IdlingResource {
override var isIdleNow: Boolean = false

init {
GlobalScope.launch {
noteRepository.pinNote("1", isPinned)
delay(1000)
isIdleNow = true
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,31 @@ class NotesScreenTest : NotyScreenTest() {
setNotyContent { NotesScreen(onNavigateToNoteDetail = { navigateToNoteId = it }) }
registerIdlingResource(prefillNotes())

onNodeWithText("Lorem Ipsum 1", useUnmergedTree = true).performClick()
onNodeWithText("Lorem Ipsum 2", useUnmergedTree = true).performClick()
waitForIdle()
assertEquals("1", navigateToNoteId)
assertEquals("2", navigateToNoteId)

onNodeWithText("Hello World 2", useUnmergedTree = true).performClick()
onNodeWithText("Hello World 3", useUnmergedTree = true).performClick()
waitForIdle()
assertEquals("2", navigateToNoteId)
assertEquals("3", navigateToNoteId)
}

@Test
fun showPinnedNotesFirst_whenPinnedNotesArePresent() = runTest {
setNotyContent { NotesScreen() }

registerIdlingResource(prefillNotes())
registerIdlingResource(pinNotes("49", "50"))

waitForIdle()

// Scroll to the top of screen
onNodeWithTag("notesList").performScrollToIndex(0)
waitForIdle()

// Pinned notes should be displayed on top of screen
onNodeWithText("Lorem Ipsum 49").assertIsDisplayed()
onNodeWithText("Lorem Ipsum 50").assertIsDisplayed()
}

@Composable
Expand Down Expand Up @@ -241,6 +259,10 @@ class NotesScreenTest : NotyScreenTest() {

private fun prefillNotes() = addNotes(notes())

private fun pinNotes(vararg noteIds: String): IdlingResource {
return updateNotePins(noteIds.toList())
}

@Suppress("SameParameterValue")
private fun addNote(title: String, note: String) = object : IdlingResource {
override var isIdleNow: Boolean = false
Expand All @@ -254,6 +276,20 @@ class NotesScreenTest : NotyScreenTest() {
}
}

private fun updateNotePins(noteIds: List<String>) = object : IdlingResource {
override var isIdleNow: Boolean = false

init {
GlobalScope.launch {
noteIds.forEach { noteId ->
noteRepository.pinNote(noteId, true)
}
delay(1_000)
isIdleNow = true
}
}
}

@Suppress("SameParameterValue")
private fun deleteNote(id: String) = object : IdlingResource {
override var isIdleNow: Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,25 @@ import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import dev.shreyaspatil.noty.R

@Composable
fun PinAction(isPinned: Boolean, onClick: () -> Unit) {
val icon = painterResource(id = if (isPinned) R.drawable.ic_pinned else R.drawable.ic_unpinned)
val (icon, contentDescription) = if (isPinned) {
R.drawable.ic_pinned to "Pinned"
} else {
R.drawable.ic_unpinned to "Not Pinned"
}
IconButton(onClick = onClick) {
Icon(
painter = icon,
contentDescription = "Pinned Note",
painter = painterResource(id = icon),
contentDescription = contentDescription,
modifier = Modifier
.padding(8.dp)
.testTag("actionTogglePin")
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,65 @@ class NoteDetailViewModelTest : ViewModelBehaviorSpec({
}
}
}

Given("A note is either pinned or unpinned") {
And("Note is not yet synced") {
val wasPinned = viewModel.currentState.isPinned
coEvery { repository.pinNote(noteId, any()) } returns Either.success("TMP_$noteId")

When("Note pin is toggled") {
viewModel.togglePin()

Then("Note should be get pinned") {
coVerify { repository.pinNote(noteId, !wasPinned) }
}

Then("Valid UI states should be get updated") {
viewModel.withState { isPinned shouldBe !wasPinned }
}

Then("Note pin should NOT be get scheduled") {
scheduledTasks.find {
it.noteId == "TMP_$noteId" && it.action == NotyTaskAction.PIN
} shouldBe null
}
}
}

And("Note is synced") {
val wasPinned = viewModel.currentState.isPinned
coEvery { repository.pinNote(noteId, any()) } returns Either.success(noteId)

When("Note pin is toggled") {
viewModel.togglePin()

Then("Note should get pinned") {
coVerify { repository.pinNote(noteId, !wasPinned) }
}

Then("Valid UI states should be get updated") {
viewModel.withState { isPinned shouldBe !wasPinned }
}

Then("Note pin should be get scheduled") {
scheduledTasks.last().let {
it.noteId shouldBe noteId
it.action shouldBe NotyTaskAction.PIN
}
}
}
}

And("Error occurs") {
coEvery { repository.pinNote(noteId, any()) } returns Either.error("Error occurred")

When("Note pin is toggled") {
viewModel.togglePin()

Then("Valid UI states should be get updated") {
viewModel.withState { error shouldBe "Error occurred" }
}
}
}
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ interface NotesDao {
@Query("SELECT * FROM notes WHERE noteId = :noteId")
fun getNoteById(noteId: String): Flow<NoteEntity?>

@Query("SELECT * FROM notes ORDER BY isPinned = 1 DESC, created")
@Query("SELECT * FROM notes ORDER BY isPinned = 1 DESC, created DESC")
fun getAllNotes(): Flow<List<NoteEntity>>

@Insert
Expand Down
9 changes: 9 additions & 0 deletions noty-android/data/remote/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ android {
}
}
}
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/LICENSE.md'
exclude 'META-INF/LICENSE-notice.md'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/DEPENDENCIES'
}
namespace 'dev.shreyaspatil.noty.data.remote'
}

Expand Down
2 changes: 1 addition & 1 deletion noty-android/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ ext {
lottieComposeVersion = '6.0.0'

// DI
daggerHiltVersion = '2.45'
daggerHiltVersion = '2.44.2'
jetpackHiltVersion = "1.0.0"

// Networking
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
package dev.shreyaspatil.noty.repository.local

import dev.shreyaspatil.noty.core.model.Note
import dev.shreyaspatil.noty.core.repository.NotyNoteRepository
import dev.shreyaspatil.noty.core.repository.Either
import dev.shreyaspatil.noty.core.repository.NotyNoteRepository
import dev.shreyaspatil.noty.data.local.dao.NotesDao
import dev.shreyaspatil.noty.data.local.entity.NoteEntity
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -51,11 +51,11 @@ class NotyLocalNoteRepository @Inject constructor(
val tempNoteId = NotyNoteRepository.generateTemporaryId()
notesDao.addNote(
NoteEntity(
tempNoteId,
title,
note,
System.currentTimeMillis(),
false
noteId = tempNoteId,
title = title,
note = note,
created = System.currentTimeMillis(),
isPinned = false
)
)
Either.success(tempNoteId)
Expand Down

0 comments on commit 04387c8

Please sign in to comment.