diff options
author | Paul Frazee <pfrazee@gmail.com> | 2022-07-26 10:29:59 -0500 |
---|---|---|
committer | Paul Frazee <pfrazee@gmail.com> | 2022-07-26 10:29:59 -0500 |
commit | 1504d144d9bbb90e4e048702f1e1b3db9d7ce17e (patch) | |
tree | 26e07d3dcfb4881b25edba434d3f4db6e72277ba | |
parent | adc25ce468666bf38892cc8251165a6fde37a2e1 (diff) | |
download | voidsky-1504d144d9bbb90e4e048702f1e1b3db9d7ce17e.tar.zst |
Implement follow/unfollow
-rw-r--r-- | src/state/lib/api.ts | 29 | ||||
-rw-r--r-- | src/state/models/profile-view.ts | 24 | ||||
-rw-r--r-- | src/view/com/composer/Composer.tsx | 1 | ||||
-rw-r--r-- | src/view/com/profile/ProfileHeader.tsx | 33 | ||||
-rw-r--r-- | 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 df0047991..64a88cdec 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 bca4c6158..b245335f1 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 57dc0ef86..6a15599d8 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 50bc2bd19..1eace94aa 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({ <Text style={s.gray}>{pluralize(view.postsCount, 'post')}</Text> </View> </View> + <View> + <Button + title={view.myState.hasFollowed ? 'Unfollow' : 'Follow'} + onPress={onPressToggleFollow} + /> + </View> </View> ) }) diff --git a/todos.txt b/todos.txt index d0c51e1a8..29e365ba0 100644 --- a/todos.txt +++ b/todos.txt @@ -1,8 +1,6 @@ Paul's todo list - Profile view - - Follow / Unfollow - - Badges - Followers list - Follows list - Composer |