From 9f4018a0de2a897c1df92a2806c39b9fda69bd8e Mon Sep 17 00:00:00 2001 From: Yohan Simard Date: Wed, 13 Nov 2019 21:30:39 +0100 Subject: [PATCH 1/8] Added a search feature in the Proximo articles + fixed typos in translations --- components/SearchHeader.js | 92 ++++++++++++++++++++ navigation/AppNavigator.js | 4 +- screens/Proximo/ProximoMainScreen.js | 26 ++++-- screens/Proximo/ProximoSearchScreen.js | 111 +++++++++++++++++++++++++ translations/en.json | 7 +- translations/fr.json | 25 +++--- 6 files changed, 242 insertions(+), 23 deletions(-) create mode 100644 components/SearchHeader.js create mode 100644 screens/Proximo/ProximoSearchScreen.js diff --git a/components/SearchHeader.js b/components/SearchHeader.js new file mode 100644 index 0000000..eb4ce6b --- /dev/null +++ b/components/SearchHeader.js @@ -0,0 +1,92 @@ +// @flow + +import * as React from "react"; +import {Header} from "native-base"; +import {Platform, StyleSheet} from "react-native"; +import {getStatusBarHeight} from "react-native-status-bar-height"; +import Touchable from 'react-native-platform-touchable'; +import ThemeManager from "../utils/ThemeManager"; +import CustomMaterialIcon from "./CustomMaterialIcon"; +import {TextInput} from "react-native-paper"; +import i18n from "i18n-js"; + + +type Props = { + navigation: Object, + searchFunction: Function +}; + +type State = { + searchString: string +} + + +/** + * Custom component defining a search header using native base + */ +export default class SearchHeader extends React.Component { + state = { + searchString: "Test", + }; + + render() { + /* TODO: + - hard coded color (not theme-specific), + - bugs with placeHolder and underlineColorAndroid (do not work), + - subtle offset of the text to fix in the inputText + - not tested on iOS + */ + return ( +
+ this.props.navigation.goBack()}> + + + + this.setState({searchString: text})} + onSubmitEditing={text => { + this.setState({searchString: text}); + this.props.searchFunction(this.state.searchString); + }} + /> + + this.props.searchFunction(this.state.searchString)}> + + +
+ ); + } +}; + + +// Fix header in status bar on Android +const styles = StyleSheet.create({ + header: { + paddingTop: getStatusBarHeight(), + height: 54 + getStatusBarHeight(), + }, +}); diff --git a/navigation/AppNavigator.js b/navigation/AppNavigator.js index f9fb664..b8072aa 100644 --- a/navigation/AppNavigator.js +++ b/navigation/AppNavigator.js @@ -5,6 +5,7 @@ import {createMaterialBottomTabNavigatorWithInitialRoute} from './MainTabNavigat import SettingsScreen from '../screens/SettingsScreen'; import AboutScreen from '../screens/About/AboutScreen'; import ProximoListScreen from '../screens/Proximo/ProximoListScreen'; +import ProximoSearchScreen from "../screens/Proximo/ProximoSearchScreen"; import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen'; import ProxiwashAboutScreen from '../screens/Proxiwash/ProxiwashAboutScreen'; import ProximoAboutScreen from '../screens/Proximo/ProximoAboutScreen'; @@ -26,6 +27,7 @@ function createAppContainerWithInitialRoute(initialRoute: string) { Main: createMaterialBottomTabNavigatorWithInitialRoute(initialRoute), // Drawer: MainDrawerNavigator, ProximoListScreen: {screen: ProximoListScreen}, + ProximoSearchScreen: {screen: ProximoSearchScreen}, SettingsScreen: {screen: SettingsScreen}, AboutScreen: {screen: AboutScreen}, AboutDependenciesScreen: {screen: AboutDependenciesScreen}, @@ -42,7 +44,7 @@ function createAppContainerWithInitialRoute(initialRoute: string) { initialRouteName: "Main", mode: 'card', headerMode: "none", - transitionConfig: () => fromRight(), + // transitionConfig: () => fromRight(), }) ); } diff --git a/screens/Proximo/ProximoMainScreen.js b/screens/Proximo/ProximoMainScreen.js index de8ec2f..a33ca01 100644 --- a/screens/Proximo/ProximoMainScreen.js +++ b/screens/Proximo/ProximoMainScreen.js @@ -79,13 +79,25 @@ export default class ProximoMainScreen extends FetchedDataSectionList { getRightButton() { return ( - this.props.navigation.navigate('ProximoAboutScreen')}> - - + + this.props.navigation.navigate('ProximoSearchScreen', {data: this.state.fetchedData})}> + + + this.props.navigation.navigate('ProximoAboutScreen')}> + + + ); } diff --git a/screens/Proximo/ProximoSearchScreen.js b/screens/Proximo/ProximoSearchScreen.js new file mode 100644 index 0000000..0f37c58 --- /dev/null +++ b/screens/Proximo/ProximoSearchScreen.js @@ -0,0 +1,111 @@ +// @flow + +import * as React from 'react'; +import {Body, Container, Content, Left, ListItem, Right, Text, Thumbnail,} from 'native-base'; +import {FlatList} from "react-native"; +import i18n from "i18n-js"; +import ThemeManager from "../../utils/ThemeManager"; +import SearchHeader from "../../components/SearchHeader"; + +type Props = { + navigation: Object, +}; + +type State = { + filteredData: Array, +}; + +/** + * Class defining proximo's article list of a certain category. + */ +export default class ProximoSearchScreen extends React.Component { + state = { + filteredData: this.props.navigation.getParam('data', {articles: [{name: "Error"}]}).articles, + }; + + + /** + * get color depending on quantity available + * + * @param availableStock + * @return + */ + getStockColor(availableStock: number) { + let color: string; + if (availableStock > 3) + color = ThemeManager.getCurrentThemeVariables().brandSuccess; + else if (availableStock > 0) + color = ThemeManager.getCurrentThemeVariables().brandWarning; + else + color = ThemeManager.getCurrentThemeVariables().brandDanger; + return color; + } + + + showItemDetails(item: Object) { + //TODO: implement onClick function (copy-paste from ProximoListScreen) + } + + /** + * Returns only the articles whose name contains str. Case and accents insensitive. + * @param str + * @returns {[]} + */ + filterData(str: string) { + let filteredData = []; + const testStr: String = str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + const articles: Object = this.props.navigation.getParam('data', {articles: [{name: "Error"}]}).articles; + for (const article: Object of articles) { + const name: String = String(article.name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "")); + if (name.includes(testStr)) { + filteredData.push(article) + } + } + return filteredData; + } + + search(str: string) { + this.setState({ + filteredData: this.filterData(str) + }) + } + + render() { + return ( + + + + item.name + item.code} + style={{minHeight: 300, width: '100%'}} + renderItem={({item}) => + {this.showItemDetails(item);}} > + + + + + + {item.name} + + + {item.quantity + ' ' + i18n.t('proximoScreen.inStock')} + + + + + {item.price}€ + + + } + /> + + + ); + } +} diff --git a/translations/en.json b/translations/en.json index e417f0d..b5a4bb7 100644 --- a/translations/en.json +++ b/translations/en.json @@ -25,7 +25,7 @@ }, "slide4": { "title": "Proximo", - "text": "Are you short on pasta? Or you maybe you feel a little peckish, then look up your INSA shop's stock in real time" + "text": "Are you short on pasta? Or maybe you feel a little peckish, then look up your INSA shop's stock in real time" }, "slide5": { "title": "Planex", @@ -129,7 +129,8 @@ "description": "The Proximo is your small grocery store maintained by students directly on the campus. Open every day from 18h30 to 19h30, we welcome you when you are short on pastas or sodas ! Different products for different problems, everything at cost price. You can pay by Lydia or cash.", "openingHours": "Openning Hours", "paymentMethods": "Payment Methods", - "paymentMethodsDescription": "Cash or Lydia" + "paymentMethodsDescription": "Cash or Lydia", + "search": "Search" }, "proxiwashScreen": { "dryer": "Dryer", @@ -141,7 +142,7 @@ "listUpdateFail": "Error while updating machines state", "error": "Could not update machines state. Pull down to retry.", "loading": "Loading...", - "description": "This is the washing service operated by Promologis for INSA's residences (We don't mind if you do not live on the campus and you do your laundry here). The room is right next to the R2, with 3 dryers and 9 washers, is open 7d/7 24h/24 ! Here you can check their availability ! You can bring your own detergent, use the one given on site or buy it at the Proximo (cheaper than the one given by the machines ). You can pay b credit card or cash.", + "description": "This is the washing service operated by Promologis for INSA's residences (We don't mind if you do not live on the campus and you do your laundry here). The room is right next to the R2, with 3 dryers and 9 washers, is open 7d/7 24h/24 ! Here you can check their availability ! You can bring your own detergent, use the one given on site or buy it at the Proximo (cheaper than the one given by the machines ). You can pay by credit card or cash.", "informationTab": "Information", "paymentTab": "Payment", "tariffs": "Tariffs", diff --git a/translations/fr.json b/translations/fr.json index b9ddb8b..31efb95 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -21,11 +21,11 @@ }, "slide3": { "title": "N'oubliez plus votre linge !", - "text": "CAMPUS vous informe de la disponibilité des machines et vous permet d'être notifiés lorsque la vôtre se termine bientôt !" + "text": "CAMPUS vous informe de la disponibilité des machines et vous permet d'être notifié lorsque la vôtre se termine bientôt !" }, "slide4": { "title": "Proximo", - "text": "Il vous manque des pâtes ? Ou un petit creux au gouter, regardez les stocks de votre supérette insaienne en temps réel" + "text": "Il vous manque des pâtes ? Ou un petit creux au goûter, regardez les stocks de votre supérette insaienne en temps réel" }, "slide5": { "title": "Planex", @@ -75,7 +75,7 @@ "dashboard": { "seeMore": "Cliquez pour plus d'infos", "todayEventsTitle": "Événements aujourd'hui", - "todayEventsSubtitleNA": "Pas d'événements", + "todayEventsSubtitleNA": "Pas d'événement", "todayEventsSubtitle": " événement aujourd'hui", "todayEventsSubtitlePlural": " événements aujourd'hui", "proximoTitle": "Proximo", @@ -126,10 +126,11 @@ "listUpdateFail": "Erreur lors de la mise à jour de la list d'articles", "loading": "Chargement...", "inStock": "en stock", - "description": "Le Proximo c’est ta petite épicerie étudiante tenu par les étudiants directement sur le campus. Ouvert tous les jours de 18h30 à 19h30, nous t’accueillons et te souvent quand tu n’as plus de pâtes ou de diluant ! Différents produits pour différentes galère, le tout à prix coûtant. Tu peux payer par Lydia ou par espèce.", + "description": "Le Proximo c’est ta petite épicerie étudiante tenue par les étudiants directement sur le campus. Ouverte tous les jours de 18h30 à 19h30, nous t’accueillons et te sauvons quand tu n’as plus de pâtes ou de diluant ! Différents produits pour différentes galères, le tout à prix coûtant. Tu peux payer par Lydia ou par espèce.", "openingHours": "Horaires d'ouverture", "paymentMethods" : "Moyens de Paiement", - "paymentMethodsDescription" : "Espèce ou Lydia" + "paymentMethodsDescription" : "Espèce ou Lydia", + "search": "Rechercher" }, "proxiwashScreen": { "dryer": "Sèche-Linge", @@ -137,9 +138,9 @@ "washer": "Lave-Linge", "washers": "Lave-Linges", "min": "min", - "listUpdated": "Etat des machines mis à jour", - "listUpdateFail": "Erreur lors de la mise à jour del'état des machines", - "error": "Impossible de mettre a jour l'état des machines. Tirez vers le bas pour reessayer.", + "listUpdated": "État des machines mis à jour", + "listUpdateFail": "Erreur lors de la mise à jour de l'état des machines", + "error": "Impossible de mettre a jour l'état des machines. Tirez vers le bas pour réessayer.", "loading": "Chargement...", "description": "C'est le service de laverie proposé par promologis pour les résidences INSA (On t'en voudra pas si tu loges pas sur le campus et que tu fais ta machine ici). Le local situé au pied du R2 avec ses 3 sèche-linges et 9 machines est ouvert 7J/7 24h/24 ! Ici tu peux vérifier leur disponibilité ! Tu peux amener ta lessive, la prendre sur place ou encore mieux l'acheter au Proximo (moins chère qu'à la laverie directement). Tu peux payer par CB ou espèces.", "informationTab": "Informations", @@ -152,7 +153,7 @@ "washerProcedure": "Déposer le linge dans le tambour sans le tasser et en respectant les charges.\n\nFermer la porte de l'appareil.\n\nSélectionner un programme avec l'une des quatre touches de programme favori standard.\n\nAprès avoir payé à la centrale de commande, appuyer sur le bouton marqué START du lave-linge.\n\nDès que le programme est terminé, l’afficheur indique 'Programme terminé', appuyer sur le bouton jaune d’ouverture du hublot pour récupérer le linge.", "washerTips": "Programme blanc/couleur : 6kg de linge sec (textiles en coton, lin, linge de corps, draps, jeans,serviettes de toilettes).\n\nProgramme nonrepassable : 3,5 kg de linge sec (textiles en fibres synthétiques, cotonet polyester mélangés).\n\nProgramme fin 30°C : 2,5 kg de linge sec (textiles délicats en fibres synthétiques, rayonne).\n\nProgramme laine 30°C : 2,5 kg de linge sec (textiles en laine et lainages lavables).", "dryerProcedure": "Déposer le linge dans le tambour sans le tasser et en respectant les charges.\n\nFermer la porte de l'appareil.\n\nSélectionner un programme avec l'une des quatre touches de programme favori standard.\n\nAprès avoir payé à la centrale de commande, appuyer sur le bouton marqué START du lave-linge.", - "dryerTips": "La durée conseillée est de 35 minutes pour 14kg de linge. Vous pouvez choisir une durée plus courte si le seche-linge n'est pas chargé.", + "dryerTips": "La durée conseillée est de 35 minutes pour 14kg de linge. Vous pouvez choisir une durée plus courte si le sèche-linge n'est pas chargé.", "procedure": "Procédure", "tips": "Conseils", @@ -161,7 +162,7 @@ "disableNotifications": "Désactiver les notifications", "ok": "OK", "cancel": "Annuler", - "finished": "Cette machine est terminée. Si vous l'avez l'avez démarée, vous pouvez récupérer votre linge.", + "finished": "Cette machine est terminée. Si vous l'avez démarrée, vous pouvez récupérer votre linge.", "ready": "Cette machine est vide et prête à être utilisée.", "running": "Cette machine a démarré à %{start} et terminera à %{end}.\nTemps restant : %{remaining} min", "broken": "Cette machine est hors service. Merci pour votre compréhension.", @@ -171,7 +172,7 @@ }, "states": { - "finished": "TERMINE", + "finished": "TERMINÉ", "ready": "DISPONIBLE", "running": "EN COURS", "broken": "HORS SERVICE", @@ -195,7 +196,7 @@ }, "general": { "loading": "Chargement...", - "networkError": "Impossible de contacter les serveurs. Assurez vous d'être connecté à internet." + "networkError": "Impossible de contacter les serveurs. Assurez-vous d'être connecté à internet." }, "date": { "daysOfWeek": { From e3bc2f7e8e3a98b0bf31a542409918a726ce5603 Mon Sep 17 00:00:00 2001 From: keplyx Date: Thu, 14 Nov 2019 16:20:06 +0100 Subject: [PATCH 2/8] Added "all articles" category + use native base for search input --- components/SearchHeader.js | 89 +++++++++++++++----------- screens/Proximo/ProximoMainScreen.js | 41 +++++++++--- screens/Proximo/ProximoSearchScreen.js | 2 +- translations/en.json | 3 +- translations/fr.json | 3 +- 5 files changed, 87 insertions(+), 51 deletions(-) diff --git a/components/SearchHeader.js b/components/SearchHeader.js index eb4ce6b..2e393c3 100644 --- a/components/SearchHeader.js +++ b/components/SearchHeader.js @@ -1,13 +1,12 @@ // @flow import * as React from "react"; -import {Header} from "native-base"; +import {Header, Item, Input, Left, Right, Body} from "native-base"; import {Platform, StyleSheet} from "react-native"; import {getStatusBarHeight} from "react-native-status-bar-height"; import Touchable from 'react-native-platform-touchable'; import ThemeManager from "../utils/ThemeManager"; import CustomMaterialIcon from "./CustomMaterialIcon"; -import {TextInput} from "react-native-paper"; import i18n from "i18n-js"; @@ -38,45 +37,57 @@ export default class SearchHeader extends React.Component { */ return (
- + this.props.navigation.goBack()}> + + + + + this.props.navigation.goBack()}> - - + width: '100%', + marginBottom: 7 + }}> + + - this.setState({searchString: text})} - onSubmitEditing={text => { - this.setState({searchString: text}); - this.props.searchFunction(this.state.searchString); - }} - /> - - this.props.searchFunction(this.state.searchString)}> - - + {/* this.setState({searchString: text})}*/} + {/* onSubmitEditing={text => {*/} + {/* this.setState({searchString: text});*/} + {/* this.props.searchFunction(this.state.searchString);*/} + {/* }}*/} + {/*/>*/} + + + this.props.searchFunction(this.state.searchString)}> + + +
); } diff --git a/screens/Proximo/ProximoMainScreen.js b/screens/Proximo/ProximoMainScreen.js index a33ca01..586e7b9 100644 --- a/screens/Proximo/ProximoMainScreen.js +++ b/screens/Proximo/ProximoMainScreen.js @@ -2,7 +2,7 @@ import * as React from 'react'; import {Platform, View} from 'react-native' -import {Badge, Body, H2, Left, ListItem, Right, Text} from 'native-base'; +import {Badge, Body, Left, ListItem, Right, Text} from 'native-base'; import i18n from "i18n-js"; import CustomMaterialIcon from "../../components/CustomMaterialIcon"; import FetchedDataSectionList from "../../components/FetchedDataSectionList"; @@ -57,22 +57,45 @@ export default class ProximoMainScreen extends FetchedDataSectionList { if (fetchedData.types !== undefined && fetchedData.articles !== undefined) { let types = fetchedData.types; let articles = fetchedData.articles; + finalData.push({ + type: { + id: "0", + name: i18n.t('proximoScreen.all'), + icon: 'star' + }, + data: this.getAvailableArticles(articles, undefined) + }); for (let i = 0; i < types.length; i++) { finalData.push({ type: types[i], - data: [] + data: this.getAvailableArticles(articles, types[i]) }); - for (let k = 0; k < articles.length; k++) { - if (articles[k]['type'].includes(types[i].id) && parseInt(articles[k]['quantity']) > 0) { - finalData[i].data.push(articles[k]); - } - } + } } finalData.sort(ProximoMainScreen.sortFinalData); return finalData; } + /** + * Get an array of available articles (in stock) of the given type + * + * @param articles The list of all articles + * @param type The type of articles to find (undefined for any type) + * @return {Array} The array of available articles + */ + static getAvailableArticles(articles: Array, type: ?Object) { + let availableArticles = []; + for (let k = 0; k < articles.length; k++) { + if ((type !== undefined && type !== null && articles[k]['type'].includes(type['id']) + || type === undefined) + && parseInt(articles[k]['quantity']) > 0) { + availableArticles.push(articles[k]); + } + } + return availableArticles; + } + static sortFinalData(a: Object, b: Object) { return a.type.id - b.type.id; } @@ -81,14 +104,14 @@ export default class ProximoMainScreen extends FetchedDataSectionList { return ( this.props.navigation.navigate('ProximoSearchScreen', {data: this.state.fetchedData})}> + icon="magnify"/> { + state = { filteredData: this.props.navigation.getParam('data', {articles: [{name: "Error"}]}).articles, }; - /** * get color depending on quantity available * diff --git a/translations/en.json b/translations/en.json index b5a4bb7..cff5cba 100644 --- a/translations/en.json +++ b/translations/en.json @@ -130,7 +130,8 @@ "openingHours": "Openning Hours", "paymentMethods": "Payment Methods", "paymentMethodsDescription": "Cash or Lydia", - "search": "Search" + "search": "Search", + "all": "All" }, "proxiwashScreen": { "dryer": "Dryer", diff --git a/translations/fr.json b/translations/fr.json index 31efb95..78c47a9 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -130,7 +130,8 @@ "openingHours": "Horaires d'ouverture", "paymentMethods" : "Moyens de Paiement", "paymentMethodsDescription" : "Espèce ou Lydia", - "search": "Rechercher" + "search": "Rechercher", + "all": "Tout" }, "proxiwashScreen": { "dryer": "Sèche-Linge", From dd11fcc2c763c7e06c64d3bd02539420af4a4dbe Mon Sep 17 00:00:00 2001 From: keplyx Date: Fri, 15 Nov 2019 15:58:05 +0100 Subject: [PATCH 3/8] Moved search header into custom header + search on key press --- components/CustomHeader.js | 25 +++++- components/SearchHeader.js | 103 ------------------------- screens/Proximo/ProximoListScreen.js | 65 +++++++++------- screens/Proximo/ProximoSearchScreen.js | 8 +- 4 files changed, 66 insertions(+), 135 deletions(-) delete mode 100644 components/SearchHeader.js diff --git a/components/CustomHeader.js b/components/CustomHeader.js index eefc355..b966c1e 100644 --- a/components/CustomHeader.js +++ b/components/CustomHeader.js @@ -1,15 +1,18 @@ // @flow import * as React from "react"; -import {Body, Header, Left, Right, Title} from "native-base"; +import {Body, Header, Input, Item, Left, Right, Title} from "native-base"; import {Platform, StyleSheet, View} from "react-native"; import {getStatusBarHeight} from "react-native-status-bar-height"; import Touchable from 'react-native-platform-touchable'; import ThemeManager from "../utils/ThemeManager"; import CustomMaterialIcon from "./CustomMaterialIcon"; +import i18n from "i18n-js"; type Props = { hasBackButton: boolean, + hasSearchField: boolean, + searchCallback: Function, leftButton: React.Node, rightButton: React.Node, title: string, @@ -29,11 +32,27 @@ export default class CustomHeader extends React.Component { static defaultProps = { hasBackButton: false, + hasSearchField: false, + searchCallback: () => null, + title: '', leftButton: , rightButton: , hasTabs: false, }; + getSearchBar() { + return ( + + this.props.searchCallback(text)}/> + + ); + } + render() { let button; // Does the app have a back button or a burger menu ? @@ -56,7 +75,9 @@ export default class CustomHeader extends React.Component { {button} - {this.props.title} + {this.props.hasSearchField ? + this.getSearchBar() : + {this.props.title}} {this.props.rightButton} diff --git a/components/SearchHeader.js b/components/SearchHeader.js deleted file mode 100644 index 2e393c3..0000000 --- a/components/SearchHeader.js +++ /dev/null @@ -1,103 +0,0 @@ -// @flow - -import * as React from "react"; -import {Header, Item, Input, Left, Right, Body} from "native-base"; -import {Platform, StyleSheet} from "react-native"; -import {getStatusBarHeight} from "react-native-status-bar-height"; -import Touchable from 'react-native-platform-touchable'; -import ThemeManager from "../utils/ThemeManager"; -import CustomMaterialIcon from "./CustomMaterialIcon"; -import i18n from "i18n-js"; - - -type Props = { - navigation: Object, - searchFunction: Function -}; - -type State = { - searchString: string -} - - -/** - * Custom component defining a search header using native base - */ -export default class SearchHeader extends React.Component { - state = { - searchString: "Test", - }; - - render() { - /* TODO: - - hard coded color (not theme-specific), - - bugs with placeHolder and underlineColorAndroid (do not work), - - subtle offset of the text to fix in the inputText - - not tested on iOS - */ - return ( -
- - this.props.navigation.goBack()}> - - - - - - - - - {/* this.setState({searchString: text})}*/} - {/* onSubmitEditing={text => {*/} - {/* this.setState({searchString: text});*/} - {/* this.props.searchFunction(this.state.searchString);*/} - {/* }}*/} - {/*/>*/} - - - this.props.searchFunction(this.state.searchString)}> - - - -
- ); - } -}; - - -// Fix header in status bar on Android -const styles = StyleSheet.create({ - header: { - paddingTop: getStatusBarHeight(), - height: 54 + getStatusBarHeight(), - }, -}); diff --git a/screens/Proximo/ProximoListScreen.js b/screens/Proximo/ProximoListScreen.js index 7c70a1b..8e367c2 100644 --- a/screens/Proximo/ProximoListScreen.js +++ b/screens/Proximo/ProximoListScreen.js @@ -58,7 +58,7 @@ type State = { */ export default class ProximoListScreen extends React.Component { - modalRef: { current: null | Modalize }; + modalRef: { current: null | Modalize }; constructor(props: any) { super(props); @@ -232,6 +232,36 @@ export default class ProximoListScreen extends React.Component { } } + getSortMenu() { + return ( + + this._menu.show() + }> + + + } + > + this.sortModeSelected(sortMode.name)}> + {this.state.sortNameIcon} + {i18n.t('proximoScreen.sortName')} + + this.sortModeSelected(sortMode.price)}> + {this.state.sortPriceIcon} + {i18n.t('proximoScreen.sortPrice')} + + + ); + } + render() { const nav = this.props.navigation; const navType = nav.getParam('type', '{name: "Error"}'); @@ -243,33 +273,12 @@ export default class ProximoListScreen extends React.Component { modalStyle={{backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor}}> {this.getModalContent()} - - this._menu.show() - }> - -
- } - > - this.sortModeSelected(sortMode.name)}> - {this.state.sortNameIcon} - {i18n.t('proximoScreen.sortName')} - - this.sortModeSelected(sortMode.price)}> - {this.state.sortPriceIcon} - {i18n.t('proximoScreen.sortPrice')} - - - }/> + console.log(text)} + rightButton={this.getSortMenu()}/> { render() { return ( - + this.search(text)}/> Date: Fri, 15 Nov 2019 16:44:10 +0100 Subject: [PATCH 4/8] Improved headers search bar design --- components/CustomHeader.js | 13 +++++++++---- native-base-theme/variables/platform.js | 1 + native-base-theme/variables/platformDark.js | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/components/CustomHeader.js b/components/CustomHeader.js index b966c1e..583324a 100644 --- a/components/CustomHeader.js +++ b/components/CustomHeader.js @@ -47,8 +47,13 @@ export default class CustomHeader extends React.Component { width: '100%', marginBottom: 7 }}> - this.props.searchCallback(text)}/> + + this.props.searchCallback(text)}/> ); } @@ -71,7 +76,7 @@ export default class CustomHeader extends React.Component { return (
- + {button} @@ -79,7 +84,7 @@ export default class CustomHeader extends React.Component { this.getSearchBar() : {this.props.title}} - + {this.props.rightButton} {this.props.hasBackButton ? : Date: Fri, 15 Nov 2019 18:08:47 +0100 Subject: [PATCH 5/8] Implemented search into each category instead of its own screen --- navigation/AppNavigator.js | 4 +- screens/Proximo/ProximoListScreen.js | 44 ++++++++-- screens/Proximo/ProximoMainScreen.js | 31 +++---- screens/Proximo/ProximoSearchScreen.js | 115 ------------------------- 4 files changed, 49 insertions(+), 145 deletions(-) delete mode 100644 screens/Proximo/ProximoSearchScreen.js diff --git a/navigation/AppNavigator.js b/navigation/AppNavigator.js index b8072aa..f9fb664 100644 --- a/navigation/AppNavigator.js +++ b/navigation/AppNavigator.js @@ -5,7 +5,6 @@ import {createMaterialBottomTabNavigatorWithInitialRoute} from './MainTabNavigat import SettingsScreen from '../screens/SettingsScreen'; import AboutScreen from '../screens/About/AboutScreen'; import ProximoListScreen from '../screens/Proximo/ProximoListScreen'; -import ProximoSearchScreen from "../screens/Proximo/ProximoSearchScreen"; import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen'; import ProxiwashAboutScreen from '../screens/Proxiwash/ProxiwashAboutScreen'; import ProximoAboutScreen from '../screens/Proximo/ProximoAboutScreen'; @@ -27,7 +26,6 @@ function createAppContainerWithInitialRoute(initialRoute: string) { Main: createMaterialBottomTabNavigatorWithInitialRoute(initialRoute), // Drawer: MainDrawerNavigator, ProximoListScreen: {screen: ProximoListScreen}, - ProximoSearchScreen: {screen: ProximoSearchScreen}, SettingsScreen: {screen: SettingsScreen}, AboutScreen: {screen: AboutScreen}, AboutDependenciesScreen: {screen: AboutDependenciesScreen}, @@ -44,7 +42,7 @@ function createAppContainerWithInitialRoute(initialRoute: string) { initialRouteName: "Main", mode: 'card', headerMode: "none", - // transitionConfig: () => fromRight(), + transitionConfig: () => fromRight(), }) ); } diff --git a/screens/Proximo/ProximoListScreen.js b/screens/Proximo/ProximoListScreen.js index 8e367c2..3280582 100644 --- a/screens/Proximo/ProximoListScreen.js +++ b/screens/Proximo/ProximoListScreen.js @@ -51,6 +51,7 @@ type State = { sortPriceIcon: React.Node, sortNameIcon: React.Node, modalCurrentDisplayItem: Object, + currentlyDisplayedData: Array, }; /** @@ -59,19 +60,21 @@ type State = { export default class ProximoListScreen extends React.Component { modalRef: { current: null | Modalize }; + originalData: Array; constructor(props: any) { super(props); this.modalRef = React.createRef(); + this.originalData = this.props.navigation.getParam('data', []); } state = { - navData: this.props.navigation.getParam('data', []).sort(sortPrice), + currentlyDisplayedData: this.props.navigation.getParam('data', []).sort(sortPrice), currentSortMode: sortMode.price, isSortReversed: false, sortPriceIcon: '', sortNameIcon: '', - modalCurrentDisplayItem: {} + modalCurrentDisplayItem: {}, }; _menu: Menu; @@ -111,7 +114,7 @@ export default class ProximoListScreen extends React.Component { currentSortMode: mode, isSortReversed: isReverse }); - let data = this.state.navData; + let data = this.state.currentlyDisplayedData; switch (mode) { case sortMode.price: if (isReverse) { @@ -192,6 +195,35 @@ export default class ProximoListScreen extends React.Component { } } + + sanitizeString(str: string) { + return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + } + + /** + * Returns only the articles whose name contains str. Case and accents insensitive. + * @param str + * @returns {[]} + */ + filterData(str: string) { + let filteredData = []; + const testStr = this.sanitizeString(str); + const articles = this.originalData; + for (const article of articles) { + const name = this.sanitizeString(article.name); + if (name.includes(testStr)) { + filteredData.push(article) + } + } + return filteredData; + } + + search(str: string) { + this.setState({ + currentlyDisplayedData: this.filterData(str) + }) + } + getModalContent() { return ( { hasBackButton={true} navigation={nav} hasSearchField={true} - searchCallback={(text) => console.log(text)} + searchCallback={(text) => this.search(text)} rightButton={this.getSortMenu()}/> item.name + item.code} style={{minHeight: 300, width: '100%'}} renderItem={({item}) => diff --git a/screens/Proximo/ProximoMainScreen.js b/screens/Proximo/ProximoMainScreen.js index 586e7b9..f04044f 100644 --- a/screens/Proximo/ProximoMainScreen.js +++ b/screens/Proximo/ProximoMainScreen.js @@ -102,25 +102,13 @@ export default class ProximoMainScreen extends FetchedDataSectionList { getRightButton() { return ( - - this.props.navigation.navigate('ProximoSearchScreen', {data: this.state.fetchedData})}> - - - this.props.navigation.navigate('ProximoAboutScreen')}> - - - + this.props.navigation.navigate('ProximoAboutScreen')}> + + ); } @@ -138,15 +126,16 @@ export default class ProximoMainScreen extends FetchedDataSectionList { {item.type.name} - + {item.data.length} {item.data.length > 1 ? i18n.t('proximoScreen.articles') : i18n.t('proximoScreen.article')} - + diff --git a/screens/Proximo/ProximoSearchScreen.js b/screens/Proximo/ProximoSearchScreen.js deleted file mode 100644 index a03e7e6..0000000 --- a/screens/Proximo/ProximoSearchScreen.js +++ /dev/null @@ -1,115 +0,0 @@ -// @flow - -import * as React from 'react'; -import {Body, Container, Content, Left, ListItem, Right, Text, Thumbnail,} from 'native-base'; -import {FlatList} from "react-native"; -import i18n from "i18n-js"; -import ThemeManager from "../../utils/ThemeManager"; -import CustomHeader from "../../components/CustomHeader"; - -type Props = { - navigation: Object, -}; - -type State = { - filteredData: Array, -}; - -/** - * Class defining proximo's article list of a certain category. - */ -export default class ProximoSearchScreen extends React.Component { - - state = { - filteredData: this.props.navigation.getParam('data', {articles: [{name: "Error"}]}).articles, - }; - - /** - * get color depending on quantity available - * - * @param availableStock - * @return - */ - getStockColor(availableStock: number) { - let color: string; - if (availableStock > 3) - color = ThemeManager.getCurrentThemeVariables().brandSuccess; - else if (availableStock > 0) - color = ThemeManager.getCurrentThemeVariables().brandWarning; - else - color = ThemeManager.getCurrentThemeVariables().brandDanger; - return color; - } - - - showItemDetails(item: Object) { - //TODO: implement onClick function (copy-paste from ProximoListScreen) - } - - /** - * Returns only the articles whose name contains str. Case and accents insensitive. - * @param str - * @returns {[]} - */ - filterData(str: string) { - let filteredData = []; - const testStr: String = str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, ""); - const articles: Object = this.props.navigation.getParam('data', {articles: [{name: "Error"}]}).articles; - for (const article: Object of articles) { - const name: String = String(article.name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "")); - if (name.includes(testStr)) { - filteredData.push(article) - } - } - return filteredData; - } - - search(str: string) { - this.setState({ - filteredData: this.filterData(str) - }) - } - - render() { - return ( - - this.search(text)}/> - - item.name + item.code} - style={{minHeight: 300, width: '100%'}} - renderItem={({item}) => - {this.showItemDetails(item);}} > - - - - - - {item.name} - - - {item.quantity + ' ' + i18n.t('proximoScreen.inStock')} - - - - - {item.price}€ - - - } - /> - - - ); - } -} From fd7567568120462d1a3ad6c663fb7cbc4d3905a9 Mon Sep 17 00:00:00 2001 From: Yohan Simard Date: Fri, 15 Nov 2019 18:41:25 +0100 Subject: [PATCH 6/8] Auto focus search bar when clicking the search icon --- components/CustomHeader.js | 10 +++++++ screens/Proximo/ProximoListScreen.js | 8 +++-- screens/Proximo/ProximoMainScreen.js | 45 +++++++++++++++++++++++----- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/components/CustomHeader.js b/components/CustomHeader.js index 583324a..0a6e4f9 100644 --- a/components/CustomHeader.js +++ b/components/CustomHeader.js @@ -13,6 +13,7 @@ type Props = { hasBackButton: boolean, hasSearchField: boolean, searchCallback: Function, + shouldFocusSearchBar: boolean, leftButton: React.Node, rightButton: React.Node, title: string, @@ -34,12 +35,20 @@ export default class CustomHeader extends React.Component { hasBackButton: false, hasSearchField: false, searchCallback: () => null, + shouldFocusSearchBar: false, title: '', leftButton: , rightButton: , hasTabs: false, }; + searchBarRef: Input; + + componentDidMount() { + if (this.searchBarRef !== undefined && this.props.shouldFocusSearchBar) + this.searchBarRef.focus(); + } + getSearchBar() { return ( { icon={'magnify'} color={ThemeManager.getCurrentThemeVariables().toolbarBtnColor}/> this.searchBarRef = ref} placeholder={i18n.t('proximoScreen.search')} placeholderTextColor={ThemeManager.getCurrentThemeVariables().toolbarPlaceholderColor} onChangeText={(text) => this.props.searchCallback(text)}/> diff --git a/screens/Proximo/ProximoListScreen.js b/screens/Proximo/ProximoListScreen.js index 3280582..cea1dae 100644 --- a/screens/Proximo/ProximoListScreen.js +++ b/screens/Proximo/ProximoListScreen.js @@ -61,15 +61,18 @@ export default class ProximoListScreen extends React.Component { modalRef: { current: null | Modalize }; originalData: Array; + navData = this.props.navigation.getParam('data', []); + shouldFocusSearchBar = this.navData['shouldFocusSearchBar']; constructor(props: any) { super(props); this.modalRef = React.createRef(); - this.originalData = this.props.navigation.getParam('data', []); + this.originalData = this.navData['data']; + } state = { - currentlyDisplayedData: this.props.navigation.getParam('data', []).sort(sortPrice), + currentlyDisplayedData: this.navData['data'].sort(sortPrice), currentSortMode: sortMode.price, isSortReversed: false, sortPriceIcon: '', @@ -310,6 +313,7 @@ export default class ProximoListScreen extends React.Component { navigation={nav} hasSearchField={true} searchCallback={(text) => this.search(text)} + shouldFocusSearchBar={this.shouldFocusSearchBar} rightButton={this.getSortMenu()}/> diff --git a/screens/Proximo/ProximoMainScreen.js b/screens/Proximo/ProximoMainScreen.js index f04044f..6108348 100644 --- a/screens/Proximo/ProximoMainScreen.js +++ b/screens/Proximo/ProximoMainScreen.js @@ -101,25 +101,54 @@ export default class ProximoMainScreen extends FetchedDataSectionList { } getRightButton() { + let searchScreenData = { + shouldFocusSearchBar: true, + data: { + type: { + id: "0", + name: i18n.t('proximoScreen.all'), + icon: 'star' + }, + data: this.getAvailableArticles(this.state.fetchedData.articles, undefined) + }, + }; + + return ( - this.props.navigation.navigate('ProximoAboutScreen')}> - - + + this.props.navigation.navigate('ProximoListScreen', searchScreenData)}> + + + this.props.navigation.navigate('ProximoAboutScreen')}> + + + ); } getRenderItem(item: Object, section: Object, data: Object) { + let dataToSend = { + shouldFocusSearchBar: false, + data: item, + }; if (item.data.length > 0) { return ( { - this.props.navigation.navigate('ProximoListScreen', item); + this.props.navigation.navigate('ProximoListScreen', dataToSend); }} > From ab7e8f92fc9a8932b02e153e8f748a18a6859d14 Mon Sep 17 00:00:00 2001 From: keplyx Date: Fri, 15 Nov 2019 19:29:25 +0100 Subject: [PATCH 7/8] Fixed focusing on search bar not working --- components/CustomHeader.js | 14 ++++++++------ screens/Proximo/ProximoListScreen.js | 5 +---- screens/Proximo/ProximoMainScreen.js | 9 +++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/components/CustomHeader.js b/components/CustomHeader.js index 0a6e4f9..837e8d6 100644 --- a/components/CustomHeader.js +++ b/components/CustomHeader.js @@ -1,7 +1,7 @@ // @flow import * as React from "react"; -import {Body, Header, Input, Item, Left, Right, Title} from "native-base"; +import {Body, Header, Input, Item, Left, Right, Title, Form} from "native-base"; import {Platform, StyleSheet, View} from "react-native"; import {getStatusBarHeight} from "react-native-status-bar-height"; import Touchable from 'react-native-platform-touchable'; @@ -42,15 +42,16 @@ export default class CustomHeader extends React.Component { hasTabs: false, }; - searchBarRef: Input; - componentDidMount() { - if (this.searchBarRef !== undefined && this.props.shouldFocusSearchBar) - this.searchBarRef.focus(); + if (this.refs.searchInput !== undefined && this.props.shouldFocusSearchBar) { + // does not work if called to early for some reason... + setInterval(() => this.refs.searchInput._root.focus(), 500); + } } getSearchBar() { return ( +
{ icon={'magnify'} color={ThemeManager.getCurrentThemeVariables().toolbarBtnColor}/> this.searchBarRef = ref} + ref="searchInput" placeholder={i18n.t('proximoScreen.search')} placeholderTextColor={ThemeManager.getCurrentThemeVariables().toolbarPlaceholderColor} onChangeText={(text) => this.props.searchCallback(text)}/> +
); } diff --git a/screens/Proximo/ProximoListScreen.js b/screens/Proximo/ProximoListScreen.js index cea1dae..7575f8c 100644 --- a/screens/Proximo/ProximoListScreen.js +++ b/screens/Proximo/ProximoListScreen.js @@ -45,7 +45,6 @@ type Props = { } type State = { - navData: Array, currentSortMode: string, isSortReversed: boolean, sortPriceIcon: React.Node, @@ -62,13 +61,12 @@ export default class ProximoListScreen extends React.Component { modalRef: { current: null | Modalize }; originalData: Array; navData = this.props.navigation.getParam('data', []); - shouldFocusSearchBar = this.navData['shouldFocusSearchBar']; + shouldFocusSearchBar = this.props.navigation.getParam('shouldFocusSearchBar', false); constructor(props: any) { super(props); this.modalRef = React.createRef(); this.originalData = this.navData['data']; - } state = { @@ -300,7 +298,6 @@ export default class ProximoListScreen extends React.Component { render() { const nav = this.props.navigation; const navType = nav.getParam('type', '{name: "Error"}'); - return ( , type: ?Object) { + getAvailableArticles(articles: Array, type: ?Object) { let availableArticles = []; for (let k = 0; k < articles.length; k++) { if ((type !== undefined && type !== null && articles[k]['type'].includes(type['id']) @@ -109,7 +109,8 @@ export default class ProximoMainScreen extends FetchedDataSectionList { name: i18n.t('proximoScreen.all'), icon: 'star' }, - data: this.getAvailableArticles(this.state.fetchedData.articles, undefined) + data: this.state.fetchedData.articles !== undefined ? + this.getAvailableArticles(this.state.fetchedData.articles, undefined) : [] }, }; From 4cb0d2f8b5f8922e2d28ded1e3ecfe19a2e33dce Mon Sep 17 00:00:00 2001 From: keplyx Date: Fri, 15 Nov 2019 19:38:43 +0100 Subject: [PATCH 8/8] fixed focus repeating every 500ms --- components/CustomHeader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/CustomHeader.js b/components/CustomHeader.js index 837e8d6..9ca3104 100644 --- a/components/CustomHeader.js +++ b/components/CustomHeader.js @@ -43,9 +43,9 @@ export default class CustomHeader extends React.Component { }; componentDidMount() { - if (this.refs.searchInput !== undefined && this.props.shouldFocusSearchBar) { + if (this.refs.searchInput !== undefined && this.refs.searchInput._root !== undefined && this.props.shouldFocusSearchBar) { // does not work if called to early for some reason... - setInterval(() => this.refs.searchInput._root.focus(), 500); + setTimeout(() => this.refs.searchInput._root.focus(), 500); } }