mirror of https://github.com/tooot-app/app
Merge branch 'main' into candidate
This commit is contained in:
commit
d0db2385ca
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "tooot",
|
"name": "tooot",
|
||||||
"version": "4.8.0",
|
"version": "4.8.1",
|
||||||
"description": "tooot for Mastodon",
|
"description": "tooot for Mastodon",
|
||||||
"author": "xmflsct <me@xmflsct.com>",
|
"author": "xmflsct <me@xmflsct.com>",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
|
@ -81,6 +81,7 @@
|
||||||
"react-native-language-detection": "^0.2.2",
|
"react-native-language-detection": "^0.2.2",
|
||||||
"react-native-mmkv": "^2.5.1",
|
"react-native-mmkv": "^2.5.1",
|
||||||
"react-native-pager-view": "^6.1.2",
|
"react-native-pager-view": "^6.1.2",
|
||||||
|
"react-native-quick-base64": "^2.0.5",
|
||||||
"react-native-reanimated": "^2.13.0",
|
"react-native-reanimated": "^2.13.0",
|
||||||
"react-native-reanimated-zoom": "^0.3.3",
|
"react-native-reanimated-zoom": "^0.3.3",
|
||||||
"react-native-safe-area-context": "^4.4.1",
|
"react-native-safe-area-context": "^4.4.1",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
AccessibilityProps,
|
AccessibilityProps,
|
||||||
Image,
|
Image,
|
||||||
|
@ -55,15 +55,18 @@ const GracefullyImage = ({
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const [imageLoaded, setImageLoaded] = useState(false)
|
const [imageLoaded, setImageLoaded] = useState(false)
|
||||||
|
|
||||||
|
const [currentUri, setCurrentUri] = useState<string | undefined>(uri.original || uri.remote)
|
||||||
const source = {
|
const source = {
|
||||||
uri: reduceMotionEnabled && uri.static ? uri.static : uri.original
|
uri: reduceMotionEnabled && uri.static ? uri.static : currentUri
|
||||||
}
|
|
||||||
const onLoad = () => {
|
|
||||||
setImageLoaded(true)
|
|
||||||
if (setImageDimensions && source.uri) {
|
|
||||||
Image.getSize(source.uri, (width, height) => setImageDimensions({ width, height }))
|
|
||||||
}
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
(uri.original ? currentUri !== uri.original : true) &&
|
||||||
|
(uri.remote ? currentUri !== uri.remote : true)
|
||||||
|
) {
|
||||||
|
setCurrentUri(uri.original || uri.remote)
|
||||||
}
|
}
|
||||||
|
}, [currentUri, uri.original, uri.remote])
|
||||||
|
|
||||||
const blurhashView = () => {
|
const blurhashView = () => {
|
||||||
if (hidden || !imageLoaded) {
|
if (hidden || !imageLoaded) {
|
||||||
|
@ -92,11 +95,19 @@ const GracefullyImage = ({
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<FastImage
|
<FastImage
|
||||||
source={{
|
source={source}
|
||||||
uri: reduceMotionEnabled && uri.static ? uri.static : uri.original
|
|
||||||
}}
|
|
||||||
style={[{ flex: 1 }, imageStyle]}
|
style={[{ flex: 1 }, imageStyle]}
|
||||||
onLoad={onLoad}
|
onLoad={() => {
|
||||||
|
setImageLoaded(true)
|
||||||
|
if (setImageDimensions && source.uri) {
|
||||||
|
Image.getSize(source.uri, (width, height) => setImageDimensions({ width, height }))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onError={() => {
|
||||||
|
if (uri.original && uri.original === currentUri && uri.remote) {
|
||||||
|
setCurrentUri(uri.remote)
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{blurhashView()}
|
{blurhashView()}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
|
@ -19,12 +19,14 @@ import {
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import * as AuthSession from 'expo-auth-session'
|
import * as AuthSession from 'expo-auth-session'
|
||||||
|
import * as Random from 'expo-random'
|
||||||
import * as WebBrowser from 'expo-web-browser'
|
import * as WebBrowser from 'expo-web-browser'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import React, { RefObject, useCallback, useState } from 'react'
|
import React, { RefObject, useCallback, useState } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { Alert, Image, KeyboardAvoidingView, Platform, TextInput, View } from 'react-native'
|
import { Alert, Image, KeyboardAvoidingView, Platform, TextInput, View } from 'react-native'
|
||||||
import { ScrollView } from 'react-native-gesture-handler'
|
import { ScrollView } from 'react-native-gesture-handler'
|
||||||
|
import { fromByteArray } from 'react-native-quick-base64'
|
||||||
import parse from 'url-parse'
|
import parse from 'url-parse'
|
||||||
import CustomText from '../Text'
|
import CustomText from '../Text'
|
||||||
|
|
||||||
|
@ -67,10 +69,6 @@ const ComponentInstance: React.FC<Props> = ({
|
||||||
const appsMutation = useAppsMutation({
|
const appsMutation = useAppsMutation({
|
||||||
retry: false,
|
retry: false,
|
||||||
onSuccess: async (data, variables) => {
|
onSuccess: async (data, variables) => {
|
||||||
const scopes = featureCheck('deprecate_auth_follow')
|
|
||||||
? ['read', 'write', 'push']
|
|
||||||
: ['read', 'write', 'follow', 'push']
|
|
||||||
|
|
||||||
const clientId = data.client_id
|
const clientId = data.client_id
|
||||||
const clientSecret = data.client_secret
|
const clientSecret = data.client_secret
|
||||||
|
|
||||||
|
@ -79,19 +77,19 @@ const ComponentInstance: React.FC<Props> = ({
|
||||||
const request = new AuthSession.AuthRequest({
|
const request = new AuthSession.AuthRequest({
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
scopes,
|
scopes: variables.scopes,
|
||||||
redirectUri
|
redirectUri
|
||||||
})
|
})
|
||||||
await request.makeAuthUrlAsync(discovery)
|
await request.makeAuthUrlAsync(discovery)
|
||||||
|
|
||||||
const promptResult = await request.promptAsync(discovery, await browserPackage())
|
const promptResult = await request.promptAsync(discovery, await browserPackage())
|
||||||
|
|
||||||
if (promptResult?.type === 'success') {
|
if (promptResult.type === 'success') {
|
||||||
const { accessToken } = await AuthSession.exchangeCodeAsync(
|
const { accessToken } = await AuthSession.exchangeCodeAsync(
|
||||||
{
|
{
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
scopes,
|
scopes: variables.scopes,
|
||||||
redirectUri,
|
redirectUri,
|
||||||
code: promptResult.params.code,
|
code: promptResult.params.code,
|
||||||
extraParams: {
|
extraParams: {
|
||||||
|
@ -162,7 +160,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||||
'admin.sign_up': false,
|
'admin.sign_up': false,
|
||||||
'admin.report': false
|
'admin.report': false
|
||||||
},
|
},
|
||||||
key: Math.random().toString(36).slice(2, 12)
|
key: fromByteArray(Random.getRandomBytes(16))
|
||||||
},
|
},
|
||||||
page_local: {
|
page_local: {
|
||||||
showBoosts: true,
|
showBoosts: true,
|
||||||
|
@ -186,7 +184,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
setGlobalStorage('accounts', accounts?.concat([accountKey]))
|
setGlobalStorage('accounts', (accounts || []).concat([accountKey]))
|
||||||
}
|
}
|
||||||
setAccount(accountKey)
|
setAccount(accountKey)
|
||||||
|
|
||||||
|
@ -195,6 +193,9 @@ const ComponentInstance: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const scopes = featureCheck('deprecate_auth_follow')
|
||||||
|
? ['read', 'write', 'push']
|
||||||
|
: ['read', 'write', 'follow', 'push']
|
||||||
const processUpdate = useCallback(() => {
|
const processUpdate = useCallback(() => {
|
||||||
if (domain) {
|
if (domain) {
|
||||||
const accounts = getGlobalStorage.object('accounts')
|
const accounts = getGlobalStorage.object('accounts')
|
||||||
|
@ -209,12 +210,12 @@ const ComponentInstance: React.FC<Props> = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: t('common:buttons.continue'),
|
text: t('common:buttons.continue'),
|
||||||
onPress: () => appsMutation.mutate({ domain })
|
onPress: () => appsMutation.mutate({ domain, scopes })
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
appsMutation.mutate({ domain })
|
appsMutation.mutate({ domain, scopes })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [domain])
|
}, [domain])
|
||||||
|
|
|
@ -126,7 +126,7 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filterResults?.length && !filterRevealed) {
|
if (filterResults?.length && !filterRevealed) {
|
||||||
return !filterResults.filter(result => result.filter_action === 'hide').length ? (
|
return !filterResults.filter(result => result.filter_action === 'hide')?.length ? (
|
||||||
<Pressable onPress={() => setFilterRevealed(!filterRevealed)}>
|
<Pressable onPress={() => setFilterRevealed(!filterRevealed)}>
|
||||||
<TimelineFiltered filterResults={filterResults} />
|
<TimelineFiltered filterResults={filterResults} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
|
@ -144,7 +144,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||||
>(queryKey)?.pages[0]
|
>(queryKey)?.pages[0]
|
||||||
|
|
||||||
prevActive.current = true
|
prevActive.current = true
|
||||||
prevStatusId.current = firstPage?.body[0].id
|
prevStatusId.current = firstPage?.body[0]?.id
|
||||||
|
|
||||||
await queryFunctionTimeline({
|
await queryFunctionTimeline({
|
||||||
queryKey,
|
queryKey,
|
||||||
|
@ -182,7 +182,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||||
flRef.current?.scrollToOffset({ offset: scrollY.value - 15, animated: true })
|
flRef.current?.scrollToOffset({ offset: scrollY.value - 15, animated: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise(promise => setTimeout(promise, 32))
|
await new Promise(promise => setTimeout(promise, 64))
|
||||||
queryClient.setQueryData<
|
queryClient.setQueryData<
|
||||||
InfiniteData<
|
InfiniteData<
|
||||||
PagedResponse<(Mastodon.Status | Mastodon.Notification | Mastodon.Conversation)[]>
|
PagedResponse<(Mastodon.Status | Mastodon.Notification | Mastodon.Conversation)[]>
|
||||||
|
@ -197,7 +197,8 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||||
const insert = prevCache.current?.slice(-PREV_PER_BATCH)
|
const insert = prevCache.current?.slice(-PREV_PER_BATCH)
|
||||||
prevCache.current = prevCache.current?.slice(0, -PREV_PER_BATCH)
|
prevCache.current = prevCache.current?.slice(0, -PREV_PER_BATCH)
|
||||||
if (insert) {
|
if (insert) {
|
||||||
return { ...page, body: [...insert, ...page.body] }
|
page.body.unshift(...insert)
|
||||||
|
return page
|
||||||
} else {
|
} else {
|
||||||
return page
|
return page
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ const TimelineCard: React.FC = () => {
|
||||||
|
|
||||||
const statusQuery = useStatusQuery({
|
const statusQuery = useStatusQuery({
|
||||||
status: match?.status ? { ...match.status, uri: status.card.url } : undefined,
|
status: match?.status ? { ...match.status, uri: status.card.url } : undefined,
|
||||||
options: { enabled: false, retry: 1 }
|
options: { enabled: false, retry: false }
|
||||||
})
|
})
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (match?.status) {
|
if (match?.status) {
|
||||||
|
@ -47,7 +47,7 @@ const TimelineCard: React.FC = () => {
|
||||||
|
|
||||||
const accountQuery = useAccountQuery({
|
const accountQuery = useAccountQuery({
|
||||||
account: match?.account ? { ...match?.account, url: status.card.url } : undefined,
|
account: match?.account ? { ...match?.account, url: status.card.url } : undefined,
|
||||||
options: { enabled: false, retry: 1 }
|
options: { enabled: false, retry: false }
|
||||||
})
|
})
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (match?.account) {
|
if (match?.account) {
|
||||||
|
|
|
@ -14,9 +14,11 @@ const HeaderSharedReplies: React.FC = () => {
|
||||||
const { t } = useTranslation(['common', 'componentTimeline'])
|
const { t } = useTranslation(['common', 'componentTimeline'])
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const mentionsBeginning = rawContent?.current?.[0]
|
const mentionsBeginning = rawContent?.current?.[0]?.length
|
||||||
|
? rawContent?.current?.[0]
|
||||||
.match(new RegExp(/^(?:@\S+\s+)+/))?.[0]
|
.match(new RegExp(/^(?:@\S+\s+)+/))?.[0]
|
||||||
?.match(new RegExp(/@\S+/, 'g'))
|
?.match(new RegExp(/@\S+/, 'g'))
|
||||||
|
: undefined
|
||||||
excludeMentions &&
|
excludeMentions &&
|
||||||
(excludeMentions.current =
|
(excludeMentions.current =
|
||||||
mentionsBeginning?.length && status?.mentions
|
mentionsBeginning?.length && status?.mentions
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
"action_true": "Deixa de silenciar l'usuari"
|
"action_true": "Deixa de silenciar l'usuari"
|
||||||
},
|
},
|
||||||
"followAs": {
|
"followAs": {
|
||||||
"trigger": "",
|
"trigger": "Segueix com...",
|
||||||
"succeed_default": "",
|
"succeed_default": "Seguint a @{{target}} com @{{source}}",
|
||||||
"succeed_locked": "",
|
"succeed_locked": "Enviada la sol·licitud de seguiment a @{{target}} com {{source}}, pendent d'aprovar-la",
|
||||||
"failed": ""
|
"failed": "Segueix com"
|
||||||
},
|
},
|
||||||
"blockReport": "",
|
"blockReport": "Bloqueja i denuncia...",
|
||||||
"block": {
|
"block": {
|
||||||
"action_false": "Bloqueja l'usuari",
|
"action_false": "Bloqueja l'usuari",
|
||||||
"action_true": "Deixa de bloquejar l'usuari",
|
"action_true": "Deixa de bloquejar l'usuari",
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
"action_true": "Stummschaltung des Nutzers aufheben"
|
"action_true": "Stummschaltung des Nutzers aufheben"
|
||||||
},
|
},
|
||||||
"followAs": {
|
"followAs": {
|
||||||
"trigger": "",
|
"trigger": "Folgen als…",
|
||||||
"succeed_default": "",
|
"succeed_default": "Folgt nun @{{target}} als @{{source}}",
|
||||||
"succeed_locked": "",
|
"succeed_locked": "Follower-Anfrage an @{{target}} mit {{source}} gesendet, Bestätigung ausstehend",
|
||||||
"failed": ""
|
"failed": "Folgen als…"
|
||||||
},
|
},
|
||||||
"blockReport": "",
|
"blockReport": "Blockieren und melden…",
|
||||||
"block": {
|
"block": {
|
||||||
"action_false": "Nutzer blockieren",
|
"action_false": "Nutzer blockieren",
|
||||||
"action_true": "User entblocken",
|
"action_true": "User entblocken",
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
"action_true": "Dejar de silenciar al usuario"
|
"action_true": "Dejar de silenciar al usuario"
|
||||||
},
|
},
|
||||||
"followAs": {
|
"followAs": {
|
||||||
"trigger": "",
|
"trigger": "Seguir como...",
|
||||||
"succeed_default": "",
|
"succeed_default": "Siguiendo @{{target}} como @{{source}}",
|
||||||
"succeed_locked": "",
|
"succeed_locked": "Enviado la solicitud de seguimiento a @{{target}} como {{source}}, pendiente de aprobación",
|
||||||
"failed": ""
|
"failed": "Seguir como"
|
||||||
},
|
},
|
||||||
"blockReport": "",
|
"blockReport": "Bloquear y denunciar...",
|
||||||
"block": {
|
"block": {
|
||||||
"action_false": "Bloquear usuario",
|
"action_false": "Bloquear usuario",
|
||||||
"action_true": "Desbloquear usuario",
|
"action_true": "Desbloquear usuario",
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
"action_true": "Dempen opheffen voor gebruiker"
|
"action_true": "Dempen opheffen voor gebruiker"
|
||||||
},
|
},
|
||||||
"followAs": {
|
"followAs": {
|
||||||
"trigger": "",
|
"trigger": "Volg als...",
|
||||||
"succeed_default": "",
|
"succeed_default": "Je volgt nu @{{target}} met @{{source}}",
|
||||||
"succeed_locked": "",
|
"succeed_locked": "Verstuurde het volgverzoek naar @{{target}} met {{source}}, in afwachting van goedkeuring",
|
||||||
"failed": ""
|
"failed": "Volg als"
|
||||||
},
|
},
|
||||||
"blockReport": "",
|
"blockReport": "Blokkeren en rapporten...",
|
||||||
"block": {
|
"block": {
|
||||||
"action_false": "Gebruiker blokkeren",
|
"action_false": "Gebruiker blokkeren",
|
||||||
"action_true": "Gebruiker deblokkeren",
|
"action_true": "Gebruiker deblokkeren",
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
"action_true": "Wyłącz wyciszenie"
|
"action_true": "Wyłącz wyciszenie"
|
||||||
},
|
},
|
||||||
"followAs": {
|
"followAs": {
|
||||||
"trigger": "",
|
"trigger": "Obserwuj jako...",
|
||||||
"succeed_default": "",
|
"succeed_default": "Teraz obserwujesz @{{target}} z @{{source}}",
|
||||||
"succeed_locked": "",
|
"succeed_locked": "Wysłano prośbę o obserwowanie do @{{target}} z {{source}}, oczekiwanie na zatwierdzenie",
|
||||||
"failed": ""
|
"failed": "Obserwuj jako"
|
||||||
},
|
},
|
||||||
"blockReport": "",
|
"blockReport": "Zablokuj i zgłoś...",
|
||||||
"block": {
|
"block": {
|
||||||
"action_false": "Zablokuj użytkownika",
|
"action_false": "Zablokuj użytkownika",
|
||||||
"action_true": "Odblokuj użytkownika",
|
"action_true": "Odblokuj użytkownika",
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
"action_true": "Зняти заглушення з користувача"
|
"action_true": "Зняти заглушення з користувача"
|
||||||
},
|
},
|
||||||
"followAs": {
|
"followAs": {
|
||||||
"trigger": "",
|
"trigger": "Стежити як...",
|
||||||
"succeed_default": "",
|
"succeed_default": "Тепер ви стежите за @{{target}} з @{{source}}",
|
||||||
"succeed_locked": "",
|
"succeed_locked": "Запит на стеження за @{{target}} з {{source}} надіслано, очікується затвердження",
|
||||||
"failed": ""
|
"failed": "Стежити як"
|
||||||
},
|
},
|
||||||
"blockReport": "",
|
"blockReport": "Заблокувати й поскаржитися...",
|
||||||
"block": {
|
"block": {
|
||||||
"action_false": "Заблокувати користувача",
|
"action_false": "Заблокувати користувача",
|
||||||
"action_true": "Розблокувати користувача",
|
"action_true": "Розблокувати користувача",
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
},
|
},
|
||||||
"followAs": {
|
"followAs": {
|
||||||
"trigger": "关注…",
|
"trigger": "关注…",
|
||||||
"succeed_default": "{{source}} 正在关注 {{target}}",
|
"succeed_default": "@{{source}} 正在关注 @{{target}}",
|
||||||
"succeed_locked": "已从 {{source}} 发送关注请求至 {{target}},等待通过",
|
"succeed_locked": "已从 @{{source}} 发送关注请求至 @{{target}},等待通过",
|
||||||
"failed": "用其它账户关注"
|
"failed": "用其它账户关注"
|
||||||
},
|
},
|
||||||
"blockReport": "屏蔽与举报…",
|
"blockReport": "屏蔽与举报…",
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
"action_true": "解除靜音使用者"
|
"action_true": "解除靜音使用者"
|
||||||
},
|
},
|
||||||
"followAs": {
|
"followAs": {
|
||||||
"trigger": "",
|
"trigger": "跟隨...",
|
||||||
"succeed_default": "",
|
"succeed_default": "@{{source}} 正在跟隨 @{{target}}",
|
||||||
"succeed_locked": "",
|
"succeed_locked": "已從 {{source}} 發送跟隨請求至 @{{target}},等待同意",
|
||||||
"failed": ""
|
"failed": "用其它帳號跟隨"
|
||||||
},
|
},
|
||||||
"blockReport": "",
|
"blockReport": "封鎖並檢舉…",
|
||||||
"block": {
|
"block": {
|
||||||
"action_false": "封鎖使用者",
|
"action_false": "封鎖使用者",
|
||||||
"action_true": "解除封鎖使用者",
|
"action_true": "解除封鎖使用者",
|
||||||
|
|
|
@ -92,7 +92,7 @@ const ScreenAccountSelection = ({
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const { t } = useTranslation('screenAccountSelection')
|
const { t } = useTranslation('screenAccountSelection')
|
||||||
|
|
||||||
const accounts = getReadableAccounts()
|
const accounts = getReadableAccounts(true)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
|
|
@ -17,7 +17,7 @@ const ComposeDrafts: React.FC<Props> = ({ accessibleRefDrafts }) => {
|
||||||
const navigation = useNavigation<any>()
|
const navigation = useNavigation<any>()
|
||||||
const { composeState } = useContext(ComposeContext)
|
const { composeState } = useContext(ComposeContext)
|
||||||
const [drafts] = useAccountStorage.object('drafts')
|
const [drafts] = useAccountStorage.object('drafts')
|
||||||
const draftsCount = drafts.filter(draft => draft.timestamp !== composeState.timestamp).length
|
const draftsCount = drafts?.filter(draft => draft.timestamp !== composeState.timestamp).length
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
layoutAnimation()
|
layoutAnimation()
|
||||||
|
|
|
@ -7,8 +7,8 @@ import { useTranslation } from 'react-i18next'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
|
|
||||||
const ComposePostingAs = () => {
|
const ComposePostingAs = () => {
|
||||||
const accounts = useGlobalStorage.object('accounts')
|
const [accounts] = useGlobalStorage.object('accounts')
|
||||||
if (!accounts.length) return null
|
if (!accounts?.length) return null
|
||||||
|
|
||||||
const { t } = useTranslation('screenCompose')
|
const { t } = useTranslation('screenCompose')
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
|
@ -7,7 +7,7 @@ import ComposeRoot from '@screens/Compose/Root'
|
||||||
import { formatText } from '@screens/Compose/utils/processText'
|
import { formatText } from '@screens/Compose/utils/processText'
|
||||||
import { useQueryClient } from '@tanstack/react-query'
|
import { useQueryClient } from '@tanstack/react-query'
|
||||||
import { handleError } from '@utils/api/helpers'
|
import { handleError } from '@utils/api/helpers'
|
||||||
import { RootStackScreenProps, useNavState } from '@utils/navigation/navigators'
|
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||||
import { searchLocalStatus } from '@utils/queryHooks/search'
|
import { searchLocalStatus } from '@utils/queryHooks/search'
|
||||||
|
@ -346,13 +346,6 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (params?.type) {
|
switch (params?.type) {
|
||||||
case undefined:
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ['Timeline', { page: 'Following' }],
|
|
||||||
exact: false
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case 'conversation':
|
|
||||||
case 'edit': // doesn't work
|
case 'edit': // doesn't work
|
||||||
// mutateTimeline.mutate({
|
// mutateTimeline.mutate({
|
||||||
// type: 'editItem',
|
// type: 'editItem',
|
||||||
|
@ -361,11 +354,20 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||||
// })
|
// })
|
||||||
// break
|
// break
|
||||||
case 'deleteEdit':
|
case 'deleteEdit':
|
||||||
case 'reply':
|
|
||||||
for (const navState of params.navigationState) {
|
for (const navState of params.navigationState) {
|
||||||
navState && queryClient.invalidateQueries(navState)
|
navState && queryClient.invalidateQueries(navState)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case 'conversation':
|
||||||
|
case 'reply':
|
||||||
|
if (params.navigationState) {
|
||||||
|
for (const navState of params.navigationState) {
|
||||||
|
navState &&
|
||||||
|
navState[1].page !== 'Following' &&
|
||||||
|
queryClient.invalidateQueries(navState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
removeDraft(composeState.timestamp)
|
removeDraft(composeState.timestamp)
|
||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
|
|
|
@ -55,7 +55,7 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||||
: t('tabs.local.name')
|
: t('tabs.local.name')
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{page.page === 'Following' && !pageLocal.showBoosts ? (
|
{page.page === 'Following' && !pageLocal?.showBoosts ? (
|
||||||
<Icon
|
<Icon
|
||||||
name='Repeat'
|
name='Repeat'
|
||||||
size={StyleConstants.Font.Size.M}
|
size={StyleConstants.Font.Size.M}
|
||||||
|
@ -64,7 +64,7 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||||
crossOut
|
crossOut
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{page.page === 'Following' && !pageLocal.showReplies ? (
|
{page.page === 'Following' && !pageLocal?.showReplies ? (
|
||||||
<Icon
|
<Icon
|
||||||
name='MessageCircle'
|
name='MessageCircle'
|
||||||
size={StyleConstants.Font.Size.M}
|
size={StyleConstants.Font.Size.M}
|
||||||
|
@ -94,20 +94,20 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.CheckboxItem
|
<DropdownMenu.CheckboxItem
|
||||||
key='showBoosts'
|
key='showBoosts'
|
||||||
value={pageLocal.showBoosts ? 'on' : 'off'}
|
value={pageLocal?.showBoosts ? 'on' : 'off'}
|
||||||
onValueChange={() => {
|
onValueChange={() => {
|
||||||
setQueryKey([
|
setQueryKey([
|
||||||
'Timeline',
|
'Timeline',
|
||||||
{
|
{
|
||||||
page: 'Following',
|
page: 'Following',
|
||||||
showBoosts: !pageLocal.showBoosts,
|
showBoosts: !pageLocal?.showBoosts,
|
||||||
showReplies: pageLocal.showReplies
|
showReplies: pageLocal?.showReplies
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
setAccountStorage([
|
setAccountStorage([
|
||||||
{
|
{
|
||||||
key: 'page_local',
|
key: 'page_local',
|
||||||
value: { ...pageLocal, showBoosts: !pageLocal.showBoosts }
|
value: { ...pageLocal, showBoosts: !pageLocal?.showBoosts }
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}}
|
}}
|
||||||
|
@ -117,20 +117,20 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||||
</DropdownMenu.CheckboxItem>
|
</DropdownMenu.CheckboxItem>
|
||||||
<DropdownMenu.CheckboxItem
|
<DropdownMenu.CheckboxItem
|
||||||
key='showReplies'
|
key='showReplies'
|
||||||
value={pageLocal.showReplies ? 'on' : 'off'}
|
value={pageLocal?.showReplies ? 'on' : 'off'}
|
||||||
onValueChange={() => {
|
onValueChange={() => {
|
||||||
setQueryKey([
|
setQueryKey([
|
||||||
'Timeline',
|
'Timeline',
|
||||||
{
|
{
|
||||||
page: 'Following',
|
page: 'Following',
|
||||||
showBoosts: pageLocal.showBoosts,
|
showBoosts: pageLocal?.showBoosts,
|
||||||
showReplies: !pageLocal.showReplies
|
showReplies: !pageLocal?.showReplies
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
setAccountStorage([
|
setAccountStorage([
|
||||||
{
|
{
|
||||||
key: 'page_local',
|
key: 'page_local',
|
||||||
value: { ...pageLocal, showReplies: !pageLocal.showReplies }
|
value: { ...pageLocal, showReplies: !pageLocal?.showReplies }
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -49,7 +49,7 @@ const TabMeProfileRoot: React.FC<
|
||||||
<ProfileAvatarHeader type='header' messageRef={messageRef} />
|
<ProfileAvatarHeader type='header' messageRef={messageRef} />
|
||||||
<MenuRow
|
<MenuRow
|
||||||
title={t('screenTabs:me.profile.root.note.title')}
|
title={t('screenTabs:me.profile.root.note.title')}
|
||||||
content={data?.source.note}
|
content={data?.source?.note}
|
||||||
loading={isFetching}
|
loading={isFetching}
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
|
|
@ -17,10 +17,12 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import * as Notifications from 'expo-notifications'
|
import * as Notifications from 'expo-notifications'
|
||||||
|
import * as Random from 'expo-random'
|
||||||
import * as WebBrowser from 'expo-web-browser'
|
import * as WebBrowser from 'expo-web-browser'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { AppState, Linking, Platform, ScrollView, View } from 'react-native'
|
import { AppState, Linking, Platform, ScrollView, View } from 'react-native'
|
||||||
|
import { fromByteArray } from 'react-native-quick-base64'
|
||||||
|
|
||||||
const TabMePush: React.FC = () => {
|
const TabMePush: React.FC = () => {
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
@ -178,6 +180,11 @@ const TabMePush: React.FC = () => {
|
||||||
|
|
||||||
setAccountStorage([{ key: 'push', value: { ...push, global: false } }])
|
setAccountStorage([{ key: 'push', value: { ...push, global: false } }])
|
||||||
} else {
|
} else {
|
||||||
|
// Fix a bug for some users of v4.8.0
|
||||||
|
let authKey = push.key
|
||||||
|
if (push.key.length <= 10) {
|
||||||
|
authKey = fromByteArray(Random.getRandomBytes(16))
|
||||||
|
}
|
||||||
// Turning on
|
// Turning on
|
||||||
const randomPath = (Math.random() + 1).toString(36).substring(2)
|
const randomPath = (Math.random() + 1).toString(36).substring(2)
|
||||||
|
|
||||||
|
@ -189,7 +196,7 @@ const TabMePush: React.FC = () => {
|
||||||
'subscription[keys][p256dh]',
|
'subscription[keys][p256dh]',
|
||||||
'BMn2PLpZrMefG981elzG6SB1EY9gU7QZwmtZ/a/J2vUeWG+zXgeskMPwHh4T/bxsD4l7/8QT94F57CbZqYRRfJo='
|
'BMn2PLpZrMefG981elzG6SB1EY9gU7QZwmtZ/a/J2vUeWG+zXgeskMPwHh4T/bxsD4l7/8QT94F57CbZqYRRfJo='
|
||||||
)
|
)
|
||||||
formData.append('subscription[keys][auth]', push.key)
|
formData.append('subscription[keys][auth]', authKey)
|
||||||
for (const [key, value] of Object.entries(push.alerts)) {
|
for (const [key, value] of Object.entries(push.alerts)) {
|
||||||
formData.append(`data[alerts][${key}]`, value.toString())
|
formData.append(`data[alerts][${key}]`, value.toString())
|
||||||
}
|
}
|
||||||
|
@ -225,7 +232,9 @@ const TabMePush: React.FC = () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
setAccountStorage([{ key: 'push', value: { ...push, global: true } }])
|
setAccountStorage([
|
||||||
|
{ key: 'push', value: { ...push, global: true, key: authKey } }
|
||||||
|
])
|
||||||
|
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
setChannels(true)
|
setChannels(true)
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { MenuContainer, MenuRow } from '@components/Menu'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
||||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||||
|
import { useFollowedTagsQuery } from '@utils/queryHooks/tags'
|
||||||
import { useAccountStorage } from '@utils/storage/actions'
|
import { useAccountStorage } from '@utils/storage/actions'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
@ -17,6 +18,12 @@ const Collections: React.FC = () => {
|
||||||
onSuccess: data => setPageMe({ ...pageMe, lists: { shown: !!data?.length } })
|
onSuccess: data => setPageMe({ ...pageMe, lists: { shown: !!data?.length } })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
useFollowedTagsQuery({
|
||||||
|
options: {
|
||||||
|
onSuccess: data =>
|
||||||
|
setPageMe({ ...pageMe, followedTags: { shown: !!data.pages[0].body.length } })
|
||||||
|
}
|
||||||
|
})
|
||||||
useAnnouncementQuery({
|
useAnnouncementQuery({
|
||||||
showAll: true,
|
showAll: true,
|
||||||
options: {
|
options: {
|
||||||
|
@ -53,7 +60,7 @@ const Collections: React.FC = () => {
|
||||||
title={t('screenTabs:me.stacks.favourites.name')}
|
title={t('screenTabs:me.stacks.favourites.name')}
|
||||||
onPress={() => navigation.navigate('Tab-Me-Favourites')}
|
onPress={() => navigation.navigate('Tab-Me-Favourites')}
|
||||||
/>
|
/>
|
||||||
{pageMe.lists.shown ? (
|
{pageMe.lists?.shown ? (
|
||||||
<MenuRow
|
<MenuRow
|
||||||
iconFront='List'
|
iconFront='List'
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
|
@ -61,7 +68,7 @@ const Collections: React.FC = () => {
|
||||||
onPress={() => navigation.navigate('Tab-Me-List-List')}
|
onPress={() => navigation.navigate('Tab-Me-List-List')}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{pageMe.followedTags.shown ? (
|
{pageMe.followedTags?.shown ? (
|
||||||
<MenuRow
|
<MenuRow
|
||||||
iconFront='Hash'
|
iconFront='Hash'
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
|
@ -69,7 +76,7 @@ const Collections: React.FC = () => {
|
||||||
onPress={() => navigation.navigate('Tab-Me-FollowedTags')}
|
onPress={() => navigation.navigate('Tab-Me-FollowedTags')}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{pageMe.announcements.shown ? (
|
{pageMe.announcements?.shown ? (
|
||||||
<MenuRow
|
<MenuRow
|
||||||
iconFront='Clipboard'
|
iconFront='Clipboard'
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { adaptiveScale } from '@utils/styles/scaling'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import { ScrollView } from 'react-native-gesture-handler'
|
import { ScrollView } from 'react-native-gesture-handler'
|
||||||
|
|
||||||
export const mapFontsizeToName = (size: StorageGlobal['app.font_size']) => {
|
export const mapFontsizeToName = (size: StorageGlobal['app.font_size']) => {
|
||||||
|
|
|
@ -70,8 +70,8 @@ const TabNotificationsFilters: React.FC<
|
||||||
<MenuRow
|
<MenuRow
|
||||||
key={index}
|
key={index}
|
||||||
title={t(`screenTabs:me.push.${type}.heading`)}
|
title={t(`screenTabs:me.push.${type}.heading`)}
|
||||||
switchValue={filters[type]}
|
switchValue={filters?.[type]}
|
||||||
switchOnValueChange={() => setFilters({ ...filters, [type]: !filters[type] })}
|
switchOnValueChange={() => setFilters({ ...filters, [type]: !filters?.[type] })}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</MenuContainer>
|
</MenuContainer>
|
||||||
|
@ -80,8 +80,8 @@ const TabNotificationsFilters: React.FC<
|
||||||
<MenuRow
|
<MenuRow
|
||||||
key={type}
|
key={type}
|
||||||
title={t(`screenTabs:me.push.${type}.heading`)}
|
title={t(`screenTabs:me.push.${type}.heading`)}
|
||||||
switchValue={filters[type]}
|
switchValue={filters?.[type]}
|
||||||
switchOnValueChange={() => setFilters({ ...filters, [type]: !filters[type] })}
|
switchOnValueChange={() => setFilters({ ...filters, [type]: !filters?.[type] })}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</MenuContainer>
|
</MenuContainer>
|
||||||
|
|
|
@ -38,8 +38,11 @@ const Root: React.FC<NativeStackScreenProps<TabPublicStackParamList, 'Tab-Public
|
||||||
const previousSegment = getGlobalStorage.string('app.prev_public_segment')
|
const previousSegment = getGlobalStorage.string('app.prev_public_segment')
|
||||||
const segments: StorageGlobal['app.prev_public_segment'][] = ['Local', 'LocalPublic', 'Trending']
|
const segments: StorageGlobal['app.prev_public_segment'][] = ['Local', 'LocalPublic', 'Trending']
|
||||||
const [segment, setSegment] = useState<number>(
|
const [segment, setSegment] = useState<number>(
|
||||||
|
Math.min(
|
||||||
|
0,
|
||||||
segments.findIndex(segment => segment === previousSegment)
|
segments.findIndex(segment => segment === previousSegment)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
const [routes] = useState([
|
const [routes] = useState([
|
||||||
{ key: 'Local', title: t('tabs.public.segments.local') },
|
{ key: 'Local', title: t('tabs.public.segments.local') },
|
||||||
{ key: 'LocalPublic', title: t('tabs.public.segments.federated') },
|
{ key: 'LocalPublic', title: t('tabs.public.segments.federated') },
|
||||||
|
|
|
@ -18,7 +18,7 @@ const AccountInformationAccount: React.FC = () => {
|
||||||
const [acct] = useAccountStorage.string('auth.account.acct')
|
const [acct] = useAccountStorage.string('auth.account.acct')
|
||||||
const domain = getAccountStorage.string('auth.account.domain')
|
const domain = getAccountStorage.string('auth.account.domain')
|
||||||
|
|
||||||
const localInstance = account?.acct.includes('@') ? account?.acct.includes(`@${domain}`) : true
|
const localInstance = account?.acct?.includes('@') ? account?.acct?.includes(`@${domain}`) : true
|
||||||
|
|
||||||
if (account || pageMe) {
|
if (account || pageMe) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -162,7 +162,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||||
}).then(res => res.body)
|
}).then(res => res.body)
|
||||||
|
|
||||||
if (!context?.ancestors.length && !context?.descendants.length) {
|
if (!context?.ancestors.length && !context?.descendants.length) {
|
||||||
return Promise.resolve([])
|
return Promise.resolve([{ ...toot }])
|
||||||
}
|
}
|
||||||
|
|
||||||
highlightIndex.current = context.ancestors.length
|
highlightIndex.current = context.ancestors.length
|
||||||
|
@ -182,7 +182,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled:
|
enabled:
|
||||||
query.isFetched &&
|
(toot._remote ? true : query.isFetched) &&
|
||||||
['public', 'unlisted'].includes(toot.visibility) &&
|
['public', 'unlisted'].includes(toot.visibility) &&
|
||||||
match?.domain !== getAccountStorage.string('auth.domain'),
|
match?.domain !== getAccountStorage.string('auth.domain'),
|
||||||
staleTime: 0,
|
staleTime: 0,
|
||||||
|
@ -198,16 +198,12 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||||
queryClient.setQueryData<{
|
queryClient.setQueryData<{
|
||||||
pages: { body: Mastodon.Status[] }[]
|
pages: { body: Mastodon.Status[] }[]
|
||||||
}>(queryKey.local, old => {
|
}>(queryKey.local, old => {
|
||||||
if (!old) return old
|
|
||||||
|
|
||||||
setHasRemoteContent(true)
|
setHasRemoteContent(true)
|
||||||
return {
|
return {
|
||||||
pages: [
|
pages: [
|
||||||
{
|
{
|
||||||
body: data.map(remote => {
|
body: data.map(remote => {
|
||||||
const localMatch = query.data?.pages[0].body.find(
|
const localMatch = old?.pages[0].body.find(local => local.uri === remote.uri)
|
||||||
local => local.uri === remote.uri
|
|
||||||
)
|
|
||||||
if (localMatch) {
|
if (localMatch) {
|
||||||
return { ...localMatch, _level: remote._level }
|
return { ...localMatch, _level: remote._level }
|
||||||
} else {
|
} else {
|
||||||
|
@ -257,7 +253,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const heights = useRef<(number | undefined)[]>([])
|
const [heights, setHeights] = useState<{ [key: number]: number }>({})
|
||||||
const MAX_LEVEL = 10
|
const MAX_LEVEL = 10
|
||||||
const ARC = StyleConstants.Avatar.XS / 4
|
const ARC = StyleConstants.Avatar.XS / 4
|
||||||
|
|
||||||
|
@ -284,7 +280,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||||
nativeEvent: {
|
nativeEvent: {
|
||||||
layout: { height }
|
layout: { height }
|
||||||
}
|
}
|
||||||
}) => (heights.current[index] = height)}
|
}) => setHeights({ ...heights, [index]: height })}
|
||||||
>
|
>
|
||||||
<TimelineDefault
|
<TimelineDefault
|
||||||
item={item}
|
item={item}
|
||||||
|
@ -326,7 +322,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||||
d={
|
d={
|
||||||
`M ${(i + 1) * StyleConstants.Spacing.S} 0 ` +
|
`M ${(i + 1) * StyleConstants.Spacing.S} 0 ` +
|
||||||
`v ${
|
`v ${
|
||||||
(heights.current[index] || 999) -
|
(heights[index] || 999) -
|
||||||
(StyleConstants.Spacing.S * 1.5 + StyleConstants.Font.Size.L) / 2 -
|
(StyleConstants.Spacing.S * 1.5 + StyleConstants.Font.Size.L) / 2 -
|
||||||
StyleConstants.Avatar.XS / 2
|
StyleConstants.Avatar.XS / 2
|
||||||
} ` +
|
} ` +
|
||||||
|
@ -347,7 +343,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||||
d={
|
d={
|
||||||
`M ${(i + 1) * StyleConstants.Spacing.S} 0 ` +
|
`M ${(i + 1) * StyleConstants.Spacing.S} 0 ` +
|
||||||
`v ${
|
`v ${
|
||||||
(heights.current[index] || 999) -
|
(heights[index] || 999) -
|
||||||
(StyleConstants.Spacing.S * 1.5 +
|
(StyleConstants.Spacing.S * 1.5 +
|
||||||
StyleConstants.Font.Size.L * 1.35) /
|
StyleConstants.Font.Size.L * 1.35) /
|
||||||
2
|
2
|
||||||
|
|
|
@ -29,24 +29,23 @@ const useAppsQuery = (
|
||||||
return useQuery(queryKey, queryFunctionApps, params?.options)
|
return useQuery(queryKey, queryFunctionApps, params?.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MutationVarsApps = { domain: string }
|
type MutationVarsApps = { domain: string; scopes: string[] }
|
||||||
|
|
||||||
export const redirectUri = AuthSession.makeRedirectUri({
|
export const redirectUri = AuthSession.makeRedirectUri({
|
||||||
native: 'tooot://instance-auth',
|
native: 'tooot://instance-auth',
|
||||||
useProxy: false
|
useProxy: false
|
||||||
})
|
})
|
||||||
const mutationFunctionApps = async ({ domain }: MutationVarsApps) => {
|
const mutationFunctionApps = async ({ domain, scopes }: MutationVarsApps) => {
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('client_name', 'tooot')
|
|
||||||
formData.append('website', 'https://tooot.app')
|
|
||||||
formData.append('redirect_uris', redirectUri)
|
|
||||||
formData.append('scopes', 'read write follow push')
|
|
||||||
|
|
||||||
return apiGeneral<Mastodon.Apps>({
|
return apiGeneral<Mastodon.Apps>({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
domain: domain,
|
domain: domain,
|
||||||
url: `api/v1/apps`,
|
url: 'api/v1/apps',
|
||||||
body: formData
|
body: {
|
||||||
|
client_name: 'tooot',
|
||||||
|
website: 'https://tooot.app',
|
||||||
|
redirect_uris: redirectUri,
|
||||||
|
scopes: scopes.join(' ')
|
||||||
|
}
|
||||||
}).then(res => res.body)
|
}).then(res => res.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { QueryClient } from '@tanstack/react-query'
|
import { QueryClient } from '@tanstack/react-query'
|
||||||
|
|
||||||
|
export const globalRetry = (failureCount: number) => failureCount <= 2
|
||||||
|
|
||||||
export const queryClient = new QueryClient({
|
export const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
|
@ -8,17 +10,9 @@ export const queryClient = new QueryClient({
|
||||||
if ([401, 404].includes(error?.status)) {
|
if ([401, 404].includes(error?.status)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (failureCount <= 2) {
|
|
||||||
return true
|
return globalRetry(failureCount)
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
logger: {
|
|
||||||
log: log => console.log(log),
|
|
||||||
warn: () => {},
|
|
||||||
error: () => {}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -52,7 +52,7 @@ export const searchLocalStatus = async (uri: Mastodon.Status['uri']): Promise<Ma
|
||||||
return await queryClient
|
return await queryClient
|
||||||
.fetchQuery(queryKey, queryFunction, { staleTime: 3600, cacheTime: 3600 })
|
.fetchQuery(queryKey, queryFunction, { staleTime: 3600, cacheTime: 3600 })
|
||||||
.then(res =>
|
.then(res =>
|
||||||
res.statuses[0].uri === uri || res.statuses[0].url === uri
|
res.statuses[0]?.uri === uri || res.statuses[0]?.url === uri
|
||||||
? res.statuses[0]
|
? res.statuses[0]
|
||||||
: Promise.reject()
|
: Promise.reject()
|
||||||
)
|
)
|
||||||
|
|
|
@ -43,7 +43,7 @@ export const useGlobalStorage = {
|
||||||
: never,
|
: never,
|
||||||
number: <T extends keyof StorageGlobal>(key: T) => {
|
number: <T extends keyof StorageGlobal>(key: T) => {
|
||||||
if (Platform.OS === 'ios') {
|
if (Platform.OS === 'ios') {
|
||||||
return useMMKVString(key, storage.global) as NonNullable<StorageGlobal[T]> extends number
|
return useMMKVNumber(key, storage.global) as NonNullable<StorageGlobal[T]> extends number
|
||||||
? [StorageGlobal[T], (valud: StorageGlobal[T]) => void]
|
? [StorageGlobal[T], (valud: StorageGlobal[T]) => void]
|
||||||
: never
|
: never
|
||||||
} else {
|
} else {
|
||||||
|
@ -233,14 +233,15 @@ export type ReadableAccountType = {
|
||||||
key: string
|
key: string
|
||||||
active: boolean
|
active: boolean
|
||||||
}
|
}
|
||||||
export const getReadableAccounts = (): ReadableAccountType[] => {
|
export const getReadableAccounts = (withoutActive: boolean = false): ReadableAccountType[] => {
|
||||||
const accountActive = getGlobalStorage.string('account.active')
|
const accountActive = !withoutActive && getGlobalStorage.string('account.active')
|
||||||
const accounts = getGlobalStorage.object('accounts')?.sort((a, b) => a.localeCompare(b))
|
const accounts = getGlobalStorage.object('accounts')?.sort((a, b) => a.localeCompare(b))
|
||||||
|
!withoutActive &&
|
||||||
accounts?.splice(
|
accounts?.splice(
|
||||||
accounts.findIndex(a => a === accountActive),
|
accounts.findIndex(a => a === accountActive),
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
accounts?.unshift(accountActive || '')
|
!withoutActive && accounts?.unshift(accountActive || '')
|
||||||
return (
|
return (
|
||||||
accounts?.map(account => {
|
accounts?.map(account => {
|
||||||
const details = getAccountDetails(
|
const details = getAccountDetails(
|
||||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -9718,6 +9718,18 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-native-quick-base64@npm:^2.0.5":
|
||||||
|
version: 2.0.5
|
||||||
|
resolution: "react-native-quick-base64@npm:2.0.5"
|
||||||
|
dependencies:
|
||||||
|
base64-js: ^1.5.1
|
||||||
|
peerDependencies:
|
||||||
|
react: "*"
|
||||||
|
react-native: "*"
|
||||||
|
checksum: 964599ad68d54dd741357850c72b4a5e08f247fa294590f18866896662107b734b6810703fdd972560cee8087095f14f06fc6ef69944c59c41dbbd885a7832bb
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-native-reanimated-zoom@npm:^0.3.3":
|
"react-native-reanimated-zoom@npm:^0.3.3":
|
||||||
version: 0.3.3
|
version: 0.3.3
|
||||||
resolution: "react-native-reanimated-zoom@npm:0.3.3"
|
resolution: "react-native-reanimated-zoom@npm:0.3.3"
|
||||||
|
@ -11412,6 +11424,7 @@ __metadata:
|
||||||
react-native-language-detection: ^0.2.2
|
react-native-language-detection: ^0.2.2
|
||||||
react-native-mmkv: ^2.5.1
|
react-native-mmkv: ^2.5.1
|
||||||
react-native-pager-view: ^6.1.2
|
react-native-pager-view: ^6.1.2
|
||||||
|
react-native-quick-base64: ^2.0.5
|
||||||
react-native-reanimated: ^2.13.0
|
react-native-reanimated: ^2.13.0
|
||||||
react-native-reanimated-zoom: ^0.3.3
|
react-native-reanimated-zoom: ^0.3.3
|
||||||
react-native-safe-area-context: ^4.4.1
|
react-native-safe-area-context: ^4.4.1
|
||||||
|
|
Loading…
Reference in New Issue