Merge pull request #230 from tooot-app/main

Release v3.5
This commit is contained in:
xmflsct 2022-02-14 23:44:26 +01:00 committed by GitHub
commit bf49c03c06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 424 additions and 137 deletions

View File

@ -57,7 +57,7 @@ private_lane :github_release do |options|
description: "No changelog provided",
commitish: git_branch,
is_prerelease: options[:prerelease],
upload_assets: options[:prerelease] ? nil : ["#{File.expand_path('..', Dir.pwd)}/tooot-#{GITHUB_RELEASE}.apk"]
upload_assets: ["#{File.expand_path('..', Dir.pwd)}/tooot-#{GITHUB_RELEASE}.apk"]
)
end
@ -133,8 +133,8 @@ private_lane :build_android do
task: 'clean bundle',
build_type: 'release',
project_dir: "./android",
print_command: false,
print_command_output: false,
print_command: true,
print_command_output: true,
properties: {
"expoSDK" => VERSIONS[:expo],
"releaseChannel" => RELEASE_CHANNEL,
@ -151,14 +151,30 @@ private_lane :build_android do
skip_upload_images: true,
skip_upload_screenshots: true
)
build_android_app(
task: 'assemble',
build_type: 'release',
project_dir: "./android",
print_command: false,
print_command_output: false,
properties: {
"expoSDK" => VERSIONS[:expo],
"releaseChannel" => RELEASE_CHANNEL,
"android.injected.signing.store.file" => "#{File.expand_path('..', Dir.pwd)}/android/tooot.jks",
"android.injected.signing.store.password" => ENV["ANDROID_KEYSTORE_PASSWORD"],
"android.injected.signing.key.alias" => ENV["ANDROID_KEYSTORE_ALIAS"],
"android.injected.signing.key.password" => ENV["ANDROID_KEYSTORE_KEY_PASSWORD"],
}
)
sh "mv #{lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]} #{File.expand_path('..', Dir.pwd)}/tooot-#{GITHUB_RELEASE}.apk"
when "release"
prepare_playstore_android
build_android_app(
task: 'clean bundle',
build_type: 'release',
project_dir: "./android",
print_command: false,
print_command_output: false,
print_command: true,
print_command_output: true,
properties: {
"expoSDK" => VERSIONS[:expo],
"releaseChannel" => RELEASE_CHANNEL,

View File

@ -377,9 +377,9 @@ PODS:
- React-Core
- react-native-cameraroll (4.1.2):
- React-Core
- react-native-netinfo (7.1.9):
- react-native-netinfo (8.0.0):
- React-Core
- react-native-pager-view (5.4.9):
- react-native-pager-view (5.4.11):
- React-Core
- react-native-safe-area-context (3.3.2):
- React-Core
@ -450,7 +450,7 @@ PODS:
- React-jsi (= 0.66.4)
- React-logger (= 0.66.4)
- React-perflogger (= 0.66.4)
- RNCAsyncStorage (1.15.17):
- RNCAsyncStorage (1.16.1):
- React-Core
- RNFastImage (8.5.11):
- React-Core
@ -458,7 +458,7 @@ PODS:
- SDWebImageWebPCoder (~> 0.8.4)
- RNGestureHandler (2.2.0):
- React-Core
- RNReanimated (2.4.0):
- RNReanimated (2.4.1):
- DoubleConversion
- FBLazyVector
- FBReactNativeSpec
@ -486,7 +486,7 @@ PODS:
- React-RCTText
- ReactCommon/turbomodule/core
- Yoga
- RNScreens (3.10.2):
- RNScreens (3.11.1):
- React-Core
- React-RCTImage
- RNSentry (3.2.13):
@ -833,8 +833,8 @@ SPEC CHECKSUMS:
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-blurhash: b7ed7b7de81ae1f9ce52d694f43c5b5cddddcae1
react-native-cameraroll: 2957f2bce63ae896a848fbe0d5352c1bd4d20866
react-native-netinfo: 87e5bfaf21ea5c6c110941290aa481dd8e849f98
react-native-pager-view: 3ee7d4c7697fb3ef788346e834a60cca97ed8540
react-native-netinfo: ba4ea50d836c60580c59079233c400753fe5bcb6
react-native-pager-view: 7f00d63688f7df9fad86dfb0154814419cc5eb8d
react-native-safe-area-context: 584dc04881deb49474363f3be89e4ca0e854c057
react-native-segmented-control: 65df6cd0619b780b3843d574a72d4c7cec396097
React-perflogger: 93075d8931c32cd1fce8a98c15d2d5ccc4d891bd
@ -849,11 +849,11 @@ SPEC CHECKSUMS:
React-RCTVibration: e3ffca672dd3772536cb844274094b0e2c31b187
React-runtimeexecutor: dec32ee6f2e2a26e13e58152271535fadff5455a
ReactCommon: 57b69f6383eafcbd7da625bfa6003810332313c4
RNCAsyncStorage: 6bd5a7ba3dde1c3facba418aa273f449bdc5437a
RNCAsyncStorage: b49b4e38a1548d03b74b30e558a1d18465b94be7
RNFastImage: cced864a4a2eac27c5c10ac16bd5e8b9d2be4504
RNGestureHandler: bf572f552ea324acd5b5464b8d30755b2d8c1de6
RNReanimated: fb56abd99a5d7efe91ae3c7bf3cd3663cee1b21b
RNScreens: d6da2b9e29cf523832c2542f47bf1287318b1868
RNReanimated: e8afbe2a9e08e9e778ea72ddb1a8533445812621
RNScreens: 4d83613b50b74ed277026375dc0810893b0c347f
RNSentry: 0aa1567f66c20390f3834637fc4f73380dcd0774
RNSVG: 551acb6562324b1d52a4e0758f7ca0ec234e278f
SDWebImage: 53179a2dba77246efa8a9b85f5c5b21f8f43e38f

View File

@ -1,10 +1,10 @@
{
"name": "tooot",
"versions": {
"native": "220206",
"native": "220214",
"major": 3,
"minor": 4,
"patch": 4,
"minor": 5,
"patch": 0,
"expo": "44.0.0"
},
"description": "tooot app for Mastodon",
@ -27,14 +27,14 @@
"dependencies": {
"@expo/react-native-action-sheet": "3.13.0",
"@neverdull-agency/expo-unlimited-secure-store": "1.0.10",
"@react-native-async-storage/async-storage": "1.15.17",
"@react-native-async-storage/async-storage": "1.16.1",
"@react-native-community/blur": "3.6.0",
"@react-native-community/cameraroll": "4.1.2",
"@react-native-community/netinfo": "7.1.9",
"@react-native-community/netinfo": "8.0.0",
"@react-native-community/segmented-control": "2.2.2",
"@react-navigation/bottom-tabs": "6.2.0",
"@react-navigation/native": "6.0.8",
"@react-navigation/native-stack": "6.4.1",
"@react-navigation/native-stack": "6.5.0",
"@react-navigation/stack": "6.1.1",
"@reduxjs/toolkit": "1.7.2",
"@sentry/react-native": "3.2.13",
@ -67,7 +67,7 @@
"lodash": "4.17.21",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-i18next": "11.15.3",
"react-i18next": "11.15.4",
"react-native": "0.66.4",
"react-native-animated-spinkit": "1.5.2",
"react-native-base64": "^0.2.1",
@ -77,14 +77,14 @@
"react-native-flash-message": "0.2.1",
"react-native-gesture-handler": "2.2.0",
"react-native-htmlview": "0.16.0",
"react-native-pager-view": "5.4.9",
"react-native-reanimated": "2.4.0",
"react-native-pager-view": "5.4.11",
"react-native-reanimated": "2.4.1",
"react-native-safe-area-context": "3.3.2",
"react-native-screens": "3.10.2",
"react-native-screens": "3.11.1",
"react-native-svg": "12.1.1",
"react-native-swipe-list-view": "3.2.9",
"react-native-tab-view": "3.1.1",
"react-query": "3.34.14",
"react-query": "3.34.15",
"react-redux": "7.2.6",
"react-timeago": "6.2.1",
"redux-persist": "6.0.0",
@ -94,10 +94,10 @@
"valid-url": "1.0.9"
},
"devDependencies": {
"@babel/core": "7.17.0",
"@babel/core": "7.17.2",
"@babel/plugin-proposal-optional-chaining": "7.16.7",
"@babel/preset-typescript": "7.16.7",
"@expo/config": "6.0.16",
"@expo/config": "6.0.18",
"@types/lodash": "4.14.178",
"@types/react": "17.0.39",
"@types/react-dom": "17.0.11",

View File

@ -1,3 +1,4 @@
import analytics from '@components/analytics'
import { HeaderLeft } from '@components/Header'
import { displayMessage, Message } from '@components/Message'
import navigationRef from '@helpers/navigationRef'
@ -14,13 +15,13 @@ import pushUseConnect from '@utils/push/useConnect'
import pushUseReceive from '@utils/push/useReceive'
import pushUseRespond from '@utils/push/useRespond'
import { updatePreviousTab } from '@utils/slices/contextsSlice'
import { checkEmojis } from '@utils/slices/instances/checkEmojis'
import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences'
import { updateConfiguration } from '@utils/slices/instances/updateConfiguration'
import { updateFilters } from '@utils/slices/instances/updateFilters'
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
import { themes } from '@utils/styles/themes'
import * as Analytics from 'expo-firebase-analytics'
import * as Linking from 'expo-linking'
import { addScreenshotListener } from 'expo-screen-capture'
import React, { useCallback, useEffect, useRef, useState } from 'react'
@ -92,6 +93,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
dispatch(updateConfiguration())
dispatch(updateFilters())
dispatch(updateAccountPreferences())
dispatch(checkEmojis())
}
}, [instanceActive])
@ -116,7 +118,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
}
if (previousRoute?.name !== currentRoute?.name) {
Analytics.logEvent('screen_view', { screen_name: currentRoute?.name })
analytics('screen_view', { screen_name: currentRoute?.name })
Sentry.Native.setContext('page', {
previous: previousRoute,
current: currentRoute

View File

@ -58,14 +58,14 @@ const apiGeneral = async <T = unknown>({
})
})
.catch(error => {
if (sentry && Math.random() < 0.01) {
Sentry.Native.setExtras({
API: 'general',
...(error.response && { response: error.response }),
...(error.request && { request: error.request })
})
Sentry.Native.captureException(error)
}
// if (sentry && Math.random() < 0.01) {
// Sentry.Native.setExtras({
// API: 'general',
// ...(error.response && { response: error.response }),
// ...(error.request && { request: error.request })
// })
// Sentry.Native.captureException(error)
// }
if (error.response) {
// The request was made and the server responded with a status code

View File

@ -98,14 +98,14 @@ const apiInstance = async <T = unknown>({
})
})
.catch(error => {
if (Math.random() < 0.001) {
Sentry.Native.setExtras({
API: 'instance',
...(error.response && { response: error.response }),
...(error.request && { request: error.request })
})
Sentry.Native.captureException(error)
}
// if (Math.random() < 0.001) {
// Sentry.Native.setExtras({
// API: 'instance',
// ...(error.response && { response: error.response }),
// ...(error.request && { request: error.request })
// })
// Sentry.Native.captureException(error)
// }
if (error.response) {
// The request was made and the server responded with a status code

View File

@ -19,7 +19,7 @@ export type Params = {
export const TOOOT_API_DOMAIN = mapEnvironment({
release: 'api.tooot.app',
candidate: 'api-candidate.tooot.app',
candidate: 'api.tooot.app',
development: 'api-development.tooot.app'
})

View File

@ -2,6 +2,7 @@ import EmojisButton from '@components/Emojis/Button'
import EmojisList from '@components/Emojis/List'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { useEmojisQuery } from '@utils/queryHooks/emojis'
import { getInstanceFrequentEmojis } from '@utils/slices/instancesSlice'
import { chunk, forEach, groupBy, sortBy } from 'lodash'
import React, {
Dispatch,
@ -11,11 +12,16 @@ import React, {
useEffect,
useReducer
} from 'react'
import { useTranslation } from 'react-i18next'
import FastImage from 'react-native-fast-image'
import { useSelector } from 'react-redux'
import EmojisContext, { emojisReducer } from './Emojis/helpers/EmojisContext'
const prefetchEmojis = (
sortedEmojis: { title: string; data: Mastodon.Emoji[][] }[],
sortedEmojis: {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[],
reduceMotionEnabled: boolean
) => {
const prefetches: { uri: string }[] = []
@ -101,14 +107,28 @@ const ComponentEmojis: React.FC<Props> = ({
[value, selectionRange.current?.start, selectionRange.current?.end]
)
const { t } = useTranslation()
const { data } = useEmojisQuery({ options: { enabled } })
const frequentEmojis = useSelector(getInstanceFrequentEmojis, () => true)
useEffect(() => {
if (data && data.length) {
let sortedEmojis: { title: string; data: Mastodon.Emoji[][] }[] = []
let sortedEmojis: {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[] = []
forEach(
groupBy(sortBy(data, ['category', 'shortcode']), 'category'),
(value, key) => sortedEmojis.push({ title: key, data: chunk(value, 5) })
)
if (frequentEmojis.length) {
sortedEmojis.unshift({
title: t('componentEmojis:frequentUsed'),
data: chunk(
frequentEmojis.map(e => e.emoji),
5
)
})
}
emojisDispatch({
type: 'load',
payload: sortedEmojis

View File

@ -1,4 +1,5 @@
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { countInstanceEmoji } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation'
import { useTheme } from '@utils/styles/ThemeManager'
@ -14,11 +15,13 @@ import {
View
} from 'react-native'
import FastImage from 'react-native-fast-image'
import { useDispatch } from 'react-redux'
import validUrl from 'valid-url'
import EmojisContext from './helpers/EmojisContext'
const EmojisList = React.memo(
() => {
const dispatch = useDispatch()
const { reduceMotionEnabled } = useAccessibility()
const { t } = useTranslation()
@ -42,12 +45,13 @@ const EmojisList = React.memo(
return (
<Pressable
key={emoji.shortcode}
onPress={() =>
onPress={() => {
emojisDispatch({
type: 'shortcode',
payload: `:${emoji.shortcode}:`
})
}
dispatch(countInstanceEmoji(emoji))
}}
>
<FastImage
accessibilityLabel={t(

View File

@ -3,7 +3,10 @@ import { createContext, Dispatch } from 'react'
export type EmojisState = {
enabled: boolean
active: boolean
emojis: { title: string; data: Mastodon.Emoji[][] }[]
emojis: {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[]
shortcode: Mastodon.Emoji['shortcode'] | null
}

View File

@ -34,8 +34,8 @@ const AttachmentImage = React.memo(
!image.meta?.original?.width ||
!image.meta?.original?.height
? attachmentAspectRatio({ total, index })
: image.meta.original.height / image.meta.original.width > 2
? 0.5
: image.meta.original.height / image.meta.original.width > 1
? 1
: image.meta.original.width / image.meta.original.height
}}
/>

View File

@ -41,7 +41,7 @@ export const shouldFilter = ({
}) => {
const instance = getInstance(store.getState())
const ownAccount =
getInstanceAccount(store.getState())?.id === status.account.id
getInstanceAccount(store.getState())?.id === status.account?.id
let shouldFilter = false
if (!ownAccount) {

View File

@ -1,7 +1,7 @@
import * as Analytics from 'expo-firebase-analytics'
const analytics = (event: string, params?: { [key: string]: any }) => {
Analytics.logEvent(event, params)
Analytics.logEvent(event, params).catch(() => {})
}
export default analytics

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'),
componentParse: require('./components/parse'),

View File

@ -0,0 +1,3 @@
{
"frequentUsed": "Frequent used"
}

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'),
componentParse: require('./components/parse'),

View File

@ -0,0 +1 @@
{}

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'),
componentParse: require('./components/parse'),

View File

@ -0,0 +1 @@
{}

View File

@ -232,6 +232,14 @@
"cancel": "$t(common:buttons.cancel)"
}
},
"darkTheme": {
"heading": "Độ tối",
"options": {
"lighter": "Thấp",
"darker": "Vừa",
"cancel": "$t(common:buttons.cancel)"
}
},
"browser": {
"heading": "Mở liên kết",
"options": {
@ -282,12 +290,12 @@
}
},
"attachments": {
"name": "<0 /><1>'s media</1>"
"name": "Bộ sưu tập của <1></1>"
},
"search": {
"header": {
"prefix": "Tìm kiếm",
"placeholder": "về..."
"placeholder": "tất cả mọi thứ"
},
"empty": {
"general": "Tìm <bold>$t(screenTabs:shared.search.sections.accounts)</bold>, <bold>$t(screenTabs:shared.search.sections.hashtags)</bold> hoặc <bold>$t(screenTabs:shared.search.sections.statuses)</bold><bold>",

View File

@ -8,6 +8,7 @@ export default {
screenImageViewer: require('./screens/imageViewer'),
screenTabs: require('./screens/tabs'),
componentEmojis: require('./components/emojis'),
componentInstance: require('./components/instance'),
componentMediaSelector: require('./components/mediaSelector'),
componentParse: require('./components/parse'),

View File

@ -0,0 +1,3 @@
{
"frequentUsed": "常用"
}

View File

@ -30,7 +30,11 @@ import FastImage from 'react-native-fast-image'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { ComposeState } from './utils/types'
import { useSelector } from 'react-redux'
import { getInstanceConfigurationStatusCharsURL } from '@utils/slices/instancesSlice'
import {
getInstanceConfigurationStatusCharsURL,
getInstanceFrequentEmojis
} from '@utils/slices/instancesSlice'
import { useTranslation } from 'react-i18next'
const prefetchEmojis = (
sortedEmojis: NonNullable<ComposeState['emoji']['emojis']>,
@ -99,15 +103,29 @@ const ComposeRoot = React.memo(
}
}, [composeState.tag])
const { t } = useTranslation()
const { data: emojisData } = useEmojisQuery({})
const frequentEmojis = useSelector(getInstanceFrequentEmojis, () => true)
useEffect(() => {
if (emojisData && emojisData.length) {
let sortedEmojis: { title: string; data: Mastodon.Emoji[][] }[] = []
const sortedEmojis: {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[] = []
forEach(
groupBy(sortBy(emojisData, ['category', 'shortcode']), 'category'),
(value, key) =>
sortedEmojis.push({ title: key, data: chunk(value, 5) })
)
if (frequentEmojis.length) {
sortedEmojis.unshift({
title: t('componentEmojis:frequentUsed'),
data: chunk(
frequentEmojis.map(e => e.emoji),
5
)
})
}
composeDispatch({
type: 'emoji',
payload: { ...composeState.emoji, emojis: sortedEmojis }

View File

@ -1,5 +1,6 @@
import haptics from '@components/haptics'
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
import { countInstanceEmoji } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { RefObject, useCallback, useContext, useEffect } from 'react'
@ -14,6 +15,7 @@ import {
View
} from 'react-native'
import FastImage from 'react-native-fast-image'
import { useDispatch } from 'react-redux'
import validUrl from 'valid-url'
import updateText from '../../updateText'
import ComposeContext from '../../utils/createContext'
@ -27,6 +29,7 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
const { reduceMotionEnabled } = useAccessibility()
const { colors } = useTheme()
const { t } = useTranslation()
const dispatch = useDispatch()
useEffect(() => {
const tagEmojis = findNodeHandle(accessibleRefEmojis.current)
@ -53,13 +56,14 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
<Pressable
key={emoji.shortcode}
onPress={() => {
haptics('Light')
updateText({
composeState,
composeDispatch,
newText: `:${emoji.shortcode}:`,
type: 'emoji'
})
haptics('Light')
dispatch(countInstanceEmoji(emoji))
}}
>
<FastImage

View File

@ -40,7 +40,12 @@ export type ComposeState = {
}
emoji: {
active: boolean
emojis: { title: string; data: Mastodon.Emoji[][] }[] | undefined
emojis:
| {
title: string
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
}[]
| undefined
}
poll: {
active: boolean

View File

@ -91,8 +91,10 @@ const AccountAttachments = React.memo(
return (
<GracefullyImage
uri={{
original: item.media_attachments[0].preview_url,
remote: item.media_attachments[0].remote_url
original:
item.media_attachments[0]?.preview_url ||
item.media_attachments[0]?.url,
remote: item.media_attachments[0]?.remote_url
}}
blurhash={item.media_attachments[0].blurhash}
dimension={{ width: width, height: width }}

View File

@ -45,6 +45,13 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
scrolled.current = true
const pointer = flattenData.findIndex(({ id }) => id === toot.id)
if (pointer === -1) return
Sentry.Native.setContext('Scroll to Index', {
type: 'original',
index: pointer,
itemsLength: flattenData.length,
id: toot.id,
flattenData: flattenData.map(({ id }) => id)
})
try {
setTimeout(() => {
flRef.current?.scrollToIndex({
@ -53,14 +60,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
})
}, 500)
} catch (err) {
if (Math.random() < 0.1) {
Sentry.Native.setContext('Scroll to Index', {
type: 'original',
index: pointer,
itemsLength: flattenData.length,
id: toot.id,
flattenData: flattenData.map(({ id }) => id)
})
if (Math.random() < 0.05) {
Sentry.Native.captureException(err)
}
}
@ -74,6 +74,12 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
error => {
const offset = error.averageItemLength * error.index
flRef.current?.scrollToOffset({ offset })
Sentry.Native.setContext('Scroll to Index', {
type: 'onScrollToIndexFailed',
index: error.index,
itemsLength,
id: toot.id
})
try {
error.index < itemsLength &&
setTimeout(
@ -85,13 +91,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
500
)
} catch (err) {
if (Math.random() < 0.1) {
Sentry.Native.setContext('Scroll to Index', {
type: 'onScrollToIndexFailed',
index: error.index,
itemsLength,
id: toot.id
})
if (Math.random() < 0.05) {
Sentry.Native.captureException(err)
}
}

View File

@ -27,7 +27,7 @@ const instancesPersistConfig = {
key: 'instances',
prefix,
storage: secureStorage,
version: 7,
version: 8,
// @ts-ignore
migrate: createMigrate(instancesMigration)
}

View File

@ -3,6 +3,7 @@ import { InstanceV4 } from './v4'
import { InstanceV5 } from './v5'
import { InstanceV6 } from './v6'
import { InstanceV7 } from './v7'
import { InstanceV8 } from './v8'
const instancesMigration = {
4: (state: InstanceV3): InstanceV4 => {
@ -76,6 +77,16 @@ const instancesMigration = {
}
})
}
},
8: (state: InstanceV7): InstanceV8 => {
return {
instances: state.instances.map(instance => {
return {
...instance,
frequentEmojis: []
}
})
}
}
}

View File

@ -0,0 +1,83 @@
import { ComposeStateDraft } from '@screens/Compose/utils/types'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
type Instance = {
active: boolean
appData: {
clientId: string
clientSecret: string
}
url: string
token: string
uri: Mastodon.Instance['uri']
urls: Mastodon.Instance['urls']
account: {
id: Mastodon.Account['id']
acct: Mastodon.Account['acct']
avatarStatic: Mastodon.Account['avatar_static']
preferences: Mastodon.Preferences
}
max_toot_chars?: number // To be deprecated in v4
configuration?: Mastodon.Instance['configuration']
filters: Mastodon.Filter[]
notifications_filter: {
follow: boolean
favourite: boolean
reblog: boolean
mention: boolean
poll: boolean
follow_request: boolean
}
push: {
global: { loading: boolean; value: boolean }
decode: { loading: boolean; value: boolean }
alerts: {
follow: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['follow']
}
favourite: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['favourite']
}
reblog: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['reblog']
}
mention: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['mention']
}
poll: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['poll']
}
}
keys: {
auth?: string
public?: string // legacy
private?: string // legacy
}
}
timelinesLookback?: {
[key: string]: {
queryKey: QueryKeyTimeline
ids: Mastodon.Status['id'][]
}
}
mePage: {
lists: { shown: boolean }
announcements: { shown: boolean; unread: number }
}
drafts: ComposeStateDraft[]
frequentEmojis: {
emoji: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>
score: number
count: number
lastUsed: Date
}[]
}
export type InstanceV8 = {
instances: Instance[]
}

View File

@ -106,7 +106,8 @@ const addInstance = createAsyncThunk(
lists: { shown: false },
announcements: { shown: false, unread: 0 }
},
drafts: []
drafts: [],
frequentEmojis: []
}
})
}

View File

@ -0,0 +1,15 @@
import apiInstance from '@api/instance'
import queryClient from '@helpers/queryClient'
import { createAsyncThunk } from '@reduxjs/toolkit'
export const checkEmojis = createAsyncThunk(
'instances/checkEmojis',
async (): Promise<Mastodon.Emoji[]> => {
const res = await apiInstance<Mastodon.Emoji[]>({
method: 'get',
url: 'custom_emojis'
}).then(res => res.body)
queryClient.setQueryData(['Emojis'], res)
return res
}
)

View File

@ -4,6 +4,7 @@ import { RootState } from '@root/store'
import { ComposeStateDraft } from '@screens/Compose/utils/types'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import addInstance from './instances/add'
import { checkEmojis } from './instances/checkEmojis'
import removeInstance from './instances/remove'
import { updateAccountPreferences } from './instances/updateAccountPreferences'
import { updateConfiguration } from './instances/updateConfiguration'
@ -81,6 +82,12 @@ export type Instance = {
announcements: { shown: boolean; unread: number }
}
drafts: ComposeStateDraft[]
frequentEmojis: {
emoji: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>
score: number
count: number
lastUsed: number
}[]
}
export type InstancesState = {
@ -184,6 +191,56 @@ const instancesSlice = createSlice({
...instances[activeIndex].mePage,
...action.payload
}
},
countInstanceEmoji: (
{ instances },
action: PayloadAction<Instance['frequentEmojis'][0]['emoji']>
) => {
const HALF_LIFE = 60 * 60 * 24 * 7 // 1 week
const calculateScore = (emoji: Instance['frequentEmojis'][0]): number => {
var seconds = (new Date().getTime() - emoji.lastUsed) / 1000
var score = emoji.count + 1
var order = Math.log(Math.max(score, 1)) / Math.LN10
var sign = score > 0 ? 1 : score === 0 ? 0 : -1
return (sign * order + seconds / HALF_LIFE) * 10
}
const activeIndex = findInstanceActive(instances)
const foundEmojiIndex = instances[activeIndex].frequentEmojis?.findIndex(
e =>
e.emoji.shortcode === action.payload.shortcode &&
e.emoji.url === action.payload.url
)
let newEmojisSort: Instance['frequentEmojis']
if (foundEmojiIndex > -1) {
newEmojisSort = instances[activeIndex].frequentEmojis
.map((e, i) =>
i === foundEmojiIndex
? {
...e,
score: calculateScore(e),
count: e.count + 1,
lastUsed: new Date().getTime()
}
: e
)
.sort((a, b) => b.score - a.score)
} else {
newEmojisSort = instances[activeIndex].frequentEmojis || []
const temp = {
emoji: action.payload,
score: 0,
count: 0,
lastUsed: new Date().getTime()
}
newEmojisSort.push({
...temp,
score: calculateScore(temp),
count: temp.count + 1
})
}
instances[activeIndex].frequentEmojis = newEmojisSort
.sort((a, b) => b.score - a.score)
.slice(0, 20)
}
},
extraReducers: builder => {
@ -321,6 +378,22 @@ const instancesSlice = createSlice({
action.meta.arg.changed
].loading = true
})
// Check if frequently used emojis still exist
.addCase(checkEmojis.fulfilled, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].frequentEmojis = state.instances[
activeIndex
].frequentEmojis?.filter(emoji => {
return action.payload.find(
e =>
e.shortcode === emoji.emoji.shortcode && e.url === emoji.emoji.url
)
})
})
.addCase(checkEmojis.rejected, (_, action) => {
console.error(action.error)
})
}
})
@ -394,6 +467,10 @@ export const getInstanceMePage = ({ instances: { instances } }: RootState) =>
export const getInstanceDrafts = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.drafts
export const getInstanceFrequentEmojis = ({
instances: { instances }
}: RootState) => instances[findInstanceActive(instances)]?.frequentEmojis
export const {
updateInstanceActive,
updateInstanceAccount,
@ -403,7 +480,8 @@ export const {
clearPushLoading,
disableAllPushes,
updateInstanceTimelineLookback,
updateInstanceMePage
updateInstanceMePage,
countInstanceEmoji
} = instancesSlice.actions
export default instancesSlice.reducer

115
yarn.lock
View File

@ -36,17 +36,17 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e"
integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==
"@babel/core@7.17.0":
version "7.17.0"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.0.tgz#16b8772b0a567f215839f689c5ded6bb20e864d5"
integrity sha512-x/5Ea+RO5MvF9ize5DeVICJoVrNv0Mi2RnIABrZEKYvPEpldXwauPkgvYA17cKa6WpU3LoYvYbuEMFtSNFsarA==
"@babel/core@7.17.2":
version "7.17.2"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.2.tgz#2c77fc430e95139d816d39b113b31bf40fb22337"
integrity sha512-R3VH5G42VSDolRHyUO4V2cfag8WHcZyxdq5Z/m8Xyb92lW/Erm/6kM+XtRFGf3Mulre3mveni2NHfEUws8wSvw==
dependencies:
"@ampproject/remapping" "^2.0.0"
"@babel/code-frame" "^7.16.7"
"@babel/generator" "^7.17.0"
"@babel/helper-compilation-targets" "^7.16.7"
"@babel/helper-module-transforms" "^7.16.7"
"@babel/helpers" "^7.17.0"
"@babel/helpers" "^7.17.2"
"@babel/parser" "^7.17.0"
"@babel/template" "^7.16.7"
"@babel/traverse" "^7.17.0"
@ -447,10 +447,10 @@
"@babel/traverse" "^7.16.5"
"@babel/types" "^7.16.0"
"@babel/helpers@^7.17.0":
version "7.17.0"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.0.tgz#79cdf6c66a579f3a7b5e739371bc63ca0306886b"
integrity sha512-Xe/9NFxjPwELUvW2dsukcMZIp6XwPSbI4ojFBJuX5ramHuVE22SVcZIwqzdWo5uCgeTXW8qV97lMvSOjq+1+nQ==
"@babel/helpers@^7.17.2":
version "7.17.2"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.2.tgz#23f0a0746c8e287773ccd27c14be428891f63417"
integrity sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==
dependencies:
"@babel/template" "^7.16.7"
"@babel/traverse" "^7.17.0"
@ -1410,12 +1410,12 @@
xcode "^3.0.1"
xml2js "0.4.23"
"@expo/config-plugins@4.0.16":
version "4.0.16"
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-4.0.16.tgz#832fb533245180c6dfd82fd359474f68a8e857b2"
integrity sha512-UTyE5JTBatMSwoOZCtX0u2H3zkp/ybMftc1228ARMop35Dbq+sYO11Ym3MrY5RPh8PYPofokDSOvoUq1jjORXw==
"@expo/config-plugins@4.0.18":
version "4.0.18"
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-4.0.18.tgz#80cf6a3aed83e78f714ad097d80e5a7cca48591a"
integrity sha512-tW4bnrnKhn+PPHF8wf1KAoubICAVUHW8CcagvyFqaRIzeh6yavMIOsQShxOVTbgx7LzSyymZ1nEs45yCGAiMfA==
dependencies:
"@expo/config-types" "^43.0.1"
"@expo/config-types" "^44.0.0"
"@expo/json-file" "8.2.34"
"@expo/plist" "0.0.17"
"@expo/sdk-runtime-versions" "^1.0.0"
@ -1481,6 +1481,11 @@
resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-43.0.1.tgz#3e047dccb371741a540980eaff26fb0c95039c30"
integrity sha512-EtllpCGDdB/UdwAIs5YXJwBLpbFQNdlLLrxIvoILA9cXrpQMWkeDCT9lQPJzFRMFcLUaMuGvkzX2tR4tx5EQFQ==
"@expo/config-types@^44.0.0":
version "44.0.0"
resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-44.0.0.tgz#d3480fe2c99f9e895dae4ebba58b74ed72d03e26"
integrity sha512-d+gpdKOAhqaD5RmcMzGgKzNtvE1w+GCqpFQNSXLliYlXjj+Tv0eL8EPeAdPtvke0vowpPFwd5McXLA90dgY6Jg==
"@expo/config@6.0.15":
version "6.0.15"
resolved "https://registry.yarnpkg.com/@expo/config/-/config-6.0.15.tgz#aa610f8b714e0b1103e13c8210059519479d11d6"
@ -1498,14 +1503,14 @@
slugify "^1.3.4"
sucrase "^3.20.0"
"@expo/config@6.0.16":
version "6.0.16"
resolved "https://registry.yarnpkg.com/@expo/config/-/config-6.0.16.tgz#3e603700b8d270d90718f8ca16a82e761a7f71fc"
integrity sha512-4UgBiEtj03zI9X0iUNClnY3Y7uUGSvmMNAfPTSpKOsYsb0S+mSN+B66fuB+H+12SUldqxDH695g5lpeIrkCiJA==
"@expo/config@6.0.18":
version "6.0.18"
resolved "https://registry.yarnpkg.com/@expo/config/-/config-6.0.18.tgz#fb3dd2dcf319a7ced77765c43e6825de4e14dff8"
integrity sha512-60z0YICI9eJWlvajl/Wgq8+lKm0jfkZOMdATDQtWz6GvWshUxd+A8EYhtgjJ9eWMGqOYkwrCpRNLHz5TZtCKWA==
dependencies:
"@babel/code-frame" "~7.10.4"
"@expo/config-plugins" "4.0.16"
"@expo/config-types" "^43.0.1"
"@expo/config-plugins" "4.0.18"
"@expo/config-types" "^44.0.0"
"@expo/json-file" "8.2.34"
getenv "^1.0.0"
glob "7.1.6"
@ -1791,10 +1796,10 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@react-native-async-storage/async-storage@1.15.17":
version "1.15.17"
resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.15.17.tgz#0dae263a52e476ffce871086f1fef5b8e44708eb"
integrity sha512-NQCFs47aFEch9kya/bqwdpvSdZaVRtzU7YB02L8VrmLSLpKgQH/1VwzFUBPcc1/JI1s3GU4yOLVrEbwxq+Fqcw==
"@react-native-async-storage/async-storage@1.16.1":
version "1.16.1"
resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.16.1.tgz#1dbaa9e0f9736e4ab8fc04c628bbb608fd80b068"
integrity sha512-aQ7ka+Ii1e/q+7AVFIZPt4kDeSH8b784wMDtz19Kf4A7hf+OgCHBlUQpOXsrv8XxhlBxu0hv4tfrDO15ChnV0Q==
dependencies:
merge-options "^3.0.4"
@ -1947,10 +1952,10 @@
sudo-prompt "^9.0.0"
wcwidth "^1.0.1"
"@react-native-community/netinfo@7.1.9":
version "7.1.9"
resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-7.1.9.tgz#d3042106ce720d5cda332d4afb1292c9b6156bb5"
integrity sha512-xxbxFherpOjQeJm3rIx6gmZNEEBqVDIcmBII5QVSN8zf3xAwmaT/RxT74ISYDkeSlZwv4eegpkD4QA1ky52CNg==
"@react-native-community/netinfo@8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-8.0.0.tgz#7be288c8226ce14ba61e10c6dbd63ef4a56b6ee9"
integrity sha512-8cjkbOWe55vzzc64hfjDv6GWSY8+kfEnxRbwTf9l3hFYDIUMRmMoW+SwxE+QoAfMY32nbEERDy68iev3busRFQ==
"@react-native-community/segmented-control@2.2.2":
version "2.2.2"
@ -2002,10 +2007,10 @@
resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.1.tgz#aacb08fe0ca4db50f0ed16b72906869ce770edf4"
integrity sha512-jIDRJaG8YPIinl4hZXJu/W3TnhDe8hLYmGSEdL1mxZ1aoNMiApCBYkgTy11oq0EfK/koZd3DPSkJNbzBAQmPJw==
"@react-navigation/native-stack@6.4.1":
version "6.4.1"
resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-6.4.1.tgz#089449ed9e1c8194f74373765637948289666d43"
integrity sha512-HBx2Ar9gvTvR+8BEgPJKn+NKT6qTfljEkhsRhNPaSBWzMxNBXACP29t9PoaIlOelkV4V9TvTnXe0NCHjDsObwg==
"@react-navigation/native-stack@6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-6.5.0.tgz#332ac7c736e2c2a2d961b324085957c3db79e1d2"
integrity sha512-X2sV+AKkqEl7k0ltjN4lMBfx+FsynrnUWkCTGiROyMRo4yWElK1jY3XSTsj5Cpso2/MUHdf9v/AOw0EgU58FsA==
dependencies:
"@react-navigation/elements" "^1.3.1"
warn-once "^0.1.0"
@ -4171,9 +4176,9 @@ flow-parser@^0.121.0:
integrity sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg==
follow-redirects@^1.14.4:
version "1.14.7"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
version "1.14.8"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
fontfaceobserver@^2.1.0:
version "2.1.0"
@ -6429,10 +6434,10 @@ react-freeze@^1.0.0:
resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.0.tgz#b21c65fe1783743007c8c9a2952b1c8879a77354"
integrity sha512-yQaiOqDmoKqks56LN9MTgY06O0qQHgV4FUrikH357DydArSZHQhl0BJFqGKIZoTqi8JizF9Dxhuk1FIZD6qCaw==
react-i18next@11.15.3:
version "11.15.3"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.15.3.tgz#7608fb3cacc02ac75a62fc2d68b579f140b198dd"
integrity sha512-RSUEM4So3Tu2JHV0JsZ5Yje+4nz66YViMfPZoywxOy0xyn3L7tE2CHvJ7Y9LUsrTU7vGmZ5bwb8PpjnkatdIxg==
react-i18next@11.15.4:
version "11.15.4"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.15.4.tgz#fe0c792ea93f038548e838cecae3ed4173822937"
integrity sha512-jKJNAcVcbPGK+yrTcXhLblgPY16n6NbpZZL3Mk8nswj1v3ayIiUBVDU09SgqnT+DluyQBS97hwSvPU5yVFG0yg==
dependencies:
"@babel/runtime" "^7.14.5"
html-escaper "^2.0.2"
@ -6519,15 +6524,15 @@ react-native-iphone-x-helper@^1.3.1:
resolved "https://registry.yarnpkg.com/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz#20c603e9a0e765fd6f97396638bdeb0e5a60b010"
integrity sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==
react-native-pager-view@5.4.9:
version "5.4.9"
resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-5.4.9.tgz#c0d40847cfeda5a4e729b53271b0ee0fedff3eb5"
integrity sha512-D6tzxpwMGdl6CXgtskGWhKRc5cJakCazESRGt7PkqnpyiH3N35ft1KmR82pCSQetAFlytFiToeu3a/dG5CELvA==
react-native-pager-view@5.4.11:
version "5.4.11"
resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-5.4.11.tgz#677540293c7b4e0e022efb45727ef9b4efa35409"
integrity sha512-4QlBL5W8yVjeYwrw89oCdABI7sDxIGapFQvIbukfB5mAj1Zn1IQPkBqROLblNFtQ8PbAeexXRgDT1ENWygiJ7A==
react-native-reanimated@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.4.0.tgz#660860f3c9d36dc09e80e784306591c67060f940"
integrity sha512-KogpQaRxFu2jgIEWRwoHaTQ2TQbHva49rJFnXtEdMZ5LJefVMFITiEwRuyjlD4ZWpstFiaP0mWkR6uuS2APutA==
react-native-reanimated@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.4.1.tgz#4e33876fba525ce60ac13ab3c81fc3a9f8b132fe"
integrity sha512-kvf7ylGlwa5hxMQ+wpPFjQrI2c6eexf53/xRo+dvXBNefGmSYaYR5sFtD0XMMzIPQlkCB9tJ0Pu9+2WCQUY7Cg==
dependencies:
"@babel/plugin-transform-object-assign" "^7.10.4"
"@types/invariant" "^2.2.35"
@ -6542,10 +6547,10 @@ react-native-safe-area-context@3.3.2:
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-3.3.2.tgz#9549a2ce580f2374edb05e49d661258d1b8bcaed"
integrity sha512-yOwiiPJ1rk+/nfK13eafbpW6sKW0jOnsRem2C1LPJjM3tfTof6hlvV5eWHATye3XOpu2cJ7N+HdkUvUDGwFD2Q==
react-native-screens@3.10.2:
version "3.10.2"
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.10.2.tgz#cbf505d61c09e29ad5b335309951a3bd81f0df19"
integrity sha512-bMKSpwMeqAoXBqTJiDEG1ogM1cMk66sEmpp/4dGqdX59v+OwMqPeTuBk37qaSuS7gPOFFKsNW2X3ymGvBT4iEw==
react-native-screens@3.11.1:
version "3.11.1"
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.11.1.tgz#9bca9968986ca9195cb1e7e6fca37543bde64ecb"
integrity sha512-ziQqVm97tNtovacyHwNmDwJPb8n9CqwsfttXx2p5Hk7wUWemDcPAX0ZJ/nNnGMSq2p2QPhPjjUpr3qKXuES0sQ==
dependencies:
react-freeze "^1.0.0"
warn-once "^0.1.0"
@ -6613,10 +6618,10 @@ react-native@0.66.4:
whatwg-fetch "^3.0.0"
ws "^6.1.4"
react-query@3.34.14:
version "3.34.14"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.34.14.tgz#ba944c1c36bbeab3e7037c42e8e862acd73111ff"
integrity sha512-KVMnM8omt+81oO9fPZfM65pGhQilpWzGsNwAqeeLMB2sG3xwY3bpIEYbhDf7FFgsqhAQfSzmCL4gRSiJaWIDwA==
react-query@3.34.15:
version "3.34.15"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.34.15.tgz#9e143b7d0aa39c515102a35120d5518ad91b10f6"
integrity sha512-dOhGLB5RT3p+wWj0rVdAompSg+R9t6oMRk+JhU8DP0tpJM2UyIv3r4Kk0zUkHSxT+QG34hFdrgdqxVWxgeNq4g==
dependencies:
"@babel/runtime" "^7.5.5"
broadcast-channel "^3.4.1"