diff --git a/package.json b/package.json index 077a8776..6f896f9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tooot", - "version": "4.8.0", + "version": "4.8.1", "description": "tooot for Mastodon", "author": "xmflsct ", "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", diff --git a/src/components/GracefullyImage.tsx b/src/components/GracefullyImage.tsx index c357576d..3ad17fbf 100644 --- a/src/components/GracefullyImage.tsx +++ b/src/components/GracefullyImage.tsx @@ -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(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} { + 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()} diff --git a/src/components/Instance/index.tsx b/src/components/Instance/index.tsx index 1fbbde76..9b53312d 100644 --- a/src/components/Instance/index.tsx +++ b/src/components/Instance/index.tsx @@ -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 = ({ 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 = ({ 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 = ({ '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 = ({ ) if (!account) { - setGlobalStorage('accounts', accounts?.concat([accountKey])) + setGlobalStorage('accounts', (accounts || []).concat([accountKey])) } setAccount(accountKey) @@ -195,6 +193,9 @@ const ComponentInstance: React.FC = ({ } }) + 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 = ({ }, { text: t('common:buttons.continue'), - onPress: () => appsMutation.mutate({ domain }) + onPress: () => appsMutation.mutate({ domain, scopes }) } ] ) } else { - appsMutation.mutate({ domain }) + appsMutation.mutate({ domain, scopes }) } } }, [domain]) diff --git a/src/components/Timeline/Notifications.tsx b/src/components/Timeline/Notifications.tsx index 108f8eac..8f2ad75f 100644 --- a/src/components/Timeline/Notifications.tsx +++ b/src/components/Timeline/Notifications.tsx @@ -126,7 +126,7 @@ const TimelineNotifications: React.FC = ({ notification, queryKey }) => { } if (filterResults?.length && !filterRevealed) { - return !filterResults.filter(result => result.filter_action === 'hide').length ? ( + return !filterResults.filter(result => result.filter_action === 'hide')?.length ? ( setFilterRevealed(!filterRevealed)}> diff --git a/src/components/Timeline/Refresh.tsx b/src/components/Timeline/Refresh.tsx index c08a7a36..2c6f8ebe 100644 --- a/src/components/Timeline/Refresh.tsx +++ b/src/components/Timeline/Refresh.tsx @@ -144,7 +144,7 @@ const TimelineRefresh: React.FC = ({ >(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 = ({ 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 = ({ 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 } diff --git a/src/components/Timeline/Shared/Card.tsx b/src/components/Timeline/Shared/Card.tsx index c92cb715..03d29f18 100644 --- a/src/components/Timeline/Shared/Card.tsx +++ b/src/components/Timeline/Shared/Card.tsx @@ -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) { diff --git a/src/components/Timeline/Shared/HeaderShared/Replies.tsx b/src/components/Timeline/Shared/HeaderShared/Replies.tsx index 5cf8e9ab..52592763 100644 --- a/src/components/Timeline/Shared/HeaderShared/Replies.tsx +++ b/src/components/Timeline/Shared/HeaderShared/Replies.tsx @@ -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 diff --git a/src/i18n/ca/components/contextMenu.json b/src/i18n/ca/components/contextMenu.json index 11333783..f63e450b 100644 --- a/src/i18n/ca/components/contextMenu.json +++ b/src/i18n/ca/components/contextMenu.json @@ -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", diff --git a/src/i18n/de/components/contextMenu.json b/src/i18n/de/components/contextMenu.json index 28c9d2a0..e5ebc881 100644 --- a/src/i18n/de/components/contextMenu.json +++ b/src/i18n/de/components/contextMenu.json @@ -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", diff --git a/src/i18n/es/components/contextMenu.json b/src/i18n/es/components/contextMenu.json index 22dff5b5..95658089 100644 --- a/src/i18n/es/components/contextMenu.json +++ b/src/i18n/es/components/contextMenu.json @@ -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", diff --git a/src/i18n/nl/components/contextMenu.json b/src/i18n/nl/components/contextMenu.json index 6486d317..f4bbf123 100644 --- a/src/i18n/nl/components/contextMenu.json +++ b/src/i18n/nl/components/contextMenu.json @@ -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", diff --git a/src/i18n/pl/components/contextMenu.json b/src/i18n/pl/components/contextMenu.json index 3295ddbd..d6afc13a 100644 --- a/src/i18n/pl/components/contextMenu.json +++ b/src/i18n/pl/components/contextMenu.json @@ -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", diff --git a/src/i18n/uk/components/contextMenu.json b/src/i18n/uk/components/contextMenu.json index 43aa9c98..d56fd2df 100644 --- a/src/i18n/uk/components/contextMenu.json +++ b/src/i18n/uk/components/contextMenu.json @@ -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": "Розблокувати користувача", diff --git a/src/i18n/zh-Hans/components/contextMenu.json b/src/i18n/zh-Hans/components/contextMenu.json index 1aaf9973..7a75c3d9 100644 --- a/src/i18n/zh-Hans/components/contextMenu.json +++ b/src/i18n/zh-Hans/components/contextMenu.json @@ -17,8 +17,8 @@ }, "followAs": { "trigger": "关注…", - "succeed_default": "{{source}} 正在关注 {{target}}", - "succeed_locked": "已从 {{source}} 发送关注请求至 {{target}},等待通过", + "succeed_default": "@{{source}} 正在关注 @{{target}}", + "succeed_locked": "已从 @{{source}} 发送关注请求至 @{{target}},等待通过", "failed": "用其它账户关注" }, "blockReport": "屏蔽与举报…", diff --git a/src/i18n/zh-Hant/components/contextMenu.json b/src/i18n/zh-Hant/components/contextMenu.json index 3cf5bc03..2b107c69 100644 --- a/src/i18n/zh-Hant/components/contextMenu.json +++ b/src/i18n/zh-Hant/components/contextMenu.json @@ -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": "解除封鎖使用者", diff --git a/src/screens/AccountSelection.tsx b/src/screens/AccountSelection.tsx index 381882cb..5d59c270 100644 --- a/src/screens/AccountSelection.tsx +++ b/src/screens/AccountSelection.tsx @@ -92,7 +92,7 @@ const ScreenAccountSelection = ({ const { colors } = useTheme() const { t } = useTranslation('screenAccountSelection') - const accounts = getReadableAccounts() + const accounts = getReadableAccounts(true) return ( = ({ accessibleRefDrafts }) => { const navigation = useNavigation() 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() diff --git a/src/screens/Compose/Root/Header/PostingAs.tsx b/src/screens/Compose/Root/Header/PostingAs.tsx index cc79dbbe..4079b0ab 100644 --- a/src/screens/Compose/Root/Header/PostingAs.tsx +++ b/src/screens/Compose/Root/Header/PostingAs.tsx @@ -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() diff --git a/src/screens/Compose/index.tsx b/src/screens/Compose/index.tsx index 5c6e6f10..351dddf5 100644 --- a/src/screens/Compose/index.tsx +++ b/src/screens/Compose/index.tsx @@ -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> = ({ } 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> = ({ // }) // 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() diff --git a/src/screens/Tabs/Local/Root.tsx b/src/screens/Tabs/Local/Root.tsx index 465ec22d..50eeea19 100644 --- a/src/screens/Tabs/Local/Root.tsx +++ b/src/screens/Tabs/Local/Root.tsx @@ -55,7 +55,7 @@ const Root: React.FC - {page.page === 'Following' && !pageLocal.showBoosts ? ( + {page.page === 'Following' && !pageLocal?.showBoosts ? ( ) : null} - {page.page === 'Following' && !pageLocal.showReplies ? ( + {page.page === 'Following' && !pageLocal?.showReplies ? ( { 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 { 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 } } ]) }} diff --git a/src/screens/Tabs/Me/Profile/Root.tsx b/src/screens/Tabs/Me/Profile/Root.tsx index 27d4331f..0bb095f2 100644 --- a/src/screens/Tabs/Me/Profile/Root.tsx +++ b/src/screens/Tabs/Me/Profile/Root.tsx @@ -49,7 +49,7 @@ const TabMeProfileRoot: React.FC< { diff --git a/src/screens/Tabs/Me/Push.tsx b/src/screens/Tabs/Me/Push.tsx index a62f1c06..09c512bf 100644 --- a/src/screens/Tabs/Me/Push.tsx +++ b/src/screens/Tabs/Me/Push.tsx @@ -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) diff --git a/src/screens/Tabs/Me/Root/Collections.tsx b/src/screens/Tabs/Me/Root/Collections.tsx index 11a89fb2..8e99c785 100644 --- a/src/screens/Tabs/Me/Root/Collections.tsx +++ b/src/screens/Tabs/Me/Root/Collections.tsx @@ -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 ? ( { onPress={() => navigation.navigate('Tab-Me-List-List')} /> ) : null} - {pageMe.followedTags.shown ? ( + {pageMe.followedTags?.shown ? ( { onPress={() => navigation.navigate('Tab-Me-FollowedTags')} /> ) : null} - {pageMe.announcements.shown ? ( + {pageMe.announcements?.shown ? ( { diff --git a/src/screens/Tabs/Notifications/Filters.tsx b/src/screens/Tabs/Notifications/Filters.tsx index 8f5f0f5a..acd62db6 100644 --- a/src/screens/Tabs/Notifications/Filters.tsx +++ b/src/screens/Tabs/Notifications/Filters.tsx @@ -70,8 +70,8 @@ const TabNotificationsFilters: React.FC< setFilters({ ...filters, [type]: !filters[type] })} + switchValue={filters?.[type]} + switchOnValueChange={() => setFilters({ ...filters, [type]: !filters?.[type] })} /> ))} @@ -80,8 +80,8 @@ const TabNotificationsFilters: React.FC< setFilters({ ...filters, [type]: !filters[type] })} + switchValue={filters?.[type]} + switchOnValueChange={() => setFilters({ ...filters, [type]: !filters?.[type] })} /> ))} diff --git a/src/screens/Tabs/Public/Root.tsx b/src/screens/Tabs/Public/Root.tsx index ef5054ec..9633aa41 100644 --- a/src/screens/Tabs/Public/Root.tsx +++ b/src/screens/Tabs/Public/Root.tsx @@ -38,7 +38,10 @@ const Root: React.FC( - segments.findIndex(segment => segment === previousSegment) + Math.min( + 0, + segments.findIndex(segment => segment === previousSegment) + ) ) const [routes] = useState([ { key: 'Local', title: t('tabs.public.segments.local') }, diff --git a/src/screens/Tabs/Shared/Account/Information/Account.tsx b/src/screens/Tabs/Shared/Account/Information/Account.tsx index 3c8f3ac2..38bb05a6 100644 --- a/src/screens/Tabs/Shared/Account/Information/Account.tsx +++ b/src/screens/Tabs/Shared/Account/Information/Account.tsx @@ -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 ( diff --git a/src/screens/Tabs/Shared/Toot.tsx b/src/screens/Tabs/Shared/Toot.tsx index 5f078717..a48e9202 100644 --- a/src/screens/Tabs/Shared/Toot.tsx +++ b/src/screens/Tabs/Shared/Toot.tsx @@ -162,7 +162,7 @@ const TabSharedToot: React.FC> = ({ }).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> = ({ }, { 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> = ({ 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> = ({ } ) - 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> = ({ nativeEvent: { layout: { height } } - }) => (heights.current[index] = height)} + }) => setHeights({ ...heights, [index]: height })} > > = ({ 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> = ({ 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 diff --git a/src/utils/queryHooks/apps.ts b/src/utils/queryHooks/apps.ts index ca0e85c5..840885fb 100644 --- a/src/utils/queryHooks/apps.ts +++ b/src/utils/queryHooks/apps.ts @@ -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({ 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) } diff --git a/src/utils/queryHooks/index.ts b/src/utils/queryHooks/index.ts index 2120a922..6c6f90e1 100644 --- a/src/utils/queryHooks/index.ts +++ b/src/utils/queryHooks/index.ts @@ -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: () => {} } }) diff --git a/src/utils/queryHooks/search.ts b/src/utils/queryHooks/search.ts index 2812fa86..0f75cdc9 100644 --- a/src/utils/queryHooks/search.ts +++ b/src/utils/queryHooks/search.ts @@ -52,7 +52,7 @@ export const searchLocalStatus = async (uri: Mastodon.Status['uri']): Promise - 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() ) diff --git a/src/utils/storage/actions.ts b/src/utils/storage/actions.ts index 1c367b3e..584c4005 100644 --- a/src/utils/storage/actions.ts +++ b/src/utils/storage/actions.ts @@ -43,7 +43,7 @@ export const useGlobalStorage = { : never, number: (key: T) => { if (Platform.OS === 'ios') { - return useMMKVString(key, storage.global) as NonNullable extends number + return useMMKVNumber(key, storage.global) as NonNullable 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( diff --git a/yarn.lock b/yarn.lock index 9e39eadf..ef1812a4 100644 --- a/yarn.lock +++ b/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