1import { StackNavigationProp } from '@react-navigation/stack';
2import * as Calendar from 'expo-calendar';
3import React from 'react';
4import { Alert, Platform, ScrollView, StyleSheet, 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';
10import Colors from '../constants/Colors';
11
12type StackNavigation = StackNavigationProp<{
13  Reminders: { calendar: any };
14  Events: { calendar: any };
15}>;
16
17const CalendarRow = (props: {
18  navigation: StackNavigation;
19  calendar: Calendar.Calendar;
20  updateCalendar: (calendarId: string) => void;
21  deleteCalendar: (calendar: any) => void;
22}) => {
23  const { calendar } = props;
24  const calendarTypeName =
25    calendar.entityType === Calendar.EntityTypes.REMINDER ? 'Reminders' : 'Events';
26  return (
27    <View style={styles.calendarRow}>
28      <HeadingText>{calendar.title}</HeadingText>
29      <MonoText>{JSON.stringify(calendar, null, 2)}</MonoText>
30      <ListButton
31        onPress={() => props.navigation.navigate(calendarTypeName, { calendar })}
32        title={`View ${calendarTypeName}`}
33      />
34      <ListButton
35        onPress={() => props.updateCalendar(calendar.id)}
36        title="Update Calendar"
37        disabled={!calendar.allowsModifications}
38      />
39      <ListButton
40        onPress={() => props.deleteCalendar(calendar)}
41        title="Delete Calendar"
42        disabled={!calendar.allowsModifications}
43      />
44    </View>
45  );
46};
47CalendarRow.navigationOptions = {
48  title: 'Calendars',
49};
50
51interface State {
52  haveCalendarPermissions: boolean;
53  haveReminderPermissions: boolean;
54  calendars: Calendar.Calendar[];
55  activeCalendarId?: string;
56  activeCalendarEvents: Calendar.Event[];
57  showAddNewEventForm: boolean;
58  editingEvent?: Calendar.Event;
59}
60
61export default class CalendarsScreen extends React.Component<
62  { navigation: StackNavigation },
63  State
64> {
65  static navigationOptions = {
66    title: 'Calendars',
67  };
68
69  readonly state: State = {
70    haveCalendarPermissions: false,
71    haveReminderPermissions: false,
72    calendars: [],
73    activeCalendarEvents: [],
74    showAddNewEventForm: false,
75  };
76
77  _askForCalendarPermissions = async () => {
78    const response = await Calendar.requestCalendarPermissionsAsync();
79    const granted = response.status === 'granted';
80    this.setState({
81      haveCalendarPermissions: granted,
82    });
83    return granted;
84  };
85
86  _askForReminderPermissions = async () => {
87    if (Platform.OS === 'android') return true;
88    const response = await Calendar.requestRemindersPermissionsAsync();
89    const granted = response.status === 'granted';
90    this.setState({
91      haveReminderPermissions: granted,
92    });
93    return granted;
94  };
95
96  _findCalendars = async () => {
97    const calendarGranted = await this._askForCalendarPermissions();
98    const reminderGranted = await this._askForReminderPermissions();
99    if (calendarGranted && reminderGranted) {
100      const eventCalendars = (await Calendar.getCalendarsAsync('event')) as unknown as any[];
101      const reminderCalendars = (
102        Platform.OS === 'ios' ? await Calendar.getCalendarsAsync('reminder') : []
103      ) as any[];
104      this.setState({ calendars: [...eventCalendars, ...reminderCalendars] });
105    }
106  };
107
108  _addCalendar = async () => {
109    const sourceDetails = Platform.select({
110      default: () => ({}),
111      ios: () => ({
112        sourceId: this.state.calendars.find((cal) => cal.source && cal.source.name === 'Default')
113          ?.source.id,
114      }),
115      android: () => {
116        const calendar = this.state.calendars.find(
117          (cal) => cal.accessLevel === Calendar.CalendarAccessLevel.OWNER
118        );
119        return calendar ? { source: calendar.source, ownerAccount: calendar.ownerAccount } : {};
120      },
121    })();
122    const newCalendar = {
123      title: 'cool new calendar',
124      entityType: Calendar.EntityTypes.EVENT,
125      color: '#c0ff33',
126      ...sourceDetails,
127      name: 'coolNewCalendar',
128      accessLevel: Calendar.CalendarAccessLevel.OWNER,
129    };
130    try {
131      await Calendar.createCalendarAsync(newCalendar);
132      Alert.alert('Calendar saved successfully');
133      this._findCalendars();
134    } catch (e) {
135      Alert.alert('Calendar not saved successfully', e.message);
136    }
137  };
138
139  _updateCalendar = async (calendarId: string) => {
140    const newCalendar = {
141      title: 'cool updated calendar',
142    };
143    try {
144      await Calendar.updateCalendarAsync(calendarId, newCalendar);
145      Alert.alert('Calendar saved successfully');
146      this._findCalendars();
147    } catch (e) {
148      Alert.alert('Calendar not saved successfully', e.message);
149    }
150  };
151
152  _deleteCalendar = async (calendar: any) => {
153    Alert.alert(`Are you sure you want to delete ${calendar.title}?`, 'This cannot be undone.', [
154      {
155        text: 'Cancel',
156        onPress: () => {},
157      },
158      {
159        text: 'OK',
160        onPress: async () => {
161          try {
162            await Calendar.deleteCalendarAsync(calendar.id);
163            Alert.alert('Calendar deleted successfully');
164            this._findCalendars();
165          } catch (e) {
166            Alert.alert('Calendar not deleted successfully', e.message);
167          }
168        },
169      },
170    ]);
171  };
172
173  render() {
174    if (this.state.calendars.length) {
175      return (
176        <ScrollView style={styles.container}>
177          <Button onPress={this._addCalendar} title="Add New Calendar" />
178          {this.state.calendars.map((calendar) => (
179            <CalendarRow
180              calendar={calendar}
181              key={calendar.id}
182              navigation={this.props.navigation}
183              updateCalendar={this._updateCalendar}
184              deleteCalendar={this._deleteCalendar}
185            />
186          ))}
187        </ScrollView>
188      );
189    }
190
191    return (
192      <View style={styles.container}>
193        <Button onPress={this._findCalendars} title="Find my Calendars" />
194      </View>
195    );
196  }
197}
198
199const styles = StyleSheet.create({
200  container: {
201    backgroundColor: Colors.greyBackground,
202    paddingHorizontal: 10,
203    paddingVertical: 16,
204    flex: 1,
205  },
206  calendarRow: {
207    marginBottom: 12,
208  },
209});
210