1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Merge branch 'main' into candidate

This commit is contained in:
xmflsct
2022-12-18 01:29:50 +01:00
82 changed files with 1076 additions and 749 deletions

View File

@@ -29,6 +29,8 @@ Please **do not** create a pull request to update translation. tooot's translati
[@janlindblom](https://github.com/janlindblom) for Swedish
[@ihoryan](https://crowdin.com/profile/ihoryan) for Ukrainian
[@duy@mas.to](https://mas.to/@duy) for Vietnamese translation
[@jimmyorz](https://github.com/jimmyorz) for Traditional Chinese translation

View File

@@ -1,10 +1 @@
Enjoy toooting! This version includes following improvements and fixes:
- Added 🇺🇦 Slava Ukraini
- Automatic setting detected language when tooting
- Remember public timeline type selection
- Show diffing of edit history
- Allow hiding boosts and replies in home timeline
- Support toot in RTL languages
- Added notification for admins
- Fix whole word filter matching
- Fix tablet cannot delete toot drafts

View File

@@ -1,10 +1 @@
toooting愉快此版本包括以下改进和修复
- 增加 🇺🇦 Slava Ukraini
- 自动识别发嘟语言
- 记住上次公共时间轴选项
- 显示编辑历史的差异
- 关注列表可隐藏转嘟和回复
- 新增管理员推送通知
- 支持嘟文右到左文字
- 修复过滤整词功能
- 修复平板不能删除草稿

View File

@@ -303,8 +303,6 @@ PODS:
- React-Core
- react-native-language-detection (0.2.2):
- React
- react-native-live-text-image-view (0.4.0):
- React-Core
- react-native-menu (0.7.2):
- React
- react-native-netinfo (9.3.7):
@@ -428,9 +426,9 @@ PODS:
- RNScreens (3.18.2):
- React-Core
- React-RCTImage
- RNSentry (4.11.0):
- RNSentry (4.12.0):
- React-Core
- Sentry/HybridSDK (= 7.31.2)
- Sentry/HybridSDK (= 7.31.3)
- RNShareMenu (6.0.0):
- React
- RNSVG (13.6.0):
@@ -441,7 +439,7 @@ PODS:
- SDWebImageWebPCoder (0.9.1):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.13)
- Sentry/HybridSDK (7.31.2)
- Sentry/HybridSDK (7.31.3)
- Swime (3.0.6)
- Yoga (1.14.0)
@@ -495,7 +493,6 @@ DEPENDENCIES:
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
- react-native-ios-context-menu (from `../node_modules/react-native-ios-context-menu`)
- react-native-language-detection (from `../node_modules/react-native-language-detection`)
- react-native-live-text-image-view (from `../node_modules/react-native-live-text-image-view`)
- "react-native-menu (from `../node_modules/@react-native-menu/menu`)"
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
@@ -630,8 +627,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-ios-context-menu"
react-native-language-detection:
:path: "../node_modules/react-native-language-detection"
react-native-live-text-image-view:
:path: "../node_modules/react-native-live-text-image-view"
react-native-menu:
:path: "../node_modules/@react-native-menu/menu"
react-native-netinfo:
@@ -740,7 +735,6 @@ SPEC CHECKSUMS:
react-native-image-picker: bf34f3f516d139ed3e24c5f5a381a91819e349ea
react-native-ios-context-menu: b170594b4448c0cd10c79e13432216bac99de1ac
react-native-language-detection: f414937fa715108ab50a6269a3de0bcb95e4ceb0
react-native-live-text-image-view: 483bacfdba464162b8cf176bba555364f18b584c
react-native-menu: 8e172cfcf0e42e92f028e7781eddf84d430cae24
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
react-native-pager-view: 54bed894cecebe28cede54c01038d9d1e122de43
@@ -765,12 +759,12 @@ SPEC CHECKSUMS:
RNGestureHandler: 62232ba8f562f7dea5ba1b3383494eb5bf97a4d3
RNReanimated: ce445c233a6ff5600223484a88ad5704945d972a
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
RNSentry: f052387ebe939949c74c2cae0c850e76f6d14ddb
RNSentry: 4c09f4dd9740cb9b33e94303de5b6d0dbeb0737d
RNShareMenu: cb9dac548c8bf147d06f0bf07296ad51ea9f5fc3
RNSVG: 3a79c0c4992213e4f06c08e62730c5e7b9e4dc17
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
SDWebImageWebPCoder: 18503de6621dd2c420d680e33d46bf8e1d5169b0
Sentry: b15765d11769852fe78c9add942f7df60ed5dbf5
Sentry: 08884c523575ec0f6690d94ed3ccb0246a1600bf
Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b
Yoga: 99caf8d5ab45e9d637ee6e0174ec16fbbb01bcfc

View File

@@ -1,6 +1,6 @@
{
"name": "tooot",
"version": "4.7.0",
"version": "4.7.1",
"description": "tooot for Mastodon",
"author": "xmflsct <me@xmflsct.com>",
"license": "GPL-3.0-or-later",
@@ -34,14 +34,14 @@
"@react-native-community/netinfo": "9.3.7",
"@react-native-community/segmented-control": "^2.2.2",
"@react-native-menu/menu": "^0.7.2",
"@react-navigation/bottom-tabs": "^6.5.0",
"@react-navigation/native": "^6.1.0",
"@react-navigation/native-stack": "^6.9.5",
"@react-navigation/stack": "^6.3.8",
"@react-navigation/bottom-tabs": "^6.5.1",
"@react-navigation/native": "^6.1.1",
"@react-navigation/native-stack": "^6.9.6",
"@react-navigation/stack": "^6.3.9",
"@reduxjs/toolkit": "^1.9.1",
"@sentry/react-native": "4.11.0",
"@sentry/react-native": "4.12.0",
"@sharcoux/slider": "^6.1.1",
"@tanstack/react-query": "^4.19.1",
"@tanstack/react-query": "^4.20.4",
"axios": "^1.2.1",
"diff": "^5.1.0",
"expo": "^47.0.8",
@@ -61,7 +61,7 @@
"expo-store-review": "^6.0.0",
"expo-video-thumbnails": "^7.0.0",
"expo-web-browser": "~12.0.0",
"i18next": "^22.4.1",
"i18next": "^22.4.5",
"linkify-it": "^4.0.1",
"lodash": "^4.17.21",
"react": "^18.2.0",
@@ -80,7 +80,6 @@
"react-native-image-picker": "^4.10.2",
"react-native-ios-context-menu": "^1.15.1",
"react-native-language-detection": "^0.2.2",
"react-native-live-text-image-view": "^0.4.0",
"react-native-pager-view": "^6.1.2",
"react-native-reanimated": "^2.13.0",
"react-native-reanimated-zoom": "^0.3.3",
@@ -89,7 +88,7 @@
"react-native-share-menu": "^6.0.0",
"react-native-svg": "^13.6.0",
"react-native-swipe-list-view": "^3.2.9",
"react-native-tab-view": "^3.3.3",
"react-native-tab-view": "^3.3.4",
"react-redux": "^8.0.5",
"redux-persist": "^6.0.0",
"rn-placeholder": "^3.0.3",

View File

@@ -395,6 +395,7 @@ declare namespace Mastodon {
mention: boolean
poll: boolean
status: boolean
update: boolean
'admin.sign_up': boolean
'admin.report': boolean
}
@@ -405,6 +406,8 @@ declare namespace Mastodon {
id: string
following: boolean
showing_reblogs: boolean
notifying?: boolean
languages?: string[]
followed_by: boolean
blocking: boolean
blocked_by: boolean

View File

@@ -1,5 +1,5 @@
import axios from 'axios'
import { ctx, handleError, userAgent } from './helpers'
import { ctx, handleError, PagedResponse, userAgent } from './helpers'
export type Params = {
method: 'get' | 'post' | 'put' | 'delete'
@@ -19,7 +19,7 @@ const apiGeneral = async <T = unknown>({
params,
headers,
body
}: Params): Promise<{ body: T }> => {
}: Params): Promise<PagedResponse<T>> => {
console.log(
ctx.bgGreen.bold(' API general ') +
' ' +

View File

@@ -54,13 +54,19 @@ const handleError =
console.error(ctx.bold(' API '), ctx.bold('request'), error)
shouldReportToSentry && Sentry.captureMessage(config.message)
return Promise.reject()
return Promise.reject(error)
} else {
console.error(ctx.bold(' API '), ctx.bold('internal'), error?.message)
shouldReportToSentry && Sentry.captureMessage(config.message)
return Promise.reject()
return Promise.reject(error)
}
}
type LinkFormat = { id: string; isOffset: boolean }
export type PagedResponse<T = unknown> = {
body: T
links: { prev?: LinkFormat; next?: LinkFormat }
}
export { ctx, handleError, userAgent }

View File

@@ -1,6 +1,6 @@
import { RootState } from '@root/store'
import axios, { AxiosRequestConfig } from 'axios'
import { ctx, handleError, userAgent } from './helpers'
import { ctx, handleError, PagedResponse, userAgent } from './helpers'
export type Params = {
method: 'get' | 'post' | 'put' | 'delete' | 'patch'
@@ -14,12 +14,6 @@ export type Params = {
extras?: Omit<AxiosRequestConfig, 'method' | 'url' | 'params' | 'headers' | 'data'>
}
type LinkFormat = { id: string; isOffset: boolean }
export type InstanceResponse<T = unknown> = {
body: T
links: { prev?: LinkFormat; next?: LinkFormat }
}
const apiInstance = async <T = unknown>({
method,
version = 'v1',
@@ -28,7 +22,7 @@ const apiInstance = async <T = unknown>({
headers,
body,
extras
}: Params): Promise<InstanceResponse<T>> => {
}: Params): Promise<PagedResponse<T>> => {
const { store } = require('@root/store')
const state = store.getState() as RootState
const instanceActive = state.instances.instances.findIndex(instance => instance.active)

View File

@@ -1,4 +1,3 @@
import * as Sentry from '@sentry/react-native'
import { mapEnvironment } from '@utils/checkEnvironment'
import axios from 'axios'
import { ctx, handleError, userAgent } from './helpers'
@@ -37,7 +36,7 @@ const apiTooot = async <T = unknown>({
)
return axios({
timeout: method === 'post' ? 1000 * 60 : 1000 * 15,
timeout: method === 'post' ? 1000 * 60 : 1000 * 30,
method,
baseURL: `https://${TOOOT_API_DOMAIN}/`,
url: `${url}`,

View File

@@ -4,15 +4,13 @@ import React, { useMemo, useState } from 'react'
import {
AccessibilityProps,
Image,
ImageStyle,
Platform,
Pressable,
StyleProp,
StyleSheet,
View,
ViewStyle
} from 'react-native'
import FastImage from 'react-native-fast-image'
import FastImage, { ImageStyle } from 'react-native-fast-image'
import { Blurhash } from 'react-native-blurhash'
// blurhas -> if blurhash, show before any loading succeed
@@ -97,30 +95,17 @@ const GracefullyImage = ({
{...(onPress ? (hidden ? { disabled: true } : { onPress }) : { disabled: true })}
>
{uri.preview && !imageLoaded ? (
<Image
fadeDuration={0}
<FastImage
source={{ uri: uri.preview }}
style={[styles.placeholder, { backgroundColor: colors.shimmerDefault }]}
/>
) : null}
{Platform.OS === 'ios' ? (
<Image
fadeDuration={0}
source={source}
style={[{ flex: 1 }, imageStyle]}
onLoad={onLoad}
onError={onError}
/>
) : (
<FastImage
fadeDuration={0}
source={source}
// @ts-ignore
style={[{ flex: 1 }, imageStyle]}
onLoad={onLoad}
onError={onError}
/>
)}
<FastImage
source={source}
style={[{ flex: 1 }, imageStyle]}
onLoad={onLoad}
onError={onError}
/>
{blurhashView}
</Pressable>
)

View File

@@ -15,6 +15,7 @@ import { Alert, Image, KeyboardAvoidingView, Platform, TextInput, View } from 'r
import { ScrollView } from 'react-native-gesture-handler'
import { useSelector } from 'react-redux'
import { Placeholder } from 'rn-placeholder'
import validUrl from 'valid-url'
import InstanceInfo from './Info'
import CustomText from '../Text'
import { useNavigation } from '@react-navigation/native'
@@ -39,12 +40,26 @@ const ComponentInstance: React.FC<Props> = ({
const navigation = useNavigation<TabMeStackNavigationProp<'Tab-Me-Root' | 'Tab-Me-Switch'>>()
const [domain, setDomain] = useState<string>('')
const [errorCode, setErrorCode] = useState<number | null>(null)
const whitelisted: boolean =
!!domain.length &&
!!errorCode &&
!!validUrl.isHttpsUri(`https://${domain}`) &&
errorCode === 401
const dispatch = useAppDispatch()
const instances = useSelector(getInstances, () => true)
const instanceQuery = useInstanceQuery({
domain,
options: { enabled: !!domain, retry: false }
options: {
enabled: !!domain,
retry: false,
onError: err => {
if (err.status) {
setErrorCode(err.status)
}
}
}
})
const deprecateAuthFollow = useSelector(checkInstanceFeature('deprecate_auth_follow'))
@@ -146,7 +161,11 @@ const ComponentInstance: React.FC<Props> = ({
borderBottomWidth: 1,
...StyleConstants.FontStyle.M,
color: colors.primaryDefault,
borderBottomColor: instanceQuery.isError ? colors.red : colors.border,
borderBottomColor: instanceQuery.isError
? whitelisted
? colors.yellow
: colors.red
: colors.border,
...(Platform.OS === 'android' && { paddingRight: 0 })
}}
editable={false}
@@ -159,12 +178,23 @@ const ComponentInstance: React.FC<Props> = ({
...StyleConstants.FontStyle.M,
marginRight: StyleConstants.Spacing.M,
color: colors.primaryDefault,
borderBottomColor: instanceQuery.isError ? colors.red : colors.border,
borderBottomColor: instanceQuery.isError
? whitelisted
? colors.yellow
: colors.red
: colors.border,
...(Platform.OS === 'android' && { paddingLeft: 0 })
}}
onChangeText={debounce(text => setDomain(text.replace(/^http(s)?\:\/\//i, '')), 1000, {
trailing: true
})}
onChangeText={debounce(
text => {
setDomain(text.replace(/^http(s)?\:\/\//i, ''))
setErrorCode(null)
},
1000,
{
trailing: true
}
)}
autoCapitalize='none'
clearButtonMode='never'
keyboardType='url'
@@ -194,39 +224,52 @@ const ComponentInstance: React.FC<Props> = ({
type='text'
content={t('server.button')}
onPress={processUpdate}
disabled={!instanceQuery.data?.uri}
disabled={!instanceQuery.data?.uri && !whitelisted}
loading={instanceQuery.isFetching || appsMutation.isLoading}
/>
</View>
<View>
<Placeholder>
<InstanceInfo
header={t('server.information.name')}
content={instanceQuery.data?.title || undefined}
potentialWidth={2}
/>
<View style={{ flex: 1, flexDirection: 'row' }}>
{whitelisted ? (
<CustomText
fontStyle='S'
style={{
color: colors.yellow,
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
paddingTop: StyleConstants.Spacing.XS
}}
>
{t('server.whitelisted')}
</CustomText>
) : (
<Placeholder>
<InstanceInfo
style={{ alignItems: 'flex-start' }}
header={t('server.information.accounts')}
content={instanceQuery.data?.stats?.user_count?.toString() || undefined}
potentialWidth={4}
header={t('server.information.name')}
content={instanceQuery.data?.title || undefined}
potentialWidth={2}
/>
<InstanceInfo
style={{ alignItems: 'center' }}
header={t('server.information.statuses')}
content={instanceQuery.data?.stats?.status_count?.toString() || undefined}
potentialWidth={4}
/>
<InstanceInfo
style={{ alignItems: 'flex-end' }}
header={t('server.information.domains')}
content={instanceQuery.data?.stats?.domain_count?.toString() || undefined}
potentialWidth={4}
/>
</View>
</Placeholder>
<View style={{ flex: 1, flexDirection: 'row' }}>
<InstanceInfo
style={{ alignItems: 'flex-start' }}
header={t('server.information.accounts')}
content={instanceQuery.data?.stats?.user_count?.toString() || undefined}
potentialWidth={4}
/>
<InstanceInfo
style={{ alignItems: 'center' }}
header={t('server.information.statuses')}
content={instanceQuery.data?.stats?.status_count?.toString() || undefined}
potentialWidth={4}
/>
<InstanceInfo
style={{ alignItems: 'flex-end' }}
header={t('server.information.domains')}
content={instanceQuery.data?.stats?.domain_count?.toString() || undefined}
potentialWidth={4}
/>
</View>
</Placeholder>
)}
<View
style={{
flexDirection: 'row',
@@ -276,7 +319,7 @@ const ComponentInstance: React.FC<Props> = ({
style={{ color: colors.blue }}
onPress={async () =>
WebBrowser.openBrowserAsync('https://tooot.app/privacy-policy', {
browserPackage: await browserPackage()
...(await browserPackage())
})
}
/>,
@@ -285,7 +328,7 @@ const ComponentInstance: React.FC<Props> = ({
style={{ color: colors.blue }}
onPress={async () =>
WebBrowser.openBrowserAsync('https://tooot.app/terms-of-service', {
browserPackage: await browserPackage()
...(await browserPackage())
})
}
/>

View File

@@ -65,7 +65,6 @@ const MenuRow: React.FC<Props> = ({
>
<TapGestureHandler
onHandlerStateChange={async ({ nativeEvent }) => {
if (typeof iconBack !== 'string') return // Let icon back handles the gesture
if (nativeEvent.state === State.ACTIVE && !loading) {
if (screenReaderEnabled && switchOnValueChange) {
switchOnValueChange()
@@ -86,7 +85,7 @@ const MenuRow: React.FC<Props> = ({
>
<View
style={{
flexGrow: 3,
flexShrink: 3,
flexDirection: 'row',
alignItems: 'center',
marginRight: StyleConstants.Spacing.M

View File

@@ -6,120 +6,144 @@ import {
useRelationshipMutation,
useRelationshipQuery
} from '@utils/queryHooks/relationship'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { useTheme } from '@utils/styles/ThemeManager'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useQueryClient } from '@tanstack/react-query'
import { useSelector } from 'react-redux'
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
export interface Props {
id: Mastodon.Account['id']
}
const RelationshipOutgoing = React.memo(
({ id }: Props) => {
const { theme } = useTheme()
const { t } = useTranslation('componentRelationship')
const RelationshipOutgoing: React.FC<Props> = ({ id }: Props) => {
const { theme } = useTheme()
const { t } = useTranslation('componentRelationship')
const query = useRelationshipQuery({ id })
const canFollowNotify = useSelector(checkInstanceFeature('account_follow_notify'))
const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }]
const queryClient = useQueryClient()
const mutation = useRelationshipMutation({
onSuccess: (res, { payload: { action } }) => {
haptics('Success')
queryClient.setQueryData<Mastodon.Relationship[]>(queryKeyRelationship, [res])
if (action === 'block') {
const queryKey = ['Timeline', { page: 'Following' }]
queryClient.invalidateQueries({ queryKey, exact: false })
}
},
onError: (err: any, { payload: { action } }) => {
displayMessage({
theme,
type: 'error',
message: t('common:message.error.message', {
function: t(`${action}.function`)
}),
...(err.status &&
typeof err.status === 'number' &&
err.data &&
err.data.error &&
typeof err.data.error === 'string' && {
description: err.data.error
})
})
const query = useRelationshipQuery({ id })
const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }]
const queryClient = useQueryClient()
const mutation = useRelationshipMutation({
onSuccess: (res, { payload: { action } }) => {
haptics('Success')
queryClient.setQueryData<Mastodon.Relationship[]>(queryKeyRelationship, [res])
if (action === 'block') {
const queryKey = ['Timeline', { page: 'Following' }]
queryClient.invalidateQueries({ queryKey, exact: false })
}
})
},
onError: (err: any, { payload: { action } }) => {
displayMessage({
theme,
type: 'error',
message: t('common:message.error.message', {
function: t(`${action}.function`)
}),
...(err.status &&
typeof err.status === 'number' &&
err.data &&
err.data.error &&
typeof err.data.error === 'string' && {
description: err.data.error
})
})
}
})
let content: string
let onPress: () => void
let content: string
let onPress: () => void
if (query.isError) {
content = t('button.error')
if (query.isError) {
content = t('button.error')
onPress = () => {}
} else {
if (query.data?.blocked_by) {
content = t('button.blocked_by')
onPress = () => {}
} else {
if (query.data?.blocked_by) {
content = t('button.blocked_by')
onPress = () => {}
if (query.data?.blocking) {
content = t('button.blocking')
onPress = () => {
mutation.mutate({
id,
type: 'outgoing',
payload: {
action: 'block',
state: query.data?.blocking
}
})
}
} else {
if (query.data?.blocking) {
content = t('button.blocking')
if (query.data?.following) {
content = t('button.following')
onPress = () => {
mutation.mutate({
id,
type: 'outgoing',
payload: {
action: 'block',
state: query.data?.blocking
action: 'follow',
state: query.data?.following
}
})
}
} else {
if (query.data?.following) {
content = t('button.following')
if (query.data?.requested) {
content = t('button.requested')
onPress = () => {
mutation.mutate({
id,
type: 'outgoing',
payload: {
action: 'follow',
state: query.data?.following
state: query.data?.requested
}
})
}
} else {
if (query.data?.requested) {
content = t('button.requested')
onPress = () => {
mutation.mutate({
id,
type: 'outgoing',
payload: {
action: 'follow',
state: query.data?.requested
}
})
}
} else {
content = t('button.default')
onPress = () => {
mutation.mutate({
id,
type: 'outgoing',
payload: {
action: 'follow',
state: false
}
})
}
content = t('button.default')
onPress = () => {
mutation.mutate({
id,
type: 'outgoing',
payload: {
action: 'follow',
state: false
}
})
}
}
}
}
}
}
return (
return (
<>
{canFollowNotify && query.data?.following ? (
<Button
type='icon'
content={query.data.notifying ? 'BellOff' : 'Bell'}
round
onPress={() =>
mutation.mutate({
id,
type: 'outgoing',
payload: {
action: 'follow',
state: false,
notify: !query.data.notifying
}
})
}
loading={query.isLoading || mutation.isLoading}
style={{ marginRight: StyleConstants.Spacing.S }}
/>
) : null}
<Button
type='text'
content={content}
@@ -127,9 +151,8 @@ const RelationshipOutgoing = React.memo(
loading={query.isLoading || mutation.isLoading}
disabled={query.isError || query.data?.blocked_by}
/>
)
},
() => true
)
</>
)
}
export default RelationshipOutgoing

View File

@@ -34,6 +34,7 @@ export interface Props {
highlighted?: boolean
disableDetails?: boolean
disableOnPress?: boolean
isConversation?: boolean
}
// When the poll is long
@@ -43,7 +44,8 @@ const TimelineDefault: React.FC<Props> = ({
rootQueryKey,
highlighted = false,
disableDetails = false,
disableOnPress = false
disableOnPress = false,
isConversation = false
}) => {
const { colors } = useTheme()
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
@@ -53,15 +55,16 @@ const TimelineDefault: React.FC<Props> = ({
const status = item.reblog ? item.reblog : item
const ownAccount = status.account?.id === instanceAccount?.id
const [spoilerExpanded, setSpoilerExpanded] = useState(
instanceAccount?.preferences['reading:expand:spoilers'] || false
instanceAccount?.preferences?.['reading:expand:spoilers'] || false
)
const spoilerHidden = status.spoiler_text?.length
? !instanceAccount?.preferences['reading:expand:spoilers'] && !spoilerExpanded
? !instanceAccount?.preferences?.['reading:expand:spoilers'] && !spoilerExpanded
: false
const copiableContent = useRef<{ content: string; complete: boolean }>({
content: '',
complete: false
})
const detectedLanguage = useRef<string>(status.language || '')
const filtered = queryKey && shouldFilter({ copiableContent, status, queryKey })
if (queryKey && filtered && !highlighted) {
@@ -74,7 +77,7 @@ const TimelineDefault: React.FC<Props> = ({
? StyleConstants.Spacing.Global.PagePadding / 1.5
: StyleConstants.Spacing.Global.PagePadding,
backgroundColor: colors.backgroundDefault,
paddingBottom: disableDetails ? StyleConstants.Spacing.Global.PagePadding / 1.5 : 0,
paddingBottom: disableDetails ? StyleConstants.Spacing.Global.PagePadding / 1.5 : 0
}
const main = () => (
<>
@@ -137,10 +140,12 @@ const TimelineDefault: React.FC<Props> = ({
ownAccount,
spoilerHidden,
copiableContent,
detectedLanguage,
highlighted,
inThread: queryKey?.[1].page === 'Toot',
disableDetails,
disableOnPress
disableOnPress,
isConversation
}}
>
{disableOnPress ? (

View File

@@ -42,10 +42,10 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
: notification.account
const ownAccount = notification.account?.id === instanceAccount?.id
const [spoilerExpanded, setSpoilerExpanded] = useState(
instanceAccount.preferences['reading:expand:spoilers'] || false
instanceAccount.preferences?.['reading:expand:spoilers'] || false
)
const spoilerHidden = notification.status?.spoiler_text?.length
? !instanceAccount.preferences['reading:expand:spoilers'] && !spoilerExpanded
? !instanceAccount.preferences?.['reading:expand:spoilers'] && !spoilerExpanded
: false
const copiableContent = useRef<{ content: string; complete: boolean }>({
content: '',

View File

@@ -31,10 +31,10 @@ const TimelineAttachment = () => {
const account = useSelector(
getInstanceAccount,
(prev, next) =>
prev.preferences['reading:expand:media'] === next.preferences['reading:expand:media']
prev.preferences?.['reading:expand:media'] === next.preferences?.['reading:expand:media']
)
const defaultSensitive = () => {
switch (account.preferences['reading:expand:media']) {
switch (account.preferences?.['reading:expand:media']) {
case 'show_all':
return false
case 'hide_all':

View File

@@ -12,7 +12,8 @@ export interface Props {
}
const TimelineAvatar: React.FC<Props> = ({ account }) => {
const { status, highlighted, disableDetails, disableOnPress } = useContext(StatusContext)
const { status, highlighted, disableDetails, disableOnPress, isConversation } =
useContext(StatusContext)
const actualAccount = account || status?.account
if (!actualAccount) return null
@@ -34,7 +35,7 @@ const TimelineAvatar: React.FC<Props> = ({ account }) => {
}
uri={{ original: actualAccount.avatar, static: actualAccount.avatar_static }}
dimension={
disableDetails
disableDetails || isConversation
? {
width: StyleConstants.Avatar.XS,
height: StyleConstants.Avatar.XS
@@ -47,7 +48,8 @@ const TimelineAvatar: React.FC<Props> = ({ account }) => {
style={{
borderRadius: StyleConstants.Avatar.M,
overflow: 'hidden',
marginRight: StyleConstants.Spacing.S
marginRight: StyleConstants.Spacing.S,
marginLeft: isConversation ? StyleConstants.Avatar.M - StyleConstants.Avatar.XS : undefined
}}
/>
)

View File

@@ -145,19 +145,21 @@ const TimelineCard: React.FC = () => {
/>
) : null}
<View style={{ flex: 1, padding: StyleConstants.Spacing.S }}>
<CustomText
fontStyle='S'
numberOfLines={2}
style={{
marginBottom: StyleConstants.Spacing.XS,
color: colors.primaryDefault
}}
fontWeight='Bold'
testID='title'
>
{status.card?.title}
</CustomText>
{status.card?.description ? (
{status.card?.title.length ? (
<CustomText
fontStyle='S'
numberOfLines={2}
style={{
marginBottom: StyleConstants.Spacing.XS,
color: colors.primaryDefault
}}
fontWeight='Bold'
testID='title'
>
{status.card.title}
</CustomText>
) : null}
{status.card?.description.length ? (
<CustomText
fontStyle='S'
numberOfLines={1}
@@ -170,9 +172,11 @@ const TimelineCard: React.FC = () => {
{status.card.description}
</CustomText>
) : null}
<CustomText fontStyle='S' numberOfLines={1} style={{ color: colors.secondary }}>
{status.card?.url}
</CustomText>
{status.card?.url.length ? (
<CustomText fontStyle='S' numberOfLines={1} style={{ color: colors.secondary }}>
{status.card.url}
</CustomText>
) : null}
</View>
</>
)
@@ -187,10 +191,6 @@ const TimelineCard: React.FC = () => {
style={{
flex: 1,
flexDirection: 'row',
minHeight:
(isStatus && foundStatus) || (isAccount && foundAccount)
? undefined
: StyleConstants.Font.LineHeight.M * 5,
marginTop: StyleConstants.Spacing.M,
borderWidth: StyleSheet.hairlineWidth,
borderRadius: StyleConstants.Spacing.S,

View File

@@ -47,7 +47,7 @@ const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoi
mentions={status.mentions}
tags={status.tags}
numberOfLines={
instanceAccount.preferences['reading:expand:spoilers'] || inThread
instanceAccount.preferences?.['reading:expand:spoilers'] || inThread
? notificationOwnToot
? 2
: 999

View File

@@ -14,11 +14,13 @@ type ContextType = {
content: string
complete: boolean
}>
detectedLanguage?: React.MutableRefObject<string>
highlighted?: boolean
inThread?: boolean
disableDetails?: boolean
disableOnPress?: boolean
isConversation?: boolean
}
const StatusContext = createContext<ContextType>({} as ContextType)

View File

@@ -11,7 +11,7 @@ import { StyleSheet, View } from 'react-native'
import StatusContext from './Context'
const TimelineFeedback = () => {
const { status, highlighted } = useContext(StatusContext)
const { status, highlighted, detectedLanguage } = useContext(StatusContext)
if (!status || !highlighted) return null
const { t } = useTranslation('componentTimeline')
@@ -80,7 +80,12 @@ const TimelineFeedback = () => {
accessibilityHint={t('shared.actionsUsers.history.accessibilityHint')}
accessibilityRole='button'
style={[styles.text, { marginRight: 0, color: colors.blue }]}
onPress={() => navigation.push('Tab-Shared-History', { id: status.id })}
onPress={() =>
navigation.push('Tab-Shared-History', {
id: status.id,
detectedLanguage: detectedLanguage?.current || status.language || ''
})
}
>
{t('shared.actionsUsers.history.text', {
count: data.length - 1

View File

@@ -36,7 +36,12 @@ const TimelineHeaderAndroid: React.FC = () => {
{queryKey ? (
<DropdownMenu.Root onOpenChange={setOpenChange}>
<DropdownMenu.Trigger>
<View style={{ padding: StyleConstants.Spacing.L }}>
<View
style={{
padding: StyleConstants.Spacing.L,
backgroundColor: colors.backgroundDefault
}}
>
<Icon
name='MoreHorizontal'
color={colors.secondary}

View File

@@ -65,7 +65,7 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
`https://${url}/admin/reports/${notification.report.id}`,
'tooot://tooot',
{
browserPackage: await browserPackage(),
...(await browserPackage()),
dismissButtonStyle: 'done',
readerMode: false
}

View File

@@ -7,41 +7,40 @@ import { useTranslation } from 'react-i18next'
import { View } from 'react-native'
export interface Props {
account: Mastodon.Account
account?: Mastodon.Account
withoutName?: boolean // For notification follow request etc.
}
const HeaderSharedAccount = React.memo(
({ account, withoutName = false }: Props) => {
const { t } = useTranslation('componentTimeline')
const { colors } = useTheme()
const HeaderSharedAccount: React.FC<Props> = ({ account, withoutName = false }) => {
if (!account) return null
return (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{withoutName ? null : (
<CustomText
accessibilityHint={t('shared.header.shared.account.name.accessibilityHint')}
style={{ marginRight: StyleConstants.Spacing.XS }}
numberOfLines={1}
>
<ParseEmojis
content={account?.display_name || account?.username}
emojis={account?.emojis}
fontBold
/>
</CustomText>
)}
const { t } = useTranslation('componentTimeline')
const { colors } = useTheme()
return (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{withoutName ? null : (
<CustomText
accessibilityHint={t('shared.header.shared.account.account.accessibilityHint')}
style={{ flexShrink: 1, color: colors.secondary }}
accessibilityHint={t('shared.header.shared.account.name.accessibilityHint')}
style={{ marginRight: StyleConstants.Spacing.XS }}
numberOfLines={1}
>
@{account.acct}
<ParseEmojis
content={account.display_name || account.username}
emojis={account.emojis}
fontBold
/>
</CustomText>
</View>
)
},
() => true
)
)}
<CustomText
accessibilityHint={t('shared.header.shared.account.account.accessibilityHint')}
style={{ flexShrink: 1, color: colors.secondary }}
numberOfLines={1}
>
@{account.acct}
</CustomText>
</View>
)
}
export default HeaderSharedAccount

View File

@@ -13,7 +13,7 @@ import { Circle } from 'react-native-animated-spinkit'
import StatusContext from './Context'
const TimelineTranslate = () => {
const { status, highlighted, copiableContent } = useContext(StatusContext)
const { status, highlighted, copiableContent, detectedLanguage } = useContext(StatusContext)
if (!status || !highlighted) return null
const { t } = useTranslation('componentTimeline')
@@ -38,14 +38,19 @@ const TimelineTranslate = () => {
? [copiableContent?.current.content]
: backupTextProcessing()
const [detectedLanguage, setDetectedLanguage] = useState<{
const [detected, setDetected] = useState<{
language: string
confidence: number
}>({ language: status.language || '', confidence: 0 })
useEffect(() => {
const detect = async () => {
const result = await detectLanguage(text.join('\n\n'))
result && setDetectedLanguage(result)
if (result) {
setDetected(result)
if (detectedLanguage) {
detectedLanguage.current = result.language
}
}
}
detect()
}, [])
@@ -57,7 +62,7 @@ const TimelineTranslate = () => {
const [enabled, setEnabled] = useState(false)
const { refetch, data, isFetching, isSuccess, isError } = useTranslateQuery({
source: detectedLanguage.language,
source: detected.language,
target: targetLanguage,
text,
options: { enabled }
@@ -66,9 +71,9 @@ const TimelineTranslate = () => {
const devView = () => {
return __DEV__ ? (
<CustomText fontStyle='S' style={{ color: colors.secondary }}>{` Source: ${
detectedLanguage?.language
detected?.language
}; Confidence: ${
detectedLanguage?.confidence.toString().slice(0, 5) || 'null'
detected?.confidence.toString().slice(0, 5) || 'null'
}; Target: ${targetLanguage}`}</CustomText>
) : null
}
@@ -78,13 +83,13 @@ const TimelineTranslate = () => {
}
if (
Platform.OS === 'ios' &&
Localization.locale.slice(0, 2).includes(detectedLanguage.language.slice(0, 2))
Localization.locale.slice(0, 2).includes(detected.language.slice(0, 2))
) {
return devView()
}
if (
Platform.OS === 'android' &&
settingsLanguage?.slice(0, 2).includes(detectedLanguage.language.slice(0, 2))
settingsLanguage?.slice(0, 2).includes(detected.language.slice(0, 2))
) {
return devView()
}

View File

@@ -41,7 +41,7 @@ const menuAccount = ({
const menus: ContextMenu[][] = [[]]
const instanceAccount = useSelector(getInstanceAccount, (prev, next) => prev.id === next.id)
const instanceAccount = useSelector(getInstanceAccount)
const ownAccount = instanceAccount?.id === account.id
const [enabled, setEnabled] = useState(openChange)

View File

@@ -17,7 +17,6 @@ const openLink = async (url: string, navigation?: any) => {
const handleNavigation = (page: 'Tab-Shared-Toot' | 'Tab-Shared-Account', options: {}) => {
if (navigation) {
// @ts-ignore
navigation.push(page, options)
} else {
// @ts-ignore
@@ -93,7 +92,7 @@ const openLink = async (url: string, navigation?: any) => {
await WebBrowser.openBrowserAsync(encodeURI(url), {
dismissButtonStyle: 'close',
enableBarCollapsing: true,
browserPackage: await browserPackage()
...(await browserPackage())
})
break
case 'external':

View File

@@ -1,16 +1,18 @@
import * as WebBrowser from 'expo-web-browser'
import { Platform } from 'react-native'
const browserPackage = async () => {
let browserPackage: string | undefined
const browserPackage = async (): Promise<{ browserPackage?: string }> => {
if (Platform.OS === 'android') {
const tabsSupportingBrowsers = await WebBrowser.getCustomTabsSupportingBrowsersAsync()
browserPackage =
tabsSupportingBrowsers?.preferredBrowserPackage ||
tabsSupportingBrowsers.browserPackages[0] ||
tabsSupportingBrowsers.servicePackages[0]
return {
browserPackage:
tabsSupportingBrowsers?.preferredBrowserPackage ||
tabsSupportingBrowsers.browserPackages[0] ||
tabsSupportingBrowsers.servicePackages[0]
}
} else {
return {}
}
return browserPackage
}
export default browserPackage

View File

@@ -1,47 +1,46 @@
[
{
"feature": "account_follow_notify",
"version": 3.3
},
{
"feature": "notification_type_status",
"version": 3.3
},
{
"feature": "account_return_suspended",
"version": 3.3,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.3.0"
"version": 3.3
},
{
"feature": "edit_post",
"version": 3.5,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
"version": 3.5
},
{
"feature": "deprecate_auth_follow",
"version": 3.5,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
"version": 3.5
},
{
"feature": "notification_type_update",
"version": 3.5,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
"version": 3.5
},
{
"feature": "notification_type_admin_signup",
"version": 3.5,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
"version": 3.5
},
{
"feature": "notification_types_positive_filter",
"version": 3.5,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
"version": 3.5
},
{
"feature": "trends_new_path",
"version": 3.5,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
"version": 3.5
},
{
"feature": "follow_tags",
"version": 4.0,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v4.0.0"
"version": 4.0
},
{
"feature": "notification_type_admin_report",
"version": 4.0,
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
"version": 4.0
}
]

View File

@@ -231,6 +231,9 @@
"status": {
"heading": "Publicació d'usuaris subscrits"
},
"update": {
"heading": "L'impuls ha sigut editat"
},
"admin.sign_up": {
"heading": "Administració: Registra"
},
@@ -348,7 +351,7 @@
"notInLists": "Altres llistes"
},
"attachments": {
"name": ""
"name": "Multimèdia de <0 /><1></1>"
},
"hashtag": {
"follow": "Segueix",
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} impulsats",
"favourited_by": "{{count}} favorits"
}
},
"resultIncomplete": ""
}
}
}

View File

@@ -231,6 +231,9 @@
"status": {
"heading": ""
},
"update": {
"heading": ""
},
"admin.sign_up": {
"heading": ""
},
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "",
"favourited_by": ""
}
},
"resultIncomplete": ""
}
}
}

View File

@@ -55,7 +55,7 @@
"accessibilityLabel": "Lesezeichen hinzufügen",
"function": "Lesezeichen setzen"
},
"openReport": ""
"openReport": "Meldung öffnen"
},
"actionsUsers": {
"reblogged_by": {

View File

@@ -3,8 +3,8 @@
"local": {
"name": "Gefolgt",
"options": {
"showBoosts": "",
"showReplies": ""
"showBoosts": "Boosts anzeigen",
"showReplies": "Antworten anzeigen"
}
},
"public": {
@@ -231,6 +231,9 @@
"status": {
"heading": "Toot eines abonnierten Nutzers"
},
"update": {
"heading": "Boost wurde bearbeitet"
},
"admin.sign_up": {
"heading": "Admin: Registrierung"
},
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} mal geboosted",
"favourited_by": "{{count}} mal geherzt"
}
},
"resultIncomplete": "Ergebnisse einer Instanz sind unvollständig"
}
}
}

View File

@@ -3,6 +3,7 @@
"textInput": {
"placeholder": "Instance's domain"
},
"whitelisted": "This may be a whitelisted instance that tooot cannot retrieve data from before logging in.",
"button": "Login",
"information": {
"name": "Name",

View File

@@ -231,6 +231,9 @@
"status": {
"heading": "Toot from subscribed users"
},
"update": {
"heading": "Reblog has been edited"
},
"admin.sign_up": {
"heading": "Admin: sign up"
},

View File

@@ -3,8 +3,8 @@
"local": {
"name": "Siguiendo",
"options": {
"showBoosts": "",
"showReplies": ""
"showBoosts": "Mostrar retoots",
"showReplies": "Mostrar respuestas"
}
},
"public": {
@@ -231,6 +231,9 @@
"status": {
"heading": "Toot de usuarios suscritos"
},
"update": {
"heading": "El impulso ha sido editado"
},
"admin.sign_up": {
"heading": "Administración: Registrarse"
},
@@ -348,7 +351,7 @@
"notInLists": "Otras listas"
},
"attachments": {
"name": ""
"name": "Multimedia de <0 /><1></1>"
},
"hashtag": {
"follow": "Seguir",
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} impulsados",
"favourited_by": "{{count}} favoritos"
}
},
"resultIncomplete": ""
}
}
}

View File

@@ -231,6 +231,9 @@
"status": {
"heading": "Pouet des utilisateurs inscrits"
},
"update": {
"heading": ""
},
"admin.sign_up": {
"heading": ""
},
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} boosté",
"favourited_by": "{{count}} mis en favori"
}
},
"resultIncomplete": ""
}
}
}

View File

@@ -231,6 +231,9 @@
"status": {
"heading": "Toot da utenti seguiti"
},
"update": {
"heading": "Il link è stato modificato"
},
"admin.sign_up": {
"heading": ""
},
@@ -348,7 +351,7 @@
"notInLists": ""
},
"attachments": {
"name": ""
"name": "Media di <0 /><1>\"</1>"
},
"hashtag": {
"follow": "Segui",
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} ricondivisioni",
"favourited_by": "{{count}} apprezzamenti"
}
},
"resultIncomplete": ""
}
}
}

View File

@@ -231,6 +231,9 @@
"status": {
"heading": "購読したユーザーのトゥート"
},
"update": {
"heading": "ブーストしたトゥートが編集されました"
},
"admin.sign_up": {
"heading": ""
},
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} ブースト",
"favourited_by": "{{count}} お気に入り"
}
},
"resultIncomplete": ""
}
}
}

View File

@@ -70,7 +70,7 @@
"name": "{{list}} 리스트의 사용자"
},
"listAdd": {
"name": ""
"name": "목록 만들기"
},
"listEdit": {
"name": "리스트 상세 편집"
@@ -231,6 +231,9 @@
"status": {
"heading": "구독한 사용자의 툿"
},
"update": {
"heading": "부스트한 툿이 수정됨"
},
"admin.sign_up": {
"heading": ""
},
@@ -348,7 +351,7 @@
"notInLists": "다른 리스트"
},
"attachments": {
"name": ""
"name": "<0 /><1>의 미디어</1>"
},
"hashtag": {
"follow": "팔로우",
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} 부스트",
"favourited_by": "{{count}} 즐겨찾기"
}
},
"resultIncomplete": ""
}
}
}

View File

@@ -3,8 +3,8 @@
"local": {
"name": "Volgend",
"options": {
"showBoosts": "",
"showReplies": ""
"showBoosts": "Boosts tonen",
"showReplies": "Reacties tonen"
}
},
"public": {
@@ -231,6 +231,9 @@
"status": {
"heading": "Toot van geabonneerde gebruikers"
},
"update": {
"heading": "De reblog is bewerkt"
},
"admin.sign_up": {
"heading": "Admin: registreren"
},
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} boostten",
"favourited_by": "{{count}} markeerden als favoriet"
}
},
"resultIncomplete": "Resultaten van een externe instantie zijn onvolledig"
}
}
}

View File

@@ -8,58 +8,58 @@
},
"inLists": "",
"mute": {
"action_false": "",
"action_true": ""
"action_false": "Wycisz użytkownika",
"action_true": "Wyłącz wyciszenie"
},
"block": {
"action_false": "",
"action_true": ""
"action_false": "Zablokuj użytkownika",
"action_true": "Odblokuj użytkownika"
},
"reports": {
"action": "Zgłoś i zablokuj"
}
},
"at": {
"direct": "",
"public": ""
"direct": "Bezpośrednia wiadomość",
"public": "Wiadomość publiczna"
},
"copy": {
"action": "",
"succeed": ""
"action": "Skopiuj wpis",
"succeed": "Skopiowano"
},
"instance": {
"title": "",
"block": {
"action": "",
"action": "Zablokuj instancję {{instance}}",
"alert": {
"title": "",
"message": "",
"title": "Na pewno zablokować {{instance}}?",
"message": "Zazwyczaj wycisza się (albo blokuje) konkretnych użytkowników. \n\nGdy zablokujesz instancję, cała jej zawartość (włączając np. obserwujące Cię osoby, które do niej należą) zostanie usunięta!",
"buttons": {
"confirm": ""
"confirm": "Na pewno?"
}
}
}
},
"share": {
"status": {
"action": ""
"action": "Udostępnij wpis"
},
"account": {
"action": ""
"action": "Udostępnij konto"
}
},
"status": {
"title": "",
"edit": {
"action": ""
"action": "Edytuj wpis"
},
"delete": {
"action": "",
"action": "Usuń wpis",
"alert": {
"title": "",
"message": "",
"title": "Na pewno usunąć?",
"message": "Wszystkie podbite i polubione wpisy zostaną wyczyszczone - wraz z odpowiedziami.",
"buttons": {
"confirm": ""
"confirm": "Na pewno?"
}
}
},
@@ -78,8 +78,8 @@
"action_true": ""
},
"pin": {
"action_false": "",
"action_true": ""
"action_false": "Przypnij wpis",
"action_true": "Odepnij wpis"
}
}
}

View File

@@ -3,8 +3,8 @@
"local": {
"name": "",
"options": {
"showBoosts": "",
"showReplies": ""
"showBoosts": "Pokaż podbicia",
"showReplies": "Pokaż odpowiedzi"
}
},
"public": {
@@ -231,6 +231,9 @@
"status": {
"heading": ""
},
"update": {
"heading": ""
},
"admin.sign_up": {
"heading": ""
},
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "",
"favourited_by": ""
}
},
"resultIncomplete": ""
}
}
}

View File

@@ -231,6 +231,9 @@
"status": {
"heading": "Toot de usuários inscritos"
},
"update": {
"heading": "Toot foi editado"
},
"admin.sign_up": {
"heading": ""
},
@@ -348,7 +351,7 @@
"notInLists": ""
},
"attachments": {
"name": ""
"name": "<0 /><1>\"s mídia</1>"
},
"hashtag": {
"follow": "Seguir",
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} boostou",
"favourited_by": "{{count}} favoritados"
}
},
"resultIncomplete": ""
}
}
}

View File

@@ -231,6 +231,9 @@
"status": {
"heading": "Inlägg från följda användare"
},
"update": {
"heading": "Boosten har redigerats"
},
"admin.sign_up": {
"heading": "Admin: registrera dig"
},
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} boostade",
"favourited_by": "{{count}} favoriter"
}
},
"resultIncomplete": "Resultat från fjärrinstans är ofullständiga"
}
}
}

View File

@@ -231,6 +231,9 @@
"status": {
"heading": "Дмух від підписників"
},
"update": {
"heading": "Передмух був відредагований"
},
"admin.sign_up": {
"heading": "Адмін: реєстрація"
},
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} передмухів",
"favourited_by": "{{count}} улюблених"
}
},
"resultIncomplete": "Результати з віддаленого інстанса неповні"
}
}
}

View File

@@ -3,8 +3,8 @@
"local": {
"name": "Đang theo dõi",
"options": {
"showBoosts": "",
"showReplies": ""
"showBoosts": "Hiện lượt đăng lại",
"showReplies": "Hiện lượt trả lời"
}
},
"public": {
@@ -231,6 +231,9 @@
"status": {
"heading": "Tút từ người đã theo dõi"
},
"update": {
"heading": "Đăng lại đã được sửa"
},
"admin.sign_up": {
"heading": "Admin: đăng ký"
},
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} đăng lại",
"favourited_by": "{{count}} thích"
}
},
"resultIncomplete": "Kết quả từ máy chủ khác luôn không đầy đủ"
}
}
}

