1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Rewrite emoji component logic to be more generic

This commit is contained in:
xmflsct
2022-09-18 01:02:25 +02:00
parent 535268c680
commit 8a054f2205
12 changed files with 367 additions and 455 deletions

View File

@@ -1,27 +1,69 @@
import { ComponentEmojis } from '@components/Emojis'
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
import { HeaderLeft, HeaderRight } from '@components/Header'
import Input from '@components/Input'
import ComponentInput from '@components/Input'
import CustomText from '@components/Text'
import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators'
import { useProfileMutation } from '@utils/queryHooks/profile'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { isEqual } from 'lodash'
import React, { RefObject, useEffect, useState } from 'react'
import React, { Dispatch, RefObject, SetStateAction, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, View } from 'react-native'
import { Alert, TextInput, View } from 'react-native'
import FlashMessage from 'react-native-flash-message'
import { ScrollView } from 'react-native-gesture-handler'
const prepareFields = (
fields: Mastodon.Field[] | undefined
): Mastodon.Field[] => {
return Array.from(Array(4).keys()).map(index => {
if (fields && fields[index]) {
return fields[index]
} else {
return { name: '', value: '', verified_at: null }
}
})
const Field: React.FC<{
allProps: EmojisState['inputProps']
setDirty: Dispatch<SetStateAction<boolean>>
index: number
field?: Mastodon.Field
}> = ({ allProps, setDirty, index, field }) => {
const { colors } = useTheme()
const { t } = useTranslation('screenTabs')
const [name, setName] = useState(field?.name || '')
const [value, setValue] = useState(field?.value || '')
allProps[index * 2] = {
ref: useRef<TextInput>(null),
value: name,
setValue: setName,
selectionRange: name ? { start: name.length, end: name.length } : { start: 0, end: 0 },
maxLength: 255
}
allProps[index * 2 + 1] = {
ref: useRef<TextInput>(null),
value,
setValue,
selectionRange: value ? { start: value.length, end: value.length } : { start: 0, end: 0 },
maxLength: 255
}
useEffect(() => {
setDirty(dirty =>
dirty ? dirty : !isEqual(field?.name, name) || !isEqual(field?.value, value)
)
}, [name, value])
return (
<>
<CustomText
fontStyle='S'
style={{
marginBottom: StyleConstants.Spacing.XS,
color: colors.primaryDefault
}}
>
{t('me.profile.fields.group', { index: index + 1 })}
</CustomText>
<ComponentInput title={t('me.profile.fields.label')} {...allProps[index * 2]} />
<ComponentInput
title={t('me.profile.fields.content')}
{...allProps[index * 2 + 1]}
multiline
/>
</>
)
}
const TabMeProfileFields: React.FC<
@@ -35,16 +77,13 @@ const TabMeProfileFields: React.FC<
},
navigation
}) => {
const { colors, theme } = useTheme()
const { theme } = useTheme()
const { t, i18n } = useTranslation('screenTabs')
const { mutateAsync, status } = useProfileMutation()
const [newFields, setNewFields] = useState(prepareFields(fields))
const allProps: EmojisState['inputProps'] = []
const [dirty, setDirty] = useState(false)
useEffect(() => {
setDirty(!isEqual(prepareFields(fields), newFields))
}, [newFields])
useEffect(() => {
navigation.setOptions({
@@ -88,9 +127,15 @@ const TabMeProfileFields: React.FC<
failed: true
},
type: 'fields_attributes',
data: newFields
.filter(field => field.name.length && field.value.length)
.map(field => ({ name: field.name, value: field.value }))
data: Array.from(Array(4).keys())
.filter(
index =>
allProps[index * 2]?.value.length || allProps[index * 2 + 1]?.value.length
)
.map(index => ({
name: allProps[index * 2].value,
value: allProps[index * 2 + 1].value
}))
}).then(() => {
navigation.navigate('Tab-Me-Profile-Root')
})
@@ -98,60 +143,22 @@ const TabMeProfileFields: React.FC<
/>
)
})
}, [theme, i18n.language, dirty, status, newFields])
}, [theme, i18n.language, dirty, status, allProps.map(p => p.value)])
return (
<ScrollView
style={{
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
marginBottom: StyleConstants.Spacing.L
}}
keyboardShouldPersistTaps='always'
>
<View style={{ marginBottom: StyleConstants.Spacing.L * 2 }}>
<ComponentEmojis inputProps={allProps}>
<View style={{ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding }}>
{Array.from(Array(4).keys()).map(index => (
<View key={index} style={{ marginBottom: StyleConstants.Spacing.M }}>
<CustomText
fontStyle='S'
style={{
marginBottom: StyleConstants.Spacing.XS,
color: colors.primaryDefault
}}
>
{t('me.profile.fields.group', { index: index + 1 })}
</CustomText>
<Input
title={t('me.profile.fields.label')}
autoFocus={false}
options={{ maxLength: 255 }}
value={newFields[index].name}
setValue={(v: any) =>
setNewFields(
newFields.map((field, i) =>
i === index ? { ...field, name: v } : field
)
)
}
emoji
/>
<Input
title={t('me.profile.fields.content')}
autoFocus={false}
options={{ maxLength: 255 }}
value={newFields[index].value}
setValue={(v: any) =>
setNewFields(
newFields.map((field, i) =>
i === index ? { ...field, value: v } : field
)
)
}
emoji
/>
</View>
<Field
key={index}
allProps={allProps}
setDirty={setDirty}
index={index}
field={fields?.[index]}
/>
))}
</View>
</ScrollView>
</ComponentEmojis>
)
}

