@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,73 @@
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 } from 'react';
16
+ import { StyleSheet } from 'react-native';
17
+
18
+ import { Checkbox, useTheme } from 'react-native-paper';
19
+ import { useTranslation } from 'react-i18next';
20
+
21
+ import { SettingsDialog } from '@olea-bps/components';
22
+
23
+ import componentStyles from './styles';
24
+
25
+ export default function SettingsDialogSelect({ title, visible, options, onDismiss, onOk, onChange, ...props }) {
26
+ const selectedOptions = Array.isArray(props?.selectedOptions) ? props.selectedOptions : [];
27
+ const { t } = useTranslation();
28
+ const theme = useTheme();
29
+
30
+ const styles = useMemo(
31
+ () => StyleSheet.create(componentStyles(theme)),
32
+ [theme, componentStyles]
33
+ )
34
+
35
+ return (
36
+ <SettingsDialog
37
+ title={title}
38
+ visible={visible}
39
+ onDismiss={() => onDismiss(selectedOptions)}
40
+ onOk={() => onOk(selectedOptions)}
41
+ >
42
+ {
43
+ options.map(
44
+ option => {
45
+ const optionKey = option?.key;
46
+ const isChecked = selectedOptions?.includes?.(optionKey) ?? false;
47
+ const colors = theme.colors;
48
+ return (
49
+ <Checkbox.Item
50
+ key={optionKey}
51
+ value={optionKey}
52
+ status={isChecked ? 'checked' : 'unchecked'}
53
+ label={t(option.label)}
54
+ labelStyle={styles.optionLabel}
55
+ color={colors.checkboxChecked}
56
+ uncheckedColor={colors.checkboxUnchecked}
57
+ mode={'android'}
58
+ onPress={
59
+ () => {
60
+ const nextSelectedOptions = selectedOptions.includes(optionKey)
61
+ ? selectedOptions.filter(selectedOption => selectedOption !== optionKey)
62
+ : [...selectedOptions, optionKey];
63
+ onChange(nextSelectedOptions);
64
+ }
65
+ }
66
+ />
67
+ )
68
+ }
69
+ )
70
+ }
71
+ </SettingsDialog>
72
+ );
73
+ }
@@ -0,0 +1,7 @@
1
+ export default function (theme) {
2
+ return {
3
+ optionLabel: {
4
+ fontSize: theme.fontSizes.m,
5
+ }
6
+ }
7
+ };
@@ -0,0 +1,201 @@
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 } from 'react';
16
+ import {
17
+ StyleSheet,
18
+ View,
19
+ ScrollView,
20
+ ActivityIndicator,
21
+ KeyboardAvoidingView,
22
+ Alert,
23
+ Text,
24
+ Linking,
25
+ } from 'react-native';
26
+ import { connect } from 'react-redux'
27
+ import { Paragraph, TextInput, Button, withTheme } from 'react-native-paper';
28
+ import { withTranslation } from 'react-i18next';
29
+
30
+ import { ApiProviderNotInitializedError, useCourses, useTimetableCode } from '@olea-bps/context-timetable';
31
+
32
+ import componentStyles from './styles';
33
+
34
+ /**
35
+ * Diese Callback-Funktion wird gerufen, wenn der vom Nutzer ausgelöste Import erfolgreich war.
36
+ * @callback onImportSuccessfullyFinished
37
+ * @returns {void}
38
+ */
39
+
40
+ /**
41
+ * Diese Callback-Funktion wird gerufen, wenn der vom Nutzer ausgelöste Importversuch fehlschägt war.
42
+ * Es wird der Grund des Fehlschlags zur weiteren Behandlung übergeben.
43
+ * @callback onImportFailed
44
+ * @param {*} reason
45
+ */
46
+
47
+ /**
48
+ * @param {*} props
49
+ * @param {onImportSuccessfullyFinished} props.onImportFailed
50
+ * @param {onImportFailed} props.ponImportSuccessfullyFinished
51
+ */
52
+ function TimetableCodeInput(props) {
53
+ const {
54
+ theme,
55
+ theme: { colors, appSettings: { modules: { timetable: { code } } } },
56
+ t,
57
+ // Callback, welche nach dem erfolgreichen Importieren aufgerufen wird.
58
+ onImportSuccessfullyFinished,
59
+ // Callback, welche nach einem fehlgeschlagenen Importversuch aufgerufen wird.
60
+ onImportFailed,
61
+ } = props;
62
+
63
+ const componentName = TimetableCodeInput.name;
64
+
65
+ // Einstellungen für den Stundenplan-Code
66
+ const timetableCodeInputLength = code?.length ?? 0;
67
+ const timetableCodeInputFilters = code?.filters;
68
+ const timetableCodeInputPreSaveFilters = Array.isArray(code?.preSaveFilters) ? code.preSaveFilters : [];
69
+ const timetableCodeInputFilterToUpperCase = timetableCodeInputFilters?.toUpperCase ?? false;
70
+
71
+ const [timetableCode, saveTimetableCode] = useTimetableCode();
72
+ const [, refreshCourses] = useCourses();
73
+
74
+ const [timetableCodeInput, setTimetableCodeInput] = useState(timetableCode);
75
+ const [importing, setImporting] = useState(false);
76
+
77
+ // Generate styles. Will be generated only if not present or the theme property changes.
78
+ const styles = useMemo(
79
+ () => StyleSheet.create(componentStyles(theme)),
80
+ [theme]
81
+ )
82
+
83
+ const handleLinkPress = (url) => {
84
+ Linking.openURL(url);
85
+ };
86
+
87
+ const renderTextWithLinks = (text) => {
88
+ const parts = text.split(/(https?:\/\/[^\s]+)/g);
89
+ return parts.map((part, index) => {
90
+ if (part.match(/https?:\/\/[^\s]+/)) {
91
+ const displayText = part.replace(/https?:\/\//, '');
92
+ return (
93
+ <Text
94
+ key={index}
95
+ style={styles.link}
96
+ onPress={() => handleLinkPress(part)}
97
+ >
98
+ {displayText}
99
+ </Text>
100
+ );
101
+ }
102
+ return part;
103
+ });
104
+ };
105
+
106
+ const paragraphText = t(timetableCode ? 'timetable:inputTimetableCode' : 'timetable:notImportedYet', { timetableCodeInputLength });
107
+
108
+ return (
109
+ <ScrollView style={styles.mainContentContainer}>
110
+ <KeyboardAvoidingView behavior={"padding"} >
111
+ <View style={styles.paragraph}>
112
+ {/*selectable and dataDetectorType only work on andorid*/}
113
+ <Paragraph style={styles.paragraphText} selectable={true} dataDetectorType={'link'}>
114
+ {renderTextWithLinks(paragraphText)}
115
+ </Paragraph>
116
+ </View>
117
+ <View style={styles.importTextInputContainer}>
118
+ <TextInput
119
+ theme={{ colors: { primary: colors.textInput, placeholder: colors.textLighter } }}
120
+ style={styles.paragraphText}
121
+ selectionColor={colors.textInputSelection}
122
+ label={t('timetable:inputPlaceholder')}
123
+ value={timetableCodeInput}
124
+ autoCapitalize={timetableCodeInputFilterToUpperCase ? 'characters' : 'none'}
125
+ maxLength={timetableCodeInputLength ? timetableCodeInputLength : null}
126
+ onChangeText={setTimetableCodeInput}
127
+ />
128
+ </View>
129
+ <Button
130
+ onPress={async () => {
131
+ // Wenn eine Mindestlänge des Stundenplandcodes konfiguriert ist und diese nicht von der Eingabe erreicht wird, wird ein Fehlerdialog angezeigt
132
+ if (timetableCodeInputLength && (timetableCodeInput?.length ?? 0) < timetableCodeInputLength) {
133
+ const alertTranslationValues = { timetableCodeInputLength };
134
+ Alert.alert(
135
+ t('timetable:errorCodeShort:title', alertTranslationValues),
136
+ t('timetable:errorCodeShort:message', alertTranslationValues),
137
+ [{ text: 'OK' }],
138
+ );
139
+ } else {
140
+ // Import-Ladeanimation aktivieren
141
+ setImporting(true);
142
+ await saveTimetableCode(
143
+ // Falls Filter eingestellt sind, werden diese nacheinander auf den Stundenplancode angewendet
144
+ timetableCodeInputPreSaveFilters.reduce(
145
+ (currentTimetableCode, preSaveFilter) => preSaveFilter?.(currentTimetableCode) ?? currentTimetableCode,
146
+ timetableCodeInput
147
+ )
148
+ );
149
+ /**
150
+ * Dieser Aufruf ist eigentlich unnötig, aber die Nutzer möchten Feedback, wenn der Stundenplan nochmal importiert wird.
151
+ * Der Stundenplan wird über einen useEffect-Hook neu geladen, sobald der neue Stundenplancode vom Context gespeichert wurde.
152
+ * Es soll aber die Lade-Animation angezeigt werden und zum Stundenplan zurückgespungen werden, auch wenn schon ein Stundenplancode importiert wurde.
153
+ *
154
+ * Da ich mir nicht besser zu helfen weiß, wird die refreshCourses-Funktion gerufen und importiert NOCHMAL den Stundenplan, nachdem dieser schon vom useEffect-Hook im Kontext importiert wurde.
155
+ * refreshCourses gibt eine Promise zurück, die mit dem fertigen Import auf nachfolgende then-Anweisungen weiterleitet.
156
+ *
157
+ * Man könnte vielleicht auch bei saveTimetableCode ein Promise zurückgeben, die den Stundenplan auch gleich importiert.
158
+ * Aber dann müsste man anstatt des useEffekt-Hooks, der auf den Stundenplancode reagiert, jeden refresh des Stundenplanes über den Code selbst implementieren.
159
+ */
160
+ refreshCourses()
161
+ // Rufe onImportSuccessfullyFinished-Callback, wenn Import erfolgreich war
162
+ .then(() => onImportSuccessfullyFinished?.())
163
+ .catch((reason) => {
164
+ if (reason instanceof ApiProviderNotInitializedError) {
165
+ console.error(componentName, ': ', reason);
166
+ } else {
167
+ console.debug(componentName, ': ', reason);
168
+ }
169
+ // Rufe onImportFailed.Callback, wenn Import fehlschlug
170
+ onImportFailed?.(reason);
171
+ })
172
+ // Import-Ladeanimation immer deaktivieren
173
+ .finally(() => setImporting(false));
174
+ }
175
+ }}
176
+ labelStyle={styles.importButtonLabel}
177
+ color={colors.buttonText}
178
+ >
179
+ {t('timetable:importButton')}
180
+ </Button>
181
+ {
182
+ // Es wird ein Ladesymbol angezeigt, wenn der Stundenplan importiert wird.
183
+ importing
184
+ ? <>
185
+ <ActivityIndicator style={styles.activity} size="large" color={colors.loadingIndicator} />
186
+ <View style={styles.paragraph}><Paragraph>{t('timetable:importInProgress', '')}</Paragraph></View>
187
+ </>
188
+ : null
189
+ }
190
+ </KeyboardAvoidingView>
191
+ </ScrollView>
192
+ );
193
+ }
194
+
195
+ const mapStateToProps = state => {
196
+ return {
197
+ settings: state.settingReducer
198
+ };
199
+ };
200
+
201
+ export default connect(mapStateToProps, null)(withTranslation()(withTheme(TimetableCodeInput)))
@@ -0,0 +1,28 @@
1
+ export default function (theme) {
2
+ return {
3
+ mainContentContainer: {
4
+ flex: 1,
5
+ backgroundColor: theme.colors.contentBackground,
6
+ },
7
+ paragraph: {
8
+ paddingTop: theme.paddings.default,
9
+ paddingLeft: theme.paddings.default,
10
+ paddingRight: theme.paddings.default
11
+ },
12
+ paragraphText: {
13
+ fontSize: theme.fontSizes.l,
14
+ lineHeight: theme.lineHeights.l
15
+ },
16
+ importButtonLabel: {
17
+ fontSize: theme.fontSizes.l,
18
+ lineHeight: theme.lineHeights.l
19
+ },
20
+ importTextInputContainer: {
21
+ paddingLeft: theme.paddings.default,
22
+ paddingRight: theme.paddings.default
23
+ },
24
+ link: {
25
+ textDecorationLine: 'underline',
26
+ },
27
+ }
28
+ };
@@ -0,0 +1,266 @@
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, useCallback, useEffect } from 'react';
16
+ import { View, Text, TouchableOpacity, StyleSheet, useWindowDimensions } from 'react-native';
17
+ import { connect } from 'react-redux';
18
+ import { withTheme } from 'react-native-paper';
19
+ import { withTranslation } from 'react-i18next';
20
+ import { onUpdateRefreshing } from '@olea-bps/core';
21
+ import componentStyles from "./styles";
22
+ import { Calendar } from 'react-native-big-calendar';
23
+ import { useCourses } from '@olea-bps/context-timetable';
24
+ import CalendarStrip from 'react-native-calendar-strip';
25
+ import CourseDetailDialog from '../component-course-detail-dialog';
26
+ import { DateTime, Duration } from 'luxon';
27
+ import { TabView } from 'react-native-tab-view';
28
+ import moment from 'moment';
29
+ import 'moment/locale/de';
30
+
31
+ /**
32
+ * Die Zeitspanne, welche vor und nach heute angezeigt werden soll.
33
+ */
34
+ const daysTabDuration = Duration.fromISO('P6M');
35
+
36
+ /**
37
+ *
38
+ * @param {object} props
39
+ * @param {(DateTime) => void} props.onDateChanged Callback, welche aufgerufen wird, wenn der Tag in der Ansicht wechselt
40
+ * @returns
41
+ */
42
+ function CalendarDay({ selectedDate, theme, settings, calendarScrollOffsetMinutes, onDateChanged, onCourseSelected }) {
43
+ const { appSettings: { modules: { timetable: { showDetails } } } } = theme;
44
+ const today = DateTime.now().toISODate();
45
+
46
+ const { width } = useWindowDimensions();
47
+
48
+ const [courses] = useCourses();
49
+
50
+ const language = settings?.settingsGeneral?.language;
51
+
52
+ const styles = useMemo(
53
+ () => StyleSheet.create(componentStyles(theme)),
54
+ [theme]
55
+ );
56
+
57
+ const calendarStripMinDate = useMemo(
58
+ () => DateTime.fromISO(today).minus(daysTabDuration).toJSDate(),
59
+ [today, daysTabDuration]
60
+ );
61
+
62
+ const calendarStripMaxDate = useMemo(
63
+ () => DateTime.fromISO(today).plus(daysTabDuration).toJSDate(),
64
+ [today, daysTabDuration]
65
+ );
66
+
67
+ const daysTabViewRoutes = useMemo(
68
+ () => {
69
+ const dateRoutes = [];
70
+ const todayDateTime = DateTime.fromISO(today);
71
+ const beginDateTime = todayDateTime.minus(daysTabDuration);
72
+ const endDateTime = todayDateTime.plus(daysTabDuration);
73
+ let currentDateTime = DateTime.fromObject(beginDateTime.toObject());
74
+
75
+ const eventColor = theme.colors.eventContainerSidebar;
76
+
77
+ while (currentDateTime <= endDateTime) {
78
+ const currentISODate = currentDateTime.toISODate();
79
+ const currentDateTimeCourses = courses?.[currentISODate]
80
+ ?.map(
81
+ course => (
82
+ {
83
+ title: course.title.data,
84
+ start: course.startDateTime,
85
+ end: course.endDateTime,
86
+ color: eventColor,
87
+ type: course.type?.data,
88
+ professor: course.lecturer[0]?.data,
89
+ room: course.room.data,
90
+ }
91
+ )
92
+ ) ?? [];
93
+
94
+ dateRoutes.push(
95
+ {
96
+ key: currentISODate,
97
+ date: currentDateTime,
98
+ events: currentDateTimeCourses,
99
+ }
100
+ );
101
+
102
+ currentDateTime = currentDateTime.plus({ day: 1 });
103
+ }
104
+
105
+ return dateRoutes;
106
+ },
107
+ [today, courses, language]
108
+ );
109
+
110
+ const daysTabViewIndexes = useMemo(
111
+ () => daysTabViewRoutes
112
+ .reduce(
113
+ (accumulator, currentRoute, currentIndex) =>
114
+ (
115
+ {
116
+ ...accumulator,
117
+ [currentRoute.key]: currentIndex
118
+ }
119
+ ),
120
+ {}
121
+ ),
122
+ [daysTabViewRoutes]
123
+ )
124
+
125
+ const daysTabViewIndex = useMemo(
126
+ () => {
127
+ const selectedISODate = selectedDate.toISODate();
128
+ return daysTabViewIndexes[selectedISODate];
129
+ },
130
+ [daysTabViewIndexes, selectedDate]
131
+ )
132
+
133
+ const renderEvent = useCallback((event, touchableOpacityProps) => {
134
+ const { style, ...restTouchableOpacityProps } = touchableOpacityProps;
135
+ return (
136
+ <TouchableOpacity {...restTouchableOpacityProps} style={[style, styles.eventContainer, { borderLeftColor: event.color || '#000' }]} >
137
+ <View style={styles.eventHeader}>
138
+ <Text style={styles.eventTitle} numberOfLines={1} ellipsizeMode="tail">
139
+ {event.title}
140
+ </Text>
141
+ <Text style={styles.eventType} numberOfLines={1} ellipsizeMode="tail">
142
+ {event.type}
143
+ </Text>
144
+ </View>
145
+ <Text style={styles.eventProfessor} numberOfLines={1} ellipsizeMode="head">
146
+ {event.professor}
147
+ </Text>
148
+ <View style={styles.eventFooter}>
149
+ <Text style={styles.eventTime} numberOfLines={1} ellipsizeMode="tail">
150
+ {`${event.start.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })} - ${event.end.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}`}
151
+ </Text>
152
+ <Text style={styles.eventRoom} numberOfLines={1} ellipsizeMode="tail">
153
+ {event.room}
154
+ </Text>
155
+ </View>
156
+ </TouchableOpacity>
157
+ );
158
+ }, [styles]);
159
+
160
+ const renderScene = useCallback(
161
+ ({ route }) => {
162
+ return (
163
+ <View style={{ flex: 1, width: width }}>
164
+ <Calendar
165
+ events={route.events}
166
+ renderEvent={renderEvent}
167
+ renderHeader={() => null}
168
+ overlapOffset={95}
169
+ mode={'day'}
170
+ height={800}
171
+ headerContentStyle={{ backgroundColor: 'transparent' }}
172
+ weekStartsOn={1}
173
+ date={route.key}
174
+ swipeEnabled={false}
175
+ onPressEvent={
176
+ showDetails
177
+ ? (event) => {
178
+ const eventDateISO = DateTime.fromJSDate(event.start).toISODate();
179
+
180
+ const eventStartTime = event.start.getTime();
181
+ const originalCourse = courses[eventDateISO]
182
+ ?.find(
183
+ course =>
184
+ eventStartTime === course.startDateTime.getTime()
185
+ &&
186
+ event?.title === course?.title?.data
187
+ &&
188
+ event?.professor === course?.lecturer[0]?.data
189
+ &&
190
+ event?.room === course?.room?.data
191
+ );
192
+
193
+ onCourseSelected(originalCourse);
194
+ }
195
+ : null
196
+ }
197
+ scrollOffsetMinutes={calendarScrollOffsetMinutes}
198
+ hourStyle={styles.hourStyle}
199
+ />
200
+ </View>
201
+ );
202
+ },
203
+ [styles, calendarScrollOffsetMinutes, courses, width, renderEvent]
204
+ );
205
+
206
+ return (
207
+ <>
208
+ <View style={styles.stripShadow}>
209
+ <CalendarStrip
210
+ style={styles.strip}
211
+ locale={{
212
+ name: language,
213
+ config: moment.localeData(language)
214
+ }}
215
+ selectedDate={selectedDate.toJSDate()}
216
+ onDateSelected={
217
+ (date) =>
218
+ onDateChanged?.(
219
+ DateTime.fromObject(
220
+ {
221
+ year: date.year(),
222
+ month: date.month() + 1,
223
+ day: date.date(),
224
+ }
225
+ )
226
+ )
227
+ }
228
+ highlightDateContainerStyle={styles.highlightDateContainer}
229
+ showMonth={false}
230
+ iconStyle={{ display: 'none' }}
231
+ scrollable={true}
232
+ minDate={calendarStripMinDate}
233
+ maxDate={calendarStripMaxDate}
234
+ />
235
+ </View>
236
+
237
+ <TabView
238
+ navigationState={{ index: daysTabViewIndex, routes: daysTabViewRoutes }}
239
+ renderTabBar={() => null}
240
+ lazy
241
+ lazyPreloadDistance={1}
242
+ onIndexChange={
243
+ (index) => {
244
+ const currentDateTime = daysTabViewRoutes?.[daysTabViewIndex].date;
245
+ const indexDelta = index - daysTabViewIndex;
246
+ const deltaDateTime = currentDateTime.plus({ days: indexDelta });
247
+ onDateChanged?.(deltaDateTime);
248
+ }
249
+ }
250
+ renderScene={renderScene}
251
+ />
252
+ </>
253
+ )
254
+ }
255
+
256
+ const mapStateToProps = (state) => {
257
+ return {
258
+ pluginComponent: state.pluginReducer.timetable.component,
259
+ pluginStyles: state.pluginReducer.timetable.styles,
260
+ settings: state.settingReducer,
261
+ refreshing: state.stateReducer.refreshing,
262
+
263
+ };
264
+ };
265
+
266
+ export default connect(mapStateToProps, { onUpdateRefreshing })(withTranslation()(withTheme(CalendarDay)));
@@ -0,0 +1,103 @@
1
+ export default function (theme) {
2
+ return {
3
+ eventContainer: {
4
+ borderLeftWidth: 4,
5
+ backgroundColor: theme.colors.eventContainerBackground,
6
+ padding: 10,
7
+ marginBottom: 10,
8
+ marginRight: 10,
9
+ borderRadius: 8,
10
+ elevation: 5,
11
+ shadowColor: '#000',
12
+ shadowOffset: { width: 0, height: 2 },
13
+ shadowOpacity: 0.3,
14
+ shadowRadius: 3,
15
+ overflow: 'visible',
16
+ },
17
+
18
+ eventHeader: {
19
+ flexDirection: 'row',
20
+ justifyContent: 'space-between',
21
+ marginBottom: 0,
22
+ },
23
+ eventTitle: {
24
+ color: theme.colors.text,
25
+ fontSize: 14,
26
+ fontWeight: 'bold',
27
+ flex: 1,
28
+ paddingRight: 10,
29
+ },
30
+ eventType: {
31
+ color: theme.colors.text,
32
+ fontSize: 13,
33
+ },
34
+ eventProfessor: {
35
+ color: theme.colors.text,
36
+ fontSize: 13,
37
+ paddingTop: 4,
38
+ },
39
+ eventFooter: {
40
+ flexDirection: 'row',
41
+ justifyContent: 'space-between',
42
+ alignItems: 'center',
43
+ marginTop: 4,
44
+ },
45
+ eventTime: {
46
+ fontSize: 13,
47
+ color: theme.colors.text,
48
+ flex: 1,
49
+ },
50
+ eventRoom: {
51
+ fontSize: 13,
52
+ color: theme.colors.text,
53
+ textAlign: 'right',
54
+ flex: 1,
55
+ },
56
+ highlightDateContainer: {
57
+ borderRadius: 100,
58
+ borderWidth: 3,
59
+ borderColor: 'red',
60
+ height: 48,
61
+ width: 48,
62
+ },
63
+ courseCard: {
64
+ paddingLeft: 5,
65
+ },
66
+ courseTimeContainerBigFont: {
67
+ width: "25%",
68
+ },
69
+ timeText: {
70
+ fontSize: theme.fontSizes.subtitle,
71
+ paddingTop: theme.paddings.small,
72
+ paddingBottom: theme.paddings.small
73
+ },
74
+ timeTextBig: {
75
+ fontSize: theme.fontSizes.m
76
+ },
77
+
78
+ courseContainerBigFont: {
79
+ width: "75%",
80
+ },
81
+ type: {
82
+ fontSize: theme.fontSizes.subtitle,
83
+ color: theme.colors.subtitle
84
+ },
85
+ strip: {
86
+ height: 65,
87
+ paddingTop: 5,
88
+ paddingBottom: 5,
89
+ elevation: 5,
90
+ backgroundColor: '#fff',
91
+ },
92
+ stripShadow: {
93
+ shadowColor: '#000',
94
+ shadowOffset: { width: 0, height: 3 },
95
+ shadowOpacity: 0.2,
96
+ shadowRadius: 2,
97
+ },
98
+ hourStyle: {
99
+ fontSize: 12,
100
+ color: theme.colors.text,
101
+ },
102
+ };
103
+ }