import {useMemo, useState} from 'react'
import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native'
import {moderateProfile} from '@atproto/api'
import {plural} from '@lingui/core/macro'
import {Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
import {type NavigationProp} from '#/lib/routes/types'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useGetConvoAvailabilityQuery} from '#/state/queries/messages/get-convo-availability'
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
import {List} from '#/view/com/util/List'
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {AvatarBubbles} from '#/components/AvatarBubbles'
import {Button, type ButtonColor, ButtonIcon} from '#/components/Button'
import type * as Dialog from '#/components/Dialog'
import * as TextField from '#/components/forms/TextField'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeftIcon} from '#/components/icons/ArrowBoxLeft'
import {
Bell2_Stroke2_Corner0_Rounded as BellIcon,
Bell2Off_Stroke2_Corner0_Rounded as BellOffIcon,
} from '#/components/icons/Bell2'
import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronIcon} from '#/components/icons/Chevron'
import {type Props as SVGIconProps} from '#/components/icons/common'
import {DotGrid3x1_Stroke2_Corner0_Rounded as EllipsisIcon} from '#/components/icons/DotGrid'
import {EditBig_Stroke2_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig'
import {Lock_Stroke2_Corner0_Rounded as LockIcon} from '#/components/icons/Lock'
import {Message_Stroke2_Corner0_Rounded as MessageIcon} from '#/components/icons/Message'
import {
Person_Stroke2_Corner2_Rounded as PersonIcon,
PersonX_Stroke2_Corner0_Rounded as PersonXIcon,
} from '#/components/icons/Person'
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
import * as Layout from '#/components/Layout'
import {InlineLinkText} from '#/components/Link'
import * as Menu from '#/components/Menu'
import {type TriggerChildProps} from '#/components/Menu/types'
import * as Prompt from '#/components/Prompt'
import {SubtleHover} from '#/components/SubtleHover'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env'
import type * as bsky from '#/types/bsky'
const MEMBER_LIMIT = 50
const ROW_SPACING = 10
type Item =
| {
type: 'MEMBERS_AND_REQUESTS'
}
| {
type: 'ADD_MEMBERS_LINK'
}
| {
type: 'CHAT_MEMBER'
profile: bsky.profile.AnyProfileView
status: 'admin' | 'member' | 'invited'
}
/**
* TODO This is just layout for now.
*/
export function MessagesConversationSettingsScreen() {
const {gtTablet} = useBreakpoints()
return (
Group chat settings
)
}
function keyExtractor(item: Item) {
return item.type === 'CHAT_MEMBER' ? item.profile.did : item.type
}
function SettingsInner() {
const initialNumToRender = useInitialNumToRender({minItemHeight: 68})
const bottomBarOffset = useBottomBarOffset()
const data: bsky.profile.AnyProfileView[] = []
const invites: string[] = []
const items = [
{
type: 'MEMBERS_AND_REQUESTS',
},
{
type: 'ADD_MEMBERS_LINK',
},
...data.map((profile, index) => ({
type: 'CHAT_MEMBER',
profile,
status:
index === 0
? 'admin'
: invites.includes(profile.did)
? 'invited'
: 'member',
})),
]
function renderItem({item}: {item: Item}) {
switch (item.type) {
case 'MEMBERS_AND_REQUESTS':
return
case 'ADD_MEMBERS_LINK':
return
case 'CHAT_MEMBER':
return
default:
return null
}
}
return (
}
renderItem={renderItem}
sideBorders={false}
windowSize={11}
onEndReachedThreshold={IS_NATIVE ? 1.5 : 0}
/>
)
}
function MembersAndRequests({
memberCount,
requestCount,
}: {
memberCount: number
requestCount: number
}) {
const t = useTheme()
const {t: l} = useLingui()
return (
Members{' '}
{l`${memberCount}/${MEMBER_LIMIT}`}
{l`${plural(requestCount, {
one: '# request',
other: '# requests',
})}`}
)
}
function AddMembersLink() {
const t = useTheme()
return (
[
a.flex_row,
a.align_center,
a.justify_between,
pressed && web({outline: 'none'}),
]}>
{({pressed}) => (
<>
Add members
>
)}
)
}
function Member({
profile,
status,
}: {
profile: bsky.profile.AnyProfileView
status: 'admin' | 'member' | 'invited'
}) {
const navigation = useNavigation()
const t = useTheme()
const {t: l} = useLingui()
const moderationOpts = useModerationOpts()
const moderation = useMemo(
() =>
moderationOpts ? moderateProfile(profile, moderationOpts) : undefined,
[profile, moderationOpts],
)
if (!moderation) return null
const invitedByDisplayName = 'Darrin Loeliger'
const isDeletedAccount = profile.handle === 'missing.invalid'
const displayName = isDeletedAccount
? l`Deleted Account`
: sanitizeDisplayName(
profile.displayName || profile.handle,
moderation.ui('displayName'),
)
let invitedBy: React.ReactNode | null = (
{l`Added by ${invitedByDisplayName}`}
)
let statusBadge: React.ReactNode | null = (
)
switch (status) {
case 'admin':
invitedBy = null
statusBadge =
break
case 'invited':
invitedBy = (
{l`Invited by ${invitedByDisplayName}`}
)
statusBadge =
break
}
return (
{
navigation.navigate('Profile', {name: profile.did})
}}>
{displayName}
{sanitizeHandle(profile.handle, '@')}
{invitedBy ? (
{invitedBy}
) : null}
{statusBadge}
)
}
function StatusBadge({label}: {label: string}) {
const t = useTheme()
return (
{label}
)
}
function StatusButton({
label,
style,
...rest
}: {
label: string
style?: StyleProp
} & TriggerChildProps['props']) {
const t = useTheme()
return (
{label}
)
}
function MemberMenu({
profile,
type,
}: {
profile: bsky.profile.AnyProfileView
type: 'member' | 'invited'
}) {
const navigation = useNavigation()
const t = useTheme()
const {t: l} = useLingui()
const ax = useAnalytics()
const requireEmailVerification = useRequireEmailVerification()
const {data: convoAvailability} = useGetConvoAvailabilityQuery(profile.did)
const {mutate: initiateConvo} = useGetConvoForMembers({
onSuccess: ({convo}) => {
ax.metric('chat:open', {logContext: 'ProfileHeader'})
navigation.navigate('MessagesConversation', {conversation: convo.id})
},
onError: () => {
Toast.show(l`Failed to create conversation`)
},
})
const onPress = () => {
if (!convoAvailability?.canChat) {
return
}
if (convoAvailability.convo) {
ax.metric('chat:open', {logContext: 'ProfileHeader'})
navigation.navigate('MessagesConversation', {
conversation: convoAvailability.convo.id,
})
} else {
ax.metric('chat:create', {logContext: 'ProfileHeader'})
initiateConvo([profile.did])
}
}
const wrappedOnPress = requireEmailVerification(onPress, {
instructions: [
Before you can message another user, you must first verify your email.
,
],
})
const moderationOpts = useModerationOpts()
const moderation = useMemo(
() =>
moderationOpts ? moderateProfile(profile, moderationOpts) : undefined,
[profile, moderationOpts],
)
if (!moderation) return null
const isDeletedAccount = profile.handle === 'missing.invalid'
const displayName = isDeletedAccount
? l`Deleted Account`
: sanitizeDisplayName(
profile.displayName || profile.handle,
moderation.ui('displayName'),
)
return (
{({props, state}) =>
type === 'invited' ? (
) : (
)
}
{
navigation.navigate('Profile', {name: profile.did})
}}>
Go to profile
Message
{type === 'member' ? (
<>
{}}>
Block
{}}>
Remove from chat
>
) : null}
{type === 'invited' ? (
{}}>
Uninvite
) : null}
)
}
function SettingsHeader({profiles}: {profiles: bsky.profile.AnyProfileView[]}) {
const t = useTheme()
const {t: l} = useLingui()
const editNamePrompt = Prompt.usePromptControl()
const inviteLinkPrompt = Prompt.usePromptControl()
const lockChatPrompt = Prompt.usePromptControl()
const [groupName, setGroupName] = useState('Work in Progress')
const [newGroupName, setNewGroupName] = useState(groupName)
const [isMuted, setIsMuted] = useState(false)
const [isLocked, setIsLocked] = useState(false)
const handleToggleMute = () => {
setIsMuted(prev => !prev)
}
const handlePromptName = () => {
editNamePrompt.open()
}
const handleEditName = () => {
setGroupName(newGroupName)
editNamePrompt.close()
}
const handlePromptInviteLink = () => {
inviteLinkPrompt.open()
}
const handleConfirmInviteLink = () => {
inviteLinkPrompt.close()
}
const handlePromptLock = () => {
lockChatPrompt.open()
}
const handleConfirmLock = () => {
setIsLocked(true)
}
const handleUnlock = () => {
setIsLocked(false)
}
return (
<>
{groupName}
Created April 2, 2026
>
)
}
function SettingsButton({
color = 'secondary',
icon,
label,
text,
onPress,
}: {
color?: ButtonColor
icon: React.ComponentType
label: string
text: string
onPress: () => void
}) {
const t = useTheme()
return (
{text}
)
}
type EditNamePromptProps = {
control: Dialog.DialogOuterProps['control']
value: string
onChangeText: (value: string) => void
onConfirm: () => void
}
function EditNamePrompt({
control,
value,
onChangeText,
onConfirm,
}: EditNamePromptProps) {
const {t: l} = useLingui()
return (
<>
Edit group name
>
)
}
function InviteLinkPrompt({
control,
onConfirm,
}: {
control: Dialog.DialogOuterProps['control']
onConfirm: () => void
}) {
const {t: l} = useLingui()
return (
)
}
function LockChatPrompt({
control,
onConfirm,
}: {
control: Dialog.DialogOuterProps['control']
onConfirm: () => void
}) {
const {t: l} = useLingui()
return (
)
}
function SubtleHoverWrapper({children}: React.PropsWithChildren) {
const {
state: hover,
onIn: onHoverIn,
onOut: onHoverOut,
} = useInteractionState()
return (
{children}
)
}