mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Reply working
This commit is contained in:
		| @@ -50,6 +50,7 @@ const Timeline: React.FC<Props> = ({ | ||||
|     } | ||||
|   ] | ||||
|   const { | ||||
|     status, | ||||
|     isSuccess, | ||||
|     isLoading, | ||||
|     isError, | ||||
| @@ -109,14 +110,8 @@ const Timeline: React.FC<Props> = ({ | ||||
|     [] | ||||
|   ) | ||||
|   const flItemEmptyComponent = useMemo( | ||||
|     () => ( | ||||
|       <TimelineEmpty | ||||
|         isLoading={isLoading} | ||||
|         isError={isError} | ||||
|         refetch={refetch} | ||||
|       /> | ||||
|     ), | ||||
|     [isLoading, isError] | ||||
|     () => <TimelineEmpty status={status} refetch={refetch} />, | ||||
|     [isLoading, isError, isSuccess] | ||||
|   ) | ||||
|   const flOnRefresh = useCallback( | ||||
|     () => | ||||
| @@ -124,7 +119,7 @@ const Timeline: React.FC<Props> = ({ | ||||
|       fetchMore( | ||||
|         { | ||||
|           direction: 'prev', | ||||
|           id: flattenData[0].id | ||||
|           id: flattenData.length ? flattenData[0].id : null | ||||
|         }, | ||||
|         { previous: true } | ||||
|       ), | ||||
| @@ -142,11 +137,6 @@ const Timeline: React.FC<Props> = ({ | ||||
|   ) | ||||
|   const flFooter = useCallback(() => { | ||||
|     return <TimelineEnd isFetchingMore={isFetchingMore} /> | ||||
|     // if (isFetchingMore) { | ||||
|     //   return <ActivityIndicator /> | ||||
|     // } else { | ||||
|     //   return <TimelineEnd /> | ||||
|     // } | ||||
|   }, [isFetchingMore]) | ||||
|   const onScrollToIndexFailed = useCallback(error => { | ||||
|     const offset = error.averageItemLength * error.index | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import React from 'react' | ||||
| import React, { useCallback, useMemo } from 'react' | ||||
| import { Pressable, StyleSheet, View } from 'react-native' | ||||
| import { useNavigation } from '@react-navigation/native' | ||||
|  | ||||
| @@ -6,40 +6,61 @@ import TimelineAvatar from './Shared/Avatar' | ||||
| import TimelineHeaderConversation from './Shared/HeaderConversation' | ||||
| import TimelineContent from './Shared/Content' | ||||
| import { StyleConstants } from 'src/utils/styles/constants' | ||||
| import TimelineActions from './Shared/Actions' | ||||
|  | ||||
| export interface Props { | ||||
|   item: Mastodon.Conversation | ||||
|   queryKey: App.QueryKey | ||||
|   highlighted?: boolean | ||||
| } | ||||
| // Unread and mark as unread | ||||
| const TimelineConversation: React.FC<Props> = ({ item }) => { | ||||
| const TimelineConversation: React.FC<Props> = ({ | ||||
|   item, | ||||
|   queryKey, | ||||
|   highlighted = false | ||||
| }) => { | ||||
|   const navigation = useNavigation() | ||||
|  | ||||
|   const conversationOnPress = useCallback( | ||||
|     () => | ||||
|       item.last_status && | ||||
|       navigation.navigate('Screen-Shared-Toot', { | ||||
|         toot: item.last_status | ||||
|       }), | ||||
|     [] | ||||
|   ) | ||||
|  | ||||
|   const conversationChildren = useMemo(() => { | ||||
|     return item.last_status && <TimelineContent status={item.last_status} /> | ||||
|   }, []) | ||||
|  | ||||
|   return ( | ||||
|     <View style={styles.statusView}> | ||||
|       <View style={styles.status}> | ||||
|         <TimelineAvatar account={item.accounts[0]} /> | ||||
|         <View style={styles.details}> | ||||
|           <TimelineHeaderConversation | ||||
|             queryKey={queryKey} | ||||
|             id={item.id} | ||||
|             account={item.accounts[0]} | ||||
|             created_at={item.last_status?.created_at} | ||||
|           /> | ||||
|           {/* Can pass toot info to next page to speed up performance */} | ||||
|           <Pressable | ||||
|             onPress={() => | ||||
|               item.last_status && | ||||
|               navigation.navigate('Screen-Shared-Toot', { | ||||
|                 toot: item.last_status.id | ||||
|               }) | ||||
|             } | ||||
|           > | ||||
|             {item.last_status ? ( | ||||
|               <TimelineContent status={item.last_status} /> | ||||
|             ) : ( | ||||
|               <></> | ||||
|             )} | ||||
|           </Pressable> | ||||
|             onPress={conversationOnPress} | ||||
|             children={conversationChildren} | ||||
|           /> | ||||
|         </View> | ||||
|       </View> | ||||
|  | ||||
|       <View | ||||
|         style={{ | ||||
|           paddingLeft: highlighted | ||||
|             ? 0 | ||||
|             : StyleConstants.Avatar.S + StyleConstants.Spacing.S | ||||
|         }} | ||||
|       > | ||||
|         <TimelineActions queryKey={queryKey} status={item.last_status!} /> | ||||
|       </View> | ||||
|     </View> | ||||
|   ) | ||||
| } | ||||
|   | ||||
| @@ -46,11 +46,14 @@ const TimelineDefault: React.FC<Props> = ({ | ||||
|   const tootChildren = useMemo( | ||||
|     () => ( | ||||
|       <View | ||||
|         style={{ | ||||
|           paddingLeft: highlighted | ||||
|             ? 0 | ||||
|             : StyleConstants.Avatar.S + StyleConstants.Spacing.S | ||||
|         }} | ||||
|         style={[ | ||||
|           styles.content, | ||||
|           { | ||||
|             paddingLeft: highlighted | ||||
|               ? 0 | ||||
|               : StyleConstants.Avatar.S + StyleConstants.Spacing.S | ||||
|           } | ||||
|         ]} | ||||
|       > | ||||
|         {actualStatus.content.length > 0 && ( | ||||
|           <TimelineContent status={actualStatus} highlighted={highlighted} /> | ||||
| @@ -98,11 +101,10 @@ const styles = StyleSheet.create({ | ||||
|   header: { | ||||
|     flex: 1, | ||||
|     width: '100%', | ||||
|     flexDirection: 'row', | ||||
|     marginBottom: StyleConstants.Spacing.S | ||||
|     flexDirection: 'row' | ||||
|   }, | ||||
|   content: { | ||||
|     paddingLeft: StyleConstants.Avatar.S + StyleConstants.Spacing.S | ||||
|     paddingTop: StyleConstants.Spacing.S | ||||
|   } | ||||
| }) | ||||
|  | ||||
|   | ||||
| @@ -1,35 +1,54 @@ | ||||
| import { Feather } from '@expo/vector-icons' | ||||
| import React from 'react' | ||||
| import React, { useMemo } from 'react' | ||||
| import { ActivityIndicator, StyleSheet, Text, View } from 'react-native' | ||||
| import { QueryStatus } from 'react-query' | ||||
| import { ButtonRow } from 'src/components/Button' | ||||
| import { StyleConstants } from 'src/utils/styles/constants' | ||||
| import { useTheme } from 'src/utils/styles/ThemeManager' | ||||
|  | ||||
| export interface Props { | ||||
|   isLoading: boolean | ||||
|   isError: boolean | ||||
|   status: QueryStatus | ||||
|   refetch: () => void | ||||
| } | ||||
|  | ||||
| const TimelineEmpty: React.FC<Props> = ({ isLoading, isError, refetch }) => { | ||||
| const TimelineEmpty: React.FC<Props> = ({ status, refetch }) => { | ||||
|   const { theme } = useTheme() | ||||
|  | ||||
|   return ( | ||||
|     <View style={styles.base}> | ||||
|       {isLoading && <ActivityIndicator />} | ||||
|       {isError && ( | ||||
|         <> | ||||
|           <Feather | ||||
|             name='frown' | ||||
|             size={StyleConstants.Font.Size.L} | ||||
|             color={theme.primary} | ||||
|           /> | ||||
|           <Text style={[styles.error, { color: theme.primary }]}>加载错误</Text> | ||||
|           <ButtonRow text='重试' onPress={() => refetch()} /> | ||||
|         </> | ||||
|       )} | ||||
|     </View> | ||||
|   ) | ||||
|   const children = useMemo(() => { | ||||
|     switch (status) { | ||||
|       case 'loading': | ||||
|         return <ActivityIndicator /> | ||||
|       case 'error': | ||||
|         return ( | ||||
|           <> | ||||
|             <Feather | ||||
|               name='frown' | ||||
|               size={StyleConstants.Font.Size.L} | ||||
|               color={theme.primary} | ||||
|             /> | ||||
|             <Text style={[styles.error, { color: theme.primary }]}> | ||||
|               加载错误 | ||||
|             </Text> | ||||
|             <ButtonRow text='重试' onPress={() => refetch()} /> | ||||
|           </> | ||||
|         ) | ||||
|       case 'success': | ||||
|         return ( | ||||
|           <> | ||||
|             <Feather | ||||
|               name='smartphone' | ||||
|               size={StyleConstants.Font.Size.L} | ||||
|               color={theme.primary} | ||||
|             /> | ||||
|             <Text style={[styles.error, { color: theme.primary }]}> | ||||
|               空无一物 | ||||
|             </Text> | ||||
|             <ButtonRow text='刷新试试' onPress={() => refetch()} /> | ||||
|           </> | ||||
|         ) | ||||
|     } | ||||
|   }, [status]) | ||||
|   return <View style={styles.base} children={children} /> | ||||
| } | ||||
|  | ||||
| const styles = StyleSheet.create({ | ||||
|   | ||||
| @@ -92,7 +92,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status }) => { | ||||
|     navigation.navigate(getCurrentTab(navigation), { | ||||
|       screen: 'Screen-Shared-Compose', | ||||
|       params: { | ||||
|         type: 'reply', | ||||
|         type: status.visibility === 'direct' ? 'conversation' : 'reply', | ||||
|         incomingStatus: status | ||||
|       } | ||||
|     }) | ||||
|   | ||||
| @@ -1,65 +1,164 @@ | ||||
| import React from 'react' | ||||
| import { StyleSheet, Text, View } from 'react-native' | ||||
| import { Feather } from '@expo/vector-icons' | ||||
| import React, { useCallback, useMemo } from 'react' | ||||
| import { Pressable, StyleSheet, Text, View } from 'react-native' | ||||
| import { useMutation, useQueryCache } from 'react-query' | ||||
| import client from 'src/api/client' | ||||
| import { toast } from 'src/components/toast' | ||||
|  | ||||
| import relativeTime from 'src/utils/relativeTime' | ||||
| import { StyleConstants } from 'src/utils/styles/constants' | ||||
| import { useTheme } from 'src/utils/styles/ThemeManager' | ||||
| import Emojis from './Emojis' | ||||
|  | ||||
| export interface Props { | ||||
|   queryKey: App.QueryKey | ||||
|   id: string | ||||
|   account: Mastodon.Account | ||||
|   created_at?: Mastodon.Status['created_at'] | ||||
| } | ||||
|  | ||||
| const HeaderConversation: React.FC<Props> = ({ account, created_at }) => { | ||||
| const fireMutation = async ({ id }: { id: string }) => { | ||||
|   const res = await client({ | ||||
|     method: 'delete', | ||||
|     instance: 'local', | ||||
|     url: `conversations/${id}` | ||||
|   }) | ||||
|   console.log(res) | ||||
|   if (!res.body.error) { | ||||
|     toast({ type: 'success', content: '删除私信成功' }) | ||||
|     return Promise.resolve() | ||||
|   } else { | ||||
|     toast({ | ||||
|       type: 'error', | ||||
|       content: '删除私信失败,请重试', | ||||
|       autoHide: false | ||||
|     }) | ||||
|     return Promise.reject() | ||||
|   } | ||||
| } | ||||
|  | ||||
| const HeaderConversation: React.FC<Props> = ({ | ||||
|   queryKey, | ||||
|   id, | ||||
|   account, | ||||
|   created_at | ||||
| }) => { | ||||
|   const queryCache = useQueryCache() | ||||
|   const [mutateAction] = useMutation(fireMutation, { | ||||
|     onMutate: () => { | ||||
|       queryCache.cancelQueries(queryKey) | ||||
|       const oldData = queryCache.getQueryData(queryKey) | ||||
|  | ||||
|       queryCache.setQueryData(queryKey, old => | ||||
|         (old as {}[]).map((paging: any) => ({ | ||||
|           toots: paging.toots.filter((toot: any) => toot.id !== id), | ||||
|           pointer: paging.pointer | ||||
|         })) | ||||
|       ) | ||||
|  | ||||
|       return oldData | ||||
|     }, | ||||
|     onError: (err, _, oldData) => { | ||||
|       toast({ type: 'error', content: '请重试', autoHide: false }) | ||||
|       queryCache.setQueryData(queryKey, oldData) | ||||
|     }, | ||||
|     onSettled: () => { | ||||
|       queryCache.invalidateQueries(queryKey) | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   const { theme } = useTheme() | ||||
|  | ||||
|   const actionOnPress = useCallback(() => mutateAction({ id }), []) | ||||
|  | ||||
|   const actionChildren = useMemo( | ||||
|     () => ( | ||||
|       <Feather | ||||
|         name='trash' | ||||
|         color={theme.secondary} | ||||
|         size={StyleConstants.Font.Size.M + 2} | ||||
|       /> | ||||
|     ), | ||||
|     [] | ||||
|   ) | ||||
|  | ||||
|   return ( | ||||
|     <View> | ||||
|     <View style={styles.base}> | ||||
|       <View style={styles.nameAndDate}> | ||||
|         <View style={styles.name}> | ||||
|           {account.emojis ? ( | ||||
|             <Emojis | ||||
|               content={account.display_name || account.username} | ||||
|               emojis={account.emojis} | ||||
|               size={14} | ||||
|               size={StyleConstants.Font.Size.M} | ||||
|               fontBold={true} | ||||
|             /> | ||||
|           ) : ( | ||||
|             <Text numberOfLines={1}> | ||||
|             <Text | ||||
|               numberOfLines={1} | ||||
|               style={[styles.nameWithoutEmoji, { color: theme.primary }]} | ||||
|             > | ||||
|               {account.display_name || account.username} | ||||
|             </Text> | ||||
|           )} | ||||
|           <Text | ||||
|             style={[styles.account, { color: theme.secondary }]} | ||||
|             numberOfLines={1} | ||||
|           > | ||||
|             @{account.acct} | ||||
|           </Text> | ||||
|         </View> | ||||
|  | ||||
|         {created_at && ( | ||||
|           <View> | ||||
|             <Text style={styles.created_at}>{relativeTime(created_at)}</Text> | ||||
|           <View style={styles.meta}> | ||||
|             <Text style={[styles.created_at, { color: theme.secondary }]}> | ||||
|               {relativeTime(created_at)} | ||||
|             </Text> | ||||
|           </View> | ||||
|         )} | ||||
|       </View> | ||||
|       <Text style={styles.account} numberOfLines={1}> | ||||
|         @{account.acct} | ||||
|       </Text> | ||||
|  | ||||
|       <Pressable | ||||
|         style={styles.action} | ||||
|         onPress={actionOnPress} | ||||
|         children={actionChildren} | ||||
|       /> | ||||
|     </View> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| const styles = StyleSheet.create({ | ||||
|   base: { | ||||
|     flex: 1, | ||||
|     flexDirection: 'row' | ||||
|   }, | ||||
|   nameAndDate: { | ||||
|     width: '100%', | ||||
|     flexDirection: 'row', | ||||
|     justifyContent: 'space-between' | ||||
|     width: '80%' | ||||
|   }, | ||||
|   name: { | ||||
|     flexDirection: 'row', | ||||
|     marginRight: 8, | ||||
|     fontWeight: '900' | ||||
|   }, | ||||
|   created_at: { | ||||
|     fontSize: 12, | ||||
|     lineHeight: 12, | ||||
|     marginTop: 8, | ||||
|     marginBottom: 8, | ||||
|     marginRight: 8 | ||||
|     flexDirection: 'row' | ||||
|   }, | ||||
|   account: { | ||||
|     lineHeight: 14, | ||||
|     flexShrink: 1 | ||||
|     flexShrink: 1, | ||||
|     marginLeft: StyleConstants.Spacing.XS | ||||
|   }, | ||||
|   nameWithoutEmoji: { | ||||
|     fontSize: StyleConstants.Font.Size.M, | ||||
|     fontWeight: StyleConstants.Font.Weight.Bold | ||||
|   }, | ||||
|   meta: { | ||||
|     flexDirection: 'row', | ||||
|     alignItems: 'center', | ||||
|     marginTop: StyleConstants.Spacing.XS, | ||||
|     marginBottom: StyleConstants.Spacing.S | ||||
|   }, | ||||
|   created_at: { | ||||
|     fontSize: StyleConstants.Font.Size.S | ||||
|   }, | ||||
|   action: { | ||||
|     flexBasis: '20%', | ||||
|     flexDirection: 'row', | ||||
|     justifyContent: 'center' | ||||
|   } | ||||
| }) | ||||
|  | ||||
|   | ||||
| @@ -61,7 +61,7 @@ const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => { | ||||
|  | ||||
|   return ( | ||||
|     <View style={styles.base}> | ||||
|       <View style={styles.nameAndAction}> | ||||
|       <View style={styles.nameAndMeta}> | ||||
|         <View style={styles.name}> | ||||
|           {emojis?.length ? ( | ||||
|             <Emojis | ||||
| @@ -85,41 +85,41 @@ const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => { | ||||
|             @{account} | ||||
|           </Text> | ||||
|         </View> | ||||
|         {queryKey && ( | ||||
|           <Pressable | ||||
|             style={styles.action} | ||||
|             onPress={onPressAction} | ||||
|             children={pressableAction} | ||||
|           /> | ||||
|         )} | ||||
|       </View> | ||||
|  | ||||
|       <View style={styles.meta}> | ||||
|         <View> | ||||
|           <Text style={[styles.created_at, { color: theme.secondary }]}> | ||||
|             {since} | ||||
|           </Text> | ||||
|         </View> | ||||
|         {status.visibility === 'private' && ( | ||||
|           <Feather | ||||
|             name='lock' | ||||
|             size={StyleConstants.Font.Size.S} | ||||
|             color={theme.secondary} | ||||
|             style={styles.visibility} | ||||
|           /> | ||||
|         )} | ||||
|         {status.application && status.application.name !== 'Web' && ( | ||||
|         <View style={styles.meta}> | ||||
|           <View> | ||||
|             <Text | ||||
|               onPress={onPressApplication} | ||||
|               style={[styles.application, { color: theme.secondary }]} | ||||
|             > | ||||
|               发自于 - {status.application.name} | ||||
|             <Text style={[styles.created_at, { color: theme.secondary }]}> | ||||
|               {since} | ||||
|             </Text> | ||||
|           </View> | ||||
|         )} | ||||
|           {status.visibility === 'private' && ( | ||||
|             <Feather | ||||
|               name='lock' | ||||
|               size={StyleConstants.Font.Size.S} | ||||
|               color={theme.secondary} | ||||
|               style={styles.visibility} | ||||
|             /> | ||||
|           )} | ||||
|           {status.application && status.application.name !== 'Web' && ( | ||||
|             <View> | ||||
|               <Text | ||||
|                 onPress={onPressApplication} | ||||
|                 style={[styles.application, { color: theme.secondary }]} | ||||
|               > | ||||
|                 发自于 - {status.application.name} | ||||
|               </Text> | ||||
|             </View> | ||||
|           )} | ||||
|         </View> | ||||
|       </View> | ||||
|  | ||||
|       {queryKey && ( | ||||
|         <Pressable | ||||
|           style={styles.action} | ||||
|           onPress={onPressAction} | ||||
|           children={pressableAction} | ||||
|         /> | ||||
|       )} | ||||
|  | ||||
|       {queryKey && ( | ||||
|         <BottomSheet | ||||
|           visible={modalVisible} | ||||
| @@ -157,29 +157,21 @@ const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => { | ||||
|  | ||||
| const styles = StyleSheet.create({ | ||||
|   base: { | ||||
|     flex: 1 | ||||
|   }, | ||||
|   nameAndAction: { | ||||
|     flex: 1, | ||||
|     flexBasis: '100%', | ||||
|     flexDirection: 'row', | ||||
|     alignItems: 'flex-start' | ||||
|     flexDirection: 'row' | ||||
|   }, | ||||
|   nameAndMeta: { | ||||
|     flexBasis: '80%' | ||||
|   }, | ||||
|   name: { | ||||
|     flexBasis: '85%', | ||||
|     flexDirection: 'row' | ||||
|   }, | ||||
|   nameWithoutEmoji: { | ||||
|     fontSize: StyleConstants.Font.Size.M, | ||||
|     fontWeight: StyleConstants.Font.Weight.Bold | ||||
|   }, | ||||
|   action: { | ||||
|     flex: 1, | ||||
|     flexDirection: 'row', | ||||
|     justifyContent: 'center' | ||||
|   }, | ||||
|   account: { | ||||
|     flexShrink: 1, | ||||
|     flex: 1, | ||||
|     marginLeft: StyleConstants.Spacing.XS | ||||
|   }, | ||||
|   meta: { | ||||
| @@ -197,6 +189,11 @@ const styles = StyleSheet.create({ | ||||
|   application: { | ||||
|     fontSize: StyleConstants.Font.Size.S, | ||||
|     marginLeft: StyleConstants.Spacing.S | ||||
|   }, | ||||
|   action: { | ||||
|     flexBasis: '20%', | ||||
|     flexDirection: 'row', | ||||
|     justifyContent: 'center' | ||||
|   } | ||||
| }) | ||||
|  | ||||
|   | ||||
| @@ -126,31 +126,29 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => { | ||||
|  | ||||
|   return ( | ||||
|     <View style={styles.base}> | ||||
|       <View style={{ flexBasis: '80%' }}> | ||||
|         <View style={styles.nameAndAction}> | ||||
|           <View style={styles.name}> | ||||
|             {emojis?.length ? ( | ||||
|               <Emojis | ||||
|                 content={name} | ||||
|                 emojis={emojis} | ||||
|                 size={StyleConstants.Font.Size.M} | ||||
|                 fontBold={true} | ||||
|               /> | ||||
|             ) : ( | ||||
|               <Text | ||||
|                 numberOfLines={1} | ||||
|                 style={[styles.nameWithoutEmoji, { color: theme.primary }]} | ||||
|               > | ||||
|                 {name} | ||||
|               </Text> | ||||
|             )} | ||||
|       <View style={styles.nameAndMeta}> | ||||
|         <View style={styles.name}> | ||||
|           {emojis?.length ? ( | ||||
|             <Emojis | ||||
|               content={name} | ||||
|               emojis={emojis} | ||||
|               size={StyleConstants.Font.Size.M} | ||||
|               fontBold={true} | ||||
|             /> | ||||
|           ) : ( | ||||
|             <Text | ||||
|               style={[styles.account, { color: theme.secondary }]} | ||||
|               numberOfLines={1} | ||||
|               style={[styles.nameWithoutEmoji, { color: theme.primary }]} | ||||
|             > | ||||
|               @{account} | ||||
|               {name} | ||||
|             </Text> | ||||
|           </View> | ||||
|           )} | ||||
|           <Text | ||||
|             style={[styles.account, { color: theme.secondary }]} | ||||
|             numberOfLines={1} | ||||
|           > | ||||
|             @{account} | ||||
|           </Text> | ||||
|         </View> | ||||
|  | ||||
|         <View style={styles.meta}> | ||||
| @@ -180,6 +178,7 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => { | ||||
|             )} | ||||
|         </View> | ||||
|       </View> | ||||
|  | ||||
|       {notification.type === 'follow' && ( | ||||
|         <View style={styles.relationship}>{relationshipIcon}</View> | ||||
|       )} | ||||
| @@ -192,22 +191,16 @@ const styles = StyleSheet.create({ | ||||
|     flex: 1, | ||||
|     flexDirection: 'row' | ||||
|   }, | ||||
|   nameAndAction: { | ||||
|     width: '100%', | ||||
|     flexDirection: 'row', | ||||
|     justifyContent: 'space-between' | ||||
|   nameAndMeta: { | ||||
|     width: '80%' | ||||
|   }, | ||||
|   name: { | ||||
|     flexBasis: '90%', | ||||
|     flexDirection: 'row' | ||||
|   }, | ||||
|   nameWithoutEmoji: { | ||||
|     fontSize: StyleConstants.Font.Size.M, | ||||
|     fontWeight: StyleConstants.Font.Weight.Bold | ||||
|   }, | ||||
|   action: { | ||||
|     alignItems: 'flex-end' | ||||
|   }, | ||||
|   account: { | ||||
|     flexShrink: 1, | ||||
|     marginLeft: StyleConstants.Spacing.XS | ||||
|   | ||||
| @@ -96,7 +96,7 @@ const AccountInformation: React.FC<Props> = ({ account }) => { | ||||
|               > | ||||
|                 <ParseContent | ||||
|                   content={field.name} | ||||
|                   size={StyleConstants.Font.Size.M} | ||||
|                   size={'M'} | ||||
|                   emojis={account.emojis} | ||||
|                   showFullLink | ||||
|                 /> | ||||
| @@ -119,7 +119,7 @@ const AccountInformation: React.FC<Props> = ({ account }) => { | ||||
|               > | ||||
|                 <ParseContent | ||||
|                   content={field.value} | ||||
|                   size={StyleConstants.Font.Size.M} | ||||
|                   size={'M'} | ||||
|                   emojis={account.emojis} | ||||
|                   showFullLink | ||||
|                 /> | ||||
| @@ -133,7 +133,7 @@ const AccountInformation: React.FC<Props> = ({ account }) => { | ||||
|         <View style={styles.note}> | ||||
|           <ParseContent | ||||
|             content={account.note} | ||||
|             size={StyleConstants.Font.Size.M} | ||||
|             size={'M'} | ||||
|             emojis={account.emojis} | ||||
|           /> | ||||
|         </View> | ||||
|   | ||||
| @@ -6,7 +6,6 @@ import React, { | ||||
|   RefObject, | ||||
|   useEffect, | ||||
|   useReducer, | ||||
|   useRef, | ||||
|   useState | ||||
| } from 'react' | ||||
| import { | ||||
| @@ -84,6 +83,7 @@ export type ComposeState = { | ||||
|   } | ||||
|   attachmentUploadProgress?: { progress: number; aspect?: number } | ||||
|   visibility: 'public' | 'unlisted' | 'private' | 'direct' | ||||
|   visibilityLock: boolean | ||||
|   replyToStatus?: Mastodon.Status | ||||
|   textInputFocus: { | ||||
|     current: 'text' | 'spoiler' | ||||
| @@ -167,6 +167,7 @@ const composeInitialState: ComposeState = { | ||||
|     getLocalAccountPreferences(store.getState())[ | ||||
|       'posting:default:visibility' | ||||
|     ] || 'public', | ||||
|   visibilityLock: false, | ||||
|   replyToStatus: undefined, | ||||
|   textInputFocus: { | ||||
|     current: 'text', | ||||
| @@ -177,7 +178,7 @@ const composeExistingState = ({ | ||||
|   type, | ||||
|   incomingStatus | ||||
| }: { | ||||
|   type: 'reply' | 'edit' | ||||
|   type: 'reply' | 'conversation' | 'edit' | ||||
|   incomingStatus: Mastodon.Status | ||||
| }): ComposeState => { | ||||
|   switch (type) { | ||||
| @@ -222,11 +223,17 @@ const composeExistingState = ({ | ||||
|         visibility: incomingStatus.visibility | ||||
|       } | ||||
|     case 'reply': | ||||
|       const replyPlaceholder = `@${ | ||||
|         incomingStatus.reblog | ||||
|           ? incomingStatus.reblog.account.acct | ||||
|           : incomingStatus.account.acct | ||||
|       } ` | ||||
|     case 'conversation': | ||||
|       const actualStatus = incomingStatus.reblog || incomingStatus | ||||
|       const allMentions = actualStatus.mentions.map( | ||||
|         mention => `@${mention.acct}` | ||||
|       ) | ||||
|       let replyPlaceholder = allMentions.join(' ') | ||||
|       if (replyPlaceholder.length === 0) { | ||||
|         replyPlaceholder = `@${actualStatus.account.acct} ` | ||||
|       } else { | ||||
|         replyPlaceholder = replyPlaceholder + ' ' | ||||
|       } | ||||
|       return { | ||||
|         ...composeInitialState, | ||||
|         text: { | ||||
| @@ -235,6 +242,10 @@ const composeExistingState = ({ | ||||
|           formatted: undefined, | ||||
|           selection: { start: 0, end: 0 } | ||||
|         }, | ||||
|         ...(type === 'conversation' && { | ||||
|           visibility: 'direct', | ||||
|           visibilityLock: true | ||||
|         }), | ||||
|         replyToStatus: incomingStatus.reblog || incomingStatus | ||||
|       } | ||||
|   } | ||||
| @@ -290,7 +301,7 @@ export interface Props { | ||||
|   route: { | ||||
|     params: | ||||
|       | { | ||||
|           type?: 'reply' | 'edit' | ||||
|           type?: 'reply' | 'conversation' | 'edit' | ||||
|           incomingStatus: Mastodon.Status | ||||
|         } | ||||
|       | undefined | ||||
| @@ -349,14 +360,22 @@ const Compose: React.FC<Props> = ({ route: { params } }) => { | ||||
|         }) | ||||
|         break | ||||
|       case 'reply': | ||||
|       case 'conversation': | ||||
|         const actualStatus = | ||||
|           params.incomingStatus.reblog || params.incomingStatus | ||||
|         const allMentions = actualStatus.mentions.map( | ||||
|           mention => `@${mention.acct}` | ||||
|         ) | ||||
|         let replyPlaceholder = allMentions.join(' ') | ||||
|         if (replyPlaceholder.length === 0) { | ||||
|           replyPlaceholder = `@${actualStatus.account.acct} ` | ||||
|         } else { | ||||
|           replyPlaceholder = replyPlaceholder + ' ' | ||||
|         } | ||||
|         formatText({ | ||||
|           textInput: 'text', | ||||
|           composeDispatch, | ||||
|           content: `@${ | ||||
|             params.incomingStatus.reblog | ||||
|               ? params.incomingStatus.reblog.account.acct | ||||
|               : params.incomingStatus.account.acct | ||||
|           } `, | ||||
|           content: replyPlaceholder, | ||||
|           disableDebounce: true | ||||
|         }) | ||||
|         break | ||||
| @@ -374,6 +393,10 @@ const Compose: React.FC<Props> = ({ route: { params } }) => { | ||||
|     } else { | ||||
|       const formData = new FormData() | ||||
|  | ||||
|       if (params?.type === 'conversation' || params?.type === 'reply') { | ||||
|         formData.append('in_reply_to_id', composeState.replyToStatus!.id) | ||||
|       } | ||||
|  | ||||
|       if (composeState.spoiler.active) { | ||||
|         formData.append('spoiler_text', composeState.spoiler.raw) | ||||
|       } | ||||
| @@ -469,6 +492,12 @@ const Compose: React.FC<Props> = ({ route: { params } }) => { | ||||
|   // doesn't work | ||||
|   const rawCount = composeState.text.raw.length | ||||
|  | ||||
|   const postButtonText = { | ||||
|     conversation: '回复私信', | ||||
|     reply: '发布回复', | ||||
|     edit: '发嘟嘟' | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}> | ||||
|       <SafeAreaView | ||||
| @@ -513,7 +542,7 @@ const Compose: React.FC<Props> = ({ route: { params } }) => { | ||||
|                 ) : ( | ||||
|                   <HeaderRight | ||||
|                     onPress={async () => tootPost()} | ||||
|                     text='发嘟嘟' | ||||
|                     text={params?.type ? postButtonText[params.type] : '发嘟嘟'} | ||||
|                     disabled={rawCount < 1 || totalTextCount > 500} | ||||
|                   /> | ||||
|                 ) | ||||
|   | ||||
| @@ -84,6 +84,7 @@ const ComposeActions: React.FC = () => { | ||||
|   }, [composeState.visibility]) | ||||
|   const visibilityOnPress = useCallback( | ||||
|     () => | ||||
|       !composeState.visibilityLock && | ||||
|       ActionSheetIOS.showActionSheetWithOptions( | ||||
|         { | ||||
|           options: ['公开', '不公开', '仅关注着', '私信', '取消'], | ||||
| @@ -164,7 +165,7 @@ const ComposeActions: React.FC = () => { | ||||
|       <Feather | ||||
|         name={visibilityIcon} | ||||
|         size={24} | ||||
|         color={theme.secondary} | ||||
|         color={composeState.visibilityLock ? theme.disabled : theme.secondary} | ||||
|         onPress={visibilityOnPress} | ||||
|       /> | ||||
|       <Feather | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import React, { useContext } from 'react' | ||||
| import { StyleSheet, View } from 'react-native' | ||||
| import { Dimensions, StyleSheet, View } from 'react-native' | ||||
| import { useTheme } from 'src/utils/styles/ThemeManager' | ||||
|  | ||||
| import TimelineAttachment from 'src/components/Timelines/Timeline/Shared/Attachment' | ||||
| @@ -8,6 +8,7 @@ import TimelineCard from 'src/components/Timelines/Timeline/Shared/Card' | ||||
| import TimelineContent from 'src/components/Timelines/Timeline/Shared/Content' | ||||
| import TimelineHeaderDefault from 'src/components/Timelines/Timeline/Shared/HeaderDefault' | ||||
| import { ComposeContext } from '../Compose' | ||||
| import { StyleConstants } from 'src/utils/styles/constants' | ||||
|  | ||||
| const ComposeReply: React.FC = () => { | ||||
|   const { | ||||
| @@ -15,8 +16,14 @@ const ComposeReply: React.FC = () => { | ||||
|   } = useContext(ComposeContext) | ||||
|   const { theme } = useTheme() | ||||
|  | ||||
|   const contentWidth = | ||||
|     Dimensions.get('window').width - | ||||
|     StyleConstants.Spacing.Global.PagePadding * 2 - // Global page padding on both sides | ||||
|     StyleConstants.Avatar.S - // Avatar width | ||||
|     StyleConstants.Spacing.S // Avatar margin to the right | ||||
|  | ||||
|   return ( | ||||
|     <View style={styles.status}> | ||||
|     <View style={[styles.status, { borderTopColor: theme.border }]}> | ||||
|       <TimelineAvatar account={replyToStatus!.account} /> | ||||
|       <View style={styles.details}> | ||||
|         <TimelineHeaderDefault status={replyToStatus!} /> | ||||
| @@ -24,7 +31,7 @@ const ComposeReply: React.FC = () => { | ||||
|           <TimelineContent status={replyToStatus!} /> | ||||
|         )} | ||||
|         {replyToStatus!.media_attachments.length > 0 && ( | ||||
|           <TimelineAttachment status={replyToStatus!} width={200} /> | ||||
|           <TimelineAttachment status={replyToStatus!} width={contentWidth} /> | ||||
|         )} | ||||
|         {replyToStatus!.card && <TimelineCard card={replyToStatus!.card} />} | ||||
|       </View> | ||||
| @@ -35,7 +42,9 @@ const ComposeReply: React.FC = () => { | ||||
| const styles = StyleSheet.create({ | ||||
|   status: { | ||||
|     flex: 1, | ||||
|     flexDirection: 'row' | ||||
|     flexDirection: 'row', | ||||
|     borderTopWidth: StyleSheet.hairlineWidth, | ||||
|     paddingTop: StyleConstants.Spacing.Global.PagePadding | ||||
|   }, | ||||
|   details: { | ||||
|     flex: 1 | ||||
|   | ||||
| @@ -143,6 +143,12 @@ export const timelineFetch = async ( | ||||
|         url: `conversations`, | ||||
|         params | ||||
|       }) | ||||
|       if (pagination) { | ||||
|         // Bug in pull to refresh in conversations | ||||
|         res.body = res.body.filter( | ||||
|           (b: Mastodon.Conversation) => b.id !== pagination.id | ||||
|         ) | ||||
|       } | ||||
|       return Promise.resolve({ toots: res.body, pointer: null }) | ||||
|  | ||||
|     case 'Bookmarks': | ||||
|   | ||||
		Reference in New Issue
	
	Block a user