Skip to content

Commit 290758e

Browse files
authored
Merge pull request #213 from sendbird/fix/rtl-qa
fix: RTL QA
2 parents 9c558e9 + 58f2d92 commit 290758e

File tree

12 files changed

+57
-34
lines changed

12 files changed

+57
-34
lines changed

packages/uikit-react-native-foundation/src/components/Text/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React from 'react';
22
import { I18nManager, Text as RNText, TextProps as RNTextProps, StyleSheet, TextStyle } from 'react-native';
33

4+
import { isStartsWithRTL } from '@sendbird/uikit-utils';
5+
46
import useUIKitTheme from '../../theme/useUIKitTheme';
57
import type { TypoName, UIKitTheme } from '../../types';
6-
import { isStartsWithRTL } from './isStartsWithRTL';
78

89
export interface RTLTextAlignSupportProps {
910
/**
@@ -33,6 +34,10 @@ const Text = ({ children, color, style, supportRTLAlign = true, originalText, ..
3334
]) as TextStyle;
3435

3536
const textAlign = (() => {
37+
if (textStyle.textAlign && textStyle.textAlign !== 'left' && textStyle.textAlign !== 'right') {
38+
return textStyle.textAlign;
39+
}
40+
3641
if (I18nManager.isRTL && supportRTLAlign) {
3742
if (
3843
(originalText && isStartsWithRTL(originalText)) ||
@@ -43,6 +48,7 @@ const Text = ({ children, color, style, supportRTLAlign = true, originalText, ..
4348
return I18nManager.doLeftAndRightSwapInRTL ? 'right' : 'left';
4449
}
4550
}
51+
4652
if (textStyle.textAlign) return textStyle.textAlign;
4753
return undefined;
4854
})();

packages/uikit-react-native-foundation/src/components/TextInput/index.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import React from 'react';
22
import { I18nManager, TextInput as RNTextInput, StyleSheet, TextInputProps, TextStyle } from 'react-native';
33

4+
import { isStartsWithRTL } from '@sendbird/uikit-utils';
5+
46
import createStyleSheet from '../../styles/createStyleSheet';
57
import useUIKitTheme from '../../theme/useUIKitTheme';
68
import type { UIKitTheme } from '../../types';
79
import { RTLTextAlignSupportProps } from '../Text';
8-
import { isStartsWithRTL } from '../Text/isStartsWithRTL';
910

1011
type Props = {
1112
variant?: keyof UIKitTheme['colors']['ui']['input'];
@@ -34,6 +35,10 @@ const TextInput = React.forwardRef<RNTextInput, Props>(function TextInput(
3435
]) as TextStyle;
3536

3637
const textAlign = (() => {
38+
if (textStyle.textAlign && textStyle.textAlign !== 'left' && textStyle.textAlign !== 'right') {
39+
return textStyle.textAlign;
40+
}
41+
3742
if (I18nManager.isRTL && supportRTLAlign) {
3843
const text = originalText || props.value || props.placeholder;
3944
// Note: TextInput is not affected by doLeftAndRightSwapInRTL
@@ -44,7 +49,7 @@ const TextInput = React.forwardRef<RNTextInput, Props>(function TextInput(
4449
}
4550
}
4651

47-
if (textStyle.textAlign) textStyle.textAlign;
52+
if (textStyle.textAlign) return textStyle.textAlign;
4853
return undefined;
4954
})();
5055

packages/uikit-react-native/src/components/GroupChannelMessageRenderer/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
122122
let seekFinished = !shouldSeekToTime;
123123

124124
const forPlayback = playerService.addPlaybackListener(({ stopped, currentTime, duration }) => {
125-
voiceMessageStatusManager.setCurrentTime(message.channelUrl, message.messageId, currentTime);
125+
voiceMessageStatusManager.setCurrentTime(message.channelUrl, message.messageId, stopped ? 0 : currentTime);
126126
if (seekFinished) {
127127
setState((prevState) => ({ ...prevState, currentTime: stopped ? 0 : currentTime, duration }));
128128
}

packages/uikit-react-native/src/components/ThreadParentMessageRenderer/ThreadParentMessage.file.voice.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type VoiceFileMessageState = {
1616
type Props = ThreadParentMessageRendererProps<{
1717
durationMetaArrayKey?: string;
1818
onUnmount: () => void;
19+
initialCurrentTime?: number;
1920
}>;
2021

2122
const ThreadParentMessageFileVoice = (props: Props) => {
@@ -25,6 +26,7 @@ const ThreadParentMessageFileVoice = (props: Props) => {
2526
parentMessage,
2627
durationMetaArrayKey = 'KEY_VOICE_MESSAGE_DURATION',
2728
onUnmount,
29+
initialCurrentTime,
2830
} = props;
2931

3032
const fileMessage: SendbirdFileMessage = parentMessage as SendbirdFileMessage;
@@ -38,7 +40,7 @@ const ThreadParentMessageFileVoice = (props: Props) => {
3840
const initialDuration = value ? parseInt(value, 10) : 0;
3941
return {
4042
status: 'paused',
41-
currentTime: 0,
43+
currentTime: initialCurrentTime || 0,
4244
duration: initialDuration,
4345
};
4446
});

packages/uikit-react-native/src/components/ThreadParentMessageRenderer/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export type ThreadParentMessageRendererProps<AdditionalProps = unknown> = {
3434
const ThreadParentMessageRenderer = (props: ThreadParentMessageRendererProps) => {
3535
const handlers = useSBUHandlers();
3636
const playerUnsubscribes = useRef<(() => void)[]>([]);
37-
const { sbOptions, currentUser, mentionManager } = useSendbirdChat();
37+
const { sbOptions, currentUser, mentionManager, voiceMessageStatusManager } = useSendbirdChat();
3838
const { palette } = useUIKitTheme();
3939
const { mediaService, playerService } = usePlatformService();
4040
const parentMessage = props.parentMessage;
@@ -68,6 +68,11 @@ const ThreadParentMessageRenderer = (props: ThreadParentMessageRendererProps) =>
6868
let seekFinished = !shouldSeekToTime;
6969

7070
const forPlayback = playerService.addPlaybackListener(({ stopped, currentTime, duration }) => {
71+
voiceMessageStatusManager.setCurrentTime(
72+
parentMessage.channelUrl,
73+
parentMessage.messageId,
74+
stopped ? 0 : currentTime,
75+
);
7176
if (seekFinished) {
7277
setState((prevState) => ({ ...prevState, currentTime: stopped ? 0 : currentTime, duration }));
7378
}
@@ -177,6 +182,10 @@ const ThreadParentMessageRenderer = (props: ThreadParentMessageRendererProps) =>
177182
return (
178183
<ThreadParentMessageFileVoice
179184
durationMetaArrayKey={VOICE_MESSAGE_META_ARRAY_DURATION_KEY}
185+
initialCurrentTime={voiceMessageStatusManager.getCurrentTime(
186+
parentMessage.channelUrl,
187+
parentMessage.messageId,
188+
)}
180189
onUnmount={() => {
181190
if (isVoiceMessage(parentMessage) && playerService.uri === parentMessage.url) {
182191
resetPlayer().catch((_) => {});

packages/uikit-react-native/src/containers/SendbirdUIKitContainer.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,6 @@ const useConfigInstance = ({ imageCompression, userMention, voiceMessage }: Send
411411
debounceMills: userMention?.debounceMills ?? MentionConfig.DEFAULT.DEBOUNCE_MILLS,
412412
delimiter: MentionConfig.DEFAULT.DELIMITER,
413413
trigger: MentionConfig.DEFAULT.TRIGGER,
414-
forceTriggerLeftInRTL: MentionConfig.DEFAULT.FORCE_TRIGGER_LEFT_IN_RTL,
415414
});
416415
}, [userMention?.mentionLimit, userMention?.suggestionLimit, userMention?.debounceMills]);
417416

packages/uikit-react-native/src/fragments/createGroupChannelFragment.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
121121
await recorderService.reset().catch(() => {});
122122
};
123123
const _onPressHeaderLeft = useFreshCallback(async () => {
124-
voiceMessageStatusManager.clear();
125124
await onBlurFragment();
126125
onPressHeaderLeft();
127126
});
@@ -144,6 +143,7 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
144143

145144
useEffect(() => {
146145
return () => {
146+
voiceMessageStatusManager.clear();
147147
onBlurFragment();
148148
};
149149
}, []);

packages/uikit-react-native/src/libs/MentionConfig.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,6 @@ export interface MentionConfigInterface {
44
debounceMills: number;
55
delimiter: string;
66
trigger: string;
7-
/**
8-
* This configuration keeps the trigger positioned to the left in RTL mode, instead of being placed after `username@`.
9-
* @example
10-
* RTL: `@username`
11-
* LTR: `@username`
12-
*/
13-
forceTriggerLeftInRTL: boolean;
147
}
158

