Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ data class ListDecryptionState<T>(
val isLoading: Boolean = false
)

/**
* Counts the visible (not hidden, not trashed) passwords per folder id, so a
* folder row can show how many passwords it directly contains. Counts direct
* children only, matching the passwords shown when the folder is opened.
*/
fun folderPasswordCounts(passwords: List<Password>): Map<String, Int> =
passwords.asSequence()
.filter { !it.hidden && !it.trashed }
.groupingBy { it.folder }
.eachCount()

@Composable
fun MixedLazyColumn(
passwords: List<Password>? = null,
Expand All @@ -62,7 +73,8 @@ fun MixedLazyColumn(
onPasswordLongClick: ((Password) -> Unit)? = null,
onFolderClick: ((Folder) -> Unit)? = null,
onFolderLongClick: ((Folder) -> Unit)? = null,
getPainterForUrl: (@Composable (String) -> Painter)? = null
getPainterForUrl: (@Composable (String) -> Painter)? = null,
folderPasswordCounts: Map<String, Int>? = null
) {
val context = LocalContext.current
val shouldShowIcon by PreferencesManager.getInstance(context).getShowIcons()
Expand All @@ -89,6 +101,7 @@ fun MixedLazyColumn(
items(items = it, key = { folder -> folder.id }) { folder ->
FolderRow(
folder = folder,
passwordCount = folderPasswordCounts?.let { it[folder.id] ?: 0 },
onFolderClick = onFolderClick,
onFolderLongClick = onFolderLongClick
)
Expand Down Expand Up @@ -179,6 +192,7 @@ fun PasswordRow(
fun FolderRow(
folder: Folder,
modifier: Modifier = Modifier,
passwordCount: Int? = null,
onFolderClick: ((Folder) -> Unit)? = null,
onFolderLongClick: ((Folder) -> Unit)? = null,
) {
Expand All @@ -196,6 +210,15 @@ fun FolderRow(
headlineContent = {
Text(text = folder.label)
},
trailingContent = passwordCount?.let { count ->
{
Text(
text = count.toString(),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
},
modifier = modifier
.combinedClickable(
onClick = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@ fun NCPNavHost(
it.parent == FoldersApi.DEFAULT_FOLDER_UUID
}
}
val folderPasswordCounts = remember(passwordsDecryptionState.decryptedList) {
folderPasswordCounts(passwordsDecryptionState.decryptedList ?: emptyList())
}
when {
foldersDecryptionState.isLoading || passwordsDecryptionState.isLoading -> {
Box(modifier = Modifier.fillMaxSize()) {
Expand Down Expand Up @@ -343,6 +346,7 @@ fun NCPNavHost(
MixedLazyColumn(
passwords = filteredPasswordsParentFolder,
folders = filteredFoldersParentFolder,
folderPasswordCounts = folderPasswordCounts,
onPasswordClick = onPasswordClick,
onPasswordLongClick = {
if (sessionOpen && (autofillData == null || autofillData.isSave()) && it.editable)
Expand Down Expand Up @@ -382,6 +386,9 @@ fun NCPNavHost(
it.parent == folderUuid
}
}
val folderPasswordCounts = remember(passwordsDecryptionState.decryptedList) {
folderPasswordCounts(passwordsDecryptionState.decryptedList ?: emptyList())
}
NCPNavHostComposable(
modalSheetState = modalSheetState,
searchVisibility = searchVisibility,
Expand Down Expand Up @@ -421,6 +428,7 @@ fun NCPNavHost(
MixedLazyColumn(
passwords = filteredPasswordsSelectedFolder,
folders = filteredFoldersSelectedFolder,
folderPasswordCounts = folderPasswordCounts,
onPasswordClick = onPasswordClick,
onPasswordLongClick = {
if (sessionOpen && (autofillData == null || autofillData.isSave()) && it.editable)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.hegocre.nextcloudpasswords

import com.hegocre.nextcloudpasswords.data.password.Password
import com.hegocre.nextcloudpasswords.ui.components.folderPasswordCounts
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test

/**
* Unit tests for the per-folder password counting used by the folder list.
*/
class FolderPasswordCountTest {
private fun password(
id: String,
folder: String,
hidden: Boolean = false,
trashed: Boolean = false
) = Password(
id = id,
label = id,
username = "",
password = "",
url = "",
notes = "",
customFields = "",
status = 0,
statusCode = "GOOD",
hash = "",
folder = folder,
revision = "",
share = null,
shared = false,
cseType = "",
cseKey = "",
sseType = "",
client = "",
hidden = hidden,
trashed = trashed,
favorite = false,
editable = true,
edited = 0,
created = 0,
updated = 0
)

private val folderA = "folder-a"
private val folderB = "folder-b"

@Test
fun countsPasswordsPerFolder() {
val counts = folderPasswordCounts(
listOf(
password("1", folderA),
password("2", folderA),
password("3", folderB)
)
)
assertEquals(2, counts[folderA])
assertEquals(1, counts[folderB])
}

@Test
fun folderWithoutPasswordsIsAbsent() {
val counts = folderPasswordCounts(listOf(password("1", folderA)))
assertNull(counts["empty-folder"])
}

@Test
fun emptyListYieldsEmptyMap() {
assertEquals(emptyMap<String, Int>(), folderPasswordCounts(emptyList()))
}

@Test
fun hiddenAndTrashedPasswordsAreExcluded() {
val counts = folderPasswordCounts(
listOf(
password("1", folderA),
password("2", folderA, hidden = true),
password("3", folderA, trashed = true)
)
)
assertEquals(1, counts[folderA])
}

@Test
fun folderWithOnlyHiddenOrTrashedPasswordsIsAbsent() {
val counts = folderPasswordCounts(
listOf(
password("1", folderA, hidden = true),
password("2", folderA, trashed = true)
)
)
assertNull(counts[folderA])
}
}