remove tab bar underline animation

zio/stable
Ansh Nanda 2023-05-15 12:31:27 -07:00
parent d7e39bde12
commit 6249bb16ca
4 changed files with 111 additions and 68 deletions

View File

@ -89,6 +89,36 @@ export class SavedFeedsModel {
} }
}) })
removeFeed(uri: string) {
this.feeds = this.feeds.filter(f => f.data.uri !== uri)
}
addFeed(algoItem: AlgoItemModel) {
this.feeds.push(new AlgoItemModel(this.rootStore, algoItem.data))
}
async save(algoItem: AlgoItemModel) {
try {
await this.rootStore.agent.app.bsky.feed.saveFeed({
feed: algoItem.getUri,
})
this.addFeed(algoItem)
} catch (e: any) {
this.rootStore.log.error('Failed to save feed', e)
}
}
async unsave(uri: string) {
try {
await this.rootStore.agent.app.bsky.feed.unsaveFeed({
feed: uri,
})
this.removeFeed(uri)
} catch (e: any) {
this.rootStore.log.error('Failed to unsanve feed', e)
}
}
// state transitions // state transitions
// = // =

View File

@ -15,9 +15,11 @@ import {observer} from 'mobx-react-lite'
import {AlgoItemModel} from 'state/models/feeds/algo/algo-item' import {AlgoItemModel} from 'state/models/feeds/algo/algo-item'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types'
import {useStores} from 'state/index'
const AlgoItem = observer( const AlgoItem = observer(
({item, style}: {item: AlgoItemModel; style?: StyleProp<ViewStyle>}) => { ({item, style}: {item: AlgoItemModel; style?: StyleProp<ViewStyle>}) => {
const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
@ -64,8 +66,10 @@ const AlgoItem = observer(
onPress={() => { onPress={() => {
if (item.data.viewer?.saved) { if (item.data.viewer?.saved) {
item.unsave() item.unsave()
store.me.savedFeeds.removeFeed(item.data.uri)
} else { } else {
item.save() item.save()
store.me.savedFeeds.addFeed(item)
} }
}} }}
label={item.data.viewer?.saved ? 'Unsave' : 'Save'} label={item.data.viewer?.saved ? 'Unsave' : 'Save'}

View File

@ -1,4 +1,4 @@
import React from 'react' import React, {useMemo} from 'react'
import {Animated, StyleSheet, TouchableOpacity} from 'react-native' import {Animated, StyleSheet, TouchableOpacity} 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'
@ -32,6 +32,11 @@ export const FeedsTabBar = observer(
store.shell.openDrawer() store.shell.openDrawer()
}, [store]) }, [store])
const items = useMemo(
() => ['Following', "What's hot", ...store.me.savedFeeds.listOfFeedNames],
[store.me.savedFeeds.listOfFeedNames],
)
return ( return (
<Animated.View style={[pal.view, pal.border, styles.tabBar, transform]}> <Animated.View style={[pal.view, pal.border, styles.tabBar, transform]}>
<TouchableOpacity <TouchableOpacity
@ -45,11 +50,7 @@ export const FeedsTabBar = observer(
</TouchableOpacity> </TouchableOpacity>
<TabBar <TabBar
{...props} {...props}
items={[ items={items}
'Following',
"What's hot",
...store.me.savedFeeds.listOfFeedNames,
]}
indicatorPosition="bottom" indicatorPosition="bottom"
indicatorColor={pal.colors.link} indicatorColor={pal.colors.link}
/> />

View File

@ -35,59 +35,59 @@ export function TabBar({
onPressSelected, onPressSelected,
}: TabBarProps) { }: TabBarProps) {
const pal = usePalette('default') const pal = usePalette('default')
const [itemLayouts, setItemLayouts] = useState<Layout[]>( // const [itemLayouts, setItemLayouts] = useState<Layout[]>(
items.map(() => ({x: 0, width: 0})), // items.map(() => ({x: 0, width: 0})),
) // )
const itemRefs = useMemo( const itemRefs = useMemo(
() => Array.from({length: items.length}).map(() => createRef<View>()), () => Array.from({length: items.length}).map(() => createRef<View>()),
[items.length], [items.length],
) )
const panX = Animated.add(position, offset) // const panX = Animated.add(position, offset)
const containerRef = useRef<View>(null) const containerRef = useRef<View>(null)
const indicatorStyle = { // const indicatorStyle = {
backgroundColor: indicatorColor || pal.colors.link, // backgroundColor: indicatorColor || pal.colors.link,
bottom: // bottom:
indicatorPosition === 'bottom' ? (isDesktopWeb ? 0 : -1) : undefined, // indicatorPosition === 'bottom' ? (isDesktopWeb ? 0 : -1) : undefined,
top: indicatorPosition === 'top' ? (isDesktopWeb ? 0 : -1) : undefined, // top: indicatorPosition === 'top' ? (isDesktopWeb ? 0 : -1) : undefined,
transform: [ // transform: [
{ // {
translateX: panX.interpolate({ // translateX: panX.interpolate({
inputRange: items.map((_item, i) => i), // inputRange: items.map((_item, i) => i),
outputRange: itemLayouts.map(l => l.x + l.width / 2), // outputRange: itemLayouts.map(l => l.x + l.width / 2),
}), // }),
}, // },
{ // {
scaleX: panX.interpolate({ // scaleX: panX.interpolate({
inputRange: items.map((_item, i) => i), // inputRange: items.map((_item, i) => i),
outputRange: itemLayouts.map(l => l.width), // outputRange: itemLayouts.map(l => l.width),
}), // }),
}, // },
], // ],
} // }
const onLayout = () => { // const onLayout = () => {
const promises = [] // const promises = []
for (let i = 0; i < items.length; i++) { // for (let i = 0; i < items.length; i++) {
promises.push( // promises.push(
new Promise<Layout>(resolve => { // new Promise<Layout>(resolve => {
if (!containerRef.current || !itemRefs[i].current) { // if (!containerRef.current || !itemRefs[i].current) {
return resolve({x: 0, width: 0}) // return resolve({x: 0, width: 0})
} // }
itemRefs[i].current?.measureLayout( // itemRefs[i].current?.measureLayout(
containerRef.current, // containerRef.current,
(x: number, _y: number, width: number) => { // (x: number, _y: number, width: number) => {
resolve({x, width}) // resolve({x, width})
}, // },
) // )
}), // }),
) // )
} // }
Promise.all(promises).then((layouts: Layout[]) => { // Promise.all(promises).then((layouts: Layout[]) => {
setItemLayouts(layouts) // setItemLayouts(layouts)
}) // })
} // }
const onPressItem = (index: number) => { const onPressItem = (index: number) => {
onSelect?.(index) onSelect?.(index)
@ -100,28 +100,31 @@ export function TabBar({
<View <View
testID={testID} testID={testID}
style={[pal.view, styles.outer]} style={[pal.view, styles.outer]}
onLayout={onLayout} // onLayout={onLayout}
ref={containerRef}> ref={containerRef}>
<Animated.View style={[styles.indicator, indicatorStyle]} /> <Animated.View style={[styles.indicator]} />
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}> <ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
{items.map((item, i) => { {items.map((item, i) => {
const selected = i === selectedPage const selected = i === selectedPage
return ( return (
<PressableWithHover <Animated.View key={item} style={selected ? styles.active : []}>
ref={itemRefs[i]} <PressableWithHover
key={item} ref={itemRefs[i]}
style={ style={[
indicatorPosition === 'top' ? styles.itemTop : styles.itemBottom indicatorPosition === 'top'
} ? styles.itemTop
hoverStyle={pal.viewLight} : styles.itemBottom,
onPress={() => onPressItem(i)}> ]}
<Text hoverStyle={pal.viewLight}
type="xl-bold" onPress={() => onPressItem(i)}>
testID={testID ? `${testID}-${item}` : undefined} <Text
style={selected ? pal.text : pal.textLight}> type="xl-bold"
{item} testID={testID ? `${testID}-${item}` : undefined}
</Text> style={selected ? pal.text : pal.textLight}>
</PressableWithHover> {item}
</Text>
</PressableWithHover>
</Animated.View>
) )
})} })}
</ScrollView> </ScrollView>
@ -152,6 +155,7 @@ const styles = isDesktopWeb
height: 3, height: 3,
zIndex: 1, zIndex: 1,
}, },
active: {},
}) })
: StyleSheet.create({ : StyleSheet.create({
outer: { outer: {
@ -174,4 +178,8 @@ const styles = isDesktopWeb
width: 1, width: 1,
height: 3, height: 3,
}, },
active: {
borderBottomColor: 'blue',
borderBottomWidth: 3,
},
}) })