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",
|
"name": "tooot",
|
||||||
"version": "4.8.0",
|
"version": "4.8.1",
|
||||||
"description": "tooot for Mastodon",
|
"description": "tooot for Mastodon",
|
||||||
"author": "xmflsct <me@xmflsct.com>",
|
"author": "xmflsct <me@xmflsct.com>",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
@ -81,6 +81,7 @@
|
|||||||
"react-native-language-detection": "^0.2.2",
|
"react-native-language-detection": "^0.2.2",
|
||||||
"react-native-mmkv": "^2.5.1",
|
"react-native-mmkv": "^2.5.1",
|
||||||
"react-native-pager-view": "^6.1.2",
|
"react-native-pager-view": "^6.1.2",
|
||||||
|
"react-native-quick-base64": "^2.0.5",
|
||||||
"react-native-reanimated": "^2.13.0",
|
"react-native-reanimated": "^2.13.0",
|
||||||
"react-native-reanimated-zoom": "^0.3.3",
|
"react-native-reanimated-zoom": "^0.3.3",
|
||||||
"react-native-safe-area-context": "^4.4.1",
|
"react-native-safe-area-context": "^4.4.1",
|
||||||
|
@ -60,7 +60,10 @@ const GracefullyImage = ({
|
|||||||
uri: reduceMotionEnabled && uri.static ? uri.static : currentUri
|
uri: reduceMotionEnabled && uri.static ? uri.static : currentUri
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
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)
|
setCurrentUri(uri.original || uri.remote)
|
||||||
}
|
}
|
||||||
}, [currentUri, uri.original, uri.remote])
|
}, [currentUri, uri.original, uri.remote])
|
||||||
|
@ -19,12 +19,14 @@ import {
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import * as AuthSession from 'expo-auth-session'
|
import * as AuthSession from 'expo-auth-session'
|
||||||
|
import * as Random from 'expo-random'
|
||||||
import * as WebBrowser from 'expo-web-browser'
|
import * as WebBrowser from 'expo-web-browser'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import React, { RefObject, useCallback, useState } from 'react'
|
import React, { RefObject, useCallback, useState } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { Alert, Image, KeyboardAvoidingView, Platform, TextInput, View } from 'react-native'
|
import { Alert, Image, KeyboardAvoidingView, Platform, TextInput, View } from 'react-native'
|
||||||
import { ScrollView } from 'react-native-gesture-handler'
|
import { ScrollView } from 'react-native-gesture-handler'
|
||||||
|
import { fromByteArray } from 'react-native-quick-base64'
|
||||||
import parse from 'url-parse'
|
import parse from 'url-parse'
|
||||||
import CustomText from '../Text'
|
import CustomText from '../Text'
|
||||||
|
|
||||||
@ -158,7 +160,7 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
'admin.sign_up': false,
|
'admin.sign_up': false,
|
||||||
'admin.report': false
|
'admin.report': false
|
||||||
},
|
},
|
||||||
key: Math.random().toString(36).slice(2, 12)
|
key: fromByteArray(Random.getRandomBytes(16))
|
||||||
},
|
},
|
||||||
page_local: {
|
page_local: {
|
||||||
showBoosts: true,
|
showBoosts: true,
|
||||||
@ -182,7 +184,7 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
setGlobalStorage('accounts', accounts?.concat([accountKey]))
|
setGlobalStorage('accounts', (accounts || []).concat([accountKey]))
|
||||||
}
|
}
|
||||||
setAccount(accountKey)
|
setAccount(accountKey)
|
||||||
|
|
||||||
|
@ -197,7 +197,8 @@ const TimelineRefresh: React.FC<Props> = ({
|
|||||||
const insert = prevCache.current?.slice(-PREV_PER_BATCH)
|
const insert = prevCache.current?.slice(-PREV_PER_BATCH)
|
||||||
prevCache.current = prevCache.current?.slice(0, -PREV_PER_BATCH)
|
prevCache.current = prevCache.current?.slice(0, -PREV_PER_BATCH)
|
||||||
if (insert) {
|
if (insert) {
|
||||||
return { ...page, body: [...insert, ...page.body] }
|
page.body.unshift(...insert)
|
||||||
|
return page
|
||||||
} else {
|
} else {
|
||||||
return page
|
return page
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,11 @@ const HeaderSharedReplies: React.FC = () => {
|
|||||||
const { t } = useTranslation(['common', 'componentTimeline'])
|
const { t } = useTranslation(['common', 'componentTimeline'])
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const mentionsBeginning = rawContent?.current?.[0]
|
const mentionsBeginning = rawContent?.current?.[0]?.length
|
||||||
.match(new RegExp(/^(?:@\S+\s+)+/))?.[0]
|
? rawContent?.current?.[0]
|
||||||
?.match(new RegExp(/@\S+/, 'g'))
|
.match(new RegExp(/^(?:@\S+\s+)+/))?.[0]
|
||||||
|
?.match(new RegExp(/@\S+/, 'g'))
|
||||||
|
: undefined
|
||||||
excludeMentions &&
|
excludeMentions &&
|
||||||
(excludeMentions.current =
|
(excludeMentions.current =
|
||||||
mentionsBeginning?.length && status?.mentions
|
mentionsBeginning?.length && status?.mentions
|
||||||
|
@ -92,7 +92,7 @@ const ScreenAccountSelection = ({
|
|||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const { t } = useTranslation('screenAccountSelection')
|
const { t } = useTranslation('screenAccountSelection')
|
||||||
|
|
||||||
const accounts = getReadableAccounts()
|
const accounts = getReadableAccounts(true)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
@ -17,7 +17,7 @@ const ComposeDrafts: React.FC<Props> = ({ accessibleRefDrafts }) => {
|
|||||||
const navigation = useNavigation<any>()
|
const navigation = useNavigation<any>()
|
||||||
const { composeState } = useContext(ComposeContext)
|
const { composeState } = useContext(ComposeContext)
|
||||||
const [drafts] = useAccountStorage.object('drafts')
|
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(() => {
|
useEffect(() => {
|
||||||
layoutAnimation()
|
layoutAnimation()
|
||||||
|
@ -7,8 +7,8 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
|
|
||||||
const ComposePostingAs = () => {
|
const ComposePostingAs = () => {
|
||||||
const accounts = useGlobalStorage.object('accounts')
|
const [accounts] = useGlobalStorage.object('accounts')
|
||||||
if (!accounts.length) return null
|
if (!accounts?.length) return null
|
||||||
|
|
||||||
const { t } = useTranslation('screenCompose')
|
const { t } = useTranslation('screenCompose')
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
@ -7,7 +7,7 @@ import ComposeRoot from '@screens/Compose/Root'
|
|||||||
import { formatText } from '@screens/Compose/utils/processText'
|
import { formatText } from '@screens/Compose/utils/processText'
|
||||||
import { useQueryClient } from '@tanstack/react-query'
|
import { useQueryClient } from '@tanstack/react-query'
|
||||||
import { handleError } from '@utils/api/helpers'
|
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 { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||||
import { searchLocalStatus } from '@utils/queryHooks/search'
|
import { searchLocalStatus } from '@utils/queryHooks/search'
|
||||||
@ -346,13 +346,6 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (params?.type) {
|
switch (params?.type) {
|
||||||
case undefined:
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ['Timeline', { page: 'Following' }],
|
|
||||||
exact: false
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case 'conversation':
|
|
||||||
case 'edit': // doesn't work
|
case 'edit': // doesn't work
|
||||||
// mutateTimeline.mutate({
|
// mutateTimeline.mutate({
|
||||||
// type: 'editItem',
|
// type: 'editItem',
|
||||||
@ -361,11 +354,20 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
|||||||
// })
|
// })
|
||||||
// break
|
// break
|
||||||
case 'deleteEdit':
|
case 'deleteEdit':
|
||||||
case 'reply':
|
|
||||||
for (const navState of params.navigationState) {
|
for (const navState of params.navigationState) {
|
||||||
navState && queryClient.invalidateQueries(navState)
|
navState && queryClient.invalidateQueries(navState)
|
||||||
}
|
}
|
||||||
break
|
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)
|
removeDraft(composeState.timestamp)
|
||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
|
@ -17,10 +17,12 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import * as Notifications from 'expo-notifications'
|
import * as Notifications from 'expo-notifications'
|
||||||
|
import * as Random from 'expo-random'
|
||||||
import * as WebBrowser from 'expo-web-browser'
|
import * as WebBrowser from 'expo-web-browser'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { AppState, Linking, Platform, ScrollView, View } from 'react-native'
|
import { AppState, Linking, Platform, ScrollView, View } from 'react-native'
|
||||||
|
import { fromByteArray } from 'react-native-quick-base64'
|
||||||
|
|
||||||
const TabMePush: React.FC = () => {
|
const TabMePush: React.FC = () => {
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
@ -178,6 +180,11 @@ const TabMePush: React.FC = () => {
|
|||||||
|
|
||||||
setAccountStorage([{ key: 'push', value: { ...push, global: false } }])
|
setAccountStorage([{ key: 'push', value: { ...push, global: false } }])
|
||||||
} else {
|
} 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
|
// Turning on
|
||||||
const randomPath = (Math.random() + 1).toString(36).substring(2)
|
const randomPath = (Math.random() + 1).toString(36).substring(2)
|
||||||
|
|
||||||
@ -189,7 +196,7 @@ const TabMePush: React.FC = () => {
|
|||||||
'subscription[keys][p256dh]',
|
'subscription[keys][p256dh]',
|
||||||
'BMn2PLpZrMefG981elzG6SB1EY9gU7QZwmtZ/a/J2vUeWG+zXgeskMPwHh4T/bxsD4l7/8QT94F57CbZqYRRfJo='
|
'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)) {
|
for (const [key, value] of Object.entries(push.alerts)) {
|
||||||
formData.append(`data[alerts][${key}]`, value.toString())
|
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') {
|
if (Platform.OS === 'android') {
|
||||||
setChannels(true)
|
setChannels(true)
|
||||||
|
@ -60,7 +60,7 @@ const Collections: React.FC = () => {
|
|||||||
title={t('screenTabs:me.stacks.favourites.name')}
|
title={t('screenTabs:me.stacks.favourites.name')}
|
||||||
onPress={() => navigation.navigate('Tab-Me-Favourites')}
|
onPress={() => navigation.navigate('Tab-Me-Favourites')}
|
||||||
/>
|
/>
|
||||||
{pageMe.lists.shown ? (
|
{pageMe.lists?.shown ? (
|
||||||
<MenuRow
|
<MenuRow
|
||||||
iconFront='List'
|
iconFront='List'
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
@ -68,7 +68,7 @@ const Collections: React.FC = () => {
|
|||||||
onPress={() => navigation.navigate('Tab-Me-List-List')}
|
onPress={() => navigation.navigate('Tab-Me-List-List')}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{pageMe.followedTags.shown ? (
|
{pageMe.followedTags?.shown ? (
|
||||||
<MenuRow
|
<MenuRow
|
||||||
iconFront='Hash'
|
iconFront='Hash'
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
@ -76,7 +76,7 @@ const Collections: React.FC = () => {
|
|||||||
onPress={() => navigation.navigate('Tab-Me-FollowedTags')}
|
onPress={() => navigation.navigate('Tab-Me-FollowedTags')}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{pageMe.announcements.shown ? (
|
{pageMe.announcements?.shown ? (
|
||||||
<MenuRow
|
<MenuRow
|
||||||
iconFront='Clipboard'
|
iconFront='Clipboard'
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
|
@ -70,8 +70,8 @@ const TabNotificationsFilters: React.FC<
|
|||||||
<MenuRow
|
<MenuRow
|
||||||
key={index}
|
key={index}
|
||||||
title={t(`screenTabs:me.push.${type}.heading`)}
|
title={t(`screenTabs:me.push.${type}.heading`)}
|
||||||
switchValue={filters[type]}
|
switchValue={filters?.[type]}
|
||||||
switchOnValueChange={() => setFilters({ ...filters, [type]: !filters[type] })}
|
switchOnValueChange={() => setFilters({ ...filters, [type]: !filters?.[type] })}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</MenuContainer>
|
</MenuContainer>
|
||||||
@ -80,8 +80,8 @@ const TabNotificationsFilters: React.FC<
|
|||||||
<MenuRow
|
<MenuRow
|
||||||
key={type}
|
key={type}
|
||||||
title={t(`screenTabs:me.push.${type}.heading`)}
|
title={t(`screenTabs:me.push.${type}.heading`)}
|
||||||
switchValue={filters[type]}
|
switchValue={filters?.[type]}
|
||||||
switchOnValueChange={() => setFilters({ ...filters, [type]: !filters[type] })}
|
switchOnValueChange={() => setFilters({ ...filters, [type]: !filters?.[type] })}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</MenuContainer>
|
</MenuContainer>
|
||||||
|
@ -38,7 +38,10 @@ const Root: React.FC<NativeStackScreenProps<TabPublicStackParamList, 'Tab-Public
|
|||||||
const previousSegment = getGlobalStorage.string('app.prev_public_segment')
|
const previousSegment = getGlobalStorage.string('app.prev_public_segment')
|
||||||
const segments: StorageGlobal['app.prev_public_segment'][] = ['Local', 'LocalPublic', 'Trending']
|
const segments: StorageGlobal['app.prev_public_segment'][] = ['Local', 'LocalPublic', 'Trending']
|
||||||
const [segment, setSegment] = useState<number>(
|
const [segment, setSegment] = useState<number>(
|
||||||
segments.findIndex(segment => segment === previousSegment)
|
Math.min(
|
||||||
|
0,
|
||||||
|
segments.findIndex(segment => segment === previousSegment)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
const [routes] = useState([
|
const [routes] = useState([
|
||||||
{ key: 'Local', title: t('tabs.public.segments.local') },
|
{ 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
|
return await queryClient
|
||||||
.fetchQuery(queryKey, queryFunction, { staleTime: 3600, cacheTime: 3600 })
|
.fetchQuery(queryKey, queryFunction, { staleTime: 3600, cacheTime: 3600 })
|
||||||
.then(res =>
|
.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]
|
? res.statuses[0]
|
||||||
: Promise.reject()
|
: Promise.reject()
|
||||||
)
|
)
|
||||||
|
@ -233,14 +233,15 @@ export type ReadableAccountType = {
|
|||||||
key: string
|
key: string
|
||||||
active: boolean
|
active: boolean
|
||||||
}
|
}
|
||||||
export const getReadableAccounts = (): ReadableAccountType[] => {
|
export const getReadableAccounts = (withoutActive: boolean = false): ReadableAccountType[] => {
|
||||||
const accountActive = getGlobalStorage.string('account.active')
|
const accountActive = !withoutActive && getGlobalStorage.string('account.active')
|
||||||
const accounts = getGlobalStorage.object('accounts')?.sort((a, b) => a.localeCompare(b))
|
const accounts = getGlobalStorage.object('accounts')?.sort((a, b) => a.localeCompare(b))
|
||||||
accounts?.splice(
|
!withoutActive &&
|
||||||
accounts.findIndex(a => a === accountActive),
|
accounts?.splice(
|
||||||
1
|
accounts.findIndex(a => a === accountActive),
|
||||||
)
|
1
|
||||||
accounts?.unshift(accountActive || '')
|
)
|
||||||
|
!withoutActive && accounts?.unshift(accountActive || '')
|
||||||
return (
|
return (
|
||||||
accounts?.map(account => {
|
accounts?.map(account => {
|
||||||
const details = getAccountDetails(
|
const details = getAccountDetails(
|
||||||
|
13
yarn.lock
13
yarn.lock
@ -9718,6 +9718,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"react-native-reanimated-zoom@npm:^0.3.3":
|
||||||
version: 0.3.3
|
version: 0.3.3
|
||||||
resolution: "react-native-reanimated-zoom@npm: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-language-detection: ^0.2.2
|
||||||
react-native-mmkv: ^2.5.1
|
react-native-mmkv: ^2.5.1
|
||||||
react-native-pager-view: ^6.1.2
|
react-native-pager-view: ^6.1.2
|
||||||
|
react-native-quick-base64: ^2.0.5
|
||||||
react-native-reanimated: ^2.13.0
|
react-native-reanimated: ^2.13.0
|
||||||
react-native-reanimated-zoom: ^0.3.3
|
react-native-reanimated-zoom: ^0.3.3
|
||||||
react-native-safe-area-context: ^4.4.1
|
react-native-safe-area-context: ^4.4.1
|
||||||
|
Reference in New Issue
Block a user