Browse Source

Sync planex bottom bar animation with scroll

Arnaud Vergnet 2 years ago
parent
commit
8506d3d81f

+ 0
- 211
src/components/Animations/AnimatedBottomBar.tsx View File

1
-/*
2
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
3
- *
4
- * This file is part of Campus INSAT.
5
- *
6
- * Campus INSAT is free software: you can redistribute it and/or modify
7
- *  it under the terms of the GNU General Public License as published by
8
- * the Free Software Foundation, either version 3 of the License, or
9
- * (at your option) any later version.
10
- *
11
- * Campus INSAT is distributed in the hope that it will be useful,
12
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
- * GNU General Public License for more details.
15
- *
16
- * You should have received a copy of the GNU General Public License
17
- * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
- */
19
-
20
-import * as React from 'react';
21
-import {
22
-  NativeScrollEvent,
23
-  NativeSyntheticEvent,
24
-  StyleSheet,
25
-  View,
26
-} from 'react-native';
27
-import { FAB, IconButton, Surface, withTheme } from 'react-native-paper';
28
-import * as Animatable from 'react-native-animatable';
29
-import { StackNavigationProp } from '@react-navigation/stack';
30
-import AutoHideHandler from '../../utils/AutoHideHandler';
31
-import { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
32
-
33
-type PropsType = {
34
-  navigation: StackNavigationProp<any>;
35
-  theme: ReactNativePaper.Theme;
36
-  onPress: (action: string, data?: string) => void;
37
-  seekAttention: boolean;
38
-};
39
-
40
-type StateType = {
41
-  currentMode: string;
42
-};
43
-
44
-const DISPLAY_MODES = {
45
-  DAY: 'agendaDay',
46
-  WEEK: 'agendaWeek',
47
-  MONTH: 'month',
48
-};
49
-
50
-const styles = StyleSheet.create({
51
-  container: {
52
-    position: 'absolute',
53
-    left: '5%',
54
-    width: '90%',
55
-  },
56
-  surface: {
57
-    position: 'relative',
58
-    flexDirection: 'row',
59
-    justifyContent: 'space-between',
60
-    alignItems: 'center',
61
-    borderRadius: 50,
62
-    elevation: 2,
63
-  },
64
-  fabContainer: {
65
-    position: 'absolute',
66
-    left: 0,
67
-    right: 0,
68
-    alignItems: 'center',
69
-    width: '100%',
70
-    height: '100%',
71
-  },
72
-  fab: {
73
-    position: 'absolute',
74
-    alignSelf: 'center',
75
-    top: '-25%',
76
-  },
77
-  side: {
78
-    flexDirection: 'row',
79
-  },
80
-  icon: {
81
-    marginLeft: 5,
82
-  },
83
-});
84
-
85
-class AnimatedBottomBar extends React.Component<PropsType, StateType> {
86
-  ref: { current: null | (Animatable.View & View) };
87
-
88
-  hideHandler: AutoHideHandler;
89
-
90
-  displayModeIcons: { [key: string]: string };
91
-
92
-  constructor(props: PropsType) {
93
-    super(props);
94
-    this.state = {
95
-      currentMode: DISPLAY_MODES.WEEK,
96
-    };
97
-    this.ref = React.createRef();
98
-    this.hideHandler = new AutoHideHandler(false);
99
-    this.hideHandler.addListener(this.onHideChange);
100
-
101
-    this.displayModeIcons = {};
102
-    this.displayModeIcons[DISPLAY_MODES.DAY] = 'calendar-text';
103
-    this.displayModeIcons[DISPLAY_MODES.WEEK] = 'calendar-week';
104
-    this.displayModeIcons[DISPLAY_MODES.MONTH] = 'calendar-range';
105
-  }
106
-
107
-  shouldComponentUpdate(nextProps: PropsType, nextState: StateType): boolean {
108
-    const { props, state } = this;
109
-    return (
110
-      nextProps.seekAttention !== props.seekAttention ||
111
-      nextState.currentMode !== state.currentMode
112
-    );
113
-  }
114
-
115
-  onHideChange = (shouldHide: boolean) => {
116
-    const ref = this.ref;
117
-    if (ref && ref.current && ref.current.fadeOutDown && ref.current.fadeInUp) {
118
-      if (shouldHide) {
119
-        ref.current.fadeOutDown(500);
120
-      } else {
121
-        ref.current.fadeInUp(500);
122
-      }
123
-    }
124
-  };
125
-
126
-  onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
127
-    this.hideHandler.onScroll(event);
128
-  };
129
-
130
-  changeDisplayMode = () => {
131
-    const { props, state } = this;
132
-    let newMode;
133
-    switch (state.currentMode) {
134
-      case DISPLAY_MODES.DAY:
135
-        newMode = DISPLAY_MODES.WEEK;
136
-        break;
137
-      case DISPLAY_MODES.WEEK:
138
-        newMode = DISPLAY_MODES.MONTH;
139
-        break;
140
-      case DISPLAY_MODES.MONTH:
141
-        newMode = DISPLAY_MODES.DAY;
142
-        break;
143
-      default:
144
-        newMode = DISPLAY_MODES.WEEK;
145
-        break;
146
-    }
147
-    this.setState({ currentMode: newMode });
148
-    props.onPress('changeView', newMode);
149
-  };
150
-
151
-  render() {
152
-    const { props, state } = this;
153
-    const buttonColor = props.theme.colors.primary;
154
-    return (
155
-      <Animatable.View
156
-        ref={this.ref}
157
-        useNativeDriver
158
-        style={{
159
-          ...styles.container,
160
-          bottom: 10 + TAB_BAR_HEIGHT,
161
-        }}
162
-      >
163
-        <Surface style={styles.surface}>
164
-          <View style={styles.fabContainer}>
165
-            <Animatable.View
166
-              style={styles.fab}
167
-              animation={props.seekAttention ? 'bounce' : undefined}
168
-              easing={'ease-out'}
169
-              iterationDelay={500}
170
-              iterationCount={'infinite'}
171
-              useNativeDriver={true}
172
-            >
173
-              <FAB
174
-                icon={'account-clock'}
175
-                onPress={() => props.navigation.navigate('group-select')}
176
-              />
177
-            </Animatable.View>
178
-          </View>
179
-          <View style={styles.side}>
180
-            <IconButton
181
-              icon={this.displayModeIcons[state.currentMode]}
182
-              color={buttonColor}
183
-              onPress={this.changeDisplayMode}
184
-            />
185
-            <IconButton
186
-              icon="clock-in"
187
-              color={buttonColor}
188
-              style={styles.icon}
189
-              onPress={() => props.onPress('today')}
190
-            />
191
-          </View>
192
-          <View style={styles.side}>
193
-            <IconButton
194
-              icon="chevron-left"
195
-              color={buttonColor}
196
-              onPress={() => props.onPress('prev')}
197
-            />
198
-            <IconButton
199
-              icon="chevron-right"
200
-              color={buttonColor}
201
-              style={styles.icon}
202
-              onPress={() => props.onPress('next')}
203
-            />
204
-          </View>
205
-        </Surface>
206
-      </Animatable.View>
207
-    );
208
-  }
209
-}
210
-
211
-export default withTheme(AnimatedBottomBar);

