1import { StackScreenProps } from '@react-navigation/stack';
2import * as Calendar from 'expo-calendar';
3import React from 'react';
4import { Alert, Platform, ScrollView, StyleSheet, Text, View } from 'react-native';
5
6import Button from '../components/Button';
7import HeadingText from '../components/HeadingText';
8import ListButton from '../components/ListButton';
9import MonoText from '../components/MonoText';
10
11const EventRow: React.FunctionComponent<{
12  event: Calendar.Event;
13  getEvent: (event: Calendar.Event) => void;
14  getAttendees: (event: Calendar.Event) => void;
15  updateEvent: (event: Calendar.Event) => void;
16  deleteEvent: (event: Calendar.Event) => void;
17  openEventInCalendar: (event: Calendar.Event) => void;
18}> = ({ event, getEvent, getAttendees, updateEvent, deleteEvent, openEventInCalendar }) => (
19  <View style={styles.eventRow}>
20    <HeadingText>{event.title}</HeadingText>
21    <MonoText>{JSON.stringify(event, null, 2)}</MonoText>
22    <ListButton onPress={() => getEvent(event)} title="Get Event Using ID" />
23    <ListButton onPress={() => getAttendees(event)} title="Get Attendees for Event" />
24    <ListButton onPress={() => updateEvent(event)} title="Update Event" />
25    <ListButton onPress={() => deleteEvent(event)} title="Delete Event" />
26    {Platform.OS === 'android' && (
27      <ListButton onPress={() => openEventInCalendar(event)} title="Open in Calendar App" />
28    )}
29  </View>
30);
31
32interface State {
33  events: Calendar.Event[];
34}
35
36type Links = {
37  Events: { calendar: Calendar.Calendar };
38};
39
40type Props = StackScreenProps<Links, 'Events'>;
41
42export default class EventsScreen extends React.Component<Props, State> {
43  static navigationOptions = {
44    title: 'Events',
45  };
46
47  readonly state: State = {
48    events: [],
49  };
50
51  componentDidMount() {
52    const { params } = this.props.route;
53    if (params) {
54      const { id } = params.calendar;
55      if (id) {
56        this._findEvents(id);
57      }
58    }
59  }
60
61  _findEvents = async (id: string) => {
62    const yesterday = new Date();
63    yesterday.setDate(yesterday.getDate() - 1);
64    const nextYear = new Date();
65    nextYear.setFullYear(nextYear.getFullYear() + 1);
66    const events = await Calendar.getEventsAsync([id], yesterday, nextYear);
67    this.setState({ events });
68  };
69
70  _addEvent = async (recurring: boolean) => {
71    const { calendar } = this.props.route.params!;
72    if (!calendar.allowsModifications) {
73      Alert.alert('This calendar does not allow modifications');
74      return;
75    }
76    const timeInOneHour = new Date();
77    timeInOneHour.setHours(timeInOneHour.getHours() + 1);
78    const newEvent: {
79      title: string;
80      location: string;
81      startDate: Date;
82      endDate: Date;
83      notes: string;
84      timeZone: string;
85      recurrenceRule?: {
86        occurrence: number;
87        frequency: string;
88      };
89    } = {
90      title: 'Celebrate Expo',
91      location: '420 Florence St',
92      startDate: new Date(),
93      endDate: timeInOneHour,
94      notes: "It's cool",
95      timeZone: 'America/Los_Angeles',
96    };
97    if (recurring) {
98      newEvent.recurrenceRule = {
99        occurrence: 5,
100        frequency: 'daily',
101      };
102    }
103    try {
104      await Calendar.createEventAsync(calendar.id, newEvent);
105      Alert.alert('Event saved successfully');
106      this._findEvents(calendar.id);
107    } catch (e) {
108      Alert.alert('Event not saved successfully', e.message);
109    }
110  };
111
112  _getEvent = async (event: Calendar.Event) => {
113    try {
114      const newEvent = await Calendar.getEventAsync(event.id!, {
115        futureEvents: false,
116        instanceStartDate: event.startDate,
117      });
118      Alert.alert('Event found using getEventAsync', JSON.stringify(newEvent));
119    } catch (e) {
120      Alert.alert('Error finding event', e.message);
121    }
122  };
123
124  _getAttendees = async (event: Calendar.Event) => {
125    try {
126      const attendees = await Calendar.getAttendeesForEventAsync(event.id!, {
127        futureEvents: false,
128        instanceStartDate: event.startDate,
129      });
130      Alert.alert('Attendees found using getAttendeesForEventAsync', JSON.stringify(attendees));
131    } catch (e) {
132      Alert.alert('Error finding attendees', e.message);
133    }
134  };
135
136  _updateEvent = async (event: Calendar.Event) => {
137    const { calendar } = this.props.route.params!;
138    if (!calendar.allowsModifications) {
139      Alert.alert('This calendar does not allow modifications');
140      return;
141    }
142    const newEvent = {
143      title: 'update test',
144    };
145    try {
146      await Calendar.updateEventAsync(event.id!, newEvent, {
147        futureEvents: false,
148        instanceStartDate: event.startDate,
149      });
150      Alert.alert('Event saved successfully');
151      this._findEvents(calendar.id);
152    } catch (e) {
153      Alert.alert('Event not saved successfully', e.message);
154    }
155  };
156
157  _deleteEvent = async (event: Calendar.Event) => {
158    try {
159      const { calendar } = this.props.route.params!;
160      await Calendar.deleteEventAsync(event.id!, {
161        futureEvents: false,
162        instanceStartDate: event.recurrenceRule ? event.startDate : undefined,
163      });
164      Alert.alert('Event deleted successfully');
165      this._findEvents(calendar.id);
166    } catch (e) {
167      Alert.alert('Event not deleted successfully', e.message);
168    }
169  };
170
171  _openEventInCalendar = (event: Calendar.Event) => {
172    Calendar.openEventInCalendar(event.id!);
173  };
174
175  _renderActionButtons = () => {
176    return (
177      <View>
178        <Button
179          onPress={() => this._addEvent(false)}
180          style={{ marginBottom: 10 }}
181          title="Add New Event"
182        />
183        <Button onPress={() => this._addEvent(true)} title="Add New Recurring Event" />
184      </View>
185    );
186  };
187
188  render() {
189    const events = this.state.events.length ? (
190      <View>
191        {this.state.events.map((event) => (
192          <EventRow
193            event={event}
194            key={`${event.id}${event.startDate}`}
195            getEvent={this._getEvent}
196            getAttendees={this._getAttendees}
197            updateEvent={this._updateEvent}
198            deleteEvent={this._deleteEvent}
199            openEventInCalendar={this._openEventInCalendar}
200          />
201        ))}
202      </View>
203    ) : (
204      <Text style={{ marginVertical: 12 }}>This calendar has no events.</Text>
205    );
206    return (
207      <ScrollView style={styles.container}>
208        {this._renderActionButtons()}
209        {events}
210      </ScrollView>
211    );
212  }
213}
214
215const styles = StyleSheet.create({
216  container: {
217    paddingHorizontal: 10,
218    paddingVertical: 16,
219    flex: 1,
220  },
221  eventRow: {
222    marginBottom: 12,
223  },
224});
225