This commit is contained in:
Zhiyuan Zheng 2021-02-11 01:33:31 +01:00
parent a40a645337
commit d1bddc696a
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
50 changed files with 271 additions and 269 deletions

View File

@ -1 +0,0 @@
<a rel="me" href="https://social.xmflsct.com/@tooot">@tooot@xmflsct.com</a>

View File

@ -25,6 +25,7 @@ public class BasePackageList {
new expo.modules.localization.LocalizationPackage(),
new expo.modules.location.LocationPackage(),
new expo.modules.permissions.PermissionsPackage(),
new expo.modules.screencapture.ScreenCapturePackage(),
new expo.modules.securestore.SecureStorePackage(),
new expo.modules.splashscreen.SplashScreenPackage(),
new expo.modules.sqlite.SQLitePackage(),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually -->
<color name="splashscreen_background">#191919</color>
<color name="splashscreen_background">#121212</color>
</resources>

View File

@ -30,10 +30,6 @@ export default (): ExpoConfig => ({
}
]
},
locales: {
en: './src/i18n/en/system.json',
zh: './src/i18n/zh-Hans/system.json'
},
android: {
versionCode: 4,
package: 'com.xmflsct.app.tooot',

View File

@ -450,8 +450,6 @@ PODS:
- RNSentry (2.1.1):
- React-Core
- Sentry (= 6.0.9)
- RNSharedElement (0.7.0):
- React
- RNSVG (12.1.0):
- React
- SDWebImage (5.10.3):
@ -551,7 +549,6 @@ DEPENDENCIES:
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- "RNSentry (from `../node_modules/@sentry/react-native`)"
- RNSharedElement (from `../node_modules/react-native-shared-element`)
- RNSVG (from `../node_modules/react-native-svg`)
- UMAppLoader (from `../node_modules/unimodules-app-loader/ios`)
- UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`)
@ -721,8 +718,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-screens"
RNSentry:
:path: "../node_modules/@sentry/react-native"
RNSharedElement:
:path: "../node_modules/react-native-shared-element"
RNSVG:
:path: "../node_modules/react-native-svg"
UMAppLoader:
@ -835,7 +830,6 @@ SPEC CHECKSUMS:
RNReanimated: e8a1520b15df106c96214161078c69e4a23b8b29
RNScreens: b6c9607e6fe47c1b6e2f1910d2acd46dd7ecea3a
RNSentry: 6b46b6fc1d715a378fbaa5d7d43bc9ce99b500e5
RNSharedElement: 00b1a1420d213a34459bb9a5aacabb38107d7948
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
SDWebImage: e378178472b735e84b007bfb55514c97948a0598
SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "25",
"green" : "25",
"red" : "25"
"blue" : "18",
"green" : "18",
"red" : "18"
}
},
"idiom" : "universal"

View File

@ -43,6 +43,7 @@
"expo-video-thumbnails": "~4.4.0",
"expo-web-browser": "~8.6.0",
"i18next": "^19.8.5",
"li": "^1.3.0",
"lodash": "^4.17.20",
"react": "16.13.1",
"react-dom": "16.13.1",
@ -108,8 +109,8 @@
"versions": {
"native": "210201",
"major": 0,
"minor": 3,
"patch": 1,
"minor": 4,
"patch": 0,
"expo": "40.0.0"
}
}

9
src/@types/app.d.ts vendored
View File

@ -15,10 +15,9 @@ declare namespace App {
| 'Favourites'
interface IImageInfo {
url: string
width?: number
height?: number
originUrl?: string
props?: any
uri: string
width: number
height: number
type?: 'image' | 'video'
}
}

View File

@ -1,4 +1,5 @@
declare module 'gl-react-blurhash'
declare module 'li'
declare module 'react-native-feather'
declare module 'react-native-htmlview'
declare module 'react-native-toast-message'

View File

@ -90,7 +90,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
url: `announcements`
})
.then(res => {
if (res?.filter(announcement => !announcement.read).length) {
if (res.body.filter(announcement => !announcement.read).length) {
navigationRef.current?.navigate('Screen-Announcements', {
showAll: false
})

View File

@ -1,6 +1,7 @@
import { RootState } from '@root/store'
import axios from 'axios'
import chalk from 'chalk'
import li from 'li'
const ctx = new chalk.Instance({ level: 3 })
@ -28,7 +29,7 @@ const client = async <T = unknown>({
headers?: { [key: string]: string }
body?: FormData
onUploadProgress?: (progressEvent: any) => void
}): Promise<T> => {
}): Promise<{ body: T; links: { prev?: string; next?: string } }> => {
const { store } = require('@root/store')
const state = (store.getState() as RootState).instances
const theLocalIndex =
@ -78,7 +79,19 @@ const client = async <T = unknown>({
...(body && { data: body }),
...(onUploadProgress && { onUploadProgress: onUploadProgress })
})
.then(response => Promise.resolve(response.data))
.then(response => {
let prev
let next
if (response.headers.link) {
const headersLinks = li.parse(response.headers.link)
prev = headersLinks.prev?.match(/_id=([0-9]*)/)[1]
next = headersLinks.next?.match(/_id=([0-9]*)/)[1]
}
return Promise.resolve({
body: response.data,
links: { prev, next }
})
})
.catch(error => {
if (error.response) {
// The request was made and the server responded with a status code
@ -97,8 +110,13 @@ const client = async <T = unknown>({
console.error(ctx.bold(' API '), ctx.bold('request'), error)
return Promise.reject()
} else {
console.error(ctx.bold(' API '), ctx.bold('other'), error.message)
return Promise.reject({ body: error.message })
console.error(
ctx.bold(' API '),
ctx.bold('internal'),
error.message,
url
)
return Promise.reject()
}
})
}

View File

@ -4,7 +4,7 @@ import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { findIndex } from 'lodash'
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { FlatListProps, StyleSheet } from 'react-native'
import { FlatList } from 'react-native-gesture-handler'
import Animated, {
@ -14,6 +14,7 @@ import Animated, {
} from 'react-native-reanimated'
import { InfiniteData, useQueryClient } from 'react-query'
import { useSelector } from 'react-redux'
import haptics from './haptics'
import TimelineConversation from './Timeline/Conversation'
import TimelineDefault from './Timeline/Default'
import TimelineEmpty from './Timeline/Empty'
@ -68,30 +69,18 @@ const Timeline: React.FC<Props> = ({
} = useTimelineQuery({
...queryKeyParams,
options: {
getPreviousPageParam: firstPage => {
return Array.isArray(firstPage) && firstPage.length
? {
direction: 'prev',
id: firstPage[0].last_status
? firstPage[0].last_status.id
: firstPage[0].id
}
: undefined
},
getNextPageParam: lastPage => {
return Array.isArray(lastPage) && lastPage.length
? {
direction: 'next',
id: lastPage[lastPage.length - 1].last_status
? lastPage[lastPage.length - 1].last_status.id
: lastPage[lastPage.length - 1].id
}
: undefined
}
getPreviousPageParam: firstPage =>
firstPage.links?.prev && {
min_id: firstPage.links.prev,
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
limit: '3'
},
getNextPageParam: lastPage =>
lastPage.links?.next && { max_id: lastPage.links.next }
}
})
const flattenData = data?.pages ? data.pages.flatMap(d => [...d]) : []
const flattenData = data?.pages ? data.pages.flatMap(d => [...d.body]) : []
const flRef = useRef<FlatList<any>>(null)
const scrolled = useRef(false)
@ -109,22 +98,30 @@ const Timeline: React.FC<Props> = ({
}, [isSuccess, flattenData.length, scrolled])
const keyExtractor = useCallback(({ id }) => id, [])
const renderItem = useCallback(({ item }) => {
switch (page) {
case 'Conversations':
return <TimelineConversation conversation={item} queryKey={queryKey} />
case 'Notifications':
return <TimelineNotifications notification={item} queryKey={queryKey} />
default:
return (
<TimelineDefault
item={item}
queryKey={queryKey}
{...(toot === item.id && { highlighted: true })}
/>
)
}
}, [])
const renderItem = useCallback(
({ item }) => {
switch (page) {
case 'Conversations':
return (
<TimelineConversation conversation={item} queryKey={queryKey} />
)
case 'Notifications':
return (
<TimelineNotifications notification={item} queryKey={queryKey} />
)
default:
return (
<TimelineDefault
item={item}
queryKey={queryKey}
{...(toot === item.id && { highlighted: true })}
{...(data?.pages[0].pinned && { pinned: data?.pages[0].pinned })}
/>
)
}
},
[data?.pages[0]]
)
const ItemSeparatorComponent = useCallback(
({ leadingItem }) => (
<ComponentSeparator
@ -146,22 +143,6 @@ const Timeline: React.FC<Props> = ({
() => !disableInfinity && !isFetchingNextPage && fetchNextPage(),
[isFetchingNextPage]
)
const prevId = useSharedValue(null)
const headerPadding = useAnimatedStyle(() => {
if (hasPreviousPage) {
if (isFetchingPreviousPage) {
return { paddingTop: withTiming(StyleConstants.Spacing.XL) }
} else {
return { paddingTop: withTiming(0) }
}
} else {
return { paddingTop: withTiming(0) }
}
}, [hasPreviousPage, isFetchingPreviousPage])
const ListHeaderComponent = useMemo(
() => <Animated.View style={headerPadding} />,
[]
)
const ListFooterComponent = useMemo(
() => <TimelineEnd hasNextPage={!disableInfinity ? hasNextPage : false} />,
[hasNextPage]
@ -180,38 +161,60 @@ const Timeline: React.FC<Props> = ({
useScrollToTop(flRef)
const queryClient = useQueryClient()
const scrollY = useSharedValue(0)
const onScroll = useCallback(
({ nativeEvent }) => (scrollY.value = nativeEvent.contentOffset.y),
[]
)
const [isFetchingLatest, setIsFetchingLatest] = useState(false)
useEffect(() => {
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
if (isFetchingLatest) {
if (!isFetchingPreviousPage) {
fetchPreviousPage()
} else {
if (data?.pages[0].body.length === 0) {
setIsFetchingLatest(false)
queryClient.setQueryData<InfiniteData<any> | undefined>(
queryKey,
data => {
if (data?.pages[0].body.length === 0) {
return {
pages: data.pages.slice(1),
pageParams: data.pageParams.slice(1)
}
} else {
return data
}
}
)
}
}
}
}, [isFetchingPreviousPage, isFetchingLatest, data?.pages[0].body])
const onScroll = useCallback(({ nativeEvent }) => {
scrollY.value = nativeEvent.contentOffset.y
}, [])
const onResponderRelease = useCallback(() => {
if (
scrollY.value <= -StyleConstants.Spacing.XL &&
!isFetchingPreviousPage &&
!isFetchingLatest &&
!disableRefresh
) {
queryClient.setQueryData<InfiniteData<any> | undefined>(
queryKey,
data => {
if (data?.pages[0].length === 0) {
if (data.pages[1]) {
prevId.value = data.pages[1][0].id
}
return {
pages: data.pages.slice(1),
pageParams: data.pageParams.slice(1)
}
} else {
prevId.value = data?.pages[0][0].id
return data
}
}
)
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
fetchPreviousPage()
flRef.current?.scrollToOffset({ animated: true, offset: 1 })
haptics('Light')
setIsFetchingLatest(true)
flRef.current?.scrollToOffset({
animated: true,
offset: 1
})
}
}, [scrollY.value, isFetchingPreviousPage, disableRefresh])
}, [scrollY.value, isFetchingLatest, disableRefresh])
const headerPadding = useAnimatedStyle(() => {
if (isFetchingLatest) {
return { paddingTop: withTiming(StyleConstants.Spacing.XL) }
} else {
return { paddingTop: withTiming(0) }
}
}, [isFetchingLatest])
const ListHeaderComponent = useMemo(
() => <Animated.View style={headerPadding} />,
[]
)
return (
<>

View File

@ -24,6 +24,7 @@ export interface Props {
highlighted?: boolean
disableDetails?: boolean
disableOnPress?: boolean
pinned: Mastodon.Status['id'][]
}
// When the poll is long
@ -33,7 +34,8 @@ const TimelineDefault: React.FC<Props> = ({
origin,
highlighted = false,
disableDetails = false,
disableOnPress = false
disableOnPress = false,
pinned
}) => {
const { theme } = useTheme()
const localAccount = useSelector(
@ -73,7 +75,7 @@ const TimelineDefault: React.FC<Props> = ({
>
{item.reblog ? (
<TimelineActioned action='reblog' account={item.account} />
) : item.isPinned ? (
) : pinned && pinned.includes(item.id) ? (
<TimelineActioned action='pinned' account={item.account} />
) : null}
@ -113,9 +115,11 @@ const TimelineDefault: React.FC<Props> = ({
sameAccount={actualStatus.account.id === localAccount?.id}
/>
) : null}
{!disableDetails && actualStatus.media_attachments.length > 0 && (
{!disableDetails &&
Array.isArray(actualStatus.media_attachments) &&
actualStatus.media_attachments.length ? (
<TimelineAttachment status={actualStatus} />
)}
) : null}
{!disableDetails && actualStatus.card && (
<TimelineCard card={actualStatus.card} />
)}

View File

@ -46,7 +46,7 @@ const TimelineHeaderNotification: React.FC<Props> = ({
onPress={() =>
navigation.navigate('Screen-Actions', {
queryKey,
status,
status: notification.status,
url: notification.status?.url || notification.status?.uri,
type: 'status'
})

View File

@ -1,4 +0,0 @@
{
"NSCameraUsageDescription": "Allow camera access to upload attachments",
"NSPhotoLibraryUsageDescription": "Allow photo library access to upload attachments"
}

View File

@ -1,4 +0,0 @@
{
"NSCameraUsageDescription": "允许tooot使用相机拍摄上传附件",
"NSPhotoLibraryUsageDescription": "允许tooot读取相册上传附件"
}

View File

@ -100,16 +100,17 @@ const ActionsStatus: React.FC<Props> = ({
}
)
dismiss()
const res = (await mutation.mutateAsync({
const res = await mutation.mutateAsync({
type: 'deleteItem',
source: 'statuses',
queryKey,
id: status.id
})) as Mastodon.Status
if (res.id) {
})
if (res.body.id) {
// @ts-ignore
navigation.navigate('Screen-Compose', {
type: 'edit',
incomingStatus: res,
incomingStatus: res.body,
queryKey
})
}

View File

@ -64,7 +64,7 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
url: `media/${attachment.remote?.id}`
})
.then(res => {
if (res.id === attachment.remote?.id) {
if (res.body.id === attachment.remote?.id) {
tempUploads.push(attachment)
}
})

View File

@ -3,15 +3,10 @@ import analytics from '@components/analytics'
import haptics from '@components/haptics'
import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header'
import { StackScreenProps } from '@react-navigation/stack'
import React, {
useCallback,
useContext,
useEffect,
useRef,
useState
} from 'react'
import React, { useCallback, useContext, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, KeyboardAvoidingView, Platform } from 'react-native'
import { useSharedValue } from 'react-native-reanimated'
import { SafeAreaView } from 'react-native-safe-area-context'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import ComposeEditAttachmentRoot from './EditAttachment/Root'
@ -39,34 +34,31 @@ const ComposeEditAttachment: React.FC<ScreenComposeEditAttachmentProp> = ({
const [altText, setAltText] = useState<string | undefined>(
theAttachment.description
)
const focus = useRef({ x: 0, y: 0 })
const focus = useSharedValue({
x: theAttachment.meta.focus.x,
y: theAttachment.meta.focus.y
})
useEffect(() => {
const unsubscribe = navigation.addListener('beforeRemove', () => {
let needUpdate = false
if (theAttachment.description !== altText) {
theAttachment.description = altText
needUpdate = true
}
if (theAttachment.type === 'image') {
if (focus.current.x !== 0 || focus.current.y !== 0) {
theAttachment.meta &&
(theAttachment.meta.focus = {
x: focus.current.x > 1 ? 1 : focus.current.x,
y: focus.current.y > 1 ? 1 : focus.current.y
})
needUpdate = true
composeDispatch({
type: 'attachment/edit',
payload: {
...theAttachment,
description: altText,
meta: {
...theAttachment.meta,
focus: {
x: focus.value.x > 1 ? 1 : focus.value.x,
y: focus.value.y > 1 ? 1 : focus.value.y
}
}
}
}
if (needUpdate) {
composeDispatch({ type: 'attachment/edit', payload: theAttachment })
}
})
})
return unsubscribe
}, [focus, altText])
}, [focus.value.x, focus.value.y, altText])
const headerLeft = useCallback(
() => (
@ -86,7 +78,7 @@ const ComposeEditAttachment: React.FC<ScreenComposeEditAttachmentProp> = ({
loading={isSubmitting}
onPress={() => {
analytics('editattachment_confirm_press')
if (!altText && focus.current.x === 0 && focus.current.y === 0) {
if (!altText && focus.value.x === 0 && focus.value.y === 0) {
navigation.goBack()
return
}
@ -95,8 +87,8 @@ const ComposeEditAttachment: React.FC<ScreenComposeEditAttachmentProp> = ({
if (altText) {
formData.append('description', altText)
}
if (focus.current.x !== 0 || focus.current.y !== 0) {
formData.append('focus', `${focus.current.x},${focus.current.y}`)
if (focus.value.x !== 0 || focus.value.y !== 0) {
formData.append('focus', `${focus.value.x},${focus.value.y}`)
}
client<Mastodon.Attachment>({
@ -129,7 +121,7 @@ const ComposeEditAttachment: React.FC<ScreenComposeEditAttachmentProp> = ({
/>
),
[]
[isSubmitting, altText, focus.value.x, focus.value.y]
)
const children = useCallback(

View File

@ -1,6 +1,6 @@
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { MutableRefObject, useContext } from 'react'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { Dimensions, StyleSheet, Text, View } from 'react-native'
import FastImage from 'react-native-fast-image'
@ -18,7 +18,7 @@ import ComposeContext from '../utils/createContext'
export interface Props {
index: number
focus: MutableRefObject<{
focus: Animated.SharedValue<{
x: number
y: number
}>
@ -57,7 +57,7 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
2
)
const updateFocus = ({ x, y }: { x: number; y: number }) => {
focus.current = { x, y }
focus.value = { x, y }
}
type PanContext = {
startX: number
@ -110,7 +110,7 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
return (
<>
<View style={{ overflow: 'hidden', flex: 1, alignItems: 'center' }}>
<View style={styles.base}>
<FastImage
style={{
width: imageDimensionis.width,
@ -160,6 +160,7 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
}
const styles = StyleSheet.create({
base: { overflow: 'hidden', flex: 1, alignItems: 'center' },
imageFocusText: {
...StyleConstants.FontStyle.M,
padding: StyleConstants.Spacing.Global.PagePadding

View File

@ -1,21 +1,16 @@
import AttachmentVideo from '@components/Timeline/Shared/Attachment/Video'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, {
Dispatch,
MutableRefObject,
SetStateAction,
useContext,
useMemo
} from 'react'
import React, { Dispatch, SetStateAction, useContext, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { ScrollView, StyleSheet, Text, TextInput, View } from 'react-native'
import Animated from 'react-native-reanimated'
import ComposeContext from '../utils/createContext'
import ComposeEditAttachmentImage from './Image'
export interface Props {
index: number
focus: MutableRefObject<{
focus: Animated.SharedValue<{
x: number
y: number
}>

View File

@ -32,7 +32,6 @@ const ComposeAttachments: React.FC = () => {
const navigation = useNavigation()
const flatListRef = useRef<FlatList>(null)
let prevOffsets = useRef<number[]>()
const sensitiveOnPress = useCallback(() => {
analytics('compose_attachment_sensitive_press', {
@ -92,7 +91,7 @@ const ComposeAttachments: React.FC = () => {
]
: attachmentsOffsets
}, [composeState.attachments.uploads.length])
let prevOffsets = useRef<number[]>()
useEffect(() => {
if (
snapToOffsets.length >
@ -105,7 +104,7 @@ const ComposeAttachments: React.FC = () => {
})
}
prevOffsets.current = snapToOffsets
}, [snapToOffsets, prevOffsets])
}, [snapToOffsets, prevOffsets.current])
const renderAttachment = useCallback(
({ item, index }: { item: ExtendedAttachment; index: number }) => {
@ -251,7 +250,7 @@ const ComposeAttachments: React.FC = () => {
showsHorizontalScrollIndicator={false}
data={composeState.attachments.uploads}
keyExtractor={item =>
item.local?.url || item.remote?.url || Math.random().toString()
item.local?.uri || item.remote?.url || Math.random().toString()
}
ListFooterComponent={
composeState.attachments.uploads.length < 4 ? listFooter : null

View File

@ -22,7 +22,7 @@ const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
// type: 'emoji',
// payload: { ...composeState.emoji, active: false }
// })
haptics('Success')
haptics('Light')
}, [composeState])
const children = useMemo(
() => <FastImage source={{ uri: emoji.url }} style={styles.emoji} />,

View File

@ -113,10 +113,10 @@ const addAttachment = async ({
body: formData
})
.then(res => {
if (res.id) {
if (res.body.id) {
composeDispatch({
type: 'attachment/upload/end',
payload: { remote: res, local: result }
payload: { remote: res.body, local: result }
})
} else {
uploadFailed()

View File

@ -70,7 +70,7 @@ const composeReducer = (
attachments: {
...state.attachments,
uploads: state.attachments.uploads.filter(
upload => upload.local.hash !== action.payload
upload => upload.local?.hash !== action.payload
)
}
}

View File

@ -28,6 +28,10 @@ const ScreenImagesViewer = ({
},
navigation
}: ScreenImagesViewerProp) => {
if (imageUrls.length === 0) {
return null
}
const [currentIndex, setCurrentIndex] = useState(
findIndex(imageUrls, ['imageIndex', imageIndex])
)

View File

@ -145,10 +145,11 @@ const ScreenTabs = React.memo(
options: {
notifyOnChangeProps: [],
select: data => {
if (data.pages[0].length) {
if (data.pages[0].body.length) {
dispatch(
updateLocalNotification({
latestTime: data.pages[0][0].created_at
// @ts-ignore
latestTime: data.pages[0].body[0].created_at
})
)
}

View File

@ -77,7 +77,9 @@ const SettingsTooot: React.FC = () => {
iconBack='ChevronRight'
onPress={() => {
const foundAccounts = data?.accounts.filter(
account => account.acct === 'tooot@xmflsct.com'
account =>
account.acct === 'tooot@xmflsct.com' ||
account.url === 'https://social.xmflsct.com/@tooot'
)
if (foundAccounts?.length === 1) {
navigation.navigate('Screen-Compose', {

View File

@ -41,6 +41,7 @@ const TabSharedAccount: React.FC<SharedAccountProp> = ({
analytics('bottomsheet_open_press', {
page: 'account'
})
// @ts-ignore
navigation.navigate('Screen-Actions', {
type: 'account',
account

View File

@ -39,7 +39,7 @@ const AccountAttachments = React.memo(
page: 'Account_Attachments' as 'Account_Attachments',
account: account?.id
}
const { data, refetch } = useTimelineQuery<Mastodon.Status[]>({
const { data, refetch } = useTimelineQuery({
...queryKeyParams,
options: { enabled: false }
})
@ -51,8 +51,8 @@ const AccountAttachments = React.memo(
const flattenData = data?.pages
? data.pages
.flatMap(d => [...d])
.filter(status => !status.sensitive)
.flatMap(d => [...d.body])
.filter(status => !(status as Mastodon.Status).sensitive)
.splice(0, DISPLAY_AMOUNT)
: []

View File

@ -26,17 +26,13 @@ const RelationshipsList: React.FC<Props> = ({ id, type }) => {
type,
id,
options: {
getNextPageParam: lastPage => {
return lastPage.length
? {
direction: 'next',
id: lastPage[lastPage.length - 1].id
}
: undefined
}
getPreviousPageParam: firstPage =>
firstPage.links?.prev && { since_id: firstPage.links.next },
getNextPageParam: lastPage =>
lastPage.links?.next && { max_id: lastPage.links.next }
}
})
const flattenData = data?.pages ? data.pages.flatMap(d => [...d]) : []
const flattenData = data?.pages ? data.pages.flatMap(d => [...d.body]) : []
const flRef = useRef<FlatList<Mastodon.Account>>(null)

View File

@ -27,7 +27,7 @@ const netInfo = async (): Promise<{
.then(res => {
log('log', 'netInfo', 'local credential check passed')
if (
res.id !==
res.body.id !==
store.getState().instances.local?.instances[activeIndex].account.id
) {
log('error', 'netInfo', 'local id does not match remote id')
@ -36,8 +36,8 @@ const netInfo = async (): Promise<{
} else {
store.dispatch(
updateLocalAccount({
acct: res.acct,
avatarStatic: res.avatar_static
acct: res.body.acct,
avatarStatic: res.body.avatar_static
})
)
return Promise.resolve({ connected: true })

View File

@ -11,7 +11,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
method: 'get',
instance: 'local',
url: `accounts/${id}`
})
}).then(res => res.body)
}
const useAccountQuery = <TData = Mastodon.Account>({

View File

@ -19,7 +19,7 @@ const queryFunction = async ({ queryKey }: { queryKey: QueryKey }) => {
instance: 'local',
localIndex: index,
url: `accounts/${id}`
})
}).then(res => res.body)
}
const useAccountCheckQuery = <TData = Mastodon.Account>({

View File

@ -21,7 +21,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKeyAnnouncement }) => {
with_dismissed: 'true'
}
})
})
}).then(res => res.body)
}
const useAnnouncementQuery = <TData = Mastodon.Announcement[]>({

View File

@ -25,7 +25,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
instanceDomain,
url: `apps`,
body: formData
})
}).then(res => res.body)
}
const useAppsQuery = <TData = Mastodon.Apps>({

View File

@ -9,7 +9,7 @@ const queryFunction = () => {
method: 'get',
instance: 'local',
url: 'custom_emojis'
})
}).then(res => res.body)
}
const useEmojisQuery = <TData = Mastodon.Emoji[]>({

View File

@ -12,7 +12,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
instance: 'remote',
instanceDomain,
url: `instance`
})
}).then(res => res.body)
}
const useInstanceQuery = <

View File

@ -9,7 +9,7 @@ const queryFunction = () => {
method: 'get',
instance: 'local',
url: 'lists'
})
}).then(res => res.body)
}
const useListsQuery = <TData = Mastodon.List[]>({

View File

@ -22,7 +22,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKeyRelationship }) => {
params: {
'id[]': id
}
})
}).then(res => res.body)
}
const useRelationshipQuery = ({
@ -61,7 +61,7 @@ const mutationFunction = async (params: MutationVarsRelationship) => {
method: 'post',
instance: 'local',
url: `follow_requests/${params.id}/${params.payload.action}`
})
}).then(res => res.body)
case 'outgoing':
return client<Mastodon.Relationship>({
method: 'post',
@ -69,7 +69,7 @@ const mutationFunction = async (params: MutationVarsRelationship) => {
url: `accounts/${params.id}/${params.payload.state ? 'un' : ''}${
params.payload.action
}`
})
}).then(res => res.body)
}
}

View File

@ -12,18 +12,10 @@ const queryFunction = ({
pageParam
}: {
queryKey: QueryKey
pageParam?: { direction: 'next'; id: Mastodon.Status['id'] }
pageParam?: { [key: string]: string }
}) => {
const { type, id } = queryKey[1]
let params: { [key: string]: string } = {}
if (pageParam) {
switch (pageParam.direction) {
case 'next':
params.max_id = pageParam.id
break
}
}
let params: { [key: string]: string } = { ...pageParam }
return client<Mastodon.Account[]>({
method: 'get',
@ -37,7 +29,14 @@ const useRelationshipsQuery = <TData = Mastodon.Account[]>({
options,
...queryKeyParams
}: QueryKey[1] & {
options?: UseInfiniteQueryOptions<Mastodon.Account[], AxiosError, TData>
options?: UseInfiniteQueryOptions<
{
body: Mastodon.Account[]
links?: { prev?: string; next?: string }
},
AxiosError,
TData
>
}) => {
const queryKey: QueryKey = ['Relationships', { ...queryKeyParams }]
return useInfiniteQuery(queryKey, queryFunction, options)

View File

@ -25,7 +25,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
instance: 'local',
url: 'search',
params: { ...(type && { type }), ...(term && { q: term }), limit }
})
}).then(res => res.body)
}
const useSearchQuery = <TData = SearchResult>({

View File

@ -28,29 +28,10 @@ const queryFunction = ({
pageParam
}: {
queryKey: QueryKeyTimeline
pageParam?: { direction: 'prev' | 'next'; id: Mastodon.Status['id'] }
pageParam?: { [key: string]: string }
}) => {
const { page, account, hashtag, list, toot } = queryKey[1]
let params: { [key: string]: string } = {}
if (pageParam) {
switch (pageParam.direction) {
case 'prev':
if (page === 'Bookmarks' || page === 'Favourites') {
params.max_id = pageParam.id
} else {
params.min_id = pageParam.id
}
break
case 'next':
if (page === 'Bookmarks' || page === 'Favourites') {
params.min_id = pageParam.id
} else {
params.max_id = pageParam.id
}
break
}
}
let params: { [key: string]: string } = { ...pageParam }
switch (page) {
case 'Following':
@ -89,7 +70,7 @@ const queryFunction = ({
})
case 'Account_Default':
if (pageParam && pageParam.direction === 'next') {
if (pageParam && pageParam.pointer === 'max_id') {
return client<Mastodon.Status[]>({
method: 'get',
instance: 'local',
@ -108,10 +89,8 @@ const queryFunction = ({
pinned: 'true'
}
}).then(async res1 => {
let toots = res1.map(status => {
status.isPinned = true
return status
})
let pinned: Mastodon.Status['id'][] = []
res1.body.forEach(status => pinned.push(status.id))
const res2 = await client<Mastodon.Status[]>({
method: 'get',
instance: 'local',
@ -120,7 +99,11 @@ const queryFunction = ({
exclude_replies: 'true'
}
})
return uniqBy([...toots, ...res2], 'id')
return {
body: uniqBy([...res1.body, ...res2.body], 'id'),
...(res2.links.next && { links: { max_id: res2.links.next } }),
pinned
}
})
}
@ -197,7 +180,9 @@ const queryFunction = ({
instance: 'local',
url: `statuses/${toot}/context`
})
return [...res2.ancestors, res1, ...res2.descendants]
return {
body: [...res2.body.ancestors, res1.body, ...res2.body.descendants]
}
})
default:
return Promise.reject()
@ -210,7 +195,18 @@ const useTimelineQuery = <TData = TimelineData>({
options,
...queryKeyParams
}: QueryKeyTimeline[1] & {
options?: UseInfiniteQueryOptions<any, AxiosError, TData>
options?: UseInfiniteQueryOptions<
{
body:
| Mastodon.Status[]
| Mastodon.Notification[]
| Mastodon.Conversation[]
links?: { prev?: string; next?: string }
pinned?: Mastodon.Status['id'][]
},
AxiosError,
TData
>
}) => {
const queryKey: QueryKeyTimeline = ['Timeline', { ...queryKeyParams }]
return useInfiniteQuery(queryKey, queryFunction, options)
@ -354,7 +350,7 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
}
type MutationOptionsTimeline = MutationOptions<
Mastodon.Conversation | Mastodon.Notification | Mastodon.Status,
{ body: Mastodon.Conversation | Mastodon.Notification | Mastodon.Status },
AxiosError,
MutationVarsTimeline
>
@ -373,7 +369,7 @@ const useTimelineMutation = ({
onSuccess?: MutationOptionsTimeline['onSuccess'] | boolean
}) => {
return useMutation<
Mastodon.Conversation | Mastodon.Notification | Mastodon.Status,
{ body: Mastodon.Conversation | Mastodon.Notification | Mastodon.Status },
AxiosError,
MutationVarsTimeline
>(mutationFunction, {

View File

@ -1,5 +1,5 @@
import { InfiniteData, QueryClient } from 'react-query'
import { QueryKeyTimeline } from '../timeline'
import { QueryKeyTimeline, TimelineData } from '../timeline'
const deleteItem = ({
queryClient,
@ -10,16 +10,15 @@ const deleteItem = ({
queryKey: QueryKeyTimeline
id: Mastodon.Status['id']
}) => {
queryClient.setQueryData<InfiniteData<Mastodon.Conversation[]> | undefined>(
queryKey,
old => {
if (old) {
old.pages = old.pages.map(page => page.filter(item => item.id !== id))
}
queryClient.setQueryData<InfiniteData<any> | undefined>(queryKey, old => {
if (old) {
old.pages = old.pages.map(page => {
page.body = page.body.filter((item: Mastodon.Status) => item.id !== id)
return page
})
return old
}
)
})
}
export default deleteItem

View File

@ -32,9 +32,10 @@ const updateStatusProperty = ({
return page
} else {
if (
typeof (page as Mastodon.Conversation[])[0].unread === 'boolean'
typeof (page.body as Mastodon.Conversation[])[0].unread ===
'boolean'
) {
const items = page as Mastodon.Conversation[]
const items = page.body as Mastodon.Conversation[]
const tootIndex = findIndex(items, ['last_status.id', id])
if (tootIndex >= 0) {
foundToot = true
@ -42,16 +43,16 @@ const updateStatusProperty = ({
}
return page
} else if (
typeof (page as Mastodon.Notification[])[0].type === 'string'
typeof (page.body as Mastodon.Notification[])[0].type === 'string'
) {
const items = page as Mastodon.Notification[]
const items = page.body as Mastodon.Notification[]
const tootIndex = findIndex(items, ['status.id', id])
if (tootIndex >= 0) {
foundToot = true
updateNotification({ item: items[tootIndex], payload })
}
} else {
const items = page as Mastodon.Status[]
const items = page.body as Mastodon.Status[]
const tootIndex = findIndex(items, [
reblog ? 'reblog.id' : 'id',
id

View File

@ -44,13 +44,13 @@ export type InstancesState = {
export const updateLocalAccountPreferences = createAsyncThunk(
'instances/updateLocalAccountPreferences',
async (): Promise<Mastodon.Preferences> => {
const preferences = await client<Mastodon.Preferences>({
const res = await client<Mastodon.Preferences>({
method: 'get',
instance: 'local',
url: `preferences`
})
return Promise.resolve(preferences)
return Promise.resolve(res.body)
}
)
@ -73,7 +73,9 @@ export const localAddInstance = createAsyncThunk(
const instanceLocal: InstancesState['local'] = store.getState().instances
.local
const { id, acct, avatar_static } = await client<Mastodon.Account>({
const {
body: { id, acct, avatar_static }
} = await client<Mastodon.Account>({
method: 'get',
instance: 'remote',
instanceDomain: url,
@ -100,7 +102,7 @@ export const localAddInstance = createAsyncThunk(
type = 'add'
}
const preferences = await client<Mastodon.Preferences>({
const { body: preferences } = await client<Mastodon.Preferences>({
method: 'get',
instance: 'remote',
instanceDomain: url,

View File

@ -48,15 +48,15 @@ const themeColors: {
background: {
light: 'rgb(250, 250, 250)',
dark: 'rgb(25, 25, 25)'
dark: 'rgb(18, 18, 18)'
},
backgroundGradientStart: {
light: 'rgba(250, 250, 250, 0.5)',
dark: 'rgba(25, 25, 25, 0.5)'
dark: 'rgba(18, 18, 18, 0.5)'
},
backgroundGradientEnd: {
light: 'rgba(250, 250, 250, 1)',
dark: 'rgba(25, 25, 25, 1)'
dark: 'rgba(18, 18, 18, 1)'
},
backgroundOverlay: {
light: 'rgba(25, 25, 25, 0.5)',

View File

@ -6968,6 +6968,11 @@ levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
li@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/li/-/li-1.3.0.tgz#22c59bcaefaa9a8ef359cf759784e4bf106aea1b"
integrity sha1-IsWbyu+qmo7zWc91l4TkvxBq6hs=
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"