about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2022-11-07 16:24:06 -0600
committerPaul Frazee <pfrazee@gmail.com>2022-11-07 16:24:06 -0600
commite650d98924051abfee40ff956f7348e2e47e7cd7 (patch)
tree85f22717863eebe8942f6b66baf17b95dcbb9b90 /src
parentd228a5f4f5d118f30129f3bafd676bfe0e80bf38 (diff)
downloadvoidsky-e650d98924051abfee40ff956f7348e2e47e7cd7.tar.zst
Add search view; factor out SuggestedFollows component; add suggested follows to search
Diffstat (limited to 'src')
-rw-r--r--src/state/models/onboard.ts2
-rw-r--r--src/view/com/discover/SuggestedFollows.tsx177
-rw-r--r--src/view/com/onboard/Follows.tsx160
-rw-r--r--src/view/lib/icons.tsx27
-rw-r--r--src/view/screens/Search.tsx56
-rw-r--r--src/view/shell/mobile/index.tsx17
6 files changed, 278 insertions, 161 deletions
diff --git a/src/state/models/onboard.ts b/src/state/models/onboard.ts
index 77a066332..20e6ecda9 100644
--- a/src/state/models/onboard.ts
+++ b/src/state/models/onboard.ts
@@ -9,7 +9,7 @@ export const OnboardStage = {
 export const OnboardStageOrder = [OnboardStage.Explainers, OnboardStage.Follows]
 
 export class OnboardModel {
-  isOnboarding: boolean = true
+  isOnboarding: boolean = false
   stage: string = OnboardStageOrder[0]
 
   constructor() {
diff --git a/src/view/com/discover/SuggestedFollows.tsx b/src/view/com/discover/SuggestedFollows.tsx
new file mode 100644
index 000000000..e73d9a7db
--- /dev/null
+++ b/src/view/com/discover/SuggestedFollows.tsx
@@ -0,0 +1,177 @@
+import React, {useMemo, useEffect} from 'react'
+import {
+  ActivityIndicator,
+  FlatList,
+  StyleSheet,
+  Text,
+  TouchableOpacity,
+  View,
+} from 'react-native'
+import LinearGradient from 'react-native-linear-gradient'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {observer} from 'mobx-react-lite'
+import {ErrorScreen} from '../util/ErrorScreen'
+import {UserAvatar} from '../util/UserAvatar'
+import {useStores} from '../../../state'
+import {
+  SuggestedActorsViewModel,
+  SuggestedActor,
+} from '../../../state/models/suggested-actors-view'
+import {s, colors, gradients} from '../../lib/styles'
+
+export const SuggestedFollows = observer(
+  ({onNoSuggestions}: {onNoSuggestions?: () => void}) => {
+    const store = useStores()
+
+    const view = useMemo<SuggestedActorsViewModel>(
+      () => new SuggestedActorsViewModel(store),
+      [],
+    )
+
+    useEffect(() => {
+      console.log('Fetching suggested actors')
+      view
+        .setup()
+        .catch((err: any) => console.error('Failed to fetch suggestions', err))
+    }, [view])
+
+    useEffect(() => {
+      if (!view.isLoading && !view.hasError && !view.hasContent) {
+        onNoSuggestions?.()
+      }
+    }, [view, view.isLoading, view.hasError, view.hasContent])
+
+    const onPressTryAgain = () =>
+      view
+        .setup()
+        .catch((err: any) => console.error('Failed to fetch suggestions', err))
+
+    const renderItem = ({item}: {item: SuggestedActor}) => <User item={item} />
+    return (
+      <View style={styles.container}>
+        {view.isLoading ? (
+          <View>
+            <ActivityIndicator />
+          </View>
+        ) : view.hasError ? (
+          <ErrorScreen
+            title="Failed to load suggestions"
+            message="There was an error while trying to load suggested follows."
+            details={view.error}
+            onPressTryAgain={onPressTryAgain}
+          />
+        ) : (
+          <View style={styles.suggestionsContainer}>
+            <FlatList
+              data={view.suggestions}
+              keyExtractor={item => item._reactKey}
+              renderItem={renderItem}
+              style={s.flex1}
+            />
+          </View>
+        )}
+      </View>
+    )
+  },
+)
+
+const User = ({item}: {item: SuggestedActor}) => {
+  return (
+    <View style={styles.actor}>
+      <View style={styles.actorMeta}>
+        <View style={styles.actorAvi}>
+          <UserAvatar
+            size={40}
+            displayName={item.displayName}
+            handle={item.handle}
+          />
+        </View>
+        <View style={styles.actorContent}>
+          <Text style={[s.f17, s.bold]} numberOfLines={1}>
+            {item.displayName}
+          </Text>
+          <Text style={[s.f14, s.gray5]} numberOfLines={1}>
+            @{item.handle}
+          </Text>
+        </View>
+        <View style={styles.actorBtn}>
+          <TouchableOpacity>
+            <LinearGradient
+              colors={[gradients.primary.start, gradients.primary.end]}
+              start={{x: 0, y: 0}}
+              end={{x: 1, y: 1}}
+              style={[styles.btn, styles.gradientBtn]}>
+              <FontAwesomeIcon icon="plus" style={[s.white, s.mr5]} size={15} />
+              <Text style={[s.white, s.fw600, s.f15]}>Follow</Text>
+            </LinearGradient>
+          </TouchableOpacity>
+        </View>
+      </View>
+      {item.description ? (
+        <View style={styles.actorDetails}>
+          <Text style={[s.f15]} numberOfLines={4}>
+            {item.description}
+          </Text>
+        </View>
+      ) : undefined}
+    </View>
+  )
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+  },
+
+  suggestionsContainer: {
+    flex: 1,
+    backgroundColor: colors.gray1,
+  },
+
+  actor: {
+    backgroundColor: colors.white,
+    borderRadius: 6,
+    margin: 2,
+    marginBottom: 0,
+  },
+  actorMeta: {
+    flexDirection: 'row',
+  },
+  actorAvi: {
+    width: 60,
+    paddingLeft: 10,
+    paddingTop: 10,
+    paddingBottom: 10,
+  },
+  actorContent: {
+    flex: 1,
+    paddingRight: 10,
+    paddingTop: 10,
+  },
+  actorBtn: {
+    paddingRight: 10,
+    paddingTop: 10,
+  },
+  actorDetails: {
+    paddingLeft: 60,
+    paddingRight: 10,
+    paddingBottom: 10,
+  },
+
+  gradientBtn: {
+    paddingHorizontal: 24,
+    paddingVertical: 6,
+  },
+  secondaryBtn: {
+    paddingHorizontal: 14,
+  },
+  btn: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'center',
+    paddingVertical: 7,
+    borderRadius: 50,
+    backgroundColor: colors.gray1,
+    marginLeft: 6,
+  },
+})
diff --git a/src/view/com/onboard/Follows.tsx b/src/view/com/onboard/Follows.tsx
index c48531522..ab65cd45d 100644
--- a/src/view/com/onboard/Follows.tsx
+++ b/src/view/com/onboard/Follows.tsx
@@ -1,78 +1,29 @@
-import React, {useMemo, useEffect} from 'react'
+import React from 'react'
 import {
-  ActivityIndicator,
-  FlatList,
   SafeAreaView,
   StyleSheet,
   Text,
   TouchableOpacity,
   View,
 } from 'react-native'
