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