@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
package/Event/index.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
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, useEffect, useCallback } from 'react';
|
|
16
|
+
import {
|
|
17
|
+
ActivityIndicator,
|
|
18
|
+
Dimensions,
|
|
19
|
+
SafeAreaView,
|
|
20
|
+
ScrollView,
|
|
21
|
+
StyleSheet,
|
|
22
|
+
Text,
|
|
23
|
+
View,
|
|
24
|
+
} from 'react-native';
|
|
25
|
+
|
|
26
|
+
import { useTranslation } from 'react-i18next';
|
|
27
|
+
import { Appbar, useTheme } from 'react-native-paper';
|
|
28
|
+
import { AccordionList } from 'accordion-collapse-react-native';
|
|
29
|
+
import { TabView, TabBar, SceneMap } from 'react-native-tab-view';
|
|
30
|
+
import { useNavigation } from '@react-navigation/native';
|
|
31
|
+
|
|
32
|
+
import moment from 'moment';
|
|
33
|
+
import { DateTime } from 'luxon';
|
|
34
|
+
|
|
35
|
+
import EventContextProvider, { usePersonalEvents, usePersonalEventsCode, useEvents } from '@olea-bps/context-event';
|
|
36
|
+
import { AppBar as AppbarComponent } from '@olea-bps/components';
|
|
37
|
+
import IconsOpenasist from '@olea-bps/icons-openasist';
|
|
38
|
+
import { EventCodeInput as EventCodeInputComponent } from '@olea-bps/components';
|
|
39
|
+
import { TimetableEvent as TimetableEventComponent } from '@olea-bps/components'
|
|
40
|
+
|
|
41
|
+
import componentStyles from './styles';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {*} allEvents List of events
|
|
45
|
+
* @param {*} start Startingpoint of the timeslot-list/start of the event-day
|
|
46
|
+
* @param {*} end Endpoint of the timeslot-list/end of the event-day
|
|
47
|
+
* @param {*} length length of the single timeslots
|
|
48
|
+
* @returns a list of objects for each timeslot including start(startTs), end(endTs) and a list(events) of events for that timeslot
|
|
49
|
+
* startTs and endTs are moment().js-elements/modified date-time-elements while events will contain the event-objects for this timeslot
|
|
50
|
+
*/
|
|
51
|
+
function generateTimeslots(allEvents, start, end, length) {
|
|
52
|
+
|
|
53
|
+
const timeslotList = [];
|
|
54
|
+
/*
|
|
55
|
+
For-Loop that loops from the start to the end in increments provided by length
|
|
56
|
+
So that there are timeslots from the start to the end of the event day
|
|
57
|
+
*/
|
|
58
|
+
for (let timeSlot = moment(start); timeSlot.isSameOrBefore(end); timeSlot.add(length, 'hours')) {
|
|
59
|
+
// Define start and end(start + length) for each timeslot
|
|
60
|
+
var timeslotStart = moment(timeSlot);
|
|
61
|
+
var timeslotEnd = moment(timeSlot).add(length, 'hours');
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
Use the build in filter()-Method to find all events in the given timeslot, so between timeslotStart and timeslotEnd
|
|
65
|
+
The timeslotStart and timeslotEnd are defined in the yyyy-MM-ddTHH:mm:ssZ-format in order to be able to compare them directly to the
|
|
66
|
+
start and end-prop in the event-Objects
|
|
67
|
+
A event will be sorted in the current timeslot, if the value of the start prop is between the start and end of the current timeslot
|
|
68
|
+
This is due to also include events that start in one timeslot and end in another timeslot
|
|
69
|
+
*/
|
|
70
|
+
var eventsInTimeslot = allEvents.filter(
|
|
71
|
+
currentEvent =>
|
|
72
|
+
moment(currentEvent.begin).isSameOrAfter(timeslotStart)
|
|
73
|
+
&&
|
|
74
|
+
moment(currentEvent.begin).isBefore(timeslotEnd)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// If there are any Event-objects in the given timeslot, the event have to be sorted in the correct order
|
|
78
|
+
if (eventsInTimeslot.length > 0) {
|
|
79
|
+
// After that, push the current timeslot-object into the timeslotList
|
|
80
|
+
timeslotList.push({ startTs: timeslotStart.format('HH:mm'), endTs: timeslotEnd.format('HH:mm'), eventsTs: eventsInTimeslot })
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return timeslotList;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Event View
|
|
89
|
+
*
|
|
90
|
+
* Parameters:
|
|
91
|
+
* - none
|
|
92
|
+
*
|
|
93
|
+
* Navigation-Parameters:
|
|
94
|
+
* - none
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Die Funktion erzeugt einen Header für die jeweilige Liste, der eine Kurzbezeichnung und ein Icon zur Identifikation beinhaltet
|
|
99
|
+
*
|
|
100
|
+
* @param {object} props beinhaltet styles, themes und weitere props
|
|
101
|
+
* @param {boolean} props.isExpanded Flag das signalisiert, ob die aktuelle Liste geöffnet ist oder nicht
|
|
102
|
+
* @param {string} props.startTime Startzeit der Liste
|
|
103
|
+
* @param {string} props.endTime Endzeit der Liste
|
|
104
|
+
*/
|
|
105
|
+
function TimeslotAccordionHeader({ startTime, endTime, theme: { colors, themeStyles }, styles, isExpanded, t }) {
|
|
106
|
+
return (
|
|
107
|
+
<View style={styles.header}>
|
|
108
|
+
<View style={[themeStyles.cardLeftIcon, { marginStart: 5 }]}>
|
|
109
|
+
<IconsOpenasist icon={('info')} size={25} color={colors.icon} />
|
|
110
|
+
</View>
|
|
111
|
+
<Text style={styles.headerText}>{`${startTime} ${t('event:type.to')} ${endTime} ${t('event:type.oclock')}`}</Text>
|
|
112
|
+
<View style={styles.arrowIcon}>
|
|
113
|
+
<IconsOpenasist icon={isExpanded ? 'up' : 'down'} size={20} color={colors.grayLight5} />
|
|
114
|
+
</View>
|
|
115
|
+
</View>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {object} props beinhaltet styles, themes und weitere props
|
|
121
|
+
* @param {Array} props.items Die Liste von Events vom aktuellen Tab
|
|
122
|
+
* @return eine Scrollview, welche eine Liste von TimeTableEventComponent enthält
|
|
123
|
+
*/
|
|
124
|
+
function EventList({ items: events = [], styles }) {
|
|
125
|
+
const { t } = useTranslation();
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<ScrollView>
|
|
129
|
+
{
|
|
130
|
+
events
|
|
131
|
+
?.map(
|
|
132
|
+
(event) => {
|
|
133
|
+
const startTime = DateTime.fromISO(event.begin).toLocaleString(DateTime.TIME_24_SIMPLE);
|
|
134
|
+
const endTime = DateTime.fromISO(event.end).toLocaleString(DateTime.TIME_24_SIMPLE);
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<TimetableEventComponent
|
|
138
|
+
key={event.id}
|
|
139
|
+
type={event?.type ? t(`event:type:${event.type}`) : null}
|
|
140
|
+
title={event?.title}
|
|
141
|
+
location={event?.location}
|
|
142
|
+
locationUrl={event?.locationUrl}
|
|
143
|
+
description={event?.description}
|
|
144
|
+
times={{ start: startTime, end: endTime }} />
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
?? null
|
|
149
|
+
}
|
|
150
|
+
</ScrollView>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/***
|
|
155
|
+
* Hook-Component welche eine Accordionliste mit dem entsprechenden Inhalt generiert. Inklusive Header.
|
|
156
|
+
*
|
|
157
|
+
* @param props beinhaltet styles, themes und weitere props
|
|
158
|
+
*/
|
|
159
|
+
function EventTimeslotList() {
|
|
160
|
+
const componentName = EventTimeslotList.name;
|
|
161
|
+
const theme = useTheme();
|
|
162
|
+
const [events, refreshEvents] = useEvents();
|
|
163
|
+
|
|
164
|
+
const styles = useMemo(
|
|
165
|
+
() => StyleSheet.create(componentStyles(theme)),
|
|
166
|
+
[theme, componentStyles]
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const timeslots = useMemo(
|
|
170
|
+
() => {
|
|
171
|
+
if (Array.isArray(events) && events.length > 0) {
|
|
172
|
+
// get the value of the start-prop of the earliest-event
|
|
173
|
+
const earliestEvent = events.reduce((previousEvent, currentEvent) => { return currentEvent.begin < previousEvent.begin ? currentEvent : previousEvent });
|
|
174
|
+
// get the value of the end-prop of the latest-event
|
|
175
|
+
const latestEvent = events.reduce((previousEvent, currentEvent) => { return currentEvent.end > previousEvent.end ? currentEvent : previousEvent });
|
|
176
|
+
return generateTimeslots(events, earliestEvent.begin, latestEvent.end, 1.5);
|
|
177
|
+
|
|
178
|
+
} else if (Array.isArray(events) && events.length === 0) {
|
|
179
|
+
return [];
|
|
180
|
+
|
|
181
|
+
} else {
|
|
182
|
+
console.debug(componentName, ':', 'No events to Generate timeslots');
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
[events, generateTimeslots]
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
useEffect(
|
|
190
|
+
() => {
|
|
191
|
+
refreshEvents();
|
|
192
|
+
},
|
|
193
|
+
[refreshEvents]
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
return Array.isArray(timeslots)
|
|
197
|
+
? timeslots.length > 0
|
|
198
|
+
? <AccordionList
|
|
199
|
+
list={timeslots}
|
|
200
|
+
header={
|
|
201
|
+
(timeslot, index, isExpanded) => <TimeslotAccordionHeader startTime={timeslot.startTs} endTime={timeslot.endTs} styles={styles} isExpanded={isExpanded} />
|
|
202
|
+
}
|
|
203
|
+
body={
|
|
204
|
+
(timeslot) => <EventList items={timeslot.eventsTs} styles={styles} />
|
|
205
|
+
}
|
|
206
|
+
/>
|
|
207
|
+
: <View style={styles.content}>
|
|
208
|
+
<Text>
|
|
209
|
+
Es werden gerade keine Veranstalltungen angeboten
|
|
210
|
+
</Text>
|
|
211
|
+
</View>
|
|
212
|
+
: <View style={styles.content}>
|
|
213
|
+
<Text>{t('canteen:notAvailable')}</Text>
|
|
214
|
+
<ActivityIndicator style={styles.activity} size='large' color={theme.colors.loadingIndicator} />
|
|
215
|
+
</View>
|
|
216
|
+
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function SelectedEventList() {
|
|
220
|
+
const componentName = SelectedEventList.name;
|
|
221
|
+
const theme = useTheme();
|
|
222
|
+
|
|
223
|
+
const [personalEvents, refreshPersonalEvents] = usePersonalEvents();
|
|
224
|
+
|
|
225
|
+
const styles = useMemo(
|
|
226
|
+
() => StyleSheet.create(componentStyles(theme)),
|
|
227
|
+
[theme, componentStyles]
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
useEffect(
|
|
231
|
+
() => {
|
|
232
|
+
refreshPersonalEvents();
|
|
233
|
+
},
|
|
234
|
+
[refreshPersonalEvents]
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<EventList
|
|
239
|
+
styles={styles}
|
|
240
|
+
items={personalEvents}
|
|
241
|
+
/>
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const TabViewScenes = SceneMap({
|
|
246
|
+
all_events: EventTimeslotList,
|
|
247
|
+
personal_events: SelectedEventList,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
export default function EventsView(props) {
|
|
252
|
+
|
|
253
|
+
const [personalEventsCode] = usePersonalEventsCode();
|
|
254
|
+
const theme = useTheme();
|
|
255
|
+
const { colors, themeStyles } = theme;
|
|
256
|
+
const { t } = useTranslation();
|
|
257
|
+
const navigation = useNavigation();
|
|
258
|
+
|
|
259
|
+
const [showCodeInput, setShowCodeInput] = useState(personalEventsCode ? false : true);
|
|
260
|
+
const [tabIndex, setTabIndex] = useState(0);
|
|
261
|
+
|
|
262
|
+
const tabRoutes = useMemo(
|
|
263
|
+
() =>
|
|
264
|
+
[
|
|
265
|
+
{
|
|
266
|
+
key: 'personal_events',
|
|
267
|
+
title: t('event:tabs.selected'),
|
|
268
|
+
accessibilityLabel: t('accessibility:event.selected'),
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
key: 'all_events',
|
|
272
|
+
title: t('event:tabs.all'),
|
|
273
|
+
accessibilityLabel: t('accessibility:event.all'),
|
|
274
|
+
}
|
|
275
|
+
],
|
|
276
|
+
[t]
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
const styles = useMemo(
|
|
281
|
+
() => StyleSheet.create(componentStyles(theme)),
|
|
282
|
+
[theme]
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
const leftAction = useMemo(
|
|
286
|
+
() => (
|
|
287
|
+
<Appbar.Action
|
|
288
|
+
icon={props => <IconsOpenasist {...props} icon={'back'} color={colors.primaryText} />}
|
|
289
|
+
onPress={() => {
|
|
290
|
+
navigation.goBack();
|
|
291
|
+
}} />
|
|
292
|
+
),
|
|
293
|
+
[colors, navigation]
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
const rightAction = useMemo(
|
|
297
|
+
() => (
|
|
298
|
+
<Appbar.Action
|
|
299
|
+
icon={showCodeInput ? 'table' : 'table-plus'}
|
|
300
|
+
color={colors.appbarIconColor}
|
|
301
|
+
onPress={() => setShowCodeInput(showCodeInput => !showCodeInput)}
|
|
302
|
+
/>
|
|
303
|
+
),
|
|
304
|
+
[showCodeInput, setShowCodeInput, colors]
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
const closeCodeInput = useCallback(
|
|
308
|
+
() => setShowCodeInput(false),
|
|
309
|
+
[setShowCodeInput]
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
const closeCodeInputAndShowPersonalEvents = useCallback(
|
|
313
|
+
() => {
|
|
314
|
+
setTabIndex(tabRoutes.findIndex(route => route.key === 'personal_events'));
|
|
315
|
+
setShowCodeInput(false);
|
|
316
|
+
},
|
|
317
|
+
[tabRoutes, setShowCodeInput, setTabIndex]
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
<SafeAreaView style={[styles.container, themeStyles.appSafeAreaContainer]}>
|
|
323
|
+
<View>
|
|
324
|
+
<AppbarComponent {...props}
|
|
325
|
+
title={t('menu:titles.event')}
|
|
326
|
+
leftAction={leftAction}
|
|
327
|
+
rightAction={rightAction}
|
|
328
|
+
/>
|
|
329
|
+
</View>
|
|
330
|
+
{
|
|
331
|
+
showCodeInput
|
|
332
|
+
? <EventCodeInputComponent {...props}
|
|
333
|
+
onClose={closeCodeInput}
|
|
334
|
+
onPersonalEventsImported={closeCodeInputAndShowPersonalEvents}
|
|
335
|
+
/>
|
|
336
|
+
: <View style={themeStyles.container}>
|
|
337
|
+
<TabView
|
|
338
|
+
index={tabIndex}
|
|
339
|
+
navigationState={{ index: tabIndex, routes: tabRoutes }}
|
|
340
|
+
initialLayout={{ height: 0, width: Dimensions.get('window').width }}
|
|
341
|
+
onIndexChange={setTabIndex}
|
|
342
|
+
renderTabBar={props =>
|
|
343
|
+
<TabBar
|
|
344
|
+
{...props}
|
|
345
|
+
style={themeStyles.tabs}
|
|
346
|
+
labelStyle={themeStyles.tab}
|
|
347
|
+
activeColor={themeStyles.tabs.activeColor}
|
|
348
|
+
inactiveColor={themeStyles.tabs.inactiveColor}
|
|
349
|
+
indicatorStyle={themeStyles.tabIndicator}
|
|
350
|
+
tabStyle={{ paddingHorizontal: 20 }}
|
|
351
|
+
/>
|
|
352
|
+
}
|
|
353
|
+
renderScene={TabViewScenes}
|
|
354
|
+
/>
|
|
355
|
+
</View>
|
|
356
|
+
}
|
|
357
|
+
</SafeAreaView>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function APIProvidetEventsView(props) {
|
|
362
|
+
return (
|
|
363
|
+
<EventContextProvider>
|
|
364
|
+
<EventsView
|
|
365
|
+
{...props}
|
|
366
|
+
/>
|
|
367
|
+
</EventContextProvider>
|
|
368
|
+
)
|
|
369
|
+
}
|
package/Event/styles.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export default function(theme) {
|
|
2
|
+
return {
|
|
3
|
+
activity: {
|
|
4
|
+
padding: 20
|
|
5
|
+
},
|
|
6
|
+
arrowIcon: {
|
|
7
|
+
marginStart: 'auto',
|
|
8
|
+
marginEnd: 10,
|
|
9
|
+
},
|
|
10
|
+
companyCity:{
|
|
11
|
+
fontSize: theme.fontSizes.s,
|
|
12
|
+
paddingTop: theme.paddings.xsmall,
|
|
13
|
+
color: theme.colors.secondaryText
|
|
14
|
+
},
|
|
15
|
+
container: {
|
|
16
|
+
flex: 1,
|
|
17
|
+
backgroundColor: theme.colors.background,
|
|
18
|
+
},
|
|
19
|
+
containerErrorMsg: {
|
|
20
|
+
flex: 1,
|
|
21
|
+
justifyContent: 'center',
|
|
22
|
+
alignContent: 'center',
|
|
23
|
+
alignItems: 'center',
|
|
24
|
+
margin: theme.paddings.default,
|
|
25
|
+
},
|
|
26
|
+
content: {
|
|
27
|
+
backgroundColor: theme.colors.background,
|
|
28
|
+
flex: 1,
|
|
29
|
+
padding: "2%",
|
|
30
|
+
},
|
|
31
|
+
date:{
|
|
32
|
+
fontSize: theme.fontSizes.s,
|
|
33
|
+
paddingTop: theme.paddings.xsmall
|
|
34
|
+
},
|
|
35
|
+
header: {
|
|
36
|
+
backgroundColor: theme.colors.background,
|
|
37
|
+
padding: 10,
|
|
38
|
+
flexDirection: "row",
|
|
39
|
+
justifyContent: 'flex-start',
|
|
40
|
+
alignItems: 'center',
|
|
41
|
+
},
|
|
42
|
+
headerText: {
|
|
43
|
+
fontSize: theme.fontSizes.l,
|
|
44
|
+
...theme.fonts.light,
|
|
45
|
+
marginTop: 10,
|
|
46
|
+
marginBottom: 10
|
|
47
|
+
},
|
|
48
|
+
innerContainer: {
|
|
49
|
+
padding: 40,
|
|
50
|
+
marginTop: 200
|
|
51
|
+
},
|
|
52
|
+
title:{
|
|
53
|
+
fontSize: theme.fontSizes.xxl,
|
|
54
|
+
color: theme.colors.jobsTitleColor
|
|
55
|
+
},
|
|
56
|
+
titleNoJobs:{
|
|
57
|
+
fontSize: theme.fontSizes.xxl,
|
|
58
|
+
color: theme.colors.jobsTitleColor,
|
|
59
|
+
textAlign: 'center',
|
|
60
|
+
marginBottom: theme.paddings.default
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
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 {
|
|
17
|
+
ActivityIndicator,
|
|
18
|
+
FlatList,
|
|
19
|
+
SafeAreaView,
|
|
20
|
+
StyleSheet,
|
|
21
|
+
Text,
|
|
22
|
+
TouchableOpacity,
|
|
23
|
+
View
|
|
24
|
+
} from 'react-native';
|
|
25
|
+
import {connect} from 'react-redux'
|
|
26
|
+
import {withTheme} from "react-native-paper";
|
|
27
|
+
import {withTranslation} from "react-i18next";
|
|
28
|
+
|
|
29
|
+
import merge from 'lodash/merge';
|
|
30
|
+
|
|
31
|
+
import {onUpdateRefreshing} from "@olea-bps/core";
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
import componentStyles from "./styles";
|
|
35
|
+
import { AppBar as AppbarComponent } from '@olea-bps/components';
|
|
36
|
+
import IconsOpenasist from "@olea-bps/icons-openasist";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Feed List View
|
|
40
|
+
*
|
|
41
|
+
* Shows a list of news feeds
|
|
42
|
+
*
|
|
43
|
+
* Parameters:
|
|
44
|
+
* - none
|
|
45
|
+
*
|
|
46
|
+
* Navigation-Parameters:
|
|
47
|
+
* - none
|
|
48
|
+
*/
|
|
49
|
+
class FeedListView extends React.Component {
|
|
50
|
+
static navigationOptions = {
|
|
51
|
+
header: null
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Styles of this component
|
|
55
|
+
styles;
|
|
56
|
+
|
|
57
|
+
constructor(props) {
|
|
58
|
+
super(props);
|
|
59
|
+
|
|
60
|
+
// ------------------------------------------------------------------------
|
|
61
|
+
// PLUGIN FUNCTIONALITY
|
|
62
|
+
// ------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
const {pluginStyles,theme} = this.props;
|
|
65
|
+
this.styles = componentStyles(theme);
|
|
66
|
+
|
|
67
|
+
if (pluginStyles) {
|
|
68
|
+
this.styles = merge(this.styles, pluginStyles);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.styles = StyleSheet.create(this.styles);
|
|
72
|
+
|
|
73
|
+
// ------------------------------------------------------------------------
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Render a feed
|
|
78
|
+
*
|
|
79
|
+
* @returns {Array}
|
|
80
|
+
*
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
_renderFeed = ({item}) => {
|
|
84
|
+
const {themeStyles, colors} = this.props.theme;
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<TouchableOpacity style={themeStyles.card} onPress={() => {this.props.navigation.navigate('FeedNews', {feedId: item.feedid, feedTitle: item.title})}}>
|
|
88
|
+
<View style={[themeStyles.cardContent, this.styles.cardContent]}>
|
|
89
|
+
<Text style={[themeStyles.cardTitle, this.styles.cardTitle]}>{item.title}</Text>
|
|
90
|
+
<Text style={[themeStyles.cardSubTitle, this.styles.cardSubTitle]}>{item.desc}</Text>
|
|
91
|
+
</View>
|
|
92
|
+
<View style={themeStyles.cardRightIcon}>
|
|
93
|
+
<IconsOpenasist icon={"forward"} size={25} color={colors.messages.icon} />
|
|
94
|
+
</View>
|
|
95
|
+
</TouchableOpacity>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Key Extractor for unique item key
|
|
102
|
+
*
|
|
103
|
+
* @param item
|
|
104
|
+
* @param index
|
|
105
|
+
* @returns {*}
|
|
106
|
+
* @private
|
|
107
|
+
*/
|
|
108
|
+
_keyExtractor = (item, index) => {
|
|
109
|
+
return 'feed_' + ((item.feedid) ? item.feedid : ((item.title) ? item.title : index));
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
render() {
|
|
114
|
+
// ------------------------------------------------------------------------
|
|
115
|
+
// PLUGIN FUNCTIONALITY
|
|
116
|
+
// ------------------------------------------------------------------------
|
|
117
|
+
const PluginComponent = this.props.pluginComponent;
|
|
118
|
+
if (PluginComponent) {
|
|
119
|
+
return <PluginComponent/>;
|
|
120
|
+
}
|
|
121
|
+
// ------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
const {feeds, theme: {colors, paddings, themeStyles}, t} = this.props;
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<SafeAreaView style={[this.styles.container, themeStyles.appSafeAreaContainer]}>
|
|
127
|
+
<AppbarComponent {...this.props} title={t('news:feeds')}/>
|
|
128
|
+
<View style={themeStyles.container}>
|
|
129
|
+
{!feeds ?
|
|
130
|
+
<ActivityIndicator style={this.styles.activity} size="large" color={colors.loadingIndicator} /> :
|
|
131
|
+
<FlatList
|
|
132
|
+
data={feeds.sort((a,b) => {
|
|
133
|
+
if(a.feedid < b.feedid){
|
|
134
|
+
return -1;
|
|
135
|
+
} else if (a.feedid > b.feedid) {
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
return 0;
|
|
139
|
+
})}
|
|
140
|
+
extraData={this.state}
|
|
141
|
+
keyExtractor={this._keyExtractor}
|
|
142
|
+
renderItem={this._renderFeed}
|
|
143
|
+
initialNumToRender={6}
|
|
144
|
+
getItemLayout={(data, index) => (
|
|
145
|
+
{length: 50, offset: 50 * index, index}
|
|
146
|
+
)}
|
|
147
|
+
contentContainerStyle={{paddingBottom: paddings.default}}
|
|
148
|
+
style={this.styles.innerContainer}
|
|
149
|
+
/>}
|
|
150
|
+
</View>
|
|
151
|
+
</SafeAreaView>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const mapStateToProps = state => {
|
|
157
|
+
return {
|
|
158
|
+
pluginComponent: state.pluginReducer.feedList.component,
|
|
159
|
+
pluginStyles: state.pluginReducer.feedList.styles,
|
|
160
|
+
feeds: state.apiReducer.feeds
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
export default connect(mapStateToProps, {onUpdateRefreshing})(withTranslation()(withTheme(FeedListView)))
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
}
|
|
15
|
+
};
|