mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Merge branch 'main' into release
This commit is contained in:
		| @@ -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", | ||||
|   | ||||
| @@ -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]) | ||||
|   | ||||
| @@ -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<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, | ||||
| @@ -182,7 +184,7 @@ const ComponentInstance: React.FC<Props> = ({ | ||||
|         ) | ||||
|  | ||||
|         if (!account) { | ||||
|           setGlobalStorage('accounts', accounts?.concat([accountKey])) | ||||
|           setGlobalStorage('accounts', (accounts || []).concat([accountKey])) | ||||
|         } | ||||
|         setAccount(accountKey) | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|                   } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 ? ( | ||||
|         <MenuRow | ||||
|           iconFront='List' | ||||
|           iconBack='ChevronRight' | ||||
| @@ -68,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' | ||||
| @@ -76,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' | ||||
|   | ||||
| @@ -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,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') }, | ||||
|   | ||||
| @@ -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() | ||||
|     ) | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
							
								
								
									
										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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user