@olea-bps/views 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/Callmanager/index.js +148 -0
  2. package/Callmanager/styles.js +62 -0
  3. package/Canteens/index.js +383 -0
  4. package/Canteens/styles.js +89 -0
  5. package/Dashboard/index.js +176 -0
  6. package/Dashboard/styles.js +16 -0
  7. package/DashboardHtwk/index.js +197 -0
  8. package/DashboardHtwk/styles.js +24 -0
  9. package/Event/index.js +369 -0
  10. package/Event/styles.js +63 -0
  11. package/FeedList/index.js +165 -0
  12. package/FeedList/styles.js +15 -0
  13. package/FeedNews/index.js +243 -0
  14. package/FeedNews/styles.js +32 -0
  15. package/Flexmenu/index.js +185 -0
  16. package/Flexmenu/styles.js +62 -0
  17. package/FlexmenuWebview/index.js +167 -0
  18. package/FlexmenuWebview/styles.js +16 -0
  19. package/Howhy/index.js +151 -0
  20. package/Howhy/styles.js +11 -0
  21. package/Jobs/index.js +217 -0
  22. package/Jobs/styles.js +41 -0
  23. package/MainMenu/index.js +329 -0
  24. package/MainMenu/styles.js +36 -0
  25. package/NewsTabbar/index.js +164 -0
  26. package/NewsTabbar/styles.js +27 -0
  27. package/Opal/index.js +136 -0
  28. package/Opal/styles.js +11 -0
  29. package/PublicTransportTicket/index.js +173 -0
  30. package/PublicTransportTicket/styles.js +17 -0
  31. package/Search/index.js +186 -0
  32. package/Search/styles.js +67 -0
  33. package/SettingsAccessibility/index.js +148 -0
  34. package/SettingsAccessibility/styles.js +13 -0
  35. package/SettingsAppInfo/index.js +86 -0
  36. package/SettingsAppInfo/styles.js +18 -0
  37. package/SettingsCanteens/index.js +249 -0
  38. package/SettingsCanteens/styles.js +33 -0
  39. package/SettingsGeneral/index.js +173 -0
  40. package/SettingsGeneral/styles.js +28 -0
  41. package/SettingsNotifications/index.js +204 -0
  42. package/SettingsNotifications/styles.js +38 -0
  43. package/SettingsUser/index.js +170 -0
  44. package/SettingsUser/styles.js +17 -0
  45. package/TimetableCalendar/index.js +322 -0
  46. package/TimetableCalendar/styles.js +117 -0
  47. package/TimetableList/index.js +397 -0
  48. package/TimetableList/styles.js +55 -0
  49. package/Webviews/index.js +158 -0
  50. package/Webviews/styles.js +11 -0
  51. package/index.js +25 -0
  52. package/package.json +41 -0
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * You may obtain a copy of the License at
5
+ *
6
+ * http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS,
10
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ * See the License for the specific language governing permissions and
12
+ * limitations under the License.
13
+ */
14
+
15
+ import React from 'react';
16
+ import {
17
+ ActivityIndicator,
18
+ Text,
19
+ SafeAreaView,
20
+ StyleSheet,
21
+ View,
22
+ FlatList,
23
+ TouchableOpacity, Image, Linking
24
+ } from 'react-native';
25
+ import {connect} from 'react-redux'
26
+ import {withTheme} from "react-native-paper";
27
+ import {withTranslation} from "react-i18next";
28
+
29
+ import merge from 'lodash/merge';
30
+ import moment from "moment";
31
+
32
+ import {feedApi, onUpdateRefreshing} from "@olea-bps/core";
33
+ import { AppBar as AppbarComponent } from '@olea-bps/components';
34
+ import IconsOpenasist from "@olea-bps/icons-openasist";
35
+
36
+ import componentStyles from "./styles";
37
+ import { handleHtmlEntities } from '@olea-bps/core/helper/format.helper';
38
+
39
+
40
+ /**
41
+ * Feed News View
42
+ *
43
+ * Shows a list of news of the provided feed
44
+ *
45
+ * Parameters:
46
+ * - none
47
+ *
48
+ * Navigation-Parameters:
49
+ * - feedId: Id of the feed to load the news from
50
+ * - feedTitle: Name/Title of the feed for view header
51
+ */
52
+ class FeedNewsView extends React.Component {
53
+ static navigationOptions = {
54
+ header: null
55
+ };
56
+
57
+ // Styles of this component
58
+ styles;
59
+
60
+ feedNews = null;
61
+
62
+ constructor(props) {
63
+ super(props);
64
+
65
+ this.state = {
66
+ inProgress: true,
67
+ hasNoNewsAvailable: false,
68
+ hasError: false
69
+ };
70
+
71
+ // ------------------------------------------------------------------------
72
+ // PLUGIN FUNCTIONALITY
73
+ // ------------------------------------------------------------------------
74
+
75
+ const {pluginStyles,theme} = this.props;
76
+ this.styles = componentStyles(theme);
77
+
78
+ if (pluginStyles) {
79
+ this.styles = merge(this.styles, pluginStyles);
80
+ }
81
+
82
+ this.styles = StyleSheet.create(this.styles);
83
+
84
+ // ------------------------------------------------------------------------
85
+ }
86
+
87
+ /**
88
+ * User pressed a card to read this news
89
+ *
90
+ * @param news
91
+ *
92
+ * @private
93
+ */
94
+ _onCardPress(news) {
95
+ this.props.navigation.navigate('NewsDetail', {news: news});
96
+ }
97
+
98
+
99
+ /**
100
+ * Render a single news item with title, description and image
101
+ *
102
+ * @param item
103
+ * @returns {*}
104
+ * @private
105
+ */
106
+ _renderNewsItem = ({item}) => {
107
+ const { theme: { themeStyles, colors}, t } = this.props;
108
+
109
+ var description = item.shortDesc
110
+ .replace(/<(?:.|\n)*?>/gm, '')
111
+ .replace('Weiterlesen ›', '');
112
+
113
+ let date = moment(item.pubdate);
114
+
115
+ let pubDate = item.pubdate;
116
+ if(date) {
117
+ pubDate = date.format('DD.MM.YYYY - HH:mm');
118
+ }
119
+
120
+ return (
121
+ <TouchableOpacity style={themeStyles.cardWithImage} onPress={() => this._onCardPress(item)}>
122
+ <View style={themeStyles.cartHeaderSplit}>
123
+ {item.imageUrl && <Image source={{uri: item.imageUrl }} style={themeStyles.cartHeaderSplitImage} resizeMode="contain"/>}
124
+ <View style={item.imageUrl ? themeStyles.cartHeaderSplitDetails : themeStyles.cartHeaderDetails}>
125
+ <Text style={[themeStyles.cardTitle, this.styles.newsTitle]}>{item.title}</Text>
126
+ <View style={[themeStyles.flexRow, {alignItems: 'center', alignContent: 'center'}]}>
127
+ <IconsOpenasist icon={'time'} size={20} color={colors.accent}/>
128
+ <Text style={[themeStyles.cardSubTitle, this.styles.newsDate]}>{pubDate}</Text>
129
+ </View>
130
+ </View>
131
+ </View>
132
+ <View style={themeStyles.flexRow}>
133
+ <View style={[themeStyles.cardContent, this.styles.newsContent]}>
134
+ <Text style={themeStyles.cardText}>{handleHtmlEntities(description)}</Text>
135
+ </View>
136
+ <View style={[themeStyles.cardRightIcon, this.styles.newsIcon]}>
137
+ <IconsOpenasist icon={"forward"} size={25} color={colors.messages.icon} />
138
+ </View>
139
+ </View>
140
+ {item.link ? (
141
+ <TouchableOpacity style={themeStyles.cardLinkExternal} onPress={() => {Linking.openURL(item.link)}}>
142
+ <Text style={themeStyles.cardLinkTextExternal}>{t('common:openInBrowser')}</Text>
143
+ <IconsOpenasist icon={"open-external"} size={18} color={colors.subtitle} />
144
+ </TouchableOpacity>
145
+ ) : null}
146
+ </TouchableOpacity>
147
+ );
148
+ };
149
+
150
+ /**
151
+ * Key Extractor for unique item key
152
+ *
153
+ * @param item
154
+ * @param index
155
+ * @returns {*}
156
+ * @private
157
+ */
158
+ _keyExtractor = (item, index) => {
159
+ return ((item.guid) ? item.guid : ((item.title) ? item.title : index));
160
+ };
161
+
162
+ render() {
163
+
164
+ // ------------------------------------------------------------------------
165
+ // PLUGIN FUNCTIONALITY
166
+ // ------------------------------------------------------------------------
167
+ const PluginComponent = this.props.pluginComponent;
168
+ if (PluginComponent) {
169
+ return <PluginComponent />;
170
+ }
171
+ // ------------------------------------------------------------------------
172
+
173
+
174
+ const {route, t} = this.props;
175
+ const {colors, paddings, themeStyles} = this.props.theme;
176
+ const {inProgress, hasNoNewsAvailable, hasError} = this.state;
177
+
178
+ const feedId = route.params && route.params.feedId || null;
179
+ const feedTitle = route.params && route.params.feedTitle || 'News';
180
+
181
+ if(!this.feedNews && feedId) {
182
+ feedApi.getFeedById(feedId, (feed) => {
183
+ if(typeof feed === 'number') {
184
+ this.feedNews = [];
185
+ this.setState({
186
+ inProgress: false,
187
+ hasNoNewsAvailable: feed === 0,
188
+ hasError: feed === -1
189
+ });
190
+ } else {
191
+ this.feedNews = feed;
192
+ this.setState({
193
+ inProgress: false,
194
+ hasNoNewsAvailable: false,
195
+ hasError: false
196
+ });
197
+ }
198
+ });
199
+ }
200
+
201
+ return (
202
+ <SafeAreaView style={[this.styles.container, themeStyles.appSafeAreaContainer]}>
203
+ <AppbarComponent {...this.props} title={feedTitle}/>
204
+ <View style={themeStyles.container}>
205
+ { !inProgress && hasNoNewsAvailable &&
206
+ <View style={themeStyles.noticeTextContainer}>
207
+ <Text style={themeStyles.noticeText}>{t('news:noResult')}</Text>
208
+ </View>
209
+ }
210
+ { !inProgress && hasError &&
211
+ <View style={themeStyles.noticeTextContainer}>
212
+ <Text style={themeStyles.noticeText}>{t('news:couldNotLoadNews')}</Text>
213
+ </View>
214
+ }
215
+ { inProgress && <ActivityIndicator style={this.styles.activity} size="large" color={colors.loadingIndicator} /> }
216
+ { !inProgress && <FlatList
217
+ data={this.feedNews}
218
+ extraData={this.state}
219
+ keyExtractor={this._keyExtractor}
220
+ renderItem={this._renderNewsItem}
221
+ initialNumToRender={6}
222
+ getItemLayout={(data, index) => (
223
+ {length: 50, offset: 50 * index, index}
224
+ )}
225
+ contentContainerStyle={{paddingBottom: paddings.default}}
226
+ style={this.styles.innerContainer}
227
+ /> }
228
+
229
+ </View>
230
+ </SafeAreaView>
231
+ );
232
+ }
233
+ }
234
+
235
+ const mapStateToProps = state => {
236
+ return {
237
+ pluginComponent: state.pluginReducer.feedNews.component,
238
+ pluginStyles: state.pluginReducer.feedNews.styles,
239
+ };
240
+ };
241
+
242
+
243
+ export default connect(mapStateToProps, {onUpdateRefreshing})(withTranslation()(withTheme(FeedNewsView)))
@@ -0,0 +1,32 @@
1
+ export default function(theme) {
2
+ return {
3
+ container: {
4
+ flex: 1
5
+ },
6
+ innerContainer: {
7
+ flex: 1,
8
+ },
9
+ activity: {
10
+ margin: 40
11
+ },
12
+ newsTitle: {
13
+ ...theme.fonts.medium,
14
+ fontSize: theme.fontSizes.xl,
15
+ lineHeight: theme.lineHeights.xxl,
16
+ marginBottom: theme.paddings.default
17
+ },
18
+ newsDate: {
19
+ ...theme.fonts.bold,
20
+ marginTop: 3,
21
+ paddingLeft: theme.paddings.small
22
+ },
23
+ newsContent: {
24
+ paddingTop: theme.paddings.default,
25
+ paddingLeft: theme.paddings.default,
26
+ paddingRight: theme.paddings.default
27
+ },
28
+ newsIcon: {
29
+ paddingRight: theme.paddings.xsmall
30
+ }
31
+ }
32
+ };
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * You may obtain a copy of the License at
5
+ *
6
+ * http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS,
10
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ * See the License for the specific language governing permissions and
12
+ * limitations under the License.
13
+ */
14
+
15
+ import { useMemo } from 'react';
16
+ import {
17
+ StyleSheet,
18
+ SafeAreaView,
19
+ View,
20
+ Text,
21
+ FlatList,
22
+ Linking
23
+ } from 'react-native';
24
+
25
+ import { connect } from 'react-redux';
26
+ import { useTheme } from 'react-native-paper';
27
+ import { useTranslation } from 'react-i18next';
28
+ import { useNavigation } from '@react-navigation/native';
29
+
30
+ import { AppBar as AppbarComponent } from '@olea-bps/components';
31
+ import IconsOpenasist from '@olea-bps/icons-openasist';
32
+ import { FlexMenuEntry } from '@olea-bps/components';
33
+
34
+ import componentStyles from './styles';
35
+
36
+ /**
37
+ * Diese View zeigt eine Ebene von Menüeintragen des Flex-Menüs an.
38
+ *
39
+ * Es sollte mindestens ein Menütyp(props.menuType) über die Eigenschaften oder ein Hauptmenüeintrag(props.route.mainMenuEntry) übergeben werden.
40
+ * Wird ein Menütyp übergeben, wird der Hauptmenüeintrag des Menütypes gesucht und die Kindermenüeintrage angezeigt.
41
+ * Wird ein Hauptmenüeintrag bzw. Elternmenüeintrag übergeben, werden dessen Kindereinträge angezeigt.
42
+ * Der Hauptmenüeintrag wird über das Navigationsframework übergeben, weshalb dieser in der 'route'-Eigenschaft zu finden ist.
43
+ * Somit werden meherer Menüebenen unterstützt.
44
+ *
45
+ * Die View heißt aus historischen Gründen CoronaView und sollte zeitnah sinvoll umbennant werden
46
+ *
47
+ * @param {object} props
48
+ * @param {string} [props.menuType] Menütyp, dessen erste Menüebene angezeigt werden soll
49
+ * @param {object} props.route Eigenschaften, welche durch das Navigationsframework übergeben werden: {@link https://reactnavigation.org/docs/params}
50
+ * @param {string} [props.route.mainMenuEntry] ID des Hauptmenüeintrag, dessen Kinder anzuzeigen sind
51
+ */
52
+ function FlexMenuView(props) {
53
+ const componentName = FlexMenuView.name;
54
+ const {
55
+ route,
56
+ } = props;
57
+
58
+ const menuType = props?.menuType ?? route?.params?.menuType;
59
+ const menuEntries = props.menuEntries ?? [];
60
+ const view = props?.view ?? route.params.view;
61
+
62
+ const theme = useTheme();
63
+ const { themeStyles, paddings, colors, appSettings } = theme;
64
+ const { t, i18n } = useTranslation();
65
+ const navigation = useNavigation();
66
+
67
+ const languages = appSettings?.languages;
68
+ // Eingestellte Sprache aus der Übersetzung holen
69
+ const selectedLanguage = i18n.language;
70
+ // Hinterlegte Sprachen durchgehen und nach dem erweiterten Sprachcode holen
71
+ const menuEntriesLanguage = languages
72
+ // Finde die derzeitige eingestellte Sprache
73
+ .find(language => language?.code === selectedLanguage)
74
+ // Erweiterten Sprachcode von der gefundenen Sprache auslesen
75
+ ?.extCode;
76
+
77
+ let mainMenuEntry;
78
+ let childMenuEntries;
79
+
80
+ const styles = useMemo(
81
+ () => StyleSheet.create(componentStyles(theme)),
82
+ [theme]
83
+ );
84
+
85
+ // Bestimmen des Hauptmenüeintrages dieser View
86
+ // Zuerst wird der Hauptmenüeintrag es den Navigationsparametern entnommen
87
+ mainMenuEntry = route?.params?.mainMenuEntry ?? null;
88
+ // Ist der Hauptmenüeintrag nicht in den Navigationsparametern enthalten gewesen, aber dennoch ist ein Menütyp vergeben, wird der Hauptmenüeintrag aus allen Menueinträgen bestimmt
89
+ if (mainMenuEntry === null && menuType !== null) {
90
+ // Der Hauptmenüeintrag ist ein Eintrag ohne einen Elterneintrag, mit dem übergenen Menütyp und der eingestellten Sprache
91
+ mainMenuEntry = menuEntries
92
+ // Es werden alle Menüeintrage behalten, die dem Menütyp entsprechen
93
+ .filter(menuEntry => menuEntry.type === menuType)
94
+ // Es werden alle Menüeintrage behalten, die der eingestellten Sprache entsprechen
95
+ .filter(menuEntry => (menuEntry.language ?? null) === menuEntriesLanguage)
96
+ // Aus den nicht ausgefilterten Menüeinträgen wird eine Eintrag genommen, der kein Elterneintrag besitzt
97
+ .find(menuEntry => (menuEntry.parentMenuEntry ?? null) === null);
98
+ }
99
+
100
+ // Ids der Untermenüpunkte aus den Ressource-links herausholen
101
+ const childMenuEntryIds = mainMenuEntry
102
+ ?.subMenuEntries
103
+ ?.map(
104
+ subMenuLinkId =>
105
+ // Der Link des Untermenüpunktes wird in seine Segmente aufgesplitted und das letzte Segment mit Inhalt als ID verwendet
106
+ subMenuLinkId
107
+ // Aufsplitten der URL in seine Segmente
108
+ .split('/')
109
+ // Entfernen aller 'leeren' Segmenente
110
+ .filter(urlSegment => urlSegment !== '')
111
+ // Nehmen des letzten Elements
112
+ .pop()
113
+ )
114
+ ?? [];
115
+
116
+ childMenuEntries = childMenuEntryIds
117
+ .map(childMenuEntryId => menuEntries.find(menuEntry => menuEntry.id === childMenuEntryId))
118
+ .filter(menuEntry => (menuEntry ?? null) !== null);
119
+
120
+ return (
121
+ <SafeAreaView style={[styles.container, themeStyles.appSafeAreaContainer]}>
122
+ <AppbarComponent {...props} title={mainMenuEntry?.title} />
123
+ <View style={themeStyles.container}>
124
+ <FlatList
125
+ data={
126
+ Array.isArray(childMenuEntries)
127
+ ? childMenuEntries
128
+ : []
129
+ }
130
+ renderItem={
131
+ ({ item: menuEntry }) => {
132
+ let menuEntryOnPress;
133
+ switch (menuEntry.openIn) {
134
+ case 'webview':
135
+ menuEntryOnPress = () => { navigation.push('FlexMenuWebView', { url: menuEntry.url, item: menuEntry }) };
136
+ break;
137
+ case 'webbrowser':
138
+ case 'browser':
139
+ menuEntryOnPress = () => {
140
+ Linking.canOpenURL(menuEntry.url)
141
+ .then(supported => {
142
+ if (supported) {
143
+ Linking.openURL(menuEntry.url);
144
+ } else {
145
+ console.debug(componentName, ':', 'Can not open', menuEntry?.url);
146
+ }
147
+ });
148
+ }
149
+ break;
150
+ case null:
151
+ menuEntryOnPress = () => { navigation.push(view, { mainMenuEntry: menuEntry, view: view }) };
152
+ break;
153
+ }
154
+
155
+ return (
156
+ <FlexMenuEntry
157
+ title={menuEntry?.title}
158
+ description={menuEntry?.description}
159
+ iconImage={menuEntry?.image}
160
+ onPress={menuEntryOnPress}
161
+ />
162
+ );
163
+ }
164
+ }
165
+ keyExtractor={item => item.id}
166
+ contentContainerStyle={{ paddingBottom: paddings.default }}
167
+ ListEmptyComponent={
168
+ <View style={[styles.containerInner, styles.containerErrorMsg]}>
169
+ <IconsOpenasist icon={'info'} size={48} color={colors.primary} />
170
+ <Text style={styles.title}>{t('corona:menuEntriesNotAvailable')}</Text>
171
+ </View>
172
+ }
173
+ />
174
+ </View>
175
+ </SafeAreaView>
176
+ );
177
+ }
178
+
179
+ const mapStateToProps = state => {
180
+ return {
181
+ menuEntries: state.apiReducer.menuEntries,
182
+ };
183
+ };
184
+
185
+ export default connect(mapStateToProps)(FlexMenuView)
@@ -0,0 +1,62 @@
1
+ export default function(theme) {
2
+ return {
3
+ container: {
4
+ flex: 1,
5
+ backgroundColor: theme.colors.background,
6
+ },
7
+ contentContainer: {
8
+ paddingTop: 0
9
+ },
10
+ titleStyle: {
11
+ ...theme.fonts.medium,
12
+ fontSize: theme.fontSizes.xxl,
13
+ alignSelf: "center",
14
+ },
15
+ cardContent: {
16
+ justifyContent: 'center',
17
+ alignItems: 'flex-start'
18
+ },
19
+ cardTitle: {
20
+ flexWrap: 'wrap',
21
+ justifyContent: 'center',
22
+ fontSize: theme.fontSizes.l,
23
+ ...theme.fonts.light,
24
+ },
25
+ cardImage: {
26
+ justifyContent: 'center',
27
+ alignContent: 'center',
28
+ paddingRight: theme.paddings.default,
29
+ },
30
+ cardSubTitle: {
31
+ width: 250,
32
+ fontSize: theme.fontSizes.s,
33
+ ...theme.fonts.regular,
34
+ lineHeight: theme.lineHeights.xxs,
35
+ color: theme.colors.subtitle,
36
+ marginTop: theme.paddings.xsmall
37
+ },
38
+ title : {
39
+ fontSize: theme.fontSizes.title,
40
+ color: theme.colors.text,
41
+ textAlign: 'center',
42
+ marginTop: theme.paddings.default,
43
+ marginLeft: theme.paddings.default,
44
+ marginRight: theme.paddings.default,
45
+ },
46
+ subtitle : {
47
+ fontSize: theme.fontSizes.subtitle,
48
+ color: theme.colors.subtitle,
49
+ textAlign: 'center',
50
+ marginTop: theme.paddings.default,
51
+ marginLeft: theme.paddings.default,
52
+ marginRight: theme.paddings.default,
53
+ },
54
+ containerErrorMsg: {
55
+ flex: 1,
56
+ justifyContent: 'center',
57
+ alignContent: 'center',
58
+ alignItems: 'center',
59
+ margin: theme.paddings.default,
60
+ },
61
+ }
62
+ };
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * You may obtain a copy of the License at
5
+ *
6
+ * http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS,
10
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ * See the License for the specific language governing permissions and
12
+ * limitations under the License.
13
+ */
14
+
15
+ import { useMemo, useState, useCallback, useRef } from 'react';
16
+ import {
17
+ StyleSheet,
18
+ SafeAreaView,
19
+ ActivityIndicator,
20
+ BackHandler,
21
+ Platform,
22
+ } from 'react-native';
23
+
24
+ import { Appbar, useTheme } from "react-native-paper";
25
+ import { useTranslation } from "react-i18next";
26
+ import { useFocusEffect } from '@react-navigation/native';
27
+ import { WebView } from 'react-native-webview';
28
+
29
+ import { AppBar as AppbarComponent } from '@olea-bps/components';
30
+ import IconsOpenasist from '@olea-bps/icons-openasist';
31
+
32
+ import componentStyles from "./styles";
33
+
34
+ /**
35
+ * Corona Info View
36
+ *
37
+ * The home screen of the app. Shows different informations like the top news, weather,
38
+ * current mensa menus and informations of the next course (if the student has imported his timtetable)
39
+ *
40
+ * Parameters:
41
+ * - none
42
+ *
43
+ * Navigation-Parameters:
44
+ * - none
45
+ */
46
+ export default function FlexMenuWebView({ route, ...props }) {
47
+ const theme = useTheme();
48
+ const { colors, themeStyles } = theme;
49
+ const { t } = useTranslation();
50
+ const flexMenuEntry = route?.params?.item ?? props?.item;
51
+
52
+ const webviewURL = route?.params?.url ?? props?.url;
53
+ const webviewTitle = flexMenuEntry.title;
54
+
55
+ const webview = useRef(null);
56
+ const [webviewCanGoBack, setWebviewCanGoBack] = useState();
57
+
58
+ /**
59
+ * Erstellen einer Liste von erlauben Hosts, die in der Webview verwendet werden dürfen.
60
+ * In der Liste ist nur ein Host, welcher aus der anzuzeigenden URL extrahiert wird.
61
+ *
62
+ * Unter iOS wird der `undefined`-Wert verwendet.
63
+ * Wird in der Webseite eine Ressource von einem anderem Host eingebunden, öffnet es die Ressource im Browser, weil der Dritthost nicht erlaubt ist.
64
+ * Somit führen zum Beispiel eingebundene Youtube-Videos zu einem öffnen des Videos im Browser.
65
+ *
66
+ * @type {string[]|undefined}
67
+ */
68
+ const webviewOriginWhitelist = useMemo(
69
+ () => {
70
+ // URL-String in URL-Instanze konvertieren
71
+ const url = new URL(webviewURL);
72
+ // Host/Origin aus URL extrahieren
73
+ const webviewURLOrigin = url.origin;
74
+
75
+ // Auswahl das Rückgabewerted abhängig vond der Plattform
76
+ // Standartmäßig wird eine Liste mit dem Host der URL zurückgegeben
77
+ // Unter iOS wird undefined zurückgegeben
78
+ return Platform.select({
79
+ default: [webviewURLOrigin],
80
+ ios: undefined,
81
+ });
82
+ },
83
+ [webviewURL]
84
+ );
85
+
86
+ // Styles of this component
87
+ const styles = useMemo(
88
+ () => StyleSheet.create(componentStyles(theme)),
89
+ [theme]
90
+ )
91
+
92
+ useFocusEffect(
93
+ useCallback(
94
+ () => {
95
+ const onBackPress = webviewCanGoBack && webview?.current
96
+ ? () => {
97
+ webview?.current?.goBack();
98
+ return true;
99
+ }
100
+ : () => {
101
+ props?.navigation?.goBack();
102
+ return true;
103
+ }
104
+
105
+ const subscription = BackHandler.addEventListener(
106
+ 'hardwareBackPress',
107
+ onBackPress
108
+ );
109
+
110
+ return () => subscription.remove();
111
+ },
112
+ [webviewCanGoBack]
113
+ )
114
+ );
115
+
116
+ return (
117
+ <SafeAreaView style={[styles.container, themeStyles.appSafeAreaContainer]}>
118
+ <AppbarComponent {...props}
119
+ title={t(webviewTitle)}
120
+ leftAction={
121
+ <Appbar.Action
122
+ icon={props => <IconsOpenasist {...props} icon={'back'} color={colors.primaryText} />}
123
+ onPress={
124
+ webviewCanGoBack
125
+ ? webview?.current?.goBack
126
+ : props?.navigation?.goBack
127
+ }
128
+ accessible={true}
129
+ accessibilityLabel={
130
+ webviewCanGoBack
131
+ ? t('accessibility:appbar.navigateBack')
132
+ : t('accessibility:appbar.back')
133
+ }
134
+ />
135
+ }
136
+ rightAction={
137
+ <Appbar.Action
138
+ icon="refresh"
139
+ onPress={webview?.current?.reload}
140
+ accessible={true}
141
+ accessibilityLabel={t('accessibility:appbar.reload')}
142
+ />
143
+ }
144
+ />
145
+ <WebView
146
+ ref={webview}
147
+ source={{ uri: webviewURL }}
148
+ startInLoadingState={true}
149
+ allowsFullscreenVideo={true}
150
+ allowsInlineMediaPlayback={true}
151
+ originWhitelist={webviewOriginWhitelist}
152
+ renderLoading={
153
+ () =>
154
+ <ActivityIndicator
155
+ style={styles.activity}
156
+ size='large'
157
+ color={colors.loadingIndicator}
158
+ />
159
+ }
160
+ onLoad={
161
+ syntheticEvent =>
162
+ setWebviewCanGoBack(syntheticEvent?.nativeEvent?.canGoBack ?? false)
163
+ }
164
+ />
165
+ </SafeAreaView>
166
+ );
167
+ }