Implement follow/unfollow
parent
adc25ce468
commit
1504d144d9
|
@ -97,6 +97,33 @@ export async function unrepost(adx: AdxClient, user: string, uri: string) {
|
||||||
return numDels > 0
|
return numDels > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function follow(
|
||||||
|
adx: AdxClient,
|
||||||
|
user: string,
|
||||||
|
subject: {did: string; name: string},
|
||||||
|
) {
|
||||||
|
return await adx
|
||||||
|
.repo(user, true)
|
||||||
|
.collection('blueskyweb.xyz:Follows')
|
||||||
|
.create('Follow', {
|
||||||
|
$type: 'blueskyweb.xyz:Follow',
|
||||||
|
subject,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unfollow(
|
||||||
|
adx: AdxClient,
|
||||||
|
user: string,
|
||||||
|
subject: {did: string},
|
||||||
|
) {
|
||||||
|
const coll = adx.repo(user, true).collection('blueskyweb.xyz:Follows')
|
||||||
|
const numDels = await deleteWhere(coll, 'Follow', record => {
|
||||||
|
return record.value.subject.did === subject.did
|
||||||
|
})
|
||||||
|
return numDels > 0
|
||||||
|
}
|
||||||
|
|
||||||
type WherePred = (_record: GetRecordResponseValidated) => Boolean
|
type WherePred = (_record: GetRecordResponseValidated) => Boolean
|
||||||
async function deleteWhere(
|
async function deleteWhere(
|
||||||
coll: AdxRepoCollectionClient,
|
coll: AdxRepoCollectionClient,
|
||||||
|
@ -104,7 +131,7 @@ async function deleteWhere(
|
||||||
cond: WherePred,
|
cond: WherePred,
|
||||||
) {
|
) {
|
||||||
const toDelete: string[] = []
|
const toDelete: string[] = []
|
||||||
iterateAll(coll, schema, record => {
|
await iterateAll(coll, schema, record => {
|
||||||
if (cond(record)) {
|
if (cond(record)) {
|
||||||
toDelete.push(record.key)
|
toDelete.push(record.key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {makeAutoObservable} from 'mobx'
|
import {makeAutoObservable, runInAction} from 'mobx'
|
||||||
import {bsky} from '@adxp/mock-api'
|
import {bsky} from '@adxp/mock-api'
|
||||||
import {RootStoreModel} from './root-store'
|
import {RootStoreModel} from './root-store'
|
||||||
|
import * as apilib from '../lib/api'
|
||||||
|
|
||||||
export class ProfileViewMyStateModel {
|
export class ProfileViewMyStateModel {
|
||||||
hasFollowed: boolean = false
|
hasFollowed: boolean = false
|
||||||
|
@ -67,6 +68,27 @@ export class ProfileViewModel implements bsky.ProfileView.Response {
|
||||||
await this._load()
|
await this._load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async toggleFollowing() {
|
||||||
|
if (this.myState.hasFollowed) {
|
||||||
|
await apilib.unfollow(this.rootStore.api, 'alice.com', {
|
||||||
|
did: this.did,
|
||||||
|
})
|
||||||
|
runInAction(() => {
|
||||||
|
this.followersCount--
|
||||||
|
this.myState.hasFollowed = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await apilib.follow(this.rootStore.api, 'alice.com', {
|
||||||
|
did: this.did,
|
||||||
|
name: this.name,
|
||||||
|
})
|
||||||
|
runInAction(() => {
|
||||||
|
this.followersCount++
|
||||||
|
this.myState.hasFollowed = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// state transitions
|
// state transitions
|
||||||
// =
|
// =
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React, {useState, forwardRef, useImperativeHandle} from 'react'
|
import React, {useState, forwardRef, useImperativeHandle} from 'react'
|
||||||
import {observer} from 'mobx-react-lite'
|
|
||||||
import {KeyboardAvoidingView, StyleSheet, TextInput, View} from 'react-native'
|
import {KeyboardAvoidingView, StyleSheet, TextInput, View} from 'react-native'
|
||||||
import Toast from '../util/Toast'
|
import Toast from '../util/Toast'
|
||||||
import ProgressCircle from '../util/ProgressCircle'
|
import ProgressCircle from '../util/ProgressCircle'
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
import React, {useState, useEffect} from 'react'
|
import React, {useState, useEffect} from 'react'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {ActivityIndicator, Image, StyleSheet, Text, View} from 'react-native'
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
Button,
|
||||||
|
Image,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View,
|
||||||
|
} from 'react-native'
|
||||||
import {OnNavigateContent} from '../../routes/types'
|
import {OnNavigateContent} from '../../routes/types'
|
||||||
import {ProfileViewModel} from '../../../state/models/profile-view'
|
import {ProfileViewModel} from '../../../state/models/profile-view'
|
||||||
import {useStores} from '../../../state'
|
import {useStores} from '../../../state'
|
||||||
import {pluralize} from '../../lib/strings'
|
import {pluralize} from '../../lib/strings'
|
||||||
import {s} from '../../lib/styles'
|
import {s} from '../../lib/styles'
|
||||||
import {AVIS} from '../../lib/assets'
|
import {AVIS} from '../../lib/assets'
|
||||||
|
import Toast from '../util/Toast'
|
||||||
|
|
||||||
export const ProfileHeader = observer(function ProfileHeader({
|
export const ProfileHeader = observer(function ProfileHeader({
|
||||||
user,
|
user,
|
||||||
|
@ -29,6 +37,23 @@ export const ProfileHeader = observer(function ProfileHeader({
|
||||||
newView.setup().catch(err => console.error('Failed to fetch profile', err))
|
newView.setup().catch(err => console.error('Failed to fetch profile', err))
|
||||||
}, [user, view?.params.user, store])
|
}, [user, view?.params.user, store])
|
||||||
|
|
||||||
|
const onPressToggleFollow = () => {
|
||||||
|
view?.toggleFollowing().then(
|
||||||
|
() => {
|
||||||
|
Toast.show(
|
||||||
|
`${view.myState.hasFollowed ? 'Following' : 'No longer following'} ${
|
||||||
|
view.displayName || view.name
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
duration: Toast.durations.LONG,
|
||||||
|
position: Toast.positions.TOP,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
err => console.error('Failed to toggle follow', err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// loading
|
// loading
|
||||||
// =
|
// =
|
||||||
if (
|
if (
|
||||||
|
@ -81,6 +106,12 @@ export const ProfileHeader = observer(function ProfileHeader({
|
||||||
<Text style={s.gray}>{pluralize(view.postsCount, 'post')}</Text>
|
<Text style={s.gray}>{pluralize(view.postsCount, 'post')}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
<View>
|
||||||
|
<Button
|
||||||
|
title={view.myState.hasFollowed ? 'Unfollow' : 'Follow'}
|
||||||
|
onPress={onPressToggleFollow}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue