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,15 +1,14 @@
|
||||
import AccountButton from '@components/AccountButton'
|
||||
import CustomText from '@components/Text'
|
||||
import navigationRef from '@helpers/navigationRef'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { getInstances } from '@utils/slices/instancesSlice'
|
||||
import { getGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as VideoThumbnails from 'expo-video-thumbnails'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FlatList, Image, ScrollView, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const Share = ({
|
||||
text,
|
||||
@@ -93,7 +92,7 @@ const ScreenAccountSelection = ({
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenAccountSelection')
|
||||
|
||||
const instances = useSelector(getInstances, () => true)
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
@@ -126,27 +125,24 @@ const ScreenAccountSelection = ({
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
}}
|
||||
>
|
||||
{instances.length
|
||||
? instances
|
||||
.slice()
|
||||
.sort((a, b) =>
|
||||
`${a.uri}${a.account.acct}`.localeCompare(`${b.uri}${b.account.acct}`)
|
||||
{accounts &&
|
||||
accounts
|
||||
.slice()
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.map((account, index) => {
|
||||
return (
|
||||
<AccountButton
|
||||
key={index}
|
||||
account={account}
|
||||
additionalActions={() =>
|
||||
navigationRef.navigate('Screen-Compose', {
|
||||
type: 'share',
|
||||
...share
|
||||
})
|
||||
}
|
||||
/>
|
||||
)
|
||||
.map((instance, index) => {
|
||||
return (
|
||||
<AccountButton
|
||||
key={index}
|
||||
instance={instance}
|
||||
additionalActions={() => {
|
||||
navigationRef.navigate('Screen-Compose', {
|
||||
type: 'share',
|
||||
...share
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import { HeaderLeft } from '@components/Header'
|
||||
import Icon from '@components/Icon'
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import CustomText from '@components/Text'
|
||||
import HeaderSharedCreated from '@components/Timeline/Shared/HeaderShared/Created'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { getInstanceDrafts, removeInstanceDraft } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage, setAccountStorage, useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
@@ -15,11 +14,19 @@ import { Dimensions, Modal, Platform, Pressable, View } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { PanGestureHandler } from 'react-native-gesture-handler'
|
||||
import { SwipeListView } from 'react-native-swipe-list-view'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from './utils/createContext'
|
||||
import { formatText } from './utils/processText'
|
||||
import { ComposeStateDraft, ExtendedAttachment } from './utils/types'
|
||||
|
||||
export const removeDraft = (timestamp: number) =>
|
||||
setAccountStorage([
|
||||
{
|
||||
key: 'drafts',
|
||||
value:
|
||||
getAccountStorage.object('drafts')?.filter(draft => draft.timestamp !== timestamp) || []
|
||||
}
|
||||
])
|
||||
|
||||
const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-DraftsList'>> = ({
|
||||
navigation,
|
||||
route: {
|
||||
@@ -39,11 +46,8 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
|
||||
}, [])
|
||||
|
||||
const { composeDispatch } = useContext(ComposeContext)
|
||||
const instanceDrafts = useSelector(getInstanceDrafts)?.filter(
|
||||
draft => draft.timestamp !== timestamp
|
||||
)
|
||||
const [drafts] = useAccountStorage.object('drafts')
|
||||
const [checkingAttachments, setCheckingAttachments] = useState(false)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const actionWidth = StyleConstants.Font.Size.L + StyleConstants.Spacing.Global.PagePadding * 4
|
||||
|
||||
@@ -72,7 +76,7 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
|
||||
</View>
|
||||
<PanGestureHandler enabled={Platform.OS === 'ios'}>
|
||||
<SwipeListView
|
||||
data={instanceDrafts}
|
||||
data={drafts.filter(draft => draft.timestamp !== timestamp)}
|
||||
renderItem={({ item }: { item: ComposeStateDraft }) => {
|
||||
return (
|
||||
<Pressable
|
||||
@@ -113,7 +117,7 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
|
||||
type: 'loadDraft',
|
||||
payload: tempDraft
|
||||
})
|
||||
dispatch(removeInstanceDraft(item.timestamp))
|
||||
removeDraft(item.timestamp)
|
||||
navigation.goBack()
|
||||
}}
|
||||
>
|
||||
@@ -172,7 +176,7 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
|
||||
justifyContent: 'flex-end',
|
||||
backgroundColor: colors.red
|
||||
}}
|
||||
onPress={() => dispatch(removeInstanceDraft(item.timestamp))}
|
||||
onPress={() => removeDraft(item.timestamp)}
|
||||
children={
|
||||
<View
|
||||
style={{
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import haptics from '@components/haptics'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { Modal, View } from 'react-native'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import ComposeContext from './utils/createContext'
|
||||
|
||||
const ComposePosting = () => {
|
||||
|
@@ -1,15 +1,14 @@
|
||||
import { emojis } from '@components/Emojis'
|
||||
import EmojisContext from '@components/Emojis/helpers/EmojisContext'
|
||||
import EmojisContext from '@components/Emojis/Context'
|
||||
import Icon from '@components/Icon'
|
||||
import { MAX_MEDIA_ATTACHMENTS } from '@components/mediaSelector'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Keyboard, Pressable, StyleSheet, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../utils/createContext'
|
||||
import chooseAndUploadAttachment from './Footer/addAttachment'
|
||||
|
||||
@@ -18,10 +17,6 @@ const ComposeActions: React.FC = () => {
|
||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||
const { t } = useTranslation(['common', 'screenCompose'])
|
||||
const { colors } = useTheme()
|
||||
const instanceConfigurationStatusMaxAttachments = useSelector(
|
||||
getInstanceConfigurationStatusMaxAttachments,
|
||||
() => true
|
||||
)
|
||||
|
||||
const attachmentColor = useMemo(() => {
|
||||
if (composeState.poll.active) return colors.disabled
|
||||
@@ -35,11 +30,8 @@ const ComposeActions: React.FC = () => {
|
||||
const attachmentOnPress = () => {
|
||||
if (composeState.poll.active) return
|
||||
|
||||
if (composeState.attachments.uploads.length < instanceConfigurationStatusMaxAttachments) {
|
||||
return chooseAndUploadAttachment({
|
||||
composeDispatch,
|
||||
showActionSheetWithOptions
|
||||
})
|
||||
if (composeState.attachments.uploads.length < MAX_MEDIA_ATTACHMENTS) {
|
||||
return chooseAndUploadAttachment({ composeDispatch, showActionSheetWithOptions })
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import Button from '@components/Button'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { getInstanceDrafts } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import React, { RefObject, useContext, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../utils/createContext'
|
||||
|
||||
export interface Props {
|
||||
@@ -17,15 +16,14 @@ const ComposeDrafts: React.FC<Props> = ({ accessibleRefDrafts }) => {
|
||||
const { t } = useTranslation('screenCompose')
|
||||
const navigation = useNavigation<any>()
|
||||
const { composeState } = useContext(ComposeContext)
|
||||
const instanceDrafts = useSelector(getInstanceDrafts)?.filter(
|
||||
draft => draft.timestamp !== composeState.timestamp
|
||||
)
|
||||
const [drafts] = useAccountStorage.object('drafts')
|
||||
const draftsCount = drafts.filter(draft => draft.timestamp !== composeState.timestamp).length
|
||||
|
||||
useEffect(() => {
|
||||
layoutAnimation()
|
||||
}, [composeState.dirty])
|
||||
|
||||
if (!composeState.dirty && instanceDrafts?.length) {
|
||||
if (!composeState.dirty && draftsCount) {
|
||||
return (
|
||||
<View
|
||||
accessible
|
||||
@@ -34,9 +32,7 @@ const ComposeDrafts: React.FC<Props> = ({ accessibleRefDrafts }) => {
|
||||
children={
|
||||
<Button
|
||||
type='text'
|
||||
content={t('content.root.drafts', {
|
||||
count: instanceDrafts.length
|
||||
})}
|
||||
content={t('content.root.drafts', { count: draftsCount })}
|
||||
onPress={() =>
|
||||
navigation.navigate('Screen-Compose-DraftsList', {
|
||||
timestamp: composeState.timestamp
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { MAX_MEDIA_ATTACHMENTS } from '@components/mediaSelector'
|
||||
import CustomText from '@components/Text'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@@ -13,7 +13,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import { FlatList, Pressable, StyleSheet, View } from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
import { ExtendedAttachment } from '../../utils/types'
|
||||
import chooseAndUploadAttachment from './addAttachment'
|
||||
@@ -31,8 +30,6 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
const { colors } = useTheme()
|
||||
const navigation = useNavigation<any>()
|
||||
|
||||
const maxAttachments = useSelector(getInstanceConfigurationStatusMaxAttachments, () => true)
|
||||
|
||||
const flatListRef = useRef<FlatList>(null)
|
||||
|
||||
const sensitiveOnPress = useCallback(
|
||||
@@ -285,7 +282,7 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
data={composeState.attachments.uploads}
|
||||
keyExtractor={item => item.local?.uri || item.remote?.url || Math.random().toString()}
|
||||
ListFooterComponent={
|
||||
composeState.attachments.uploads.length < maxAttachments ? listFooter : null
|
||||
composeState.attachments.uploads.length < MAX_MEDIA_ATTACHMENTS ? listFooter : null
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
@@ -3,14 +3,13 @@ import Icon from '@components/Icon'
|
||||
import { MenuRow } from '@components/Menu'
|
||||
import CustomText from '@components/Text'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { getInstanceConfigurationPoll } from '@utils/slices/instancesSlice'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, TextInput, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
|
||||
const ComposePoll: React.FC = () => {
|
||||
@@ -24,11 +23,11 @@ const ComposePoll: React.FC = () => {
|
||||
const { t } = useTranslation(['common', 'screenCompose'])
|
||||
const { colors, mode } = useTheme()
|
||||
|
||||
const instanceConfigurationPoll = useSelector(getInstanceConfigurationPoll, () => true)
|
||||
const MAX_OPTIONS = instanceConfigurationPoll.max_options
|
||||
const MAX_CHARS_PER_OPTION = instanceConfigurationPoll.max_characters_per_option
|
||||
const MIN_EXPIRATION = instanceConfigurationPoll.min_expiration
|
||||
const MAX_EXPIRATION = instanceConfigurationPoll.max_expiration
|
||||
const { data } = useInstanceQuery()
|
||||
const MAX_OPTIONS = data?.configuration?.polls.max_options || 4
|
||||
const MAX_CHARS_PER_OPTION = data?.configuration?.polls.max_characters_per_option
|
||||
const MIN_EXPIRATION = data?.configuration?.polls.min_expiration || 300
|
||||
const MAX_EXPIRATION = data?.configuration?.polls.max_expiration || 2629746
|
||||
|
||||
const [firstRender, setFirstRender] = useState(true)
|
||||
useEffect(() => {
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import mediaSelector from '@components/mediaSelector'
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import * as Crypto from 'expo-crypto'
|
||||
import * as VideoThumbnails from 'expo-video-thumbnails'
|
||||
import i18next from 'i18next'
|
||||
import { Dispatch } from 'react'
|
||||
import { Alert } from 'react-native'
|
||||
import { ComposeAction } from '../../utils/types'
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
import i18next from 'i18next'
|
||||
import apiInstance from '@api/instance'
|
||||
import mediaSelector from '@components/mediaSelector'
|
||||
import { Asset } from 'react-native-image-picker'
|
||||
import { ComposeAction } from '../../utils/types'
|
||||
|
||||
export interface Props {
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
|
@@ -1,36 +0,0 @@
|
||||
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React, { useContext } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../utils/createContext'
|
||||
import ComposePostingAs from './Header/PostingAs'
|
||||
import ComposeSpoilerInput from './Header/SpoilerInput'
|
||||
import ComposeTextInput from './Header/TextInput'
|
||||
|
||||
const ComposeRootHeader: React.FC = () => {
|
||||
const { composeState } = useContext(ComposeContext)
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const localInstances = useSelector(getInstances, (prev, next) => prev.length === next.length)
|
||||
|
||||
return (
|
||||
<View>
|
||||
{instanceActive !== -1 && localInstances.length > 1 ? (
|
||||
<View style={styles.postingAs}>
|
||||
<ComposePostingAs />
|
||||
</View>
|
||||
) : null}
|
||||
{composeState.spoiler.active ? <ComposeSpoilerInput /> : null}
|
||||
<ComposeTextInput />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
postingAs: {
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginTop: StyleConstants.Spacing.S
|
||||
}
|
||||
})
|
||||
|
||||
export default ComposeRootHeader
|
@@ -1,24 +1,32 @@
|
||||
import CustomText from '@components/Text'
|
||||
import { getInstanceAccount, getInstanceUri } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { View } from 'react-native'
|
||||
|
||||
const ComposePostingAs = () => {
|
||||
const accounts = useGlobalStorage.object('accounts')
|
||||
if (!accounts.length) return null
|
||||
|
||||
const { t } = useTranslation('screenCompose')
|
||||
const { colors } = useTheme()
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount, (prev, next) => prev?.acct === next?.acct)
|
||||
const instanceUri = useSelector(getInstanceUri)
|
||||
|
||||
return (
|
||||
<CustomText fontStyle='S' style={{ color: colors.secondary }}>
|
||||
{t('content.root.header.postingAs', {
|
||||
acct: instanceAccount?.acct,
|
||||
domain: instanceUri
|
||||
})}
|
||||
</CustomText>
|
||||
<View
|
||||
style={{
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginTop: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<CustomText fontStyle='S' style={{ color: colors.secondary }}>
|
||||
{t('content.root.header.postingAs', {
|
||||
acct: getAccountStorage.string('auth.account.acct'),
|
||||
domain: getAccountStorage.string('auth.domain')
|
||||
})}
|
||||
</CustomText>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import CustomText from '@components/Text'
|
||||
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, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TextInput } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
import { formatText } from '../../utils/processText'
|
||||
|
||||
@@ -15,7 +14,7 @@ const ComposeSpoilerInput: React.FC = () => {
|
||||
const { t } = useTranslation('screenCompose')
|
||||
const { colors, mode } = useTheme()
|
||||
|
||||
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||
const [adaptiveFontsize] = useGlobalStorage.number('app.font_size')
|
||||
const adaptedFontsize = adaptiveScale(StyleConstants.Font.Size.M, adaptiveFontsize)
|
||||
const adaptedLineheight = adaptiveScale(StyleConstants.Font.LineHeight.M, adaptiveFontsize)
|
||||
|
||||
|
@@ -1,14 +1,13 @@
|
||||
import { MAX_MEDIA_ATTACHMENTS } from '@components/mediaSelector'
|
||||
import CustomText from '@components/Text'
|
||||
import PasteInput, { PastedFile } from '@mattermost/react-native-paste-input'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
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, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
import { formatText } from '../../utils/processText'
|
||||
import { uploadAttachment } from '../Footer/addAttachment'
|
||||
@@ -18,9 +17,7 @@ const ComposeTextInput: React.FC = () => {
|
||||
const { t } = useTranslation(['common', 'screenCompose'])
|
||||
const { colors, mode } = useTheme()
|
||||
|
||||
const maxAttachments = useSelector(getInstanceConfigurationStatusMaxAttachments, () => true)
|
||||
|
||||
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||
const [adaptiveFontsize] = useGlobalStorage.number('app.font_size')
|
||||
const adaptedFontsize = adaptiveScale(StyleConstants.Font.Size.M, adaptiveFontsize)
|
||||
const adaptedLineheight = adaptiveScale(StyleConstants.Font.LineHeight.M, adaptiveFontsize)
|
||||
|
||||
@@ -72,7 +69,7 @@ const ComposeTextInput: React.FC = () => {
|
||||
scrollEnabled={false}
|
||||
disableCopyPaste={false}
|
||||
onPaste={(error: string | null | undefined, files: PastedFile[]) => {
|
||||
if (composeState.attachments.uploads.length + files.length > maxAttachments) {
|
||||
if (composeState.attachments.uploads.length + files.length > MAX_MEDIA_ATTACHMENTS) {
|
||||
Alert.alert(
|
||||
t('screenCompose:content.root.header.textInput.keyboardImage.exceedMaximum.title'),
|
||||
undefined,
|
||||
|
20
src/screens/Compose/Root/Header/index.tsx
Normal file
20
src/screens/Compose/Root/Header/index.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { View } from 'react-native'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
import ComposePostingAs from './PostingAs'
|
||||
import ComposeSpoilerInput from './SpoilerInput'
|
||||
import ComposeTextInput from './TextInput'
|
||||
|
||||
const ComposeRootHeader: React.FC = () => {
|
||||
const { composeState } = useContext(ComposeContext)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ComposePostingAs />
|
||||
{composeState.spoiler.active ? <ComposeSpoilerInput /> : null}
|
||||
<ComposeTextInput />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default ComposeRootHeader
|
@@ -5,26 +5,17 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useEffect, useMemo, useRef } from 'react'
|
||||
import { AccessibilityInfo, findNodeHandle, FlatList, View } from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
import ComposeActions from './Root/Actions'
|
||||
import ComposePosting from './Posting'
|
||||
import ComposeRootFooter from './Root/Footer'
|
||||
import ComposeRootHeader from './Root/Header'
|
||||
import ComposeRootSuggestion from './Root/Suggestion'
|
||||
import ComposeContext from './utils/createContext'
|
||||
import ComposeDrafts from './Root/Drafts'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getInstanceConfigurationStatusCharsURL } from '@utils/slices/instancesSlice'
|
||||
|
||||
export let instanceConfigurationStatusCharsURL = 23
|
||||
import ComposePosting from '../Posting'
|
||||
import ComposeContext from '../utils/createContext'
|
||||
import ComposeActions from './Actions'
|
||||
import ComposeDrafts from './Drafts'
|
||||
import ComposeRootFooter from './Footer'
|
||||
import ComposeRootHeader from './Header'
|
||||
import ComposeRootSuggestion from './Suggestion'
|
||||
|
||||
const ComposeRoot = () => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
instanceConfigurationStatusCharsURL = useSelector(
|
||||
getInstanceConfigurationStatusCharsURL,
|
||||
() => true
|
||||
)
|
||||
|
||||
const accessibleRefDrafts = useRef(null)
|
||||
const accessibleRefAttachments = useRef(null)
|
||||
|
@@ -1,37 +1,37 @@
|
||||
import { handleError } from '@api/helpers'
|
||||
import { ComponentEmojis } from '@components/Emojis'
|
||||
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
|
||||
import { EmojisState } from '@components/Emojis/Context'
|
||||
import haptics from '@components/haptics'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import haptics from '@root/components/haptics'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import ComposeRoot from '@screens/Compose/Root'
|
||||
import { formatText } from '@screens/Compose/utils/processText'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { handleError } from '@utils/api/helpers'
|
||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { useTimelineMutation } from '@utils/queryHooks/timeline'
|
||||
import { updateStoreReview } from '@utils/slices/contextsSlice'
|
||||
import {
|
||||
getInstanceAccount,
|
||||
getInstanceConfigurationStatusMaxChars,
|
||||
removeInstanceDraft,
|
||||
updateInstanceDraft
|
||||
} from '@utils/slices/instancesSlice'
|
||||
getAccountStorage,
|
||||
getGlobalStorage,
|
||||
setAccountStorage,
|
||||
setGlobalStorage
|
||||
} from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as StoreReview from 'expo-store-review'
|
||||
import { filter } from 'lodash'
|
||||
import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Keyboard, Platform } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeDraftsList from './Compose/DraftsList'
|
||||
import ComposeEditAttachment from './Compose/EditAttachment'
|
||||
import { uploadAttachment } from './Compose/Root/Footer/addAttachment'
|
||||
import ComposeContext from './Compose/utils/createContext'
|
||||
import composeInitialState from './Compose/utils/initialState'
|
||||
import composeParseState from './Compose/utils/parseState'
|
||||
import composePost from './Compose/utils/post'
|
||||
import composeReducer from './Compose/utils/reducer'
|
||||
import ComposeDraftsList, { removeDraft } from './DraftsList'
|
||||
import ComposeEditAttachment from './EditAttachment'
|
||||
import { uploadAttachment } from './Root/Footer/addAttachment'
|
||||
import ComposeContext from './utils/createContext'
|
||||
import composeInitialState from './utils/initialState'
|
||||
import composeParseState from './utils/parseState'
|
||||
import composePost from './utils/post'
|
||||
import composeReducer from './utils/reducer'
|
||||
|
||||
const Stack = createNativeStackNavigator()
|
||||
|
||||
@@ -54,12 +54,8 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
}
|
||||
}, [])
|
||||
|
||||
const localAccount = useSelector(getInstanceAccount, (prev, next) =>
|
||||
prev?.preferences && next?.preferences
|
||||
? prev?.preferences['posting:default:visibility'] ===
|
||||
next?.preferences['posting:default:visibility']
|
||||
: true
|
||||
)
|
||||
const { data: preferences } = usePreferencesQuery()
|
||||
|
||||
const initialReducerState = useMemo(() => {
|
||||
if (params) {
|
||||
return composeParseState(params)
|
||||
@@ -70,21 +66,19 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
attachments: {
|
||||
...composeInitialState.attachments,
|
||||
sensitive:
|
||||
localAccount?.preferences && localAccount?.preferences['posting:default:sensitive']
|
||||
? localAccount?.preferences['posting:default:sensitive']
|
||||
preferences?.['posting:default:sensitive'] !== undefined
|
||||
? preferences['posting:default:sensitive']
|
||||
: false
|
||||
},
|
||||
visibility:
|
||||
localAccount?.preferences && localAccount.preferences['posting:default:visibility']
|
||||
? localAccount.preferences['posting:default:visibility']
|
||||
: 'public'
|
||||
visibility: preferences?.['posting:default:visibility'] || 'public'
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const [composeState, composeDispatch] = useReducer(composeReducer, initialReducerState)
|
||||
|
||||
const maxTootChars = useSelector(getInstanceConfigurationStatusMaxChars, () => true)
|
||||
const { data: dataInstance } = useInstanceQuery()
|
||||
const maxTootChars = dataInstance?.configuration?.statuses.max_characters || 500
|
||||
const totalTextCount =
|
||||
(composeState.spoiler.active ? composeState.spoiler.count : 0) + composeState.text.count
|
||||
|
||||
@@ -177,29 +171,35 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
}, [params?.type])
|
||||
|
||||
const saveDraft = () => {
|
||||
dispatch(
|
||||
updateInstanceDraft({
|
||||
timestamp: composeState.timestamp,
|
||||
spoiler: composeState.spoiler.raw,
|
||||
text: composeState.text.raw,
|
||||
poll: composeState.poll,
|
||||
attachments: composeState.attachments,
|
||||
visibility: composeState.visibility,
|
||||
visibilityLock: composeState.visibilityLock,
|
||||
replyToStatus: composeState.replyToStatus
|
||||
})
|
||||
const payload = {
|
||||
timestamp: composeState.timestamp,
|
||||
spoiler: composeState.spoiler.raw,
|
||||
text: composeState.text.raw,
|
||||
poll: composeState.poll,
|
||||
attachments: composeState.attachments,
|
||||
visibility: composeState.visibility,
|
||||
visibilityLock: composeState.visibilityLock,
|
||||
replyToStatus: composeState.replyToStatus
|
||||
}
|
||||
|
||||
const currentDrafts = getAccountStorage.object('drafts') || []
|
||||
const draftIndex = currentDrafts?.findIndex(
|
||||
({ timestamp }) => timestamp === composeState.timestamp
|
||||
)
|
||||
if (draftIndex === -1) {
|
||||
currentDrafts?.unshift(payload)
|
||||
} else {
|
||||
currentDrafts[draftIndex] = payload
|
||||
}
|
||||
setAccountStorage([{ key: 'drafts', value: currentDrafts }])
|
||||
}
|
||||
const removeDraft = useCallback(() => {
|
||||
dispatch(removeInstanceDraft(composeState.timestamp))
|
||||
}, [composeState.timestamp])
|
||||
useEffect(() => {
|
||||
const autoSave = composeState.dirty
|
||||
? setInterval(() => {
|
||||
saveDraft()
|
||||
}, 1000)
|
||||
: removeDraft()
|
||||
return () => autoSave && clearInterval(autoSave)
|
||||
: removeDraft(composeState.timestamp)
|
||||
return () => (autoSave ? clearInterval(autoSave) : undefined)
|
||||
}, [composeState])
|
||||
|
||||
const headerLeft = useCallback(
|
||||
@@ -217,7 +217,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
text: t('screenCompose:heading.left.alert.buttons.delete'),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
removeDraft()
|
||||
removeDraft(composeState.timestamp)
|
||||
navigation.goBack()
|
||||
}
|
||||
},
|
||||
@@ -239,7 +239,6 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
),
|
||||
[composeState]
|
||||
)
|
||||
const dispatch = useAppDispatch()
|
||||
const headerRightDisabled = useMemo(() => {
|
||||
if (totalTextCount > maxTootChars) {
|
||||
return true
|
||||
@@ -277,7 +276,14 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
if (Platform.OS === 'ios' && Platform.constants.osVersion === '13.3') {
|
||||
// https://github.com/tooot-app/app/issues/59
|
||||
} else {
|
||||
dispatch(updateStoreReview(1))
|
||||
const currentCount = getGlobalStorage.number('app.count_till_store_review')
|
||||
if (currentCount === 10) {
|
||||
StoreReview?.isAvailableAsync()
|
||||
.then(() => StoreReview.requestReview())
|
||||
.catch(() => {})
|
||||
} else {
|
||||
setGlobalStorage('app.count_till_store_review', (currentCount || 0) + 1)
|
||||
}
|
||||
}
|
||||
|
||||
switch (params?.type) {
|
||||
@@ -296,7 +302,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
}
|
||||
break
|
||||
}
|
||||
removeDraft()
|
||||
removeDraft(composeState.timestamp)
|
||||
navigation.goBack()
|
||||
})
|
||||
.catch(error => {
|
@@ -1,14 +1,12 @@
|
||||
import { store } from '@root/store'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import composeInitialState from './initialState'
|
||||
import { ComposeState } from './types'
|
||||
|
||||
const assignVisibility = (
|
||||
target: ComposeState['visibility']
|
||||
): Pick<ComposeState, 'visibility' | 'visibilityLock'> => {
|
||||
const accountPreference =
|
||||
getInstanceAccount(store.getState())?.preferences?.['posting:default:visibility'] || 'public'
|
||||
const preferences = getAccountStorage.object('preferences')
|
||||
|
||||
switch (target) {
|
||||
case 'direct':
|
||||
@@ -16,13 +14,13 @@ const assignVisibility = (
|
||||
case 'private':
|
||||
return { visibility: 'private', visibilityLock: false }
|
||||
case 'unlisted':
|
||||
if (accountPreference === 'private') {
|
||||
if (preferences?.['posting:default:visibility'] === 'private') {
|
||||
return { visibility: 'private', visibilityLock: false }
|
||||
} else {
|
||||
return { visibility: 'unlisted', visibilityLock: false }
|
||||
}
|
||||
case 'public':
|
||||
switch (accountPreference) {
|
||||
switch (preferences) {
|
||||
case 'private':
|
||||
return { visibility: 'private', visibilityLock: false }
|
||||
case 'unlisted':
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import detectLanguage from '@helpers/detectLanguage'
|
||||
import { ComposeState } from '@screens/Compose/utils/types'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import detectLanguage from '@utils/helpers/detectLanguage'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import * as Crypto from 'expo-crypto'
|
||||
import { getPureContent } from './processText'
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { emojis } from '@components/Emojis'
|
||||
import CustomText from '@components/Text'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import { QueryKeyInstance } from '@utils/queryHooks/instance'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import LinkifyIt from 'linkify-it'
|
||||
import { debounce, differenceWith, isEqual } from 'lodash'
|
||||
import React, { Dispatch } from 'react'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { ComposeAction, ComposeState } from './types'
|
||||
import { instanceConfigurationStatusCharsURL } from '../Root'
|
||||
import CustomText from '@components/Text'
|
||||
import { emojis } from '@components/Emojis'
|
||||
|
||||
export interface Params {
|
||||
textInput: ComposeState['textInputFocus']['current']
|
||||
@@ -140,7 +141,11 @@ const formatText = ({ textInput, composeDispatch, content, disableDebounce = fal
|
||||
contentLength = contentLength + main.length
|
||||
break
|
||||
default:
|
||||
contentLength = contentLength + instanceConfigurationStatusCharsURL
|
||||
const queryKeyInstance: QueryKeyInstance = ['Instance']
|
||||
contentLength =
|
||||
contentLength +
|
||||
(queryClient.getQueryData<Mastodon.Instance<any>>(queryKeyInstance)?.configuration
|
||||
?.statuses.characters_reserved_per_url || 23)
|
||||
break
|
||||
}
|
||||
_content = next
|
||||
|
96
src/screens/Compose/utils/types.d.ts
vendored
96
src/screens/Compose/utils/types.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { RefObject } from 'react';
|
||||
import { RefObject } from 'react'
|
||||
import { Asset } from 'react-native-image-picker'
|
||||
|
||||
export type ExtendedAttachment = {
|
||||
@@ -67,65 +67,65 @@ export type ComposeState = {
|
||||
|
||||
export type ComposeAction =
|
||||
| {
|
||||
type: 'loadDraft'
|
||||
payload: ComposeStateDraft
|
||||
}
|
||||
type: 'loadDraft'
|
||||
payload: ComposeStateDraft
|
||||
}
|
||||
| {
|
||||
type: 'dirty'
|
||||
payload: ComposeState['dirty']
|
||||
}
|
||||
type: 'dirty'
|
||||
payload: ComposeState['dirty']
|
||||
}
|
||||
| {
|
||||
type: 'posting'
|
||||
payload: ComposeState['posting']
|
||||
}
|
||||
type: 'posting'
|
||||
payload: ComposeState['posting']
|
||||
}
|
||||
| {
|
||||
type: 'spoiler'
|
||||
payload: Partial<ComposeState['spoiler']>
|
||||
}
|
||||
type: 'spoiler'
|
||||
payload: Partial<ComposeState['spoiler']>
|
||||
}
|
||||
| {
|
||||
type: 'text'
|
||||
payload: Partial<ComposeState['text']>
|
||||
}
|
||||
type: 'text'
|
||||
payload: Partial<ComposeState['text']>
|
||||
}
|
||||
| {
|
||||
type: 'tag'
|
||||
payload: ComposeState['tag']
|
||||
}
|
||||
type: 'tag'
|
||||
payload: ComposeState['tag']
|
||||
}
|
||||
| {
|
||||
type: 'poll'
|
||||
payload: Partial<ComposeState['poll']>
|
||||
}
|
||||
type: 'poll'
|
||||
payload: Partial<ComposeState['poll']>
|
||||
}
|
||||
| {
|
||||
type: 'attachments/sensitive'
|
||||
payload: Pick<ComposeState['attachments'], 'sensitive'>
|
||||
}
|
||||
type: 'attachments/sensitive'
|
||||
payload: Pick<ComposeState['attachments'], 'sensitive'>
|
||||
}
|
||||
| {
|
||||
type: 'attachment/upload/start'
|
||||
payload: Pick<ExtendedAttachment, 'local' | 'uploading'>
|
||||
}
|
||||
type: 'attachment/upload/start'
|
||||
payload: Pick<ExtendedAttachment, 'local' | 'uploading'>
|
||||
}
|
||||
| {
|
||||
type: 'attachment/upload/end'
|
||||
payload: { remote: Mastodon.Attachment; local: Asset }
|
||||
}
|
||||
type: 'attachment/upload/end'
|
||||
payload: { remote: Mastodon.Attachment; local: Asset }
|
||||
}
|
||||
| {
|
||||
type: 'attachment/upload/fail'
|
||||
payload: ExtendedAttachment['local']['hash']
|
||||
}
|
||||
type: 'attachment/upload/fail'
|
||||
payload: ExtendedAttachment['local']['hash']
|
||||
}
|
||||
| {
|
||||
type: 'attachment/delete'
|
||||
payload: NonNullable<ExtendedAttachment['remote']>['id']
|
||||
}
|
||||
type: 'attachment/delete'
|
||||
payload: NonNullable<ExtendedAttachment['remote']>['id']
|
||||
}
|
||||
| {
|
||||
type: 'attachment/edit'
|
||||
payload: ExtendedAttachment['remote']
|
||||
}
|
||||
type: 'attachment/edit'
|
||||
payload: ExtendedAttachment['remote']
|
||||
}
|
||||
| {
|
||||
type: 'visibility'
|
||||
payload: ComposeState['visibility']
|
||||
}
|
||||
type: 'visibility'
|
||||
payload: ComposeState['visibility']
|
||||
}
|
||||
| {
|
||||
type: 'textInputFocus'
|
||||
payload: Partial<ComposeState['textInputFocus']>
|
||||
}
|
||||
type: 'textInputFocus'
|
||||
payload: Partial<ComposeState['textInputFocus']>
|
||||
}
|
||||
| {
|
||||
type: 'removeReply'
|
||||
}
|
||||
type: 'removeReply'
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
@@ -18,9 +18,9 @@ import {
|
||||
} from 'react-native'
|
||||
import { Directions, Gesture, LongPressGestureHandler } from 'react-native-gesture-handler'
|
||||
import { runOnJS, useSharedValue } from 'react-native-reanimated'
|
||||
import { Zoom, createZoomListComponent } from 'react-native-reanimated-zoom'
|
||||
import { createZoomListComponent, Zoom } from 'react-native-reanimated-zoom'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import saveImage from './ImageViewer/save'
|
||||
import saveImage from './save'
|
||||
|
||||
const ZoomFlatList = createZoomListComponent(FlatList)
|
||||
|
@@ -8,13 +8,12 @@ import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import usePopToTop from '@utils/navigation/usePopToTop'
|
||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getInstanceFollowingPage, updateInstanceFollowingPage } from '@utils/slices/instancesSlice'
|
||||
import { setAccountStorage, useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { View } from 'react-native'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
|
||||
const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-Root'>> = ({
|
||||
@@ -25,11 +24,10 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
|
||||
const { data: lists } = useListsQuery()
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const instanceFollowingPage = useSelector(getInstanceFollowingPage)
|
||||
const [pageLocal] = useAccountStorage.object('page_local')
|
||||
const [queryKey, setQueryKey] = useState<QueryKeyTimeline>([
|
||||
'Timeline',
|
||||
{ page: 'Following', ...instanceFollowingPage }
|
||||
{ page: 'Following', ...pageLocal }
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -59,7 +57,7 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
: t('tabs.local.name')
|
||||
}
|
||||
/>
|
||||
{page.page === 'Following' && !instanceFollowingPage.showBoosts ? (
|
||||
{page.page === 'Following' && !pageLocal.showBoosts ? (
|
||||
<Icon
|
||||
name='Repeat'
|
||||
size={StyleConstants.Font.Size.M}
|
||||
@@ -68,7 +66,7 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
crossOut
|
||||
/>
|
||||
) : null}
|
||||
{page.page === 'Following' && !instanceFollowingPage.showReplies ? (
|
||||
{page.page === 'Following' && !pageLocal.showReplies ? (
|
||||
<Icon
|
||||
name='MessageCircle'
|
||||
size={StyleConstants.Font.Size.M}
|
||||
@@ -90,9 +88,7 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item
|
||||
key='default'
|
||||
onSelect={() =>
|
||||
setQueryKey(['Timeline', { page: 'Following', ...instanceFollowingPage }])
|
||||
}
|
||||
onSelect={() => setQueryKey(['Timeline', { page: 'Following', ...pageLocal }])}
|
||||
disabled={page.page === 'Following'}
|
||||
>
|
||||
<DropdownMenu.ItemTitle children={t('tabs.local.name')} />
|
||||
@@ -100,19 +96,22 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.CheckboxItem
|
||||
key='showBoosts'
|
||||
value={instanceFollowingPage.showBoosts ? 'on' : 'off'}
|
||||
value={pageLocal.showBoosts ? 'on' : 'off'}
|
||||
onValueChange={() => {
|
||||
setQueryKey([
|
||||
'Timeline',
|
||||
{
|
||||
page: 'Following',
|
||||
showBoosts: !instanceFollowingPage.showBoosts,
|
||||
showReplies: instanceFollowingPage.showReplies
|
||||
showBoosts: !pageLocal.showBoosts,
|
||||
showReplies: pageLocal.showReplies
|
||||
}
|
||||
])
|
||||
setAccountStorage([
|
||||
{
|
||||
key: 'page_local',
|
||||
value: { ...pageLocal, showBoosts: !pageLocal.showBoosts }
|
||||
}
|
||||
])
|
||||
dispatch(
|
||||
updateInstanceFollowingPage({ showBoosts: !instanceFollowingPage.showBoosts })
|
||||
)
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemIndicator />
|
||||
@@ -120,19 +119,22 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
</DropdownMenu.CheckboxItem>
|
||||
<DropdownMenu.CheckboxItem
|
||||
key='showReplies'
|
||||
value={instanceFollowingPage.showReplies ? 'on' : 'off'}
|
||||
value={pageLocal.showReplies ? 'on' : 'off'}
|
||||
onValueChange={() => {
|
||||
setQueryKey([
|
||||
'Timeline',
|
||||
{
|
||||
page: 'Following',
|
||||
showBoosts: instanceFollowingPage.showBoosts,
|
||||
showReplies: !instanceFollowingPage.showReplies
|
||||
showBoosts: pageLocal.showBoosts,
|
||||
showReplies: !pageLocal.showReplies
|
||||
}
|
||||
])
|
||||
setAccountStorage([
|
||||
{
|
||||
key: 'page_local',
|
||||
value: { ...pageLocal, showReplies: !pageLocal.showReplies }
|
||||
}
|
||||
])
|
||||
dispatch(
|
||||
updateInstanceFollowingPage({ showReplies: !instanceFollowingPage.showReplies })
|
||||
)
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemTitle children={t('tabs.local.options.showReplies')} />
|
||||
@@ -174,7 +176,7 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, [mode, queryKey[1], instanceFollowingPage, lists])
|
||||
}, [mode, queryKey[1], pageLocal, lists])
|
||||
|
||||
usePopToTop()
|
||||
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
|
||||
import { EmojisState } from '@components/Emojis/Context'
|
||||
import haptics from '@components/haptics'
|
||||
import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import ComponentInput from '@components/Input'
|
||||
import { displayMessage, Message } from '@components/Message'
|
||||
import Selections from '@components/Selections'
|
||||
import CustomText from '@components/Text'
|
||||
import { CommonActions } from '@react-navigation/native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { QueryKeyLists, useListsMutation } from '@utils/queryHooks/lists'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Platform, ScrollView, TextInput } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { Alert, ScrollView, TextInput } from 'react-native'
|
||||
|
||||
const TabMeListEdit: React.FC<TabMeStackScreenProps<'Tab-Me-List-Edit'>> = ({
|
||||
navigation,
|
||||
|
@@ -2,6 +2,7 @@ import Icon from '@components/Icon'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { QueryKeyLists, useListsMutation } from '@utils/queryHooks/lists'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
@@ -9,7 +10,6 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
import { menuListAccounts, menuListDelete, menuListEdit } from './menus'
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import navigationRef from '@helpers/navigationRef'
|
||||
import { UseMutationResult } from '@tanstack/react-query'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import i18next from 'i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { UseMutationResult } from '@tanstack/react-query'
|
||||
|
||||
export const menuListAccounts = ({ params }: { params: Mastodon.List }) => ({
|
||||
key: 'list-accounts',
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ComponentEmojis } from '@components/Emojis'
|
||||
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
|
||||
import { EmojisState } from '@components/Emojis/Context'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import ComponentInput from '@components/Input'
|
||||
import CustomText from '@components/Text'
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ComponentEmojis } from '@components/Emojis'
|
||||
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
|
||||
import { EmojisState } from '@components/Emojis/Context'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import ComponentInput from '@components/Input'
|
||||
import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators'
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ComponentEmojis } from '@components/Emojis'
|
||||
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
|
||||
import { EmojisState } from '@components/Emojis/Context'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import ComponentInput from '@components/Input'
|
||||
import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators'
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { QueryKeyPreferences } from '@utils/queryHooks/preferences'
|
||||
import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { RefObject } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -24,7 +24,11 @@ const TabMeProfileRoot: React.FC<
|
||||
|
||||
const { data, isFetching } = useProfileQuery()
|
||||
const { mutateAsync } = useProfileMutation()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const refetchPreferences = () => {
|
||||
const queryKeyPreferences: QueryKeyPreferences = ['Preferences']
|
||||
queryClient.refetchQueries(queryKeyPreferences)
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
@@ -117,7 +121,7 @@ const TabMeProfileRoot: React.FC<
|
||||
},
|
||||
type: 'source[privacy]',
|
||||
data: indexVisibilityMapping[buttonIndex]
|
||||
}).then(() => dispatch(updateAccountPreferences()))
|
||||
}).then(() => refetchPreferences())
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -139,7 +143,7 @@ const TabMeProfileRoot: React.FC<
|
||||
},
|
||||
type: 'source[sensitive]',
|
||||
data: data?.source.sensitive === undefined ? true : !data.source.sensitive
|
||||
}).then(() => dispatch(updateAccountPreferences()))
|
||||
}).then(() => refetchPreferences())
|
||||
}
|
||||
loading={isFetching}
|
||||
/>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
||||
import { HeaderLeft } from '@components/Header'
|
||||
import { Message } from '@components/Message'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { TabMeProfileStackParamList, TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||
@@ -6,10 +6,10 @@ import React, { useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { KeyboardAvoidingView, Platform } from 'react-native'
|
||||
import FlashMessage from 'react-native-flash-message'
|
||||
import TabMeProfileFields from './Profile/Fields'
|
||||
import TabMeProfileName from './Profile/Name'
|
||||
import TabMeProfileNote from './Profile/Note'
|
||||
import TabMeProfileRoot from './Profile/Root'
|
||||
import TabMeProfileFields from './Fields'
|
||||
import TabMeProfileName from './Name'
|
||||
import TabMeProfileNote from './Note'
|
||||
import TabMeProfileRoot from './Root'
|
||||
|
||||
const Stack = createNativeStackNavigator<TabMeProfileStackParamList>()
|
||||
|
@@ -1,40 +1,39 @@
|
||||
import Button from '@components/Button'
|
||||
import Icon from '@components/Icon'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import CustomText from '@components/Text'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { isDevelopment } from '@utils/checkEnvironment'
|
||||
import * as Sentry from '@sentry/react-native'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import apiTooot, { TOOOT_API_DOMAIN } from '@utils/api/tooot'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { isDevelopment } from '@utils/helpers/checkEnvironment'
|
||||
import { PUSH_ADMIN, PUSH_DEFAULT, setChannels } from '@utils/push/constants'
|
||||
import { updateExpoToken } from '@utils/push/updateExpoToken'
|
||||
import { useAppsQuery } from '@utils/queryHooks/apps'
|
||||
import { useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import { getExpoToken, retrieveExpoToken } from '@utils/slices/appSlice'
|
||||
import { PUSH_ADMIN, PUSH_DEFAULT, usePushFeatures } from '@utils/slices/instances/push/utils'
|
||||
import { updateInstancePush } from '@utils/slices/instances/updatePush'
|
||||
import { updateInstancePushAlert } from '@utils/slices/instances/updatePushAlert'
|
||||
import { updateInstancePushDecode } from '@utils/slices/instances/updatePushDecode'
|
||||
import { getInstance, getInstancePush } from '@utils/slices/instancesSlice'
|
||||
import { setAccountStorage, useAccountStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AppState, Linking, ScrollView, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { AppState, Linking, Platform, ScrollView, View } from 'react-native'
|
||||
|
||||
const TabMePush: React.FC = () => {
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const instance = useSelector(getInstance)
|
||||
const expoToken = useSelector(getExpoToken)
|
||||
const [expoToken] = useGlobalStorage.string('app.expo_token')
|
||||
const [push] = useAccountStorage.object('push')
|
||||
const [domain] = useAccountStorage.string('auth.domain')
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const [accountAcct] = useAccountStorage.string('auth.account.acct')
|
||||
|
||||
const appsQuery = useAppsQuery()
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const instancePush = useSelector(getInstancePush)
|
||||
|
||||
const [pushAvailable, setPushAvailable] = useState<boolean>()
|
||||
const [pushEnabled, setPushEnabled] = useState<boolean>()
|
||||
const [pushCanAskAgain, setPushCanAskAgain] = useState<boolean>()
|
||||
@@ -45,7 +44,7 @@ const TabMePush: React.FC = () => {
|
||||
setPushEnabled(permissions.granted)
|
||||
setPushCanAskAgain(permissions.canAskAgain)
|
||||
layoutAnimation()
|
||||
dispatch(retrieveExpoToken())
|
||||
await updateExpoToken()
|
||||
}
|
||||
|
||||
if (appsQuery.data?.vapid_key) {
|
||||
@@ -54,7 +53,7 @@ const TabMePush: React.FC = () => {
|
||||
if (isDevelopment) {
|
||||
setPushAvailable(true)
|
||||
} else {
|
||||
setPushAvailable(!!expoToken)
|
||||
setPushAvailable(!!expoToken?.length)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,26 +63,29 @@ const TabMePush: React.FC = () => {
|
||||
}
|
||||
}, [appsQuery.data?.vapid_key])
|
||||
|
||||
const pushFeatures = usePushFeatures()
|
||||
|
||||
const alerts = () =>
|
||||
instancePush?.alerts
|
||||
? PUSH_DEFAULT(pushFeatures).map(alert => (
|
||||
push?.alerts
|
||||
? PUSH_DEFAULT.map(alert => (
|
||||
<MenuRow
|
||||
key={alert}
|
||||
title={t(`me.push.${alert}.heading`)}
|
||||
switchDisabled={!pushEnabled || !instancePush.global}
|
||||
switchValue={instancePush?.alerts[alert]}
|
||||
switchOnValueChange={() =>
|
||||
dispatch(
|
||||
updateInstancePushAlert({
|
||||
alerts: {
|
||||
...instancePush?.alerts,
|
||||
[alert]: !instancePush?.alerts[alert]
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
switchDisabled={!pushEnabled || !push.global}
|
||||
switchValue={push?.alerts[alert]}
|
||||
switchOnValueChange={async () => {
|
||||
const alerts = { ...push?.alerts, [alert]: !push?.alerts[alert] }
|
||||
const formData = new FormData()
|
||||
for (const [key, value] of Object.entries(alerts)) {
|
||||
formData.append(`data[alerts][${key}]`, value.toString())
|
||||
}
|
||||
|
||||
await apiInstance<Mastodon.PushSubscription>({
|
||||
method: 'put',
|
||||
url: 'push/subscription',
|
||||
body: formData
|
||||
})
|
||||
|
||||
setAccountStorage([{ key: 'push', value: { ...push, alerts } }])
|
||||
}}
|
||||
/>
|
||||
))
|
||||
: null
|
||||
@@ -91,26 +93,34 @@ const TabMePush: React.FC = () => {
|
||||
const profileQuery = useProfileQuery()
|
||||
const adminAlerts = () =>
|
||||
profileQuery.data?.role?.permissions
|
||||
? PUSH_ADMIN(pushFeatures, profileQuery.data?.role?.permissions).map(({ type }) => (
|
||||
? PUSH_ADMIN.map(({ type }) => (
|
||||
<MenuRow
|
||||
key={type}
|
||||
title={t(`me.push.${type}.heading`)}
|
||||
switchDisabled={!pushEnabled || !instancePush.global}
|
||||
switchValue={instancePush?.alerts[type]}
|
||||
switchOnValueChange={() =>
|
||||
dispatch(
|
||||
updateInstancePushAlert({
|
||||
alerts: {
|
||||
...instancePush?.alerts,
|
||||
[type]: !instancePush?.alerts[type]
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
switchDisabled={!pushEnabled || !push.global}
|
||||
switchValue={push?.alerts[type]}
|
||||
switchOnValueChange={async () => {
|
||||
const alerts = { ...push?.alerts, [type]: !push?.alerts[type] }
|
||||
const formData = new FormData()
|
||||
for (const [key, value] of Object.entries(alerts)) {
|
||||
formData.append(`data[alerts][${key}]`, value.toString())
|
||||
}
|
||||
|
||||
await apiInstance<Mastodon.PushSubscription>({
|
||||
method: 'put',
|
||||
url: 'push/subscription',
|
||||
body: formData
|
||||
})
|
||||
|
||||
setAccountStorage([{ key: 'push', value: { ...push, alerts } }])
|
||||
}}
|
||||
/>
|
||||
))
|
||||
: null
|
||||
|
||||
const pushPath = `${expoToken}/${domain}/${accountId}`
|
||||
const accountFull = `@${accountAcct}@${domain}`
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
{!!appsQuery.data?.vapid_key ? (
|
||||
@@ -142,24 +152,103 @@ const TabMePush: React.FC = () => {
|
||||
) : null}
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('me.push.global.heading', {
|
||||
acct: `@${instance.account.acct}@${instance.uri}`
|
||||
})}
|
||||
title={t('me.push.global.heading', { acct: `@${accountAcct}@${domain}` })}
|
||||
description={t('me.push.global.description')}
|
||||
switchDisabled={!pushEnabled}
|
||||
switchValue={pushEnabled === false ? false : instancePush?.global}
|
||||
switchOnValueChange={() => dispatch(updateInstancePush(!instancePush?.global))}
|
||||
switchValue={pushEnabled === false ? false : push?.global}
|
||||
switchOnValueChange={async () => {
|
||||
if (push.global) {
|
||||
// Turning off
|
||||
await apiInstance({
|
||||
method: 'delete',
|
||||
url: 'push/subscription'
|
||||
})
|
||||
await apiTooot({
|
||||
method: 'delete',
|
||||
url: `push/unsubscribe/${pushPath}`
|
||||
})
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
Notifications.deleteNotificationChannelGroupAsync(accountFull)
|
||||
}
|
||||
|
||||
setAccountStorage([{ key: 'push', value: { ...push, global: false } }])
|
||||
} else {
|
||||
// Turning on
|
||||
const randomPath = (Math.random() + 1).toString(36).substring(2)
|
||||
|
||||
const endpoint = `https://${TOOOT_API_DOMAIN}/push/send/${pushPath}/${randomPath}`
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('subscription[endpoint]', endpoint)
|
||||
formData.append(
|
||||
'subscription[keys][p256dh]',
|
||||
'BMn2PLpZrMefG981elzG6SB1EY9gU7QZwmtZ/a/J2vUeWG+zXgeskMPwHh4T/bxsD4l7/8QT94F57CbZqYRRfJo='
|
||||
)
|
||||
formData.append('subscription[keys][auth]', push.key)
|
||||
for (const [key, value] of Object.entries(push.alerts)) {
|
||||
formData.append(`data[alerts][${key}]`, value.toString())
|
||||
}
|
||||
|
||||
const res = await apiInstance<Mastodon.PushSubscription>({
|
||||
method: 'post',
|
||||
url: 'push/subscription',
|
||||
body: formData
|
||||
})
|
||||
|
||||
if (!res.body.server_key?.length) {
|
||||
displayMessage({
|
||||
type: 'danger',
|
||||
duration: 'long',
|
||||
message: t('me.push.missingServerKey.message'),
|
||||
description: t('me.push.missingServerKey.description')
|
||||
})
|
||||
Sentry.setContext('Push server key', {
|
||||
instance: domain,
|
||||
resBody: res.body
|
||||
})
|
||||
Sentry.captureMessage('Push register error')
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
await apiTooot({
|
||||
method: 'post',
|
||||
url: `push/subscribe/${pushPath}`,
|
||||
body: {
|
||||
accountFull,
|
||||
serverKey: res.body.server_key,
|
||||
auth: push.decode === false ? null : push.key
|
||||
}
|
||||
})
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
setChannels(true)
|
||||
}
|
||||
|
||||
setAccountStorage([{ key: 'push', value: { ...push, global: true } }])
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MenuContainer>
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('me.push.decode.heading')}
|
||||
description={t('me.push.decode.description')}
|
||||
switchDisabled={!pushEnabled || !instancePush?.global}
|
||||
switchValue={instancePush?.decode}
|
||||
switchOnValueChange={() =>
|
||||
dispatch(updateInstancePushDecode(!instancePush?.decode))
|
||||
}
|
||||
switchDisabled={!pushEnabled || !push?.global}
|
||||
switchValue={push?.decode}
|
||||
switchOnValueChange={async () => {
|
||||
await apiTooot({
|
||||
method: 'put',
|
||||
url: `push/update-decode/${pushPath}`,
|
||||
body: { auth: push?.decode ? null : push.key }
|
||||
})
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
setChannels(true)
|
||||
}
|
||||
|
||||
setAccountStorage([{ key: 'push', value: { ...push, decode: !push.decode } }])
|
||||
}}
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('me.push.howitworks')}
|
||||
|
@@ -1,58 +1,44 @@
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||
import { useFollowedTagsQuery } from '@utils/queryHooks/tags'
|
||||
import { getInstanceMePage, updateInstanceMePage } from '@utils/slices/instancesSlice'
|
||||
import { getInstancePush } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const Collections: React.FC = () => {
|
||||
const { t } = useTranslation(['screenAnnouncements', 'screenTabs'])
|
||||
const navigation = useNavigation<any>()
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const mePage = useSelector(getInstanceMePage)
|
||||
const [pageMe, setPageMe] = useAccountStorage.object('page_me')
|
||||
|
||||
useFollowedTagsQuery({
|
||||
options: {
|
||||
onSuccess: data =>
|
||||
dispatch(
|
||||
updateInstanceMePage({
|
||||
followedTags: { shown: !!data?.pages?.[0].body?.length }
|
||||
})
|
||||
)
|
||||
setPageMe({ ...pageMe, followedTags: { shown: !!data?.pages?.[0].body?.length } })
|
||||
}
|
||||
})
|
||||
useListsQuery({
|
||||
options: {
|
||||
onSuccess: data =>
|
||||
dispatch(
|
||||
updateInstanceMePage({
|
||||
lists: { shown: !!data?.length }
|
||||
})
|
||||
)
|
||||
onSuccess: data => setPageMe({ ...pageMe, lists: { shown: !!data?.length } })
|
||||
}
|
||||
})
|
||||
useAnnouncementQuery({
|
||||
showAll: true,
|
||||
options: {
|
||||
onSuccess: data =>
|
||||
dispatch(
|
||||
updateInstanceMePage({
|
||||
announcements: {
|
||||
shown: !!data?.length ? true : false,
|
||||
unread: data?.filter(announcement => !announcement.read).length
|
||||
}
|
||||
})
|
||||
)
|
||||
setPageMe({
|
||||
...pageMe,
|
||||
announcements: {
|
||||
shown: !!data?.length ? true : false,
|
||||
unread: data?.filter(announcement => !announcement.read).length
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const instancePush = useSelector(getInstancePush, (prev, next) => prev?.global === next?.global)
|
||||
const [instancePush] = useAccountStorage.object('push')
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
@@ -74,7 +60,7 @@ const Collections: React.FC = () => {
|
||||
title={t('screenTabs:me.stacks.favourites.name')}
|
||||
onPress={() => navigation.navigate('Tab-Me-Favourites')}
|
||||
/>
|
||||
{mePage.lists.shown ? (
|
||||
{pageMe.lists.shown ? (
|
||||
<MenuRow
|
||||
iconFront='List'
|
||||
iconBack='ChevronRight'
|
||||
@@ -82,7 +68,7 @@ const Collections: React.FC = () => {
|
||||
onPress={() => navigation.navigate('Tab-Me-List-List')}
|
||||
/>
|
||||
) : null}
|
||||
{mePage.followedTags.shown ? (
|
||||
{pageMe.followedTags.shown ? (
|
||||
<MenuRow
|
||||
iconFront='Hash'
|
||||
iconBack='ChevronRight'
|
||||
@@ -90,15 +76,15 @@ const Collections: React.FC = () => {
|
||||
onPress={() => navigation.navigate('Tab-Me-FollowedTags')}
|
||||
/>
|
||||
) : null}
|
||||
{mePage.announcements.shown ? (
|
||||
{pageMe.announcements.shown ? (
|
||||
<MenuRow
|
||||
iconFront='Clipboard'
|
||||
iconBack='ChevronRight'
|
||||
title={t('screenAnnouncements:heading')}
|
||||
content={
|
||||
mePage.announcements.unread
|
||||
pageMe.announcements.unread
|
||||
? t('screenTabs:me.root.announcements.content.unread', {
|
||||
amount: mePage.announcements.unread
|
||||
amount: pageMe.announcements.unread
|
||||
})
|
||||
: t('screenTabs:me.root.announcements.content.read')
|
||||
}
|
||||
|
@@ -1,20 +1,15 @@
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@root/components/haptics'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import removeInstance from '@utils/slices/instances/remove'
|
||||
import { getInstance } from '@utils/slices/instancesSlice'
|
||||
import haptics from '@components/haptics'
|
||||
import { removeAccount, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const Logout: React.FC = () => {
|
||||
const { t } = useTranslation(['common', 'screenTabs'])
|
||||
const dispatch = useAppDispatch()
|
||||
const queryClient = useQueryClient()
|
||||
const instance = useSelector(getInstance)
|
||||
|
||||
const [accountActive] = useGlobalStorage.string('account.active')
|
||||
|
||||
return (
|
||||
<Button
|
||||
@@ -35,10 +30,9 @@ const Logout: React.FC = () => {
|
||||
text: t('screenTabs:me.root.logout.alert.buttons.logout'),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
if (instance) {
|
||||
haptics('Success')
|
||||
queryClient.clear()
|
||||
dispatch(removeInstance(instance))
|
||||
if (accountActive) {
|
||||
haptics('Light')
|
||||
removeAccount(accountActive)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -1,17 +1,16 @@
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { getInstanceActive, getInstanceUrl } from '@utils/slices/instancesSlice'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { getAccountStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const Settings: React.FC = () => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const navigation = useNavigation<any>()
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const url = useSelector(getInstanceUrl)
|
||||
|
||||
const [accountActive] = useGlobalStorage.string('account.active')
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
@@ -21,14 +20,14 @@ const Settings: React.FC = () => {
|
||||
title={t('me.stacks.settings.name')}
|
||||
onPress={() => navigation.navigate('Tab-Me-Settings')}
|
||||
/>
|
||||
{instanceActive !== -1 ? (
|
||||
{accountActive ? (
|
||||
<MenuRow
|
||||
iconFront='Sliders'
|
||||
iconBack='ExternalLink'
|
||||
title={t('me.stacks.webSettings.name')}
|
||||
onPress={async () =>
|
||||
WebBrowser.openAuthSessionAsync(
|
||||
`https://${url}/settings/preferences`,
|
||||
`https://${getAccountStorage.string('auth.domain')}/settings/preferences`,
|
||||
'tooot://tooot',
|
||||
{
|
||||
...(await browserPackage()),
|
||||
|
@@ -10,16 +10,15 @@ import AccountContext from '@screens/Tabs/Shared/Account/utils/createContext'
|
||||
import accountInitialState from '@screens/Tabs/Shared/Account/utils/initialState'
|
||||
import accountReducer from '@screens/Tabs/Shared/Account/utils/reducer'
|
||||
import { useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import React, { useReducer, useRef } from 'react'
|
||||
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const TabMeRoot: React.FC = () => {
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const [accountActive] = useGlobalStorage.string('account.active')
|
||||
|
||||
const { data } = useProfileQuery({
|
||||
options: { enabled: instanceActive !== -1, keepPreviousData: false }
|
||||
options: { enabled: !!accountActive, keepPreviousData: false }
|
||||
})
|
||||
|
||||
const scrollRef = useRef<Animated.ScrollView>(null)
|
||||
@@ -34,18 +33,18 @@ const TabMeRoot: React.FC = () => {
|
||||
|
||||
return (
|
||||
<AccountContext.Provider value={{ accountState, accountDispatch }}>
|
||||
{instanceActive !== -1 && data ? <AccountNav scrollY={scrollY} account={data} /> : null}
|
||||
{accountActive && data ? <AccountNav scrollY={scrollY} account={data} /> : null}
|
||||
<Animated.ScrollView
|
||||
ref={scrollRef}
|
||||
keyboardShouldPersistTaps='handled'
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={16}
|
||||
>
|
||||
{instanceActive !== -1 ? <MyInfo account={data} /> : <ComponentInstance />}
|
||||
{instanceActive !== -1 ? <Collections /> : null}
|
||||
{accountActive ? <MyInfo account={data} /> : <ComponentInstance />}
|
||||
{accountActive ? <Collections /> : null}
|
||||
<Settings />
|
||||
{instanceActive !== -1 ? <AccountInformationSwitch /> : null}
|
||||
{instanceActive !== -1 ? <Logout /> : null}
|
||||
{accountActive ? <AccountInformationSwitch /> : null}
|
||||
{accountActive ? <Logout /> : null}
|
||||
</Animated.ScrollView>
|
||||
</AccountContext.Provider>
|
||||
)
|
@@ -1,19 +1,16 @@
|
||||
import { MenuContainer } from '@components/Menu'
|
||||
import CustomText from '@components/Text'
|
||||
import { getInstanceVersion } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import Constants from 'expo-constants'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const SettingsAnalytics: React.FC = () => {
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const instanceVersion = useSelector(getInstanceVersion, () => true)
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuContainer>
|
||||
@@ -34,7 +31,7 @@ const SettingsAnalytics: React.FC = () => {
|
||||
color: colors.secondary
|
||||
}}
|
||||
>
|
||||
{t('me.settings.instanceVersion', { version: instanceVersion })}
|
||||
{t('me.settings.instanceVersion', { version: getAccountStorage.string('version') })}
|
||||
</CustomText>
|
||||
</MenuContainer>
|
||||
</>
|
||||
|
@@ -1,47 +1,34 @@
|
||||
import haptics from '@components/haptics'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { LOCALES } from '@root/i18n/locales'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import {
|
||||
changeBrowser,
|
||||
changeTheme,
|
||||
getSettingsTheme,
|
||||
getSettingsBrowser,
|
||||
getSettingsFontsize,
|
||||
getSettingsDarkTheme,
|
||||
changeDarkTheme,
|
||||
getSettingsAutoplayGifv,
|
||||
changeAutoplayGifv
|
||||
} from '@utils/slices/settingsSlice'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Localization from 'expo-localization'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Linking, Platform } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { mapFontsizeToName } from '../SettingsFontsize'
|
||||
import { LOCALES } from '@i18n/locales'
|
||||
|
||||
const SettingsApp: React.FC = () => {
|
||||
const navigation = useNavigation<any>()
|
||||
const dispatch = useAppDispatch()
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
const { colors } = useTheme()
|
||||
const { t, i18n } = useTranslation(['common', 'screenTabs'])
|
||||
|
||||
const settingsFontsize = useSelector(getSettingsFontsize)
|
||||
const settingsTheme = useSelector(getSettingsTheme)
|
||||
const settingsDarkTheme = useSelector(getSettingsDarkTheme)
|
||||
const settingsBrowser = useSelector(getSettingsBrowser)
|
||||
const settingsAutoplayGifv = useSelector(getSettingsAutoplayGifv)
|
||||
const [fontSize] = useGlobalStorage.number('app.font_size')
|
||||
const [theme, setTheme] = useGlobalStorage.string('app.theme')
|
||||
const [themeDark, setThemeDark] = useGlobalStorage.string('app.theme.dark')
|
||||
const [browser, setBrowser] = useGlobalStorage.string('app.browser')
|
||||
const [autoplayGifv, setAutoplayGifv] = useGlobalStorage.boolean('app.auto_play_gifv')
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('screenTabs:me.stacks.fontSize.name')}
|
||||
content={t(`screenTabs:me.fontSize.sizes.${mapFontsizeToName(settingsFontsize)}`)}
|
||||
content={t(`screenTabs:me.fontSize.sizes.${mapFontsizeToName(fontSize || 0)}`)}
|
||||
iconBack='ChevronRight'
|
||||
onPress={() => navigation.navigate('Tab-Me-Settings-Fontsize')}
|
||||
/>
|
||||
@@ -50,7 +37,9 @@ const SettingsApp: React.FC = () => {
|
||||
content={
|
||||
// @ts-ignore
|
||||
LOCALES[
|
||||
Platform.OS === 'ios' ? Localization.locale.toLowerCase() : i18n.language.toLowerCase()
|
||||
Platform.OS === 'ios'
|
||||
? Localization.locale.toLowerCase().replace(new RegExp(/.*-.*(-.*)/, 'i'), '')
|
||||
: i18n.language.toLowerCase()
|
||||
]
|
||||
}
|
||||
iconBack='ChevronRight'
|
||||
@@ -62,7 +51,7 @@ const SettingsApp: React.FC = () => {
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('screenTabs:me.settings.theme.heading')}
|
||||
content={t(`screenTabs:me.settings.theme.options.${settingsTheme}`)}
|
||||
content={t(`screenTabs:me.settings.theme.options.${theme || 'auto'}`)}
|
||||
iconBack='ChevronRight'
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
@@ -80,16 +69,16 @@ const SettingsApp: React.FC = () => {
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
haptics('Success')
|
||||
dispatch(changeTheme('auto'))
|
||||
haptics('Light')
|
||||
setTheme('auto')
|
||||
break
|
||||
case 1:
|
||||
haptics('Success')
|
||||
dispatch(changeTheme('light'))
|
||||
haptics('Light')
|
||||
setTheme('light')
|
||||
break
|
||||
case 2:
|
||||
haptics('Success')
|
||||
dispatch(changeTheme('dark'))
|
||||
haptics('Light')
|
||||
setTheme('dark')
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -98,7 +87,7 @@ const SettingsApp: React.FC = () => {
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('screenTabs:me.settings.darkTheme.heading')}
|
||||
content={t(`screenTabs:me.settings.darkTheme.options.${settingsDarkTheme}`)}
|
||||
content={t(`screenTabs:me.settings.darkTheme.options.${themeDark || 'lighter'}`)}
|
||||
iconBack='ChevronRight'
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
@@ -115,12 +104,12 @@ const SettingsApp: React.FC = () => {
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
haptics('Success')
|
||||
dispatch(changeDarkTheme('lighter'))
|
||||
haptics('Light')
|
||||
setThemeDark('lighter')
|
||||
break
|
||||
case 1:
|
||||
haptics('Success')
|
||||
dispatch(changeDarkTheme('darker'))
|
||||
haptics('Light')
|
||||
setThemeDark('darker')
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -129,7 +118,7 @@ const SettingsApp: React.FC = () => {
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('screenTabs:me.settings.browser.heading')}
|
||||
content={t(`screenTabs:me.settings.browser.options.${settingsBrowser}`)}
|
||||
content={t(`screenTabs:me.settings.browser.options.${browser || 'internal'}`)}
|
||||
iconBack='ChevronRight'
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
@@ -146,12 +135,12 @@ const SettingsApp: React.FC = () => {
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
haptics('Success')
|
||||
dispatch(changeBrowser('internal'))
|
||||
haptics('Light')
|
||||
setBrowser('internal')
|
||||
break
|
||||
case 1:
|
||||
haptics('Success')
|
||||
dispatch(changeBrowser('external'))
|
||||
haptics('Light')
|
||||
setBrowser('external')
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -160,8 +149,8 @@ const SettingsApp: React.FC = () => {
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('screenTabs:me.settings.autoplayGifv.heading')}
|
||||
switchValue={settingsAutoplayGifv}
|
||||
switchOnValueChange={() => dispatch(changeAutoplayGifv(!settingsAutoplayGifv))}
|
||||
switchValue={autoplayGifv}
|
||||
switchOnValueChange={() => setAutoplayGifv(!autoplayGifv)}
|
||||
/>
|
||||
</MenuContainer>
|
||||
)
|
||||
|
@@ -1,64 +1,34 @@
|
||||
import Button from '@components/Button'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import CustomText from '@components/Text'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { persistor } from '@root/store'
|
||||
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { storage } from '@utils/storage'
|
||||
import { getGlobalStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Localization from 'expo-localization'
|
||||
import React from 'react'
|
||||
import { DevSettings } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { MMKV } from 'react-native-mmkv'
|
||||
|
||||
const SettingsDev: React.FC = () => {
|
||||
const { colors } = useTheme()
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const instances = useSelector(getInstances, () => true)
|
||||
|
||||
const [accounts] = useGlobalStorage.object('accounts')
|
||||
const [account] = useGlobalStorage.string('account.active')
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
selectable
|
||||
style={{
|
||||
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
color: colors.primaryDefault
|
||||
}}
|
||||
>
|
||||
{JSON.stringify(Localization.locales)}
|
||||
</CustomText>
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
selectable
|
||||
style={{
|
||||
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
color: colors.primaryDefault
|
||||
}}
|
||||
>
|
||||
{instances[instanceActive]?.token}
|
||||
</CustomText>
|
||||
<MenuRow
|
||||
title={'Local active index'}
|
||||
content={typeof instanceActive + ' - ' + instanceActive}
|
||||
onPress={() => {}}
|
||||
/>
|
||||
<MenuRow title='Active account' content={account || '-'} onPress={() => {}} />
|
||||
<MenuRow
|
||||
title={'Saved local instances'}
|
||||
content={instances.length.toString()}
|
||||
content={accounts?.length.toString()}
|
||||
iconBack='ChevronRight'
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
options: instances
|
||||
.map(instance => {
|
||||
return instance.url + ': ' + instance.account.id
|
||||
})
|
||||
.concat(['Cancel']),
|
||||
cancelButtonIndex: instances.length,
|
||||
options: (accounts || []).concat(['Cancel']),
|
||||
cancelButtonIndex: accounts?.length,
|
||||
...androidActionSheetStyles(colors)
|
||||
},
|
||||
() => {}
|
||||
@@ -76,14 +46,24 @@ const SettingsDev: React.FC = () => {
|
||||
/>
|
||||
<Button
|
||||
type='text'
|
||||
content={'Purge secure storage'}
|
||||
content={'Purge MMKV'}
|
||||
style={{
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2,
|
||||
marginBottom: StyleConstants.Spacing.Global.PagePadding
|
||||
}}
|
||||
destructive
|
||||
onPress={() => {
|
||||
persistor.purge().then(() => DevSettings.reload())
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
if (!accounts) return
|
||||
|
||||
for (const account of accounts) {
|
||||
console.log('Clearing', account)
|
||||
const temp = new MMKV({ id: account })
|
||||
temp.clearAll()
|
||||
}
|
||||
|
||||
console.log('Clearing', 'global')
|
||||
storage.global.clearAll()
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
|
@@ -1,27 +1,24 @@
|
||||
import Icon from '@components/Icon'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { getAccountStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import Constants from 'expo-constants'
|
||||
import * as Linking from 'expo-linking'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getInstanceActive, getInstanceVersion } from '@utils/slices/instancesSlice'
|
||||
import { Platform } from 'react-native'
|
||||
import Constants from 'expo-constants'
|
||||
import { getExpoToken } from '@utils/slices/appSlice'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
|
||||
const SettingsTooot: React.FC = () => {
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const navigation = useNavigation<any>()
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const instanceVersion = useSelector(getInstanceVersion, () => true)
|
||||
const expoToken = useSelector(getExpoToken)
|
||||
const [accountActive] = useGlobalStorage.string('account.active')
|
||||
const [expoToken] = useGlobalStorage.string('app.expo_token')
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
@@ -44,7 +41,7 @@ const SettingsTooot: React.FC = () => {
|
||||
content={<Icon name='Mail' size={StyleConstants.Font.Size.M} color={colors.secondary} />}
|
||||
iconBack='ChevronRight'
|
||||
onPress={async () => {
|
||||
if (instanceActive !== -1) {
|
||||
if (accountActive) {
|
||||
navigation.navigate('Screen-Compose', {
|
||||
type: 'conversation',
|
||||
accts: ['tooot@xmflsct.com'],
|
||||
@@ -55,9 +52,9 @@ const SettingsTooot: React.FC = () => {
|
||||
' - ' +
|
||||
(Constants.expoConfig?.version ? `t/${Constants.expoConfig?.version}` : '') +
|
||||
' - ' +
|
||||
(instanceVersion ? `m/${instanceVersion}` : '') +
|
||||
`m/${getAccountStorage.string('version')}` +
|
||||
' - ' +
|
||||
(expoToken
|
||||
(expoToken?.length
|
||||
? `e/${expoToken.replace(/^ExponentPushToken\[/, '').replace(/\]$/, '')}`
|
||||
: '') +
|
||||
']'
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { isDevelopment } from '@utils/checkEnvironment'
|
||||
import { isDevelopment } from '@utils/helpers/checkEnvironment'
|
||||
import React from 'react'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import SettingsAnalytics from './Settings/Analytics'
|
||||
import SettingsApp from './Settings/App'
|
||||
import SettingsDev from './Settings/Dev'
|
||||
import SettingsTooot from './Settings/Tooot'
|
||||
import SettingsAnalytics from './Analytics'
|
||||
import SettingsApp from './App'
|
||||
import SettingsDev from './Dev'
|
||||
import SettingsTooot from './Tooot'
|
||||
|
||||
const TabMeSettings: React.FC = () => {
|
||||
return (
|
@@ -3,20 +3,18 @@ import haptics from '@components/haptics'
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import CustomText from '@components/Text'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { SettingsLatest } from '@utils/migrations/settings/migration'
|
||||
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { changeFontsize, getSettingsFontsize } from '@utils/slices/settingsSlice'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StorageGlobal } from '@utils/storage/global'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { adaptiveScale } from '@utils/styles/scaling'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useMemo } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export const mapFontsizeToName = (size: SettingsLatest['fontsize']) => {
|
||||
export const mapFontsizeToName = (size: StorageGlobal['app.font_size']) => {
|
||||
switch (size) {
|
||||
case -1:
|
||||
return 'S'
|
||||
@@ -28,14 +26,16 @@ export const mapFontsizeToName = (size: SettingsLatest['fontsize']) => {
|
||||
return 'XL'
|
||||
case 3:
|
||||
return 'XXL'
|
||||
default:
|
||||
return 'M'
|
||||
}
|
||||
}
|
||||
|
||||
const TabMeSettingsFontsize: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Fontsize'>> = () => {
|
||||
const { colors, theme } = useTheme()
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const initialSize = useSelector(getSettingsFontsize)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const [fontSize, setFontSize] = useGlobalStorage.number('app.font_size')
|
||||
|
||||
const item = {
|
||||
id: 'demo',
|
||||
@@ -69,31 +69,6 @@ const TabMeSettingsFontsize: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Fon
|
||||
mentions: []
|
||||
}
|
||||
|
||||
const sizesDemo = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{([-1, 0, 1, 2, 3] as [-1, 0, 1, 2, 3]).map(size => (
|
||||
<CustomText
|
||||
key={size}
|
||||
style={{
|
||||
marginHorizontal: StyleConstants.Spacing.XS,
|
||||
paddingHorizontal: StyleConstants.Spacing.XS,
|
||||
marginBottom: StyleConstants.Spacing.M,
|
||||
fontSize: adaptiveScale(StyleConstants.Font.Size.M, size),
|
||||
lineHeight: adaptiveScale(StyleConstants.Font.LineHeight.M, size),
|
||||
color: initialSize === size ? colors.primaryDefault : colors.secondary,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: colors.border
|
||||
}}
|
||||
fontWeight={initialSize === size ? 'Bold' : undefined}
|
||||
>
|
||||
{t(`me.fontSize.sizes.${mapFontsizeToName(size)}`)}
|
||||
</CustomText>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}, [theme, initialSize])
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<View
|
||||
@@ -104,7 +79,24 @@ const TabMeSettingsFontsize: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Fon
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
}}
|
||||
>
|
||||
{sizesDemo}
|
||||
{([-1, 0, 1, 2, 3] as [-1, 0, 1, 2, 3]).map(size => (
|
||||
<CustomText
|
||||
key={size}
|
||||
style={{
|
||||
marginHorizontal: StyleConstants.Spacing.XS,
|
||||
paddingHorizontal: StyleConstants.Spacing.XS,
|
||||
marginBottom: StyleConstants.Spacing.M,
|
||||
fontSize: adaptiveScale(StyleConstants.Font.Size.M, size),
|
||||
lineHeight: adaptiveScale(StyleConstants.Font.LineHeight.M, size),
|
||||
color: fontSize === size ? colors.primaryDefault : colors.secondary,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: colors.border
|
||||
}}
|
||||
fontWeight={fontSize === size ? 'Bold' : undefined}
|
||||
>
|
||||
{t(`me.fontSize.sizes.${mapFontsizeToName(size)}`)}
|
||||
</CustomText>
|
||||
))}
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
@@ -115,38 +107,34 @@ const TabMeSettingsFontsize: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Fon
|
||||
>
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (initialSize > -1) {
|
||||
if (fontSize && fontSize > -1) {
|
||||
haptics('Light')
|
||||
// @ts-ignore
|
||||
dispatch(changeFontsize(initialSize - 1))
|
||||
setFontSize(fontSize - 1)
|
||||
}
|
||||
}}
|
||||
type='icon'
|
||||
content='Minus'
|
||||
round
|
||||
disabled={initialSize <= -1}
|
||||
disabled={(fontSize || 0) <= -1}
|
||||
style={{ marginHorizontal: StyleConstants.Spacing.S }}
|
||||
/>
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (initialSize < 3) {
|
||||
if (fontSize && fontSize < 3) {
|
||||
haptics('Light')
|
||||
// @ts-ignore
|
||||
dispatch(changeFontsize(initialSize + 1))
|
||||
setFontSize(fontSize + 1)
|
||||
}
|
||||
}}
|
||||
type='icon'
|
||||
content='Plus'
|
||||
round
|
||||
disabled={initialSize >= 3}
|
||||
disabled={(fontSize || 0) >= 3}
|
||||
style={{ marginHorizontal: StyleConstants.Spacing.S }}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
marginVertical: StyleConstants.Spacing.L
|
||||
}}
|
||||
>
|
||||
<View style={{ marginVertical: StyleConstants.Spacing.L }}>
|
||||
<ComponentSeparator
|
||||
extraMarginLeft={-StyleConstants.Spacing.Global.PagePadding}
|
||||
extraMarginRight={-StyleConstants.Spacing.Global.PagePadding}
|
||||
|
@@ -1,33 +1,32 @@
|
||||
import haptics from '@components/haptics'
|
||||
import { MenuRow } from '@components/Menu'
|
||||
import { LOCALES } from '@root/i18n/locales'
|
||||
import { LOCALES } from '@i18n/locales'
|
||||
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { setChannels } from '@utils/slices/instances/push/utils'
|
||||
import { getInstances } from '@utils/slices/instancesSlice'
|
||||
import { changeLanguage } from '@utils/slices/settingsSlice'
|
||||
import { setChannels } from '@utils/push/constants'
|
||||
import { getGlobalStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FlatList, Platform } from 'react-native'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
const TabMeSettingsLanguage: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Language'>> = ({
|
||||
navigation
|
||||
}) => {
|
||||
const { i18n } = useTranslation('screenTabs')
|
||||
const languages = Object.entries(LOCALES)
|
||||
const instances = useSelector(getInstances)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [_, setLanguage] = useGlobalStorage.string('app.language')
|
||||
|
||||
const change = (lang: string) => {
|
||||
haptics('Success')
|
||||
|
||||
dispatch(changeLanguage(lang))
|
||||
setLanguage(lang)
|
||||
i18n.changeLanguage(lang)
|
||||
|
||||
// Update Android notification channel language
|
||||
if (Platform.OS === 'android') {
|
||||
instances.forEach(instance => setChannels(instance, true))
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
accounts?.forEach(account => setChannels(true, account))
|
||||
}
|
||||
|
||||
navigation.pop(1)
|
||||
|
@@ -1,27 +1,23 @@
|
||||
import AccountButton from '@components/AccountButton'
|
||||
import ComponentInstance from '@components/Instance'
|
||||
import CustomText from '@components/Text'
|
||||
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
||||
import { getGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { KeyboardAvoidingView, Platform, StyleSheet, View } from 'react-native'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const TabMeSwitch: React.FC = () => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const { colors } = useTheme()
|
||||
const instances = useSelector(getInstances, () => true)
|
||||
const instanceActive = useSelector(getInstanceActive, () => true)
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
const accountActive = getGlobalStorage.string('account.active')
|
||||
|
||||
const scrollViewRef = useRef<ScrollView>(null)
|
||||
useEffect(() => {
|
||||
setTimeout(
|
||||
() => scrollViewRef.current?.scrollToEnd({ animated: true }),
|
||||
150
|
||||
)
|
||||
setTimeout(() => scrollViewRef.current?.scrollToEnd({ animated: true }), 150)
|
||||
}, [scrollViewRef.current])
|
||||
|
||||
return (
|
||||
@@ -45,11 +41,7 @@ const TabMeSwitch: React.FC = () => {
|
||||
>
|
||||
{t('me.switch.new')}
|
||||
</CustomText>
|
||||
<ComponentInstance
|
||||
scrollViewRef={scrollViewRef}
|
||||
disableHeaderImage
|
||||
goBack
|
||||
/>
|
||||
<ComponentInstance scrollViewRef={scrollViewRef} disableHeaderImage goBack />
|
||||
</View>
|
||||
|
||||
<View
|
||||
@@ -79,29 +71,19 @@ const TabMeSwitch: React.FC = () => {
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
}}
|
||||
>
|
||||
{instances.length
|
||||
? instances
|
||||
.slice()
|
||||
.sort((a, b) =>
|
||||
`${a.uri}${a.account.acct}`.localeCompare(
|
||||
`${b.uri}${b.account.acct}`
|
||||
)
|
||||
{accounts &&
|
||||
accounts
|
||||
.slice()
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.map((account, index) => {
|
||||
return (
|
||||
<AccountButton
|
||||
key={index}
|
||||
account={account}
|
||||
selected={account === accountActive}
|
||||
/>
|
||||
)
|
||||
.map((instance, index) => {
|
||||
const localAccount = instances[instanceActive!]
|
||||
return (
|
||||
<AccountButton
|
||||
key={index}
|
||||
instance={instance}
|
||||
selected={
|
||||
instance.url === localAccount.url &&
|
||||
instance.token === localAccount.token &&
|
||||
instance.account.id === localAccount.account.id
|
||||
}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
@@ -3,6 +3,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { TabMeStackParamList } from '@utils/navigation/navigators'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import TabShared from '../Shared'
|
||||
import TabMeBookmarks from './Bookmarks'
|
||||
import TabMeConversations from './Cconversations'
|
||||
import TabMeFavourites from './Favourites'
|
||||
@@ -18,7 +19,6 @@ import TabMeSettings from './Settings'
|
||||
import TabMeSettingsFontsize from './SettingsFontsize'
|
||||
import TabMeSettingsLanguage from './SettingsLanguage'
|
||||
import TabMeSwitch from './Switch'
|
||||
import TabShared from '../Shared'
|
||||
|
||||
const Stack = createNativeStackNavigator<TabMeStackParamList>()
|
||||
|
||||
|
@@ -1,32 +1,22 @@
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { TabNotificationsStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import { PUSH_ADMIN, PUSH_DEFAULT } from '@utils/push/constants'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { PUSH_ADMIN, PUSH_DEFAULT, usePushFeatures } from '@utils/slices/instances/push/utils'
|
||||
import {
|
||||
getInstanceNotificationsFilter,
|
||||
updateInstanceNotificationsFilter
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { setAccountStorage, useAccountStorage } from '@utils/storage/actions'
|
||||
import { isEqual } from 'lodash'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const TabNotificationsFilters: React.FC<
|
||||
TabNotificationsStackScreenProps<'Tab-Notifications-Filters'>
|
||||
> = ({ navigation }) => {
|
||||
const { t } = useTranslation(['common', 'screenTabs'])
|
||||
|
||||
const pushFeatures = usePushFeatures()
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const instanceNotificationsFilter = useSelector(getInstanceNotificationsFilter)
|
||||
const [instanceNotificationsFilter] = useAccountStorage.object('notifications')
|
||||
const [filters, setFilters] = useState(instanceNotificationsFilter)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
@@ -62,7 +52,7 @@ const TabNotificationsFilters: React.FC<
|
||||
content={t('common:buttons.apply')}
|
||||
onPress={() => {
|
||||
if (changed) {
|
||||
dispatch(updateInstanceNotificationsFilter(filters))
|
||||
setAccountStorage([{ key: 'notifications', value: filters }])
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Notifications' }]
|
||||
queryClient.invalidateQueries({ queryKey })
|
||||
}
|
||||
@@ -73,12 +63,10 @@ const TabNotificationsFilters: React.FC<
|
||||
})
|
||||
}, [filters])
|
||||
|
||||
const profileQuery = useProfileQuery()
|
||||
|
||||
return (
|
||||
<ScrollView style={{ flex: 1 }}>
|
||||
<MenuContainer>
|
||||
{PUSH_DEFAULT(pushFeatures).map((type, index) => (
|
||||
{PUSH_DEFAULT.map((type, index) => (
|
||||
<MenuRow
|
||||
key={index}
|
||||
title={t(`screenTabs:me.push.${type}.heading`)}
|
||||
@@ -86,7 +74,7 @@ const TabNotificationsFilters: React.FC<
|
||||
switchOnValueChange={() => setFilters({ ...filters, [type]: !filters[type] })}
|
||||
/>
|
||||
))}
|
||||
{PUSH_ADMIN(pushFeatures, profileQuery.data?.role?.permissions).map(({ type }) => (
|
||||
{PUSH_ADMIN.map(({ type }) => (
|
||||
<MenuRow
|
||||
key={type}
|
||||
title={t(`screenTabs:me.push.${type}.heading`)}
|
||||
|
@@ -3,18 +3,16 @@ import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import SegmentedControl from '@react-native-community/segmented-control'
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { ContextsLatest } from '@utils/migrations/contexts/migration'
|
||||
import { TabPublicStackParamList } from '@utils/navigation/navigators'
|
||||
import usePopToTop from '@utils/navigation/usePopToTop'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getPreviousSegment, updatePreviousSegment } from '@utils/slices/contextsSlice'
|
||||
import { getGlobalStorage, setGlobalStorage } from '@utils/storage/actions'
|
||||
import { StorageGlobal } from '@utils/storage/global'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dimensions } from 'react-native'
|
||||
import { SceneMap, TabView } from 'react-native-tab-view'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const Route = ({ route: { key: page } }: { route: any }) => {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page }]
|
||||
@@ -41,9 +39,8 @@ const Root: React.FC<NativeStackScreenProps<TabPublicStackParamList, 'Tab-Public
|
||||
const { mode } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const previousSegment = useSelector(getPreviousSegment, () => true)
|
||||
const segments: ContextsLatest['previousSegment'][] = ['Local', 'LocalPublic', 'Trending']
|
||||
const previousSegment = getGlobalStorage.string('app.prev_public_segment')
|
||||
const segments: StorageGlobal['app.prev_public_segment'][] = ['Local', 'LocalPublic', 'Trending']
|
||||
const [segment, setSegment] = useState<number>(
|
||||
segments.findIndex(segment => segment === previousSegment)
|
||||
)
|
||||
@@ -62,7 +59,7 @@ const Root: React.FC<NativeStackScreenProps<TabPublicStackParamList, 'Tab-Public
|
||||
selectedIndex={segment}
|
||||
onChange={({ nativeEvent }) => {
|
||||
setSegment(nativeEvent.selectedSegmentIndex)
|
||||
dispatch(updatePreviousSegment(segments[nativeEvent.selectedSegmentIndex]))
|
||||
setGlobalStorage('app.prev_public_segment', segments[nativeEvent.selectedSegmentIndex])
|
||||
}}
|
||||
style={{ flexBasis: '65%' }}
|
||||
/>
|
||||
|
@@ -1,10 +1,9 @@
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import navigationRef from '@helpers/navigationRef'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import React from 'react'
|
||||
import { Dimensions, Image } from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export interface Props {
|
||||
account?: Mastodon.Account
|
||||
@@ -13,7 +12,7 @@ export interface Props {
|
||||
const AccountHeader: React.FC<Props> = ({ account }) => {
|
||||
const topInset = useSafeAreaInsets().top
|
||||
|
||||
useSelector(getInstanceActive)
|
||||
useGlobalStorage.string('account.active')
|
||||
|
||||
return (
|
||||
<GracefullyImage
|
||||
|
@@ -3,7 +3,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { Placeholder, Fade } from 'rn-placeholder'
|
||||
import { Fade, Placeholder } from 'rn-placeholder'
|
||||
import AccountInformationAccount from './Information/Account'
|
||||
import AccountInformationActions from './Information/Actions'
|
||||
import AccountInformationAvatar from './Information/Avatar'
|
||||
@@ -37,7 +37,7 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
||||
|
||||
<AccountInformationName account={account} />
|
||||
|
||||
<AccountInformationAccount account={account} />
|
||||
<AccountInformationAccount account={account} myInfo={myInfo} />
|
||||
|
||||
<AccountInformationFields account={account} myInfo={myInfo} />
|
||||
|
||||
|
@@ -1,35 +1,34 @@
|
||||
import Icon from '@components/Icon'
|
||||
import CustomText from '@components/Text'
|
||||
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
|
||||
import { getInstanceAccount, getInstanceUri } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage, useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { PlaceholderLine } from 'rn-placeholder'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account | undefined
|
||||
myInfo?: boolean
|
||||
}
|
||||
|
||||
const AccountInformationAccount: React.FC<Props> = ({ account }) => {
|
||||
const AccountInformationAccount: React.FC<Props> = ({ account, myInfo }) => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const { colors } = useTheme()
|
||||
const instanceAccount = useSelector(getInstanceAccount, (prev, next) => prev?.acct === next?.acct)
|
||||
const instanceUri = useSelector(getInstanceUri)
|
||||
|
||||
const [acct] = useAccountStorage.string('auth.account.acct')
|
||||
const domain = getAccountStorage.string('auth.domain')
|
||||
|
||||
const { data: relationship } = useRelationshipQuery({
|
||||
id: account?.id || '',
|
||||
options: { enabled: account !== undefined }
|
||||
})
|
||||
|
||||
const localInstance = account?.acct.includes('@')
|
||||
? account?.acct.includes(`@${instanceUri}`)
|
||||
: true
|
||||
const localInstance = account?.acct.includes('@') ? account?.acct.includes(`@${domain}`) : true
|
||||
|
||||
if (account || (localInstance && instanceAccount)) {
|
||||
if (account || localInstance) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@@ -52,8 +51,8 @@ const AccountInformationAccount: React.FC<Props> = ({ account }) => {
|
||||
}}
|
||||
selectable
|
||||
>
|
||||
@{account?.acct}
|
||||
{localInstance ? `@${instanceUri}` : null}
|
||||
@{myInfo ? acct : account?.acct}
|
||||
{localInstance ? `@${domain}` : null}
|
||||
</CustomText>
|
||||
{relationship?.followed_by ? t('shared.account.followed_by') : null}
|
||||
</CustomText>
|
||||
|
@@ -3,12 +3,11 @@ import menuAt from '@components/contextMenu/at'
|
||||
import { RelationshipOutgoing } from '@components/Relationship'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
|
||||
export interface Props {
|
||||
@@ -50,8 +49,8 @@ const AccountInformationActions: React.FC<Props> = ({ account, myInfo }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||
const ownAccount = account?.id === instanceAccount?.id && account?.acct === instanceAccount?.acct
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const ownAccount = account?.id === accountId
|
||||
|
||||
const query = useRelationshipQuery({ id: account.id })
|
||||
const mAt = menuAt({ account })
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import navigationRef from '@helpers/navigationRef'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account | undefined
|
||||
@@ -15,7 +14,9 @@ export interface Props {
|
||||
|
||||
const AccountInformationAvatar: React.FC<Props> = ({ account, myInfo }) => {
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
useSelector(getInstanceActive)
|
||||
|
||||
useGlobalStorage.string('account.active')
|
||||
|
||||
return (
|
||||
<GracefullyImage
|
||||
key={account?.avatar}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import CustomText from '@components/Text'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { StyleConstants } from '@root/utils/styles/constants'
|
||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
|
@@ -15,10 +15,10 @@ import { useTranslation } from 'react-i18next'
|
||||
import { Text, View } from 'react-native'
|
||||
import { useSharedValue } from 'react-native-reanimated'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
import AccountAttachments from './Account/Attachments'
|
||||
import AccountHeader from './Account/Header'
|
||||
import AccountInformation from './Account/Information'
|
||||
import AccountNav from './Account/Nav'
|
||||
import AccountAttachments from './Attachments'
|
||||
import AccountHeader from './Header'
|
||||
import AccountInformation from './Information'
|
||||
import AccountNav from './Nav'
|
||||
|
||||
const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>> = ({
|
||||
navigation,
|
@@ -4,13 +4,12 @@ import { displayMessage } from '@components/Message'
|
||||
import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { QueryKeyFollowedTags, useTagsMutation, useTagsQuery } from '@utils/queryHooks/tags'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const TabSharedHashtag: React.FC<TabSharedStackScreenProps<'Tab-Shared-Hashtag'>> = ({
|
||||
navigation,
|
||||
@@ -29,7 +28,7 @@ const TabSharedHashtag: React.FC<TabSharedStackScreenProps<'Tab-Shared-Hashtag'>
|
||||
|
||||
const { t } = useTranslation(['common', 'screenTabs'])
|
||||
|
||||
const canFollowTags = useSelector(checkInstanceFeature('follow_tags'))
|
||||
const canFollowTags = featureCheck('follow_tags')
|
||||
const { data, isFetching, refetch } = useTagsQuery({
|
||||
tag: hashtag,
|
||||
options: { enabled: canFollowTags }
|
||||
|
@@ -6,7 +6,7 @@ import CustomText from '@components/Text'
|
||||
import TimelineAttachment from '@components/Timeline/Shared/Attachment'
|
||||
import StatusContext from '@components/Timeline/Shared/Context'
|
||||
import HeaderSharedCreated from '@components/Timeline/Shared/HeaderShared/Created'
|
||||
import removeHTML from '@helpers/removeHTML'
|
||||
import removeHTML from '@utils/helpers/removeHTML'
|
||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useStatusHistory } from '@utils/queryHooks/statusesHistory'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
|
@@ -1,18 +1,17 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import ComponentAccount from '@components/Account'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import Selections from '@components/Selections'
|
||||
import CustomText from '@components/Text'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useRulesQuery } from '@utils/queryHooks/reports'
|
||||
import { getInstanceUri } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform, ScrollView, TextInput, View } from 'react-native'
|
||||
import { Switch } from 'react-native-gesture-handler'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>> = ({
|
||||
navigation,
|
||||
@@ -74,9 +73,8 @@ const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>>
|
||||
})
|
||||
}, [isReporting, comment, forward, categories, rules])
|
||||
|
||||
const instanceUri = useSelector(getInstanceUri)
|
||||
const localInstance = account?.acct.includes('@')
|
||||
? account?.acct.includes(`@${instanceUri}`)
|
||||
? account?.acct.includes(`@${getAccountStorage.string('auth.domain')}`)
|
||||
: true
|
||||
|
||||
const rulesQuery = useRulesQuery()
|
||||
|
@@ -1,14 +1,14 @@
|
||||
import { HeaderLeft } from '@components/Header'
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { InfiniteQueryObserver, useQueryClient } from '@tanstack/react-query'
|
||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FlatList, View } from 'react-native'
|
||||
import { InfiniteQueryObserver, useQueryClient } from '@tanstack/react-query'
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
|
||||
const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
navigation,
|
||||
@@ -132,7 +132,9 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ marginLeft: Math.max(0, levels.current - 1) * StyleConstants.Spacing.S }}>
|
||||
<View
|
||||
style={{ marginLeft: Math.max(0, levels.current - 1) * StyleConstants.Spacing.S }}
|
||||
>
|
||||
<TimelineDefault
|
||||
item={item}
|
||||
queryKey={queryKey}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import ComponentAccount from '@components/Account'
|
||||
import { HeaderLeft } from '@components/Header'
|
||||
import Icon from '@components/Icon'
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import CustomText from '@components/Text'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { SearchResult } from '@utils/queryHooks/search'
|
||||
import { QueryKeyUsers, useUsersQuery } from '@utils/queryHooks/users'
|
||||
|
@@ -3,27 +3,22 @@ import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
||||
import { RootStackScreenProps, ScreenTabsStackParamList } from '@utils/navigation/navigators'
|
||||
import { getPreviousTab } from '@utils/slices/contextsSlice'
|
||||
import { getInstanceAccount, getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import { getGlobalStorage, useAccountStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import TabLocal from './Tabs/Local'
|
||||
import TabMe from './Tabs/Me'
|
||||
import TabNotifications from './Tabs/Notifications'
|
||||
import TabPublic from './Tabs/Public'
|
||||
import TabLocal from './Local'
|
||||
import TabMe from './Me'
|
||||
import TabNotifications from './Notifications'
|
||||
import TabPublic from './Public'
|
||||
|
||||
const Tab = createBottomTabNavigator<ScreenTabsStackParamList>()
|
||||
|
||||
const ScreenTabs = ({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const instanceAccount = useSelector(
|
||||
getInstanceAccount,
|
||||
(prev, next) => prev?.avatarStatic === next?.avatarStatic
|
||||
)
|
||||
const [accountActive] = useGlobalStorage.string('account.active')
|
||||
const [avatarStatic] = useAccountStorage.string('auth.account.avatar_static')
|
||||
|
||||
const composeListeners = useMemo(
|
||||
() => ({
|
||||
@@ -50,18 +45,16 @@ const ScreenTabs = ({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
||||
[]
|
||||
)
|
||||
|
||||
const previousTab = useSelector(getPreviousTab, () => true)
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName={instanceActive !== -1 ? previousTab : 'Tab-Me'}
|
||||
initialRouteName={accountActive ? getGlobalStorage.string('app.prev_tab') : 'Tab-Me'}
|
||||
screenOptions={({ route }) => ({
|
||||
headerShown: false,
|
||||
tabBarActiveTintColor: colors.primaryDefault,
|
||||
tabBarInactiveTintColor: colors.secondary,
|
||||
tabBarShowLabel: false,
|
||||
...(Platform.OS === 'android' && { tabBarHideOnKeyboard: true }),
|
||||
tabBarStyle: { display: instanceActive !== -1 ? 'flex' : 'none' },
|
||||
tabBarStyle: { display: accountActive ? 'flex' : 'none' },
|
||||
tabBarIcon: ({
|
||||
focused,
|
||||
color,
|
||||
@@ -83,8 +76,7 @@ const ScreenTabs = ({ navigation }: RootStackScreenProps<'Screen-Tabs'>) => {
|
||||
case 'Tab-Me':
|
||||
return (
|
||||
<GracefullyImage
|
||||
key={instanceAccount?.avatarStatic}
|
||||
uri={{ original: instanceAccount?.avatarStatic }}
|
||||
uri={{ original: avatarStatic }}
|
||||
dimension={{
|
||||
width: size,
|
||||
height: size
|
326
src/screens/index.tsx
Normal file
326
src/screens/index.tsx
Normal file
@@ -0,0 +1,326 @@
|
||||
import { HeaderLeft } from '@components/Header'
|
||||
import { displayMessage, Message } from '@components/Message'
|
||||
import { NavigationContainer } from '@react-navigation/native'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import ScreenAccountSelection from '@screens/AccountSelection'
|
||||
import ScreenActions from '@screens/Actions'
|
||||
import ScreenAnnouncements from '@screens/Announcements'
|
||||
import ScreenCompose from '@screens/Compose'
|
||||
import ScreenImagesViewer from '@screens/ImageViewer'
|
||||
import ScreenTabs from '@screens/Tabs'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import pushUseConnect from '@utils/push/useConnect'
|
||||
import pushUseReceive from '@utils/push/useReceive'
|
||||
import pushUseRespond from '@utils/push/useRespond'
|
||||
import { useEmojisQuery } from '@utils/queryHooks/emojis'
|
||||
import { useFiltersQuery } from '@utils/queryHooks/filters'
|
||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import { setAccount, setGlobalStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { themes } from '@utils/styles/themes'
|
||||
import * as Linking from 'expo-linking'
|
||||
import { addScreenshotListener } from 'expo-screen-capture'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { IntlProvider } from 'react-intl'
|
||||
import { Alert, Platform, StatusBar } from 'react-native'
|
||||
import ShareMenu from 'react-native-share-menu'
|
||||
import { routingInstrumentation } from '../utils/startup/sentry'
|
||||
|
||||
const Stack = createNativeStackNavigator<RootStackParamList>()
|
||||
|
||||
export interface Props {
|
||||
localCorrupt?: string
|
||||
}
|
||||
|
||||
const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
const { t, i18n } = useTranslation([
|
||||
'common',
|
||||
'screens',
|
||||
'screenAnnouncements',
|
||||
'screenAccountSelection'
|
||||
])
|
||||
|
||||
const [accounts] = useGlobalStorage.object('accounts')
|
||||
const [accountActive] = useGlobalStorage.string('account.active')
|
||||
const { colors, theme } = useTheme()
|
||||
|
||||
// Push hooks
|
||||
pushUseConnect()
|
||||
pushUseReceive()
|
||||
pushUseRespond()
|
||||
|
||||
// Prevent screenshot alert
|
||||
useEffect(() => {
|
||||
const screenshotListener = addScreenshotListener(() =>
|
||||
Alert.alert(t('screens:screenshot.title'), t('screens:screenshot.message'), [
|
||||
{ text: t('common:buttons.confirm'), style: 'destructive' }
|
||||
])
|
||||
)
|
||||
Platform.select({ ios: screenshotListener })
|
||||
return () => screenshotListener.remove()
|
||||
}, [])
|
||||
|
||||
// On launch display login credentials corrupt information
|
||||
useEffect(() => {
|
||||
const showLocalCorrect = () => {
|
||||
if (localCorrupt) {
|
||||
displayMessage({
|
||||
message: t('screens:localCorrupt.message'),
|
||||
description: localCorrupt.length ? localCorrupt : undefined,
|
||||
type: 'danger'
|
||||
})
|
||||
// @ts-ignore
|
||||
navigationRef.navigate('Screen-Tabs', {
|
||||
screen: 'Tab-Me'
|
||||
})
|
||||
}
|
||||
}
|
||||
return showLocalCorrect()
|
||||
}, [localCorrupt])
|
||||
|
||||
// Lazily update users's preferences, for e.g. composing default visibility
|
||||
useInstanceQuery({ options: { enabled: !!accountActive } })
|
||||
useProfileQuery({ options: { enabled: !!accountActive } })
|
||||
usePreferencesQuery({ options: { enabled: !!accountActive } })
|
||||
useFiltersQuery({ options: { enabled: !!accountActive } })
|
||||
useEmojisQuery({ options: { enabled: !!accountActive } })
|
||||
|
||||
// Callbacks
|
||||
const navigationContainerOnStateChange = useCallback(() => {
|
||||
const currentRoute = navigationRef.getCurrentRoute()
|
||||
|
||||
const matchTabName = currentRoute?.name?.match(/(Tab-.*)-Root/)
|
||||
if (matchTabName?.[1]) {
|
||||
// @ts-ignore
|
||||
setGlobalStorage('app.prev_tab', matchTabName[1])
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Deep linking for compose
|
||||
const [deeplinked, setDeeplinked] = useState(false)
|
||||
useEffect(() => {
|
||||
const getUrlAsync = async () => {
|
||||
setDeeplinked(true)
|
||||
|
||||
const initialUrl = await Linking.parseInitialURLAsync()
|
||||
|
||||
if (initialUrl.path) {
|
||||
const paths = initialUrl.path.split('/')
|
||||
|
||||
if (paths.length) {
|
||||
if (accountActive && !accounts?.includes(accountActive)) {
|
||||
setAccount(accountActive)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (initialUrl.hostname === 'compose') {
|
||||
navigationRef.navigate('Screen-Compose')
|
||||
}
|
||||
}
|
||||
if (!deeplinked) {
|
||||
getUrlAsync()
|
||||
}
|
||||
}, [accounts, accountActive, deeplinked])
|
||||
|
||||
// Share Extension
|
||||
const handleShare = useCallback(
|
||||
(
|
||||
item?:
|
||||
| {
|
||||
data: { mimeType: string; data: string }[]
|
||||
mimeType: undefined
|
||||
}
|
||||
| { data: string | string[]; mimeType: string }
|
||||
) => {
|
||||
if (!accountActive) {
|
||||
return
|
||||
}
|
||||
if (!item || !item.data) {
|
||||
return
|
||||
}
|
||||
|
||||
let text: string | undefined = undefined
|
||||
let media: { uri: string; mime: string }[] = []
|
||||
|
||||
const typesImage = ['png', 'jpg', 'jpeg', 'gif']
|
||||
const typesVideo = ['mp4', 'm4v', 'mov', 'webm', 'mpeg']
|
||||
const filterMedia = ({ uri, mime }: { uri: string; mime: string }) => {
|
||||
if (mime.startsWith('image/')) {
|
||||
if (!typesImage.includes(mime.split('/')[1])) {
|
||||
console.warn('Image type not supported:', mime.split('/')[1])
|
||||
displayMessage({
|
||||
message: t('screens:shareError.imageNotSupported', {
|
||||
type: mime.split('/')[1]
|
||||
}),
|
||||
type: 'danger'
|
||||
})
|
||||
return
|
||||
}
|
||||
media.push({ uri, mime })
|
||||
} else if (mime.startsWith('video/')) {
|
||||
if (!typesVideo.includes(mime.split('/')[1])) {
|
||||
console.warn('Video type not supported:', mime.split('/')[1])
|
||||
displayMessage({
|
||||
message: t('screens:shareError.videoNotSupported', {
|
||||
type: mime.split('/')[1]
|
||||
}),
|
||||
type: 'danger'
|
||||
})
|
||||
return
|
||||
}
|
||||
media.push({ uri, mime })
|
||||
} else {
|
||||
if (typesImage.includes(uri.split('.').pop() || '')) {
|
||||
media.push({ uri, mime: 'image/jpg' })
|
||||
return
|
||||
}
|
||||
if (typesVideo.includes(uri.split('.').pop() || '')) {
|
||||
media.push({ uri, mime: 'video/mp4' })
|
||||
return
|
||||
}
|
||||
text = !text ? uri : text.concat(text, `\n${uri}`)
|
||||
}
|
||||
}
|
||||
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
if (!Array.isArray(item.data) || !item.data) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const d of item.data) {
|
||||
if (typeof d !== 'string') {
|
||||
filterMedia({ uri: d.data, mime: d.mimeType })
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'android':
|
||||
if (!item.mimeType) {
|
||||
return
|
||||
}
|
||||
if (Array.isArray(item.data)) {
|
||||
for (const d of item.data) {
|
||||
filterMedia({ uri: d, mime: item.mimeType })
|
||||
}
|
||||
} else {
|
||||
filterMedia({ uri: item.data, mime: item.mimeType })
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (!text && !media.length) {
|
||||
return
|
||||
} else {
|
||||
if (accounts?.length) {
|
||||
navigationRef.navigate('Screen-AccountSelection', {
|
||||
share: { text, media }
|
||||
})
|
||||
} else {
|
||||
navigationRef.navigate('Screen-Compose', {
|
||||
type: 'share',
|
||||
text,
|
||||
media
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
useEffect(() => {
|
||||
ShareMenu.getInitialShare(handleShare)
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
const listener = ShareMenu.addNewShareListener(handleShare)
|
||||
return () => {
|
||||
listener.remove()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<IntlProvider locale={i18n.language}>
|
||||
<StatusBar
|
||||
backgroundColor={colors.backgroundDefault}
|
||||
{...(Platform.OS === 'android' && {
|
||||
barStyle: theme === 'light' ? 'dark-content' : 'light-content'
|
||||
})}
|
||||
/>
|
||||
<NavigationContainer
|
||||
ref={navigationRef}
|
||||
theme={themes[theme]}
|
||||
onReady={() => routingInstrumentation.registerNavigationContainer(navigationRef)}
|
||||
onStateChange={navigationContainerOnStateChange}
|
||||
>
|
||||
<Stack.Navigator initialRouteName='Screen-Tabs'>
|
||||
<Stack.Screen
|
||||
name='Screen-Tabs'
|
||||
component={ScreenTabs}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
|
||||
<Stack.Screen
|
||||
name='Screen-Actions'
|
||||
component={ScreenActions}
|
||||
options={{
|
||||
presentation: 'transparentModal',
|
||||
animation: 'fade',
|
||||
headerShown: false
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Screen-Announcements'
|
||||
component={ScreenAnnouncements}
|
||||
options={({ navigation }) => ({
|
||||
presentation: 'transparentModal',
|
||||
animation: 'fade',
|
||||
headerShown: true,
|
||||
headerShadowVisible: false,
|
||||
headerTransparent: true,
|
||||
headerStyle: { backgroundColor: 'transparent' },
|
||||
headerLeft: () => <HeaderLeft content='X' onPress={() => navigation.goBack()} />,
|
||||
title: t('screenAnnouncements:heading')
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Screen-Compose'
|
||||
component={ScreenCompose}
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: 'fullScreenModal'
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Screen-ImagesViewer'
|
||||
component={ScreenImagesViewer}
|
||||
options={{ headerShown: false, animation: 'fade' }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Screen-AccountSelection'
|
||||
component={ScreenAccountSelection}
|
||||
options={({ navigation }) => ({
|
||||
title: t('screenAccountSelection:heading'),
|
||||
headerShadowVisible: false,
|
||||
presentation: 'modal',
|
||||
gestureEnabled: false,
|
||||
headerLeft: () => (
|
||||
<HeaderLeft
|
||||
type='text'
|
||||
content={t('common:buttons.cancel')}
|
||||
onPress={() => navigation.goBack()}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
|
||||
<Message />
|
||||
</NavigationContainer>
|
||||
</IntlProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default Screens
|
Reference in New Issue
Block a user