An ATproto social media client -- with an independent Appview.

Always show post dropdown button at the bottom of the post, add share button to highlighted post (#2646)

* Always show post dropdown at the bottom

* Rm useless view (no longer needed)

* space icons evenly in big, add equal padding

* add share icon

* add onShare

* confirmed figma noob. figured it out.

* use our svg naming strat

* Update icon

---------

Co-authored-by: Hailey <me@haileyok.com>
Co-authored-by: Eric Bailey <git@esb.lol>

authored by danabra.mov

Hailey
Eric Bailey
and committed by
GitHub
ad8f9e56 7f4dbe94

+51 -39
+1
assets/icons/arrowOutOfBox_stroke2_corner0_rounded.svg
···
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12.707 3.293a1 1 0 0 0-1.414 0l-4.5 4.5a1 1 0 0 0 1.414 1.414L11 6.414v8.836a1 1 0 1 0 2 0V6.414l2.793 2.793a1 1 0 1 0 1.414-1.414l-4.5-4.5ZM5 12.75a1 1 0 1 0-2 0V20a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-7.25a1 1 0 1 0-2 0V19H5v-6.25Z" clip-rule="evenodd"/></svg>
+5
src/components/icons/ArrowOutOfBox.tsx
···
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const ArrowOutOfBox_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 + path: 'M12.707 3.293a1 1 0 0 0-1.414 0l-4.5 4.5a1 1 0 0 0 1.414 1.414L11 6.414v8.836a1 1 0 1 0 2 0V6.414l2.793 2.793a1 1 0 1 0 1.414-1.414l-4.5-4.5ZM5 12.75a1 1 0 1 0-2 0V20a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-7.25a1 1 0 1 0-2 0V19H5v-6.25Z', 5 + })
+1 -24
src/view/com/post-thread/PostThreadItem.tsx
··· 23 import {PostMeta} from '../util/PostMeta' 24 import {PostEmbeds} from '../util/post-embeds' 25 import {PostCtrls} from '../util/post-ctrls/PostCtrls' 26 - import {PostDropdownBtn} from '../util/forms/PostDropdownBtn' 27 import {PostHider} from '../util/moderation/PostHider' 28 import {ContentHider} from '../util/moderation/ContentHider' 29 import {PostAlerts} from '../util/moderation/PostAlerts' ··· 43 import {useOpenLink} from '#/state/preferences/in-app-browser' 44 import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' 45 import {ThreadPost} from '#/state/queries/post-thread' 46 - import {useSession} from '#/state/session' 47 import {WhoCanReply} from '../threadgate/WhoCanReply' 48 49 export function PostThreadItem({ ··· 162 const {_} = useLingui() 163 const langPrefs = useLanguagePrefs() 164 const {openComposer} = useComposerControls() 165 - const {currentAccount} = useSession() 166 const [limitLines, setLimitLines] = React.useState( 167 () => countLines(richText?.text) >= MAX_POST_LINES, 168 ) ··· 188 return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by') 189 }, [post.uri, post.author]) 190 const repostsTitle = _(msg`Reposts of this post`) 191 - const isModeratedPost = 192 - moderation.decisions.post.cause?.type === 'label' && 193 - moderation.decisions.post.cause.label.src !== currentAccount?.did 194 195 const translatorUrl = getTranslatorLink( 196 record?.text || '', ··· 331 </Link> 332 </View> 333 </View> 334 - <PostDropdownBtn 335 - testID="postDropdownBtn" 336 - postAuthor={post.author} 337 - postCid={post.cid} 338 - postUri={post.uri} 339 - record={record} 340 - richText={richText} 341 - showAppealLabelItem={ 342 - post.author.did === currentAccount?.did && isModeratedPost 343 - } 344 - style={{ 345 - paddingVertical: 6, 346 - paddingHorizontal: 10, 347 - marginLeft: 'auto', 348 - width: 40, 349 - }} 350 - /> 351 </View> 352 <View style={[s.pl10, s.pr10, s.pb10]}> 353 <ContentHider ··· 437 ) : ( 438 <></> 439 )} 440 - <View style={[s.pl10, s.pb5]}> 441 <PostCtrls 442 big 443 post={post}
··· 23 import {PostMeta} from '../util/PostMeta' 24 import {PostEmbeds} from '../util/post-embeds' 25 import {PostCtrls} from '../util/post-ctrls/PostCtrls' 26 import {PostHider} from '../util/moderation/PostHider' 27 import {ContentHider} from '../util/moderation/ContentHider' 28 import {PostAlerts} from '../util/moderation/PostAlerts' ··· 42 import {useOpenLink} from '#/state/preferences/in-app-browser' 43 import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' 44 import {ThreadPost} from '#/state/queries/post-thread' 45 import {WhoCanReply} from '../threadgate/WhoCanReply' 46 47 export function PostThreadItem({ ··· 160 const {_} = useLingui() 161 const langPrefs = useLanguagePrefs() 162 const {openComposer} = useComposerControls() 163 const [limitLines, setLimitLines] = React.useState( 164 () => countLines(richText?.text) >= MAX_POST_LINES, 165 ) ··· 185 return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by') 186 }, [post.uri, post.author]) 187 const repostsTitle = _(msg`Reposts of this post`) 188 189 const translatorUrl = getTranslatorLink( 190 record?.text || '', ··· 325 </Link> 326 </View> 327 </View> 328 </View> 329 <View style={[s.pl10, s.pr10, s.pb10]}> 330 <ContentHider ··· 414 ) : ( 415 <></> 416 )} 417 + <View style={[s.pl10, s.pr10, s.pb5]}> 418 <PostCtrls 419 big 420 post={post}
+44 -15
src/view/com/util/post-ctrls/PostCtrls.tsx
··· 9 import { 10 AppBskyFeedDefs, 11 AppBskyFeedPost, 12 RichText as RichTextAPI, 13 } from '@atproto/api' 14 import {Text} from '../text/Text' ··· 30 import {useRequireAuth} from '#/state/session' 31 import {msg} from '@lingui/macro' 32 import {useLingui} from '@lingui/react' 33 34 let PostCtrls = ({ 35 big, ··· 116 closeModal, 117 ]) 118 119 return ( 120 <View style={[styles.ctrls, style]}> 121 <View 122 style={[ 123 - styles.ctrl, 124 post.viewer?.replyDisabled ? {opacity: 0.5} : undefined, 125 ]}> 126 <TouchableOpacity ··· 149 ) : undefined} 150 </TouchableOpacity> 151 </View> 152 - <View style={styles.ctrl}> 153 <RepostButton 154 big={big} 155 isReposted={!!post.viewer?.repost} ··· 158 onQuote={onQuote} 159 /> 160 </View> 161 - <View style={styles.ctrl}> 162 <TouchableOpacity 163 testID="likeBtn" 164 style={[styles.btn, !big && styles.btnPad]} ··· 193 ) : undefined} 194 </TouchableOpacity> 195 </View> 196 - {big ? undefined : ( 197 - <View style={styles.ctrl}> 198 - <PostDropdownBtn 199 - testID="postDropdownBtn" 200 - postAuthor={post.author} 201 - postCid={post.cid} 202 - postUri={post.uri} 203 - record={record} 204 - richText={richText} 205 - showAppealLabelItem={showAppealLabelItem} 206 - style={styles.btnPad} 207 - /> 208 </View> 209 )} 210 </View> 211 ) 212 } ··· 222 ctrl: { 223 flex: 1, 224 alignItems: 'flex-start', 225 }, 226 btn: { 227 flexDirection: 'row',
··· 9 import { 10 AppBskyFeedDefs, 11 AppBskyFeedPost, 12 + AtUri, 13 RichText as RichTextAPI, 14 } from '@atproto/api' 15 import {Text} from '../text/Text' ··· 31 import {useRequireAuth} from '#/state/session' 32 import {msg} from '@lingui/macro' 33 import {useLingui} from '@lingui/react' 34 + import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox' 35 + import {toShareUrl} from 'lib/strings/url-helpers' 36 + import {shareUrl} from 'lib/sharing' 37 + import {makeProfileLink} from 'lib/routes/links' 38 39 let PostCtrls = ({ 40 big, ··· 121 closeModal, 122 ]) 123 124 + const onShare = useCallback(() => { 125 + const urip = new AtUri(post.uri) 126 + const href = makeProfileLink(post.author, 'post', urip.rkey) 127 + const url = toShareUrl(href) 128 + shareUrl(url) 129 + }, [post.uri, post.author]) 130 + 131 return ( 132 <View style={[styles.ctrls, style]}> 133 <View 134 style={[ 135 + big ? styles.ctrlBig : styles.ctrl, 136 post.viewer?.replyDisabled ? {opacity: 0.5} : undefined, 137 ]}> 138 <TouchableOpacity ··· 161 ) : undefined} 162 </TouchableOpacity> 163 </View> 164 + <View style={big ? styles.ctrlBig : styles.ctrl}> 165 <RepostButton 166 big={big} 167 isReposted={!!post.viewer?.repost} ··· 170 onQuote={onQuote} 171 /> 172 </View> 173 + <View style={big ? styles.ctrlBig : styles.ctrl}> 174 <TouchableOpacity 175 testID="likeBtn" 176 style={[styles.btn, !big && styles.btnPad]} ··· 205 ) : undefined} 206 </TouchableOpacity> 207 </View> 208 + {big && ( 209 + <View style={styles.ctrlBig}> 210 + <TouchableOpacity 211 + testID="likeBtn" 212 + style={[styles.btn]} 213 + onPress={onShare} 214 + accessibilityRole="button" 215 + accessibilityLabel={`${ 216 + post.viewer?.like ? _(msg`Unlike`) : _(msg`Like`) 217 + } (${post.likeCount} ${pluralize(post.likeCount || 0, 'like')})`} 218 + accessibilityHint="" 219 + hitSlop={big ? HITSLOP_20 : HITSLOP_10}> 220 + <ArrowOutOfBox style={[defaultCtrlColor, styles.mt1]} width={22} /> 221 + </TouchableOpacity> 222 </View> 223 )} 224 + <View style={big ? styles.ctrlBig : styles.ctrl}> 225 + <PostDropdownBtn 226 + testID="postDropdownBtn" 227 + postAuthor={post.author} 228 + postCid={post.cid} 229 + postUri={post.uri} 230 + record={record} 231 + richText={richText} 232 + showAppealLabelItem={showAppealLabelItem} 233 + style={styles.btnPad} 234 + /> 235 + </View> 236 </View> 237 ) 238 } ··· 248 ctrl: { 249 flex: 1, 250 alignItems: 'flex-start', 251 + }, 252 + ctrlBig: { 253 + alignItems: 'center', 254 }, 255 btn: { 256 flexDirection: 'row',