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

View File

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

View File

@ -33,6 +33,20 @@ export class AlgoItemModel {
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
// =
async save() {
@ -57,19 +71,52 @@ export class AlgoItemModel {
}
}
// async getFeedSkeleton() {
// const res = await this.rootStore.agent.app.bsky.feed.getFeedSkeleton({
// feed: this.data.uri,
// })
// const skeleton = res.data.feed
// console.log('skeleton', skeleton)
// return skeleton
// }
// async getFeed() {
// const feed = await this.rootStore.agent.app.bsky.feed.getFeed({
// feed: this.data.uri,
// })
// console.log('feed', feed)
// return feed
// }
async like() {
try {
this.toggleLiked = true
await this.rootStore.agent.app.bsky.feed.like.create(
{
repo: this.rootStore.me.did,
},
{
subject: {
uri: this.data.uri,
cid: this.data.cid,
},
createdAt: new Date().toString(),
},
)
} 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]}
onPress={() => {
navigation.navigate('CustomFeed', {
name: item.data.creator.did,
name: item.data.displayName,
rkey: item.data.uri,
})
}}
@ -40,25 +40,27 @@ const AlgoItem = observer(
</View>
<View style={[styles.headerTextContainer]}>
<Text style={[pal.text, s.bold]}>
{item.data.displayName ? item.data.displayName : 'Feed name'}
{item.data.displayName ?? 'Feed name'}
</Text>
<Text style={[pal.textLight, styles.description]}>
{item.data.description ??
'THIS IS A FEED DESCRIPTION, IT WILL TELL YOU WHAT THE FEED IS ABOUT. THIS IS A COOL FEED ABOUT COOL PEOPLE.'}
{item.data.description ?? 'Feed description'}
</Text>
</View>
</View>
{/* TODO: this feed is like by *3* people UserAvatars and others */}
<View style={styles.bottomContainer}>
<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} />
</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>
<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 ? (
<HeartIconSolid
style={styles.ctrlIconLiked as StyleProp<ViewStyle>}
style={styles.ctrlIconLiked}
size={opts.big ? 22 : 16}
/>
) : (

View File

@ -1,23 +1,30 @@
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 {colors, s} from 'lib/styles'
import {observer} from 'mobx-react-lite'
import React, {useEffect, useMemo, useRef} from 'react'
import {FlatList, StyleSheet, View} from 'react-native'
import React, {useMemo, useRef} from 'react'
import {FlatList, StyleSheet, TouchableOpacity, View} from 'react-native'
import {useStores} from 'state/index'
import {AlgoItemModel} from 'state/models/feeds/algo/algo-item'
import {PostsFeedModel} from 'state/models/feeds/posts'
import {useCustomFeed} from 'view/com/algos/useCustomFeed'
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {Feed} from 'view/com/posts/Feed'
import {UserAvatar} from 'view/com/util/UserAvatar'
import {ViewHeader} from 'view/com/util/ViewHeader'
import {Button} from 'view/com/util/forms/Button'
import {Text} from 'view/com/util/text/Text'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'CustomFeed'>
export const CustomFeed = withAuthRequired(
observer(({route}: Props) => {
observer(({route, navigation}: Props) => {
const rootStore = useStores()
const scrollElRef = useRef<FlatList>(null)
const {rkey, name} = route.params
const currentFeed = useCustomFeed(rkey)
const pal = usePalette('default')
const scrollElRef = useRef<FlatList>(null)
const algoFeed: PostsFeedModel = useMemo(() => {
const feed = new PostsFeedModel(rootStore, 'custom', {
@ -29,13 +36,62 @@ export const CustomFeed = withAuthRequired(
return (
<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
scrollElRef={scrollElRef}
testID={'test-feed'}
key="default"
feed={algoFeed}
headerOffset={12}
/>
</View>
)
@ -47,4 +103,37 @@ const styles = StyleSheet.create({
flex: 1,
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,
},
})