+ 176
- 0
src/components/Animations/PlanexBottomBar.tsx View File

1
+/*
2
+ * Copyright (c) 2019 - 2020 Arnaud Vergnet.
3
+ *
4
+ * This file is part of Campus INSAT.
5
+ *
6
+ * Campus INSAT is free software: you can redistribute it and/or modify
7
+ *  it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * Campus INSAT is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
+ */
19
+
20
+import React, { useState } from 'react';
21
+import { StyleSheet, View, Animated } from 'react-native';
22
+import { FAB, IconButton, Surface, useTheme } from 'react-native-paper';
23
+import * as Animatable from 'react-native-animatable';
24
+import { TAB_BAR_HEIGHT } from '../Tabbar/CustomTabBar';
25
+import { useNavigation } from '@react-navigation/core';
26
+import { useCollapsible } from '../../utils/CollapsibleContext';
27
+
28
+type Props = {
29
+  onPress: (action: string, data?: string) => void;
30
+  seekAttention: boolean;
31
+};
32
+
33
+const DISPLAY_MODES = {
34
+  DAY: 'agendaDay',
35
+  WEEK: 'agendaWeek',
36
+  MONTH: 'month',
37
+};
38
+
39
+const styles = StyleSheet.create({
40
+  container: {
41
+    position: 'absolute',
42
+    left: '5%',
43
+    width: '90%',
44
+  },
45
+  surface: {
46
+    position: 'relative',
47
+    flexDirection: 'row',
48
+    justifyContent: 'space-between',
49
+    alignItems: 'center',
50
+    borderRadius: 50,
51
+    elevation: 2,
52
+  },
53
+  fabContainer: {
54
+    position: 'absolute',
55
+    left: 0,
56
+    right: 0,
57
+    alignItems: 'center',
58
+    width: '100%',
59
+    height: '100%',
60
+  },
61
+  fab: {
62
+    position: 'absolute',
63
+    alignSelf: 'center',
64
+    top: '-25%',
65
+  },
66
+  side: {
67
+    flexDirection: 'row',
68
+  },
69
+  icon: {
70
+    marginLeft: 5,
71
+  },
72
+});
73
+
74
+const DISPLAY_MODE_ICONS = {
75
+  [DISPLAY_MODES.DAY]: 'calendar-text',
76
+  [DISPLAY_MODES.WEEK]: 'calendar-week',
77
+  [DISPLAY_MODES.MONTH]: 'calendar-range',
78
+};
79
+
80
+function PlanexBottomBar(props: Props) {
81
+  const navigation = useNavigation();
82
+  const theme = useTheme();
83
+  const [currentMode, setCurrentMode] = useState(DISPLAY_MODES.WEEK);
84
+
85
+  const { collapsible } = useCollapsible();
86
+
87
+  const changeDisplayMode = () => {
88
+    let newMode;
89
+    switch (currentMode) {
90
+      case DISPLAY_MODES.DAY:
91
+        newMode = DISPLAY_MODES.WEEK;
92
+        break;
93
+      case DISPLAY_MODES.WEEK:
94
+        newMode = DISPLAY_MODES.MONTH;
95
+        break;
96
+      case DISPLAY_MODES.MONTH:
97
+        newMode = DISPLAY_MODES.DAY;
98
+        break;
99
+      default:
100
+        newMode = DISPLAY_MODES.WEEK;
101
+        break;
102
+    }
103
+    setCurrentMode(newMode);
104
+    props.onPress('changeView', newMode);
105
+  };
106
+
107
+  let translateY: number | Animated.AnimatedInterpolation = 0;
108
+  let opacity: number | Animated.AnimatedInterpolation = 1;
109
+  let scale: number | Animated.AnimatedInterpolation = 1;
110
+  if (collapsible) {
111
+    translateY = Animated.multiply(-3, collapsible.translateY);
112
+    opacity = Animated.subtract(1, collapsible.progress);
113
+    scale = Animated.add(
114
+      0.5,
115
+      Animated.multiply(0.5, Animated.subtract(1, collapsible.progress))
116
+    );
117
+  }
118
+
119
+  const buttonColor = theme.colors.primary;
120
+  return (
121
+    <Animated.View
122
+      style={{
123
+        ...styles.container,
124
+        bottom: 10 + TAB_BAR_HEIGHT,
125
+        transform: [{ translateY: translateY }, { scale: scale }],
126
+        opacity: opacity,
127
+      }}
128
+    >
129
+      <Surface style={styles.surface}>
130
+        <View style={styles.fabContainer}>
131
+          <Animatable.View
132
+            style={styles.fab}
133
+            animation={props.seekAttention ? 'bounce' : undefined}
134
+            easing={'ease-out'}
135
+            iterationDelay={500}
136
+            iterationCount={'infinite'}
137
+            useNativeDriver={true}
138
+          >
139
+            <FAB
140
+              icon={'account-clock'}
141
+              onPress={() => navigation.navigate('group-select')}
142
+            />
143
+          </Animatable.View>
144
+        </View>
145
+        <View style={styles.side}>
146
+          <IconButton
147
+            icon={DISPLAY_MODE_ICONS[currentMode]}
148
+            color={buttonColor}
149
+            onPress={changeDisplayMode}
150
+          />
151
+          <IconButton
152
+            icon="clock-in"
153
+            color={buttonColor}
154
+            style={styles.icon}
155
+            onPress={() => props.onPress('today')}
156
+          />
157
+        </View>
158
+        <View style={styles.side}>
159
+          <IconButton
160
+            icon="chevron-left"
161
+            color={buttonColor}
162
+            onPress={() => props.onPress('prev')}
163
+          />
164
+          <IconButton
165
+            icon="chevron-right"
166
+            color={buttonColor}
167
+            style={styles.icon}
168
+            onPress={() => props.onPress('next')}
169
+          />
170
+        </View>
171
+      </Surface>
172
+    </Animated.View>
173
+  );
174
+}
175
+
176
+export default PlanexBottomBar;

