diff --git a/android/app/build.gradle b/android/app/build.gradle index 9826e70f..947ce178 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -231,6 +231,7 @@ android { proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } } + namespace 'com.xmflsct.app.tooot' // applicationVariants are e.g. debug, release applicationVariants.all { variant -> diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 85aa5430..545f156f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools" + package="com.xmflsct.app.tooot"> diff --git a/android/build.gradle b/android/build.gradle index e90d3c65..02553cd8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -22,7 +22,7 @@ buildscript { jcenter() } dependencies { - classpath("com.android.tools.build:gradle:7.2.1") + classpath('com.android.tools.build:gradle:7.3.1') classpath("com.facebook.react:react-native-gradle-plugin") classpath("de.undercouch:gradle-download-task:5.0.1") diff --git a/android/gradle.properties b/android/gradle.properties index 2f0b0ba8..717237a0 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -26,7 +26,7 @@ android.useAndroidX=true android.enableJetifier=true # Version of flipper SDK to use with React Native -FLIPPER_VERSION=0.125.0 +FLIPPER_VERSION=0.176.1 # Use this property to specify which architecture you want to build. # You can also override it from the CLI using diff --git a/fastlane/metadata/en-US/release_notes.txt b/fastlane/metadata/en-US/release_notes.txt index 87d4f712..5bc2a4ea 100644 --- a/fastlane/metadata/en-US/release_notes.txt +++ b/fastlane/metadata/en-US/release_notes.txt @@ -1,5 +1,6 @@ Enjoy toooting! This version includes following improvements and fixes: - Auto fetch remote content in conversations! +- Remember last read position in timeline! - Allowing adding more context of reports - Option to disable autoplay gif - Hide boosts from users diff --git a/fastlane/metadata/zh-Hans/release_notes.txt b/fastlane/metadata/zh-Hans/release_notes.txt index 41ef5bc6..bb70b250 100644 --- a/fastlane/metadata/zh-Hans/release_notes.txt +++ b/fastlane/metadata/zh-Hans/release_notes.txt @@ -1,5 +1,6 @@ toooting愉快!此版本包括以下改进和修复: - 主动获取对话的远程内容 +- 自动加载上次我的关注的阅读位置 - 可添加举报细节 - 新增暂停自动播放gif动画选项 - 隐藏用户的转嘟 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2ccb5d97..6c4225a0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -16,7 +16,7 @@ PODS: - ExpoModulesCore - EXNotifications (0.17.0): - ExpoModulesCore - - Expo (47.0.11): + - Expo (47.0.12): - ExpoModulesCore - ExpoCrypto (12.1.0): - ExpoModulesCore @@ -26,7 +26,7 @@ PODS: - ExpoModulesCore - ExpoLocalization (14.0.0): - ExpoModulesCore - - ExpoModulesCore (1.1.0): + - ExpoModulesCore (1.1.1): - React-Core - ReactCommon/turbomodule/core - ExpoRandom (13.0.0): @@ -39,6 +39,9 @@ PODS: - ExpoModulesCore - EXScreenCapture (5.0.0): - ExpoModulesCore + - EXScreenOrientation (5.0.1): + - ExpoModulesCore + - React-Core - EXSecureStore (12.0.0): - ExpoModulesCore - EXSplashScreen (0.17.5): @@ -298,7 +301,7 @@ PODS: - React-Core - react-native-blurhash (1.1.10): - React-Core - - react-native-cameraroll (5.2.0): + - react-native-cameraroll (5.2.1): - React-Core - react-native-image-picker (4.10.3): - React-Core @@ -470,6 +473,7 @@ DEPENDENCIES: - ExpoVideoThumbnails (from `../node_modules/expo-video-thumbnails/ios`) - ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`) - EXScreenCapture (from `../node_modules/expo-screen-capture/ios`) + - EXScreenOrientation (from `../node_modules/expo-screen-orientation/ios`) - EXSecureStore (from `../node_modules/expo-secure-store/ios`) - EXSplashScreen (from `../node_modules/expo-splash-screen/ios`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) @@ -582,6 +586,8 @@ EXTERNAL SOURCES: :path: "../node_modules/expo-web-browser/ios" EXScreenCapture: :path: "../node_modules/expo-screen-capture/ios" + EXScreenOrientation: + :path: "../node_modules/expo-screen-orientation/ios" EXSecureStore: :path: "../node_modules/expo-secure-store/ios" EXSplashScreen: @@ -705,17 +711,18 @@ SPEC CHECKSUMS: EXFileSystem: 60602b6eefa6873f97172c684b7537c9760b50d6 EXFont: 319606bfe48c33b5b5063fb0994afdc496befe80 EXNotifications: babce2a87b7922051354fcfe7a74dd279b7e272a - Expo: dedd83acfd4d70cbec6ac2f1b4433462d95c70bc + Expo: f48d305fda3e4e501d686e6bad7d8c8373828279 ExpoCrypto: 6eb2a5ede7d95b7359a5f0391ee0c5d2ecd144b3 ExpoHaptics: 129d3f8d44c2205adcdf8db760602818463d5437 ExpoKeepAwake: 69b59d0a8d2b24de9f82759c39b3821fec030318 ExpoLocalization: e202d1e2a4950df17ac8d0889d65a1ffd7532d7e - ExpoModulesCore: 089e1ac0f0edee4dd0af0eb4e3f7b44d72cc418d + ExpoModulesCore: 485dff3a59b036a33b6050c0a5aea3cf1037fdd1 ExpoRandom: 58b7e0a5fe1adf1cb6dc1cbe503a6fe9524f36ce ExpoStoreReview: 713336ff504db3a6983475bf7c67519cc5efc86f ExpoVideoThumbnails: 424db02cedfbbe2d498bcb2712ea4ba8a9dcb453 ExpoWebBrowser: 073e50f16669d498fb49063b9b7fe780b24f7fda EXScreenCapture: d9f1ec31042dfef109290d06c2b4789b7444d16d + EXScreenOrientation: 07e5aeff07bce09a2b214981e612d87fd7719997 EXSecureStore: daec0117c922a67c658cb229152a9e252e5c1750 EXSplashScreen: 3e989924f61a8dd07ee4ea584c6ba14be9b51949 FBLazyVector: 48289402952f4f7a4e235de70a9a590aa0b79ef4 @@ -744,7 +751,7 @@ SPEC CHECKSUMS: React-logger: 1623c216abaa88974afce404dc8f479406bbc3a0 react-native-blur: 50c9feabacbc5f49b61337ebc32192c6be7ec3c3 react-native-blurhash: add4df9a937b4e021a24bc67a0714f13e0bd40b7 - react-native-cameraroll: 0ff04cc4e0ff5f19a94ff4313e5c8bc4503cd86d + react-native-cameraroll: f94bf9f46c998963ecd2bb6e9a3f9cca59b6d9f1 react-native-image-picker: 60f4246eb5bb7187fc15638a8c1f13abd3820695 react-native-ios-context-menu: b170594b4448c0cd10c79e13432216bac99de1ac react-native-language-detection: f414937fa715108ab50a6269a3de0bcb95e4ceb0 diff --git a/package.json b/package.json index 7dea8757..144490ed 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@mattermost/react-native-paste-input": "^0.5.2", "@neverdull-agency/expo-unlimited-secure-store": "^1.0.10", "@react-native-async-storage/async-storage": "~1.17.11", - "@react-native-camera-roll/camera-roll": "^5.2.0", + "@react-native-camera-roll/camera-roll": "^5.2.1", "@react-native-clipboard/clipboard": "^1.11.1", "@react-native-community/blur": "^4.3.0", "@react-native-community/netinfo": "9.3.7", @@ -42,7 +42,7 @@ "@tanstack/react-query": "^4.20.9", "axios": "^1.2.2", "diff": "^5.1.0", - "expo": "^47.0.11", + "expo": "^47.0.12", "expo-auth-session": "^3.8.0", "expo-av": "^13.1.0", "expo-constants": "^14.1.0", @@ -54,6 +54,7 @@ "expo-notifications": "^0.17.0", "expo-random": "^13.0.0", "expo-screen-capture": "^5.0.0", + "expo-screen-orientation": "^5.0.1", "expo-secure-store": "^12.0.0", "expo-splash-screen": "^0.17.5", "expo-store-review": "^6.1.0", @@ -65,7 +66,7 @@ "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-i18next": "^12.1.1", + "react-i18next": "^12.1.4", "react-intl": "^6.2.5", "react-native": "^0.70.6", "react-native-animated-spinkit": "^1.5.2", diff --git a/src/components/Parse/HTML.tsx b/src/components/Parse/HTML.tsx index a9e213af..8b3ed892 100644 --- a/src/components/Parse/HTML.tsx +++ b/src/components/Parse/HTML.tsx @@ -49,10 +49,10 @@ const ParseHTML: React.FC = ({ StyleConstants.Font.Size[size], adaptiveSize ? adaptiveFontsize : 0 ) - const adaptedLineheight = adaptiveScale( - StyleConstants.Font.LineHeight[size], - adaptiveSize ? adaptiveFontsize : 0 - ) + const adaptedLineheight = + Platform.OS === 'ios' + ? adaptiveScale(StyleConstants.Font.LineHeight[size], adaptiveSize ? adaptiveFontsize : 0) + : undefined const navigation = useNavigation>() const { params } = useRoute() @@ -119,7 +119,11 @@ const ParseHTML: React.FC = ({ const href = node.attribs.href if (classes) { if (classes.includes('hashtag')) { - const tag = href.match(new RegExp(/\/tags?\/(.*)/, 'i'))?.[1].toLowerCase() + const children = node.children.map(unwrapNode).join('') + const tag = + href.match(new RegExp(/\/tags?\/(.*)/, 'i'))?.[1]?.toLowerCase() || + children.match(new RegExp(/#(\S+)/))?.[1]?.toLowerCase() + const paramsHashtag = (params as { hashtag: Mastodon.Tag['name'] } | undefined) ?.hashtag const sameHashtag = paramsHashtag === tag @@ -143,7 +147,7 @@ const ParseHTML: React.FC = ({ !sameHashtag && navigation.push('Tab-Shared-Hashtag', { hashtag: tag }) } - children={node.children.map(unwrapNode).join('')} + children={children} /> ) } @@ -199,7 +203,10 @@ const ParseHTML: React.FC = ({ break case 'br': return ( - + {'\n'} ) @@ -208,7 +215,11 @@ const ParseHTML: React.FC = ({ return ( {node.children.map((c, i) => renderNode(c, i))} - {'\n\n'} + + {'\n\n'} + ) } else { diff --git a/src/components/Timeline/Footer.tsx b/src/components/Timeline/Footer.tsx index c37fc27b..391c498f 100644 --- a/src/components/Timeline/Footer.tsx +++ b/src/components/Timeline/Footer.tsx @@ -16,16 +16,7 @@ export interface Props { const TimelineFooter: React.FC = ({ queryKey, disableInfinity }) => { const { hasNextPage } = useTimelineQuery({ ...queryKey[1], - options: { - enabled: !disableInfinity, - notifyOnChangeProps: ['hasNextPage'], - getNextPageParam: lastPage => - lastPage?.links?.next && { - ...(lastPage.links.next.isOffset - ? { offset: lastPage.links.next.id } - : { max_id: lastPage.links.next.id }) - } - } + options: { enabled: !disableInfinity, notifyOnChangeProps: ['hasNextPage'] } }) const { colors } = useTheme() diff --git a/src/components/Timeline/Refresh.tsx b/src/components/Timeline/Refresh.tsx index 5fda1626..c08a7a36 100644 --- a/src/components/Timeline/Refresh.tsx +++ b/src/components/Timeline/Refresh.tsx @@ -7,18 +7,19 @@ import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline' +import { setAccountStorage } from '@utils/storage/actions' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' -import React, { RefObject, useEffect, useRef, useState } from 'react' +import React, { RefObject, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { FlatList, Platform, Text, View } from 'react-native' -import { Circle } from 'react-native-animated-spinkit' import Animated, { Extrapolate, interpolate, runOnJS, useAnimatedReaction, useAnimatedStyle, + useDerivedValue, useSharedValue, withTiming } from 'react-native-reanimated' @@ -26,21 +27,25 @@ import Animated, { export interface Props { flRef: RefObject> queryKey: QueryKeyTimeline + fetchingActive: React.MutableRefObject scrollY: Animated.SharedValue fetchingType: Animated.SharedValue<0 | 1 | 2> disableRefresh?: boolean + readMarker?: 'read_marker_following' } -const CONTAINER_HEIGHT = StyleConstants.Spacing.M * 2 +const CONTAINER_HEIGHT = StyleConstants.Spacing.M * 2.5 export const SEPARATION_Y_1 = -(CONTAINER_HEIGHT / 2 + StyleConstants.Font.Size.S / 2) export const SEPARATION_Y_2 = -(CONTAINER_HEIGHT * 1.5 + StyleConstants.Font.Size.S / 2) const TimelineRefresh: React.FC = ({ flRef, queryKey, + fetchingActive, scrollY, fetchingType, - disableRefresh = false + disableRefresh = false, + readMarker }) => { if (Platform.OS !== 'ios') { return null @@ -55,7 +60,15 @@ const TimelineRefresh: React.FC = ({ const prevStatusId = useRef() const queryClient = useQueryClient() - const { refetch, isFetching } = useTimelineQuery({ ...queryKey[1] }) + const { refetch, isRefetching } = useTimelineQuery({ ...queryKey[1] }) + + useDerivedValue(() => { + if (prevActive.current || isRefetching) { + fetchingActive.current = true + } else { + fetchingActive.current = false + } + }, [prevActive.current, isRefetching]) const { t } = useTranslation('componentTimeline') const { colors } = useTheme() @@ -83,7 +96,7 @@ const TimelineRefresh: React.FC = ({ const arrowStage = useSharedValue(0) useAnimatedReaction( () => { - if (isFetching) { + if (fetchingActive.current) { return false } switch (arrowStage.value) { @@ -116,9 +129,10 @@ const TimelineRefresh: React.FC = ({ runOnJS(haptics)('Light') } }, - [isFetching] + [fetchingActive.current] ) + const fetchAndScrolled = useSharedValue(false) const runFetchPrevious = async () => { if (prevActive.current) return @@ -134,29 +148,12 @@ const TimelineRefresh: React.FC = ({ await queryFunctionTimeline({ queryKey, - pageParam: firstPage?.links?.prev && { - ...(firstPage.links.prev.isOffset - ? { offset: firstPage.links.prev.id } - : { min_id: firstPage.links.prev.id }) - }, + pageParam: firstPage?.links?.prev, meta: {} - }).then(res => { - queryClient.setQueryData< - InfiniteData< - PagedResponse<(Mastodon.Status | Mastodon.Notification | Mastodon.Conversation)[]> - > - >(queryKey, old => { - if (!old) return old - - prevCache.current = res.body.slice(0, -PREV_PER_BATCH) - return { ...old, pages: [{ ...res, body: res.body.slice(-PREV_PER_BATCH) }, ...old.pages] } - }) }) - } - useEffect(() => { - const loop = async () => { - for await (const _ of Array(Math.ceil((prevCache.current?.length || 0) / PREV_PER_BATCH))) { - await new Promise(promise => setTimeout(promise, 32)) + .then(res => { + if (!res.body.length) return + queryClient.setQueryData< InfiniteData< PagedResponse<(Mastodon.Status | Mastodon.Notification | Mastodon.Conversation)[]> @@ -164,33 +161,62 @@ const TimelineRefresh: React.FC = ({ >(queryKey, old => { if (!old) return old + prevCache.current = res.body.slice(0, -PREV_PER_BATCH) return { ...old, - pages: old.pages.map((page, index) => { - if (index === 0) { - const insert = prevCache.current?.slice(-PREV_PER_BATCH) - prevCache.current = prevCache.current?.slice(0, -PREV_PER_BATCH) - if (insert) { - return { ...page, body: [...insert, ...page.body] } - } else { - return page - } - } else { - return page - } - }) + pages: [{ ...res, body: res.body.slice(-PREV_PER_BATCH) }, ...old.pages] } }) - break - } - prevActive.current = false - } - loop() - }, [prevCache.current]) + return res.body.length - PREV_PER_BATCH + }) + .then(async nextLength => { + if (!nextLength) { + prevActive.current = false + return + } + + for (let [index] of Array(Math.ceil(nextLength / PREV_PER_BATCH)).entries()) { + if (!fetchAndScrolled.value && index < 3 && scrollY.value > 15) { + fetchAndScrolled.value = true + flRef.current?.scrollToOffset({ offset: scrollY.value - 15, animated: true }) + } + + await new Promise(promise => setTimeout(promise, 32)) + queryClient.setQueryData< + InfiniteData< + PagedResponse<(Mastodon.Status | Mastodon.Notification | Mastodon.Conversation)[]> + > + >(queryKey, old => { + if (!old) return old + + return { + ...old, + pages: old.pages.map((page, index) => { + if (index === 0) { + const insert = prevCache.current?.slice(-PREV_PER_BATCH) + prevCache.current = prevCache.current?.slice(0, -PREV_PER_BATCH) + if (insert) { + return { ...page, body: [...insert, ...page.body] } + } else { + return page + } + } else { + return page + } + }) + } + }) + } + prevActive.current = false + }) + } const runFetchLatest = async () => { queryClient.invalidateQueries(queryKey) + if (readMarker) { + setAccountStorage([{ key: readMarker, value: undefined }]) + } await refetch() setTimeout(() => flRef.current?.scrollToOffset({ offset: 0 }), 50) } @@ -224,61 +250,53 @@ const TimelineRefresh: React.FC = ({ alignItems: 'center' }} > - {prevActive.current || isFetching ? ( - - - - ) : ( - <> - - { - if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) { - setTextRight(nativeEvent.layout.x + nativeEvent.layout.width) - } - }} - children={t('refresh.fetchPreviousPage')} + + { + if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) { + setTextRight(nativeEvent.layout.x + nativeEvent.layout.width) + } + }} + children={t('refresh.fetchPreviousPage')} + /> + - - } - /> - - - { - if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) { - setTextRight(nativeEvent.layout.x + nativeEvent.layout.width) - } - }} - children={t('refresh.refetch')} - /> - - - )} + } + /> + + + { + if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) { + setTextRight(nativeEvent.layout.x + nativeEvent.layout.width) + } + }} + children={t('refresh.refetch')} + /> + ) } diff --git a/src/components/Timeline/Shared/Attachment/Video.tsx b/src/components/Timeline/Shared/Attachment/Video.tsx index 6f4a86ad..cd8ac046 100644 --- a/src/components/Timeline/Shared/Attachment/Video.tsx +++ b/src/components/Timeline/Shared/Attachment/Video.tsx @@ -4,6 +4,7 @@ import { useGlobalStorage } from '@utils/storage/actions' import { StyleConstants } from '@utils/styles/constants' import { ResizeMode, Video, VideoFullscreenUpdate } from 'expo-av' import { Platform } from 'expo-modules-core' +import * as ScreenOrientation from 'expo-screen-orientation' import React, { useRef, useState } from 'react' import { Pressable, View } from 'react-native' import { Blurhash } from 'react-native-blurhash' @@ -72,14 +73,21 @@ const AttachmentVideo: React.FC = ({ posterStyle: { resizeMode: ResizeMode.COVER } })} useNativeControls={false} - onFullscreenUpdate={event => { - if (event.fullscreenUpdate === VideoFullscreenUpdate.PLAYER_DID_DISMISS) { - Platform.OS === 'android' && setVideoResizeMode(ResizeMode.COVER) - if (gifv && !reduceMotionEnabled && autoplayGifv) { - videoPlayer.current?.playAsync() - } else { - videoPlayer.current?.pauseAsync() - } + onFullscreenUpdate={async ({ fullscreenUpdate }) => { + switch (fullscreenUpdate) { + case VideoFullscreenUpdate.PLAYER_DID_PRESENT: + Platform.OS === 'android' && (await ScreenOrientation.unlockAsync()) + break + case VideoFullscreenUpdate.PLAYER_WILL_DISMISS: + Platform.OS === 'android' && + (await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT)) + Platform.OS === 'android' && setVideoResizeMode(ResizeMode.COVER) + if (gifv && !reduceMotionEnabled && autoplayGifv) { + videoPlayer.current?.playAsync() + } else { + videoPlayer.current?.pauseAsync() + } + break } }} onPlaybackStatusUpdate={event => { diff --git a/src/components/Timeline/index.tsx b/src/components/Timeline/index.tsx index 7e4f1c92..b0af184e 100644 --- a/src/components/Timeline/index.tsx +++ b/src/components/Timeline/index.tsx @@ -1,9 +1,14 @@ import ComponentSeparator from '@components/Separator' +import TimelineDefault from '@components/Timeline/Default' import { useScrollToTop } from '@react-navigation/native' import { UseInfiniteQueryOptions } from '@tanstack/react-query' import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline' import { flattenPages } from '@utils/queryHooks/utils' -import { useGlobalStorageListener } from '@utils/storage/actions' +import { + getAccountStorage, + setAccountStorage, + useGlobalStorageListener +} from '@utils/storage/actions' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React, { RefObject, useRef } from 'react' @@ -13,7 +18,7 @@ import TimelineEmpty from './Empty' import TimelineFooter from './Footer' import TimelineRefresh, { SEPARATION_Y_1, SEPARATION_Y_2 } from './Refresh' -const AnimatedFlatList = Animated.createAnimatedComponent(FlatList) +const AnimatedFlatList = Animated.createAnimatedComponent(FlatList) export interface Props { flRef?: RefObject> @@ -24,7 +29,8 @@ export interface Props { > disableRefresh?: boolean disableInfinity?: boolean - customProps: Partial> & Pick, 'renderItem'> + readMarker?: 'read_marker_following' + customProps?: Partial> } const Timeline: React.FC = ({ @@ -33,6 +39,7 @@ const Timeline: React.FC = ({ queryOptions, disableRefresh = false, disableInfinity = false, + readMarker = undefined, customProps }) => { const { colors } = useTheme() @@ -45,17 +52,12 @@ const Timeline: React.FC = ({ notifyOnChangeProps: Platform.select({ ios: ['dataUpdatedAt', 'isFetching'], android: ['dataUpdatedAt', 'isFetching', 'isLoading'] - }), - getNextPageParam: lastPage => - lastPage?.links?.next && { - ...(lastPage.links.next.isOffset - ? { offset: lastPage.links.next.id } - : { max_id: lastPage.links.next.id }) - } + }) } }) const flRef = useRef(null) + const fetchingActive = useRef(false) const scrollY = useSharedValue(0) const fetchingType = useSharedValue<0 | 1 | 2>(0) @@ -78,6 +80,32 @@ const Timeline: React.FC = ({ [isFetching] ) + const viewabilityConfigCallbackPairs = useRef< + Pick, 'viewabilityConfigCallbackPairs'>['viewabilityConfigCallbackPairs'] + >( + readMarker + ? [ + { + viewabilityConfig: { + minimumViewTime: 300, + itemVisiblePercentThreshold: 80, + waitForInteraction: true + }, + onViewableItemsChanged: ({ viewableItems }) => { + const marker = readMarker ? getAccountStorage.string(readMarker) : undefined + + const firstItemId = viewableItems.filter(item => item.isViewable)[0]?.item.id + if (!fetchingActive.current && firstItemId && firstItemId > (marker || '0')) { + setAccountStorage([{ key: readMarker, value: firstItemId }]) + } else { + // setAccountStorage([{ key: readMarker, value: '109519141378761752' }]) + } + } + } + ] + : undefined + ) + const androidRefreshControl = Platform.select({ android: { refreshControl: ( @@ -86,7 +114,12 @@ const Timeline: React.FC = ({ colors={[colors.primaryDefault]} progressBackgroundColor={colors.backgroundDefault} refreshing={isFetching || isLoading} - onRefresh={() => refetch()} + onRefresh={() => { + if (readMarker) { + setAccountStorage([{ key: readMarker, value: undefined }]) + } + refetch() + }} /> ) } @@ -102,9 +135,11 @@ const Timeline: React.FC = ({ = ({ onScroll={onScroll} windowSize={7} data={flattenPages(data)} + {...(customProps?.renderItem + ? { renderItem: customProps.renderItem } + : { renderItem: ({ item }) => })} initialNumToRender={6} maxToRenderPerBatch={3} onEndReached={() => !disableInfinity && !isFetchingNextPage && fetchNextPage()} @@ -129,6 +167,7 @@ const Timeline: React.FC = ({ /> ) } + viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current} {...(!isLoading && { maintainVisibleContentPosition: { minIndexForVisible: 0 diff --git a/src/screens/Tabs/Local/Root.tsx b/src/screens/Tabs/Local/Root.tsx index 57d65863..465ec22d 100644 --- a/src/screens/Tabs/Local/Root.tsx +++ b/src/screens/Tabs/Local/Root.tsx @@ -2,7 +2,6 @@ import { HeaderRight } from '@components/Header' import Icon from '@components/Icon' import CustomText from '@components/Text' import Timeline from '@components/Timeline' -import TimelineDefault from '@components/Timeline/Default' import { NativeStackScreenProps } from '@react-navigation/native-stack' import { TabLocalStackParamList } from '@utils/navigation/navigators' import { useListsQuery } from '@utils/queryHooks/lists' @@ -178,14 +177,7 @@ const Root: React.FC - }} - /> - ) + return } export default Root diff --git a/src/screens/Tabs/Me/Bookmarks.tsx b/src/screens/Tabs/Me/Bookmarks.tsx index 808dc3e1..83ffae0d 100644 --- a/src/screens/Tabs/Me/Bookmarks.tsx +++ b/src/screens/Tabs/Me/Bookmarks.tsx @@ -1,5 +1,4 @@ import Timeline from '@components/Timeline' -import TimelineDefault from '@components/Timeline/Default' import { NativeStackScreenProps } from '@react-navigation/native-stack' import { TabMeStackParamList } from '@utils/navigation/navigators' import { QueryKeyTimeline } from '@utils/queryHooks/timeline' @@ -13,14 +12,7 @@ const TabMeBookmarks: React.FC - }} - /> - ) + return } export default TabMeBookmarks diff --git a/src/screens/Tabs/Me/Favourites.tsx b/src/screens/Tabs/Me/Favourites.tsx index fe9db5ae..a183751e 100644 --- a/src/screens/Tabs/Me/Favourites.tsx +++ b/src/screens/Tabs/Me/Favourites.tsx @@ -1,5 +1,4 @@ import Timeline from '@components/Timeline' -import TimelineDefault from '@components/Timeline/Default' import { NativeStackScreenProps } from '@react-navigation/native-stack' import { TabMeStackParamList } from '@utils/navigation/navigators' import { QueryKeyTimeline } from '@utils/queryHooks/timeline' @@ -13,14 +12,7 @@ const TabMeFavourites: React.FC< navigation.setParams({ queryKey: queryKey }) }, []) - return ( - - }} - /> - ) + return } export default TabMeFavourites diff --git a/src/screens/Tabs/Me/List/Accounts.tsx b/src/screens/Tabs/Me/List/Accounts.tsx index 0ee1d0ef..a49624d4 100644 --- a/src/screens/Tabs/Me/List/Accounts.tsx +++ b/src/screens/Tabs/Me/List/Accounts.tsx @@ -23,17 +23,7 @@ const TabMeListAccounts: React.FC> const { t } = useTranslation(['common', 'screenTabs']) const queryKey: QueryKeyListAccounts = ['ListAccounts', { id: params.id }] - const { data, refetch, fetchNextPage, hasNextPage } = useListAccountsQuery({ - ...queryKey[1], - options: { - getNextPageParam: lastPage => - lastPage?.links?.next && { - ...(lastPage.links.next.isOffset - ? { offset: lastPage.links.next.id } - : { max_id: lastPage.links.next.id }) - } - } - }) + const { data, refetch, fetchNextPage, hasNextPage } = useListAccountsQuery({ ...queryKey[1] }) const mutation = useListAccountsMutation({ onSuccess: () => { diff --git a/src/screens/Tabs/Me/List/index.tsx b/src/screens/Tabs/Me/List/index.tsx index 5fe3cd7b..71b59b4b 100644 --- a/src/screens/Tabs/Me/List/index.tsx +++ b/src/screens/Tabs/Me/List/index.tsx @@ -1,7 +1,6 @@ import Icon from '@components/Icon' import { displayMessage } from '@components/Message' import Timeline from '@components/Timeline' -import TimelineDefault from '@components/Timeline/Default' import { NativeStackScreenProps } from '@react-navigation/native-stack' import { useQueryClient } from '@tanstack/react-query' import { TabMeStackParamList } from '@utils/navigation/navigators' @@ -74,14 +73,7 @@ const TabMeList: React.FC - }} - /> - ) + return } export default TabMeList diff --git a/src/screens/Tabs/Public/Root.tsx b/src/screens/Tabs/Public/Root.tsx index bdfc410a..ef5054ec 100644 --- a/src/screens/Tabs/Public/Root.tsx +++ b/src/screens/Tabs/Public/Root.tsx @@ -1,6 +1,5 @@ import { HeaderRight } from '@components/Header' import Timeline from '@components/Timeline' -import TimelineDefault from '@components/Timeline/Default' import SegmentedControl from '@react-native-community/segmented-control' import { useNavigation } from '@react-navigation/native' import { NativeStackNavigationProp, NativeStackScreenProps } from '@react-navigation/native-stack' @@ -21,15 +20,7 @@ const Route = ({ route: { key: page } }: { route: any }) => { useEffect(() => { navigation.setParams({ queryKey }) }, []) - return ( - - }} - /> - ) + return } const renderScene = SceneMap({ diff --git a/src/screens/Tabs/Shared/Attachments.tsx b/src/screens/Tabs/Shared/Attachments.tsx index 11f420fe..d6f5cdd5 100644 --- a/src/screens/Tabs/Shared/Attachments.tsx +++ b/src/screens/Tabs/Shared/Attachments.tsx @@ -2,7 +2,6 @@ import { HeaderLeft } from '@components/Header' import { ParseEmojis } from '@components/Parse' import CustomText from '@components/Text' import Timeline from '@components/Timeline' -import TimelineDefault from '@components/Timeline/Default' import { TabSharedStackScreenProps } from '@utils/navigation/navigators' import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { useTheme } from '@utils/styles/ThemeManager' @@ -49,14 +48,7 @@ const TabSharedAttachments: React.FC - }} - /> - ) + return } export default TabSharedAttachments diff --git a/src/screens/Tabs/Shared/Hashtag.tsx b/src/screens/Tabs/Shared/Hashtag.tsx index 542fe67b..149459f0 100644 --- a/src/screens/Tabs/Shared/Hashtag.tsx +++ b/src/screens/Tabs/Shared/Hashtag.tsx @@ -2,7 +2,6 @@ import haptics from '@components/haptics' import { HeaderLeft, HeaderRight } from '@components/Header' import { displayMessage } from '@components/Message' import Timeline from '@components/Timeline' -import TimelineDefault from '@components/Timeline/Default' import { useQueryClient } from '@tanstack/react-query' import { featureCheck } from '@utils/helpers/featureCheck' import { TabSharedStackScreenProps } from '@utils/navigation/navigators' @@ -82,14 +81,7 @@ const TabSharedHashtag: React.FC }) }, [canFollowTags, data?.following, isFetching]) - return ( - - }} - /> - ) + return } export default TabSharedHashtag diff --git a/src/screens/Tabs/Shared/Toot.tsx b/src/screens/Tabs/Shared/Toot.tsx index 8364db05..5f078717 100644 --- a/src/screens/Tabs/Shared/Toot.tsx +++ b/src/screens/Tabs/Shared/Toot.tsx @@ -74,7 +74,7 @@ const TabSharedToot: React.FC> = ({ const match = urlMatcher(toot.url || toot.uri) const highlightIndex = useRef(0) - const query = useQuery<{ pages: { body: (Mastodon.Status & { key?: 'cached' })[] }[] }>( + const query = useQuery<{ pages: { body: Mastodon.Status[] }[] }>( queryKey.local, async () => { const context = await apiInstance<{ @@ -94,11 +94,13 @@ const TabSharedToot: React.FC> = ({ { body: statuses.map((status, index) => { if (index < highlightIndex.current || status.id === toot.id) { - return { ...status, _level: 0 } + status._level = 0 + return status } else { const repliedLevel: number = statuses.find(s => s.id === status.in_reply_to_id)?._level || 0 - return { ...status, _level: repliedLevel + 1 } + status._level = repliedLevel + 1 + return status } }) } @@ -106,7 +108,7 @@ const TabSharedToot: React.FC> = ({ } }, { - initialData: { pages: [{ body: [{ ...toot, _level: 0, key: 'cached' }] }] }, + placeholderData: { pages: [{ body: [toot] }] }, enabled: !toot._remote, staleTime: 0, refetchOnMount: true, @@ -169,11 +171,13 @@ const TabSharedToot: React.FC> = ({ return statuses.map((status, index) => { if (index < highlightIndex.current || status.id === toot.id) { - return { ...status, _level: 0 } + status._level = 0 + return status } const repliedLevel: number = statuses.find(s => s.id === status.in_reply_to_id)?._level || 0 - return { ...status, _level: repliedLevel + 1 } + status._level = repliedLevel + 1 + return status }) }, { @@ -184,12 +188,12 @@ const TabSharedToot: React.FC> = ({ staleTime: 0, refetchOnMount: true, onSuccess: data => { - if (query.data.pages[0].body.length < 1 && data.length < 1) { + if ((query.data?.pages[0].body.length || 0) < 1 && data.length < 1) { navigation.goBack() return } - if (query.data.pages[0].body.length < data.length) { + if ((query.data?.pages[0].body.length || 0) < data.length) { queryClient.cancelQueries(queryKey.local) queryClient.setQueryData<{ pages: { body: Mastodon.Status[] }[] @@ -201,12 +205,11 @@ const TabSharedToot: React.FC> = ({ pages: [ { body: data.map(remote => { - const localMatch = query.data.pages[0].body.find( + const localMatch = query.data?.pages[0].body.find( local => local.uri === remote.uri ) if (localMatch) { - delete localMatch.key - return localMatch + return { ...localMatch, _level: remote._level } } else { return { ...remote, @@ -265,9 +268,9 @@ const TabSharedToot: React.FC> = ({ windowSize={7} data={query.data?.pages?.[0].body} renderItem={({ item, index }) => { - const prev = query.data.pages[0].body[index - 1]?._level || 0 + const prev = query.data?.pages[0].body[index - 1]?._level || 0 const curr = item._level - const next = query.data.pages[0].body[index + 1]?._level || 0 + const next = query.data?.pages[0].body[index + 1]?._level || 0 return ( > = ({ }) : null} {/* > = ({ style={{ position: 'absolute', top: 20, left: 4, color: colors.yellow }} /> */} @@ -421,7 +424,7 @@ const TabSharedToot: React.FC> = ({ const offset = error.averageItemLength * error.index flRef.current?.scrollToOffset({ offset }) try { - error.index < query.data.pages[0].body.length && + error.index < (query.data?.pages[0].body.length || 0) && setTimeout( () => flRef.current?.scrollToIndex({ diff --git a/src/screens/Tabs/Shared/Users.tsx b/src/screens/Tabs/Shared/Users.tsx index 7f6a118e..e161b1f8 100644 --- a/src/screens/Tabs/Shared/Users.tsx +++ b/src/screens/Tabs/Shared/Users.tsx @@ -33,12 +33,7 @@ const TabSharedUsers: React.FC> = const queryKey: QueryKeyUsers = ['Users', params] const { data, isFetching, hasNextPage, fetchNextPage, isFetchingNextPage } = useUsersQuery({ - ...queryKey[1], - options: { - getPreviousPageParam: firstPage => - firstPage.links?.prev?.id && { min_id: firstPage.links.prev.id }, - getNextPageParam: lastPage => lastPage.links?.next?.id && { max_id: lastPage.links.next.id } - } + ...queryKey[1] }) const [isSearching, setIsSearching] = useState(false) diff --git a/src/utils/api/general.ts b/src/utils/api/general.ts index d8433546..f2c929d6 100644 --- a/src/utils/api/general.ts +++ b/src/utils/api/general.ts @@ -1,5 +1,5 @@ import axios from 'axios' -import { ctx, handleError, PagedResponse, userAgent } from './helpers' +import { ctx, handleError, PagedResponse, parseHeaderLinks, userAgent } from './helpers' export type Params = { method: 'get' | 'post' | 'put' | 'delete' @@ -49,29 +49,7 @@ const apiGeneral = async ({ ? (body as (FormData & { _parts: [][] }) | undefined)?._parts?.length : Object.keys(body).length) && { data: body }) }) - .then(response => { - let links: { - prev?: { id: string; isOffset: boolean } - next?: { id: string; isOffset: boolean } - } = {} - - if (response.headers?.link) { - const linksParsed = response.headers.link.matchAll( - new RegExp('[?&](.*?_id|offset)=(.*?)>; *rel="(.*?)"', 'gi') - ) - for (const link of linksParsed) { - switch (link[3]) { - case 'prev': - links.prev = { id: link[2], isOffset: link[1].includes('offset') } - break - case 'next': - links.next = { id: link[2], isOffset: link[1].includes('offset') } - break - } - } - } - return Promise.resolve({ body: response.data, links }) - }) + .then(response => ({ body: response.data, links: parseHeaderLinks(response.headers.link) })) .catch(handleError()) } diff --git a/src/utils/api/helpers/index.ts b/src/utils/api/helpers/index.ts index babf3ea5..bb160942 100644 --- a/src/utils/api/helpers/index.ts +++ b/src/utils/api/helpers/index.ts @@ -2,6 +2,7 @@ import * as Sentry from '@sentry/react-native' import chalk from 'chalk' import Constants from 'expo-constants' import { Platform } from 'react-native' +import parse from 'url-parse' const userAgent = { 'User-Agent': `tooot/${Constants.expoConfig?.version} ${Platform.OS}/${Platform.Version}` @@ -64,10 +65,42 @@ const handleError = } } +export const parseHeaderLinks = (headerLink?: string): PagedResponse['links'] => { + if (!headerLink) return undefined + + const links: PagedResponse['links'] = {} + + const linkParsed = [...headerLink.matchAll(/<(\S+?)>; *rel="(next|prev)"/gi)] + for (const link of linkParsed) { + const queries = parse(link[1], true).query + const isOffset = !!queries.offset?.length + + switch (link[2]) { + case 'prev': + const prevId = isOffset ? queries.offset : queries.min_id + if (prevId) links.prev = isOffset ? { offset: prevId } : { min_id: prevId } + break + case 'next': + const nextId = isOffset ? queries.offset : queries.max_id + if (nextId) links.next = isOffset ? { offset: nextId } : { max_id: nextId } + break + } + } + + if (links.prev || links.next) { + return links + } else { + return undefined + } +} + type LinkFormat = { id: string; isOffset: boolean } export type PagedResponse = { body: T - links?: { prev?: LinkFormat; next?: LinkFormat } + links?: { + prev?: { min_id: string } | { offset: string } + next?: { max_id: string } | { offset: string } + } } export { ctx, handleError, userAgent } diff --git a/src/utils/api/instance.ts b/src/utils/api/instance.ts index 810df489..b31977dd 100644 --- a/src/utils/api/instance.ts +++ b/src/utils/api/instance.ts @@ -1,6 +1,6 @@ import { getAccountDetails } from '@utils/storage/actions' import axios, { AxiosRequestConfig } from 'axios' -import { ctx, handleError, PagedResponse, userAgent } from './helpers' +import { ctx, handleError, PagedResponse, parseHeaderLinks, userAgent } from './helpers' export type Params = { method: 'get' | 'post' | 'put' | 'delete' | 'patch' @@ -57,29 +57,7 @@ const apiInstance = async ({ ...((body as (FormData & { _parts: [][] }) | undefined)?._parts.length && { data: body }), ...extras }) - .then(response => { - let links: { - prev?: { id: string; isOffset: boolean } - next?: { id: string; isOffset: boolean } - } = {} - - if (response.headers?.link) { - const linksParsed = response.headers.link.matchAll( - new RegExp('[?&](.*?_id|offset)=(.*?)>; *rel="(.*?)"', 'gi') - ) - for (const link of linksParsed) { - switch (link[3]) { - case 'prev': - links.prev = { id: link[2], isOffset: link[1].includes('offset') } - break - case 'next': - links.next = { id: link[2], isOffset: link[1].includes('offset') } - break - } - } - } - return Promise.resolve({ body: response.data, links }) - }) + .then(response => ({ body: response.data, links: parseHeaderLinks(response.headers.link) })) .catch(handleError()) } diff --git a/src/utils/queryHooks/lists.ts b/src/utils/queryHooks/lists.ts index a78c46a7..c38da5e4 100644 --- a/src/utils/queryHooks/lists.ts +++ b/src/utils/queryHooks/lists.ts @@ -1,15 +1,16 @@ import { - QueryFunctionContext, - useInfiniteQuery, - UseInfiniteQueryOptions, - useMutation, - UseMutationOptions, - useQuery, - UseQueryOptions + QueryFunctionContext, + useInfiniteQuery, + UseInfiniteQueryOptions, + useMutation, + UseMutationOptions, + useQuery, + UseQueryOptions } from '@tanstack/react-query' import { PagedResponse } from '@utils/api/helpers' import apiInstance from '@utils/api/instance' import { AxiosError } from 'axios' +import { infinitePageParams } from './utils' export type QueryKeyLists = ['Lists'] @@ -98,10 +99,16 @@ const useListAccountsQuery = ({ options, ...queryKeyParams }: QueryKeyListAccounts[1] & { - options?: UseInfiniteQueryOptions, AxiosError> + options?: Omit< + UseInfiniteQueryOptions, AxiosError>, + 'getPreviousPageParam' | 'getNextPageParam' + > }) => { const queryKey: QueryKeyListAccounts = ['ListAccounts', queryKeyParams] - return useInfiniteQuery(queryKey, accountsQueryFunction, options) + return useInfiniteQuery(queryKey, accountsQueryFunction, { + ...options, + ...infinitePageParams + }) } type AccountsMutationVarsLists = { diff --git a/src/utils/queryHooks/timeline.ts b/src/utils/queryHooks/timeline.ts index b85ed23c..315cadf0 100644 --- a/src/utils/queryHooks/timeline.ts +++ b/src/utils/queryHooks/timeline.ts @@ -18,6 +18,7 @@ import { searchLocalStatus } from './search' import deleteItem from './timeline/deleteItem' import editItem from './timeline/editItem' import updateStatusProperty from './timeline/updateStatusProperty' +import { infinitePageParams } from './utils' export type QueryKeyTimeline = [ 'Timeline', @@ -57,7 +58,25 @@ export const queryFunctionTimeline = async ({ pageParam }: QueryFunctionContext) => { const page = queryKey[1] - let params: { [key: string]: string } = { limit: 40, ...pageParam } + + let marker: string | undefined + if (page.page === 'Following' && !pageParam?.offset && !pageParam?.min_id && !pageParam?.max_id) { + const storedMarker = getAccountStorage.string('read_marker_following') + if (storedMarker) { + await apiInstance({ + method: 'get', + url: 'timelines/home', + params: { limit: 1, min_id: storedMarker } + }).then(res => { + if (res.body.length) { + marker = storedMarker + } + }) + } + } + let params: { [key: string]: string } = marker + ? { limit: 40, max_id: marker } + : { limit: 40, ...pageParam } switch (page.page) { case 'Following': @@ -137,7 +156,16 @@ export const queryFunctionTimeline = async ({ case 'Account': if (!page.id) return Promise.reject('Timeline query account id not provided') - if (page.exclude_reblogs) { + if (page.only_media) { + return apiInstance({ + method: 'get', + url: `accounts/${page.id}/statuses`, + params: { + only_media: 'true', + ...params + } + }) + } else if (page.exclude_reblogs) { if (pageParam && pageParam.hasOwnProperty('max_id')) { return apiInstance({ method: 'get', @@ -177,8 +205,8 @@ export const queryFunctionTimeline = async ({ url: `accounts/${page.id}/statuses`, params: { ...params, - exclude_replies: page.exclude_reblogs.toString(), - only_media: page.only_media.toString() + exclude_replies: false, + only_media: false } }) } @@ -228,14 +256,18 @@ const useTimelineQuery = ({ options, ...queryKeyParams }: QueryKeyTimeline[1] & { - options?: UseInfiniteQueryOptions, AxiosError> + options?: Omit< + UseInfiniteQueryOptions, AxiosError>, + 'getPreviousPageParam' | 'getNextPageParam' + > }) => { const queryKey: QueryKeyTimeline = ['Timeline', { ...queryKeyParams }] return useInfiniteQuery(queryKey, queryFunctionTimeline, { refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false, - ...options + ...options, + ...infinitePageParams }) } @@ -431,7 +463,6 @@ const useTimelineMutation = ({ updateStatusProperty(params, navigationState) break case 'editItem': - console.log('YES!!!') editItem(params) break case 'deleteItem': diff --git a/src/utils/queryHooks/users.ts b/src/utils/queryHooks/users.ts index 14c78345..87fd1366 100644 --- a/src/utils/queryHooks/users.ts +++ b/src/utils/queryHooks/users.ts @@ -9,6 +9,7 @@ import apiInstance from '@utils/api/instance' import { urlMatcher } from '@utils/helpers/urlMatcher' import { TabSharedStackParamList } from '@utils/navigation/navigators' import { AxiosError } from 'axios' +import { infinitePageParams } from './utils' export type QueryKeyUsers = ['Users', TabSharedStackParamList['Tab-Shared-Users']] @@ -73,13 +74,19 @@ const useUsersQuery = ({ options, ...queryKeyParams }: QueryKeyUsers[1] & { - options?: UseInfiniteQueryOptions< - PagedResponse & { warnIncomplete: boolean; remoteData: boolean }, - AxiosError + options?: Omit< + UseInfiniteQueryOptions< + PagedResponse & { warnIncomplete: boolean; remoteData: boolean }, + AxiosError + >, + 'getPreviousPageParam' | 'getNextPageParam' > }) => { const queryKey: QueryKeyUsers = ['Users', { ...queryKeyParams }] - return useInfiniteQuery(queryKey, queryFunction, options) + return useInfiniteQuery(queryKey, queryFunction, { + ...options, + ...infinitePageParams + }) } export { useUsersQuery } diff --git a/src/utils/queryHooks/utils.ts b/src/utils/queryHooks/utils.ts index ad06ff9e..e83c3a29 100644 --- a/src/utils/queryHooks/utils.ts +++ b/src/utils/queryHooks/utils.ts @@ -2,10 +2,8 @@ import { InfiniteData } from '@tanstack/react-query' import { PagedResponse } from '@utils/api/helpers' export const infinitePageParams = { - getPreviousPageParam: (firstPage: PagedResponse) => - firstPage.links?.prev && { min_id: firstPage.links.next }, - getNextPageParam: (lastPage: PagedResponse) => - lastPage.links?.next && { max_id: lastPage.links.next } + getPreviousPageParam: (firstPage: PagedResponse) => firstPage.links?.prev, + getNextPageParam: (lastPage: PagedResponse) => lastPage.links?.next } export const flattenPages = (data: InfiniteData> | undefined): T[] | [] => diff --git a/src/utils/storage/account/v0.ts b/src/utils/storage/account/v0.ts index d1fb9ea3..d1ec06de 100644 --- a/src/utils/storage/account/v0.ts +++ b/src/utils/storage/account/v0.ts @@ -24,6 +24,7 @@ export type AccountV0 = { 'auth.account.domain': string // used for username 'auth.account.avatar_static': string version: string + read_marker_following?: string // number // boolean // object diff --git a/src/utils/storage/actions.ts b/src/utils/storage/actions.ts index 2fdec586..88c234e3 100644 --- a/src/utils/storage/actions.ts +++ b/src/utils/storage/actions.ts @@ -1,5 +1,6 @@ import { queryClient } from '@utils/queryHooks' import { storage } from '@utils/storage' +import { Platform } from 'react-native' import { MMKV, useMMKVBoolean, @@ -40,10 +41,21 @@ export const useGlobalStorage = { useMMKVString(key, storage.global) as NonNullable extends string ? [StorageGlobal[T], (valud: StorageGlobal[T]) => void] : never, - number: (key: T) => - useMMKVNumber(key, storage.global) as NonNullable extends number - ? [StorageGlobal[T], (valud: StorageGlobal[T]) => void] - : never, + number: (key: T) => { + if (Platform.OS === 'ios') { + return useMMKVString(key, storage.global) as NonNullable extends number + ? [StorageGlobal[T], (valud: StorageGlobal[T]) => void] + : never + } else { + const [num, setNum] = useMMKVString(key, storage.global) + // @ts-ignore + return [parseInt(num), v => setNum(v.toString())] as NonNullable< + StorageGlobal[T] + > extends number + ? [StorageGlobal[T], (valud: StorageGlobal[T]) => void] + : never + } + }, boolean: (key: T) => useMMKVBoolean(key, storage.global) as NonNullable extends boolean ? [StorageGlobal[T], (valud: StorageGlobal[T]) => void] diff --git a/yarn.lock b/yarn.lock index 4925bcac..b43642b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1476,7 +1476,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.8.4": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.8.4": version: 7.20.7 resolution: "@babel/runtime@npm:7.20.7" dependencies: @@ -2744,12 +2744,12 @@ __metadata: languageName: node linkType: hard -"@react-native-camera-roll/camera-roll@npm:^5.2.0": - version: 5.2.0 - resolution: "@react-native-camera-roll/camera-roll@npm:5.2.0" +"@react-native-camera-roll/camera-roll@npm:^5.2.1": + version: 5.2.1 + resolution: "@react-native-camera-roll/camera-roll@npm:5.2.1" peerDependencies: react-native: ">=0.59" - checksum: 27800224bdbd128800e6a64046d76865814a43215ad3a0df9a93eeac64b523e3b68382d7cabb79b574e354d03dddc625b93fa2b6010ed04f6a89296038e3eabf + checksum: db901554170cced81db4b01c0e89324905fe4c6915a41e4cf4b6d1fa35f5894d18d1d734304d516824bff434784e4b571e9fa379e850f8dd4d42a28e3eab9eee languageName: node linkType: hard @@ -5692,13 +5692,13 @@ __metadata: languageName: node linkType: hard -"expo-modules-core@npm:1.1.0": - version: 1.1.0 - resolution: "expo-modules-core@npm:1.1.0" +"expo-modules-core@npm:1.1.1": + version: 1.1.1 + resolution: "expo-modules-core@npm:1.1.1" dependencies: compare-versions: ^3.4.0 invariant: ^2.2.4 - checksum: f5add659b2f43c2784dce86986ec709feac38a60881afd013049ef86b106eeba9217427d01b9da27e00b678e9f47518ffa5d7676f95408c6e3966093d48252e8 + checksum: 6a2b6b5d1f56f197ddf8b3f3a638d8f15534657218bdf895bd96c07cb34fc8404761d6a9206527fa5b67baade60858b4530de147fe1f3b7db8205c5d9d059b31 languageName: node linkType: hard @@ -5741,6 +5741,15 @@ __metadata: languageName: node linkType: hard +"expo-screen-orientation@npm:^5.0.1": + version: 5.0.1 + resolution: "expo-screen-orientation@npm:5.0.1" + peerDependencies: + expo: "*" + checksum: 7ede30533a8c492f82b58c3b8be110b6373ffcc2cbe273299d9f15d9aa943d678d8aaffb3d2565780b45d1d5a2a1ddea54d813fc84c06e30e3cfd59abbd8e30e + languageName: node + linkType: hard + "expo-secure-store@npm:^12.0.0": version: 12.0.0 resolution: "expo-secure-store@npm:12.0.0" @@ -5791,9 +5800,9 @@ __metadata: languageName: node linkType: hard -"expo@npm:^47.0.11": - version: 47.0.11 - resolution: "expo@npm:47.0.11" +"expo@npm:^47.0.12": + version: 47.0.12 + resolution: "expo@npm:47.0.12" dependencies: "@babel/runtime": ^7.14.0 "@expo/cli": 0.4.11 @@ -5810,7 +5819,7 @@ __metadata: expo-font: ~11.0.1 expo-keep-awake: ~11.0.1 expo-modules-autolinking: 1.0.1 - expo-modules-core: 1.1.0 + expo-modules-core: 1.1.1 fbemitter: ^3.0.0 getenv: ^1.0.0 invariant: ^2.2.4 @@ -5823,7 +5832,7 @@ __metadata: optional: true bin: expo: bin/cli.js - checksum: 5e470d7b0c94ddade3f77e4e5038f025635f432f1e56a9e541455f5e71b5f4919c7c756639b5bf31ff6b877ad254a77290efe0305d48712e5406c76f3c8b7e77 + checksum: 24f7073660fb4c4c76a398d722d1fcae1ea654463faf2a730e986f884618a93618d1ee9116fe6c8199338c2e8aa716c2d389344a967824d264e2440eb7f16340 languageName: node linkType: hard @@ -9416,11 +9425,11 @@ __metadata: languageName: node linkType: hard -"react-i18next@npm:^12.1.1": - version: 12.1.1 - resolution: "react-i18next@npm:12.1.1" +"react-i18next@npm:^12.1.4": + version: 12.1.4 + resolution: "react-i18next@npm:12.1.4" dependencies: - "@babel/runtime": ^7.14.5 + "@babel/runtime": ^7.20.6 html-parse-stringify: ^3.0.1 peerDependencies: i18next: ">= 19.0.0" @@ -9430,7 +9439,7 @@ __metadata: optional: true react-native: optional: true - checksum: baeb1957d70281d1a95ef0b94801bc2e6bf17066a49a45f32cd1a706ead3ddce2ab3e7b321e07c43f082efa9987c2d55856a5bdf419d9ad628dbfa9ec87af8ea + checksum: 6f0f8a47f0bf7da2c9ac383c88b6ef02446d9d1aa2609cfcc98d8a28999da85361f065fbe7f7f4af910df4e8d53af7879db4b9cd681274d582fc7bdd1b07813b languageName: node linkType: hard @@ -11254,7 +11263,7 @@ __metadata: "@mattermost/react-native-paste-input": ^0.5.2 "@neverdull-agency/expo-unlimited-secure-store": ^1.0.10 "@react-native-async-storage/async-storage": ~1.17.11 - "@react-native-camera-roll/camera-roll": ^5.2.0 + "@react-native-camera-roll/camera-roll": ^5.2.1 "@react-native-clipboard/clipboard": ^1.11.1 "@react-native-community/blur": ^4.3.0 "@react-native-community/netinfo": 9.3.7 @@ -11281,7 +11290,7 @@ __metadata: chalk: ^4.1.2 diff: ^5.1.0 dotenv: ^16.0.3 - expo: ^47.0.11 + expo: ^47.0.12 expo-auth-session: ^3.8.0 expo-av: ^13.1.0 expo-constants: ^14.1.0 @@ -11293,6 +11302,7 @@ __metadata: expo-notifications: ^0.17.0 expo-random: ^13.0.0 expo-screen-capture: ^5.0.0 + expo-screen-orientation: ^5.0.1 expo-secure-store: ^12.0.0 expo-splash-screen: ^0.17.5 expo-store-review: ^6.1.0 @@ -11304,7 +11314,7 @@ __metadata: lodash: ^4.17.21 react: ^18.2.0 react-dom: ^18.2.0 - react-i18next: ^12.1.1 + react-i18next: ^12.1.4 react-intl: ^6.2.5 react-native: ^0.70.6 react-native-animated-spinkit: ^1.5.2