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