1
0
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:
xmflsct
2022-12-28 23:41:36 +01:00
committed by GitHub
parent 71ccb4a93c
commit 1ea6aff328
214 changed files with 2151 additions and 3694 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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 }}
>

View File

@ -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']> = []

View File

@ -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

View File

@ -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 }

View File

@ -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 = {

View File

@ -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

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -1,4 +0,0 @@
import ParseEmojis from './Parse/Emojis'
import ParseHTML from './Parse/HTML'
export { ParseEmojis, ParseHTML }

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,4 @@
import ParseEmojis from './Emojis'
import ParseHTML from './HTML'
export { ParseEmojis, ParseHTML }

View File

@ -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']

View File

@ -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 })

View File

@ -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'

View File

@ -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)

View File

@ -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) {

View File

@ -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>>

View File

@ -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', {

View File

@ -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':

View File

@ -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)

View File

@ -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'

View File

@ -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

View File

@ -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
}

View File

@ -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'

View File

@ -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()),

View File

@ -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'

View File

@ -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 &&

View File

@ -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'

View File

@ -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 (
<>

View File

@ -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(() => {

View File

@ -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',

View File

@ -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',

View File

@ -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) {

View File

@ -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, {