Badges support in the app

This commit is contained in:
Harish Vishwakarma 2026-03-23 19:47:31 +05:30
parent 5fb7e01dbf
commit 4612031785
28 changed files with 191 additions and 59 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_12" data-name="Layer 12" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
<defs>
<style>
.cls-1 {
fill: #652d90;
}
.cls-2 {
fill: #ffde17;
}
</style>
</defs>
<g>
<path class="cls-2" d="M500,224.206c0-74.5345-194.9003-17.3066-150.6048,44.2215-44.2955-61.5281-177.3163,91.9861-102.7888,118.6246-74.5275-26.6385-103.4357,174.4223-22.3381,155.3652-81.0976,19.0571,3.285,203.8292,65.2047,142.7784-61.9197,61.0508,108.9628,170.8703,132.0456,84.8605-23.0828,86.0097,180.0456,86.0097,156.9629,0,23.0828,86.0097,193.9653-23.8098,132.0456-84.8605,61.9197,61.0508,146.3023-123.7213,65.2047-142.7784,81.0976,19.0571,52.1894-182.0037-22.3381-155.3652,74.5275-26.6385-58.4933-180.1527-102.7888-118.6246,44.2955-61.5281-150.6048-118.756-150.6048-44.2215Z"/>
<polygon class="cls-1" points="680.2813 428.2256 624.5839 374.5166 447.929 557.7138 379.2446 491.4722 325.5312 547.1663 449.8956 667.1077 453.7317 663.1302 453.7493 663.1472 680.2813 428.2256"/>
</g>
<g>
<path class="cls-2" d="M500,224.206c0-74.5345-194.9003-17.3066-150.6048,44.2215-44.2955-61.5281-177.3163,91.9861-102.7888,118.6246-74.5275-26.6385-103.4357,174.4223-22.3381,155.3652-81.0976,19.0571,3.285,203.8292,65.2047,142.7784-61.9197,61.0508,108.9628,170.8703,132.0456,84.8605-23.0828,86.0097,180.0456,86.0097,156.9629,0,23.0828,86.0097,193.9653-23.8098,132.0456-84.8605,61.9197,61.0508,146.3023-123.7213,65.2047-142.7784,81.0976,19.0571,52.1894-182.0037-22.3381-155.3652,74.5275-26.6385-58.4933-180.1527-102.7888-118.6246,44.2955-61.5281-150.6048-118.756-150.6048-44.2215Z"/>
<polygon class="cls-1" points="680.2813 428.2256 624.5839 374.5166 447.929 557.7138 379.2446 491.4722 325.5312 547.1663 449.8956 667.1077 453.7317 663.1302 453.7493 663.1472 680.2813 428.2256"/>
</g>
<g>
<path class="cls-2" d="M500,224.206c0-74.5345-194.9003-17.3066-150.6048,44.2215-44.2955-61.5281-177.3163,91.9861-102.7888,118.6246-74.5275-26.6385-103.4357,174.4223-22.3381,155.3652-81.0976,19.0571,3.285,203.8292,65.2047,142.7784-61.9197,61.0508,108.9628,170.8703,132.0456,84.8605-23.0828,86.0097,180.0456,86.0097,156.9629,0,23.0828,86.0097,193.9653-23.8098,132.0456-84.8605,61.9197,61.0508,146.3023-123.7213,65.2047-142.7784,81.0976,19.0571,52.1894-182.0037-22.3381-155.3652,74.5275-26.6385-58.4933-180.1527-102.7888-118.6246,44.2955-61.5281-150.6048-118.756-150.6048-44.2215Z"/>
<polygon class="cls-1" points="680.2813 428.2256 624.5839 374.5166 447.929 557.7138 379.2446 491.4722 325.5312 547.1663 449.8956 667.1077 453.7317 663.1302 453.7493 663.1472 680.2813 428.2256"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -5,9 +5,15 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.ui.Modifier
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import chat.zekochat.api.PeptideAPI
import chat.zekochat.api.internals.BrushCompat
import chat.zekochat.api.internals.Roles
@ -44,16 +50,30 @@ fun MemberListItem(
colors = TransparentListItemColours,
modifier = modifier,
headlineContent = {
Text(
text = member?.nickname
?: user?.displayName
?: user?.username
?: user?.id
?: userId,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = LocalTextStyle.current.copy(brush = colour),
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = member?.nickname
?: user?.displayName
?: user?.username
?: user?.id
?: userId,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = LocalTextStyle.current.copy(brush = colour),
modifier = Modifier.weight(1f)
)
if (hasVerifiedVendor(user?.badges)) {
Spacer(modifier = Modifier.width(4.dp))
UserVendorBadge(
badges = user?.badges,
size = 16.dp
)
}
}
},
supportingContent = {
user?.status?.text?.let {

View File

@ -417,6 +417,14 @@ fun Message(
)
)
if (hasVerifiedVendor(author.badges)) {
Spacer(modifier = Modifier.width(5.dp))
UserVendorBadge(
badges = author.badges,
size = 16.dp
)
}
InlineBadges(
bot = author.bot != null && message.masquerade == null,
bridge = message.masquerade != null && author.bot != null,

View File

@ -50,77 +50,70 @@ fun BadgeListEntry(badge: UserBadges) {
UserBadges.Developer.value -> {
BadgeListEntryTemplate(
label = stringResource(R.string.user_badge_developer),
icon = painterResource(R.drawable.user_badge_developer)
icon = painterResource(R.drawable.badge_developer)
)
}
UserBadges.Translator.value -> {
BadgeListEntryTemplate(
label = stringResource(R.string.user_badge_translator),
icon = painterResource(R.drawable.user_badge_translator)
label = stringResource(R.string.user_badge_first_100_members),
icon = painterResource(R.drawable.badge_first_100_members)
)
}
UserBadges.Supporter.value -> {
BadgeListEntryTemplate(
label = stringResource(R.string.user_badge_supporter),
icon = painterResource(R.drawable.user_badge_supporter)
icon = painterResource(R.drawable.badge_supporter)
)
}
UserBadges.ResponsibleDisclosure.value -> {
BadgeListEntryTemplate(
label = stringResource(R.string.user_badge_responsible_disclosure),
icon = painterResource(R.drawable.user_badge_disclosure)
label = stringResource(R.string.user_badge_trusted_seller),
icon = painterResource(R.drawable.badge_trusted_seller)
)
}
UserBadges.Founder.value -> {
BadgeListEntryTemplate(
label = stringResource(R.string.user_badge_founder),
icon = painterResource(R.drawable.user_badge_founder)
icon = painterResource(R.drawable.badge_founder)
)
}
UserBadges.PlatformModeration.value -> {
BadgeListEntryTemplate(
label = stringResource(R.string.user_badge_platform_moderation),
icon = painterResource(R.drawable.user_badge_moderation)
)
}
UserBadges.ActiveSupporter.value -> {
BadgeListEntryTemplate(
label = stringResource(R.string.user_badge_active_supporter),
icon = painterResource(R.drawable.icn_emoji_people_24dp)
label = stringResource(R.string.user_badge_administrator),
icon = painterResource(R.drawable.badge_administrator)
)
}
UserBadges.Paw.value -> {
BadgeListEntryTemplate(
label = stringResource(R.string.user_badge_paw),
icon = painterResource(R.drawable.user_badge_paw)
label = stringResource(R.string.user_badge_clown),
icon = painterResource(R.drawable.badge_clown)
)
}
UserBadges.EarlyAdopter.value -> {
BadgeListEntryTemplate(
label = stringResource(R.string.user_badge_early_adopter),
icon = painterResource(R.drawable.user_badge_early_adopter)
label = stringResource(R.string.user_badge_top_contributor),
icon = painterResource(R.drawable.badge_top_contributor)
)
}
UserBadges.ReservedRelevantJokeBadge1.value -> {
BadgeListEntryTemplate(
label = stringResource(R.string.user_badge_reserved_relevant_joke_badge_1),
icon = painterResource(R.drawable.user_badge_reserved_relevant_one)
label = stringResource(R.string.user_badge_karen),
icon = painterResource(R.drawable.badge_karen)
)
}
UserBadges.ReservedRelevantJokeBadge2.value -> {
BadgeListEntryTemplate(
label = stringResource(R.string.user_badge_reserved_relevant_joke_badge_2),
icon = painterResource(R.drawable.user_badge_reserved_relevant_two)
label = stringResource(R.string.user_badge_gump),
icon = painterResource(R.drawable.badge_gump)
)
}
}
@ -132,7 +125,7 @@ fun UserBadgeList(badges: Long) {
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
UserBadges.entries
.filter { badges has it }
.filter { it != UserBadges.ActiveSupporter && (badges has it) }
.forEach { badge ->
BadgeListEntry(badge)
}
@ -147,34 +140,34 @@ fun UserBadgeRow(badges: Long) {
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
UserBadges.entries
.filter { badges has it }
.filter { it != UserBadges.ActiveSupporter && (badges has it) }
.forEach { badge ->
Image(
painter = when (badge) {
UserBadges.Developer -> painterResource(R.drawable.user_badge_developer)
UserBadges.Translator -> painterResource(R.drawable.user_badge_translator)
UserBadges.Supporter -> painterResource(R.drawable.user_badge_supporter)
UserBadges.ResponsibleDisclosure -> painterResource(R.drawable.user_badge_disclosure)
UserBadges.Founder -> painterResource(R.drawable.user_badge_founder)
UserBadges.PlatformModeration -> painterResource(R.drawable.user_badge_moderation)
UserBadges.Developer -> painterResource(R.drawable.badge_developer)
UserBadges.Translator -> painterResource(R.drawable.badge_first_100_members)
UserBadges.Supporter -> painterResource(R.drawable.badge_supporter)
UserBadges.ResponsibleDisclosure -> painterResource(R.drawable.badge_trusted_seller)
UserBadges.Founder -> painterResource(R.drawable.badge_founder)
UserBadges.PlatformModeration -> painterResource(R.drawable.badge_administrator)
UserBadges.ActiveSupporter -> painterResource(R.drawable.icn_emoji_people_24dp)
UserBadges.Paw -> painterResource(R.drawable.user_badge_paw)
UserBadges.EarlyAdopter -> painterResource(R.drawable.user_badge_early_adopter)
UserBadges.ReservedRelevantJokeBadge1 -> painterResource(R.drawable.user_badge_reserved_relevant_one)
UserBadges.ReservedRelevantJokeBadge2 -> painterResource(R.drawable.user_badge_reserved_relevant_two)
UserBadges.Paw -> painterResource(R.drawable.badge_clown)
UserBadges.EarlyAdopter -> painterResource(R.drawable.badge_top_contributor)
UserBadges.ReservedRelevantJokeBadge1 -> painterResource(R.drawable.badge_karen)
UserBadges.ReservedRelevantJokeBadge2 -> painterResource(R.drawable.badge_gump)
},
contentDescription = when (badge) {
UserBadges.Developer -> stringResource(R.string.user_badge_developer)
UserBadges.Translator -> stringResource(R.string.user_badge_translator)
UserBadges.Translator -> stringResource(R.string.user_badge_first_100_members)
UserBadges.Supporter -> stringResource(R.string.user_badge_supporter)
UserBadges.ResponsibleDisclosure -> stringResource(R.string.user_badge_responsible_disclosure)
UserBadges.ResponsibleDisclosure -> stringResource(R.string.user_badge_trusted_seller)
UserBadges.Founder -> stringResource(R.string.user_badge_founder)
UserBadges.PlatformModeration -> stringResource(R.string.user_badge_platform_moderation)
UserBadges.PlatformModeration -> stringResource(R.string.user_badge_administrator)
UserBadges.ActiveSupporter -> stringResource(R.string.user_badge_active_supporter)
UserBadges.Paw -> stringResource(R.string.user_badge_paw)
UserBadges.EarlyAdopter -> stringResource(R.string.user_badge_early_adopter)
UserBadges.ReservedRelevantJokeBadge1 -> stringResource(R.string.user_badge_reserved_relevant_joke_badge_1)
UserBadges.ReservedRelevantJokeBadge2 -> stringResource(R.string.user_badge_reserved_relevant_joke_badge_2)
UserBadges.Paw -> stringResource(R.string.user_badge_clown)
UserBadges.EarlyAdopter -> stringResource(R.string.user_badge_top_contributor)
UserBadges.ReservedRelevantJokeBadge1 -> stringResource(R.string.user_badge_karen)
UserBadges.ReservedRelevantJokeBadge2 -> stringResource(R.string.user_badge_gump)
},
modifier = Modifier
.size(32.dp)

View File

@ -0,0 +1,40 @@
package chat.zekochat.composables.chat
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import chat.zekochat.R
import chat.zekochat.api.schemas.UserBadges
/**
* Compact "Verified Vendor" indicator shown next to usernames.
*
* Web mapping notes:
* - ResponsibleDisclosure (8) and ReservedRelevantJokeBadge1 (512) both trigger the vendor badge.
*/
fun hasVerifiedVendor(badges: Long?): Boolean {
val b = badges ?: 0L
return (b and UserBadges.ResponsibleDisclosure.value) != 0L ||
(b and UserBadges.ReservedRelevantJokeBadge1.value) != 0L
}
@Composable
fun UserVendorBadge(
badges: Long?,
modifier: Modifier = Modifier,
size: Dp = 16.dp,
) {
if (!hasVerifiedVendor(badges)) return
Image(
painter = painterResource(R.drawable.badge_verified_vendor),
contentDescription = stringResource(R.string.user_badge_verified_vendor),
modifier = modifier.then(Modifier.size(size)),
)
}

View File

@ -63,6 +63,8 @@ import chat.zekochat.api.settings.NotificationType
import chat.zekochat.api.settings.SyncedSettings
import chat.zekochat.callbacks.Action
import chat.zekochat.callbacks.ActionChannel
import chat.zekochat.composables.chat.UserBadgeRow
import chat.zekochat.composables.chat.UserVendorBadge
import chat.zekochat.composables.generic.NonIdealState
import chat.zekochat.composables.generic.SheetButton
import chat.zekochat.composables.generic.UserAvatar
@ -193,13 +195,29 @@ fun UserInfoSheet(
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
resolvedUser!!.username?.let { uname ->
Text(
text = "@$uname",
style = MaterialTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp)
) {
Text(
text = "@$uname",
style = MaterialTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
UserVendorBadge(
badges = resolvedUser!!.badges,
size = 16.dp
)
}
}
val badgesValue = resolvedUser!!.badges ?: 0L
if (badgesValue > 0L) {
Spacer(Modifier.height(8.dp))
UserBadgeRow(badges = badgesValue)
}
}
if (Experiments.enableServerIdentityOptions.isEnabled) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="1000"
android:viewportHeight="1000">
<!-- Yellow star/pill shape (from verifiedvendor.svg). -->
<path
android:fillColor="#FFDE17"
android:pathData="M500,224.206c0-74.5345-194.9003-17.3066-150.6048,44.2215-44.2955-61.5281-177.3163,91.9861-102.7888,118.6246-74.5275-26.6385-103.4357,174.4223-22.3381,155.3652-81.0976,19.0571,3.285,203.8292,65.2047,142.7784-61.9197,61.0508,108.9628,170.8703,132.0456,84.8605-23.0828,86.0097,180.0456,86.0097,156.9629,0,23.0828,86.0097,193.9653-23.8098,132.0456-84.8605,61.9197,61.0508,146.3023-123.7213,65.2047-142.7784,81.0976,19.0571,52.1894-182.0037-22.3381-155.3652,74.5275-26.6385-58.4933-180.1527-102.7888-118.6246,44.2955-61.5281-150.6048-118.756-150.6048-44.2215Z" />
<!-- Purple highlight polygon (from verifiedvendor.svg). -->
<path
android:fillColor="#652D90"
android:pathData="M680.2813,428.2256L624.5839,374.5166L447.929,557.7138L379.2446,491.4722L325.5312,547.1663L449.8956,667.1077L453.7317,663.1302L453.7493,663.1472Z" />
</vector>

View File

@ -465,6 +465,16 @@
<string name="user_badge_reserved_relevant_joke_badge_1" translatable="false">sus</string>
<string name="user_badge_reserved_relevant_joke_badge_2" translatable="false">It\'s Morbin Time</string>
<!-- Web badge mapping labels (used as accessibility/tooltips on Android). -->
<string name="user_badge_first_100_members" translatable="false">First 100 Members</string>
<string name="user_badge_top_contributor" translatable="false">Top Contributor</string>
<string name="user_badge_administrator" translatable="false">Administrator</string>
<string name="user_badge_trusted_seller" translatable="false">Trusted Seller</string>
<string name="user_badge_karen" translatable="false">Karen</string>
<string name="user_badge_gump" translatable="false">Gump</string>
<string name="user_badge_clown" translatable="false">Clown</string>
<string name="user_badge_verified_vendor" translatable="false">Verified Vendor</string>
<string name="add_server_sheet_step_0_title">Add Server</string>
<string name="add_server_sheet_step_0_description">A server is where the magic happens. Chat with your friends, share memes, build communities, and more.</string>
<string name="add_server_sheet_step_0_join">Join by invite code or link</string>