View File

@@ -231,6 +231,9 @@
"status": {
"heading": "订阅用户的嘟文"
},
"update": {
"heading": "转嘟被编辑"
},
"admin.sign_up": {
"heading": "管理员:用户注册"
},
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} 人转嘟",
"favourited_by": "{{count}} 人喜欢"
}
},
"resultIncomplete": "来自远程实例的结果不完整"
}
}
}

View File

@@ -31,8 +31,8 @@
"notification": "{{name}} 轉嘟了您的嘟文"
},
"update": "轉嘟已編輯",
"admin.sign_up": "",
"admin.report": ""
"admin.sign_up": "{{name}} 加入了站點",
"admin.report": "{{name}} 檢舉:"
},
"actions": {
"reply": {
@@ -55,7 +55,7 @@
"accessibilityLabel": "將嘟文加入書籤",
"function": "加入書籤"
},
"openReport": ""
"openReport": "打開檢舉"
},
"actionsUsers": {
"reblogged_by": {

View File

@@ -3,8 +3,8 @@
"local": {
"name": "跟隨中",
"options": {
"showBoosts": "",
"showReplies": ""
"showBoosts": "顯示轉嘟",
"showReplies": "顯示回覆"
}
},
"public": {
@@ -231,11 +231,14 @@
"status": {
"heading": "訂閱使用者的嘟文"
},
"update": {
"heading": "轉嘟被編輯"
},
"admin.sign_up": {
"heading": "管理員:註冊"
"heading": "管理員:使用者註冊"
},
"admin.report": {
"heading": "管理員:回報"
"heading": "管理員:檢舉"
},
"howitworks": "了解通知訊息轉發如何工作"
},
@@ -395,7 +398,8 @@
"statuses": {
"reblogged_by": "{{count}} 人轉嘟",
"favourited_by": "{{count}} 人喜歡"
}
},
"resultIncomplete": "來自遠端站點的結果不完整"
}
}
}

