@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.
- package/AppBar/index.js +83 -0
- package/AppBar/styles.js +27 -0
- package/BookDetail/index.js +268 -0
- package/BookDetail/styles.js +124 -0
- package/Component/342/200/216JobsFilter/index.js +188 -0
- package/Component/342/200/216JobsFilter/styles.js +12 -0
- package/ConnectivityWarning/index.js +65 -0
- package/ConnectivityWarning/styles.js +19 -0
- package/ContactDetail/index.js +232 -0
- package/ContactDetail/styles.js +32 -0
- package/CourseDetail/index.js +357 -0
- package/CourseDetail/styles.js +59 -0
- package/CourseDetailDialog/index.js +169 -0
- package/CourseDetailDialog/styles.js +116 -0
- package/CourseInfo/index.js +219 -0
- package/CourseInfo/styles.js +40 -0
- package/DevelopmentDialog/index.js +208 -0
- package/DevelopmentDialog/styles.js +10 -0
- package/EventCodeInput/index.js +146 -0
- package/EventCodeInput/styles.js +108 -0
- package/FlexMenuEntry/index.js +84 -0
- package/FlexMenuEntry/styles.js +27 -0
- package/MainMenuEntry/index.js +88 -0
- package/MainMenuEntry/styles.js +28 -0
- package/MealItem/index.js +87 -0
- package/MealItem/styles.js +73 -0
- package/MensaMenu/index.js +307 -0
- package/MensaMenu/styles.js +94 -0
- package/MensaSlider/index.js +184 -0
- package/MensaSlider/styles.js +53 -0
- package/Modal/index.js +106 -0
- package/Modal/styles.js +8 -0
- package/NewsDetail/index.js +377 -0
- package/NewsDetail/styles.js +77 -0
- package/NewsList/index.js +120 -0
- package/NewsList/styles.js +19 -0
- package/NewsListItem/index.js +89 -0
- package/NewsListItem/styles.js +32 -0
- package/OtherCourses/index.js +152 -0
- package/OtherCourses/styles.js +10 -0
- package/PtsDeparture/index.js +140 -0
- package/PtsDeparture/styles.js +7 -0
- package/PtsStation/index.js +183 -0
- package/PtsStation/styles.js +47 -0
- package/QuickLinks/index.js +127 -0
- package/QuickLinks/styles.js +45 -0
- package/RoomDetail/index.js +281 -0
- package/RoomDetail/styles.js +56 -0
- package/ScaledImage/index.js +92 -0
- package/SearchResults/index.js +362 -0
- package/SearchResults/styles.js +59 -0
- package/SettingSection/index.js +54 -0
- package/SettingSection/styles.js +15 -0
- package/SettingsDialog/index.js +52 -0
- package/SettingsDialog/styles.js +12 -0
- package/SettingsDialogRadio/index.js +66 -0
- package/SettingsDialogRadio/styles.js +7 -0
- package/SettingsDialogSelect/index.js +73 -0
- package/SettingsDialogSelect/styles.js +7 -0
- package/TimetableCodeInput/index.js +201 -0
- package/TimetableCodeInput/styles.js +28 -0
- package/TimetableDay/index.js +266 -0
- package/TimetableDay/styles.js +103 -0
- package/TimetableEvent/index.js +163 -0
- package/TimetableEvent/styles.js +108 -0
- package/TimetableList/index.js +116 -0
- package/TimetableList/styles.js +109 -0
- package/TimetableMonth/index.js +156 -0
- package/TimetableMonth/styles.js +29 -0
- package/TimetableWeek/index.js +245 -0
- package/TimetableWeek/styles.js +58 -0
- package/TopNews/index.js +282 -0
- package/TopNews/styles.js +125 -0
- package/TopNewsHtwk/index.js +279 -0
- package/TopNewsHtwk/styles.js +142 -0
- package/WebView/index.js +108 -0
- package/WebView/styles.js +11 -0
- package/index.js +39 -0
- 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))
|
package/Modal/styles.js
ADDED
|
@@ -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
|
+
}
|