allow changing pinned feed order on web

zio/stable
Ansh Nanda 2023-05-16 23:38:34 -07:00
parent 99d66679b3
commit 3501fda015
6 changed files with 116 additions and 25 deletions

View File

@ -1,4 +1,4 @@
import {makeAutoObservable} from 'mobx' import {makeAutoObservable, runInAction} from 'mobx'
import {AppBskyFeedGetSavedFeeds as GetSavedFeeds} from '@atproto/api' import {AppBskyFeedGetSavedFeeds as GetSavedFeeds} from '@atproto/api'
import {RootStoreModel} from '../../root-store' import {RootStoreModel} from '../../root-store'
import {bundleAsync} from 'lib/async/bundle' import {bundleAsync} from 'lib/async/bundle'
@ -103,6 +103,43 @@ export class SavedFeedsModel {
return this.pinned.find(f => f.data.uri === feed.data.uri) ? true : false return this.pinned.find(f => f.data.uri === feed.data.uri) ? true : false
} }
movePinnedItem(item: AlgoItemModel, direction: 'up' | 'down') {
if (this.pinned.length < 2) {
throw new Error('Array must have at least 2 items')
}
const index = this.pinned.indexOf(item)
if (index === -1) {
throw new Error('Item not found in array')
}
const len = this.pinned.length
runInAction(() => {
if (direction === 'up') {
if (index === 0) {
// Remove the item from the first place and put it at the end
this.pinned.push(this.pinned.shift()!)
} else {
// Swap the item with the one before it
const temp = this.pinned[index]
this.pinned[index] = this.pinned[index - 1]
this.pinned[index - 1] = temp
}
} else if (direction === 'down') {
if (index === len - 1) {
// Remove the item from the last place and put it at the start
this.pinned.unshift(this.pinned.pop()!)
} else {
// Swap the item with the one after it
const temp = this.pinned[index]
this.pinned[index] = this.pinned[index + 1]
this.pinned[index + 1] = temp
}
}
// this.pinned = [...this.pinned]
})
}
// public api // public api
// = // =

View File