View File

@@ -286,7 +286,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
type: 'editItem',
queryKey: params.queryKey,
rootQueryKey: params.rootQueryKey,
status: res.body
status: res
})
break
case 'deleteEdit':

View File

@@ -7,9 +7,8 @@ import { ComposeState } from './types'
const assignVisibility = (
target: ComposeState['visibility']
): Pick<ComposeState, 'visibility' | 'visibilityLock'> => {
const accountPreference = getInstanceAccount(store.getState())?.preferences[
'posting:default:visibility'
]
const accountPreference =
getInstanceAccount(store.getState())?.preferences?.['posting:default:visibility'] || 'public'
switch (target) {
case 'direct':

View File

@@ -1,4 +1,4 @@
import apiInstance, { InstanceResponse } from '@api/instance'
import apiInstance from '@api/instance'
import detectLanguage from '@helpers/detectLanguage'
import { ComposeState } from '@screens/Compose/utils/types'
import { RootStackParamList } from '@utils/navigation/navigators'
@@ -8,7 +8,7 @@ import { getPureContent } from './processText'
const composePost = async (
params: RootStackParamList['Screen-Compose'],
composeState: ComposeState
): Promise<InstanceResponse<Mastodon.Status>> => {
): Promise<Mastodon.Status> => {
const formData = new FormData()
const detectedLanguage = await detectLanguage(
@@ -74,7 +74,7 @@ const composePost = async (
)
},
body: formData
})
}).then(res => res.body)
}
export default composePost

