· 6 years ago · Oct 06, 2019, 01:06 AM
1import React, { memo, useState, useEffect } from "react";
2import { SafeAreaView, View, TouchableOpacity } from "react-native";
3import { Agenda } from "react-native-calendars";
4import styled from "@emotion/native";
5import { FontAwesome5 } from "@expo/vector-icons";
6import { Auth, API, graphqlOperation } from "aws-amplify";
7import eachDayOfInterval from "date-fns/eachDayOfInterval";
8import parse from "date-fns/parse";
9import startOfMonth from "date-fns/startOfMonth";
10import startOfDay from "date-fns/startOfDay";
11import endOfMonth from "date-fns/endOfMonth";
12import endOfDay from "date-fns/endOfDay";
13import subMonths from "date-fns/subMonths";
14import addMonths from "date-fns/addMonths";
15import getTime from "date-fns/getTime";
16import format from "date-fns/format";
17import LottieView from "lottie-react-native";
18
19import Colors from "../constants/Colors";
20import { listEvents } from "../graphql/queries";
21import { onCreateEvent } from "../graphql/subscriptions";
22
23const AddEventButton = styled.TouchableOpacity`
24 position: absolute;
25 bottom: 24px;
26 right: 24px;
27 width: 64px;
28 height: 64px;
29 border-radius: 32px;
30 background-color: ${Colors.foreground};
31 justify-content: center;
32 align-items: center;
33 box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
34`;
35
36const EventTitle = styled.Text`
37 color: ${Colors.text};
38 font-size: 16px;
39 font-family: "overpass-black";
40`;
41
42const EventDates = styled.Text`
43 color: ${Colors.text};
44 font-family: "overpass-bold";
45 font-size: 12px;
46`;
47
48const EventDatesContainer = styled.View`
49 flex-direction: row;
50`;
51
52const EmptyDate = memo(() => {
53 return (
54 <View
55 style={{
56 flex: 1,
57 flexDirection: "row",
58 alignItems: "center",
59 marginRight: 24
60 }}
61 >
62 <View
63 style={{
64 marginTop: 24,
65 flex: 1,
66 height: 1,
67 backgroundColor: "#e4eaed"
68 }}
69 />
70 </View>
71 );
72});
73
74const Item = memo(({ item }) => (
75 <View
76 style={{
77 flex: 1,
78 marginRight: 24,
79 marginTop: 32
80 }}
81 >
82 <TouchableOpacity>
83 <View
84 style={{
85 backgroundColor: Colors.foreground,
86 paddingHorizontal: 16,
87 paddingVertical: 8,
88 borderRadius: 4
89 }}
90 >
91 <EventTitle>{item.title}</EventTitle>
92 <EventDatesContainer>
93 <EventDates>{item.description}</EventDates>
94 </EventDatesContainer>
95 </View>
96 </TouchableOpacity>
97 </View>
98));
99
100export default function EventsScreen({ navigation }) {
101 const [user, setUser] = useState(null);
102 const [calendarDays, setCalendarDays] = useState({});
103 const [activeMonth, setActiveMonth] = useState(null);
104 const [selectedDate, setSelectedDate] = useState(null);
105
106 // activeMonth controlled by agenda component
107 // fetch events for time frame
108 // add to calendar days
109 // set calendar selected date
110 useEffect(() => {
111 const getDaysForMonth = async () => {
112 try {
113 const start = activeMonth
114 ? activeMonth
115 : startOfDay(subMonths(startOfMonth(new Date()), 1));
116 const end = activeMonth
117 ? endOfDay(endOfMonth(activeMonth))
118 : endOfDay(endOfMonth(addMonths(new Date(), 1)));
119
120 const result = await API.graphql(
121 graphqlOperation(listEvents, {
122 filter: {
123 timestamp: {
124 ge: `${getTime(start)}`
125 },
126 and: [
127 {
128 timestamp: {
129 le: `${getTime(end)}`
130 }
131 }
132 ]
133 }
134 })
135 );
136
137 const events = result.data.listEvents.items;
138
139 const month = eachDayOfInterval({
140 start,
141 end
142 });
143
144 const items = month.reduce((mem, day) => {
145 // get date string
146 const dateString = format(day, "yyyy-MM-dd");
147
148 // find event
149 const event = events.find(
150 event =>
151 dateString >= event.dates.start && dateString <= event.dates.end
152 );
153
154 // if no event, set day empty and move on
155 if (!event) {
156 mem[dateString] = [];
157 return mem;
158 }
159
160 // check to see if this is the first event after today
161 // if so set selectedDate
162 const today = format(new Date(), "yyyy-MM-dd");
163 if (!selectedDate && dateString > today) {
164 setSelectedDate(event.dates.start);
165 }
166
167 // get event days so we can mark agenda: 'Day (1/4)'
168 const eventStart = parse(event.dates.start, "yyyy-MM-dd", new Date());
169 const eventEnd = parse(event.dates.end, "yyyy-MM-dd", new Date());
170 const eventDays = eachDayOfInterval({
171 start: eventStart,
172 end: eventEnd
173 });
174
175 // map over event days and update the current day of month
176 // we can use the index and length to build the string
177 eventDays.map((eventDay, index, self) => {
178 const eventDayString = format(eventDay, "yyyy-MM-dd");
179 if (eventDayString === dateString) {
180 mem[dateString] = [
181 {
182 key: event.id,
183 title: event.title,
184 description: `Day (${index + 1}/${self.length})`,
185 event
186 }
187 ];
188 }
189 });
190
191 return mem;
192 }, {});
193
194 // update calendar days with month
195 setCalendarDays({
196 ...calendarDays,
197 ...items
198 });
199 } catch (error) {
200 console.log(error.message);
201 }
202 };
203
204 getDaysForMonth();
205 }, [activeMonth, setActiveMonth]);
206
207 useEffect(() => {
208 Auth.currentAuthenticatedUser()
209 .then(user => setUser(user))
210 .catch(error => console.log(error));
211 }, []);
212
213 useEffect(() => {
214 if (!user) return;
215
216 const subscription = API.graphql(
217 graphqlOperation(onCreateEvent, { owner: user.username })
218 ).subscribe({
219 next: event => {
220 setEvents([...events, event.value.data.onCreateEvent]);
221 }
222 });
223
224 return () => subscription.unsubscribe();
225 }, [user, setUser]);
226
227 return (
228 <SafeAreaView
229 style={{
230 backgroundColor: selectedDate ? Colors.foreground : Colors.background,
231 flex: 1
232 }}
233 >
234 <View style={{ flex: 1 }}>
235 {!selectedDate && (
236 <View
237 style={{
238 flex: 1,
239 justifyContent: "center",
240 alignItems: "center"
241 }}
242 >
243 <LottieView
244 autoPlay
245 loop
246 style={{ width: 250, height: 250 }}
247 source={require("../assets/lottie/lottie-events-loading.json")}
248 />
249 </View>
250 )}
251 {selectedDate && (
252 <Agenda
253 selected={selectedDate}
254 loadItemsForMonth={day => {
255 const start = startOfMonth(
256 parse(day.dateString, "yyyy-MM-dd", new Date())
257 );
258 setActiveMonth(start);
259 }}
260 items={calendarDays}
261 rowHasChanged={(r1, r2) => {
262 return r1.title !== r2.title;
263 }}
264 renderEmptyDate={() => <EmptyDate />}
265 renderItem={item => <Item item={item} />}
266 theme={{
267 backgroundColor: Colors.background,
268 selectedDayBackgroundColor: Colors.tintColor,
269 selectedDayTextColor: Colors.foreground,
270 todayTextColor: Colors.tintColor,
271 dayTextColor: Colors.text,
272 textDisabledColor: Colors.inactive,
273 dotColor: Colors.tintColor,
274 selectedDotColor: Colors.foreground,
275 monthTextColor: Colors.text,
276 indicatorColor: Colors.tintColor,
277 agendaDayTextColor: Colors.tintColor,
278 agendaDayNumColor: Colors.tintColor,
279 agendaTodayColor: Colors.tintColor,
280 agendaKnobColor: Colors.inactive
281 }}
282 />
283 )}
284 </View>
285 <AddEventButton onPress={() => navigation.navigate("CreateEvent")}>
286 <FontAwesome5 name="calendar-plus" color={Colors.tintColor} size={24} />
287 </AddEventButton>
288 </SafeAreaView>
289 );
290}
291
292EventsScreen.navigationOptions = {
293 header: null
294};