@ -19,6 +19,7 @@ import {useStores} from 'state/index'
import {HeartIconSolid} from 'lib/icons' import {HeartIconSolid} from 'lib/icons'
import {pluralize} from 'lib/strings/helpers' import {pluralize} from 'lib/strings/helpers'
import {AtUri} from '@atproto/api' import {AtUri} from '@atproto/api'
import {isWeb} from 'platform/detection'
const AlgoItem = observer( const AlgoItem = observer(
({ ({
@ -37,8 +38,9 @@ const AlgoItem = observer(
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
// TODO: this is pretty hacky, but it works for now // TODO: this is pretty hacky, but it works for now
// causes issues on web
useFocusEffect(() => { useFocusEffect(() => {
if (reloadOnFocus) { if (reloadOnFocus && !isWeb) {
item.reload() item.reload()
} }
}) })

View File

@ -1,4 +1,4 @@
import React from 'react' import React, {useMemo} from 'react'
import {Animated, StyleSheet} from 'react-native' import {Animated, StyleSheet} from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {TabBar} from 'view/com/pager/TabBar' import {TabBar} from 'view/com/pager/TabBar'
@ -27,6 +27,14 @@ const FeedsTabBarDesktop = observer(
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
) => { ) => {
const store = useStores() const store = useStores()
const items = useMemo(
() => [
'Following',
"What's hot",
...store.me.savedFeeds.listOfPinnedFeedNames,
],
[store.me.savedFeeds.listOfPinnedFeedNames],
)
const pal = usePalette('default') const pal = usePalette('default')
const interp = useAnimatedValue(0) const interp = useAnimatedValue(0)
@ -44,12 +52,13 @@ const FeedsTabBarDesktop = observer(
{translateY: Animated.multiply(interp, -100)}, {translateY: Animated.multiply(interp, -100)},
], ],
} }
return ( return (
// @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf // @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
<Animated.View style={[pal.view, styles.tabBar, transform]}> <Animated.View style={[pal.view, styles.tabBar, transform]}>
<TabBar <TabBar
{...props} {...props}
items={['Following', "What's hot"]} items={items}
indicatorPosition="bottom" indicatorPosition="bottom"
indicatorColor={pal.colors.link} indicatorColor={pal.colors.link}
/> />

View File

@ -8,6 +8,7 @@ import {faAngleUp} from '@fortawesome/free-solid-svg-icons/faAngleUp'
import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft' import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft'
import {faArrowRight} from '@fortawesome/free-solid-svg-icons/faArrowRight' import {faArrowRight} from '@fortawesome/free-solid-svg-icons/faArrowRight'
import {faArrowUp} from '@fortawesome/free-solid-svg-icons/faArrowUp' import {faArrowUp} from '@fortawesome/free-solid-svg-icons/faArrowUp'
import {faArrowDown} from '@fortawesome/free-solid-svg-icons/faArrowDown'
import {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowRightFromBracket' import {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowRightFromBracket'
import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket' import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket'
import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare' import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare'
@ -87,6 +88,7 @@ export function setup() {
faArrowLeft, faArrowLeft,
faArrowRight, faArrowRight,
faArrowUp, faArrowUp,
faArrowDown,
faArrowRightFromBracket, faArrowRightFromBracket,
faArrowUpFromBracket, faArrowUpFromBracket,
faArrowUpRightFromSquare, faArrowUpRightFromSquare,

View File

@ -38,8 +38,6 @@ export const CustomFeed = withAuthRequired(
return feed return feed
}, [rootStore, uri]) }, [rootStore, uri])
console.log(currentFeed?.data.creator)
const _ListHeaderComponent = () => { const _ListHeaderComponent = () => {
return ( return (
<View style={[styles.headerContainer]}> <View style={[styles.headerContainer]}>

View File

@ -5,6 +5,7 @@ import {
View, View,
ActivityIndicator, ActivityIndicator,
Pressable, Pressable,
TouchableOpacity,
} from 'react-native' } from 'react-native'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect} from '@react-navigation/native'
import {NativeStackScreenProps} from '@react-navigation/native-stack' import {NativeStackScreenProps} from '@react-navigation/native-stack'
@ -17,7 +18,7 @@ import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {ViewHeader} from 'view/com/util/ViewHeader' import {ViewHeader} from 'view/com/util/ViewHeader'
import {CenteredView} from 'view/com/util/Views' import {CenteredView} from 'view/com/util/Views'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
import {isDesktopWeb} from 'platform/detection' import {isDesktopWeb, isWeb} from 'platform/detection'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import DraggableFlatList, { import DraggableFlatList, {
ShadowDecorator, ShadowDecorator,
@ -25,6 +26,7 @@ import DraggableFlatList, {
} from 'react-native-draggable-flatlist' } from 'react-native-draggable-flatlist'
import {SavedFeedItem} from 'view/com/algos/SavedFeedItem' import {SavedFeedItem} from 'view/com/algos/SavedFeedItem'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {AlgoItemModel} from 'state/models/feeds/algo/algo-item'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PinnedFeeds'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'PinnedFeeds'>
@ -73,7 +75,7 @@ export const PinnedFeeds = withAuthRequired(
<ViewHeader title="Arrange Pinned Feeds" showOnDesktop /> <ViewHeader title="Arrange Pinned Feeds" showOnDesktop />
<DraggableFlatList <DraggableFlatList
containerStyle={[!isDesktopWeb && s.flex1]} containerStyle={[!isDesktopWeb && s.flex1]}
data={savedFeeds.pinned} data={[...savedFeeds.pinned]} // make a copy so this FlatList re-renders when pinned changes
keyExtractor={item => item.data.uri} keyExtractor={item => item.data.uri}
refreshing={savedFeeds.isRefreshing} refreshing={savedFeeds.isRefreshing}
refreshControl={ refreshControl={
@ -84,23 +86,7 @@ export const PinnedFeeds = withAuthRequired(
titleColor={pal.colors.text} titleColor={pal.colors.text}
/> />
} }
renderItem={({item, drag}) => ( renderItem={({item, drag}) => <PinnedItem item={item} drag={drag} />}
<ScaleDecorator>
<ShadowDecorator>
<Pressable
accessibilityRole="button"
onLongPress={drag}
style={styles.itemContainer}>
<FontAwesomeIcon
icon="bars"
size={20}
style={[styles.icon, pal.text]}
/>
<SavedFeedItem item={item} savedFeeds={savedFeeds} />
</Pressable>
</ShadowDecorator>
</ScaleDecorator>
)}
initialNumToRender={10} initialNumToRender={10}
ListFooterComponent={_ListFooterComponent} ListFooterComponent={_ListFooterComponent}
ListEmptyComponent={_ListEmptyComponent} ListEmptyComponent={_ListEmptyComponent}
@ -114,6 +100,58 @@ export const PinnedFeeds = withAuthRequired(
}), }),
) )
const PinnedItem = observer(
({item, drag}: {item: AlgoItemModel; drag: () => void}) => {
const pal = usePalette('default')
const rootStore = useStores()
const savedFeeds = useMemo(() => rootStore.me.savedFeeds, [rootStore])
return (
<ScaleDecorator>
<ShadowDecorator>
<Pressable
accessibilityRole="button"
onLongPress={drag}
style={styles.itemContainer}>
{isWeb ? (
<View style={styles.webArrowButtonsContainer}>
<TouchableOpacity
accessibilityRole="button"
onPress={() => {
savedFeeds.movePinnedItem(item, 'up')
}}>
<FontAwesomeIcon
icon="arrow-up"
size={20}
style={[styles.icon, pal.text, styles.webArrowUpButton]}
/>
</TouchableOpacity>
<TouchableOpacity
accessibilityRole="button"
onPress={() => {
savedFeeds.movePinnedItem(item, 'down')
}}>
<FontAwesomeIcon
icon="arrow-down"
size={20}
style={[styles.icon, pal.text]}
/>
</TouchableOpacity>
</View>
) : (
<FontAwesomeIcon
icon="bars"
size={20}
style={[styles.icon, pal.text]}
/>
)}
<SavedFeedItem item={item} savedFeeds={savedFeeds} />
</Pressable>
</ShadowDecorator>
</ScaleDecorator>
)
},
)
const styles = StyleSheet.create({ const styles = StyleSheet.create({
footer: { footer: {
paddingVertical: 20, paddingVertical: 20,
@ -135,4 +173,9 @@ const styles = StyleSheet.create({
borderTopWidth: 0, borderTopWidth: 0,
}, },
icon: {marginRight: 10}, icon: {marginRight: 10},
webArrowButtonsContainer: {
flexDirection: 'column',
justifyContent: 'space-around',
},
webArrowUpButton: {marginBottom: 10},
}) })