Skip to content

Commit

Permalink
add search history
Browse files Browse the repository at this point in the history
  • Loading branch information
Illu committed May 19, 2020
1 parent 787707f commit 2d5f734
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 55 deletions.
1 change: 1 addition & 0 deletions assets/icons/arrow-up-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export {default as CheckCircle} from './check-circle.svg';
export {default as Alert} from './alert-circle.svg';
export {default as Clock} from './clock.svg';
export {default as Compass} from './compass.svg';
export {default as Briefcase} from './briefcase.svg';
export {default as Briefcase} from './briefcase.svg';
export {default as ArrowUpLeft} from './arrow-up-left.svg';
20 changes: 12 additions & 8 deletions src/common/BigTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { TouchableOpacity } from "react-native";

const Wrapper = styled.View`
flex-direction: row;
justify-content: space-between;
padding: 10px 20px;
align-items: center;
margin: 10px 20px;
justify-content: space-between;
width: 100%;
`;

const TitleText = styled.Text`
Expand All @@ -16,22 +17,25 @@ const TitleText = styled.Text`
`;

const SeeMoreText = styled.Text`
color: ${({ theme }) => theme.text};
color: ${({ theme }) => theme.accent};
text-align: right;
`;

const BigTitle = ({
title,
onSeeMore,
onAction,
actionText,
}: {
title: string;
onSeeMore?: () => void;
onAction?: () => void;
actionText?: string;
}) => {
return (
<Wrapper>
<TitleText>{title}</TitleText>
{onSeeMore && (
<TouchableOpacity onPress={onSeeMore}>
<SeeMoreText>See more</SeeMoreText>
{actionText && (
<TouchableOpacity onPress={onAction}>
<SeeMoreText>{actionText}</SeeMoreText>
</TouchableOpacity>
)}
</Wrapper>
Expand Down
47 changes: 10 additions & 37 deletions src/common/__tests__/__snapshots__/BigTitle.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ exports[`BigTitle renders correctly 1`] = `
"alignItems": "center",
"flexDirection": "row",
"justifyContent": "space-between",
"marginBottom": 10,
"marginLeft": 20,
"marginRight": 20,
"marginTop": 10,
"paddingBottom": 10,
"paddingLeft": 20,
"paddingRight": 20,
"paddingTop": 10,
"width": "100%",
},
]
}
Expand Down Expand Up @@ -40,10 +41,11 @@ exports[`BigTitle renders correctly with the See More option 1`] = `
"alignItems": "center",
"flexDirection": "row",
"justifyContent": "space-between",
"marginBottom": 10,
"marginLeft": 20,
"marginRight": 20,
"marginTop": 10,
"paddingBottom": 10,
"paddingLeft": 20,
"paddingRight": 20,
"paddingTop": 10,
"width": "100%",
},
]
}
Expand All @@ -61,34 +63,5 @@ exports[`BigTitle renders correctly with the See More option 1`] = `
>
$_TITLE_TEXT_$
</Text>
<View
accessible={true}
focusable={true}
isTVSelectable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
>
<Text
style={
Array [
Object {
"color": "rgb(28, 28, 30)",
},
]
}
>
See more
</Text>
</View>
</View>
`;
11 changes: 10 additions & 1 deletion src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ const IconWrapper = styled.View`
left: 24px;
`;

const SearchBar = ({ launchSearch }) => {
interface Props {
launchSearch: () => void;
value: string;
onChangeText: (str: string) => void;
}

const SearchBar: React.FC<Props> = ({ launchSearch, onChangeText, value }) => {
const insets = useSafeArea();
const { colors } = useTheme();
return (
Expand All @@ -35,7 +41,10 @@ const SearchBar = ({ launchSearch }) => {
returnKeyType="search"
onSubmitEditing={({ nativeEvent }) => launchSearch(nativeEvent.text)}
placeholder="Search"
clearButtonMode="always"
onChangeText={onChangeText}
placeholderTextColor={colors.placeholderText}
value={value}
/>
<IconWrapper>
<Icon name="Search" color={colors.placeholderText} size={20} />
Expand Down
86 changes: 80 additions & 6 deletions src/screens/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import React, { useEffect, useContext } from "react";
import React, { useEffect, useContext, useState } from "react";
import styled from "styled-components";
import { observer } from "mobx-react";
import { TouchableOpacity, Linking } from "react-native";
import { TouchableOpacity, View } from "react-native";
import Searchbar from "../components/SearchBar";
import ResultCard from "../components/ResultCard";
import BigTitle from "../common/BigTitle";
import Loader from "../common/Loader";
import { STATES } from "../constants";
import Search from "../stores/Search";
import { useTheme } from "@react-navigation/native";
import firebase from "react-native-firebase";
import { openLink } from "../helpers/OpenLink";
import AppState from "../stores/AppState";
import Icon from "../common/Icon";
import { useTheme } from "@react-navigation/native";

const ContentWrapper = styled.SafeAreaView`
flex: 1;
Expand All @@ -35,16 +37,48 @@ const ResultCount = styled.Text`
color: ${({ theme }) => theme.text};
`;

