mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
619 restructure local storage (#628)
* To MMKV migration working * POC migrated font size settings * Moved settings to mmkv * Fix typos * Migrated contexts slice * Migrated app slice * POC instance emoji update * Migrated drafts * Migrated simple instance properties * All migrated! * Re-structure files * Tolerant of undefined settings * Can properly logging in and out including empty state
This commit is contained in:
@ -1,19 +1,24 @@
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import initQuery from '@utils/initQuery'
|
||||
import { InstanceLatest } from '@utils/migrations/instances/migration'
|
||||
import { generateAccountKey, getAccountDetails, setAccount } from '@utils/storage/actions'
|
||||
import { StorageGlobal } from '@utils/storage/global'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import Button from './Button'
|
||||
import haptics from './haptics'
|
||||
|
||||
interface Props {
|
||||
instance: InstanceLatest
|
||||
account: NonNullable<StorageGlobal['accounts']>[number]
|
||||
selected?: boolean
|
||||
additionalActions?: () => void
|
||||
}
|
||||
|
||||
const AccountButton: React.FC<Props> = ({ instance, selected = false, additionalActions }) => {
|
||||
const AccountButton: React.FC<Props> = ({ account, selected = false, additionalActions }) => {
|
||||
const navigation = useNavigation()
|
||||
const accountDetails = getAccountDetails(
|
||||
['auth.account.acct', 'auth.domain', 'auth.account.id'],
|
||||
account
|
||||
)
|
||||
if (!accountDetails) return null
|
||||
|
||||
return (
|
||||
<Button
|
||||
@ -23,10 +28,17 @@ const AccountButton: React.FC<Props> = ({ instance, selected = false, additional
|
||||
marginBottom: StyleConstants.Spacing.M,
|
||||
marginRight: StyleConstants.Spacing.M
|
||||
}}
|
||||
content={`@${instance.account.acct}@${instance.uri}${selected ? ' ✓' : ''}`}
|
||||
content={`@${accountDetails['auth.account.acct']}@${accountDetails['auth.domain']}${
|
||||
selected ? ' ✓' : ''
|
||||
}`}
|
||||
onPress={() => {
|
||||
haptics('Light')
|
||||
initQuery({ instance })
|
||||
setAccount(
|
||||
generateAccountKey({
|
||||
domain: accountDetails['auth.domain'],
|
||||
id: accountDetails['auth.account.id']
|
||||
})
|
||||
)
|
||||
navigation.goBack()
|
||||
if (additionalActions) {
|
||||
additionalActions()
|
||||
|
@ -4,7 +4,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { Keyboard, Pressable, View } from 'react-native'
|
||||
import EmojisContext from './helpers/EmojisContext'
|
||||
import EmojisContext from './Context'
|
||||
|
||||
const EmojisButton: React.FC = () => {
|
||||
const { colors } = useTheme()
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { emojis } from '@components/Emojis'
|
||||
import Icon from '@components/Icon'
|
||||
import CustomText from '@components/Text'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { countInstanceEmoji } from '@utils/slices/instancesSlice'
|
||||
import { StorageAccount } from '@utils/storage/account'
|
||||
import { getAccountStorage, setAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -20,10 +20,9 @@ import {
|
||||
} from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import validUrl from 'valid-url'
|
||||
import EmojisContext from './helpers/EmojisContext'
|
||||
import EmojisContext from './Context'
|
||||
|
||||
const EmojisList = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
const { reduceMotionEnabled } = useAccessibility()
|
||||
const { t } = useTranslation(['common', 'screenCompose'])
|
||||
|
||||
@ -75,7 +74,59 @@ const EmojisList = () => {
|
||||
key={emoji.shortcode}
|
||||
onPress={() => {
|
||||
addEmoji(`:${emoji.shortcode}:`)
|
||||
dispatch(countInstanceEmoji(emoji))
|
||||
|
||||
const HALF_LIFE = 60 * 60 * 24 * 7 // 1 week
|
||||
const calculateScore = (
|
||||
emoji: StorageAccount['emojis_frequent'][number]
|
||||
): number => {
|
||||
var seconds = (new Date().getTime() - emoji.lastUsed) / 1000
|
||||
var score = emoji.count + 1
|
||||
var order = Math.log(Math.max(score, 1)) / Math.LN10
|
||||
var sign = score > 0 ? 1 : score === 0 ? 0 : -1
|
||||
return (sign * order + seconds / HALF_LIFE) * 10
|
||||
}
|
||||
|
||||
const currentEmojis = getAccountStorage.object('emojis_frequent')
|
||||
const foundEmojiIndex = currentEmojis?.findIndex(
|
||||
e => e.emoji.shortcode === emoji.shortcode && e.emoji.url === emoji.url
|
||||
)
|
||||
|
||||
let newEmojisSort: StorageAccount['emojis_frequent']
|
||||
if (foundEmojiIndex === -1) {
|
||||
newEmojisSort = currentEmojis || []
|
||||
const temp = {
|
||||
emoji,
|
||||
score: 0,
|
||||
count: 0,
|
||||
lastUsed: new Date().getTime()
|
||||
}
|
||||
newEmojisSort.push({
|
||||
...temp,
|
||||
score: calculateScore(temp),
|
||||
count: temp.count + 1
|
||||
})
|
||||
} else {
|
||||
newEmojisSort =
|
||||
currentEmojis
|
||||
?.map((e, i) =>
|
||||
i === foundEmojiIndex
|
||||
? {
|
||||
...e,
|
||||
score: calculateScore(e),
|
||||
count: e.count + 1,
|
||||
lastUsed: new Date().getTime()
|
||||
}
|
||||
: e
|
||||
)
|
||||
.sort((a, b) => b.score - a.score) || []
|
||||
}
|
||||
|
||||
setAccountStorage([
|
||||
{
|
||||
key: 'emojis_frequent',
|
||||
value: newEmojisSort.sort((a, b) => b.score - a.score).slice(0, 20)
|
||||
}
|
||||
])
|
||||
}}
|
||||
style={{ padding: StyleConstants.Spacing.S }}
|
||||
>
|
||||
|
@ -2,14 +2,13 @@ import EmojisButton from '@components/Emojis/Button'
|
||||
import EmojisList from '@components/Emojis/List'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { useEmojisQuery } from '@utils/queryHooks/emojis'
|
||||
import { getInstanceFrequentEmojis } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { chunk, forEach, groupBy, sortBy } from 'lodash'
|
||||
import React, { createRef, PropsWithChildren, useEffect, useReducer, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Keyboard, KeyboardAvoidingView, View } from 'react-native'
|
||||
import { Edge, SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useSelector } from 'react-redux'
|
||||
import EmojisContext, { Emojis, emojisReducer, EmojisState } from './Emojis/helpers/EmojisContext'
|
||||
import EmojisContext, { Emojis, emojisReducer, EmojisState } from './Context'
|
||||
|
||||
export type Props = {
|
||||
inputProps: EmojisState['inputProps']
|
||||
@ -36,7 +35,7 @@ const ComponentEmojis: React.FC<Props & PropsWithChildren> = ({
|
||||
|
||||
const { t } = useTranslation(['componentEmojis'])
|
||||
const { data } = useEmojisQuery({})
|
||||
const frequentEmojis = useSelector(getInstanceFrequentEmojis, () => true)
|
||||
const [frequentEmojis] = useAccountStorage.object('emojis_frequent')
|
||||
useEffect(() => {
|
||||
if (data && data.length) {
|
||||
let sortedEmojis: NonNullable<Emojis['current']> = []
|
@ -10,8 +10,8 @@ import {
|
||||
View,
|
||||
ViewStyle
|
||||
} from 'react-native'
|
||||
import FastImage, { ImageStyle } from 'react-native-fast-image'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import FastImage, { ImageStyle } from 'react-native-fast-image'
|
||||
|
||||
// blurhas -> if blurhash, show before any loading succeed
|
||||
// original -> load original
|
||||
|
@ -1,5 +1,5 @@
|
||||
import HeaderLeft from '@components/Header/Left'
|
||||
import HeaderCenter from '@components/Header/Center'
|
||||
import HeaderLeft from '@components/Header/Left'
|
||||
import HeaderRight from '@components/Header/Right'
|
||||
|
||||
export { HeaderLeft, HeaderCenter, HeaderRight }
|
@ -3,7 +3,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { forwardRef, RefObject } from 'react'
|
||||
import { Platform, TextInput, TextInputProps, View } from 'react-native'
|
||||
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated'
|
||||
import { EmojisState } from './Emojis/helpers/EmojisContext'
|
||||
import { EmojisState } from './Emojis/Context'
|
||||
import CustomText from './Text'
|
||||
|
||||
export type Props = {
|
||||
|
@ -1,55 +0,0 @@
|
||||
import CustomText from '@components/Text'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { View, ViewStyle } from 'react-native'
|
||||
import { PlaceholderLine } from 'rn-placeholder'
|
||||
|
||||
export interface Props {
|
||||
style?: ViewStyle
|
||||
header: string
|
||||
content?: string
|
||||
potentialWidth?: number
|
||||
}
|
||||
|
||||
const InstanceInfo: React.FC<Props> = ({ style, header, content, potentialWidth }) => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
flex: 1,
|
||||
marginTop: StyleConstants.Spacing.M,
|
||||
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
|
||||
paddingRight: StyleConstants.Spacing.Global.PagePadding
|
||||
},
|
||||
style
|
||||
]}
|
||||
accessible
|
||||
>
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{
|
||||
marginBottom: StyleConstants.Spacing.XS,
|
||||
color: colors.primaryDefault
|
||||
}}
|
||||
fontWeight='Bold'
|
||||
children={header}
|
||||
/>
|
||||
{content ? (
|
||||
<CustomText fontStyle='M' style={{ color: colors.primaryDefault }} children={content} />
|
||||
) : (
|
||||
<PlaceholderLine
|
||||
width={potentialWidth ? potentialWidth * StyleConstants.Font.Size.M : undefined}
|
||||
height={StyleConstants.Font.LineHeight.M}
|
||||
color={colors.shimmerDefault}
|
||||
noMargin
|
||||
style={{ borderRadius: 0 }}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default InstanceInfo
|
@ -1,28 +1,35 @@
|
||||
import Button from '@components/Button'
|
||||
import Icon from '@components/Icon'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import apiGeneral from '@utils/api/general'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import { TabMeStackNavigationProp } from '@utils/navigation/navigators'
|
||||
import { redirectUri, useAppsMutation } from '@utils/queryHooks/apps'
|
||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||
import { checkInstanceFeature, getInstances } from '@utils/slices/instancesSlice'
|
||||
import { StorageAccount } from '@utils/storage/account'
|
||||
import {
|
||||
generateAccountKey,
|
||||
getGlobalStorage,
|
||||
setAccountStorage,
|
||||
setGlobalStorage
|
||||
} from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as AuthSession from 'expo-auth-session'
|
||||
import * as Random from 'expo-random'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import { debounce } from 'lodash'
|
||||
import React, { RefObject, useCallback, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Alert, Image, KeyboardAvoidingView, Platform, TextInput, View } from 'react-native'
|
||||
import base64 from 'react-native-base64'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Placeholder } from 'rn-placeholder'
|
||||
import validUrl from 'valid-url'
|
||||
import InstanceInfo from './Info'
|
||||
import CustomText from '../Text'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { TabMeStackNavigationProp } from '@utils/navigation/navigators'
|
||||
import queryClient from '@helpers/queryClient'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import addInstance from '@utils/slices/instances/add'
|
||||
import { storage } from '@utils/storage'
|
||||
import { MMKV } from 'react-native-mmkv'
|
||||
|
||||
export interface Props {
|
||||
scrollViewRef?: RefObject<ScrollView>
|
||||
@ -47,8 +54,6 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
!!validUrl.isHttpsUri(`https://${domain}`) &&
|
||||
errorCode === 401
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const instances = useSelector(getInstances, () => true)
|
||||
const instanceQuery = useInstanceQuery({
|
||||
domain,
|
||||
options: {
|
||||
@ -62,7 +67,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
}
|
||||
})
|
||||
|
||||
const deprecateAuthFollow = useSelector(checkInstanceFeature('deprecate_auth_follow'))
|
||||
const deprecateAuthFollow = featureCheck('deprecate_auth_follow')
|
||||
|
||||
const appsMutation = useAppsMutation({
|
||||
retry: false,
|
||||
@ -97,14 +102,86 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
{ tokenEndpoint: `https://${variables.domain}/oauth/token` }
|
||||
)
|
||||
queryClient.clear()
|
||||
dispatch(
|
||||
addInstance({
|
||||
domain,
|
||||
token: accessToken,
|
||||
instance: instanceQuery.data!,
|
||||
appData: { clientId, clientSecret }
|
||||
})
|
||||
|
||||
const {
|
||||
body: { id, acct, avatar_static }
|
||||
} = await apiGeneral<Mastodon.Account>({
|
||||
method: 'get',
|
||||
domain,
|
||||
url: `api/v1/accounts/verify_credentials`,
|
||||
headers: { Authorization: `Bearer ${accessToken}` }
|
||||
})
|
||||
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
const accountKey = generateAccountKey({ domain, id })
|
||||
const account = accounts?.find(account => account === accountKey)
|
||||
|
||||
const accountDetails: StorageAccount = {
|
||||
'auth.clientId': clientId,
|
||||
'auth.clientSecret': clientSecret,
|
||||
'auth.token': accessToken,
|
||||
'auth.domain': domain,
|
||||
'auth.account.id': id,
|
||||
'auth.account.acct': acct,
|
||||
'auth.account.avatar_static': avatar_static,
|
||||
version: instanceQuery.data?.version || '0',
|
||||
preferences: undefined,
|
||||
notifications: {
|
||||
follow: true,
|
||||
follow_request: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true,
|
||||
poll: true,
|
||||
status: true,
|
||||
update: true,
|
||||
'admin.sign_up': true,
|
||||
'admin.report': true
|
||||
},
|
||||
push: {
|
||||
global: false,
|
||||
decode: false,
|
||||
alerts: {
|
||||
follow: true,
|
||||
follow_request: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true,
|
||||
poll: true,
|
||||
status: true,
|
||||
update: true,
|
||||
'admin.sign_up': false,
|
||||
'admin.report': false
|
||||
},
|
||||
key: base64.encodeFromByteArray(Random.getRandomBytes(16))
|
||||
},
|
||||
page_local: {
|
||||
showBoosts: true,
|
||||
showReplies: true
|
||||
},
|
||||
page_me: {
|
||||
followedTags: { shown: false },
|
||||
lists: { shown: false },
|
||||
announcements: { shown: false, unread: 0 }
|
||||
},
|
||||
drafts: [],
|
||||
emojis_frequent: []
|
||||
}
|
||||
|
||||
setAccountStorage(
|
||||
Object.keys(accountDetails).map((key: keyof StorageAccount) => ({
|
||||
key,
|
||||
value: accountDetails[key]
|
||||
})),
|
||||
accountKey
|
||||
)
|
||||
storage.account = new MMKV({ id: accountKey })
|
||||
|
||||
if (!account) {
|
||||
setGlobalStorage('accounts', accounts?.concat([accountKey]))
|
||||
}
|
||||
setGlobalStorage('account.active', accountKey)
|
||||
|
||||
goBack && navigation.goBack()
|
||||
}
|
||||
}
|
||||
@ -112,7 +189,8 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
|
||||
const processUpdate = useCallback(() => {
|
||||
if (domain) {
|
||||
if (instances && instances.filter(instance => instance.url === domain).length) {
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
if (accounts && accounts.filter(account => account.startsWith(`${domain}/`)).length) {
|
||||
Alert.alert(
|
||||
t('componentInstance:update.alert.title'),
|
||||
t('componentInstance:update.alert.message'),
|
||||
@ -208,7 +286,8 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
text === domain &&
|
||||
instanceQuery.isSuccess &&
|
||||
instanceQuery.data &&
|
||||
instanceQuery.data.uri
|
||||
// @ts-ignore
|
||||
(instanceQuery.data.domain || instanceQuery.data.uri)
|
||||
) {
|
||||
processUpdate()
|
||||
}
|
||||
@ -228,7 +307,8 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
type='text'
|
||||
content={t('componentInstance:server.button')}
|
||||
onPress={processUpdate}
|
||||
disabled={!instanceQuery.data?.uri && !whitelisted}
|
||||
// @ts-ignore
|
||||
disabled={!(instanceQuery.data?.domain || instanceQuery.data?.uri) && !whitelisted}
|
||||
loading={instanceQuery.isFetching || appsMutation.isLoading}
|
||||
/>
|
||||
</View>
|
||||
@ -245,35 +325,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
>
|
||||
{t('componentInstance:server.whitelisted')}
|
||||
</CustomText>
|
||||
) : (
|
||||
<Placeholder>
|
||||
<InstanceInfo
|
||||
header={t('componentInstance:server.information.name')}
|
||||
content={instanceQuery.data?.title || undefined}
|
||||
potentialWidth={2}
|
||||
/>
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<InstanceInfo
|
||||
style={{ alignItems: 'flex-start' }}
|
||||
header={t('componentInstance:server.information.accounts')}
|
||||
content={instanceQuery.data?.stats?.user_count?.toString() || undefined}
|
||||
potentialWidth={4}
|
||||
/>
|
||||
<InstanceInfo
|
||||
style={{ alignItems: 'center' }}
|
||||
header={t('componentInstance:server.information.statuses')}
|
||||
content={instanceQuery.data?.stats?.status_count?.toString() || undefined}
|
||||
potentialWidth={4}
|
||||
/>
|
||||
<InstanceInfo
|
||||
style={{ alignItems: 'flex-end' }}
|
||||
header={t('componentInstance:server.information.domains')}
|
||||
content={instanceQuery.data?.stats?.domain_count?.toString() || undefined}
|
||||
potentialWidth={4}
|
||||
/>
|
||||
</View>
|
||||
</Placeholder>
|
||||
)}
|
||||
) : null}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { View } from 'react-native'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
|
||||
export interface Props {
|
||||
children: React.ReactNode
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
import { View } from 'react-native'
|
||||
import CustomText from '@components/Text'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import CustomText from '@components/Text'
|
||||
import React from 'react'
|
||||
import { View } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
heading: string
|
||||
|
@ -1,4 +0,0 @@
|
||||
import ParseEmojis from './Parse/Emojis'
|
||||
import ParseHTML from './Parse/HTML'
|
||||
|
||||
export { ParseEmojis, ParseHTML }
|
@ -1,13 +1,12 @@
|
||||
import CustomText from '@components/Text'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { getSettingsFontsize } from '@utils/slices/settingsSlice'
|
||||
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 FastImage from 'react-native-fast-image'
|
||||
import { useSelector } from 'react-redux'
|
||||
import validUrl from 'valid-url'
|
||||
|
||||
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)
|
||||
@ -27,7 +26,7 @@ const ParseEmojis = React.memo(
|
||||
|
||||
const { reduceMotionEnabled } = useAccessibility()
|
||||
|
||||
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||
const [adaptiveFontsize] = useGlobalStorage.number('app.font_size')
|
||||
const adaptedFontsize = adaptiveScale(
|
||||
StyleConstants.Font.Size[size],
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
|
@ -5,7 +5,7 @@ import { useNavigation, useRoute } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { useFollowedTagsQuery } from '@utils/queryHooks/tags'
|
||||
import { getSettingsFontsize } from '@utils/slices/settingsSlice'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { adaptiveScale } from '@utils/styles/scaling'
|
||||
@ -16,7 +16,6 @@ import { isEqual } from 'lodash'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, Text, TextStyleIOS, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export interface Props {
|
||||
content: string
|
||||
@ -52,7 +51,7 @@ const ParseHTML = React.memo(
|
||||
selectable = false,
|
||||
setSpoilerExpanded
|
||||
}: Props) => {
|
||||
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||
const [adaptiveFontsize] = useGlobalStorage.number('app.font_size')
|
||||
const adaptedFontsize = adaptiveScale(
|
||||
StyleConstants.Font.Size[size],
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
|
4
src/components/Parse/index.tsx
Normal file
4
src/components/Parse/index.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
import ParseEmojis from './Emojis'
|
||||
import ParseHTML from './HTML'
|
||||
|
||||
export { ParseEmojis, ParseHTML }
|
@ -1,6 +1,7 @@
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { QueryKeyRelationship, useRelationshipMutation } from '@utils/queryHooks/relationship'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
@ -8,7 +9,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
export interface Props {
|
||||
id: Mastodon.Account['id']
|
||||
|
@ -1,20 +1,19 @@
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useRoute } from '@react-navigation/native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import {
|
||||
QueryKeyRelationship,
|
||||
useRelationshipMutation,
|
||||
useRelationshipQuery
|
||||
} from '@utils/queryHooks/relationship'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { View } from 'react-native'
|
||||
import { useRoute } from '@react-navigation/native'
|
||||
|
||||
export interface Props {
|
||||
id: Mastodon.Account['id']
|
||||
@ -24,7 +23,7 @@ const RelationshipOutgoing: React.FC<Props> = ({ id }: Props) => {
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation(['common', 'componentRelationship'])
|
||||
|
||||
const canFollowNotify = useSelector(checkInstanceFeature('account_follow_notify'))
|
||||
const canFollowNotify = featureCheck('account_follow_notify')
|
||||
|
||||
const query = useRelationshipQuery({ id })
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import TimelineActions from './Shared/Actions'
|
||||
import TimelineContent from './Shared/Content'
|
||||
import StatusContext from './Shared/Context'
|
||||
|
@ -9,17 +9,18 @@ import TimelineCard from '@components/Timeline/Shared/Card'
|
||||
import TimelineContent from '@components/Timeline/Shared/Content'
|
||||
import TimelineHeaderDefault from '@components/Timeline/Shared/HeaderDefault'
|
||||
import TimelinePoll from '@components/Timeline/Shared/Poll'
|
||||
import removeHTML from '@helpers/removeHTML'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import removeHTML from '@utils/helpers/removeHTML'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { checkInstanceFeature, getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { Fragment, useRef, useState } from 'react'
|
||||
import { Pressable, StyleProp, View, ViewStyle } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import * as ContextMenu from 'zeego/context-menu'
|
||||
import StatusContext from './Shared/Context'
|
||||
import TimelineFeedback from './Shared/Feedback'
|
||||
@ -60,14 +61,15 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
const { colors } = useTheme()
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const { data: preferences } = usePreferencesQuery()
|
||||
|
||||
const ownAccount = status.account?.id === instanceAccount?.id
|
||||
const ownAccount = status.account?.id === accountId
|
||||
const [spoilerExpanded, setSpoilerExpanded] = useState(
|
||||
instanceAccount?.preferences?.['reading:expand:spoilers'] || false
|
||||
preferences?.['reading:expand:spoilers'] || false
|
||||
)
|
||||
const spoilerHidden = status.spoiler_text?.length
|
||||
? !instanceAccount?.preferences?.['reading:expand:spoilers'] && !spoilerExpanded
|
||||
? !preferences?.['reading:expand:spoilers'] && !spoilerExpanded
|
||||
: false
|
||||
const detectedLanguage = useRef<string>(status.language || '')
|
||||
|
||||
@ -134,7 +136,7 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
if (!ownAccount) {
|
||||
let filterResults: FilteredProps['filterResults'] = []
|
||||
const [filterRevealed, setFilterRevealed] = useState(false)
|
||||
const hasFilterServerSide = useSelector(checkInstanceFeature('filter_server_side'))
|
||||
const hasFilterServerSide = featureCheck('filter_server_side')
|
||||
if (hasFilterServerSide) {
|
||||
if (status.filtered?.length) {
|
||||
filterResults = status.filtered?.map(filter => filter.filter)
|
||||
|
@ -11,14 +11,15 @@ import TimelineHeaderNotification from '@components/Timeline/Shared/HeaderNotifi
|
||||
import TimelinePoll from '@components/Timeline/Shared/Poll'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { checkInstanceFeature, getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { Fragment, useCallback, useRef, useState } from 'react'
|
||||
import React, { Fragment, useCallback, useState } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import * as ContextMenu from 'zeego/context-menu'
|
||||
import StatusContext from './Shared/Context'
|
||||
import TimelineFiltered, { FilteredProps, shouldFilter } from './Shared/Filtered'
|
||||
@ -31,7 +32,8 @@ export interface Props {
|
||||
}
|
||||
|
||||
const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const { data: preferences } = usePreferencesQuery()
|
||||
|
||||
const status = notification.status?.reblog ? notification.status.reblog : notification.status
|
||||
const account =
|
||||
@ -40,12 +42,12 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||
: notification.status
|
||||
? notification.status.account
|
||||
: notification.account
|
||||
const ownAccount = notification.account?.id === instanceAccount?.id
|
||||
const ownAccount = notification.account?.id === accountId
|
||||
const [spoilerExpanded, setSpoilerExpanded] = useState(
|
||||
instanceAccount.preferences?.['reading:expand:spoilers'] || false
|
||||
preferences?.['reading:expand:spoilers'] || false
|
||||
)
|
||||
const spoilerHidden = notification.status?.spoiler_text?.length
|
||||
? !instanceAccount.preferences?.['reading:expand:spoilers'] && !spoilerExpanded
|
||||
? !preferences?.['reading:expand:spoilers'] && !spoilerExpanded
|
||||
: false
|
||||
|
||||
const { colors } = useTheme()
|
||||
@ -117,7 +119,7 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||
if (!ownAccount) {
|
||||
let filterResults: FilteredProps['filterResults'] = []
|
||||
const [filterRevealed, setFilterRevealed] = useState(false)
|
||||
const hasFilterServerSide = useSelector(checkInstanceFeature('filter_server_side'))
|
||||
const hasFilterServerSide = featureCheck('filter_server_side')
|
||||
if (notification.status) {
|
||||
if (hasFilterServerSide) {
|
||||
if (notification.status.filtered?.length) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { InfiniteData, useQueryClient } from '@tanstack/react-query'
|
||||
import { QueryKeyTimeline, TimelineData, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -16,7 +17,6 @@ import Animated, {
|
||||
useSharedValue,
|
||||
withTiming
|
||||
} from 'react-native-reanimated'
|
||||
import { InfiniteData, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
export interface Props {
|
||||
flRef: RefObject<FlatList<any>>
|
||||
|
@ -2,24 +2,23 @@ import Icon from '@components/Icon'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import CustomText from '@components/Text'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import {
|
||||
MutationVarsTimelineUpdateStatusProperty,
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { uniqBy } from 'lodash'
|
||||
import React, { useCallback, useContext, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, StyleSheet, View } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
import StatusContext from './Context'
|
||||
|
||||
const TimelineActions: React.FC = () => {
|
||||
@ -76,12 +75,12 @@ const TimelineActions: React.FC = () => {
|
||||
}
|
||||
})
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const onPressReply = useCallback(() => {
|
||||
const accts = uniqBy(
|
||||
([status.account] as Mastodon.Account[] & Mastodon.Mention[])
|
||||
.concat(status.mentions)
|
||||
.filter(d => d?.id !== instanceAccount?.id),
|
||||
.filter(d => d?.id !== accountId),
|
||||
d => d?.id
|
||||
).map(d => d?.acct)
|
||||
navigation.navigate('Screen-Compose', {
|
||||
|
@ -7,13 +7,12 @@ import AttachmentVideo from '@components/Timeline/Shared/Attachment/Video'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import StatusContext from './Context'
|
||||
|
||||
const TimelineAttachment = () => {
|
||||
@ -28,13 +27,10 @@ const TimelineAttachment = () => {
|
||||
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
|
||||
const account = useSelector(
|
||||
getInstanceAccount,
|
||||
(prev, next) =>
|
||||
prev.preferences?.['reading:expand:media'] === next.preferences?.['reading:expand:media']
|
||||
)
|
||||
const { data: preferences } = usePreferencesQuery()
|
||||
|
||||
const defaultSensitive = () => {
|
||||
switch (account.preferences?.['reading:expand:media']) {
|
||||
switch (preferences?.['reading:expand:media']) {
|
||||
case 'show_all':
|
||||
return false
|
||||
case 'hide_all':
|
||||
|
@ -1,15 +1,14 @@
|
||||
import Button from '@components/Button'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { ResizeMode, Video, VideoFullscreenUpdate } from 'expo-av'
|
||||
import { Platform } from 'expo-modules-core'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import AttachmentAltText from './AltText'
|
||||
import { Platform } from 'expo-modules-core'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { aspectRatio } from './dimensions'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getSettingsAutoplayGifv } from '@utils/slices/settingsSlice'
|
||||
|
||||
export interface Props {
|
||||
total: number
|
||||
@ -27,7 +26,7 @@ const AttachmentVideo: React.FC<Props> = ({
|
||||
gifv = false
|
||||
}) => {
|
||||
const { reduceMotionEnabled } = useAccessibility()
|
||||
const autoplayGifv = useSelector(getSettingsAutoplayGifv)
|
||||
const [autoplayGifv] = useGlobalStorage.boolean('app.auto_play_gifv')
|
||||
|
||||
const videoPlayer = useRef<Video>(null)
|
||||
const [videoLoading, setVideoLoading] = useState(false)
|
||||
|
@ -2,8 +2,8 @@ import ComponentAccount from '@components/Account'
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import openLink from '@components/openLink'
|
||||
import CustomText from '@components/Text'
|
||||
import { matchAccount, matchStatus } from '@helpers/urlMatcher'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { matchAccount, matchStatus } from '@utils/helpers/urlMatcher'
|
||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||
import { useSearchQuery } from '@utils/queryHooks/search'
|
||||
import { useStatusQuery } from '@utils/queryHooks/status'
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import CustomText from '@components/Text'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { isRtlLang } from 'rtl-detect'
|
||||
import StatusContext from './Context'
|
||||
|
||||
@ -21,7 +20,8 @@ const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoi
|
||||
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||
|
||||
const { data: preferences } = usePreferencesQuery()
|
||||
|
||||
return (
|
||||
<View>
|
||||
@ -63,7 +63,7 @@ const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoi
|
||||
mentions={status.mentions}
|
||||
tags={status.tags}
|
||||
numberOfLines={
|
||||
instanceAccount.preferences?.['reading:expand:spoilers'] || inThread
|
||||
preferences?.['reading:expand:spoilers'] || inThread
|
||||
? notificationOwnToot
|
||||
? 2
|
||||
: 999
|
||||
|
@ -1,8 +1,8 @@
|
||||
import CustomText from '@components/Text'
|
||||
import removeHTML from '@helpers/removeHTML'
|
||||
import { store } from '@root/store'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import removeHTML from '@utils/helpers/removeHTML'
|
||||
import { QueryKeyFilters } from '@utils/queryHooks/filters'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getInstance } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
@ -75,7 +75,6 @@ export const shouldFilter = ({
|
||||
status: Pick<Mastodon.Status, 'content' | 'spoiler_text'>
|
||||
}): FilteredProps['filterResults'] | undefined => {
|
||||
const page = queryKey[1]
|
||||
const instance = getInstance(store.getState())
|
||||
|
||||
let returnFilter: FilteredProps['filterResults'] | undefined
|
||||
|
||||
@ -100,7 +99,8 @@ export const shouldFilter = ({
|
||||
break
|
||||
}
|
||||
}
|
||||
instance?.filters?.forEach(filter => {
|
||||
const queryKeyFilters: QueryKeyFilters = ['Filters']
|
||||
queryClient.getQueryData<Mastodon.Filter<'v1'>[]>(queryKeyFilters)?.forEach(filter => {
|
||||
if (returnFilter) {
|
||||
return
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ import Icon from '@components/Icon'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import CustomText from '@components/Text'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useTimelineMutation } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import StatusContext from './Context'
|
||||
import HeaderSharedCreated from './HeaderShared/Created'
|
||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||
|
@ -5,15 +5,14 @@ import menuShare from '@components/contextMenu/share'
|
||||
import menuStatus from '@components/contextMenu/status'
|
||||
import Icon from '@components/Icon'
|
||||
import { RelationshipIncoming, RelationshipOutgoing } from '@components/Relationship'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import { getInstanceUrl } from '@utils/slices/instancesSlice'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import React, { Fragment, useContext, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform, Pressable, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
import StatusContext from './Context'
|
||||
import HeaderSharedAccount from './HeaderShared/Account'
|
||||
@ -48,8 +47,6 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
||||
const mStatus = menuStatus({ status, queryKey })
|
||||
const mInstance = menuInstance({ status, queryKey })
|
||||
|
||||
const url = useSelector(getInstanceUrl)
|
||||
|
||||
const actions = () => {
|
||||
switch (notification.type) {
|
||||
case 'follow':
|
||||
@ -63,7 +60,9 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
||||
content={t('shared.actions.openReport')}
|
||||
onPress={async () =>
|
||||
WebBrowser.openAuthSessionAsync(
|
||||
`https://${url}/admin/reports/${notification.report.id}`,
|
||||
`https://${getAccountStorage.string('auth.domain')}/admin/reports/${
|
||||
notification.report.id
|
||||
}`,
|
||||
'tooot://tooot',
|
||||
{
|
||||
...(await browserPackage()),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import CustomText from '@components/Text'
|
||||
import { ParseEmojis } from '@root/components/Parse'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
|
@ -5,6 +5,7 @@ import { displayMessage } from '@components/Message'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import RelativeTime from '@components/RelativeTime'
|
||||
import CustomText from '@components/Text'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
MutationVarsTimelineUpdateStatusProperty,
|
||||
useTimelineMutation
|
||||
@ -16,7 +17,6 @@ import { maxBy } from 'lodash'
|
||||
import React, { useCallback, useContext, useMemo, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import StatusContext from './Context'
|
||||
|
||||
const TimelinePoll: React.FC = () => {
|
||||
@ -58,6 +58,7 @@ const TimelinePoll: React.FC = () => {
|
||||
theme,
|
||||
type: 'error',
|
||||
message: t('common:message.error.message', {
|
||||
// @ts-ignore
|
||||
function: t(`componentTimeline:shared.poll.meta.button.${theParams.payload.type}` as any)
|
||||
}),
|
||||
...(err.status &&
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import CustomText from '@components/Text'
|
||||
import detectLanguage from '@helpers/detectLanguage'
|
||||
import getLanguage from '@helpers/getLanguage'
|
||||
import detectLanguage from '@utils/helpers/detectLanguage'
|
||||
import getLanguage from '@utils/helpers/getLanguage'
|
||||
import { useTranslateQuery } from '@utils/queryHooks/translate'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
|
@ -2,16 +2,15 @@ import ComponentSeparator from '@components/Separator'
|
||||
import { useScrollToTop } from '@react-navigation/native'
|
||||
import { UseInfiniteQueryOptions } from '@tanstack/react-query'
|
||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import { useGlobalStorageListener } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { RefObject, useCallback, useRef } from 'react'
|
||||
import { FlatList, FlatListProps, Platform, RefreshControl } from 'react-native'
|
||||
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
|
||||
import { useSelector } from 'react-redux'
|
||||
import TimelineEmpty from './Timeline/Empty'
|
||||
import TimelineFooter from './Timeline/Footer'
|
||||
import TimelineRefresh, { SEPARATION_Y_1, SEPARATION_Y_2 } from './Timeline/Refresh'
|
||||
import TimelineEmpty from './Empty'
|
||||
import TimelineFooter from './Footer'
|
||||
import TimelineRefresh, { SEPARATION_Y_1, SEPARATION_Y_2 } from './Refresh'
|
||||
|
||||
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
|
||||
|
||||
@ -100,12 +99,9 @@ const Timeline: React.FC<Props> = ({
|
||||
})
|
||||
|
||||
useScrollToTop(flRef)
|
||||
useSelector(getInstanceActive, (prev, next) => {
|
||||
if (prev !== next) {
|
||||
flRef.current?.scrollToOffset({ offset: 0, animated: false })
|
||||
}
|
||||
return prev === next
|
||||
})
|
||||
useGlobalStorageListener('account.active', () =>
|
||||
flRef.current?.scrollToOffset({ offset: 0, animated: false })
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
@ -2,6 +2,7 @@ import haptics from '@components/haptics'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { TabSharedStackParamList } from '@utils/navigation/navigators'
|
||||
import {
|
||||
QueryKeyRelationship,
|
||||
@ -13,12 +14,10 @@ import {
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Platform } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const menuAccount = ({
|
||||
type,
|
||||
@ -43,8 +42,7 @@ const menuAccount = ({
|
||||
|
||||
const menus: ContextMenu[][] = [[]]
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount)
|
||||
const ownAccount = instanceAccount?.id === account.id
|
||||
const ownAccount = useAccountStorage.string('auth.account.id')['0'] === account.id
|
||||
|
||||
const [enabled, setEnabled] = useState(openChange)
|
||||
useEffect(() => {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { getHost } from '@utils/helpers/urlMatcher'
|
||||
import { QueryKeyTimeline, useTimelineMutation } from '@utils/queryHooks/timeline'
|
||||
import { getInstanceUrl } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const menuInstance = ({
|
||||
status,
|
||||
@ -35,10 +35,9 @@ const menuInstance = ({
|
||||
|
||||
const menus: ContextMenu[][] = []
|
||||
|
||||
const currentInstance = useSelector(getInstanceUrl)
|
||||
const instance = status.uri && status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
|
||||
const instance = getHost(status.uri)
|
||||
|
||||
if (currentInstance !== instance && instance) {
|
||||
if (instance === getAccountStorage.string('auth.domain')) {
|
||||
menus.push([
|
||||
{
|
||||
key: 'instance-block',
|
||||
|
@ -1,19 +1,19 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import {
|
||||
MutationVarsTimelineUpdateStatusProperty,
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { checkInstanceFeature, getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const menuStatus = ({
|
||||
status,
|
||||
@ -57,10 +57,10 @@ const menuStatus = ({
|
||||
|
||||
const menus: ContextMenu[][] = []
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount, (prev, next) => prev.id === next.id)
|
||||
const ownAccount = instanceAccount?.id === status.account?.id
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const ownAccount = accountId === status.account?.id
|
||||
|
||||
const canEditPost = useSelector(checkInstanceFeature('edit_post'))
|
||||
const canEditPost = featureCheck('edit_post')
|
||||
|
||||
menus.push([
|
||||
{
|
||||
@ -203,9 +203,7 @@ const menuStatus = ({
|
||||
}),
|
||||
disabled: false,
|
||||
destructive: false,
|
||||
hidden:
|
||||
!ownAccount &&
|
||||
!status.mentions.filter(mention => mention.id === instanceAccount.id).length
|
||||
hidden: !ownAccount && !status.mentions.filter(mention => mention.id === accountId).length
|
||||
},
|
||||
title: t('componentContextMenu:status.mute.action', {
|
||||
defaultValue: 'false',
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
import { store } from '@root/store'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import { QueryKeyInstance } from '@utils/queryHooks/instance'
|
||||
import i18next from 'i18next'
|
||||
import { Asset, launchImageLibrary } from 'react-native-image-picker'
|
||||
|
||||
const queryKeyInstance: QueryKeyInstance = ['Instance']
|
||||
export const MAX_MEDIA_ATTACHMENTS: number =
|
||||
queryClient.getQueryData<Mastodon.Instance<any>>(queryKeyInstance)?.configuration?.statuses
|
||||
.max_media_attachments || 4
|
||||
|
||||
export interface Props {
|
||||
mediaType?: 'photo' | 'video'
|
||||
resize?: { width?: number; height?: number }
|
||||
@ -22,7 +27,7 @@ const mediaSelector = async ({
|
||||
indicateMaximum = false,
|
||||
showActionSheetWithOptions
|
||||
}: Props): Promise<Asset[]> => {
|
||||
const _maximum = maximum || getInstanceConfigurationStatusMaxAttachments(store.getState()) || 4
|
||||
const _maximum = maximum || MAX_MEDIA_ATTACHMENTS
|
||||
|
||||
const options = () => {
|
||||
switch (mediaType) {
|
||||
|
@ -1,10 +1,9 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import navigationRef from '@helpers/navigationRef'
|
||||
import { matchAccount, matchStatus } from '@helpers/urlMatcher'
|
||||
import { store } from '@root/store'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { matchAccount, matchStatus } from '@utils/helpers/urlMatcher'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { SearchResult } from '@utils/queryHooks/search'
|
||||
import { getSettingsBrowser } from '@utils/slices/settingsSlice'
|
||||
import { getGlobalStorage } from '@utils/storage/actions'
|
||||
import * as Linking from 'expo-linking'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import validUrl from 'valid-url'
|
||||
@ -89,7 +88,7 @@ const openLink = async (url: string, navigation?: any) => {
|
||||
loadingLink = false
|
||||
const validatedUrl = validUrl.isWebUri(url)
|
||||
if (validatedUrl) {
|
||||
switch (getSettingsBrowser(store.getState())) {
|
||||
switch (getGlobalStorage.string('app.browser')) {
|
||||
// Some links might end with an empty space at the end that triggers an error
|
||||
case 'internal':
|
||||
await WebBrowser.openBrowserAsync(validatedUrl, {
|
||||
|
Reference in New Issue
Block a user