169
class MentionConfig {
@@ -20,7 +13,6 @@ class MentionConfig {
2013
DEBOUNCE_MILLS: 300,
2114
DELIMITER: ' ',
2215
TRIGGER: '@',
23-
FORCE_TRIGGER_LEFT_IN_RTL: true,
2416
};
2517
constructor(private _config: MentionConfigInterface) {}
2618

@@ -43,10 +35,6 @@ class MentionConfig {
4335
get trigger() {
4436
return this._config.trigger;
4537
}
46-
47-
get forceTriggerLeftInRTL() {
48-
return this._config.forceTriggerLeftInRTL;
49-
}
5038
}
5139

5240
export default MentionConfig;

packages/uikit-react-native/src/libs/MentionManager.tsx

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import React from 'react';
2-
import { I18nManager } from 'react-native';
32

43
import { Text, createStyleSheet } from '@sendbird/uikit-react-native-foundation';
54
import type { SendbirdFileMessage, SendbirdUser, SendbirdUserMessage } from '@sendbird/uikit-utils';
6-
import { createMentionTemplateRegex, replaceWithRegex } from '@sendbird/uikit-utils';
5+
import { createMentionTemplateRegex, isEndsWithRTL, replaceWithRegex } from '@sendbird/uikit-utils';
76

87
import type { MentionedUser, Range } from '../types';
98
import type { MentionConfigInterface } from './MentionConfig';
@@ -22,12 +21,18 @@ class MentionManager {
2221
this._templateRegex = createMentionTemplateRegex(this.config.trigger);
2322
}
2423

25-
get triggerDirPrefixForDisplay() {
26-
if (this.config.forceTriggerLeftInRTL) {
27-
return SPAN_DIRECTION.LRM;
28-
}
29-
return I18nManager.isRTL ? SPAN_DIRECTION.RLM : SPAN_DIRECTION.LRM;
30-
}
24+
// Note: When the input starts in LTR and the mentioned user's name is in RTL, it appears as "Hello @{cibarA}."
25+
// If typing continues in RTL, the mention is rendered as: "Hello @{txeTlanoitiddA}{cibarA}."
26+
//
27+
// Conversely, if the input starts in RTL and the mentioned user's name is in LTR, it appears as "{Eng}@ cibarA."
28+
// If typing continues, it is rendered as: "{Eng}{AdditionalText}@ cibarA."
29+
//
30+
// While this follows the natural text direction, it can make mentions harder to distinguish.
31+
// To address this, we use the RLM or LRM Unicode characters to reset subsequent spans based on the last text string of the user's name.
32+
// By applying this trick, the result will be displayed as "Hello @{cibarA} {txeTlanoitiddA}" or "{AdditionalText} {Eng}@ cibarA," ensuring the mention block remains clearly distinguishable.
33+
getDirectionOfNextSpan = (name: string) => {
34+
return isEndsWithRTL(name) ? SPAN_DIRECTION.LRM : SPAN_DIRECTION.RLM;
35+
};
3136

3237
public rangeHelpers = {
3338
inRangeUnderOver(start: number, num: number, end: number) {
@@ -131,9 +136,9 @@ class MentionManager {
131136
* @description User to @user.nickname text format
132137
* */
133138
public asMentionedMessageText = (user: SendbirdUser, delimiter = false) => {
134-
const prefix = this.triggerDirPrefixForDisplay;
139+
const prefix = '';
135140
const content = `${this.config.trigger}${user.nickname}`;
136-
const postfix = delimiter ? this.config.delimiter : '';
141+
const postfix = this.getDirectionOfNextSpan(user.nickname) + (delimiter ? this.config.delimiter : '');
137142

138143
return prefix + content + postfix;
139144
};

packages/uikit-utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export { default as arrayToMap, arrayToMapWithGetter } from './shared/arrayToMap
33
export * from './shared/regex';
44
export * from './shared/bufferedRequest';
55
export * from './shared/file';
6+
export * from './shared/rtl';
67
export * from './shared';
78

89
export * from './hooks';

0 commit comments

Comments
 (0)