mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Updates
This commit is contained in:
		| @@ -86,6 +86,7 @@ GEM | ||||
|       xcpretty (~> 0.3.0) | ||||
|       xcpretty-travis-formatter (>= 0.0.3) | ||||
|     fastlane-plugin-json (1.0.0) | ||||
|     fastlane-plugin-sentry (1.8.0) | ||||
|     fastlane-plugin-versioning_android (0.1.0) | ||||
|     fastlane-plugin-yarn (1.2) | ||||
|     gh_inspector (1.1.3) | ||||
| @@ -200,6 +201,7 @@ PLATFORMS | ||||
| DEPENDENCIES | ||||
|   fastlane | ||||
|   fastlane-plugin-json | ||||
|   fastlane-plugin-sentry | ||||
|   fastlane-plugin-versioning_android | ||||
|   fastlane-plugin-yarn | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ fastlane_version "2.173.0" | ||||
| skip_docs | ||||
|  | ||||
| ensure_env_vars( | ||||
|   env_vars: ["TOOOT_ENVIRONMENT"] | ||||
|   env_vars: ["TOOOT_ENVIRONMENT", "SENTRY_ORGANIZATION", "SENTRY_PROJECT", "SENTRY_AUTH_TOKEN"] | ||||
| ) | ||||
|  | ||||
| VERSIONS = read_json( json_path: "./package.json" )[:versions] | ||||
| @@ -15,7 +15,7 @@ case ENVIRONMENT | ||||
| when "development" | ||||
|   GITHUB_RELEASE= "" | ||||
| when "staging" | ||||
|   GITHUB_RELEASE = "v#{VERSION}-rc#{VERSIONS[:patch]}" | ||||
|   GITHUB_RELEASE = "v#{VERSION}-#{VERSIONS[:patch]}" | ||||
| when "production" | ||||
|   GITHUB_RELEASE = "v#{VERSION}" | ||||
| end | ||||
| @@ -37,6 +37,15 @@ private_lane :update_expo_ios do | ||||
|   set_info_plist_value( path: EXPO_PLIST, key: "EXUpdatesReleaseChannel", value: RELEASE_CHANNEL ) | ||||
| end | ||||
|  | ||||
| desc 'IOS: Upload dSYM' | ||||
| lane :upload_symbols_ios do | ||||
|   download_dsyms | ||||
|   sentry_upload_dsym( | ||||
|     org_slug: ENV["SENTRY_ORGANIZATION"], | ||||
|     project_slug: ENV["SENTRY_PROJECT"] | ||||
|   ) | ||||
| end | ||||
|  | ||||
| desc "ANDROID: Prepare play store" | ||||
| private_lane :prepare_playstore_android do | ||||
|   android_set_version_name( version_name: VERSION, gradle_file: "./android/app/build.gradle" ) | ||||
| @@ -77,13 +86,14 @@ private_lane :build_ios do | ||||
|   when "staging" | ||||
|     prepare_appstore_ios | ||||
|     match( type: "appstore", readonly: true ) | ||||
|     build_ios_app( export_method: "app-store" ) | ||||
|     build_ios_app( export_method: "app-store", include_symbols: true, include_bitcode: true ) | ||||
|     upload_to_testflight( | ||||
|       demo_account_required: true, | ||||
|       distribute_external: true, | ||||
|       groups: "内测用户", | ||||
|       changelog: "Ready for testing" | ||||
|     ) | ||||
|     upload_symbols_ios | ||||
|   when "production" | ||||
|     prepare_appstore_ios | ||||
|     match( type: "appstore", readonly: true ) | ||||
|   | ||||
| @@ -5,3 +5,4 @@ | ||||
| gem 'fastlane-plugin-yarn' | ||||
| gem 'fastlane-plugin-json' | ||||
| gem 'fastlane-plugin-versioning_android' | ||||
| gem 'fastlane-plugin-sentry' | ||||
|   | ||||
| @@ -64,7 +64,7 @@ | ||||
|     "react-native-tab-view-viewpager-adapter": "^1.1.0", | ||||
|     "react-native-toast-message": "^1.4.3", | ||||
|     "react-native-unimodules": "~0.12.0", | ||||
|     "react-query": "^3.8.2", | ||||
|     "react-query": "^3.9.7", | ||||
|     "react-redux": "^7.2.2", | ||||
|     "react-timeago": "^5.2.0", | ||||
|     "reconnecting-websocket": "^4.4.0", | ||||
|   | ||||
| @@ -162,12 +162,12 @@ const ComponentInstance: React.FC<Props> = ({ | ||||
|             type='text' | ||||
|             content={t('server.button.local')} | ||||
|             onPress={processUpdate} | ||||
|             disabled={!instanceQuery.data?.uri || !agreed} | ||||
|             disabled={!instanceQuery.data?.uri} | ||||
|             loading={instanceQuery.isFetching || appsQuery.isFetching} | ||||
|           /> | ||||
|         </View> | ||||
|  | ||||
|         <EULA agreed={agreed} setAgreed={setAgreed} /> | ||||
|         {/* <EULA agreed={agreed} setAgreed={setAgreed} /> */} | ||||
|  | ||||
|         <View> | ||||
|           <Placeholder | ||||
|   | ||||
| @@ -82,19 +82,26 @@ const Timeline: React.FC<Props> = ({ | ||||
|     ...queryKeyParams, | ||||
|     options: { | ||||
|       getPreviousPageParam: firstPage => | ||||
|         firstPage.links?.prev && { | ||||
|         firstPage?.links?.prev && { | ||||
|           min_id: firstPage.links.prev, | ||||
|           // https://github.com/facebook/react-native/issues/25239#issuecomment-731100372 | ||||
|           limit: '6' | ||||
|           limit: '5' | ||||
|         }, | ||||
|  | ||||
|       getNextPageParam: lastPage => | ||||
|         lastPage.links?.next && { max_id: lastPage.links.next } | ||||
|         lastPage?.links?.next && { max_id: lastPage.links.next } | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   const flattenData = data?.pages ? data.pages.flatMap(d => [...d.body]) : [] | ||||
|  | ||||
|   // Auto go back when toot page is empty | ||||
|   const navigation = useNavigation() | ||||
|   useEffect(() => { | ||||
|     if (toot && isSuccess && flattenData.length === 0) { | ||||
|       navigation.goBack() | ||||
|     } | ||||
|   }, [isSuccess, flattenData.length]) | ||||
|   // Toot page auto scroll to selected toot | ||||
|   const flRef = useRef<FlatList<any>>(null) | ||||
|   const scrolled = useRef(false) | ||||
| @@ -119,13 +126,6 @@ const Timeline: React.FC<Props> = ({ | ||||
|       350 | ||||
|     ) | ||||
|   }, []) | ||||
|   // Auto go back when toot page is empty | ||||
|   const navigation = useNavigation() | ||||
|   useEffect(() => { | ||||
|     if (toot && isSuccess && flattenData.length === 0) { | ||||
|       navigation.goBack() | ||||
|     } | ||||
|   }, [isSuccess, flattenData.length]) | ||||
|  | ||||
|   const keyExtractor = useCallback(({ id }) => id, []) | ||||
|   const renderItem = useCallback( | ||||
| @@ -233,26 +233,57 @@ const Timeline: React.FC<Props> = ({ | ||||
|     scrollY.value = nativeEvent.contentOffset.y | ||||
|   }, []) | ||||
|   const onResponderRelease = useCallback(() => { | ||||
|     if ( | ||||
|       scrollY.value <= -StyleConstants.Spacing.XL && | ||||
|       isFetchingLatest === 0 && | ||||
|       !disableRefresh | ||||
|     ) { | ||||
|       haptics('Light') | ||||
|       setIsFetchingLatest(1) | ||||
|       flRef.current?.scrollToOffset({ | ||||
|         animated: true, | ||||
|         offset: 1 | ||||
|       }) | ||||
|     if (!disableRefresh) { | ||||
|       const separation01 = -( | ||||
|         (StyleConstants.Spacing.M * 2.5) / 2 + | ||||
|         StyleConstants.Font.Size.S / 2 | ||||
|       ) | ||||
|       const separation02 = -( | ||||
|         StyleConstants.Spacing.M * 2.5 * 1.5 + | ||||
|         StyleConstants.Font.Size.S / 2 | ||||
|       ) | ||||
|       if ( | ||||
|         scrollY.value <= separation02 && | ||||
|         !isFetching && | ||||
|         isFetchingLatest === 0 | ||||
|       ) { | ||||
|         haptics('Light') | ||||
|         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 | ||||
|             } | ||||
|           } | ||||
|         ) | ||||
|         refetch() | ||||
|       } else if ( | ||||
|         scrollY.value <= separation01 && | ||||
|         !isFetching && | ||||
|         isFetchingLatest === 0 | ||||
|       ) { | ||||
|         haptics('Light') | ||||
|         setIsFetchingLatest(1) | ||||
|         flRef.current?.scrollToOffset({ | ||||
|           animated: true, | ||||
|           offset: 1 | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
|   }, [scrollY.value, isFetchingLatest, disableRefresh]) | ||||
|   }, [scrollY.value, isFetching, isFetchingLatest, disableRefresh]) | ||||
|   const headerPadding = useAnimatedStyle(() => { | ||||
|     if (isFetchingLatest !== 0) { | ||||
|       return { paddingTop: withTiming(StyleConstants.Spacing.XL) } | ||||
|     } else { | ||||
|       return { paddingTop: withTiming(0) } | ||||
|     return { | ||||
|       paddingTop: | ||||
|         isFetchingLatest !== 0 || (isFetching && !isLoading) | ||||
|           ? withTiming(StyleConstants.Spacing.M * 2.5) | ||||
|           : withTiming(0) | ||||
|     } | ||||
|   }, [isFetchingLatest]) | ||||
|   }, [isFetchingLatest, isFetching, isLoading]) | ||||
|   const ListHeaderComponent = useMemo( | ||||
|     () => <Animated.View style={headerPadding} />, | ||||
|     [] | ||||
| @@ -267,7 +298,7 @@ const Timeline: React.FC<Props> = ({ | ||||
|             colors={[theme.primary]} | ||||
|             progressBackgroundColor={theme.background} | ||||
|             refreshing={isFetching || isLoading} | ||||
|             // onRefresh={() => refetch()} | ||||
|             onRefresh={() => refetch()} | ||||
|           /> | ||||
|         ) | ||||
|       }, | ||||
| @@ -276,8 +307,14 @@ const Timeline: React.FC<Props> = ({ | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <TimelineRefresh isLoading={isLoading} disable={disableRefresh} /> | ||||
|       <TimelineRefresh | ||||
|         scrollY={scrollY} | ||||
|         isLoading={isLoading} | ||||
|         isFetching={isFetching} | ||||
|         disable={disableRefresh} | ||||
|       /> | ||||
|       <FlatList | ||||
|         scrollEventThrottle={16} | ||||
|         onScroll={onScroll} | ||||
|         onResponderRelease={onResponderRelease} | ||||
|         ref={flRef} | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline' | ||||
| import { getLocalAccount } from '@utils/slices/instancesSlice' | ||||
| import { StyleConstants } from '@utils/styles/constants' | ||||
| import { useTheme } from '@utils/styles/ThemeManager' | ||||
| import { uniqBy } from 'lodash' | ||||
| import React, { useCallback } from 'react' | ||||
| import { Pressable, StyleSheet, View } from 'react-native' | ||||
| import { useSelector } from 'react-redux' | ||||
| @@ -75,7 +76,6 @@ const TimelineDefault: React.FC<Props> = ({ | ||||
|         } | ||||
|       ]} | ||||
|       onPress={onPress} | ||||
|       disabled={queryKey && queryKey[1].toot !== undefined} | ||||
|     > | ||||
|       {item.reblog ? ( | ||||
|         <TimelineActioned action='reblog' account={item.account} /> | ||||
| @@ -143,11 +143,13 @@ const TimelineDefault: React.FC<Props> = ({ | ||||
|             queryKey={queryKey} | ||||
|             rootQueryKey={rootQueryKey} | ||||
|             status={actualStatus} | ||||
|             accts={([actualStatus.account] as Mastodon.Account[] & | ||||
|               Mastodon.Mention[]) | ||||
|               .concat(actualStatus.mentions) | ||||
|               .filter(d => d.id !== localAccount?.id) | ||||
|               .map(d => d.acct)} | ||||
|             accts={uniqBy( | ||||
|               ([actualStatus.account] as Mastodon.Account[] & | ||||
|                 Mastodon.Mention[]) | ||||
|                 .concat(actualStatus.mentions) | ||||
|                 .filter(d => d.id !== localAccount?.id), | ||||
|               d => d.id | ||||
|             ).map(d => d.acct)} | ||||
|             reblog={item.reblog ? true : false} | ||||
|           /> | ||||
|         </View> | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline' | ||||
| import { getLocalAccount } from '@utils/slices/instancesSlice' | ||||
| import { StyleConstants } from '@utils/styles/constants' | ||||
| import { useTheme } from '@utils/styles/ThemeManager' | ||||
| import { uniqBy } from 'lodash' | ||||
| import React, { useCallback } from 'react' | ||||
| import { Pressable, StyleSheet, View } from 'react-native' | ||||
| import { useSelector } from 'react-redux' | ||||
| @@ -126,11 +127,13 @@ const TimelineNotifications: React.FC<Props> = ({ | ||||
|           <TimelineActions | ||||
|             queryKey={queryKey} | ||||
|             status={notification.status} | ||||
|             accts={([notification.status.account] as Mastodon.Account[] & | ||||
|               Mastodon.Mention[]) | ||||
|               .concat(notification.status.mentions) | ||||
|               .filter(d => d.id !== localAccount?.id) | ||||
|               .map(d => d.acct)} | ||||
|             accts={uniqBy( | ||||
|               ([notification.status.account] as Mastodon.Account[] & | ||||
|                 Mastodon.Mention[]) | ||||
|                 .concat(notification.status.mentions) | ||||
|                 .filter(d => d.id !== localAccount?.id), | ||||
|               d => d.id | ||||
|             ).map(d => d.acct)} | ||||
|             reblog={false} | ||||
|           /> | ||||
|         </View> | ||||
|   | ||||
| @@ -1,25 +1,164 @@ | ||||
| import haptics from '@components/haptics' | ||||
| import Icon from '@components/Icon' | ||||
| import { StyleConstants } from '@utils/styles/constants' | ||||
| import { useTheme } from '@utils/styles/ThemeManager' | ||||
| import React from 'react' | ||||
| import { StyleSheet, View } from 'react-native' | ||||
| import React, { useCallback, useState } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | ||||
| import { StyleSheet, Text, View } from 'react-native' | ||||
| import { Circle } from 'react-native-animated-spinkit' | ||||
| import Animated, { | ||||
|   Extrapolate, | ||||
|   interpolate, | ||||
|   runOnJS, | ||||
|   useAnimatedReaction, | ||||
|   useAnimatedStyle, | ||||
|   useSharedValue, | ||||
|   withTiming | ||||
| } from 'react-native-reanimated' | ||||
|  | ||||
| export interface Props { | ||||
|   scrollY: Animated.SharedValue<number> | ||||
|   isLoading: boolean | ||||
|   isFetching: boolean | ||||
|   disable?: boolean | ||||
| } | ||||
|  | ||||
| const TimelineRefresh: React.FC<Props> = ({ isLoading, disable = false }) => { | ||||
|   const { theme } = useTheme() | ||||
|   return !isLoading && !disable ? ( | ||||
|     <View | ||||
|       style={styles.base} | ||||
|       children={ | ||||
|         <Circle size={StyleConstants.Font.Size.L} color={theme.secondary} /> | ||||
|       } | ||||
|     /> | ||||
|   ) : null | ||||
| } | ||||
| const CONTAINER_HEIGHT = StyleConstants.Spacing.M * 2.5 | ||||
|  | ||||
| const TimelineRefresh = React.memo( | ||||
|   ({ scrollY, isLoading, isFetching, disable = false }: Props) => { | ||||
|     if (disable || isLoading) { | ||||
|       return null | ||||
|     } | ||||
|  | ||||
|     const { t } = useTranslation('componentTimeline') | ||||
|     const { theme } = useTheme() | ||||
|  | ||||
|     const separation01 = -( | ||||
|       CONTAINER_HEIGHT / 2 + | ||||
|       StyleConstants.Font.Size.S / 2 | ||||
|     ) | ||||
|     const separation02 = -( | ||||
|       CONTAINER_HEIGHT * 1.5 + | ||||
|       StyleConstants.Font.Size.S / 2 | ||||
|     ) | ||||
|     const [textRight, setTextRight] = useState(0) | ||||
|     const arrowY = useAnimatedStyle(() => ({ | ||||
|       transform: [ | ||||
|         { | ||||
|           translateY: interpolate( | ||||
|             scrollY.value, | ||||
|             [0, separation01], | ||||
|             [ | ||||
|               -CONTAINER_HEIGHT / 2 - StyleConstants.Font.Size.M / 2, | ||||
|               CONTAINER_HEIGHT / 2 - StyleConstants.Font.Size.S / 2 | ||||
|             ], | ||||
|             Extrapolate.CLAMP | ||||
|           ) | ||||
|         } | ||||
|       ] | ||||
|     })) | ||||
|     const arrowTop = useAnimatedStyle(() => ({ | ||||
|       marginTop: | ||||
|         scrollY.value < separation02 | ||||
|           ? withTiming(CONTAINER_HEIGHT) | ||||
|           : withTiming(0) | ||||
|     })) | ||||
|  | ||||
|     const arrowStage = useSharedValue(0) | ||||
|     const onLayout = useCallback( | ||||
|       ({ nativeEvent }) => { | ||||
|         if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) { | ||||
|           setTextRight(nativeEvent.layout.x + nativeEvent.layout.width) | ||||
|         } | ||||
|       }, | ||||
|       [textRight] | ||||
|     ) | ||||
|     useAnimatedReaction( | ||||
|       () => { | ||||
|         if (isFetching) { | ||||
|           return false | ||||
|         } | ||||
|         switch (arrowStage.value) { | ||||
|           case 0: | ||||
|             if (scrollY.value < separation01) { | ||||
|               arrowStage.value = 1 | ||||
|               return true | ||||
|             } | ||||
|             return false | ||||
|           case 1: | ||||
|             if (scrollY.value < separation02) { | ||||
|               arrowStage.value = 2 | ||||
|               return true | ||||
|             } | ||||
|             if (scrollY.value > separation01) { | ||||
|               arrowStage.value = 0 | ||||
|               return false | ||||
|             } | ||||
|             return false | ||||
|           case 2: | ||||
|             if (scrollY.value > separation02) { | ||||
|               arrowStage.value = 1 | ||||
|               return false | ||||
|             } | ||||
|             return false | ||||
|         } | ||||
|       }, | ||||
|       data => { | ||||
|         if (data) { | ||||
|           runOnJS(haptics)('Light') | ||||
|         } | ||||
|       }, | ||||
|       [isFetching] | ||||
|     ) | ||||
|  | ||||
|     return ( | ||||
|       <View style={styles.base}> | ||||
|         {isFetching ? ( | ||||
|           <View style={styles.container2}> | ||||
|             <Circle size={StyleConstants.Font.Size.L} color={theme.secondary} /> | ||||
|           </View> | ||||
|         ) : ( | ||||
|           <> | ||||
|             <View style={styles.container1}> | ||||
|               <Text | ||||
|                 style={[styles.explanation, { color: theme.primary }]} | ||||
|                 onLayout={onLayout} | ||||
|                 children={t('refresh.fetchPreviousPage')} | ||||
|               /> | ||||
|               <Animated.View | ||||
|                 style={[ | ||||
|                   { | ||||
|                     position: 'absolute', | ||||
|                     left: textRight + StyleConstants.Spacing.S | ||||
|                   }, | ||||
|                   arrowY, | ||||
|                   arrowTop | ||||
|                 ]} | ||||
|                 children={ | ||||
|                   <Icon | ||||
|                     name='ArrowLeft' | ||||
|                     size={StyleConstants.Font.Size.M} | ||||
|                     color={theme.primary} | ||||
|                   /> | ||||
|                 } | ||||
|               /> | ||||
|             </View> | ||||
|             <View style={styles.container2}> | ||||
|               <Text | ||||
|                 style={[styles.explanation, { color: theme.primary }]} | ||||
|                 onLayout={onLayout} | ||||
|                 children={t('refresh.refetch')} | ||||
|               /> | ||||
|             </View> | ||||
|           </> | ||||
|         )} | ||||
|       </View> | ||||
|     ) | ||||
|   }, | ||||
|   (prev, next) => | ||||
|     prev.isLoading === next.isLoading && prev.isFetching === next.isFetching | ||||
| ) | ||||
|  | ||||
| const styles = StyleSheet.create({ | ||||
|   base: { | ||||
| @@ -27,9 +166,18 @@ const styles = StyleSheet.create({ | ||||
|     top: 0, | ||||
|     left: 0, | ||||
|     right: 0, | ||||
|     height: StyleConstants.Spacing.XL, | ||||
|     justifyContent: 'center', | ||||
|     height: CONTAINER_HEIGHT * 2, | ||||
|     alignItems: 'center' | ||||
|   }, | ||||
|   container1: { | ||||
|     flex: 1, | ||||
|     flexDirection: 'row', | ||||
|     height: CONTAINER_HEIGHT | ||||
|   }, | ||||
|   container2: { height: CONTAINER_HEIGHT, justifyContent: 'center' }, | ||||
|   explanation: { | ||||
|     fontSize: StyleConstants.Font.Size.S, | ||||
|     lineHeight: CONTAINER_HEIGHT | ||||
|   } | ||||
| }) | ||||
|  | ||||
|   | ||||
| @@ -11,10 +11,9 @@ export default { | ||||
|   end: { | ||||
|     message: 'The end, what about a cup of <0 />' | ||||
|   }, | ||||
|   header: { | ||||
|     explanation: | ||||
|       'External instance might not be known to logged in instance, thus actions are not allowed but only for reading. You can switch to any instance in the settings.', | ||||
|     button: 'Go to settings' | ||||
|   refresh: { | ||||
|     fetchPreviousPage: 'Refresh upwards', | ||||
|     refetch: 'Refresh all' | ||||
|   }, | ||||
|   shared: { | ||||
|     actioned: { | ||||
|   | ||||
| @@ -11,10 +11,9 @@ export default { | ||||
|   end: { | ||||
|     message: '居然刷到底了,喝杯 <0 /> 吧' | ||||
|   }, | ||||
|   header: { | ||||
|     explanation: | ||||
|       '围观的社区可能不属于已经登录的社区的已知连结,因此只可围观嘟文,不能进行操作。设置里可以切换想要围观的社区。', | ||||
|     button: '前往设置' | ||||
|   refresh: { | ||||
|     fetchPreviousPage: '向上刷新', | ||||
|     refetch: '完全刷新' | ||||
|   }, | ||||
|   shared: { | ||||
|     actioned: { | ||||
|   | ||||
| @@ -8736,10 +8736,10 @@ react-navigation@*, react-navigation@^4.4.3: | ||||
|     "@react-navigation/core" "^3.7.9" | ||||
|     "@react-navigation/native" "^3.8.3" | ||||
|  | ||||
| react-query@^3.8.2: | ||||
|   version "3.8.2" | ||||
|   resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.8.2.tgz#e2dac76b5d9b3465d854f4ca040a35ba4bcae1ae" | ||||
|   integrity sha512-Ha8+WZLIHOUkKhqE4+2RZZB03WvEWmNhN4WIIw3zWVCtR7blo2n2TXtu+d3YhaY1o6dt+sHT+J+zNV8IzR583w== | ||||
| react-query@^3.9.7: | ||||
|   version "3.9.7" | ||||
|   resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.9.7.tgz#324c697f418827c129c8c126d233c6052bb1e35e" | ||||
|   integrity sha512-vpQgRFOljd7Lr1wL8hOwxWzb7awLIjaeqaq6DJ1fzA8N9mK1fAkK+UVrt8WaXJGBfz7JEnfCiXuENQspk0N7Sw== | ||||
|   dependencies: | ||||
|     "@babel/runtime" "^7.5.5" | ||||
|     match-sorter "^6.0.2" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user