Combine actions, convert to new menu (#3174)

* Combine actions, convert to new menu

* remove about tab and move content to header

* Tweak alignment

* fix missing rkey

* hog the like button

* Add a little more whitespace

* Improve a11y

* Yeah toast

* Update usage

* Pin to Home

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
zio/stable
Eric Bailey 2024-03-12 13:50:53 -05:00 committed by GitHub
parent 8123299192
commit c9d821c572
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 182 additions and 202 deletions

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M2 12a2 2 0 1 1 4 0 2 2 0 0 1-4 0Zm16 0a2 2 0 1 1 4 0 2 2 0 0 1-4 0Zm-6-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 245 B

View File

@ -228,6 +228,7 @@ export function InlineLink({
onPress: outerOnPress,
download,
selectable,
label,
...rest
}: InlineLinkProps) {
const t = useTheme()
@ -255,7 +256,8 @@ export function InlineLink({
return (
<Text
selectable={selectable}
label={href}
accessibilityHint=""
accessibilityLabel={label || href}
{...rest}
style={[
{color: t.palette.primary_500},

View File

@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const DotGrid_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M2 12a2 2 0 1 1 4 0 2 2 0 0 1-4 0Zm16 0a2 2 0 1 1 4 0 2 2 0 0 1-4 0Zm-6-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z',
})

View File

@ -0,0 +1,9 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Heart2_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M16.734 5.091c-1.238-.276-2.708.047-4.022 1.38a1 1 0 0 1-1.424 0C9.974 5.137 8.504 4.814 7.266 5.09c-1.263.282-2.379 1.206-2.92 2.556C3.33 10.18 4.252 14.84 12 19.348c7.747-4.508 8.67-9.168 7.654-11.7-.541-1.351-1.657-2.275-2.92-2.557Zm4.777 1.812c1.604 4-.494 9.69-9.022 14.47a1 1 0 0 1-.978 0C2.983 16.592.885 10.902 2.49 6.902c.779-1.942 2.414-3.334 4.342-3.764 1.697-.378 3.552.003 5.169 1.286 1.617-1.283 3.472-1.664 5.17-1.286 1.927.43 3.562 1.822 4.34 3.764Z',
})
export const Heart2_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12.489 21.372c8.528-4.78 10.626-10.47 9.022-14.47-.779-1.941-2.414-3.333-4.342-3.763-1.697-.378-3.552.003-5.169 1.287-1.617-1.284-3.472-1.665-5.17-1.287-1.927.43-3.562 1.822-4.34 3.764-1.605 4 .493 9.69 9.021 14.47a1 1 0 0 0 .978 0Z',
})

View File

@ -1,11 +1,9 @@
import React, {useMemo, useCallback} from 'react'
import {Dimensions, StyleSheet, View} from 'react-native'
import {StyleSheet, View, Pressable} from 'react-native'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {useIsFocused, useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {usePalette} from 'lib/hooks/usePalette'
import {HeartIcon, HeartIconSolid} from 'lib/icons'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {CommonNavigatorParams} from 'lib/routes/types'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {s} from 'lib/styles'
@ -13,7 +11,7 @@ import {FeedDescriptor} from '#/state/queries/post-feed'
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader'
import {Feed} from 'view/com/posts/Feed'
import {TextLink} from 'view/com/util/Link'
import {InlineLink} from '#/components/Link'
import {ListRef} from 'view/com/util/List'
import {Button} from 'view/com/util/forms/Button'
import {Text} from 'view/com/util/text/Text'
@ -29,15 +27,10 @@ import {shareUrl} from 'lib/sharing'
import {toShareUrl} from 'lib/strings/url-helpers'
import {Haptics} from 'lib/haptics'
import {useAnalytics} from 'lib/analytics/analytics'
import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
import {useScrollHandlers} from '#/lib/ScrollContext'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {makeCustomFeedLink} from 'lib/routes/links'
import {pluralize} from 'lib/strings/helpers'
import {CenteredView, ScrollView} from 'view/com/util/Views'
import {CenteredView} from 'view/com/util/Views'
import {NavigationProp} from 'lib/routes/types'
import {sanitizeHandle} from 'lib/strings/handles'
import {makeProfileLink} from 'lib/routes/links'
import {ComposeIcon2} from 'lib/icons'
import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro'
@ -59,9 +52,21 @@ import {useComposerControls} from '#/state/shell/composer'
import {truncateAndInvalidate} from '#/state/queries/util'
import {isNative} from '#/platform/detection'
import {listenSoftReset} from '#/state/events'
import {atoms as a} from '#/alf'
import {atoms as a, useTheme} from '#/alf'
import * as Menu from '#/components/Menu'
import {HITSLOP_20} from '#/lib/constants'
import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
import {
Heart2_Stroke2_Corner0_Rounded as HeartOutline,
Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
} from '#/components/icons/Heart2'
import {Button as NewButton, ButtonText} from '#/components/Button'
const SECTION_TITLES = ['Posts', 'About']
const SECTION_TITLES = ['Posts']
interface SectionRef {
scrollToTop: () => void
@ -148,7 +153,7 @@ export function ProfileFeedScreenInner({
feedInfo: FeedSourceFeedInfo
}) {
const {_} = useLingui()
const pal = usePalette('default')
const t = useTheme()
const {hasSession, currentAccount} = useSession()
const {openModal} = useModalControls()
const {openComposer} = useComposerControls()
@ -200,9 +205,11 @@ export function ProfileFeedScreenInner({
if (isSaved) {
await removeFeed({uri: feedInfo.uri})
resetRemoveFeed()
Toast.show(_(msg`Removed from your feeds`))
} else {
await saveFeed({uri: feedInfo.uri})
resetSaveFeed()
Toast.show(_(msg`Saved to your feeds`))
}
} catch (err) {
Toast.show(
@ -263,69 +270,9 @@ export function ProfileFeedScreenInner({
[feedSectionRef],
)
// render
// =
const dropdownItems: DropdownItem[] = React.useMemo(() => {
return [
hasSession && {
testID: 'feedHeaderDropdownToggleSavedBtn',
label: isSaved ? _(msg`Remove from my feeds`) : _(msg`Add to my feeds`),
onPress: isSavePending || isRemovePending ? undefined : onToggleSaved,
icon: isSaved
? {
ios: {
name: 'trash',
},
android: 'ic_delete',
web: ['far', 'trash-can'],
}
: {
ios: {
name: 'plus',
},
android: '',
web: 'plus',
},
},
hasSession && {
testID: 'feedHeaderDropdownReportBtn',
label: _(msg`Report feed`),
onPress: onPressReport,
icon: {
ios: {
name: 'exclamationmark.triangle',
},
android: 'ic_menu_report_image',
web: 'circle-exclamation',
},
},
{
testID: 'feedHeaderDropdownShareBtn',
label: _(msg`Share feed`),
onPress: onPressShare,
icon: {
ios: {
name: 'square.and.arrow.up',
},
android: 'ic_menu_share',
web: 'share',
},
},
].filter(Boolean) as DropdownItem[]
}, [
hasSession,
onToggleSaved,
onPressReport,
onPressShare,
isSaved,
isSavePending,
isRemovePending,
_,
])
const renderHeader = useCallback(() => {
return (
<>
<ProfileSubpageHeader
isLoading={false}
href={feedInfo.route.href}
@ -338,55 +285,117 @@ export function ProfileFeedScreenInner({
: undefined
}
avatarType="algo">
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
{feedInfo && hasSession && (
<>
<Button
disabled={isSavePending || isRemovePending}
type="default"
label={isSaved ? _(msg`Unsave`) : _(msg`Save`)}
onPress={onToggleSaved}
style={styles.btn}
/>
<Button
<NewButton
testID={isPinned ? 'unpinBtn' : 'pinBtn'}
disabled={isPinPending || isUnpinPending}
type={isPinned ? 'default' : 'inverted'}
label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)}
onPress={onTogglePinned}
style={styles.btn}
size="small"
variant="solid"
color={isPinned ? 'secondary' : 'primary'}
label={isPinned ? _(msg`Unpin from home`) : _(msg`Pin to home`)}
onPress={onTogglePinned}>
<ButtonText>
{isPinned ? _(msg`Unpin`) : _(msg`Pin to Home`)}
</ButtonText>
</NewButton>
)}
<Menu.Root>
<Menu.Trigger label={_(msg`Open feed options menu`)}>
{({props, state}) => {
return (
<Pressable
{...props}
hitSlop={HITSLOP_20}
style={[
a.justify_center,
a.align_center,
a.rounded_full,
{height: 36, width: 36},
t.atoms.bg_contrast_50,
(state.hovered || state.pressed) && [
t.atoms.bg_contrast_100,
],
]}
testID="headerDropdownBtn">
<Ellipsis
size="lg"
fill={t.atoms.text_contrast_medium.color}
/>
</Pressable>
)
}}
</Menu.Trigger>
<Menu.Outer>
<Menu.Group>
{hasSession && (
<>
<Menu.Item
disabled={isSavePending || isRemovePending}
testID="feedHeaderDropdownToggleSavedBtn"
label={
isSaved
? _(msg`Remove from my feeds`)
: _(msg`Save to my feeds`)
}
onPress={onToggleSaved}>
<Menu.ItemText>
{isSaved
? _(msg`Remove from my feeds`)
: _(msg`Save to my feeds`)}
</Menu.ItemText>
<Menu.ItemIcon
icon={isSaved ? Trash : Plus}
position="right"
/>
</Menu.Item>
<Menu.Item
testID="feedHeaderDropdownReportBtn"
label={_(msg`Report feed`)}
onPress={onPressReport}>
<Menu.ItemText>{_(msg`Report feed`)}</Menu.ItemText>
<Menu.ItemIcon icon={CircleInfo} position="right" />
</Menu.Item>
</>
)}
<NativeDropdown
testID="headerDropdownBtn"
items={dropdownItems}
accessibilityLabel={_(msg`More options`)}
accessibilityHint="">
<View style={[pal.viewLight, styles.btn]}>
<FontAwesomeIcon
icon="ellipsis"
size={20}
color={pal.colors.text}
/>
<Menu.Item
testID="feedHeaderDropdownShareBtn"
label={_(msg`Share feed`)}
onPress={onPressShare}>
<Menu.ItemText>{_(msg`Share feed`)}</Menu.ItemText>
<Menu.ItemIcon icon={Share} position="right" />
</Menu.Item>
</Menu.Group>
</Menu.Outer>
</Menu.Root>
</View>
</NativeDropdown>
</ProfileSubpageHeader>
<AboutSection
feedOwnerDid={feedInfo.creatorDid}
feedRkey={feedInfo.route.params.rkey}
feedInfo={feedInfo}
/>
</>
)
}, [
_,
hasSession,
pal,
feedInfo,
isPinned,
onTogglePinned,
onToggleSaved,
dropdownItems,
currentAccount?.did,
isPinPending,
isRemovePending,
isSavePending,
isSaved,
isUnpinPending,
onPressReport,
onPressShare,
t,
])
return (
@ -405,18 +414,6 @@ export function ProfileFeedScreenInner({
isFocused={isScreenFocused && isFocused}
/>
)}
{({headerHeight, scrollElRef}) => (
<AboutSection
feedOwnerDid={feedInfo.creatorDid}
feedRkey={feedInfo.route.params.rkey}
feedInfo={feedInfo}
headerHeight={headerHeight}
scrollElRef={
scrollElRef as React.MutableRefObject<ScrollView | null>
}
isOwner={feedInfo.creatorDid === currentAccount?.did}
/>
)}
</PagerWithHeader>
{hasSession && (
<FAB
@ -505,21 +502,14 @@ function AboutSection({
feedOwnerDid,
feedRkey,
feedInfo,
headerHeight,
scrollElRef,
isOwner,
}: {
feedOwnerDid: string
feedRkey: string
feedInfo: FeedSourceFeedInfo
headerHeight: number
scrollElRef: React.MutableRefObject<ScrollView | null>
isOwner: boolean
}) {
const t = useTheme()
const pal = usePalette('default')
const {_} = useLingui()
const scrollHandlers = useScrollHandlers()
const onScroll = useAnimatedScrollHandler(scrollHandlers)
const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri)
const {hasSession} = useSession()
const {track} = useAnalytics()
@ -555,24 +545,8 @@ function AboutSection({
}, [likeUri, isLiked, feedInfo, likeFeed, unlikeFeed, track, _])
return (
<ScrollView
ref={scrollElRef}
onScroll={onScroll}
scrollEventThrottle={1}
contentContainerStyle={{
paddingTop: headerHeight,
minHeight: Dimensions.get('window').height * 1.5,
}}>
<View
style={[
{
borderTopWidth: 1,
paddingVertical: 20,
paddingHorizontal: 20,
gap: 12,
},
pal.border,
]}>
<View style={[styles.aboutSectionContainer]}>
<View style={[a.pt_sm]}>
{feedInfo.description ? (
<RichText
testID="listDescription"
@ -584,50 +558,34 @@ function AboutSection({
<Trans>No description</Trans>
</Text>
)}
<View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
<Button
type="default"
</View>
<View style={[a.flex_row, a.gap_sm, a.align_center, a.pb_sm]}>
<NewButton
size="small"
variant="solid"
color="secondary"
shape="round"
label={isLiked ? _(msg`Unlike this feed`) : _(msg`Like this feed`)}
testID="toggleLikeBtn"
accessibilityLabel={_(msg`Like this feed`)}
accessibilityHint=""
disabled={!hasSession || isLikePending || isUnlikePending}
onPress={onToggleLiked}
style={{paddingHorizontal: 10}}>
onPress={onToggleLiked}>
{isLiked ? (
<HeartIconSolid size={19} style={s.likeColor} />
<HeartFilled size="md" fill={s.likeColor.color} />
) : (
<HeartIcon strokeWidth={3} size={19} style={pal.textLight} />
<HeartOutline size="md" fill={t.atoms.text_contrast_medium.color} />
)}
</Button>
</NewButton>
{typeof likeCount === 'number' && (
<TextLink
href={makeCustomFeedLink(feedOwnerDid, feedRkey, 'liked-by')}
text={_(
msg`Liked by ${likeCount} ${pluralize(likeCount, 'user')}`,
)}
style={[pal.textLight, s.semiBold]}
/>
<InlineLink
label={_(msg`View users who like this feed`)}
to={makeCustomFeedLink(feedOwnerDid, feedRkey, 'liked-by')}
style={[t.atoms.text_contrast_medium, a.font_bold]}>
{_(msg`Liked by ${likeCount} ${pluralize(likeCount, 'user')}`)}
</InlineLink>
)}
</View>
<Text type="md" style={[pal.textLight]} numberOfLines={1}>
{isOwner ? (
<Trans>Created by you</Trans>
) : (
<Trans>
Created by{' '}
<TextLink
text={sanitizeHandle(feedInfo.creatorHandle, '@')}
href={makeProfileLink({
did: feedInfo.creatorDid,
handle: feedInfo.creatorHandle,
})}
style={pal.textLight}
/>
</Trans>
)}
</Text>
</View>
</ScrollView>
)
}
@ -647,4 +605,9 @@ const styles = StyleSheet.create({
paddingVertical: 14,
borderRadius: 6,
},
aboutSectionContainer: {
paddingVertical: 4,
paddingHorizontal: 16,
gap: 12,
},
})