@olea-bps/components 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 (79) hide show
  1. package/AppBar/index.js +83 -0
  2. package/AppBar/styles.js +27 -0
  3. package/BookDetail/index.js +268 -0
  4. package/BookDetail/styles.js +124 -0
  5. package/Component/342/200/216JobsFilter/index.js +188 -0
  6. package/Component/342/200/216JobsFilter/styles.js +12 -0
  7. package/ConnectivityWarning/index.js +65 -0
  8. package/ConnectivityWarning/styles.js +19 -0
  9. package/ContactDetail/index.js +232 -0
  10. package/ContactDetail/styles.js +32 -0
  11. package/CourseDetail/index.js +357 -0
  12. package/CourseDetail/styles.js +59 -0
  13. package/CourseDetailDialog/index.js +169 -0
  14. package/CourseDetailDialog/styles.js +116 -0
  15. package/CourseInfo/index.js +219 -0
  16. package/CourseInfo/styles.js +40 -0
  17. package/DevelopmentDialog/index.js +208 -0
  18. package/DevelopmentDialog/styles.js +10 -0
  19. package/EventCodeInput/index.js +146 -0
  20. package/EventCodeInput/styles.js +108 -0
  21. package/FlexMenuEntry/index.js +84 -0
  22. package/FlexMenuEntry/styles.js +27 -0
  23. package/MainMenuEntry/index.js +88 -0
  24. package/MainMenuEntry/styles.js +28 -0
  25. package/MealItem/index.js +87 -0
  26. package/MealItem/styles.js +73 -0
  27. package/MensaMenu/index.js +307 -0
  28. package/MensaMenu/styles.js +94 -0
  29. package/MensaSlider/index.js +184 -0
  30. package/MensaSlider/styles.js +53 -0
  31. package/Modal/index.js +106 -0
  32. package/Modal/styles.js +8 -0
  33. package/NewsDetail/index.js +377 -0
  34. package/NewsDetail/styles.js +77 -0
  35. package/NewsList/index.js +120 -0
  36. package/NewsList/styles.js +19 -0
  37. package/NewsListItem/index.js +89 -0
  38. package/NewsListItem/styles.js +32 -0
  39. package/OtherCourses/index.js +152 -0
  40. package/OtherCourses/styles.js +10 -0
  41. package/PtsDeparture/index.js +140 -0
  42. package/PtsDeparture/styles.js +7 -0
  43. package/PtsStation/index.js +183 -0
  44. package/PtsStation/styles.js +47 -0
  45. package/QuickLinks/index.js +127 -0
  46. package/QuickLinks/styles.js +45 -0
  47. package/RoomDetail/index.js +281 -0
  48. package/RoomDetail/styles.js +56 -0
  49. package/ScaledImage/index.js +92 -0
  50. package/SearchResults/index.js +362 -0
  51. package/SearchResults/styles.js +59 -0
  52. package/SettingSection/index.js +54 -0
  53. package/SettingSection/styles.js +15 -0
  54. package/SettingsDialog/index.js +52 -0
  55. package/SettingsDialog/styles.js +12 -0
  56. package/SettingsDialogRadio/index.js +66 -0
  57. package/SettingsDialogRadio/styles.js +7 -0
  58. package/SettingsDialogSelect/index.js +73 -0
  59. package/SettingsDialogSelect/styles.js +7 -0
  60. package/TimetableCodeInput/index.js +201 -0
  61. package/TimetableCodeInput/styles.js +28 -0
  62. package/TimetableDay/index.js +266 -0
  63. package/TimetableDay/styles.js +103 -0
  64. package/TimetableEvent/index.js +163 -0
  65. package/TimetableEvent/styles.js +108 -0
  66. package/TimetableList/index.js +116 -0
  67. package/TimetableList/styles.js +109 -0
  68. package/TimetableMonth/index.js +156 -0
  69. package/TimetableMonth/styles.js +29 -0
  70. package/TimetableWeek/index.js +245 -0
  71. package/TimetableWeek/styles.js +58 -0
  72. package/TopNews/index.js +282 -0
  73. package/TopNews/styles.js +125 -0
  74. package/TopNewsHtwk/index.js +279 -0
  75. package/TopNewsHtwk/styles.js +142 -0
  76. package/WebView/index.js +108 -0
  77. package/WebView/styles.js +11 -0
  78. package/index.js +39 -0
  79. package/package.json +37 -0
