@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,397 @@
|
|
|
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 { useState, useMemo, useCallback } from 'react';
|
|
16
|
+
import {
|
|
17
|
+
SafeAreaView,
|
|
18
|
+
StyleSheet,
|
|
19
|
+
View,
|
|
20
|
+
ScrollView,
|
|
21
|
+
TouchableOpacity,
|
|
22
|
+
RefreshControl,
|
|
23
|
+
ActivityIndicator,
|
|
24
|
+
Linking,
|
|
25
|
+
} from 'react-native';
|
|
26
|
+
import { connect } from 'react-redux'
|
|
27
|
+
import { TabView, TabBar, TabBarItem } from 'react-native-tab-view';
|
|
28
|
+
import { Appbar, useTheme, Text } from 'react-native-paper';
|
|
29
|
+
import { useTranslation } from 'react-i18next';
|
|
30
|
+
import { useFocusEffect } from '@react-navigation/native';
|
|
31
|
+
|
|
32
|
+
import { DateTime, Duration } from 'luxon';
|
|
33
|
+
|
|
34
|
+
import { onUpdateRefreshing, store } from '@olea-bps/core';
|
|
35
|
+
import { ApiProviderNotInitializedError, useCourses, useTimetableCode } from '@olea-bps/context-timetable';
|
|
36
|
+
import { AppBar as AppbarComponent } from '@olea-bps/components';
|
|
37
|
+
import { OtherCourses as OtherCoursesComponent } from '@olea-bps/components';
|
|
38
|
+
import { TimetableList as TimetableListComponent } from '@olea-bps/components';
|
|
39
|
+
import { TimetableCodeInput } from '@olea-bps/components';
|
|
40
|
+
import { CourseDetailDialog } from '@olea-bps/components';
|
|
41
|
+
|
|
42
|
+
import componentStyles from './styles';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Timetable View
|
|
46
|
+
*
|
|
47
|
+
* Displays the timetable, if the has imported one. If no timetable
|
|
48
|
+
* is available, the view will provide a import. The view also
|
|
49
|
+
* provides a button to import a new timetable.
|
|
50
|
+
*
|
|
51
|
+
* Parameters:
|
|
52
|
+
* - none
|
|
53
|
+
*
|
|
54
|
+
* Navigation-Parameters:
|
|
55
|
+
* - none
|
|
56
|
+
*/
|
|
57
|
+
function TimetableViewList(props) {
|
|
58
|
+
const theme = useTheme();
|
|
59
|
+
const { t } = useTranslation();
|
|
60
|
+
const [selectedCourse, setSelectedCourse] = useState(null);
|
|
61
|
+
|
|
62
|
+
// Get all required constants from props
|
|
63
|
+
const {
|
|
64
|
+
settings: { settingsGeneral: { language } },
|
|
65
|
+
} = props;
|
|
66
|
+
|
|
67
|
+
const { colors, themeStyles, appSettings: { modules: { timetable: { downloadEnabled } } } } = theme;
|
|
68
|
+
|
|
69
|
+
const componentName = TimetableViewList.name;
|
|
70
|
+
const timetableSettings = theme?.appSettings?.modules?.timetable;
|
|
71
|
+
const disabledWeekdays = timetableSettings?.disabledWeekdays ?? [];
|
|
72
|
+
const weeksToRender = timetableSettings?.weeksToRender ?? 2;
|
|
73
|
+
// Jetziger Zeitpunkt
|
|
74
|
+
const now = DateTime.now();
|
|
75
|
+
// Zeitpunkt, der auf den Wochenanfang zeigt
|
|
76
|
+
const startDate = now.startOf('week');
|
|
77
|
+
// Zeitpunkt, der auf den letzten anzuzeigenen Tag zeigt
|
|
78
|
+
const endDate = startDate.plus({ weeks: weeksToRender }).endOf('week');
|
|
79
|
+
|
|
80
|
+
// Generiert die Tabs beginnend beim Wochenanfang bis zum letzten Tag der Ansicht
|
|
81
|
+
// Es werden die nicht anzuzeigenden Wochentage herausgefiltert
|
|
82
|
+
const daysTabViewRoutes = useMemo(
|
|
83
|
+
() => {
|
|
84
|
+
// Berechenen der Anzahl der zu zeigenden Tage bzw. Anzahl der Tabs
|
|
85
|
+
const daysToRender = Duration.fromDurationLike({ weeks: weeksToRender }).as('days');
|
|
86
|
+
|
|
87
|
+
// Erstellen der Tabs
|
|
88
|
+
return Array.from(
|
|
89
|
+
// Es wird ein Array erzeugt, welches so lang ist wie die Anzahl der zu zeigenden Tage
|
|
90
|
+
{ length: daysToRender },
|
|
91
|
+
// Tabs anhand des Indexes berechnen
|
|
92
|
+
(_, index) => {
|
|
93
|
+
|
|
94
|
+
// Festlegen des Tages des entsprechenden Tabs beim Index
|
|
95
|
+
const currentDay = startDate
|
|
96
|
+
// Es wird der Start genommen und um index-viele Tage in die Zukunft verschoben
|
|
97
|
+
.plus({ days: index })
|
|
98
|
+
// Abschliesend wird die Uhrzeit auf 00:00, also den Anfang des Tages, gesetzt.
|
|
99
|
+
.startOf('day');
|
|
100
|
+
|
|
101
|
+
// Tab wird als Objekt zurückgeben
|
|
102
|
+
return {
|
|
103
|
+
// Der Schlüssel des Tabs ist das Datum des Tabs im ISO-Format
|
|
104
|
+
// Somit lässt sich über das Datum der entsprechende Tab aktivieren
|
|
105
|
+
key: currentDay.toISODate(),
|
|
106
|
+
// Für die weiter Verarbeitung, wird das Datum als Datumsobjekt mitgeben
|
|
107
|
+
// Dem Datumsobject wird die eingestellt Sprache gegeben
|
|
108
|
+
date: currentDay.setLocale(language),
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
.filter(dayRoute => !disabledWeekdays.includes(dayRoute.date.weekday))
|
|
113
|
+
},
|
|
114
|
+
[weeksToRender, disabledWeekdays, startDate, language]
|
|
115
|
+
);
|
|
116
|
+
// Create state for tab view index
|
|
117
|
+
const [daysTabViewActiveRouteIndex, setDaysTabViewActiveRouteIndex] = useState(() => {
|
|
118
|
+
// Present day
|
|
119
|
+
const today = DateTime.now().startOf('day');
|
|
120
|
+
// Search for the index of today or the day after. If no index can be found, -1 is taken.
|
|
121
|
+
let initialDayTabIndex = daysTabViewRoutes.findIndex(dayTab => dayTab.date >= today);
|
|
122
|
+
// If no index could be found, the last tab is taken.
|
|
123
|
+
// If only one week is rendered and the weekend days are switched off, no following day will be found.
|
|
124
|
+
initialDayTabIndex = initialDayTabIndex === -1 ? daysTabViewRoutes.length - 1 : initialDayTabIndex;
|
|
125
|
+
return initialDayTabIndex;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const [timetableCode] = useTimetableCode();
|
|
129
|
+
const [courses, refreshCourses] = useCourses();
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* State der speichert, ob die Code-Eingabe gezeigt werden soll.
|
|
133
|
+
*
|
|
134
|
+
* Der State kann 3 Zustände annehmen:
|
|
135
|
+
* - null : Es wurde noch kein Stundenplan importiert -> Code-Eingabe wird angezeigt
|
|
136
|
+
* - true : Der Nutzer möchte die Code-Eingabe sehen -> Code-Eingabe wird angezeigt
|
|
137
|
+
* - false: Die Nutzer möchte die Code-Eingabe nicht sehen -> Stundenplan wird angezeigt
|
|
138
|
+
*
|
|
139
|
+
* Der null-Wert wird benutzt um festzustellen, dass die App die Code-Eingabe anzeigt, weil bisher kein Stundenplan importiert wurde.
|
|
140
|
+
* Somit kann nach dem Import geprüft werden, ob der State den null-Wert hält und automatisch zu den Stundenplan schalten.
|
|
141
|
+
*/
|
|
142
|
+
const [showImport, setShowImport] = useState(timetableCode === null && courses === null ? true : false);
|
|
143
|
+
const [coursesRefreshing, setCoursesRefreshing] = useState(false);
|
|
144
|
+
|
|
145
|
+
// Generate styles. Will be generated only if not present or the theme property changes.
|
|
146
|
+
const styles = useMemo(
|
|
147
|
+
() => StyleSheet.create(componentStyles(theme)),
|
|
148
|
+
[theme]
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
const otherCourses = courses?.filter?.(course => course?.times?.some(courseTime => courseTime?.dayOfWeek === 0));
|
|
152
|
+
const activeTabDate = daysTabViewRoutes[daysTabViewActiveRouteIndex]?.date;
|
|
153
|
+
const activeTabWeek = activeTabDate.weekNumber;
|
|
154
|
+
|
|
155
|
+
// Refreshen der Vorlesungen, wenn die View angeziegt wird und den Fokus vom nutzer bekommt.
|
|
156
|
+
useFocusEffect(
|
|
157
|
+
useCallback(
|
|
158
|
+
() => {
|
|
159
|
+
refreshCourses(startDate.toISODate(), endDate.toISODate())
|
|
160
|
+
.catch((reason) => {
|
|
161
|
+
if (reason instanceof ApiProviderNotInitializedError) {
|
|
162
|
+
console.error(componentName, ': ', reason);
|
|
163
|
+
} else {
|
|
164
|
+
console.debug(componentName, ': ', reason);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
[])
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
/*
|
|
172
|
+
Depending on the existence of a timetable, either the timetable or the view for the input of a timetablecode will be rendered
|
|
173
|
+
To decide which components have to rendered, the flag showImport is used.
|
|
174
|
+
If its value is true, there already is an imported timetable as well as a list of courses
|
|
175
|
+
and the function for rendering the timetable-tabview(renderTabView) is called.
|
|
176
|
+
Otherwise, the function for entering a timetablecode will be called(renderTimetableCodeInput)
|
|
177
|
+
*/
|
|
178
|
+
return (
|
|
179
|
+
<SafeAreaView style={[styles.container, themeStyles.appSafeAreaContainer]}>
|
|
180
|
+
<AppbarComponent {...props}
|
|
181
|
+
title={t('timetable:title')}
|
|
182
|
+
subtitle={`${t('timetable:weekNumber', { kw: activeTabWeek })} / ${t(activeTabWeek % 2 === 0 ? 'timetable:evenWeek' : 'timetable:oddWeek')}`}
|
|
183
|
+
// in addition to the selection of the icon for the right Action in the app-bar the color for the icon also has to be defined
|
|
184
|
+
rightAction={
|
|
185
|
+
<>
|
|
186
|
+
{
|
|
187
|
+
downloadEnabled
|
|
188
|
+
? <Appbar.Action
|
|
189
|
+
icon={'download'}
|
|
190
|
+
onPress={
|
|
191
|
+
async () => {
|
|
192
|
+
const { modules: { timetable: { downloadUrl } } } = props.theme.appSettings;
|
|
193
|
+
await Linking.openURL(downloadUrl + timetableCode);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
color={colors.appbarIconColor}
|
|
197
|
+
accessibilityLabel={t('accessibility:timetable:downloadTimetableButton')}
|
|
198
|
+
/>
|
|
199
|
+
: null
|
|
200
|
+
}
|
|
201
|
+
<Appbar.Action
|
|
202
|
+
icon={showImport ? 'table' : 'table-plus'}
|
|
203
|
+
color={colors.appbarIconColor}
|
|
204
|
+
onPress={
|
|
205
|
+
() => setShowImport(currentValue => !currentValue)
|
|
206
|
+
}
|
|
207
|
+
accessibilityLabel={t(
|
|
208
|
+
showImport
|
|
209
|
+
? 'accessibility:timetable:showImportButton'
|
|
210
|
+
: 'accessibility:timetable:showTimetableButton'
|
|
211
|
+
)}
|
|
212
|
+
/>
|
|
213
|
+
</>
|
|
214
|
+
}
|
|
215
|
+
/>
|
|
216
|
+
{
|
|
217
|
+
!(showImport) // !FIXME: If-Cases sollten getauscht werden, damit Code einfacher wird
|
|
218
|
+
? <View style={themeStyles.container}>
|
|
219
|
+
<TabView
|
|
220
|
+
style={props.style}
|
|
221
|
+
navigationState={{ index: daysTabViewActiveRouteIndex, routes: daysTabViewRoutes }}
|
|
222
|
+
onIndexChange={setDaysTabViewActiveRouteIndex}
|
|
223
|
+
renderTabBar={
|
|
224
|
+
(props) =>
|
|
225
|
+
<TabBar
|
|
226
|
+
{...props}
|
|
227
|
+
scrollEnabled
|
|
228
|
+
style={themeStyles.tabs}
|
|
229
|
+
labelStyle={themeStyles.tab}
|
|
230
|
+
activeColor={themeStyles.tabs.activeColor}
|
|
231
|
+
inactiveColor={themeStyles.tabs.inactiveColor}
|
|
232
|
+
indicatorStyle={themeStyles.tabIndicator}
|
|
233
|
+
tabStyle={{ width: 'auto', paddingHorizontal: 20 }}
|
|
234
|
+
renderTabBarItem={({ route, navigationState, ...rest}) =>
|
|
235
|
+
<TabBarItem
|
|
236
|
+
{...rest}
|
|
237
|
+
key={route.key}
|
|
238
|
+
route={route}
|
|
239
|
+
navigationState={navigationState}
|
|
240
|
+
labelStyle={themeStyles.tab}
|
|
241
|
+
activeColor={themeStyles.tabs.activeColor}
|
|
242
|
+
inactiveColor={themeStyles.tabs.inactiveColor}
|
|
243
|
+
// Die einbindung von moment.js zum Anzeigen des Wochentages sollte langfristig entfernt werden.
|
|
244
|
+
// Funktioniert die Luxon funktionalität der Wochenanzeige nicht unter iOS datetime.toFormat('ccc')
|
|
245
|
+
label={
|
|
246
|
+
// Generieren das Tab-Textes
|
|
247
|
+
({ route }) => {
|
|
248
|
+
// Datumsobject aus dem Tab holen
|
|
249
|
+
const date = route.date;
|
|
250
|
+
// Kurzbezeichnung des Wochentages bestimmen
|
|
251
|
+
const shortWeekDay = t(`common:isoWeekDay:${date.weekday}:short`); // Falls hermes Intl soweit ist, lieber folgende Zeile verwenden: const shortWeekDay = date.weekdayShort;
|
|
252
|
+
// Datum, bestehend aus Tag und Monat, bestimmen
|
|
253
|
+
const localeDate = date.toLocaleString(
|
|
254
|
+
{
|
|
255
|
+
day: '2-digit',
|
|
256
|
+
month: '2-digit',
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Tab-Text aus Wochentag und Datum generieren
|
|
261
|
+
return <Text>{shortWeekDay}, {localeDate}</Text>;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
accessible={true}
|
|
265
|
+
accessibilityLabel={route.date.toLocaleString(DateTime.DATE_FULL)}
|
|
266
|
+
/>
|
|
267
|
+
}
|
|
268
|
+
/>
|
|
269
|
+
}
|
|
270
|
+
renderScene={
|
|
271
|
+
({ route }) => {
|
|
272
|
+
const dayIsoDate = route.key;
|
|
273
|
+
const coursesOfDay = courses?.[dayIsoDate];
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<View style={themeStyles.container}>
|
|
277
|
+
<View style={styles.horizontalSeperator} />
|
|
278
|
+
<ScrollView
|
|
279
|
+
refreshControl={
|
|
280
|
+
<RefreshControl
|
|
281
|
+
refreshing={coursesRefreshing}
|
|
282
|
+
onRefresh={
|
|
283
|
+
() => {
|
|
284
|
+
setCoursesRefreshing(true);
|
|
285
|
+
refreshCourses(dayIsoDate, dayIsoDate)
|
|
286
|
+
.finally(() => setCoursesRefreshing(false));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/>
|
|
290
|
+
}>
|
|
291
|
+
<View style={styles.renderList}>
|
|
292
|
+
{
|
|
293
|
+
coursesOfDay?.length
|
|
294
|
+
? coursesOfDay.map(
|
|
295
|
+
(course, index) => {
|
|
296
|
+
const courseTime = course?.times?.[0];
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
<TimetableListComponent
|
|
300
|
+
{...props}
|
|
301
|
+
key={`${course?.title?.data}${course?.type?.data}${courseTime.start}${courseTime.end}`}
|
|
302
|
+
course={course}
|
|
303
|
+
times={courseTime}
|
|
304
|
+
onCourseSelected={course => setSelectedCourse(course)}
|
|
305
|
+
/>
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
: <View style={styles.emptyListContainer}>
|
|
310
|
+
<Text style={styles.emptyText}>{t('timetable:noEvents')}</Text>
|
|
311
|
+
</View>
|
|
312
|
+
}
|
|
313
|
+
</View>
|
|
314
|
+
</ScrollView>
|
|
315
|
+
</View>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
lazy
|
|
320
|
+
lazyPreloadDistance={2}
|
|
321
|
+
renderLazyPlaceholder={(route) =>
|
|
322
|
+
<View style={styles.centerContainer}>
|
|
323
|
+
<ActivityIndicator
|
|
324
|
+
style={styles.activity}
|
|
325
|
+
size={'large'}
|
|
326
|
+
color={theme.colors.loadingIndicator}
|
|
327
|
+
/>
|
|
328
|
+
</View>
|
|
329
|
+
}
|
|
330
|
+
/>
|
|
331
|
+
{
|
|
332
|
+
otherCourses?.length
|
|
333
|
+
? <TouchableOpacity
|
|
334
|
+
style={styles.otherContainer}
|
|
335
|
+
onPress={
|
|
336
|
+
() => {
|
|
337
|
+
// Never provide props directly if you are using translations in this or a parent component
|
|
338
|
+
const otherCoursesComponentProps = { navigation: { ...props.navigation } };
|
|
339
|
+
|
|
340
|
+
store.dispatch(
|
|
341
|
+
{
|
|
342
|
+
type: 'UPDATE_MODAL_CONTENT',
|
|
343
|
+
modalContent: (
|
|
344
|
+
<View style={styles.modalContainer}>
|
|
345
|
+
<OtherCoursesComponent
|
|
346
|
+
{...otherCoursesComponentProps}
|
|
347
|
+
course={otherCourses}
|
|
348
|
+
/>
|
|
349
|
+
</View>
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
);
|
|
353
|
+
props.navigation.navigate('Modal');
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
>
|
|
357
|
+
<Text
|
|
358
|
+
style={styles.otherTitle}
|
|
359
|
+
>
|
|
360
|
+
{t('timetable:moreEvents')}
|
|
361
|
+
</Text>
|
|
362
|
+
</TouchableOpacity>
|
|
363
|
+
: null
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
<CourseDetailDialog
|
|
368
|
+
course={selectedCourse}
|
|
369
|
+
visible={selectedCourse ? true : false}
|
|
370
|
+
onClose={() => setSelectedCourse(null)}
|
|
371
|
+
onDismiss={() => setSelectedCourse(null)}
|
|
372
|
+
/>
|
|
373
|
+
|
|
374
|
+
</View>
|
|
375
|
+
: <TimetableCodeInput
|
|
376
|
+
onImportSuccessfullyFinished={
|
|
377
|
+
// Wenn Stundenplan erfolgreich importiert wurde, wird der Stundenplan angezeigt
|
|
378
|
+
() => setShowImport(false)
|
|
379
|
+
}
|
|
380
|
+
/>
|
|
381
|
+
}
|
|
382
|
+
</SafeAreaView>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const mapStateToProps = state => {
|
|
387
|
+
return {
|
|
388
|
+
pluginComponent: state.pluginReducer.timetable.component,
|
|
389
|
+
pluginStyles: state.pluginReducer.timetable.styles,
|
|
390
|
+
courses: state.apiReducer.courses,
|
|
391
|
+
coursesStatus: state.apiReducer.coursesStatus,
|
|
392
|
+
refreshing: state.stateReducer.refreshing,
|
|
393
|
+
settings: state.settingReducer
|
|
394
|
+
};
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
export default connect(mapStateToProps, { onUpdateRefreshing })(TimetableViewList)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {Dimensions} from "react-native";
|
|
2
|
+
|
|
3
|
+
export default function(theme) {
|
|
4
|
+
const width = Dimensions.get('window').width;
|
|
5
|
+
return {
|
|
6
|
+
horizontalSeperator: {
|
|
7
|
+
height: 1,
|
|
8
|
+
width: "100%",
|
|
9
|
+
backgroundColor: theme.colors.listSeperator
|
|
10
|
+
},
|
|
11
|
+
otherContainer: {
|
|
12
|
+
width: width,
|
|
13
|
+
height: "10%",
|
|
14
|
+
backgroundColor: theme.colors.primary,
|
|
15
|
+
shadowColor: theme.colors.shadow,
|
|
16
|
+
shadowOffset: {
|
|
17
|
+
width: 0,
|
|
18
|
+
height: 1,
|
|
19
|
+
},
|
|
20
|
+
shadowOpacity: 0.5,
|
|
21
|
+
shadowRadius: 10,
|
|
22
|
+
elevation: 3,
|
|
23
|
+
justifyContent: 'center'
|
|
24
|
+
},
|
|
25
|
+
otherTitle: {
|
|
26
|
+
fontSize: theme.fontSizes.xxl,
|
|
27
|
+
...theme.fonts.medium,
|
|
28
|
+
textAlign: "center",
|
|
29
|
+
padding: theme.paddings.small,
|
|
30
|
+
color: theme.colors.primaryText,
|
|
31
|
+
},
|
|
32
|
+
container: {
|
|
33
|
+
flex: 1,
|
|
34
|
+
backgroundColor: theme.colors.contentBackground,
|
|
35
|
+
},
|
|
36
|
+
modalContainer: {
|
|
37
|
+
flex: 1,
|
|
38
|
+
},
|
|
39
|
+
renderList: {
|
|
40
|
+
marginBottom: theme.paddings.small
|
|
41
|
+
},
|
|
42
|
+
emptyListContainer: {
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
justifyContent: "center",
|
|
45
|
+
width: "100%",
|
|
46
|
+
height: 400,
|
|
47
|
+
padding: theme.paddings.default,
|
|
48
|
+
marginTop: theme.paddings.default
|
|
49
|
+
},
|
|
50
|
+
emptyText: {
|
|
51
|
+
textAlign: "center",
|
|
52
|
+
fontSize: theme.fontSizes.title
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
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, Linking} 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 merge from 'lodash/merge';
|
|
21
|
+
|
|
22
|
+
import componentStyles from "./styles";
|
|
23
|
+
import { AppBar as AppbarComponent } from '@olea-bps/components';
|
|
24
|
+
import IconsOpenasist from "@olea-bps/icons-openasist";
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Webview View
|
|
29
|
+
*
|
|
30
|
+
* Use this víew to show a webpage (via url).
|
|
31
|
+
*
|
|
32
|
+
* Parameters:
|
|
33
|
+
* - none
|
|
34
|
+
*
|
|
35
|
+
* Navigation-Parameters:
|
|
36
|
+
* - title: Header title
|
|
37
|
+
* - url: Webpage Url
|
|
38
|
+
*/
|
|
39
|
+
class WebviewsView extends React.Component {
|
|
40
|
+
static navigationOptions = {
|
|
41
|
+
header: null
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
state = {
|
|
45
|
+
refreshing: false,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Styles of this component
|
|
49
|
+
styles;
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
webView = {
|
|
54
|
+
canGoBack: false,
|
|
55
|
+
ref: null,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
jsScript = `
|
|
59
|
+
const meta = document.createElement('meta');
|
|
60
|
+
meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0');
|
|
61
|
+
meta.setAttribute('name', 'viewport');
|
|
62
|
+
document.head.appendChild(meta);
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
constructor(props) {
|
|
66
|
+
super(props);
|
|
67
|
+
// ------------------------------------------------------------------------
|
|
68
|
+
// PLUGIN FUNCTIONALITY
|
|
69
|
+
// ------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
const {pluginStyles, theme} = this.props;
|
|
72
|
+
this.styles = componentStyles(theme);
|
|
73
|
+
|
|
74
|
+
if (pluginStyles) {
|
|
75
|
+
this.styles = merge(this.styles, pluginStyles);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.styles = StyleSheet.create(this.styles);
|
|
79
|
+
|
|
80
|
+
// ------------------------------------------------------------------------
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Intercept URL loading in the WebView.
|
|
85
|
+
* If the base URL of the event is different from the initial base URL, open it in the external browser.
|
|
86
|
+
* Still allows url navigation with # values in the URL by comparing the base URL of the event with the base URL of the initial URL.
|
|
87
|
+
*/
|
|
88
|
+
onShouldStartLoadWithRequest = (event) => {
|
|
89
|
+
const {url} = this.props.route.params;
|
|
90
|
+
const baseUrl = url.split('#')[0];
|
|
91
|
+
const eventBaseUrl = event.url.split('#')[0];
|
|
92
|
+
|
|
93
|
+
if (eventBaseUrl !== baseUrl) {
|
|
94
|
+
Linking.openURL(event.url);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
render() {
|
|
101
|
+
const {route} = this.props;
|
|
102
|
+
const {colors, themeStyles, customScript} = this.props.theme;
|
|
103
|
+
// The language is necessary because it is needed in the header in the source-prop of the webview elemnent
|
|
104
|
+
// to display the website in the selected system-language
|
|
105
|
+
const {language} = this.props.settings.settingsGeneral
|
|
106
|
+
|
|
107
|
+
// Get the current title of the element, that should be displayed e.g. Imprint, data policy, etc.
|
|
108
|
+
const title = route.params && route.params.title || null;
|
|
109
|
+
// Get the url for the current element defined in the settings
|
|
110
|
+
const url = route.params && route.params.url || null;
|
|
111
|
+
|
|
112
|
+
// The returned View contains a Appbar followed by the webview that calls the websites provided in the URL
|
|
113
|
+
return (
|
|
114
|
+
<SafeAreaView style={[this.styles.container, themeStyles.appSafeAreaContainer]}>
|
|
115
|
+
<View style={{zIndex: 100, position: 'relative'}}>
|
|
116
|
+
<AppbarComponent {...this.props}
|
|
117
|
+
title={title}
|
|
118
|
+
leftAction={
|
|
119
|
+
<Appbar.Action
|
|
120
|
+
icon={props => <IconsOpenasist {...props} icon={'back'} color={colors.primaryText} /> }
|
|
121
|
+
onPress={() => {
|
|
122
|
+
if (this.webView.canGoBack) {
|
|
123
|
+
this.webView.ref.goBack();
|
|
124
|
+
} else {
|
|
125
|
+
this.props.navigation.goBack(null);
|
|
126
|
+
}
|
|
127
|
+
}} />
|
|
128
|
+
}
|
|
129
|
+
rightAction={<Appbar.Action icon="refresh" onPress={() => this.webView.ref.reload()}/>}/>
|
|
130
|
+
</View>
|
|
131
|
+
<WebView
|
|
132
|
+
ref={ref => this.webView.ref = ref}
|
|
133
|
+
// In addition to only providing the url as a source, headers can be defined that will contain additional information
|
|
134
|
+
// In this case the header provides information about the current system-language in order to display a given translation of the website
|
|
135
|
+
source={{ uri: url, headers: {
|
|
136
|
+
"Accept-Language": `${language}`,
|
|
137
|
+
} }}
|
|
138
|
+
startInLoadingState={true}
|
|
139
|
+
renderLoading={() => <ActivityIndicator style={this.styles.activity} size="large" color={colors.loadingIndicator} />}
|
|
140
|
+
onNavigationStateChange={(navState) => { this.webView.canGoBack = navState.canGoBack; }}
|
|
141
|
+
injectedJavaScript={this.jsScript + customScript}
|
|
142
|
+
onMessage={() => {}}
|
|
143
|
+
onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
|
|
144
|
+
/>
|
|
145
|
+
</SafeAreaView>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const mapStateToProps = state => {
|
|
151
|
+
return {
|
|
152
|
+
// The settings are necessary to determine the current system-language so that the websites in the webview can be
|
|
153
|
+
// translated in the selected language
|
|
154
|
+
settings: state.settingReducer
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export default connect(mapStateToProps, null)(withTheme(WebviewsView))
|
package/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { default as Callmanager } from './Callmanager';
|
|
2
|
+
export { default as Canteens } from './Canteens';
|
|
3
|
+
export { default as Dashboard } from './Dashboard';
|
|
4
|
+
export { default as DashboardHtwk } from './DashboardHtwk';
|
|
5
|
+
export { default as Event } from './Event';
|
|
6
|
+
export { default as FeedList } from './FeedList';
|
|
7
|
+
export { default as FeedNews } from './FeedNews';
|
|
8
|
+
export { default as Flexmenu } from './Flexmenu';
|
|
9
|
+
export { default as FlexmenuWebview } from './FlexmenuWebview';
|
|
10
|
+
export { default as Howhy } from './Howhy';
|
|
11
|
+
export { default as Jobs } from './Jobs';
|
|
12
|
+
export { default as MainMenu } from './MainMenu';
|
|
13
|
+
export { default as NewsTabbar } from './NewsTabbar';
|
|
14
|
+
export { default as Opal } from './Opal';
|
|
15
|
+
export { default as PublicTransportTicket } from './PublicTransportTicket';
|
|
16
|
+
export { default as Search } from './Search';
|
|
17
|
+
export { default as SettingsAccessibility } from './SettingsAccessibility';
|
|
18
|
+
export { default as SettingsAppInfo } from './SettingsAppInfo';
|
|
19
|
+
export { default as SettingsCanteens } from './SettingsCanteens';
|
|
20
|
+
export { default as SettingsGeneral } from './SettingsGeneral';
|
|
21
|
+
export { default as SettingsNotifications } from './SettingsNotifications';
|
|
22
|
+
export { default as SettingsUser } from './SettingsUser';
|
|
23
|
+
export { default as TimetableCalendar } from './TimetableCalendar';
|
|
24
|
+
export { default as TimetableList } from './TimetableList';
|
|
25
|
+
export { default as Webviews } from './Webviews';
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@olea-bps/views",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Consolidated views for OLEA",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/tuc-urz/olea.git",
|
|
9
|
+
"directory": "packages/views"
|
|
10
|
+
},
|
|
11
|
+
"author": "codeculture<info@codeculture.de>",
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"files": [
|
|
14
|
+
"index.js",
|
|
15
|
+
"*/index.js",
|
|
16
|
+
"*/styles.js",
|
|
17
|
+
"*/**/*"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"react-native-paper": "^5.14.5",
|
|
21
|
+
"@olea-bps/core": "^1.0.0",
|
|
22
|
+
"react-native-webview": "13.15.0",
|
|
23
|
+
"@react-navigation/drawer": "^7.3.12",
|
|
24
|
+
"@react-navigation/native": "^7.1.9",
|
|
25
|
+
"react-native-elements": "^3.4.3",
|
|
26
|
+
"react-native-modalbox": "^2.0.2",
|
|
27
|
+
"react-native-vector-icons": "^10.2.0",
|
|
28
|
+
"react-native-tab-view": "^4.1.3",
|
|
29
|
+
"@olea-bps/icons-openasist": "^1.0.0",
|
|
30
|
+
"expo-application": "^6.1.5",
|
|
31
|
+
"moment": "^2.29.4",
|
|
32
|
+
"react-native-swipe-gestures": "^1.0.5",
|
|
33
|
+
"@olea-bps/views": "^1.0.0",
|
|
34
|
+
"@olea-bps/components": "^1.0.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": "^19.0.0",
|
|
38
|
+
"react-native": "^0.81.0",
|
|
39
|
+
"react-native-paper": "^5.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|