mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Merge branch 'main' into candidate
This commit is contained in:
@ -0,0 +1,14 @@
|
|||||||
|
diff --git a/src/functions/Helpers.ts b/src/functions/Helpers.ts
|
||||||
|
index e04486540494891ab07ec130b686dc4acddf2d0c..265e6ac11439276a1c52c222dfc4c50daf1689ae 100644
|
||||||
|
--- a/src/functions/Helpers.ts
|
||||||
|
+++ b/src/functions/Helpers.ts
|
||||||
|
@@ -77,7 +77,8 @@ export function getNativeNodeHandle(nativeRef: React.Component){
|
||||||
|
const nodeHandle = findNodeHandle(nativeRef);
|
||||||
|
|
||||||
|
if(nodeHandle == null){
|
||||||
|
- throw new Error('Unable to get the node handle for the native ref.');
|
||||||
|
+ return 0
|
||||||
|
+ // throw new Error('Unable to get the node handle for the native ref.');
|
||||||
|
};
|
||||||
|
|
||||||
|
return nodeHandle;
|
@ -119,6 +119,7 @@
|
|||||||
"react-native-fast-image@^8.6.3": "patch:react-native-fast-image@npm%3A8.6.3#./.yarn/patches/react-native-fast-image-npm-8.6.3-03ee2d23c0.patch",
|
"react-native-fast-image@^8.6.3": "patch:react-native-fast-image@npm%3A8.6.3#./.yarn/patches/react-native-fast-image-npm-8.6.3-03ee2d23c0.patch",
|
||||||
"expo-av@^13.0.2": "patch:expo-av@npm%3A13.0.2#./.yarn/patches/expo-av-npm-13.0.2-7a651776f1.patch",
|
"expo-av@^13.0.2": "patch:expo-av@npm%3A13.0.2#./.yarn/patches/expo-av-npm-13.0.2-7a651776f1.patch",
|
||||||
"react-native-share-menu@^6.0.0": "patch:react-native-share-menu@npm%3A6.0.0#./.yarn/patches/react-native-share-menu-npm-6.0.0-f1094c3204.patch",
|
"react-native-share-menu@^6.0.0": "patch:react-native-share-menu@npm%3A6.0.0#./.yarn/patches/react-native-share-menu-npm-6.0.0-f1094c3204.patch",
|
||||||
"@types/react-native-share-menu@^5.0.2": "patch:@types/react-native-share-menu@npm%3A5.0.2#./.yarn/patches/@types-react-native-share-menu-npm-5.0.2-373df17ecc.patch"
|
"@types/react-native-share-menu@^5.0.2": "patch:@types/react-native-share-menu@npm%3A5.0.2#./.yarn/patches/@types-react-native-share-menu-npm-5.0.2-373df17ecc.patch",
|
||||||
|
"react-native-ios-context-menu@^1.15.1": "patch:react-native-ios-context-menu@npm%3A1.15.1#./.yarn/patches/react-native-ios-context-menu-npm-1.15.1-0034bfa5ba.patch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
34
src/App.tsx
34
src/App.tsx
@ -10,13 +10,7 @@ import log from '@utils/startup/log'
|
|||||||
import netInfo from '@utils/startup/netInfo'
|
import netInfo from '@utils/startup/netInfo'
|
||||||
import push from '@utils/startup/push'
|
import push from '@utils/startup/push'
|
||||||
import sentry from '@utils/startup/sentry'
|
import sentry from '@utils/startup/sentry'
|
||||||
import { storage } from '@utils/storage'
|
import { getGlobalStorage, setAccount, setGlobalStorage } from '@utils/storage/actions'
|
||||||
import {
|
|
||||||
getGlobalStorage,
|
|
||||||
removeAccount,
|
|
||||||
setAccount,
|
|
||||||
setGlobalStorage
|
|
||||||
} from '@utils/storage/actions'
|
|
||||||
import { migrateFromAsyncStorage, versionStorageGlobal } from '@utils/storage/migrations/toMMKV'
|
import { migrateFromAsyncStorage, versionStorageGlobal } from '@utils/storage/migrations/toMMKV'
|
||||||
import ThemeManager from '@utils/styles/ThemeManager'
|
import ThemeManager from '@utils/styles/ThemeManager'
|
||||||
import * as Localization from 'expo-localization'
|
import * as Localization from 'expo-localization'
|
||||||
@ -24,7 +18,6 @@ import * as SplashScreen from 'expo-splash-screen'
|
|||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { LogBox, Platform } from 'react-native'
|
import { LogBox, Platform } from 'react-native'
|
||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||||
import { MMKV } from 'react-native-mmkv'
|
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
||||||
import { enableFreeze } from 'react-native-screens'
|
import { enableFreeze } from 'react-native-screens'
|
||||||
import i18n from './i18n'
|
import i18n from './i18n'
|
||||||
@ -36,6 +29,7 @@ Platform.select({
|
|||||||
|
|
||||||
dev()
|
dev()
|
||||||
sentry()
|
sentry()
|
||||||
|
netInfo()
|
||||||
audio()
|
audio()
|
||||||
push()
|
push()
|
||||||
enableFreeze(true)
|
enableFreeze(true)
|
||||||
@ -46,7 +40,6 @@ SplashScreen.preventAutoHideAsync()
|
|||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
log('log', 'App', 'rendering App')
|
log('log', 'App', 'rendering App')
|
||||||
const [appIsReady, setAppIsReady] = useState(false)
|
const [appIsReady, setAppIsReady] = useState(false)
|
||||||
const [localCorrupt, setLocalCorrupt] = useState<string>()
|
|
||||||
|
|
||||||
const [hasMigrated, setHasMigrated] = useState<boolean>(versionStorageGlobal !== undefined)
|
const [hasMigrated, setHasMigrated] = useState<boolean>(versionStorageGlobal !== undefined)
|
||||||
|
|
||||||
@ -61,36 +54,19 @@ const App: React.FC = () => {
|
|||||||
log('log', 'App', 'loading from MMKV')
|
log('log', 'App', 'loading from MMKV')
|
||||||
const account = getGlobalStorage.string('account.active')
|
const account = getGlobalStorage.string('account.active')
|
||||||
if (account) {
|
if (account) {
|
||||||
const storageAccount = new MMKV({ id: account })
|
await setAccount(account)
|
||||||
const token = storageAccount.getString('auth.token')
|
|
||||||
if (token) {
|
|
||||||
log('log', 'App', `Binding storage of ${account}`)
|
|
||||||
storage.account = storageAccount
|
|
||||||
} else {
|
|
||||||
log('log', 'App', `Token not found for ${account}`)
|
|
||||||
removeAccount(account)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log('log', 'App', 'No active account available')
|
log('log', 'App', 'No active account available')
|
||||||
const accounts = getGlobalStorage.object('accounts')
|
const accounts = getGlobalStorage.object('accounts')
|
||||||
if (accounts?.length) {
|
if (accounts?.length) {
|
||||||
log('log', 'App', `Setting active account ${accounts[accounts.length - 1]}`)
|
log('log', 'App', `Setting active account ${accounts[accounts.length - 1]}`)
|
||||||
setAccount(accounts[accounts.length - 1])
|
await setAccount(accounts[accounts.length - 1])
|
||||||
} else {
|
} else {
|
||||||
setGlobalStorage('account.active', undefined)
|
setGlobalStorage('account.active', undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let netInfoRes = undefined
|
|
||||||
try {
|
|
||||||
netInfoRes = await netInfo()
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (netInfoRes && netInfoRes.corrupted && netInfoRes.corrupted.length) {
|
|
||||||
setLocalCorrupt(netInfoRes.corrupted)
|
|
||||||
}
|
|
||||||
|
|
||||||
log('log', 'App', `locale: ${Localization.locale}`)
|
log('log', 'App', `locale: ${Localization.locale}`)
|
||||||
const language = getLanguage()
|
const language = getLanguage()
|
||||||
if (!language) {
|
if (!language) {
|
||||||
@ -124,7 +100,7 @@ const App: React.FC = () => {
|
|||||||
<ActionSheetProvider>
|
<ActionSheetProvider>
|
||||||
<AccessibilityManager>
|
<AccessibilityManager>
|
||||||
<ThemeManager>
|
<ThemeManager>
|
||||||
<Screens localCorrupt={localCorrupt} />
|
<Screens />
|
||||||
</ThemeManager>
|
</ThemeManager>
|
||||||
</AccessibilityManager>
|
</AccessibilityManager>
|
||||||
</ActionSheetProvider>
|
</ActionSheetProvider>
|
||||||
|
@ -193,10 +193,12 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const scopes = featureCheck('deprecate_auth_follow')
|
|
||||||
? ['read', 'write', 'push']
|
|
||||||
: ['read', 'write', 'follow', 'push']
|
|
||||||
const processUpdate = useCallback(() => {
|
const processUpdate = useCallback(() => {
|
||||||
|
const scopes = () =>
|
||||||
|
featureCheck('deprecate_auth_follow', instanceQuery.data?.version)
|
||||||
|
? ['read', 'write', 'push']
|
||||||
|
: ['read', 'write', 'follow', 'push']
|
||||||
|
|
||||||
if (domain) {
|
if (domain) {
|
||||||
const accounts = getGlobalStorage.object('accounts')
|
const accounts = getGlobalStorage.object('accounts')
|
||||||
if (accounts?.filter(account => account.startsWith(`${domain}/`)).length) {
|
if (accounts?.filter(account => account.startsWith(`${domain}/`)).length) {
|
||||||
@ -210,15 +212,15 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: t('common:buttons.continue'),
|
text: t('common:buttons.continue'),
|
||||||
onPress: () => appsMutation.mutate({ domain, scopes })
|
onPress: () => appsMutation.mutate({ domain, scopes: scopes() })
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
appsMutation.mutate({ domain, scopes })
|
appsMutation.mutate({ domain, scopes: scopes() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [domain])
|
}, [domain, instanceQuery.data?.version])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
|
@ -8,7 +8,7 @@ import Root from './Root'
|
|||||||
const Stack = createNativeStackNavigator<TabLocalStackParamList>()
|
const Stack = createNativeStackNavigator<TabLocalStackParamList>()
|
||||||
|
|
||||||
const TabLocal: React.FC = () => {
|
const TabLocal: React.FC = () => {
|
||||||
usePopToTop()
|
usePopToTop('Tab-Local-Root')
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
||||||
<Stack.Screen name='Tab-Local-Root' component={Root} />
|
<Stack.Screen name='Tab-Local-Root' component={Root} />
|
||||||
|
@ -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'
|
||||||
|
@ -32,7 +32,7 @@ const Logout: React.FC = () => {
|
|||||||
onPress: () => {
|
onPress: () => {
|
||||||
if (accountActive) {
|
if (accountActive) {
|
||||||
haptics('Light')
|
haptics('Light')
|
||||||
removeAccount(accountActive)
|
removeAccount(accountActive, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -44,7 +44,7 @@ const Root: React.FC<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TabNotifications: React.FC = () => {
|
const TabNotifications: React.FC = () => {
|
||||||
usePopToTop()
|
usePopToTop('Tab-Notifications-Root')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
||||||
|
@ -8,7 +8,7 @@ import Root from './Root'
|
|||||||
const Stack = createNativeStackNavigator<TabPublicStackParamList>()
|
const Stack = createNativeStackNavigator<TabPublicStackParamList>()
|
||||||
|
|
||||||
const TabPublic: React.FC = () => {
|
const TabPublic: React.FC = () => {
|
||||||
usePopToTop()
|
usePopToTop('Tab-Public-Root')
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
||||||
<Stack.Screen name='Tab-Public-Root' component={Root} />
|
<Stack.Screen name='Tab-Public-Root' component={Root} />
|
||||||
|
@ -74,8 +74,55 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
const PREV_PER_BATCH = 1
|
const PREV_PER_BATCH = 1
|
||||||
const ancestorsCache = useRef<(Mastodon.Status & { _level?: number; key?: string })[]>()
|
const ancestorsCache = useRef<(Mastodon.Status & { _level?: number; key?: string })[]>()
|
||||||
const loaded = useRef<boolean>(false)
|
const loaded = useRef<boolean>(false)
|
||||||
|
const prependContent = async () => {
|
||||||
|
loaded.current = true
|
||||||
|
|
||||||
|
if (ancestorsCache.current?.length) {
|
||||||
|
switch (Platform.OS) {
|
||||||
|
case 'ios':
|
||||||
|
for (let [] of Array(
|
||||||
|
Math.ceil(ancestorsCache.current.length / PREV_PER_BATCH)
|
||||||
|
).entries()) {
|
||||||
|
await new Promise(promise => setTimeout(promise, 64))
|
||||||
|
queryClient.setQueryData<{ pages: { body: Mastodon.Status[] }[] }>(
|
||||||
|
queryKey.local,
|
||||||
|
old => {
|
||||||
|
const insert = ancestorsCache.current?.slice(-PREV_PER_BATCH)
|
||||||
|
ancestorsCache.current = ancestorsCache.current?.slice(0, -PREV_PER_BATCH)
|
||||||
|
if (insert) {
|
||||||
|
old?.pages[0].body.unshift(...insert)
|
||||||
|
}
|
||||||
|
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
queryClient.setQueryData<{ pages: { body: Mastodon.Status[] }[] }>(
|
||||||
|
queryKey.local,
|
||||||
|
old => {
|
||||||
|
ancestorsCache.current && old?.pages[0].body.unshift(...ancestorsCache.current)
|
||||||
|
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
flRef.current?.scrollToIndex({
|
||||||
|
index: ancestorsCache.current?.length || 0,
|
||||||
|
viewOffset: 50
|
||||||
|
})
|
||||||
|
}, 50)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const match = urlMatcher(toot.url || toot.uri)
|
const match = urlMatcher(toot.url || toot.uri)
|
||||||
|
const remoteQueryEnabled =
|
||||||
|
['public', 'unlisted'].includes(toot.visibility) &&
|
||||||
|
match?.domain !== getAccountStorage.string('auth.domain')
|
||||||
const query = useQuery<{
|
const query = useQuery<{
|
||||||
pages: { body: (Mastodon.Status & { _level?: number; key?: string })[] }[]
|
pages: { body: (Mastodon.Status & { _level?: number; key?: string })[] }[]
|
||||||
}>(
|
}>(
|
||||||
@ -115,11 +162,15 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
enabled: !toot._remote,
|
enabled: !toot._remote,
|
||||||
staleTime: 0,
|
staleTime: 0,
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
onSuccess: data => {
|
onSuccess: async data => {
|
||||||
if (data.pages[0].body.length < 1) {
|
if (data.pages[0].body.length < 1) {
|
||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!remoteQueryEnabled) {
|
||||||
|
await prependContent()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -192,12 +243,10 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled:
|
enabled: (toot._remote ? true : query.isFetched) && remoteQueryEnabled,
|
||||||
(toot._remote ? true : query.isFetched) &&
|
|
||||||
['public', 'unlisted'].includes(toot.visibility) &&
|
|
||||||
match?.domain !== getAccountStorage.string('auth.domain'),
|
|
||||||
staleTime: 0,
|
staleTime: 0,
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
|
retry: false,
|
||||||
onSuccess: async data => {
|
onSuccess: async data => {
|
||||||
if ((query.data?.pages[0].body.length || 0) < 1 && data.length < 1) {
|
if ((query.data?.pages[0].body.length || 0) < 1 && data.length < 1) {
|
||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
@ -245,48 +294,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSettled: async () => {
|
onSettled: async () => {
|
||||||
loaded.current = true
|
await prependContent()
|
||||||
|
|
||||||
if (ancestorsCache.current?.length) {
|
|
||||||
switch (Platform.OS) {
|
|
||||||
case 'ios':
|
|
||||||
for (let [] of Array(
|
|
||||||
Math.ceil(ancestorsCache.current.length / PREV_PER_BATCH)
|
|
||||||
).entries()) {
|
|
||||||
await new Promise(promise => setTimeout(promise, 64))
|
|
||||||
queryClient.setQueryData<{ pages: { body: Mastodon.Status[] }[] }>(
|
|
||||||
queryKey.local,
|
|
||||||
old => {
|
|
||||||
const insert = ancestorsCache.current?.slice(-PREV_PER_BATCH)
|
|
||||||
ancestorsCache.current = ancestorsCache.current?.slice(0, -PREV_PER_BATCH)
|
|
||||||
if (insert) {
|
|
||||||
old?.pages[0].body.unshift(...insert)
|
|
||||||
}
|
|
||||||
|
|
||||||
return old
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
queryClient.setQueryData<{ pages: { body: Mastodon.Status[] }[] }>(
|
|
||||||
queryKey.local,
|
|
||||||
old => {
|
|
||||||
ancestorsCache.current && old?.pages[0].body.unshift(...ancestorsCache.current)
|
|
||||||
|
|
||||||
return old
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
flRef.current?.scrollToIndex({
|
|
||||||
index: ancestorsCache.current?.length || 0,
|
|
||||||
viewOffset: 50
|
|
||||||
})
|
|
||||||
}, 50)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -32,11 +32,7 @@ import { routingInstrumentation } from '../utils/startup/sentry'
|
|||||||
|
|
||||||
const Stack = createNativeStackNavigator<RootStackParamList>()
|
const Stack = createNativeStackNavigator<RootStackParamList>()
|
||||||
|
|
||||||
export interface Props {
|
const Screens: React.FC = () => {
|
||||||
localCorrupt?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|
||||||
const { t, i18n } = useTranslation([
|
const { t, i18n } = useTranslation([
|
||||||
'common',
|
'common',
|
||||||
'screens',
|
'screens',
|
||||||
@ -64,24 +60,6 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
return () => screenshotListener.remove()
|
return () => screenshotListener.remove()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// On launch display login credentials corrupt information
|
|
||||||
useEffect(() => {
|
|
||||||
const showLocalCorrect = () => {
|
|
||||||
if (localCorrupt) {
|
|
||||||
displayMessage({
|
|
||||||
message: t('screens:localCorrupt.message'),
|
|
||||||
description: localCorrupt.length ? localCorrupt : undefined,
|
|
||||||
type: 'danger'
|
|
||||||
})
|
|
||||||
// @ts-ignore
|
|
||||||
navigationRef.navigate('Screen-Tabs', {
|
|
||||||
screen: 'Tab-Me'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return showLocalCorrect()
|
|
||||||
}, [localCorrupt])
|
|
||||||
|
|
||||||
// Lazily update users's preferences, for e.g. composing default visibility
|
// Lazily update users's preferences, for e.g. composing default visibility
|
||||||
useInstanceQuery({ options: { enabled: !!accountActive } })
|
useInstanceQuery({ options: { enabled: !!accountActive } })
|
||||||
useProfileQuery({ options: { enabled: !!accountActive } })
|
useProfileQuery({ options: { enabled: !!accountActive } })
|
||||||
|
@ -51,8 +51,6 @@ const features = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export const featureCheck = (feature: string): boolean => {
|
export const featureCheck = (feature: string, v?: string): boolean =>
|
||||||
const version = getAccountStorage.string('version')
|
(features.find(f => f.feature === feature)?.version || 999) <=
|
||||||
return !!features.filter(f => f.feature === feature).filter(f => parseFloat(version) >= f.version)
|
parseFloat(v || getAccountStorage.string('version'))
|
||||||
?.length
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import * as htmlparser2 from 'htmlparser2'
|
import * as htmlparser2 from 'htmlparser2'
|
||||||
|
|
||||||
const removeHTML = (text: string): string => {
|
const removeHTML = (text: string): string => {
|
||||||
|
if (!text) return ''
|
||||||
|
|
||||||
let raw: string = ''
|
let raw: string = ''
|
||||||
|
|
||||||
const parser = new htmlparser2.Parser({
|
const parser = new htmlparser2.Parser({
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { StackActions, useFocusEffect, useNavigation } from '@react-navigation/native'
|
import { StackActions } from '@react-navigation/native'
|
||||||
import { useGlobalStorage } from '@utils/storage/actions'
|
import { useGlobalStorage } from '@utils/storage/actions'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
import navigationRef from './navigationRef'
|
||||||
|
|
||||||
// Mostly used when switching account and sub pages were still querying the old instance
|
// Mostly used when switching account and sub pages were still querying the old instance
|
||||||
|
|
||||||
const usePopToTop = () => {
|
const usePopToTop = (name: string) => {
|
||||||
const navigation = useNavigation()
|
|
||||||
const [accountActive] = useGlobalStorage.string('account.active')
|
const [accountActive] = useGlobalStorage.string('account.active')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
navigation.dispatch(StackActions.popToTop())
|
const currentRoute = navigationRef.getCurrentRoute()
|
||||||
|
if (currentRoute && currentRoute.name !== name) {
|
||||||
|
navigationRef.dispatch(StackActions.popToTop())
|
||||||
|
}
|
||||||
}, [accountActive])
|
}, [accountActive])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,67 +1,15 @@
|
|||||||
import NetInfo from '@react-native-community/netinfo'
|
import NetInfo from '@react-native-community/netinfo'
|
||||||
import { onlineManager } from '@tanstack/react-query'
|
import { onlineManager } from '@tanstack/react-query'
|
||||||
import apiInstance from '@utils/api/instance'
|
|
||||||
import { storage } from '@utils/storage'
|
|
||||||
import { getAccountStorage, removeAccount, setAccountStorage } from '@utils/storage/actions'
|
|
||||||
import log from './log'
|
import log from './log'
|
||||||
|
|
||||||
const netInfo = async (): Promise<{
|
const netInfo = () => {
|
||||||
connected?: boolean
|
|
||||||
corrupted?: string
|
|
||||||
} | void> => {
|
|
||||||
log('log', 'netInfo', 'initializing')
|
log('log', 'netInfo', 'initializing')
|
||||||
|
|
||||||
const netInfo = await NetInfo.fetch()
|
|
||||||
|
|
||||||
onlineManager.setEventListener(setOnline => {
|
onlineManager.setEventListener(setOnline => {
|
||||||
return NetInfo.addEventListener(state => {
|
return NetInfo.addEventListener(state => {
|
||||||
setOnline(!!state.isConnected)
|
setOnline(!!state.isConnected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
if (netInfo.isConnected) {
|
|
||||||
log('log', 'netInfo', 'network connected')
|
|
||||||
if (storage.account) {
|
|
||||||
const domain = getAccountStorage.string('auth.domain')
|
|
||||||
const id = getAccountStorage.string('auth.account.id')
|
|
||||||
const account = `${domain}/${id}`
|
|
||||||
log('log', 'netInfo', 'checking locally stored credentials')
|
|
||||||
|
|
||||||
let resVerify: Mastodon.Account
|
|
||||||
try {
|
|
||||||
resVerify = await apiInstance<Mastodon.Account>({
|
|
||||||
method: 'get',
|
|
||||||
url: `accounts/verify_credentials`
|
|
||||||
}).then(res => res.body)
|
|
||||||
} catch (error: any) {
|
|
||||||
log('error', 'netInfo', 'local credential check failed')
|
|
||||||
if (error?.status && error.status == 401) {
|
|
||||||
removeAccount(account)
|
|
||||||
}
|
|
||||||
return Promise.resolve({ corrupted: error.data?.error })
|
|
||||||
}
|
|
||||||
|
|
||||||
log('log', 'netInfo', 'local credential check passed')
|
|
||||||
if (resVerify.id !== id) {
|
|
||||||
log('error', 'netInfo', 'local id does not match remote id')
|
|
||||||
removeAccount(account)
|
|
||||||
return Promise.resolve({ connected: true, corrupted: '' })
|
|
||||||
} else {
|
|
||||||
setAccountStorage([
|
|
||||||
{ key: 'auth.account.acct', value: resVerify.acct },
|
|
||||||
{ key: 'auth.account.avatar_static', value: resVerify.avatar_static }
|
|
||||||
])
|
|
||||||
|
|
||||||
return Promise.resolve({ connected: true })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log('log', 'netInfo', 'no local credential found')
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log('warn', 'netInfo', 'network not connected')
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default netInfo
|
export default netInfo
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
|
import { displayMessage } from '@components/Message'
|
||||||
|
import i18n from '@i18n/index'
|
||||||
|
import apiGeneral from '@utils/api/general'
|
||||||
|
import navigationRef from '@utils/navigation/navigationRef'
|
||||||
import { queryClient } from '@utils/queryHooks'
|
import { queryClient } from '@utils/queryHooks'
|
||||||
|
import log from '@utils/startup/log'
|
||||||
import { storage } from '@utils/storage'
|
import { storage } from '@utils/storage'
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
import {
|
import {
|
||||||
@ -222,10 +227,98 @@ export const generateAccountKey = ({
|
|||||||
}) => `${domain}/${id}`
|
}) => `${domain}/${id}`
|
||||||
|
|
||||||
export const setAccount = async (account: string) => {
|
export const setAccount = async (account: string) => {
|
||||||
storage.account = new MMKV({ id: account })
|
const temp = new MMKV({ id: account })
|
||||||
setGlobalStorage('account.active', account)
|
const token = temp.getString('auth.token')
|
||||||
await queryClient.resetQueries()
|
const domain = temp.getString('auth.domain')
|
||||||
queryClient.clear()
|
|
||||||
|
if (!token || !domain) {
|
||||||
|
await removeAccount(account)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await apiGeneral<Mastodon.Account>({
|
||||||
|
method: 'get',
|
||||||
|
domain,
|
||||||
|
url: 'api/v1/accounts/verify_credentials',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => res.body)
|
||||||
|
.then(async a => {
|
||||||
|
temp.set('auth.account.acct', a.acct)
|
||||||
|
temp.set('auth.account.avatar_static', a.avatar_static)
|
||||||
|
|
||||||
|
log('log', 'setAccount', `binding storage of ${account}`)
|
||||||
|
await queryClient.resetQueries()
|
||||||
|
queryClient.clear()
|
||||||
|
|
||||||
|
storage.account = temp
|
||||||
|
setGlobalStorage('account.active', account)
|
||||||
|
})
|
||||||
|
.catch(async error => {
|
||||||
|
if (error?.status && error.status == 401) {
|
||||||
|
log('log', 'setAccount', `unauthorised ${account}`)
|
||||||
|
await removeAccount(account)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const removeAccount = async (account: string, warning: boolean = true) => {
|
||||||
|
const temp = new MMKV({ id: account })
|
||||||
|
|
||||||
|
if (warning) {
|
||||||
|
const acct = temp.getString('auth.account.acct')
|
||||||
|
const domain = temp.getString('auth.account.domain')
|
||||||
|
displayMessage({
|
||||||
|
message: i18n.t('screens:localCorrupt.message'),
|
||||||
|
...(acct && domain && { description: `@${acct}@${domain}` }),
|
||||||
|
type: 'danger'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
navigationRef.navigate('Screen-Tabs', { screen: 'Tab-Me' })
|
||||||
|
|
||||||
|
const revokeDetails = {
|
||||||
|
domain: temp.getString('auth.domain'),
|
||||||
|
client_id: temp.getString('auth.clientId'),
|
||||||
|
client_secret: temp.getString('auth.clientSecret'),
|
||||||
|
token: temp.getString('auth.token')
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
revokeDetails.domain &&
|
||||||
|
revokeDetails.client_id &&
|
||||||
|
revokeDetails.client_secret &&
|
||||||
|
revokeDetails.token
|
||||||
|
) {
|
||||||
|
const body = new FormData()
|
||||||
|
body.append('client_id', revokeDetails.client_id)
|
||||||
|
body.append('client_secret', revokeDetails.client_secret)
|
||||||
|
body.append('token', revokeDetails.token)
|
||||||
|
apiGeneral({ method: 'post', domain: revokeDetails.domain, url: '/oauth/revoke', body })
|
||||||
|
}
|
||||||
|
|
||||||
|
const currAccounts: NonNullable<StorageGlobal['accounts']> =
|
||||||
|
getGlobalStorage.object('accounts') || []
|
||||||
|
const nextAccounts: NonNullable<StorageGlobal['accounts']> = currAccounts.filter(
|
||||||
|
a => a !== account
|
||||||
|
)
|
||||||
|
|
||||||
|
storage.global.set('accounts', JSON.stringify(nextAccounts))
|
||||||
|
|
||||||
|
if (nextAccounts.length) {
|
||||||
|
log('log', 'removeAccount', `trying next account ${nextAccounts[nextAccounts.length - 1]}`)
|
||||||
|
await setAccount(nextAccounts[nextAccounts.length - 1])
|
||||||
|
} else {
|
||||||
|
log('log', 'removeAccount', 'setting to undefined')
|
||||||
|
await queryClient.resetQueries()
|
||||||
|
queryClient.clear()
|
||||||
|
|
||||||
|
storage.account = undefined
|
||||||
|
setGlobalStorage('account.active', undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
new MMKV({ id: account }).clearAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReadableAccountType = {
|
export type ReadableAccountType = {
|
||||||
@ -263,25 +356,3 @@ export const getReadableAccounts = (withoutActive: boolean = false): ReadableAcc
|
|||||||
}) || []
|
}) || []
|
||||||
).filter(a => a.acct.length)
|
).filter(a => a.acct.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removeAccount = async (account: string) => {
|
|
||||||
const currAccounts: NonNullable<StorageGlobal['accounts']> = JSON.parse(
|
|
||||||
storage.global.getString('accounts') || '[]'
|
|
||||||
)
|
|
||||||
const nextAccounts: NonNullable<StorageGlobal['accounts']> = currAccounts.filter(
|
|
||||||
a => a !== account
|
|
||||||
)
|
|
||||||
|
|
||||||
storage.global.set('accounts', JSON.stringify(nextAccounts))
|
|
||||||
|
|
||||||
if (nextAccounts.length) {
|
|
||||||
await setAccount(nextAccounts[nextAccounts.length - 1])
|
|
||||||
} else {
|
|
||||||
storage.account = undefined
|
|
||||||
setGlobalStorage('account.active', undefined)
|
|
||||||
queryClient.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
const temp = new MMKV({ id: account })
|
|
||||||
temp.clearAll()
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user