+ 119
- 0
src/components/Screens/PlanexWebview.tsx View File

1
+import React from 'react';
2
+import { View } from 'react-native';
3
+import GENERAL_STYLES from '../../constants/Styles';
4
+import Urls from '../../constants/Urls';
5
+import DateManager from '../../managers/DateManager';
6
+import ThemeManager from '../../managers/ThemeManager';
7
+import { PlanexGroupType } from '../../screens/Planex/GroupSelectionScreen';
8
+import ErrorView from './ErrorView';
9
+import WebViewScreen from './WebViewScreen';
10
+import i18n from 'i18n-js';
11
+
12
+type Props = {
13
+  currentGroup?: PlanexGroupType;
14
+  injectJS: string;
15
+  onMessage: (event: { nativeEvent: { data: string } }) => void;
16
+};
17
+
18
+// Watch for changes in the calendar and call the remove alpha function to prevent invisible events
19
+const OBSERVE_MUTATIONS_INJECTED =
20
+  'function removeAlpha(node) {\n' +
21
+  '    let bg = node.css("background-color");\n' +
22
+  '    if (bg.match("^rgba")) {\n' +
23
+  "        let a = bg.slice(5).split(',');\n" +
24
+  '        // Fix for tooltips with broken background\n' +
25
+  '        if (parseInt(a[0]) === parseInt(a[1]) && parseInt(a[1]) === parseInt(a[2]) && parseInt(a[2]) === 0) {\n' +
26
+  "            a[0] = a[1] = a[2] = '255';\n" +
27
+  '        }\n' +
28
+  "        let newBg ='rgb(' + a[0] + ',' + a[1] + ',' + a[2] + ')';\n" +
29
+  '        node.css("background-color", newBg);\n' +
30
+  '    }\n' +
31
+  '}\n' +
32
+  '// Observe for planning DOM changes\n' +
33
+  'let observer = new MutationObserver(function(mutations) {\n' +
34
+  '    for (let i = 0; i < mutations.length; i++) {\n' +
35
+  "        if (mutations[i]['addedNodes'].length > 0 &&\n" +
36
+  '            ($(mutations[i][\'addedNodes\'][0]).hasClass("fc-event") || $(mutations[i][\'addedNodes\'][0]).hasClass("tooltiptopicevent")))\n' +
37
+  "            removeAlpha($(mutations[i]['addedNodes'][0]))\n" +
38
+  '    }\n' +
39
+  '});\n' +
40
+  '// observer.observe(document.querySelector(".fc-body"), {attributes: false, childList: true, characterData: false, subtree:true});\n' +
41
+  'observer.observe(document.querySelector("body"), {attributes: false, childList: true, characterData: false, subtree:true});\n' +
42
+  '// Run remove alpha a first time on whole planning. Useful when code injected after planning fully loaded.\n' +
43
+  '$(".fc-event-container .fc-event").each(function(index) {\n' +
44
+  '    removeAlpha($(this));\n' +
45
+  '});';
46
+
47
+// Overrides default settings to send a message to the webview when clicking on an event
48
+const FULL_CALENDAR_SETTINGS = `
49
+let calendar = $('#calendar').fullCalendar('getCalendar');
50
+calendar.option({
51
+  eventClick: function (data, event, view) {
52
+      let message = {
53
+      title: data.title,
54
+      color: data.color,
55
+      start: data.start._d,
56
+      end: data.end._d,
57
+    };
58
+   window.ReactNativeWebView.postMessage(JSON.stringify(message));
59
+  }
60
+});`;
61
+
62
+// Mobile friendly CSS
63
+const CUSTOM_CSS =
64
+  'body>.container{padding-top:20px; padding-bottom: 50px}header,#entite,#groupe_visibility,#calendar .fc-left,#calendar .fc-right{display:none}#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}';
65
+
66
+// Dark mode CSS, to be used with the mobile friendly css
67
+const CUSTOM_CSS_DARK =
68
+  '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}';
69
+
70
+// Inject the custom css into the webpage
71
+const INJECT_STYLE = `$('head').append('<style>${CUSTOM_CSS}</style>');`;
72
+
73
+// Inject the dark mode into the webpage, to call after the custom css inject above
74
+const INJECT_STYLE_DARK = `$('head').append('<style>${CUSTOM_CSS_DARK}</style>');`;
75
+
76
+/**
77
+ * Generates custom JavaScript to be injected into the webpage
78
+ *
79
+ * @param groupID The current group selected
80
+ */
81
+const generateInjectedJS = (group: PlanexGroupType | undefined) => {
82
+  let customInjectedJS = `$(document).ready(function() {
83
+      ${OBSERVE_MUTATIONS_INJECTED}
84
+      ${INJECT_STYLE}
85
+      ${FULL_CALENDAR_SETTINGS}`;
86
+  if (group) {
87
+    customInjectedJS += `displayAde(${group.id});`;
88
+  }
89
+  if (DateManager.isWeekend(new Date())) {
90
+    customInjectedJS += `calendar.next();`;
91
+  }
92
+  if (ThemeManager.getNightMode()) {
93
+    customInjectedJS += INJECT_STYLE_DARK;
94
+  }
95
+  customInjectedJS += 'removeAlpha();});true;'; // Prevents crash on ios
96
+  return customInjectedJS;
97
+};
98
+
99
+function PlanexWebview(props: Props) {
100
+  return (
101
+    <View style={GENERAL_STYLES.flex}>
102
+      {!props.currentGroup ? (
103
+        <ErrorView
104
+          icon={'account-clock'}
105
+          message={i18n.t('screens.planex.noGroupSelected')}
106
+        />
107
+      ) : null}
108
+      <WebViewScreen
109
+        url={Urls.planex.planning}
110
+        initialJS={generateInjectedJS(props.currentGroup)}
111
+        injectJS={props.injectJS}
112
+        onMessage={props.onMessage}
113
+        showAdvancedControls={false}
114
+      />
115
+    </View>
116
+  );
117
+}
118
+
119
+export default PlanexWebview;

