mirror of
https://github.com/tooot-app/app
synced 2025-04-07 07:01:11 +02:00
Allow custom exit
This commit is contained in:
parent
8ef2b89a09
commit
7d9056b562
@ -1,20 +1,12 @@
|
|||||||
import EmojisButton from '@components/Emojis/Button'
|
import EmojisButton from '@components/Emojis/Button'
|
||||||
import EmojisList from '@components/Emojis/List'
|
import EmojisList from '@components/Emojis/List'
|
||||||
import { PasteInputRef } from '@mattermost/react-native-paste-input'
|
|
||||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||||
import { useEmojisQuery } from '@utils/queryHooks/emojis'
|
import { useEmojisQuery } from '@utils/queryHooks/emojis'
|
||||||
import { getInstanceFrequentEmojis } from '@utils/slices/instancesSlice'
|
import { getInstanceFrequentEmojis } from '@utils/slices/instancesSlice'
|
||||||
import { chunk, forEach, groupBy, sortBy } from 'lodash'
|
import { chunk, forEach, groupBy, sortBy } from 'lodash'
|
||||||
import React, {
|
import React, { createRef, PropsWithChildren, useEffect, useReducer, useState } from 'react'
|
||||||
createRef,
|
|
||||||
PropsWithChildren,
|
|
||||||
RefObject,
|
|
||||||
useEffect,
|
|
||||||
useReducer,
|
|
||||||
useState
|
|
||||||
} from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Keyboard, KeyboardAvoidingView, TextInput, View } from 'react-native'
|
import { Keyboard, KeyboardAvoidingView, View } from 'react-native'
|
||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
import { Edge, SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
import { Edge, SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
@ -49,7 +41,6 @@ const prefetchEmojis = (
|
|||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
inputProps: EmojisState['inputProps']
|
inputProps: EmojisState['inputProps']
|
||||||
focusRef?: RefObject<TextInput | PasteInputRef>
|
|
||||||
customButton?: boolean
|
customButton?: boolean
|
||||||
customEdges?: Edge[]
|
customEdges?: Edge[]
|
||||||
customBehavior?: 'height' | 'padding' | 'position'
|
customBehavior?: 'height' | 'padding' | 'position'
|
||||||
@ -60,7 +51,6 @@ export const emojis: Emojis = createRef()
|
|||||||
const ComponentEmojis: React.FC<Props & PropsWithChildren> = ({
|
const ComponentEmojis: React.FC<Props & PropsWithChildren> = ({
|
||||||
children,
|
children,
|
||||||
inputProps,
|
inputProps,
|
||||||
focusRef,
|
|
||||||
customButton = false,
|
customButton = false,
|
||||||
customEdges = ['bottom'],
|
customEdges = ['bottom'],
|
||||||
customBehavior
|
customBehavior
|
||||||
@ -79,14 +69,14 @@ const ComponentEmojis: React.FC<Props & PropsWithChildren> = ({
|
|||||||
if (data && data.length) {
|
if (data && data.length) {
|
||||||
let sortedEmojis: NonNullable<Emojis['current']> = []
|
let sortedEmojis: NonNullable<Emojis['current']> = []
|
||||||
forEach(groupBy(sortBy(data, ['category', 'shortcode']), 'category'), (value, key) =>
|
forEach(groupBy(sortBy(data, ['category', 'shortcode']), 'category'), (value, key) =>
|
||||||
sortedEmojis.push({ title: key, data: chunk(value, 4) })
|
sortedEmojis.push({ title: key, data: chunk(value, 5) })
|
||||||
)
|
)
|
||||||
if (frequentEmojis.length) {
|
if (frequentEmojis.length) {
|
||||||
sortedEmojis.unshift({
|
sortedEmojis.unshift({
|
||||||
title: t('componentEmojis:frequentUsed'),
|
title: t('componentEmojis:frequentUsed'),
|
||||||
data: chunk(
|
data: chunk(
|
||||||
frequentEmojis.map(e => e.emoji),
|
frequentEmojis.map(e => e.emoji),
|
||||||
4
|
5
|
||||||
),
|
),
|
||||||
type: 'frequent'
|
type: 'frequent'
|
||||||
})
|
})
|
||||||
@ -115,11 +105,6 @@ const ComponentEmojis: React.FC<Props & PropsWithChildren> = ({
|
|||||||
hideSubscription.remove()
|
hideSubscription.remove()
|
||||||
}
|
}
|
||||||
}, [inputProps])
|
}, [inputProps])
|
||||||
useEffect(() => {
|
|
||||||
if (focusRef) {
|
|
||||||
setTimeout(() => focusRef.current?.focus(), 500)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView style={{ flex: 1 }} behavior={customBehavior}>
|
<KeyboardAvoidingView style={{ flex: 1 }} behavior={customBehavior}>
|
||||||
@ -129,7 +114,7 @@ const ComponentEmojis: React.FC<Props & PropsWithChildren> = ({
|
|||||||
{children}
|
{children}
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
keyboardShown ? { position: 'absolute', bottom: 0, width: '100%' } : null,
|
{ position: 'absolute', bottom: 0, width: '100%' },
|
||||||
{
|
{
|
||||||
marginBottom: keyboardShown && emojisState.targetIndex === -1 ? insets.bottom : 0
|
marginBottom: keyboardShown && emojisState.targetIndex === -1 ? insets.bottom : 0
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ const EmojisList = () => {
|
|||||||
const { reduceMotionEnabled } = useAccessibility()
|
const { reduceMotionEnabled } = useAccessibility()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { emojisState } = useContext(EmojisContext)
|
const { emojisState, emojisDispatch } = useContext(EmojisContext)
|
||||||
const { colors, mode } = useTheme()
|
const { colors, mode } = useTheme()
|
||||||
|
|
||||||
const addEmoji = (shortcode: string) => {
|
const addEmoji = (shortcode: string) => {
|
||||||
@ -165,6 +165,17 @@ const EmojisList = () => {
|
|||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
|
<Pressable
|
||||||
|
style={{ paddingLeft: StyleConstants.Spacing.M }}
|
||||||
|
onPress={() => {
|
||||||
|
if (emojisState.targetIndex !== -1) {
|
||||||
|
emojisState.inputProps[emojisState.targetIndex].ref?.current?.focus()
|
||||||
|
}
|
||||||
|
emojisDispatch({ type: 'target', payload: -1 })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name='ChevronDown' size={StyleConstants.Font.Size.L} color={colors.secondary} />
|
||||||
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
<SectionList
|
<SectionList
|
||||||
accessible
|
accessible
|
||||||
|
@ -5,9 +5,8 @@ type inputProps = {
|
|||||||
value: [string, (value: string) => void]
|
value: [string, (value: string) => void]
|
||||||
selection: [{ start: number; end?: number }, (selection: { start: number; end?: number }) => void]
|
selection: [{ start: number; end?: number }, (selection: { start: number; end?: number }) => void]
|
||||||
isFocused: MutableRefObject<boolean>
|
isFocused: MutableRefObject<boolean>
|
||||||
ref?: RefObject<TextInput> // For controlling focus
|
ref: RefObject<TextInput> // For controlling focus
|
||||||
maxLength?: number
|
maxLength?: number
|
||||||
addFunc?: (add: string) => void // For none default state update
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Emojis = MutableRefObject<
|
export type Emojis = MutableRefObject<
|
||||||
|
@ -365,7 +365,8 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
|||||||
selection => composeDispatch({ type: 'text', payload: { selection } })
|
selection => composeDispatch({ type: 'text', payload: { selection } })
|
||||||
],
|
],
|
||||||
isFocused: composeState.textInputFocus.isFocused.text,
|
isFocused: composeState.textInputFocus.isFocused.text,
|
||||||
maxLength: maxTootChars - (composeState.spoiler.active ? composeState.spoiler.count : 0)
|
maxLength: maxTootChars - (composeState.spoiler.active ? composeState.spoiler.count : 0),
|
||||||
|
ref: composeState.textInputFocus.refs.text
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: [
|
value: [
|
||||||
@ -377,7 +378,8 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
|||||||
selection => composeDispatch({ type: 'spoiler', payload: { selection } })
|
selection => composeDispatch({ type: 'spoiler', payload: { selection } })
|
||||||
],
|
],
|
||||||
isFocused: composeState.textInputFocus.isFocused.spoiler,
|
isFocused: composeState.textInputFocus.isFocused.spoiler,
|
||||||
maxLength: maxTootChars - composeState.text.count
|
maxLength: maxTootChars - composeState.text.count,
|
||||||
|
ref: composeState.textInputFocus.refs.spoiler
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { HeaderLeft } from '@components/Header'
|
import { HeaderLeft } from '@components/Header'
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||||
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
|
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
|
||||||
import React, { useCallback } from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { KeyboardAvoidingView, Platform } from 'react-native'
|
import { KeyboardAvoidingView, Platform } from 'react-native'
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import ComponentSeparator from '@components/Separator'
|
import ComponentSeparator from '@components/Separator'
|
||||||
import { useEmojisQuery } from '@utils/queryHooks/emojis'
|
|
||||||
import { useSearchQuery } from '@utils/queryHooks/search'
|
import { useSearchQuery } from '@utils/queryHooks/search'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { chunk, forEach, groupBy, sortBy } from 'lodash'
|
|
||||||
import React, { useContext, useEffect, useMemo, useRef } from 'react'
|
import React, { useContext, useEffect, useMemo, useRef } from 'react'
|
||||||
import { AccessibilityInfo, findNodeHandle, FlatList, View } from 'react-native'
|
import { AccessibilityInfo, findNodeHandle, FlatList, View } from 'react-native'
|
||||||
import { Circle } from 'react-native-animated-spinkit'
|
import { Circle } from 'react-native-animated-spinkit'
|
||||||
@ -14,19 +12,13 @@ import ComposeRootHeader from './Root/Header'
|
|||||||
import ComposeRootSuggestion from './Root/Suggestion'
|
import ComposeRootSuggestion from './Root/Suggestion'
|
||||||
import ComposeContext from './utils/createContext'
|
import ComposeContext from './utils/createContext'
|
||||||
import ComposeDrafts from './Root/Drafts'
|
import ComposeDrafts from './Root/Drafts'
|
||||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import {
|
import { getInstanceConfigurationStatusCharsURL } from '@utils/slices/instancesSlice'
|
||||||
getInstanceConfigurationStatusCharsURL,
|
|
||||||
getInstanceFrequentEmojis
|
|
||||||
} from '@utils/slices/instancesSlice'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
export let instanceConfigurationStatusCharsURL = 23
|
export let instanceConfigurationStatusCharsURL = 23
|
||||||
|
|
||||||
const ComposeRoot = React.memo(
|
const ComposeRoot = React.memo(
|
||||||
() => {
|
() => {
|
||||||
const { reduceMotionEnabled } = useAccessibility()
|
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
instanceConfigurationStatusCharsURL = useSelector(
|
instanceConfigurationStatusCharsURL = useSelector(
|
||||||
@ -42,7 +34,7 @@ const ComposeRoot = React.memo(
|
|||||||
tagDrafts && AccessibilityInfo.setAccessibilityFocus(tagDrafts)
|
tagDrafts && AccessibilityInfo.setAccessibilityFocus(tagDrafts)
|
||||||
}, [accessibleRefDrafts.current])
|
}, [accessibleRefDrafts.current])
|
||||||
|
|
||||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
const { composeState } = useContext(ComposeContext)
|
||||||
|
|
||||||
const mapSchemaToType = () => {
|
const mapSchemaToType = () => {
|
||||||
if (composeState.tag) {
|
if (composeState.tag) {
|
||||||
@ -71,30 +63,6 @@ const ComposeRoot = React.memo(
|
|||||||
}
|
}
|
||||||
}, [composeState.tag])
|
}, [composeState.tag])
|
||||||
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { data: emojisData } = useEmojisQuery({})
|
|
||||||
const frequentEmojis = useSelector(getInstanceFrequentEmojis, () => true)
|
|
||||||
useEffect(() => {
|
|
||||||
if (emojisData && emojisData.length) {
|
|
||||||
const sortedEmojis: {
|
|
||||||
title: string
|
|
||||||
data: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>[][]
|
|
||||||
}[] = []
|
|
||||||
forEach(groupBy(sortBy(emojisData, ['category', 'shortcode']), 'category'), (value, key) =>
|
|
||||||
sortedEmojis.push({ title: key, data: chunk(value, 5) })
|
|
||||||
)
|
|
||||||
if (frequentEmojis.length) {
|
|
||||||
sortedEmojis.unshift({
|
|
||||||
title: t('componentEmojis:frequentUsed'),
|
|
||||||
data: chunk(
|
|
||||||
frequentEmojis.map(e => e.emoji),
|
|
||||||
5
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [emojisData, reduceMotionEnabled])
|
|
||||||
|
|
||||||
const listEmpty = useMemo(() => {
|
const listEmpty = useMemo(() => {
|
||||||
if (isFetching) {
|
if (isFetching) {
|
||||||
return (
|
return (
|
||||||
|
@ -21,6 +21,7 @@ const ComposeSpoilerInput: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
|
ref={composeState.textInputFocus.refs.spoiler}
|
||||||
keyboardAppearance={mode}
|
keyboardAppearance={mode}
|
||||||
style={{
|
style={{
|
||||||
...StyleConstants.FontStyle.M,
|
...StyleConstants.FontStyle.M,
|
||||||
|
@ -39,7 +39,7 @@ const composeInitialState: Omit<ComposeState, 'timestamp'> = {
|
|||||||
replyToStatus: undefined,
|
replyToStatus: undefined,
|
||||||
textInputFocus: {
|
textInputFocus: {
|
||||||
current: 'text',
|
current: 'text',
|
||||||
refs: { text: createRef() },
|
refs: { text: createRef(), spoiler: createRef() },
|
||||||
isFocused: { text: createRef(), spoiler: createRef() }
|
isFocused: { text: createRef(), spoiler: createRef() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
src/screens/Compose/utils/types.d.ts
vendored
7
src/screens/Compose/utils/types.d.ts
vendored
@ -1,3 +1,4 @@
|
|||||||
|
import { RefObject } from 'react';
|
||||||
import { Asset } from 'react-native-image-picker'
|
import { Asset } from 'react-native-image-picker'
|
||||||
|
|
||||||
export type ExtendedAttachment = {
|
export type ExtendedAttachment = {
|
||||||
@ -39,10 +40,6 @@ export type ComposeState = {
|
|||||||
index: number
|
index: number
|
||||||
lastIndex: number
|
lastIndex: number
|
||||||
raw: string
|
raw: string
|
||||||
// type: 'url' | 'accounts' | 'hashtags'
|
|
||||||
// text: string
|
|
||||||
// offset: number
|
|
||||||
// length: number
|
|
||||||
}
|
}
|
||||||
poll: {
|
poll: {
|
||||||
active: boolean
|
active: boolean
|
||||||
@ -62,7 +59,7 @@ export type ComposeState = {
|
|||||||
replyToStatus?: Mastodon.Status
|
replyToStatus?: Mastodon.Status
|
||||||
textInputFocus: {
|
textInputFocus: {
|
||||||
current: 'text' | 'spoiler'
|
current: 'text' | 'spoiler'
|
||||||
refs: { text: RefObject<TextInput> }
|
refs: { text: RefObject<TextInput>, spoiler: RefObject<TextInput> }
|
||||||
isFocused: { text: MutableRefObject<boolean>, spoiler: MutableRefObject<boolean> }
|
isFocused: { text: MutableRefObject<boolean>, spoiler: MutableRefObject<boolean> }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { Dispatch, RefObject, SetStateAction, useEffect, useRef, useState } from 'react'
|
import React, { Dispatch, RefObject, SetStateAction, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Alert, ScrollView } from 'react-native'
|
import { Alert, ScrollView, TextInput } from 'react-native'
|
||||||
import FlashMessage from 'react-native-flash-message'
|
import FlashMessage from 'react-native-flash-message'
|
||||||
|
|
||||||
const Field: React.FC<{
|
const Field: React.FC<{
|
||||||
@ -21,19 +21,23 @@ const Field: React.FC<{
|
|||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const { t } = useTranslation('screenTabs')
|
const { t } = useTranslation('screenTabs')
|
||||||
|
|
||||||
|
const nameRef = useRef<TextInput>(null)
|
||||||
|
const valueRef = useRef<TextInput>(null)
|
||||||
const [name, setName] = useState(field?.name || '')
|
const [name, setName] = useState(field?.name || '')
|
||||||
const [value, setValue] = useState(field?.value || '')
|
const [value, setValue] = useState(field?.value || '')
|
||||||
allProps[index * 2] = {
|
allProps[index * 2] = {
|
||||||
value: [name, setName],
|
value: [name, setName],
|
||||||
selection: useState({ start: name.length }),
|
selection: useState({ start: name.length }),
|
||||||
isFocused: useRef<boolean>(false),
|
isFocused: useRef<boolean>(false),
|
||||||
maxLength: 255
|
maxLength: 255,
|
||||||
|
ref: nameRef
|
||||||
}
|
}
|
||||||
allProps[index * 2 + 1] = {
|
allProps[index * 2 + 1] = {
|
||||||
value: [value, setValue],
|
value: [value, setValue],
|
||||||
selection: useState({ start: value.length }),
|
selection: useState({ start: value.length }),
|
||||||
isFocused: useRef<boolean>(false),
|
isFocused: useRef<boolean>(false),
|
||||||
maxLength: 255
|
maxLength: 255,
|
||||||
|
ref: valueRef
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -93,7 +93,7 @@ const TabMeProfileName: React.FC<
|
|||||||
}, [theme, i18n.language, dirty, status, value])
|
}, [theme, i18n.language, dirty, status, value])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ComponentEmojis inputProps={[displayNameProps]} focusRef={displayNameProps.ref}>
|
<ComponentEmojis inputProps={[displayNameProps]}>
|
||||||
<ScrollView style={{ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding }}>
|
<ScrollView style={{ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding }}>
|
||||||
<ComponentInput
|
<ComponentInput
|
||||||
{...displayNameProps}
|
{...displayNameProps}
|
||||||
|
@ -93,7 +93,7 @@ const TabMeProfileNote: React.FC<
|
|||||||
}, [theme, i18n.language, dirty, status, notes])
|
}, [theme, i18n.language, dirty, status, notes])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ComponentEmojis inputProps={[notesProps]} focusRef={notesProps.ref}>
|
<ComponentEmojis inputProps={[notesProps]}>
|
||||||
<ScrollView style={{ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding }}>
|
<ScrollView style={{ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding }}>
|
||||||
<ComponentInput {...notesProps} multiline />
|
<ComponentInput {...notesProps} multiline />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user