diff --git a/app/src/main/assets/badges/administrator.png b/app/src/main/assets/badges/administrator.png new file mode 100644 index 00000000..ab4cf796 Binary files /dev/null and b/app/src/main/assets/badges/administrator.png differ diff --git a/app/src/main/assets/badges/clown.png b/app/src/main/assets/badges/clown.png new file mode 100644 index 00000000..5d800687 Binary files /dev/null and b/app/src/main/assets/badges/clown.png differ diff --git a/app/src/main/assets/badges/developer.png b/app/src/main/assets/badges/developer.png new file mode 100644 index 00000000..590f74cd Binary files /dev/null and b/app/src/main/assets/badges/developer.png differ diff --git a/app/src/main/assets/badges/first_100_members.png b/app/src/main/assets/badges/first_100_members.png new file mode 100644 index 00000000..61b6b1ae Binary files /dev/null and b/app/src/main/assets/badges/first_100_members.png differ diff --git a/app/src/main/assets/badges/founder.png b/app/src/main/assets/badges/founder.png new file mode 100644 index 00000000..1f7e6666 Binary files /dev/null and b/app/src/main/assets/badges/founder.png differ diff --git a/app/src/main/assets/badges/gump.png b/app/src/main/assets/badges/gump.png new file mode 100644 index 00000000..18daf3b1 Binary files /dev/null and b/app/src/main/assets/badges/gump.png differ diff --git a/app/src/main/assets/badges/karen.png b/app/src/main/assets/badges/karen.png new file mode 100644 index 00000000..edae58ae Binary files /dev/null and b/app/src/main/assets/badges/karen.png differ diff --git a/app/src/main/assets/badges/supporter.png b/app/src/main/assets/badges/supporter.png new file mode 100644 index 00000000..90c9c9e7 Binary files /dev/null and b/app/src/main/assets/badges/supporter.png differ diff --git a/app/src/main/assets/badges/top-contributor.png b/app/src/main/assets/badges/top-contributor.png new file mode 100644 index 00000000..2e267a20 Binary files /dev/null and b/app/src/main/assets/badges/top-contributor.png differ diff --git a/app/src/main/assets/badges/trusted-seller.png b/app/src/main/assets/badges/trusted-seller.png new file mode 100644 index 00000000..f92e0fce Binary files /dev/null and b/app/src/main/assets/badges/trusted-seller.png differ diff --git a/app/src/main/assets/badges/verifiedvendor.svg b/app/src/main/assets/badges/verifiedvendor.svg new file mode 100644 index 00000000..d8024f5c --- /dev/null +++ b/app/src/main/assets/badges/verifiedvendor.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/chat/zekochat/composables/chat/MemberListItem.kt b/app/src/main/java/chat/zekochat/composables/chat/MemberListItem.kt index 19017820..3ef82b8c 100644 --- a/app/src/main/java/chat/zekochat/composables/chat/MemberListItem.kt +++ b/app/src/main/java/chat/zekochat/composables/chat/MemberListItem.kt @@ -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 { diff --git a/app/src/main/java/chat/zekochat/composables/chat/Message.kt b/app/src/main/java/chat/zekochat/composables/chat/Message.kt index 4bc89300..572cccf8 100644 --- a/app/src/main/java/chat/zekochat/composables/chat/Message.kt +++ b/app/src/main/java/chat/zekochat/composables/chat/Message.kt @@ -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, diff --git a/app/src/main/java/chat/zekochat/composables/chat/UserBadgeList.kt b/app/src/main/java/chat/zekochat/composables/chat/UserBadgeList.kt index d1b09347..d8d5d78f 100644 --- a/app/src/main/java/chat/zekochat/composables/chat/UserBadgeList.kt +++ b/app/src/main/java/chat/zekochat/composables/chat/UserBadgeList.kt @@ -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) diff --git a/app/src/main/java/chat/zekochat/composables/chat/UserVendorBadge.kt b/app/src/main/java/chat/zekochat/composables/chat/UserVendorBadge.kt new file mode 100644 index 00000000..a0592498 --- /dev/null +++ b/app/src/main/java/chat/zekochat/composables/chat/UserVendorBadge.kt @@ -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)), + ) +} + diff --git a/app/src/main/java/chat/zekochat/sheets/UserInfoSheet.kt b/app/src/main/java/chat/zekochat/sheets/UserInfoSheet.kt index 48c8ad00..ec1181d7 100644 --- a/app/src/main/java/chat/zekochat/sheets/UserInfoSheet.kt +++ b/app/src/main/java/chat/zekochat/sheets/UserInfoSheet.kt @@ -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) { diff --git a/app/src/main/res/drawable/badge_administrator.png b/app/src/main/res/drawable/badge_administrator.png new file mode 100644 index 00000000..ab4cf796 Binary files /dev/null and b/app/src/main/res/drawable/badge_administrator.png differ diff --git a/app/src/main/res/drawable/badge_clown.png b/app/src/main/res/drawable/badge_clown.png new file mode 100644 index 00000000..5d800687 Binary files /dev/null and b/app/src/main/res/drawable/badge_clown.png differ diff --git a/app/src/main/res/drawable/badge_developer.png b/app/src/main/res/drawable/badge_developer.png new file mode 100644 index 00000000..590f74cd Binary files /dev/null and b/app/src/main/res/drawable/badge_developer.png differ diff --git a/app/src/main/res/drawable/badge_first_100_members.png b/app/src/main/res/drawable/badge_first_100_members.png new file mode 100644 index 00000000..61b6b1ae Binary files /dev/null and b/app/src/main/res/drawable/badge_first_100_members.png differ diff --git a/app/src/main/res/drawable/badge_founder.png b/app/src/main/res/drawable/badge_founder.png new file mode 100644 index 00000000..1f7e6666 Binary files /dev/null and b/app/src/main/res/drawable/badge_founder.png differ diff --git a/app/src/main/res/drawable/badge_gump.png b/app/src/main/res/drawable/badge_gump.png new file mode 100644 index 00000000..18daf3b1 Binary files /dev/null and b/app/src/main/res/drawable/badge_gump.png differ diff --git a/app/src/main/res/drawable/badge_karen.png b/app/src/main/res/drawable/badge_karen.png new file mode 100644 index 00000000..edae58ae Binary files /dev/null and b/app/src/main/res/drawable/badge_karen.png differ diff --git a/app/src/main/res/drawable/badge_supporter.png b/app/src/main/res/drawable/badge_supporter.png new file mode 100644 index 00000000..90c9c9e7 Binary files /dev/null and b/app/src/main/res/drawable/badge_supporter.png differ diff --git a/app/src/main/res/drawable/badge_top_contributor.png b/app/src/main/res/drawable/badge_top_contributor.png new file mode 100644 index 00000000..2e267a20 Binary files /dev/null and b/app/src/main/res/drawable/badge_top_contributor.png differ diff --git a/app/src/main/res/drawable/badge_trusted_seller.png b/app/src/main/res/drawable/badge_trusted_seller.png new file mode 100644 index 00000000..f92e0fce Binary files /dev/null and b/app/src/main/res/drawable/badge_trusted_seller.png differ diff --git a/app/src/main/res/drawable/badge_verified_vendor.xml b/app/src/main/res/drawable/badge_verified_vendor.xml new file mode 100644 index 00000000..fe230dd5 --- /dev/null +++ b/app/src/main/res/drawable/badge_verified_vendor.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0892fa01..92a05ce6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -465,6 +465,16 @@ sus It\'s Morbin Time + + First 100 Members + Top Contributor + Administrator + Trusted Seller + Karen + Gump + Clown + Verified Vendor + Add Server A server is where the magic happens. Chat with your friends, share memes, build communities, and more. Join by invite code or link