Add the ability to navigate to posts within a thread

zio/stable
Paul Frazee 2022-07-21 13:07:24 -05:00
parent 139c9deb75
commit 28dbc5f5e6
7 changed files with 46 additions and 20 deletions

View File

@ -15,7 +15,7 @@
"dependencies": { "dependencies": {
"@adxp/auth": "*", "@adxp/auth": "*",
"@adxp/common": "*", "@adxp/common": "*",
"@adxp/mock-api": "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#3714722a09503caad6e3aba7f9e9027994d07bdb", "@adxp/mock-api": "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#464712517e8f42b307622aa625bf2b2e11ad763e",
"@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1",

View File

@ -24,6 +24,7 @@ export class PostThreadViewPostModel implements bsky.PostThreadView.Post {
| bsky.PostThreadView.RecordEmbed | bsky.PostThreadView.RecordEmbed
| bsky.PostThreadView.ExternalEmbed | bsky.PostThreadView.ExternalEmbed
| bsky.PostThreadView.UnknownEmbed | bsky.PostThreadView.UnknownEmbed
parent?: PostThreadViewPostModel
replyCount: number = 0 replyCount: number = 0
replies?: PostThreadViewPostModel[] replies?: PostThreadViewPostModel[]
repostCount: number = 0 repostCount: number = 0
@ -34,11 +35,25 @@ export class PostThreadViewPostModel implements bsky.PostThreadView.Post {
makeAutoObservable(this) makeAutoObservable(this)
this._reactKey = reactKey this._reactKey = reactKey
if (v) { if (v) {
Object.assign(this, _omit(v, 'replies')) // copy everything but the replies Object.assign(this, _omit(v, 'parent', 'replies')) // copy everything but the replies and the parent
} }
} }
setReplies(keyGen: Generator<string>, v: bsky.PostThreadView.Post) { assignTreeModels(keyGen: Generator<string>, v: bsky.PostThreadView.Post) {
// parents
if (v.parent) {
// TODO: validate .record
const parentModel = new PostThreadViewPostModel(
keyGen.next().value,
v.parent,
)
parentModel._depth = this._depth - 1
if (v.parent.parent) {
parentModel.assignTreeModels(keyGen, v.parent)
}
this.parent = parentModel
}
// replies
if (v.replies) { if (v.replies) {
const replies = [] const replies = []
for (const item of v.replies) { for (const item of v.replies) {
@ -46,7 +61,7 @@ export class PostThreadViewPostModel implements bsky.PostThreadView.Post {
const itemModel = new PostThreadViewPostModel(keyGen.next().value, item) const itemModel = new PostThreadViewPostModel(keyGen.next().value, item)
itemModel._depth = this._depth + 1 itemModel._depth = this._depth + 1
if (item.replies) { if (item.replies) {
itemModel.setReplies(keyGen, item) itemModel.assignTreeModels(keyGen, item)
} }
replies.push(itemModel) replies.push(itemModel)
} }
@ -161,7 +176,7 @@ export class PostThreadViewModel implements bsky.PostThreadView.Response {
const keyGen = reactKeyGenerator() const keyGen = reactKeyGenerator()
const thread = new PostThreadViewPostModel(keyGen.next().value, res.thread) const thread = new PostThreadViewPostModel(keyGen.next().value, res.thread)
thread._isHighlightedPost = true thread._isHighlightedPost = true
thread.setReplies(keyGen, res.thread) thread.assignTreeModels(keyGen, res.thread)
this.thread = thread this.thread = thread
} }
} }

View File

@ -78,6 +78,9 @@ export const PostThread = observer(function PostThread({
function* flattenThread( function* flattenThread(
post: PostThreadViewPostModel, post: PostThreadViewPostModel,
): Generator<PostThreadViewPostModel, void> { ): Generator<PostThreadViewPostModel, void> {
if (post.parent) {
yield* flattenThread(post.parent)
}
yield post yield post
if (post.replies?.length) { if (post.replies?.length) {
for (const reply of post.replies) { for (const reply of post.replies) {

View File

@ -8,7 +8,7 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
} from 'react-native' } from 'react-native'
import {bsky} from '@adxp/mock-api' import {bsky, AdxUri} from '@adxp/mock-api'
import moment from 'moment' import moment from 'moment'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {OnNavigateContent} from '../../routes/types' import {OnNavigateContent} from '../../routes/types'
@ -31,7 +31,8 @@ function iter<T>(n: number, fn: (i: number) => T): Array<T> {
} }
export const PostThreadItem = observer(function PostThreadItem({ export const PostThreadItem = observer(function PostThreadItem({
item, // onNavigateContent, item,
onNavigateContent,
}: { }: {
item: PostThreadViewPostModel item: PostThreadViewPostModel
onNavigateContent: OnNavigateContent onNavigateContent: OnNavigateContent
@ -39,12 +40,16 @@ export const PostThreadItem = observer(function PostThreadItem({
const record = item.record as unknown as bsky.Post.Record const record = item.record as unknown as bsky.Post.Record
const hasEngagement = item.likeCount || item.repostCount const hasEngagement = item.likeCount || item.repostCount
const onPressOuter = () => { const onPressOuter = () => {
// TODO onNavigateContent const urip = new AdxUri(item.uri)
onNavigateContent('PostThread', {
name: item.author.name,
recordKey: urip.recordKey,
})
} }
return ( return (
<TouchableOpacity style={styles.outer} onPress={onPressOuter}> <TouchableOpacity style={styles.outer} onPress={onPressOuter}>
<View style={styles.layout}> <View style={styles.layout}>
{iter(item._depth, () => ( {iter(Math.abs(item._depth), () => (
<View style={styles.replyBar} /> <View style={styles.replyBar} />
))} ))}
<View style={styles.layoutAvi}> <View style={styles.layoutAvi}>
@ -143,7 +148,7 @@ const styles = StyleSheet.create({
}, },
replyBar: { replyBar: {
width: 5, width: 5,
backgroundColor: '#d4f0ff', backgroundColor: 'gray',
marginRight: 2, marginRight: 2,
}, },
layoutAvi: { layoutAvi: {

View File

@ -54,20 +54,24 @@ const tabBarScreenOptions = ({
route: RouteProp<ParamListBase, string> route: RouteProp<ParamListBase, string>
}) => ({ }) => ({
headerShown: false, headerShown: false,
tabBarIcon: (_state: {focused: boolean; color: string; size: number}) => { tabBarIcon: (state: {focused: boolean; color: string; size: number}) => {
switch (route.name) { switch (route.name) {
case 'Home': case 'Home':
return <FontAwesomeIcon icon="house" /> return <FontAwesomeIcon icon="house" style={{color: state.color}} />
case 'Search': case 'Search':
return <FontAwesomeIcon icon="magnifying-glass" /> return (
<FontAwesomeIcon
icon="magnifying-glass"
style={{color: state.color}}
/>
)
case 'Notifications': case 'Notifications':
return <FontAwesomeIcon icon="bell" /> return <FontAwesomeIcon icon="bell" style={{color: state.color}} />
case 'Menu': case 'Menu':
return <FontAwesomeIcon icon="bars" /> return <FontAwesomeIcon icon="bars" style={{color: state.color}} />
default: default:
return <FontAwesomeIcon icon="bars" /> return <FontAwesomeIcon icon="bars" style={{color: state.color}} />
} }
// return <Text>{route.name?.[0] || ''}</Text>
}, },
}) })

View File

@ -8,7 +8,6 @@ Paul's todo list
- Thread view - Thread view
- Mock API support fetch on thread that's not root - Mock API support fetch on thread that's not root
- Header (back btn, highlight) - Header (back btn, highlight)
- Navigate on post press
- View likes list - View likes list
- View reposts list - View reposts list
- Reply control - Reply control

View File

@ -55,9 +55,9 @@
ucans "0.9.0-alpha3" ucans "0.9.0-alpha3"
uint8arrays "^3.0.0" uint8arrays "^3.0.0"
"@adxp/mock-api@git+ssh://git@github.com:bluesky-social/adx-mock-api.git#3714722a09503caad6e3aba7f9e9027994d07bdb": "@adxp/mock-api@git+ssh://git@github.com:bluesky-social/adx-mock-api.git#464712517e8f42b307622aa625bf2b2e11ad763e":
version "0.0.1" version "0.0.1"
resolved "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#3714722a09503caad6e3aba7f9e9027994d07bdb" resolved "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#464712517e8f42b307622aa625bf2b2e11ad763e"
dependencies: dependencies:
ajv "^8.11.0" ajv "^8.11.0"
ajv-formats "^2.1.1" ajv-formats "^2.1.1"