+ 17
- 158
src/screens/Planex/PlanexScreen.tsx View File

17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
  */
18
  */
19
 
19
 
20
-import React, { useCallback, useRef, useState } from 'react';
20
+import React, { useCallback, useState } from 'react';
21
 import { Title, useTheme } from 'react-native-paper';
21
 import { Title, useTheme } from 'react-native-paper';
22
 import i18n from 'i18n-js';
22
 import i18n from 'i18n-js';
23
-import {
24
-  NativeScrollEvent,
25
-  NativeSyntheticEvent,
26
-  StyleSheet,
27
-  View,
28
-} from 'react-native';
23
+import { StyleSheet, View } from 'react-native';
29
 import {
24
 import {
30
   CommonActions,
25
   CommonActions,
31
   useFocusEffect,
26
   useFocusEffect,
32
   useNavigation,
27
   useNavigation,
33
 } from '@react-navigation/native';
28
 } from '@react-navigation/native';
34
 import Autolink from 'react-native-autolink';
29
 import Autolink from 'react-native-autolink';
35
-import ThemeManager from '../../managers/ThemeManager';
36
-import WebViewScreen from '../../components/Screens/WebViewScreen';
37
 import AsyncStorageManager from '../../managers/AsyncStorageManager';
30
 import AsyncStorageManager from '../../managers/AsyncStorageManager';
