about summary refs log tree commit diff
path: root/src/view/screens
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-04-18 18:29:54 -0500
committerGitHub <noreply@github.com>2023-04-18 18:29:54 -0500
commit75fd653be3a391d0edc11a9b35ed636c7cbe3b11 (patch)
tree38fc788652cac037df991be73d87eba263dc683f /src/view/screens
parent1ab8f315179cc1c93f1319b0e6fb1f75aa1c5954 (diff)
downloadvoidsky-75fd653be3a391d0edc11a9b35ed636c7cbe3b11.tar.zst
Rework search suggestions for performance (#492)
Diffstat (limited to 'src/view/screens')
-rw-r--r--src/view/screens/Search.tsx197
-rw-r--r--src/view/screens/Search.web.tsx59
-rw-r--r--src/view/screens/SearchMobile.tsx87
3 files changed, 40 insertions, 303 deletions
diff --git a/src/view/screens/Search.tsx b/src/view/screens/Search.tsx
index ed9effd0b..bf9857df4 100644
--- a/src/view/screens/Search.tsx
+++ b/src/view/screens/Search.tsx
@@ -1,196 +1 @@
-import React from 'react'
-import {
-  Keyboard,
-  RefreshControl,
-  StyleSheet,
-  TouchableWithoutFeedback,
-  View,
-} from 'react-native'
-import {useFocusEffect} from '@react-navigation/native'
-import {withAuthRequired} from 'view/com/auth/withAuthRequired'
-import {ScrollView} from 'view/com/util/Views'
-import {
-  NativeStackScreenProps,
-  SearchTabNavigatorParams,
-} from 'lib/routes/types'
-import {observer} from 'mobx-react-lite'
-import {Text} from 'view/com/util/text/Text'
-import {useStores} from 'state/index'
-import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete'
-import {SearchUIModel} from 'state/models/ui/search'
-import {FoafsModel} from 'state/models/discovery/foafs'
-import {SuggestedActorsModel} from 'state/models/discovery/suggested-actors'
-import {HeaderWithInput} from 'view/com/search/HeaderWithInput'
-import {Suggestions} from 'view/com/search/Suggestions'
-import {SearchResults} from 'view/com/search/SearchResults'
-import {s} from 'lib/styles'
-import {ProfileCard} from 'view/com/profile/ProfileCard'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
-
-type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'>
-export const SearchScreen = withAuthRequired(
-  observer<Props>(({}: Props) => {
-    const pal = usePalette('default')
-    const store = useStores()
-    const scrollElRef = React.useRef<ScrollView>(null)
-    const onMainScroll = useOnMainScroll(store)
-    const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false)
-    const [query, setQuery] = React.useState<string>('')
-    const autocompleteView = React.useMemo<UserAutocompleteModel>(
-      () => new UserAutocompleteModel(store),
-      [store],
-    )
-    const foafs = React.useMemo<FoafsModel>(
-      () => new FoafsModel(store),
-      [store],
-    )
-    const suggestedActors = React.useMemo<SuggestedActorsModel>(
-      () => new SuggestedActorsModel(store),
-      [store],
-    )
-    const [searchUIModel, setSearchUIModel] = React.useState<
-      SearchUIModel | undefined
-    >()
-    const [refreshing, setRefreshing] = React.useState(false)
-
-    const onSoftReset = () => {
-      scrollElRef.current?.scrollTo({x: 0, y: 0})
-    }
-
-    useFocusEffect(
-      React.useCallback(() => {
-        const softResetSub = store.onScreenSoftReset(onSoftReset)
-        const cleanup = () => {
-          softResetSub.remove()
-        }
-
-        store.shell.setMinimalShellMode(false)
-        autocompleteView.setup()
-        if (!foafs.hasData) {
-          foafs.fetch()
-        }
-        if (!suggestedActors.hasLoaded) {
-          suggestedActors.loadMore(true)
-        }
-
-        return cleanup
-      }, [store, autocompleteView, foafs, suggestedActors]),
-    )
-
-    const onChangeQuery = React.useCallback(
-      (text: string) => {
-        setQuery(text)
-        if (text.length > 0) {
-          autocompleteView.setActive(true)
-          autocompleteView.setPrefix(text)
-        } else {
-          autocompleteView.setActive(false)
-        }
-      },
-      [setQuery, autocompleteView],
-    )
-
-    const onPressClearQuery = React.useCallback(() => {
-      setQuery('')
-    }, [setQuery])
-
-    const onPressCancelSearch = React.useCallback(() => {
-      setQuery('')
-      autocompleteView.setActive(false)
-      setSearchUIModel(undefined)
-      store.shell.setIsDrawerSwipeDisabled(false)
-    }, [setQuery, autocompleteView, store])
-
-    const onSubmitQuery = React.useCallback(() => {
-      const model = new SearchUIModel(store)
-      model.fetch(query)
-      setSearchUIModel(model)
-      store.shell.setIsDrawerSwipeDisabled(true)
-    }, [query, setSearchUIModel, store])
-
-    const onRefresh = React.useCallback(async () => {
-      setRefreshing(true)
-      try {
-        await foafs.fetch()
-      } finally {
-        setRefreshing(false)
-      }
-    }, [foafs, setRefreshing])
-
-    return (
-      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
-        <View style={[pal.view, styles.container]}>
-          <HeaderWithInput
-            isInputFocused={isInputFocused}
-            query={query}
-            setIsInputFocused={setIsInputFocused}
-            onChangeQuery={onChangeQuery}
-            onPressClearQuery={onPressClearQuery}
-            onPressCancelSearch={onPressCancelSearch}
-            onSubmitQuery={onSubmitQuery}
-          />
-          {searchUIModel ? (
-            <SearchResults model={searchUIModel} />
-          ) : (
-            <ScrollView
-              ref={scrollElRef}
-              testID="searchScrollView"
-              style={pal.view}
-              onScroll={onMainScroll}
-              scrollEventThrottle={100}
-              refreshControl={
-                <RefreshControl
-                  refreshing={refreshing}
-                  onRefresh={onRefresh}
-                  tintColor={pal.colors.text}
-                  titleColor={pal.colors.text}
-                />
-              }>
-              {query && autocompleteView.searchRes.length ? (
-                <>
-                  {autocompleteView.searchRes.map(item => (
-                    <ProfileCard
-                      key={item.did}
-                      testID={`searchAutoCompleteResult-${item.handle}`}
-                      handle={item.handle}
-                      displayName={item.displayName}
-                      labels={item.labels}
-                      avatar={item.avatar}
-                    />
-                  ))}
-                </>
-              ) : query && !autocompleteView.searchRes.length ? (
-                <View>
-                  <Text style={[pal.textLight, styles.searchPrompt]}>
-                    No results found for {autocompleteView.prefix}
-                  </Text>
-                </View>
-              ) : isInputFocused ? (
-                <View>
-                  <Text style={[pal.textLight, styles.searchPrompt]}>
-                    Search for users on the network
-                  </Text>
-                </View>
-              ) : (
-                <Suggestions foafs={foafs} suggestedActors={suggestedActors} />
-              )}
-              <View style={s.footerSpacer} />
-            </ScrollView>
-          )}
-        </View>
-      </TouchableWithoutFeedback>
-    )
-  }),
-)
-
-const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-  },
-
-  searchPrompt: {
-    textAlign: 'center',
-    paddingTop: 10,
-  },
-})
+export * from './SearchMobile'
diff --git a/src/view/screens/Search.web.tsx b/src/view/screens/Search.web.tsx
index 92df1d920..62f3fb900 100644
--- a/src/view/screens/Search.web.tsx
+++ b/src/view/screens/Search.web.tsx
@@ -1,10 +1,8 @@
 import React from 'react'
