From cf09107cf45d5877bdffbd9adcc788254b64c517 Mon Sep 17 00:00:00 2001 From: natjms Date: Sat, 22 May 2021 08:09:30 -0300 Subject: [PATCH] Enable fetching instance search results on improved search interface --- src/components/pages/discover/search.js | 396 +++++++++++++++++------- src/components/pages/profile.js | 15 +- src/requests.js | 11 +- 3 files changed, 298 insertions(+), 124 deletions(-) diff --git a/src/components/pages/discover/search.js b/src/components/pages/discover/search.js index 20810f3..f4c8766 100644 --- a/src/components/pages/discover/search.js +++ b/src/components/pages/discover/search.js @@ -1,96 +1,213 @@ -import React, { useState } from "react"; -import { View, TextInput, Text, Dimensions, Image } from "react-native"; +import React, { useState, useEffect } from "react"; +import { + View, + TextInput, + Text, + Dimensions, + Image, +} from "react-native"; +import { TabView, TabBar, SceneMap } from "react-native-tab-view"; +import { FontAwesome } from '@expo/vector-icons'; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import * as requests from "src/requests"; import { StatusBarSpace } from "src/interface/rendering"; import { ScreenWithTrayJsx } from "src/components/navigation/navigators"; -import { TouchableWithoutFeedback } from "react-native-gesture-handler"; - -const TEST_IMAGE = "https://cache.desktopnexus.com/thumbseg/2255/2255124-bigthumbnail.jpg"; -const TEST_ACCOUNTS = [ - { - id: 1, - avatar: TEST_IMAGE, - username: "njms", - acct: "njms", - display_name: "Nat🔆", - }, - { - id: 2, - avatar: TEST_IMAGE, - username: "njms", - acct: "njms", - display_name: "Nat🔆", - } -]; - -const TEST_HASHTAGS = [ - { - id: 1, - name: "hashtag1", - }, - { - id: 2, - name: "hashtag2", - }, -]; +import { TouchableOpacity } from "react-native-gesture-handler"; function navCallbackFactory(navigation, route) { return params => { - console.log("test") navigation.navigate(route, params); } } const SearchJsx = ({navigation}) => { + // The number of additional items to fetch each time + const FETCH_LIMIT = 5; + let [state, setState] = useState({ query: "", + loaded: false, + accountOffset: 0, + hashtagOffset: 0, }); - const accountCallback = navCallbackFactory(navigation, "ViewProfile"); - const hashtagCallback = navCallbackFactory(navigation, "ViewHashtag"); + useEffect(() => { + let instance, accessToken; + AsyncStorage + .multiGet([ + "@user_instance", + "@user_token", + ]) + .then(([instancePair, tokenPair]) => { + instance = instancePair[1]; + accessToken = JSON.parse(tokenPair[1]).access_token; + + setState({...state, + instance, + accessToken, + loaded: true, + }); + }); + }, []); + + const _handleSearch = async () => { + const results = await requests.fetchSearchResults( + state.instance, + state.accessToken, + { + q: state.query, + limit: FETCH_LIMIT, + } + ); + + setState({...state, + results, + accountOffset: FETCH_LIMIT, + hashtagOffset: FETCH_LIMIT, + }); + }; + + const _handleShowMoreAccounts = async () => { + const { accounts } = await requests.fetchSearchResults( + state.instance, + state.accessToken, + { + q: state.query, + type: "accounts", + offset: state.accountOffset, + limit: FETCH_LIMIT, + } + ); + + setState({...state, + results: {...state.results, + accounts: state.results.accounts.concat(accounts), + }, + accountOffset: state.accountOffset + FETCH_LIMIT, + }); + }; + + const _handleShowMoreHashtags = async () => { + const { hashtags } = await requests.fetchSearchResults( + state.instance, + state.accessToken, + { + q: state.query, + type: "hashtags", + offset: state.hashtagOffset, + limit: FETCH_LIMIT, + } + ); + + setState({...state, + results: {...state.results, + hashtags: state.results.hashtags.concat(hashtags), + }, + hashtagOffset: state.hashtagOffset + FETCH_LIMIT, + }); + }; + + const [ index, setIndex ] = useState(0); + const [ routes ] = useState([ + { + key: "accounts", + icon: "user", + }, + { + key: "hashtags", + icon: "hashtag", + }, + ]); + + const AccountRenderer = () => ( + + ); + + const HashtagRenderer = () => ( + + ); + + const renderScene = SceneMap({ + accounts: AccountRenderer, + hashtags: HashtagRenderer, + }); + + const renderTabBar = (props) => ( + + ); + + const renderIcon = ({ route, color }) => ( + + ); return ( - - - - setState({query: q}) } - onBlur = { - () => { - if (state.query == "") { - navigation.navigate("Discover"); + <> + { state.loaded + ? + + setState({ ...state, query: q }) } - } + onBlur = { + () => { + if (state.query == "") { + navigation.navigate("Discover"); + } + } + } + value = { state.query } /> + + + + + { state.results + ? + : <> } - value = { state.query } /> - - { state.query == "" ? - - : - Accounts - - Hashtags - - + + : <> } - + ); }; const SearchItemJsx = (props) => { return ( - props.callback(props.params) }> + props.callback(props.navParams) }> { { props.children } - + ); }; -const AccountsListJsx = (props) => { +const AccountListJsx = (props) => { return ( - { - props.data.map(item => { - return ( - - - { item.username } - - - { item.display_name } - - - ); - }) - } + <> + { + props.data.map(item => { + return ( + + + { item.acct } + + + { item.display_name } + + + ); + }) + } + + <> + { props.data.length == props.offset + ? + + + Show more? + + + + : <> + } + ); }; @@ -131,36 +263,58 @@ const AccountsListJsx = (props) => { const HashtagListJsx = (props) => { return ( - { - props.data.map(item => { - return ( - - - #{ item.name } - - - ); - }) - } + <> + { + props.data.map((item, i) => { + return ( + + + #{ item.name } + + + ); + }) + } + + <> + { props.data.length == props.offset + ? + + + Show more? + + + + :<> + } + ); } +const SCREEN_WIDTH = Dimensions.get("window").width; const styles = { form: { - display: "flex", - justifyContent: "center", - backgroundColor: "white", - padding: 20 - }, - searchBar: { - padding: 10, - fontSize: 17, - color: "#888" + container: { + flexDirection: "row", + justifyContent: "center", + backgroundColor: "white", + padding: 20, + }, + input: { + flexGrow: 1, + padding: 10, + fontSize: 17, + color: "#888" + }, + submit: { + padding: 20, + } }, label: { padding: 10, @@ -184,7 +338,25 @@ const styles = { height: 50, borderRadius: 25, marginRight: 10, - } + }, + showMore: { + container: { + justifyContent: "center", + alignItems: "center" + }, + button: { + borderWidth: 1, + borderColor: "#888", + borderRadius: 5, + padding: 10, + margin: 20 + }, + }, + + tabBar: { + indicator: { backgroundColor: "black" }, + tab: { backgroundColor: "white" }, + }, } export default SearchJsx; diff --git a/src/components/pages/profile.js b/src/components/pages/profile.js index 70681e6..a915d6c 100644 --- a/src/components/pages/profile.js +++ b/src/components/pages/profile.js @@ -71,29 +71,22 @@ const ViewProfileJsx = ({navigation}) => { .multiGet(["@user_profile", "@user_instance", "@user_token"]) .then(([ ownProfilePair, ownDomainPair, tokenPair ]) => { ownProfile = JSON.parse(ownProfilePair[1]); - ownDomain = ownDomainPair[1]; + instance = ownDomainPair[1]; accessToken = JSON.parse(tokenPair[1]).access_token; - const parsedAcct = state.profile.acct.split("@"); - domain = parsedAcct.length == 1 - ? ownDomain // There's no @ in the acct, thus it's a local user - : parsedAcct [1] // The part of profile.acct after the @ - return Promise.all([ requests.fetchFollowing( - ownDomain, + instance, ownProfile.id, accessToken ), requests.fetchFollowers( - domain, + instance, state.profile.id, accessToken ), requests.fetchAccountStatuses( - // NOTE: Should be fetched from remote instance if - // necessary Thus, we use domain and not ownDomain - domain, + instance, state.profile.id, accessToken ) diff --git a/src/requests.js b/src/requests.js index c3b9bdd..e144110 100644 --- a/src/requests.js +++ b/src/requests.js @@ -55,7 +55,7 @@ export async function postForm(url, data, token = false) { export async function get(url, token = false, data = false) { let completeURL; if (data) { - let params = new URLSearchParams(data) + let params = new URLSearchParams(data); completeURL = `${url}?${params.toString()}`; } else { completeURL = url; @@ -113,3 +113,12 @@ export async function fetchPublicTimeline(domain, token, params = false) { ); return resp.json(); } + +export async function fetchSearchResults(domain, token, params) { + const resp = await get( + `https://${domain}/api/v2/search`, + token, + params + ); + return resp.json(); +}