From 1504d144d9bbb90e4e048702f1e1b3db9d7ce17e Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 26 Jul 2022 10:29:59 -0500 Subject: [PATCH] Implement follow/unfollow --- src/state/lib/api.ts | 29 +++++++++++++++++++++- src/state/models/profile-view.ts | 24 ++++++++++++++++++- src/view/com/composer/Composer.tsx | 1 - src/view/com/profile/ProfileHeader.tsx | 33 +++++++++++++++++++++++++- todos.txt | 2 -- 5 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/state/lib/api.ts b/src/state/lib/api.ts index df004799..64a88cde 100644 --- a/src/state/lib/api.ts +++ b/src/state/lib/api.ts @@ -97,6 +97,33 @@ export async function unrepost(adx: AdxClient, user: string, uri: string) { 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 async function deleteWhere( coll: AdxRepoCollectionClient, @@ -104,7 +131,7 @@ async function deleteWhere( cond: WherePred, ) { const toDelete: string[] = [] - iterateAll(coll, schema, record => { + await iterateAll(coll, schema, record => { if (cond(record)) { toDelete.push(record.key) } diff --git a/src/state/models/profile-view.ts b/src/state/models/profile-view.ts index bca4c615..b245335f 100644 --- a/src/state/models/profile-view.ts +++ b/src/state/models/profile-view.ts @@ -1,6 +1,7 @@ -import {makeAutoObservable} from 'mobx' +import {makeAutoObservable, runInAction} from 'mobx' import {bsky} from '@adxp/mock-api' import {RootStoreModel} from './root-store' +import * as apilib from '../lib/api' export class ProfileViewMyStateModel { hasFollowed: boolean = false @@ -67,6 +68,27 @@ export class ProfileViewModel implements bsky.ProfileView.Response { 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 // = diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 57dc0ef8..6a15599d 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -1,5 +1,4 @@ import React, {useState, forwardRef, useImperativeHandle} from 'react' -import {observer} from 'mobx-react-lite' import {KeyboardAvoidingView, StyleSheet, TextInput, View} from 'react-native' import Toast from '../util/Toast' import ProgressCircle from '../util/ProgressCircle' diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index 50bc2bd1..1eace94a 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -1,12 +1,20 @@ import React, {useState, useEffect} from 'react' 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 {ProfileViewModel} from '../../../state/models/profile-view' import {useStores} from '../../../state' import {pluralize} from '../../lib/strings' import {s} from '../../lib/styles' import {AVIS} from '../../lib/assets' +import Toast from '../util/Toast' export const ProfileHeader = observer(function ProfileHeader({ user, @@ -29,6 +37,23 @@ export const ProfileHeader = observer(function ProfileHeader({ newView.setup().catch(err => console.error('Failed to fetch profile', err)) }, [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 // = if ( @@ -81,6 +106,12 @@ export const ProfileHeader = observer(function ProfileHeader({ {pluralize(view.postsCount, 'post')} + +