about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/state/lib/api.ts29
-rw-r--r--src/state/models/profile-view.ts24
-rw-r--r--src/view/com/composer/Composer.tsx1
-rw-r--r--src/view/com/profile/ProfileHeader.tsx33
-rw-r--r--todos.txt2
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