diff --git a/src/components/Custom/AnimatedBottomBar.js b/src/components/Custom/AnimatedBottomBar.js index 830ca19..49da962 100644 --- a/src/components/Custom/AnimatedBottomBar.js +++ b/src/components/Custom/AnimatedBottomBar.js @@ -2,12 +2,14 @@ import * as React from 'react'; import {StyleSheet, View} from "react-native"; -import {IconButton, Surface, withTheme} from "react-native-paper"; +import {Button, IconButton, Surface, withTheme} from "react-native-paper"; import AutoHideComponent from "./AutoHideComponent"; type Props = { + navigation: Object, theme: Object, onPress: Function, + currentGroup: string, } type State = { @@ -39,6 +41,10 @@ class AnimatedBottomBar extends React.Component { this.displayModeIcons[DISPLAY_MODES.MONTH] = "calendar-range"; } + shouldComponentUpdate(nextProps: Props) { + return (nextProps.currentGroup !== this.props.currentGroup); + } + onScroll = (event: Object) => { this.ref.current.onScroll(event); }; @@ -79,6 +85,13 @@ class AnimatedBottomBar extends React.Component { style={{marginLeft: 5}} onPress={() => this.props.onPress('today', undefined)}/> + { + + state = { + expanded: false, + } + + shouldComponentUpdate(nextProps: Props, nextSate: State) { + if (nextProps.currentSearchString !== this.props.currentSearchString) + this.state.expanded = nextProps.currentSearchString.length > 0; + + return (nextProps.currentSearchString !== this.props.currentSearchString) + || (nextSate.expanded !== this.state.expanded); + } + + onPress = () => this.setState({expanded: !this.state.expanded}); + + keyExtractor = (item: Object) => item.id.toString(); + + renderItem = ({item}: Object) => { + if (stringMatchQuery(item.name, this.props.currentSearchString)) { + const onPress = () => this.props.onGroupPress(item); + return ( + + } + right={props => + } + style={{ + height: LIST_ITEM_HEIGHT, + justifyContent: 'center', + }} + /> + ); + } else + return null; + } + + itemLayout = (data: Object, index: number) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index}); + + render() { + const item = this.props.item; + return ( + + {/*$FlowFixMe*/} + + + ); + } +} \ No newline at end of file diff --git a/src/managers/AsyncStorageManager.js b/src/managers/AsyncStorageManager.js index 9b93502..29c3ed0 100644 --- a/src/managers/AsyncStorageManager.js +++ b/src/managers/AsyncStorageManager.js @@ -79,6 +79,11 @@ export default class AsyncStorageManager { default: '1', current: '', }, + planexCurrentGroup: { + key: 'planexCurrentGroup', + default: '', + current: '', + }, }; /** diff --git a/src/navigation/MainTabNavigator.js b/src/navigation/MainTabNavigator.js index 24d4fd3..4a21a2f 100644 --- a/src/navigation/MainTabNavigator.js +++ b/src/navigation/MainTabNavigator.js @@ -20,6 +20,7 @@ import ScannerScreen from "../screens/ScannerScreen"; import MaterialHeaderButtons, {Item} from "../components/Custom/HeaderButton"; import FeedItemScreen from "../screens/FeedItemScreen"; import {createCollapsibleStack} from "react-navigation-collapsible"; +import GroupSelectionScreen from "../screens/GroupSelectionScreen"; const TAB_ICONS = { @@ -283,6 +284,22 @@ function PlanexStackComponent() { useNativeDriver: false, // native driver does not work with webview } )} + {createCollapsibleStack( + { + return { + title: 'GroupSelectionScreen', + ...TransitionPresets.ModalSlideFromBottomIOS, + }; + }} + />, + { + collapsedColor: 'transparent', + useNativeDriver: true, + } + )} ); } diff --git a/src/screens/GroupSelectionScreen.js b/src/screens/GroupSelectionScreen.js new file mode 100644 index 0000000..2ce3721 --- /dev/null +++ b/src/screens/GroupSelectionScreen.js @@ -0,0 +1,170 @@ +// @flow + +import * as React from 'react'; +import {Platform, View} from "react-native"; +import i18n from "i18n-js"; +import {Searchbar, withTheme} from "react-native-paper"; +import {stringMatchQuery} from "../utils/Search"; +import WebSectionList from "../components/Lists/WebSectionList"; +import GroupListAccordion from "../components/Lists/GroupListAccordion"; + +const LIST_ITEM_HEIGHT = 70; + +type Props = { + navigation: Object, + route: Object, + theme: Object, + collapsibleStack: Object, +} + +type State = { + currentSearchString: string, +}; + +function sortName(a, b) { + if (a.name.toLowerCase() < b.name.toLowerCase()) + return -1; + if (a.name.toLowerCase() > b.name.toLowerCase()) + return 1; + return 0; +} + +const GROUPS_URL = 'http://planex.insa-toulouse.fr/wsAdeGrp.php?projectId=1'; + +/** + * Class defining proximo's article list of a certain category. + */ +class GroupSelectionScreen extends React.Component { + + constructor(props) { + super(props); + this.state = { + currentSearchString: '', + }; + } + + /** + * Creates the header content + */ + componentDidMount() { + this.props.navigation.setOptions({ + headerTitle: this.getSearchBar, + headerBackTitleVisible: false, + headerTitleContainerStyle: Platform.OS === 'ios' ? + {marginHorizontal: 0, width: '70%'} : + {marginHorizontal: 0, right: 50, left: 50}, + }); + } + + /** + * Gets the header search bar + * + * @return {*} + */ + getSearchBar = () => { + return ( + + ); + }; + + /** + * Callback used when the search changes + * + * @param str The new search string + */ + onSearchStringChange = (str: string) => { + this.setState({currentSearchString: str}) + }; + + /** + * Callback used when clicking an article in the list. + * It opens the modal to show detailed information about the article + * + * @param item The article pressed + */ + onListItemPress = (item: Object) => { + this.props.navigation.navigate("planex", { + screen: "index", + params: {group: item} + }); + }; + + shouldDisplayAccordion(item: Object) { + let shouldDisplay = false; + for (let i = 0; i < item.content.length; i++) { + if (stringMatchQuery(item.content[i].name, this.state.currentSearchString)) { + shouldDisplay = true; + break; + } + } + return shouldDisplay; + } + + /** + * Gets a render item for the given article + * + * @param item The article to render + * @return {*} + */ + renderItem = ({item}: Object) => { + if (this.shouldDisplayAccordion(item)) { + return ( + + ); + } else + return null; + }; + + generateData(fetchedData: Object) { + let data = []; + for (let key in fetchedData) { + data.push(fetchedData[key]); + } + data.sort(sortName); + return data; + } + + /** + * Creates the dataset to be used in the FlatList + * + * @param fetchedData + * @return {*} + * */ + createDataset = (fetchedData: Object) => { + return [ + { + title: '', + data: this.generateData(fetchedData) + } + ]; + } + + render() { + return ( + + + + ); + } +} + +export default withTheme(GroupSelectionScreen); diff --git a/src/screens/Websites/PlanexScreen.js b/src/screens/Websites/PlanexScreen.js index 17e1e59..db3be9b 100644 --- a/src/screens/Websites/PlanexScreen.js +++ b/src/screens/Websites/PlanexScreen.js @@ -12,9 +12,11 @@ import {withCollapsible} from "../../utils/withCollapsible"; import {dateToString, getTimeOnlyString} from "../../utils/Planning"; import DateManager from "../../managers/DateManager"; import AnimatedBottomBar from "../../components/Custom/AnimatedBottomBar"; +import {CommonActions} from "@react-navigation/native"; type Props = { navigation: Object, + route: Object, theme: Object, collapsibleStack: Object, } @@ -24,6 +26,7 @@ type State = { dialogVisible: boolean, dialogTitle: string, dialogMessage: string, + currentGroup: Object, } @@ -105,10 +108,13 @@ const LISTEN_TO_MESSAGES = ` document.addEventListener("message", function(event) { //alert(event.data); var data = JSON.parse(event.data); - $('#calendar').fullCalendar(data.action, data.data); + if (data.action === "setGroup") + displayAde(data.data); + else + $('#calendar').fullCalendar(data.action, data.data); }, false);` -const CUSTOM_CSS = "body>.container{padding-top:20px; padding-bottom: 50px}header{display:none}.fc-toolbar .fc-center{width:100%}.fc-toolbar .fc-center>*{float:none;width:100%;margin:0}#entite{margin-bottom:5px!important}#entite,#groupe{width:calc(100% - 20px);margin:0 10px}#calendar .fc-left,#calendar .fc-right{display:none}#groupe_visibility{width:100%}#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-title{font-size:.6rem}#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-time{font-size:.5rem}#calendar .fc-month-view .fc-content-skeleton .fc-title{font-size:.6rem}#calendar .fc-month-view .fc-content-skeleton .fc-time{font-size:.7rem}.fc-axis{font-size:.8rem;width:15px!important}.fc-day-header{font-size:.8rem}.fc-unthemed td.fc-today{background:#be1522; opacity:0.4}"; +const CUSTOM_CSS = "body>.container{padding-top:20px; padding-bottom: 50px}header,#entite,#groupe_visibility,#calendar .fc-left,#calendar .fc-right{display:none}.fc-toolbar .fc-center{width:100%}.fc-toolbar .fc-center>*{float:none;width:100%;margin:0}#entite{margin-bottom:5px!important}#entite,#groupe{width:calc(100% - 20px);margin:0 10px}#groupe_visibility{width:100%}#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-title{font-size:.6rem}#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-time{font-size:.5rem}#calendar .fc-month-view .fc-content-skeleton .fc-title{font-size:.6rem}#calendar .fc-month-view .fc-content-skeleton .fc-time{font-size:.7rem}.fc-axis{font-size:.8rem;width:15px!important}.fc-day-header{font-size:.8rem}.fc-unthemed td.fc-today{background:#be1522; opacity:0.4}"; const CUSTOM_CSS_DARK = "body{background-color:#121212}.fc-unthemed .fc-content,.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-list-view,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#222}.fc-toolbar .fc-center>*,h2,table{color:#fff}.fc-event-container{color:#121212}.fc-event-container .fc-bg{opacity:0.2;background-color:#000}.fc-unthemed td.fc-today{background:#be1522; opacity:0.4}"; const INJECT_STYLE = ` @@ -127,15 +133,6 @@ class PlanexScreen extends React.Component { customInjectedJS: string; - state = { - bannerVisible: - AsyncStorageManager.getInstance().preferences.planexShowBanner.current === '1' && - AsyncStorageManager.getInstance().preferences.defaultStartScreen.current !== 'Planex', - dialogVisible: false, - dialogTitle: "", - dialogMessage: "", - }; - /** * Defines custom injected JavaScript to improve the page display on mobile */ @@ -143,16 +140,58 @@ class PlanexScreen extends React.Component { super(); this.webScreenRef = React.createRef(); this.barRef = React.createRef(); - this.generateInjectedCSS(); + + let currentGroup = AsyncStorageManager.getInstance().preferences.planexCurrentGroup.current; + if (currentGroup === '') + currentGroup = {name: "SELECT GROUP", id: 0}; + else + currentGroup = JSON.parse(currentGroup); + this.state = { + bannerVisible: + AsyncStorageManager.getInstance().preferences.planexShowBanner.current === '1' && + AsyncStorageManager.getInstance().preferences.defaultStartScreen.current !== 'Planex', + dialogVisible: false, + dialogTitle: "", + dialogMessage: "", + currentGroup: currentGroup, + }; + this.generateInjectedJS(currentGroup.id); } - generateInjectedCSS() { - this.customInjectedJS = - "$(document).ready(function() {" + - OBSERVE_MUTATIONS_INJECTED + - FULL_CALENDAR_SETTINGS + - LISTEN_TO_MESSAGES + - INJECT_STYLE; + componentDidMount() { + this.props.navigation.addListener('focus', this.onScreenFocus); + } + + onScreenFocus = () => { + this.handleNavigationParams(); + }; + + handleNavigationParams = () => { + if (this.props.route.params !== undefined) { + if (this.props.route.params.group !== undefined && this.props.route.params.group !== null) { + // reset params to prevent infinite loop + this.selectNewGroup(this.props.route.params.group); + this.props.navigation.dispatch(CommonActions.setParams({group: null})); + } + } + }; + + selectNewGroup(group: Object) { + this.sendMessage('setGroup', group.id); + this.setState({currentGroup: group}); + AsyncStorageManager.getInstance().savePref( + AsyncStorageManager.getInstance().preferences.planexCurrentGroup.key, + JSON.stringify(group)); + this.generateInjectedJS(group.id); + } + + generateInjectedJS(groupID: number) { + this.customInjectedJS = "$(document).ready(function() {" + + OBSERVE_MUTATIONS_INJECTED + + FULL_CALENDAR_SETTINGS + + "displayAde(" + groupID + ");" // Reset Ade + + LISTEN_TO_MESSAGES + + INJECT_STYLE; if (ThemeManager.getNightMode()) this.customInjectedJS += "$('head').append('');"; @@ -162,10 +201,10 @@ class PlanexScreen extends React.Component { '});true;'; // Prevents crash on ios } - componentWillUpdate(prevProps: Props) { - if (prevProps.theme.dark !== this.props.theme.dark) - this.generateInjectedCSS(); - } + // componentWillUpdate(prevProps: Props) { + // if (prevProps.theme.dark !== this.props.theme.dark) + // this.generateInjectedCSS(); + // } /** * Callback used when closing the banner. @@ -269,8 +308,10 @@ class PlanexScreen extends React.Component { ? this.getWebView() : {this.getWebView()}} ); diff --git a/src/utils/Search.js b/src/utils/Search.js index a9cfa90..501cf58 100644 --- a/src/utils/Search.js +++ b/src/utils/Search.js @@ -11,7 +11,8 @@ export function sanitizeString(str: string): string { return str.toLowerCase() .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") - .replace(" ", ""); + .replace(/ /g, "") + .replace(/_/g, ""); } export function stringMatchQuery(str: string, query: string) {