import { useState, useRef } from 'react'; import { View, Text, TextInput, Pressable, Image, ActivityIndicator, Alert, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import * as FileSystem from 'expo-file-system'; import * as ImagePicker from 'expo-image-picker'; import { apiFetch } from '../lib/api'; import { resolveAvatar } from '../lib/resolveAvatar'; import { useAuthStore } from '../stores/auth'; import { colors } from '../lib/theme'; type Props = { onPosted?: () => void; }; export function ComposeCard({ onPosted }: Props) { const { t } = useTranslation(); const { user } = useAuthStore(); const queryClient = useQueryClient(); const inputRef = useRef(null); const [focused, setFocused] = useState(false); const [content, setContent] = useState(''); const [imageUri, setImageUri] = useState(null); const [posting, setPosting] = useState(false); const avatarId = user?.user_metadata?.avatar_id as string | undefined; const nickname = (user?.user_metadata?.username as string | undefined) ?? t('community.compose_default_user'); const avatarUrl = resolveAvatar(avatarId ?? null, nickname); const cancel = () => { setContent(''); setImageUri(null); setFocused(false); inputRef.current?.blur(); }; const pickImage = async () => { const perm = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (!perm.granted) { Alert.alert(t('community.compose_photo_perm_title'), t('community.compose_photo_perm_desc')); return; } const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: false, quality: 0.8, }); if (!result.canceled && result.assets[0]?.uri) { setImageUri(result.assets[0].uri); } }; const submit = async () => { if (!content.trim() && !imageUri) return; setPosting(true); try { let uploadedImageUrl: string | undefined; if (imageUri) { const base64 = await FileSystem.readAsStringAsync(imageUri, { encoding: FileSystem.EncodingType.Base64, }); const upload = await apiFetch<{ url: string }>('/api/community/upload-image', { method: 'POST', body: { image: `data:image/jpeg;base64,${base64}`, mimeType: 'image/jpeg', }, }); uploadedImageUrl = upload?.url; } await apiFetch('/api/community/post', { method: 'POST', body: { category: 'story', content: content.trim(), ...(uploadedImageUrl ? { imageUrl: uploadedImageUrl } : {}), }, }); cancel(); queryClient.invalidateQueries({ queryKey: ['community-posts'] }); onPosted?.(); } catch (err: any) { Alert.alert(t('common.error'), err?.message ?? t('community.post_failed')); } finally { setPosting(false); } }; const showActions = focused || content.length > 0; return ( setFocused(true)} placeholder={t('community.compose_placeholder')} placeholderTextColor="#a3a3a3" multiline className="text-sm text-neutral-900 leading-5 min-h-[40px]" style={{ textAlignVertical: 'top', fontFamily: 'Nunito_400Regular' }} /> {imageUri && ( setImageUri(null)} className="absolute top-2 right-2 w-7 h-7 rounded-full bg-black/50 items-center justify-center" > )} {showActions && ( ({ opacity: pressed ? 0.6 : 1 })} > {t('community.image')} {t('common.cancel')} ({ opacity: pressed || !content.trim() || posting ? 0.5 : 1, })} > {posting ? ( ) : ( {t('community.share')} )} )} ); }