about summary refs log tree commit diff
path: root/src/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/view')
-rw-r--r--src/view/com/profile/ProfileCard.tsx59
-rw-r--r--src/view/com/profile/ProfileHeader.tsx9
-rw-r--r--src/view/com/profile/ProfileMembers.tsx69
-rw-r--r--src/view/com/util/Selector.tsx2
-rw-r--r--src/view/routes.ts2
-rw-r--r--src/view/screens/Profile.tsx105
-rw-r--r--src/view/screens/ProfileMembers.tsx24
7 files changed, 232 insertions, 38 deletions
diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx
new file mode 100644
index 000000000..cb58aec3f
--- /dev/null
+++ b/src/view/com/profile/ProfileCard.tsx
@@ -0,0 +1,59 @@
+import React from 'react'
+import {StyleSheet, Text, View} from 'react-native'
+import {Link} from '../util/Link'
+import {UserAvatar} from '../util/UserAvatar'
+import {s, colors} from '../../lib/styles'
+
+export function ProfileCard({
+  did,
+  handle,
+  displayName,
+  description,
+}: {
+  did: string
+  handle: string
+  displayName?: string
+  description?: string
+}) {
+  return (
+    <Link style={styles.outer} href={`/profile/${handle}`} title={handle}>
+      <View style={styles.layout}>
+        <View style={styles.layoutAvi}>
+          <UserAvatar size={40} displayName={displayName} handle={handle} />
+        </View>
+        <View style={styles.layoutContent}>
+          <Text style={[s.f16, s.bold]}>{displayName || handle}</Text>
+          <Text style={[s.f15, s.gray5]}>@{handle}</Text>
+        </View>
+      </View>
+    </Link>
+  )
+}
+
+const styles = StyleSheet.create({
+  outer: {
+    marginTop: 1,
+    backgroundColor: colors.white,
+  },
+  layout: {
+    flexDirection: 'row',
+  },
+  layoutAvi: {
+    width: 60,
+    paddingLeft: 10,
+    paddingTop: 10,
+    paddingBottom: 10,
+  },
+  avi: {
+    width: 40,
+    height: 40,
+    borderRadius: 20,
+    resizeMode: 'cover',
+  },
+  layoutContent: {
+    flex: 1,
+    paddingRight: 10,
+    paddingTop: 12,
+    paddingBottom: 10,
+  },
+})
diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx
index 984190283..d1dcd0525 100644
--- a/src/view/com/profile/ProfileHeader.tsx
+++ b/src/view/com/profile/ProfileHeader.tsx
@@ -57,6 +57,9 @@ export const ProfileHeader = observer(function ProfileHeader({
   const onPressFollows = () => {
     store.nav.navigate(`/profile/${view.handle}/follows`)
   }
+  const onPressMembers = () => {
+    store.nav.navigate(`/profile/${view.handle}/members`)
+  }
 
   // loading
   // =
@@ -173,12 +176,12 @@ export const ProfileHeader = observer(function ProfileHeader({
           {view.isScene ? (
             <TouchableOpacity
               style={[s.flexRow, s.mr10]}
-              onPress={onPressFollows}>
+              onPress={onPressMembers}>
               <Text style={[s.bold, s.mr2, styles.metricsText]}>
-                {view.followsCount}
+                {view.membersCount}
               </Text>
               <Text style={[s.gray5, styles.metricsText]}>
-                {pluralize(view.followsCount, 'member')}
+                {pluralize(view.membersCount, 'member')}
               </Text>
             </TouchableOpacity>
           ) : undefined}
diff --git a/src/view/com/profile/ProfileMembers.tsx b/src/view/com/profile/ProfileMembers.tsx
new file mode 100644
index 000000000..11db02054
--- /dev/null
+++ b/src/view/com/profile/ProfileMembers.tsx
@@ -0,0 +1,69 @@
+import React, {useState, useEffect} from 'react'
+import {observer} from 'mobx-react-lite'
+import {ActivityIndicator, FlatList, Text, View} from 'react-native'
+import {MembersViewModel, MemberItem} from '../../../state/models/members-view'
+import {ProfileCard} from './ProfileCard'
+import {useStores} from '../../../state'
+
+export const ProfileMembers = observer(function ProfileMembers({
+  name,
+}: {
+  name: string
+}) {
+  const store = useStores()
+  const [view, setView] = useState<MembersViewModel | undefined>()
+
+  useEffect(() => {
+    if (view?.params.actor === name) {
+      console.log('Members doing nothing')
+      return // no change needed? or trigger refresh?
+    }
+    console.log('Fetching members', name)
+    const newView = new MembersViewModel(store, {actor: name})
+    setView(newView)
+    newView.setup().catch(err => console.error('Failed to fetch members', err))
+  }, [name, view?.params.actor, store])
+
+  // loading
+  // =
+  if (
+    !view ||
+    (view.isLoading && !view.isRefreshing) ||
+    view.params.actor !== name
+  ) {
+    return (
+      <View>
+        <ActivityIndicator />
+      </View>
+    )
+  }
+
+  // error
+  // =
+  if (view.hasError) {
+    return (
+      <View>
+        <Text>{view.error}</Text>
+      </View>
+    )
+  }
+
+  // loaded
+  // =
+  const renderItem = ({item}: {item: MemberItem}) => (
+    <ProfileCard
+      did={item.did}
+      handle={item.handle}
+      displayName={item.displayName}
+    />
+  )
+  return (
+    <View>
+      <FlatList
+        data={view.members}
+        keyExtractor={item => item._reactKey}
+        renderItem={renderItem}
+      />
+    </View>
+  )
+})
diff --git a/src/view/com/util/Selector.tsx b/src/view/com/util/Selector.tsx
index e68310682..06e8cda80 100644
--- a/src/view/com/util/Selector.tsx
+++ b/src/view/com/util/Selector.tsx
@@ -41,7 +41,7 @@ export function Selector({
       width: middle.width,
     }
     return [left, middle, right]
-  }, [selectedIndex, itemLayouts])
+  }, [selectedIndex, items, itemLayouts])
 
   const interp = swipeGestureInterp || DEFAULT_SWIPE_GESTURE_INTERP
   const underlinePos = useAnimatedStyle(() => {
diff --git a/src/view/routes.ts b/src/view/routes.ts
index a72afe592..a1f8ab289 100644
--- a/src/view/routes.ts
+++ b/src/view/routes.ts
@@ -13,6 +13,7 @@ import {PostRepostedBy} from './screens/PostRepostedBy'
 import {Profile} from './screens/Profile'
 import {ProfileFollowers} from './screens/ProfileFollowers'
 import {ProfileFollows} from './screens/ProfileFollows'
+import {ProfileMembers} from './screens/ProfileMembers'
 import {Settings} from './screens/Settings'
 
 export type ScreenParams = {
@@ -37,6 +38,7 @@ export const routes: Route[] = [
   [Profile, ['far', 'user'], r('/profile/(?<name>[^/]+)')],
   [ProfileFollowers, 'users', r('/profile/(?<name>[^/]+)/followers')],
   [ProfileFollows, 'users', r('/profile/(?<name>[^/]+)/follows')],
+  [ProfileMembers, 'users', r('/profile/(?<name>[^/]+)/members')],
   [
     PostThread,
     ['far', 'message'],
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 6f7281bd9..fce77aac3 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -7,6 +7,7 @@ import {ProfileUiModel, Sections} from '../../state/models/profile-ui'
 import {useStores} from '../../state'
 import {ProfileHeader} from '../com/profile/ProfileHeader'
 import {FeedItem} from '../com/posts/FeedItem'
+import {ProfileCard} from '../com/profile/ProfileCard'
 import {ErrorScreen} from '../com/util/ErrorScreen'
 import {ErrorMessage} from '../com/util/ErrorMessage'
 import {s, colors} from '../lib/styles'
@@ -76,44 +77,78 @@ export const Profile = observer(({visible, params}: ScreenParams) => {
   let renderItem
   let items: any[] = []
   if (uiState) {
-    if (
-      uiState.selectedView === Sections.Posts ||
-      uiState.selectedView === Sections.Trending
-    ) {
-      if (uiState.isInitialLoading) {
-        items.push(LOADING_ITEM)
-        renderItem = () => <Text style={styles.loading}>Loading...</Text>
-      } else if (uiState.feed.hasError) {
-        items.push({
-          _reactKey: '__error__',
-          error: uiState.feed.error,
-        })
-        renderItem = (item: any) => (
-          <View style={s.p5}>
-            <ErrorMessage
-              message={item.error}
-              onPressTryAgain={onPressTryAgain}
-            />
-          </View>
-        )
-      } else if (uiState.currentView.hasContent) {
-        items = uiState.feed.feed.slice()
-        if (uiState.feed.hasReachedEnd) {
-          items.push(END_ITEM)
+    if (uiState.isInitialLoading) {
+      items.push(LOADING_ITEM)
+      renderItem = () => <Text style={styles.loading}>Loading...</Text>
+    } else if (uiState.currentView.hasError) {
+      items.push({
+        _reactKey: '__error__',
+        error: uiState.currentView.error,
+      })
+      renderItem = (item: any) => (
+        <View style={s.p5}>
+          <ErrorMessage
+            message={item.error}
+            onPressTryAgain={onPressTryAgain}
+          />
+        </View>
+      )
+    } else {
+      if (
+        uiState.selectedView === Sections.Posts ||
+        uiState.selectedView === Sections.Trending
+      ) {
+        if (uiState.feed.hasContent) {
+          items = uiState.feed.feed.slice()
+          if (uiState.feed.hasReachedEnd) {
+            items.push(END_ITEM)
+          }
+          renderItem = (item: any) => {
+            if (item === END_ITEM) {
+              return <Text style={styles.endItem}>- end of feed -</Text>
+            }
+            return <FeedItem item={item} />
+          }
+        } else if (uiState.feed.isEmpty) {
+          items.push(EMPTY_ITEM)
+          renderItem = () => <Text style={styles.loading}>No posts yet!</Text>
         }
-        renderItem = (item: any) => {
-          if (item === END_ITEM) {
-            return <Text style={styles.endItem}>- end of feed -</Text>
+      } else if (uiState.selectedView === Sections.Scenes) {
+        if (uiState.memberships.hasContent) {
+          items = uiState.memberships.memberships.slice()
+          renderItem = (item: any) => {
+            return (
+              <ProfileCard
+                did={item.did}
+                handle={item.handle}
+                displayName={item.displayName}
+              />
+            )
           }
-          return <FeedItem item={item} />
+        } else if (uiState.memberships.isEmpty) {
+          items.push(EMPTY_ITEM)
+          renderItem = () => <Text style={styles.loading}>No scenes yet!</Text>
         }
-      } else if (uiState.currentView.isEmpty) {
+      } else if (uiState.selectedView === Sections.Members) {
+        if (uiState.members.hasContent) {
+          items = uiState.members.members.slice()
+          renderItem = (item: any) => {
+            return (
+              <ProfileCard
+                did={item.did}
+                handle={item.handle}
+                displayName={item.displayName}
+              />
+            )
+          }
+        } else if (uiState.members.isEmpty) {
+          items.push(EMPTY_ITEM)
+          renderItem = () => <Text style={styles.loading}>No members yet!</Text>
+        }
+      } else {
         items.push(EMPTY_ITEM)
-        renderItem = () => <Text style={styles.loading}>No posts yet!</Text>
+        renderItem = () => <Text>TODO</Text>
       }
-    } else {
-      items.push(EMPTY_ITEM)
-      renderItem = () => <Text>TODO</Text>
     }
   }
   if (!renderItem) {
@@ -129,7 +164,7 @@ export const Profile = observer(({visible, params}: ScreenParams) => {
           details={uiState.profile.error}
           onPressTryAgain={onPressTryAgain}
         />
-      ) : (
+      ) : uiState.profile.hasLoaded ? (
         <ViewSelector
           sections={uiState.selectorItems}
           items={items}
@@ -140,6 +175,8 @@ export const Profile = observer(({visible, params}: ScreenParams) => {
           onRefresh={onRefresh}
           onEndReached={onEndReached}
         />
+      ) : (
+        renderHeader()
       )}
     </View>
   )
diff --git a/src/view/screens/ProfileMembers.tsx b/src/view/screens/ProfileMembers.tsx
new file mode 100644
index 000000000..dd2221091
--- /dev/null
+++ b/src/view/screens/ProfileMembers.tsx
@@ -0,0 +1,24 @@
+import React, {useEffect} from 'react'
+import {View} from 'react-native'
+import {ViewHeader} from '../com/util/ViewHeader'
+import {ProfileMembers as ProfileMembersComponent} from '../com/profile/ProfileMembers'
+import {ScreenParams} from '../routes'
+import {useStores} from '../../state'
+
+export const ProfileMembers = ({visible, params}: ScreenParams) => {
+  const store = useStores()
+  const {name} = params
+
+  useEffect(() => {
+    if (visible) {
+      store.nav.setTitle(`Members of ${name}`)
+    }
+  }, [store, visible, name])
+
+  return (
+    <View>
+      <ViewHeader title="Members" subtitle={`of ${name}`} />
+      <ProfileMembersComponent name={name} />
+    </View>
+  )
+}