View File

@@ -16,7 +16,6 @@ import {
ViewToken
} from 'react-native'
import { Directions, Gesture, LongPressGestureHandler } from 'react-native-gesture-handler'
import { LiveTextImageView } from 'react-native-live-text-image-view'
import { runOnJS, useSharedValue } from 'react-native-reanimated'
import { Zoom, createZoomListComponent } from 'react-native-reanimated-zoom'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
@@ -117,22 +116,19 @@ const ScreenImagesViewer = ({
justifyContent: 'center'
}}
>
<LiveTextImageView>
<GracefullyImage
uri={{ preview: item.preview_url, remote: item.remote_url, original: item.url }}
blurhash={item.blurhash}
dimension={{
width:
screenRatio > imageRatio
? (SCREEN_HEIGHT / imageHeight) * imageWidth
: SCREEN_WIDTH,
height:
screenRatio > imageRatio
? SCREEN_HEIGHT
: (SCREEN_WIDTH / imageWidth) * imageHeight
}}
/>
</LiveTextImageView>
<GracefullyImage
uri={{ preview: item.preview_url, remote: item.remote_url, original: item.url }}
dimension={{
width:
screenRatio > imageRatio
? (SCREEN_HEIGHT / imageHeight) * imageWidth
: SCREEN_WIDTH,
height:
screenRatio > imageRatio
? SCREEN_HEIGHT
: (SCREEN_WIDTH / imageWidth) * imageHeight
}}
/>
</View>
}
/>

View File

