diff --git a/src/components/HomeAlgoNoticeDialog.tsx b/src/components/HomeAlgoNoticeDialog.tsx
new file mode 100644
index 0000000000..d84aa1250a
--- /dev/null
+++ b/src/components/HomeAlgoNoticeDialog.tsx
@@ -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 (
+
+ Your home algorithm
+
+ This feed is set as your home algorithm, which is used as your home
+ screen when you open the app.
+
+
+
+
+
+ )
+}
diff --git a/src/components/icons/Home.tsx b/src/components/icons/Home.tsx
new file mode 100644
index 0000000000..e150b7b813
--- /dev/null
+++ b/src/components/icons/Home.tsx
@@ -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',
+})
diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts
index 85e3f9a25d..c1a383672a 100644
--- a/src/state/queries/preferences/index.ts
+++ b/src/state/queries/preferences/index.ts
@@ -3,6 +3,7 @@ import {
AppBskyActorDefs,
BSKY_LABELER_DID,
BskyFeedViewPreference,
+ BskyHomeAlgoPreference,
LabelPreference,
ModerationOpts,
} 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,
+ })
+ },
+ })
+}
diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx
index 2e769f8d8a..d0c45bfc4d 100644
--- a/src/view/com/feeds/FeedSourceCard.tsx
+++ b/src/view/com/feeds/FeedSourceCard.tsx
@@ -25,6 +25,7 @@ import * as Toast from 'view/com/util/Toast'
import {useTheme} from '#/alf'
import {atoms as a} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+import {HomeAlgoNoticeDialog} from '#/components/HomeAlgoNoticeDialog'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText'
@@ -312,16 +313,7 @@ export function FeedSourceCardLoaded({
confirmButtonColor="negative"
/>
-
- Your home algorithm
-
- This feed is set as your home algorithm, which is used as your home
- screen when you open the app.
-
-
-
-
-
+
>
)
}
diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx
index 814c1e8558..8ae949b477 100644
--- a/src/view/screens/ProfileFeed.tsx
+++ b/src/view/screens/ProfileFeed.tsx
@@ -7,6 +7,7 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {useQueryClient} from '@tanstack/react-query'
import {HITSLOP_20} from '#/lib/constants'
+import {useGate} from '#/lib/statsig/statsig'
import {logger} from '#/logger'
import {isNative} from '#/platform/detection'
import {listenSoftReset} from '#/state/events'
@@ -20,6 +21,7 @@ import {
UsePreferencesQueryResponse,
useRemoveFeedMutation,
useSaveFeedMutation,
+ useSetHomeAlgoMutation,
useUnpinFeedMutation,
} from '#/state/queries/preferences'
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 {CenteredView} from 'view/com/util/Views'
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 {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
import {
Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
Heart2_Stroke2_Corner0_Rounded as HeartOutline,
} 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 {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
import {InlineLinkText} from '#/components/Link'
@@ -162,6 +168,10 @@ export function ProfileFeedScreenInner({
const playHaptic = useHaptics()
const feedSectionRef = React.useRef(null)
const isScreenFocused = useIsFocused()
+ const isHomeAlgoExperimentEnabled = useGate(
+ 'reduced_onboarding_and_home_algo',
+ )
+ const homeAlgoDialogControl = useDialogControl()
const {
mutateAsync: saveFeed,
@@ -187,6 +197,11 @@ export function ProfileFeedScreenInner({
reset: resetUnpinFeed,
isPending: isUnpinPending,
} = useUnpinFeedMutation()
+ const {
+ mutateAsync: setHomeAlgo,
+ variables: homeAlgoVariables,
+ isPending: isSetHomeAlgoPending,
+ } = useSetHomeAlgoMutation()
const isSaved =
!removedFeed &&
@@ -194,6 +209,11 @@ export function ProfileFeedScreenInner({
const isPinned =
!unpinnedFeed &&
(!!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)
@@ -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 url = toShareUrl(feedInfo.route.href)
shareUrl(url)
@@ -293,20 +323,38 @@ export function ProfileFeedScreenInner({
}
avatarType="algo">
- {feedInfo && hasSession && (
-
-
- {isPinned ? _(msg`Unpin`) : _(msg`Pin to Home`)}
-
-
- )}
+ {feedInfo &&
+ hasSession &&
+ (isHomeAlgoExperimentEnabled && isHomeAlgo ? (
+ {
+ homeAlgoDialogControl.open()
+ }}>
+
+ Home Algo
+
+ ) : (
+
+
+ {isPinned ? _(msg`Unpin`) : _(msg`Pin to Home`)}
+
+
+ ))}
{({props, state}) => {
@@ -338,25 +386,40 @@ export function ProfileFeedScreenInner({
{hasSession && (
<>
-
-
- {isSaved
- ? _(msg`Remove from my feeds`)
- : _(msg`Save to my feeds`)}
-
-
-
+ {!isHomeAlgo && (
+ <>
+
+
+ {isSaved
+ ? _(msg`Remove from my feeds`)
+ : _(msg`Save to my feeds`)}
+
+
+
+
+
+
+ {_(msg`Set as home algorithm`)}
+
+
+
+ >
+ )}
+
+
>
)
}, [
@@ -403,6 +468,11 @@ export function ProfileFeedScreenInner({
onPressReport,
onPressShare,
t,
+ isHomeAlgoExperimentEnabled,
+ isHomeAlgo,
+ homeAlgoDialogControl,
+ onSetHomeAlgo,
+ isSetHomeAlgoPending,
])
return (