Add the ability to navigate to posts within a thread
parent
139c9deb75
commit
28dbc5f5e6
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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>
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue