-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Clipclops] Add screen to view and send clip clops (#3754)
* add new routes with placeholder screens * add clops list * add a clop input * add some better padding to the clops * some more adjustments * add rnkc * implement rnkc * implement rnkc * be a little less weird about it * rename clop stuff * rename more clop * one more * [Clipclops] Temp codegenerated lexicon (#3749) * add codegenerated lexicon * replace hailey's types * use codegen'd types in components * fix error + throw if fetch failed * remove bad imports * update messageslist and messageitem * import useState * add clop service URL hook * add dm service url storage * use context * use context for service url (temp) * remove log * nits --------- Co-authored-by: Samuel Newman <[email protected]>
- Loading branch information
Showing
28 changed files
with
1,295 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import React from 'react' | ||
import {Pressable, TextInput, View} from 'react-native' | ||
|
||
import {atoms as a, useTheme} from '#/alf' | ||
import {Text} from '#/components/Typography' | ||
|
||
export function MessageInput({ | ||
onSendMessage, | ||
onFocus, | ||
onBlur, | ||
}: { | ||
onSendMessage: (message: string) => void | ||
onFocus: () => void | ||
onBlur: () => void | ||
}) { | ||
const t = useTheme() | ||
const [message, setMessage] = React.useState('') | ||
|
||
const inputRef = React.useRef<TextInput>(null) | ||
|
||
const onSubmit = React.useCallback(() => { | ||
onSendMessage(message) | ||
setMessage('') | ||
setTimeout(() => { | ||
inputRef.current?.focus() | ||
}, 100) | ||
}, [message, onSendMessage]) | ||
|
||
return ( | ||
<View | ||
style={[ | ||
a.flex_row, | ||
a.py_sm, | ||
a.px_sm, | ||
a.rounded_full, | ||
a.mt_sm, | ||
t.atoms.bg_contrast_25, | ||
]}> | ||
<TextInput | ||
accessibilityLabel="Text input field" | ||
accessibilityHint="Write a message" | ||
value={message} | ||
onChangeText={setMessage} | ||
placeholder="Write a message" | ||
style={[a.flex_1, a.text_sm, a.px_sm]} | ||
onSubmitEditing={onSubmit} | ||
onFocus={onFocus} | ||
onBlur={onBlur} | ||
placeholderTextColor={t.palette.contrast_500} | ||
ref={inputRef} | ||
/> | ||
<Pressable | ||
accessibilityRole="button" | ||
style={[ | ||
a.rounded_full, | ||
a.align_center, | ||
a.justify_center, | ||
{height: 30, width: 30, backgroundColor: t.palette.primary_500}, | ||
]} | ||
onPress={onSubmit}> | ||
<Text style={a.text_md}>🐴</Text> | ||
</Pressable> | ||
</View> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import React from 'react' | ||
import {View} from 'react-native' | ||
|
||
import {atoms as a, useTheme} from '#/alf' | ||
import {Text} from '#/components/Typography' | ||
import * as TempDmChatDefs from '#/temp/dm/defs' | ||
|
||
export function MessageItem({item}: {item: TempDmChatDefs.MessageView}) { | ||
const t = useTheme() | ||
|
||
return ( | ||
<View | ||
style={[ | ||
a.py_sm, | ||
a.px_md, | ||
a.my_xs, | ||
a.rounded_md, | ||
{ | ||
backgroundColor: t.palette.primary_500, | ||
maxWidth: '65%', | ||
borderRadius: 17, | ||
}, | ||
]}> | ||
<Text style={[a.text_md, {lineHeight: 1.2, color: 'white'}]}> | ||
{item.text} | ||
</Text> | ||
</View> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import React, {useCallback, useMemo, useRef, useState} from 'react' | ||
import {Alert, FlatList, View, ViewToken} from 'react-native' | ||
import {KeyboardAvoidingView} from 'react-native-keyboard-controller' | ||
|
||
import {isWeb} from 'platform/detection' | ||
import {MessageInput} from '#/screens/Messages/Conversation/MessageInput' | ||
import {MessageItem} from '#/screens/Messages/Conversation/MessageItem' | ||
import { | ||
useChat, | ||
useChatLogQuery, | ||
useSendMessageMutation, | ||
} from '#/screens/Messages/Temp/query/query' | ||
import {Loader} from '#/components/Loader' | ||
import {Text} from '#/components/Typography' | ||
import * as TempDmChatDefs from '#/temp/dm/defs' | ||
|
||
function MaybeLoader({isLoading}: {isLoading: boolean}) { | ||
return ( | ||
<View | ||
style={{ | ||
height: 50, | ||
width: '100%', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
}}> | ||
{isLoading && <Loader size="xl" />} | ||
</View> | ||
) | ||
} | ||
|
||
function renderItem({ | ||
item, | ||
}: { | ||
item: TempDmChatDefs.MessageView | TempDmChatDefs.DeletedMessage | ||
}) { | ||
if (TempDmChatDefs.isMessageView(item)) return <MessageItem item={item} /> | ||
|
||
if (TempDmChatDefs.isDeletedMessage(item)) return <Text>Deleted message</Text> | ||
|
||
return null | ||
} | ||
|
||
// TODO rm | ||
// TEMP: This is a temporary function to generate unique keys for mutation placeholders | ||
const generateUniqueKey = () => `_${Math.random().toString(36).substr(2, 9)}` | ||
|
||
function onScrollToEndFailed() { | ||
// Placeholder function. You have to give FlatList something or else it will error. | ||
} | ||
|
||
export function MessagesList({chatId}: {chatId: string}) { | ||
const flatListRef = useRef<FlatList>(null) | ||
|
||
// Whenever we reach the end (visually the top), we don't want to keep calling it. We will set `isFetching` to true | ||
// once the request for new posts starts. Then, we will change it back to false after the content size changes. | ||
const isFetching = useRef(false) | ||
|
||
// We use this to know if we should scroll after a new clop is added to the list | ||
const isAtBottom = useRef(false) | ||
|
||
// Because the viewableItemsChanged callback won't have access to the updated state, we use a ref to store the | ||
// total number of clops | ||
// TODO this needs to be set to whatever the initial number of messages is | ||
const totalMessages = useRef(10) | ||
|
||
// TODO later | ||
const [_, setShowSpinner] = useState(false) | ||
|
||
// Query Data | ||
const {data: chat} = useChat(chatId) | ||
const {mutate: sendMessage} = useSendMessageMutation(chatId) | ||
useChatLogQuery() | ||
|
||
const [onViewableItemsChanged, viewabilityConfig] = useMemo(() => { | ||
return [ | ||
(info: {viewableItems: Array<ViewToken>; changed: Array<ViewToken>}) => { | ||
const firstVisibleIndex = info.viewableItems[0]?.index | ||
|
||
isAtBottom.current = Number(firstVisibleIndex) < 2 | ||
}, | ||
{ | ||
itemVisiblePercentThreshold: 50, | ||
minimumViewTime: 10, | ||
}, | ||
] | ||
}, []) | ||
|
||
const onContentSizeChange = useCallback(() => { | ||
if (isAtBottom.current) { | ||
flatListRef.current?.scrollToOffset({offset: 0, animated: true}) | ||
} | ||
|
||
isFetching.current = false | ||
setShowSpinner(false) | ||
}, []) | ||
|
||
const onEndReached = useCallback(() => { | ||
if (isFetching.current) return | ||
isFetching.current = true | ||
setShowSpinner(true) | ||
|
||
// Eventually we will add more here when we hit the top through RQuery | ||
// We wouldn't actually use a timeout, but there would be a delay while loading | ||
setTimeout(() => { | ||
// Do something | ||
setShowSpinner(false) | ||
}, 1000) | ||
}, []) | ||
|
||
const onInputFocus = useCallback(() => { | ||
if (!isAtBottom.current) { | ||
flatListRef.current?.scrollToOffset({offset: 0, animated: true}) | ||
} | ||
}, []) | ||
|
||
const onSendMessage = useCallback( | ||
async (message: string) => { | ||
if (!message) return | ||
|
||
try { | ||
sendMessage({ | ||
message, | ||
tempId: generateUniqueKey(), | ||
}) | ||
} catch (e: any) { | ||
Alert.alert(e.toString()) | ||
} | ||
}, | ||
[sendMessage], | ||
) | ||
|
||
const onInputBlur = useCallback(() => {}, []) | ||
|
||
const messages = useMemo(() => { | ||
if (!chat) return [] | ||
|
||
const filtered = chat.messages.filter( | ||
( | ||
message, | ||
): message is | ||
| TempDmChatDefs.MessageView | ||
| TempDmChatDefs.DeletedMessage => { | ||
return ( | ||
TempDmChatDefs.isMessageView(message) || | ||
TempDmChatDefs.isDeletedMessage(message) | ||
) | ||
}, | ||
) | ||
totalMessages.current = filtered.length | ||
}, [chat]) | ||
|
||
return ( | ||
<KeyboardAvoidingView | ||
style={{flex: 1, marginBottom: isWeb ? 20 : 85}} | ||
behavior="padding" | ||
keyboardVerticalOffset={70} | ||
contentContainerStyle={{flex: 1}}> | ||
<FlatList | ||
data={messages} | ||
keyExtractor={item => item.id} | ||
renderItem={renderItem} | ||
contentContainerStyle={{paddingHorizontal: 10}} | ||
// In the future, we might want to adjust this value. Not very concerning right now as long as we are only | ||
// dealing with text. But whenever we have images or other media and things are taller, we will want to lower | ||
// this...probably | ||
initialNumToRender={20} | ||
// Same with the max to render per batch. Let's be safe for now though. | ||
maxToRenderPerBatch={25} | ||
inverted={true} | ||
onEndReached={onEndReached} | ||
onScrollToIndexFailed={onScrollToEndFailed} | ||
onContentSizeChange={onContentSizeChange} | ||
onViewableItemsChanged={onViewableItemsChanged} | ||
viewabilityConfig={viewabilityConfig} | ||
maintainVisibleContentPosition={{ | ||
minIndexForVisible: 0, | ||
}} | ||
// This is actually a header since we are inverted! | ||
ListFooterComponent={<MaybeLoader isLoading={false} />} | ||
removeClippedSubviews={true} | ||
ref={flatListRef} | ||
keyboardDismissMode="none" | ||
/> | ||
<View style={{paddingHorizontal: 10}}> | ||
<MessageInput | ||
onSendMessage={onSendMessage} | ||
onFocus={onInputFocus} | ||
onBlur={onInputBlur} | ||
/> | ||
</View> | ||
</KeyboardAvoidingView> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.