new design for custom feed preview

zio/stable
Ansh Nanda 2023-05-15 17:59:36 -07:00
parent 6249bb16ca
commit c4a666c221
7 changed files with 199 additions and 34 deletions

View File

@ -443,7 +443,7 @@ export function HeartIcon({
size = 24, size = 24,
strokeWidth = 1.5, strokeWidth = 1.5,
}: { }: {
style?: StyleProp<ViewStyle> style?: StyleProp<TextStyle>
size?: string | number size?: string | number
strokeWidth: number strokeWidth: number
}) { }) {
@ -464,7 +464,7 @@ export function HeartIconSolid({
style, style,
size = 24, size = 24,
}: { }: {
style?: StyleProp<ViewStyle> style?: StyleProp<TextStyle>
size?: string | number size?: string | number
}) { }) {
return ( return (

View File

@ -21,7 +21,7 @@ export type CommonNavigatorParams = {
CopyrightPolicy: undefined CopyrightPolicy: undefined
AppPasswords: undefined AppPasswords: undefined
SavedFeeds: undefined SavedFeeds: undefined
CustomFeed: {name: string; rkey: string} CustomFeed: {name?: string; rkey: string}
MutedAccounts: undefined MutedAccounts: undefined
BlockedAccounts: undefined BlockedAccounts: undefined
} }

View File

@ -33,6 +33,20 @@ export class AlgoItemModel {
return this.data.uri return this.data.uri
} }
get isSaved() {
return this.data.viewer?.saved
}
get isLiked() {
return this.data.viewer?.liked
}
set toggleLiked(value: boolean) {
if (this.data.viewer) {
this.data.viewer.liked = value
}
}
// public apis // public apis
// = // =
async save() { async save() {
@ -57,19 +71,52 @@ export class AlgoItemModel {
} }
} }
// async getFeedSkeleton() { async like() {
// const res = await this.rootStore.agent.app.bsky.feed.getFeedSkeleton({ try {
// feed: this.data.uri, this.toggleLiked = true
// }) await this.rootStore.agent.app.bsky.feed.like.create(
// const skeleton = res.data.feed {
// console.log('skeleton', skeleton) repo: this.rootStore.me.did,
// return skeleton },
// } {
// async getFeed() { subject: {
// const feed = await this.rootStore.agent.app.bsky.feed.getFeed({ uri: this.data.uri,
// feed: this.data.uri, cid: this.data.cid,
// }) },
// console.log('feed', feed) createdAt: new Date().toString(),
// return feed },
// } )
} catch (e: any) {
this.rootStore.log.error('Failed to like feed', e)
}
}
static async getView(store: RootStoreModel, uri: string) {
const res = await store.agent.app.bsky.feed.getFeedGenerator({
feed: uri,
})
const view = res.data.view
return view
}
async checkIsValid() {
const res = await this.rootStore.agent.app.bsky.feed.getFeedGenerator({
feed: this.data.uri,
})
return res.data.isValid
}
async checkIsOnline() {
const res = await this.rootStore.agent.app.bsky.feed.getFeedGenerator({
feed: this.data.uri,
})
return res.data.isOnline
}
async reload() {
const res = await this.rootStore.agent.app.bsky.feed.getFeedGenerator({
feed: this.data.uri,
})
this.data = res.data.view
}
} }

View File

