Skip to content

Commit 97cf72f

Browse files
authored
fix: ui redesign bugs (#3529)
## 🎯 Goal This PR includes a batch of follow-up fixes on top of the redesign branch, focused on sample app UX regressions, channel list actions, and Android behaviour. On the platform side, it improves Android behaviour by removing the old IME hack from keyboard avoiding logic and makes our `KeyboardCompatibleView` compatible with ETE on `Android` 🚀 . It also rounds out the redesign polish by making filters toggleable and reflecting the related behaviour in sample app screens. Included changes - enable audio recording in threads - use the correct outlined video icon - hide read indicators for deleted last messages - fix back navigation in channel creation screens - fix Android keyboard avoiding without the IME hack - remove mark unread for own messages (this was never working well) - make filters toggleable - reflect filter behavior in sample app screens - add muted icon/state to details bottom sheet - make swipe actions always use mute ## 🛠 Implementation details <!-- Provide a description of the implementation --> ## 🎨 UI Changes <!-- Add relevant screenshots --> <details> <summary>iOS</summary> <table> <thead> <tr> <td>Before</td> <td>After</td> </tr> </thead> <tbody> <tr> <td> <!--<img src="" /> --> </td> <td> <!--<img src="" /> --> </td> </tr> </tbody> </table> </details> <details> <summary>Android</summary> <table> <thead> <tr> <td>Before</td> <td>After</td> </tr> </thead> <tbody> <tr> <td> <!--<img src="" /> --> </td> <td> <!--<img src="" /> --> </td> </tr> </tbody> </table> </details> ## 🧪 Testing <!-- Explain how this change can be tested (or why it can't be tested) --> ## ☑️ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android
1 parent 8d63792 commit 97cf72f

24 files changed

+402
-132
lines changed

examples/SampleApp/android/app/src/main/java/com/sampleapp/MainActivity.kt

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
package io.getstream.reactnative.sampleapp
22

3+
import android.graphics.Color
34
import android.os.Build
45
import android.os.Bundle
5-
import android.view.View
6-
import androidx.core.graphics.Insets
7-
import androidx.core.view.ViewCompat
8-
import androidx.core.view.WindowInsetsCompat
9-
import androidx.core.view.updatePadding
106
import com.facebook.react.ReactActivity
117
import com.facebook.react.ReactActivityDelegate
128
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
@@ -17,28 +13,12 @@ class MainActivity : ReactActivity() {
1713
override fun onCreate(savedInstanceState: Bundle?) {
1814
super.onCreate(null)
1915

20-
if (Build.VERSION.SDK_INT >= 35) {
21-
val rootView = findViewById<View>(android.R.id.content)
16+
window.navigationBarColor = Color.TRANSPARENT
17+
window.statusBarColor = Color.TRANSPARENT
2218

23-
val initial = Insets.of(
24-
rootView.paddingLeft,
25-
rootView.paddingTop,
26-
rootView.paddingRight,
27-
rootView.paddingBottom
28-
)
29-
30-
ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets ->
31-
val ime = insets.getInsets(WindowInsetsCompat.Type.ime())
32-
33-
v.updatePadding(
34-
left = initial.left,
35-
top = initial.top,
36-
right = initial.right,
37-
bottom = initial.bottom + ime.bottom
38-
)
39-
40-
insets
41-
}
19+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
20+
window.isNavigationBarContrastEnforced = false
21+
window.isStatusBarContrastEnforced = false
4222
}
4323
}
4424

examples/SampleApp/android/app/src/main/res/values/styles.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
<!-- Base application theme. -->
44
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
5-
<!-- Customize your theme here. -->
5+
<item name="android:navigationBarColor">@android:color/transparent</item>
6+
<item name="android:statusBarColor">@android:color/transparent</item>
7+
<item name="android:enforceNavigationBarContrast">false</item>
68
</style>
79

810
</resources>

examples/SampleApp/src/components/ChannelDetailProfileSection.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import React, { useMemo } from 'react';
22
import { StyleSheet, Text, View } from 'react-native';
3-
import { useTheme } from 'stream-chat-react-native';
3+
4+
import { ChannelPreviewMutedStatus, useTheme } from 'stream-chat-react-native';
45

56
type ChannelDetailProfileSectionProps = {
67
avatar: React.ReactNode;
8+
muted?: boolean;
79
subtitle: string;
810
title: string;
911
};
1012

1113
export const ChannelDetailProfileSection = React.memo(
12-
({ avatar, subtitle, title }: ChannelDetailProfileSectionProps) => {
14+
({ avatar, muted, subtitle, title }: ChannelDetailProfileSectionProps) => {
1315
const {
1416
theme: { semantics },
1517
} = useTheme();
@@ -19,9 +21,12 @@ export const ChannelDetailProfileSection = React.memo(
1921
<View style={styles.container}>
2022
{avatar}
2123
<View style={styles.heading}>
22-
<Text style={[styles.title, { color: semantics.textPrimary }]} numberOfLines={2}>
23-
{title}
24-
</Text>
24+
<View style={styles.titleRow}>
25+
<Text style={[styles.title, { color: semantics.textPrimary }]} numberOfLines={2}>
26+
{title}
27+
</Text>
28+
{muted ? <ChannelPreviewMutedStatus /> : null}
29+
</View>
2530
{subtitle ? (
2631
<Text style={[styles.subtitle, { color: semantics.textSecondary }]} numberOfLines={1}>
2732
{subtitle}
@@ -49,8 +54,16 @@ const useStyles = () =>
4954
gap: 8,
5055
width: '100%',
5156
},
57+
titleRow: {
58+
alignItems: 'center',
59+
flexDirection: 'row',
60+
gap: 4,
61+
justifyContent: 'center',
62+
maxWidth: '100%',
63+
},
5264
title: {
5365
fontSize: 22,
66+
flexShrink: 1,
5467
fontWeight: '600',
5568
lineHeight: 24,
5669
textAlign: 'center',

examples/SampleApp/src/screens/ChannelScreen.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ export const ChannelScreen: React.FC<ChannelScreenProps> = ({ navigation, route
273273
messageInputFloating={messageInputFloating}
274274
onPressMessage={onPressMessage}
275275
initialScrollToFirstUnreadMessage
276-
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : -300}
276+
keyboardVerticalOffset={0}
277277
messageActions={messageActions}
278278
MessageLocation={MessageLocation}
279279
messageId={messageId}

examples/SampleApp/src/screens/GroupChannelDetailsScreen.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import React, { useCallback, useMemo, useState } from 'react';
22
import { Pressable, ScrollView, StyleSheet, Switch, Text, View } from 'react-native';
3+
34
import { SafeAreaView } from 'react-native-safe-area-context';
5+
46
import { RouteProp, useNavigation } from '@react-navigation/native';
7+
8+
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
9+
import type { ChannelMemberResponse, UserResponse } from 'stream-chat';
10+
511
import {
612
ChannelAvatar,
713
useChannelPreviewDisplayName,
14+
useIsChannelMuted,
815
useOverlayContext,
916
useTheme,
1017
Pin,
@@ -26,9 +33,6 @@ import { GoForward } from '../icons/GoForward';
2633
import { LeaveGroup } from '../icons/LeaveGroup';
2734
import { Mute } from '../icons/Mute';
2835
import { Picture } from '../icons/Picture';
29-
30-
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
31-
import type { ChannelMemberResponse, UserResponse } from 'stream-chat';
3236
import type { StackNavigatorParamList } from '../types';
3337

3438
const MAX_VISIBLE_MEMBERS = 5;
@@ -55,6 +59,7 @@ export const GroupChannelDetailsScreen: React.FC<GroupChannelDetailsProps> = ({
5559
const {
5660
theme: { semantics },
5761
} = useTheme();
62+
const { muted: channelMuted } = useIsChannelMuted(channel);
5863

5964
const [muted, setMuted] = useState(
6065
chatClient?.mutedChannels.some((mute) => mute.channel?.id === channel?.id),
@@ -197,6 +202,7 @@ export const GroupChannelDetailsScreen: React.FC<GroupChannelDetailsProps> = ({
197202
<ScrollView contentContainerStyle={styles.scrollContent} style={styles.container}>
198203
<ChannelDetailProfileSection
199204
avatar={<ChannelAvatar channel={channel} size='2xl' />}
205+
muted={channelMuted}
200206
title={displayName}
201207
subtitle={memberStatusText}
202208
/>
@@ -317,11 +323,7 @@ export const GroupChannelDetailsScreen: React.FC<GroupChannelDetailsProps> = ({
317323
onClose={closeContactDetail}
318324
visible={selectedMember !== null}
319325
/>
320-
<EditGroupBottomSheet
321-
channel={channel}
322-
onClose={closeEditSheet}
323-
visible={editVisible}
324-
/>
326+
<EditGroupBottomSheet channel={channel} onClose={closeEditSheet} visible={editVisible} />
325327
</SafeAreaView>
326328
);
327329
};

examples/SampleApp/src/screens/NewDirectMessagingScreen.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, { useEffect, useRef, useState } from 'react';
2-
import { Platform, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
1+
import React, { useCallback, useEffect, useRef, useState } from 'react';
2+
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
33
import { useFocusEffect } from '@react-navigation/native';
44
import { SafeAreaView } from 'react-native-safe-area-context';
55
import {
@@ -204,9 +204,20 @@ export const NewDirectMessagingScreen: React.FC<NewDirectMessagingScreenProps> =
204204
initChannel();
205205
}, [chatClient, selectedUserIds, selectedUsersLength]);
206206

207+
const onBackPress = useCallback(() => {
208+
reset();
209+
210+
if (!navigation.canGoBack()) {
211+
navigation.reset({ index: 0, routes: [{ name: 'MessagingScreen' }] });
212+
return;
213+
}
214+
215+
navigation.goBack();
216+
}, [navigation, reset]);
217+
207218
const renderUserSearch = ({ inSafeArea }: { inSafeArea: boolean }) => (
208219
<View style={[{ backgroundColor: white }, focusOnSearchInput ? styles.container : undefined]}>
209-
<ScreenHeader inSafeArea={inSafeArea} onBack={reset} titleText='New Chat' />
220+
<ScreenHeader inSafeArea={inSafeArea} onBack={onBackPress} titleText='New Chat' />
210221
<TouchableOpacity
211222
activeOpacity={1}
212223
onPress={() => {
@@ -341,7 +352,7 @@ export const NewDirectMessagingScreen: React.FC<NewDirectMessagingScreenProps> =
341352
channel={currentChannel.current}
342353
EmptyStateIndicator={EmptyMessagesIndicator}
343354
enforceUniqueReaction
344-
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : -300}
355+
keyboardVerticalOffset={0}
345356
onChangeText={setMessageInputText}
346357
overrideOwnCapabilities={{ sendMessage: true }}
347358
SendButton={NewDirectMessagingSendButton}

examples/SampleApp/src/screens/NewGroupChannelAddMemberScreen.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useCallback } from 'react';
22
import { FlatList, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native';
33
import { Search, useTheme } from 'stream-chat-react-native';
44

@@ -79,6 +79,17 @@ export const NewGroupChannelAddMemberScreen: React.FC<Props> = ({ navigation })
7979
const { onChangeSearchText, onFocusInput, removeUser, reset, searchText, selectedUsers } =
8080
useUserSearchContext();
8181

82+
const onBackPress = useCallback(() => {
83+
reset();
84+
85+
if (!navigation.canGoBack()) {
86+
navigation.reset({ index: 0, routes: [{ name: 'MessagingScreen' }] });
87+
return;
88+
}
89+
90+
navigation.goBack();
91+
}, [navigation, reset]);
92+
8293
const onRightArrowPress = () => {
8394
if (selectedUsers.length === 0) {
8495
return;
@@ -93,7 +104,7 @@ export const NewGroupChannelAddMemberScreen: React.FC<Props> = ({ navigation })
93104
return (
94105
<View style={styles.container}>
95106
<ScreenHeader
96-
onBack={reset}
107+
onBack={onBackPress}
97108
// eslint-disable-next-line react/no-unstable-nested-components
98109
RightContent={() => (
99110
<RightArrowButton disabled={selectedUsers.length === 0} onPress={onRightArrowPress} />

examples/SampleApp/src/screens/OneOnOneChannelDetailScreen.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
import React, { useCallback, useState } from 'react';
22
import { ScrollView, StyleSheet, Switch } from 'react-native';
3-
import { ChannelAvatar, Delete, useTheme, Pin, CircleBan } from 'stream-chat-react-native';
43
import { SafeAreaView } from 'react-native-safe-area-context';
54

5+
import type { RouteProp } from '@react-navigation/native';
6+
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
7+
8+
import {
9+
ChannelAvatar,
10+
CircleBan,
11+
Delete,
12+
useChannelMuteActive,
13+
Pin,
14+
useTheme,
15+
} from 'stream-chat-react-native';
16+
617
import { ChannelDetailProfileSection } from '../components/ChannelDetailProfileSection';
718
import { ConfirmationBottomSheet } from '../components/ConfirmationBottomSheet';
819
import { ListItem } from '../components/ListItem';
@@ -13,11 +24,8 @@ import { File } from '../icons/File';
1324
import { GoForward } from '../icons/GoForward';
1425
import { Mute } from '../icons/Mute';
1526
import { Picture } from '../icons/Picture';
16-
import { getUserActivityStatus } from '../utils/getUserActivityStatus';
17-
18-
import type { RouteProp } from '@react-navigation/native';
19-
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
2027
import type { StackNavigatorParamList } from '../types';
28+
import { getUserActivityStatus } from '../utils/getUserActivityStatus';
2129

2230
type OneOnOneChannelDetailScreenRouteProp = RouteProp<
2331
StackNavigatorParamList,
@@ -44,6 +52,7 @@ export const OneOnOneChannelDetailScreen: React.FC<Props> = ({
4452
theme: { semantics },
4553
} = useTheme();
4654
const { chatClient } = useAppContext();
55+
const userMuted = useChannelMuteActive(channel);
4756

4857
const [confirmationVisible, setConfirmationVisible] = useState(false);
4958
const [blockUserConfirmationVisible, setBlockUserConfirmationVisible] = useState(false);
@@ -141,6 +150,7 @@ export const OneOnOneChannelDetailScreen: React.FC<Props> = ({
141150
<ScrollView contentContainerStyle={styles.scrollContent} style={styles.container}>
142151
<ChannelDetailProfileSection
143152
avatar={<ChannelAvatar channel={channel} size='2xl' />}
153+
muted={userMuted}
144154
title={user.name || user.id}
145155
subtitle={activityStatus}
146156
/>

examples/SampleApp/src/screens/ThreadScreen.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useCallback } from 'react';
2-
import { Platform, StyleSheet, View } from 'react-native';
2+
import { StyleSheet, View } from 'react-native';
33

44
import {
55
AlsoSentToChannelHeaderPressPayload,
@@ -149,11 +149,11 @@ export const ThreadScreen: React.FC<ThreadScreenProps> = ({
149149
return (
150150
<View style={[styles.container, { backgroundColor: white }]}>
151151
<Channel
152-
audioRecordingEnabled={false}
152+
audioRecordingEnabled={true}
153153
AttachmentPickerSelectionBar={CustomAttachmentPickerSelectionBar}
154154
channel={channel}
155155
enforceUniqueReaction
156-
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : -300}
156+
keyboardVerticalOffset={0}
157157
messageActions={messageActions}
158158
messageInputFloating={messageInputFloating}
159159
MessageLocation={MessageLocation}

package/src/components/AttachmentPicker/components/AttachmentPickerContent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
} from '../../../contexts';
2222
import { useTheme } from '../../../contexts/themeContext/ThemeContext';
2323
import { useAttachmentPickerState, useStableCallback } from '../../../hooks';
24-
import { Camera, FilePickerIcon, PollThumbnail, Recorder } from '../../../icons';
24+
import { Camera, FilePickerIcon, PollThumbnail, VideoIcon } from '../../../icons';
2525
import { primitives } from '../../../theme';
2626
import { CommandSuggestionItem } from '../../AutoCompleteInput/AutoCompleteSuggestionItem';
2727

@@ -197,7 +197,7 @@ export const AttachmentCameraPicker = (
197197
/>
198198
) : (
199199
<AttachmentPickerGenericContent
200-
Icon={videoOnly ? Recorder : Camera}
200+
Icon={videoOnly ? VideoIcon : Camera}
201201
onPress={openCameraPicker}
202202
height={height}
203203
buttonText={t('Open Camera')}

0 commit comments

Comments
 (0)