import React, {useState, useEffect} from 'react' import { ActivityIndicator, StyleProp, StyleSheet, Text, View, ViewStyle, } from 'react-native' import {observer} from 'mobx-react-lite' import Clipboard from '@react-native-clipboard/clipboard' import {AtUri} from '../../../third-party/uri' import * as PostType from '../../../third-party/api/src/client/types/app/bsky/feed/post' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {PostThreadViewModel} from '../../../state/models/post-thread-view' import {Link} from '../util/Link' import {UserInfoText} from '../util/UserInfoText' import {PostMeta} from '../util/PostMeta' import {PostEmbeds} from '../util/PostEmbeds' import {PostCtrls} from '../util/PostCtrls' import {RichText} from '../util/RichText' import * as Toast from '../util/Toast' import {UserAvatar} from '../util/UserAvatar' import {useStores} from '../../../state' import {s, colors} from '../../lib/styles' export const Post = observer(function Post({ uri, initView, showReplyLine, style, }: { uri: string initView?: PostThreadViewModel showReplyLine?: boolean style?: StyleProp }) { const store = useStores() const [view, setView] = useState(initView) const [deleted, setDeleted] = useState(false) useEffect(() => { if (initView || view?.params.uri === uri) { return // no change needed? or trigger refresh? } const newView = new PostThreadViewModel(store, {uri, depth: 0}) setView(newView) newView.setup().catch(err => console.error('Failed to fetch post', err)) }, [initView, uri, view?.params.uri, store]) // deleted // = if (deleted) { return } // loading // = if (!view || view.isLoading || view.params.uri !== uri) { return ( ) } // error // = if (view.hasError || !view.thread) { return ( {view.error || 'Thread not found'} ) } // loaded // = const item = view.thread const record = view.thread?.record as unknown as PostType.Record const itemUrip = new AtUri(item.uri) const itemHref = `/profile/${item.author.handle}/post/${itemUrip.rkey}` const itemTitle = `Post by ${item.author.handle}` const authorHref = `/profile/${item.author.handle}` const authorTitle = item.author.handle let replyAuthorDid = '' let replyHref = '' if (record.reply) { const urip = new AtUri(record.reply.parent?.uri || record.reply.root.uri) replyAuthorDid = urip.hostname replyHref = `/profile/${urip.hostname}/post/${urip.rkey}` } const onPressReply = () => { store.shell.openComposer({ replyTo: { uri: item.uri, cid: item.cid, text: item.record.text as string, author: { handle: item.author.handle, displayName: item.author.displayName, avatar: item.author.avatar, }, }, }) } const onPressToggleRepost = () => { item .toggleRepost() .catch(e => console.error('Failed to toggle repost', record, e)) } const onPressToggleUpvote = () => { item .toggleUpvote() .catch(e => console.error('Failed to toggle upvote', record, e)) } const onCopyPostText = () => { Clipboard.setString(record.text) Toast.show('Copied to clipboard') } const onDeletePost = () => { item.delete().then( () => { setDeleted(true) Toast.show('Post deleted') }, e => { console.error(e) Toast.show('Failed to delete post, please try again') }, ) } return ( {showReplyLine && } {replyHref !== '' && ( Reply to )} ) }) const styles = StyleSheet.create({ outer: { marginTop: 1, borderRadius: 6, backgroundColor: colors.white, padding: 10, }, layout: { flexDirection: 'row', }, layoutAvi: { width: 60, }, layoutContent: { flex: 1, }, postTextContainer: { flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', minHeight: 36, paddingBottom: 8, }, postText: { fontFamily: 'System', fontSize: 16, lineHeight: 20.8, // 1.3 of 16px }, replyLine: { position: 'absolute', left: 36, top: 70, bottom: 0, borderLeftWidth: 2, borderLeftColor: colors.gray2, }, })