Skip to content

Commit

Permalink
Merged PR 35479: Toon aantal nieuwe berichten bij ingeklapte chat
Browse files Browse the repository at this point in the history
Related work items: #129659
  • Loading branch information
frankfe-amsterdam committed Nov 5, 2024
2 parents 86a84df + d672809 commit c2e9b21
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ - (NSDictionary *)parseEntryToDictionary:(id<SMIConversationEntry>)entry
messageDict[@"payloadId"] = payload.identifier ?: @"";
messageDict[@"inReplyToEntryId"] = payload.inReplyToEntryId ?: @"";
NSString * type = entry.type;
messageDict[@"type"] = type;
messageDict[@"entryType"] = type;
if (format == SMIConversationFormatTypesAttachments) {
id<SMIAttachments> attachmentsPayload = (id<SMIAttachments>)payload;
//https://salesforce-async-messaging.github.io/messaging-in-app-ios/Protocols/SMIAttachments.html#/c:objc(pl)SMIAttachments(py)attachments
Expand Down
15 changes: 15 additions & 0 deletions react-native-salesforce-messaging-in-app/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,20 @@ export enum ConversationEntryFormat {
webview = 'WebView',
}

/**
* @deprecated unverified values, use ConversationEntryFormat
*/
export enum ConversationEntryType {
deliveryAcknowledgement = 'DeliveryAcknowledgement',
message = 'Message',
participantChanged = 'ParticipantChanged',
readAcknowledgement = 'ReadAcknowledgement',
routingResult = 'RoutingResult',
routingWorkResult = 'RoutingWorkResult',
typingIndicator = 'TypingIndicator',
unknownEntry = 'UnknownEntry',
}

export enum ConversationEntryStatus {
sending = 'Sending',
sent = 'Sent',
Expand All @@ -143,6 +157,7 @@ export enum ConversationEntrySenderRole {
export type ConversationEntryBase = {
conversationId: string
entryId: string
entryType: ConversationEntryType
// format: ConversationEntryFormat
/**
* the id of the ConversationEntry this is a reply to
Expand Down
62 changes: 25 additions & 37 deletions src/components/ui/feedback/Badge.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import {AccessibilityProps, StyleSheet, Text, View} from 'react-native'
import {
AccessibilityProps,
Platform,
StyleSheet,
Text,
View,
} from 'react-native'
import {Row} from '@/components/ui/layout/Row'
import {type TestProps} from '@/components/ui/types'
import {useDeviceContext} from '@/hooks/useDeviceContext'
Expand Down Expand Up @@ -28,15 +34,15 @@ export const Badge = ({
variant = 'default',
}: BadgeProps) => {
const {fontScale} = useDeviceContext()
const styles = useThemable(createStyles(fontScale, variant))
const styles = useThemable(createStyles(fontScale, variant, value))

return (
<Row align="start">
<View style={styles.circle}>
<Text
accessibilityLabel={accessibilityLabel}
accessibilityLanguage={accessibilityLanguage}
accessible={!!variantConfig[variant]}
accessible={variant !== 'on-icon'}
numberOfLines={1}
style={styles.text}
testID={testID}>
Expand All @@ -47,58 +53,40 @@ export const Badge = ({
)
}

type VariantConfig = {
[v in OmitUndefined<BadgeProps['variant']>]: {
accessible?: boolean
diameter: number
text: number
}
}

const variantConfig: VariantConfig = {
default: {
diameter: 22,
text: 14,
},
'on-icon': {
accessible: false,
diameter: 16,
text: 12,
},
small: {
diameter: 16,
text: 12,
},
}
const MARGIN_SINGLE_DIGIT = 1.2
const MARGIN_DOUBLE_DIGIT = 1.4

const createStyles =
(
fontScale: Device['fontScale'],
variant: OmitUndefined<BadgeProps['variant']>,
value: number,
) =>
({color, size, text}: Theme) => {
const {diameter, text: textSize} = variantConfig[variant]
({color, text}: Theme) => {
const fontSize = text.fontSize[variant === 'small' ? 'small' : 'body']
const scalesWithFont = variant !== 'on-icon'
const scaleFactor = scalesWithFont ? fontScale : 1
const marginFactor = value > 9 ? MARGIN_DOUBLE_DIGIT : MARGIN_SINGLE_DIGIT

const scaledDiameter = diameter * scaleFactor
const scaledTextSize = textSize * scaleFactor
const scaledDiameter = marginFactor * scaleFactor * fontSize

return StyleSheet.create({
circle: {
flexDirection: 'row',
justifyContent: 'center',
minWidth: scaledDiameter, // Prevent the circle becoming a vertical oval
paddingStart: size.spacing.xs + 0.5, // Nudge center-alignment because of even width
paddingEnd: size.spacing.xs,
alignItems: 'center',
height: scaledDiameter,
width: scaledDiameter,
borderRadius: scaledDiameter / 2,
backgroundColor: color.badge.background,
},
text: {
fontFamily: text.fontFamily.bold,
fontSize: scaledTextSize,
lineHeight: scaledDiameter,
fontFamily:
variant === 'default'
? text.fontFamily.regular
: text.fontFamily.bold,
fontSize,
color: color.text.inverse,
bottom: Platform.OS === 'android' ? 2 : 1 * fontScale,
},
})
}
8 changes: 7 additions & 1 deletion src/modules/chat/components/ChatHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {ScreenTitle} from '@/components/ui/text/ScreenTitle'
import {useToggle} from '@/hooks/useToggle'
import {MeatballsMenu} from '@/modules/chat/assets/MeatballsMenu'
import {ChatMenu} from '@/modules/chat/components/ChatMenu'
import {NewMessageIndicator} from '@/modules/chat/components/NewMessageIndicator'
import {useChat} from '@/modules/chat/slice'
import {useTheme} from '@/themes/useTheme'

Expand Down Expand Up @@ -89,7 +90,12 @@ export const ChatHeader = () => {
testID="ChatHeaderMeatballsMenuButton"
/>
</Animated.View>
<ScreenTitle text="Chat" />
<Row
gutter="xs"
valign="center">
<NewMessageIndicator />
<ScreenTitle text="Chat" />
</Row>
<Animated.View style={expandIconStyle}>
<IconButton
icon={
Expand Down
14 changes: 14 additions & 0 deletions src/modules/chat/components/NewMessageIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {useContext} from 'react'
import {Badge} from '@/components/ui/feedback/Badge'
import {ChatContext} from '@/modules/chat/providers/chat.provider'

export const NewMessageIndicator = () => {
const {newMessagesCount} = useContext(ChatContext)

return newMessagesCount ? (
<Badge
testID="ChatNewMessageIndicatorBadge"
value={newMessagesCount}
/>
) : null
}
22 changes: 22 additions & 0 deletions src/modules/chat/providers/chat.provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@ import {
RemoteConfiguration,
} from 'react-native-salesforce-messaging-in-app/src/types'
import {useCoreConfig} from '@/modules/chat/hooks/useCoreConfig'
import {useChat} from '@/modules/chat/slice'
import {filterOutDeliveryAcknowledgements} from '@/modules/chat/utils/filterOutDeliveryAcknowledgements'
import {isNewMessage} from '@/modules/chat/utils/isNewMessage'

type ChatContextType = {
employeeInChat: boolean
isWaitingForAgent: boolean
messages: ConversationEntry[]
newMessagesCount: number
ready: boolean
remoteConfiguration: RemoteConfiguration | undefined
}

const initialValue: ChatContextType = {
messages: [],
newMessagesCount: 0,
ready: false,
employeeInChat: false,
remoteConfiguration: undefined,
Expand All @@ -33,6 +37,8 @@ type Props = {
}

export const ChatProvider = ({children}: Props) => {
const {isMaximized, isMinimized} = useChat()
const [newMessagesCount, setNewMessagesCount] = useState(0)
const coreConfig = useCoreConfig()
const [conversationId, setConversationId] = useState<string>()
const {
Expand All @@ -48,9 +54,23 @@ export const ChatProvider = ({children}: Props) => {
conversationId,
})

useEffect(() => {
if (isMinimized && isNewMessage(messages[messages.length - 1]?.format)) {
setNewMessagesCount(count => count + 1)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [messages.length])

useEffect(() => {
if (isMaximized) {
setNewMessagesCount(0)
}
}, [isMaximized])

useEffect(() => {
setConversationId(newConversationId ?? conversationId)
}, [conversationId, newConversationId])

useEffect(() => {
if (remoteConfiguration) {
const remoteConfig = JSON.parse(
Expand All @@ -75,6 +95,7 @@ export const ChatProvider = ({children}: Props) => {
messages: isTyping
? [...filterOutDeliveryAcknowledgements(messages), isTyping]
: filterOutDeliveryAcknowledgements(messages),
newMessagesCount,
ready,
employeeInChat,
remoteConfiguration,
Expand All @@ -85,6 +106,7 @@ export const ChatProvider = ({children}: Props) => {
isTyping,
isWaitingForAgent,
messages,
newMessagesCount,
ready,
remoteConfiguration,
],
Expand Down
2 changes: 2 additions & 0 deletions src/modules/chat/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const useChat = () => {
const isOpen = useSelector(selectChatIsOpen)
const visibility = useSelector(selectChatVisibility)
const isMaximized = visibility === ChatVisibility.maximized
const isMinimized = visibility === ChatVisibility.minimized
const minimizedHeight = useSelector(selectChatMinimizedHeight)
const dispatch = useDispatch()

Expand All @@ -105,6 +106,7 @@ export const useChat = () => {
return {
close,
isMaximized,
isMinimized,
isOpen,
open,
maximize,
Expand Down
68 changes: 68 additions & 0 deletions src/modules/chat/utils/isNewMessage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {ConversationEntryFormat} from 'react-native-salesforce-messaging-in-app/src/types'
import {isNewMessage} from '@/modules/chat/utils/isNewMessage'

describe('isNewMessage', () => {
it('should return true for attachments format', () => {
expect(isNewMessage(ConversationEntryFormat.attachments)).toBe(true)
})

it('should return true for carousel format', () => {
expect(isNewMessage(ConversationEntryFormat.carousel)).toBe(true)
})

it('should return true for imageMessage format', () => {
expect(isNewMessage(ConversationEntryFormat.imageMessage)).toBe(true)
})

it('should return true for inputs format', () => {
expect(isNewMessage(ConversationEntryFormat.inputs)).toBe(true)
})

it('should return true for listPicker format', () => {
expect(isNewMessage(ConversationEntryFormat.listPicker)).toBe(true)
})

it('should return true for richLink format', () => {
expect(isNewMessage(ConversationEntryFormat.richLink)).toBe(true)
})

it('should return true for quickReplies format', () => {
expect(isNewMessage(ConversationEntryFormat.quickReplies)).toBe(true)
})

it('should return true for routingResult format', () => {
expect(isNewMessage(ConversationEntryFormat.routingResult)).toBe(true)
})

it('should return true for routingWorkResult format', () => {
expect(isNewMessage(ConversationEntryFormat.routingWorkResult)).toBe(true)
})

it('should return true for selections format', () => {
expect(isNewMessage(ConversationEntryFormat.selections)).toBe(true)
})

it('should return true for text format', () => {
expect(isNewMessage(ConversationEntryFormat.text)).toBe(true)
})

it('should return false for unspecified format', () => {
expect(isNewMessage(ConversationEntryFormat.unspecified)).toBe(false)
})

it('should return false for webview format', () => {
expect(isNewMessage(ConversationEntryFormat.webview)).toBe(false)
})

it('should return false for typingStartedIndicator format', () => {
expect(isNewMessage(ConversationEntryFormat.typingStartedIndicator)).toBe(
false,
)
})

it('should return false for typingStoppedIndicator format', () => {
expect(isNewMessage(ConversationEntryFormat.typingStoppedIndicator)).toBe(
false,
)
})
})
14 changes: 14 additions & 0 deletions src/modules/chat/utils/isNewMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {ConversationEntryFormat} from 'react-native-salesforce-messaging-in-app/src/types'

export const isNewMessage = (format: ConversationEntryFormat) =>
format === ConversationEntryFormat.attachments ||
format === ConversationEntryFormat.carousel ||
format === ConversationEntryFormat.imageMessage ||
format === ConversationEntryFormat.inputs ||
format === ConversationEntryFormat.listPicker ||
format === ConversationEntryFormat.richLink ||
format === ConversationEntryFormat.quickReplies ||
format === ConversationEntryFormat.routingResult ||
format === ConversationEntryFormat.routingWorkResult ||
format === ConversationEntryFormat.selections ||
format === ConversationEntryFormat.text

0 comments on commit c2e9b21

Please sign in to comment.