package/Modal/index.js ADDED
@@ -0,0 +1,106 @@
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 {SafeAreaView, StyleSheet} from 'react-native';
17
+ import {connect} from 'react-redux'
18
+ import {withTheme} from 'react-native-paper';
19
+
20
+ import merge from 'lodash/merge';
21
+
22
+ import componentStyles from "./styles"
23
+
24
+
25
+ /**
26
+ * Modal Component
27
+ *
28
+ * This component receives its content through a redux state. The origin component pushes
29
+ * the content to this state and triggers a navigation to "Modal". This will display the
30
+ * modal with the provided content.
31
+ *
32
+ * Parameters:
33
+ * - none
34
+ *
35
+ * Navigation-Parameters:
36
+ * - none
37
+ *
38
+ *
39
+ *
40
+ * Sample usage:
41
+ *
42
+ store.dispatch({
43
+ type: 'UPDATE_MODAL_CONTENT',
44
+ modalContent: (
45
+ <View style={this.styles.modalContainer}>
46
+ <NewsDetailComponent news={news} {...this.props} />
47
+ </View>
48
+ )
49
+ });
50
+ this.props.navigation.navigate('Modal');
51
+ */
52
+ class ModalComponent extends React.Component {
53
+
54
+ // Styles of this component
55
+ styles;
56
+
57
+ constructor(props) {
58
+ super(props);
59
+
60
+ // ------------------------------------------------------------------------
61
+ // PLUGIN FUNCTIONALITY
62
+ // ------------------------------------------------------------------------
63
+
64
+ const { pluginStyles, theme } = this.props;
65
+ this.styles = componentStyles(theme);
66
+
67
+ if(pluginStyles) {
68
+ this.styles = merge(this.styles, pluginStyles);
69
+ }
70
+
71
+ this.styles = StyleSheet.create(this.styles);
72
+ }
73
+
74
+ render() {
75
+ // ------------------------------------------------------------------------
76
+ // PLUGIN FUNCTIONALITY
77
+ // ------------------------------------------------------------------------
78
+ const PluginComponent = this.props.pluginComponent;
79
+ if (PluginComponent) {
80
+ return <PluginComponent />;
81
+ }
82
+ // ------------------------------------------------------------------------
83
+
84
+
85
+ const {themeStyles} = this.props.theme;
86
+ const { modalContent } = this.props;
87
+
88
+ return (
89
+ <SafeAreaView style={[this.styles.container, themeStyles.appSafeAreaContainer]}>
90
+ {modalContent}
91
+ </SafeAreaView>
92
+ );
93
+ }
94
+ }
95
+
96
+
97
+
98
+ const mapStateToProps = state => {
99
+ return {
100
+ pluginComponent: state.pluginReducer.modal.component,
101
+ pluginStyles: state.pluginReducer.modal.styles,
102
+ modalContent: state.stateReducer.modalContent
103
+ };
104
+ };
105
+
106
+ export default connect(mapStateToProps, null)(withTheme(ModalComponent))
@@ -0,0 +1,8 @@
1
+
2
+ export default function(theme) {
3
+ return {
4
+ container: {
5
+ flex: 1,
6
+ }
7
+ }
8
+ };
@@ -0,0 +1,377 @@
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 PropTypes from 'prop-types';
17
+
18
+ import {
19
+ StyleSheet,
20
+ Text,
21
+ View,
22
+ SafeAreaView,
23
+ Share,
24
+ Dimensions,
25
+ Animated,
26
+ Platform,
27
+ Linking,
28
+ PixelRatio
29
+ } from 'react-native';
30
+ import {Appbar, Headline, withTheme} from "react-native-paper";
31
+ import { WebView } from 'react-native-webview';
32
+ import {withTranslation} from "react-i18next";
33
+ import { connect } from 'react-redux'
34
+ import merge from 'lodash/merge';
35
+ import moment from "moment";
36
+
37
+ import WebViewAutoHeight from '@olea-bps/react-native-webview-autoheight';
38
+
39
+
40
+ import componentStyles from "./styles"
41
+ import { AppBar as AppbarComponent } from '@olea-bps/components';
42
+ import {selectFeedById} from "@olea-bps/core/redux/reducers/api";
43
+
44
+
45
+ /**
46
+ * News Detail Component
47
+ *
48
+ * Shows the image and the text of a single news. Provides a share functionality.
49
+ *
50
+ * Parameters:
51
+ * - news: News object with all informations of it (for use in modal)
52
+ *
53
+ * Navigation-Parameters:
54
+ * - news: News object with all informations of it (for use as standalone view)
55
+ */
56
+ class NewsDetailComponent extends React.Component {
57
+ static propTypes = {
58
+ news: PropTypes.object,
59
+ };
60
+
61
+ /**
62
+ * Constant to store news types. These types are used to change the view accordingly.
63
+ */
64
+ static newsTypes = Object.freeze({
65
+ topNews: 'TopNews',
66
+ })
67
+
68
+ // Styles of this component
69
+ styles;
70
+
71
+ news = null;
72
+
73
+ currentURL = null;
74
+
75
+ urlRegex = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/;
76
+
77
+ maxHeaderHeight;
78
+ minHeaderHeight = 120;
79
+ headerScrollDistance;
80
+ fontScaling;
81
+
82
+ constructor(props) {
83
+ super(props);
84
+ // ------------------------------------------------------------------------
85
+ // PLUGIN FUNCTIONALITY
86
+ // ------------------------------------------------------------------------
87
+
88
+ const { pluginStyles,theme } = this.props;
89
+ this.styles = componentStyles(theme);
90
+
91
+ if(pluginStyles) {
92
+ this.styles = merge(this.styles, pluginStyles);
93
+ }
94
+
95
+ this.styles = StyleSheet.create(this.styles);
96
+
97
+ // ------------------------------------------------------------------------
98
+
99
+ // Initialize dimension-dependent properties
100
+ this.maxHeaderHeight = Dimensions.get('window').height / 3.8;
101
+ this.headerScrollDistance = this.maxHeaderHeight - this.minHeaderHeight;
102
+ this.fontScaling = PixelRatio.getFontScale();
103
+
104
+ this._onScroll = this._onScroll.bind(this);
105
+
106
+ this.state = {
107
+ imageHeight: Dimensions.get('window').height / 3.8,
108
+ scrollY: new Animated.Value(Platform.OS === 'ios' ? -this.maxHeaderHeight :0)
109
+ };
110
+ };
111
+
112
+ /**
113
+ * Share function for contact
114
+ *
115
+ * @returns {Promise<void>}
116
+ * @private
117
+ */
118
+ async _onShare() {
119
+ try {
120
+ let message = this.news.title + '\n\n' + this.news.link;
121
+ const result = await Share.share({
122
+ message: message
123
+ });
124
+
125
+ if (result.action === Share.sharedAction) {
126
+ if (result.activityType) {
127
+ // shared with activity type of result.activityType
128
+ } else {
129
+ // shared
130
+ }
131
+ } else if (result.action === Share.dismissedAction) {
132
+ // dismissed
133
+ }
134
+ } catch (error) {
135
+ alert(error.message);
136
+ }
137
+ };
138
+
139
+
140
+ _onScroll = event => {
141
+ let scrollPosY = event.nativeEvent.contentOffset.y;
142
+ let imageHeight = Dimensions.get('window').height / 2.8;
143
+
144
+
145
+ let calculatedHeight = imageHeight - scrollPosY;
146
+ let limit = 80;
147
+ this.setState({imageHeight: calculatedHeight > limit ? calculatedHeight : limit});
148
+ };
149
+
150
+ /**
151
+ * get the news content
152
+ *
153
+ * @param news
154
+ * @param css
155
+ *
156
+ * @returns {{html: string}}
157
+ * @private
158
+ */
159
+ _getNewsContent = (news, css) => {
160
+ const newsContent = {html: css + "<div class='content'><meta http-equiv='Content-Type' content='text/html; charset=utf-8' />" + news.desc + "</div>"};
161
+ if (Platform.OS === 'android') {
162
+ newsContent.baseUrl = '';
163
+ }
164
+ return newsContent;
165
+ };
166
+
167
+ /**
168
+ * checks if the url is a valid one -> if so, it will open the URL in the browser
169
+ * otherwise it stops the request
170
+ *
171
+ * @param url
172
+ * @returns {boolean}
173
+ */
174
+ startLoadingRequest = ({url}) => {
175
+ if (!url.match(this.urlRegex)) {
176
+ return true;
177
+ } else if (url.match(this.urlRegex) && Linking.canOpenURL(url)){
178
+ //Android does not support the stopLoading method
179
+ if (Platform.OS === 'android') {
180
+ this.webView && this.webView.goBack();
181
+ Linking.openURL(url);
182
+ } else {
183
+ this.webView.stopLoading();
184
+ Linking.openURL(url);
185
+ }
186
+ }
187
+ };
188
+
189
+ _renderTopNewsContent = (news) => {
190
+ const {customScript} = this.props.theme;
191
+ return (
192
+ <View style={this.styles.container}>
193
+ <WebView
194
+ injectedJavaScript={customScript}
195
+ source={{ uri: news.link }}
196
+ />
197
+ </View>
198
+ );
199
+ }
200
+
201
+
202
+ /**
203
+ * Render news contact details content
204
+ *
205
+ * @returns {*}
206
+ * @private
207
+ */
208
+ _renderContent = (news) => {
209
+ const {appSettings, css, themeStyles} = this.props.theme;
210
+ this.currentURL = news.link;
211
+
212
+
213
+
214
+ let imageSrc = null;
215
+ if(news?.contentImageUrl) {
216
+ imageSrc = {uri: news.contentImageUrl}
217
+ }
218
+ const scrollY = Animated.add(
219
+ this.state.scrollY,
220
+ Platform.OS === 'ios' ? this.maxHeaderHeight : 0,
221
+ );
222
+
223
+ const headerTranslate = scrollY.interpolate({
224
+ inputRange: [0, this.headerScrollDistance],
225
+ outputRange: [0, -this.headerScrollDistance],
226
+ extrapolate: 'clamp',
227
+ });
228
+ const imageTranslate = scrollY.interpolate({
229
+ inputRange: [0, this.headerScrollDistance],
230
+ outputRange: [0, 1],
231
+ extrapolate: 'clamp',
232
+ });
233
+
234
+ const titleOpacity = scrollY.interpolate({
235
+ inputRange: [0, this.headerScrollDistance / 2, this.headerScrollDistance],
236
+ outputRange: [0, 1, 1],
237
+ extrapolate: 'clamp',
238
+ });
239
+
240
+ const titleScale = scrollY.interpolate({
241
+ inputRange: [0, this.headerScrollDistance / 2, this.headerScrollDistance],
242
+ outputRange: [1, 1, 0.8],
243
+ extrapolate: 'clamp',
244
+ });
245
+ let titleTranslate = scrollY.interpolate({
246
+ inputRange: [0, this.headerScrollDistance / 2, this.headerScrollDistance],
247
+ outputRange: [0, 0, -8],
248
+ extrapolate: 'clamp',
249
+ });
250
+
251
+ // No image
252
+ if(imageSrc == null) {
253
+ imageSrc = appSettings.header;
254
+ }
255
+
256
+
257
+ let barTitle = news.title.replace(/<(?:.|\n)*?>/gm, '');
258
+ let barTitleFontSize = 22;
259
+ let barTitleLineHeight = 22;
260
+ let barTitleHeight = 120;
261
+ if(barTitle.length > 80)
262
+ barTitleFontSize = 18;
263
+
264
+ if(barTitle.length > 120)
265
+ barTitleFontSize = 16;
266
+
267
+ if(this.fontScaling > 2) {
268
+ barTitleFontSize = 15;
269
+ barTitleLineHeight = 14;
270
+ barTitleHeight = 140;
271
+ }
272
+ if(this.fontScaling > 2.3) {
273
+ barTitleFontSize = 12;
274
+ barTitleLineHeight = 14;
275
+ barTitleHeight = 160;
276
+ titleTranslate = scrollY.interpolate({
277
+ inputRange: [0, this.headerScrollDistance / 2, this.headerScrollDistance],
278
+ outputRange: [0, 0, -25],
279
+ extrapolate: 'clamp',
280
+ });
281
+ }
282
+ barTitleFontSize = Math.round(barTitleFontSize * 10000) / 10000;
283
+
284
+ let pubDate = news ? news.pubdate : null ;
285
+ let date = news ? moment(news.pubdate) : moment();
286
+ if(date)
287
+ pubDate = date.format('DD.MM.YYYY HH:mm');
288
+
289
+ return (
290
+ <View style={this.styles.container}>
291
+ <Animated.ScrollView style={[this.styles.containerInner]}>
292
+ <Animated.View
293
+ pointerEvents="none"
294
+ style={[this.styles.header, { height: this.maxHeaderHeight }]}>
295
+ {imageSrc != null &&
296
+ <Animated.Image
297
+ source={imageSrc}
298
+ style={[
299
+ this.styles.image,
300
+ {
301
+ height: this.maxHeaderHeight,
302
+
303
+ }
304
+ ]}/>}
305
+ </Animated.View>
306
+ {news.contentImageDesc ? (<Text style={this.styles.imageDesc}>{news.contentImageDesc}</Text>) : null}
307
+ <View style={[this.styles.containerContent, Platform.OS === 'android' && {paddingBottom: this.maxHeaderHeight + this.styles.containerContent.paddingVertical}]}>
308
+ <Headline style={this.styles.title}>{news.title}</Headline>
309
+ <WebViewAutoHeight source={this._getNewsContent(news, css)}
310
+ style={{...themeStyles.webview, ...this.styles.newsContent}}
311
+ scrollEnabled={false}
312
+ contentInset={{top: 0, left: 0, right: 0, bottom: 0}}
313
+ onNavigationStateChange={this.startLoadingRequest}
314
+ ref={el => {this.webView = el}}/>
315
+ <Text style={[this.styles.newsContent, this.styles.newsContentText]}>{pubDate}</Text>
316
+ </View>
317
+ </Animated.ScrollView>
318
+ </View>
319
+ );
320
+ };
321
+
322
+ render() {
323
+ // ------------------------------------------------------------------------
324
+ // PLUGIN FUNCTIONALITY
325
+ // ------------------------------------------------------------------------
326
+ const PluginComponent = this.props.pluginComponent;
327
+ if (PluginComponent) {
328
+ return <PluginComponent />;
329
+ }
330
+ // ------------------------------------------------------------------------
331
+
332
+ const {themeStyles} = this.props.theme;
333
+ let { news, route, t, feed } = this.props;
334
+ let { newsType } = route.params;
335
+
336
+ if(!news){
337
+ return (
338
+ <SafeAreaView style={[this.styles.container, themeStyles.appSafeAreaContainer]}>
339
+ <AppbarComponent {...this.props}
340
+ title={t('news:title')}/>
341
+ <View style={this.styles.container}>
342
+ <Text>{t('news:couldNotLoad')}</Text>
343
+ </View>
344
+ </SafeAreaView>
345
+ );
346
+ }
347
+
348
+ this.news = news;
349
+ const title = feed ? feed.title : t('news:feeds');
350
+
351
+ return (
352
+ <SafeAreaView style={[this.styles.container, themeStyles.appSafeAreaContainer]}>
353
+ <AppbarComponent {...this.props}
354
+ title={title}
355
+ rightAction={newsType ? null : <Appbar.Action icon="share-variant" onPress={this._onShare.bind(this)}/>}/>
356
+ {newsType === NewsDetailComponent.newsTypes.topNews ? this._renderTopNewsContent(news) : this._renderContent(news)}
357
+ </SafeAreaView>
358
+ );
359
+ }
360
+ }
361
+
362
+
363
+ const mapStateToProps = (state, ownProps) => {
364
+ let {news, route} = ownProps;
365
+ if (!news) {
366
+ news = route.params && route.params.news || null;
367
+ }
368
+
369
+ return {
370
+ pluginComponent: state.pluginReducer.newsDetail.component,
371
+ pluginStyles: state.pluginReducer.newsDetail.styles,
372
+ feed: selectFeedById(state, news.feedId),
373
+ news: news
374
+ };
375
+ };
376
+
377
+ export default connect(mapStateToProps, null)(withTranslation()(withTheme(NewsDetailComponent)))
@@ -0,0 +1,77 @@
1
+ import {Dimensions} from "react-native";
2
+
3
+ export default function(theme) {
4
+ const height = Dimensions.get('window').height;
5
+ const width = Dimensions.get('window').width;
6
+ return {
7
+ overflow: {
8
+ backgroundColor: theme.colors.overlayColor,
9
+ },
10
+ image: {
11
+ position: 'absolute',
12
+ top: 0,
13
+ left: 0,
14
+ right: 0,
15
+ width: null,
16
+ resizeMode: 'cover',
17
+ },
18
+ container: {
19
+ flex: 1,
20
+ overflow: 'hidden'
21
+ },
22
+ containerInner: {
23
+ flex: 1
24
+ },
25
+ containerContent: {
26
+ backgroundColor: theme.colors.contentBackground,
27
+ flex: 1,
28
+ paddingHorizontal: 5,
29
+ paddingVertical: 20,
30
+ minHeight: height * 1.25 // To make sure the animation won't break / stop
31
+ },
32
+ activity: {
33
+ marginTop: 20
34
+ },
35
+ badges: {
36
+ flexDirection: 'row',
37
+ flexWrap: 'wrap',
38
+ paddingHorizontal: 10
39
+ },
40
+ badge: {
41
+ marginRight: 5,
42
+ marginBottom: 5
43
+ },
44
+ title: {
45
+ paddingLeft: 10,
46
+ fontSize: theme.fontSizes.title,
47
+ lineHeight: theme.lineHeights.titleBig
48
+ },
49
+ bar: {
50
+ alignItems: 'flex-start',
51
+ justifyContent: 'center',
52
+ position: 'absolute',
53
+ top: 5,
54
+ left: 0,
55
+ right: 0,
56
+ },
57
+ barText: {
58
+ color: theme.colors.primaryText,
59
+ ...theme.fonts.bold,
60
+ },
61
+ newsContent : {
62
+ marginVertical: theme.paddings.default,
63
+ marginHorizontal: theme.paddings.small,
64
+ width: width - (2 * theme.paddings.small)
65
+ },
66
+ newsContentText: {
67
+ fontSize: theme.fontSizes.m
68
+ },
69
+ imageDesc: {
70
+ backgroundColor: theme.colors.overlayColor,
71
+ color: theme.colors.primaryText,
72
+ paddingVertical: 2,
73
+ paddingHorizontal: theme.paddings.small,
74
+ fontSize: theme.fontSizes.xs
75
+ }
76
+ };
77
+ }
@@ -0,0 +1,120 @@
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 { useEffect, useState, useMemo, useCallback } from 'react';
16
+ import {
17
+ AppState,
18
+ ActivityIndicator,
19
+ Text,
20
+ StyleSheet,
21
+ View,
22
+ FlatList,
23
+ } from 'react-native';
24
+ import { useTheme } from 'react-native-paper';
25
+ import { useTranslation } from 'react-i18next';
26
+ import { useFocusEffect } from '@react-navigation/native';
27
+
28
+ import { NewsListItem } from '@olea-bps/components'
29
+ import { useApiProvider } from '@olea-bps/context-news';
30
+
31
+ import componentStyles from './styles';
32
+
33
+ export default function NewsList(props) {
34
+ const theme = useTheme();
35
+ const { t } = useTranslation()
36
+ const apiProvider = useApiProvider();
37
+
38
+ const { navigation, active, newsChannelId } = props;
39
+ const { themeStyles, colors, paddings } = theme;
40
+
41
+ const [news, setNews] = useState(null);
42
+
43
+ const styles = useMemo(
44
+ () => StyleSheet.create(componentStyles(theme)),
45
+ [theme, componentStyles]
46
+ );
47
+
48
+ const fetchNewsByNewsChannel = useCallback(
49
+ () => {
50
+ if (apiProvider && newsChannelId) {
51
+ // News von API abrufen
52
+ apiProvider.getNews(newsChannelId)
53
+ // An jede News wird die originale NewsChannel-ID angefügt
54
+ .then(newsItems => newsItems.map(news => ({ ...news, newsChannelId })))
55
+ // Aktualisieren des States für News
56
+ .then(setNews);
57
+ }
58
+ },
59
+ [newsChannelId, apiProvider]
60
+ )
61
+
62
+ // Aktualisieren der News, wenn das Modul gewechselt wurde und wieder zurück ins News-Modul gewechselt wird bzw. generell die Komponente im Fokus der App ist
63
+ useFocusEffect(
64
+ fetchNewsByNewsChannel
65
+ );
66
+
67
+ // Aktualisieren der News, wenn die NewsList in einer TabView ist und gerade als Tab aktiv ist
68
+ useEffect(
69
+ () => {
70
+ if (active) {
71
+ fetchNewsByNewsChannel();
72
+ }
73
+ },
74
+ [active, fetchNewsByNewsChannel]
75
+ )
76
+
77
+ // Aktualisieren der News, wenn App im Hintergrund war und wieder vorgeholt wird
78
+ useEffect(
79
+ () => {
80
+ const subscription = AppState.addEventListener(
81
+ 'change',
82
+ nextAppState => {
83
+ if (nextAppState === 'active') {
84
+ fetchNewsByNewsChannel();
85
+ }
86
+ });
87
+
88
+ return () => {
89
+ subscription.remove();
90
+ };
91
+ },
92
+ [fetchNewsByNewsChannel]
93
+ );
94
+
95
+ return (
96
+ <View style={themeStyles.container}>
97
+ {
98
+ Array.isArray(news)
99
+ ? <FlatList
100
+ data={news}
101
+ keyExtractor={(news, index) => news?.guid ?? news?.title ?? index}
102
+ renderItem={({ item }) => <NewsListItem news={item} navigation={navigation} />}
103
+ ItemSeparatorComponent={(props) => {
104
+ return (<View style={styles.listSeperator} />);
105
+ }}
106
+ initialNumToRender={6}
107
+ contentContainerStyle={{ paddingBottom: paddings.default }}
108
+ style={styles.innerContainer}
109
+ ListEmptyComponent={
110
+ <View style={themeStyles.noticeTextContainer}>
111
+ <Text style={themeStyles.noticeText}>{t('news:noResult')}</Text>
112
+ </View>
113
+ }
114
+ />
115
+ : <ActivityIndicator style={styles.activity} size="large" color={colors.loadingIndicator} />
116
+
117
+ }
118
+ </View>
119
+ );
120
+ }