@@ -3,13 +3,17 @@ import Icon from '@components/Icon'
import { MenuContainer, MenuRow } from '@components/Menu'
import CustomText from '@components/Text'
import browserPackage from '@helpers/browserPackage'
import { checkPermission } from '@helpers/permissions'
import { useAppDispatch } from '@root/store'
import { isDevelopment } from '@utils/checkEnvironment'
import { useAppsQuery } from '@utils/queryHooks/apps'
import { useProfileQuery } from '@utils/queryHooks/profile'
import { getExpoToken, retrieveExpoToken } from '@utils/slices/appSlice'
import { PUSH_ADMIN, PUSH_DEFAULT, setChannels } from '@utils/slices/instances/push/utils'
import {
PUSH_ADMIN,
PUSH_DEFAULT,
setChannels,
usePushFeatures
} from '@utils/slices/instances/push/utils'
import { updateInstancePush } from '@utils/slices/instances/updatePush'
import { updateInstancePushAlert } from '@utils/slices/instances/updatePushAlert'
import { updateInstancePushDecode } from '@utils/slices/instances/updatePushDecode'
@@ -73,9 +77,11 @@ const TabMePush: React.FC = () => {
}
}, [appsQuery.data?.vapid_key])
const pushFeatures = usePushFeatures()
const alerts = () =>
instancePush?.alerts
? PUSH_DEFAULT.map(alert => (
? PUSH_DEFAULT(pushFeatures).map(alert => (
<MenuRow
key={alert}
title={t(`me.push.${alert}.heading`)}
@@ -86,7 +92,7 @@ const TabMePush: React.FC = () => {
updateInstancePushAlert({
alerts: {
...instancePush?.alerts,
[alert]: instancePush?.alerts[alert]
[alert]: !instancePush?.alerts[alert]
}
})
)
@@ -98,26 +104,24 @@ const TabMePush: React.FC = () => {
const profileQuery = useProfileQuery()
const adminAlerts = () =>
profileQuery.data?.role?.permissions
? PUSH_ADMIN.map(({ type, permission }) =>
checkPermission(permission, profileQuery.data.role?.permissions) ? (
<MenuRow
key={type}
title={t(`me.push.${type}.heading`)}
switchDisabled={!pushEnabled || !instancePush.global}
switchValue={instancePush?.alerts[type]}
switchOnValueChange={() =>
dispatch(
updateInstancePushAlert({
alerts: {
...instancePush?.alerts,
[type]: instancePush?.alerts[type]
}
})
)
}
/>
) : null
)
? PUSH_ADMIN(pushFeatures, profileQuery.data?.role?.permissions).map(({ type }) => (
<MenuRow
key={type}
title={t(`me.push.${type}.heading`)}
switchDisabled={!pushEnabled || !instancePush.global}
switchValue={instancePush?.alerts[type]}
switchOnValueChange={() =>
dispatch(
updateInstancePushAlert({
alerts: {
...instancePush?.alerts,
[type]: !instancePush?.alerts[type]
}
})
)
}
/>
))
: null
return (
@@ -164,7 +168,6 @@ const TabMePush: React.FC = () => {
<MenuRow
title={t('me.push.decode.heading')}
description={t('me.push.decode.description')}
loading={instancePush?.decode}
switchDisabled={!pushEnabled || !instancePush?.global}
switchValue={instancePush?.decode}
switchOnValueChange={() =>
@@ -176,7 +179,7 @@ const TabMePush: React.FC = () => {
iconBack='ExternalLink'
onPress={async () =>
WebBrowser.openBrowserAsync('https://tooot.app/how-push-works', {
browserPackage: await browserPackage()
...(await browserPackage())
})
}
/>

View File

@@ -4,7 +4,11 @@ import { useAppDispatch } from '@root/store'
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
import { useListsQuery } from '@utils/queryHooks/lists'
import { useFollowedTagsQuery } from '@utils/queryHooks/tags'
import { getInstanceMePage, updateInstanceMePage } from '@utils/slices/instancesSlice'
import {
checkInstanceFeature,
getInstanceMePage,
updateInstanceMePage
} from '@utils/slices/instancesSlice'
import { getInstancePush } from '@utils/slices/instancesSlice'
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
@@ -17,8 +21,10 @@ const Collections: React.FC = () => {
const dispatch = useAppDispatch()
const mePage = useSelector(getInstanceMePage)
const canFollowTags = useSelector(checkInstanceFeature('follow_tags'))
useFollowedTagsQuery({
options: {
enabled: canFollowTags,
onSuccess: data =>
dispatch(
updateInstanceMePage({

View File

@@ -31,7 +31,7 @@ const Settings: React.FC = () => {
`https://${url}/settings/preferences`,
'tooot://tooot',
{
browserPackage: await browserPackage(),
...(await browserPackage()),
dismissButtonStyle: 'done',
readerMode: false
}

View File

@@ -64,7 +64,7 @@ const SettingsTooot: React.FC = () => {
})
} else {
WebBrowser.openBrowserAsync('https://social.xmflsct.com/@tooot', {
browserPackage: await browserPackage()
...(await browserPackage())
})
}
}}

View File

@@ -1,10 +1,11 @@
import haptics from '@components/haptics'
import { MenuContainer, MenuRow } from '@components/Menu'
import { MenuRow } from '@components/Menu'
import { LOCALES } from '@root/i18n/locales'
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
import { setChannels } from '@utils/slices/instances/push/utils'
import { getInstances } from '@utils/slices/instancesSlice'
import { changeLanguage } from '@utils/slices/settingsSlice'
import { StyleConstants } from '@utils/styles/constants'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { FlatList, Platform } from 'react-native'
@@ -33,22 +34,21 @@ const TabMeSettingsLanguage: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Lan
}
return (
<MenuContainer>
<FlatList
data={languages}
renderItem={({ item }) => {
return (
<MenuRow
key={item[0]}
title={item[1]}
iconBack={item[0] === i18n.language ? 'Check' : undefined}
iconBackColor={'blue'}
onPress={() => item[0] !== i18n.language && change(item[0])}
/>
)
}}
/>
</MenuContainer>
<FlatList
style={{ flex: 1, paddingHorizontal: StyleConstants.Spacing.Global.PagePadding }}
data={languages}
renderItem={({ item }) => {
return (
<MenuRow
key={item[0]}
title={item[1]}
iconBack={item[0] === i18n.language ? 'Check' : undefined}
iconBackColor={'blue'}
onPress={() => item[0] !== i18n.language && change(item[0])}
/>
)
}}
/>
)
}

View File

@@ -1,17 +1,12 @@
import { HeaderLeft, HeaderRight } from '@components/Header'
import { MenuContainer, MenuRow } from '@components/Menu'
import {
checkPermission,
PERMISSION_MANAGE_REPORTS,
PERMISSION_MANAGE_USERS
} from '@helpers/permissions'
import { useAppDispatch } from '@root/store'
import { useQueryClient } from '@tanstack/react-query'
import { TabNotificationsStackScreenProps } from '@utils/navigation/navigators'
import { useProfileQuery } from '@utils/queryHooks/profile'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { PUSH_ADMIN, PUSH_DEFAULT, usePushFeatures } from '@utils/slices/instances/push/utils'
import {
checkInstanceFeature,
getInstanceNotificationsFilter,
updateInstanceNotificationsFilter
} from '@utils/slices/instancesSlice'
@@ -22,32 +17,12 @@ import { Alert } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'
import { useSelector } from 'react-redux'
export const NOTIFICATIONS_FILTERS_DEFAULT: [
'follow',
'follow_request',
'favourite',
'reblog',
'mention',
'poll',
'status',
'update'
] = ['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll', 'status', 'update']
export const NOTIFICATIONS_FILTERS_ADMIN: {
type: 'admin.sign_up' | 'admin.report'
permission: number
}[] = [
{ type: 'admin.sign_up', permission: PERMISSION_MANAGE_USERS },
{ type: 'admin.report', permission: PERMISSION_MANAGE_REPORTS }
]
const TabNotificationsFilters: React.FC<
TabNotificationsStackScreenProps<'Tab-Notifications-Filters'>
> = ({ navigation }) => {
const { t } = useTranslation('screenTabs')
const hasTypeStatus = useSelector(checkInstanceFeature('notification_type_status'))
const hasTypeUpdate = useSelector(checkInstanceFeature('notification_type_update'))
const pushFeatures = usePushFeatures()
const dispatch = useAppDispatch()
@@ -103,16 +78,7 @@ const TabNotificationsFilters: React.FC<
return (
<ScrollView style={{ flex: 1 }}>
<MenuContainer>
{NOTIFICATIONS_FILTERS_DEFAULT.filter(type => {
switch (type) {
case 'status':
return hasTypeStatus
case 'update':
return hasTypeUpdate
default:
return true
}
}).map((type, index) => (
{PUSH_DEFAULT(pushFeatures).map((type, index) => (
<MenuRow
key={index}
title={t(`notifications.filters.options.${type}`)}
@@ -120,9 +86,7 @@ const TabNotificationsFilters: React.FC<
switchOnValueChange={() => setFilters({ ...filters, [type]: !filters[type] })}
/>
))}
{NOTIFICATIONS_FILTERS_ADMIN.filter(({ permission }) =>
checkPermission(permission, profileQuery.data?.role?.permissions)
).map(({ type }) => (
{PUSH_ADMIN(pushFeatures, profileQuery.data?.role?.permissions).map(({ type }) => (
<MenuRow
key={type}
title={t(`notifications.filters.options.${type}`)}

View File

@@ -11,25 +11,45 @@ import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
import { useStatusHistory } from '@utils/queryHooks/statusesHistory'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { diffWords } from 'diff'
import { diffChars, diffWords } from 'diff'
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { FlatList, View } from 'react-native'
const SCRIPTS_WITHOUT_BOUNDARIES = [
'my',
'zh',
'ja',
'kar',
'km',
'lp',
'phag',
'pwo',
'kar',
'lana',
'th',
'bo'
]
const ContentView: React.FC<{
withoutBoundary: boolean
item: Mastodon.StatusHistory
prevItem?: Mastodon.StatusHistory
}> = ({ item, prevItem }) => {
}> = ({ withoutBoundary, item, prevItem }) => {
const { colors } = useTheme()
const changesSpoiler = diffWords(
removeHTML(prevItem?.spoiler_text || item.spoiler_text || ''),
removeHTML(item.spoiler_text || '')
)
const changesContent = diffWords(
removeHTML(prevItem?.content || item.content),
removeHTML(item.content)
)
const changesSpoiler = withoutBoundary
? diffChars(
removeHTML(prevItem?.spoiler_text || item.spoiler_text || ''),
removeHTML(item.spoiler_text || '')
)
: diffWords(
removeHTML(prevItem?.spoiler_text || item.spoiler_text || ''),
removeHTML(item.spoiler_text || '')
)
const changesContent = withoutBoundary
? diffChars(removeHTML(prevItem?.content || item.content), removeHTML(item.content))
: diffWords(removeHTML(prevItem?.content || item.content), removeHTML(item.content))
return (
// @ts-ignore
@@ -91,7 +111,7 @@ const ContentView: React.FC<{
const TabSharedHistory: React.FC<TabSharedStackScreenProps<'Tab-Shared-History'>> = ({
navigation,
route: {
params: { id }
params: { id, detectedLanguage }
}
}) => {
const { t } = useTranslation('screenTabs')
@@ -106,12 +126,20 @@ const TabSharedHistory: React.FC<TabSharedStackScreenProps<'Tab-Shared-History'>
const dataReversed = data ? [...data].reverse() : []
const withoutBoundary = !!SCRIPTS_WITHOUT_BOUNDARIES.filter(script =>
detectedLanguage?.toLocaleLowerCase().startsWith(script)
).length
return (
<FlatList
style={{ flex: 1, minHeight: '100%' }}
data={dataReversed}
renderItem={({ item, index }) => (
<ContentView item={item} prevItem={dataReversed[index + 1]} />
<ContentView
withoutBoundary={withoutBoundary}
item={item}
prevItem={dataReversed[index + 1]}
/>
)}
ItemSeparatorComponent={ComponentSeparator}
/>

View File

@@ -49,7 +49,6 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
<TextInput
ref={inputRef}
accessibilityRole='search'
keyboardAppearance={mode}
style={{
fontSize: StyleConstants.Font.Size.M,
flex: 1,
@@ -60,7 +59,6 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
borderBottomColor: colors.border,
borderBottomWidth: 1
}}
autoFocus
onChangeText={debounce(
text => {
setSearchTerm(text)
@@ -82,6 +80,13 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
}
})
}, [mode])
useEffect(() => {
const unsubscribe = navigation.addListener('transitionEnd', e => {
inputRef.current?.focus()
})
return unsubscribe
}, [navigation])
const mapKeyToTranslations = {
accounts: t('shared.search.sections.accounts'),

View File

@@ -34,6 +34,10 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
queryKey,
enabled: false
})
const replyLevels = useRef<{ id: string; level: number }[]>([])
const data = useRef<Mastodon.Status[]>()
const highlightIndex = useRef<number>(0)
useEffect(() => {
return observer.subscribe(result => {
if (result.isSuccess) {
@@ -46,6 +50,24 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
navigation.goBack()
return
}
data.current = flattenData
highlightIndex.current = flattenData.findIndex(({ id }) => id === toot.id)
for (const [index, status] of flattenData.entries()) {
if (status.id === toot.id) continue
if (status.in_reply_to_id === toot.id) continue
if (!replyLevels.current.find(reply => reply.id === status.in_reply_to_id)) {
const prevLevel =
replyLevels.current.find(reply => reply.id === flattenData[index - 1].in_reply_to_id)
?.level || 0
replyLevels.current.push({
id: status.in_reply_to_id,
level: prevLevel + 1
})
}
}
setItemsLength(flattenData.length)
if (!scrolled.current) {
scrolled.current = true
@@ -68,7 +90,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
}
}
})
}, [scrolled.current])
}, [scrolled.current, replyLevels.current])
return (
<Timeline
@@ -76,14 +98,94 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
queryKey={queryKey}
queryOptions={{ staleTime: 0, refetchOnMount: true }}
customProps={{
renderItem: ({ item }) => (
<TimelineDefault
item={item}
queryKey={queryKey}
rootQueryKey={rootQueryKey}
highlighted={toot.id === item.id}
/>
),
renderItem: ({ item }) => {
return (
<TimelineDefault
item={item}
queryKey={queryKey}
rootQueryKey={rootQueryKey}
highlighted={toot.id === item.id}
/>
)
},
// renderItem: ({ item, index }) => {
// const levels = {
// previous:
// replyLevels.current.find(
// reply => reply.id === data.current?.[index - 1]?.in_reply_to_id
// )?.level || 0,
// current:
// replyLevels.current.find(reply => reply.id === item.in_reply_to_id)?.level || 0,
// next:
// replyLevels.current.find(
// reply => reply.id === data.current?.[index + 1]?.in_reply_to_id
// )?.level || 0
// }
// return (
// <>
// <TimelineDefault
// item={item}
// queryKey={queryKey}
// rootQueryKey={rootQueryKey}
// highlighted={toot.id === item.id}
// isConversation={toot.id !== item.id}
// />
// {Array.from(Array(levels.current)).map((_, i) => {
// if (index < highlightIndex.current) return null
// if (
// levels.previous + 1 === levels.current ||
// (levels.previous && levels.current && levels.previous === levels.current)
// ) {
// return (
// <View
// key={i}
// style={{
// position: 'absolute',
// top: 0,
// left: StyleConstants.Spacing.Global.PagePadding / 2 + 8 * i,
// height:
// levels.current === levels.next
// ? StyleConstants.Spacing.Global.PagePadding
// : StyleConstants.Spacing.Global.PagePadding + StyleConstants.Avatar.XS,
// borderLeftColor: colors.border,
// borderLeftWidth: 1
// }}
// />
// )
// } else {
// return null
// }
// })}
// {Array.from(Array(levels.next)).map((_, i) => {
// if (index < highlightIndex.current) return null
// if (
// levels.current + 1 === levels.next ||
// (levels.current && levels.next && levels.current === levels.next)
// ) {
// return (
// <View
// key={i}
// style={{
// position: 'absolute',
// top:
// levels.current + 1 === levels.next && levels.next > i + 1
// ? StyleConstants.Spacing.Global.PagePadding + StyleConstants.Avatar.XS
// : StyleConstants.Spacing.Global.PagePadding,
// left: StyleConstants.Spacing.Global.PagePadding / 2 + 8 * i,
// height: 200,
// borderLeftColor: colors.border,
// borderLeftWidth: 1
// }}
// />
// )
// } else {
// return null
// }
// })}
// </>
// )
// },
onScrollToIndexFailed: error => {
const offset = error.averageItemLength * error.index
flRef.current?.scrollToOffset({ offset })

View File

@@ -1,15 +1,18 @@
import apiInstance from '@api/instance'
import ComponentAccount from '@components/Account'
import { HeaderLeft } from '@components/Header'
import Icon from '@components/Icon'
import ComponentSeparator from '@components/Separator'
import CustomText from '@components/Text'
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
import { SearchResult } from '@utils/queryHooks/search'
import { QueryKeyUsers, useUsersQuery } from '@utils/queryHooks/users'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useCallback, useEffect } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { View } from 'react-native'
import { Circle, Flow } from 'react-native-animated-spinkit'
import { FlatList } from 'react-native-gesture-handler'
const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> = ({
@@ -26,7 +29,7 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> =
}, [])
const queryKey: QueryKeyUsers = ['Users', params]
const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = useUsersQuery({
const { data, isFetching, hasNextPage, fetchNextPage, isFetchingNextPage } = useUsersQuery({
...queryKey[1],
options: {
getPreviousPageParam: firstPage =>
@@ -41,17 +44,67 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> =
[hasNextPage, isFetchingNextPage]
)
const [isSearching, setIsSearching] = useState(false)
return (
<FlatList
windowSize={7}
data={flattenData}
style={{
minHeight: '100%'
minHeight: '100%',
paddingVertical: StyleConstants.Spacing.Global.PagePadding
}}
renderItem={({ item }) => <ComponentAccount account={item} />}
renderItem={({ item }) => (
<ComponentAccount
account={item}
props={{
disabled: isSearching,
onPress: () => {
if (data?.pages[0]?.remoteData) {
setIsSearching(true)
apiInstance<SearchResult>({
version: 'v2',
method: 'get',
url: 'search',
params: {
q: `@${item.acct}`,
type: 'accounts',
limit: 1,
resolve: true
}
})
.then(res => {
setIsSearching(false)
if (res.body.accounts[0]) {
navigation.push('Tab-Shared-Account', { account: res.body.accounts[0] })
}
})
.catch(() => setIsSearching(false))
} else {
navigation.push('Tab-Shared-Account', { account: item })
}
}
}}
children={<Flow size={StyleConstants.Font.Size.L} color={colors.secondary} />}
/>
)}
onEndReached={onEndReached}
onEndReachedThreshold={0.75}
ItemSeparatorComponent={ComponentSeparator}
ListEmptyComponent={
isFetching ? (
<View
style={{
flex: 1,
minHeight: '100%',
justifyContent: 'center',
alignItems: 'center'
}}
>
<Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
</View>
) : null
}
maintainVisibleContentPosition={{
minIndexForVisible: 0,
autoscrollToTopThreshold: 2

View File

@@ -156,6 +156,7 @@ const instancesMigration = {
mention: instance.push.alerts.mention.value,
poll: instance.push.alerts.poll.value,
status: instance.push.alerts.status.value,
update: false,
'admin.sign_up': false,
'admin.report': false
}

View File

@@ -14,7 +14,7 @@ export type InstanceV11 = {
id: Mastodon.Account['id']
acct: Mastodon.Account['acct']
avatarStatic: Mastodon.Account['avatar_static']
preferences: Mastodon.Preferences
preferences?: Mastodon.Preferences
}
version: string
configuration?: Mastodon.Instance['configuration']

View File

@@ -103,6 +103,7 @@ export type TabSharedStackParamList = {
}
'Tab-Shared-History': {
id: Mastodon.Status['id']
detectedLanguage: string
}
'Tab-Shared-Search': undefined
'Tab-Shared-Toot': {

View File

@@ -1,12 +1,13 @@
import apiGeneral from '@api/general'
import { handleError } from '@api/helpers'
import apiTooot from '@api/tooot'
import { displayMessage } from '@components/Message'
import navigationRef from '@helpers/navigationRef'
import { useAppDispatch } from '@root/store'
import * as Sentry from '@sentry/react-native'
import { useQuery } from '@tanstack/react-query'
import { getExpoToken, retrieveExpoToken } from '@utils/slices/appSlice'
import { disableAllPushes, getInstances } from '@utils/slices/instancesSlice'
import { AxiosError } from 'axios'
import * as Notifications from 'expo-notifications'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
@@ -25,16 +26,22 @@ const pushUseConnect = () => {
const instances = useSelector(getInstances, (prev, next) => prev.length === next.length)
const pushEnabled = instances.filter(instance => instance.push.global)
const connect = () => {
apiTooot({
method: 'get',
url: `/push/connect/${expoToken}`
})
.then(() => Notifications.setBadgeCountAsync(0))
.catch(error => {
handleError({ message: 'Push connect error', captureResponse: true })
Notifications.setBadgeCountAsync(0)
const connectQuery = useQuery<any, AxiosError>(
['tooot', { endpoint: 'push/connect' }],
() =>
apiTooot<Mastodon.Status>({
method: 'get',
url: `push/connect/${expoToken}`
}),
{
enabled: false,
retry: 10,
retryOnMount: false,
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
onSettled: () => Notifications.setBadgeCountAsync(0),
onError: error => {
if (error?.status == 404) {
displayMessage({
type: 'danger',
@@ -72,21 +79,22 @@ const pushUseConnect = () => {
}
})
}
})
}
}
}
)
useEffect(() => {
Sentry.setContext('Push', { expoToken, pushEnabledCount: pushEnabled.length })
if (expoToken && pushEnabled.length) {
connect()
connectQuery.refetch()
}
const appStateListener = AppState.addEventListener('change', state => {
if (expoToken && pushEnabled.length && state === 'active') {
Notifications.getBadgeCountAsync().then(count => {
if (count > 0) {
connect()
connectQuery.refetch()
}
})
}

View File

@@ -1,4 +1,4 @@
import apiInstance, { InstanceResponse } from '@api/instance'
import apiInstance from '@api/instance'
import { AxiosError } from 'axios'
import {
QueryFunctionContext,
@@ -9,6 +9,7 @@ import {
useQuery,
UseQueryOptions
} from '@tanstack/react-query'
import { PagedResponse } from '@api/helpers'
export type QueryKeyLists = ['Lists']
@@ -97,7 +98,7 @@ const useListAccountsQuery = ({
options,
...queryKeyParams
}: QueryKeyListAccounts[1] & {
options?: UseInfiniteQueryOptions<InstanceResponse<Mastodon.Account[]>, AxiosError>
options?: UseInfiniteQueryOptions<PagedResponse<Mastodon.Account[]>, AxiosError>
}) => {
const queryKey: QueryKeyListAccounts = ['ListAccounts', queryKeyParams]
return useInfiniteQuery(queryKey, accountsQueryFunction, options)

View File

@@ -8,14 +8,9 @@ import {
UseQueryOptions
} from '@tanstack/react-query'
export type QueryKeyRelationship = [
'Relationship',
{ id: Mastodon.Account['id'] }
]
export type QueryKeyRelationship = ['Relationship', { id: Mastodon.Account['id'] }]
const queryFunction = async ({
queryKey
}: QueryFunctionContext<QueryKeyRelationship>) => {
const queryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeyRelationship>) => {
const { id } = queryKey[1]
const res = await apiInstance<Mastodon.Relationship[]>({
@@ -32,11 +27,7 @@ const useRelationshipQuery = ({
options,
...queryKeyParams
}: QueryKeyRelationship[1] & {
options?: UseQueryOptions<
Mastodon.Relationship[],
AxiosError,
Mastodon.Relationship
>
options?: UseQueryOptions<Mastodon.Relationship[], AxiosError, Mastodon.Relationship>
}) => {
const queryKey: QueryKeyRelationship = ['Relationship', { ...queryKeyParams }]
return useQuery(queryKey, queryFunction, {
@@ -54,7 +45,20 @@ type MutationVarsRelationship =
| {
id: Mastodon.Account['id']
type: 'outgoing'
payload: { action: 'follow' | 'block'; state: boolean }
payload: {
action: 'block'
state: boolean
notify?: undefined
}
}
| {
id: Mastodon.Account['id']
type: 'outgoing'
payload: {
action: 'follow'
state: boolean
notify?: boolean
}
}
const mutationFunction = async (params: MutationVarsRelationship) => {
@@ -65,21 +69,20 @@ const mutationFunction = async (params: MutationVarsRelationship) => {
url: `follow_requests/${params.id}/${params.payload.action}`
}).then(res => res.body)
case 'outgoing':
const formData = new FormData()
typeof params.payload.notify === 'boolean' &&
formData.append('notify', params.payload.notify.toString())
return apiInstance<Mastodon.Relationship>({
method: 'post',
url: `accounts/${params.id}/${params.payload.state ? 'un' : ''}${
params.payload.action
}`
url: `accounts/${params.id}/${params.payload.state ? 'un' : ''}${params.payload.action}`,
body: formData
}).then(res => res.body)
}
}
const useRelationshipMutation = (
options: UseMutationOptions<
Mastodon.Relationship,
AxiosError,
MutationVarsRelationship
>
options: UseMutationOptions<Mastodon.Relationship, AxiosError, MutationVarsRelationship>
) => {
return useMutation(mutationFunction, options)
}

View File

@@ -17,17 +17,18 @@ export type SearchResult = {
statuses: Mastodon.Status[]
}
const queryFunction = async ({
queryKey
}: QueryFunctionContext<QueryKeySearch>) => {
const queryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeySearch>) => {
const { type, term, limit = 20 } = queryKey[1]
if (!term?.length) {
return Promise.reject()
}
const res = await apiInstance<SearchResult>({
version: 'v2',
method: 'get',
url: 'search',
params: {
q: term,
...(type && { type }),
...(term && { q: term }),
limit,
resolve: true
}
@@ -35,7 +36,7 @@ const queryFunction = async ({
return res.body
}
const useSearchQuery = <T = unknown>({
const useSearchQuery = <T = SearchResult>({
options,
...queryKeyParams
}: QueryKeySearch[1] & {

View File

@@ -1,4 +1,4 @@
import apiInstance, { InstanceResponse } from '@api/instance'
import apiInstance from '@api/instance'
import { AxiosError } from 'axios'
import {
QueryFunctionContext,
@@ -10,12 +10,13 @@ import {
UseQueryOptions
} from '@tanstack/react-query'
import { infinitePageParams } from './utils'
import { PagedResponse } from '@api/helpers'
export type QueryKeyFollowedTags = ['FollowedTags']
const useFollowedTagsQuery = (
params: {
options?: Omit<
UseInfiniteQueryOptions<InstanceResponse<Mastodon.Tag[]>, AxiosError>,
UseInfiniteQueryOptions<PagedResponse<Mastodon.Tag[]>, AxiosError>,
'getPreviousPageParam' | 'getNextPageParam'
>
} | void

View File

@@ -1,4 +1,4 @@
import apiInstance, { InstanceResponse } from '@api/instance'
import apiInstance from '@api/instance'
import haptics from '@components/haptics'
import queryClient from '@helpers/queryClient'
import { store } from '@root/store'
@@ -15,6 +15,7 @@ import {
import deleteItem from './timeline/deleteItem'
import editItem from './timeline/editItem'
import updateStatusProperty from './timeline/updateStatusProperty'
import { PagedResponse } from '@api/helpers'
export type QueryKeyTimeline = [
'Timeline',
@@ -240,7 +241,7 @@ const useTimelineQuery = ({
options,
...queryKeyParams
}: QueryKeyTimeline[1] & {
options?: UseInfiniteQueryOptions<InstanceResponse<Mastodon.Status[]>, AxiosError>
options?: UseInfiniteQueryOptions<PagedResponse<Mastodon.Status[]>, AxiosError>
}) => {
const queryKey: QueryKeyTimeline = ['Timeline', { ...queryKeyParams }]
return useInfiniteQuery(queryKey, queryFunction, {

View File

@@ -1,4 +1,4 @@
import apiInstance, { InstanceResponse } from '@api/instance'
import apiInstance from '@api/instance'
import { TabSharedStackParamList } from '@utils/navigation/navigators'
import { AxiosError } from 'axios'
import {
@@ -7,10 +7,15 @@ import {
UseInfiniteQueryOptions
} from '@tanstack/react-query'
import apiGeneral from '@api/general'
import { PagedResponse } from '@api/helpers'
export type QueryKeyUsers = ['Users', TabSharedStackParamList['Tab-Shared-Users']]
const queryFunction = ({ queryKey, pageParam }: QueryFunctionContext<QueryKeyUsers>) => {
const queryFunction = async ({
queryKey,
pageParam,
meta
}: QueryFunctionContext<QueryKeyUsers>) => {
const page = queryKey[1]
let params: { [key: string]: string } = { ...pageParam }
@@ -20,7 +25,7 @@ const queryFunction = ({ queryKey, pageParam }: QueryFunctionContext<QueryKeyUse
method: 'get',
url: `${page.reference}/${page.status.id}/${page.type}`,
params
}).then(res => ({ ...res, warnIncomplete: false }))
})
case 'accounts':
const localInstance = page.account.username === page.account.acct
if (localInstance) {
@@ -28,59 +33,43 @@ const queryFunction = ({ queryKey, pageParam }: QueryFunctionContext<QueryKeyUse
method: 'get',
url: `${page.reference}/${page.account.id}/${page.type}`,
params
}).then(res => ({ ...res, warnIncomplete: false }))
})
} else {
const domain = page.account.url.match(
/^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/\n]+)/i
)?.[1]
if (!domain) {
return apiInstance<Mastodon.Account[]>({
let res: PagedResponse<Mastodon.Account[]>
try {
const domain = page.account.url.match(
/^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/\n]+)/i
)?.[1]
if (!domain?.length) {
throw new Error()
}
const resLookup = await apiGeneral<Mastodon.Account>({
method: 'get',
domain,
url: 'api/v1/accounts/lookup',
params: { acct: page.account.acct }
})
if (resLookup?.body) {
res = await apiGeneral<Mastodon.Account[]>({
method: 'get',
domain,
url: `api/v1/${page.reference}/${resLookup.body.id}/${page.type}`,
params
})
return { ...res, remoteData: true }
} else {
throw new Error()
}
} catch {
res = await apiInstance<Mastodon.Account[]>({
method: 'get',
url: `${page.reference}/${page.account.id}/${page.type}`,
params
}).then(res => ({ ...res, warnIncomplete: true }))
})
return { ...res, warnIncomplete: true }
}
return apiGeneral<{ accounts: Mastodon.Account[] }>({
method: 'get',
domain,
url: 'api/v2/search',
params: {
q: `@${page.account.acct}`,
type: 'accounts',
limit: '1'
}
})
.then(res => {
if (res?.body?.accounts?.length === 1) {
return apiGeneral<Mastodon.Account[]>({
method: 'get',
domain,
url: `api/v1/${page.reference}/${res.body.accounts[0].id}/${page.type}`,
params
})
.catch(() => {
return apiInstance<Mastodon.Account[]>({
method: 'get',
url: `${page.reference}/${page.account.id}/${page.type}`,
params
}).then(res => ({ ...res, warnIncomplete: true }))
})
.then(res => ({ ...res, warnIncomplete: false }))
} else {
return apiInstance<Mastodon.Account[]>({
method: 'get',
url: `${page.reference}/${page.account.id}/${page.type}`,
params
}).then(res => ({ ...res, warnIncomplete: true }))
}
})
.catch(() => {
return apiInstance<Mastodon.Account[]>({
method: 'get',
url: `${page.reference}/${page.account.id}/${page.type}`,
params
}).then(res => ({ ...res, warnIncomplete: true }))
})
}
}
}
@@ -90,7 +79,7 @@ const useUsersQuery = ({
...queryKeyParams
}: QueryKeyUsers[1] & {
options?: UseInfiniteQueryOptions<
InstanceResponse<Mastodon.Account[]> & { warnIncomplete: boolean },
PagedResponse<Mastodon.Account[]> & { warnIncomplete: boolean; remoteData: boolean },
AxiosError
>
}) => {

View File

@@ -1,8 +1,8 @@
import { InstanceResponse } from '@api/instance'
import { PagedResponse } from '@api/helpers'
export const infinitePageParams = {
getPreviousPageParam: (firstPage: InstanceResponse<any>) =>
getPreviousPageParam: (firstPage: PagedResponse<any>) =>
firstPage.links?.prev && { min_id: firstPage.links.next },
getNextPageParam: (lastPage: InstanceResponse<any>) =>
getNextPageParam: (lastPage: PagedResponse<any>) =>
lastPage.links?.next && { max_id: lastPage.links.next }
}

View File

@@ -102,6 +102,7 @@ const addInstance = createAsyncThunk(
mention: true,
poll: true,
status: true,
update: true,
'admin.sign_up': false,
'admin.report': false
},

View File

@@ -62,10 +62,9 @@ const pushRegister = async (
'BMn2PLpZrMefG981elzG6SB1EY9gU7QZwmtZ/a/J2vUeWG+zXgeskMPwHh4T/bxsD4l7/8QT94F57CbZqYRRfJo='
)
formData.append('subscription[keys][auth]', auth)
Object.keys(alerts).map(key =>
// @ts-ignore
formData.append(`data[alerts][${key}]`, alerts[key].value.toString())
)
for (const [key, value] of Object.entries(alerts)) {
formData.append(`data[alerts][${key}]`, value.toString())
}
const res = await apiInstance<Mastodon.PushSubscription>({
method: 'post',

View File

@@ -1,3 +1,4 @@
import features from '@helpers/features'
import {
checkPermission,
PERMISSION_MANAGE_REPORTS,
@@ -7,22 +8,59 @@ import queryClient from '@helpers/queryClient'
import i18n from '@root/i18n/i18n'
import { InstanceLatest } from '@utils/migrations/instances/migration'
import { queryFunctionProfile, QueryKeyProfile } from '@utils/queryHooks/profile'
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
import * as Notifications from 'expo-notifications'
import { useSelector } from 'react-redux'
export const PUSH_DEFAULT: [
'follow',
'follow_request',
'favourite',
'reblog',
'mention',
'poll',
'status'
] = ['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll', 'status']
export const usePushFeatures = () => {
const hasTypeStatus = useSelector(checkInstanceFeature('notification_type_status'))
const hasTypeUpdate = useSelector(checkInstanceFeature('notification_type_update'))
const hasTypeAdminSignup = useSelector(checkInstanceFeature('notification_type_admin_signup'))
const hasTypeAdminReport = useSelector(checkInstanceFeature('notification_type_admin_report'))
return { hasTypeStatus, hasTypeUpdate, hasTypeAdminSignup, hasTypeAdminReport }
}
export const PUSH_ADMIN: { type: 'admin.sign_up' | 'admin.report'; permission: number }[] = [
{ type: 'admin.sign_up', permission: PERMISSION_MANAGE_USERS },
{ type: 'admin.report', permission: PERMISSION_MANAGE_REPORTS }
]
export const PUSH_DEFAULT = ({
hasTypeUpdate,
hasTypeStatus
}: {
hasTypeUpdate: boolean
hasTypeStatus: boolean
}) =>
['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll', 'update', 'status'].filter(
type => {
switch (type) {
case 'status':
return hasTypeStatus
case 'update':
return hasTypeUpdate
default:
return true
}
}
) as ['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll', 'update', 'status']
export const PUSH_ADMIN = (
{
hasTypeAdminSignup,
hasTypeAdminReport
}: {
hasTypeAdminSignup: boolean
hasTypeAdminReport: boolean
},
permissions?: string | number | undefined
) =>
[
{ type: 'admin.sign_up', permission: PERMISSION_MANAGE_USERS },
{ type: 'admin.report', permission: PERMISSION_MANAGE_REPORTS }
].filter(({ type, permission }) => {
switch (type) {
case 'admin.sign_up':
return hasTypeAdminSignup && checkPermission(permission, permissions)
case 'admin.report':
return hasTypeAdminReport && checkPermission(permission, permissions)
}
}) as { type: 'admin.sign_up' | 'admin.report'; permission: number }[]
export const setChannels = async (instance: InstanceLatest) => {
const account = `@${instance.account.acct}@${instance.uri}`
@@ -48,23 +86,32 @@ export const setChannels = async (instance: InstanceLatest) => {
await Notifications.setNotificationChannelGroupAsync(account, { name: account })
}
const checkFeature = (feature: string) =>
features
.filter(f => f.feature === feature)
.filter(f => parseFloat(instance.version) >= f.version)?.length > 0
const checkFeatures = {
hasTypeStatus: checkFeature('notification_type_status'),
hasTypeUpdate: checkFeature('notification_type_update'),
hasTypeAdminSignup: checkFeature('notification_type_admin_signup'),
hasTypeAdminReport: checkFeature('notification_type_admin_report')
}
if (!instance.push.decode) {
await setChannel('default')
for (const push of PUSH_DEFAULT) {
for (const push of PUSH_DEFAULT(checkFeatures)) {
await deleteChannel(push)
}
for (const { type } of PUSH_ADMIN) {
for (const { type } of PUSH_ADMIN(checkFeatures, profileQuery.role?.permissions)) {
await deleteChannel(type)
}
} else {
await deleteChannel('default')
for (const push of PUSH_DEFAULT) {
for (const push of PUSH_DEFAULT(checkFeatures)) {
await setChannel(push)
}
for (const { type, permission } of PUSH_ADMIN) {
if (checkPermission(permission, profileQuery.role?.permissions)) {
await setChannel(type)
}
for (const { type } of PUSH_ADMIN(checkFeatures, profileQuery.role?.permissions)) {
await setChannel(type)
}
}
}

View File

@@ -10,10 +10,9 @@ export const updateInstancePushAlert = createAsyncThunk(
alerts: InstanceLatest['push']['alerts']
}): Promise<InstanceLatest['push']['alerts']> => {
const formData = new FormData()
Object.keys(alerts).map(alert =>
// @ts-ignore
formData.append(`data[alerts][${alert}]`, alerts[alert].value.toString())
)
for (const [key, value] of Object.entries(alerts)) {
formData.append(`data[alerts][${key}]`, value.toString())
}
await apiInstance<Mastodon.PushSubscription>({
method: 'put',

View File

@@ -220,7 +220,8 @@ const instancesSlice = createSlice({
// Update Instance Configuration
.addCase(updateConfiguration.fulfilled, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].version = action.payload?.version || '0'
state.instances[activeIndex].version =
typeof action.payload.version === 'string' ? action.payload.version : '0'
state.instances[activeIndex].configuration = action.payload.configuration
})
.addCase(updateConfiguration.rejected, (_, action) => {
@@ -250,6 +251,9 @@ const instancesSlice = createSlice({
.addCase(checkEmojis.fulfilled, (state, action) => {
if (!action.payload || !action.payload.length) return
const activeIndex = findInstanceActive(state.instances)
if (!Array.isArray(state.instances[activeIndex].frequentEmojis)) {
state.instances[activeIndex].frequentEmojis = []
}
state.instances[activeIndex].frequentEmojis = state.instances[
activeIndex
]?.frequentEmojis?.filter(emoji => {

255
yarn.lock
View File

@@ -2404,63 +2404,63 @@
resolved "https://registry.yarnpkg.com/@react-native/polyfills/-/polyfills-2.0.0.tgz#4c40b74655c83982c8cf47530ee7dc13d957b6aa"
integrity sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ==
"@react-navigation/bottom-tabs@^6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-6.5.0.tgz#8bcd98733988ffe3e5124b45920608f22b2576e4"
integrity sha512-7jKBHAk/HpIwXEsKKegB0DabPhFWS9PH2Bsmd4HUdvHj103m7H3OsrszuPB9Uxbe3RkGNtcJzeQnYOanVzNR+w==
"@react-navigation/bottom-tabs@^6.5.1":
version "6.5.1"
resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-6.5.1.tgz#ef146502674e68736cbb47500ba1e3e481456869"
integrity sha512-XhY3rdfI/lxiG/TfdxvYYKSJR6N6K42VEBBQX8yvGBq+7aeCKaX5D7MF6yiI6ViVZ8uPhGyh/MJM3srUW+Yj7w==
dependencies:
"@react-navigation/elements" "^1.3.10"
"@react-navigation/elements" "^1.3.11"
color "^4.2.3"
warn-once "^0.1.0"
"@react-navigation/core@^6.4.4":
version "6.4.4"
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-6.4.4.tgz#270ef88d3c3a225f9a58bbe89eb8a8bc96af44a9"
integrity sha512-skdTzr6sOceEusEDG+e58zaSpgy1Yz7eZGFtmkmdYAFkZDy5nkIY/0nYuXP0waUYarNXg6lNEVkF995/kZXHZg==
"@react-navigation/core@^6.4.5":
version "6.4.5"
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-6.4.5.tgz#8254b25c476857d53a649af8e3fded0cbe6e1ded"
integrity sha512-wcde35HeOM5r2P25EwLQZyJ1yhXDGKuWpnKfsSI1xrgYIvWdYi3j/yGnwgNGDelCmtUt1Fyk2pmOv8sEku9KkA==
dependencies:
"@react-navigation/routers" "^6.1.5"
"@react-navigation/routers" "^6.1.6"
escape-string-regexp "^4.0.0"
nanoid "^3.1.23"
query-string "^7.1.3"
react-is "^16.13.0"
use-latest-callback "^0.1.5"
"@react-navigation/elements@^1.3.10":
version "1.3.10"
resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.10.tgz#7f4281eddef5ff424169413daaab9a66b27c4949"
integrity sha512-JFaoZG9S+Zz291CvAMeGw8kNl/g2AaY9Pbo+VcYO+JM6UF/E5Obq9ga2ydxDrn3an7wzdl6flA/4lWhqG82Vqw==
"@react-navigation/elements@^1.3.11":
version "1.3.11"
resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.11.tgz#135c2cb3ae4a31bc835bb731110fd1ef9c38e237"
integrity sha512-o4J0g4ofJbbn68e4TpuGkuZLtq5mLll7Ndz9C4O4RvD2chchLuGQ5TycIPTKP428cz8JzuTCFqUe/ZhOPSsudw==
"@react-navigation/native-stack@^6.9.5":
version "6.9.5"
resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-6.9.5.tgz#805adf6699524519ff4bf50ac960a923da1bc223"
integrity sha512-1ZIrla+b4gB8KDC6QewtZ/1yOS23bQctwR4Pf6ECA0stEH8ibbxh70iiI/LluL5CtWxrWfgOrl1jpQsVvtKY+Q==
"@react-navigation/native-stack@^6.9.6":
version "6.9.6"
resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-6.9.6.tgz#d88cc332f19415fa2c57b8f2b9a6fc2cabf9733e"
integrity sha512-Puqo5Y7MwYZvGELN73nMzaWTMdJu58stp9M809h4eoi4SzS0u/mFtypquEcW1gXdmRlDMmqXTtR/NBPSc2kLJg==
dependencies:
"@react-navigation/elements" "^1.3.10"
"@react-navigation/elements" "^1.3.11"
warn-once "^0.1.0"
"@react-navigation/native@^6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-6.1.0.tgz#ea0c79b6b0f67397305fec54eb4dbc3dd3eb087b"
integrity sha512-CdjOmbE4c/UczczqeP7ZrFXJcjnXOCwY1PDNjX51Ph1b2tHXpQ41/089k3R49dc5i2sFLk6jKaryFU2dcLr8jw==
"@react-navigation/native@^6.1.1":
version "6.1.1"
resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-6.1.1.tgz#79d91db04fbad277f355a10405516df4f67cd308"
integrity sha512-iIozx9c66EjSFyzKrZPixnk6vBuivYXp0jmbKCJXNIa7MY+8OLx9CXj/+1py/l/OGlXDhI6jiUWWetOfOtMaBQ==
dependencies:
"@react-navigation/core" "^6.4.4"
"@react-navigation/core" "^6.4.5"
escape-string-regexp "^4.0.0"
fast-deep-equal "^3.1.3"
nanoid "^3.1.23"
"@react-navigation/routers@^6.1.5":
version "6.1.5"
resolved "https://registry.yarnpkg.com/@react-navigation/routers/-/routers-6.1.5.tgz#b3e06bc09346ad94206bcc71c46538d5b6dc4883"
integrity sha512-JzMRiRRu8J0yUMC7BV8wOVzevjkHnIPONbpCTL/vH5yceTm+dSH/U3esIObgk8wYYbov+jYlVhwUQNGRb2to6g==
"@react-navigation/routers@^6.1.6":
version "6.1.6"
resolved "https://registry.yarnpkg.com/@react-navigation/routers/-/routers-6.1.6.tgz#f57f2a73855d329255aa225fdad75ae8e7700c6d"
integrity sha512-Z5DeCW3pUvMafbU9Cjy1qJYC2Bvl8iy3+PfsB0DsAwQ6zZ3WAXW5FTMX4Gb9H+Jg6qHWGbMFFwlYpS3UJ3tlVQ==
dependencies:
nanoid "^3.1.23"
"@react-navigation/stack@^6.3.8":
version "6.3.8"
resolved "https://registry.yarnpkg.com/@react-navigation/stack/-/stack-6.3.8.tgz#bc36beb4fba1c47a45e01c9dcc501be0f6f10d54"
integrity sha512-tnSmI5wmRLA293tPo43niCOOcWsWFB1XPGeUBVsz/lJnQZIHUUx0h42t53fU22DHGXee7PiZ78Lvr1Q0m/JZJQ==
"@react-navigation/stack@^6.3.9":
version "6.3.9"
resolved "https://registry.yarnpkg.com/@react-navigation/stack/-/stack-6.3.9.tgz#e4e2b6aa25973b3bc59200b7e3bf980e93cb5b4f"
integrity sha512-mTT0ZM/Zvp46s8UnphAspg2MR5Ds0mqZa/bEjzFgaYo+cLfjIXttUZYgsMG2fhvoTHpU7QFcZ/0kVhi4jVlzKQ==
dependencies:
"@react-navigation/elements" "^1.3.10"
"@react-navigation/elements" "^1.3.11"
color "^4.2.3"
warn-once "^0.1.0"
@@ -2482,14 +2482,14 @@
component-type "^1.2.1"
join-component "^1.1.0"
"@sentry/browser@7.21.1":
version "7.21.1"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.21.1.tgz#bffa3ea19050c06400107d2297b9802f9719f98b"
integrity sha512-cS2Jz2+fs9+4pJqLJPtYqGyY97ywJDWAWIR1Yla3hs1QQuH6m0Nz3ojZD1gE2eKH9mHwkGbnNAh+hHcrYrfGzw==
"@sentry/browser@7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.26.0.tgz#152d0f59df85be3ed8a7161b0ca90a4dca7b382d"
integrity sha512-S6uW+Ni2VLGHUV9eAUtTy5QEvqKeOhcnWnv+2yTGDtQCJ0SuHfXRCM7ASAQYBiKffZqIFc9Z2XNU/2cuWcXJmw==
dependencies:
"@sentry/core" "7.21.1"
"@sentry/types" "7.21.1"
"@sentry/utils" "7.21.1"
"@sentry/core" "7.26.0"
"@sentry/types" "7.26.0"
"@sentry/utils" "7.26.0"
tslib "^1.9.3"
"@sentry/cli@1.74.4":
@@ -2518,83 +2518,83 @@
proxy-from-env "^1.1.0"
which "^2.0.2"
"@sentry/core@7.21.1":
version "7.21.1"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.21.1.tgz#d0423282d90875625802dfe380f9657e9242b72b"
integrity sha512-Og5wEEsy24fNvT/T7IKjcV4EvVK5ryY2kxbJzKY6GU2eX+i+aBl+n/vp7U0Es351C/AlTkS+0NOUsp2TQQFxZA==
"@sentry/core@7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.26.0.tgz#8f9fa439b40560edd09b464292d3084e1f16228f"
integrity sha512-ydi236ZoP/xpvLdf7B8seKjCcGc5Z+q9c14tHCFusplPZgLSXcYpiiLIDWmF7OAXO89sSbb1NaFt9YB0LkYdLQ==
dependencies:
"@sentry/types" "7.21.1"
"@sentry/utils" "7.21.1"
"@sentry/types" "7.26.0"
"@sentry/utils" "7.26.0"
tslib "^1.9.3"
"@sentry/hub@7.21.1":
version "7.21.1"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-7.21.1.tgz#5c046fd2ca7eaf14cd0bf70617cc8b057f9b88ce"
integrity sha512-WXQJ5z5iUZO6sOq3f7A3eIwvb/GoVJ3q5DQDTNF7HB/qcm11u5QPlAHTFBCsKJdT39NaE78JcMluiHZUWT+C5A==
"@sentry/hub@7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-7.26.0.tgz#a8d04c37384263439764f896ea0acbcf790dc521"
integrity sha512-djAMuA4/Jy28dOSy9z5ccXBDyYk1N9m0ljle+dKqjfJwv440tCGyoxm2arqJFHbXvqwJTt2Giv8ASR4uGD1UNg==
dependencies:
"@sentry/core" "7.21.1"
"@sentry/types" "7.21.1"
"@sentry/utils" "7.21.1"
"@sentry/core" "7.26.0"
"@sentry/types" "7.26.0"
"@sentry/utils" "7.26.0"
tslib "^1.9.3"
"@sentry/integrations@7.21.1":
version "7.21.1"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.21.1.tgz#8a5d5595521c4891cc2582f98d253a074ba54abc"
integrity sha512-DbQZSdsqaD9RTy5WvLzonoJa2CIgeapnGfFOadnQGOD8A8GT9Bre/BgcQ5ksHqGnVfzYwpU26k/ue9gjXnI/Pg==
"@sentry/integrations@7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.26.0.tgz#958e3ed52a37a14c6f11aebf11b485eca52e56aa"
integrity sha512-5tyBA5BnZEuosSIvBP7mJz66xJaZTb/k1EzHEc0hR2Mw8QpLgMneDZBfi4vdbhxtGpJKC/gURoUGZf9hpwW+DA==
dependencies:
"@sentry/types" "7.21.1"
"@sentry/utils" "7.21.1"
"@sentry/types" "7.26.0"
"@sentry/utils" "7.26.0"
localforage "^1.8.1"
tslib "^1.9.3"
"@sentry/react-native@4.11.0":
version "4.11.0"
resolved "https://registry.yarnpkg.com/@sentry/react-native/-/react-native-4.11.0.tgz#6820aa51ac2ed225daa0c02b58956588ad12dc62"
integrity sha512-dC8rPOabs/DNAC1Yc2mICo0vkMq+GmLEBxi2l8zIzf8e99Qii0zrGuVng9vc+PRwdBMQrKw7i17SlFEqsaoksA==
"@sentry/react-native@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@sentry/react-native/-/react-native-4.12.0.tgz#3eff5eb0d03e5abdf24b74a5d5221d4416ef97cf"
integrity sha512-KBRXXqqGg67oqhtGzZQ8Ls4rxxUozDyL8tMLmRLk//bkSWBC6XslyjkZkQrB1VZsv2ikEYCUgyAP7fzcj6Bbig==
dependencies:
"@sentry/browser" "7.21.1"
"@sentry/browser" "7.26.0"
"@sentry/cli" "1.74.4"
"@sentry/core" "7.21.1"
"@sentry/hub" "7.21.1"
"@sentry/integrations" "7.21.1"
"@sentry/react" "7.21.1"
"@sentry/tracing" "7.21.1"
"@sentry/types" "7.21.1"
"@sentry/utils" "7.21.1"
"@sentry/core" "7.26.0"
"@sentry/hub" "7.26.0"
"@sentry/integrations" "7.26.0"
"@sentry/react" "7.26.0"
"@sentry/tracing" "7.26.0"
"@sentry/types" "7.26.0"
"@sentry/utils" "7.26.0"
"@sentry/wizard" "1.4.0"
"@sentry/react@7.21.1":
version "7.21.1"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.21.1.tgz#275e6fd46212f608f382c7dde46d21e748f93491"
integrity sha512-w91PIUyX07mErKgrBQA+7ID8zFKrYDUYSOrFSHufg5DdPq4EpHiNDe/Yngg3e9ELhtr1AbCnEvx9wlvqLi3nZQ==
"@sentry/react@7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.26.0.tgz#548e1d29083d0e4732f2828d8cb8f9d0a150ae4e"
integrity sha512-v5XKpG1PF4qnWvG8E0N1kcUk74lTp+TDfKx5x996NIja2oOTp/JL9V0Q+lAMlB1EKgJuxLe92IeqD5/DTtzE7A==
dependencies:
"@sentry/browser" "7.21.1"
"@sentry/types" "7.21.1"
"@sentry/utils" "7.21.1"
"@sentry/browser" "7.26.0"
"@sentry/types" "7.26.0"
"@sentry/utils" "7.26.0"
hoist-non-react-statics "^3.3.2"
tslib "^1.9.3"
"@sentry/tracing@7.21.1":
version "7.21.1"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.21.1.tgz#db02643e84960f1ea14b35fe75a93fc0bbca1fcb"
integrity sha512-b1BTPsRaNQpohzegoz59KGuBl+To651vEq0vMS4tCzSyIdxkYso3JCrjDdEqW/2MliQYANNVrUai2bmwmU9h1g==
"@sentry/tracing@7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.26.0.tgz#25105f8aec64a0e7113e09674d300190378b1daa"
integrity sha512-UK8EiXxJrDTWD82Oasj2WP/QuQ+wzPlg74vYmxl1ie/LRs6C6wHkilBZwDV9HnDdqAqSjl0al8oBa075lK+U3Q==
dependencies:
"@sentry/core" "7.21.1"
"@sentry/types" "7.21.1"
"@sentry/utils" "7.21.1"
"@sentry/core" "7.26.0"
"@sentry/types" "7.26.0"
"@sentry/utils" "7.26.0"
tslib "^1.9.3"
"@sentry/types@7.21.1":
version "7.21.1"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.21.1.tgz#408a7b95a66ddc30c4359979594e03bee8f9fbdc"
integrity sha512-3/IKnd52Ol21amQvI+kz+WB76s8/LR5YvFJzMgIoI2S8d82smIr253zGijRXxHPEif8kMLX4Yt+36VzrLxg6+A==
"@sentry/types@7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.26.0.tgz#2fe8a38a143797abecbcd53175ebf8bf736e18de"
integrity sha512-U2s0q3ALwWFdHJBgn8nrG9bCTJZ3hAqL/I2Si4Mf0ZWnJ/KTJKbtyrputHr8wMbHvX0NZTJGTxFVUO46J+GBRA==
"@sentry/utils@7.21.1":
version "7.21.1"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.21.1.tgz#96582345178015fd32fe9159c25c44ccf2f99d2a"
integrity sha512-F0W0AAi8tgtTx6ApZRI2S9HbXEA9ENX1phTZgdNNWcMFm1BNbc21XEwLqwXBNjub5nlA6CE8xnjXRgdZKx4kzQ==
"@sentry/utils@7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.26.0.tgz#4b501064c5220947f210aa2d59e9b8bf60677502"
integrity sha512-nIC1PRyoMBi4QB7XNCWaPDqaQbPayMwAvUm6W3MC5bHPfVZmmFt+3sLZQKUD/E0NeQnJ3vTyPewPF/LfxLOE5A==
dependencies:
"@sentry/types" "7.21.1"
"@sentry/types" "7.26.0"
tslib "^1.9.3"
"@sentry/wizard@1.4.0":
@@ -2660,17 +2660,17 @@
dependencies:
defer-to-connect "^2.0.0"
"@tanstack/query-core@4.19.1":
version "4.19.1"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.19.1.tgz#2e92d9e8a50884eb231c5beb4386e131ebe34306"
integrity sha512-Zp0aIose5C8skBzqbVFGk9HJsPtUhRVDVNWIqVzFbGQQgYSeLZMd3Sdb4+EnA5wl1J7X+bre2PJGnQg9x/zHOA==
"@tanstack/query-core@4.20.4":
version "4.20.4"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.20.4.tgz#1f7975a2db26a8bc2f382bad8a44cd422c846b17"
integrity sha512-lhLtGVNJDsJ/DyZXrLzekDEywQqRVykgBqTmkv0La32a/RleILXy6JMLBb7UmS3QCatg/F/0N9/5b0i5j6IKcA==
"@tanstack/react-query@^4.19.1":
version "4.19.1"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.19.1.tgz#43356dd537127e76d75f5a2769eb23dafd9a3690"
integrity sha512-5dvHvmc0vrWI03AJugzvKfirxCyCLe+qawrWFCXdu8t7dklIhJ7D5ZhgTypv7mMtIpdHPcECtCiT/+V74wCn2A==
"@tanstack/react-query@^4.20.4":
version "4.20.4"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.20.4.tgz#562b34fb919adea884eccaba2b5be50e8ba7fb16"
integrity sha512-SJRxx13k/csb9lXAJfycgVA1N/yU/h3bvRNWP0+aHMfMjmbyX82FdoAcckDBbOdEyAupvb0byelNHNeypCFSyA==
dependencies:
"@tanstack/query-core" "4.19.1"
"@tanstack/query-core" "4.20.4"
use-sync-external-store "^1.2.0"
"@types/cacheable-request@^6.0.1":
@@ -2771,9 +2771,9 @@
integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
"@types/node@*":
version "18.11.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.12.tgz#89e7f8aa8c88abf432f9bd594888144d7dba10aa"
integrity sha512-FgD3NtTAKvyMmD44T07zz2fEf+OKwutgBCEVM8GcvMGVGaDktiLNTDvPwC/LUe3PinMW+X6CuLOF2Ui1mAlSXg==
version "18.11.15"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.15.tgz#de0e1fbd2b22b962d45971431e2ae696643d3f5d"
integrity sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==
"@types/prop-types@*":
version "15.7.5"
@@ -6369,9 +6369,9 @@ find-yarn-workspace-root@^2.0.0, find-yarn-workspace-root@~2.0.0:
micromatch "^4.0.2"
flow-parser@0.*:
version "0.195.1"
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.195.1.tgz#1f4017afd46c7873ed8dd9505f5fe5f00e08dd6c"
integrity sha512-8U3yQ8dny+CIsAjnH1QqpNw1mxLmqyp+0Mz/uh+YIH7srDkfabebwtFSnGv2m8yB2ZEPBrPwFYHe1kHKr/KQuw==
version "0.196.0"
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.196.0.tgz#d62f85e02792fe625509a11343a8611ff976facd"
integrity sha512-739keKrDa+/5wpGFirMVK04elYMBjnwX2VH0WneKm3zx4K60ic8acJ6Ya6BekuHKLHIuz36ZUOQuGSUjc8D21A==
flow-parser@^0.121.0:
version "0.121.0"
@@ -6604,7 +6604,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3:
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385"
integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==
@@ -7186,10 +7186,10 @@ https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1:
agent-base "6"
debug "4"
i18next@^22.4.1:
version "22.4.1"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.1.tgz#90041c018ab93a59b6e09d6b20b35e5500f32827"
integrity sha512-BZWjrTX+OVaSxV5s94s2M+7wSlevdPgNtjeL/xXHl0CKkPGyjHNlkN6eN+0e0ex+EO8ec57OALRDUpiSSCo54g==
i18next@^22.4.5:
version "22.4.5"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.5.tgz#7324e4946c2facbe743ca25bca8980af05b0a109"
integrity sha512-Kc+Ow0guRetUq+kv02tj0Yof9zveROPBAmJ8UxxNODLVBRSwsM4iD0Gw3BEieOmkWemF6clU3K1fbnCuTqiN2Q==
dependencies:
"@babel/runtime" "^7.20.6"
@@ -7348,11 +7348,11 @@ internal-ip@4.3.0, internal-ip@^4.3.0:
ipaddr.js "^1.9.0"
internal-slot@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==
version "1.0.4"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.4.tgz#8551e7baf74a7a6ba5f749cfb16aa60722f0d6f3"
integrity sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==
dependencies:
get-intrinsic "^1.1.0"
get-intrinsic "^1.1.3"
has "^1.0.3"
side-channel "^1.0.4"
@@ -9167,9 +9167,9 @@ node-releases@^1.1.61:
integrity sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==
node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==
version "2.0.7"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.7.tgz#593edbc7c22860ee4d32d3933cfebdfab0c0e0e5"
integrity sha512-EJ3rzxL9pTWPjk5arA0s0dgXpnyiAbJDE6wHT62g7VsgrgQgmmZ+Ru++M1BFofncWja+Pnn3rEr3fieRySAdKQ==
node-stream-zip@^1.9.1:
version "1.15.0"
@@ -10755,11 +10755,6 @@ react-native-language-detection@^0.2.2:
resolved "https://registry.yarnpkg.com/react-native-language-detection/-/react-native-language-detection-0.2.2.tgz#4cc94177aa1c4575c4656f6d42456fa6c72ed5db"
integrity sha512-6u1JBgr+UG/GX/xMmT4K8CaBlSep4XfM91jwUzRA/Y3bMCHDx7bNVxGQvqvzkmOchby9h66XD8F5Eo+kV01CAA==
react-native-live-text-image-view@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/react-native-live-text-image-view/-/react-native-live-text-image-view-0.4.0.tgz#d23d5850788609fd1448533213fc6c453c584761"
integrity sha512-PhVFE0YogSLrTlnHIxUWL80CrmbDvxk1JvLx4UHiRHRwLda4dV/e/9Q+Pyh7Vq7qwAPE6vGoN6sjbXNaj4WCew==
react-native-pager-view@^6.1.2:
version "6.1.2"
resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.1.2.tgz#3522079b9a9d6634ca5e8d153bc0b4d660254552"
@@ -10814,10 +10809,10 @@ react-native-swipe-list-view@^3.2.9:
resolved "https://registry.yarnpkg.com/react-native-swipe-list-view/-/react-native-swipe-list-view-3.2.9.tgz#d725c7cdf481dd5df12a00dbfe0120013b5f2e59"
integrity sha512-SjAEuHc/D6ovp+RjDUhfNmw6NYOntdT7+GFhfMGfP/BSLMuMWynpzJy9GKQeyB8sI78T6Lzip21TVbongOg1Mw==
react-native-tab-view@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-3.3.3.tgz#33a94db2ccbe7fe8fff0c0e3f1da09cbd07edba7"
integrity sha512-hrxGu/Bj/GRXt+6ErqUFMAw2E82tjeTJ97nH1dZ2FSnLYOVnY9FDqju8TkQtT3GpJGL6Ur/ZkRT4eQfYwTZbtg==
react-native-tab-view@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-3.3.4.tgz#856d4527f3bbf05e2649302ec80abe9f2f004666"
integrity sha512-rceAYWpHa6knA7tsTnnjlcOxlCErR4F+yXQPpNm125IvYFyv09YRhE5uMU2IzyPIQ1CJvADCHurF3KySzVI+4Q==
dependencies:
use-latest-callback "^0.1.5"