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 e7069c68..3ad17fbf 100644 --- a/src/components/GracefullyImage.tsx +++ b/src/components/GracefullyImage.tsx @@ -60,7 +60,10 @@ const GracefullyImage = ({ uri: reduceMotionEnabled && uri.static ? uri.static : currentUri } useEffect(() => { - if (currentUri !== uri.original && currentUri !== uri.remote) { + if ( + (uri.original ? currentUri !== uri.original : true) && + (uri.remote ? currentUri !== uri.remote : true) + ) { setCurrentUri(uri.original || uri.remote) } }, [currentUri, uri.original, uri.remote]) diff --git a/src/components/Instance/index.tsx b/src/components/Instance/index.tsx index 2826c8bb..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' @@ -158,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, @@ -182,7 +184,7 @@ const ComponentInstance: React.FC = ({ ) if (!account) { - setGlobalStorage('accounts', accounts?.concat([accountKey])) + setGlobalStorage('accounts', (accounts || []).concat([accountKey])) } setAccount(accountKey) diff --git a/src/components/Timeline/Refresh.tsx b/src/components/Timeline/Refresh.tsx index adda245c..2c6f8ebe 100644 --- a/src/components/Timeline/Refresh.tsx +++ b/src/components/Timeline/Refresh.tsx @@ -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/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/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/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 b836f42c..8e99c785 100644 --- a/src/screens/Tabs/Me/Root/Collections.tsx +++ b/src/screens/Tabs/Me/Root/Collections.tsx @@ -60,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 ? ( 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/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 919f0854..584c4005 100644 --- a/src/utils/storage/actions.ts +++ b/src/utils/storage/actions.ts @@ -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