mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
A lot of updates
This commit is contained in:
@ -1,5 +1,12 @@
|
||||
import React from 'react'
|
||||
import { ActivityIndicator, AppState, FlatList, Text, View } from 'react-native'
|
||||
import React, { useCallback } from 'react'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
AppState,
|
||||
FlatList,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native'
|
||||
import { setFocusHandler, useInfiniteQuery } from 'react-query'
|
||||
|
||||
import TimelineNotifications from 'src/components/Timelines/Timeline/Notifications'
|
||||
@ -51,6 +58,40 @@ const Timeline: React.FC<Props> = ({
|
||||
const flattenData = data ? data.flatMap(d => [...d?.toots]) : []
|
||||
// const flattenPointer = data ? data.flatMap(d => [d?.pointer]) : []
|
||||
|
||||
const flKeyExtrator = useCallback(({ id }) => id, [])
|
||||
const flRenderItem = useCallback(({ item }) => {
|
||||
switch (page) {
|
||||
case 'Conversations':
|
||||
return <TimelineConversation item={item} />
|
||||
case 'Notifications':
|
||||
return <TimelineNotifications notification={item} queryKey={queryKey} />
|
||||
default:
|
||||
return <TimelineDefault item={item} queryKey={queryKey} />
|
||||
}
|
||||
}, [])
|
||||
const flItemSeparatorComponent = useCallback(() => <TimelineSeparator />, [])
|
||||
const flOnRefresh = useCallback(
|
||||
() =>
|
||||
!disableRefresh &&
|
||||
fetchMore(
|
||||
{
|
||||
direction: 'prev',
|
||||
id: flattenData[0].id
|
||||
},
|
||||
{ previous: true }
|
||||
),
|
||||
[disableRefresh]
|
||||
)
|
||||
const flOnEndReach = useCallback(
|
||||
() =>
|
||||
!disableRefresh &&
|
||||
fetchMore({
|
||||
direction: 'next',
|
||||
id: flattenData[flattenData.length - 1].id
|
||||
}),
|
||||
[disableRefresh]
|
||||
)
|
||||
|
||||
let content
|
||||
if (!isSuccess) {
|
||||
content = <ActivityIndicator />
|
||||
@ -60,53 +101,18 @@ const Timeline: React.FC<Props> = ({
|
||||
content = (
|
||||
<>
|
||||
<FlatList
|
||||
style={{ minHeight: '100%' }}
|
||||
scrollEnabled={scrollEnabled} // For timeline in Account view
|
||||
data={flattenData}
|
||||
keyExtractor={({ id }) => id}
|
||||
renderItem={({ item, index, separators }) => {
|
||||
switch (page) {
|
||||
case 'Conversations':
|
||||
return <TimelineConversation key={index} item={item} />
|
||||
case 'Notifications':
|
||||
return (
|
||||
<TimelineNotifications
|
||||
key={index}
|
||||
notification={item}
|
||||
queryKey={queryKey}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<TimelineDefault
|
||||
key={index}
|
||||
item={item}
|
||||
queryKey={queryKey}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}}
|
||||
ItemSeparatorComponent={() => <TimelineSeparator />}
|
||||
onRefresh={flOnRefresh}
|
||||
renderItem={flRenderItem}
|
||||
onEndReached={flOnEndReach}
|
||||
keyExtractor={flKeyExtrator}
|
||||
style={styles.flatList}
|
||||
scrollEnabled={scrollEnabled} // For timeline in Account view
|
||||
ItemSeparatorComponent={flItemSeparatorComponent}
|
||||
refreshing={!disableRefresh && isLoading}
|
||||
onEndReachedThreshold={!disableRefresh ? 0.5 : null}
|
||||
// require getItemLayout
|
||||
// {...(flattenPointer[0] && { initialScrollIndex: flattenPointer[0] })}
|
||||
{...(!disableRefresh && {
|
||||
onRefresh: () =>
|
||||
fetchMore(
|
||||
{
|
||||
direction: 'prev',
|
||||
id: flattenData[0].id
|
||||
},
|
||||
{ previous: true }
|
||||
),
|
||||
refreshing: isLoading,
|
||||
onEndReached: () => {
|
||||
fetchMore({
|
||||
direction: 'next',
|
||||
id: flattenData[flattenData.length - 1].id
|
||||
})
|
||||
},
|
||||
onEndReachedThreshold: 0.5
|
||||
})}
|
||||
/>
|
||||
{isFetchingMore && <ActivityIndicator />}
|
||||
</>
|
||||
@ -116,4 +122,10 @@ const Timeline: React.FC<Props> = ({
|
||||
return <View>{content}</View>
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flatList: {
|
||||
minHeight: '100%'
|
||||
}
|
||||
})
|
||||
|
||||
export default Timeline
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { Dimensions, Pressable, StyleSheet, View } from 'react-native'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
@ -24,6 +24,43 @@ const TimelineDefault: React.FC<Props> = ({ item, queryKey }) => {
|
||||
|
||||
let actualStatus = item.reblog ? item.reblog : item
|
||||
|
||||
const pressableToot = useCallback(
|
||||
() =>
|
||||
navigation.navigate('Screen-Shared-Toot', {
|
||||
toot: actualStatus.id
|
||||
}),
|
||||
[]
|
||||
)
|
||||
const childrenToot = useMemo(
|
||||
() => (
|
||||
<>
|
||||
{actualStatus.content ? (
|
||||
<Content
|
||||
content={actualStatus.content}
|
||||
emojis={actualStatus.emojis}
|
||||
mentions={actualStatus.mentions}
|
||||
spoiler_text={actualStatus.spoiler_text}
|
||||
// tags={actualStatus.tags}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{actualStatus.poll && <Poll poll={actualStatus.poll} />}
|
||||
{actualStatus.media_attachments.length > 0 && (
|
||||
<Attachment
|
||||
media_attachments={actualStatus.media_attachments}
|
||||
sensitive={actualStatus.sensitive}
|
||||
width={
|
||||
Dimensions.get('window').width - constants.SPACING_M * 2 - 50 - 8
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{actualStatus.card && <Card card={actualStatus.card} />}
|
||||
</>
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
const statusView = useMemo(() => {
|
||||
return (
|
||||
<View style={styles.statusView}>
|
||||
@ -54,40 +91,7 @@ const TimelineDefault: React.FC<Props> = ({ item, queryKey }) => {
|
||||
application={item.application}
|
||||
/>
|
||||
{/* Can pass toot info to next page to speed up performance */}
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
navigation.navigate('Screen-Shared-Toot', {
|
||||
toot: actualStatus.id
|
||||
})
|
||||
}
|
||||
>
|
||||
{actualStatus.content ? (
|
||||
<Content
|
||||
content={actualStatus.content}
|
||||
emojis={actualStatus.emojis}
|
||||
mentions={actualStatus.mentions}
|
||||
spoiler_text={actualStatus.spoiler_text}
|
||||
// tags={actualStatus.tags}
|
||||
// style={{ flex: 1 }}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{actualStatus.poll && <Poll poll={actualStatus.poll} />}
|
||||
{actualStatus.media_attachments.length > 0 && (
|
||||
<Attachment
|
||||
media_attachments={actualStatus.media_attachments}
|
||||
sensitive={actualStatus.sensitive}
|
||||
width={
|
||||
Dimensions.get('window').width -
|
||||
constants.SPACING_M * 2 -
|
||||
50 -
|
||||
8
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{actualStatus.card && <Card card={actualStatus.card} />}
|
||||
</Pressable>
|
||||
<Pressable onPress={pressableToot} children={childrenToot} />
|
||||
<ActionsStatus queryKey={queryKey} status={actualStatus} />
|
||||
</View>
|
||||
</View>
|
||||
@ -114,4 +118,12 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
})
|
||||
|
||||
export default TimelineDefault
|
||||
export default React.memo(TimelineDefault, (prev, next) => {
|
||||
let skipUpdate = true
|
||||
skipUpdate = prev.item.id === next.item.id
|
||||
skipUpdate = prev.item.replies_count === next.item.replies_count
|
||||
skipUpdate = prev.item.favourited === next.item.favourited
|
||||
skipUpdate = prev.item.reblogged === next.item.reblogged
|
||||
skipUpdate = prev.item.bookmarked === next.item.bookmarked
|
||||
return skipUpdate
|
||||
})
|
||||
|
@ -108,4 +108,4 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
})
|
||||
|
||||
export default Actioned
|
||||
export default React.memo(Actioned)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import {
|
||||
ActionSheetIOS,
|
||||
Clipboard,
|
||||
@ -11,12 +11,14 @@ import {
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { useMutation, useQueryCache } from 'react-query'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import { findIndex } from 'lodash'
|
||||
|
||||
import client from 'src/api/client'
|
||||
import { getLocalAccountId } from 'src/utils/slices/instancesSlice'
|
||||
import { store } from 'src/store'
|
||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||
import constants from 'src/utils/styles/constants'
|
||||
import { toast } from 'src/components/toast'
|
||||
|
||||
const fireMutation = async ({
|
||||
id,
|
||||
@ -25,14 +27,7 @@ const fireMutation = async ({
|
||||
prevState
|
||||
}: {
|
||||
id: string
|
||||
type:
|
||||
| 'favourite'
|
||||
| 'reblog'
|
||||
| 'bookmark'
|
||||
| 'mute'
|
||||
| 'pin'
|
||||
| 'delete'
|
||||
| 'account/mute'
|
||||
type: 'favourite' | 'reblog' | 'bookmark' | 'mute' | 'pin' | 'delete'
|
||||
stateKey:
|
||||
| 'favourited'
|
||||
| 'reblogged'
|
||||
@ -53,27 +48,13 @@ const fireMutation = async ({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
endpoint: `statuses/${id}/${prevState ? 'un' : ''}${type}`
|
||||
})
|
||||
}) // bug in response from Mastodon
|
||||
|
||||
if (!res.body[stateKey] === prevState) {
|
||||
if (type === 'bookmark' || 'mute' || 'pin')
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
position: 'bottom',
|
||||
text1: '功能成功',
|
||||
visibilityTime: 2000,
|
||||
autoHide: true,
|
||||
bottomOffset: 65
|
||||
})
|
||||
toast({ type: 'success', content: '功能成功' })
|
||||
return Promise.resolve(res.body)
|
||||
} else {
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
position: 'bottom',
|
||||
text1: '请重试',
|
||||
autoHide: false,
|
||||
bottomOffset: 65
|
||||
})
|
||||
toast({ type: 'error', content: '功能错误' })
|
||||
return Promise.reject()
|
||||
}
|
||||
break
|
||||
@ -85,23 +66,10 @@ const fireMutation = async ({
|
||||
})
|
||||
|
||||
if (res.body[stateKey] === id) {
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
position: 'bottom',
|
||||
text1: '删除成功',
|
||||
visibilityTime: 2000,
|
||||
autoHide: true,
|
||||
bottomOffset: 65
|
||||
})
|
||||
toast({ type: 'success', content: '删除成功' })
|
||||
return Promise.resolve(res.body)
|
||||
} else {
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
position: 'bottom',
|
||||
text1: '请重试',
|
||||
autoHide: false,
|
||||
bottomOffset: 65
|
||||
})
|
||||
toast({ type: 'error', content: '删除失败' })
|
||||
return Promise.reject()
|
||||
}
|
||||
break
|
||||
@ -120,136 +88,204 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
|
||||
state ? theme.primary : theme.secondary
|
||||
|
||||
const localAccountId = getLocalAccountId(store.getState())
|
||||
const [modalVisible, setModalVisible] = useState(false)
|
||||
const [bottomSheetVisible, setBottomSheetVisible] = useState(false)
|
||||
|
||||
const queryCache = useQueryCache()
|
||||
const [mutateAction] = useMutation(fireMutation, {
|
||||
onMutate: () => {
|
||||
onMutate: ({ id, type, stateKey, prevState }) => {
|
||||
queryCache.cancelQueries(queryKey)
|
||||
const prevData = queryCache.getQueryData(queryKey)
|
||||
return prevData
|
||||
},
|
||||
onSuccess: (newData, params) => {
|
||||
if (params.type === 'reblog') {
|
||||
queryCache.invalidateQueries(['Following', { page: 'Following' }])
|
||||
const oldData = queryCache.getQueryData(queryKey)
|
||||
|
||||
switch (type) {
|
||||
case 'favourite':
|
||||
case 'reblog':
|
||||
case 'bookmark':
|
||||
case 'mute':
|
||||
case 'pin':
|
||||
queryCache.setQueryData(queryKey, old =>
|
||||
(old as {}[]).map((paging: any) => ({
|
||||
toots: paging.toots.map((toot: any) => {
|
||||
if (toot.id === id) {
|
||||
toot[stateKey] =
|
||||
typeof prevState === 'boolean' ? !prevState : true
|
||||
}
|
||||
return toot
|
||||
}),
|
||||
pointer: paging.pointer
|
||||
}))
|
||||
)
|
||||
break
|
||||
case 'delete':
|
||||
queryCache.setQueryData(queryKey, old =>
|
||||
(old as {}[]).map((paging: any) => ({
|
||||
toots: paging.toots.map((toot: any, index: number) => {
|
||||
if (toot.id === id) {
|
||||
paging.toots.splice(index, 1)
|
||||
}
|
||||
return toot
|
||||
}),
|
||||
pointer: paging.pointer
|
||||
}))
|
||||
)
|
||||
break
|
||||
}
|
||||
// queryCache.setQueryData(queryKey, (oldData: any) => {
|
||||
// oldData &&
|
||||
// oldData.map((paging: any) => {
|
||||
// paging.toots.map(
|
||||
// (status: Mastodon.Status | Mastodon.Notification, i: number) => {
|
||||
// if (status.id === newData.id) {
|
||||
// paging.toots[i] = newData
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// })
|
||||
// return oldData
|
||||
// })
|
||||
return Promise.resolve()
|
||||
|
||||
return oldData
|
||||
},
|
||||
onError: (err, variables, prevData) => {
|
||||
queryCache.setQueryData(queryKey, prevData)
|
||||
},
|
||||
onSettled: () => {
|
||||
queryCache.invalidateQueries(queryKey)
|
||||
onError: (err, _, oldData) => {
|
||||
toast({ type: 'error', content: '请重试' })
|
||||
queryCache.setQueryData(queryKey, oldData)
|
||||
}
|
||||
})
|
||||
|
||||
const onPressReply = useCallback(() => {}, [])
|
||||
const onPressReblog = useCallback(
|
||||
() =>
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'reblog',
|
||||
stateKey: 'reblogged',
|
||||
prevState: status.reblogged
|
||||
}),
|
||||
[status.reblogged]
|
||||
)
|
||||
const onPressFavourite = useCallback(
|
||||
() =>
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'favourite',
|
||||
stateKey: 'favourited',
|
||||
prevState: status.favourited
|
||||
}),
|
||||
[status.favourited]
|
||||
)
|
||||
const onPressBookmark = useCallback(
|
||||
() =>
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'bookmark',
|
||||
stateKey: 'bookmarked',
|
||||
prevState: status.bookmarked
|
||||
}),
|
||||
[status.bookmarked]
|
||||
)
|
||||
const onPressShare = useCallback(() => setBottomSheetVisible(true), [])
|
||||
|
||||
const childrenReply = useMemo(
|
||||
() => (
|
||||
<>
|
||||
<Feather
|
||||
name='message-circle'
|
||||
color={iconColor}
|
||||
size={constants.FONT_SIZE_M + 2}
|
||||
/>
|
||||
{status.replies_count > 0 && (
|
||||
<Text
|
||||
style={{
|
||||
color: theme.secondary,
|
||||
fontSize: constants.FONT_SIZE_M,
|
||||
marginLeft: constants.SPACING_XS
|
||||
}}
|
||||
>
|
||||
{status.replies_count}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
[status.replies_count]
|
||||
)
|
||||
const childrenReblog = useMemo(
|
||||
() => (
|
||||
<Feather
|
||||
name='repeat'
|
||||
color={
|
||||
status.visibility === 'public' || status.visibility === 'unlisted'
|
||||
? iconColorAction(status.reblogged)
|
||||
: theme.disabled
|
||||
}
|
||||
size={constants.FONT_SIZE_M + 2}
|
||||
/>
|
||||
),
|
||||
[status.reblogged]
|
||||
)
|
||||
const childrenFavourite = useMemo(
|
||||
() => (
|
||||
<Feather
|
||||
name='heart'
|
||||
color={iconColorAction(status.favourited)}
|
||||
size={constants.FONT_SIZE_M + 2}
|
||||
/>
|
||||
),
|
||||
[status.favourited]
|
||||
)
|
||||
const childrenBookmark = useMemo(
|
||||
() => (
|
||||
<Feather
|
||||
name='bookmark'
|
||||
color={iconColorAction(status.bookmarked)}
|
||||
size={constants.FONT_SIZE_M + 2}
|
||||
/>
|
||||
),
|
||||
[status.bookmarked]
|
||||
)
|
||||
const childrenShare = useMemo(
|
||||
() => (
|
||||
<Feather
|
||||
name='share-2'
|
||||
color={iconColor}
|
||||
size={constants.FONT_SIZE_M + 2}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={styles.actions}>
|
||||
<Pressable style={styles.action}>
|
||||
<Feather
|
||||
name='message-circle'
|
||||
color={iconColor}
|
||||
size={constants.FONT_SIZE_M + 2}
|
||||
/>
|
||||
{status.replies_count > 0 && (
|
||||
<Text
|
||||
style={{
|
||||
color: theme.secondary,
|
||||
fontSize: constants.FONT_SIZE_M,
|
||||
marginLeft: constants.SPACING_XS
|
||||
}}
|
||||
>
|
||||
{status.replies_count}
|
||||
</Text>
|
||||
)}
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={styles.action}
|
||||
onPress={onPressReply}
|
||||
children={childrenReply}
|
||||
/>
|
||||
|
||||
<Pressable
|
||||
style={styles.action}
|
||||
onPress={() =>
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'reblog',
|
||||
stateKey: 'reblogged',
|
||||
prevState: status.reblogged
|
||||
})
|
||||
onPress={
|
||||
status.visibility === 'public' || status.visibility === 'unlisted'
|
||||
? onPressReblog
|
||||
: null
|
||||
}
|
||||
>
|
||||
<Feather
|
||||
name='repeat'
|
||||
color={iconColorAction(status.reblogged)}
|
||||
size={constants.FONT_SIZE_M + 2}
|
||||
/>
|
||||
</Pressable>
|
||||
children={childrenReblog}
|
||||
/>
|
||||
|
||||
<Pressable
|
||||
style={styles.action}
|
||||
onPress={() =>
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'favourite',
|
||||
stateKey: 'favourited',
|
||||
prevState: status.favourited
|
||||
})
|
||||
}
|
||||
>
|
||||
<Feather
|
||||
name='heart'
|
||||
color={iconColorAction(status.favourited)}
|
||||
size={constants.FONT_SIZE_M + 2}
|
||||
/>
|
||||
</Pressable>
|
||||
onPress={onPressFavourite}
|
||||
children={childrenFavourite}
|
||||
/>
|
||||
|
||||
<Pressable
|
||||
style={styles.action}
|
||||
onPress={() =>
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'bookmark',
|
||||
stateKey: 'bookmarked',
|
||||
prevState: status.bookmarked
|
||||
})
|
||||
}
|
||||
>
|
||||
<Feather
|
||||
name='bookmark'
|
||||
color={iconColorAction(status.bookmarked)}
|
||||
size={constants.FONT_SIZE_M + 2}
|
||||
/>
|
||||
</Pressable>
|
||||
onPress={onPressBookmark}
|
||||
children={childrenBookmark}
|
||||
/>
|
||||
|
||||
<Pressable style={styles.action} onPress={() => setModalVisible(true)}>
|
||||
<Feather
|
||||
name='share-2'
|
||||
color={iconColor}
|
||||
size={constants.FONT_SIZE_M + 2}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={styles.action}
|
||||
onPress={onPressShare}
|
||||
children={childrenShare}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Modal
|
||||
animationType='fade'
|
||||
presentationStyle='overFullScreen'
|
||||
transparent
|
||||
visible={modalVisible}
|
||||
visible={bottomSheetVisible}
|
||||
>
|
||||
<Pressable
|
||||
style={styles.modalBackground}
|
||||
onPress={() => setModalVisible(false)}
|
||||
onPress={() => setBottomSheetVisible(false)}
|
||||
>
|
||||
<View style={styles.modalSheet}>
|
||||
<Pressable
|
||||
@ -266,15 +302,8 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
|
||||
},
|
||||
() => {},
|
||||
() => {
|
||||
setModalVisible(false)
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
position: 'bottom',
|
||||
text1: '分享成功',
|
||||
visibilityTime: 2000,
|
||||
autoHide: true,
|
||||
bottomOffset: 65
|
||||
})
|
||||
setBottomSheetVisible(false)
|
||||
toast({ type: 'success', content: '分享成功' })
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -284,15 +313,8 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
Clipboard.setString(status.uri)
|
||||
setModalVisible(false)
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
position: 'bottom',
|
||||
text1: '链接复制成功',
|
||||
visibilityTime: 2000,
|
||||
autoHide: true,
|
||||
bottomOffset: 65
|
||||
})
|
||||
setBottomSheetVisible(false)
|
||||
toast({ type: 'success', content: '链接复制成功' })
|
||||
}}
|
||||
>
|
||||
<Text>复制链接</Text>
|
||||
@ -300,7 +322,7 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
|
||||
{status.account.id === localAccountId && (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'delete',
|
||||
@ -314,7 +336,7 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
|
||||
<Text>(删除并重发)</Text>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'mute',
|
||||
@ -325,10 +347,11 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
|
||||
>
|
||||
<Text>{status.muted ? '取消静音' : '静音'}</Text>
|
||||
</Pressable>
|
||||
{/* Also note that reblogs cannot be pinned. */}
|
||||
{status.account.id === localAccountId && (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
setBottomSheetVisible(false)
|
||||
mutateAction({
|
||||
id: status.id,
|
||||
type: 'pin',
|
||||
|
@ -84,4 +84,4 @@ const Attachment: React.FC<Props> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default Attachment
|
||||
export default React.memo(Attachment, () => true)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Image, Pressable, StyleSheet } from 'react-native'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
@ -12,15 +12,14 @@ export interface Props {
|
||||
const Avatar: React.FC<Props> = ({ uri, id }) => {
|
||||
const navigation = useNavigation()
|
||||
// Need to fix go back root
|
||||
const onPress = useCallback(() => {
|
||||
navigation.navigate('Screen-Shared-Account', {
|
||||
id: id
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.avatar}
|
||||
onPress={() => {
|
||||
navigation.navigate('Screen-Shared-Account', {
|
||||
id: id
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Pressable style={styles.avatar} onPress={onPress}>
|
||||
<Image source={{ uri: uri }} style={styles.image} />
|
||||
</Pressable>
|
||||
)
|
||||
@ -39,4 +38,4 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
})
|
||||
|
||||
export default Avatar
|
||||
export default React.memo(Avatar, () => true)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Image, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
@ -8,32 +8,29 @@ export interface Props {
|
||||
|
||||
const Card: React.FC<Props> = ({ card }) => {
|
||||
const navigation = useNavigation()
|
||||
const onPress = useCallback(() => {
|
||||
navigation.navigate('Screen-Shared-Webview', {
|
||||
uri: card.url
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
card && (
|
||||
<Pressable
|
||||
style={styles.card}
|
||||
onPress={() => {
|
||||
navigation.navigate('Webview', {
|
||||
uri: card.url
|
||||
})
|
||||
}}
|
||||
>
|
||||
{card.image && (
|
||||
<View style={styles.left}>
|
||||
<Image source={{ uri: card.image }} style={styles.image} />
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.right}>
|
||||
<Text numberOfLines={1}>{card.title}</Text>
|
||||
{card.description ? (
|
||||
<Text numberOfLines={2}>{card.description}</Text>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Text numberOfLines={1}>{card.url}</Text>
|
||||
<Pressable style={styles.card} onPress={onPress}>
|
||||
{card.image && (
|
||||
<View style={styles.left}>
|
||||
<Image source={{ uri: card.image }} style={styles.image} />
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
)}
|
||||
<View style={styles.right}>
|
||||
<Text numberOfLines={1}>{card.title}</Text>
|
||||
{card.description ? (
|
||||
<Text numberOfLines={2}>{card.description}</Text>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Text numberOfLines={1}>{card.url}</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
@ -56,4 +53,4 @@ const styles = StyleSheet.create({
|
||||
}
|
||||
})
|
||||
|
||||
export default Card
|
||||
export default React.memo(Card, () => true)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Modal, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import Toast from 'react-native-toast-message'
|
||||
@ -12,6 +12,9 @@ import { getLocalAccountId, getLocalUrl } from 'src/utils/slices/instancesSlice'
|
||||
import { store } from 'src/store'
|
||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||
import constants from 'src/utils/styles/constants'
|
||||
import BottomSheet from 'src/components/BottomSheet'
|
||||
import BottomSheetRow from 'src/components/BottomSheet/Row'
|
||||
import { toast } from 'src/components/toast'
|
||||
|
||||
const fireMutation = async ({
|
||||
id,
|
||||
@ -32,24 +35,11 @@ const fireMutation = async ({
|
||||
endpoint: `accounts/${id}/${type}`
|
||||
})
|
||||
|
||||
if (res.body[stateKey] === true) {
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
position: 'bottom',
|
||||
text1: '功能成功',
|
||||
visibilityTime: 2000,
|
||||
autoHide: true,
|
||||
bottomOffset: 65
|
||||
})
|
||||
if (res.body[stateKey!] === true) {
|
||||
toast({ type: 'success', content: '功能成功' })
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
position: 'bottom',
|
||||
text1: '请重试',
|
||||
autoHide: false,
|
||||
bottomOffset: 65
|
||||
})
|
||||
toast({ type: 'error', content: '功能错误', autoHide: false })
|
||||
return Promise.reject()
|
||||
}
|
||||
break
|
||||
@ -64,57 +54,40 @@ const fireMutation = async ({
|
||||
})
|
||||
|
||||
if (!res.body.error) {
|
||||
Toast.show({
|
||||
type: 'success',
|
||||
position: 'bottom',
|
||||
text1: '隐藏域名成功',
|
||||
visibilityTime: 2000,
|
||||
autoHide: true,
|
||||
bottomOffset: 65
|
||||
})
|
||||
toast({ type: 'success', content: '隐藏域名成功' })
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
Toast.show({
|
||||
toast({
|
||||
type: 'error',
|
||||
position: 'bottom',
|
||||
text1: '隐藏域名失败,请重试',
|
||||
autoHide: false,
|
||||
bottomOffset: 65
|
||||
content: '隐藏域名失败,请重试',
|
||||
autoHide: false
|
||||
})
|
||||
return Promise.reject()
|
||||
}
|
||||
break
|
||||
case 'reports':
|
||||
console.log('reporting')
|
||||
res = await client({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
endpoint: `reports`,
|
||||
query: {
|
||||
account_id: id || ''
|
||||
}
|
||||
})
|
||||
console.log(res.body)
|
||||
if (!res.body.error) {
|
||||
toast({ type: 'success', content: '举报账户成功' })
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
toast({
|
||||
type: 'error',
|
||||
content: '举报账户失败,请重试',
|
||||
autoHide: false
|
||||
})
|
||||
return Promise.reject()
|
||||
}
|
||||
break
|
||||
// case 'reports':
|
||||
// res = await client({
|
||||
// method: 'post',
|
||||
// instance: 'local',
|
||||
// endpoint: `reports`,
|
||||
// query: {
|
||||
// domain: id || ''
|
||||
// }
|
||||
// })
|
||||
|
||||
// if (!res.body.error) {
|
||||
// Toast.show({
|
||||
// type: 'success',
|
||||
// position: 'bottom',
|
||||
// text1: '隐藏域名成功',
|
||||
// visibilityTime: 2000,
|
||||
// autoHide: true,
|
||||
// bottomOffset: 65
|
||||
// })
|
||||
// return Promise.resolve()
|
||||
// } else {
|
||||
// Toast.show({
|
||||
// type: 'error',
|
||||
// position: 'bottom',
|
||||
// text1: '隐藏域名失败,请重试',
|
||||
// autoHide: false,
|
||||
// bottomOffset: 65
|
||||
// })
|
||||
// return Promise.reject()
|
||||
// }
|
||||
// break
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,31 +124,12 @@ const HeaderDefault: React.FC<Props> = ({
|
||||
const [mutateAction] = useMutation(fireMutation, {
|
||||
onMutate: () => {
|
||||
queryCache.cancelQueries(queryKey)
|
||||
const prevData = queryCache.getQueryData(queryKey)
|
||||
return prevData
|
||||
const oldData = queryCache.getQueryData(queryKey)
|
||||
return oldData
|
||||
},
|
||||
onSuccess: (newData, params) => {
|
||||
if (params.type === 'domain_blocks') {
|
||||
console.log('clearing cache')
|
||||
queryCache.invalidateQueries(['Following', { page: 'Following' }])
|
||||
}
|
||||
// queryCache.setQueryData(queryKey, (oldData: any) => {
|
||||
// oldData &&
|
||||
// oldData.map((paging: any) => {
|
||||
// paging.toots.map(
|
||||
// (status: Mastodon.Status | Mastodon.Notification, i: number) => {
|
||||
// if (status.id === newData.id) {
|
||||
// paging.toots[i] = newData
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// })
|
||||
// return oldData
|
||||
// })
|
||||
return Promise.resolve()
|
||||
},
|
||||
onError: (err, variables, prevData) => {
|
||||
queryCache.setQueryData(queryKey, prevData)
|
||||
onError: (err, _, oldData) => {
|
||||
toast({ type: 'error', content: '请重试', autoHide: false })
|
||||
queryCache.setQueryData(queryKey, oldData)
|
||||
},
|
||||
onSettled: () => {
|
||||
queryCache.invalidateQueries(queryKey)
|
||||
@ -189,6 +143,24 @@ const HeaderDefault: React.FC<Props> = ({
|
||||
}, 1000)
|
||||
}, [since])
|
||||
|
||||
const onPressAction = useCallback(() => setModalVisible(true), [])
|
||||
const onPressApplication = useCallback(() => {
|
||||
navigation.navigate('Webview', {
|
||||
uri: application!.website
|
||||
})
|
||||
}, [])
|
||||
|
||||
const pressableAction = useMemo(
|
||||
() => (
|
||||
<Feather
|
||||
name='more-horizontal'
|
||||
color={theme.secondary}
|
||||
size={constants.FONT_SIZE_M + 2}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.nameAndAction}>
|
||||
@ -212,17 +184,12 @@ const HeaderDefault: React.FC<Props> = ({
|
||||
@{account}
|
||||
</Text>
|
||||
</View>
|
||||
{accountId !== localAccountId && domain !== localDomain && (
|
||||
{(accountId !== localAccountId || domain !== localDomain) && (
|
||||
<Pressable
|
||||
style={styles.action}
|
||||
onPress={() => setModalVisible(true)}
|
||||
>
|
||||
<Feather
|
||||
name='more-horizontal'
|
||||
color={theme.secondary}
|
||||
size={constants.FONT_SIZE_M + 2}
|
||||
/>
|
||||
</Pressable>
|
||||
onPress={onPressAction}
|
||||
children={pressableAction}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.meta}>
|
||||
@ -234,11 +201,7 @@ const HeaderDefault: React.FC<Props> = ({
|
||||
{application && application.name !== 'Web' && (
|
||||
<View>
|
||||
<Text
|
||||
onPress={() => {
|
||||
navigation.navigate('Webview', {
|
||||
uri: application.website
|
||||
})
|
||||
}}
|
||||
onPress={onPressApplication}
|
||||
style={[styles.application, { color: theme.secondary }]}
|
||||
>
|
||||
发自于 - {application.name}
|
||||
@ -246,75 +209,65 @@ const HeaderDefault: React.FC<Props> = ({
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Modal
|
||||
animationType='fade'
|
||||
presentationStyle='overFullScreen'
|
||||
transparent
|
||||
<BottomSheet
|
||||
visible={modalVisible}
|
||||
handleDismiss={() => setModalVisible(false)}
|
||||
>
|
||||
<Pressable
|
||||
style={styles.modalBackground}
|
||||
onPress={() => setModalVisible(false)}
|
||||
>
|
||||
<View style={styles.modalSheet}>
|
||||
{accountId !== localAccountId && (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
mutateAction({
|
||||
id: accountId,
|
||||
type: 'mute',
|
||||
stateKey: 'muting'
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Text>静音用户</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
{accountId !== localAccountId && (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
mutateAction({
|
||||
id: accountId,
|
||||
type: 'block',
|
||||
stateKey: 'blocking'
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Text>屏蔽用户</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
{domain !== localDomain && (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
mutateAction({
|
||||
id: domain,
|
||||
type: 'domain_blocks'
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Text>屏蔽域名</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
{accountId !== localAccountId && (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setModalVisible(false)
|
||||
mutateAction({
|
||||
id: accountId,
|
||||
type: 'reports'
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Text>举报用户</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
</Modal>
|
||||
{accountId !== localAccountId && (
|
||||
<BottomSheetRow
|
||||
onPressFunction={() => {
|
||||
setModalVisible(false)
|
||||
mutateAction({
|
||||
id: accountId,
|
||||
type: 'mute',
|
||||
stateKey: 'muting'
|
||||
})
|
||||
}}
|
||||
icon='eye-off'
|
||||
text={`隐藏 @${account} 的嘟嘟`}
|
||||
/>
|
||||
)}
|
||||
{accountId !== localAccountId && (
|
||||
<BottomSheetRow
|
||||
onPressFunction={() => {
|
||||
setModalVisible(false)
|
||||
mutateAction({
|
||||
id: accountId,
|
||||
type: 'block',
|
||||
stateKey: 'blocking'
|
||||
})
|
||||
}}
|
||||
icon='x-circle'
|
||||
text={`屏蔽用户 @${account}`}
|
||||
/>
|
||||
)}
|
||||
{domain !== localDomain && (
|
||||
<BottomSheetRow
|
||||
onPressFunction={() => {
|
||||
setModalVisible(false)
|
||||
mutateAction({
|
||||
id: domain,
|
||||
type: 'domain_blocks'
|
||||
})
|
||||
}}
|
||||
icon='cloud-off'
|
||||
text={`屏蔽域名 ${domain}`}
|
||||
/>
|
||||
)}
|
||||
{accountId !== localAccountId && (
|
||||
<BottomSheetRow
|
||||
onPressFunction={() => {
|
||||
setModalVisible(false)
|
||||
mutateAction({
|
||||
id: accountId,
|
||||
type: 'reports'
|
||||
})
|
||||
}}
|
||||
icon='alert-triangle'
|
||||
text={`举报 @${account}`}
|
||||
/>
|
||||
)}
|
||||
</BottomSheet>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@ -349,22 +302,7 @@ const styles = StyleSheet.create({
|
||||
application: {
|
||||
fontSize: constants.FONT_SIZE_S,
|
||||
marginLeft: constants.SPACING_S
|
||||
},
|
||||
modalBackground: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-end'
|
||||
},
|
||||
modalSheet: {
|
||||
width: '100%',
|
||||
height: '50%',
|
||||
backgroundColor: 'white',
|
||||
flex: 1
|
||||
}
|
||||
})
|
||||
|
||||
export default HeaderDefault
|
||||
export default React.memo(HeaderDefault, () => true)
|
||||
|
Reference in New Issue
Block a user