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:
@@ -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
|
||||
|
@@ -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
|
@@ -1,10 +1 @@
|
||||
toooting愉快!此版本包括以下改进和修复:
|
||||
- 增加 🇺🇦 Slava Ukraini
|
||||
- 自动识别发嘟语言
|
||||
- 记住上次公共时间轴选项
|
||||
- 显示编辑历史的差异
|
||||
- 关注列表可隐藏转嘟和回复
|
||||
- 新增管理员推送通知
|
||||
- 支持嘟文右到左文字
|
||||
- 修复过滤整词功能
|
||||
- 修复平板不能删除草稿
|
@@ -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
|
||||
|
||||
|
19
package.json
19
package.json
@@ -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",
|
||||
|
3
src/@types/mastodon.d.ts
vendored
3
src/@types/mastodon.d.ts
vendored
@@ -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
|
||||
|
@@ -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 ') +
|
||||
' ' +
|
||||
|
@@ -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 }
|
||||
|
@@ -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)
|
||||
|
@@ -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}`,
|
||||
|
@@ -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>
|
||||
)
|
||||
|
@@ -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())
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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 ? (
|
||||
|
@@ -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: '',
|
||||
|
@@ -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':
|
||||
|
@@ -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
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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()
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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':
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
]
|
@@ -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": ""
|
||||
}
|
||||
}
|
||||
}
|
@@ -231,6 +231,9 @@
|
||||
"status": {
|
||||
"heading": ""
|
||||
},
|
||||
"update": {
|
||||
"heading": ""
|
||||
},
|
||||
"admin.sign_up": {
|
||||
"heading": ""
|
||||
},
|
||||
@@ -395,7 +398,8 @@
|
||||
"statuses": {
|
||||
"reblogged_by": "",
|
||||
"favourited_by": ""
|
||||
}
|
||||
},
|
||||
"resultIncomplete": ""
|
||||
}
|
||||
}
|
||||
}
|
@@ -55,7 +55,7 @@
|
||||
"accessibilityLabel": "Lesezeichen hinzufügen",
|
||||
"function": "Lesezeichen setzen"
|
||||
},
|
||||
"openReport": ""
|
||||
"openReport": "Meldung öffnen"
|
||||
},
|
||||
"actionsUsers": {
|
||||
"reblogged_by": {
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@@ -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",
|
||||
|
@@ -231,6 +231,9 @@
|
||||
"status": {
|
||||
"heading": "Toot from subscribed users"
|
||||
},
|
||||
"update": {
|
||||
"heading": "Reblog has been edited"
|
||||
},
|
||||
"admin.sign_up": {
|
||||
"heading": "Admin: sign up"
|
||||
},
|
||||
|
@@ -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": ""
|
||||
}
|
||||
}
|
||||
}
|
@@ -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": ""
|
||||
}
|
||||
}
|
||||
}
|
@@ -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": ""
|
||||
}
|
||||
}
|
||||
}
|
@@ -231,6 +231,9 @@
|
||||
"status": {
|
||||
"heading": "購読したユーザーのトゥート"
|
||||
},
|
||||
"update": {
|
||||
"heading": "ブーストしたトゥートが編集されました"
|
||||
},
|
||||
"admin.sign_up": {
|
||||
"heading": ""
|
||||
},
|
||||
@@ -395,7 +398,8 @@
|
||||
"statuses": {
|
||||
"reblogged_by": "{{count}} ブースト",
|
||||
"favourited_by": "{{count}} お気に入り"
|
||||
}
|
||||
},
|
||||
"resultIncomplete": ""
|
||||
}
|
||||
}
|
||||
}
|
@@ -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": ""
|
||||
}
|
||||
}
|
||||
}
|
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@@ -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": ""
|
||||
}
|
||||
}
|
||||
}
|
@@ -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": ""
|
||||
}
|
||||
}
|
||||
}
|
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@@ -231,6 +231,9 @@
|
||||
"status": {
|
||||
"heading": "Дмух від підписників"
|
||||
},
|
||||
"update": {
|
||||
"heading": "Передмух був відредагований"
|
||||
},
|
||||
"admin.sign_up": {
|
||||
"heading": "Адмін: реєстрація"
|
||||
},
|
||||
@@ -395,7 +398,8 @@
|
||||
"statuses": {
|
||||
"reblogged_by": "{{count}} передмухів",
|
||||
"favourited_by": "{{count}} улюблених"
|
||||
}
|
||||
},
|
||||
"resultIncomplete": "Результати з віддаленого інстанса неповні"
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 đủ"
|
||||
}
|
||||
}
|
||||
}
|
@@ -231,6 +231,9 @@
|
||||
"status": {
|
||||
"heading": "订阅用户的嘟文"
|
||||
},
|
||||
"update": {
|
||||
"heading": "转嘟被编辑"
|
||||
},
|
||||
"admin.sign_up": {
|
||||
"heading": "管理员:用户注册"
|
||||
},
|
||||
@@ -395,7 +398,8 @@
|
||||
"statuses": {
|
||||
"reblogged_by": "{{count}} 人转嘟",
|
||||
"favourited_by": "{{count}} 人喜欢"
|
||||
}
|
||||
},
|
||||
"resultIncomplete": "来自远程实例的结果不完整"
|
||||
}
|
||||
}
|
||||
}
|
@@ -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": {
|
||||
|
@@ -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": "來自遠端站點的結果不完整"
|
||||
}
|
||||
}
|
||||
}
|
@@ -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':
|
||||
|
@@ -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':
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
}
|
||||
/>
|
||||
|
@@ -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())
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
@@ -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({
|
||||
|
@@ -31,7 +31,7 @@ const Settings: React.FC = () => {
|
||||
`https://${url}/settings/preferences`,
|
||||
'tooot://tooot',
|
||||
{
|
||||
browserPackage: await browserPackage(),
|
||||
...(await browserPackage()),
|
||||
dismissButtonStyle: 'done',
|
||||
readerMode: false
|
||||
}
|
||||
|
@@ -64,7 +64,7 @@ const SettingsTooot: React.FC = () => {
|
||||
})
|
||||
} else {
|
||||
WebBrowser.openBrowserAsync('https://social.xmflsct.com/@tooot', {
|
||||
browserPackage: await browserPackage()
|
||||
...(await browserPackage())
|
||||
})
|
||||
}
|
||||
}}
|
||||
|
@@ -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])}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -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}`)}
|
||||
|
@@ -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}
|
||||
/>
|
||||
|
@@ -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'),
|
||||
|
@@ -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 })
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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']
|
||||
|
@@ -103,6 +103,7 @@ export type TabSharedStackParamList = {
|
||||
}
|
||||
'Tab-Shared-History': {
|
||||
id: Mastodon.Status['id']
|
||||
detectedLanguage: string
|
||||
}
|
||||
'Tab-Shared-Search': undefined
|
||||
'Tab-Shared-Toot': {
|
||||
|
@@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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] & {
|
||||
|
@@ -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
|
||||
|
@@ -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, {
|
||||
|
@@ -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
|
||||
>
|
||||
}) => {
|
||||
|
@@ -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 }
|
||||
}
|
||||
|
@@ -102,6 +102,7 @@ const addInstance = createAsyncThunk(
|
||||
mention: true,
|
||||
poll: true,
|
||||
status: true,
|
||||
update: true,
|
||||
'admin.sign_up': false,
|
||||
'admin.report': false
|
||||
},
|
||||
|
@@ -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',
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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',
|
||||
|
@@ -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
255
yarn.lock
@@ -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"
|
||||
|
||||
|
Reference in New Issue
Block a user