Files
frontend/src/screens/create/CreatePostScreen.tsx

940 lines
27 KiB
TypeScript
Raw Normal View History

/**
* CreatePostScreen
* BBS -
*
*
*
*
*/
import React, { useState, useCallback } from 'react';
import {
View,
ScrollView,
StyleSheet,
TouchableOpacity,
TextInput,
Alert,
KeyboardAvoidingView,
Platform,
Animated,
Image,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import * as ImagePicker from 'expo-image-picker';
import { colors, spacing, fontSizes, borderRadius, shadows } from '../../theme';
import { Text, Button, ResponsiveContainer } from '../../components/common';
import { postService, showPrompt, voteService } from '../../services';
import { ApiError } from '../../services/api';
import { uploadService } from '../../services/uploadService';
import VoteEditor from '../../components/business/VoteEditor';
import { useResponsive, useResponsiveValue } from '../../hooks';
const MAX_TITLE_LENGTH = 100;
const MAX_CONTENT_LENGTH = 2000;
// 表情面板高度
const EMOJI_PANEL_HEIGHT = 280;
// 常用表情列表
const EMOJIS = [
'😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂',
'🙂', '🙃', '😉', '😊', '😇', '🥰', '😍', '🤩',
'😘', '😗', '😚', '😙', '🥲', '😋', '😛', '😜',
'🤪', '😝', '🤑', '🤗', '🤭', '🤫', '🤔', '🤐',
'🤨', '😐', '😑', '😶', '😏', '😒', '🙄',
'😬', '🤥', '😌', '😔', '😪', '🤤', '😴', '😷',
'🤒', '🤕', '🤢', '🤮', '🤧', '🥵', '🥶', '🥴',
'😵', '🤯', '🤠', '🥳', '🥸', '😎', '🤓',
'🧐', '😕', '😟', '🙁', '☹️', '😮', '😯', '😲',
'😳', '🥺', '😦', '😧', '😨', '😰', '😥', '😢',
'😭', '😱', '😖', '😣', '😞', '😓', '😩', '😫',
'🥱', '😤', '😡', '😠', '🤬', '😈', '👿', '💀',
'👋', '🤚', '🖐️', '✋', '🖖', '👌', '🤌', '🤏',
'✌️', '🤞', '🤟', '🤘', '🤙', '👈', '👉', '👆',
'👍', '👎', '✊', '👊', '🤛', '🤜', '👏', '🙌',
'👐', '🤲', '🤝', '🙏', '✍️', '💪', '🦾', '🦵',
'❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍',
'🤎', '💔', '❤️\u200d🔥', '❤️\u200d🩹', '💕', '💞', '💓', '💗',
'💖', '💘', '💝', '🎉', '🎊', '🎁', '🎈', '✨',
'🔥', '💯', '💢', '💥', '💫', '💦', '💨', '🕳️',
];
// 动画值
const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity);
const getPublishErrorMessage = (error: unknown): string => {
if (error instanceof ApiError && error.message?.trim()) {
return error.message.trim();
}
return '发布失败,请重试';
};
export const CreatePostScreen: React.FC = () => {
const navigation = useNavigation();
// 响应式布局
const { isWideScreen, width } = useResponsive();
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [images, setImages] = useState<{ uri: string; uploading?: boolean }[]>([]);
const [tags, setTags] = useState<string[]>([]);
const [tagInput, setTagInput] = useState('');
const [showTagInput, setShowTagInput] = useState(false);
const [showEmojiPanel, setShowEmojiPanel] = useState(false);
const [posting, setPosting] = useState(false);
// 投票相关状态
const [isVotePost, setIsVotePost] = useState(false);
const [voteOptions, setVoteOptions] = useState<string[]>(['', '']); // 默认2个选项
// 内容输入框引用
const contentInputRef = React.useRef<TextInput>(null);
const [selection, setSelection] = useState({ start: 0, end: 0 });
// 动画值
const fadeAnim = React.useRef(new Animated.Value(0)).current;
const slideAnim = React.useRef(new Animated.Value(20)).current;
// 响应式图片网格配置
const imagesPerRow = useResponsiveValue({ xs: 3, sm: 3, md: 4, lg: 5, xl: 6 });
const imageGap = 8;
const availableWidth = isWideScreen
? Math.min(width, 800) - spacing.lg * 2
: width - spacing.lg * 2;
const imageSize = (availableWidth - imageGap * (imagesPerRow - 1)) / imagesPerRow;
React.useEffect(() => {
Animated.parallel([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 250,
useNativeDriver: true,
}),
Animated.timing(slideAnim, {
toValue: 0,
duration: 250,
useNativeDriver: true,
}),
]).start();
}, []);
// 选择图片
const handlePickImage = async () => {
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!permissionResult.granted) {
Alert.alert('权限不足', '需要访问相册权限来选择图片');
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: 'images',
allowsMultipleSelection: true,
selectionLimit: 0,
quality: 1,
});
if (!result.canceled && result.assets) {
const newImages = result.assets.map(asset => ({ uri: asset.uri, uploading: true }));
setImages(prev => [...prev, ...newImages]);
// 上传图片
for (let i = 0; i < newImages.length; i++) {
const asset = result.assets[i];
const uploadResult = await uploadService.uploadImage({
uri: asset.uri,
type: asset.mimeType || 'image/jpeg',
});
if (uploadResult) {
setImages(prev => {
const updated = [...prev];
const index = prev.findIndex(img => img.uri === asset.uri);
if (index !== -1) {
updated[index] = { uri: uploadResult.url, uploading: false };
}
return updated;
});
} else {
Alert.alert('上传失败', '图片上传失败,请重试');
setImages(prev => prev.filter(img => img.uri !== asset.uri));
}
}
}
};
// 拍照
const handleTakePhoto = async () => {
const permissionResult = await ImagePicker.requestCameraPermissionsAsync();
if (!permissionResult.granted) {
Alert.alert('权限不足', '需要访问相机权限来拍照');
return;
}
const result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
quality: 0.8,
});
if (!result.canceled && result.assets[0]) {
const asset = result.assets[0];
setImages(prev => [...prev, { uri: asset.uri, uploading: true }]);
// 上传图片
const uploadResult = await uploadService.uploadImage({
uri: asset.uri,
type: asset.mimeType || 'image/jpeg',
});
if (uploadResult) {
setImages(prev => {
const updated = [...prev];
const index = prev.findIndex(img => img.uri === asset.uri);
if (index !== -1) {
updated[index] = { uri: uploadResult.url, uploading: false };
}
return updated;
});
} else {
Alert.alert('上传失败', '图片上传失败,请重试');
setImages(prev => prev.filter(img => img.uri !== asset.uri));
}
}
};
// 删除图片
const handleRemoveImage = (index: number) => {
setImages(images.filter((_, i) => i !== index));
};
// 添加标签
const handleAddTag = useCallback(() => {
const tag = tagInput.trim().replace(/^#/, '');
if (tag && !tags.includes(tag) && tags.length < 5) {
setTags([...tags, tag]);
setTagInput('');
setShowTagInput(false);
}
}, [tagInput, tags]);
// 删除标签
const handleRemoveTag = (tag: string) => {
setTags(tags.filter(t => t !== tag));
};
// 插入表情
const handleInsertEmoji = (emoji: string) => {
const newContent = content.slice(0, selection.start) + emoji + content.slice(selection.end);
setContent(newContent);
const newPosition = selection.start + emoji.length;
setSelection({ start: newPosition, end: newPosition });
};
// 关闭所有面板
const closeAllPanels = () => {
setShowEmojiPanel(false);
setShowTagInput(false);
};
// 切换表情面板
const handleToggleEmojiPanel = () => {
closeAllPanels();
setShowEmojiPanel(!showEmojiPanel);
};
// 切换标签输入
const handleToggleTagInput = () => {
closeAllPanels();
setShowTagInput(!showTagInput);
};
// 切换投票模式
const handleToggleVote = () => {
closeAllPanels();
setIsVotePost(!isVotePost);
};
// 添加投票选项
const handleAddVoteOption = () => {
if (voteOptions.length < 10) {
setVoteOptions([...voteOptions, '']);
}
};
// 删除投票选项
const handleRemoveVoteOption = (index: number) => {
if (voteOptions.length > 2) {
setVoteOptions(voteOptions.filter((_, i) => i !== index));
}
};
// 更新投票选项
const handleUpdateVoteOption = (index: number, value: string) => {
const newOptions = [...voteOptions];
newOptions[index] = value;
setVoteOptions(newOptions);
};
// 发布帖子
const handlePost = async () => {
if (!content.trim()) {
Alert.alert('错误', '请输入帖子内容');
return;
}
// 检查是否有图片正在上传
const uploadingImages = images.filter(img => img.uploading);
if (uploadingImages.length > 0) {
Alert.alert('请稍候', '图片正在上传中,请稍后再试');
return;
}
setPosting(true);
try {
const imageUrls = images.map(img => img.uri);
if (isVotePost) {
// 验证投票选项
const validOptions = voteOptions.filter(opt => opt.trim() !== '');
if (validOptions.length < 2) {
Alert.alert('错误', '投票选项至少需要2个');
setPosting(false);
return;
}
// 创建投票帖子
await voteService.createVotePost({
title: title.trim() || '无标题',
content: content.trim(),
images: imageUrls,
vote_options: validOptions,
});
showPrompt({
type: 'info',
title: '审核中',
message: '投票帖已提交,内容审核中,稍后展示',
duration: 2600,
});
navigation.goBack();
} else {
// 创建普通帖子
await postService.createPost({
title: title.trim() || '无标题',
content: content.trim(),
images: imageUrls,
});
showPrompt({
type: 'info',
title: '审核中',
message: '帖子已提交,内容审核中,稍后展示',
duration: 2600,
});
navigation.goBack();
}
} catch (error) {
console.error('发布帖子失败:', error);
Alert.alert('错误', getPublishErrorMessage(error));
} finally {
setPosting(false);
}
};
// 渲染图片网格
const renderImageGrid = () => {
if (images.length === 0) return null;
return (
<View style={styles.imageGrid}>
{images.map((img, index) => (
<Animated.View
key={`${img.uri}-${index}`}
style={[
styles.imageGridItem,
{
width: imageSize,
height: imageSize,
opacity: fadeAnim,
transform: [{ scale: fadeAnim }]
}
]}
>
<Image source={{ uri: img.uri }} style={styles.gridImage} resizeMode="cover" />
{img.uploading && (
<View style={styles.uploadingOverlay}>
<MaterialCommunityIcons name="loading" size={20} color={colors.text.inverse} />
</View>
)}
<TouchableOpacity
style={styles.removeImageButton}
onPress={() => handleRemoveImage(index)}
disabled={img.uploading}
hitSlop={{ top: 10, right: 10, bottom: 10, left: 10 }}
>
<View style={styles.removeImageButtonInner}>
<MaterialCommunityIcons name="close" size={12} color={colors.text.inverse} />
</View>
</TouchableOpacity>
</Animated.View>
))}
{/* 添加图片按钮 */}
<TouchableOpacity
style={[
styles.addImageGridButton,
{ width: imageSize, height: imageSize }
]}
onPress={handlePickImage}
>
<MaterialCommunityIcons name="plus" size={32} color={colors.text.hint} />
</TouchableOpacity>
</View>
);
};
// 渲染标签
const renderTags = () => {
if (tags.length === 0 && !showTagInput) return null;
return (
<View style={styles.tagsSection}>
<View style={styles.tagsContainer}>
{tags.map((tag, index) => (
<View key={index} style={styles.tag}>
<MaterialCommunityIcons name="pound" size={12} color={colors.primary.main} />
<Text variant="caption" color={colors.primary.main} style={styles.tagText}>{tag}</Text>
{/* 独立的删除按钮 */}
<TouchableOpacity
style={styles.tagDeleteButton}
onPress={() => handleRemoveTag(tag)}
hitSlop={{ top: 5, right: 5, bottom: 5, left: 5 }}
>
<MaterialCommunityIcons name="close-circle" size={14} color={colors.primary.main} />
</TouchableOpacity>
</View>
))}
{tags.length < 5 && showTagInput && (
<View style={styles.tagInputWrapper}>
<MaterialCommunityIcons name="pound" size={14} color={colors.primary.main} />
<TextInput
style={styles.tagInput}
value={tagInput}
onChangeText={setTagInput}
placeholder="输入话题"
placeholderTextColor={colors.text.hint}
onSubmitEditing={handleAddTag}
returnKeyType="done"
autoFocus
maxLength={20}
/>
<TouchableOpacity onPress={() => { setTagInput(''); setShowTagInput(false); }} style={styles.tagCancelButton}>
<MaterialCommunityIcons name="close" size={16} color={colors.text.secondary} />
</TouchableOpacity>
<TouchableOpacity onPress={handleAddTag} style={styles.tagConfirmButton}>
<MaterialCommunityIcons name="check" size={16} color={colors.primary.main} />
</TouchableOpacity>
</View>
)}
{tags.length < 5 && !showTagInput && (
<TouchableOpacity style={styles.addTagButton} onPress={() => setShowTagInput(true)}>
<MaterialCommunityIcons name="plus" size={14} color={colors.text.secondary} />
<Text variant="caption" color={colors.text.secondary} style={styles.addTagText}></Text>
</TouchableOpacity>
)}
</View>
</View>
);
};
// 渲染投票编辑器
const renderVoteEditor = () => {
if (!isVotePost) return null;
return (
<View style={isWideScreen && styles.voteEditorWide}>
<VoteEditor
options={voteOptions}
onAddOption={handleAddVoteOption}
onRemoveOption={handleRemoveVoteOption}
onUpdateOption={handleUpdateVoteOption}
maxOptions={10}
minOptions={2}
maxLength={50}
/>
</View>
);
};
// 渲染内容输入区
const renderContentSection = () => (
<View style={styles.contentSection}>
{/* 标题输入 */}
<TextInput
style={[
styles.titleInput,
isWideScreen && styles.titleInputWide,
]}
value={title}
onChangeText={setTitle}
placeholder="添加标题(可选)"
placeholderTextColor={colors.text.hint}
maxLength={MAX_TITLE_LENGTH}
/>
{/* 正文输入 */}
<TextInput
ref={contentInputRef}
style={[
styles.contentInput,
isWideScreen && styles.contentInputWide,
]}
value={content}
onChangeText={setContent}
placeholder="分享新鲜事..."
placeholderTextColor={colors.text.hint}
multiline
textAlignVertical="top"
maxLength={MAX_CONTENT_LENGTH}
onSelectionChange={(e) => setSelection(e.nativeEvent.selection)}
/>
</View>
);
// 渲染表情面板
const renderEmojiPanel = () => {
if (!showEmojiPanel) return null;
return (
<View style={[
styles.emojiPanel,
isWideScreen && styles.emojiPanelWide,
]}>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.emojiGrid}
>
{EMOJIS.map((emoji, index) => (
<TouchableOpacity
key={index}
style={styles.emojiItem}
onPress={() => handleInsertEmoji(emoji)}
>
<Text style={styles.emojiText}>{emoji}</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
);
};
// 渲染底部工具栏
const renderToolbar = () => (
<View style={styles.toolbar}>
{/* 左侧功能按钮组 */}
<View style={styles.toolbarLeft}>
<TouchableOpacity
style={styles.toolbarButton}
onPress={handlePickImage}
disabled={posting}
>
<View style={styles.toolbarButtonInner}>
<MaterialCommunityIcons
name="image-outline"
size={24}
color={colors.text.secondary}
/>
</View>
</TouchableOpacity>
<TouchableOpacity
style={styles.toolbarButton}
onPress={handleTakePhoto}
disabled={posting}
>
<View style={styles.toolbarButtonInner}>
<MaterialCommunityIcons
name="camera-outline"
size={24}
color={colors.text.secondary}
/>
</View>
</TouchableOpacity>
<TouchableOpacity
style={styles.toolbarButton}
onPress={handleToggleTagInput}
disabled={tags.length >= 5 || posting}
>
<View style={styles.toolbarButtonInner}>
<MaterialCommunityIcons
name="pound"
size={24}
color={tags.length >= 5 ? colors.text.disabled : (showTagInput ? colors.primary.main : colors.text.secondary)}
/>
{tags.length > 0 && (
<View style={styles.tagBadge}>
<Text style={styles.tagBadgeText}>{tags.length}</Text>
</View>
)}
</View>
</TouchableOpacity>
<TouchableOpacity
style={styles.toolbarButton}
onPress={handleToggleEmojiPanel}
disabled={posting}
>
<View style={styles.toolbarButtonInner}>
<MaterialCommunityIcons
name={showEmojiPanel ? "emoticon-happy" : "emoticon-happy-outline"}
size={24}
color={showEmojiPanel ? colors.primary.main : colors.text.secondary}
/>
</View>
</TouchableOpacity>
{/* 投票按钮 */}
<TouchableOpacity
style={styles.toolbarButton}
onPress={handleToggleVote}
disabled={posting}
>
<View style={styles.toolbarButtonInner}>
<MaterialCommunityIcons
name={isVotePost ? "vote" : "vote-outline"}
size={24}
color={isVotePost ? colors.primary.main : colors.text.secondary}
/>
</View>
</TouchableOpacity>
</View>
{/* 右侧发布按钮 */}
<View style={styles.toolbarRight}>
<Text variant="caption" color={colors.text.hint} style={styles.charCount}>
{content.length}/{MAX_CONTENT_LENGTH}
</Text>
<TouchableOpacity
style={[
styles.postButton,
(!content.trim() || posting) && styles.postButtonDisabled
]}
onPress={handlePost}
disabled={!content.trim() || posting}
activeOpacity={0.8}
>
{posting ? (
<MaterialCommunityIcons name="loading" size={18} color={colors.primary.contrast} />
) : (
<Text variant="body" color={colors.primary.contrast} style={styles.postButtonText}>
</Text>
)}
</TouchableOpacity>
</View>
</View>
);
// 渲染主内容
const renderMainContent = () => (
<>
<ScrollView
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
{/* 内容输入区 */}
{renderContentSection()}
{/* 投票编辑器 */}
{renderVoteEditor()}
{/* 图片网格 */}
{renderImageGrid()}
{/* 标签 */}
{renderTags()}
</ScrollView>
{/* 表情面板 - 位于底部工具栏上方 */}
{renderEmojiPanel()}
{/* 底部工具栏 - 重新设计 */}
{renderToolbar()}
</>
);
return (
<SafeAreaView style={styles.container} edges={['bottom']}>
<KeyboardAvoidingView
style={styles.flex}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={Platform.OS === 'ios' ? 90 : 0}
>
{isWideScreen ? (
<ResponsiveContainer maxWidth={800}>
{renderMainContent()}
</ResponsiveContainer>
) : (
renderMainContent()
)}
</KeyboardAvoidingView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
flex: {
flex: 1,
},
container: {
flex: 1,
backgroundColor: colors.background.paper,
},
scrollContent: {
paddingBottom: spacing.xl,
},
// 内容输入区
contentSection: {
paddingHorizontal: spacing.lg,
paddingTop: spacing.md,
},
titleInput: {
fontSize: fontSizes.lg,
fontWeight: '600',
color: colors.text.primary,
paddingVertical: spacing.sm,
marginBottom: spacing.sm,
},
titleInputWide: {
fontSize: fontSizes.xl,
},
contentInput: {
fontSize: fontSizes.md,
color: colors.text.primary,
minHeight: 120,
lineHeight: 22,
paddingVertical: spacing.sm,
},
contentInputWide: {
fontSize: fontSizes.lg,
lineHeight: 26,
minHeight: 150,
},
// 图片网格
imageGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
paddingHorizontal: spacing.lg,
paddingTop: spacing.md,
gap: 8,
},
imageGridItem: {
borderRadius: borderRadius.md,
overflow: 'hidden',
backgroundColor: colors.background.disabled,
},
gridImage: {
width: '100%',
height: '100%',
},
uploadingOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
},
removeImageButton: {
position: 'absolute',
top: 4,
right: 4,
zIndex: 10,
},
removeImageButtonInner: {
width: 20,
height: 20,
borderRadius: borderRadius.full,
backgroundColor: 'rgba(0, 0, 0, 0.6)',
justifyContent: 'center',
alignItems: 'center',
},
addImageGridButton: {
borderRadius: borderRadius.md,
borderWidth: 1,
borderColor: colors.divider,
borderStyle: 'dashed',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.background.default,
},
// 标签
tagsSection: {
paddingHorizontal: spacing.lg,
paddingTop: spacing.lg,
},
tagsContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: spacing.sm,
},
tag: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.primary.light + '15',
borderRadius: borderRadius.full,
paddingLeft: spacing.sm,
paddingRight: spacing.xs,
paddingVertical: spacing.xs,
},
tagText: {
marginLeft: spacing.xs,
fontWeight: '500',
},
tagDeleteButton: {
marginLeft: spacing.xs,
padding: 2,
},
tagInputWrapper: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.background.default,
borderRadius: borderRadius.full,
paddingLeft: spacing.sm,
paddingRight: spacing.xs,
paddingVertical: spacing.xs,
borderWidth: 1,
borderColor: colors.primary.main,
},
tagInput: {
fontSize: fontSizes.sm,
color: colors.text.primary,
minWidth: 80,
marginLeft: spacing.xs,
padding: 0,
},
tagCancelButton: {
padding: spacing.xs,
marginRight: spacing.xs,
},
tagConfirmButton: {
padding: spacing.xs,
},
addTagButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.background.default,
borderRadius: borderRadius.full,
paddingHorizontal: spacing.md,
paddingVertical: spacing.xs,
borderWidth: 1,
borderColor: colors.divider,
borderStyle: 'dashed',
},
addTagText: {
marginLeft: spacing.xs,
},
// 投票编辑器宽屏样式
voteEditorWide: {
maxWidth: 600,
alignSelf: 'center',
width: '100%',
},
// 底部工具栏
toolbar: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: spacing.lg,
paddingVertical: spacing.sm,
backgroundColor: colors.background.paper,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: colors.divider,
},
toolbarLeft: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs,
},
toolbarRight: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.md,
},
toolbarButton: {
position: 'relative',
},
toolbarButtonInner: {
width: 40,
height: 40,
borderRadius: borderRadius.full,
justifyContent: 'center',
alignItems: 'center',
},
tagBadge: {
position: 'absolute',
top: 4,
right: 4,
minWidth: 16,
height: 16,
borderRadius: 8,
backgroundColor: colors.primary.main,
justifyContent: 'center',
alignItems: 'center',
},
tagBadgeText: {
color: colors.primary.contrast,
fontSize: fontSizes.xs,
fontWeight: '600',
},
charCount: {
fontSize: fontSizes.xs,
},
postButton: {
backgroundColor: colors.primary.main,
paddingHorizontal: spacing.lg,
paddingVertical: spacing.sm,
borderRadius: borderRadius.full,
minWidth: 64,
alignItems: 'center',
justifyContent: 'center',
},
postButtonDisabled: {
backgroundColor: colors.text.disabled,
},
postButtonText: {
fontWeight: '600',
fontSize: fontSizes.sm,
},
// 表情面板
emojiPanel: {
height: EMOJI_PANEL_HEIGHT,
backgroundColor: colors.background.paper,
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: colors.divider,
},
emojiPanelWide: {
height: 320,
},
emojiGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
padding: spacing.md,
justifyContent: 'flex-start',
},
emojiItem: {
width: '12.5%',
aspectRatio: 1,
justifyContent: 'center',
alignItems: 'center',
},
emojiText: {
fontSize: 24,
},
});
export default CreatePostScreen;