-import {StyleSheet, View} from 'react-native'
 import {SearchUIModel} from 'state/models/ui/search'
 import {FoafsModel} from 'state/models/discovery/foafs'
 import {SuggestedActorsModel} from 'state/models/discovery/suggested-actors'
 import {withAuthRequired} from 'view/com/auth/withAuthRequired'
-import {ScrollView} from 'view/com/util/Views'
 import {Suggestions} from 'view/com/search/Suggestions'
 import {SearchResults} from 'view/com/search/SearchResults'
 import {observer} from 'mobx-react-lite'
@@ -13,15 +11,12 @@ import {
   SearchTabNavigatorParams,
 } from 'lib/routes/types'
 import {useStores} from 'state/index'
-import {s} from 'lib/styles'
-import {usePalette} from 'lib/hooks/usePalette'
 import * as Mobile from './SearchMobile'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 
 type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'>
 export const SearchScreen = withAuthRequired(
   observer(({navigation, route}: Props) => {
-    const pal = usePalette('default')
     const store = useStores()
     const params = route.params || {}
     const foafs = React.useMemo<FoafsModel>(
@@ -59,58 +54,6 @@ export const SearchScreen = withAuthRequired(
       return <Mobile.SearchScreen navigation={navigation} route={route} />
     }
 
-    return (
-      <ScrollView
-        testID="searchScrollView"
-        style={[pal.view, styles.container]}
-        scrollEventThrottle={100}>
-        <Suggestions foafs={foafs} suggestedActors={suggestedActors} />
-        <View style={s.footerSpacer} />
-      </ScrollView>
-    )
+    return <Suggestions foafs={foafs} suggestedActors={suggestedActors} />
   }),
 )
-
-const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-  },
-
-  header: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    paddingHorizontal: 12,
-    paddingTop: 4,
-    marginBottom: 14,
-  },
-  headerMenuBtn: {
-    width: 40,
-    height: 30,
-    marginLeft: 6,
-  },
-  headerSearchContainer: {
-    flex: 1,
-    flexDirection: 'row',
-    alignItems: 'center',
-    borderRadius: 30,
-    paddingHorizontal: 12,
-    paddingVertical: 8,
-  },
-  headerSearchIcon: {
-    marginRight: 6,
-    alignSelf: 'center',
-  },
-  headerSearchInput: {
-    flex: 1,
-    fontSize: 17,
-  },
-  headerCancelBtn: {
-    width: 60,
-    paddingLeft: 10,
-  },
-
-  searchPrompt: {
-    textAlign: 'center',
-    paddingTop: 10,
-  },
-})
diff --git a/src/view/screens/SearchMobile.tsx b/src/view/screens/SearchMobile.tsx
index e1fb3ec0a..679fb07c2 100644
--- a/src/view/screens/SearchMobile.tsx
+++ b/src/view/screens/SearchMobile.tsx
@@ -1,14 +1,13 @@
 import React from 'react'
 import {
   Keyboard,
-  RefreshControl,
   StyleSheet,
   TouchableWithoutFeedback,
   View,
 } from 'react-native'
 import {useFocusEffect} from '@react-navigation/native'
 import {withAuthRequired} from 'view/com/auth/withAuthRequired'