38
 import AlertDialog from '../../components/Dialogs/AlertDialog';
31
 import AlertDialog from '../../components/Dialogs/AlertDialog';
39
 import { dateToString, getTimeOnlyString } from '../../utils/Planning';
32
 import { dateToString, getTimeOnlyString } from '../../utils/Planning';
40
 import DateManager from '../../managers/DateManager';
33
 import DateManager from '../../managers/DateManager';
41
-import AnimatedBottomBar from '../../components/Animations/AnimatedBottomBar';
42
-import ErrorView from '../../components/Screens/ErrorView';
43
 import type { PlanexGroupType } from './GroupSelectionScreen';
34
 import type { PlanexGroupType } from './GroupSelectionScreen';
44
 import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
35
 import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
45
 import MascotPopup from '../../components/Mascot/MascotPopup';
36
 import MascotPopup from '../../components/Mascot/MascotPopup';
46
 import { getPrettierPlanexGroupName } from '../../utils/Utils';
37
 import { getPrettierPlanexGroupName } from '../../utils/Utils';
47
 import GENERAL_STYLES from '../../constants/Styles';
38
 import GENERAL_STYLES from '../../constants/Styles';
48
-import Urls from '../../constants/Urls';
49
-
50
-// // JS + JQuery functions used to remove alpha from events. Copy paste in browser console for quick testing
51
-// // Remove alpha from given Jquery node
52
-// function removeAlpha(node) {
53
-//     let bg = node.css("background-color");
54
-//     if (bg.match("^rgba")) {
55
-//         let a = bg.slice(5).split(',');
56
-//         // Fix for tooltips with broken background
57
-//         if (parseInt(a[0]) === parseInt(a[1]) && parseInt(a[1]) === parseInt(a[2]) && parseInt(a[2]) === 0) {
58
-//             a[0] = a[1] = a[2] = '255';
59
-//         }
60
-//         let newBg ='rgb(' + a[0] + ',' + a[1] + ',' + a[2] + ')';
61
-//         node.css("background-color", newBg);
62
-//     }
63
-// }
64
-// // Observe for planning DOM changes
65
-// let observer = new MutationObserver(function(mutations) {
66
-//     for (let i = 0; i < mutations.length; i++) {
67
-//         if (mutations[i]['addedNodes'].length > 0 &&
68
-//             ($(mutations[i]['addedNodes'][0]).hasClass("fc-event") || $(mutations[i]['addedNodes'][0]).hasClass("tooltiptopicevent")))
69
-//             removeAlpha($(mutations[i]['addedNodes'][0]))
70
-//     }
71
-// });
72
-// // observer.observe(document.querySelector(".fc-body"), {attributes: false, childList: true, characterData: false, subtree:true});
73
-// observer.observe(document.querySelector("body"), {attributes: false, childList: true, characterData: false, subtree:true});
74
-// // Run remove alpha a first time on whole planning. Useful when code injected after planning fully loaded.
75
-// $(".fc-event-container .fc-event").each(function(index) {
76
-//     removeAlpha($(this));
77
-// });
78
-
79
-// Watch for changes in the calendar and call the remove alpha function to prevent invisible events
80
-const OBSERVE_MUTATIONS_INJECTED =
81
-  'function removeAlpha(node) {\n' +
82
-  '    let bg = node.css("background-color");\n' +
83
-  '    if (bg.match("^rgba")) {\n' +
84
-  "        let a = bg.slice(5).split(',');\n" +
85
-  '        // Fix for tooltips with broken background\n' +
86
-  '        if (parseInt(a[0]) === parseInt(a[1]) && parseInt(a[1]) === parseInt(a[2]) && parseInt(a[2]) === 0) {\n' +
87
-  "            a[0] = a[1] = a[2] = '255';\n" +
88
-  '        }\n' +
89
-  "        let newBg ='rgb(' + a[0] + ',' + a[1] + ',' + a[2] + ')';\n" +
90
-  '        node.css("background-color", newBg);\n' +
91
-  '    }\n' +
92
-  '}\n' +
93
-  '// Observe for planning DOM changes\n' +
94
-  'let observer = new MutationObserver(function(mutations) {\n' +
95
-  '    for (let i = 0; i < mutations.length; i++) {\n' +
96
-  "        if (mutations[i]['addedNodes'].length > 0 &&\n" +
97
-  '            ($(mutations[i][\'addedNodes\'][0]).hasClass("fc-event") || $(mutations[i][\'addedNodes\'][0]).hasClass("tooltiptopicevent")))\n' +
98
-  "            removeAlpha($(mutations[i]['addedNodes'][0]))\n" +
99
-  '    }\n' +
100
-  '});\n' +
101
-  '// observer.observe(document.querySelector(".fc-body"), {attributes: false, childList: true, characterData: false, subtree:true});\n' +
102
-  'observer.observe(document.querySelector("body"), {attributes: false, childList: true, characterData: false, subtree:true});\n' +
103
-  '// Run remove alpha a first time on whole planning. Useful when code injected after planning fully loaded.\n' +
104
-  '$(".fc-event-container .fc-event").each(function(index) {\n' +
105
-  '    removeAlpha($(this));\n' +
106
-  '});';
107
-
108
-// Overrides default settings to send a message to the webview when clicking on an event
109
-const FULL_CALENDAR_SETTINGS = `
110
-let calendar = $('#calendar').fullCalendar('getCalendar');
111
-calendar.option({
112
-  eventClick: function (data, event, view) {
113
-      let message = {
114
-      title: data.title,
115
-      color: data.color,
116
-      start: data.start._d,
117
-      end: data.end._d,
118
-    };
119
-   window.ReactNativeWebView.postMessage(JSON.stringify(message));
120
-  }
121
-});`;
122
-
123
-// Mobile friendly CSS
124
-const CUSTOM_CSS =
125
-  'body>.container{padding-top:20px; padding-bottom: 50px}header,#entite,#groupe_visibility,#calendar .fc-left,#calendar .fc-right{display:none}#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}';
126
-
127
-// Dark mode CSS, to be used with the mobile friendly css
128
-const CUSTOM_CSS_DARK =
129
-  '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}';
130
-
131
-// Inject the custom css into the webpage
132
-const INJECT_STYLE = `$('head').append('<style>${CUSTOM_CSS}</style>');`;
133
-
134
-// Inject the dark mode into the webpage, to call after the custom css inject above
135
-const INJECT_STYLE_DARK = `$('head').append('<style>${CUSTOM_CSS_DARK}</style>');`;
39
+import PlanexWebview from '../../components/Screens/PlanexWebview';
40
+import PlanexBottomBar from '../../components/Animations/PlanexBottomBar';
136
 
