@@ -1,8 +1,8 @@
import { useState } from 'react'
import { View } from 'react-native'
import { type ChatBskyConvoDefs } from '@atproto/api'
import { ChatBskyActorDefs , ChatBskyConvoDefs } from '@atproto/api'
import { Trans , useLingui } from '@lingui/react/macro'
import { StackActions , useNavigation } from '@react-navigation/native'
import { useNavigation } from '@react-navigation/native'
import { useBottomBarOffset } from '#/lib/hooks/useBottomBarOffset'
import { useInitialNumToRender } from '#/lib/hooks/useInitialNumToRender'
@@ -38,7 +38,6 @@ import {
} from '#/components/icons/Bell2'
import { ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon } from '#/components/icons/ChainLink'
import { type Props as SVGIconProps } from '#/components/icons/common'
import { DotGrid3x1_Stroke2_Corner0_Rounded as EllipsisIcon } from '#/components/icons/DotGrid'
import { EditBig_Stroke2_Corner2_Rounded as EditIcon } from '#/components/icons/EditBig'
import { Flag_Stroke2_Corner0_Rounded as FlagIcon } from '#/components/icons/Flag'
import { Lock_Stroke2_Corner0_Rounded as LockIcon } from '#/components/icons/Lock'
@@ -47,18 +46,13 @@ import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast'
import { Text } from '#/components/Typography'
import * as bsky from '#/types/bsky'
import { InviteLinkDialog } from '../components/InviteLinkDialog'
import { AddMembersLink } from './AddMembersLink'
import { Member , MemberPlaceholder } from './Member'
import { MembersAndRequests } from './MembersAndRequests'
import { EditNamePrompt , LeaveChatPrompt , LockChatPrompt } from './prompts'
const dateFormatter = new Intl . DateTimeFormat ( undefined , {
month : 'long' ,
day : 'numeric' ,
year : 'numeric' ,
} )
type Item =
| { type : 'MEMBERS_AND_REQUESTS' ; key : string }
| { type : 'ADD_MEMBERS_LINK' ; key : string }
@@ -119,11 +113,9 @@ function SettingsInner() {
if ( ! isConvoActive ( convoState ) ) {
return (
< Layout.Content >
< View style = { [ a . align_center , a . justify_center , a . flex_1 , a . py_4xl ] } >
< View style = { [ a . flex_1 , a . align_center , a . justify_center ] } >
< Loader size = "xl" / >
< / View >
< / Layout.Content >
)
}
@@ -150,6 +142,19 @@ function keyExtractor(item: Item) {
return item . key
}
function isGroupMember (
member : ChatBskyActorDefs.ProfileViewBasic ,
) : member is GroupConvoMember {
// Kind is missing when the account has been deleted.
return (
member . kind === undefined ||
bsky . dangerousIsType < ChatBskyActorDefs.GroupConvoMember > (
member . kind ,
ChatBskyActorDefs . isGroupConvoMember ,
)
)
}
function GroupSettings ( {
convo ,
} : {
@@ -160,12 +165,12 @@ function GroupSettings({
const { currentAccount } = useSession ( )
const primaryMember = convo ? . primaryMember
const isOwner = ! ! primaryMember && primaryMember . did === currentAccount ? . did
const primaryMember = convo . primaryMember
const isOwner = primaryMember . did === currentAccount ? . did
const { data : memberListData = [ ] , isPending } = useListConvoMembersQuery ( {
convoId : convo.view.id ,
placeholderData : convo?.members ,
placeholderData : convo.members ,
} )
// TODO Need this data in order to populate this array. -dsb
@@ -193,18 +198,19 @@ function GroupSettings({
]
if ( isPending ) {
// should never be pending if we correctly set the query cache data
Array . from ( { length : 5 } ) . forEach ( ( _ , i ) = >
items . push ( {
type : 'CHAT_MEMBER_PLACEHOLDER' ,
items . push (
. . . Array . from ( { length : 5 } , ( _ , i ) = > ( {
type : 'CHAT_MEMBER_PLACEHOLDER' as const ,
key : ` chat-member-placeholder- ${ i } ` ,
} ) ,
} ) ) ,
)
} else {
items . push (
. . . memberListData
. filter ( isGroupMember )
. sort ( ( a , b ) = > {
const aIsOwner = a . did === primaryMember ? . did
const bIsOwner = b . did === primaryMember ? . did
const aIsOwner = a . did === primaryMember . did
const bIsOwner = b . did === primaryMember . did
const aIsSelf = a . did === currentAccount ? . did
const bIsSelf = b . did === currentAccount ? . did
if ( aIsOwner !== bIsOwner ) return aIsOwner ? - 1 : 1
@@ -215,9 +221,9 @@ function GroupSettings({
( profile ) : Item = > ( {
type : 'CHAT_MEMBER' ,
key : profile.did ,
profile : profile as GroupConvoMember ,
profile ,
status :
primaryMember?.did === profile . did
primaryMember.did === profile . did
? 'owner'
: invites . includes ( profile . did )
? 'invited'
@@ -239,16 +245,16 @@ function GroupSettings({
/ >
)
case 'ADD_MEMBERS_LINK' :
return convo ? < AddMembersLink convo = { convo } / > : null
return < AddMembersLink convo = { convo } / >
case 'CHAT_MEMBER' :
return convo ? (
return (
< Member
convo = { convo }
profile = { item . profile }
status = { item . status }
isOwner = { isOwner }
/ >
) : null
)
case 'CHAT_MEMBER_PLACEHOLDER' :
return < MemberPlaceholder / >
default :
@@ -265,13 +271,7 @@ function GroupSettings({
desktopFixedHeight
initialNumToRender = { initialNumToRender }
keyExtractor = { keyExtractor }
ListHeaderComponent = {
convo ? . kind === 'group' ? (
< SettingsHeader convo = { convo } isOwner = { isOwner } / >
) : (
< SettingsHeaderPlaceholder / >
)
}
ListHeaderComponent = { < SettingsHeader convo = { convo } isOwner = { isOwner } / > }
renderItem = { renderItem }
sideBorders = { false }
windowSize = { 11 }
@@ -287,7 +287,7 @@ function SettingsHeader({
isOwner : boolean
} ) {
const t = useTheme ( )
const { t : l } = useLingui ( )
const { i18n , t : l } = useLingui ( )
const navigation = useNavigation < NavigationProp > ( )
@@ -296,16 +296,17 @@ function SettingsHeader({
const lockStatus = convo . details . lockStatus
// TODO Enable this once the feature is working end-to-end. -dsb
// const {joinLink} = convo.details
const isJoinLinkEnabled = false
// const isJoinLinkEnabled =
// isOwner || (!isOwner && joinLink?.enabledStatus === 'enabled')
const { joinLink } = convo . details
const isJoinLinkEnabled = isOwner || joinLink ? . enabledStatus === 'enabled'
// TODO Enable this once the feature is working end-to-end. -dsb
const isReportLinkEnabled = false
const { mutate : editGroupName } = useEditGroupChatName ( convo . view . id , {
const { mutate : editGroupName , isPending : isEditingName } =
useEditGroupChatName ( convo . view . id , {
onSuccess : ( ) = > {
Toast . show ( l ( { message : 'Group chat name updated' , context : 'toast' } ) )
} ,
onError : e = > {
setNewGroupName ( groupName )
logger . error ( 'Failed to edit group chat name' , { message : e } )
@@ -313,7 +314,7 @@ function SettingsHeader({
} ,
} )
const { mutate : muteConvo } = useMuteConvo ( convo . view . id , {
const { mutate : muteConvo , isPending : isMuting } = useMuteConvo ( convo . view . id , {
onSuccess : data = > {
if ( data . convo . muted ) {
Toast . show ( l ( { message : 'Group chat muted' , context : 'toast' } ) )
@@ -327,23 +328,28 @@ function SettingsHeader({
} ,
} )
const { mutate : leaveConvo } = useLeaveConvo ( convo . view . id , {
const { mutate : leaveConvo , isPending : isLeaving } = useLeaveConvo (
convo . view . id ,
{
onSuccess : ( ) = > {
// Settings > Chat > Chat list
navigation . dispatch ( StackActions . pop ( 2 ) )
navigation . replace ( 'Messages' , { animation : 'pop' } )
} ,
onError : e = > {
logger . error ( 'Failed to leave group chat' , { message : e } )
Toast . show ( l ( { message : 'Failed to leave group chat' , context : 'toast' } ) , {
type : 'error' ,
} )
Toast . show (
l ( { message : 'Failed to leave group chat' , context : 'toast' } ) ,
{ type : 'error' } ,
)
} ,
} )
} ,
)
const { mutate : lockConvo } = useLockConvo ( convo . view . id , {
const { mutate : lockConvo , isPending : isLocking } = useLockConvo (
convo . view . id ,
{
onSuccess : data = > {
const kind = data . convo . kind as ChatBskyConvoDefs . GroupConvo
if ( kind . lockStatus === 'locked' ) {
if ( ! ChatBskyConvoDefs . isGroupConvo ( data . convo . kind ) ) return
if ( data . convo . kind . lockStatus === 'locked' ) {
Toast . show ( l ( { message : 'Group chat locked' , context : 'toast' } ) )
} else {
Toast . show ( l ( { message : 'Group chat unlocked' , context : 'toast' } ) )
@@ -358,7 +364,8 @@ function SettingsHeader({
Toast . show ( l ` Failed to unlock group chat ` , { type : 'error' } )
}
} ,
} )
} ,
)
const inviteLinkDialog = Dialog . useDialogControl ( )
const editNamePrompt = Prompt . usePromptControl ( )
@@ -389,9 +396,7 @@ function SettingsHeader({
lockConvo ( { lock : false } )
}
// TODO The creation date doesn't exist yet. -dsb
const showCreatedAt = false
const createdAt = new Date ( )
const createdAt = new Date ( convo . details . createdAt )
const canLockGroupChat = isOwner && lockStatus !== 'locked-permanently'
@@ -412,7 +417,6 @@ function SettingsHeader({
] } >
{ groupName }
< / Text >
{ showCreatedAt ? (
< Text
style = { [
a . text_sm ,
@@ -421,9 +425,15 @@ function SettingsHeader({
a . px_xl ,
t . atoms . text_contrast_high ,
] } >
< Trans > Created { dateFormatter . format ( createdAt ) } < / Trans >
< Trans >
Created { ' ' }
{ i18n . date ( createdAt , {
month : 'long' ,
day : 'numeric' ,
year : 'numeric' ,
} ) }
< / Trans >
< / Text >
) : null }
< View
style = { [
a . flex_row ,
@@ -434,6 +444,7 @@ function SettingsHeader({
] } >
< SettingsButton
color = { convo . view . muted ? 'negative_subtle' : 'secondary' }
disabled = { isMuting }
icon = { convo . view . muted ? BellOffIcon : BellIcon }
label = {
convo . view . muted
@@ -445,6 +456,7 @@ function SettingsHeader({
/ >
{ isOwner ? (
< SettingsButton
disabled = { isEditingName }
icon = { EditIcon }
label = { l ` Edit this group chat’ s name ` }
text = { l ` Edit name ` }
@@ -453,6 +465,7 @@ function SettingsHeader({
) : null }
{ isJoinLinkEnabled ? (
< SettingsButton
disabled = { lockStatus !== 'unlocked' }
icon = { ChainLinkIcon }
label = {
isOwner
@@ -466,6 +479,7 @@ function SettingsHeader({
{ canLockGroupChat ? (
< SettingsButton
color = { lockStatus === 'locked' ? 'negative_subtle' : 'secondary' }
disabled = { isLocking }
icon = { LockIcon }
label = {
lockStatus === 'locked'
@@ -478,18 +492,17 @@ function SettingsHeader({
}
/ >
) : null }
{ isOwner ? null : isReportLinkEnabled ? (
{ ! isOwner && isReportLinkEnabled && (
< SettingsButton
color = "secondary"
icon = { FlagIcon }
label = { l ` Report this group chat ` }
text = { l ` Report ` }
onPress = { handleReportChat }
/ >
) : null }
{ isOwner ? null : (
) }
{ ! isOwner && (
< SettingsButton
color = "secondary"
disabled = { isLeaving }
icon = { ArrowBoxLeftIcon }
label = { l ` Leave this group chat ` }
text = { l ` Leave ` }
@@ -519,45 +532,6 @@ function SettingsHeader({
)
}
function SettingsHeaderPlaceholder() {
const t = useTheme ( )
return (
< View style = { [ a . px_xl , a . py_4xl , a . border_b , t . atoms . border_contrast_low ] } >
< View style = { [ a . align_center , a . justify_center ] } >
< AvatarBubbles profiles = { [ ] } / >
< / View >
< Text
style = { [ a . text_2xl , a . font_bold , a . text_center , a . pt_lg , t . atoms . text ] } >
…
< / Text >
< Text
style = { [
a . text_sm ,
a . text_center ,
a . pt_xs ,
a . px_xl ,
t . atoms . text_contrast_high ,
] } >
…
< / Text >
< View
style = { [
a . flex_row ,
a . align_center ,
a . justify_center ,
a . gap_2xl ,
a . pt_2xl ,
] } >
< SettingsButtonPlaceholder / >
< SettingsButtonPlaceholder / >
< SettingsButtonPlaceholder / >
< SettingsButtonPlaceholder / >
< / View >
< / View >
)
}
function SettingsButton ( {
color = 'secondary' ,
disabled ,
@@ -606,27 +580,3 @@ function SettingsButton({
< / View >
)
}
function SettingsButtonPlaceholder() {
const t = useTheme ( )
const { t : l } = useLingui ( )
return (
< View style = { [ a . align_center ] } >
< Button color = "secondary" size = "large" shape = "round" label = { l ` Loading… ` } >
< ButtonIcon icon = { EllipsisIcon } size = "md" / >
< / Button >
< Text
numberOfLines = { 1 }
style = { [
a . text_xs ,
a . font_medium ,
a . text_center ,
a . pt_xs ,
t . atoms . text ,
] } >
…
< / Text >
< / View >
)
}