mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

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

[Clipclops] Use API data for clipclop list (#3769)

* use real API

* remove extra tab icon

* messages list web layout + style improvements

* use style's text color for input

* make new chat button way more obvious

---------

Co-authored-by: Hailey <me@haileyok.com>

authored by samuel.fm

Hailey and committed by
GitHub
7b694fd8 bcd36780

+201 -121
+7 -2
src/components/dms/NewChat.tsx
··· 23 23 import {ListMaybePlaceholder} from '../Lists' 24 24 import {Text} from '../Typography' 25 25 26 - export function NewChat({onNewChat}: {onNewChat: (chatId: string) => void}) { 27 - const control = Dialog.useDialogControl() 26 + export function NewChat({ 27 + control, 28 + onNewChat, 29 + }: { 30 + control: Dialog.DialogControlProps 31 + onNewChat: (chatId: string) => void 32 + }) { 28 33 const t = useTheme() 29 34 const {_} = useLingui() 30 35
+1 -1
src/screens/Messages/Conversation/MessageInput.tsx
··· 42 42 value={message} 43 43 onChangeText={setMessage} 44 44 placeholder="Write a message" 45 - style={[a.flex_1, a.text_sm, a.px_sm]} 45 + style={[a.flex_1, a.text_sm, a.px_sm, t.atoms.text]} 46 46 onSubmitEditing={onSubmit} 47 47 onFocus={onFocus} 48 48 onBlur={onBlur}
+163 -107
src/screens/Messages/List/index.tsx
··· 1 + /* eslint-disable react/prop-types */ 2 + 1 3 import React, {useCallback, useMemo, useState} from 'react' 2 4 import {View} from 'react-native' 3 - import {msg} from '@lingui/macro' 5 + import {msg, Trans} from '@lingui/macro' 4 6 import {useLingui} from '@lingui/react' 5 7 import {NativeStackScreenProps} from '@react-navigation/native-stack' 6 - import {useInfiniteQuery} from '@tanstack/react-query' 7 8 8 9 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 9 10 import {MessagesTabNavigatorParams} from '#/lib/routes/types' ··· 14 15 import {List} from '#/view/com/util/List' 15 16 import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' 16 17 import {ViewHeader} from '#/view/com/util/ViewHeader' 17 - import {useTheme} from '#/alf' 18 + import {useBreakpoints, useTheme} from '#/alf' 18 19 import {atoms as a} from '#/alf' 20 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 21 + import {DialogControlProps, useDialogControl} from '#/components/Dialog' 22 + import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope' 19 23 import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider' 20 24 import {Link} from '#/components/Link' 21 25 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 22 26 import {Text} from '#/components/Typography' 27 + import * as TempDmChatDefs from '#/temp/dm/defs' 23 28 import {NewChat} from '../../../components/dms/NewChat' 24 29 import {ClipClopGate} from '../gate' 30 + import {useListChats} from '../Temp/query/query' 25 31 26 32 type Props = NativeStackScreenProps<MessagesTabNavigatorParams, 'MessagesList'> 27 33 export function MessagesListScreen({navigation}: Props) { 28 34 const {_} = useLingui() 29 35 const t = useTheme() 36 + const newChatControl = useDialogControl() 37 + const {gtMobile} = useBreakpoints() 30 38 31 39 const renderButton = useCallback(() => { 32 40 return ( ··· 50 58 fetchNextPage, 51 59 error, 52 60 refetch, 53 - } = usePlaceholderConversations() 61 + } = useListChats() 54 62 55 63 const isError = !!error 56 64 57 65 const conversations = useMemo(() => { 58 66 if (data?.pages) { 59 - return data.pages.flat() 67 + return data.pages.flatMap(page => page.chats) 60 68 } 61 69 return [] 62 70 }, [data]) ··· 85 93 navigation.navigate('MessagesConversation', {conversation}), 86 94 [navigation], 87 95 ) 96 + 97 + const onNavigateToSettings = useCallback(() => { 98 + navigation.navigate('MessagesSettings') 99 + }, [navigation]) 100 + 101 + const renderItem = useCallback(({item}: {item: TempDmChatDefs.ChatView}) => { 102 + return <ChatListItem key={item.id} chat={item} /> 103 + }, []) 88 104 89 105 const gate = useGate() 90 106 if (!gate('dms')) return <ClipClopGate /> ··· 102 118 errorMessage={cleanError(error)} 103 119 onRetry={isError ? refetch : undefined} 104 120 /> 105 - <NewChat onNewChat={onNewChat} /> 121 + <NewChat onNewChat={onNewChat} control={newChatControl} /> 106 122 </> 107 123 ) 108 124 } 109 125 110 126 return ( 111 127 <View style={a.flex_1}> 112 - <ViewHeader 113 - title={_(msg`Messages`)} 114 - showOnDesktop 115 - renderButton={renderButton} 116 - showBorder 117 - canGoBack={false} 118 - /> 119 - <NewChat onNewChat={onNewChat} /> 128 + {!gtMobile && ( 129 + <ViewHeader 130 + title={_(msg`Messages`)} 131 + renderButton={renderButton} 132 + showBorder 133 + canGoBack={false} 134 + /> 135 + )} 136 + <NewChat onNewChat={onNewChat} control={newChatControl} /> 120 137 <List 121 138 data={conversations} 122 - renderItem={({item}) => { 123 - return ( 124 - <Link 125 - to={`/messages/3kqzb4mytxk2v`} 126 - style={[a.flex_1, a.pl_md, a.py_sm, a.gap_md, a.pr_2xl]}> 127 - <PreviewableUserAvatar profile={item.profile} size={44} /> 128 - <View style={[a.flex_1]}> 129 - <View 130 - style={[ 131 - a.flex_row, 132 - a.align_center, 133 - a.justify_between, 134 - a.gap_lg, 135 - a.flex_1, 136 - ]}> 137 - <Text numberOfLines={1}> 138 - <Text style={item.unread && a.font_bold}> 139 - {item.profile.displayName || item.profile.handle} 140 - </Text>{' '} 141 - <Text style={t.atoms.text_contrast_medium}> 142 - @{item.profile.handle} 143 - </Text> 144 - </Text> 145 - {item.unread && ( 146 - <View 147 - style={[ 148 - a.ml_2xl, 149 - {backgroundColor: t.palette.primary_500}, 150 - a.rounded_full, 151 - {height: 7, width: 7}, 152 - ]} 153 - /> 154 - )} 155 - </View> 156 - <Text 157 - numberOfLines={2} 158 - style={[ 159 - a.text_sm, 160 - item.unread ? a.font_bold : t.atoms.text_contrast_medium, 161 - ]}> 162 - {item.lastMessage} 163 - </Text> 164 - </View> 165 - </Link> 166 - ) 167 - }} 168 - keyExtractor={item => item.profile.did} 139 + renderItem={renderItem} 140 + keyExtractor={item => item.id} 169 141 refreshing={isPTRing} 170 142 onRefresh={onRefresh} 171 143 onEndReached={onEndReached} 144 + ListHeaderComponent={ 145 + <DesktopHeader 146 + newChatControl={newChatControl} 147 + onNavigateToSettings={onNavigateToSettings} 148 + /> 149 + } 172 150 ListFooterComponent={ 173 151 <ListFooter 174 152 isFetchingNextPage={isFetchingNextPage} ··· 180 158 onEndReachedThreshold={3} 181 159 initialNumToRender={initialNumToRender} 182 160 windowSize={11} 161 + // @ts-ignore our .web version only -sfn 162 + desktopFixedHeight 183 163 /> 184 164 </View> 185 165 ) 186 166 } 187 167 188 - function usePlaceholderConversations() { 168 + function ChatListItem({chat}: {chat: TempDmChatDefs.ChatView}) { 169 + const t = useTheme() 170 + const {_} = useLingui() 189 171 const {getAgent} = useAgent() 190 172 191 - return useInfiniteQuery({ 192 - queryKey: ['messages'], 193 - queryFn: async () => { 194 - const people = await getAgent().getProfiles({actors: PLACEHOLDER_PEOPLE}) 195 - return people.data.profiles.map(profile => ({ 196 - profile, 197 - unread: Math.random() > 0.5, 198 - lastMessage: getRandomPost(), 199 - })) 200 - }, 201 - initialPageParam: undefined, 202 - getNextPageParam: () => undefined, 203 - }) 173 + let lastMessage = _(msg`No messages yet`) 174 + if (TempDmChatDefs.isMessageView(chat.lastMessage)) { 175 + lastMessage = chat.lastMessage.text 176 + } 177 + 178 + const otherUser = chat.members.find( 179 + member => member.did !== getAgent().session?.did, 180 + ) 181 + 182 + if (!otherUser) { 183 + return null 184 + } 185 + 186 + return ( 187 + <Link to={`/messages/${chat.id}`} style={a.flex_1}> 188 + {({hovered, pressed}) => ( 189 + <View 190 + style={[ 191 + a.flex_row, 192 + a.flex_1, 193 + a.pl_md, 194 + a.py_sm, 195 + a.gap_md, 196 + a.pr_2xl, 197 + (hovered || pressed) && t.atoms.bg_contrast_25, 198 + ]}> 199 + <View pointerEvents="none"> 200 + <PreviewableUserAvatar profile={otherUser} size={42} /> 201 + </View> 202 + <View style={[a.flex_1]}> 203 + <Text numberOfLines={1} style={a.leading_snug}> 204 + <Text style={[t.atoms.text, chat.unreadCount > 0 && a.font_bold]}> 205 + {otherUser.displayName || otherUser.handle} 206 + </Text>{' '} 207 + <Text style={t.atoms.text_contrast_medium}> 208 + @{otherUser.handle} 209 + </Text> 210 + </Text> 211 + <Text 212 + numberOfLines={2} 213 + style={[ 214 + a.text_sm, 215 + chat.unread ? a.font_bold : t.atoms.text_contrast_medium, 216 + ]}> 217 + {lastMessage} 218 + </Text> 219 + </View> 220 + {chat.unreadCount > 0 && ( 221 + <View 222 + style={[ 223 + a.flex_0, 224 + a.ml_2xl, 225 + a.mt_xs, 226 + {backgroundColor: t.palette.primary_500}, 227 + a.rounded_full, 228 + {height: 7, width: 7}, 229 + ]} 230 + /> 231 + )} 232 + </View> 233 + )} 234 + </Link> 235 + ) 204 236 } 205 237 206 - const PLACEHOLDER_PEOPLE = [ 207 - 'pfrazee.com', 208 - 'haileyok.com', 209 - 'danabra.mov', 210 - 'esb.lol', 211 - 'samuel.bsky.team', 212 - ] 238 + function DesktopHeader({ 239 + newChatControl, 240 + onNavigateToSettings, 241 + }: { 242 + newChatControl: DialogControlProps 243 + onNavigateToSettings: () => void 244 + }) { 245 + const t = useTheme() 246 + const {_} = useLingui() 247 + const {gtMobile, gtTablet} = useBreakpoints() 213 248 214 - function getRandomPost() { 215 - const num = Math.floor(Math.random() * 10) 216 - switch (num) { 217 - case 0: 218 - return 'hello' 219 - case 1: 220 - return 'lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua' 221 - case 2: 222 - return 'banger post' 223 - case 3: 224 - return 'lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua' 225 - case 4: 226 - return 'lol look at this bug' 227 - case 5: 228 - return 'wow' 229 - case 6: 230 - return "that's pretty cool, wow!" 231 - case 7: 232 - return 'I think this is a bug' 233 - case 8: 234 - return 'Hello World!' 235 - case 9: 236 - return 'DMs when???' 237 - default: 238 - return 'this is unlikely' 249 + if (!gtMobile) { 250 + return null 239 251 } 252 + 253 + return ( 254 + <View 255 + style={[ 256 + t.atoms.bg, 257 + t.atoms.border_contrast_low, 258 + a.border_b, 259 + a.flex_row, 260 + a.align_center, 261 + a.justify_between, 262 + a.gap_lg, 263 + a.px_lg, 264 + a.py_sm, 265 + ]}> 266 + <Text style={[a.text_2xl, a.font_bold]}> 267 + <Trans>Messages</Trans> 268 + </Text> 269 + <View style={[a.flex_row, a.align_center, a.gap_md]}> 270 + <Button 271 + label={_(msg`Message settings`)} 272 + color="secondary" 273 + size="large" 274 + variant="ghost" 275 + style={[{height: 'auto', width: 'auto'}, a.px_sm, a.py_sm]} 276 + onPress={onNavigateToSettings}> 277 + <ButtonIcon icon={SettingsSlider} /> 278 + </Button> 279 + {gtTablet && ( 280 + <Button 281 + label={_(msg`New chat`)} 282 + color="primary" 283 + size="large" 284 + variant="solid" 285 + style={[{height: 'auto', width: 'auto'}, a.px_md, a.py_sm]} 286 + onPress={newChatControl.open}> 287 + <ButtonIcon icon={Envelope} position="right" /> 288 + <ButtonText> 289 + <Trans>New chat</Trans> 290 + </ButtonText> 291 + </Button> 292 + )} 293 + </View> 294 + </View> 295 + ) 240 296 }
+30 -1
src/screens/Messages/Temp/query/query.ts
··· 1 - import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' 1 + import { 2 + useInfiniteQuery, 3 + useMutation, 4 + useQuery, 5 + useQueryClient, 6 + } from '@tanstack/react-query' 2 7 3 8 import {useAgent} from '#/state/session' 4 9 import * as TempDmChatDefs from '#/temp/dm/defs' ··· 6 11 import * as TempDmChatGetChatForMembers from '#/temp/dm/getChatForMembers' 7 12 import * as TempDmChatGetChatLog from '#/temp/dm/getChatLog' 8 13 import * as TempDmChatGetChatMessages from '#/temp/dm/getChatMessages' 14 + import * as TempDmChatListChats from '#/temp/dm/listChats' 9 15 import {useDmServiceUrlStorage} from '../useDmServiceUrlStorage' 10 16 11 17 /** ··· 250 256 onError, 251 257 }) 252 258 } 259 + 260 + export function useListChats() { 261 + const headers = useHeaders() 262 + const {serviceUrl} = useDmServiceUrlStorage() 263 + 264 + return useInfiniteQuery({ 265 + queryKey: ['chats'], 266 + queryFn: async ({pageParam}) => { 267 + const response = await fetch( 268 + `${serviceUrl}/xrpc/temp.dm.listChats${ 269 + pageParam ? `?cursor=${pageParam}` : '' 270 + }`, 271 + {headers}, 272 + ) 273 + 274 + if (!response.ok) throw new Error('Failed to fetch chats') 275 + 276 + return (await response.json()) as TempDmChatListChats.OutputSchema 277 + }, 278 + initialPageParam: undefined as string | undefined, 279 + getNextPageParam: lastPage => lastPage.cursor, 280 + }) 281 + }
-10
src/view/shell/bottom-bar/BottomBarWeb.tsx
··· 122 122 ) 123 123 }} 124 124 </NavItem> 125 - <NavItem routeName="Messages" href="/messages"> 126 - {() => { 127 - return ( 128 - <Envelope 129 - size="lg" 130 - style={[styles.ctrlIcon, pal.text, styles.messagesIcon]} 131 - /> 132 - ) 133 - }} 134 - </NavItem> 135 125 {gate('dms') && ( 136 126 <NavItem routeName="Messages" href="/messages"> 137 127 {({isActive}) => {