Group geohash people as People and Anon

Replaces the "on location" / "teleported in" / "anonymous" split with two
sections: peers who announced a nickname, then the anons.

Teleport state was never worth a section of its own -- every row already
carries it as a distinct glyph -- and splitting on it fragmented the short
list people actually read, in a channel where most participants are
anonymous anyway.

Self stays in the People section even when unnamed.
This commit is contained in:
callebtc 2026-07-27 10:33:29 +02:00
parent 9703ebfcaf
commit bcfc33f35c
3 changed files with 28 additions and 33 deletions

View File

@ -3,6 +3,7 @@ package com.bitchat.android.ui
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Email
import androidx.compose.material.icons.outlined.Explore
import androidx.compose.material.icons.outlined.HelpOutline
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material.icons.outlined.LocationOn
import android.util.Log
@ -59,8 +60,8 @@ fun GeohashPeopleList(
Column(modifier = modifier) {
if (geohashPeople.isEmpty()) {
SheetIconSectionHeader(
icon = Icons.Outlined.LocationOn,
title = stringResource(R.string.section_on_location)
icon = Icons.Outlined.Person,
title = stringResource(R.string.section_people)
)
Surface(
modifier = Modifier
@ -126,15 +127,18 @@ fun GeohashPeopleList(
viewModel.isPersonTeleported(person.id)
}
// Anonymous participants form their own trailing section rather than a tail on each
// of the others. A busy geohash is mostly anons, and splitting them across "on
// location" and "teleported in" pushed the few recognisable names out of view twice
// over. Self is never grouped as an anon even when unnamed — you always want to find
// yourself where you actually are.
// Two groups: peers who announced a nickname, then the anons.
//
// A busy geohash is mostly anonymous drive-by participants, and mixing them in pushed
// the few recognisable names out of view. Teleport state is not a grouping any more —
// it is already on every row as its own glyph, so splitting "on location" from
// "teleported in" only fragmented the short list that people actually read.
//
// Self is never grouped as an anon even when unnamed: you always want to find yourself
// among the people, not buried at the bottom.
val isSelf: (GeoPerson) -> Boolean = { myHex != null && it.id == myHex }
val namedPeople = orderedPeople.filter { isSelf(it) || !it.isAnonymous() }
val anonPeople = orderedPeople.filter { !isSelf(it) && it.isAnonymous() }
val (teleportedPeople, localPeople) = namedPeople.partition { personTeleported(it) }
@Composable
fun personRow(person: GeoPerson) {
@ -157,30 +161,19 @@ fun GeohashPeopleList(
)
}
if (localPeople.isNotEmpty()) {
if (namedPeople.isNotEmpty()) {
SheetIconSectionHeader(
icon = Icons.Outlined.LocationOn,
title = stringResource(R.string.section_on_location)
icon = Icons.Outlined.Person,
title = stringResource(R.string.section_people)
)
PeopleCard(people = localPeople, row = { personRow(it) })
}
if (teleportedPeople.isNotEmpty()) {
SheetIconSectionHeader(
icon = Icons.Outlined.Explore,
title = stringResource(R.string.section_teleported_in),
modifier = Modifier.padding(top = if (localPeople.isNotEmpty()) 20.dp else 0.dp)
)
PeopleCard(people = teleportedPeople, row = { personRow(it) })
PeopleCard(people = namedPeople, row = { personRow(it) })
}
if (anonPeople.isNotEmpty()) {
SheetIconSectionHeader(
icon = Icons.Outlined.Person,
title = stringResource(R.string.section_anonymous),
modifier = Modifier.padding(
top = if (localPeople.isNotEmpty() || teleportedPeople.isNotEmpty()) 20.dp else 0.dp
)
icon = Icons.Outlined.HelpOutline,
title = stringResource(R.string.section_anon),
modifier = Modifier.padding(top = if (namedPeople.isNotEmpty()) 20.dp else 0.dp)
)
PeopleCard(people = anonPeople, capped = true, row = { personRow(it) })
}

View File

@ -314,9 +314,8 @@
<!-- People sheet -->
<string name="people_count_title">People (%1$d)</string>
<string name="section_on_location">On location</string>
<string name="section_teleported_in">Teleported in</string>
<string name="section_anonymous">Anonymous</string>
<string name="section_people">People</string>
<string name="section_anon">Anon</string>
<string name="people_n_more">%1$d more\u2026</string>
<string name="grant_location_permission">Grant location permission</string>

View File

@ -104,7 +104,10 @@ class GeohashAnonOrderingTest {
// MARK: - Sectioning
/** Mirrors the grouping in GeohashPeopleList: self is never treated as an anon. */
/**
* Mirrors the two-group split in GeohashPeopleList: peers who announced a nickname, then the
* anons. Self is never treated as an anon.
*/
private fun sections(people: List<GeoPerson>, myId: String?): Triple<List<String>, List<String>, List<String>> {
val isSelf: (GeoPerson) -> Boolean = { myId != null && it.id == myId }
val named = people.filter { isSelf(it) || !it.isAnonymous() }
@ -117,7 +120,7 @@ class GeohashAnonOrderingTest {
}
@Test
fun `anons are grouped out of the named sections entirely`() {
fun `anons are grouped out of the people section entirely`() {
val people = listOf(person("alice"), person("anon1"), person("bob"), person("anon2"))
val (named, anons, _) = sections(people, myId = null)
@ -126,7 +129,7 @@ class GeohashAnonOrderingTest {
}
@Test
fun `self stays in the named sections even when unnamed`() {
fun `self stays in the people section even when unnamed`() {
// You always want to find yourself where you actually are, not buried in the anon section.
val me = person("anon")
val people = listOf(me, person("alice"), person("anon2"))
@ -138,7 +141,7 @@ class GeohashAnonOrderingTest {
}
@Test
fun `a list of only anons yields no named section`() {
fun `a list of only anons yields no people section`() {
val people = (1..4).map { person("anon$it") }
val (named, anons, _) = sections(people, myId = null)