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