Handle home algo on ProfileFeed screen
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {DialogOuterProps} from '#/components/Dialog'
|
||||||
|
import * as Prompt from '#/components/Prompt'
|
||||||
|
|
||||||
|
export function HomeAlgoNoticeDialog({
|
||||||
|
control,
|
||||||
|
}: {
|
||||||
|
control: DialogOuterProps['control']
|
||||||
|
}) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
return (
|
||||||
|
<Prompt.Outer control={control}>
|
||||||
|
<Prompt.TitleText>Your home algorithm</Prompt.TitleText>
|
||||||
|
<Prompt.DescriptionText>
|
||||||
|
This feed is set as your home algorithm, which is used as your home
|
||||||
|
screen when you open the app.
|
||||||
|
</Prompt.DescriptionText>
|
||||||
|
<Prompt.Actions>
|
||||||
|
<Prompt.Cancel cta={_(msg`Close`)} />
|
||||||
|
</Prompt.Actions>
|
||||||
|
</Prompt.Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const Home_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M11.46 1.362a2 2 0 0 1 1.08 0c.249.07.448.188.611.301.146.102.306.232.467.363l6.421 5.218.046.036c.169.137.38.308.54.53a2 2 0 0 1 .304.64c.073.264.072.536.071.753v9.229c0 .252 0 .498-.017.706a2.023 2.023 0 0 1-.201.77 2 2 0 0 1-.874.874 2.02 2.02 0 0 1-.77.201c-.208.017-.454.017-.706.017H5.568c-.252 0-.498 0-.706-.017a2.02 2.02 0 0 1-.77-.201 2 2 0 0 1-.874-.874 2.022 2.022 0 0 1-.201-.77C3 18.93 3 18.684 3 18.432V9.203c0-.217-.002-.49.07-.754a2 2 0 0 1 .304-.638c.16-.223.372-.394.541-.53l.045-.037 6.422-5.218c.161-.13.321-.26.467-.362.163-.114.362-.232.612-.302Zm.532 1.943c-.077.054-.18.136-.37.29l-6.4 5.2a6.315 6.315 0 0 0-.215.18c-.002 0-.003.002-.004.003v.004C5 9.036 5 9.112 5 9.262V18.4a8.18 8.18 0 0 0 .011.588l.014.002c.116.01.278.01.575.01H8v-5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v5h2.4a8.207 8.207 0 0 0 .589-.012v-.013c.01-.116.011-.279.011-.575V9.262c0-.15 0-.226-.003-.28v-.004l-.003-.003a6.448 6.448 0 0 0-.216-.18l-6.4-5.2a7.373 7.373 0 0 0-.37-.29L12 3.299l-.008.006ZM14 19v-5h-4v5h4Z',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Home_Filled_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M13.261 1.736a2 2 0 0 0-2.522 0l-7 5.687A2 2 0 0 0 3 8.976V19a2 2 0 0 0 2 2h3v-8a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v8h3a2 2 0 0 0 2-2V8.976a2 2 0 0 0-.739-1.553l-7-5.687ZM14 21h-4v-7h4v7Z',
|
||||||
|
})
|
||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
AppBskyActorDefs,
|
AppBskyActorDefs,
|
||||||
BSKY_LABELER_DID,
|
BSKY_LABELER_DID,
|
||||||
BskyFeedViewPreference,
|
BskyFeedViewPreference,
|
||||||
|
BskyHomeAlgoPreference,
|
||||||
LabelPreference,
|
LabelPreference,
|
||||||
ModerationOpts,
|
ModerationOpts,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
@@ -345,3 +346,17 @@ export function useRemoveMutedWordMutation() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useSetHomeAlgoMutation() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (pref: BskyHomeAlgoPreference) => {
|
||||||
|
await getAgent().setHomeAlgoPref(pref)
|
||||||
|
// triggers a refetch
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: preferencesQueryKey,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import * as Toast from 'view/com/util/Toast'
|
|||||||
import {useTheme} from '#/alf'
|
import {useTheme} from '#/alf'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import {HomeAlgoNoticeDialog} from '#/components/HomeAlgoNoticeDialog'
|
||||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
@@ -312,16 +313,7 @@ export function FeedSourceCardLoaded({
|
|||||||
confirmButtonColor="negative"
|
confirmButtonColor="negative"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Prompt.Outer control={homeAlgoDialogControl}>
|
<HomeAlgoNoticeDialog control={homeAlgoDialogControl} />
|
||||||
<Prompt.TitleText>Your home algorithm</Prompt.TitleText>
|
|
||||||
<Prompt.DescriptionText>
|
|
||||||
This feed is set as your home algorithm, which is used as your home
|
|
||||||
screen when you open the app.
|
|
||||||
</Prompt.DescriptionText>
|
|
||||||
<Prompt.Actions>
|
|
||||||
<Prompt.Cancel cta={_(msg`Cool, cool cool cool`)} />
|
|
||||||
</Prompt.Actions>
|
|
||||||
</Prompt.Outer>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
|||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {HITSLOP_20} from '#/lib/constants'
|
import {HITSLOP_20} from '#/lib/constants'
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
import {listenSoftReset} from '#/state/events'
|
import {listenSoftReset} from '#/state/events'
|
||||||
@@ -20,6 +21,7 @@ import {
|
|||||||
UsePreferencesQueryResponse,
|
UsePreferencesQueryResponse,
|
||||||
useRemoveFeedMutation,
|
useRemoveFeedMutation,
|
||||||
useSaveFeedMutation,
|
useSaveFeedMutation,
|
||||||
|
useSetHomeAlgoMutation,
|
||||||
useUnpinFeedMutation,
|
useUnpinFeedMutation,
|
||||||
} from '#/state/queries/preferences'
|
} from '#/state/queries/preferences'
|
||||||
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
||||||
@@ -52,14 +54,18 @@ import {Text} from 'view/com/util/text/Text'
|
|||||||
import * as Toast from 'view/com/util/Toast'
|
import * as Toast from 'view/com/util/Toast'
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
import {CenteredView} from 'view/com/util/Views'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button as NewButton, ButtonText} from '#/components/Button'
|
import {Button as NewButton, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
|
import {HomeAlgoNoticeDialog} from '#/components/HomeAlgoNoticeDialog'
|
||||||
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
|
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
|
||||||
|
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
|
import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
|
||||||
import {
|
import {
|
||||||
Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
|
Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
|
||||||
Heart2_Stroke2_Corner0_Rounded as HeartOutline,
|
Heart2_Stroke2_Corner0_Rounded as HeartOutline,
|
||||||
} from '#/components/icons/Heart2'
|
} from '#/components/icons/Heart2'
|
||||||
|
import {Home_Stroke2_Corner0_Rounded as Home} from '#/components/icons/Home'
|
||||||
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
||||||
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
||||||
import {InlineLinkText} from '#/components/Link'
|
import {InlineLinkText} from '#/components/Link'
|
||||||
@@ -162,6 +168,10 @@ export function ProfileFeedScreenInner({
|
|||||||
const playHaptic = useHaptics()
|
const playHaptic = useHaptics()
|
||||||
const feedSectionRef = React.useRef<SectionRef>(null)
|
const feedSectionRef = React.useRef<SectionRef>(null)
|
||||||
const isScreenFocused = useIsFocused()
|
const isScreenFocused = useIsFocused()
|
||||||
|
const isHomeAlgoExperimentEnabled = useGate(
|
||||||
|
'reduced_onboarding_and_home_algo',
|
||||||
|
)
|
||||||
|
const homeAlgoDialogControl = useDialogControl()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mutateAsync: saveFeed,
|
mutateAsync: saveFeed,
|
||||||
@@ -187,6 +197,11 @@ export function ProfileFeedScreenInner({
|
|||||||
reset: resetUnpinFeed,
|
reset: resetUnpinFeed,
|
||||||
isPending: isUnpinPending,
|
isPending: isUnpinPending,
|
||||||
} = useUnpinFeedMutation()
|
} = useUnpinFeedMutation()
|
||||||
|
const {
|
||||||
|
mutateAsync: setHomeAlgo,
|
||||||
|
variables: homeAlgoVariables,
|
||||||
|
isPending: isSetHomeAlgoPending,
|
||||||
|
} = useSetHomeAlgoMutation()
|
||||||
|
|
||||||
const isSaved =
|
const isSaved =
|
||||||
!removedFeed &&
|
!removedFeed &&
|
||||||
@@ -194,6 +209,11 @@ export function ProfileFeedScreenInner({
|
|||||||
const isPinned =
|
const isPinned =
|
||||||
!unpinnedFeed &&
|
!unpinnedFeed &&
|
||||||
(!!pinnedFeed || preferences.feeds.pinned.includes(feedInfo.uri))
|
(!!pinnedFeed || preferences.feeds.pinned.includes(feedInfo.uri))
|
||||||
|
const isHomeAlgo =
|
||||||
|
(preferences.homeAlgo?.enabled &&
|
||||||
|
preferences.homeAlgo?.uri &&
|
||||||
|
preferences.homeAlgo.uri === feedInfo.uri) ||
|
||||||
|
(homeAlgoVariables?.enabled && homeAlgoVariables.uri === feedInfo.uri)
|
||||||
|
|
||||||
useSetTitle(feedInfo?.displayName)
|
useSetTitle(feedInfo?.displayName)
|
||||||
|
|
||||||
@@ -258,6 +278,16 @@ export function ProfileFeedScreenInner({
|
|||||||
_,
|
_,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const onSetHomeAlgo = React.useCallback(async () => {
|
||||||
|
try {
|
||||||
|
playHaptic()
|
||||||
|
await setHomeAlgo({enabled: true, uri: feedInfo.uri})
|
||||||
|
} catch (e: any) {
|
||||||
|
Toast.show(_(msg`There was an issue contacting the server`))
|
||||||
|
logger.error('ProfileFeed: failed to set home algo', {message: e.message})
|
||||||
|
}
|
||||||
|
}, [setHomeAlgo, feedInfo, _, playHaptic])
|
||||||
|
|
||||||
const onPressShare = React.useCallback(() => {
|
const onPressShare = React.useCallback(() => {
|
||||||
const url = toShareUrl(feedInfo.route.href)
|
const url = toShareUrl(feedInfo.route.href)
|
||||||
shareUrl(url)
|
shareUrl(url)
|
||||||
@@ -293,20 +323,38 @@ export function ProfileFeedScreenInner({
|
|||||||
}
|
}
|
||||||
avatarType="algo">
|
avatarType="algo">
|
||||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||||
{feedInfo && hasSession && (
|
{feedInfo &&
|
||||||
<NewButton
|
hasSession &&
|
||||||
testID={isPinned ? 'unpinBtn' : 'pinBtn'}
|
(isHomeAlgoExperimentEnabled && isHomeAlgo ? (
|
||||||
disabled={isPinPending || isUnpinPending}
|
<NewButton
|
||||||
size="small"
|
variant="solid"
|
||||||
variant="solid"
|
color="secondary"
|
||||||
color={isPinned ? 'secondary' : 'primary'}
|
size="small"
|
||||||
label={isPinned ? _(msg`Unpin from home`) : _(msg`Pin to home`)}
|
label={_(
|
||||||
onPress={onTogglePinned}>
|
msg`This feed is already set as your home algorithm.`,
|
||||||
<ButtonText>
|
)}
|
||||||
{isPinned ? _(msg`Unpin`) : _(msg`Pin to Home`)}
|
onPress={() => {
|
||||||
</ButtonText>
|
homeAlgoDialogControl.open()
|
||||||
</NewButton>
|
}}>
|
||||||
)}
|
<ButtonIcon icon={Check} position="left" />
|
||||||
|
<ButtonText>Home Algo</ButtonText>
|
||||||
|
</NewButton>
|
||||||
|
) : (
|
||||||
|
<NewButton
|
||||||
|
testID={isPinned ? 'unpinBtn' : 'pinBtn'}
|
||||||
|
disabled={isPinPending || isUnpinPending}
|
||||||
|
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.Root>
|
||||||
<Menu.Trigger label={_(msg`Open feed options menu`)}>
|
<Menu.Trigger label={_(msg`Open feed options menu`)}>
|
||||||
{({props, state}) => {
|
{({props, state}) => {
|
||||||
@@ -338,25 +386,40 @@ export function ProfileFeedScreenInner({
|
|||||||
<Menu.Group>
|
<Menu.Group>
|
||||||
{hasSession && (
|
{hasSession && (
|
||||||
<>
|
<>
|
||||||
<Menu.Item
|
{!isHomeAlgo && (
|
||||||
disabled={isSavePending || isRemovePending}
|
<>
|
||||||
testID="feedHeaderDropdownToggleSavedBtn"
|
<Menu.Item
|
||||||
label={
|
disabled={isSavePending || isRemovePending}
|
||||||
isSaved
|
testID="feedHeaderDropdownToggleSavedBtn"
|
||||||
? _(msg`Remove from my feeds`)
|
label={
|
||||||
: _(msg`Save to my feeds`)
|
isSaved
|
||||||
}
|
? _(msg`Remove from my feeds`)
|
||||||
onPress={onToggleSaved}>
|
: _(msg`Save to my feeds`)
|
||||||
<Menu.ItemText>
|
}
|
||||||
{isSaved
|
onPress={onToggleSaved}>
|
||||||
? _(msg`Remove from my feeds`)
|
<Menu.ItemText>
|
||||||
: _(msg`Save to my feeds`)}
|
{isSaved
|
||||||
</Menu.ItemText>
|
? _(msg`Remove from my feeds`)
|
||||||
<Menu.ItemIcon
|
: _(msg`Save to my feeds`)}
|
||||||
icon={isSaved ? Trash : Plus}
|
</Menu.ItemText>
|
||||||
position="right"
|
<Menu.ItemIcon
|
||||||
/>
|
icon={isSaved ? Trash : Plus}
|
||||||
</Menu.Item>
|
position="right"
|
||||||
|
/>
|
||||||
|
</Menu.Item>
|
||||||
|
|
||||||
|
<Menu.Item
|
||||||
|
disabled={isSetHomeAlgoPending}
|
||||||
|
testID="feedHeaderDropdownSetHomeAlgoBtn"
|
||||||
|
label={_(msg`Set as home algorithm`)}
|
||||||
|
onPress={onSetHomeAlgo}>
|
||||||
|
<Menu.ItemText>
|
||||||
|
{_(msg`Set as home algorithm`)}
|
||||||
|
</Menu.ItemText>
|
||||||
|
<Menu.ItemIcon icon={Home} position="right" />
|
||||||
|
</Menu.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
testID="feedHeaderDropdownReportBtn"
|
testID="feedHeaderDropdownReportBtn"
|
||||||
@@ -385,6 +448,8 @@ export function ProfileFeedScreenInner({
|
|||||||
feedRkey={feedInfo.route.params.rkey}
|
feedRkey={feedInfo.route.params.rkey}
|
||||||
feedInfo={feedInfo}
|
feedInfo={feedInfo}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<HomeAlgoNoticeDialog control={homeAlgoDialogControl} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}, [
|
}, [
|
||||||
@@ -403,6 +468,11 @@ export function ProfileFeedScreenInner({
|
|||||||
onPressReport,
|
onPressReport,
|
||||||
onPressShare,
|
onPressShare,
|
||||||
t,
|
t,
|
||||||
|
isHomeAlgoExperimentEnabled,
|
||||||
|
isHomeAlgo,
|
||||||
|
homeAlgoDialogControl,
|
||||||
|
onSetHomeAlgo,
|
||||||
|
isSetHomeAlgoPending,
|
||||||
])
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user