mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 19:08:23 +00:00
Merge pull request #496 from greenart7c3/claude/group-intents-by-type-m9ndb1
Group signing requests by type and payload in multi-event screens
This commit is contained in:
+49
-10
@@ -21,6 +21,7 @@ import androidx.compose.material3.TriStateCheckbox
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -151,19 +152,57 @@ fun BunkerMultiEventHomeScreen(
|
|||||||
Text(stringResource(R.string.select_deselect_all))
|
Text(stringResource(R.string.select_deselect_all))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val groups = remember(bunkerRequests) {
|
||||||
|
groupRequests(bunkerRequests) {
|
||||||
|
requestGroupKey(
|
||||||
|
type = BunkerRequestUtils.getTypeFromBunker(it.request),
|
||||||
|
eventKind = (it.request as? BunkerRequestSign)?.event?.kind,
|
||||||
|
encryptedData = it.encryptedData,
|
||||||
|
nip44v3Kind = BunkerRequestUtils.getNip44v3Kind(it.request),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val expandedGroups = remember { mutableStateMapOf<RequestGroupKey, Boolean>() }
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
Modifier.weight(1f),
|
Modifier.weight(1f),
|
||||||
) {
|
) {
|
||||||
items(bunkerRequests, key = { it.request.id }) { bunkerRequest ->
|
groups.forEach { (groupKey, groupItems) ->
|
||||||
BunkerRequestCard(
|
val expanded = groups.size == 1 || (expandedGroups[groupKey] ?: false)
|
||||||
context = context,
|
if (groups.size > 1) {
|
||||||
bunkerRequest = bunkerRequest,
|
item(key = "group-header:${groupKey.type.name}:${groupKey.payload?.name ?: ""}:${groupKey.kind ?: ""}") {
|
||||||
checked = MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] ?: true,
|
val groupState = when {
|
||||||
onToggleChecked = {
|
groupItems.all { MultiEventScreenIntents.checkedStates[it.request.id] ?: true } -> ToggleableState.On
|
||||||
val current = MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] ?: true
|
groupItems.none { MultiEventScreenIntents.checkedStates[it.request.id] ?: true } -> ToggleableState.Off
|
||||||
MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] = !current
|
else -> ToggleableState.Indeterminate
|
||||||
},
|
}
|
||||||
)
|
RequestGroupHeader(
|
||||||
|
label = groupKey.toLabel(context),
|
||||||
|
count = groupItems.size,
|
||||||
|
state = groupState,
|
||||||
|
expanded = expanded,
|
||||||
|
onToggle = {
|
||||||
|
val newValue = groupState != ToggleableState.On
|
||||||
|
MultiEventScreenIntents.checkedStates.putAll(groupItems.associate { it.request.id to newValue })
|
||||||
|
},
|
||||||
|
onExpandToggle = {
|
||||||
|
expandedGroups[groupKey] = !(expandedGroups[groupKey] ?: false)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expanded) {
|
||||||
|
items(groupItems, key = { it.request.id }) { bunkerRequest ->
|
||||||
|
BunkerRequestCard(
|
||||||
|
context = context,
|
||||||
|
bunkerRequest = bunkerRequest,
|
||||||
|
checked = MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] ?: true,
|
||||||
|
onToggleChecked = {
|
||||||
|
val current = MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] ?: true
|
||||||
|
MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] = !current
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+44
-10
@@ -23,6 +23,7 @@ import androidx.compose.material3.TriStateCheckbox
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -122,19 +123,52 @@ fun IntentMultiEventHomeScreen(
|
|||||||
Text(stringResource(R.string.select_deselect_all))
|
Text(stringResource(R.string.select_deselect_all))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val groups = remember(intents) {
|
||||||
|
groupRequests(intents) {
|
||||||
|
requestGroupKey(it.type, it.event?.kind, it.encryptedData, it.nip44v3Kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val expandedGroups = remember { mutableStateMapOf<RequestGroupKey, Boolean>() }
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
Modifier.weight(1f),
|
Modifier.weight(1f),
|
||||||
) {
|
) {
|
||||||
items(intents, key = { it.id }) { intent ->
|
groups.forEach { (groupKey, groupIntents) ->
|
||||||
IntentRequestCard(
|
val expanded = groups.size == 1 || (expandedGroups[groupKey] ?: false)
|
||||||
context = context,
|
if (groups.size > 1) {
|
||||||
intent = intent,
|
item(key = "group-header:${groupKey.type.name}:${groupKey.payload?.name ?: ""}:${groupKey.kind ?: ""}") {
|
||||||
checked = MultiEventScreenIntents.checkedStates[intent.id] ?: true,
|
val groupState = when {
|
||||||
onToggleChecked = {
|
groupIntents.all { MultiEventScreenIntents.checkedStates[it.id] ?: true } -> ToggleableState.On
|
||||||
val current = MultiEventScreenIntents.checkedStates[intent.id] ?: true
|
groupIntents.none { MultiEventScreenIntents.checkedStates[it.id] ?: true } -> ToggleableState.Off
|
||||||
MultiEventScreenIntents.checkedStates[intent.id] = !current
|
else -> ToggleableState.Indeterminate
|
||||||
},
|
}
|
||||||
)
|
RequestGroupHeader(
|
||||||
|
label = groupKey.toLabel(context),
|
||||||
|
count = groupIntents.size,
|
||||||
|
state = groupState,
|
||||||
|
expanded = expanded,
|
||||||
|
onToggle = {
|
||||||
|
val newValue = groupState != ToggleableState.On
|
||||||
|
MultiEventScreenIntents.checkedStates.putAll(groupIntents.associate { it.id to newValue })
|
||||||
|
},
|
||||||
|
onExpandToggle = {
|
||||||
|
expandedGroups[groupKey] = !(expandedGroups[groupKey] ?: false)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expanded) {
|
||||||
|
items(groupIntents, key = { it.id }) { intent ->
|
||||||
|
IntentRequestCard(
|
||||||
|
context = context,
|
||||||
|
intent = intent,
|
||||||
|
checked = MultiEventScreenIntents.checkedStates[intent.id] ?: true,
|
||||||
|
onToggleChecked = {
|
||||||
|
val current = MultiEventScreenIntents.checkedStates[intent.id] ?: true
|
||||||
|
MultiEventScreenIntents.checkedStates[intent.id] = !current
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package com.greenart7c3.nostrsigner.ui.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ExpandMore
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TriStateCheckbox
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.state.ToggleableState
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.greenart7c3.nostrsigner.R
|
||||||
|
import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind
|
||||||
|
import com.greenart7c3.nostrsigner.models.EncryptedDataKind
|
||||||
|
import com.greenart7c3.nostrsigner.models.EventEncryptedDataKind
|
||||||
|
import com.greenart7c3.nostrsigner.models.Permission
|
||||||
|
import com.greenart7c3.nostrsigner.models.PrivateZapEncryptedDataKind
|
||||||
|
import com.greenart7c3.nostrsigner.models.SignerType
|
||||||
|
import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind
|
||||||
|
import com.greenart7c3.nostrsigner.models.encryptDecryptSignerTypes
|
||||||
|
|
||||||
|
enum class RequestPayloadShape {
|
||||||
|
EVENT,
|
||||||
|
TAG_ARRAY,
|
||||||
|
CLEAR_TEXT,
|
||||||
|
PRIVATE_ZAP,
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RequestGroupKey(
|
||||||
|
val type: SignerType,
|
||||||
|
val kind: Int?,
|
||||||
|
val payload: RequestPayloadShape?,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun requestGroupKey(
|
||||||
|
type: SignerType,
|
||||||
|
eventKind: Int?,
|
||||||
|
encryptedData: EncryptedDataKind?,
|
||||||
|
nip44v3Kind: Int?,
|
||||||
|
): RequestGroupKey = when {
|
||||||
|
type == SignerType.SIGN_EVENT -> RequestGroupKey(type, eventKind, null)
|
||||||
|
type == SignerType.NIP44_V3_ENCRYPT || type == SignerType.NIP44_V3_DECRYPT -> RequestGroupKey(type, nip44v3Kind, null)
|
||||||
|
type in encryptDecryptSignerTypes -> when (encryptedData) {
|
||||||
|
is EventEncryptedDataKind -> RequestGroupKey(type, encryptedData.event.kind, RequestPayloadShape.EVENT)
|
||||||
|
is TagArrayEncryptedDataKind -> RequestGroupKey(type, null, RequestPayloadShape.TAG_ARRAY)
|
||||||
|
is ClearTextEncryptedDataKind -> RequestGroupKey(type, null, RequestPayloadShape.CLEAR_TEXT)
|
||||||
|
is PrivateZapEncryptedDataKind -> RequestGroupKey(type, null, RequestPayloadShape.PRIVATE_ZAP)
|
||||||
|
else -> RequestGroupKey(type, null, null)
|
||||||
|
}
|
||||||
|
else -> RequestGroupKey(type, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> groupRequests(
|
||||||
|
items: List<T>,
|
||||||
|
keyOf: (T) -> RequestGroupKey,
|
||||||
|
): List<Pair<RequestGroupKey, List<T>>> = items.groupBy(keyOf).toList()
|
||||||
|
.sortedWith(
|
||||||
|
compareBy(
|
||||||
|
{ it.first.type.ordinal },
|
||||||
|
{ it.first.payload?.ordinal ?: -1 },
|
||||||
|
{ it.first.kind ?: -1 },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun RequestGroupKey.toLabel(context: Context): String {
|
||||||
|
val isEncrypt = type.name.contains("ENCRYPT")
|
||||||
|
val nip = type.name.split("_").first()
|
||||||
|
return when {
|
||||||
|
type == SignerType.CONNECT -> context.getString(R.string.connect)
|
||||||
|
type == SignerType.SIGN_EVENT -> Permission("sign_event", kind).toLocalizedString(context)
|
||||||
|
payload == RequestPayloadShape.EVENT -> {
|
||||||
|
val kindLabel = Permission("sign_event", kind).toLocalizedString(context)
|
||||||
|
if (isEncrypt) {
|
||||||
|
context.getString(R.string.encrypt_with, kindLabel, nip)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.read_from_encrypted_content, kindLabel, nip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
payload == RequestPayloadShape.TAG_ARRAY -> if (isEncrypt) {
|
||||||
|
context.getString(R.string.encrypt_this_list_of_tags_with, nip)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.read_this_list_of_tags_from_encrypted_content, nip)
|
||||||
|
}
|
||||||
|
payload == RequestPayloadShape.CLEAR_TEXT -> if (isEncrypt) {
|
||||||
|
context.getString(R.string.encrypt_this_text_with, nip)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.read_this_text_from_encrypted_content, nip)
|
||||||
|
}
|
||||||
|
payload == RequestPayloadShape.PRIVATE_ZAP -> context.getString(R.string.decrypt_zap_event).replaceFirstChar { it.uppercase() }
|
||||||
|
(type == SignerType.NIP44_V3_ENCRYPT || type == SignerType.NIP44_V3_DECRYPT) && kind != null -> {
|
||||||
|
val kindLabel = Permission("sign_event", kind).toLocalizedString(context)
|
||||||
|
if (isEncrypt) {
|
||||||
|
context.getString(R.string.encrypt_with, kindLabel, nip)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.read_from_encrypted_content, kindLabel, nip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> Permission(type.name.lowercase(), null).toLocalizedString(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RequestGroupHeader(
|
||||||
|
label: String,
|
||||||
|
count: Int,
|
||||||
|
state: ToggleableState,
|
||||||
|
expanded: Boolean,
|
||||||
|
onToggle: () -> Unit,
|
||||||
|
onExpandToggle: () -> Unit,
|
||||||
|
) {
|
||||||
|
val rotation by animateFloatAsState(
|
||||||
|
targetValue = if (expanded) 180f else 0f,
|
||||||
|
label = "group header chevron rotation",
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onExpandToggle() },
|
||||||
|
) {
|
||||||
|
TriStateCheckbox(
|
||||||
|
state = state,
|
||||||
|
onClick = onToggle,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "$label ($count)",
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ExpandMore,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 12.dp)
|
||||||
|
.rotate(rotation),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package com.greenart7c3.nostrsigner.ui.components
|
||||||
|
|
||||||
|
import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind
|
||||||
|
import com.greenart7c3.nostrsigner.models.EventEncryptedDataKind
|
||||||
|
import com.greenart7c3.nostrsigner.models.PrivateZapEncryptedDataKind
|
||||||
|
import com.greenart7c3.nostrsigner.models.SignerType
|
||||||
|
import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind
|
||||||
|
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class RequestGroupUtilsTest {
|
||||||
|
|
||||||
|
private fun amberEvent(kind: Int) = AmberEvent("id", "pubkey", 0L, kind, arrayOf(), "", "")
|
||||||
|
|
||||||
|
private fun eventData(kind: Int) = EventEncryptedDataKind(amberEvent(kind), null, "result")
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `sign event groups by event kind`() {
|
||||||
|
val key1 = requestGroupKey(SignerType.SIGN_EVENT, 1, null, null)
|
||||||
|
val key1b = requestGroupKey(SignerType.SIGN_EVENT, 1, null, null)
|
||||||
|
val key22242 = requestGroupKey(SignerType.SIGN_EVENT, 22242, null, null)
|
||||||
|
|
||||||
|
assertEquals(key1, key1b)
|
||||||
|
assertEquals(1, key1.kind)
|
||||||
|
assertEquals(22242, key22242.kind)
|
||||||
|
assertEquals(SignerType.SIGN_EVENT, key1.type)
|
||||||
|
assertEquals(null, key1.payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `encrypt decrypt separates by payload shape`() {
|
||||||
|
val eventKey = requestGroupKey(SignerType.NIP44_DECRYPT, null, eventData(4), null)
|
||||||
|
val tagKey = requestGroupKey(SignerType.NIP44_DECRYPT, null, TagArrayEncryptedDataKind(arrayOf(), "r"), null)
|
||||||
|
val textKey = requestGroupKey(SignerType.NIP44_DECRYPT, null, ClearTextEncryptedDataKind("t", "r"), null)
|
||||||
|
val zapKey = requestGroupKey(SignerType.DECRYPT_ZAP_EVENT, null, PrivateZapEncryptedDataKind("r"), null)
|
||||||
|
|
||||||
|
assertEquals(RequestPayloadShape.EVENT, eventKey.payload)
|
||||||
|
assertEquals(4, eventKey.kind)
|
||||||
|
assertEquals(RequestPayloadShape.TAG_ARRAY, tagKey.payload)
|
||||||
|
assertEquals(null, tagKey.kind)
|
||||||
|
assertEquals(RequestPayloadShape.CLEAR_TEXT, textKey.payload)
|
||||||
|
assertEquals(RequestPayloadShape.PRIVATE_ZAP, zapKey.payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `encrypt decrypt event payloads separate by embedded event kind`() {
|
||||||
|
val kind1 = requestGroupKey(SignerType.NIP44_ENCRYPT, null, eventData(1), null)
|
||||||
|
val kind1b = requestGroupKey(SignerType.NIP44_ENCRYPT, null, eventData(1), null)
|
||||||
|
val kind7 = requestGroupKey(SignerType.NIP44_ENCRYPT, null, eventData(7), null)
|
||||||
|
|
||||||
|
assertEquals(kind1, kind1b)
|
||||||
|
assertEquals(1, kind1.kind)
|
||||||
|
assertEquals(7, kind7.kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `nip44 v3 groups by explicit kind`() {
|
||||||
|
val enc9 = requestGroupKey(SignerType.NIP44_V3_ENCRYPT, null, null, 9)
|
||||||
|
val dec9 = requestGroupKey(SignerType.NIP44_V3_DECRYPT, null, null, 9)
|
||||||
|
val decNull = requestGroupKey(SignerType.NIP44_V3_DECRYPT, null, null, null)
|
||||||
|
|
||||||
|
assertEquals(9, enc9.kind)
|
||||||
|
assertEquals(9, dec9.kind)
|
||||||
|
assertEquals(null, decNull.kind)
|
||||||
|
assertEquals(null, enc9.payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `other types group by type alone`() {
|
||||||
|
val connect = requestGroupKey(SignerType.CONNECT, null, null, null)
|
||||||
|
val pubKey = requestGroupKey(SignerType.GET_PUBLIC_KEY, null, null, null)
|
||||||
|
|
||||||
|
assertEquals(RequestGroupKey(SignerType.CONNECT, null, null), connect)
|
||||||
|
assertEquals(RequestGroupKey(SignerType.GET_PUBLIC_KEY, null, null), pubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `groupRequests sorts by type ordinal then payload then kind`() {
|
||||||
|
val items = listOf(
|
||||||
|
RequestGroupKey(SignerType.SIGN_EVENT, 22242, null),
|
||||||
|
RequestGroupKey(SignerType.NIP44_DECRYPT, null, RequestPayloadShape.CLEAR_TEXT),
|
||||||
|
RequestGroupKey(SignerType.CONNECT, null, null),
|
||||||
|
RequestGroupKey(SignerType.SIGN_EVENT, 1, null),
|
||||||
|
RequestGroupKey(SignerType.NIP44_DECRYPT, 4, RequestPayloadShape.EVENT),
|
||||||
|
)
|
||||||
|
|
||||||
|
val groups = groupRequests(items) { it }
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf(
|
||||||
|
RequestGroupKey(SignerType.CONNECT, null, null),
|
||||||
|
RequestGroupKey(SignerType.SIGN_EVENT, 1, null),
|
||||||
|
RequestGroupKey(SignerType.SIGN_EVENT, 22242, null),
|
||||||
|
RequestGroupKey(SignerType.NIP44_DECRYPT, 4, RequestPayloadShape.EVENT),
|
||||||
|
RequestGroupKey(SignerType.NIP44_DECRYPT, null, RequestPayloadShape.CLEAR_TEXT),
|
||||||
|
),
|
||||||
|
groups.map { it.first },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `groupRequests preserves input order within a group`() {
|
||||||
|
data class Item(val id: String, val kind: Int)
|
||||||
|
|
||||||
|
val items = listOf(
|
||||||
|
Item("a", 1),
|
||||||
|
Item("b", 7),
|
||||||
|
Item("c", 1),
|
||||||
|
Item("d", 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
val groups = groupRequests(items) { RequestGroupKey(SignerType.SIGN_EVENT, it.kind, null) }
|
||||||
|
|
||||||
|
assertEquals(listOf("a", "c", "d"), groups.first { it.first.kind == 1 }.second.map { it.id })
|
||||||
|
assertEquals(listOf("b"), groups.first { it.first.kind == 7 }.second.map { it.id })
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user