remove tab bar underline animation
parent
d7e39bde12
commit
6249bb16ca
|
@ -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
|
||||||
// =
|
// =
|
||||||
|
|
||||||
|
|
|
@ -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'}
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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,19 +100,21 @@ 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 (
|
||||||
|
<Animated.View key={item} style={selected ? styles.active : []}>
|
||||||
<PressableWithHover
|
<PressableWithHover
|
||||||
ref={itemRefs[i]}
|
ref={itemRefs[i]}
|
||||||
key={item}
|
style={[
|
||||||
style={
|
indicatorPosition === 'top'
|
||||||
indicatorPosition === 'top' ? styles.itemTop : styles.itemBottom
|
? styles.itemTop
|
||||||
}
|
: styles.itemBottom,
|
||||||
|
]}
|
||||||
hoverStyle={pal.viewLight}
|
hoverStyle={pal.viewLight}
|
||||||
onPress={() => onPressItem(i)}>
|
onPress={() => onPressItem(i)}>
|
||||||
<Text
|
<Text
|
||||||
|
@ -122,6 +124,7 @@ export function TabBar({
|
||||||
{item}
|
{item}
|
||||||
</Text>
|
</Text>
|
||||||
</PressableWithHover>
|
</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,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue