Skip to content

Commit 254b00f

Browse files
committed
UserStatusScreen: Add and use EmojiInput, for status emoji
Taking care not to send the status-emoji params to the server if the server doesn't support status emoji.
1 parent c7013e3 commit 254b00f

File tree

2 files changed

+142
-9
lines changed

2 files changed

+142
-9
lines changed

src/user-statuses/EmojiInput.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/* @flow strict-local */
2+
import React, { useContext, useCallback, useMemo } from 'react';
3+
import type { Node } from 'react';
4+
import { Platform } from 'react-native';
5+
import type { AppNavigationProp } from '../nav/AppNavigator';
6+
7+
import { ThemeContext } from '../styles/theme';
8+
import Touchable from '../common/Touchable';
9+
import Emoji from '../emoji/Emoji';
10+
import { Icon } from '../common/Icons';
11+
import type { EmojiType } from '../types';
12+
import { createStyleSheet, BORDER_COLOR } from '../styles';
13+
14+
export type Value = null | {| +type: EmojiType, +code: string, +name: string |};
15+
16+
export type Props = $ReadOnly<{|
17+
value: Value,
18+
onChangeValue: Value => void,
19+
20+
/**
21+
* Component must be under the stack nav that has the emoji-picker screen
22+
*
23+
* Pass this down from props or `useNavigation`.
24+
*/
25+
navigation: AppNavigationProp<>,
26+
27+
/**
28+
* Give appropriate right margin
29+
*/
30+
rightMargin?: true,
31+
|}>;
32+
33+
/**
34+
* A controlled input component to let the user choose an emoji.
35+
*
36+
* When pressed, opens the emoji-picker screen, and populates with the emoji
37+
* chosen by the user, if any.
38+
*
39+
* Designed for harmony with our Input component. If changing the appearance
40+
* of this or that component, we should try to keep that harmony.
41+
*/
42+
export default function EmojiInput(props: Props): Node {
43+
const { value, onChangeValue, navigation, rightMargin } = props;
44+
45+
const { color } = useContext(ThemeContext);
46+
47+
const handlePress = useCallback(() => {
48+
navigation.push('emoji-picker', { onPressEmoji: onChangeValue });
49+
}, [navigation, onChangeValue]);
50+
51+
const styles = useMemo(
52+
() =>
53+
createStyleSheet({
54+
touchable: {
55+
// Min touch-target size
56+
minWidth: 48,
57+
minHeight: 48,
58+
59+
alignItems: 'center',
60+
justifyContent: 'center',
61+
62+
marginRight: rightMargin ? 4 : undefined,
63+
64+
// For harmony with the `Input` component, which differs between
65+
// platforms because of platform conventions. Border on iOS, no
66+
// border on Android.
67+
...(Platform.OS === 'ios'
68+
? {
69+
borderWidth: 1,
70+
borderColor: BORDER_COLOR,
71+
borderRadius: 2,
72+
padding: 8,
73+
}
74+
: Object.freeze({})),
75+
},
76+
}),
77+
[rightMargin],
78+
);
79+
80+
return (
81+
<Touchable style={styles.touchable} onPress={handlePress}>
82+
{value ? (
83+
<Emoji code={value.code} type={value.type} size={24} />
84+
) : (
85+
<Icon color={color} size={24} name="smile" />
86+
)}
87+
</Touchable>
88+
);
89+
}

src/user-statuses/UserStatusScreen.js

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ import type { RouteProp } from '../react-navigation';
99
import type { AppNavigationProp } from '../nav/AppNavigator';
1010
import { useSelector } from '../react-redux';
1111
import Input from '../common/Input';
12+
import EmojiInput from './EmojiInput';
13+
import type { Value as EmojiInputValue } from './EmojiInput';
14+
import { emojiTypeFromReactionType, reactionTypeFromEmojiType } from '../emoji/data';
1215
import SelectableOptionRow from '../common/SelectableOptionRow';
1316
import Screen from '../common/Screen';
1417
import ZulipButton from '../common/ZulipButton';
15-
import { getAuth, getOwnUserId } from '../selectors';
18+
import { getZulipFeatureLevel, getAuth, getOwnUserId } from '../selectors';
1619
import { getUserStatus } from './userStatusesModel';
1720
import type { UserStatus } from '../api/modelTypes';
1821
import { IconCancel, IconDone } from '../common/Icons';
@@ -46,41 +49,82 @@ const statusTextFromInputValue = (v: string): $PropertyType<UserStatus, 'status_
4649