@ -29,7 +29,7 @@ const AlgoItem = observer(
style={[styles.container, style]} style={[styles.container, style]}
onPress={() => { onPress={() => {
navigation.navigate('CustomFeed', { navigation.navigate('CustomFeed', {
name: item.data.creator.did, name: item.data.displayName,
rkey: item.data.uri, rkey: item.data.uri,
}) })
}} }}
@ -40,25 +40,27 @@ const AlgoItem = observer(
</View> </View>
<View style={[styles.headerTextContainer]}> <View style={[styles.headerTextContainer]}>
<Text style={[pal.text, s.bold]}> <Text style={[pal.text, s.bold]}>
{item.data.displayName ? item.data.displayName : 'Feed name'} {item.data.displayName ?? 'Feed name'}
</Text> </Text>
<Text style={[pal.textLight, styles.description]}> <Text style={[pal.textLight, styles.description]}>
{item.data.description ?? {item.data.description ?? 'Feed description'}
'THIS IS A FEED DESCRIPTION, IT WILL TELL YOU WHAT THE FEED IS ABOUT. THIS IS A COOL FEED ABOUT COOL PEOPLE.'}
</Text> </Text>
</View> </View>
</View> </View>
{/* TODO: this feed is like by *3* people UserAvatars and others */}
<View style={styles.bottomContainer}> <View style={styles.bottomContainer}>
<View style={styles.likedByContainer}> <View style={styles.likedByContainer}>
<View style={styles.likedByAvatars}> {/* <View style={styles.likedByAvatars}>
<UserAvatar size={24} avatar={item.data.avatar} /> <UserAvatar size={24} avatar={item.data.avatar} />
<UserAvatar size={24} avatar={item.data.avatar} /> <UserAvatar size={24} avatar={item.data.avatar} />
<UserAvatar size={24} avatar={item.data.avatar} /> <UserAvatar size={24} avatar={item.data.avatar} />
</View> </View> */}
<Text style={[pal.text, pal.textLight]}>Liked by 3 others</Text> <Text style={[pal.text, pal.textLight]}>
{item.data.likeCount && item.data.likeCount > 1
? `Liked by ${item.data.likeCount} others`
: 'Be the first to like this'}
</Text>
</View> </View>
<View> <View>
<Button <Button

View File

@ -0,0 +1,27 @@
import {useEffect, useState} from 'react'
import {useStores} from 'state/index'
import {AlgoItemModel} from 'state/models/feeds/algo/algo-item'
export function useCustomFeed(uri: string) {
const store = useStores()
const [item, setItem] = useState<AlgoItemModel>()
useEffect(() => {
async function fetchView() {
const res = await store.agent.app.bsky.feed.getFeedGenerator({
feed: uri,
})
const view = res.data.view
return view
}
async function buildFeedItem() {
const view = await fetchView()
if (view) {
const temp = new AlgoItemModel(store, view)
setItem(temp)
}
}
buildFeedItem()
}, [store, uri])
return item
}

View File

@ -240,7 +240,7 @@ export function PostCtrls(opts: PostCtrlsOpts) {
}> }>
{opts.isLiked ? ( {opts.isLiked ? (
<HeartIconSolid <HeartIconSolid
style={styles.ctrlIconLiked as StyleProp<ViewStyle>} style={styles.ctrlIconLiked}
size={opts.big ? 22 : 16} size={opts.big ? 22 : 16}
/> />
) : ( ) : (

View File

@ -1,23 +1,30 @@
import {NativeStackScreenProps} from '@react-navigation/native-stack' import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {usePalette} from 'lib/hooks/usePalette'
import {HeartIcon} from 'lib/icons'
import {CommonNavigatorParams} from 'lib/routes/types' import {CommonNavigatorParams} from 'lib/routes/types'
import {colors, s} from 'lib/styles'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import React, {useEffect, useMemo, useRef} from 'react' import React, {useMemo, useRef} from 'react'
import {FlatList, StyleSheet, View} from 'react-native' import {FlatList, StyleSheet, TouchableOpacity, View} from 'react-native'
import {useStores} from 'state/index' import {useStores} from 'state/index'
import {AlgoItemModel} from 'state/models/feeds/algo/algo-item'
import {PostsFeedModel} from 'state/models/feeds/posts' import {PostsFeedModel} from 'state/models/feeds/posts'
import {useCustomFeed} from 'view/com/algos/useCustomFeed'
import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {Feed} from 'view/com/posts/Feed' import {Feed} from 'view/com/posts/Feed'
import {UserAvatar} from 'view/com/util/UserAvatar'
import {ViewHeader} from 'view/com/util/ViewHeader' import {ViewHeader} from 'view/com/util/ViewHeader'
import {Button} from 'view/com/util/forms/Button'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'CustomFeed'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'CustomFeed'>
export const CustomFeed = withAuthRequired( export const CustomFeed = withAuthRequired(
observer(({route}: Props) => { observer(({route, navigation}: Props) => {
const rootStore = useStores() const rootStore = useStores()
const scrollElRef = useRef<FlatList>(null)
const {rkey, name} = route.params const {rkey, name} = route.params
const currentFeed = useCustomFeed(rkey)
const pal = usePalette('default')
const scrollElRef = useRef<FlatList>(null)
const algoFeed: PostsFeedModel = useMemo(() => { const algoFeed: PostsFeedModel = useMemo(() => {
const feed = new PostsFeedModel(rootStore, 'custom', { const feed = new PostsFeedModel(rootStore, 'custom', {
@ -29,13 +36,62 @@ export const CustomFeed = withAuthRequired(
return ( return (
<View style={[styles.container]}> <View style={[styles.container]}>
<ViewHeader title={'Custom Feed'} showOnDesktop /> <View>
<ViewHeader
title={name ?? `${currentFeed?.data.creator.displayName}'s feed`}
showOnDesktop
/>
<View style={[styles.center]}>
<View style={[styles.header]}>
<View style={styles.avatarContainer}>
<UserAvatar
size={30}
avatar={currentFeed?.data.creator.avatar}
/>
<Text style={[pal.textLight]}>
@{currentFeed?.data.creator.handle}
</Text>
</View>
<Text style={[pal.text]}>{currentFeed?.data.description}</Text>
</View>
<View style={[styles.buttonsContainer]}>
<Button
type={currentFeed?.isSaved ? 'default' : 'inverted'}
style={[styles.saveButton]}
onPress={() => {
if (currentFeed?.data.viewer?.saved) {
currentFeed?.unsave()
rootStore.me.savedFeeds.removeFeed(currentFeed!.data.uri)
} else {
currentFeed!.save()
rootStore.me.savedFeeds.addFeed(currentFeed!)
}
}}
label={currentFeed?.data.viewer?.saved ? 'Unsave' : 'Save'}
/>
<TouchableOpacity
accessibilityRole="button"
onPress={() => {
currentFeed?.like()
}}
style={[styles.likeButton]}>
<Text style={[pal.text, s.semiBold]}>
{currentFeed?.data.likeCount}
</Text>
<HeartIcon strokeWidth={3} size={18} style={styles.liked} />
</TouchableOpacity>
</View>
</View>
</View>
<Feed <Feed
scrollElRef={scrollElRef} scrollElRef={scrollElRef}
testID={'test-feed'} testID={'test-feed'}
key="default" key="default"
feed={algoFeed} feed={algoFeed}
headerOffset={12}
/> />
</View> </View>
) )
@ -47,4 +103,37 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
height: '100%', height: '100%',
}, },
center: {alignItems: 'center', justifyContent: 'center', gap: 8},
header: {
alignItems: 'center',
gap: 8,
},
avatarContainer: {
flexDirection: 'row',
gap: 8,
},
buttonsContainer: {
flexDirection: 'row',
gap: 8,
},
saveButton: {
minWidth: 100,
alignItems: 'center',
},
liked: {
color: colors.red3,
},
notLiked: {
color: colors.gray3,
},
likeButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 4,
paddingHorizontal: 8,
borderRadius: 24,
backgroundColor: colors.gray1,
gap: 4,
},
}) })