Implement like and repost
parent
cc8a170204
commit
0ec0ba996f
|
@ -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#19dc93e569fa71ae3de85876b3707afd47a6fe8c",
|
"@adxp/mock-api": "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#0159e865560c12fb7004862c7d9d48420ed93878",
|
||||||
"@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",
|
||||||
|
|
|
@ -4,7 +4,16 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// import {ReactNativeStore} from './auth'
|
// import {ReactNativeStore} from './auth'
|
||||||
import {AdxClient, AdxRepoClient, AdxUri, bsky} from '@adxp/mock-api'
|
import {
|
||||||
|
AdxClient,
|
||||||
|
AdxRepoClient,
|
||||||
|
AdxRepoCollectionClient,
|
||||||
|
AdxUri,
|
||||||
|
bsky,
|
||||||
|
SchemaOpt,
|
||||||
|
ListRecordsResponseValidated,
|
||||||
|
GetRecordResponseValidated,
|
||||||
|
} from '@adxp/mock-api'
|
||||||
import * as storage from './storage'
|
import * as storage from './storage'
|
||||||
import {postTexts} from './mock-data/post-texts'
|
import {postTexts} from './mock-data/post-texts'
|
||||||
import {replyTexts} from './mock-data/reply-texts'
|
import {replyTexts} from './mock-data/reply-texts'
|
||||||
|
@ -19,6 +28,78 @@ export async function setup(adx: AdxClient) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function like(adx: AdxClient, user: string, uri: string) {
|
||||||
|
await adx.repo(user, true).collection('blueskyweb.xyz:Likes').create('Like', {
|
||||||
|
$type: 'blueskyweb.xyz:Like',
|
||||||
|
subject: uri,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unlike(adx: AdxClient, user: string, uri: string) {
|
||||||
|
const coll = adx.repo(user, true).collection('blueskyweb.xyz:Likes')
|
||||||
|
const numDels = await deleteWhere(coll, 'Like', record => {
|
||||||
|
return record.value.subject === uri
|
||||||
|
})
|
||||||
|
return numDels > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function repost(adx: AdxClient, user: string, uri: string) {
|
||||||
|
await adx
|
||||||
|
.repo(user, true)
|
||||||
|
.collection('blueskyweb.xyz:Posts')
|
||||||
|
.create('Repost', {
|
||||||
|
$type: 'blueskyweb.xyz:Repost',
|
||||||
|
subject: uri,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unrepost(adx: AdxClient, user: string, uri: string) {
|
||||||
|
const coll = adx.repo(user, true).collection('blueskyweb.xyz:Posts')
|
||||||
|
const numDels = await deleteWhere(coll, 'Repost', record => {
|
||||||
|
return record.value.subject === uri
|
||||||
|
})
|
||||||
|
return numDels > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type WherePred = (record: GetRecordResponseValidated) => Boolean
|
||||||
|
async function deleteWhere(
|
||||||
|
coll: AdxRepoCollectionClient,
|
||||||
|
schema: SchemaOpt,
|
||||||
|
cond: WherePred,
|
||||||
|
) {
|
||||||
|
const toDelete: string[] = []
|
||||||
|
iterateAll(coll, schema, record => {
|
||||||
|
if (cond(record)) {
|
||||||
|
toDelete.push(record.key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for (const key of toDelete) {
|
||||||
|
await coll.del(key)
|
||||||
|
}
|
||||||
|
return toDelete.length
|
||||||
|
}
|
||||||
|
|
||||||
|
type IterateAllCb = (record: GetRecordResponseValidated) => void
|
||||||
|
async function iterateAll(
|
||||||
|
coll: AdxRepoCollectionClient,
|
||||||
|
schema: SchemaOpt,
|
||||||
|
cb: IterateAllCb,
|
||||||
|
) {
|
||||||
|
let cursor
|
||||||
|
let res: ListRecordsResponseValidated
|
||||||
|
do {
|
||||||
|
res = await coll.list(schema, {after: cursor, limit: 100})
|
||||||
|
for (const record of res.records) {
|
||||||
|
if (record.valid) {
|
||||||
|
cb(record)
|
||||||
|
cursor = record.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (res.records.length === 100)
|
||||||
|
}
|
||||||
|
|
||||||
// TEMPORARY
|
// TEMPORARY
|
||||||
// mock api config
|
// mock api config
|
||||||
// =======
|
// =======
|
||||||
|
|
|
@ -1,6 +1,17 @@
|
||||||
import {makeAutoObservable} from 'mobx'
|
import {makeAutoObservable, runInAction} from 'mobx'
|
||||||
import {bsky} from '@adxp/mock-api'
|
import {bsky} from '@adxp/mock-api'
|
||||||
|
import _omit from 'lodash.omit'
|
||||||
import {RootStoreModel} from './root-store'
|
import {RootStoreModel} from './root-store'
|
||||||
|
import * as apilib from '../lib/api'
|
||||||
|
|
||||||
|
export class FeedViewItemMyStateModel {
|
||||||
|
hasLiked: boolean = false
|
||||||
|
hasReposted: boolean = false
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
makeAutoObservable(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class FeedViewItemModel implements bsky.FeedView.FeedItem {
|
export class FeedViewItemModel implements bsky.FeedView.FeedItem {
|
||||||
// ui state
|
// ui state
|
||||||
|
@ -19,11 +30,51 @@ export class FeedViewItemModel implements bsky.FeedView.FeedItem {
|
||||||
repostCount: number = 0
|
repostCount: number = 0
|
||||||
likeCount: number = 0
|
likeCount: number = 0
|
||||||
indexedAt: string = ''
|
indexedAt: string = ''
|
||||||
|
myState = new FeedViewItemMyStateModel()
|
||||||
|
|
||||||
constructor(reactKey: string, v: bsky.FeedView.FeedItem) {
|
constructor(
|
||||||
makeAutoObservable(this)
|
public rootStore: RootStoreModel,
|
||||||
|
reactKey: string,
|
||||||
|
v: bsky.FeedView.FeedItem,
|
||||||
|
) {
|
||||||
|
makeAutoObservable(this, {rootStore: false})
|
||||||
this._reactKey = reactKey
|
this._reactKey = reactKey
|
||||||
Object.assign(this, v)
|
Object.assign(this, _omit(v, 'myState'))
|
||||||
|
if (v.myState) {
|
||||||
|
Object.assign(this.myState, v.myState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleLike() {
|
||||||
|
if (this.myState.hasLiked) {
|
||||||
|
await apilib.unlike(this.rootStore.api, 'alice.com', this.uri)
|
||||||
|
runInAction(() => {
|
||||||
|
this.likeCount--
|
||||||
|
this.myState.hasLiked = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await apilib.like(this.rootStore.api, 'alice.com', this.uri)
|
||||||
|
runInAction(() => {
|
||||||
|
this.likeCount++
|
||||||
|
this.myState.hasLiked = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleRepost() {
|
||||||
|
if (this.myState.hasReposted) {
|
||||||
|
await apilib.unrepost(this.rootStore.api, 'alice.com', this.uri)
|
||||||
|
runInAction(() => {
|
||||||
|
this.repostCount--
|
||||||
|
this.myState.hasReposted = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await apilib.repost(this.rootStore.api, 'alice.com', this.uri)
|
||||||
|
runInAction(() => {
|
||||||
|
this.repostCount++
|
||||||
|
this.myState.hasReposted = true
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +228,6 @@ export class FeedViewModel implements bsky.FeedView.Response {
|
||||||
|
|
||||||
private _append(keyId: number, item: bsky.FeedView.FeedItem) {
|
private _append(keyId: number, item: bsky.FeedView.FeedItem) {
|
||||||
// TODO: validate .record
|
// TODO: validate .record
|
||||||
this.feed.push(new FeedViewItemModel(`item-${keyId}`, item))
|
this.feed.push(new FeedViewItemModel(this.rootStore, `item-${keyId}`, item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {makeAutoObservable, runInAction} from 'mobx'
|
||||||
import {bsky, AdxUri} from '@adxp/mock-api'
|
import {bsky, AdxUri} from '@adxp/mock-api'
|
||||||
import _omit from 'lodash.omit'
|
import _omit from 'lodash.omit'
|
||||||
import {RootStoreModel} from './root-store'
|
import {RootStoreModel} from './root-store'
|
||||||
|
import * as apilib from '../lib/api'
|
||||||
|
|
||||||
function* reactKeyGenerator(): Generator<string> {
|
function* reactKeyGenerator(): Generator<string> {
|
||||||
let counter = 0
|
let counter = 0
|
||||||
|
@ -10,6 +11,15 @@ function* reactKeyGenerator(): Generator<string> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class PostThreadViewPostMyStateModel {
|
||||||
|
hasLiked: boolean = false
|
||||||
|
hasReposted: boolean = false
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
makeAutoObservable(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class PostThreadViewPostModel implements bsky.PostThreadView.Post {
|
export class PostThreadViewPostModel implements bsky.PostThreadView.Post {
|
||||||
// ui state
|
// ui state
|
||||||
_reactKey: string = ''
|
_reactKey: string = ''
|
||||||
|
@ -30,12 +40,20 @@ export class PostThreadViewPostModel implements bsky.PostThreadView.Post {
|
||||||
repostCount: number = 0
|
repostCount: number = 0
|
||||||
likeCount: number = 0
|
likeCount: number = 0
|
||||||
indexedAt: string = ''
|
indexedAt: string = ''
|
||||||
|
myState = new PostThreadViewPostMyStateModel()
|
||||||
|
|
||||||
constructor(reactKey: string, v?: bsky.PostThreadView.Post) {
|
constructor(
|
||||||
makeAutoObservable(this)
|
public rootStore: RootStoreModel,
|
||||||
|
reactKey: string,
|
||||||
|
v?: bsky.PostThreadView.Post,
|
||||||
|
) {
|
||||||
|
makeAutoObservable(this, {rootStore: false})
|
||||||
this._reactKey = reactKey
|
this._reactKey = reactKey
|
||||||
if (v) {
|
if (v) {
|
||||||
Object.assign(this, _omit(v, 'parent', 'replies')) // copy everything but the replies and the parent
|
Object.assign(this, _omit(v, 'parent', 'replies', 'myState')) // replies and parent are handled via assignTreeModels
|
||||||
|
if (v.myState) {
|
||||||
|
Object.assign(this.myState, v.myState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +62,7 @@ export class PostThreadViewPostModel implements bsky.PostThreadView.Post {
|
||||||
if (v.parent) {
|
if (v.parent) {
|
||||||
// TODO: validate .record
|
// TODO: validate .record
|
||||||
const parentModel = new PostThreadViewPostModel(
|
const parentModel = new PostThreadViewPostModel(
|
||||||
|
this.rootStore,
|
||||||
keyGen.next().value,
|
keyGen.next().value,
|
||||||
v.parent,
|
v.parent,
|
||||||
)
|
)
|
||||||
|
@ -58,7 +77,11 @@ export class PostThreadViewPostModel implements bsky.PostThreadView.Post {
|
||||||
const replies = []
|
const replies = []
|
||||||
for (const item of v.replies) {
|
for (const item of v.replies) {
|
||||||
// TODO: validate .record
|
// TODO: validate .record
|
||||||
const itemModel = new PostThreadViewPostModel(keyGen.next().value, item)
|
const itemModel = new PostThreadViewPostModel(
|
||||||
|
this.rootStore,
|
||||||
|
keyGen.next().value,
|
||||||
|
item,
|
||||||
|
)
|
||||||
itemModel._depth = this._depth + 1
|
itemModel._depth = this._depth + 1
|
||||||
if (item.replies) {
|
if (item.replies) {
|
||||||
itemModel.assignTreeModels(keyGen, item)
|
itemModel.assignTreeModels(keyGen, item)
|
||||||
|
@ -68,10 +91,41 @@ export class PostThreadViewPostModel implements bsky.PostThreadView.Post {
|
||||||
this.replies = replies
|
this.replies = replies
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const UNLOADED_THREAD = new PostThreadViewPostModel('')
|
|
||||||
|
|
||||||
export class PostThreadViewModel implements bsky.PostThreadView.Response {
|
async toggleLike() {
|
||||||
|
if (this.myState.hasLiked) {
|
||||||
|
await apilib.unlike(this.rootStore.api, 'alice.com', this.uri)
|
||||||
|
runInAction(() => {
|
||||||
|
this.likeCount--
|
||||||
|
this.myState.hasLiked = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await apilib.like(this.rootStore.api, 'alice.com', this.uri)
|
||||||
|
runInAction(() => {
|
||||||
|
this.likeCount++
|
||||||
|
this.myState.hasLiked = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleRepost() {
|
||||||
|
if (this.myState.hasReposted) {
|
||||||
|
await apilib.unrepost(this.rootStore.api, 'alice.com', this.uri)
|
||||||
|
runInAction(() => {
|
||||||
|
this.repostCount--
|
||||||
|
this.myState.hasReposted = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await apilib.repost(this.rootStore.api, 'alice.com', this.uri)
|
||||||
|
runInAction(() => {
|
||||||
|
this.repostCount++
|
||||||
|
this.myState.hasReposted = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PostThreadViewModel {
|
||||||
// state
|
// state
|
||||||
isLoading = false
|
isLoading = false
|
||||||
isRefreshing = false
|
isRefreshing = false
|
||||||
|
@ -81,7 +135,7 @@ export class PostThreadViewModel implements bsky.PostThreadView.Response {
|
||||||
params: bsky.PostThreadView.Params
|
params: bsky.PostThreadView.Params
|
||||||
|
|
||||||
// data
|
// data
|
||||||
thread: PostThreadViewPostModel = UNLOADED_THREAD
|
thread?: PostThreadViewPostModel
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public rootStore: RootStoreModel,
|
public rootStore: RootStoreModel,
|
||||||
|
@ -99,7 +153,7 @@ export class PostThreadViewModel implements bsky.PostThreadView.Response {
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasContent() {
|
get hasContent() {
|
||||||
return this.thread !== UNLOADED_THREAD
|
return typeof this.thread !== 'undefined'
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasError() {
|
get hasError() {
|
||||||
|
@ -177,7 +231,11 @@ export class PostThreadViewModel implements bsky.PostThreadView.Response {
|
||||||
private _replaceAll(res: bsky.PostThreadView.Response) {
|
private _replaceAll(res: bsky.PostThreadView.Response) {
|
||||||
// TODO: validate .record
|
// TODO: validate .record
|
||||||
const keyGen = reactKeyGenerator()
|
const keyGen = reactKeyGenerator()
|
||||||
const thread = new PostThreadViewPostModel(keyGen.next().value, res.thread)
|
const thread = new PostThreadViewPostModel(
|
||||||
|
this.rootStore,
|
||||||
|
keyGen.next().value,
|
||||||
|
res.thread,
|
||||||
|
)
|
||||||
thread._isHighlightedPost = true
|
thread._isHighlightedPost = true
|
||||||
thread.assignTreeModels(keyGen, res.thread)
|
thread.assignTreeModels(keyGen, res.thread)
|
||||||
this.thread = thread
|
this.thread = thread
|
||||||
|
|
|
@ -2,6 +2,14 @@ import {makeAutoObservable} from 'mobx'
|
||||||
import {bsky} from '@adxp/mock-api'
|
import {bsky} from '@adxp/mock-api'
|
||||||
import {RootStoreModel} from './root-store'
|
import {RootStoreModel} from './root-store'
|
||||||
|
|
||||||
|
export class ProfileViewMyStateModel {
|
||||||
|
hasFollowed: boolean = false
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
makeAutoObservable(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ProfileViewModel implements bsky.ProfileView.Response {
|
export class ProfileViewModel implements bsky.ProfileView.Response {
|
||||||
// state
|
// state
|
||||||
isLoading = false
|
isLoading = false
|
||||||
|
@ -19,6 +27,7 @@ export class ProfileViewModel implements bsky.ProfileView.Response {
|
||||||
followsCount: number = 0
|
followsCount: number = 0
|
||||||
postsCount: number = 0
|
postsCount: number = 0
|
||||||
badges: bsky.ProfileView.Badge[] = []
|
badges: bsky.ProfileView.Badge[] = []
|
||||||
|
myState = new ProfileViewMyStateModel()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public rootStore: RootStoreModel,
|
public rootStore: RootStoreModel,
|
||||||
|
@ -101,5 +110,8 @@ export class ProfileViewModel implements bsky.ProfileView.Response {
|
||||||
this.followsCount = res.followsCount
|
this.followsCount = res.followsCount
|
||||||
this.postsCount = res.postsCount
|
this.postsCount = res.postsCount
|
||||||
this.badges = res.badges
|
this.badges = res.badges
|
||||||
|
if (res.myState) {
|
||||||
|
Object.assign(this.myState, res.myState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,16 @@ export const FeedItem = observer(function FeedItem({
|
||||||
name: item.author.name,
|
name: item.author.name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const onPressToggleRepost = () => {
|
||||||
|
item
|
||||||
|
.toggleRepost()
|
||||||
|
.catch(e => console.error('Failed to toggle repost', record, e))
|
||||||
|
}
|
||||||
|
const onPressToggleLike = () => {
|
||||||
|
item
|
||||||
|
.toggleLike()
|
||||||
|
.catch(e => console.error('Failed to toggle like', record, e))
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity style={styles.outer} onPress={onPressOuter}>
|
<TouchableOpacity style={styles.outer} onPress={onPressOuter}>
|
||||||
|
@ -75,21 +85,34 @@ export const FeedItem = observer(function FeedItem({
|
||||||
/>
|
/>
|
||||||
<Text>{item.replyCount}</Text>
|
<Text>{item.replyCount}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.ctrl}>
|
<TouchableOpacity style={styles.ctrl} onPress={onPressToggleRepost}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
style={styles.ctrlIcon}
|
style={
|
||||||
|
item.myState.hasReposted
|
||||||
|
? styles.ctrlIconReposted
|
||||||
|
: styles.ctrlIcon
|
||||||
|
}
|
||||||
icon="retweet"
|
icon="retweet"
|
||||||
size={22}
|
size={22}
|
||||||
/>
|
/>
|
||||||
<Text>{item.repostCount}</Text>
|
<Text
|
||||||
</View>
|
style={
|
||||||
<View style={styles.ctrl}>
|
item.myState.hasReposted ? [s.bold, s.green] : undefined
|
||||||
|
}>
|
||||||
|
{item.repostCount}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.ctrl} onPress={onPressToggleLike}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
style={styles.ctrlIcon}
|
style={
|
||||||
icon={['far', 'heart']}
|
item.myState.hasLiked ? styles.ctrlIconLiked : styles.ctrlIcon
|
||||||
|
}
|
||||||
|
icon={[item.myState.hasLiked ? 'fas' : 'far', 'heart']}
|
||||||
/>
|
/>
|
||||||
<Text>{item.likeCount}</Text>
|
<Text style={item.myState.hasLiked ? [s.bold, s.red] : undefined}>
|
||||||
</View>
|
{item.likeCount}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
<View style={styles.ctrl}>
|
<View style={styles.ctrl}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
style={styles.ctrlIcon}
|
style={styles.ctrlIcon}
|
||||||
|
@ -158,4 +181,12 @@ const styles = StyleSheet.create({
|
||||||
marginRight: 5,
|
marginRight: 5,
|
||||||
color: 'gray',
|
color: 'gray',
|
||||||
},
|
},
|
||||||
|
ctrlIconReposted: {
|
||||||
|
marginRight: 5,
|
||||||
|
color: 'green',
|
||||||
|
},
|
||||||
|
ctrlIconLiked: {
|
||||||
|
marginRight: 5,
|
||||||
|
color: 'red',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -56,7 +56,7 @@ export const PostThread = observer(function PostThread({
|
||||||
|
|
||||||
// loaded
|
// loaded
|
||||||
// =
|
// =
|
||||||
const posts = Array.from(flattenThread(view.thread))
|
const posts = view.thread ? Array.from(flattenThread(view.thread)) : []
|
||||||
const renderItem = ({item}: {item: PostThreadViewPostModel}) => (
|
const renderItem = ({item}: {item: PostThreadViewPostModel}) => (
|
||||||
<PostThreadItem item={item} onNavigateContent={onNavigateContent} />
|
<PostThreadItem item={item} onNavigateContent={onNavigateContent} />
|
||||||
)
|
)
|
||||||
|
|
|
@ -40,6 +40,16 @@ export const PostThreadItem = observer(function PostThreadItem({
|
||||||
name: item.author.name,
|
name: item.author.name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const onPressToggleRepost = () => {
|
||||||
|
item
|
||||||
|
.toggleRepost()
|
||||||
|
.catch(e => console.error('Failed to toggle repost', record, e))
|
||||||
|
}
|
||||||
|
const onPressToggleLike = () => {
|
||||||
|
item
|
||||||
|
.toggleLike()
|
||||||
|
.catch(e => console.error('Failed to toggle like', record, e))
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity style={styles.outer} onPress={onPressOuter}>
|
<TouchableOpacity style={styles.outer} onPress={onPressOuter}>
|
||||||
|
@ -108,21 +118,34 @@ export const PostThreadItem = observer(function PostThreadItem({
|
||||||
/>
|
/>
|
||||||
<Text>{item.replyCount}</Text>
|
<Text>{item.replyCount}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.ctrl}>
|
<TouchableOpacity style={styles.ctrl} onPress={onPressToggleRepost}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
style={styles.ctrlIcon}
|
style={
|
||||||
|
item.myState.hasReposted
|
||||||
|
? styles.ctrlIconReposted
|
||||||
|
: styles.ctrlIcon
|
||||||
|
}
|
||||||
icon="retweet"
|
icon="retweet"
|
||||||
size={22}
|
size={22}
|
||||||
/>
|
/>
|
||||||
<Text>{item.repostCount}</Text>
|
<Text
|
||||||
</View>
|
style={
|
||||||
<View style={styles.ctrl}>
|
item.myState.hasReposted ? [s.bold, s.green] : undefined
|
||||||
|
}>
|
||||||
|
{item.repostCount}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity style={styles.ctrl} onPress={onPressToggleLike}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
style={styles.ctrlIcon}
|
style={
|
||||||
icon={['far', 'heart']}
|
item.myState.hasLiked ? styles.ctrlIconLiked : styles.ctrlIcon
|
||||||
|
}
|
||||||
|
icon={[item.myState.hasLiked ? 'fas' : 'far', 'heart']}
|
||||||
/>
|
/>
|
||||||
<Text>{item.likeCount}</Text>
|
<Text style={item.myState.hasLiked ? [s.bold, s.red] : undefined}>
|
||||||
</View>
|
{item.likeCount}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
<View style={styles.ctrl}>
|
<View style={styles.ctrl}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
style={styles.ctrlIcon}
|
style={styles.ctrlIcon}
|
||||||
|
@ -205,4 +228,12 @@ const styles = StyleSheet.create({
|
||||||
marginRight: 5,
|
marginRight: 5,
|
||||||
color: 'gray',
|
color: 'gray',
|
||||||
},
|
},
|
||||||
|
ctrlIconReposted: {
|
||||||
|
marginRight: 5,
|
||||||
|
color: 'green',
|
||||||
|
},
|
||||||
|
ctrlIconLiked: {
|
||||||
|
marginRight: 5,
|
||||||
|
color: 'red',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {faBars} from '@fortawesome/free-solid-svg-icons/faBars'
|
||||||
import {faBell} from '@fortawesome/free-solid-svg-icons/faBell'
|
import {faBell} from '@fortawesome/free-solid-svg-icons/faBell'
|
||||||
import {faComment} from '@fortawesome/free-regular-svg-icons/faComment'
|
import {faComment} from '@fortawesome/free-regular-svg-icons/faComment'
|
||||||
import {faHeart} from '@fortawesome/free-regular-svg-icons/faHeart'
|
import {faHeart} from '@fortawesome/free-regular-svg-icons/faHeart'
|
||||||
|
import {faHeart as fasHeart} from '@fortawesome/free-solid-svg-icons/faHeart'
|
||||||
import {faHouse} from '@fortawesome/free-solid-svg-icons/faHouse'
|
import {faHouse} from '@fortawesome/free-solid-svg-icons/faHouse'
|
||||||
import {faMagnifyingGlass} from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass'
|
import {faMagnifyingGlass} from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass'
|
||||||
import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSquare'
|
import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSquare'
|
||||||
|
@ -38,6 +39,7 @@ export function setup() {
|
||||||
faBell,
|
faBell,
|
||||||
faComment,
|
faComment,
|
||||||
faHeart,
|
faHeart,
|
||||||
|
fasHeart,
|
||||||
faHouse,
|
faHouse,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faRetweet,
|
faRetweet,
|
||||||
|
|
|
@ -34,6 +34,9 @@ export const s = StyleSheet.create({
|
||||||
// colors
|
// colors
|
||||||
black: {color: 'black'},
|
black: {color: 'black'},
|
||||||
gray: {color: 'gray'},
|
gray: {color: 'gray'},
|
||||||
|
blue: {color: 'blue'},
|
||||||
|
green: {color: 'green'},
|
||||||
|
red: {color: 'red'},
|
||||||
|
|
||||||
// margins
|
// margins
|
||||||
mr2: {marginRight: 2},
|
mr2: {marginRight: 2},
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
Paul's todo list
|
Paul's todo list
|
||||||
|
|
||||||
- Posts in Thread and Feed view
|
- Posts in Thread and Feed view
|
||||||
- Like btn
|
|
||||||
- Repost btn
|
|
||||||
- Share btn
|
- Share btn
|
||||||
- Thread view
|
- Thread view
|
||||||
- View likes list
|
- View likes list
|
||||||
|
|
|
@ -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#19dc93e569fa71ae3de85876b3707afd47a6fe8c":
|
"@adxp/mock-api@git+ssh://git@github.com:bluesky-social/adx-mock-api.git#0159e865560c12fb7004862c7d9d48420ed93878":
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#19dc93e569fa71ae3de85876b3707afd47a6fe8c"
|
resolved "git+ssh://git@github.com:bluesky-social/adx-mock-api.git#0159e865560c12fb7004862c7d9d48420ed93878"
|
||||||
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