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:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tooot",
|
"name": "tooot",
|
||||||
"version": "4.8.2",
|
"version": "4.8.3",
|
||||||
"description": "tooot for Mastodon",
|
"description": "tooot for Mastodon",
|
||||||
"author": "xmflsct <me@xmflsct.com>",
|
"author": "xmflsct <me@xmflsct.com>",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
@@ -70,7 +70,6 @@
|
|||||||
"react-i18next": "^12.1.4",
|
"react-i18next": "^12.1.4",
|
||||||
"react-intl": "^6.2.5",
|
"react-intl": "^6.2.5",
|
||||||
"react-native": "^0.70.6",
|
"react-native": "^0.70.6",
|
||||||
"react-native-animated-spinkit": "^1.5.2",
|
|
||||||
"react-native-blurhash": "^1.1.10",
|
"react-native-blurhash": "^1.1.10",
|
||||||
"react-native-fast-image": "^8.6.3",
|
"react-native-fast-image": "^8.6.3",
|
||||||
"react-native-feather": "^1.1.2",
|
"react-native-feather": "^1.1.2",
|
||||||
|
@@ -10,7 +10,6 @@ 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 timezone from '@utils/startup/timezone'
|
|
||||||
import { storage } from '@utils/storage'
|
import { storage } from '@utils/storage'
|
||||||
import {
|
import {
|
||||||
getGlobalStorage,
|
getGlobalStorage,
|
||||||
@@ -39,7 +38,6 @@ dev()
|
|||||||
sentry()
|
sentry()
|
||||||
audio()
|
audio()
|
||||||
push()
|
push()
|
||||||
timezone()
|
|
||||||
enableFreeze(true)
|
enableFreeze(true)
|
||||||
|
|
||||||
log('log', 'App', 'delay splash')
|
log('log', 'App', 'delay splash')
|
||||||
|
@@ -3,7 +3,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { AccessibilityProps, Pressable, StyleProp, View, ViewStyle } from 'react-native'
|
import { AccessibilityProps, Pressable, StyleProp, View, ViewStyle } from 'react-native'
|
||||||
import { Flow } from 'react-native-animated-spinkit'
|
import { Loading } from './Loading'
|
||||||
import CustomText from './Text'
|
import CustomText from './Text'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -53,7 +53,7 @@ const Button: React.FC<Props> = ({
|
|||||||
const loadingSpinkit = () =>
|
const loadingSpinkit = () =>
|
||||||
loading ? (
|
loading ? (
|
||||||
<View style={{ position: 'absolute' }}>
|
<View style={{ position: 'absolute' }}>
|
||||||
<Flow size={StyleConstants.Font.Size[size]} color={colors.secondary} />
|
<Loading />
|
||||||
</View>
|
</View>
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
import { Loading } from '@components/Loading'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { AccessibilityProps, Pressable, View } from 'react-native'
|
import { AccessibilityProps, Pressable, View } from 'react-native'
|
||||||
import { Flow } from 'react-native-animated-spinkit'
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
accessibilityLabel?: string
|
accessibilityLabel?: string
|
||||||
@@ -43,7 +43,7 @@ const HeaderRight: React.FC<Props> = ({
|
|||||||
const loadingSpinkit = () =>
|
const loadingSpinkit = () =>
|
||||||
loading ? (
|
loading ? (
|
||||||
<View style={{ position: 'absolute' }}>
|
<View style={{ position: 'absolute' }}>
|
||||||
<Flow size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} />
|
<Loading />
|
||||||
</View>
|
</View>
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
|
12
src/components/Loading.tsx
Normal file
12
src/components/Loading.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import { ActivityIndicator, ViewProps } from 'react-native'
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
size?: 'small' | 'large'
|
||||||
|
} & ViewProps
|
||||||
|
|
||||||
|
export const Loading: React.FC<Props> = ({ size = 'small', ...rest }) => {
|
||||||
|
const { colors } = useTheme()
|
||||||
|
|
||||||
|
return <ActivityIndicator size={size} color={colors.secondary} {...rest} />
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
import { Loading } from '@components/Loading'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
@@ -6,7 +7,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||||||
import { ColorDefinitions } from '@utils/styles/themes'
|
import { ColorDefinitions } from '@utils/styles/themes'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import { Flow } from 'react-native-animated-spinkit'
|
|
||||||
import { State, Switch, TapGestureHandler } from 'react-native-gesture-handler'
|
import { State, Switch, TapGestureHandler } from 'react-native-gesture-handler'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -150,7 +150,7 @@ const MenuRow: React.FC<Props> = ({
|
|||||||
) : null}
|
) : null}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<View style={{ position: 'absolute' }}>
|
<View style={{ position: 'absolute' }}>
|
||||||
<Flow size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} />
|
<Loading />
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
@@ -36,6 +36,7 @@ export interface Props {
|
|||||||
disableDetails?: boolean
|
disableDetails?: boolean
|
||||||
disableOnPress?: boolean
|
disableOnPress?: boolean
|
||||||
isConversation?: boolean
|
isConversation?: boolean
|
||||||
|
noBackground?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the poll is long
|
// When the poll is long
|
||||||
@@ -45,7 +46,8 @@ const TimelineDefault: React.FC<Props> = ({
|
|||||||
highlighted = false,
|
highlighted = false,
|
||||||
disableDetails = false,
|
disableDetails = false,
|
||||||
disableOnPress = false,
|
disableOnPress = false,
|
||||||
isConversation = false
|
isConversation = false,
|
||||||
|
noBackground = false
|
||||||
}) => {
|
}) => {
|
||||||
const status = item.reblog ? item.reblog : item
|
const status = item.reblog ? item.reblog : item
|
||||||
const rawContent = useRef<string[]>([])
|
const rawContent = useRef<string[]>([])
|
||||||
@@ -77,7 +79,7 @@ const TimelineDefault: React.FC<Props> = ({
|
|||||||
padding: disableDetails
|
padding: disableDetails
|
||||||
? StyleConstants.Spacing.Global.PagePadding / 1.5
|
? StyleConstants.Spacing.Global.PagePadding / 1.5
|
||||||
: StyleConstants.Spacing.Global.PagePadding,
|
: StyleConstants.Spacing.Global.PagePadding,
|
||||||
backgroundColor: colors.backgroundDefault,
|
backgroundColor: noBackground ? undefined : colors.backgroundDefault,
|
||||||
paddingBottom: disableDetails ? StyleConstants.Spacing.Global.PagePadding / 1.5 : 0
|
paddingBottom: disableDetails ? StyleConstants.Spacing.Global.PagePadding / 1.5 : 0
|
||||||
}
|
}
|
||||||
const main = () => (
|
const main = () => (
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
import { Loading } from '@components/Loading'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
@@ -7,7 +8,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
queryKey: QueryKeyTimeline
|
queryKey: QueryKeyTimeline
|
||||||
@@ -25,7 +25,7 @@ const TimelineEmpty: React.FC<Props> = ({ queryKey }) => {
|
|||||||
const children = () => {
|
const children = () => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'loading':
|
case 'loading':
|
||||||
return <Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
|
return <Loading />
|
||||||
case 'error':
|
case 'error':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
import { Loading } from '@components/Loading'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
@@ -6,7 +7,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Trans } from 'react-i18next'
|
import { Trans } from 'react-i18next'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
queryKey: QueryKeyTimeline
|
queryKey: QueryKeyTimeline
|
||||||
@@ -31,7 +31,7 @@ const TimelineFooter: React.FC<Props> = ({ queryKey, disableInfinity }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!disableInfinity && hasNextPage ? (
|
{!disableInfinity && hasNextPage ? (
|
||||||
<Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
|
<Loading />
|
||||||
) : (
|
) : (
|
||||||
<CustomText fontStyle='S' style={{ color: colors.secondary }}>
|
<CustomText fontStyle='S' style={{ color: colors.secondary }}>
|
||||||
<Trans
|
<Trans
|
||||||
|
@@ -64,6 +64,9 @@ const TimelineCard: React.FC = () => {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
if (status.media_attachments.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
if ((!status.card?.image || !status.card.title) && !status.card?.description) {
|
if ((!status.card?.image || !status.card.title) && !status.card?.description) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { Loading } from '@components/Loading'
|
||||||
import { ParseHTML } from '@components/Parse'
|
import { ParseHTML } from '@components/Parse'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import detectLanguage from '@utils/helpers/detectLanguage'
|
import detectLanguage from '@utils/helpers/detectLanguage'
|
||||||
@@ -9,7 +10,6 @@ import * as Localization from 'expo-localization'
|
|||||||
import React, { useContext, useEffect, useState } from 'react'
|
import React, { useContext, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Platform, Pressable } from 'react-native'
|
import { Platform, Pressable } from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
|
||||||
import StatusContext from './Context'
|
import StatusContext from './Context'
|
||||||
|
|
||||||
const TimelineTranslate = () => {
|
const TimelineTranslate = () => {
|
||||||
@@ -111,13 +111,7 @@ const TimelineTranslate = () => {
|
|||||||
})
|
})
|
||||||
: t('componentTimeline:shared.translate.default')}
|
: t('componentTimeline:shared.translate.default')}
|
||||||
</CustomText>
|
</CustomText>
|
||||||
{isFetching ? (
|
{isFetching ? <Loading style={{ marginLeft: StyleConstants.Spacing.S }} /> : null}
|
||||||
<Circle
|
|
||||||
size={StyleConstants.Font.Size.M}
|
|
||||||
color={colors.disabled}
|
|
||||||
style={{ marginLeft: StyleConstants.Spacing.S }}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</Pressable>
|
</Pressable>
|
||||||
{devView()}
|
{devView()}
|
||||||
{data && data.error === undefined
|
{data && data.error === undefined
|
||||||
|
@@ -9,13 +9,7 @@ import { getGlobalStorage } from '@utils/storage/actions'
|
|||||||
import * as Linking from 'expo-linking'
|
import * as Linking from 'expo-linking'
|
||||||
import * as WebBrowser from 'expo-web-browser'
|
import * as WebBrowser from 'expo-web-browser'
|
||||||
|
|
||||||
export let loadingLink = false
|
|
||||||
|
|
||||||
const openLink = async (url: string, navigation?: any) => {
|
const openLink = async (url: string, navigation?: any) => {
|
||||||
if (loadingLink) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleNavigation = (page: 'Tab-Shared-Toot' | 'Tab-Shared-Account', options: any) => {
|
const handleNavigation = (page: 'Tab-Shared-Toot' | 'Tab-Shared-Account', options: any) => {
|
||||||
if (navigation) {
|
if (navigation) {
|
||||||
navigation.push(page, options)
|
navigation.push(page, options)
|
||||||
@@ -28,7 +22,6 @@ const openLink = async (url: string, navigation?: any) => {
|
|||||||
const match = urlMatcher(url)
|
const match = urlMatcher(url)
|
||||||
// If a tooot can be found
|
// If a tooot can be found
|
||||||
if (match?.status?.id) {
|
if (match?.status?.id) {
|
||||||
loadingLink = true
|
|
||||||
let response: Mastodon.Status | undefined = undefined
|
let response: Mastodon.Status | undefined = undefined
|
||||||
|
|
||||||
const queryKey: QueryKeyStatus = [
|
const queryKey: QueryKeyStatus = [
|
||||||
@@ -39,15 +32,13 @@ const openLink = async (url: string, navigation?: any) => {
|
|||||||
|
|
||||||
if (cache) {
|
if (cache) {
|
||||||
handleNavigation('Tab-Shared-Toot', { toot: cache })
|
handleNavigation('Tab-Shared-Toot', { toot: cache })
|
||||||
loadingLink = false
|
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
response = await searchLocalStatus(url)
|
response = await searchLocalStatus(url, true)
|
||||||
} catch {}
|
} catch {}
|
||||||
if (response) {
|
if (response) {
|
||||||
handleNavigation('Tab-Shared-Toot', { toot: response })
|
handleNavigation('Tab-Shared-Toot', { toot: response })
|
||||||
loadingLink = false
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +51,6 @@ const openLink = async (url: string, navigation?: any) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingLink = true
|
|
||||||
let response: Mastodon.Account | undefined = undefined
|
let response: Mastodon.Account | undefined = undefined
|
||||||
|
|
||||||
const queryKey: QueryKeyAccount = [
|
const queryKey: QueryKeyAccount = [
|
||||||
@@ -71,21 +61,18 @@ const openLink = async (url: string, navigation?: any) => {
|
|||||||
|
|
||||||
if (cache) {
|
if (cache) {
|
||||||
handleNavigation('Tab-Shared-Account', { account: cache })
|
handleNavigation('Tab-Shared-Account', { account: cache })
|
||||||
loadingLink = false
|
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
response = await searchLocalAccount(url)
|
response = await searchLocalAccount(url, true)
|
||||||
} catch {}
|
} catch {}
|
||||||
if (response) {
|
if (response) {
|
||||||
handleNavigation('Tab-Shared-Account', { account: response })
|
handleNavigation('Tab-Shared-Account', { account: response })
|
||||||
loadingLink = false
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingLink = false
|
|
||||||
switch (getGlobalStorage.string('app.browser')) {
|
switch (getGlobalStorage.string('app.browser')) {
|
||||||
// Some links might end with an empty space at the end that triggers an error
|
// Some links might end with an empty space at the end that triggers an error
|
||||||
case 'internal':
|
case 'internal':
|
||||||
|
@@ -373,7 +373,8 @@
|
|||||||
"hashtags": "Hashtag",
|
"hashtags": "Hashtag",
|
||||||
"statuses": "Toot"
|
"statuses": "Toot"
|
||||||
},
|
},
|
||||||
"notFound": "Cannot find <bold>{{searchTerm}}</bold> related {{type}}"
|
"notFound": "Cannot find <bold>{{searchTerm}}</bold> related {{type}}",
|
||||||
|
"noResult": "Cannot find anything, please try a different term"
|
||||||
},
|
},
|
||||||
"toot": {
|
"toot": {
|
||||||
"name": "Discussions",
|
"name": "Discussions",
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import * as Localization from 'expo-localization'
|
||||||
import i18n from 'i18next'
|
import i18n from 'i18next'
|
||||||
import { initReactI18next } from 'react-i18next'
|
import { initReactI18next } from 'react-i18next'
|
||||||
|
|
||||||
@@ -128,4 +129,12 @@ i18n.use(initReactI18next).init({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const timezone = Localization.getCalendars()[0].timeZone
|
||||||
|
if (timezone && '__setDefaultTimeZone' in Intl.DateTimeFormat) {
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
Intl.DateTimeFormat.__setDefaultTimeZone(timezone)
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
export default i18n
|
export default i18n
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
|
import { Loading } from '@components/Loading'
|
||||||
import { ParseHTML } from '@components/Parse'
|
import { ParseHTML } from '@components/Parse'
|
||||||
import RelativeTime from '@components/RelativeTime'
|
import RelativeTime from '@components/RelativeTime'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
@@ -20,7 +21,6 @@ import {
|
|||||||
StyleSheet,
|
StyleSheet,
|
||||||
View
|
View
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
|
||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
import { FlatList, ScrollView } from 'react-native-gesture-handler'
|
import { FlatList, ScrollView } from 'react-native-gesture-handler'
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
@@ -191,14 +191,8 @@ const ScreenAnnouncements: React.FC<RootStackScreenProps<'Screen-Announcements'>
|
|||||||
|
|
||||||
const ListEmptyComponent = () => {
|
const ListEmptyComponent = () => {
|
||||||
return (
|
return (
|
||||||
<View
|
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||||
style={{
|
<Loading />
|
||||||
width: Dimensions.get('window').width,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
import { Loading } from '@components/Loading'
|
||||||
import { MAX_MEDIA_ATTACHMENTS } from '@components/mediaSelector'
|
import { MAX_MEDIA_ATTACHMENTS } from '@components/mediaSelector'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||||
@@ -11,7 +12,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||||||
import React, { RefObject, useContext, useEffect, useRef } from 'react'
|
import React, { RefObject, useContext, useEffect, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { FlatList, Pressable, StyleSheet, View } from 'react-native'
|
import { FlatList, Pressable, StyleSheet, View } from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
|
||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
import ComposeContext from '../../utils/createContext'
|
import ComposeContext from '../../utils/createContext'
|
||||||
import { ExtendedAttachment } from '../../utils/types'
|
import { ExtendedAttachment } from '../../utils/types'
|
||||||
@@ -135,7 +135,7 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
|||||||
backgroundColor: colors.backgroundOverlayInvert
|
backgroundColor: colors.backgroundOverlayInvert
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Circle size={StyleConstants.Font.Size.L} color={colors.primaryOverlay} />
|
<Loading />
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View
|
<View
|
||||||
|
@@ -2,13 +2,13 @@ import ComponentAccount from '@components/Account'
|
|||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import ComponentHashtag from '@components/Hashtag'
|
import ComponentHashtag from '@components/Hashtag'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
import { Loading } from '@components/Loading'
|
||||||
import ComponentSeparator from '@components/Separator'
|
import ComponentSeparator from '@components/Separator'
|
||||||
import { useSearchQuery } from '@utils/queryHooks/search'
|
import { useSearchQuery } from '@utils/queryHooks/search'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { Fragment, useContext, useEffect } from 'react'
|
import React, { Fragment, useContext, useEffect } from 'react'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
|
||||||
import ComposeContext from '../utils/createContext'
|
import ComposeContext from '../utils/createContext'
|
||||||
import { formatText } from '../utils/processText'
|
import { formatText } from '../utils/processText'
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ const ComposeRootSuggestions: React.FC = () => {
|
|||||||
const { isFetching, data, refetch } = useSearchQuery({
|
const { isFetching, data, refetch } = useSearchQuery({
|
||||||
type: mapSchemaToType(),
|
type: mapSchemaToType(),
|
||||||
term: composeState.tag?.raw.substring(1),
|
term: composeState.tag?.raw.substring(1),
|
||||||
limit: 10,
|
...(mapSchemaToType() === 'accounts' && { following: true }),
|
||||||
options: { enabled: false }
|
options: { enabled: false }
|
||||||
})
|
})
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -125,7 +125,7 @@ const ComposeRootSuggestions: React.FC = () => {
|
|||||||
key='listEmpty'
|
key='listEmpty'
|
||||||
style={{ flex: 1, alignItems: 'center', marginVertical: StyleConstants.Spacing.M }}
|
style={{ flex: 1, alignItems: 'center', marginVertical: StyleConstants.Spacing.M }}
|
||||||
>
|
>
|
||||||
<Circle size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} />
|
<Loading />
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<>{main()}</>
|
<>{main()}</>
|
||||||
|
@@ -4,7 +4,7 @@ import { CameraRoll } from '@react-native-camera-roll/camera-roll'
|
|||||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||||
import * as FileSystem from 'expo-file-system'
|
import * as FileSystem from 'expo-file-system'
|
||||||
import i18next from 'i18next'
|
import i18next from 'i18next'
|
||||||
import { PermissionsAndroid, Platform } from 'react-native'
|
import { Linking, PermissionsAndroid, Platform } from 'react-native'
|
||||||
|
|
||||||
type CommonProps = {
|
type CommonProps = {
|
||||||
image: RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
image: RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||||
@@ -19,7 +19,11 @@ const saveIos = async ({ image }: CommonProps) => {
|
|||||||
message: i18next.t('screenImageViewer:content.save.succeed')
|
message: i18next.t('screenImageViewer:content.save.succeed')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(err => {
|
||||||
|
if (err?.code === 'E_PHOTO_LIBRARY_AUTH_DENIED') {
|
||||||
|
Linking.openSettings()
|
||||||
|
return
|
||||||
|
}
|
||||||
if (image.remote_url) {
|
if (image.remote_url) {
|
||||||
CameraRoll.save(image.remote_url)
|
CameraRoll.save(image.remote_url)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import ComponentHashtag from '@components/Hashtag'
|
import ComponentHashtag from '@components/Hashtag'
|
||||||
|
import { Loading } from '@components/Loading'
|
||||||
import ComponentSeparator from '@components/Separator'
|
import ComponentSeparator from '@components/Separator'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import { useTrendsQuery } from '@utils/queryHooks/trends'
|
import { useTrendsQuery } from '@utils/queryHooks/trends'
|
||||||
@@ -6,26 +7,37 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, TextInput, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
isFetching: boolean
|
isFetching: boolean
|
||||||
inputRef: React.RefObject<TextInput>
|
searchTerm: string
|
||||||
setSearchTerm: React.Dispatch<React.SetStateAction<string>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchEmpty: React.FC<Props> = ({ isFetching, inputRef, setSearchTerm }) => {
|
const SearchEmpty: React.FC<Props> = ({ isFetching, searchTerm }) => {
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const { t } = useTranslation('screenTabs')
|
const { t } = useTranslation('screenTabs')
|
||||||
|
|
||||||
const trendsTags = useTrendsQuery({ type: 'tags' })
|
const trendsTags = useTrendsQuery({ type: 'tags' })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ paddingVertical: StyleConstants.Spacing.Global.PagePadding }}>
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minHeight: '100%',
|
||||||
|
paddingVertical: StyleConstants.Spacing.Global.PagePadding
|
||||||
|
}}
|
||||||
|
>
|
||||||
{isFetching ? (
|
{isFetching ? (
|
||||||
<View style={{ flex: 1, alignItems: 'center' }}>
|
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<Circle size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} />
|
<Loading />
|
||||||
|
</View>
|
||||||
|
) : searchTerm.length ? (
|
||||||
|
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||||
|
<CustomText
|
||||||
|
style={{ color: colors.primaryDefault }}
|
||||||
|
children={t('shared.search.noResult')}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@@ -131,7 +131,6 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
>
|
>
|
||||||
<SectionList
|
<SectionList
|
||||||
style={{ minHeight: '100%' }}
|
|
||||||
sections={data || []}
|
sections={data || []}
|
||||||
renderItem={({ item, section }: { item: any; section: any }) => {
|
renderItem={({ item, section }: { item: any; section: any }) => {
|
||||||
switch (section.title) {
|
switch (section.title) {
|
||||||
@@ -146,9 +145,7 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
stickySectionHeadersEnabled
|
stickySectionHeadersEnabled
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={<SearchEmpty isFetching={isFetching} searchTerm={searchTerm} />}
|
||||||
<SearchEmpty isFetching={isFetching} inputRef={inputRef} setSearchTerm={setSearchTerm} />
|
|
||||||
}
|
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
renderSectionHeader={({ section: { translation } }) => (
|
renderSectionHeader={({ section: { translation } }) => (
|
||||||
<View
|
<View
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { HeaderLeft } from '@components/Header'
|
import { HeaderLeft } from '@components/Header'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
import { Loading } from '@components/Loading'
|
||||||
import ComponentSeparator from '@components/Separator'
|
import ComponentSeparator from '@components/Separator'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import TimelineDefault from '@components/Timeline/Default'
|
import TimelineDefault from '@components/Timeline/Default'
|
||||||
@@ -15,8 +16,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Alert, FlatList, Pressable, View } from 'react-native'
|
import { Alert, FlatList, Platform, Pressable, View } from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
|
||||||
import { Path, Svg } from 'react-native-svg'
|
import { Path, Svg } from 'react-native-svg'
|
||||||
|
|
||||||
const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||||
@@ -34,6 +34,8 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
remote: ['Timeline', { page: 'Toot', toot: toot.id, remote: true }]
|
remote: ['Timeline', { page: 'Toot', toot: toot.id, remote: true }]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const flRef = useRef<FlatList<Mastodon.Status & { _level?: number }>>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerTitle: () => (
|
headerTitle: () => (
|
||||||
@@ -69,12 +71,12 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
navigation.setParams({ toot, queryKey: queryKey.local })
|
navigation.setParams({ toot, queryKey: queryKey.local })
|
||||||
}, [hasRemoteContent])
|
}, [hasRemoteContent])
|
||||||
|
|
||||||
const flRef = useRef<FlatList>(null)
|
const PREV_PER_BATCH = 1
|
||||||
const scrolled = useRef(false)
|
const ancestorsCache = useRef<(Mastodon.Status & { _level?: number })[]>()
|
||||||
|
const loaded = useRef<boolean>(false)
|
||||||
|
|
||||||
const match = urlMatcher(toot.url || toot.uri)
|
const match = urlMatcher(toot.url || toot.uri)
|
||||||
const highlightIndex = useRef<number>(0)
|
const query = useQuery<{ pages: { body: (Mastodon.Status & { _level?: number })[] }[] }>(
|
||||||
const query = useQuery<{ pages: { body: Mastodon.Status[] }[] }>(
|
|
||||||
queryKey.local,
|
queryKey.local,
|
||||||
async () => {
|
async () => {
|
||||||
const context = await apiInstance<{
|
const context = await apiInstance<{
|
||||||
@@ -85,15 +87,14 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
url: `statuses/${toot.id}/context`
|
url: `statuses/${toot.id}/context`
|
||||||
}).then(res => res.body)
|
}).then(res => res.body)
|
||||||
|
|
||||||
highlightIndex.current = context.ancestors.length
|
ancestorsCache.current = [...context.ancestors]
|
||||||
|
const statuses = [{ ...toot }, ...context.descendants]
|
||||||
const statuses = [...context.ancestors, { ...toot }, ...context.descendants]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pages: [
|
pages: [
|
||||||
{
|
{
|
||||||
body: statuses.map((status, index) => {
|
body: statuses.map((status, index) => {
|
||||||
if (index < highlightIndex.current || status.id === toot.id) {
|
if (index === 0) {
|
||||||
status._level = 0
|
status._level = 0
|
||||||
return status
|
return status
|
||||||
} else {
|
} else {
|
||||||
@@ -108,7 +109,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
placeholderData: { pages: [{ body: [toot] }] },
|
placeholderData: { pages: [{ body: [{ ...toot, _level: 0 }] }] },
|
||||||
enabled: !toot._remote,
|
enabled: !toot._remote,
|
||||||
staleTime: 0,
|
staleTime: 0,
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
@@ -117,30 +118,10 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scrolled.current) {
|
|
||||||
scrolled.current = true
|
|
||||||
const pointer = data.pages[0].body.findIndex(({ id }) => id === toot.id)
|
|
||||||
if (pointer < 1) return
|
|
||||||
const length = flRef.current?.props.data?.length
|
|
||||||
if (!length) return
|
|
||||||
try {
|
|
||||||
setTimeout(() => {
|
|
||||||
try {
|
|
||||||
flRef.current?.scrollToIndex({
|
|
||||||
index: pointer,
|
|
||||||
viewOffset: 100
|
|
||||||
})
|
|
||||||
} catch {}
|
|
||||||
}, 500)
|
|
||||||
} catch (error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
useQuery<Mastodon.Status[]>(
|
const remoteQuery = useQuery<Mastodon.Status[]>(
|
||||||
queryKey.remote,
|
queryKey.remote,
|
||||||
async () => {
|
async () => {
|
||||||
const domain = match?.domain
|
const domain = match?.domain
|
||||||
@@ -165,19 +146,45 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
return Promise.resolve([{ ...toot }])
|
return Promise.resolve([{ ...toot }])
|
||||||
}
|
}
|
||||||
|
|
||||||
highlightIndex.current = context.ancestors.length
|
ancestorsCache.current = context.ancestors.map(ancestor => {
|
||||||
|
const localMatch = ancestorsCache.current?.find(local => local.uri === ancestor.uri)
|
||||||
const statuses = [...context.ancestors, { ...toot }, ...context.descendants]
|
if (localMatch) {
|
||||||
|
return { ...localMatch, _level: 0 }
|
||||||
|
} 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
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const statuses = [{ ...toot }, ...context.descendants]
|
||||||
return statuses.map((status, index) => {
|
return statuses.map((status, index) => {
|
||||||
if (index < highlightIndex.current || status.id === toot.id) {
|
if (index === 0) {
|
||||||
status._level = 0
|
status._level = 0
|
||||||
return status
|
return status
|
||||||
}
|
} else {
|
||||||
|
const repliedLevel: number =
|
||||||
const repliedLevel: number = statuses.find(s => s.id === status.in_reply_to_id)?._level || 0
|
statuses.find(s => s.id === status.in_reply_to_id)?._level || 0
|
||||||
status._level = repliedLevel + 1
|
status._level = repliedLevel + 1
|
||||||
return status
|
return status
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -187,7 +194,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
match?.domain !== getAccountStorage.string('auth.domain'),
|
match?.domain !== getAccountStorage.string('auth.domain'),
|
||||||
staleTime: 0,
|
staleTime: 0,
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
onSuccess: 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()
|
||||||
return
|
return
|
||||||
@@ -195,9 +202,9 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
|
|
||||||
if ((query.data?.pages[0].body.length || 0) < data.length) {
|
if ((query.data?.pages[0].body.length || 0) < data.length) {
|
||||||
queryClient.cancelQueries(queryKey.local)
|
queryClient.cancelQueries(queryKey.local)
|
||||||
queryClient.setQueryData<{
|
queryClient.setQueryData<{ pages: { body: Mastodon.Status[] }[] }>(
|
||||||
pages: { body: Mastodon.Status[] }[]
|
queryKey.local,
|
||||||
}>(queryKey.local, old => {
|
old => {
|
||||||
setHasRemoteContent(true)
|
setHasRemoteContent(true)
|
||||||
return {
|
return {
|
||||||
pages: [
|
pages: [
|
||||||
@@ -229,25 +236,51 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
scrolled.current = true
|
loaded.current = true
|
||||||
const pointer = data.findIndex(({ id }) => id === toot.id)
|
|
||||||
if (pointer < 1) return
|
if (ancestorsCache.current?.length) {
|
||||||
const length = flRef.current?.props.data?.length
|
switch (Platform.OS) {
|
||||||
if (!length) return
|
case 'ios':
|
||||||
try {
|
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(() => {
|
setTimeout(() => {
|
||||||
try {
|
|
||||||
flRef.current?.scrollToIndex({
|
flRef.current?.scrollToIndex({
|
||||||
index: pointer,
|
index: ancestorsCache.current?.length || 0,
|
||||||
viewOffset: 100
|
viewOffset: 50
|
||||||
})
|
})
|
||||||
} catch {}
|
}, 50)
|
||||||
}, 500)
|
break
|
||||||
} catch (error) {
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,29 +298,18 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
data={query.data?.pages?.[0].body}
|
data={query.data?.pages?.[0].body}
|
||||||
renderItem={({ item, index }) => {
|
renderItem={({ item, index }) => {
|
||||||
const prev = query.data?.pages[0].body[index - 1]?._level || 0
|
const prev = query.data?.pages[0].body[index - 1]?._level || 0
|
||||||
const curr = item._level
|
const curr = item._level || 0
|
||||||
const next = query.data?.pages[0].body[index + 1]?._level || 0
|
const next = query.data?.pages[0].body[index + 1]?._level || 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{ paddingLeft: Math.min(curr - 1, MAX_LEVEL) * StyleConstants.Spacing.S }}
|
||||||
paddingLeft:
|
|
||||||
index > highlightIndex.current
|
|
||||||
? Math.min(item._level - 1, MAX_LEVEL) * StyleConstants.Spacing.S
|
|
||||||
: undefined
|
|
||||||
}}
|
|
||||||
onLayout={({
|
onLayout={({
|
||||||
nativeEvent: {
|
nativeEvent: {
|
||||||
layout: { height }
|
layout: { height }
|
||||||
}
|
}
|
||||||
}) => setHeights({ ...heights, [index]: height })}
|
}) => setHeights({ ...heights, [index]: height })}
|
||||||
>
|
>
|
||||||
<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'}
|
|
||||||
/>
|
|
||||||
{curr > 1 || next > 1
|
{curr > 1 || next > 1
|
||||||
? [...new Array(curr)].map((_, i) => {
|
? [...new Array(curr)].map((_, i) => {
|
||||||
if (i > MAX_LEVEL) return null
|
if (i > MAX_LEVEL) return null
|
||||||
@@ -371,6 +393,13 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
|
<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'}
|
||||||
|
noBackground
|
||||||
|
/>
|
||||||
{/* <CustomText
|
{/* <CustomText
|
||||||
children={query.data?.pages[0].body[index - 1]?._level}
|
children={query.data?.pages[0].body[index - 1]?._level}
|
||||||
style={{ position: 'absolute', top: 4, left: 4, color: colors.red }}
|
style={{ position: 'absolute', top: 4, left: 4, color: colors.red }}
|
||||||
@@ -397,7 +426,7 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
? 0
|
? 0
|
||||||
: StyleConstants.Avatar.XS +
|
: StyleConstants.Avatar.XS +
|
||||||
StyleConstants.Spacing.S +
|
StyleConstants.Spacing.S +
|
||||||
Math.min(Math.max(0, leadingItem._level - 1), MAX_LEVEL) *
|
Math.min(Math.max(0, (leadingItem._level || 0) - 1), MAX_LEVEL) *
|
||||||
StyleConstants.Spacing.S
|
StyleConstants.Spacing.S
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -416,35 +445,27 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
onScrollToIndexFailed={error => {
|
ListFooterComponent={
|
||||||
|
query.isFetching || remoteQuery.isFetching ? (
|
||||||
|
<View style={{ flex: 1, alignItems: 'center' }} children={<Loading />} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
{...(loaded.current && { maintainVisibleContentPosition: { minIndexForVisible: 0 } })}
|
||||||
|
{...(Platform.OS !== 'ios' && {
|
||||||
|
onScrollToIndexFailed: error => {
|
||||||
const offset = error.averageItemLength * error.index
|
const offset = error.averageItemLength * error.index
|
||||||
flRef.current?.scrollToOffset({ offset })
|
flRef.current?.scrollToOffset({ offset })
|
||||||
try {
|
error.index < (ancestorsCache.current?.length || 0) &&
|
||||||
error.index < (query.data?.pages[0].body.length || 0) &&
|
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() =>
|
() =>
|
||||||
flRef.current?.scrollToIndex({
|
flRef.current?.scrollToIndex({
|
||||||
index: error.index,
|
index: error.index,
|
||||||
viewOffset: 100
|
viewOffset: 50
|
||||||
}),
|
}),
|
||||||
500
|
50
|
||||||
)
|
)
|
||||||
} catch {}
|
|
||||||
}}
|
|
||||||
ListFooterComponent={
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
alignItems: 'center',
|
|
||||||
backgroundColor: colors.backgroundDefault,
|
|
||||||
marginHorizontal: StyleConstants.Spacing.M
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{query.isFetching ? (
|
|
||||||
<Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
}
|
}
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import ComponentAccount from '@components/Account'
|
import ComponentAccount from '@components/Account'
|
||||||
import { HeaderLeft } from '@components/Header'
|
import { HeaderLeft } from '@components/Header'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
import { Loading } from '@components/Loading'
|
||||||
import ComponentSeparator from '@components/Separator'
|
import ComponentSeparator from '@components/Separator'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import apiInstance from '@utils/api/instance'
|
import apiInstance from '@utils/api/instance'
|
||||||
@@ -13,7 +14,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import { Circle, Flow } from 'react-native-animated-spinkit'
|
|
||||||
import { FlatList } from 'react-native-gesture-handler'
|
import { FlatList } from 'react-native-gesture-handler'
|
||||||
|
|
||||||
const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> = ({
|
const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> = ({
|
||||||
@@ -36,7 +36,7 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> =
|
|||||||
...queryKey[1]
|
...queryKey[1]
|
||||||
})
|
})
|
||||||
|
|
||||||
const [isSearching, setIsSearching] = useState(false)
|
const [isSearching, setIsSearching] = useState<number | null>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
@@ -46,14 +46,14 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> =
|
|||||||
minHeight: '100%',
|
minHeight: '100%',
|
||||||
paddingVertical: StyleConstants.Spacing.Global.PagePadding
|
paddingVertical: StyleConstants.Spacing.Global.PagePadding
|
||||||
}}
|
}}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item, index }) => (
|
||||||
<ComponentAccount
|
<ComponentAccount
|
||||||
account={item}
|
account={item}
|
||||||
props={{
|
props={{
|
||||||
disabled: isSearching,
|
disabled: isSearching === index,
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
if (data?.pages[0]?.remoteData) {
|
if (data?.pages[0]?.remoteData) {
|
||||||
setIsSearching(true)
|
setIsSearching(index)
|
||||||
apiInstance<SearchResult>({
|
apiInstance<SearchResult>({
|
||||||
version: 'v2',
|
version: 'v2',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@@ -66,18 +66,18 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> =
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
setIsSearching(false)
|
setIsSearching(null)
|
||||||
if (res.body.accounts[0]) {
|
if (res.body.accounts[0]) {
|
||||||
navigation.push('Tab-Shared-Account', { account: res.body.accounts[0] })
|
navigation.push('Tab-Shared-Account', { account: res.body.accounts[0] })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => setIsSearching(false))
|
.catch(() => setIsSearching(null))
|
||||||
} else {
|
} else {
|
||||||
navigation.push('Tab-Shared-Account', { account: item })
|
navigation.push('Tab-Shared-Account', { account: item })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
children={<Flow size={StyleConstants.Font.Size.L} color={colors.secondary} />}
|
children={<Loading />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
onEndReached={() => hasNextPage && !isFetchingNextPage && fetchNextPage()}
|
onEndReached={() => hasNextPage && !isFetchingNextPage && fetchNextPage()}
|
||||||
@@ -93,7 +93,7 @@ const TabSharedUsers: React.FC<TabSharedStackScreenProps<'Tab-Shared-Users'>> =
|
|||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Circle size={StyleConstants.Font.Size.L} color={colors.secondary} />
|
<Loading />
|
||||||
</View>
|
</View>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ export type Params = {
|
|||||||
}
|
}
|
||||||
headers?: { [key: string]: string }
|
headers?: { [key: string]: string }
|
||||||
body?: FormData
|
body?: FormData
|
||||||
extras?: Omit<AxiosRequestConfig, 'method' | 'url' | 'params' | 'headers' | 'data'>
|
extras?: Omit<AxiosRequestConfig, 'method' | 'baseURL' | 'url' | 'params' | 'headers' | 'data'>
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiInstance = async <T = unknown>({
|
const apiInstance = async <T = unknown>({
|
||||||
|
@@ -4,7 +4,7 @@ import { getGlobalStorage, setGlobalStorage } from '@utils/storage/actions'
|
|||||||
import * as Notifications from 'expo-notifications'
|
import * as Notifications from 'expo-notifications'
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
|
|
||||||
export const updateExpoToken = async () => {
|
export const updateExpoToken = async (): Promise<string> => {
|
||||||
const expoToken = getGlobalStorage.string('app.expo_token')
|
const expoToken = getGlobalStorage.string('app.expo_token')
|
||||||
|
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
@@ -12,16 +12,19 @@ export const updateExpoToken = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (expoToken?.length) {
|
if (expoToken?.length) {
|
||||||
return Promise.resolve()
|
return Promise.resolve(expoToken)
|
||||||
} else {
|
} else {
|
||||||
if (isDevelopment) {
|
if (isDevelopment) {
|
||||||
setGlobalStorage('app.expo_token', 'ExponentPushToken[DEVELOPMENT_1]')
|
setGlobalStorage('app.expo_token', 'ExponentPushToken[DEVELOPMENT_1]')
|
||||||
return Promise.resolve()
|
return Promise.resolve('ExponentPushToken[DEVELOPMENT_1]')
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Notifications.getExpoPushTokenAsync({
|
return await Notifications.getExpoPushTokenAsync({
|
||||||
experienceId: '@xmflsct/tooot',
|
experienceId: '@xmflsct/tooot',
|
||||||
applicationId: 'com.xmflsct.app.tooot'
|
applicationId: 'com.xmflsct.app.tooot'
|
||||||
}).then(({ data }) => setGlobalStorage('app.expo_token', data))
|
}).then(({ data }) => {
|
||||||
|
setGlobalStorage('app.expo_token', data)
|
||||||
|
return data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import apiGeneral from '@utils/api/general'
|
|||||||
import apiTooot from '@utils/api/tooot'
|
import apiTooot from '@utils/api/tooot'
|
||||||
import navigationRef from '@utils/navigation/navigationRef'
|
import navigationRef from '@utils/navigation/navigationRef'
|
||||||
import {
|
import {
|
||||||
|
generateAccountKey,
|
||||||
getAccountDetails,
|
getAccountDetails,
|
||||||
getGlobalStorage,
|
getGlobalStorage,
|
||||||
setAccountStorage,
|
setAccountStorage,
|
||||||
@@ -12,7 +13,7 @@ import {
|
|||||||
} from '@utils/storage/actions'
|
} from '@utils/storage/actions'
|
||||||
import { AxiosError } from 'axios'
|
import { AxiosError } from 'axios'
|
||||||
import * as Notifications from 'expo-notifications'
|
import * as Notifications from 'expo-notifications'
|
||||||
import { useEffect } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { AppState } from 'react-native'
|
import { AppState } from 'react-native'
|
||||||
import { updateExpoToken } from './updateExpoToken'
|
import { updateExpoToken } from './updateExpoToken'
|
||||||
@@ -20,22 +21,32 @@ import { updateExpoToken } from './updateExpoToken'
|
|||||||
const pushUseConnect = () => {
|
const pushUseConnect = () => {
|
||||||
const { t } = useTranslation('screens')
|
const { t } = useTranslation('screens')
|
||||||
|
|
||||||
useEffect(() => {
|
const startupChecked = useRef<boolean>(false)
|
||||||
updateExpoToken()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const [expoToken] = useGlobalStorage.string('app.expo_token')
|
const [expoToken] = useGlobalStorage.string('app.expo_token')
|
||||||
const pushEnabledCount = getGlobalStorage.object('accounts')?.filter(account => {
|
const accounts = getGlobalStorage.object('accounts')
|
||||||
return getAccountDetails(['push'], account)?.push?.global
|
const pushEnabled = accounts
|
||||||
}).length
|
?.map(account => {
|
||||||
|
const details = getAccountDetails(['push', 'auth.domain', 'auth.account.id'], account)
|
||||||
|
if (details?.push?.global) {
|
||||||
|
return {
|
||||||
|
accountKey: generateAccountKey({
|
||||||
|
domain: details['auth.domain'],
|
||||||
|
id: details['auth.account.id']
|
||||||
|
}),
|
||||||
|
push: details.push
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(d => !!d)
|
||||||
|
|
||||||
const connectQuery = useQuery<any, AxiosError>(
|
const connectQuery = useQuery<{ accounts: string[] } | undefined, AxiosError>(
|
||||||
['tooot', { endpoint: 'push/connect' }],
|
['tooot', { endpoint: 'push/connect' }],
|
||||||
() =>
|
() =>
|
||||||
apiTooot<Mastodon.Status>({
|
apiTooot<{ accounts: string[] } | undefined>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: `push/connect/${expoToken}`
|
url: `push/connect/${expoToken}`
|
||||||
}),
|
}).then(res => res.body),
|
||||||
{
|
{
|
||||||
enabled: false,
|
enabled: false,
|
||||||
retry: (failureCount, error) => {
|
retry: (failureCount, error) => {
|
||||||
@@ -48,6 +59,17 @@ const pushUseConnect = () => {
|
|||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnReconnect: false,
|
refetchOnReconnect: false,
|
||||||
onSettled: () => Notifications.setBadgeCountAsync(0),
|
onSettled: () => Notifications.setBadgeCountAsync(0),
|
||||||
|
onSuccess: data => {
|
||||||
|
if (!startupChecked.current && data?.accounts) {
|
||||||
|
startupChecked.current = true
|
||||||
|
for (const acct of data.accounts) {
|
||||||
|
const matchedAcct = pushEnabled?.find(p => p?.accountKey === acct)
|
||||||
|
if (matchedAcct && !matchedAcct.push.global) {
|
||||||
|
setAccountStorage([{ key: 'push', value: { ...matchedAcct.push, global: true } }])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
onError: error => {
|
onError: error => {
|
||||||
if (error?.status == 404) {
|
if (error?.status == 404) {
|
||||||
displayMessage({
|
displayMessage({
|
||||||
@@ -96,18 +118,21 @@ const pushUseConnect = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Sentry.setTags({ expoToken, pushEnabledCount })
|
updateExpoToken().then(async token => {
|
||||||
|
const badgeCount = await Notifications.getBadgeCountAsync()
|
||||||
if (expoToken && pushEnabledCount) {
|
if (token && (pushEnabled?.length || badgeCount)) {
|
||||||
connectQuery.refetch()
|
connectQuery.refetch()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
Sentry.setTags({ expoToken, pushEnabledCount: pushEnabled?.length })
|
||||||
|
|
||||||
const appStateListener = AppState.addEventListener('change', state => {
|
const appStateListener = AppState.addEventListener('change', state => {
|
||||||
if (expoToken && pushEnabledCount && state === 'active') {
|
if (expoToken && pushEnabled?.length && state === 'active') {
|
||||||
Notifications.getBadgeCountAsync().then(count => {
|
Notifications.getBadgeCountAsync().then(count => {
|
||||||
if (count > 0) {
|
if (count > 0) connectQuery.refetch()
|
||||||
connectQuery.refetch()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -115,7 +140,7 @@ const pushUseConnect = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
appStateListener.remove()
|
appStateListener.remove()
|
||||||
}
|
}
|
||||||
}, [expoToken, pushEnabledCount])
|
}, [expoToken, pushEnabled?.length])
|
||||||
}
|
}
|
||||||
|
|
||||||
export default pushUseConnect
|
export default pushUseConnect
|
||||||
|
@@ -9,6 +9,7 @@ export type QueryKeySearch = [
|
|||||||
type?: 'accounts' | 'hashtags' | 'statuses'
|
type?: 'accounts' | 'hashtags' | 'statuses'
|
||||||
term?: string
|
term?: string
|
||||||
limit?: number
|
limit?: number
|
||||||
|
following?: boolean
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -18,8 +19,8 @@ export type SearchResult = {
|
|||||||
statuses: Mastodon.Status[]
|
statuses: Mastodon.Status[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeySearch>) => {
|
const queryFunction = async ({ queryKey, meta }: QueryFunctionContext<QueryKeySearch>) => {
|
||||||
const { type, term, limit = 20 } = queryKey[1]
|
const { type, term, limit = 10, following = false } = queryKey[1]
|
||||||
if (!term?.length) {
|
if (!term?.length) {
|
||||||
return Promise.reject('Empty search term')
|
return Promise.reject('Empty search term')
|
||||||
}
|
}
|
||||||
@@ -31,8 +32,10 @@ const queryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeySearch>)
|
|||||||
q: term,
|
q: term,
|
||||||
...(type && { type }),
|
...(type && { type }),
|
||||||
limit,
|
limit,
|
||||||
resolve: true
|
resolve: true,
|
||||||
}
|
following
|
||||||
|
},
|
||||||
|
...(meta && { extras: meta })
|
||||||
})
|
})
|
||||||
return res.body
|
return res.body
|
||||||
}
|
}
|
||||||
@@ -47,10 +50,18 @@ const useSearchQuery = <T = SearchResult>({
|
|||||||
return useQuery(queryKey, queryFunction, { ...options, staleTime: 3600, cacheTime: 3600 })
|
return useQuery(queryKey, queryFunction, { ...options, staleTime: 3600, cacheTime: 3600 })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const searchLocalStatus = async (uri: Mastodon.Status['uri']): Promise<Mastodon.Status> => {
|
export const searchLocalStatus = async (
|
||||||
|
uri: Mastodon.Status['uri'],
|
||||||
|
timeout: boolean = false
|
||||||
|
): Promise<Mastodon.Status> => {
|
||||||
const queryKey: QueryKeySearch = ['Search', { type: 'statuses', term: uri, limit: 1 }]
|
const queryKey: QueryKeySearch = ['Search', { type: 'statuses', term: uri, limit: 1 }]
|
||||||
return await queryClient
|
return await queryClient
|
||||||
.fetchQuery(queryKey, queryFunction, { staleTime: 3600, cacheTime: 3600 })
|
.fetchQuery(queryKey, queryFunction, {
|
||||||
|
staleTime: 3600,
|
||||||
|
cacheTime: 3600,
|
||||||
|
retry: false,
|
||||||
|
...(timeout && { meta: { timeout: 1000 } })
|
||||||
|
})
|
||||||
.then(res =>
|
.then(res =>
|
||||||
res.statuses[0]?.uri === uri || res.statuses[0]?.url === uri
|
res.statuses[0]?.uri === uri || res.statuses[0]?.url === uri
|
||||||
? res.statuses[0]
|
? res.statuses[0]
|
||||||
@@ -59,11 +70,17 @@ export const searchLocalStatus = async (uri: Mastodon.Status['uri']): Promise<Ma
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const searchLocalAccount = async (
|
export const searchLocalAccount = async (
|
||||||
url: Mastodon.Account['url']
|
url: Mastodon.Account['url'],
|
||||||
|
timeout: boolean = false
|
||||||
): Promise<Mastodon.Account> => {
|
): Promise<Mastodon.Account> => {
|
||||||
const queryKey: QueryKeySearch = ['Search', { type: 'accounts', term: url, limit: 1 }]
|
const queryKey: QueryKeySearch = ['Search', { type: 'accounts', term: url, limit: 1 }]
|
||||||
return await queryClient
|
return await queryClient
|
||||||
.fetchQuery(queryKey, queryFunction, { staleTime: 3600, cacheTime: 3600 })
|
.fetchQuery(queryKey, queryFunction, {
|
||||||
|
staleTime: 3600,
|
||||||
|
cacheTime: 3600,
|
||||||
|
retry: false,
|
||||||
|
...(timeout && { meta: { timeout: 1000 } })
|
||||||
|
})
|
||||||
.then(res => (res.accounts[0].url === url ? res.accounts[0] : Promise.reject()))
|
.then(res => (res.accounts[0].url === url ? res.accounts[0] : Promise.reject()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ import apiInstance from '@utils/api/instance'
|
|||||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||||
import { useNavState } from '@utils/navigation/navigators'
|
import { useNavState } from '@utils/navigation/navigators'
|
||||||
import { queryClient } from '@utils/queryHooks'
|
import { queryClient } from '@utils/queryHooks'
|
||||||
import { getAccountStorage } from '@utils/storage/actions'
|
import { getAccountStorage, setAccountStorage } from '@utils/storage/actions'
|
||||||
import { AxiosError } from 'axios'
|
import { AxiosError } from 'axios'
|
||||||
import { uniqBy } from 'lodash'
|
import { uniqBy } from 'lodash'
|
||||||
import { searchLocalStatus } from './search'
|
import { searchLocalStatus } from './search'
|
||||||
@@ -85,6 +85,11 @@ export const queryFunctionTimeline = async ({
|
|||||||
url: 'timelines/home',
|
url: 'timelines/home',
|
||||||
params
|
params
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
if (marker && !res.body.length) {
|
||||||
|
setAccountStorage([{ key: 'read_marker_following', value: undefined }])
|
||||||
|
return Promise.reject()
|
||||||
|
}
|
||||||
|
|
||||||
if (!page.showBoosts || !page.showReplies) {
|
if (!page.showBoosts || !page.showReplies) {
|
||||||
return {
|
return {
|
||||||
...res,
|
...res,
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
import * as Localization from 'expo-localization'
|
|
||||||
import log from './log'
|
|
||||||
|
|
||||||
const timezone = () => {
|
|
||||||
log('log', 'Timezone', Localization.getCalendars()[0].timeZone || 'unknown')
|
|
||||||
if ('__setDefaultTimeZone' in Intl.DateTimeFormat) {
|
|
||||||
try {
|
|
||||||
// @ts-ignore
|
|
||||||
Intl.DateTimeFormat.__setDefaultTimeZone(Intl.DateTimeFormat.__setDefaultTimeZone('xxx'))
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default timezone
|
|
11
yarn.lock
11
yarn.lock
@@ -9549,16 +9549,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-native-animated-spinkit@npm:^1.5.2":
|
|
||||||
version: 1.5.2
|
|
||||||
resolution: "react-native-animated-spinkit@npm:1.5.2"
|
|
||||||
peerDependencies:
|
|
||||||
react: "*"
|
|
||||||
react-native: "*"
|
|
||||||
checksum: 5d84b0958b3f9db5223d7c2af242eae45f133cf50715be56f1e266d7c04fe5ecdbd7e01b08146748b06daaf914fe8bc64f13df7faaee98bc687094ca10cae61e
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"react-native-blurhash@npm:^1.1.10":
|
"react-native-blurhash@npm:^1.1.10":
|
||||||
version: 1.1.10
|
version: 1.1.10
|
||||||
resolution: "react-native-blurhash@npm:1.1.10"
|
resolution: "react-native-blurhash@npm:1.1.10"
|
||||||
@@ -11412,7 +11402,6 @@ __metadata:
|
|||||||
react-i18next: ^12.1.4
|
react-i18next: ^12.1.4
|
||||||
react-intl: ^6.2.5
|
react-intl: ^6.2.5
|
||||||
react-native: ^0.70.6
|
react-native: ^0.70.6
|
||||||
react-native-animated-spinkit: ^1.5.2
|
|
||||||
react-native-blurhash: ^1.1.10
|
react-native-blurhash: ^1.1.10
|
||||||
react-native-clean-project: ^4.0.1
|
react-native-clean-project: ^4.0.1
|
||||||
react-native-fast-image: ^8.6.3
|
react-native-fast-image: ^8.6.3
|
||||||
|
Reference in New Issue
Block a user