Implement follow/unfollow

zio/stable
Paul Frazee 2022-07-26 10:29:59 -05:00
parent adc25ce468
commit 1504d144d9
5 changed files with 83 additions and 6 deletions

View File

@ -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)
} }

View File

@ -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
// = // =

View File

@ -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'

View File

@ -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>
) )
}) })

View File

@ -1,8 +1,6 @@
Paul's todo list Paul's todo list
- Profile view - Profile view
- Follow / Unfollow
- Badges
- Followers list - Followers list
- Follows list - Follows list
- Composer - Composer