@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.
- package/Callmanager/index.js +148 -0
- package/Callmanager/styles.js +62 -0
- package/Canteens/index.js +383 -0
- package/Canteens/styles.js +89 -0
- package/Dashboard/index.js +176 -0
- package/Dashboard/styles.js +16 -0
- package/DashboardHtwk/index.js +197 -0
- package/DashboardHtwk/styles.js +24 -0
- package/Event/index.js +369 -0
- package/Event/styles.js +63 -0
- package/FeedList/index.js +165 -0
- package/FeedList/styles.js +15 -0
- package/FeedNews/index.js +243 -0
- package/FeedNews/styles.js +32 -0
- package/Flexmenu/index.js +185 -0
- package/Flexmenu/styles.js +62 -0
- package/FlexmenuWebview/index.js +167 -0
- package/FlexmenuWebview/styles.js +16 -0
- package/Howhy/index.js +151 -0
- package/Howhy/styles.js +11 -0
- package/Jobs/index.js +217 -0
- package/Jobs/styles.js +41 -0
- package/MainMenu/index.js +329 -0
- package/MainMenu/styles.js +36 -0
- package/NewsTabbar/index.js +164 -0
- package/NewsTabbar/styles.js +27 -0
- package/Opal/index.js +136 -0
- package/Opal/styles.js +11 -0
- package/PublicTransportTicket/index.js +173 -0
- package/PublicTransportTicket/styles.js +17 -0
- package/Search/index.js +186 -0
- package/Search/styles.js +67 -0
- package/SettingsAccessibility/index.js +148 -0
- package/SettingsAccessibility/styles.js +13 -0
- package/SettingsAppInfo/index.js +86 -0
- package/SettingsAppInfo/styles.js +18 -0
- package/SettingsCanteens/index.js +249 -0
- package/SettingsCanteens/styles.js +33 -0
- package/SettingsGeneral/index.js +173 -0
- package/SettingsGeneral/styles.js +28 -0
- package/SettingsNotifications/index.js +204 -0
- package/SettingsNotifications/styles.js +38 -0
- package/SettingsUser/index.js +170 -0
- package/SettingsUser/styles.js +17 -0
- package/TimetableCalendar/index.js +322 -0
- package/TimetableCalendar/styles.js +117 -0
- package/TimetableList/index.js +397 -0
- package/TimetableList/styles.js +55 -0
- package/Webviews/index.js +158 -0
- package/Webviews/styles.js +11 -0
- package/index.js +25 -0
- package/package.json +41 -0
|
@@ -0,0 +1,329 @@
|
|
|
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, { useMemo, useEffect } from 'react';
|
|
16
|
+
import {
|
|
17
|
+
SafeAreaView,
|
|
18
|
+
ScrollView,
|
|
19
|
+
StyleSheet,
|
|
20
|
+
Text,
|
|
21
|
+
} from 'react-native';
|
|
22
|
+
|
|
23
|
+
import { connect } from 'react-redux'
|
|
24
|
+
import { withTheme, useTheme, Appbar } from 'react-native-paper';
|
|
25
|
+
import { TabView, TabBar, TabBarItem } from "react-native-tab-view";
|
|
26
|
+
import { withTranslation, useTranslation } from 'react-i18next';
|
|
27
|
+
|
|
28
|
+
import merge from 'lodash/merge';
|
|
29
|
+
import moment from "moment/moment";
|
|
30
|
+
|
|
31
|
+
import { onUpdateRefreshing, useActiveStagingMenuItems } from '@olea-bps/core';
|
|
32
|
+
import { AppBar as AppbarComponent } from '@olea-bps/components';
|
|
33
|
+
import { MainMenuEntry } from '@olea-bps/components';
|
|
34
|
+
import { DevelopmentDialog } from '@olea-bps/components';
|
|
35
|
+
import { useMainMenuEntries } from '@olea-bps/context-flex-menu';
|
|
36
|
+
|
|
37
|
+
import componentStyles from './styles'
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Die Daten für einen Menüeintrag, welche für das Rendern eines Menüseintarges in einem Menü verwendet wird.
|
|
41
|
+
* Es kann eine Views oder eine URL als Ziel hinterlegt werden.
|
|
42
|
+
* Soll eine URL in einer webview geöffnet werden, muss eine URL als String über das Datenfels `url` hinterlegt werden.
|
|
43
|
+
* Eine View wird geöffnet, wenn das Datenfeld `view` einen View-Namen enthält.
|
|
44
|
+
* Das anzuzeigende Icon kann über das Datenfeld `icon` oder `iconSVG` übergeben werden.
|
|
45
|
+
*
|
|
46
|
+
* @typedef {object} MenuItem
|
|
47
|
+
* @property {string} title - Titel des Menüeintrages
|
|
48
|
+
* @property {string} [icon] Schlüssel des Icons, welches mit dem Titel angeziegt werden soll. Stattdessen kann auch `iconSVG` verwendet werden.
|
|
49
|
+
* @property {React.ReactNode} [iconSVG] Icon als SVG-ReactNode. Stattdessen kann auch `icon` verwendet werden.
|
|
50
|
+
* @property {string} [url] URL welche in einer Webview geöffnet werden soll, wenn der Nutzer den Menueintrag auswählt.
|
|
51
|
+
* @property {string} [view] Name der zu öffnenden View
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Liste von Hauptmenüeinträgen
|
|
56
|
+
*
|
|
57
|
+
* @param {object} props
|
|
58
|
+
* @param {MenuItem[]} props.menuItems Liste der anzuzeigenden Menueinträge als {@link MenuItem}s
|
|
59
|
+
* @returns {React.JSX.Element[]}
|
|
60
|
+
*/
|
|
61
|
+
function MainMenuEntryList({ menuItems }) {
|
|
62
|
+
return menuItems
|
|
63
|
+
? menuItems.map(
|
|
64
|
+
menuItem =>
|
|
65
|
+
<MainMenuEntry
|
|
66
|
+
key={menuItem.title}
|
|
67
|
+
title={menuItem.title}
|
|
68
|
+
view={menuItem?.view}
|
|
69
|
+
url={menuItem?.url}
|
|
70
|
+
icon={menuItem?.icon}
|
|
71
|
+
iconSVG={menuItem?.iconSVG}
|
|
72
|
+
isLocalized={menuItem?.isLocalized}
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
: (
|
|
76
|
+
<Text>Nicht verfügbar</Text>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Tabbar für die Kategorien des Menüs.
|
|
82
|
+
*/
|
|
83
|
+
function MainMenuTabbar(props) {
|
|
84
|
+
const { t } = useTranslation();
|
|
85
|
+
const theme = useTheme();
|
|
86
|
+
const { themeStyles } = theme;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<TabBar
|
|
90
|
+
{...props}
|
|
91
|
+
scrollEnabled
|
|
92
|
+
style={themeStyles.tabs}
|
|
93
|
+
labelStyle={themeStyles.tab}
|
|
94
|
+
activeColor={themeStyles.tabs.activeColor}
|
|
95
|
+
inactiveColor={themeStyles.tabs.inactiveColor}
|
|
96
|
+
indicatorStyle={themeStyles.tabIndicator}
|
|
97
|
+
tabStyle={{ width: 'auto', paddingHorizontal: 20 }}
|
|
98
|
+
renderTabBarItem={({ route, navigationState, ...rest}) =>
|
|
99
|
+
<TabBarItem
|
|
100
|
+
{...rest}
|
|
101
|
+
key={route.key}
|
|
102
|
+
route={route}
|
|
103
|
+
navigationState={navigationState}
|
|
104
|
+
labelStyle={themeStyles.tab}
|
|
105
|
+
activeColor={themeStyles.tabs.activeColor}
|
|
106
|
+
inactiveColor={themeStyles.tabs.inactiveColor}
|
|
107
|
+
// Die einbindung von moment.js zum Anzeigen des Wochentages sollte langfristig entfernt werden.
|
|
108
|
+
// Funktioniert die Luxon funktionalität der Wochenanzeige nicht unter iOS datetime.toFormat('ccc')
|
|
109
|
+
labelText={t(route.title).toUpperCase()}
|
|
110
|
+
accessible={true}
|
|
111
|
+
accessibilityLabel={t(route.title)}
|
|
112
|
+
/>
|
|
113
|
+
}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Scene/Inhalt eines Tabs des Hauptmenüs.
|
|
120
|
+
*
|
|
121
|
+
* @param {object} props
|
|
122
|
+
* @param {object} props.route
|
|
123
|
+
* @param {string} props.route.key Schlüssel der Route und Schlüssel, welcher für die errechnung der Menüeintrage verwendet wird
|
|
124
|
+
*/
|
|
125
|
+
function MainMenuScene({ route: { key: mainMenuItemsKey } }) {
|
|
126
|
+
const theme = useTheme();
|
|
127
|
+
const { i18n } = useTranslation();
|
|
128
|
+
const appSettings = theme?.appSettings;
|
|
129
|
+
const availableMenuItems = appSettings?.mainMenu?.items?.[mainMenuItemsKey];
|
|
130
|
+
const activeStagingMenuItems = useActiveStagingMenuItems();
|
|
131
|
+
const [flexMainMenuEntries, refreshFlexMenuEntries] = useMainMenuEntries();
|
|
132
|
+
|
|
133
|
+
useEffect(
|
|
134
|
+
() => {
|
|
135
|
+
refreshFlexMenuEntries?.();
|
|
136
|
+
},
|
|
137
|
+
[refreshFlexMenuEntries]
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
// Verfügbare Sprachen
|
|
141
|
+
const languages = appSettings?.languages;
|
|
142
|
+
// Eingestellte Sprache aus der Übersetzung holen
|
|
143
|
+
const selectedLanguage = i18n.language;
|
|
144
|
+
// Hinterlegte Sprachen durchgehen und nach dem erweiterten Sprachcode holen
|
|
145
|
+
const selectedExtLanguageCode = languages
|
|
146
|
+
// Finde die derzeitige eingestellte Sprache
|
|
147
|
+
.find(language => language?.code === selectedLanguage)
|
|
148
|
+
// Erweiterten Sprachcode von der gefundenen Sprache auslesen
|
|
149
|
+
?.extCode;
|
|
150
|
+
|
|
151
|
+
const styles = useMemo(
|
|
152
|
+
() => StyleSheet.create(componentStyles(theme)),
|
|
153
|
+
[theme]
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
// Es werden Hauptmenüeintrage herausgefiltert, die nur im Stagingmodus angezeigt werden sollen, aber nicht aktiv sind
|
|
159
|
+
const activeMenuItems = useMemo(
|
|
160
|
+
() => availableMenuItems.filter(
|
|
161
|
+
menuItem => {
|
|
162
|
+
const menuItemKey = `${menuItem?.title}`;
|
|
163
|
+
// Ist der Menüpunkt durch Appeinstellungen aktiv?
|
|
164
|
+
const isActive = menuItem?.active ?? true;
|
|
165
|
+
// Ist der Menüpunkt durch die Entwicklereinstellungen aktiviert?
|
|
166
|
+
const isActiveStagingMenuItem = activeStagingMenuItems.includes(menuItemKey);
|
|
167
|
+
|
|
168
|
+
// Entweder ist der Menüpunkt standadtmäßig aktiv oder er wurde durch den Developmenteinstellungen-Dialog aktiviert
|
|
169
|
+
return isActive || isActiveStagingMenuItem;
|
|
170
|
+
}
|
|
171
|
+
),
|
|
172
|
+
[availableMenuItems, activeStagingMenuItems]
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const flexMenuItems = useMemo(
|
|
176
|
+
() => flexMainMenuEntries
|
|
177
|
+
.filter(flexMainMenuEntry => flexMainMenuEntry.language === selectedExtLanguageCode)
|
|
178
|
+
.filter(flexMainMenuEntry => flexMainMenuEntry.type === mainMenuItemsKey)
|
|
179
|
+
.map(
|
|
180
|
+
flexMainMenuEntry =>
|
|
181
|
+
({
|
|
182
|
+
title: flexMainMenuEntry?.title,
|
|
183
|
+
icon: flexMainMenuEntry?.icon,
|
|
184
|
+
...(
|
|
185
|
+
flexMainMenuEntry?.url
|
|
186
|
+
? { url: flexMainMenuEntry.url }
|
|
187
|
+
: { view: flexMainMenuEntry?.id }
|
|
188
|
+
)
|
|
189
|
+
})
|
|
190
|
+
),
|
|
191
|
+
[flexMainMenuEntries, selectedExtLanguageCode, mainMenuItemsKey]
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<ScrollView style={styles.container}>
|
|
196
|
+
<MainMenuEntryList
|
|
197
|
+
menuItems={[
|
|
198
|
+
...activeMenuItems,
|
|
199
|
+
...flexMenuItems,
|
|
200
|
+
]}
|
|
201
|
+
/>
|
|
202
|
+
</ScrollView>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Main Menu View
|
|
208
|
+
*
|
|
209
|
+
* Show a menu of views which are not available in
|
|
210
|
+
* the bottom tab bar. The menu split in categories.
|
|
211
|
+
* Each category has its own tab.
|
|
212
|
+
*
|
|
213
|
+
* Parameters:
|
|
214
|
+
* - none
|
|
215
|
+
*
|
|
216
|
+
* Navigation-Parameters:
|
|
217
|
+
* - none
|
|
218
|
+
*/
|
|
219
|
+
class MainMenuView extends React.Component {
|
|
220
|
+
static navigationOptions = {
|
|
221
|
+
header: null
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Styles of this component
|
|
225
|
+
styles;
|
|
226
|
+
|
|
227
|
+
state = {
|
|
228
|
+
index: 0,
|
|
229
|
+
routes: null,
|
|
230
|
+
developCounter: 0,
|
|
231
|
+
isDevelopMenuVisible: false,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
constructor(props) {
|
|
236
|
+
super(props);
|
|
237
|
+
|
|
238
|
+
// ------------------------------------------------------------------------
|
|
239
|
+
// PLUGIN FUNCTIONALITY
|
|
240
|
+
// ------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
const { pluginStyles, theme } = this.props;
|
|
243
|
+
this.styles = componentStyles(theme);
|
|
244
|
+
|
|
245
|
+
if (pluginStyles) {
|
|
246
|
+
this.styles = merge(this.styles, pluginStyles);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this.styles = StyleSheet.create(this.styles);
|
|
250
|
+
|
|
251
|
+
// ------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
const { appSettings } = this.props.theme;
|
|
254
|
+
const menuRoutes = appSettings.mainMenu.routes;
|
|
255
|
+
if (menuRoutes) {
|
|
256
|
+
this.state.routes = menuRoutes;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
componentDidMount() {
|
|
261
|
+
this.props.navigation.addListener('focus', () => {
|
|
262
|
+
this.setState({ developCounter: 0 });
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
_hideDevDialog = () => {
|
|
267
|
+
this.setState({ isDevelopMenuVisible: false });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
render() {
|
|
271
|
+
// ------------------------------------------------------------------------
|
|
272
|
+
// PLUGIN FUNCTIONALITY
|
|
273
|
+
// ------------------------------------------------------------------------
|
|
274
|
+
const PluginComponent = this.props.pluginComponent;
|
|
275
|
+
if (PluginComponent) {
|
|
276
|
+
return <PluginComponent />;
|
|
277
|
+
}
|
|
278
|
+
// ------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
const {
|
|
281
|
+
t,
|
|
282
|
+
theme: { themeStyles, colors },
|
|
283
|
+
settings: { settingsDevelop: { useStaging } } } = this.props;
|
|
284
|
+
const {
|
|
285
|
+
developCounter
|
|
286
|
+
} = this.state;
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<SafeAreaView style={[this.styles.container, themeStyles.appSafeAreaContainer]}>
|
|
290
|
+
<AppbarComponent title={t('menu:title')} {...this.props}
|
|
291
|
+
rightAction={
|
|
292
|
+
<>
|
|
293
|
+
{useStaging ? <Text>Server: Staging</Text> : null}
|
|
294
|
+
<Appbar.Action icon={developCounter >= 10 ? 'grid' : undefined} onPress={this._handlePressDevelop.bind(this)} />
|
|
295
|
+
</>
|
|
296
|
+
}
|
|
297
|
+
/>
|
|
298
|
+
{this.state.routes && <TabView
|
|
299
|
+
style={this.props.style}
|
|
300
|
+
navigationState={this.state}
|
|
301
|
+
renderTabBar={MainMenuTabbar}
|
|
302
|
+
renderScene={(props) => <MainMenuScene {...props} />}
|
|
303
|
+
onIndexChange={index => this.setState({ index })}
|
|
304
|
+
/>}
|
|
305
|
+
<DevelopmentDialog
|
|
306
|
+
visible={this.state.isDevelopMenuVisible}
|
|
307
|
+
onDismiss={this._hideDevDialog}
|
|
308
|
+
/>
|
|
309
|
+
</SafeAreaView>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
_handlePressDevelop = () => {
|
|
314
|
+
if (this.state.developCounter >= 10) {
|
|
315
|
+
this.setState({ isDevelopMenuVisible: true });
|
|
316
|
+
}
|
|
317
|
+
this.setState({ developCounter: this.state.developCounter + 1 });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const mapStateToProps = state => {
|
|
322
|
+
return {
|
|
323
|
+
pluginComponent: state.pluginReducer.mainMenu.component,
|
|
324
|
+
pluginStyles: state.pluginReducer.mainMenu.styles,
|
|
325
|
+
settings: state.settingReducer
|
|
326
|
+
};
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
export default connect(mapStateToProps, { onUpdateRefreshing })(withTranslation()(withTheme(MainMenuView)))
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export default function (theme) {
|
|
2
|
+
return {
|
|
3
|
+
container: {
|
|
4
|
+
flex: 1,
|
|
5
|
+
paddingTop: 0,
|
|
6
|
+
backgroundColor: theme.colors.background,
|
|
7
|
+
},
|
|
8
|
+
optionTextContainer: {
|
|
9
|
+
flex: 1,
|
|
10
|
+
flexDirection: 'row',
|
|
11
|
+
paddingVertical: 20,
|
|
12
|
+
paddingHorizontal: 20,
|
|
13
|
+
alignContent: 'center'
|
|
14
|
+
},
|
|
15
|
+
optionsTitleText: {
|
|
16
|
+
fontSize: theme.fontSizes.l,
|
|
17
|
+
textAlign: 'left'
|
|
18
|
+
},
|
|
19
|
+
optionIconContainer: {
|
|
20
|
+
flex: 0.2
|
|
21
|
+
},
|
|
22
|
+
option: {
|
|
23
|
+
backgroundColor: theme.colors.background,
|
|
24
|
+
paddingHorizontal: 0,
|
|
25
|
+
paddingVertical: 5,
|
|
26
|
+
borderBottomWidth: 1,
|
|
27
|
+
borderBottomColor: theme.colors.accent
|
|
28
|
+
},
|
|
29
|
+
optionText: {
|
|
30
|
+
flex: 1,
|
|
31
|
+
fontSize: theme.fontSizes.subtitle,
|
|
32
|
+
marginTop: 1,
|
|
33
|
+
alignSelf: 'center'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
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, { useMemo, useState, useEffect } from 'react';
|
|
16
|
+
import {
|
|
17
|
+
ActivityIndicator,
|
|
18
|
+
SafeAreaView,
|
|
19
|
+
StyleSheet,
|
|
20
|
+
Text,
|
|
21
|
+
View,
|
|
22
|
+
Dimensions
|
|
23
|
+
} from 'react-native';
|
|
24
|
+
|
|
25
|
+
import { TabView, TabBar, TabBarItem } from "react-native-tab-view";
|
|
26
|
+
import { useTheme } from 'react-native-paper';
|
|
27
|
+
import { useTranslation } from 'react-i18next';
|
|
28
|
+
|
|
29
|
+
import { AppBar as AppbarComponent } from '@olea-bps/components';
|
|
30
|
+
import { NewsList } from '@olea-bps/components';
|
|
31
|
+
import { useNewsChannels } from '@olea-bps/context-news';
|
|
32
|
+
|
|
33
|
+
import componentStyles from './styles';
|
|
34
|
+
|
|
35
|
+
export default function NewsTabBarView(props) {
|
|
36
|
+
const theme = useTheme();
|
|
37
|
+
const { colors, themeStyles } = theme;
|
|
38
|
+
const { t } = useTranslation();
|
|
39
|
+
const [newsChannels, refreshNewsChannels] = useNewsChannels();
|
|
40
|
+
|
|
41
|
+
const [newsTabIndex, setNewsTabIndex] = useState(0);
|
|
42
|
+
|
|
43
|
+
const newsTabRoutes = useMemo(
|
|
44
|
+
() =>
|
|
45
|
+
Array.isArray(newsChannels)
|
|
46
|
+
? newsChannels?.map(
|
|
47
|
+
newschannel =>
|
|
48
|
+
({
|
|
49
|
+
key: newschannel.feedid,
|
|
50
|
+
title: newschannel.title,
|
|
51
|
+
})
|
|
52
|
+
)
|
|
53
|
+
: null,
|
|
54
|
+
[newsChannels]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const styles = useMemo(
|
|
58
|
+
() => StyleSheet.create(componentStyles(theme)),
|
|
59
|
+
[theme, componentStyles]
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
useEffect(
|
|
63
|
+
() => {
|
|
64
|
+
refreshNewsChannels?.();
|
|
65
|
+
},
|
|
66
|
+
[refreshNewsChannels]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<SafeAreaView style={[styles.container, themeStyles.appSafeAreaContainer]}>
|
|
71
|
+
<AppbarComponent
|
|
72
|
+
{...props}
|
|
73
|
+
title={t('news:feeds')}
|
|
74
|
+
/>
|
|
75
|
+
<View style={themeStyles.container}>
|
|
76
|
+
{
|
|
77
|
+
!Array.isArray(newsTabRoutes)
|
|
78
|
+
? <ActivityIndicator
|
|
79
|
+
style={styles.activity}
|
|
80
|
+
size="large"
|
|
81
|
+
color={colors.loadingIndicator}
|
|
82
|
+
/>
|
|
83
|
+
: newsTabRoutes.length
|
|
84
|
+
? newsTabRoutes.length === 1
|
|
85
|
+
? <View style={styles.screenView} >
|
|
86
|
+
<View style={styles.tabHeaderView} >
|
|
87
|
+
<Text style={styles.tabHeaderText} >
|
|
88
|
+
{newsTabRoutes[0].title}
|
|
89
|
+
</Text>
|
|
90
|
+
</View>
|
|
91
|
+
<NewsList
|
|
92
|
+
active={true}
|
|
93
|
+
newsChannelId={newsTabRoutes[0].key}
|
|
94
|
+
navigation={props.navigation}
|
|
95
|
+
/>
|
|
96
|
+
</View>
|
|
97
|
+
: <TabView
|
|
98
|
+
style={props.style}
|
|
99
|
+
navigationState={{
|
|
100
|
+
routes: newsTabRoutes,
|
|
101
|
+
index: newsTabIndex,
|
|
102
|
+
}}
|
|
103
|
+
renderTabBar={
|
|
104
|
+
props => {
|
|
105
|
+
const { key, ...restProps } = props;
|
|
106
|
+
return (<View style={styles.tabBarContainer}>
|
|
107
|
+
<TabBar
|
|
108
|
+
{...restProps}
|
|
109
|
+
scrollEnabled
|
|
110
|
+
style={themeStyles.tabs}
|
|
111
|
+
labelStyle={themeStyles.tab}
|
|
112
|
+
indicatorStyle={themeStyles.tabIndicator}
|
|
113
|
+
tabStyle={{ width: 'auto', paddingHorizontal: 20 }}
|
|
114
|
+
initialLayout={{
|
|
115
|
+
width: Dimensions.get('window').width,
|
|
116
|
+
height: Dimensions.get('window').height
|
|
117
|
+
}}
|
|
118
|
+
renderTabBarItem={({ route, navigationState, ...rest}) =>
|
|
119
|
+
<TabBarItem
|
|
120
|
+
{...rest}
|
|
121
|
+
key={route.key}
|
|
122
|
+
route={route}
|
|
123
|
+
navigationState={navigationState}
|
|
124
|
+
labelStyle={themeStyles.tab}
|
|
125
|
+
activeColor={themeStyles.tabs.activeColor}
|
|
126
|
+
inactiveColor={themeStyles.tabs.inactiveColor}
|
|
127
|
+
// Die einbindung von moment.js zum Anzeigen des Wochentages sollte langfristig entfernt werden.
|
|
128
|
+
// Funktioniert die Luxon funktionalität der Wochenanzeige nicht unter iOS datetime.toFormat('ccc')
|
|
129
|
+
labelText={route.title}
|
|
130
|
+
accessible={true}
|
|
131
|
+
accessibilityLabel={route.title}
|
|
132
|
+
/>
|
|
133
|
+
}
|
|
134
|
+
/>
|
|
135
|
+
</View>);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
renderScene={
|
|
139
|
+
({ route }) =>
|
|
140
|
+
<NewsList
|
|
141
|
+
active={route.key === newsTabRoutes[newsTabIndex].key}
|
|
142
|
+
newsChannelId={route.key}
|
|
143
|
+
navigation={props.navigation}
|
|
144
|
+
/>
|
|
145
|
+
}
|
|
146
|
+
onIndexChange={setNewsTabIndex}
|
|
147
|
+
lazy
|
|
148
|
+
lazyPreloadDistance={1}
|
|
149
|
+
/>
|
|
150
|
+
: <View style={styles.screenView} >
|
|
151
|
+
<View style={styles.tabHeaderView} >
|
|
152
|
+
<Text style={styles.tabHeaderText} >
|
|
153
|
+
{t('news:noResult')}
|
|
154
|
+
</Text>
|
|
155
|
+
</View>
|
|
156
|
+
<Text>
|
|
157
|
+
{t('news:noResult')}
|
|
158
|
+
</Text>
|
|
159
|
+
</View>
|
|
160
|
+
}
|
|
161
|
+
</View>
|
|
162
|
+
</SafeAreaView>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default function(theme) {
|
|
2
|
+
return {
|
|
3
|
+
container: {
|
|
4
|
+
flex: 1
|
|
5
|
+
},
|
|
6
|
+
innerContainer: {
|
|
7
|
+
flex: 1
|
|
8
|
+
},
|
|
9
|
+
cardContent: {
|
|
10
|
+
paddingLeft: theme.paddings.large,
|
|
11
|
+
paddingTop: theme.paddings.small,
|
|
12
|
+
paddingBottom: theme.paddings.small
|
|
13
|
+
},
|
|
14
|
+
screenView: {
|
|
15
|
+
flex: 1
|
|
16
|
+
},
|
|
17
|
+
tabHeaderText: {
|
|
18
|
+
...theme.fonts.medium,
|
|
19
|
+
color: theme.colors.primaryText
|
|
20
|
+
},
|
|
21
|
+
tabHeaderView: {
|
|
22
|
+
backgroundColor: theme.colors.primary,
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
padding: theme.paddings.xsmall
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
package/Opal/index.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
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, ActivityIndicator, View} from 'react-native';
|
|
17
|
+
import {connect} from 'react-redux'
|
|
18
|
+
import {Appbar, withTheme} from "react-native-paper";
|
|
19
|
+
import { WebView } from 'react-native-webview';
|
|
20
|
+
import {withTranslation} from "react-i18next";
|
|
21
|
+
import merge from 'lodash/merge';
|
|
22
|
+
|
|
23
|
+
import componentStyles from "./styles";
|
|
24
|
+
import { AppBar as AppbarComponent } from '@olea-bps/components';
|
|
25
|
+
import IconsOpenasist from "@olea-bps/icons-openasist";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Opal View
|
|
29
|
+
*
|
|
30
|
+
* Shows the opal site in a webview
|
|
31
|
+
*
|
|
32
|
+
* Parameters:
|
|
33
|
+
* - none
|
|
34
|
+
*
|
|
35
|
+
* Navigation-Parameters:
|
|
36
|
+
* - none
|
|
37
|
+
*/
|
|
38
|
+
class OpalView extends React.Component {
|
|
39
|
+
static navigationOptions = {
|
|
40
|
+
header: null
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
state = {
|
|
45
|
+
refreshing: false,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Styles of this component
|
|
49
|
+
styles;
|
|
50
|
+
|
|
51
|
+
webView = {
|
|
52
|
+
canGoBack: false,
|
|
53
|
+
ref: null,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
constructor(props) {
|
|
57
|
+
super(props);
|
|
58
|
+
|
|
59
|
+
// ------------------------------------------------------------------------
|
|
60
|
+
// PLUGIN FUNCTIONALITY
|
|
61
|
+
// ------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
const {pluginStyles, theme} = this.props;
|
|
64
|
+
this.styles = componentStyles(theme);
|
|
65
|
+
|
|
66
|
+
if (pluginStyles) {
|
|
67
|
+
this.styles = merge(this.styles, pluginStyles);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.styles = StyleSheet.create(this.styles);
|
|
71
|
+
|
|
72
|
+
// ------------------------------------------------------------------------
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
render() {
|
|
77
|
+
// ------------------------------------------------------------------------
|
|
78
|
+
// PLUGIN FUNCTIONALITY
|
|
79
|
+
// ------------------------------------------------------------------------
|
|
80
|
+
const PluginComponent = this.props.pluginComponent;
|
|
81
|
+
if (PluginComponent) {
|
|
82
|
+
return <PluginComponent />;
|
|
83
|
+
}
|
|
84
|
+
// ------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
const {theme:{colors, themeStyles, customScript}, t } = this.props;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<SafeAreaView style={[this.styles.container, themeStyles.appSafeAreaContainer]}>
|
|
90
|
+
<View style={{zIndex: 100, position: 'relative'}}>
|
|
91
|
+
<AppbarComponent {...this.props}
|
|
92
|
+
title={t('opal:title')}
|
|
93
|
+
leftAction={
|
|
94
|
+
<Appbar.Action
|
|
95
|
+
icon={props => <IconsOpenasist {...props} icon={'back'} color={colors.primaryText} /> }
|
|
96
|
+
onPress={() => {
|
|
97
|
+
if (this.webView.canGoBack) {
|
|
98
|
+
this.webView.ref.goBack();
|
|
99
|
+
} else {
|
|
100
|
+
this.props.navigation.goBack(null);
|
|
101
|
+
}
|
|
102
|
+
}}
|
|
103
|
+
accessible={true}
|
|
104
|
+
accessibilityLabel={(this.webView.canGoBack) ? t('accessibility:appbar.navigateBack') : t('accessibility:appbar.back')}
|
|
105
|
+
/>
|
|
106
|
+
}
|
|
107
|
+
rightAction={
|
|
108
|
+
<Appbar.Action
|
|
109
|
+
icon="refresh"
|
|
110
|
+
onPress={() => this.webView.ref.reload()}
|
|
111
|
+
accessible={true}
|
|
112
|
+
accessibilityLabel={t('accessibility:appbar.reload')}
|
|
113
|
+
/>
|
|
114
|
+
}/>
|
|
115
|
+
</View>
|
|
116
|
+
<WebView
|
|
117
|
+
ref={ref => this.webView.ref = ref}
|
|
118
|
+
source={{uri: t('opal:url')}}
|
|
119
|
+
startInLoadingState={true}
|
|
120
|
+
injectedJavaScript={customScript}
|
|
121
|
+
renderLoading={() => <ActivityIndicator style={this.styles.activity} size="large" color={colors.loadingIndicator} />}
|
|
122
|
+
onNavigationStateChange={(navState) => { this.webView.canGoBack = navState.canGoBack; }}
|
|
123
|
+
/>
|
|
124
|
+
</SafeAreaView>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const mapStateToProps = state => {
|
|
130
|
+
return {
|
|
131
|
+
pluginComponent: state.pluginReducer.opal.component,
|
|
132
|
+
pluginStyles: state.pluginReducer.opal.styles,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export default connect(mapStateToProps, null)(withTranslation()(withTheme(OpalView)))
|