Skip to content

Commit

Permalink
feat: added replies to posts
Browse files Browse the repository at this point in the history
  • Loading branch information
noelzappy committed Mar 5, 2023
1 parent fa68b0f commit dc232b6
Show file tree
Hide file tree
Showing 8 changed files with 342 additions and 17 deletions.
9 changes: 9 additions & 0 deletions @types/post.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,12 @@ export type Post = {
prompt: ChatMessage;
response: ChatMessage;
};

export type PostReply = {
id: string;
user: User;
date: string;
text: string;
parentReply?: string;
post: string;
};
5 changes: 5 additions & 0 deletions src/components/ChatBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Props = {
disabled?: boolean;
onShare?: (item: ChatMessage) => void;
selected?: boolean;
fontSize?: number;
};

const ChatBubble = ({
Expand All @@ -25,6 +26,7 @@ const ChatBubble = ({
disabled,
onShare,
selected,
fontSize,
}: Props) => {
const { Fonts, Common, Colors, Layout } = useTheme();
const { user } = useSelector((state: RootState) => state.auth);
Expand Down Expand Up @@ -102,6 +104,7 @@ const ChatBubble = ({
{
color,
marginBottom: 5,
fontSize: 12,
},
Fonts.textBold,
]}
Expand All @@ -116,6 +119,7 @@ const ChatBubble = ({
Fonts.textSmall,
{
color,
fontSize,
},
]}
>
Expand Down Expand Up @@ -213,6 +217,7 @@ ChatBubble.defaultProps = {
disabled: false,
onShare: () => {},
selected: false,
fontSize: 14,
};

export default ChatBubble;
126 changes: 126 additions & 0 deletions src/components/PostHead.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React, { useRef, useState } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { useTheme } from '@/hooks';
import { Post } from 'types/post';
import { Avatar, ListItem } from '@rneui/base';
import ImageView from 'react-native-image-viewing';
import ViewShot from 'react-native-view-shot';
import ChatBubble from './ChatBubble';

type Props = {
item: Post;
onPress: () => void;
};

const PostHead = ({ item, onPress }: Props) => {
const { Fonts, Common, Layout, Gutters } = useTheme();

const shotRef = useRef<any>(null);
const [showImagViewer, setShowImagViewer] = useState(false);
const [imageUri, setImageUri] = useState(undefined);

const onCapture = () => {
shotRef.current?.capture().then((uri: any) => {
setImageUri(uri);
setShowImagViewer(true);
});
};

return (
<>
<View
style={[Layout.row, Layout.alignItemsCenter, Gutters.regularTMargin]}
>
<Avatar
source={{ uri: 'https://picsum.photos/200' }}
rounded
size="small"
containerStyle={[
Gutters.smallRMargin,
{
marginLeft: 10,
},
]}
/>
<View>
<Text style={[Fonts.textSmall, Fonts.textBold]} numberOfLines={1}>
{item.user.name}
</Text>

<Text
style={[
Fonts.textSmall,
{
fontSize: 12,
},
]}
numberOfLines={1}
>
@{item.user.name}
</Text>
</View>
</View>

<ListItem bottomDivider onPress={onPress}>
<ListItem.Content>
<View
style={[
{
paddingBottom: 3,
},
]}
>
<Text style={[Fonts.textSmall]}>{item.description}</Text>
</View>

<TouchableOpacity
style={[Common.viewImage]}
onPress={onCapture}
activeOpacity={0.8}
>
<ViewShot
ref={shotRef}
options={{
fileName: `post-${item.id}`,
format: 'jpg',
quality: 0.9,
}}
>
<View
style={[
{
padding: 5,
},
]}
>
<ChatBubble
item={item.prompt}
showSender
disabled
fontSize={13}
/>
<ChatBubble
item={item.response}
showSender
disabled
fontSize={13}
/>
</View>
</ViewShot>
</TouchableOpacity>
</ListItem.Content>
</ListItem>

<ImageView
images={[{ uri: imageUri }]}
imageIndex={0}
visible={showImagViewer && !!imageUri}
onRequestClose={() => {
setShowImagViewer(false);
}}
/>
</>
);
};

export default PostHead;
83 changes: 83 additions & 0 deletions src/components/ReplyItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { useTheme } from '@/hooks';
import { PostReply } from 'types/post';
import { Avatar, ListItem } from '@rneui/base';

type Props = {
item: PostReply;
onPress: () => void;
};

const ReplyItem = ({ item, onPress }: Props) => {
const { Fonts, Layout, Gutters } = useTheme();

return (
<TouchableOpacity
style={[
{
paddingLeft: 10,
},
]}
onPress={onPress}
>
<View
style={[Layout.row, Layout.alignItemsCenter, Gutters.regularTMargin]}
>
<Avatar
source={{ uri: 'https://picsum.photos/200' }}
rounded
size="small"
/>

<View
style={[
{
marginLeft: 10,
},
]}
>
<Text style={[Fonts.textSmall, Fonts.textBold]} numberOfLines={1}>
{item.user.name}
</Text>

<Text
style={[
Fonts.textSmall,
{
fontSize: 12,
},
]}
numberOfLines={1}
>
@{item.user.name}
</Text>
</View>
</View>
<ListItem
bottomDivider
containerStyle={[
{
alignItems: 'stretch',
},
]}
>
<View />
<ListItem.Content>
<View
style={[
{
paddingBottom: 3,
marginLeft: 10,
},
]}
>
<Text style={[Fonts.textSmall]}>{item.text}</Text>
</View>
</ListItem.Content>
</ListItem>
</TouchableOpacity>
);
};

export default ReplyItem;
2 changes: 2 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export { default as Spacer } from './Space';
export { default as MessageSkeleton } from './MessageSkeleton';
export { default as PostItem } from './PostItem';
export { default as ChatBubble } from './ChatBubble';
export { default as PostHead } from './PostHead';
export { default as ReplyItem } from './ReplyItem';
6 changes: 4 additions & 2 deletions src/hooks/useInfiniteQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ type GetResultTypeFromEndpoint<Endpoint> = Endpoint extends QueryHooks<
: never;

interface UseInfiniteQueryOptions {
page: number;
limit: number;
page?: number;
limit?: number;
chat?: string;
post?: string;
}

export function useInfiniteQuery<
Expand Down
92 changes: 78 additions & 14 deletions src/screens/Post.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,95 @@
import React from 'react';
import { View, Text, FlatList, ListRenderItem } from 'react-native';
import { useTheme } from '@/hooks';
import { Wrapper, PostItem } from '@/components';
import React, { useState, useEffect } from 'react';
import {
FlatList,
KeyboardAvoidingView,
ListRenderItem,
RefreshControl,
} from 'react-native';
import { useInfiniteQuery, useTheme } from '@/hooks';
import { Wrapper, ReplyItem, PostHead } from '@/components';
import { AllScreenProps } from 'types/navigation';
import { Post } from 'types/post';
import { PostReply } from 'types/post';
import { postApi, useCreatePostReplyMutation } from '@/services/modules/posts';
import { Input, LinearProgress } from '@rneui/base';
import { useToast } from 'react-native-toast-notifications';

const Screen = ({ route, navigation }: AllScreenProps) => {
const { Fonts, Gutters, Layout } = useTheme();
const { Fonts, Gutters, Layout, Colors } = useTheme();

const toast = useToast();

const [text, setText] = useState('');

const { post } = route.params;

const renderItem: ListRenderItem<Post> = ({ item }) => {
return (
<PostItem
item={item}
onPress={() => navigation.navigate('Post', { post: item })}
/>
);
const res = useInfiniteQuery(postApi.endpoints.getPostReplies, {
post: post.id,
page: 1,
});

const [createPostReply, { isLoading, error }] = useCreatePostReplyMutation();

useEffect(() => {
if (error) {
toast.show('We could not publish your reply', {
type: 'danger',
});
}
}, [error]);

const onReply = () => {
createPostReply({
post: post.id,
text,
});
setText('');
res.refetch();
};

const renderItem: ListRenderItem<PostReply> = ({ item }) => {
return <ReplyItem item={item} onPress={() => {}} />;
};

return (
<Wrapper scrollable={false}>
<FlatList
ListHeaderComponent={() => {
return <PostItem item={post} onPress={() => {}} />;
return <PostHead item={post} onPress={() => {}} />;
}}
data={res.data}
renderItem={renderItem}
refreshControl={
<RefreshControl refreshing={res.isLoading} onRefresh={res.refetch} />
}
/>
<KeyboardAvoidingView behavior="padding">
<Input
placeholder="Reply to this post"
onChangeText={setText}
value={text}
inputContainerStyle={[
{
height: 50,
backgroundColor: Colors.grayLighter,
paddingHorizontal: 10,
},
]}
containerStyle={{
paddingHorizontal: 0,
}}
rightIcon={{
name: 'send',
color: Colors.primary,
onPress: () => {
if (!text) return;
onReply();
},
type: 'material-community',
}}
disabled={isLoading}
/>
{isLoading && <LinearProgress color={Colors.primary} />}
</KeyboardAvoidingView>
</Wrapper>
);
};
Expand Down
Loading

0 comments on commit dc232b6

Please sign in to comment.