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