4750
const inputValueFromStatusText = (t: $PropertyType<UserStatus, 'status_text'>): string => t ?? '';
4851

52+
const statusEmojiFromInputValue = (v: EmojiInputValue): $PropertyType<UserStatus, 'status_emoji'> =>
53+
v
54+
? {
55+
emoji_name: v.name,
56+
emoji_code: v.code,
57+
reaction_type: reactionTypeFromEmojiType(v.type, v.name),
58+
}
59+
: null;
60+
61+
const inputValueFromStatusEmoji = (e: $PropertyType<UserStatus, 'status_emoji'>): EmojiInputValue =>
62+
e
63+
? {
64+
type: emojiTypeFromReactionType(e.reaction_type),
65+
code: e.emoji_code,
66+
name: e.emoji_name,
67+
}
68+
: null;
69+
4970
export default function UserStatusScreen(props: Props): Node {
5071
const { navigation } = props;
5172

73+
// TODO(server-5.0): Cut conditionals on emoji-status support (emoji
74+
// supported as of FL 86: https://zulip.com/api/changelog )
75+
const serverSupportsEmojiStatus = useSelector(getZulipFeatureLevel) >= 86;
76+
5277
const _ = useContext(TranslationContext);
5378
const auth = useSelector(getAuth);
5479
const ownUserId = useSelector(getOwnUserId);
5580
const userStatusText = useSelector(state => getUserStatus(state, ownUserId).status_text);
81+
const userStatusEmoji = useSelector(state => getUserStatus(state, ownUserId).status_emoji);
5682

5783
const [textInputValue, setTextInputValue] = useState<string>(
5884
inputValueFromStatusText(userStatusText),
5985
);
86+
const [emojiInputValue, setEmojiInputValue] = useState<EmojiInputValue>(
87+
inputValueFromStatusEmoji(userStatusEmoji),
88+
);
6089

6190
const sendToServer = useCallback(
6291
partialUserStatus => {
63-
api.updateUserStatus(auth, partialUserStatus);
92+
const copy = { ...partialUserStatus };
93+
// TODO: Put conditional inside `api.updateUserStatus` itself; see
94+
// https://github.com/zulip/zulip-mobile/issues/4659#issuecomment-914996061
95+
if (!serverSupportsEmojiStatus) {
96+
delete copy.status_emoji;
97+
}
98+
api.updateUserStatus(auth, copy);
6499
navigation.goBack();
65100
},
66-
[navigation, auth],
101+
[serverSupportsEmojiStatus, navigation, auth],
67102
);
68103

69104
const handlePressUpdate = useCallback(() => {
70-
sendToServer({ status_text: statusTextFromInputValue(textInputValue) });
71-
}, [textInputValue, sendToServer]);
105+
sendToServer({
106+
status_text: statusTextFromInputValue(textInputValue),
107+
status_emoji: statusEmojiFromInputValue(emojiInputValue),
108+
});
109+
}, [textInputValue, emojiInputValue, sendToServer]);
72110

73111
const handlePressClear = useCallback(() => {
74112
setTextInputValue(inputValueFromStatusText(null));
75-
sendToServer({ status_text: null });
113+
setEmojiInputValue(inputValueFromStatusEmoji(null));
114+
sendToServer({ status_text: null, status_emoji: null });
76115
}, [sendToServer]);
77116

78117
return (
79118
<Screen title="User status">
80119
<View style={styles.inputRow}>
81-
{
82-
// TODO: Input for emoji status
83-
}
120+
{serverSupportsEmojiStatus && (
121+
<EmojiInput
122+
navigation={navigation}
123+
value={emojiInputValue}
124+
onChangeValue={setEmojiInputValue}
125+
rightMargin
126+
/>
127+
)}
84128
<Input
85129
autoFocus
86130
maxLength={60}

0 commit comments

Comments
 (0)