const HistoryCard = styled.TouchableOpacity`
width: 100%;
background: ${({ theme }) => theme.secondary};
padding: 15px 20px;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-color: ${({ theme }) => theme.uiAccent};
`;

const HistoryText = styled.Text`
color: ${({ theme }) => theme.text};
`;

const HintText = styled.Text`
color: ${({ theme }) => theme.text};
font-size: 15px;
margin: 15px 20px;
text-align: center;
`;

const HintTitle = styled.Text`
color: ${({ theme }) => theme.text};
font-weight: bold;
font-size: 20px;
margin-top: 100px;
`;

const SearchScreen = observer(({ navigation }) => {
const searchStore = useContext(Search);
const appStateStore = useContext(AppState);
const { colors } = useTheme();
const [searchStr, setSearchStr] = useState("");

useEffect(() => {
firebase.analytics().setCurrentScreen("SEARCH");
searchStore.initStore();
}, []);

const showDetails = (data) => {
navigation.navigate("Details", { data });
};
const searchStore = useContext(Search);
const appStateStore = useContext(AppState);
const { results, searchLaunches, totalResults, state } = searchStore;

const launchSearch = (text: string) => {
Expand All @@ -54,7 +88,14 @@ const SearchScreen = observer(({ navigation }) => {

return (
<ContentWrapper>
<Searchbar launchSearch={launchSearch} />
<Searchbar
launchSearch={launchSearch}
value={searchStr}
onChangeText={(str) => {
setSearchStr(str);
if (str.length === 0) searchStore.clearResults();
}}
/>
<ScrollWrapper contentContainerStyle={{ alignItems: "center" }}>
{state === STATES.LOADING && <Loader />}
{results.length >= 0 && state === STATES.SUCCESS && (
Expand All @@ -65,6 +106,39 @@ const SearchScreen = observer(({ navigation }) => {
))}
</>
)}
{state === STATES.IDLE && searchStore.history.length > 0 && (
<>
<BigTitle
title="Recent searches"
onAction={searchStore.clearHistory}
actionText="Clear"
/>
{searchStore.history.map((item, index) => (
<HistoryCard
key={index}
style={{
borderTopWidth: index !== 0 ? 0.5 : 0,
}}
onPress={() => {
setSearchStr(item);
searchLaunches(item);
}}
>
<HistoryText>{item}</HistoryText>
<Icon name="ArrowUpLeft" color={colors.uiAccent} />
</HistoryCard>
))}
</>
)}
{state === STATES.IDLE && searchStore.history.length === 0 && (
<>
<HintTitle>Find a launch</HintTitle>
<HintText>
Search accross past and upcoming launches with a rocket or mission
name.
</HintText>
</>
)}
<TouchableOpacity
onPress={() =>
openLink("https://launchlibrary.net/", appStateStore.browser)
Expand Down
48 changes: 46 additions & 2 deletions src/stores/Search.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,42 @@
import { decorate, observable } from "mobx";
import { STATES, API_URL, NEWS_API_URL } from "../constants";
import { decorate, observable, action } from "mobx";
import { STATES, API_URL } from "../constants";
import { createContext } from "react";
import AsyncStorage from "@react-native-community/async-storage";

class Search {
state = STATES.IDLE;
results = [];
totalResults = "";
history = [];

initStore = async () => {
try {
const value = await AsyncStorage.getItem("@Moonwalk:search");
if (value !== null) {
const data = JSON.parse(value);
this.history = data.history;
}
} catch (error) {}
};

saveData = async () => {
try {
await AsyncStorage.setItem(
"@Moonwalk:search",
JSON.stringify({
history: this.history,
})
);
} catch (error) {}
};

addHistoryItem = (str) => {
this.history.unshift(str);
if (this.history.length > 4) {
this.history.pop();
}
this.saveData();
};

searchLaunches = (str) => {
this.state = STATES.LOADING;
Expand All @@ -15,17 +46,30 @@ class Search {
this.results = data.launches || [];
this.totalResults = data.total;
this.state = STATES.SUCCESS;
this.addHistoryItem(str);
})
.catch((err) => {
this.state = STATES.ERROR;
});
};

clearHistory = () => {
this.history = [];
this.saveData();
};

clearResults = () => {
this.results = [];
this.state = STATES.IDLE;
};
}

decorate(Search, {
state: observable,
results: observable,
totalResults: observable,
history: observable,
clearResults: action,
});

export default createContext(new Search());

0 comments on commit 2d5f734

Please sign in to comment.