@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
@@ -0,0 +1,219 @@
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, { useState, useMemo, useEffect } from 'react';
16
+ import {
17
+ StyleSheet,
18
+ Text,
19
+ TouchableOpacity,
20
+ View,
21
+ } from 'react-native';
22
+
23
+ import { useTheme } from 'react-native-paper';
24
+ import { useTranslation } from 'react-i18next';
25
+ import { useNavigation } from '@react-navigation/native';
26
+
27
+ import moment from 'moment';
28
+ import { DateTime } from 'luxon';
29
+
30
+ import IconsOpenasist from '@olea-bps/icons-openasist';
31
+ import { useDateCourses } from '@olea-bps/context-timetable';
32
+
33
+ import componentStyles from './styles';
34
+
35
+ /**
36
+ * Course Info Component
37
+ *
38
+ * Shows the informations of the next course, if the student has imported a timetable.
39
+ * If where is no timetable available, a notice will be displayed.
40
+ *
41
+ * Parameters:
42
+ * - none
43
+ *
44
+ * Navigation-Parameters:
45
+ * - none
46
+ */
47
+ export default function CourseInfoComponent(props) {
48
+ const componentName = CourseInfoComponent.name;
49
+ const theme = useTheme();
50
+ const { colors } = theme;
51
+ const { t } = useTranslation();
52
+ const navigation = useNavigation();
53
+
54
+ const [now, setNow] = useState(moment());
55
+
56
+ const todayDate = DateTime.now().toISODate();
57
+ const academicQuarter = moment.duration(30, 'minutes');
58
+
59
+ // Vorlesungen von Stundenplan-Kontext abrufen, die heute stattfinden.
60
+ const [courses] = useDateCourses(todayDate);
61
+
62
+ console.debug(componentName, ': Count of courses: ', courses?.length ?? 0);
63
+
64
+ // Genieren der Styles, wenn nicht vorhanden oder das Theme sich geändert hat
65
+ const styles = useMemo(
66
+ () => StyleSheet.create(componentStyles(theme)),
67
+ [theme]
68
+ );
69
+
70
+ const isoWeekDaysLabels = useMemo(
71
+ () => new Map(
72
+ ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']
73
+ .map(
74
+ (dayTranslationLabel, index) =>
75
+ [
76
+ index + 1,
77
+ {
78
+ short: t(`common:day${dayTranslationLabel}Short`),
79
+ long: t(`common:day${dayTranslationLabel}`),
80
+ }
81
+ ]
82
+ )
83
+ ),
84
+ [t]
85
+ );
86
+
87
+ // Suchen der nächsten Vorlesung
88
+ const nextCourse = courses
89
+ // Berechnen der heutigen Startzeitpunkt der Vorlesung als Datetime und speichern als startMoment-Property.
90
+ // Berechnen der heutigen mit academischen Viertel der Vorlesung als Datetime und speichern als academicQuarterMoment-Property.
91
+ ?.map?.(course => {
92
+ // Holen der Uhrzeit der Vorlesung
93
+ const courseTime = course.times[0];
94
+ // Erstellen der datetime aus dem heutigen Datum und der Enduhrzeit
95
+ const courseStartMoment = moment(courseTime.start, 'HH:mm');
96
+
97
+ return {
98
+ ...course,
99
+ startMoment: courseStartMoment,
100
+ academicQuarterMoment: courseStartMoment.add(academicQuarter),
101
+ // Ursprüngliche Vorlesung als eigene Instanz einbetten
102
+ // Diese Instanze sollte als Abhängigkeit für Hooks verwendet werden, hier bei jedem Rendern eine neue Instaze entsteht
103
+ origineCourse: course,
104
+ }
105
+ })
106
+ // Sortieren der Vorlesung nach ihren Start-Datetimes. unix()-Methode gibt einen Integer-Zeitstempel zurück.
107
+ ?.sort?.((courseA, courseB) => courseA.startMoment.unix() - courseB.startMoment.unix())
108
+ // Suche die erste Vorlesung, anhand der Startzeit mit academischen Viertel nach Jetzt ist.
109
+ ?.find?.(course => course.academicQuarterMoment.isAfter(now));
110
+
111
+ const nextOrigineCourse = nextCourse?.origineCourse
112
+
113
+ console.debug(componentName, ': next course:', nextCourse);
114
+
115
+ // now neu setzen, wenn die nächste Vorlesung anfängt oder der Tag zu Ende ist
116
+ useEffect(
117
+ () => {
118
+ // Millisekunden bis zum anzeigen der nächste Vorlesung bzw. des Tagesende ausrechnen und den mathematischen Betrag nehmen
119
+ const timeoutToCalculateNextCourse = Math.abs(
120
+ moment().diff(
121
+ // Prüfen ob es heute noch eine Vorlesung geben wird
122
+ nextCourse
123
+ // Es wird der Anfang mit academischen Viertel der nächsten/aktuellen Vorlesung genommen
124
+ ? nextCourse.academicQuarterMoment
125
+ // Es wird das Ende des heutigen Tages genommen
126
+ : moment().endOf('day')
127
+ )
128
+ );
129
+
130
+ console.debug(componentName, ': recalculating next course in ', timeoutToCalculateNextCourse, ' miliseconds');
131
+
132
+ // Timer setzen und Handel zum Löschen des Timeouts zwischenspeichern
133
+ const timeoutHandle = setTimeout(() => setNow(moment()), timeoutToCalculateNextCourse);
134
+ return () => {
135
+ // Timer löschen, falls die nächste Vorlesung wechselt
136
+ clearTimeout(timeoutHandle);
137
+ }
138
+ },
139
+ [nextOrigineCourse]
140
+ );
141
+
142
+ const errorMessage = !nextOrigineCourse && courses?.length ? t('course:noLecturesAvailable') : t('course:noLectureFound');
143
+
144
+ return useMemo(
145
+ () => {
146
+ const courseTitle = nextOrigineCourse?.title?.data;
147
+ const courseTime = nextOrigineCourse?.times?.[0];
148
+ const courseIsoDayOfWeek = courseTime?.dayOfWeek;
149
+ const courseWeekDaysLabels = isoWeekDaysLabels.get(courseIsoDayOfWeek);
150
+
151
+ const courseTimeStart = courseTime?.start;
152
+ const courseTimeStartAccessibility = courseTimeStart
153
+ ? courseTimeStart.slice(0, 2) + ' ' + t('accessibility:pts:speechTime') + ' ' + (courseTimeStart.slice(3, 5) !== '00' ? courseTimeStart.slice(3, 5) : '') + ','
154
+ : null;
155
+
156
+ const courseTimeEnd = courseTime?.end;
157
+ const coursetimeEndAccessibility = courseTimeEnd
158
+ ? courseTimeEnd.slice(0, 2) + ' ' + t('accessibility:pts:speechTime') + ' ' + (courseTimeEnd.slice(3, 5) !== '00' ? courseTimeEnd.slice(3, 5) : '') + ','
159
+ : null;
160
+
161
+ const courseDayOfWeek = courseWeekDaysLabels?.short;
162
+ const courseDayOfWeekAccessibility = courseWeekDaysLabels?.long;
163
+
164
+ const courseTimeText = courseDayOfWeek && courseTimeStart && courseTimeEnd
165
+ ? `${courseDayOfWeek} ${courseTimeStart} - ${courseTimeEnd}`
166
+ : t('common:noDetails');
167
+
168
+ const courseTimeAccessibility = courseDayOfWeekAccessibility && courseTimeStartAccessibility && coursetimeEndAccessibility
169
+ ? `${courseDayOfWeekAccessibility} ${t('accessibility:timetable:at')} ${courseTimeStartAccessibility} ${t('accessibility:timetable:to')} ${coursetimeEndAccessibility}`
170
+ : null;
171
+
172
+ const courseRoom = Array.isArray(nextOrigineCourse?.room)
173
+ ? nextOrigineCourse?.room?.[0]?.data
174
+ : nextOrigineCourse?.room?.data;
175
+
176
+ const courseRoomText = courseRoom?.replace(/\([\w\W]+/, '') ?? t('common:noDetails');
177
+
178
+ return (
179
+ <TouchableOpacity
180
+ onPress={
181
+ () => navigation.navigate(
182
+ 'timetableModule',
183
+ nextOrigineCourse
184
+ ? { course: nextOrigineCourse }
185
+ : { showCodeInput: true }
186
+ )
187
+ }
188
+ accessible={true}
189
+ accessibilityLabel={t('course:nextLecture')}
190
+ accessibilityHint={
191
+ nextOrigineCourse
192
+ ? [courseTitle, courseTimeAccessibility, courseRoom].join(' ')
193
+ : errorMessage
194
+ }
195
+ >
196
+ <View style={styles.container}>
197
+ <Text style={styles.headline}>{t('course:nextLecture')}</Text>
198
+ </View>
199
+ <View style={styles.innerContainer}>
200
+ <View style={styles.iconContainer}>
201
+ <IconsOpenasist icon={"timetable"} color={colors.black} size={40} />
202
+ </View>
203
+ {
204
+ nextOrigineCourse
205
+ ? <View style={styles.contentContainer}>
206
+ <Text style={styles.title}>{courseTitle}</Text>
207
+ <Text style={styles.title}>{courseTimeText}{courseTimeText && courseRoomText ? ' | ' : ''}{courseRoomText}</Text>
208
+ </View>
209
+ : <View style={styles.contentContainer}>
210
+ <Text style={styles.title}>{errorMessage}</Text>
211
+ </View>
212
+ }
213
+ </View>
214
+ </TouchableOpacity>
215
+ )
216
+ },
217
+ [nextOrigineCourse, errorMessage, navigation, styles, t, colors]
218
+ );
219
+ }
@@ -0,0 +1,40 @@
1
+ export default function(theme) {
2
+ return {
3
+ container: {
4
+ flex: 1,
5
+ flexDirection: 'row',
6
+ alignItems: 'center',
7
+ paddingHorizontal: theme.paddings.default,
8
+ paddingVertical: theme.paddings.small,
9
+ paddingBottom: 0
10
+ },
11
+ innerContainer: {
12
+ flex: 1,
13
+ flexDirection: 'row',
14
+ paddingVertical: theme.paddings.small,
15
+ paddingHorizontal: theme.paddings.default,
16
+
17
+ },
18
+ iconContainer: {
19
+ paddingTop: theme.paddings.xsmall / 2,
20
+ paddingBottom: theme.paddings.xsmall / 2,
21
+ paddingRight: theme.paddings.small,
22
+ },
23
+ contentContainer: {
24
+ flex: 1,
25
+ padding: theme.paddings.xsmall
26
+ },
27
+ headline: {
28
+ ...theme.fonts.regular,
29
+ fontSize: theme.fontSizes.m,
30
+ textTransform: 'uppercase'
31
+ },
32
+ title: {
33
+ ...theme.fonts.regular,
34
+ fontSize: theme.fontSizes.m,
35
+ },
36
+ modalContainer: {
37
+ flex: 1
38
+ }
39
+ }
40
+ };
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * You may obtain a copy of the License at
5
+ *
6
+ * http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS,
10
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ * See the License for the specific language governing permissions and
12
+ * limitations under the License.
13
+ */
14
+
15
+ import { useMemo, useCallback, FC } from 'react';
16
+ import {
17
+ StyleSheet,
18
+ Text,
19
+ View
20
+ } from 'react-native';
21
+
22
+ import { useDispatch } from 'react-redux';
23
+ import { useTheme, Portal, Dialog, Checkbox, Button } from 'react-native-paper';
24
+ import { useTranslation } from 'react-i18next';
25
+
26
+ import { onSettingDevelopOverride, useActiveStagingMenuItems, useShowDeeplinkAlert, useStagingServer, useSubscriberId } from '@olea-bps/core';
27
+
28
+ import componentStyles from './styles'
29
+
30
+ /**
31
+ * Ein Dialog, welches Optionen für Entwickler anzeigt.
32
+ * Der Dialog verwendet sein eigenes {@link Portal}, sodass nur die Einbindung dieser Komponente für die richtige Funktionweise nötig ist.
33
+ *
34
+ * Über diesen Dialog, kann auf den Staging-Server umgeschaltet werden.
35
+ *
36
+ * @param {object} props
37
+ * @param {boolean} props.visible Wird der Dialog angezeigt?
38
+ * @param {() => void} [props.onDismiss] Callback-Funktion welche ausgeführt wird, wenn der Dialog geschlossen werden soll.
39
+ * @returns {FC}
40
+ */
41
+ export default function DevelopmentDialog({ visible, onDismiss }) {
42
+ const { t } = useTranslation();
43
+ const theme = useTheme();
44
+ const { colors, appSettings } = theme;
45
+ const mainMenuSettings = appSettings?.mainMenu;
46
+ const menuItems = mainMenuSettings?.items ?? {};
47
+ const menuRoutes = mainMenuSettings?.routes ?? [];
48
+ const isStagingServerActive = useStagingServer();
49
+ const subscriberId = useSubscriberId();
50
+ const showDeeplinkAlert = useShowDeeplinkAlert();
51
+ const activeStagingMenuItems = useActiveStagingMenuItems();
52
+ const dispatch = useDispatch();
53
+
54
+ const styles = useMemo(
55
+ () => StyleSheet.create(componentStyles(theme)),
56
+ [theme, componentStyles]
57
+ )
58
+
59
+ // Erzeugen einer Funktion, die die Entwicklereinstellugen setzt.
60
+ // Dabei werden immer die derzeitigen Werte gesetzt, außer es werden neue Werte zum überschreiben übergeben.
61
+ // Diese Funktion sollte langfristig entfallen.
62
+ const setDevelopmentSettings = useCallback(
63
+ (settings) => dispatch(
64
+ onSettingDevelopOverride(
65
+ 'settingsDevelop',
66
+ {
67
+ useStaging: isStagingServerActive,
68
+ showDeeplinkAlert: showDeeplinkAlert,
69
+ activeStagingMenuItems: activeStagingMenuItems,
70
+ ...settings,
71
+ }
72
+ )
73
+ ),
74
+ [isStagingServerActive, showDeeplinkAlert, activeStagingMenuItems, onSettingDevelopOverride]
75
+ )
76
+
77
+ const output = [];
78
+
79
+ output.push(
80
+ <View key={'item_use_stg'} style={styles.selectOption}>
81
+ <Text>{t('settings:develop.useStagingServer')}</Text>
82
+ <Checkbox.Android status={isStagingServerActive ? 'checked' : 'unchecked'}
83
+ onPress={() => setDevelopmentSettings({ useStaging: !isStagingServerActive })}
84
+ color={colors.checkboxChecked}
85
+ uncheckedColor={colors.checkboxUnchecked}
86
+ />
87
+ </View>
88
+ );
89
+
90
+ output.push(
91
+ <View key={'item_notification_subscriber'} style={[{ marginBottom: 30 }]}>
92
+ <Text>{t('settings:develop.notificationSubscriberId')}</Text>
93
+ <Text>{subscriberId}</Text>
94
+ </View>
95
+ );
96
+ output.push(
97
+ <View key={'item_notification_push_message'} style={[{ marginBottom: 30 }]}>
98
+ <Text>{t('settings:develop.resetPushMessage')}</Text>
99
+ <Button
100
+ buttonColor={colors.primary}
101
+ textColor={colors.buttonText}
102
+ onPress={() => dispatch({ type: 'UPDATE_PUSH_MESSAGE_SHOWN', pushMessageShown: false })}
103
+ >
104
+ Reset
105
+ </Button>
106
+ </View>
107
+ );
108
+ output.push(
109
+ <View key={'item_deeplink_show_url'} style={[{ marginBottom: 30 }, styles.selectOption]}>
110
+ <Text>{t('settings:develop.showDeepLink')}</Text>
111
+ <Checkbox
112
+ status={showDeeplinkAlert ? 'checked' : 'unchecked'}
113
+ onPress={() => setDevelopmentSettings({ showDeeplinkAlert: !showDeeplinkAlert })}
114
+ color={colors.checkboxChecked}
115
+ uncheckedColor={colors.checkboxUnchecked}
116
+ ></Checkbox>
117
+ </View>
118
+ );
119
+
120
+ // Für jeden Menü-Tab einen Bereich erstellen und im Bereich für jeden Menüpunkt eine Checkbox anbieten.
121
+ // Menüpunkte-Objekt in ein Array umwandeln
122
+ const activatableMenuItemsSettings = Object.entries(menuItems)
123
+ // Aus der Liste der Menüpunkten, werden die Menüpunkte herausgefiltert, die immer aktiv/eingeschaltet sind.
124
+ .map(
125
+ ([menuItemKey, menuItemEntries]) => {
126
+ const stagingMenuItemEntries = menuItemEntries.filter(
127
+ menuItemEntry =>
128
+ // Wenn active-Property nicht vorhanden, ist der Menüpunkt activ, dann Negierung, weil nicht aktive Menueinträge gefiltert werden
129
+ !(menuItemEntry?.active ?? true)
130
+ );
131
+ return [menuItemKey, stagingMenuItemEntries];
132
+ }
133
+ )
134
+ // Es werden Menü-Tabs herausgefiltert, die keine Menüpunkte, die nur im Stagingmodus zu sehen sind.
135
+ .filter(
136
+ ([menuItemKey, stagingMenuItemEntries]) => (stagingMenuItemEntries?.length ?? 0) > 0
137
+ )
138
+ // Rendern der Menü-Tabs und deren Menüeinträge
139
+ .map(
140
+ ([menuItemKey, stagingMenuItemEntries]) => {
141
+ const mainMenuRouteTitleKey = menuRoutes
142
+ ?.find(menuRoute => menuRoute?.key === menuItemKey)
143
+ ?.title;
144
+
145
+ return (
146
+ < View key={`item_menuitems_${menuItemKey}`} >
147
+ <Text>{t(mainMenuRouteTitleKey)}-Tab</Text>
148
+ {
149
+ // Rendern der Menüpunkte eines Menü-Tabs
150
+ stagingMenuItemEntries.map(
151
+ stagingMenuItemEntry => {
152
+ const stagingMenuItemEntryKey = `${stagingMenuItemEntry?.title}`;
153
+
154
+ // Der Menüpunkt ist aktiv, wenn sein Schlüssel in der Liste der activen Menüpunkte zu finden ist
155
+ const isActive = activeStagingMenuItems.includes(stagingMenuItemEntryKey);
156
+
157
+ return (
158
+ <Checkbox.Item
159
+ key={stagingMenuItemEntryKey}
160
+ label={t(stagingMenuItemEntry?.title)}
161
+ status={isActive ? 'checked' : 'unchecked'}
162
+ color={colors.checkboxChecked}
163
+ uncheckedColor={colors.checkboxUnchecked}
164
+ theme={theme}
165
+ style={styles.selectOption}
166
+ onPress={() => setDevelopmentSettings(
167
+ {
168
+ activeStagingMenuItems: isActive
169
+ // Wenn der Menüpunkt schon aktiv ist, wird er aus der Liste der aktiven Punkte entfernt.
170
+ ? activeStagingMenuItems.filter(activeStagingMenuItem => activeStagingMenuItem !== stagingMenuItemEntryKey)
171
+ // Wenn der Menüpunkt nicht aktiv ist, wird er zur Liste der aktiven Punkte hinzugefügt.
172
+ : [...activeStagingMenuItems, stagingMenuItemEntryKey]
173
+ }
174
+ )}
175
+ />
176
+ )
177
+ }
178
+ )
179
+ }
180
+ </View>
181
+ )
182
+ }
183
+ );
184
+
185
+ return (
186
+ <Portal>
187
+ <Dialog visible={visible} onDismiss={onDismiss}>
188
+ <Dialog.Title>{t('settings:develop.dialog')}</Dialog.Title>
189
+ <Dialog.Content>
190
+ {
191
+ [
192
+ ...output,
193
+ ...activatableMenuItemsSettings,
194
+ ]
195
+ }
196
+ </Dialog.Content>
197
+ <Dialog.Actions>
198
+ <Button
199
+ onPress={onDismiss}
200
+ textColor={colors.buttonText}
201
+ >
202
+ {t('common:okLabel')}
203
+ </Button>
204
+ </Dialog.Actions>
205
+ </Dialog>
206
+ </Portal>
207
+ );
208
+ }
@@ -0,0 +1,10 @@
1
+ export default function (theme) {
2
+ return {
3
+ selectOption: {
4
+ display: 'flex',
5
+ flexDirection: 'row',
6
+ justifyContent: 'space-between'
7
+ },
8
+ }
9
+ };
10
+
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Licensed under the Apache License, Version 2.0 (the "License");
3
+ * you may not use this file except in compliance with the License.
4
+ * You may obtain a copy of the License at
5
+ *
6
+ * http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS,
10
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ * See the License for the specific language governing permissions and
12
+ * limitations under the License.
13
+ */
14
+
15
+ import { useMemo, useState } from 'react';
16
+ import {
17
+ ActivityIndicator,
18
+ KeyboardAvoidingView,
19
+ ScrollView,
20
+ StyleSheet,
21
+ TouchableOpacity,
22
+ View,
23
+ Linking,
24
+ Text
25
+ } from 'react-native';
26
+
27
+ import { useTranslation } from 'react-i18next';
28
+ import {
29
+ Headline,
30
+ Paragraph,
31
+ TextInput,
32
+ useTheme,
33
+ } from 'react-native-paper';
34
+
35
+ import { usePersonalEvents, usePersonalEventsCode } from '@olea-bps/context-event';
36
+
37
+ import componentStyles from './styles';
38
+
39
+
40
+ /**
41
+ * View Name
42
+ *
43
+ * Shows the View
44
+ *
45
+ * Parameters:
46
+ * - none
47
+ *
48
+ * Navigation-Parameters:
49
+ * - none
50
+ */
51
+ export default function EventCodeInputComponent({ onClose, onPersonalEventsImported }) {
52
+ const theme = useTheme();
53
+ const { colors } = theme;
54
+ const { t } = useTranslation();
55
+
56
+ const [, refreshPersonalEvents] = usePersonalEvents();
57
+ const [personalEventsCode, setPersonalEventsCode] = usePersonalEventsCode();
58
+ const [personalEventsImportedImporting, setPersonalEventsImportedImporting] = useState(false);
59
+ const [personalEventsCodeInput, setPersonalEventsCodeInput] = useState(personalEventsCode ? personalEventsCode : '');
60
+
61
+ const styles = useMemo(
62
+ () => StyleSheet.create(componentStyles(theme)),
63
+ [theme]
64
+ )
65
+
66
+ const handleImportButtonPressed = () => {
67
+ setPersonalEventsImportedImporting(true);
68
+
69
+ if (personalEventsCodeInput.length < 8) {
70
+ alert(t('eventCode:codeShortError'));
71
+ setPersonalEventsImportedImporting(false);
72
+ } else if (personalEventsCodeInput.length > 8) {
73
+ alert(t('eventCode:codeLongError'));
74
+ setPersonalEventsImportedImporting(false);
75
+ } else {
76
+ setPersonalEventsCode(personalEventsCodeInput);
77
+ refreshPersonalEvents()
78
+ .then(() => setPersonalEventsImportedImporting(false))
79
+ .then(() => onPersonalEventsImported?.());
80
+ }
81
+ }
82
+
83
+ return (
84
+ <ScrollView style={styles.container}>
85
+ <KeyboardAvoidingView behavior={"padding"} >
86
+ <View style={styles.paragraph}>
87
+ <Text style={styles.paragraphText} selectable={true} dataDetectorType={'link'}>
88
+ {
89
+ personalEventsCodeInput
90
+ ? t('eventCode:inputEventCode')
91
+ : <Text style={styles.paragraphText} selectable={true} dataDetectorType={'link'}>
92
+ {t('eventCode:notImportedYet')}
93
+ <Text style={styles.linkText} selctable={true} dataDetectorType={'link'} onPress={() => Linking.openURL('https://www.tu-chemnitz.de/tu/veranstaltungen/tuctag/alle-angebote.html')}>
94
+ {t('eventCode:tucDayLinkText')}
95
+ </Text>
96
+ </Text>
97
+ }
98
+ </Text>
99
+ </View>
100
+ <View style={styles.importTextInput}>
101
+ <TextInput
102
+ theme={{ colors: { primary: colors.textInput, placeholder: colors.textLighter } }}
103
+ style={styles.paragraphText}
104
+ maxLength={8}
105
+ selectionColor={colors.textInputSelection}
106
+ label={t('eventCode:inputPlaceholder')}
107
+ value={personalEventsCodeInput}
108
+ onChangeText={text => setPersonalEventsCodeInput(text.trim())}
109
+ />
110
+ </View>
111
+
112
+ <View style={styles.container}>
113
+ <TouchableOpacity
114
+ style={styles.importButton}
115
+ labelStyle={styles.importButtonLabel}
116
+ label={t('eventCode:importButton')}
117
+ onPress={handleImportButtonPressed}
118
+ >
119
+ <Headline style={styles.importButtonLabel} color={colors.buttonText}>
120
+ {t('eventCode:importButton')}
121
+ </Headline>
122
+ </TouchableOpacity>
123
+ <TouchableOpacity
124
+ style={styles.continueWithoutCodeButton}
125
+ labelStyle={styles.importButtonLabel}
126
+ label={t('eventCode:importButton')}
127
+ onPress={() => onClose?.()}>
128
+ <Headline style={styles.continueWithoutCodeButtonLabel} color={colors.buttonText}>{t('eventCode:continueWithoutCodeButton')}</Headline>
129
+ </TouchableOpacity>
130
+ </View>
131
+
132
+
133
+ {
134
+ personalEventsImportedImporting
135
+ ? <ActivityIndicator style={styles.activity} size="large" color={colors.loadingIndicator} />
136
+ : null
137
+ }
138
+ {
139
+ personalEventsImportedImporting
140
+ ? <View style={styles.paragraph}><Paragraph>{t('timetable:importInProgress', '')}</Paragraph></View>
141
+ : null
142
+ }
143
+ </KeyboardAvoidingView>
144
+ </ScrollView>
145
+ );
146
+ }