Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[Clipclops] Add clop sent time to clipclop (#3772)

* add message sent time to message

* fix last message in group logic

authored by

Samuel Newman and committed by
GitHub
611ff0c7 7b694fd8

+162 -51
+126 -23
src/screens/Messages/Conversation/MessageItem.tsx
··· 1 - import React from 'react' 2 - import {View} from 'react-native' 1 + import React, {useCallback} from 'react' 2 + import {StyleProp, TextStyle, View} from 'react-native' 3 + import {msg} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 3 5 4 6 import {useAgent} from '#/state/session' 7 + import {TimeElapsed} from '#/view/com/util/TimeElapsed' 5 8 import {atoms as a, useTheme} from '#/alf' 6 9 import {Text} from '#/components/Typography' 7 10 import * as TempDmChatDefs from '#/temp/dm/defs' 8 11 9 - export function MessageItem({item}: {item: TempDmChatDefs.MessageView}) { 12 + export function MessageItem({ 13 + item, 14 + next, 15 + }: { 16 + item: TempDmChatDefs.MessageView 17 + next: TempDmChatDefs.MessageView | TempDmChatDefs.DeletedMessage | null 18 + }) { 10 19 const t = useTheme() 11 20 const {getAgent} = useAgent() 12 21 13 - const fromMe = item.sender?.did === getAgent().session?.did 22 + const isFromSelf = item.sender?.did === getAgent().session?.did 23 + 24 + const isNextFromSelf = 25 + TempDmChatDefs.isMessageView(next) && 26 + next.sender?.did === getAgent().session?.did 27 + 28 + const isLastInGroup = !next || isFromSelf ? !isNextFromSelf : isNextFromSelf 14 29 15 30 return ( 16 - <View 17 - style={[ 18 - a.py_sm, 19 - a.px_md, 20 - a.my_xs, 21 - a.rounded_md, 22 - fromMe ? a.self_end : a.self_start, 23 - { 24 - backgroundColor: fromMe 25 - ? t.palette.primary_500 26 - : t.palette.contrast_50, 27 - maxWidth: '65%', 28 - borderRadius: 17, 29 - }, 30 - ]}> 31 - <Text 32 - style={[a.text_md, a.leading_snug, fromMe && {color: t.palette.white}]}> 33 - {item.text} 34 - </Text> 31 + <View> 32 + <View 33 + style={[ 34 + a.py_sm, 35 + a.px_lg, 36 + a.my_2xs, 37 + a.rounded_md, 38 + isFromSelf ? a.self_end : a.self_start, 39 + { 40 + maxWidth: '65%', 41 + backgroundColor: isFromSelf 42 + ? t.palette.primary_500 43 + : t.palette.contrast_50, 44 + borderRadius: 17, 45 + }, 46 + isFromSelf 47 + ? {borderBottomRightRadius: isLastInGroup ? 2 : 17} 48 + : {borderBottomLeftRadius: isLastInGroup ? 2 : 17}, 49 + ]}> 50 + <Text 51 + style={[ 52 + a.text_md, 53 + a.leading_snug, 54 + isFromSelf && {color: t.palette.white}, 55 + ]}> 56 + {item.text} 57 + </Text> 58 + </View> 59 + <Metadata 60 + message={item} 61 + isLastInGroup={isLastInGroup} 62 + style={isFromSelf ? a.text_right : a.text_left} 63 + /> 35 64 </View> 36 65 ) 37 66 } 67 + 68 + function Metadata({ 69 + message, 70 + isLastInGroup, 71 + style, 72 + }: { 73 + message: TempDmChatDefs.MessageView 74 + isLastInGroup: boolean 75 + style: StyleProp<TextStyle> 76 + }) { 77 + const t = useTheme() 78 + const {_} = useLingui() 79 + 80 + const relativeTimestamp = useCallback( 81 + (timestamp: string) => { 82 + const date = new Date(timestamp) 83 + const now = new Date() 84 + 85 + const time = new Intl.DateTimeFormat(undefined, { 86 + hour: 'numeric', 87 + minute: 'numeric', 88 + hour12: true, 89 + }).format(date) 90 + 91 + const diff = now.getTime() - date.getTime() 92 + 93 + // under 1 minute 94 + if (diff < 1000 * 60) { 95 + return _(msg`Now`) 96 + } 97 + 98 + // in the last day 99 + if (now.getDate() === date.getDate()) { 100 + return time 101 + } 102 + 103 + // if yesterday 104 + if (diff < 24 * 60 * 60 * 1000 && now.getDate() - date.getDate() === 1) { 105 + return _(msg`Yesterday, ${time}`) 106 + } 107 + 108 + return new Intl.DateTimeFormat(undefined, { 109 + hour: 'numeric', 110 + minute: 'numeric', 111 + hour12: true, 112 + day: 'numeric', 113 + month: 'numeric', 114 + year: 'numeric', 115 + }).format(date) 116 + }, 117 + [_], 118 + ) 119 + 120 + if (!isLastInGroup) { 121 + return null 122 + } 123 + 124 + return ( 125 + <TimeElapsed timestamp={message.sentAt} timeToString={relativeTimestamp}> 126 + {({timeElapsed}) => ( 127 + <Text 128 + style={[ 129 + t.atoms.text_contrast_medium, 130 + a.text_xs, 131 + a.mt_xs, 132 + a.mb_lg, 133 + style, 134 + ]}> 135 + {timeElapsed} 136 + </Text> 137 + )} 138 + </TimeElapsed> 139 + ) 140 + }
+28 -20
src/screens/Messages/Conversation/MessagesList.tsx
··· 3 3 import {Alert} from 'react-native' 4 4 import {KeyboardAvoidingView} from 'react-native-keyboard-controller' 5 5 6 - import {isWeb} from 'platform/detection' 6 + import {isWeb} from '#/platform/detection' 7 7 import {MessageInput} from '#/screens/Messages/Conversation/MessageInput' 8 8 import {MessageItem} from '#/screens/Messages/Conversation/MessageItem' 9 9 import { ··· 15 15 import {Text} from '#/components/Typography' 16 16 import * as TempDmChatDefs from '#/temp/dm/defs' 17 17 18 + type MessageWithNext = { 19 + message: TempDmChatDefs.MessageView | TempDmChatDefs.DeletedMessage 20 + next: TempDmChatDefs.MessageView | TempDmChatDefs.DeletedMessage | null 21 + } 22 + 18 23 function MaybeLoader({isLoading}: {isLoading: boolean}) { 19 24 return ( 20 25 <View ··· 29 34 ) 30 35 } 31 36 32 - function renderItem({ 33 - item, 34 - }: { 35 - item: TempDmChatDefs.MessageView | TempDmChatDefs.DeletedMessage 36 - }) { 37 - if (TempDmChatDefs.isMessageView(item)) return <MessageItem item={item} /> 37 + function renderItem({item}: {item: MessageWithNext}) { 38 + if (TempDmChatDefs.isMessageView(item.message)) 39 + return <MessageItem item={item.message} next={item.next} /> 38 40 39 41 if (TempDmChatDefs.isDeletedMessage(item)) return <Text>Deleted message</Text> 40 42 ··· 136 138 const messages = useMemo(() => { 137 139 if (!chat) return [] 138 140 139 - const filtered = chat.messages.filter( 140 - ( 141 - message, 142 - ): message is 143 - | TempDmChatDefs.MessageView 144 - | TempDmChatDefs.DeletedMessage => { 145 - return ( 146 - TempDmChatDefs.isMessageView(message) || 147 - TempDmChatDefs.isDeletedMessage(message) 148 - ) 149 - }, 150 - ) 141 + const filtered = chat.messages 142 + .filter( 143 + ( 144 + message, 145 + ): message is 146 + | TempDmChatDefs.MessageView 147 + | TempDmChatDefs.DeletedMessage => { 148 + return ( 149 + TempDmChatDefs.isMessageView(message) || 150 + TempDmChatDefs.isDeletedMessage(message) 151 + ) 152 + }, 153 + ) 154 + .reduce((acc, message) => { 155 + // convert [n1, n2, n3, ...] to [{message: n1, next: n2}, {message: n2, next: n3}, {message: n3, next: n4}, ...] 156 + 157 + return [...acc, {message, next: acc.at(-1)?.message ?? null}] 158 + }, [] as MessageWithNext[]) 151 159 totalMessages.current = filtered.length 152 160 153 161 return filtered ··· 161 169 contentContainerStyle={{flex: 1}}> 162 170 <FlatList 163 171 data={messages} 164 - keyExtractor={item => item.id} 172 + keyExtractor={item => item.message.id} 165 173 renderItem={renderItem} 166 174 contentContainerStyle={{paddingHorizontal: 10}} 167 175 // In the future, we might want to adjust this value. Not very concerning right now as long as we are only
+2 -6
src/screens/Messages/Temp/query/query.ts
··· 140 140 id: variables.tempId, 141 141 text: variables.message, 142 142 sender: {did: headers.Authorization}, // TODO a real DID get 143 + sentAt: new Date().toISOString(), 143 144 }, 144 145 ...prev.messages, 145 146 ], ··· 151 152 return { 152 153 ...prev, 153 154 messages: prev.messages.map(m => 154 - m.id === variables.tempId 155 - ? { 156 - ...m, 157 - id: result.id, 158 - } 159 - : m, 155 + m.id === variables.tempId ? {...m, id: result.id} : m, 160 156 ), 161 157 } 162 158 })
+6 -2
src/view/com/util/TimeElapsed.tsx
··· 6 6 export function TimeElapsed({ 7 7 timestamp, 8 8 children, 9 + timeToString = ago, 9 10 }: { 10 11 timestamp: string 11 12 children: ({timeElapsed}: {timeElapsed: string}) => JSX.Element 13 + timeToString?: (timeElapsed: string) => string 12 14 }) { 13 15 const tick = useTickEveryMinute() 14 - const [timeElapsed, setTimeAgo] = React.useState(() => ago(timestamp)) 16 + const [timeElapsed, setTimeAgo] = React.useState(() => 17 + timeToString(timestamp), 18 + ) 15 19 16 20 const [prevTick, setPrevTick] = React.useState(tick) 17 21 if (prevTick !== tick) { 18 22 setPrevTick(tick) 19 - setTimeAgo(ago(timestamp)) 23 + setTimeAgo(timeToString(timestamp)) 20 24 } 21 25 22 26 return children({timeElapsed})