Last active
September 17, 2019 20:11
-
-
Save kkemple/8121af53f990aa36041c628f908157df to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { memo, useState, useEffect } from "react"; | |
import { SafeAreaView, View, TouchableOpacity } from "react-native"; | |
import { Agenda } from "react-native-calendars"; | |
import styled from "@emotion/native"; | |
import { FontAwesome5 } from "@expo/vector-icons"; | |
import { Auth, API, graphqlOperation } from "aws-amplify"; | |
import eachDayOfInterval from "date-fns/eachDayOfInterval"; | |
import parse from "date-fns/parse"; | |
import startOfMonth from "date-fns/startOfMonth"; | |
import startOfDay from "date-fns/startOfDay"; | |
import endOfMonth from "date-fns/endOfMonth"; | |
import endOfDay from "date-fns/endOfDay"; | |
import subMonths from "date-fns/subMonths"; | |
import addMonths from "date-fns/addMonths"; | |
import getTime from "date-fns/getTime"; | |
import format from "date-fns/format"; | |
import LottieView from "lottie-react-native"; | |
import Colors from "../constants/Colors"; | |
import { listEvents } from "../graphql/queries"; | |
import { onCreateEvent } from "../graphql/subscriptions"; | |
const AddEventButton = styled.TouchableOpacity` | |
position: absolute; | |
bottom: 24px; | |
right: 24px; | |
width: 64px; | |
height: 64px; | |
border-radius: 32px; | |
background-color: ${Colors.foreground}; | |
justify-content: center; | |
align-items: center; | |
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2); | |
`; | |
const EventTitle = styled.Text` | |
color: ${Colors.text}; | |
font-size: 16px; | |
font-family: "overpass-black"; | |
`; | |
const EventDates = styled.Text` | |
color: ${Colors.text}; | |
font-family: "overpass-bold"; | |
font-size: 12px; | |
`; | |
const EventDatesContainer = styled.View` | |
flex-direction: row; | |
`; | |
const EmptyDate = memo(() => { | |
return ( | |
<View | |
style={{ | |
flex: 1, | |
flexDirection: "row", | |
alignItems: "center", | |
marginRight: 24 | |
}} | |
> | |
<View | |
style={{ | |
marginTop: 24, | |
flex: 1, | |
height: 1, | |
backgroundColor: "#e4eaed" | |
}} | |
/> | |
</View> | |
); | |
}); | |
const Item = memo(({ item }) => ( | |
<View | |
style={{ | |
flex: 1, | |
marginRight: 24, | |
marginTop: 32 | |
}} | |
> | |
<TouchableOpacity> | |
<View | |
style={{ | |
backgroundColor: Colors.foreground, | |
paddingHorizontal: 16, | |
paddingVertical: 8, | |
borderRadius: 4 | |
}} | |
> | |
<EventTitle>{item.title}</EventTitle> | |
<EventDatesContainer> | |
<EventDates>{item.description}</EventDates> | |
</EventDatesContainer> | |
</View> | |
</TouchableOpacity> | |
</View> | |
)); | |
export default function EventsScreen({ navigation }) { | |
const [user, setUser] = useState(null); | |
const [calendarDays, setCalendarDays] = useState({}); | |
const [activeMonth, setActiveMonth] = useState(null); | |
const [selectedDate, setSelectedDate] = useState(null); | |
// activeMonth controlled by agenda component | |
// fetch events for time frame | |
// add to calendar days | |
// set calendar selected date | |
useEffect(() => { | |
const getDaysForMonth = async () => { | |
try { | |
const start = activeMonth | |
? activeMonth | |
: startOfDay(subMonths(startOfMonth(new Date()), 1)); | |
const end = activeMonth | |
? endOfDay(endOfMonth(activeMonth)) | |
: endOfDay(endOfMonth(addMonths(new Date(), 1))); | |
const result = await API.graphql( | |
graphqlOperation(listEvents, { | |
filter: { | |
timestamp: { | |
ge: `${getTime(start)}` | |
}, | |
and: [ | |
{ | |
timestamp: { | |
le: `${getTime(end)}` | |
} | |
} | |
] | |
} | |
}) | |
); | |
const events = result.data.listEvents.items; | |
const month = eachDayOfInterval({ | |
start, | |
end | |
}); | |
const items = month.reduce((mem, day) => { | |
// get date string | |
const dateString = format(day, "yyyy-MM-dd"); | |
// find event | |
const event = events.find( | |
event => | |
dateString >= event.dates.start && dateString <= event.dates.end | |
); | |
// if no event, set day empty and move on | |
if (!event) { | |
mem[dateString] = []; | |
return mem; | |
} | |
// check to see if this is the first event after today | |
// if so set selectedDate | |
const today = format(new Date(), "yyyy-MM-dd"); | |
if (!selectedDate && dateString > today) { | |
setSelectedDate(event.dates.start); | |
} | |
// get event days so we can mark agenda: 'Day (1/4)' | |
const eventStart = parse(event.dates.start, "yyyy-MM-dd", new Date()); | |
const eventEnd = parse(event.dates.end, "yyyy-MM-dd", new Date()); | |
const eventDays = eachDayOfInterval({ | |
start: eventStart, | |
end: eventEnd | |
}); | |
// map over event days and update the current day of month | |
// we can use the index and length to build the string | |
eventDays.map((eventDay, index, self) => { | |
const eventDayString = format(eventDay, "yyyy-MM-dd"); | |
if (eventDayString === dateString) { | |
mem[dateString] = [ | |
{ | |
key: event.id, | |
title: event.title, | |
description: `Day (${index + 1}/${self.length})`, | |
event | |
} | |
]; | |
} | |
}); | |
return mem; | |
}, {}); | |
// update calendar days with month | |
setCalendarDays({ | |
...calendarDays, | |
...items | |
}); | |
} catch (error) { | |
console.log(error.message); | |
} | |
}; | |
getDaysForMonth(); | |
}, [activeMonth, setActiveMonth]); | |
useEffect(() => { | |
Auth.currentAuthenticatedUser() | |
.then(user => setUser(user)) | |
.catch(error => console.log(error)); | |
}, []); | |
useEffect(() => { | |
if (!user) return; | |
const subscription = API.graphql( | |
graphqlOperation(onCreateEvent, { owner: user.username }) | |
).subscribe({ | |
next: event => { | |
setEvents([...events, event.value.data.onCreateEvent]); | |
} | |
}); | |
return () => subscription.unsubscribe(); | |
}, [user, setUser]); | |
return ( | |
<SafeAreaView | |
style={{ | |
backgroundColor: selectedDate ? Colors.foreground : Colors.background, | |
flex: 1 | |
}} | |
> | |
<View style={{ flex: 1 }}> | |
{!selectedDate && ( | |
<View | |
style={{ | |
flex: 1, | |
justifyContent: "center", | |
alignItems: "center" | |
}} | |
> | |
<LottieView | |
autoPlay | |
loop | |
style={{ width: 250, height: 250 }} | |
source={require("../assets/lottie/lottie-events-loading.json")} | |
/> | |
</View> | |
)} | |
{selectedDate && ( | |
<Agenda | |
selected={selectedDate} | |
loadItemsForMonth={day => { | |
const start = startOfMonth( | |
parse(day.dateString, "yyyy-MM-dd", new Date()) | |
); | |
setActiveMonth(start); | |
}} | |
items={calendarDays} | |
rowHasChanged={(r1, r2) => { | |
return r1.title !== r2.title; | |
}} | |
renderEmptyDate={() => <EmptyDate />} | |
renderItem={item => <Item item={item} />} | |
theme={{ | |
backgroundColor: Colors.background, | |
selectedDayBackgroundColor: Colors.tintColor, | |
selectedDayTextColor: Colors.foreground, | |
todayTextColor: Colors.tintColor, | |
dayTextColor: Colors.text, | |
textDisabledColor: Colors.inactive, | |
dotColor: Colors.tintColor, | |
selectedDotColor: Colors.foreground, | |
monthTextColor: Colors.text, | |
indicatorColor: Colors.tintColor, | |
agendaDayTextColor: Colors.tintColor, | |
agendaDayNumColor: Colors.tintColor, | |
agendaTodayColor: Colors.tintColor, | |
agendaKnobColor: Colors.inactive | |
}} | |
/> | |
)} | |
</View> | |
<AddEventButton onPress={() => navigation.navigate("CreateEvent")}> | |
<FontAwesome5 name="calendar-plus" color={Colors.tintColor} size={24} /> | |
</AddEventButton> | |
</SafeAreaView> | |
); | |
} | |
EventsScreen.navigationOptions = { | |
header: null | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment