mirror of
https://github.com/tooot-app/app
synced 2025-04-15 10:47:46 +02:00
Emoji done
This commit is contained in:
parent
d59fabd47f
commit
5866d016bc
@ -12,6 +12,7 @@ import {
|
|||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
import { StyleConstants } from 'src/utils/styles/constants'
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
|
import Button from './Button'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
@ -85,12 +86,10 @@ const BottomSheet: React.FC<Props> = ({ children, visible, handleDismiss }) => {
|
|||||||
style={[styles.handle, { backgroundColor: theme.background }]}
|
style={[styles.handle, { backgroundColor: theme.background }]}
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
<Pressable
|
<Button
|
||||||
onPress={() => closeModal.start(() => handleDismiss())}
|
onPress={() => closeModal.start(() => handleDismiss())}
|
||||||
style={[styles.cancel, { borderColor: theme.primary }]}
|
text='取消'
|
||||||
>
|
/>
|
||||||
<Text style={[styles.text, { color: theme.primary }]}>取消</Text>
|
|
||||||
</Pressable>
|
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -111,17 +110,6 @@ const styles = StyleSheet.create({
|
|||||||
height: StyleConstants.Spacing.S / 2,
|
height: StyleConstants.Spacing.S / 2,
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
top: -StyleConstants.Spacing.M * 2
|
top: -StyleConstants.Spacing.M * 2
|
||||||
},
|
|
||||||
cancel: {
|
|
||||||
padding: StyleConstants.Spacing.S,
|
|
||||||
marginLeft: StyleConstants.Spacing.L,
|
|
||||||
marginRight: StyleConstants.Spacing.L,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 100
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
fontSize: StyleConstants.Font.Size.L,
|
|
||||||
textAlign: 'center'
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,38 +1,74 @@
|
|||||||
|
import { Feather } from '@expo/vector-icons'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Pressable, StyleSheet, Text } from 'react-native'
|
import { Pressable, StyleSheet, Text } from 'react-native'
|
||||||
import { StyleConstants } from 'src/utils/styles/constants'
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
|
|
||||||
export interface Props {
|
type PropsBase = {
|
||||||
onPress: () => void
|
onPress: () => void
|
||||||
text: string
|
disabled?: boolean
|
||||||
fontSize?: 'S' | 'M' | 'L'
|
buttonSize?: 'S' | 'M'
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button: React.FC<Props> = ({ onPress, text, fontSize = 'M' }) => {
|
export interface PropsText extends PropsBase {
|
||||||
|
text: string
|
||||||
|
icon?: string
|
||||||
|
size?: 'S' | 'M' | 'L'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PropsIcon extends PropsBase {
|
||||||
|
text?: string
|
||||||
|
icon: string
|
||||||
|
size?: 'S' | 'M' | 'L'
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button: React.FC<PropsText | PropsIcon> = ({
|
||||||
|
onPress,
|
||||||
|
disabled = false,
|
||||||
|
buttonSize = 'M',
|
||||||
|
text,
|
||||||
|
icon,
|
||||||
|
size = 'M'
|
||||||
|
}) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={onPress}
|
{...(!disabled && { onPress })}
|
||||||
style={[styles.button, { borderColor: theme.primary }]}
|
style={[
|
||||||
|
styles.button,
|
||||||
|
{
|
||||||
|
borderColor: disabled ? theme.secondary : theme.primary,
|
||||||
|
paddingTop: StyleConstants.Spacing[buttonSize === 'M' ? 'S' : 'XS'],
|
||||||
|
paddingBottom: StyleConstants.Spacing[buttonSize === 'M' ? 'S' : 'XS']
|
||||||
|
}
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Text
|
{icon ? (
|
||||||
style={[
|
<Feather
|
||||||
styles.text,
|
name={icon}
|
||||||
{ color: theme.primary, fontSize: StyleConstants.Font.Size[fontSize] }
|
size={StyleConstants.Font.Size[size]}
|
||||||
]}
|
color={disabled ? theme.secondary : theme.primary}
|
||||||
>
|
/>
|
||||||
{text}
|
) : (
|
||||||
</Text>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.text,
|
||||||
|
{
|
||||||
|
color: disabled ? theme.secondary : theme.primary,
|
||||||
|
fontSize: StyleConstants.Font.Size[size]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
button: {
|
button: {
|
||||||
paddingTop: StyleConstants.Spacing.S,
|
|
||||||
paddingBottom: StyleConstants.Spacing.S,
|
|
||||||
paddingLeft: StyleConstants.Spacing.M,
|
paddingLeft: StyleConstants.Spacing.M,
|
||||||
paddingRight: StyleConstants.Spacing.M,
|
paddingRight: StyleConstants.Spacing.M,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
|
@ -58,7 +58,7 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
height: StyleConstants.Avatar.L,
|
height: StyleConstants.Avatar.L,
|
||||||
marginTop: StyleConstants.Spacing.M,
|
marginTop: StyleConstants.Spacing.M,
|
||||||
borderWidth: 0.5,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
borderRadius: 6
|
borderRadius: 6
|
||||||
},
|
},
|
||||||
left: {
|
left: {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { Button, StyleSheet, Text, TextInput, View } from 'react-native'
|
import { StyleSheet, Text, TextInput, View } from 'react-native'
|
||||||
import { useQuery } from 'react-query'
|
import { useQuery } from 'react-query'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ import { useTheme } from 'src/utils/styles/ThemeManager'
|
|||||||
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleConstants } from 'src/utils/styles/constants'
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
|
import Button from 'src/components/Button'
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
const Login: React.FC = () => {
|
||||||
const { t } = useTranslation('meRoot')
|
const { t } = useTranslation('meRoot')
|
||||||
@ -145,9 +146,9 @@ const Login: React.FC = () => {
|
|||||||
returnKeyType='go'
|
returnKeyType='go'
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
title={t('content.login.button')}
|
|
||||||
disabled={!data?.uri}
|
|
||||||
onPress={async () => await createApplication()}
|
onPress={async () => await createApplication()}
|
||||||
|
text={t('content.login.button')}
|
||||||
|
disabled={!data?.uri}
|
||||||
/>
|
/>
|
||||||
{isSuccess && data && data.uri && (
|
{isSuccess && data && data.uri && (
|
||||||
<View>
|
<View>
|
||||||
|
@ -208,13 +208,13 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
account_types: { marginLeft: StyleConstants.Spacing.S },
|
account_types: { marginLeft: StyleConstants.Spacing.S },
|
||||||
fields: {
|
fields: {
|
||||||
borderTopWidth: 0.5,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
marginBottom: StyleConstants.Spacing.M
|
marginBottom: StyleConstants.Spacing.M
|
||||||
},
|
},
|
||||||
field: {
|
field: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
borderBottomWidth: 0.5,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
paddingTop: StyleConstants.Spacing.S,
|
paddingTop: StyleConstants.Spacing.S,
|
||||||
paddingBottom: StyleConstants.Spacing.S
|
paddingBottom: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
|
@ -5,7 +5,7 @@ import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
|||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
|
||||||
import { store } from 'src/store'
|
import { store } from 'src/store'
|
||||||
import PostMain from './Compose/PostMain'
|
import ComposeRoot from './Compose/Root'
|
||||||
import client from 'src/api/client'
|
import client from 'src/api/client'
|
||||||
import { getLocalAccountPreferences } from 'src/utils/slices/instancesSlice'
|
import { getLocalAccountPreferences } from 'src/utils/slices/instancesSlice'
|
||||||
import { HeaderLeft, HeaderRight } from 'src/components/Header'
|
import { HeaderLeft, HeaderRight } from 'src/components/Header'
|
||||||
@ -105,7 +105,7 @@ export type PostAction =
|
|||||||
|
|
||||||
const postInitialState: PostState = {
|
const postInitialState: PostState = {
|
||||||
text: {
|
text: {
|
||||||
count: 0,
|
count: 500,
|
||||||
raw: '',
|
raw: '',
|
||||||
formatted: undefined
|
formatted: undefined
|
||||||
},
|
},
|
||||||
@ -285,13 +285,15 @@ const Compose: React.FC = () => {
|
|||||||
<HeaderRight
|
<HeaderRight
|
||||||
onPress={async () => tootPost()}
|
onPress={async () => tootPost()}
|
||||||
text='发嘟嘟'
|
text='发嘟嘟'
|
||||||
disabled={postState.text.raw.length < 1}
|
disabled={
|
||||||
|
postState.text.raw.length < 1 || postState.text.count < 0
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{() => (
|
{() => (
|
||||||
<PostMain postState={postState} postDispatch={postDispatch} />
|
<ComposeRoot postState={postState} postDispatch={postDispatch} />
|
||||||
)}
|
)}
|
||||||
</Stack.Screen>
|
</Stack.Screen>
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
|
147
src/screens/Shared/Compose/Actions.tsx
Normal file
147
src/screens/Shared/Compose/Actions.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
import React, { Dispatch } from 'react'
|
||||||
|
import {
|
||||||
|
ActionSheetIOS,
|
||||||
|
Keyboard,
|
||||||
|
Pressable,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TextInput
|
||||||
|
} from 'react-native'
|
||||||
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
|
import { PostAction, PostState } from '../Compose'
|
||||||
|
import addAttachments from './addAttachments'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
textInputRef: React.RefObject<TextInput>
|
||||||
|
postState: PostState
|
||||||
|
postDispatch: Dispatch<PostAction>
|
||||||
|
}
|
||||||
|
|
||||||
|
const ComposeActions: React.FC<Props> = ({
|
||||||
|
textInputRef,
|
||||||
|
postState,
|
||||||
|
postDispatch
|
||||||
|
}) => {
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
const getVisibilityIcon = () => {
|
||||||
|
switch (postState.visibility) {
|
||||||
|
case 'public':
|
||||||
|
return 'globe'
|
||||||
|
case 'unlisted':
|
||||||
|
return 'unlock'
|
||||||
|
case 'private':
|
||||||
|
return 'lock'
|
||||||
|
case 'direct':
|
||||||
|
return 'mail'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
style={[
|
||||||
|
styles.additions,
|
||||||
|
{ backgroundColor: theme.background, borderTopColor: theme.border }
|
||||||
|
]}
|
||||||
|
onPress={() => Keyboard.dismiss()}
|
||||||
|
>
|
||||||
|
<Feather
|
||||||
|
name='aperture'
|
||||||
|
size={24}
|
||||||
|
color={postState.poll.active ? theme.secondary : theme.primary}
|
||||||
|
onPress={async () =>
|
||||||
|
!postState.poll.active &&
|
||||||
|
(await addAttachments({ postState, postDispatch }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Feather
|
||||||
|
name='bar-chart-2'
|
||||||
|
size={24}
|
||||||
|
color={
|
||||||
|
postState.attachments.length > 0 ? theme.secondary : theme.primary
|
||||||
|
}
|
||||||
|
onPress={() => {
|
||||||
|
if (postState.attachments.length === 0) {
|
||||||
|
postDispatch({
|
||||||
|
type: 'poll',
|
||||||
|
payload: { ...postState.poll, active: !postState.poll.active }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (postState.poll.active) {
|
||||||
|
textInputRef.current?.focus()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Feather
|
||||||
|
name={getVisibilityIcon()}
|
||||||
|
size={24}
|
||||||
|
color={theme.primary}
|
||||||
|
onPress={() =>
|
||||||
|
ActionSheetIOS.showActionSheetWithOptions(
|
||||||
|
{
|
||||||
|
options: ['公开', '不公开', '仅关注着', '私信', '取消'],
|
||||||
|
cancelButtonIndex: 4
|
||||||
|
},
|
||||||
|
buttonIndex => {
|
||||||
|
switch (buttonIndex) {
|
||||||
|
case 0:
|
||||||
|
postDispatch({ type: 'visibility', payload: 'public' })
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
postDispatch({ type: 'visibility', payload: 'unlisted' })
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
postDispatch({ type: 'visibility', payload: 'private' })
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
postDispatch({ type: 'visibility', payload: 'direct' })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Feather
|
||||||
|
name='smile'
|
||||||
|
size={24}
|
||||||
|
color={postState.emojis?.length ? theme.primary : theme.secondary}
|
||||||
|
onPress={() => {
|
||||||
|
if (postState.emojis?.length && postState.overlay === null) {
|
||||||
|
Keyboard.dismiss()
|
||||||
|
postDispatch({ type: 'overlay', payload: 'emojis' })
|
||||||
|
}
|
||||||
|
if (postState.overlay === 'emojis') {
|
||||||
|
postDispatch({ type: 'overlay', payload: null })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.count,
|
||||||
|
{ color: postState.text.count < 0 ? theme.error : theme.primary }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{postState.text.count}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
additions: {
|
||||||
|
height: 45,
|
||||||
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
count: {
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: StyleConstants.Font.Size.M,
|
||||||
|
fontWeight: StyleConstants.Font.Weight.Bold
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default ComposeActions
|
@ -9,7 +9,7 @@ export interface Props {
|
|||||||
postDispatch: Dispatch<PostAction>
|
postDispatch: Dispatch<PostAction>
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostAttachments: React.FC<Props> = ({ postState, postDispatch }) => {
|
const ComposeAttachments: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
{postState.attachments.map((attachment, index) => (
|
{postState.attachments.map((attachment, index) => (
|
||||||
@ -63,4 +63,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default PostAttachments
|
export default ComposeAttachments
|
108
src/screens/Shared/Compose/Emojis.tsx
Normal file
108
src/screens/Shared/Compose/Emojis.tsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { forEach, groupBy, sortBy } from 'lodash'
|
||||||
|
import React, { Dispatch } from 'react'
|
||||||
|
import {
|
||||||
|
Image,
|
||||||
|
Pressable,
|
||||||
|
SectionList,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
View
|
||||||
|
} from 'react-native'
|
||||||
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
|
|
||||||
|
import { PostAction, PostState } from '../Compose'
|
||||||
|
import updateText from './updateText'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
textInputRef: React.RefObject<TextInput>
|
||||||
|
onChangeText: any
|
||||||
|
postState: PostState
|
||||||
|
postDispatch: Dispatch<PostAction>
|
||||||
|
}
|
||||||
|
|
||||||
|
const ComposeEmojis: React.FC<Props> = ({
|
||||||
|
textInputRef,
|
||||||
|
onChangeText,
|
||||||
|
postState,
|
||||||
|
postDispatch
|
||||||
|
}) => {
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
let sortedEmojis: { title: string; data: Mastodon.Emoji[] }[] = []
|
||||||
|
forEach(
|
||||||
|
groupBy(sortBy(postState.emojis, ['category', 'shortcode']), 'category'),
|
||||||
|
(value, key) => sortedEmojis.push({ title: key, data: value })
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.base}>
|
||||||
|
<SectionList
|
||||||
|
horizontal
|
||||||
|
sections={sortedEmojis}
|
||||||
|
keyExtractor={item => item.shortcode}
|
||||||
|
renderSectionHeader={({ section: { title } }) => (
|
||||||
|
<Text style={[styles.group, { color: theme.secondary }]}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
renderItem={({ section, index }) => {
|
||||||
|
if (index === 0) {
|
||||||
|
return (
|
||||||
|
<View key={section.title} style={styles.emojis}>
|
||||||
|
{section.data.map(emoji => (
|
||||||
|
<Pressable
|
||||||
|
key={emoji.shortcode}
|
||||||
|
onPress={() => {
|
||||||
|
updateText({
|
||||||
|
onChangeText,
|
||||||
|
postState,
|
||||||
|
newText: `:${emoji.shortcode}:`
|
||||||
|
})
|
||||||
|
textInputRef.current?.focus()
|
||||||
|
postDispatch({ type: 'overlay', payload: null })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image source={{ uri: emoji.url }} style={styles.emoji} />
|
||||||
|
</Pressable>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
base: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
height: 260
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: StyleConstants.Spacing.L,
|
||||||
|
fontSize: StyleConstants.Font.Size.S
|
||||||
|
},
|
||||||
|
emojis: {
|
||||||
|
flex: 1,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
marginTop: StyleConstants.Spacing.M,
|
||||||
|
marginLeft: StyleConstants.Spacing.M
|
||||||
|
},
|
||||||
|
emoji: {
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
padding: StyleConstants.Spacing.S,
|
||||||
|
margin: StyleConstants.Spacing.S
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default ComposeEmojis
|
186
src/screens/Shared/Compose/Poll.tsx
Normal file
186
src/screens/Shared/Compose/Poll.tsx
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import React, { Dispatch, useEffect, useState } from 'react'
|
||||||
|
import {
|
||||||
|
ActionSheetIOS,
|
||||||
|
Pressable,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
View
|
||||||
|
} from 'react-native'
|
||||||
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
|
||||||
|
import { PostAction, PostState } from '../Compose'
|
||||||
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
|
import Button from 'src/components/Button'
|
||||||
|
import { MenuContainer, MenuRow } from 'src/components/Menu'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
postState: PostState
|
||||||
|
postDispatch: Dispatch<PostAction>
|
||||||
|
}
|
||||||
|
|
||||||
|
const ComposePoll: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
const expireMapping: { [key: string]: string } = {
|
||||||
|
'300': '5分钟',
|
||||||
|
'1800': '30分钟',
|
||||||
|
'3600': '1小时',
|
||||||
|
'21600': '6小时',
|
||||||
|
'86400': '1天',
|
||||||
|
'259200': '3天',
|
||||||
|
'604800': '7天'
|
||||||
|
}
|
||||||
|
|
||||||
|
const [firstRender, setFirstRender] = useState(true)
|
||||||
|
useEffect(() => {
|
||||||
|
setFirstRender(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.base, { borderColor: theme.border }]}>
|
||||||
|
<View style={styles.options}>
|
||||||
|
{[...Array(postState.poll.total)].map((e, i) => (
|
||||||
|
<View key={i} style={styles.option}>
|
||||||
|
<Feather
|
||||||
|
name={postState.poll.multiple ? 'square' : 'circle'}
|
||||||
|
size={StyleConstants.Font.Size.L}
|
||||||
|
color={theme.secondary}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
{...(i === 0 && firstRender && { autoFocus: true })}
|
||||||
|
style={[
|
||||||
|
styles.textInput,
|
||||||
|
{ borderColor: theme.border, color: theme.primary }
|
||||||
|
]}
|
||||||
|
placeholder={`选项 ${i}`}
|
||||||
|
placeholderTextColor={theme.secondary}
|
||||||
|
maxLength={50}
|
||||||
|
value={postState.poll.options[i]}
|
||||||
|
onChangeText={e =>
|
||||||
|
postDispatch({
|
||||||
|
type: 'poll',
|
||||||
|
payload: {
|
||||||
|
...postState.poll,
|
||||||
|
options: { ...postState.poll.options, [i]: e }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<View style={styles.controlAmount}>
|
||||||
|
<View style={styles.firstButton}>
|
||||||
|
<Button
|
||||||
|
onPress={() =>
|
||||||
|
postState.poll.total > 2 &&
|
||||||
|
postDispatch({
|
||||||
|
type: 'poll',
|
||||||
|
payload: { ...postState.poll, total: postState.poll.total - 1 }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
icon='minus'
|
||||||
|
disabled={!(postState.poll.total > 2)}
|
||||||
|
buttonSize='S'
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Button
|
||||||
|
onPress={() =>
|
||||||
|
postState.poll.total < 4 &&
|
||||||
|
postDispatch({
|
||||||
|
type: 'poll',
|
||||||
|
payload: { ...postState.poll, total: postState.poll.total + 1 }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
icon='plus'
|
||||||
|
disabled={!(postState.poll.total < 4)}
|
||||||
|
buttonSize='S'
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<MenuContainer>
|
||||||
|
<MenuRow
|
||||||
|
title='可选项'
|
||||||
|
content={postState.poll.multiple ? '多选' : '单选'}
|
||||||
|
onPress={() =>
|
||||||
|
ActionSheetIOS.showActionSheetWithOptions(
|
||||||
|
{
|
||||||
|
options: ['单选', '多选', '取消'],
|
||||||
|
cancelButtonIndex: 2
|
||||||
|
},
|
||||||
|
index =>
|
||||||
|
index < 2 &&
|
||||||
|
postDispatch({
|
||||||
|
type: 'poll',
|
||||||
|
payload: { ...postState.poll, multiple: index === 1 }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
iconBack='chevron-right'
|
||||||
|
/>
|
||||||
|
<MenuRow
|
||||||
|
title='有效期'
|
||||||
|
content={expireMapping[postState.poll.expire]}
|
||||||
|
onPress={() =>
|
||||||
|
ActionSheetIOS.showActionSheetWithOptions(
|
||||||
|
{
|
||||||
|
options: [...Object.values(expireMapping), '取消'],
|
||||||
|
cancelButtonIndex: 7
|
||||||
|
},
|
||||||
|
index =>
|
||||||
|
index < 7 &&
|
||||||
|
postDispatch({
|
||||||
|
type: 'poll',
|
||||||
|
payload: {
|
||||||
|
...postState.poll,
|
||||||
|
expire: Object.keys(expireMapping)[index]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
iconBack='chevron-right'
|
||||||
|
/>
|
||||||
|
</MenuContainer>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
base: {
|
||||||
|
flex: 1,
|
||||||
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderRadius: 6
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
marginTop: StyleConstants.Spacing.M,
|
||||||
|
marginBottom: StyleConstants.Spacing.S
|
||||||
|
},
|
||||||
|
option: {
|
||||||
|
marginLeft: StyleConstants.Spacing.M,
|
||||||
|
marginRight: StyleConstants.Spacing.M,
|
||||||
|
marginBottom: StyleConstants.Spacing.S,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
textInput: {
|
||||||
|
flex: 1,
|
||||||
|
padding: StyleConstants.Spacing.S,
|
||||||
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderRadius: 6,
|
||||||
|
fontSize: StyleConstants.Font.Size.M,
|
||||||
|
marginLeft: StyleConstants.Spacing.S
|
||||||
|
},
|
||||||
|
controlAmount: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
marginRight: StyleConstants.Spacing.M,
|
||||||
|
marginBottom: StyleConstants.Spacing.M
|
||||||
|
},
|
||||||
|
firstButton: {
|
||||||
|
marginRight: StyleConstants.Spacing.S
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default ComposePoll
|
@ -1,44 +0,0 @@
|
|||||||
import React, { Dispatch } from 'react'
|
|
||||||
import { Image, Pressable } from 'react-native'
|
|
||||||
|
|
||||||
import { PostAction, PostState } from '../Compose'
|
|
||||||
import updateText from './updateText'
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
onChangeText: any
|
|
||||||
postState: PostState
|
|
||||||
postDispatch: Dispatch<PostAction>
|
|
||||||
}
|
|
||||||
|
|
||||||
const PostEmojis: React.FC<Props> = ({
|
|
||||||
onChangeText,
|
|
||||||
postState,
|
|
||||||
postDispatch
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{postState.emojis?.map((emoji, index) => (
|
|
||||||
<Pressable
|
|
||||||
key={index}
|
|
||||||
onPress={() => {
|
|
||||||
updateText({
|
|
||||||
onChangeText,
|
|
||||||
postState,
|
|
||||||
newText: `:${emoji.shortcode}:`
|
|
||||||
})
|
|
||||||
|
|
||||||
postDispatch({ type: 'overlay', payload: null })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
key={index}
|
|
||||||
source={{ uri: emoji.url }}
|
|
||||||
style={{ width: 24, height: 24 }}
|
|
||||||
/>
|
|
||||||
</Pressable>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PostEmojis
|
|
@ -1,144 +0,0 @@
|
|||||||
import React, { Dispatch, useState } from 'react'
|
|
||||||
import {
|
|
||||||
ActionSheetIOS,
|
|
||||||
Pressable,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
TextInput,
|
|
||||||
View
|
|
||||||
} from 'react-native'
|
|
||||||
import { Feather } from '@expo/vector-icons'
|
|
||||||
|
|
||||||
import { PostAction, PostState } from '../Compose'
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
postState: PostState
|
|
||||||
postDispatch: Dispatch<PostAction>
|
|
||||||
}
|
|
||||||
|
|
||||||
const PostPoll: React.FC<Props> = ({ postState, postDispatch }) => {
|
|
||||||
const expireMapping: { [key: string]: string } = {
|
|
||||||
'300': '5分钟',
|
|
||||||
'1800': '30分钟',
|
|
||||||
'3600': '1小时',
|
|
||||||
'21600': '6小时',
|
|
||||||
'86400': '1天',
|
|
||||||
'259200': '3天',
|
|
||||||
'604800': '7天'
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.base}>
|
|
||||||
{[...Array(postState.poll.total)].map((e, i) => (
|
|
||||||
<View key={i} style={styles.option}>
|
|
||||||
{postState.poll.multiple ? (
|
|
||||||
<Feather name='square' size={20} />
|
|
||||||
) : (
|
|
||||||
<Feather name='circle' size={20} />
|
|
||||||
)}
|
|
||||||
<TextInput
|
|
||||||
style={styles.textInput}
|
|
||||||
maxLength={50}
|
|
||||||
value={postState.poll.options[i]}
|
|
||||||
onChangeText={e =>
|
|
||||||
postDispatch({
|
|
||||||
type: 'poll',
|
|
||||||
payload: {
|
|
||||||
...postState.poll,
|
|
||||||
options: { ...postState.poll.options, [i]: e }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
<View style={styles.totalControl}>
|
|
||||||
<Feather
|
|
||||||
name='minus'
|
|
||||||
size={20}
|
|
||||||
color={postState.poll.total > 2 ? 'black' : 'grey'}
|
|
||||||
onPress={() =>
|
|
||||||
postState.poll.total > 2 &&
|
|
||||||
postDispatch({
|
|
||||||
type: 'poll',
|
|
||||||
payload: { ...postState.poll, total: postState.poll.total - 1 }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Feather
|
|
||||||
name='plus'
|
|
||||||
size={20}
|
|
||||||
color={postState.poll.total < 4 ? 'black' : 'grey'}
|
|
||||||
onPress={() =>
|
|
||||||
postState.poll.total < 4 &&
|
|
||||||
postDispatch({
|
|
||||||
type: 'poll',
|
|
||||||
payload: { ...postState.poll, total: postState.poll.total + 1 }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Pressable
|
|
||||||
onPress={() =>
|
|
||||||
ActionSheetIOS.showActionSheetWithOptions(
|
|
||||||
{
|
|
||||||
options: ['单选', '多选', '取消'],
|
|
||||||
cancelButtonIndex: 2
|
|
||||||
},
|
|
||||||
index =>
|
|
||||||
index < 2 &&
|
|
||||||
postDispatch({
|
|
||||||
type: 'poll',
|
|
||||||
payload: { ...postState.poll, multiple: index === 1 }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Text>{postState.poll.multiple ? '多选' : '单选'}</Text>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable
|
|
||||||
onPress={() =>
|
|
||||||
ActionSheetIOS.showActionSheetWithOptions(
|
|
||||||
{
|
|
||||||
options: [...Object.values(expireMapping), '取消'],
|
|
||||||
cancelButtonIndex: 7
|
|
||||||
},
|
|
||||||
index =>
|
|
||||||
index < 7 &&
|
|
||||||
postDispatch({
|
|
||||||
type: 'poll',
|
|
||||||
payload: {
|
|
||||||
...postState.poll,
|
|
||||||
expire: Object.keys(expireMapping)[index]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Text>{expireMapping[postState.poll.expire]}</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
base: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: 'green'
|
|
||||||
},
|
|
||||||
option: {
|
|
||||||
height: 30,
|
|
||||||
margin: 5,
|
|
||||||
flexDirection: 'row'
|
|
||||||
},
|
|
||||||
textInput: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: 'white'
|
|
||||||
},
|
|
||||||
totalControl: {
|
|
||||||
alignSelf: 'flex-end',
|
|
||||||
flexDirection: 'row'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default PostPoll
|
|
@ -3,6 +3,7 @@ import React, {
|
|||||||
Dispatch,
|
Dispatch,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useRef,
|
||||||
useState
|
useState
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
@ -21,20 +22,24 @@ import * as ImagePicker from 'expo-image-picker'
|
|||||||
import { debounce, differenceWith, isEqual } from 'lodash'
|
import { debounce, differenceWith, isEqual } from 'lodash'
|
||||||
|
|
||||||
import Autolinker from 'src/modules/autolinker'
|
import Autolinker from 'src/modules/autolinker'
|
||||||
import PostEmojis from './PostEmojis'
|
import ComposeEmojis from './Emojis'
|
||||||
import PostPoll from './PostPoll'
|
import ComposePoll from './Poll'
|
||||||
import PostSuggestions from './PostSuggestions'
|
import ComposeSuggestions from './Suggestions'
|
||||||
import { emojisFetch } from 'src/utils/fetches/emojisFetch'
|
import { emojisFetch } from 'src/utils/fetches/emojisFetch'
|
||||||
import { PostAction, PostState } from 'src/screens/Shared/Compose'
|
import { PostAction, PostState } from 'src/screens/Shared/Compose'
|
||||||
import addAttachments from './addAttachments'
|
import addAttachments from './addAttachments'
|
||||||
import PostAttachments from './PostAttachments'
|
import ComposeAttachments from './Attachments'
|
||||||
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
|
import ComposeActions from './Actions'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
postState: PostState
|
postState: PostState
|
||||||
postDispatch: Dispatch<PostAction>
|
postDispatch: Dispatch<PostAction>
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
const ComposeRoot: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||||
|
const { theme } = useTheme()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
const { status } = await ImagePicker.requestCameraRollPermissionsAsync()
|
const { status } = await ImagePicker.requestCameraRollPermissionsAsync()
|
||||||
@ -44,8 +49,6 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
})()
|
})()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const [editorMinHeight, setEditorMinHeight] = useState(0)
|
|
||||||
|
|
||||||
const { data: emojisData } = useQuery(['Emojis'], emojisFetch)
|
const { data: emojisData } = useQuery(['Emojis'], emojisFetch)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (emojisData && emojisData.length) {
|
if (emojisData && emojisData.length) {
|
||||||
@ -153,33 +156,49 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const getVisibilityIcon = () => {
|
const textInputRef = useRef<TextInput>(null)
|
||||||
switch (postState.visibility) {
|
|
||||||
case 'public':
|
const renderOverlay = (overlay: PostState['overlay']) => {
|
||||||
return 'globe'
|
switch (overlay) {
|
||||||
case 'unlisted':
|
case 'emojis':
|
||||||
return 'unlock'
|
return (
|
||||||
case 'private':
|
<View style={styles.emojis}>
|
||||||
return 'lock'
|
<ComposeEmojis
|
||||||
case 'direct':
|
textInputRef={textInputRef}
|
||||||
return 'mail'
|
onChangeText={onChangeText}
|
||||||
|
postState={postState}
|
||||||
|
postDispatch={postDispatch}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
case 'suggestions':
|
||||||
|
return (
|
||||||
|
<View style={styles.suggestions}>
|
||||||
|
<ComposeSuggestions
|
||||||
|
onChangeText={onChangeText}
|
||||||
|
postState={postState}
|
||||||
|
postDispatch={postDispatch}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.contentView}
|
style={[styles.contentView]}
|
||||||
alwaysBounceVertical={false}
|
alwaysBounceVertical={false}
|
||||||
keyboardDismissMode='interactive'
|
keyboardDismissMode='interactive'
|
||||||
|
// child touch event not picked up
|
||||||
|
keyboardShouldPersistTaps='always'
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[
|
style={[
|
||||||
styles.textInput
|
styles.textInput,
|
||||||
// {
|
{
|
||||||
// flex: postState.overlay ? 0 : 1,
|
color: theme.primary
|
||||||
// minHeight: editorMinHeight + 14
|
}
|
||||||
// }
|
|
||||||
]}
|
]}
|
||||||
autoCapitalize='none'
|
autoCapitalize='none'
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
@ -187,10 +206,8 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
enablesReturnKeyAutomatically
|
enablesReturnKeyAutomatically
|
||||||
multiline
|
multiline
|
||||||
placeholder='想说点什么'
|
placeholder='想说点什么'
|
||||||
|
placeholderTextColor={theme.secondary}
|
||||||
onChangeText={content => onChangeText({ content })}
|
onChangeText={content => onChangeText({ content })}
|
||||||
onContentSizeChange={({ nativeEvent }) => {
|
|
||||||
setEditorMinHeight(nativeEvent.contentSize.height)
|
|
||||||
}}
|
|
||||||
onSelectionChange={({
|
onSelectionChange={({
|
||||||
nativeEvent: {
|
nativeEvent: {
|
||||||
selection: { start, end }
|
selection: { start, end }
|
||||||
@ -198,13 +215,17 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
}) => {
|
}) => {
|
||||||
postDispatch({ type: 'selection', payload: { start, end } })
|
postDispatch({ type: 'selection', payload: { start, end } })
|
||||||
}}
|
}}
|
||||||
|
ref={textInputRef}
|
||||||
scrollEnabled
|
scrollEnabled
|
||||||
>
|
>
|
||||||
<Text>{postState.text.formatted}</Text>
|
<Text>{postState.text.formatted}</Text>
|
||||||
</TextInput>
|
</TextInput>
|
||||||
|
|
||||||
|
{renderOverlay(postState.overlay)}
|
||||||
|
|
||||||
{postState.attachments.length > 0 && (
|
{postState.attachments.length > 0 && (
|
||||||
<View style={styles.attachments}>
|
<View style={styles.attachments}>
|
||||||
<PostAttachments
|
<ComposeAttachments
|
||||||
postState={postState}
|
postState={postState}
|
||||||
postDispatch={postDispatch}
|
postDispatch={postDispatch}
|
||||||
/>
|
/>
|
||||||
@ -212,94 +233,15 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
)}
|
)}
|
||||||
{postState.poll.active && (
|
{postState.poll.active && (
|
||||||
<View style={styles.poll}>
|
<View style={styles.poll}>
|
||||||
<PostPoll postState={postState} postDispatch={postDispatch} />
|
<ComposePoll postState={postState} postDispatch={postDispatch} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{postState.overlay === 'suggestions' ? (
|
|
||||||
<View style={styles.suggestions}>
|
|
||||||
<PostSuggestions
|
|
||||||
onChangeText={onChangeText}
|
|
||||||
postState={postState}
|
|
||||||
postDispatch={postDispatch}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
{postState.overlay === 'emojis' ? (
|
|
||||||
<View style={styles.emojis}>
|
|
||||||
<PostEmojis
|
|
||||||
onChangeText={onChangeText}
|
|
||||||
postState={postState}
|
|
||||||
postDispatch={postDispatch}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<Pressable style={styles.additions} onPress={() => Keyboard.dismiss()}>
|
<ComposeActions
|
||||||
<Feather
|
textInputRef={textInputRef}
|
||||||
name='paperclip'
|
postState={postState}
|
||||||
size={24}
|
postDispatch={postDispatch}
|
||||||
color={postState.poll.active ? 'gray' : 'black'}
|
/>
|
||||||
onPress={async () =>
|
|
||||||
!postState.poll.active &&
|
|
||||||
(await addAttachments({ postState, postDispatch }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Feather
|
|
||||||
name='bar-chart-2'
|
|
||||||
size={24}
|
|
||||||
color={postState.attachments.length > 0 ? 'gray' : 'black'}
|
|
||||||
onPress={() =>
|
|
||||||
postState.attachments.length === 0 &&
|
|
||||||
postDispatch({
|
|
||||||
type: 'poll',
|
|
||||||
payload: { ...postState.poll, active: !postState.poll.active }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Feather
|
|
||||||
name={getVisibilityIcon()}
|
|
||||||
size={24}
|
|
||||||
onPress={() =>
|
|
||||||
ActionSheetIOS.showActionSheetWithOptions(
|
|
||||||
{
|
|
||||||
options: ['公开', '不公开', '仅关注着', '私信', '取消'],
|
|
||||||
cancelButtonIndex: 4
|
|
||||||
},
|
|
||||||
buttonIndex => {
|
|
||||||
switch (buttonIndex) {
|
|
||||||
case 0:
|
|
||||||
postDispatch({ type: 'visibility', payload: 'public' })
|
|
||||||
break
|
|
||||||
case 1:
|
|
||||||
postDispatch({ type: 'visibility', payload: 'unlisted' })
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
postDispatch({ type: 'visibility', payload: 'private' })
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
postDispatch({ type: 'visibility', payload: 'direct' })
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Feather
|
|
||||||
name='smile'
|
|
||||||
size={24}
|
|
||||||
color={postState.emojis?.length ? 'black' : 'white'}
|
|
||||||
onPress={() => {
|
|
||||||
if (postState.emojis?.length && postState.overlay === null) {
|
|
||||||
postDispatch({ type: 'overlay', payload: 'emojis' })
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Text>{postState.text.count}</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -308,36 +250,29 @@ const styles = StyleSheet.create({
|
|||||||
base: {
|
base: {
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
contentView: {
|
contentView: { flex: 1 },
|
||||||
flex: 1,
|
|
||||||
backgroundColor: 'gray'
|
|
||||||
},
|
|
||||||
textInput: {
|
textInput: {
|
||||||
backgroundColor: 'lightgray',
|
fontSize: StyleConstants.Font.Size.M,
|
||||||
paddingBottom: 20
|
marginTop: StyleConstants.Spacing.S,
|
||||||
|
marginBottom: StyleConstants.Spacing.M,
|
||||||
|
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
|
||||||
|
paddingRight: StyleConstants.Spacing.Global.PagePadding
|
||||||
},
|
},
|
||||||
attachments: {
|
attachments: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
height: 100
|
height: 100
|
||||||
},
|
},
|
||||||
poll: {
|
poll: {
|
||||||
height: 100
|
flex: 1,
|
||||||
|
padding: StyleConstants.Spacing.Global.PagePadding
|
||||||
},
|
},
|
||||||
suggestions: {
|
suggestions: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: 'lightyellow'
|
backgroundColor: 'lightyellow'
|
||||||
},
|
},
|
||||||
emojis: {
|
emojis: {
|
||||||
flex: 1,
|
flex: 1
|
||||||
backgroundColor: 'lightblue'
|
|
||||||
},
|
|
||||||
additions: {
|
|
||||||
height: 44,
|
|
||||||
backgroundColor: 'red',
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-around',
|
|
||||||
alignItems: 'center'
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default PostMain
|
export default ComposeRoot
|
@ -46,7 +46,7 @@ export interface Props {
|
|||||||
postDispatch: Dispatch<PostAction>
|
postDispatch: Dispatch<PostAction>
|
||||||
}
|
}
|
||||||
|
|
||||||
const PostSuggestions: React.FC<Props> = ({
|
const ComposeSuggestions: React.FC<Props> = ({
|
||||||
onChangeText,
|
onChangeText,
|
||||||
postState,
|
postState,
|
||||||
postDispatch
|
postDispatch
|
||||||
@ -94,4 +94,4 @@ const PostSuggestions: React.FC<Props> = ({
|
|||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PostSuggestions
|
export default ComposeSuggestions
|
@ -9,16 +9,27 @@ const updateText = ({
|
|||||||
postState: PostState
|
postState: PostState
|
||||||
newText: string
|
newText: string
|
||||||
}) => {
|
}) => {
|
||||||
onChangeText({
|
if (postState.text.raw.length) {
|
||||||
content: postState.text.raw
|
const contentFront = postState.text.raw.slice(0, postState.selection.start)
|
||||||
? [
|
const contentRear = postState.text.raw.slice(postState.selection.end)
|
||||||
postState.text.raw.slice(0, postState.selection.start),
|
|
||||||
newText,
|
const whiteSpaceFront = /\s/g.test(contentFront.slice(-1))
|
||||||
postState.text.raw.slice(postState.selection.end)
|
const whiteSpaceRear = /\s/g.test(contentRear.slice(-1))
|
||||||
].join('')
|
|
||||||
: newText,
|
const newTextWithSpace = `${whiteSpaceFront ? '' : ' '}${newText}${
|
||||||
disableDebounce: true
|
whiteSpaceRear ? '' : ' '
|
||||||
})
|
}`
|
||||||
|
|
||||||
|
onChangeText({
|
||||||
|
content: [contentFront, newTextWithSpace, contentRear].join(''),
|
||||||
|
disableDebounce: true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
onChangeText({
|
||||||
|
content: `${newText} `,
|
||||||
|
disableDebounce: true
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default updateText
|
export default updateText
|
||||||
|
Loading…
x
Reference in New Issue
Block a user