From 7ac789c18cc19394979d138fa4b5fb16e66f2c14 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Mon, 15 Nov 2021 22:34:43 +0100 Subject: [PATCH] Added instance configuration support --- src/@types/mastodon.d.ts | 24 +++- src/Screens.tsx | 2 + src/components/Instance/Auth.tsx | 6 +- src/components/mediaSelector.ts | 7 +- src/screens/Compose.tsx | 7 +- src/screens/Compose/Root.tsx | 9 ++ src/screens/Compose/Root/Actions.tsx | 11 +- src/screens/Compose/Root/Footer/Poll.tsx | 105 +++++++++++------- .../Compose/Root/Footer/addAttachment.ts | 2 +- src/screens/Compose/formatText.tsx | 3 +- src/store.ts | 2 +- src/utils/migrations/instances/migration.ts | 7 +- src/utils/migrations/instances/v5.ts | 93 ++++++++++++++++ src/utils/slices/instances/add.ts | 9 +- .../slices/instances/updateConfiguration.ts | 12 ++ src/utils/slices/instancesSlice.ts | 104 ++++++++++------- 16 files changed, 302 insertions(+), 101 deletions(-) create mode 100644 src/utils/migrations/instances/v5.ts create mode 100644 src/utils/slices/instances/updateConfiguration.ts diff --git a/src/@types/mastodon.d.ts b/src/@types/mastodon.d.ts index c0154742..de1742b7 100644 --- a/src/@types/mastodon.d.ts +++ b/src/@types/mastodon.d.ts @@ -299,8 +299,28 @@ declare namespace Mastodon { // Others thumbnail?: string contact_account?: Account - - // Custom + configuration?: { + statuses: { + max_characters: number + max_media_attachments: number + characters_reserved_per_url: number + } + media_attachments: { + supported_mime_types: string[] + image_size_limit: number + image_matrix_limit: number + video_size_limit: number + video_frame_rate_limit: number + video_matrix_limit: number + } + polls: { + max_options: number + max_characters_per_option: number + min_expiration: number + max_expiration: number + } + } + // Custom - to be deprecated in v4 max_toot_chars?: number } diff --git a/src/Screens.tsx b/src/Screens.tsx index b5fc58f5..8397144b 100644 --- a/src/Screens.tsx +++ b/src/Screens.tsx @@ -15,6 +15,7 @@ import pushUseReceive from '@utils/push/useReceive' import pushUseRespond from '@utils/push/useRespond' import { updatePreviousTab } from '@utils/slices/contextsSlice' import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences' +import { updateConfiguration } from '@utils/slices/instances/updateConfiguration' import { updateFilters } from '@utils/slices/instances/updateFilters' import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice' import { useTheme } from '@utils/styles/ThemeManager' @@ -108,6 +109,7 @@ const Screens: React.FC = ({ localCorrupt }) => { // Lazily update users's preferences, for e.g. composing default visibility useEffect(() => { if (instanceActive !== -1) { + dispatch(updateConfiguration()) dispatch(updateFilters()) dispatch(updateAccountPreferences()) } diff --git a/src/components/Instance/Auth.tsx b/src/components/Instance/Auth.tsx index 765d20ef..8d0e9e49 100644 --- a/src/components/Instance/Auth.tsx +++ b/src/components/Instance/Auth.tsx @@ -22,9 +22,8 @@ const InstanceAuth = React.memo( useProxy: false }) - const navigation = useNavigation< - TabMeStackNavigationProp<'Tab-Me-Root' | 'Tab-Me-Switch'> - >() + const navigation = + useNavigation>() const queryClient = useQueryClient() const dispatch = useDispatch() @@ -70,7 +69,6 @@ const InstanceAuth = React.memo( domain: instanceDomain, token: accessToken, instance, - max_toot_chars: instance.max_toot_chars, appData }) ) diff --git a/src/components/mediaSelector.ts b/src/components/mediaSelector.ts index c3c73b18..478bf3ec 100644 --- a/src/components/mediaSelector.ts +++ b/src/components/mediaSelector.ts @@ -11,7 +11,7 @@ export interface Props { resize?: { width?: number; height?: number } // Resize mode contain showActionSheetWithOptions: ( options: ActionSheetOptions, - callback: (i: number) => void + callback: (i?: number | undefined) => void | Promise ) => void } @@ -57,9 +57,8 @@ const mediaSelector = async ({ }, async buttonIndex => { if (buttonIndex === 0) { - const { - status - } = await ImagePicker.requestMediaLibraryPermissionsAsync() + const { status } = + await ImagePicker.requestMediaLibraryPermissionsAsync() if (status !== 'granted') { Alert.alert( i18next.t('componentMediaSelector:library.alert.title'), diff --git a/src/screens/Compose.tsx b/src/screens/Compose.tsx index ba7ef1a7..c1cd1fed 100644 --- a/src/screens/Compose.tsx +++ b/src/screens/Compose.tsx @@ -9,7 +9,7 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { updateStoreReview } from '@utils/slices/contextsSlice' import { getInstanceAccount, - getInstanceMaxTootChar, + getInstanceConfigurationStatusMaxChars, removeInstanceDraft, updateInstanceDraft } from '@utils/slices/instancesSlice' @@ -103,7 +103,10 @@ const ScreenCompose: React.FC> = ({ initialReducerState ) - const maxTootChars = useSelector(getInstanceMaxTootChar, () => true) + const maxTootChars = useSelector( + getInstanceConfigurationStatusMaxChars, + () => true + ) const totalTextCount = (composeState.spoiler.active ? composeState.spoiler.count : 0) + composeState.text.count diff --git a/src/screens/Compose/Root.tsx b/src/screens/Compose/Root.tsx index 742b5f62..2d4d72d6 100644 --- a/src/screens/Compose/Root.tsx +++ b/src/screens/Compose/Root.tsx @@ -29,6 +29,8 @@ import ComposeDrafts from './Root/Drafts' import FastImage from 'react-native-fast-image' import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { ComposeState } from './utils/types' +import { useSelector } from 'react-redux' +import { getInstanceConfigurationStatusCharsURL } from '@utils/slices/instancesSlice' const prefetchEmojis = ( sortedEmojis: NonNullable, @@ -54,11 +56,18 @@ const prefetchEmojis = ( } catch {} } +export let instanceConfigurationStatusCharsURL = 23 + const ComposeRoot = React.memo( () => { const { reduceMotionEnabled } = useAccessibility() const { theme } = useTheme() + instanceConfigurationStatusCharsURL = useSelector( + getInstanceConfigurationStatusCharsURL, + () => true + ) + const accessibleRefDrafts = useRef(null) const accessibleRefAttachments = useRef(null) const accessibleRefEmojis = useRef(null) diff --git a/src/screens/Compose/Root/Actions.tsx b/src/screens/Compose/Root/Actions.tsx index 5a46245f..c3c8c178 100644 --- a/src/screens/Compose/Root/Actions.tsx +++ b/src/screens/Compose/Root/Actions.tsx @@ -1,12 +1,14 @@ import analytics from '@components/analytics' import Icon from '@components/Icon' import { useActionSheet } from '@expo/react-native-action-sheet' +import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice' import { StyleConstants } from '@utils/styles/constants' import layoutAnimation from '@utils/styles/layoutAnimation' import { useTheme } from '@utils/styles/ThemeManager' import React, { useCallback, useContext, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { Pressable, StyleSheet, View } from 'react-native' +import { useSelector } from 'react-redux' import ComposeContext from '../utils/createContext' import addAttachment from './Footer/addAttachment' @@ -15,6 +17,10 @@ const ComposeActions: React.FC = () => { const { composeState, composeDispatch } = useContext(ComposeContext) const { t } = useTranslation('screenCompose') const { theme } = useTheme() + const instanceConfigurationStatusMaxAttachments = useSelector( + getInstanceConfigurationStatusMaxAttachments, + () => true + ) const attachmentColor = useMemo(() => { if (composeState.poll.active) return theme.disabled @@ -28,7 +34,10 @@ const ComposeActions: React.FC = () => { const attachmentOnPress = useCallback(async () => { if (composeState.poll.active) return - if (composeState.attachments.uploads.length < 4) { + if ( + composeState.attachments.uploads.length < + instanceConfigurationStatusMaxAttachments + ) { analytics('compose_actions_attachment_press', { count: composeState.attachments.uploads.length }) diff --git a/src/screens/Compose/Root/Footer/Poll.tsx b/src/screens/Compose/Root/Footer/Poll.tsx index f3c8f89d..363ddb4d 100644 --- a/src/screens/Compose/Root/Footer/Poll.tsx +++ b/src/screens/Compose/Root/Footer/Poll.tsx @@ -3,11 +3,13 @@ import Button from '@components/Button' import Icon from '@components/Icon' import { MenuRow } from '@components/Menu' import { useActionSheet } from '@expo/react-native-action-sheet' +import { getInstanceConfigurationPoll } from '@utils/slices/instancesSlice' 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 { StyleSheet, Text, TextInput, View } from 'react-native' +import { useSelector } from 'react-redux' import ComposeContext from '../../utils/createContext' const ComposePoll: React.FC = () => { @@ -21,6 +23,16 @@ const ComposePoll: React.FC = () => { const { t } = useTranslation('screenCompose') const { mode, theme } = 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 [firstRender, setFirstRender] = useState(true) useEffect(() => { setFirstRender(false) @@ -67,7 +79,7 @@ const ComposePoll: React.FC = () => { : t('content.root.footer.poll.option.placeholder.single') } placeholderTextColor={theme.disabled} - maxLength={50} + maxLength={MAX_CHARS_PER_OPTION} // @ts-ignore value={options[i]} onChangeText={e => @@ -82,37 +94,38 @@ const ComposePoll: React.FC = () => { })} - -