diff --git a/README.md b/README.md index 9e10710f..e69de29b 100644 --- a/README.md +++ b/README.md @@ -1 +0,0 @@ -@tooot@xmflsct.com \ No newline at end of file diff --git a/android/app/src/main/java/com/xmflsct/app/tooot/generated/BasePackageList.java b/android/app/src/main/java/com/xmflsct/app/tooot/generated/BasePackageList.java index 07f590fa..dfd10e2d 100644 --- a/android/app/src/main/java/com/xmflsct/app/tooot/generated/BasePackageList.java +++ b/android/app/src/main/java/com/xmflsct/app/tooot/generated/BasePackageList.java @@ -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(), diff --git a/android/app/src/main/res/drawable-night/splashscreen_image.png b/android/app/src/main/res/drawable-night/splashscreen_image.png index 83ca55a4..8ba9b37a 100644 Binary files a/android/app/src/main/res/drawable-night/splashscreen_image.png and b/android/app/src/main/res/drawable-night/splashscreen_image.png differ diff --git a/android/app/src/main/res/values-night/colors.xml b/android/app/src/main/res/values-night/colors.xml index 55096642..9c941a17 100644 --- a/android/app/src/main/res/values-night/colors.xml +++ b/android/app/src/main/res/values-night/colors.xml @@ -1,5 +1,5 @@ - #191919 + #121212 diff --git a/app.config.ts b/app.config.ts index b99e6197..bf7bb859 100644 --- a/app.config.ts +++ b/app.config.ts @@ -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', diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d2315a0a..58ec9d41 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -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 diff --git a/ios/tooot/Images.xcassets/SplashScreen.imageset/dark_splashscreen.png b/ios/tooot/Images.xcassets/SplashScreen.imageset/dark_splashscreen.png index 83ca55a4..8ba9b37a 100644 Binary files a/ios/tooot/Images.xcassets/SplashScreen.imageset/dark_splashscreen.png and b/ios/tooot/Images.xcassets/SplashScreen.imageset/dark_splashscreen.png differ diff --git a/ios/tooot/RootViewColor.xcassets/Background.colorset/Contents.json b/ios/tooot/RootViewColor.xcassets/Background.colorset/Contents.json index ff4bf220..dd794a6f 100644 --- a/ios/tooot/RootViewColor.xcassets/Background.colorset/Contents.json +++ b/ios/tooot/RootViewColor.xcassets/Background.colorset/Contents.json @@ -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" diff --git a/package.json b/package.json index d1782c77..9c0f4969 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/@types/app.d.ts b/src/@types/app.d.ts index 959b393b..489f987e 100644 --- a/src/@types/app.d.ts +++ b/src/@types/app.d.ts @@ -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' } } diff --git a/src/@types/untyped.d.ts b/src/@types/untyped.d.ts index fae6f608..24592684 100644 --- a/src/@types/untyped.d.ts +++ b/src/@types/untyped.d.ts @@ -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' diff --git a/src/Screens.tsx b/src/Screens.tsx index 49349274..7e0a015a 100644 --- a/src/Screens.tsx +++ b/src/Screens.tsx @@ -90,7 +90,7 @@ const Screens: React.FC = ({ 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 }) diff --git a/src/api/client.ts b/src/api/client.ts index 52cdac4f..66ea911a 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -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 ({ headers?: { [key: string]: string } body?: FormData onUploadProgress?: (progressEvent: any) => void -}): Promise => { +}): 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 ({ ...(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 ({ 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() } }) } diff --git a/src/components/Timeline.tsx b/src/components/Timeline.tsx index 9945d668..afcb7957 100644 --- a/src/components/Timeline.tsx +++ b/src/components/Timeline.tsx @@ -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 = ({ } = 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>(null) const scrolled = useRef(false) @@ -109,22 +98,30 @@ const Timeline: React.FC = ({ }, [isSuccess, flattenData.length, scrolled]) const keyExtractor = useCallback(({ id }) => id, []) - const renderItem = useCallback(({ item }) => { - switch (page) { - case 'Conversations': - return - case 'Notifications': - return - default: - return ( - - ) - } - }, []) + const renderItem = useCallback( + ({ item }) => { + switch (page) { + case 'Conversations': + return ( + + ) + case 'Notifications': + return ( + + ) + default: + return ( + + ) + } + }, + [data?.pages[0]] + ) const ItemSeparatorComponent = useCallback( ({ leadingItem }) => ( = ({ () => !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( - () => , - [] - ) const ListFooterComponent = useMemo( () => , [hasNextPage] @@ -180,38 +161,60 @@ const Timeline: React.FC = ({ 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 | 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 | 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( + () => , + [] + ) return ( <> diff --git a/src/components/Timeline/Default.tsx b/src/components/Timeline/Default.tsx index b602c854..91cb330b 100644 --- a/src/components/Timeline/Default.tsx +++ b/src/components/Timeline/Default.tsx @@ -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 = ({ origin, highlighted = false, disableDetails = false, - disableOnPress = false + disableOnPress = false, + pinned }) => { const { theme } = useTheme() const localAccount = useSelector( @@ -73,7 +75,7 @@ const TimelineDefault: React.FC = ({ > {item.reblog ? ( - ) : item.isPinned ? ( + ) : pinned && pinned.includes(item.id) ? ( ) : null} @@ -113,9 +115,11 @@ const TimelineDefault: React.FC = ({ sameAccount={actualStatus.account.id === localAccount?.id} /> ) : null} - {!disableDetails && actualStatus.media_attachments.length > 0 && ( + {!disableDetails && + Array.isArray(actualStatus.media_attachments) && + actualStatus.media_attachments.length ? ( - )} + ) : null} {!disableDetails && actualStatus.card && ( )} diff --git a/src/components/Timeline/Shared/HeaderNotification.tsx b/src/components/Timeline/Shared/HeaderNotification.tsx index 338edab4..3db92160 100644 --- a/src/components/Timeline/Shared/HeaderNotification.tsx +++ b/src/components/Timeline/Shared/HeaderNotification.tsx @@ -46,7 +46,7 @@ const TimelineHeaderNotification: React.FC = ({ onPress={() => navigation.navigate('Screen-Actions', { queryKey, - status, + status: notification.status, url: notification.status?.url || notification.status?.uri, type: 'status' }) diff --git a/src/i18n/en/system.json b/src/i18n/en/system.json deleted file mode 100644 index c0b5f7fe..00000000 --- a/src/i18n/en/system.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "NSCameraUsageDescription": "Allow camera access to upload attachments", - "NSPhotoLibraryUsageDescription": "Allow photo library access to upload attachments" -} \ No newline at end of file diff --git a/src/i18n/zh-Hans/system.json b/src/i18n/zh-Hans/system.json deleted file mode 100644 index 637191f4..00000000 --- a/src/i18n/zh-Hans/system.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "NSCameraUsageDescription": "允许tooot使用相机拍摄上传附件", - "NSPhotoLibraryUsageDescription": "允许tooot读取相册上传附件" -} \ No newline at end of file diff --git a/src/screens/Actions/Status.tsx b/src/screens/Actions/Status.tsx index 169d0970..68954ca7 100644 --- a/src/screens/Actions/Status.tsx +++ b/src/screens/Actions/Status.tsx @@ -100,16 +100,17 @@ const ActionsStatus: React.FC = ({ } ) 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 }) } diff --git a/src/screens/Compose/DraftsList/Root.tsx b/src/screens/Compose/DraftsList/Root.tsx index b5200064..b289a45f 100644 --- a/src/screens/Compose/DraftsList/Root.tsx +++ b/src/screens/Compose/DraftsList/Root.tsx @@ -64,7 +64,7 @@ const ComposeDraftsListRoot: React.FC = ({ timestamp }) => { url: `media/${attachment.remote?.id}` }) .then(res => { - if (res.id === attachment.remote?.id) { + if (res.body.id === attachment.remote?.id) { tempUploads.push(attachment) } }) diff --git a/src/screens/Compose/EditAttachment.tsx b/src/screens/Compose/EditAttachment.tsx index 21247ef1..b2296e4e 100644 --- a/src/screens/Compose/EditAttachment.tsx +++ b/src/screens/Compose/EditAttachment.tsx @@ -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 = ({ const [altText, setAltText] = useState( 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 = ({ 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 = ({ 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({ @@ -129,7 +121,7 @@ const ComposeEditAttachment: React.FC = ({ /> ), - [] + [isSubmitting, altText, focus.value.x, focus.value.y] ) const children = useCallback( diff --git a/src/screens/Compose/EditAttachment/Image.tsx b/src/screens/Compose/EditAttachment/Image.tsx index 5b267ae4..7e86c40b 100644 --- a/src/screens/Compose/EditAttachment/Image.tsx +++ b/src/screens/Compose/EditAttachment/Image.tsx @@ -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 = ({ 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 = ({ index, focus }) => { return ( <> - + = ({ index, focus }) => { } const styles = StyleSheet.create({ + base: { overflow: 'hidden', flex: 1, alignItems: 'center' }, imageFocusText: { ...StyleConstants.FontStyle.M, padding: StyleConstants.Spacing.Global.PagePadding diff --git a/src/screens/Compose/EditAttachment/Root.tsx b/src/screens/Compose/EditAttachment/Root.tsx index 98e5b384..782ea96b 100644 --- a/src/screens/Compose/EditAttachment/Root.tsx +++ b/src/screens/Compose/EditAttachment/Root.tsx @@ -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 }> diff --git a/src/screens/Compose/Root/Footer/Attachments.tsx b/src/screens/Compose/Root/Footer/Attachments.tsx index 4786ed1f..493ea87b 100644 --- a/src/screens/Compose/Root/Footer/Attachments.tsx +++ b/src/screens/Compose/Root/Footer/Attachments.tsx @@ -32,7 +32,6 @@ const ComposeAttachments: React.FC = () => { const navigation = useNavigation() const flatListRef = useRef(null) - let prevOffsets = useRef() const sensitiveOnPress = useCallback(() => { analytics('compose_attachment_sensitive_press', { @@ -92,7 +91,7 @@ const ComposeAttachments: React.FC = () => { ] : attachmentsOffsets }, [composeState.attachments.uploads.length]) - + let prevOffsets = useRef() 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 diff --git a/src/screens/Compose/Root/Footer/Emojis.tsx b/src/screens/Compose/Root/Footer/Emojis.tsx index 1775186c..26a017f5 100644 --- a/src/screens/Compose/Root/Footer/Emojis.tsx +++ b/src/screens/Compose/Root/Footer/Emojis.tsx @@ -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( () => , diff --git a/src/screens/Compose/Root/Footer/addAttachment.ts b/src/screens/Compose/Root/Footer/addAttachment.ts index 3459b56d..8ebee3e9 100644 --- a/src/screens/Compose/Root/Footer/addAttachment.ts +++ b/src/screens/Compose/Root/Footer/addAttachment.ts @@ -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() diff --git a/src/screens/Compose/utils/reducer.ts b/src/screens/Compose/utils/reducer.ts index a4b448c3..e7fecc30 100644 --- a/src/screens/Compose/utils/reducer.ts +++ b/src/screens/Compose/utils/reducer.ts @@ -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 ) } } diff --git a/src/screens/ImagesViewer.tsx b/src/screens/ImagesViewer.tsx index f10d645c..e1a26daa 100644 --- a/src/screens/ImagesViewer.tsx +++ b/src/screens/ImagesViewer.tsx @@ -28,6 +28,10 @@ const ScreenImagesViewer = ({ }, navigation }: ScreenImagesViewerProp) => { + if (imageUrls.length === 0) { + return null + } + const [currentIndex, setCurrentIndex] = useState( findIndex(imageUrls, ['imageIndex', imageIndex]) ) diff --git a/src/screens/Tabs.tsx b/src/screens/Tabs.tsx index b3bfc2d8..e19918c2 100644 --- a/src/screens/Tabs.tsx +++ b/src/screens/Tabs.tsx @@ -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 }) ) } diff --git a/src/screens/Tabs/Me/Settings/Tooot.tsx b/src/screens/Tabs/Me/Settings/Tooot.tsx index 6d55b430..26200673 100644 --- a/src/screens/Tabs/Me/Settings/Tooot.tsx +++ b/src/screens/Tabs/Me/Settings/Tooot.tsx @@ -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', { diff --git a/src/screens/Tabs/Shared/Account.tsx b/src/screens/Tabs/Shared/Account.tsx index 1b46a289..8484c3fb 100644 --- a/src/screens/Tabs/Shared/Account.tsx +++ b/src/screens/Tabs/Shared/Account.tsx @@ -41,6 +41,7 @@ const TabSharedAccount: React.FC = ({ analytics('bottomsheet_open_press', { page: 'account' }) + // @ts-ignore navigation.navigate('Screen-Actions', { type: 'account', account diff --git a/src/screens/Tabs/Shared/Account/Attachments.tsx b/src/screens/Tabs/Shared/Account/Attachments.tsx index 6bfbd24e..4b2c3855 100644 --- a/src/screens/Tabs/Shared/Account/Attachments.tsx +++ b/src/screens/Tabs/Shared/Account/Attachments.tsx @@ -39,7 +39,7 @@ const AccountAttachments = React.memo( page: 'Account_Attachments' as 'Account_Attachments', account: account?.id } - const { data, refetch } = useTimelineQuery({ + 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) : [] diff --git a/src/screens/Tabs/Shared/Relationships/List.tsx b/src/screens/Tabs/Shared/Relationships/List.tsx index 133a4e34..79bf0e22 100644 --- a/src/screens/Tabs/Shared/Relationships/List.tsx +++ b/src/screens/Tabs/Shared/Relationships/List.tsx @@ -26,17 +26,13 @@ const RelationshipsList: React.FC = ({ 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>(null) diff --git a/src/startup/netInfo.ts b/src/startup/netInfo.ts index 23303628..050fe9c3 100644 --- a/src/startup/netInfo.ts +++ b/src/startup/netInfo.ts @@ -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 }) diff --git a/src/utils/queryHooks/account.ts b/src/utils/queryHooks/account.ts index f13fd2bd..3020b4e0 100644 --- a/src/utils/queryHooks/account.ts +++ b/src/utils/queryHooks/account.ts @@ -11,7 +11,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => { method: 'get', instance: 'local', url: `accounts/${id}` - }) + }).then(res => res.body) } const useAccountQuery = ({ diff --git a/src/utils/queryHooks/accountCheck.ts b/src/utils/queryHooks/accountCheck.ts index 3af6e3be..272d32d1 100644 --- a/src/utils/queryHooks/accountCheck.ts +++ b/src/utils/queryHooks/accountCheck.ts @@ -19,7 +19,7 @@ const queryFunction = async ({ queryKey }: { queryKey: QueryKey }) => { instance: 'local', localIndex: index, url: `accounts/${id}` - }) + }).then(res => res.body) } const useAccountCheckQuery = ({ diff --git a/src/utils/queryHooks/announcement.ts b/src/utils/queryHooks/announcement.ts index a1fbea53..532736a5 100644 --- a/src/utils/queryHooks/announcement.ts +++ b/src/utils/queryHooks/announcement.ts @@ -21,7 +21,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKeyAnnouncement }) => { with_dismissed: 'true' } }) - }) + }).then(res => res.body) } const useAnnouncementQuery = ({ diff --git a/src/utils/queryHooks/apps.ts b/src/utils/queryHooks/apps.ts index 80c2e147..3e7d9c0a 100644 --- a/src/utils/queryHooks/apps.ts +++ b/src/utils/queryHooks/apps.ts @@ -25,7 +25,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => { instanceDomain, url: `apps`, body: formData - }) + }).then(res => res.body) } const useAppsQuery = ({ diff --git a/src/utils/queryHooks/emojis.ts b/src/utils/queryHooks/emojis.ts index d0e548c3..79c6bf29 100644 --- a/src/utils/queryHooks/emojis.ts +++ b/src/utils/queryHooks/emojis.ts @@ -9,7 +9,7 @@ const queryFunction = () => { method: 'get', instance: 'local', url: 'custom_emojis' - }) + }).then(res => res.body) } const useEmojisQuery = ({ diff --git a/src/utils/queryHooks/instance.ts b/src/utils/queryHooks/instance.ts index a66f8d22..88161ac3 100644 --- a/src/utils/queryHooks/instance.ts +++ b/src/utils/queryHooks/instance.ts @@ -12,7 +12,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => { instance: 'remote', instanceDomain, url: `instance` - }) + }).then(res => res.body) } const useInstanceQuery = < diff --git a/src/utils/queryHooks/lists.ts b/src/utils/queryHooks/lists.ts index 15439297..dacd76c4 100644 --- a/src/utils/queryHooks/lists.ts +++ b/src/utils/queryHooks/lists.ts @@ -9,7 +9,7 @@ const queryFunction = () => { method: 'get', instance: 'local', url: 'lists' - }) + }).then(res => res.body) } const useListsQuery = ({ diff --git a/src/utils/queryHooks/relationship.ts b/src/utils/queryHooks/relationship.ts index 90fa1d3d..3adac0b6 100644 --- a/src/utils/queryHooks/relationship.ts +++ b/src/utils/queryHooks/relationship.ts @@ -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({ 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) } } diff --git a/src/utils/queryHooks/relationships.ts b/src/utils/queryHooks/relationships.ts index f82965f1..f9b8cf77 100644 --- a/src/utils/queryHooks/relationships.ts +++ b/src/utils/queryHooks/relationships.ts @@ -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({ method: 'get', @@ -37,7 +29,14 @@ const useRelationshipsQuery = ({ options, ...queryKeyParams }: QueryKey[1] & { - options?: UseInfiniteQueryOptions + options?: UseInfiniteQueryOptions< + { + body: Mastodon.Account[] + links?: { prev?: string; next?: string } + }, + AxiosError, + TData + > }) => { const queryKey: QueryKey = ['Relationships', { ...queryKeyParams }] return useInfiniteQuery(queryKey, queryFunction, options) diff --git a/src/utils/queryHooks/search.ts b/src/utils/queryHooks/search.ts index b5ab2eab..b0c485da 100644 --- a/src/utils/queryHooks/search.ts +++ b/src/utils/queryHooks/search.ts @@ -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 = ({ diff --git a/src/utils/queryHooks/timeline.ts b/src/utils/queryHooks/timeline.ts index 5e2445ea..da1fd54b 100644 --- a/src/utils/queryHooks/timeline.ts +++ b/src/utils/queryHooks/timeline.ts @@ -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({ 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({ 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 = ({ options, ...queryKeyParams }: QueryKeyTimeline[1] & { - options?: UseInfiniteQueryOptions + 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, { diff --git a/src/utils/queryHooks/timeline/deleteItem.ts b/src/utils/queryHooks/timeline/deleteItem.ts index ca8a9ebf..b333196a 100644 --- a/src/utils/queryHooks/timeline/deleteItem.ts +++ b/src/utils/queryHooks/timeline/deleteItem.ts @@ -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 | undefined>( - queryKey, - old => { - if (old) { - old.pages = old.pages.map(page => page.filter(item => item.id !== id)) - } - + queryClient.setQueryData | 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 diff --git a/src/utils/queryHooks/timeline/updateStatusProperty.ts b/src/utils/queryHooks/timeline/updateStatusProperty.ts index 0b1e84d0..52602c69 100644 --- a/src/utils/queryHooks/timeline/updateStatusProperty.ts +++ b/src/utils/queryHooks/timeline/updateStatusProperty.ts @@ -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 diff --git a/src/utils/slices/instancesSlice.ts b/src/utils/slices/instancesSlice.ts index 50c0f3e9..edd0e457 100644 --- a/src/utils/slices/instancesSlice.ts +++ b/src/utils/slices/instancesSlice.ts @@ -44,13 +44,13 @@ export type InstancesState = { export const updateLocalAccountPreferences = createAsyncThunk( 'instances/updateLocalAccountPreferences', async (): Promise => { - const preferences = await client({ + const res = await client({ 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({ + const { + body: { id, acct, avatar_static } + } = await client({ method: 'get', instance: 'remote', instanceDomain: url, @@ -100,7 +102,7 @@ export const localAddInstance = createAsyncThunk( type = 'add' } - const preferences = await client({ + const { body: preferences } = await client({ method: 'get', instance: 'remote', instanceDomain: url, diff --git a/src/utils/styles/themes.ts b/src/utils/styles/themes.ts index 7d53f4ff..59c812a2 100644 --- a/src/utils/styles/themes.ts +++ b/src/utils/styles/themes.ts @@ -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)', diff --git a/yarn.lock b/yarn.lock index 7c0ff3d5..8903c129 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"