mirror of
https://github.com/tooot-app/app
synced 2025-05-28 18:04:18 +02:00
commit
af5bfecb06
@ -1,6 +1,3 @@
|
||||
name({
|
||||
'default' => "tooot"
|
||||
})
|
||||
keywords({
|
||||
'default' => "Mastodon,tooot,social,decentralized,长毛象,社交,去中心"
|
||||
})
|
||||
|
1
fastlane/metadata/android/en-US/title.txt
Symbolic link
1
fastlane/metadata/android/en-US/title.txt
Symbolic link
@ -0,0 +1 @@
|
||||
../../en-US/name.txt
|
@ -1 +0,0 @@
|
||||
tooot
|
1
fastlane/metadata/android/zh-CN/title.txt
Symbolic link
1
fastlane/metadata/android/zh-CN/title.txt
Symbolic link
@ -0,0 +1 @@
|
||||
../../zh-Hans/name.txt
|
1
fastlane/metadata/default/name.txt
Symbolic link
1
fastlane/metadata/default/name.txt
Symbolic link
@ -0,0 +1 @@
|
||||
../en-US/name.txt
|
1
fastlane/metadata/en-US/name.txt
Normal file
1
fastlane/metadata/en-US/name.txt
Normal file
@ -0,0 +1 @@
|
||||
tooot - multilingual Mastodon app
|
@ -1 +1 @@
|
||||
Open source Mastodon client
|
||||
Simple, just works
|
1
fastlane/metadata/zh-Hans/name.txt
Normal file
1
fastlane/metadata/zh-Hans/name.txt
Normal file
@ -0,0 +1 @@
|
||||
tooot - 探索联邦宇宙
|
@ -1 +1 @@
|
||||
开源毛象客户端
|
||||
简约,想你所想
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tooot",
|
||||
"version": "4.8.5",
|
||||
"version": "4.8.6",
|
||||
"description": "tooot for Mastodon",
|
||||
"author": "xmflsct <me@xmflsct.com>",
|
||||
"license": "GPL-3.0-or-later",
|
||||
|
38
src/App.tsx
38
src/App.tsx
@ -2,6 +2,7 @@ import { ActionSheetProvider } from '@expo/react-native-action-sheet'
|
||||
import * as Sentry from '@sentry/react-native'
|
||||
import { QueryClientProvider } from '@tanstack/react-query'
|
||||
import AccessibilityManager from '@utils/accessibility/AccessibilityManager'
|
||||
import { connectVerify } from '@utils/api/helpers/connect'
|
||||
import getLanguage from '@utils/helpers/getLanguage'
|
||||
import { queryClient } from '@utils/queryHooks'
|
||||
import audio from '@utils/startup/audio'
|
||||
@ -23,6 +24,10 @@ import { enableFreeze } from 'react-native-screens'
|
||||
import i18n from './i18n'
|
||||
import Screens from './screens'
|
||||
|
||||
export const GLOBAL: { connect?: boolean } = {
|
||||
connect: undefined
|
||||
}
|
||||
|
||||
Platform.select({
|
||||
android: LogBox.ignoreLogs(['Setting a timer for a long period of time'])
|
||||
})
|
||||
@ -50,20 +55,29 @@ const App: React.FC = () => {
|
||||
await migrateFromAsyncStorage()
|
||||
setHasMigrated(true)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const useConnect = getGlobalStorage.boolean('app.connect')
|
||||
GLOBAL.connect = useConnect
|
||||
log('log', 'App', `connect: ${useConnect}`)
|
||||
if (useConnect) {
|
||||
await connectVerify()
|
||||
.then(() => log('log', 'App', 'connected'))
|
||||
.catch(() => log('warn', 'App', 'connect verify failed'))
|
||||
}
|
||||
|
||||
log('log', 'App', 'loading from MMKV')
|
||||
const account = getGlobalStorage.string('account.active')
|
||||
if (account) {
|
||||
await setAccount(account)
|
||||
} else {
|
||||
log('log', 'App', 'loading from MMKV')
|
||||
const account = getGlobalStorage.string('account.active')
|
||||
if (account) {
|
||||
await setAccount(account)
|
||||
log('log', 'App', 'No active account available')
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
if (accounts?.length) {
|
||||
log('log', 'App', `Setting active account ${accounts[accounts.length - 1]}`)
|
||||
await setAccount(accounts[accounts.length - 1])
|
||||
} else {
|
||||
log('log', 'App', 'No active account available')
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
if (accounts?.length) {
|
||||
log('log', 'App', `Setting active account ${accounts[accounts.length - 1]}`)
|
||||
await setAccount(accounts[accounts.length - 1])
|
||||
} else {
|
||||
setGlobalStorage('account.active', undefined)
|
||||
}
|
||||
setGlobalStorage('account.active', undefined)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,7 @@ const ComponentAccount: React.FC<PropsWithChildren & Props> = ({ account, props,
|
||||
borderRadius: 8,
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
dim
|
||||
/>
|
||||
<View style={{ flex: 1 }}>
|
||||
<CustomText numberOfLines={1}>
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { ReadableAccountType, setAccount } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import Button from './Button'
|
||||
import { Pressable } from 'react-native'
|
||||
import GracefullyImage from './GracefullyImage'
|
||||
import haptics from './haptics'
|
||||
import Icon from './Icon'
|
||||
import CustomText from './Text'
|
||||
|
||||
interface Props {
|
||||
account: ReadableAccountType
|
||||
@ -11,26 +15,56 @@ interface Props {
|
||||
}
|
||||
|
||||
const AccountButton: React.FC<Props> = ({ account, additionalActions }) => {
|
||||
const { colors } = useTheme()
|
||||
const navigation = useNavigation()
|
||||
|
||||
return (
|
||||
<Button
|
||||
type='text'
|
||||
selected={account.active}
|
||||
<Pressable
|
||||
style={{
|
||||
marginBottom: StyleConstants.Spacing.M,
|
||||
marginRight: StyleConstants.Spacing.M
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: StyleConstants.Spacing.S,
|
||||
paddingHorizontal: StyleConstants.Spacing.S * 1.5,
|
||||
borderColor: account.active ? colors.blue : colors.border,
|
||||
borderWidth: 1,
|
||||
borderRadius: 99,
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}}
|
||||
content={account.acct}
|
||||
onPress={() => {
|
||||
onPress={async () => {
|
||||
await setAccount(account.key)
|
||||
haptics('Light')
|
||||
setAccount(account.key)
|
||||
navigation.goBack()
|
||||
if (additionalActions) {
|
||||
additionalActions()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<GracefullyImage
|
||||
uri={{ original: account.avatar_static }}
|
||||
dimension={{
|
||||
width: StyleConstants.Font.Size.L,
|
||||
height: StyleConstants.Font.Size.L
|
||||
}}
|
||||
style={{ borderRadius: StyleConstants.Font.Size.L / 2, overflow: 'hidden' }}
|
||||
/>
|
||||
<CustomText
|
||||
fontStyle='M'
|
||||
fontWeight={account.active ? 'Bold' : 'Normal'}
|
||||
style={{
|
||||
color: account.active ? colors.blue : colors.primaryDefault,
|
||||
marginLeft: StyleConstants.Spacing.S
|
||||
}}
|
||||
children={account.acct}
|
||||
/>
|
||||
{account.active ? (
|
||||
<Icon
|
||||
name='check'
|
||||
size={StyleConstants.Font.Size.L}
|
||||
color={colors.blue}
|
||||
style={{ marginLeft: StyleConstants.Spacing.S }}
|
||||
/>
|
||||
) : null}
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { emojis } from '@components/Emojis'
|
||||
import Icon from '@components/Icon'
|
||||
import CustomText from '@components/Text'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { connectImage } from '@utils/api/helpers/connect'
|
||||
import { StorageAccount } from '@utils/storage/account'
|
||||
import { getAccountStorage, setAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
@ -133,7 +134,7 @@ const EmojisList = () => {
|
||||
emoji: emoji.shortcode
|
||||
})}
|
||||
accessibilityHint={t('screenCompose:content.root.footer.emojis.accessibilityHint')}
|
||||
source={{ uri }}
|
||||
source={connectImage({ uri })}
|
||||
style={{ width: 32, height: 32 }}
|
||||
/>
|
||||
</Pressable>
|
||||
|
@ -2,8 +2,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { Fragment } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { View, ViewStyle } from 'react-native'
|
||||
import { TouchableNativeFeedback } from 'react-native-gesture-handler'
|
||||
import { Pressable, View, ViewStyle } from 'react-native'
|
||||
import Icon from './Icon'
|
||||
import CustomText from './Text'
|
||||
|
||||
@ -19,7 +18,7 @@ export const Filter: React.FC<Props> = ({ onPress, filter, button, style }) => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
return (
|
||||
<TouchableNativeFeedback onPress={onPress}>
|
||||
<Pressable onPress={onPress}>
|
||||
<View
|
||||
style={{
|
||||
paddingVertical: StyleConstants.Spacing.S,
|
||||
@ -106,6 +105,6 @@ export const Filter: React.FC<Props> = ({ onPress, filter, button, style }) => {
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableNativeFeedback>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { connectImage } from '@utils/api/helpers/connect'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import {
|
||||
@ -37,6 +38,7 @@ export interface Props {
|
||||
height: number
|
||||
}>
|
||||
>
|
||||
dim?: boolean
|
||||
}
|
||||
|
||||
const GracefullyImage = ({
|
||||
@ -49,14 +51,15 @@ const GracefullyImage = ({
|
||||
onPress,
|
||||
style,
|
||||
imageStyle,
|
||||
setImageDimensions
|
||||
setImageDimensions,
|
||||
dim
|
||||
}: Props) => {
|
||||
const { reduceMotionEnabled } = useAccessibility()
|
||||
const { colors } = useTheme()
|
||||
const { colors, theme } = useTheme()
|
||||
const [imageLoaded, setImageLoaded] = useState(false)
|
||||
|
||||
const [currentUri, setCurrentUri] = useState<string | undefined>(uri.original || uri.remote)
|
||||
const source = {
|
||||
const source: { uri?: string } = {
|
||||
uri: reduceMotionEnabled && uri.static ? uri.static : currentUri
|
||||
}
|
||||
useEffect(() => {
|
||||
@ -90,12 +93,12 @@ const GracefullyImage = ({
|
||||
>
|
||||
{uri.preview && !imageLoaded ? (
|
||||
<FastImage
|
||||
source={{ uri: uri.preview }}
|
||||
source={connectImage({ uri: uri.preview })}
|
||||
style={[styles.placeholder, { backgroundColor: colors.shimmerDefault }]}
|
||||
/>
|
||||
) : null}
|
||||
<FastImage
|
||||
source={source}
|
||||
source={connectImage(source)}
|
||||
style={[{ flex: 1 }, imageStyle]}
|
||||
onLoad={() => {
|
||||
setImageLoaded(true)
|
||||
@ -110,6 +113,14 @@ const GracefullyImage = ({
|
||||
}}
|
||||
/>
|
||||
{blurhashView()}
|
||||
{dim && theme !== 'light' ? (
|
||||
<View
|
||||
style={[
|
||||
styles.placeholder,
|
||||
{ backgroundColor: 'black', opacity: theme === 'dark_lighter' ? 0.18 : 0.36 }
|
||||
]}
|
||||
/>
|
||||
) : null}
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import CustomText from '@components/Text'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { connectImage } from '@utils/api/helpers/connect'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { adaptiveScale } from '@utils/styles/scaling'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { Platform, TextStyle } from 'react-native'
|
||||
import { ColorValue, Platform, TextStyle } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
|
||||
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)
|
||||
@ -14,6 +15,7 @@ export interface Props {
|
||||
content?: string
|
||||
emojis?: Mastodon.Emoji[]
|
||||
size?: 'S' | 'M' | 'L'
|
||||
color?: ColorValue
|
||||
adaptiveSize?: boolean
|
||||
fontBold?: boolean
|
||||
style?: TextStyle
|
||||
@ -23,6 +25,7 @@ const ParseEmojis: React.FC<Props> = ({
|
||||
content,
|
||||
emojis,
|
||||
size = 'M',
|
||||
color,
|
||||
adaptiveSize = false,
|
||||
fontBold = false,
|
||||
style
|
||||
@ -41,13 +44,13 @@ const ParseEmojis: React.FC<Props> = ({
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
)
|
||||
|
||||
const { colors, theme } = useTheme()
|
||||
const { colors } = useTheme()
|
||||
|
||||
return (
|
||||
<CustomText
|
||||
style={[
|
||||
{
|
||||
color: colors.primaryDefault,
|
||||
color: color || colors.primaryDefault,
|
||||
fontSize: adaptedFontsize,
|
||||
lineHeight: adaptedLineheight
|
||||
},
|
||||
@ -75,7 +78,7 @@ const ParseEmojis: React.FC<Props> = ({
|
||||
<CustomText key={emojiShortcode + i}>
|
||||
{i === 0 ? ' ' : undefined}
|
||||
<FastImage
|
||||
source={{ uri: uri.trim() }}
|
||||
source={connectImage({ uri: uri.trim() })}
|
||||
style={{
|
||||
width: adaptedFontsize,
|
||||
height: adaptedFontsize,
|
||||
|
@ -16,11 +16,12 @@ import { ElementType, parseDocument } from 'htmlparser2'
|
||||
import i18next from 'i18next'
|
||||
import React, { useContext, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform, Pressable, Text, View } from 'react-native'
|
||||
import { ColorValue, Platform, Pressable, Text, View } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
content: string
|
||||
size?: 'S' | 'M' | 'L'
|
||||
color?: ColorValue
|
||||
adaptiveSize?: boolean
|
||||
showFullLink?: boolean
|
||||
numberOfLines?: number
|
||||
@ -34,6 +35,7 @@ export interface Props {
|
||||
const ParseHTML: React.FC<Props> = ({
|
||||
content,
|
||||
size = 'M',
|
||||
color,
|
||||
adaptiveSize = false,
|
||||
showFullLink = false,
|
||||
numberOfLines = 10,
|
||||
@ -58,6 +60,7 @@ const ParseHTML: React.FC<Props> = ({
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
const { params } = useRoute()
|
||||
const { colors } = useTheme()
|
||||
const colorPrimary = color || colors.primaryDefault
|
||||
const { t } = useTranslation('componentParse')
|
||||
if (!expandHint) {
|
||||
expandHint = t('HTML.defaultHint')
|
||||
@ -111,6 +114,7 @@ const ParseHTML: React.FC<Props> = ({
|
||||
content={content}
|
||||
emojis={status?.emojis || emojis}
|
||||
size={size}
|
||||
color={colorPrimary}
|
||||
adaptiveSize={adaptiveSize}
|
||||
/>
|
||||
)
|
||||
@ -181,7 +185,7 @@ const ParseHTML: React.FC<Props> = ({
|
||||
return (
|
||||
<Text
|
||||
key={index}
|
||||
style={{ color: matchedMention ? colors.blue : colors.primaryDefault }}
|
||||
style={{ color: matchedMention ? colors.blue : colorPrimary }}
|
||||
onPress={() =>
|
||||
matchedMention &&
|
||||
!disableDetails &&
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { ColorValue, TouchableNativeFeedback, View } from 'react-native'
|
||||
import { ColorValue, Pressable, View } from 'react-native'
|
||||
import { SwipeListView } from 'react-native-swipe-list-view'
|
||||
import haptics from './haptics'
|
||||
import Icon, { IconName } from './Icon'
|
||||
@ -25,7 +25,7 @@ export const SwipeToActions = <T extends unknown>({
|
||||
renderHiddenItem={({ item }) => (
|
||||
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'flex-end' }}>
|
||||
{actions.map((action, index) => (
|
||||
<TouchableNativeFeedback
|
||||
<Pressable
|
||||
key={index}
|
||||
onPress={() => {
|
||||
haptics(action.haptic || 'Light')
|
||||
@ -43,7 +43,7 @@ export const SwipeToActions = <T extends unknown>({
|
||||
>
|
||||
<Icon name={action.icon} color='white' size={StyleConstants.Font.Size.L} />
|
||||
</View>
|
||||
</TouchableNativeFeedback>
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
@ -88,6 +88,7 @@ const TimelineConversation: React.FC<Props> = ({ conversation, queryKey, highlig
|
||||
: StyleConstants.Avatar.M
|
||||
}}
|
||||
style={{ flex: 1, flexBasis: '50%' }}
|
||||
dim
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
|
@ -33,6 +33,7 @@ export interface Props {
|
||||
item: Mastodon.Status & { _pinned?: boolean } // For account page, internal property
|
||||
queryKey?: QueryKeyTimeline
|
||||
highlighted?: boolean
|
||||
suppressSpoiler?: boolean // Same content as the main thread, can be dimmed
|
||||
disableDetails?: boolean
|
||||
disableOnPress?: boolean
|
||||
isConversation?: boolean
|
||||
@ -44,6 +45,7 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
item,
|
||||
queryKey,
|
||||
highlighted = false,
|
||||
suppressSpoiler = false,
|
||||
disableDetails = false,
|
||||
disableOnPress = false,
|
||||
isConversation = false,
|
||||
@ -170,6 +172,7 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
detectedLanguage,
|
||||
excludeMentions,
|
||||
highlighted,
|
||||
suppressSpoiler,
|
||||
inThread: queryKey?.[1].page === 'Toot',
|
||||
disableDetails,
|
||||
disableOnPress,
|
||||
|
@ -17,9 +17,9 @@ import Animated, {
|
||||
Extrapolate,
|
||||
interpolate,
|
||||
runOnJS,
|
||||
SharedValue,
|
||||
useAnimatedReaction,
|
||||
useAnimatedStyle,
|
||||
useDerivedValue,
|
||||
useSharedValue,
|
||||
withTiming
|
||||
} from 'react-native-reanimated'
|
||||
@ -27,9 +27,10 @@ import Animated, {
|
||||
export interface Props {
|
||||
flRef: RefObject<FlatList<any>>
|
||||
queryKey: QueryKeyTimeline
|
||||
fetchingActive: React.MutableRefObject<boolean>
|
||||
scrollY: Animated.SharedValue<number>
|
||||
fetchingType: Animated.SharedValue<0 | 1 | 2>
|
||||
isFetchingPrev: SharedValue<boolean>
|
||||
setFetchedCount: React.Dispatch<React.SetStateAction<number | null>>
|
||||
scrollY: SharedValue<number>
|
||||
fetchingType: SharedValue<0 | 1 | 2>
|
||||
disableRefresh?: boolean
|
||||
readMarker?: 'read_marker_following'
|
||||
}
|
||||
@ -41,7 +42,8 @@ export const SEPARATION_Y_2 = -(CONTAINER_HEIGHT * 1.5 + StyleConstants.Font.Siz
|
||||
const TimelineRefresh: React.FC<Props> = ({
|
||||
flRef,
|
||||
queryKey,
|
||||
fetchingActive,
|
||||
isFetchingPrev,
|
||||
setFetchedCount,
|
||||
scrollY,
|
||||
fetchingType,
|
||||
disableRefresh = false,
|
||||
@ -55,20 +57,11 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||
}
|
||||
|
||||
const PREV_PER_BATCH = 1
|
||||
const prevActive = useRef<boolean>(false)
|
||||
const prevCache = useRef<(Mastodon.Status | Mastodon.Notification | Mastodon.Conversation)[]>()
|
||||
const prevStatusId = useRef<Mastodon.Status['id']>()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const { refetch, isRefetching } = useTimelineQuery({ ...queryKey[1] })
|
||||
|
||||
useDerivedValue(() => {
|
||||
if (prevActive.current || isRefetching) {
|
||||
fetchingActive.current = true
|
||||
} else {
|
||||
fetchingActive.current = false
|
||||
}
|
||||
}, [prevActive.current, isRefetching])
|
||||
const { refetch } = useTimelineQuery({ ...queryKey[1] })
|
||||
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const { colors } = useTheme()
|
||||
@ -96,7 +89,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||
const arrowStage = useSharedValue(0)
|
||||
useAnimatedReaction(
|
||||
() => {
|
||||
if (fetchingActive.current) {
|
||||
if (isFetchingPrev.value) {
|
||||
return false
|
||||
}
|
||||
switch (arrowStage.value) {
|
||||
@ -128,13 +121,12 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||
if (data) {
|
||||
runOnJS(haptics)('Light')
|
||||
}
|
||||
},
|
||||
[fetchingActive.current]
|
||||
}
|
||||
)
|
||||
|
||||
const fetchAndScrolled = useSharedValue(false)
|
||||
const runFetchPrevious = async () => {
|
||||
if (prevActive.current) return
|
||||
if (isFetchingPrev.value) return
|
||||
|
||||
const firstPage =
|
||||
queryClient.getQueryData<
|
||||
@ -143,7 +135,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||
>
|
||||
>(queryKey)?.pages[0]
|
||||
|
||||
prevActive.current = true
|
||||
isFetchingPrev.value = true
|
||||
prevStatusId.current = firstPage?.body[0]?.id
|
||||
|
||||
await queryFunctionTimeline({
|
||||
@ -151,7 +143,9 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||
pageParam: firstPage?.links?.prev,
|
||||
meta: {}
|
||||
})
|
||||
.then(res => {
|
||||
.then(async res => {
|
||||
setFetchedCount(res.body.length)
|
||||
|
||||
if (!res.body.length) return
|
||||
|
||||
queryClient.setQueryData<
|
||||
@ -172,7 +166,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||
})
|
||||
.then(async nextLength => {
|
||||
if (!nextLength) {
|
||||
prevActive.current = false
|
||||
isFetchingPrev.value = false
|
||||
return
|
||||
}
|
||||
|
||||
@ -209,7 +203,7 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||
}
|
||||
})
|
||||
}
|
||||
prevActive.current = false
|
||||
isFetchingPrev.value = false
|
||||
})
|
||||
}
|
||||
|
||||
@ -218,6 +212,18 @@ const TimelineRefresh: React.FC<Props> = ({
|
||||
if (readMarker) {
|
||||
setAccountStorage([{ key: readMarker, value: undefined }])
|
||||
}
|
||||
queryClient.setQueryData<
|
||||
InfiniteData<
|
||||
PagedResponse<(Mastodon.Status | Mastodon.Notification | Mastodon.Conversation)[]>
|
||||
>
|
||||
>(queryKey, old => {
|
||||
if (!old) return old
|
||||
|
||||
return {
|
||||
pages: [old.pages[0]],
|
||||
pageParams: [old.pageParams[0]]
|
||||
}
|
||||
})
|
||||
await refetch()
|
||||
setTimeout(() => flRef.current?.scrollToOffset({ offset: 0 }), 50)
|
||||
}
|
||||
|
@ -83,11 +83,9 @@ const AttachmentAudio: React.FC<Props> = ({ total, index, sensitiveShown, audio
|
||||
<>
|
||||
{audio.preview_url ? (
|
||||
<GracefullyImage
|
||||
uri={{
|
||||
original: audio.preview_url,
|
||||
remote: audio.preview_remote_url
|
||||
}}
|
||||
uri={{ original: audio.preview_url, remote: audio.preview_remote_url }}
|
||||
style={styles.background}
|
||||
dim
|
||||
/>
|
||||
) : null}
|
||||
<Button
|
||||
|
@ -39,6 +39,7 @@ const AttachmentImage = ({
|
||||
blurhash={image.blurhash}
|
||||
onPress={() => navigateToImagesViewer(image.id)}
|
||||
style={{ aspectRatio: aspectRatio({ total, index, ...image.meta?.original }) }}
|
||||
dim
|
||||
/>
|
||||
</View>
|
||||
<AttachmentAltText sensitiveShown={sensitiveShown} text={image.description} />
|
||||
|
@ -31,8 +31,7 @@ const TimelineAvatar: React.FC<Props> = ({ account }) => {
|
||||
})
|
||||
})}
|
||||
onPress={() =>
|
||||
!disableOnPress &&
|
||||
navigation.push('Tab-Shared-Account', { account: actualAccount })
|
||||
!disableOnPress && navigation.push('Tab-Shared-Account', { account: actualAccount })
|
||||
}
|
||||
uri={{ original: actualAccount.avatar, static: actualAccount.avatar_static }}
|
||||
dimension={
|
||||
@ -51,6 +50,7 @@ const TimelineAvatar: React.FC<Props> = ({ account }) => {
|
||||
overflow: 'hidden',
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
dim
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ const TimelineCard: React.FC = () => {
|
||||
blurhash={status.card.blurhash}
|
||||
style={{ flexBasis: StyleConstants.Font.LineHeight.M * 5 }}
|
||||
imageStyle={{ borderTopLeftRadius: 6, borderBottomLeftRadius: 6 }}
|
||||
dim
|
||||
/>
|
||||
) : null}
|
||||
<View style={{ flex: 1, padding: StyleConstants.Spacing.S }}>
|
||||
|
@ -14,7 +14,7 @@ export interface Props {
|
||||
}
|
||||
|
||||
const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoilerExpanded }) => {
|
||||
const { status, highlighted, inThread } = useContext(StatusContext)
|
||||
const { status, highlighted, suppressSpoiler, inThread } = useContext(StatusContext)
|
||||
if (!status || typeof status.content !== 'string' || !status.content.length) return null
|
||||
|
||||
const { colors } = useTheme()
|
||||
@ -35,6 +35,7 @@ const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoi
|
||||
size={highlighted ? 'L' : 'M'}
|
||||
adaptiveSize
|
||||
numberOfLines={999}
|
||||
color={suppressSpoiler ? colors.disabled : undefined}
|
||||
/>
|
||||
{inThread ? (
|
||||
<CustomText
|
||||
|
@ -15,6 +15,7 @@ type StatusContextType = {
|
||||
excludeMentions?: React.MutableRefObject<Mastodon.Mention[]>
|
||||
|
||||
highlighted?: boolean
|
||||
suppressSpoiler?: boolean
|
||||
inThread?: boolean
|
||||
disableDetails?: boolean
|
||||
disableOnPress?: boolean
|
||||
|
@ -15,8 +15,8 @@ const TimelineFullConversation = () => {
|
||||
return queryKey &&
|
||||
queryKey[1].page !== 'Toot' &&
|
||||
status.in_reply_to_account_id &&
|
||||
(status.mentions.length === 0 ||
|
||||
status.mentions.filter(mention => mention.id !== status.in_reply_to_account_id).length) ? (
|
||||
(status.mentions?.length === 0 ||
|
||||
status.mentions?.filter(mention => mention.id !== status.in_reply_to_account_id).length) ? (
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{
|
||||
|
@ -22,7 +22,7 @@ const HeaderSharedReplies: React.FC = () => {
|
||||
excludeMentions &&
|
||||
(excludeMentions.current =
|
||||
mentionsBeginning?.length && status?.mentions
|
||||
? status.mentions.filter(mention => mentionsBeginning.includes(`@${mention.username}`))
|
||||
? status.mentions?.filter(mention => mentionsBeginning.includes(`@${mention.username}`))
|
||||
: [])
|
||||
|
||||
return excludeMentions?.current.length ? (
|
||||
|
@ -1,4 +1,5 @@
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import CustomText from '@components/Text'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { useScrollToTop } from '@react-navigation/native'
|
||||
import { UseInfiniteQueryOptions } from '@tanstack/react-query'
|
||||
@ -11,9 +12,21 @@ import {
|
||||
} from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { RefObject, useRef } from 'react'
|
||||
import React, { RefObject, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FlatList, FlatListProps, Platform, RefreshControl } from 'react-native'
|
||||
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
|
||||
import Animated, {
|
||||
Easing,
|
||||
runOnJS,
|
||||
useAnimatedReaction,
|
||||
useAnimatedScrollHandler,
|
||||
useAnimatedStyle,
|
||||
useDerivedValue,
|
||||
useSharedValue,
|
||||
withDelay,
|
||||
withSequence,
|
||||
withTiming
|
||||
} from 'react-native-reanimated'
|
||||
import TimelineEmpty from './Empty'
|
||||
import TimelineFooter from './Footer'
|
||||
import TimelineRefresh, { SEPARATION_Y_1, SEPARATION_Y_2 } from './Refresh'
|
||||
@ -42,9 +55,10 @@ const Timeline: React.FC<Props> = ({
|
||||
readMarker = undefined,
|
||||
customProps
|
||||
}) => {
|
||||
const { colors } = useTheme()
|
||||
const { colors, theme } = useTheme()
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
|
||||
const { data, refetch, isFetching, isLoading, fetchNextPage, isFetchingNextPage } =
|
||||
const { data, refetch, isFetching, isLoading, isRefetching, fetchNextPage, isFetchingNextPage } =
|
||||
useTimelineQuery({
|
||||
...queryKey[1],
|
||||
options: {
|
||||
@ -57,7 +71,47 @@ const Timeline: React.FC<Props> = ({
|
||||
})
|
||||
|
||||
const flRef = useRef<FlatList>(null)
|
||||
const fetchingActive = useRef<boolean>(false)
|
||||
const isFetchingPrev = useSharedValue<boolean>(false)
|
||||
const [fetchedCount, setFetchedCount] = useState<number | null>(null)
|
||||
const fetchedNoticeHeight = useSharedValue<number>(100)
|
||||
const notifiedFetchedNotice = useSharedValue<boolean>(false)
|
||||
useAnimatedReaction(
|
||||
() => isFetchingPrev.value,
|
||||
(curr, prev) => {
|
||||
if (curr === true && prev === false) {
|
||||
notifiedFetchedNotice.value = true
|
||||
}
|
||||
}
|
||||
)
|
||||
useAnimatedReaction(
|
||||
() => fetchedCount,
|
||||
(curr, prev) => {
|
||||
if (curr !== null && prev === null) {
|
||||
notifiedFetchedNotice.value = false
|
||||
}
|
||||
},
|
||||
[fetchedCount]
|
||||
)
|
||||
const fetchedNoticeTop = useDerivedValue(() => {
|
||||
if (notifiedFetchedNotice.value || fetchedCount !== null) {
|
||||
return withSequence(
|
||||
withTiming(fetchedNoticeHeight.value + 16 + 4),
|
||||
withDelay(
|
||||
2000,
|
||||
withTiming(
|
||||
0,
|
||||
{ easing: Easing.out(Easing.ease) },
|
||||
finished => finished && runOnJS(setFetchedCount)(null)
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}, [fetchedCount])
|
||||
const fetchedNoticeAnimate = useAnimatedStyle(() => ({
|
||||
transform: [{ translateY: fetchedNoticeTop.value }]
|
||||
}))
|
||||
|
||||
const scrollY = useSharedValue(0)
|
||||
const fetchingType = useSharedValue<0 | 1 | 2>(0)
|
||||
@ -95,7 +149,12 @@ const Timeline: React.FC<Props> = ({
|
||||
const marker = readMarker ? getAccountStorage.string(readMarker) : undefined
|
||||
|
||||
const firstItemId = viewableItems.filter(item => item.isViewable)[0]?.item.id
|
||||
if (!fetchingActive.current && firstItemId && firstItemId > (marker || '0')) {
|
||||
if (
|
||||
!isFetchingPrev.value &&
|
||||
!isRefetching &&
|
||||
firstItemId &&
|
||||
firstItemId > (marker || '0')
|
||||
) {
|
||||
setAccountStorage([{ key: readMarker, value: firstItemId }])
|
||||
} else {
|
||||
// setAccountStorage([{ key: readMarker, value: '109519141378761752' }])
|
||||
@ -135,7 +194,8 @@ const Timeline: React.FC<Props> = ({
|
||||
<TimelineRefresh
|
||||
flRef={flRef}
|
||||
queryKey={queryKey}
|
||||
fetchingActive={fetchingActive}
|
||||
isFetchingPrev={isFetchingPrev}
|
||||
setFetchedCount={setFetchedCount}
|
||||
scrollY={scrollY}
|
||||
fetchingType={fetchingType}
|
||||
disableRefresh={disableRefresh}
|
||||
@ -176,6 +236,44 @@ const Timeline: React.FC<Props> = ({
|
||||
{...androidRefreshControl}
|
||||
{...customProps}
|
||||
/>
|
||||
{!disableRefresh ? (
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
position: 'absolute',
|
||||
alignSelf: 'center',
|
||||
top: -fetchedNoticeHeight.value - 16,
|
||||
paddingVertical: StyleConstants.Spacing.S,
|
||||
paddingHorizontal: StyleConstants.Spacing.M,
|
||||
backgroundColor: colors.backgroundDefault,
|
||||
shadowColor: colors.primaryDefault,
|
||||
shadowOffset: { width: 0, height: 0 },
|
||||
shadowOpacity: theme === 'light' ? 0.16 : 0.24,
|
||||
borderRadius: 99,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
fetchedNoticeAnimate
|
||||
]}
|
||||
onLayout={({
|
||||
nativeEvent: {
|
||||
layout: { height }
|
||||
}
|
||||
}) => (fetchedNoticeHeight.value = height)}
|
||||
>
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{ color: colors.primaryDefault }}
|
||||
children={
|
||||
fetchedCount !== null
|
||||
? fetchedCount > 0
|
||||
? t('refresh.fetched.found', { count: fetchedCount })
|
||||
: t('refresh.fetched.none')
|
||||
: t('refresh.fetching')
|
||||
}
|
||||
/>
|
||||
</Animated.View>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ const menuStatus = ({
|
||||
hidden:
|
||||
!ownAccount &&
|
||||
queryKey[1].page !== 'Notifications' &&
|
||||
!status.mentions.find(
|
||||
!status.mentions?.find(
|
||||
mention => mention.acct === accountAcct && mention.username === accountAcct
|
||||
) &&
|
||||
!status.muted
|
||||
|
@ -16,7 +16,7 @@
|
||||
"action_true": "Deixa de silenciar l'usuari"
|
||||
},
|
||||
"followAs": {
|
||||
"trigger": "",
|
||||
"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"
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Més recent des d'aquí",
|
||||
"refetch": "A l'últim"
|
||||
"refetch": "A l'últim",
|
||||
"fetching": "Obtenint publicacions...",
|
||||
"fetched": {
|
||||
"none": "No n'hi ha de noves",
|
||||
"found": "S'ha obtingut {{count}}"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -46,7 +46,7 @@
|
||||
"name": "Etiquetes seguides"
|
||||
},
|
||||
"fontSize": {
|
||||
"name": "Mida de la font de la publicació"
|
||||
"name": "Mida de la font"
|
||||
},
|
||||
"language": {
|
||||
"name": "Idioma"
|
||||
@ -136,7 +136,7 @@
|
||||
},
|
||||
"preferences": {
|
||||
"visibility": {
|
||||
"title": "Visibilitat de la publicació per defecte",
|
||||
"title": "Visibilitat per defecte",
|
||||
"options": {
|
||||
"public": "Públic",
|
||||
"unlisted": "Sense llistar",
|
||||
@ -147,11 +147,11 @@
|
||||
"title": "Marca el contingut com a sensible"
|
||||
},
|
||||
"media": {
|
||||
"title": "Mostra el contingut multimèdia",
|
||||
"title": "Multimèdia",
|
||||
"options": {
|
||||
"default": "Amaga el contingut multimèdia com a sensible",
|
||||
"show_all": "Mostra sempre el contingut multimèdia",
|
||||
"hide_all": "Amaga sempre el contingut multimèdia"
|
||||
"default": "Amaga els sensibles",
|
||||
"show_all": "Mostra'ls sempre",
|
||||
"hide_all": "Amaga'ls sempre"
|
||||
}
|
||||
},
|
||||
"spoilers": {
|
||||
@ -178,7 +178,7 @@
|
||||
"context": "Aplica a <0 />",
|
||||
"contexts": {
|
||||
"home": "Seguits i llistes",
|
||||
"notifications": "Notificació",
|
||||
"notifications": "Notificacions",
|
||||
"public": "federat",
|
||||
"thread": "Conversa",
|
||||
"account": "Perfil"
|
||||
@ -199,17 +199,17 @@
|
||||
"context": "Aplica a",
|
||||
"contexts": {
|
||||
"home": "Seguits i llistes",
|
||||
"notifications": "Notificació",
|
||||
"notifications": "Notificacions",
|
||||
"public": "Línia de temps federada",
|
||||
"thread": "Vista de conversa",
|
||||
"account": "Vista del perfil"
|
||||
},
|
||||
"action": "Quan coincideix",
|
||||
"actions": {
|
||||
"warn": "",
|
||||
"warn": "Contret però pot ser revelat",
|
||||
"hide": "Amagat completament"
|
||||
},
|
||||
"keywords": "Coincidències per aquestes paraules claus",
|
||||
"keywords": "Coincidències amb",
|
||||
"keyword": "Paraula clau",
|
||||
"statuses": "Coincideixen aquestes publicacions"
|
||||
},
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "",
|
||||
"refetch": ""
|
||||
"refetch": "",
|
||||
"fetching": "",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Neuere Einträge",
|
||||
"refetch": "Zum letzten"
|
||||
"refetch": "Zum letzten",
|
||||
"fetching": "",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Νεότερες από εδώ",
|
||||
"refetch": "Μέχρι την τελευταία"
|
||||
"refetch": "Μέχρι την τελευταία",
|
||||
"fetching": "",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Newer from here",
|
||||
"refetch": "To latest"
|
||||
"refetch": "To latest",
|
||||
"fetching": "Fetching newer toots ...",
|
||||
"fetched": {
|
||||
"none": "No newer toot",
|
||||
"found": "Fetched {{count}} toots"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,7 @@
|
||||
"action_true": "Dejar de silenciar al usuario"
|
||||
},
|
||||
"followAs": {
|
||||
"trigger": "",
|
||||
"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"
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Más reciente desde aquí",
|
||||
"refetch": "Al último"
|
||||
"refetch": "Al último",
|
||||
"fetching": "Obteniendo publicaciones...",
|
||||
"fetched": {
|
||||
"none": "No hay nuevas",
|
||||
"found": "Se ha obtenido {{count}}"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -136,7 +136,7 @@
|
||||
},
|
||||
"preferences": {
|
||||
"visibility": {
|
||||
"title": "Visibilidad de publicación predeterminada",
|
||||
"title": "Visibilidad por defecto",
|
||||
"options": {
|
||||
"public": "Público",
|
||||
"unlisted": "No listado",
|
||||
@ -147,11 +147,11 @@
|
||||
"title": "Marcar el contenido multimedia como sensibles por defecto"
|
||||
},
|
||||
"media": {
|
||||
"title": "Mostrar el contenido multimedia",
|
||||
"title": "Multimedia",
|
||||
"options": {
|
||||
"default": "Ocultar los contenidos multimedia marcados como sensibles",
|
||||
"show_all": "Mostrar siempre el contenido multimedia",
|
||||
"hide_all": "Siempre ocultar el contenido multimedia"
|
||||
"default": "Ocultar los sensibles",
|
||||
"show_all": "Mostrar siempre",
|
||||
"hide_all": "Ocultar siempre"
|
||||
}
|
||||
},
|
||||
"spoilers": {
|
||||
@ -178,7 +178,7 @@
|
||||
"context": "Se aplica en <0 />",
|
||||
"contexts": {
|
||||
"home": "Seguidos y listas",
|
||||
"notifications": "Notificación",
|
||||
"notifications": "Notificaciones",
|
||||
"public": "Federado",
|
||||
"thread": "Conversación",
|
||||
"account": "Perfil"
|
||||
@ -199,17 +199,17 @@
|
||||
"context": "Se aplica en",
|
||||
"contexts": {
|
||||
"home": "Seguidos y listas",
|
||||
"notifications": "Notificación",
|
||||
"notifications": "Notificaciones",
|
||||
"public": "Cronología federada",
|
||||
"thread": "Vista de conversación",
|
||||
"account": "Vista de perfil"
|
||||
},
|
||||
"action": "Al coincidir",
|
||||
"actions": {
|
||||
"warn": "",
|
||||
"warn": "Contraído pero puede ser revelado",
|
||||
"hide": "Oculto completamente"
|
||||
},
|
||||
"keywords": "Coincide con estas palabras clave",
|
||||
"keywords": "Coincide con",
|
||||
"keyword": "Palabra clave",
|
||||
"statuses": "Coincide con estas publicaciones"
|
||||
},
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Berrienak hemendik hasita",
|
||||
"refetch": "Azkenekora arte"
|
||||
"refetch": "Azkenekora arte",
|
||||
"fetching": "",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Plus récent à partir d'ici",
|
||||
"refetch": "À la dernière"
|
||||
"refetch": "À la dernière",
|
||||
"fetching": "",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Più recenti da qui",
|
||||
"refetch": "Al più recente"
|
||||
"refetch": "Al più recente",
|
||||
"fetching": "",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "前のページを取得",
|
||||
"refetch": "更新"
|
||||
"refetch": "更新",
|
||||
"fetching": "",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "이 시점에 이어서 불러오기",
|
||||
"refetch": "최신 내용 불러오기"
|
||||
"refetch": "최신 내용 불러오기",
|
||||
"fetching": "",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Nieuwere vanaf hier",
|
||||
"refetch": "Naar nieuwste"
|
||||
"refetch": "Naar nieuwste",
|
||||
"fetching": "Nieuwere toots ophalen...",
|
||||
"fetched": {
|
||||
"none": "Geen nieuwere toot",
|
||||
"found": "{{count}} toots opgehaald"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Nowsze",
|
||||
"refetch": "Do najnowszych"
|
||||
"refetch": "Do najnowszych",
|
||||
"fetching": "",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -5,7 +5,7 @@
|
||||
"cancel": "Cancelar",
|
||||
"discard": "Descartar",
|
||||
"continue": "Continuar",
|
||||
"create": "",
|
||||
"create": "Criar",
|
||||
"delete": "Excluir",
|
||||
"done": "Concluído",
|
||||
"confirm": "Confirmar"
|
||||
|
@ -6,9 +6,9 @@
|
||||
"action_false": "Seguir usuário",
|
||||
"action_true": "Deixar de seguir usuário"
|
||||
},
|
||||
"inLists": "",
|
||||
"inLists": "Listas contendo usuário ...",
|
||||
"showBoosts": {
|
||||
"action_false": "",
|
||||
"action_false": "Mostrar boosts do usuário",
|
||||
"action_true": ""
|
||||
},
|
||||
"mute": {
|
||||
@ -21,7 +21,7 @@
|
||||
"succeed_locked": "",
|
||||
"failed": ""
|
||||
},
|
||||
"blockReport": "",
|
||||
"blockReport": "Bloquear e denunciar",
|
||||
"block": {
|
||||
"action_false": "Bloquear usuário",
|
||||
"action_true": "Desbloquear usuário",
|
||||
@ -56,11 +56,11 @@
|
||||
},
|
||||
"hashtag": {
|
||||
"follow": {
|
||||
"action_false": "",
|
||||
"action_true": ""
|
||||
"action_false": "Seguir",
|
||||
"action_true": "Deixar de seguir"
|
||||
},
|
||||
"filter": {
|
||||
"action": ""
|
||||
"action": "Filtrar hashtag ..."
|
||||
}
|
||||
},
|
||||
"share": {
|
||||
@ -99,8 +99,8 @@
|
||||
"action_true": "Desafixar toot"
|
||||
},
|
||||
"filter": {
|
||||
"action_false": "",
|
||||
"action_true": ""
|
||||
"action_false": "Filtrar toot ...",
|
||||
"action_true": "Gerenciar filtros ..."
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Mais novo aqui",
|
||||
"refetch": "Mais recente"
|
||||
"refetch": "Mais recente",
|
||||
"fetching": "Buscando toots mais recentes...",
|
||||
"fetched": {
|
||||
"none": "Nenhum novo toot",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
@ -116,7 +121,7 @@
|
||||
"accessibilityHint": "Conta do usuário"
|
||||
}
|
||||
},
|
||||
"application": "",
|
||||
"application": "via {{application}}",
|
||||
"edited": {
|
||||
"accessibilityLabel": "Toot editado"
|
||||
},
|
||||
|
@ -61,7 +61,7 @@
|
||||
"name": "Criar uma lista"
|
||||
},
|
||||
"listEdit": {
|
||||
"name": ""
|
||||
"name": "Editar Detalhes da Lista"
|
||||
},
|
||||
"lists": {
|
||||
"name": "Listas"
|
||||
@ -116,7 +116,7 @@
|
||||
"empty": "Nenhum usuário adicionado a esta lista"
|
||||
},
|
||||
"listEdit": {
|
||||
"heading": "",
|
||||
"heading": "Editar detalhes da lista",
|
||||
"title": "Título",
|
||||
"repliesPolicy": {
|
||||
"heading": "Mostrar respostas para:",
|
||||
@ -144,7 +144,7 @@
|
||||
}
|
||||
},
|
||||
"sensitive": {
|
||||
"title": ""
|
||||
"title": "Marcar mídia como sensível por padrão"
|
||||
},
|
||||
"media": {
|
||||
"title": "",
|
||||
@ -166,22 +166,22 @@
|
||||
},
|
||||
"web_only": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"description": "As configurações abaixo só podem ser atualizadas usando a interface web"
|
||||
}
|
||||
},
|
||||
"preferencesFilters": {
|
||||
"expired": "",
|
||||
"keywords_one": "",
|
||||
"keywords_other": "",
|
||||
"statuses_one": "",
|
||||
"statuses_other": "",
|
||||
"context": "",
|
||||
"keywords_one": "{{count}} palavra-chave",
|
||||
"keywords_other": "{{count}} palavras-chave",
|
||||
"statuses_one": "{{count}} toot",
|
||||
"statuses_other": "{{count}} toots",
|
||||
"context": "Aplica-se em <0 />",
|
||||
"contexts": {
|
||||
"home": "",
|
||||
"notifications": "",
|
||||
"public": "",
|
||||
"home": "seguindo e listas",
|
||||
"notifications": "notificação",
|
||||
"public": "global",
|
||||
"thread": "",
|
||||
"account": ""
|
||||
"account": "perfil"
|
||||
}
|
||||
},
|
||||
"preferencesFilter": {
|
||||
@ -196,22 +196,22 @@
|
||||
"604800": "Após 1 semana",
|
||||
"18144000": "Após 1 mês"
|
||||
},
|
||||
"context": "",
|
||||
"context": "Aplica-se em",
|
||||
"contexts": {
|
||||
"home": "",
|
||||
"notifications": "",
|
||||
"public": "",
|
||||
"home": "Seguindo e listas",
|
||||
"notifications": "Notificação",
|
||||
"public": "Linha do tempo global",
|
||||
"thread": "",
|
||||
"account": ""
|
||||
},
|
||||
"action": "",
|
||||
"actions": {
|
||||
"warn": "",
|
||||
"hide": ""
|
||||
"warn": "Recolhido, mas pode ser revelado",
|
||||
"hide": "Esconder completamente"
|
||||
},
|
||||
"keywords": "",
|
||||
"keyword": "",
|
||||
"statuses": ""
|
||||
"keyword": "Palavra-chave",
|
||||
"statuses": "Corresponde a estes toots"
|
||||
},
|
||||
"profile": {
|
||||
"feedback": {
|
||||
@ -252,7 +252,7 @@
|
||||
"label": "Rótulo",
|
||||
"content": "Conteúdo"
|
||||
},
|
||||
"mediaSelectionFailed": ""
|
||||
"mediaSelectionFailed": "Falha no processamento da imagem. Por favor, tente novamente."
|
||||
},
|
||||
"push": {
|
||||
"notAvailable": "Seu telefone não suporta notificação de envio de tooot",
|
||||
@ -343,7 +343,7 @@
|
||||
"heading": "Tema escuro",
|
||||
"options": {
|
||||
"lighter": "Padrão",
|
||||
"darker": ""
|
||||
"darker": "Preto verdadeiro"
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
@ -354,7 +354,7 @@
|
||||
}
|
||||
},
|
||||
"autoplayGifv": {
|
||||
"heading": ""
|
||||
"heading": "Reproduzir GIFs automaticamente na linha do tempo"
|
||||
},
|
||||
"feedback": {
|
||||
"heading": "Pedidos de Funcionalidades"
|
||||
@ -392,37 +392,37 @@
|
||||
"suspended": "Conta suspensa pelos moderadores do seu servidor"
|
||||
},
|
||||
"accountInLists": {
|
||||
"name": "",
|
||||
"name": "Listas de @{{username}}",
|
||||
"inLists": "",
|
||||
"notInLists": ""
|
||||
"notInLists": "Outras listas"
|
||||
},
|
||||
"attachments": {
|
||||
"name": "<0 /><1>\"s mídia</1>"
|
||||
},
|
||||
"filter": {
|
||||
"name": "",
|
||||
"existed": ""
|
||||
"name": "Adicionar ao filtro",
|
||||
"existed": "Existe nestes filtros"
|
||||
},
|
||||
"history": {
|
||||
"name": "Histórico de Edição"
|
||||
},
|
||||
"report": {
|
||||
"name": "",
|
||||
"report": "",
|
||||
"name": "Denuncia {{acct}}",
|
||||
"report": "Denunciar",
|
||||
"forward": {
|
||||
"heading": ""
|
||||
"heading": "Encaminhar anonimamente para o servidor remoto {{instance}}"
|
||||
},
|
||||
"reasons": {
|
||||
"heading": "",
|
||||
"spam": "",
|
||||
"other": "",
|
||||
"violation": ""
|
||||
"heading": "O que há de errado com essa conta?",
|
||||
"spam": "É spam",
|
||||
"other": "É outra coisa",
|
||||
"violation": "Viola as regras do servidor"
|
||||
},
|
||||
"comment": {
|
||||
"heading": ""
|
||||
"heading": "Deseja nos dizer mais alguma coisa?"
|
||||
},
|
||||
"violatedRules": {
|
||||
"heading": ""
|
||||
"heading": "Regras violadas do servidor"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
@ -442,7 +442,7 @@
|
||||
}
|
||||
},
|
||||
"trending": {
|
||||
"tags": ""
|
||||
"tags": "Hashtags em alta"
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
@ -451,13 +451,13 @@
|
||||
"statuses": "Toot"
|
||||
},
|
||||
"notFound": "Não foi possível encontrar <bold>{{searchTerm}}</bold> {{type}} relacionado",
|
||||
"noResult": ""
|
||||
"noResult": "Não foi possível encontrar nada, tente um termo diferente"
|
||||
},
|
||||
"toot": {
|
||||
"name": "Discussões",
|
||||
"remoteFetch": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"message": "O conteúdo global nem sempre está disponível na instância local. Estes conteúdos são obtidos de instâncias remotas e marcados. Você pode interagir com esses conteúdos normalmente."
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "",
|
||||
"refetch": ""
|
||||
"refetch": "",
|
||||
"fetching": "",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Nyare härifrån",
|
||||
"refetch": "Till senaste"
|
||||
"refetch": "Till senaste",
|
||||
"fetching": "",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "З цього моменту",
|
||||
"refetch": "До кінця"
|
||||
"refetch": "До кінця",
|
||||
"fetching": "Отримання нових дмухів ...",
|
||||
"fetched": {
|
||||
"none": "Немає нових дмухів",
|
||||
"found": "Отримано {{count}} дмухів"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "Trước đó",
|
||||
"refetch": "Trang cuối"
|
||||
"refetch": "Trang cuối",
|
||||
"fetching": "",
|
||||
"fetched": {
|
||||
"none": "",
|
||||
"found": ""
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "较新于此的嘟嘟",
|
||||
"refetch": "最新的嘟嘟"
|
||||
"refetch": "最新的嘟嘟",
|
||||
"fetching": "获取较新的嘟文…",
|
||||
"fetched": {
|
||||
"none": "没有更新的嘟文",
|
||||
"found": "已获取 {{count}} 条嘟文"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -16,7 +16,12 @@
|
||||
},
|
||||
"refresh": {
|
||||
"fetchPreviousPage": "較新的嘟文",
|
||||
"refetch": "到最新的位置"
|
||||
"refetch": "到最新的位置",
|
||||
"fetching": "取得較新的嘟文 …",
|
||||
"fetched": {
|
||||
"none": "沒有更新的嘟文",
|
||||
"found": "已取得 {{count}} 條嘟文"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"actioned": {
|
||||
|
@ -92,7 +92,7 @@ const ScreenAccountSelection = ({
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenAccountSelection')
|
||||
|
||||
const accounts = getReadableAccounts(true)
|
||||
const accounts = getReadableAccounts()
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
@ -129,7 +129,7 @@ const ScreenAccountSelection = ({
|
||||
return (
|
||||
<AccountButton
|
||||
key={index}
|
||||
account={account}
|
||||
account={{ ...account, active: false }}
|
||||
additionalActions={() =>
|
||||
navigationRef.navigate('Screen-Compose', {
|
||||
type: 'share',
|
||||
|
@ -6,6 +6,7 @@ import RelativeTime from '@components/RelativeTime'
|
||||
import CustomText from '@components/Text'
|
||||
import { BlurView } from '@react-native-community/blur'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { connectImage } from '@utils/api/helpers/connect'
|
||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useAnnouncementMutation, useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
@ -139,9 +140,9 @@ const ScreenAnnouncements: React.FC<RootStackScreenProps<'Screen-Announcements'>
|
||||
>
|
||||
{reaction.url ? (
|
||||
<FastImage
|
||||
source={{
|
||||
source={connectImage({
|
||||
uri: reduceMotionEnabled ? reaction.static_url : reaction.url
|
||||
}}
|
||||
})}
|
||||
style={{
|
||||
width: StyleConstants.Font.LineHeight.M + 3,
|
||||
height: StyleConstants.Font.LineHeight.M
|
||||
|
@ -3,6 +3,7 @@ import Icon from '@components/Icon'
|
||||
import { SwipeToActions } from '@components/SwipeToActions'
|
||||
import CustomText from '@components/Text'
|
||||
import HeaderSharedCreated from '@components/Timeline/Shared/HeaderShared/Created'
|
||||
import { connectImage } from '@utils/api/helpers/connect'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { getAccountStorage, setAccountStorage, useAccountStorage } from '@utils/storage/actions'
|
||||
@ -154,9 +155,11 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
|
||||
4,
|
||||
marginLeft: index !== 0 ? StyleConstants.Spacing.S : 0
|
||||
}}
|
||||
source={{
|
||||
uri: attachment.local?.thumbnail || attachment.remote?.preview_url
|
||||
}}
|
||||
source={
|
||||
attachment.local?.thumbnail
|
||||
? { uri: attachment.local?.thumbnail }
|
||||
: connectImage({ uri: attachment.remote?.preview_url })
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
|
@ -6,6 +6,7 @@ import { MAX_MEDIA_ATTACHMENTS } from '@components/mediaSelector'
|
||||
import CustomText from '@components/Text'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { connectImage } from '@utils/api/helpers/connect'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
@ -105,7 +106,11 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
>
|
||||
<FastImage
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
source={{ uri: item.local?.thumbnail || item.remote?.preview_url }}
|
||||
source={
|
||||
item.local?.thumbnail
|
||||
? { uri: item.local?.thumbnail }
|
||||
: connectImage({ uri: item.remote?.preview_url })
|
||||
}
|
||||
/>
|
||||
{item.remote?.meta?.original?.duration ? (
|
||||
<CustomText
|
||||
@ -164,7 +169,8 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
haptics('Success')
|
||||
}}
|
||||
/>
|
||||
{composeState.type === 'edit' && featureCheck('edit_media_details') ? (
|
||||
{composeState.type !== 'edit' ||
|
||||
(composeState.type === 'edit' && featureCheck('edit_media_details')) ? (
|
||||
<Button
|
||||
accessibilityLabel={t('content.root.footer.attachments.edit.accessibilityLabel', {
|
||||
attachment: index + 1
|
||||
|
@ -96,10 +96,10 @@ const Collections: React.FC = () => {
|
||||
iconBack='chevron-right'
|
||||
title={t('screenTabs:me.stacks.push.name')}
|
||||
content={
|
||||
typeof instancePush.global === 'boolean'
|
||||
typeof instancePush?.global === 'boolean'
|
||||
? t('screenTabs:me.root.push.content', {
|
||||
defaultValue: 'false',
|
||||
context: instancePush.global.toString()
|
||||
context: instancePush?.global.toString()
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
import haptics from '@components/haptics'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { LOCALES } from '@i18n/locales'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { connectVerify } from '@utils/api/helpers/connect'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Localization from 'expo-localization'
|
||||
import React from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Linking, Platform } from 'react-native'
|
||||
import { GLOBAL } from '../../../../App'
|
||||
import { mapFontsizeToName } from '../SettingsFontsize'
|
||||
import { LOCALES } from '@i18n/locales'
|
||||
|
||||
const SettingsApp: React.FC = () => {
|
||||
const navigation = useNavigation<any>()
|
||||
@ -24,6 +26,23 @@ const SettingsApp: React.FC = () => {
|
||||
const [browser, setBrowser] = useGlobalStorage.string('app.browser')
|
||||
const [autoplayGifv, setAutoplayGifv] = useGlobalStorage.boolean('app.auto_play_gifv')
|
||||
|
||||
const [connect, setConnect] = useGlobalStorage.boolean('app.connect')
|
||||
const [showConnect, setShowConnect] = useState(connect)
|
||||
useEffect(() => {
|
||||
connectVerify()
|
||||
.then(() => {
|
||||
setShowConnect(true)
|
||||
})
|
||||
.catch(() => {
|
||||
if (connect) {
|
||||
GLOBAL.connect = false
|
||||
setConnect(false)
|
||||
} else {
|
||||
setShowConnect(false)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
@ -152,6 +171,16 @@ const SettingsApp: React.FC = () => {
|
||||
switchValue={autoplayGifv}
|
||||
switchOnValueChange={() => setAutoplayGifv(!autoplayGifv)}
|
||||
/>
|
||||
{showConnect ? (
|
||||
<MenuRow
|
||||
title='使用代理'
|
||||
switchValue={connect || false}
|
||||
switchOnValueChange={() => {
|
||||
GLOBAL.connect = !connect
|
||||
setConnect(!connect)
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</MenuContainer>
|
||||
)
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ const AccountAttachments: React.FC = () => {
|
||||
dimension={{ width: width, height: width }}
|
||||
style={{ marginLeft: StyleConstants.Spacing.Global.PagePadding }}
|
||||
onPress={() => navigation.push('Tab-Shared-Toot', { toot: item })}
|
||||
dim
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ const AccountHeader: React.FC = () => {
|
||||
)
|
||||
}
|
||||
}}
|
||||
dim
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ const AccountInformationAvatar: React.FC = () => {
|
||||
}
|
||||
}
|
||||
}}
|
||||
dim
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
|
||||
account,
|
||||
_local: true,
|
||||
options: {
|
||||
placeholderData: (account._remote
|
||||
? { ...account, id: undefined }
|
||||
: account) as Mastodon.Account,
|
||||
onSuccess: a => {
|
||||
if (account._remote) {
|
||||
setQueryKey([
|
||||
|
@ -7,6 +7,7 @@ import TimelineDefault from '@components/Timeline/Default'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import apiGeneral from '@utils/api/general'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { appendRemote } from '@utils/helpers/appendRemote'
|
||||
import { urlMatcher } from '@utils/helpers/urlMatcher'
|
||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { queryClient } from '@utils/queryHooks'
|
||||
@ -206,26 +207,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
if (localMatch) {
|
||||
return localMatch
|
||||
} else {
|
||||
return {
|
||||
...ancestor,
|
||||
_remote: true,
|
||||
account: { ...ancestor.account, _remote: true },
|
||||
mentions: ancestor.mentions.map(mention => ({
|
||||
...mention,
|
||||
_remote: true
|
||||
})),
|
||||
...(ancestor.reblog && {
|
||||
reblog: {
|
||||
...ancestor.reblog,
|
||||
_remote: true,
|
||||
account: { ...ancestor.reblog.account, _remote: true },
|
||||
mentions: ancestor.reblog.mentions.map(mention => ({
|
||||
...mention,
|
||||
_remote: true
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
return appendRemote.status(ancestor)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -268,23 +250,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
if (localMatch) {
|
||||
return { ...localMatch, _level: remote._level }
|
||||
} else {
|
||||
return {
|
||||
...remote,
|
||||
_remote: true,
|
||||
account: { ...remote.account, _remote: true },
|
||||
mentions: remote.mentions.map(mention => ({ ...mention, _remote: true })),
|
||||
...(remote.reblog && {
|
||||
reblog: {
|
||||
...remote.reblog,
|
||||
_remote: true,
|
||||
account: { ...remote.reblog.account, _remote: true },
|
||||
mentions: remote.reblog.mentions.map(mention => ({
|
||||
...mention,
|
||||
_remote: true
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
return appendRemote.status(remote)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -380,8 +346,13 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
<TimelineDefault
|
||||
item={item}
|
||||
queryKey={item._remote ? queryKey.remote : queryKey.local}
|
||||
highlighted={toot.id === item.id || item.id === 'cached'}
|
||||
isConversation={toot.id !== item.id && item.id !== 'cached'}
|
||||
highlighted={toot.id === item.id}
|
||||
suppressSpoiler={
|
||||
toot.id !== item.id &&
|
||||
!!toot.spoiler_text?.length &&
|
||||
toot.spoiler_text === item.spoiler_text
|
||||
}
|
||||
isConversation={toot.id !== item.id}
|
||||
noBackground
|
||||
/>
|
||||
{/* <CustomText
|
||||
|
@ -4,14 +4,12 @@ import Icon from '@components/Icon'
|
||||
import { Loading } from '@components/Loading'
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import CustomText from '@components/Text'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { SearchResult } from '@utils/queryHooks/search'
|
||||
import { QueryKeyUsers, useUsersQuery } from '@utils/queryHooks/users'
|
||||
import { flattenPages } from '@utils/queryHooks/utils'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { View } from 'react-native'
|
||||
import { FlatList } from 'react-native-gesture-handler'
|
||||
@ -36,8 +34,6 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> =
|
||||
...queryKey[1]
|
||||
})
|
||||
|
||||
const [isSearching, setIsSearching] = useState<number | null>(null)
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
windowSize={7}
|
||||
@ -46,38 +42,10 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> =
|
||||
minHeight: '100%',
|
||||
paddingVertical: StyleConstants.Spacing.Global.PagePadding
|
||||
}}
|
||||
renderItem={({ item, index }) => (
|
||||
renderItem={({ item }) => (
|
||||
<ComponentAccount
|
||||
account={item}
|
||||
props={{
|
||||
disabled: isSearching === index,
|
||||
onPress: () => {
|
||||
if (data?.pages[0]?.remoteData) {
|
||||
setIsSearching(index)
|
||||
apiInstance<SearchResult>({
|
||||
version: 'v2',
|
||||
method: 'get',
|
||||
url: 'search',
|
||||
params: {
|
||||
q: `@${item.acct}`,
|
||||
type: 'accounts',
|
||||
limit: 1,
|
||||
resolve: true
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
setIsSearching(null)
|
||||
if (res.body.accounts[0]) {
|
||||
navigation.push('Tab-Shared-Account', { account: res.body.accounts[0] })
|
||||
}
|
||||
})
|
||||
.catch(() => setIsSearching(null))
|
||||
} else {
|
||||
navigation.push('Tab-Shared-Account', { account: item })
|
||||
}
|
||||
}
|
||||
}}
|
||||
children={<Loading />}
|
||||
props={{ onPress: () => navigation.push('Tab-Shared-Account', { account: item }) }}
|
||||
/>
|
||||
)}
|
||||
onEndReached={() => hasNextPage && !isFetchingNextPage && fetchNextPage()}
|
||||
|
@ -2,11 +2,11 @@ import GracefullyImage from '@components/GracefullyImage'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
||||
import { RootStackScreenProps, ScreenTabsStackParamList } from '@utils/navigation/navigators'
|
||||
import { ScreenTabsStackParamList } from '@utils/navigation/navigators'
|
||||
import { getGlobalStorage, useAccountStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import { Platform, View } from 'react-native'
|
||||
import TabLocal from './Local'
|
||||
import TabMe from './Me'
|
||||
import TabNotifications from './Notifications'
|
||||
@ -14,7 +14,7 @@ import TabPublic from './Public'
|
||||
|
||||
const Tab = createBottomTabNavigator<ScreenTabsStackParamList>()
|
||||
|
||||
const ScreenTabs = ({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
||||
const ScreenTabs = () => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
const [accountActive] = useGlobalStorage.string('account.active')
|
||||
@ -50,19 +50,19 @@ const ScreenTabs = ({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
||||
return <Icon name='bell' size={size} color={color} />
|
||||
case 'Tab-Me':
|
||||
return (
|
||||
<GracefullyImage
|
||||
uri={{ original: avatarStatic }}
|
||||
dimension={{
|
||||
width: size,
|
||||
height: size
|
||||
}}
|
||||
style={{
|
||||
borderRadius: size,
|
||||
overflow: 'hidden',
|
||||
borderWidth: focused ? 2 : 0,
|
||||
borderColor: focused ? colors.secondary : color
|
||||
}}
|
||||
/>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<GracefullyImage
|
||||
uri={{ original: avatarStatic }}
|
||||
dimension={{ width: size, height: size }}
|
||||
style={{
|
||||
borderRadius: size,
|
||||
overflow: 'hidden',
|
||||
borderWidth: focused ? 2 : 0,
|
||||
borderColor: focused ? colors.primaryDefault : color
|
||||
}}
|
||||
/>
|
||||
<Icon name='more-vertical' size={size / 1.5} color={colors.secondary} />
|
||||
</View>
|
||||
)
|
||||
default:
|
||||
return <Icon name='alert-octagon' size={size} color={color} />
|
||||
@ -74,13 +74,13 @@ const ScreenTabs = ({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
||||
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
||||
<Tab.Screen
|
||||
name='Tab-Compose'
|
||||
listeners={{
|
||||
listeners={({ navigation }) => ({
|
||||
tabPress: e => {
|
||||
e.preventDefault()
|
||||
haptics('Light')
|
||||
navigation.navigate('Screen-Compose')
|
||||
}
|
||||
}}
|
||||
})}
|
||||
>
|
||||
{() => null}
|
||||
</Tab.Screen>
|
||||
@ -88,15 +88,13 @@ const ScreenTabs = ({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
||||
<Tab.Screen
|
||||
name='Tab-Me'
|
||||
component={TabMe}
|
||||
listeners={{
|
||||
listeners={({ navigation }) => ({
|
||||
tabLongPress: () => {
|
||||
haptics('Light')
|
||||
//@ts-ignore
|
||||
navigation.navigate('Tab-Me', { screen: 'Tab-Me-Root' })
|
||||
//@ts-ignore
|
||||
navigation.navigate('Tab-Me', { screen: 'Tab-Me-Switch' })
|
||||
}
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
)
|
||||
|
@ -1,5 +1,7 @@
|
||||
import axios from 'axios'
|
||||
import { GLOBAL } from '../../App'
|
||||
import { ctx, handleError, PagedResponse, parseHeaderLinks, userAgent } from './helpers'
|
||||
import { CONNECT_DOMAIN } from './helpers/connect'
|
||||
|
||||
export type Params = {
|
||||
method: 'get' | 'post' | 'put' | 'delete'
|
||||
@ -35,14 +37,15 @@ const apiGeneral = async <T = unknown>({
|
||||
return axios({
|
||||
timeout: method === 'post' ? 1000 * 60 : 1000 * 15,
|
||||
method,
|
||||
baseURL: `https://${domain}/`,
|
||||
baseURL: `https://${GLOBAL.connect ? CONNECT_DOMAIN() : domain}`,
|
||||
url,
|
||||
params,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
...userAgent,
|
||||
...headers,
|
||||
...(body && body instanceof FormData && { 'Content-Type': 'multipart/form-data' })
|
||||
...(body && body instanceof FormData && { 'Content-Type': 'multipart/form-data' }),
|
||||
...(GLOBAL.connect && { 'x-tooot-domain': domain })
|
||||
},
|
||||
data: body
|
||||
})
|
||||
|
145
src/utils/api/helpers/connect.ts
Normal file
145
src/utils/api/helpers/connect.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { mapEnvironment } from '@utils/helpers/checkEnvironment'
|
||||
import { setGlobalStorage } from '@utils/storage/actions'
|
||||
import axios from 'axios'
|
||||
import parse from 'url-parse'
|
||||
import { userAgent } from '.'
|
||||
import { GLOBAL } from '../../../App'
|
||||
|
||||
const list = [
|
||||
'n61owz4leck',
|
||||
'z9skyp2f0m',
|
||||
'nc2dqtyxevj',
|
||||
'tgl97fgudrf',
|
||||
'eo2sj0ut2s',
|
||||
'a75auwihvyi',
|
||||
'vzkpud5y5b',
|
||||
'3uivf7yyex',
|
||||
'pxfoa1wbor',
|
||||
'3cor5jempc',
|
||||
'9o32znuepr',
|
||||
'9ayt1l2dzpi',
|
||||
'60iu4rz8js',
|
||||
'dzoa1lbxbv',
|
||||
'82rpiiqw21',
|
||||
'fblij1c9gyl',
|
||||
'wk2x048g8gl',
|
||||
'9x91yrbtmn',
|
||||
'dgu5p7eif6',
|
||||
'uftwyhrkgrh',
|
||||
'vv5hay15vjk',
|
||||
'ooj9ihtyur',
|
||||
'o8r7phzd58',
|
||||
'pujwyg269s',
|
||||
'l6yq5nr8lv',
|
||||
'ocyrlfmdnl',
|
||||
'rdtpeip5e2',
|
||||
'ykzb5784js',
|
||||
'm34z7j5us1i',
|
||||
'tqsfr0orqa',
|
||||
'8ncrt0mifa',
|
||||
'ygce2fdmsm',
|
||||
'22vk7csljz',
|
||||
'7mmb6hrih1',
|
||||
'grla5cpgau',
|
||||
'0vygyvs4k7',
|
||||
'1texbe32sf',
|
||||
'ckwvauiiol',
|
||||
'qkxryrbpxx',
|
||||
'ptb19c0ks9g',
|
||||
'3bpe76o6stg',
|
||||
'd507ejce9g',
|
||||
'jpul5v2mqej',
|
||||
'6m5uxemc79',
|
||||
'wxbtoo9t3p',
|
||||
'8qco3d0idh',
|
||||
'u00c2xiabvf',
|
||||
'hutkqwrcy8',
|
||||
't6vrkzhpzo',
|
||||
'wy6e529mnb',
|
||||
'kzzrlfa59pg',
|
||||
'mmo4sv4a7s',
|
||||
'u0dishl20k',
|
||||
'8qyx25bq3u',
|
||||
'd3mucdzlu1',
|
||||
'y123m81vsjl',
|
||||
'51opvzdo6k',
|
||||
'r4z333th9u',
|
||||
'q77hl0ggfr',
|
||||
'bsk1f2wi52g',
|
||||
'eubnxpv0pz',
|
||||
'h11pk7qm8i',
|
||||
'brhxw45vd5',
|
||||
'vtnvlsrn1z',
|
||||
'0q5w0hhzb5',
|
||||
'vq2rz02ayf',
|
||||
'hml3igfwkq',
|
||||
'39qs7vhenl',
|
||||
'5vcv775rug',
|
||||
'kjom5gr7i3',
|
||||
't2kmaoeb5x',
|
||||
'ni6ow1z11b',
|
||||
'yvgtoc3d88',
|
||||
'iax04eatnz',
|
||||
'esxyu9zujg',
|
||||
'73xa28n278',
|
||||
'5x63a8l24k',
|
||||
'dy1trb0b3sj',
|
||||
'd4c31j23m8',
|
||||
'ho76046l0j',
|
||||
'sw8lj5u2ef',
|
||||
'z5cn21mew5',
|
||||
'wxj73nmqwa',
|
||||
'gdj00dlx98',
|
||||
'0v76xag64i',
|
||||
'j35104qduhj',
|
||||
'l63r7h0ss6',
|
||||
'e5xdv7t1q0h',
|
||||
'4icoh8t4c8',
|
||||
'nbk36jt4sq',
|
||||
'zi0n0cv4tk',
|
||||
'o7qkfp3rxu',
|
||||
'xd2wefzd27',
|
||||
'rg7e6tsacx',
|
||||
'9lrq3s4vfm',
|
||||
'srs9p21lxoh',
|
||||
'n8xymau42t',
|
||||
'q5cik283fg',
|
||||
'68ye9feqs5',
|
||||
'xjc5anubnv'
|
||||
]
|
||||
|
||||
export const CONNECT_DOMAIN = () =>
|
||||
mapEnvironment({
|
||||
release: `${list[Math.floor(Math.random() * (100 - 0) + 0)]}.tooot.app`,
|
||||
candidate: 'connect-candidate.tooot.app',
|
||||
development: 'connect-development.tooot.app'
|
||||
})
|
||||
|
||||
export const connectImage = ({
|
||||
uri
|
||||
}: {
|
||||
uri?: string
|
||||
}): { uri?: string; headers?: { 'x-tooot-domain': string } } => {
|
||||
if (GLOBAL.connect) {
|
||||
if (uri) {
|
||||
const host = parse(uri).host
|
||||
return { uri: uri.replace(host, CONNECT_DOMAIN()), headers: { 'x-tooot-domain': host } }
|
||||
} else {
|
||||
return { uri }
|
||||
}
|
||||
} else {
|
||||
return { uri }
|
||||
}
|
||||
}
|
||||
|
||||
export const connectVerify = () =>
|
||||
axios({
|
||||
method: 'get',
|
||||
baseURL: `https://${CONNECT_DOMAIN()}`,
|
||||
url: 'verify',
|
||||
headers: { ...userAgent }
|
||||
}).catch(err => {
|
||||
GLOBAL.connect = false
|
||||
setGlobalStorage('app.connect', false)
|
||||
return Promise.reject(err)
|
||||
})
|
@ -1,8 +1,10 @@
|
||||
import * as Sentry from '@sentry/react-native'
|
||||
import { setGlobalStorage } from '@utils/storage/actions'
|
||||
import chalk from 'chalk'
|
||||
import Constants from 'expo-constants'
|
||||
import { Platform } from 'react-native'
|
||||
import parse from 'url-parse'
|
||||
import { GLOBAL } from '../../../App'
|
||||
|
||||
const userAgent = {
|
||||
'User-Agent': `tooot/${Constants.expoConfig?.version} ${Platform.OS}/${Platform.Version}`
|
||||
@ -18,6 +20,12 @@ const handleError =
|
||||
} | void
|
||||
) =>
|
||||
(error: any) => {
|
||||
if (GLOBAL.connect) {
|
||||
if (error?.response?.status == 403 && error?.response?.data == 'connect_blocked') {
|
||||
GLOBAL.connect = false
|
||||
setGlobalStorage('app.connect', false)
|
||||
}
|
||||
}
|
||||
const shouldReportToSentry = config && (config.captureRequest || config.captureResponse)
|
||||
shouldReportToSentry && Sentry.setContext('Error object', error)
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { getAccountDetails } from '@utils/storage/actions'
|
||||
import { StorageGlobal } from '@utils/storage/global'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import { GLOBAL } from '../../App'
|
||||
import { ctx, handleError, PagedResponse, parseHeaderLinks, userAgent } from './helpers'
|
||||
import { CONNECT_DOMAIN } from './helpers/connect'
|
||||
|
||||
export type Params = {
|
||||
account?: StorageGlobal['account.active']
|
||||
@ -43,11 +45,13 @@ const apiInstance = async <T = unknown>({
|
||||
method + ctx.blue(' -> ') + `/${url}` + (params ? ctx.blue(' -> ') : ''),
|
||||
params ? params : ''
|
||||
)
|
||||
console.log('body', body)
|
||||
|
||||
return axios({
|
||||
timeout: method === 'post' ? 1000 * 60 : 1000 * 15,
|
||||
method,
|
||||
baseURL: `https://${accountDetails['auth.domain']}/api/${version}/`,
|
||||
baseURL: `https://${
|
||||
GLOBAL.connect ? CONNECT_DOMAIN() : accountDetails['auth.domain']
|
||||
}/api/${version}`,
|
||||
url,
|
||||
params,
|
||||
headers: {
|
||||
@ -55,7 +59,8 @@ const apiInstance = async <T = unknown>({
|
||||
...userAgent,
|
||||
...headers,
|
||||
Authorization: `Bearer ${accountDetails['auth.token']}`,
|
||||
...(body && body instanceof FormData && { 'Content-Type': 'multipart/form-data' })
|
||||
...(body && body instanceof FormData && { 'Content-Type': 'multipart/form-data' }),
|
||||
...(GLOBAL.connect && { 'x-tooot-domain': accountDetails['auth.domain'] })
|
||||
},
|
||||
data: body,
|
||||
...extras
|
||||
|
23
src/utils/helpers/appendRemote.ts
Normal file
23
src/utils/helpers/appendRemote.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// Central place appending _remote internal prop
|
||||
|
||||
export const appendRemote = {
|
||||
status: (status: Mastodon.Status) => ({
|
||||
...status,
|
||||
...(status.reblog && {
|
||||
reblog: {
|
||||
...status.reblog,
|
||||
account: appendRemote.account(status.reblog.account),
|
||||
mentions: appendRemote.mentions(status.reblog.mentions)
|
||||
}
|
||||
}),
|
||||
account: appendRemote.account(status.account),
|
||||
mentions: appendRemote.mentions(status.mentions),
|
||||
_remote: true
|
||||
}),
|
||||
account: (account: Mastodon.Account) => ({
|
||||
...account,
|
||||
_remote: true
|
||||
}),
|
||||
mentions: (mentions: Mastodon.Mention[]) =>
|
||||
mentions?.map(mention => ({ ...mention, _remote: true }))
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import parse from 'url-parse'
|
||||
|
||||
// Would mess with the /@username format
|
||||
const BLACK_LIST = ['matters.news', 'medium.com']
|
||||
|
||||
export const urlMatcher = (
|
||||
url: string
|
||||
):
|
||||
@ -14,6 +17,10 @@ export const urlMatcher = (
|
||||
if (!parsed.hostname.length || !parsed.pathname.length) return undefined
|
||||
|
||||
const domain = parsed.hostname
|
||||
if (BLACK_LIST.includes(domain)) {
|
||||
return
|
||||
}
|
||||
|
||||
const _remote = parsed.hostname !== getAccountStorage.string('auth.domain')
|
||||
|
||||
let statusId: string | undefined
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { QueryFunctionContext, useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import apiGeneral from '@utils/api/general'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { appendRemote } from '@utils/helpers/appendRemote'
|
||||
import { urlMatcher } from '@utils/helpers/urlMatcher'
|
||||
import { AxiosError } from 'axios'
|
||||
import { searchLocalAccount } from './search'
|
||||
@ -34,14 +35,14 @@ const accountQueryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeyA
|
||||
method: 'get',
|
||||
domain: domain,
|
||||
url: `api/v1/accounts/${id}`
|
||||
}).then(res => ({ ...res.body, _remote: true }))
|
||||
}).then(res => appendRemote.account(res.body))
|
||||
} else if (acct) {
|
||||
matchedAccount = await apiGeneral<Mastodon.Account>({
|
||||
method: 'get',
|
||||
domain: domain,
|
||||
url: 'api/v1/accounts/lookup',
|
||||
params: { acct }
|
||||
}).then(res => ({ ...res.body, _remote: true }))
|
||||
}).then(res => appendRemote.account(res.body))
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { QueryFunctionContext, useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import apiGeneral from '@utils/api/general'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { appendRemote } from '@utils/helpers/appendRemote'
|
||||
import { urlMatcher } from '@utils/helpers/urlMatcher'
|
||||
import { AxiosError } from 'axios'
|
||||
import { searchLocalStatus } from './search'
|
||||
@ -26,7 +27,7 @@ const queryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeyStatus>)
|
||||
method: 'get',
|
||||
domain,
|
||||
url: `api/v1/statuses/${id}`
|
||||
}).then(res => ({ ...res.body, _remote: true }))
|
||||
}).then(res => appendRemote.status(res.body))
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
import apiGeneral from '@utils/api/general'
|
||||
import { PagedResponse } from '@utils/api/helpers'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { appendRemote } from '@utils/helpers/appendRemote'
|
||||
import { urlMatcher } from '@utils/helpers/urlMatcher'
|
||||
import { TabSharedStackParamList } from '@utils/navigation/navigators'
|
||||
import { AxiosError } from 'axios'
|
||||
@ -54,7 +55,11 @@ const queryFunction = async ({ queryKey, pageParam }: QueryFunctionContext<Query
|
||||
url: `api/v1/accounts/${resLookup.body.id}/${page.type}`,
|
||||
params
|
||||
})
|
||||
return { ...res, remoteData: true }
|
||||
return {
|
||||
...res,
|
||||
body: res.body.map(account => appendRemote.account(account)),
|
||||
remoteData: true
|
||||
}
|
||||
} else {
|
||||
throw new Error()
|
||||
}
|
||||
|
@ -323,27 +323,30 @@ export const removeAccount = async (account: string, warning: boolean = true) =>
|
||||
}
|
||||
|
||||
export type ReadableAccountType = {
|
||||
avatar_static: string
|
||||
acct: string
|
||||
key: string
|
||||
active: boolean
|
||||
}
|
||||
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))
|
||||
!withoutActive &&
|
||||
accounts?.splice(
|
||||
accounts.findIndex(a => a === accountActive),
|
||||
1
|
||||
)
|
||||
!withoutActive && accounts?.unshift(accountActive || '')
|
||||
export const getReadableAccounts = (): ReadableAccountType[] => {
|
||||
const accountActive = getGlobalStorage.string('account.active')
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
|
||||
return (
|
||||
accounts?.map(account => {
|
||||
const details = getAccountDetails(
|
||||
['auth.account.acct', 'auth.account.domain', 'auth.domain', 'auth.account.id'],
|
||||
[
|
||||
'auth.account.avatar_static',
|
||||
'auth.account.acct',
|
||||
'auth.account.domain',
|
||||
'auth.domain',
|
||||
'auth.account.id'
|
||||
],
|
||||
account
|
||||
)
|
||||
if (details) {
|
||||
return {
|
||||
avatar_static: details['auth.account.avatar_static'],
|
||||
acct: `@${details['auth.account.acct']}@${details['auth.account.domain']}`,
|
||||
key: generateAccountKey({
|
||||
domain: details['auth.domain'],
|
||||
@ -352,7 +355,7 @@ export const getReadableAccounts = (withoutActive: boolean = false): ReadableAcc
|
||||
active: account === accountActive
|
||||
}
|
||||
} else {
|
||||
return { acct: '', key: '', active: false }
|
||||
return { avatar_static: '', acct: '', key: '', active: false }
|
||||
}
|
||||
}) || []
|
||||
).filter(a => a.acct.length)
|
||||
|
@ -17,6 +17,7 @@ export type GlobalV0 = {
|
||||
'version.account': number
|
||||
// boolean
|
||||
'app.auto_play_gifv'?: boolean
|
||||
'app.connect'?: boolean
|
||||
|
||||
//// account
|
||||
// string
|
||||
|
Loading…
x
Reference in New Issue
Block a user