View File

@@ -1,14 +1,15 @@
import { ComponentEmojis } from '@components/Emojis'
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
import { HeaderLeft, HeaderRight } from '@components/Header'
import Input from '@components/Input'
import ComponentInput from '@components/Input'
import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators'
import { useProfileMutation } from '@utils/queryHooks/profile'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { RefObject, useEffect, useState } from 'react'
import React, { RefObject, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, StyleSheet } from 'react-native'
import { Alert, TextInput, View } from 'react-native'
import FlashMessage from 'react-native-flash-message'
import { ScrollView } from 'react-native-gesture-handler'
const TabMeProfileName: React.FC<
TabMeProfileStackScreenProps<'Tab-Me-Profile-Name'> & {
@@ -26,6 +27,15 @@ const TabMeProfileName: React.FC<
const { mutateAsync, status } = useProfileMutation()
const [displayName, setDisplayName] = useState(display_name)
const displayNameProps: NonNullable<EmojisState['targetProps']> = {
ref: useRef<TextInput>(null),
value: displayName,
setValue: setDisplayName,
selectionRange: displayName
? { start: displayName.length, end: displayName.length }
: { start: 0, end: 0 },
maxLength: 30
}
const [dirty, setDirty] = useState(false)
useEffect(() => {
@@ -85,27 +95,18 @@ const TabMeProfileName: React.FC<
}, [theme, i18n.language, dirty, status, displayName])
return (
<ScrollView style={styles.base} keyboardShouldPersistTaps='always'>
<Input
value={displayName}
setValue={setDisplayName}
emoji
options={{
maxLength: 30,
autoCapitalize: 'none',
autoComplete: 'username',
textContentType: 'username',
autoCorrect: false
}}
/>
</ScrollView>
<ComponentEmojis inputProps={[displayNameProps]} focusRef={displayNameProps.ref}>
<View style={{ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding }}>
<ComponentInput
{...displayNameProps}
autoCapitalize='none'
autoComplete='username'
textContentType='username'
autoCorrect={false}
/>
</View>
</ComponentEmojis>
)
}
const styles = StyleSheet.create({
base: {
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding
}
})
export default TabMeProfileName

View File

