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