41
 
137
 const styles = StyleSheet.create({
42
 const styles = StyleSheet.create({
138
   container: {
43
   container: {
140
     height: '100%',
45
     height: '100%',
141
     width: '100%',
46
     width: '100%',
142
   },
47
   },
48
+  popup: {
49
+    borderWidth: 2,
50
+  },
143
 });
51
 });
144
 
52
 
145
 type Props = {
53
 type Props = {
153
 function PlanexScreen(props: Props) {
61
 function PlanexScreen(props: Props) {
154
   const navigation = useNavigation();
62
   const navigation = useNavigation();
155
   const theme = useTheme();
63
   const theme = useTheme();
156
-  const barRef = useRef<typeof AnimatedBottomBar>();
157
 
64
 
158
   const [dialogContent, setDialogContent] = useState<
65
   const [dialogContent, setDialogContent] = useState<
159
     | undefined
66
     | undefined
199
    *
106
    *
200
    * @returns {*}
107
    * @returns {*}
201
    */
108
    */
202
-  const getWebView = () => {
203
-    return (
204
-      <View style={GENERAL_STYLES.flex}>
205
-        {!currentGroup ? (
206
-          <ErrorView
207
-            icon={'account-clock'}
208
-            message={i18n.t('screens.planex.noGroupSelected')}
209
-          />
210
-        ) : null}
211
-        <WebViewScreen
212
-          url={Urls.planex.planning}
213
-          initialJS={generateInjectedJS(currentGroup)}
214
-          injectJS={injectJS}
215
-          onMessage={onMessage}
216
-          onScroll={onScroll}
217
-          showAdvancedControls={false}
218
-        />
219
-      </View>
220
-    );
221
-  };
109
+  const getWebView = () => (
110
+    <PlanexWebview
111
+      currentGroup={currentGroup}
112
+      injectJS={injectJS}
113
+      onMessage={onMessage}
114
+    />
115
+  );
222
 
116
 
223
   /**
117
   /**
224
    * Callback used when the user clicks on the navigate to settings button.
118
    * Callback used when the user clicks on the navigate to settings button.
301
   const hideDialog = () => setDialogContent(undefined);
195
   const hideDialog = () => setDialogContent(undefined);
302
 
196
 
303
   /**
197
   /**
304
-   * Binds the onScroll event to the control bar for automatic hiding based on scroll direction and speed
305
-   *
306
-   * @param event
307
-   */
308
-  const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
309
-    if (barRef.current) {
310
-      barRef.current.onScroll(event);
311
-    }
312
-  };
313
-
314
-  /**
315
    * Sends the webpage a message with the new group to select and save it to preferences
198
    * Sends the webpage a message with the new group to select and save it to preferences
316
    *
199
    *
317
    * @param group The group object selected
200
    * @param group The group object selected
323
       AsyncStorageManager.PREFERENCES.planexCurrentGroup.key,
206
       AsyncStorageManager.PREFERENCES.planexCurrentGroup.key,
324
       group
207
       group
325
     );
208
     );
326
-    navigation.setOptions({ title: getPrettierPlanexGroupName(group.name) });
327
-  };
328
 
209
 
329
-  /**
330
-   * Generates custom JavaScript to be injected into the webpage
331
-   *
332
-   * @param groupID The current group selected
333
-   */
334
-  const generateInjectedJS = (group: PlanexGroupType | undefined) => {
335
-    let customInjectedJS = `$(document).ready(function() {
336
-      ${OBSERVE_MUTATIONS_INJECTED}
337
-      ${INJECT_STYLE}
338
-      ${FULL_CALENDAR_SETTINGS}`;
339
-    if (group) {
340
-      customInjectedJS += `displayAde(${group.id});`;
341
-    }
342
-    if (DateManager.isWeekend(new Date())) {
343
-      customInjectedJS += `calendar.next();`;
344
-    }
345
-    if (ThemeManager.getNightMode()) {
346
-      customInjectedJS += INJECT_STYLE_DARK;
347
-    }
348
-    customInjectedJS += 'removeAlpha();});true;'; // Prevents crash on ios
349
-    return customInjectedJS;
210
+    navigation.setOptions({ title: getPrettierPlanexGroupName(group.name) });
350
   };
211
   };
351
 
212
 
352
   return (
213
   return (
389
         message={dialogContent ? dialogContent.message : ''}
250
         message={dialogContent ? dialogContent.message : ''}
390
         style={
251
         style={
391
           dialogContent
252
           dialogContent
392
-            ? { borderColor: dialogContent.color, borderWidth: 2 }
253
+            ? { borderColor: dialogContent.color, ...styles.popup }
393
             : undefined
254
             : undefined
394
         }
255
         }
395
       />
256
       />
396
-      <AnimatedBottomBar
397
-        navigation={navigation}
398
-        ref={barRef}
257
+      <PlanexBottomBar
399
         onPress={sendMessage}
258
         onPress={sendMessage}
400
         seekAttention={currentGroup === undefined}
259
         seekAttention={currentGroup === undefined}
401
       />
260
       />

+ 19
- 0
src/utils/customHooks.tsx View File

1
+/*
2
+ * Copyright (c) 2019 - 2020 Arnaud Vergnet.
3
+ *
4
+ * This file is part of Campus INSAT.
5
+ *
6
+ * Campus INSAT is free software: you can redistribute it and/or modify
7
+ *  it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * Campus INSAT is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
+ */
19
+
1
 import { DependencyList, useEffect, useRef, useState } from 'react';
20
 import { DependencyList, useEffect, useRef, useState } from 'react';
2
 import { REQUEST_STATUS } from './Requests';
21
 import { REQUEST_STATUS } from './Requests';
3
 
22
 

Loading…
Cancel
Save