Skip to content
26 changes: 18 additions & 8 deletions examples/SampleApp/src/screens/ChannelScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,9 @@ const ChannelHeader: React.FC<ChannelHeaderProps> = ({ channel }) => {
// Either provide channel or channelId.
export const ChannelScreen: React.FC<ChannelScreenProps> = ({
navigation,
route: {
params: { channel: channelFromProp, channelId, messageId },
},
route,
}) => {
const { channel: channelFromProp, channelId, messageId } = route.params;
const {
chatClient,
messageListImplementation,
Expand Down Expand Up @@ -180,33 +179,44 @@ export const ChannelScreen: React.FC<ChannelScreenProps> = ({
if (!thread || !channel) {
return;
}

if (messageId) {
navigation.setParams({ messageId: undefined });
}

setSelectedThread(thread);
setThread(thread);
navigation.navigate('ThreadScreen', {
channel,
thread,
targetedMessageId: undefined,
});
},
[channel, navigation, setThread],
[channel, messageId, navigation, setThread],
);

const onAlsoSentToChannelHeaderPress = useCallback(
async ({ parentMessage, targetedMessageId }: AlsoSentToChannelHeaderPressPayload) => {
if (!channel || !parentMessage) {
return;
}

if (messageId) {
navigation.setParams({ messageId: undefined });
}

setSelectedThread(parentMessage);
setThread(parentMessage);
const params: StackNavigatorParamList['ThreadScreen'] = {
channel,
targetedMessageId,
thread: parentMessage,
};
const hasThreadInStack = navigation.getState().routes.some((route) => {
if (route.name !== 'ThreadScreen') {
const hasThreadInStack = navigation.getState().routes.some((stackRoute) => {
if (stackRoute.name !== 'ThreadScreen') {
return false;
}
const routeParams = route.params as StackNavigatorParamList['ThreadScreen'] | undefined;
const routeParams = stackRoute.params as StackNavigatorParamList['ThreadScreen'] | undefined;
const routeThreadId =
(routeParams?.thread as LocalMessage)?.id ??
(routeParams?.thread as ThreadType)?.thread?.id;
Expand All @@ -221,7 +231,7 @@ export const ChannelScreen: React.FC<ChannelScreenProps> = ({

navigation.navigate('ThreadScreen', params);
},
[channel, navigation, setThread],
[channel, messageId, navigation, setThread],
);

const handleMessageInfo = useCallback((message: LocalMessage) => {
Expand Down
12 changes: 6 additions & 6 deletions examples/SampleApp/src/screens/ThreadScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,9 @@ const ThreadHeader: React.FC<ThreadHeaderProps> = ({ thread }) => {

export const ThreadScreen: React.FC<ThreadScreenProps> = ({
navigation,
route: {
params: { channel, thread, targetedMessageId: targetedMessageIdFromParams },
},
route,
}) => {
const { channel, thread, targetedMessageId: targetedMessageIdFromParams } = route.params;
const {
theme: {
semantics,
Expand All @@ -88,6 +87,7 @@ export const ThreadScreen: React.FC<ThreadScreenProps> = ({
const { t } = useTranslationContext();
const { setThread } = useStreamChatContext();
const { messageInputFloating, messageListImplementation } = useAppContext();

const onPressMessage: NonNullable<React.ComponentProps<typeof Channel>['onPressMessage']> = (
payload,
) => {
Expand Down Expand Up @@ -126,11 +126,11 @@ export const ThreadScreen: React.FC<ThreadScreenProps> = ({
};
const hasChannelInStack = navigation
.getState()
.routes.some((route) => {
if (route.name !== 'ChannelScreen') {
.routes.some((stackRoute) => {
if (stackRoute.name !== 'ChannelScreen') {
return false;
}
const routeParams = route.params as StackNavigatorParamList['ChannelScreen'] | undefined;
const routeParams = stackRoute.params as StackNavigatorParamList['ChannelScreen'] | undefined;
const routeChannelId = routeParams?.channel?.id ?? routeParams?.channelId;
return routeChannelId === channel.id;
});
Expand Down
84 changes: 46 additions & 38 deletions package/src/components/Poll/CreatePollContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { StyleSheet, Switch, Text, View } from 'react-native';

import { ScrollView } from 'react-native-gesture-handler';
import { useSharedValue } from 'react-native-reanimated';
import Animated, { LinearTransition, useSharedValue } from 'react-native-reanimated';

import { PollComposerState, VotingVisibility } from 'stream-chat';

Expand Down Expand Up @@ -153,49 +153,57 @@ export const CreatePollContent = () => {
>
<NameField />
<CreatePollOptions currentOptionPositions={currentOptionPositions} />
<View style={[styles.optionCardWrapper, optionCardWrapper]}>
<Animated.View
layout={LinearTransition.duration(200)}
style={[styles.optionCardWrapper, optionCardWrapper]}
>
<MultipleAnswersField />
<View style={[styles.optionCard, anonymousPoll.wrapper]}>
<View style={[styles.optionCardContent, anonymousPoll.optionCardContent]}>
<Text style={[styles.title, anonymousPoll.title]}>{t('Anonymous voting')}</Text>
<Text style={[styles.description, anonymousPoll.description]}>Hide who voted</Text>
</View>
<Animated.View
layout={LinearTransition.duration(200)}
style={[styles.optionCardWrapper, optionCardWrapper]}
>
<View style={[styles.optionCard, anonymousPoll.wrapper]}>
<View style={[styles.optionCardContent, anonymousPoll.optionCardContent]}>
<Text style={[styles.title, anonymousPoll.title]}>{t('Anonymous voting')}</Text>
<Text style={[styles.description, anonymousPoll.description]}>Hide who voted</Text>
</View>

<Switch
onValueChange={onAnonymousPollChangeHandler}
value={isAnonymousPoll}
style={[styles.optionCardSwitch, anonymousPoll.optionCardSwitch]}
/>
</View>
<View style={[styles.optionCard, suggestOption.wrapper]}>
<View style={[styles.optionCardContent, suggestOption.optionCardContent]}>
<Text style={[styles.title, suggestOption.title]}>{t('Suggest an option')}</Text>
<Text style={[styles.description, suggestOption.description]}>
Let others add options
</Text>
<Switch
onValueChange={onAnonymousPollChangeHandler}
value={isAnonymousPoll}
style={[styles.optionCardSwitch, anonymousPoll.optionCardSwitch]}
/>
</View>
<View style={[styles.optionCard, suggestOption.wrapper]}>
<View style={[styles.optionCardContent, suggestOption.optionCardContent]}>
<Text style={[styles.title, suggestOption.title]}>{t('Suggest an option')}</Text>
<Text style={[styles.description, suggestOption.description]}>
Let others add options
</Text>
</View>

<Switch
onValueChange={onAllowUserSuggestedOptionsChangeHandler}
value={allowUserSuggestedOptions}
style={[styles.optionCardSwitch, suggestOption.optionCardSwitch]}
/>
</View>
<View style={[styles.optionCard, addComment.wrapper]}>
<View style={[styles.optionCardContent, addComment.optionCardContent]}>
<Text style={[styles.title, addComment.title]}>{t('Add a comment')}</Text>
<Text style={[styles.description, addComment.description]}>
Add a comment to the poll
</Text>
<Switch
onValueChange={onAllowUserSuggestedOptionsChangeHandler}
value={allowUserSuggestedOptions}
style={[styles.optionCardSwitch, suggestOption.optionCardSwitch]}
/>
</View>
<View style={[styles.optionCard, addComment.wrapper]}>
<View style={[styles.optionCardContent, addComment.optionCardContent]}>
<Text style={[styles.title, addComment.title]}>{t('Add a comment')}</Text>
<Text style={[styles.description, addComment.description]}>
Add a comment to the poll
</Text>
</View>

<Switch
onValueChange={onAllowAnswersChangeHandler}
value={allowAnswers}
style={[styles.optionCardSwitch, addComment.optionCardSwitch]}
/>
</View>
</View>
<Switch
onValueChange={onAllowAnswersChangeHandler}
value={allowAnswers}
style={[styles.optionCardSwitch, addComment.optionCardSwitch]}
/>
</View>
</Animated.View>
</Animated.View>
</ScrollView>
</>
);
Expand Down
6 changes: 4 additions & 2 deletions package/src/components/Poll/Poll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { StyleSheet, Text, View } from 'react-native';

import { PollOption as PollOptionClass } from 'stream-chat';

import { PollButtons, PollOption } from './components';
import { PollButtons, PollOption, ShowAllOptionsButton } from './components';

import { usePollState } from './hooks/usePollState';

Expand All @@ -16,6 +16,7 @@ import {
} from '../../contexts';

import { primitives } from '../../theme';
import { defaultPollOptionCount } from '../../utils/constants';

export type PollProps = Pick<PollContextValue, 'poll' | 'message'> &
Pick<MessagesContextValue, 'PollContent'>;
Expand Down Expand Up @@ -79,10 +80,11 @@ export const PollContent = ({
{PollHeaderOverride ? <PollHeaderOverride /> : <PollHeader />}
<View style={[styles.optionsWrapper, optionsWrapper]}>
{options
?.slice(0, 10)
?.slice(0, defaultPollOptionCount)
?.map((option: PollOptionClass) => (
<PollOption key={`message_poll_option_${option.id}`} option={option} />
))}
<ShowAllOptionsButton />
</View>
{PollButtonsOverride ? <PollButtonsOverride /> : <PollButtons />}
</View>
Expand Down
11 changes: 5 additions & 6 deletions package/src/components/Poll/components/CreatePollOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import Animated, {
useSharedValue,
withDelay,
withSpring,
withTiming,
} from 'react-native-reanimated';

import { PollComposerOption, PollComposerState } from 'stream-chat';
Expand Down Expand Up @@ -580,11 +579,11 @@ export const CreatePollOptions = ({ currentOptionPositions }: CreatePollOptionsP
useAnimatedReaction(
() => currentOptionPositions.value.totalHeight,
(currentValue, previousValue) => {
if (currentValue !== previousValue) {
animatedOptionsContainerHeight.value = withTiming(currentValue, {
duration: 200,
});
if (currentValue === previousValue) {
return;
}

animatedOptionsContainerHeight.value = currentValue;
},
);

Expand Down Expand Up @@ -707,7 +706,7 @@ export const CreatePollOptions = ({ currentOptionPositions }: CreatePollOptionsP
return (
<View style={[styles.container, container]}>
<Text style={[styles.title, title]}>{t('Options')}</Text>
<Animated.View style={animatedOptionsContainerStyle}>
<Animated.View layout={LinearTransition.duration(200)} style={animatedOptionsContainerStyle}>
{options.map((option, index) => (
<MemoizedCreatePollOption
optionsCount={options.length}
Expand Down
59 changes: 13 additions & 46 deletions package/src/components/Poll/components/MultipleAnswersField.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
import React, { useCallback, useMemo, useState } from 'react';
import { StyleSheet, Switch, Text, View } from 'react-native';

import { PollComposerState } from 'stream-chat';
import Animated, { LinearTransition } from 'react-native-reanimated';

import { MultipleVotesSettings } from './MultipleVotesSettings';

import { useTheme, useTranslationContext } from '../../../contexts';
import { useMessageComposer } from '../../../contexts/messageInputContext/hooks/useMessageComposer';
import { useStateStore } from '../../../hooks/useStateStore';
import { primitives } from '../../../theme';
import { Input } from '../../ui/Input/Input';

const pollComposerStateSelector = (state: PollComposerState) => ({
error: state.errors.max_votes_allowed,
max_votes_allowed: state.data.max_votes_allowed,
});

export const MultipleAnswersField = () => {
const [allowMultipleVotes, setAllowMultipleVotes] = useState<boolean>(false);
const { t } = useTranslationContext();
const messageComposer = useMessageComposer();
const { pollComposer } = messageComposer;
const { handleFieldBlur, updateFields } = pollComposer;
const { error, max_votes_allowed } = useStateStore(pollComposer.state, pollComposerStateSelector);
const { updateFields } = pollComposer;

const {
theme: {
Expand All @@ -40,24 +34,16 @@ export const MultipleAnswersField = () => {
[updateFields],
);

const onChangeTextHandler = useCallback(
async (newText: string) => {
await updateFields({ max_votes_allowed: newText });
},
[updateFields],
);

const onBlurHandler = useCallback(async () => {
await handleFieldBlur('max_votes_allowed');
}, [handleFieldBlur]);

return (
<View style={[styles.multipleAnswersWrapper, multipleAnswers.wrapper]}>
<Animated.View
layout={LinearTransition.duration(200)}
style={[styles.multipleAnswersWrapper, multipleAnswers.wrapper]}
>
<View style={[styles.optionCard, multipleAnswers.optionCard]}>
<View style={[styles.optionCardContent, multipleAnswers.optionCardContent]}>
<Text style={[styles.title, multipleAnswers.title]}>{t('Multiple answers')}</Text>
<Text style={[styles.title, multipleAnswers.title]}>{t('Multiple votes')}</Text>
<Text style={[styles.description, multipleAnswers.description]}>
Select more than one option
{t('Select more than one option')}
</Text>
</View>
<Switch
Expand All @@ -66,21 +52,8 @@ export const MultipleAnswersField = () => {
style={[styles.optionCardSwitch, multipleAnswers.optionCardSwitch]}
/>
</View>
{allowMultipleVotes ? (
<Input
inputMode='numeric'
placeholder={t('Maximum votes per person')}
variant='ghost'
state={max_votes_allowed && error ? 'error' : 'default'}
onChangeText={onChangeTextHandler}
onBlur={onBlurHandler}
helperText={true}
infoText={t('Type a number from 2 to 10')}
errorMessage={error ? t(error) : undefined}
containerStyle={styles.maxVotesInput}
/>
) : null}
</View>
{allowMultipleVotes ? <MultipleVotesSettings /> : null}
</Animated.View>
);
};

Expand All @@ -90,14 +63,11 @@ const useStyles = () => {
} = useTheme();
return useMemo(() => {
return StyleSheet.create({
maxVotesInput: {
paddingLeft: 0,
},
multipleAnswersWrapper: {
backgroundColor: semantics.inputOptionCardBg,
padding: primitives.spacingMd,
borderRadius: primitives.radiusLg,
gap: primitives.spacingSm,
gap: primitives.spacingMd,
},
title: {
color: semantics.textPrimary,
Expand All @@ -119,9 +89,6 @@ const useStyles = () => {
justifyContent: 'space-between',
flexDirection: 'row',
},
optionCardWrapper: {
gap: primitives.spacingMd,
},
optionCardSwitch: { width: 64 },
});
}, [semantics]);
Expand Down
Loading
Loading