Merge branch 'main' into candidate

This commit is contained in:
xmflsct 2023-01-11 22:54:37 +01:00
commit d0db2385ca
33 changed files with 190 additions and 150 deletions

View File

@ -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",

View File

@ -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
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 = () => {
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>

View File

@ -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])

View File

@ -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>

View File

@ -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
}

View File

@ -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) {

View File

@ -14,9 +14,11 @@ const HeaderSharedReplies: React.FC = () => {
const { t } = useTranslation(['common', 'componentTimeline'])
const { colors } = useTheme()
const mentionsBeginning = rawContent?.current?.[0]
.match(new RegExp(/^(?:@\S+\s+)+/))?.[0]
?.match(new RegExp(/@\S+/, 'g'))
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

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "Розблокувати користувача",

View File

@ -17,8 +17,8 @@
},
"followAs": {
"trigger": "关注…",
"succeed_default": "{{source}} 正在关注 {{target}}",
"succeed_locked": "已从 {{source}} 发送关注请求至 {{target}},等待通过",
"succeed_default": "@{{source}} 正在关注 @{{target}}",
"succeed_locked": "已从 @{{source}} 发送关注请求至 @{{target}},等待通过",
"failed": "用其它账户关注"
},
"blockReport": "屏蔽与举报…",

View File

@ -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": "解除封鎖使用者",

View File

@ -92,7 +92,7 @@ const ScreenAccountSelection = ({
const { colors } = useTheme()
const { t } = useTranslation('screenAccountSelection')
const accounts = getReadableAccounts()
const accounts = getReadableAccounts(true)
return (
<ScrollView

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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 }
}
])
}}

View File

@ -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={() => {

View File

@ -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)

View File

@ -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'

View File

@ -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']) => {

View File

@ -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>

View File

@ -38,7 +38,10 @@ 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>(
segments.findIndex(segment => segment === previousSegment)
Math.min(
0,
segments.findIndex(segment => segment === previousSegment)
)
)
const [routes] = useState([
{ key: 'Local', title: t('tabs.public.segments.local') },

View File

@ -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 (

View File

@ -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

View File

@ -1,9 +1,9 @@
import {
QueryFunctionContext,
useMutation,
UseMutationOptions,
useQuery,
UseQueryOptions
QueryFunctionContext,
useMutation,
UseMutationOptions,
useQuery,
UseQueryOptions
} from '@tanstack/react-query'
import apiGeneral from '@utils/api/general'
import apiInstance from '@utils/api/instance'
@ -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)
}

View File

@ -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: () => {}
}
})

View File

@ -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()
)

View File

@ -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))
accounts?.splice(
accounts.findIndex(a => a === accountActive),
1
)
accounts?.unshift(accountActive || '')
!withoutActive &&
accounts?.splice(
accounts.findIndex(a => a === accountActive),
1
)
!withoutActive && accounts?.unshift(accountActive || '')
return (
accounts?.map(account => {
const details = getAccountDetails(

View File

@ -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