-import LinearGradient from 'react-native-linear-gradient'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {observer} from 'mobx-react-lite'
-import {ErrorScreen} from '../util/ErrorScreen'
-import {UserAvatar} from '../util/UserAvatar'
+import {SuggestedFollows} from '../discover/SuggestedFollows'
 import {useStores} from '../../../state'
-import {
-  SuggestedActorsViewModel,
-  SuggestedActor,
-} from '../../../state/models/suggested-actors-view'
-import {s, colors, gradients} from '../../lib/styles'
+import {s} from '../../lib/styles'
 
 export const Follows = observer(() => {
   const store = useStores()
 
-  const view = useMemo<SuggestedActorsViewModel>(
-    () => new SuggestedActorsViewModel(store),
-    [],
-  )
-
-  useEffect(() => {
-    console.log('Fetching suggested actors')
-    view
-      .setup()
-      .catch((err: any) => console.error('Failed to fetch suggestions', err))
-  }, [view])
-
-  useEffect(() => {
-    if (!view.isLoading && !view.hasError && !view.hasContent) {
-      // no suggestions, bounce from this view
-      store.onboard.next()
-    }
-  }, [view, view.isLoading, view.hasError, view.hasContent])
-
-  const onPressTryAgain = () =>
-    view
-      .setup()
-      .catch((err: any) => console.error('Failed to fetch suggestions', err))
+  const onNoSuggestions = () => {
+    // no suggestions, bounce from this view
+    store.onboard.next()
+  }
   const onPressNext = () => store.onboard.next()
 
-  const renderItem = ({item}: {item: SuggestedActor}) => <User item={item} />
   return (
     <SafeAreaView style={styles.container}>
       <Text style={styles.title}>Suggested follows</Text>
-      {view.isLoading ? (
-        <View>
-          <ActivityIndicator />
-        </View>
-      ) : view.hasError ? (
-        <ErrorScreen
-          title="Failed to load suggestions"
-          message="There was an error while trying to load suggested follows."
-          details={view.error}
-          onPressTryAgain={onPressTryAgain}
-        />
-      ) : (
-        <View style={styles.suggestionsContainer}>
-          <FlatList
-            data={view.suggestions}
-            keyExtractor={item => item._reactKey}
-            renderItem={renderItem}
-            style={s.flex1}
-          />
-        </View>
-      )}
+      <SuggestedFollows onNoSuggestions={onNoSuggestions} />
       <View style={styles.footer}>
         <TouchableOpacity onPress={onPressNext}>
           <Text style={[s.blue3, s.f18]}>Skip</Text>
@@ -86,49 +37,6 @@ export const Follows = observer(() => {
   )
 })
 
-const User = ({item}: {item: SuggestedActor}) => {
-  return (
-    <View style={styles.actor}>
-      <View style={styles.actorMeta}>
-        <View style={styles.actorAvi}>
-          <UserAvatar
-            size={40}
-            displayName={item.displayName}
-            handle={item.handle}
-          />
-        </View>
-        <View style={styles.actorContent}>
-          <Text style={[s.f17, s.bold]} numberOfLines={1}>
-            {item.displayName}
-          </Text>
-          <Text style={[s.f14, s.gray5]} numberOfLines={1}>
-            @{item.handle}
-          </Text>
-        </View>
-        <View style={styles.actorBtn}>
-          <TouchableOpacity>
-            <LinearGradient
-              colors={[gradients.primary.start, gradients.primary.end]}
-              start={{x: 0, y: 0}}
-              end={{x: 1, y: 1}}
-              style={[styles.btn, styles.gradientBtn]}>
-              <FontAwesomeIcon icon="plus" style={[s.white, s.mr5]} size={15} />
-              <Text style={[s.white, s.fw600, s.f15]}>Follow</Text>
-            </LinearGradient>
-          </TouchableOpacity>
-        </View>
-      </View>
-      {item.description ? (
-        <View style={styles.actorDetails}>
-          <Text style={[s.f15]} numberOfLines={4}>
-            {item.description}
-          </Text>
-        </View>
-      ) : undefined}
-    </View>
-  )
-}
-
 const styles = StyleSheet.create({
   container: {
     flex: 1,
@@ -141,58 +49,6 @@ const styles = StyleSheet.create({
     paddingBottom: 12,
   },
 
-  suggestionsContainer: {
-    flex: 1,
-    backgroundColor: colors.gray1,
-  },
-
-  actor: {
-    backgroundColor: colors.white,
-    borderRadius: 6,
-    margin: 2,
-    marginBottom: 0,
-  },
-  actorMeta: {
-    flexDirection: 'row',
-  },
-  actorAvi: {
-    width: 60,
-    paddingLeft: 10,
-    paddingTop: 10,
-    paddingBottom: 10,
-  },
-  actorContent: {
-    flex: 1,
-    paddingRight: 10,
-    paddingTop: 10,
-  },
-  actorBtn: {
-    paddingRight: 10,
-    paddingTop: 10,
-  },
-  actorDetails: {
-    paddingLeft: 60,
-    paddingRight: 10,
-    paddingBottom: 10,
-  },
-
-  gradientBtn: {
-    paddingHorizontal: 24,
-    paddingVertical: 6,
-  },
-  secondaryBtn: {
-    paddingHorizontal: 14,
-  },
-  btn: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'center',
-    paddingVertical: 7,
-    borderRadius: 50,
-    backgroundColor: colors.gray1,
-    marginLeft: 6,
-  },
-
   footer: {
     flexDirection: 'row',
     paddingHorizontal: 32,
diff --git a/src/view/lib/icons.tsx b/src/view/lib/icons.tsx
index bf4d242f0..b42fc6fc8 100644
--- a/src/view/lib/icons.tsx
+++ b/src/view/lib/icons.tsx
@@ -53,6 +53,33 @@ export function HomeIcon({
   )
 }
 
+// Copyright (c) 2020 Refactoring UI Inc.
+// https://github.com/tailwindlabs/heroicons/blob/master/LICENSE
+export function MangifyingGlassIcon({
+  style,
+  size,
+}: {
+  style?: StyleProp<ViewStyle>
+  size?: string | number
+}) {
+  return (
+    <Svg
+      fill="none"
+      viewBox="0 0 24 24"
+      strokeWidth={2}
+      stroke="currentColor"
+      width={size || 24}
+      height={size || 24}
+      style={style}>
+      <Path
+        strokeLinecap="round"
+        strokeLinejoin="round"
+        d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
+      />
+    </Svg>
+  )
+}
+
 // https://github.com/Remix-Design/RemixIcon/blob/master/License
 export function BellIcon({
   style,
diff --git a/src/view/screens/Search.tsx b/src/view/screens/Search.tsx
index aea54051e..735326025 100644
--- a/src/view/screens/Search.tsx
+++ b/src/view/screens/Search.tsx
@@ -1,11 +1,57 @@
-import React from 'react'
-import {Text, View} from 'react-native'
+import React, {useEffect} from 'react'
+import {StyleSheet, Text, View} from 'react-native'
+import {ViewHeader} from '../com/util/ViewHeader'
+import {SuggestedFollows} from '../com/discover/SuggestedFollows'
 import {ScreenParams} from '../routes'
+import {useStores} from '../../state'
+import {colors} from '../lib/styles'
+
+export const Search = ({visible, params}: ScreenParams) => {
+  const store = useStores()
+  const {name} = params
+
+  useEffect(() => {
+    if (visible) {
+      store.nav.setTitle(`Search`)
+    }
+  }, [store, visible, name])
 
-export const Search = ({params}: ScreenParams) => {
   return (
-    <View style={{justifyContent: 'center', alignItems: 'center'}}>
-      <Text style={{fontSize: 20, fontWeight: 'bold'}}>Search</Text>
+    <View style={styles.container}>
+      <ViewHeader title="Search" />
+      <View style={styles.todoContainer}>
+        <Text style={styles.todoLabel}>
+          Search is still being implemented. Check back soon!
+        </Text>
+      </View>
+      <Text style={styles.heading}>Suggested follows</Text>
+      <SuggestedFollows />
     </View>
   )
 }
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: colors.white,
+  },
+
+  todoContainer: {
+    backgroundColor: colors.pink1,
+    margin: 10,
+    padding: 10,
+    borderRadius: 6,
+  },
+  todoLabel: {
+    color: colors.pink5,
+    textAlign: 'center',
+  },
+
+  heading: {
+    fontSize: 16,
+    fontWeight: 'bold',
+    paddingTop: 12,
+    paddingBottom: 6,
+    paddingHorizontal: 12,
+  },
+})
diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx
index b359bdcb3..49b18a481 100644
--- a/src/view/shell/mobile/index.tsx
+++ b/src/view/shell/mobile/index.tsx
@@ -33,7 +33,12 @@ import {MainMenu} from './MainMenu'
 import {TabsSelector} from './TabsSelector'
 import {Composer} from './Composer'
 import {s, colors} from '../../lib/styles'
-import {GridIcon, HomeIcon, BellIcon} from '../../lib/icons'
+import {
+  GridIcon,
+  HomeIcon,
+  MangifyingGlassIcon,
+  BellIcon,
+} from '../../lib/icons'
 
 const SWIPE_GESTURE_DIST_TRIGGER = 0.5
 const SWIPE_GESTURE_VEL_TRIGGER = 2500
@@ -45,7 +50,7 @@ const Btn = ({
   onPress,
   onLongPress,
 }: {
-  icon: IconProp | 'menu' | 'house' | 'bell'
+  icon: IconProp | 'menu' | 'house' | 'bell' | 'search'
   inactive?: boolean
   notificationCount?: number
   onPress?: (event: GestureResponderEvent) => void
@@ -58,6 +63,11 @@ const Btn = ({
     IconEl = GridIcon
   } else if (icon === 'house') {
     IconEl = HomeIcon
+    size = 24
+  } else if (icon === 'search') {
+    IconEl = MangifyingGlassIcon
+    size = 24
+    addedStyles = {position: 'relative', top: -1} as ViewStyle
   } else if (icon === 'bell') {
     IconEl = BellIcon
     size = 24
@@ -114,6 +124,7 @@ export const MobileShell: React.FC = observer(() => {
       store.nav.navigate('/')
     }
   }
+  const onPressSearch = () => store.nav.navigate('/search')
   const onPressMenu = () => setMainMenuActive(true)
   const onPressNotifications = () => store.nav.navigate('/notifications')
   const onPressTabs = () => setTabsSelectorActive(true)
@@ -210,7 +221,7 @@ export const MobileShell: React.FC = observer(() => {
       </SafeAreaView>
       <View style={styles.bottomBar}>
         <Btn icon="house" onPress={onPressHome} />
-        <Btn icon="search" inactive={true} onPress={() => {} /* TODO */} />
+        <Btn icon="search" onPress={onPressSearch} />
         <Btn icon="menu" onPress={onPressMenu} />
         <Btn
           icon="bell"