-import {ScrollView} from 'view/com/util/Views'
+import {FlatList, ScrollView} from 'view/com/util/Views'
 import {
   NativeStackScreenProps,
   SearchTabNavigatorParams,
@@ -33,7 +32,8 @@ export const SearchScreen = withAuthRequired(
   observer<Props>(({}: Props) => {
     const pal = usePalette('default')
     const store = useStores()
-    const scrollElRef = React.useRef<ScrollView>(null)
+    const scrollViewRef = React.useRef<ScrollView>(null)
+    const flatListRef = React.useRef<FlatList>(null)
     const onMainScroll = useOnMainScroll(store)
     const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false)
     const [query, setQuery] = React.useState<string>('')
@@ -52,31 +52,6 @@ export const SearchScreen = withAuthRequired(
     const [searchUIModel, setSearchUIModel] = React.useState<
       SearchUIModel | undefined
     >()
-    const [refreshing, setRefreshing] = React.useState(false)
-
-    const onSoftReset = () => {
-      scrollElRef.current?.scrollTo({x: 0, y: 0})
-    }
-
-    useFocusEffect(
-      React.useCallback(() => {
-        const softResetSub = store.onScreenSoftReset(onSoftReset)
-        const cleanup = () => {
-          softResetSub.remove()
-        }
-
-        store.shell.setMinimalShellMode(false)
-        autocompleteView.setup()
-        if (!foafs.hasData) {
-          foafs.fetch()
-        }
-        if (!suggestedActors.hasLoaded) {
-          suggestedActors.loadMore(true)
-        }
-
-        return cleanup
-      }, [store, autocompleteView, foafs, suggestedActors]),
-    )
 
     const onChangeQuery = React.useCallback(
       (text: string) => {
@@ -109,14 +84,31 @@ export const SearchScreen = withAuthRequired(
       store.shell.setIsDrawerSwipeDisabled(true)
     }, [query, setSearchUIModel, store])
 
-    const onRefresh = React.useCallback(async () => {
-      setRefreshing(true)
-      try {
-        await foafs.fetch()
-      } finally {
-        setRefreshing(false)
-      }
-    }, [foafs, setRefreshing])
+    const onSoftReset = React.useCallback(() => {
+      scrollViewRef.current?.scrollTo({x: 0, y: 0})
+      flatListRef.current?.scrollToOffset({offset: 0})
+      onPressCancelSearch()
+    }, [scrollViewRef, flatListRef, onPressCancelSearch])
+
+    useFocusEffect(
+      React.useCallback(() => {
+        const softResetSub = store.onScreenSoftReset(onSoftReset)
+        const cleanup = () => {
+          softResetSub.remove()
+        }
+
+        store.shell.setMinimalShellMode(false)
+        autocompleteView.setup()
+        if (!foafs.hasData) {
+          foafs.fetch()
+        }
+        if (!suggestedActors.hasLoaded) {
+          suggestedActors.loadMore(true)
+        }
+
+        return cleanup
+      }, [store, autocompleteView, foafs, suggestedActors, onSoftReset]),
+    )
 
     return (
       <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
@@ -132,21 +124,19 @@ export const SearchScreen = withAuthRequired(
           />
           {searchUIModel ? (
             <SearchResults model={searchUIModel} />
+          ) : !isInputFocused && !query ? (
+            <Suggestions
+              ref={flatListRef}
+              foafs={foafs}
+              suggestedActors={suggestedActors}
+            />
           ) : (
             <ScrollView
-              ref={scrollElRef}
+              ref={scrollViewRef}
               testID="searchScrollView"
               style={pal.view}
               onScroll={onMainScroll}
-              scrollEventThrottle={100}
-              refreshControl={
-                <RefreshControl
-                  refreshing={refreshing}
-                  onRefresh={onRefresh}
-                  tintColor={pal.colors.text}
-                  titleColor={pal.colors.text}
-                />
-              }>
+              scrollEventThrottle={100}>
               {query && autocompleteView.searchRes.length ? (
                 <>
                   {autocompleteView.searchRes.map(item => (
@@ -155,6 +145,7 @@ export const SearchScreen = withAuthRequired(
                       testID={`searchAutoCompleteResult-${item.handle}`}
                       handle={item.handle}
                       displayName={item.displayName}
+                      labels={item.labels}
                       avatar={item.avatar}
                     />
                   ))}
@@ -171,9 +162,7 @@ export const SearchScreen = withAuthRequired(
                     Search for users on the network
                   </Text>
                 </View>
-              ) : (
-                <Suggestions foafs={foafs} suggestedActors={suggestedActors} />
-              )}
+              ) : null}
               <View style={s.footerSpacer} />
             </ScrollView>
           )}