Migrate SavedFeeds screen

This commit is contained in:
Eric Bailey
2024-04-22 17:39:19 -05:00
parent 38445349f0
commit 69725f8f37
+77 -80
View File
@@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import {ActivityIndicator, Pressable, StyleSheet, View} from 'react-native' import {ActivityIndicator, Pressable, StyleSheet, View} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -7,13 +8,11 @@ import {useFocusEffect} from '@react-navigation/native'
import {NativeStackScreenProps} from '@react-navigation/native-stack' import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {track} from '#/lib/analytics/analytics' import {track} from '#/lib/analytics/analytics'
import {useGate} from '#/lib/statsig/statsig'
import {logger} from '#/logger' import {logger} from '#/logger'
import { import {
usePinFeedMutation,
usePreferencesQuery, usePreferencesQuery,
useSetSaveFeedsMutation, useSetSaveFeedsMutation,
useUnpinFeedMutation, useUpdateSavedFeedMutation,
} from '#/state/queries/preferences' } from '#/state/queries/preferences'
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types' import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
@@ -65,10 +64,9 @@ export function SavedFeeds({}: Props) {
const currentFeeds = const currentFeeds =
optimisticSavedFeedsResponse && !setSavedFeedsError optimisticSavedFeedsResponse && !setSavedFeedsError
? optimisticSavedFeedsResponse ? optimisticSavedFeedsResponse
: preferences?.feeds || {saved: [], pinned: []} : preferences?.savedFeeds || []
const unpinned = currentFeeds.saved.filter(f => { const pinnedFeeds = currentFeeds.filter(f => f.pinned)
return !currentFeeds.pinned?.includes(f) const unpinnedFeeds = currentFeeds.filter(f => !f.pinned)
})
useFocusEffect( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
@@ -93,7 +91,7 @@ export function SavedFeeds({}: Props) {
</View> </View>
{preferences?.feeds ? ( {preferences?.feeds ? (
!currentFeeds.pinned.length ? ( !pinnedFeeds.length ? (
<View <View
style={[ style={[
pal.border, pal.border,
@@ -106,10 +104,10 @@ export function SavedFeeds({}: Props) {
</Text> </Text>
</View> </View>
) : ( ) : (
currentFeeds.pinned.map(uri => ( pinnedFeeds.map(f => (
<ListItem <ListItem
key={uri} key={f.id}
feedUri={uri} feed={f}
isPinned isPinned
setSavedFeeds={setSavedFeeds} setSavedFeeds={setSavedFeeds}
resetSaveFeedsMutationState={resetSaveFeedsMutationState} resetSaveFeedsMutationState={resetSaveFeedsMutationState}
@@ -127,7 +125,7 @@ export function SavedFeeds({}: Props) {
</Text> </Text>
</View> </View>
{preferences?.feeds ? ( {preferences?.feeds ? (
!unpinned.length ? ( !unpinnedFeeds.length ? (
<View <View
style={[ style={[
pal.border, pal.border,
@@ -140,10 +138,10 @@ export function SavedFeeds({}: Props) {
</Text> </Text>
</View> </View>
) : ( ) : (
unpinned.map(uri => ( unpinnedFeeds.map(f => (
<ListItem <ListItem
key={uri} key={f.id}
feedUri={uri} feed={f}
isPinned={false} isPinned={false}
setSavedFeeds={setSavedFeeds} setSavedFeeds={setSavedFeeds}
resetSaveFeedsMutationState={resetSaveFeedsMutationState} resetSaveFeedsMutationState={resetSaveFeedsMutationState}
@@ -178,16 +176,15 @@ export function SavedFeeds({}: Props) {
} }
function ListItem({ function ListItem({
feedUri, feed,
isPinned, isPinned,
currentFeeds, currentFeeds,
setSavedFeeds, setSavedFeeds,
resetSaveFeedsMutationState, resetSaveFeedsMutationState,
preferences,
}: { }: {
feedUri: string // uri feed: AppBskyActorDefs.SavedFeed
isPinned: boolean isPinned: boolean
currentFeeds: {saved: string[]; pinned: string[]} currentFeeds: AppBskyActorDefs.SavedFeed[]
setSavedFeeds: ReturnType<typeof useSetSaveFeedsMutation>['mutateAsync'] setSavedFeeds: ReturnType<typeof useSetSaveFeedsMutation>['mutateAsync']
resetSaveFeedsMutationState: ReturnType< resetSaveFeedsMutationState: ReturnType<
typeof useSetSaveFeedsMutation typeof useSetSaveFeedsMutation
@@ -197,17 +194,9 @@ function ListItem({
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const playHaptic = useHaptics() const playHaptic = useHaptics()
const {isPending: isPinPending, mutateAsync: pinFeed} = usePinFeedMutation() const {isPending: isUpdatePending, mutateAsync: updateFeed} =
const {isPending: isUnpinPending, mutateAsync: unpinFeed} = useUpdateSavedFeedMutation()
useUnpinFeedMutation() const feedUri = feed.value
const isPending = isPinPending || isUnpinPending
const gate = useGate()
const primaryAlgo = preferences.primaryAlgorithm
const isPrimaryAlgoExperimentEnabled = gate(
'reduced_onboarding_and_home_algo',
)
const isPrimaryAlgo = primaryAlgo?.enabled && primaryAlgo?.uri === feedUri
const showPinButton = !(isPrimaryAlgoExperimentEnabled && isPrimaryAlgo)
const onTogglePinned = React.useCallback(async () => { const onTogglePinned = React.useCallback(async () => {
playHaptic() playHaptic()
@@ -215,67 +204,74 @@ function ListItem({
try { try {
resetSaveFeedsMutationState() resetSaveFeedsMutationState()
if (isPinned) { if (feed.pinned) {
await unpinFeed({uri: feedUri}) await updateFeed({
...feed,
pinned: false,
})
} else { } else {
await pinFeed({uri: feedUri}) await updateFeed({
...feed,
pinned: true,
})
} }
} catch (e) { } catch (e) {
Toast.show(_(msg`There was an issue contacting the server`)) Toast.show(_(msg`There was an issue contacting the server`))
logger.error('Failed to toggle pinned feed', {message: e}) logger.error('Failed to toggle pinned feed', {message: e})
} }
}, [ }, [_, playHaptic, feed, updateFeed, resetSaveFeedsMutationState])
playHaptic,
resetSaveFeedsMutationState,
isPinned,
unpinFeed,
feedUri,
pinFeed,
_,
])
const onPressUp = React.useCallback(async () => { const onPressUp = React.useCallback(async () => {
if (!isPinned) return if (!isPinned) return
// create new array, do not mutate const nextFeeds = currentFeeds.slice()
const pinned = [...currentFeeds.pinned] const ids = currentFeeds.map(f => f.id)
const index = pinned.indexOf(feedUri) const index = ids.indexOf(feed.id)
const nextIndex = index - 1
if (index === -1 || index === 0) return if (index === -1 || index === 0) return
;[pinned[index], pinned[index - 1]] = [pinned[index - 1], pinned[index]] ;[nextFeeds[index], nextFeeds[nextIndex]] = [
nextFeeds[nextIndex],
nextFeeds[index],
]
try { try {
await setSavedFeeds({saved: currentFeeds.saved, pinned}) await setSavedFeeds(nextFeeds)
track('CustomFeed:Reorder', { track('CustomFeed:Reorder', {
uri: feedUri, uri: feed.value,
index: pinned.indexOf(feedUri), index: nextIndex,
}) })
} catch (e) { } catch (e) {
Toast.show(_(msg`There was an issue contacting the server`)) Toast.show(_(msg`There was an issue contacting the server`))
logger.error('Failed to set pinned feed order', {message: e}) logger.error('Failed to set pinned feed order', {message: e})
} }
}, [feedUri, isPinned, setSavedFeeds, currentFeeds, _]) }, [feed, isPinned, setSavedFeeds, currentFeeds, _])
const onPressDown = React.useCallback(async () => { const onPressDown = React.useCallback(async () => {
if (!isPinned) return if (!isPinned) return
const pinned = [...currentFeeds.pinned] const nextFeeds = currentFeeds.slice()
const index = pinned.indexOf(feedUri) const ids = currentFeeds.map(f => f.id)
const index = ids.indexOf(feed.id)
const nextIndex = index + 1
if (index === -1 || index >= pinned.length - 1) return if (index === -1 || index >= nextFeeds.length - 1) return
;[pinned[index], pinned[index + 1]] = [pinned[index + 1], pinned[index]] ;[nextFeeds[index], nextFeeds[nextIndex]] = [
nextFeeds[nextIndex],
nextFeeds[index],
]
try { try {
await setSavedFeeds({saved: currentFeeds.saved, pinned}) await setSavedFeeds(nextFeeds)
track('CustomFeed:Reorder', { track('CustomFeed:Reorder', {
uri: feedUri, uri: feed.value,
index: pinned.indexOf(feedUri), index: nextIndex,
}) })
} catch (e) { } catch (e) {
Toast.show(_(msg`There was an issue contacting the server`)) Toast.show(_(msg`There was an issue contacting the server`))
logger.error('Failed to set pinned feed order', {message: e}) logger.error('Failed to set pinned feed order', {message: e})
} }
}, [feedUri, isPinned, setSavedFeeds, currentFeeds, _]) }, [feed, isPinned, setSavedFeeds, currentFeeds, _])
return ( return (
<Pressable <Pressable
@@ -284,12 +280,13 @@ function ListItem({
{isPinned ? ( {isPinned ? (
<View style={styles.webArrowButtonsContainer}> <View style={styles.webArrowButtonsContainer}>
<Pressable <Pressable
disabled={isPending} disabled={isUpdatePending}
accessibilityRole="button" accessibilityRole="button"
onPress={onPressUp} onPress={onPressUp}
hitSlop={HITSLOP_TOP} hitSlop={HITSLOP_TOP}
style={state => ({ style={state => ({
opacity: state.hovered || state.focused || isPending ? 0.5 : 1, opacity:
state.hovered || state.focused || isUpdatePending ? 0.5 : 1,
})}> })}>
<FontAwesomeIcon <FontAwesomeIcon
icon="arrow-up" icon="arrow-up"
@@ -298,12 +295,13 @@ function ListItem({
/> />
</Pressable> </Pressable>
<Pressable <Pressable
disabled={isPending} disabled={isUpdatePending}
accessibilityRole="button" accessibilityRole="button"
onPress={onPressDown} onPress={onPressDown}
hitSlop={HITSLOP_BOTTOM} hitSlop={HITSLOP_BOTTOM}
style={state => ({ style={state => ({
opacity: state.hovered || state.focused || isPending ? 0.5 : 1, opacity:
state.hovered || state.focused || isUpdatePending ? 0.5 : 1,
})}> })}>
<FontAwesomeIcon icon="arrow-down" size={12} style={[pal.text]} /> <FontAwesomeIcon icon="arrow-down" size={12} style={[pal.text]} />
</Pressable> </Pressable>
@@ -316,24 +314,23 @@ function ListItem({
showSaveBtn showSaveBtn
showMinimalPlaceholder showMinimalPlaceholder
/> />
{showPinButton && ( <View style={{paddingRight: 16}}>
<View style={{paddingRight: 16}}> <Pressable
<Pressable disabled={isUpdatePending}
disabled={isPending} accessibilityRole="button"
accessibilityRole="button" hitSlop={10}
hitSlop={10} onPress={onTogglePinned}
onPress={onTogglePinned} style={state => ({
style={state => ({ opacity:
opacity: state.hovered || state.focused || isPending ? 0.5 : 1, state.hovered || state.focused || isUpdatePending ? 0.5 : 1,
})}> })}>
<FontAwesomeIcon <FontAwesomeIcon
icon="thumb-tack" icon="thumb-tack"
size={20} size={20}
color={isPinned ? colors.blue3 : pal.colors.icon} color={isPinned ? colors.blue3 : pal.colors.icon}
/> />
</Pressable> </Pressable>
</View> </View>
)}
</Pressable> </Pressable>
) )
} }