any updates here? i’m also noticing weird behavior surrounding the typing indicators. Mainly, if a user is typing in my web app in a group channel, and someone in that channel has the mobile app open, but not on their messaging screen, the typing indicator shows indefinitely if they navigate to the messaging screen shortly after. Whereas if they are already on the messaging screen when the typing starts, the typing indicator will go away after a certain timeout when the typing on the web app stops.
This first video illustrates the behavior when the mobile user is on the messaging screen when the typing begins and ends.
This second video illustrates the behavior when the mobile user is away from the messaging screen when the type begins, and then navigates to the messaging screen.
As you can see, the typing indicator never disappears even once the typing has been stopped for the same amount of time as it was in the previous video.
Here is the relevant code for displaying the typing indicator in the app:
export const TypingIndicator: FC = () => {
const isConnected = useAppSelector(selectIsConnected)
const [typingUsers, setTypingUsers] = useState<string[]>([])
useEffect(() => {
if (!isConnected) return
const handler = new GroupChannelHandler({
onTypingStatusUpdated(channel) {
setTypingUsers(channel.getTypingUsers().map(({ nickname }) => nickname))
},
})
sb.groupChannel.addGroupChannelHandler(
'TYPING_USERS_CHANNEL_HANDLER',
handler,
)
return () => {
setTypingUsers([])
sb.groupChannel.removeGroupChannelHandler('TYPING_USERS_CHANNEL_HANDLER')
}
}, [isConnected])
if (!typingUsers.length) return null
return (
<>
<View style={styles.container}>
<View style={[styles.dot, { backgroundColor: dotColor[0] }]} />
<View style={[styles.dot, { backgroundColor: dotColor[1] }]} />
<View style={[styles.dot, { backgroundColor: dotColor[2] }]} />
</View>
<Text style={styles.text}>{typingUsers.join('\n')}</Text>
</>
)
}
and the code that triggers the event in the web application
export const MessageInput: React.FC = () => {
const classes = useStyles()
const [messageText, setMessageText] = useState('')
const selectedChannel = useAppSelector(selectSelectedChannel)
const dispatch = useAppDispatch()
const hasValidStagedMessage = !!messageText.trim()
if (!selectedChannel) return null
const onClick = async () => {
dispatch(sendMessage({ url: selectedChannel.url, text: messageText }))
setMessageText('')
await sendStopTyping()
}
const sendStartTyping = useCallback(
throttle(
() => {
sb.groupChannel
.buildGroupChannelFromSerializedData(selectedChannel)
.startTyping()
},
TYPING_UPDATE_THROTTLE_MS,
{ trailing: false },
),
[],
)
const sendStopTyping = async () =>
await sb.groupChannel
.buildGroupChannelFromSerializedData(selectedChannel)
.endTyping()
return (
<div className={classes.container}>
<TextField
value={messageText}
multiline
fullWidth
size="small"
variant="outlined"
placeholder="Type a message"
InputProps={{
className: classes.input,
fullWidth: true,
endAdornment: (
<IconButton
size="small"
disabled={!selectedChannel || !hasValidStagedMessage}
onClick={onClick}>
<SendIcon fontSize="small" />
</IconButton>
),
}}
onChange={async event => {
setMessageText(event.currentTarget.value)
event.currentTarget.value ? sendStartTyping() : sendStopTyping()
}}
onKeyDown={e => {
if (!e.shiftKey && e.keyCode === 13) {
e.preventDefault()
if (!hasValidStagedMessage) return
onClick()
}
}}
/>
</div>
)
}