@@ -1,14 +1,15 @@
import { ComponentEmojis } from '@components/Emojis'
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
import { HeaderLeft, HeaderRight } from '@components/Header'
import Input from '@components/Input'
import ComponentInput from '@components/Input'
import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators'
import { useProfileMutation } from '@utils/queryHooks/profile'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { RefObject, useEffect, useState } from 'react'
import React, { RefObject, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, StyleSheet, View } from 'react-native'
import { Alert, TextInput, View } from 'react-native'
import FlashMessage from 'react-native-flash-message'
import { ScrollView } from 'react-native-gesture-handler'
const TabMeProfileNote: React.FC<
TabMeProfileStackScreenProps<'Tab-Me-Profile-Note'> & {
@@ -25,12 +26,19 @@ const TabMeProfileNote: React.FC<
const { t, i18n } = useTranslation('screenTabs')
const { mutateAsync, status } = useProfileMutation()
const [newNote, setNewNote] = useState(note)
const [notes, setNotes] = useState(note)
const notesProps: NonNullable<EmojisState['targetProps']> = {
ref: useRef<TextInput>(null),
value: notes,
setValue: setNotes,
selectionRange: notes ? { start: notes.length, end: notes.length } : { start: 0, end: 0 },
maxLength: 500
}
const [dirty, setDirty] = useState(false)
useEffect(() => {
setDirty(note !== newNote)
}, [newNote])
setDirty(note !== notes)
}, [notes])
useEffect(() => {
navigation.setOptions({
@@ -74,7 +82,7 @@ const TabMeProfileNote: React.FC<
failed: true
},
type: 'note',
data: newNote
data: notes
}).then(() => {
navigation.navigate('Tab-Me-Profile-Root')
})
@@ -82,27 +90,15 @@ const TabMeProfileNote: React.FC<
/>
)
})
}, [theme, i18n.language, dirty, status, newNote])
}, [theme, i18n.language, dirty, status, notes])
return (
<ScrollView style={styles.base} keyboardShouldPersistTaps='always'>
<View style={{ marginBottom: StyleConstants.Spacing.XL * 2 }}>
<Input
value={newNote}
setValue={setNewNote}
multiline
emoji
options={{ maxLength: 500 }}
/>
<ComponentEmojis inputProps={[notesProps]} focusRef={notesProps.ref}>
<View style={{ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding }}>
<ComponentInput {...notesProps} multiline />
</View>
</ScrollView>
</ComponentEmojis>
)
}
const styles = StyleSheet.create({
base: {
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding
}
})
export default TabMeProfileNote

View File

@@ -1,18 +1,16 @@
import Input from '@components/Input'
import { ParseEmojis } from '@components/Parse'
import CustomText from '@components/Text'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useMemo, useState } from 'react'
import React, { useMemo } from 'react'
import { View } from 'react-native'
import { PlaceholderLine } from 'rn-placeholder'
export interface Props {
account: Mastodon.Account | undefined
edit?: boolean // Editing mode
}
const AccountInformationName: React.FC<Props> = ({ account, edit }) => {
const AccountInformationName: React.FC<Props> = ({ account }) => {
const { colors } = useTheme()
const movedContent = useMemo(() => {
@@ -30,8 +28,6 @@ const AccountInformationName: React.FC<Props> = ({ account, edit }) => {
}
}, [account?.moved])
const [displatName, setDisplayName] = useState(account?.display_name)
return (
<View
style={{
@@ -42,25 +38,21 @@ const AccountInformationName: React.FC<Props> = ({ account, edit }) => {
}}
>
{account ? (
edit ? (
<Input title='昵称' value={displatName} setValue={setDisplayName} />
) : (
<>
<CustomText
style={{
textDecorationLine: account?.moved ? 'line-through' : undefined
}}
>
<ParseEmojis
content={account.display_name || account.username}
emojis={account.emojis}
size='L'
fontBold
/>
</CustomText>
{movedContent}
</>
)
<>
<CustomText
style={{
textDecorationLine: account?.moved ? 'line-through' : undefined
}}
>
<ParseEmojis
content={account.display_name || account.username}
emojis={account.emojis}
size='L'
fontBold
/>
</CustomText>
{movedContent}
</>
) : (
<PlaceholderLine
width={StyleConstants.Font.Size.L * 2}
@@ -74,7 +66,4 @@ const AccountInformationName: React.FC<Props> = ({ account, edit }) => {
)
}
export default React.memo(
AccountInformationName,
(_, next) => next.account === undefined
)
export default React.memo(AccountInformationName, (_, next) => next.account === undefined)

View File

@@ -1,4 +1,3 @@
import Input from '@components/Input'
import { ParseHTML } from '@components/Parse'
import { StyleConstants } from '@utils/styles/constants'
import React, { useState } from 'react'
@@ -7,16 +6,11 @@ import { StyleSheet, View } from 'react-native'
export interface Props {
account: Mastodon.Account | undefined
myInfo?: boolean
edit?: boolean
}
const AccountInformationNote = React.memo(
({ account, myInfo, edit }: Props) => {
({ account, myInfo }: Props) => {
const [note, setNote] = useState(account?.source?.note)
if (edit) {
return <Input title='简介' value={note} setValue={setNote} multiline />
}
if (
myInfo ||
!account?.note ||