export async function addSchedule(data) { //#ifdef APP-PLUS const {platform} = uni.getSystemInfoSync() if (platform == 'android') { return androidSchedule(data) } else if (platform == 'ios') { return iosSchedule(data) } //#endif } async function androidSchedule (data) { const calendarURL = 'content://com.android.calendar/calendars' const calendarEventURL = 'content://com.android.calendar/events' const calendarRemiderURL = 'content://com.android.calendar/reminders' const CalendarContract = plus.android.import('android.provider.CalendarContract') const account = await createAccountData() const main = plus.android.runtimeMainActivity() const timeZone = plus.android.invoke('java.util.TimeZone', 'getDefault') const timeZoneId = plus.android.invoke(timeZone, 'getID') await checkPermission() let accountId = getAccount() if (!accountId) { accountId = addAccount() } if (!accountId) return const calendarId = insertCalendar({ calendar_id: accountId, title: data.title, description: data.description, dtstart: data.dtstart, dtend: data.dtend, }) if (!calendarId) return insertReminder({event_id: calendarId}) // insertAlarm(data.dtstart) async function checkPermission() { return new Promise((resolve) => { plus.android.requestPermissions(['android.permission.READ_CALENDAR', 'android.permission.WRITE_CALENDAR'], ({deniedAlways, deniedPresent, granted}) => { if (deniedAlways.length > 0) { //权限被永久拒绝 // 弹出提示框解释为何需要权限,引导用户打开设置页面开启 uni.showModal({ title: '提示', content: '请打开日历权限', success: ({confirm}) => { if (!confirm) return const intent = plus.android.newObject("android.content.Intent") const Settings = plus.android.newObject("android.provider.Settings") const action = plus.android.getAttribute(Settings, 'ACTION_APPLICATION_DETAILS_SETTINGS') plus.android.invoke(intent, 'setAction', action) const uri = plus.android.invoke('android.net.Uri', 'fromParts', 'package', main.getPackageName(), null) plus.android.invoke(intent, 'setData', uri) main.startActivity(intent) } }) } else if (deniedPresent.length > 0) { //权限被临时拒绝 uni.showToast({title: '您拒绝了日历写入权限', icon: 'none'}) } else if (granted.length > 0) { //权限被允许 resolve() } }, err => { uni.showToast({title: `申请权限错误:${err.code} ${err.message}`, icon: 'none'}) }) }) } function createAccountData() { return new Promise((resolve) => { plus.runtime.getProperty(plus.runtime.appid, ({appid, name}) => { resolve({ name, account_name: appid, account_type: CalendarContract.ACCOUNT_TYPE_LOCAL, display_name: name, }) }) }) } function getAccount() { const uri = plus.android.invoke('android.net.Uri', 'parse', calendarURL) const userCursor = plus.android.invoke(main.getContentResolver(), "query", uri, null, null, null, null) while (plus.android.invoke(userCursor, 'moveToNext')) { const _account_name = plus.android.invoke(userCursor, 'getString', plus.android.invoke(userCursor, 'getColumnIndex', 'account_name')) const _account_type = plus.android.invoke(userCursor, 'getString', plus.android.invoke(userCursor, 'getColumnIndex', 'account_type')) const _id = plus.android.invoke(userCursor, 'getString', plus.android.invoke(userCursor, 'getColumnIndex', '_id')) if (_account_name == account.account_name && _account_type == account.account_type) { return _id } } } function addAccount() { const value = plus.android.newObject('android.content.ContentValues') plus.android.invoke(value, 'put', 'name', account.name) plus.android.invoke(value, 'put', 'account_name', account.account_name) plus.android.invoke(value, 'put', 'account_type', account.account_type) plus.android.invoke(value, 'put', 'calendar_displayName', account.display_name) plus.android.invoke(value, 'put', 'visible', 1) plus.android.invoke(value, 'put', 'sync_events', 1) plus.android.invoke(value, 'put', 'calendar_timezone', timeZoneId) plus.android.invoke(value, 'put', 'ownerAccount', account.account_name) // plus.android.invoke(value, 'put', 'canOrganizerRespond', 0) // plus.android.invoke(value, 'put', 'calendar_color', -9206951) // plus.android.invoke(value, 'put', 'calendar_access_level', 700) const calendarUri = plus.android.invoke('android.net.Uri', 'parse', calendarURL) const buildUpon = plus.android.invoke(calendarUri, "buildUpon") plus.android.invoke(buildUpon, "appendQueryParameter", CalendarContract.CALLER_IS_SYNCADAPTER, "true") plus.android.invoke(buildUpon, "appendQueryParameter", "account_name", account.account_name) plus.android.invoke(buildUpon, "appendQueryParameter", "account_type", account.account_type) const build = plus.android.invoke(buildUpon, "build") const newAccoutList = plus.android.invoke(main.getContentResolver(), "insert", build, value) const newAccout_id = plus.android.invoke(plus.android.invoke(newAccoutList, "getPathSegments"), "get", 1) return newAccout_id } function insertCalendar({calendar_id, title, description, dtstart, dtend}) { const value = plus.android.newObject('android.content.ContentValues') const systemInfo = uni.getSystemInfoSync() plus.android.invoke(value, 'put', 'calendar_id', (systemInfo.brand || '').toLowerCase() == 'xiaomi' ? 1 : calendar_id) plus.android.invoke(value, 'put', 'title', title) plus.android.invoke(value, 'put', 'description', description) plus.android.invoke(value, 'put', 'dtstart', dtstart) plus.android.invoke(value, 'put', 'dtend', dtend) plus.android.invoke(value, 'put', 'eventTimezone', timeZoneId) // plus.android.invoke(value, 'put', 'allDay', 1) // plus.android.invoke(value, 'put', 'rrule', 'FREQ=MONTHLY;BYMONTHDAY=24') //rrule参考 https://www.cnblogs.com/ice5/p/14023771.html plus.android.invoke(value, 'put', 'hasAlarm', 1) //是否闹钟提醒 默认提醒 因为大部分手机未实现此功能 故未实现 const calendarEventUri = plus.android.invoke('android.net.Uri', 'parse', calendarEventURL) const newEvent = plus.android.invoke(main.getContentResolver(), 'insert', calendarEventUri, value) const id = plus.android.invoke(newEvent, 'getLastPathSegment') return id } function insertReminder({event_id}) { const value = plus.android.newObject('android.content.ContentValues') plus.android.invoke(value, 'put', 'event_id', event_id) plus.android.invoke(value, 'put', 'minutes', 5) plus.android.invoke(value, 'put', 'method', 1) const calendarRemiderUri = plus.android.invoke('android.net.Uri', 'parse', calendarRemiderURL) const res = plus.android.invoke(main.getContentResolver(), 'insert', calendarRemiderUri, value) return res } function insertAlarm(dtstart) { const AlarmManager = plus.android.importClass("android.app.AlarmManager") const Context = plus.android.importClass("android.content.Context") const alarm = main.getSystemService(Context.ALARM_SERVICE) const intent = plus.android.newObject("android.content.Intent") const PendingIntent = plus.android.importClass("android.app.PendingIntent") const pendingIntent = PendingIntent.getActivity(main, 0, intent, 0) alarm.set(AlarmManager.RTC_WAKEUP, dtstart, pendingIntent) } } function iosSchedule(data) { return new Promise((resolve) => { const eventStore = plus.ios.newObject('EKEventStore') // const hasMethod = plus.ios.invoke(eventStore, 'respondsToSelector:', plus.ios.newObject('@selector', 'requestAccessToEntityType:completion:')) plus.ios.invoke(eventStore, 'requestAccessToEntityType:completion:', 0, () => { const calendar = plus.ios.invoke(eventStore, 'defaultCalendarForNewEvents') if (!calendar) { // uni.showModal({ // title: '提示', // content: '请打开日历权限', // success: ({confirm}) => { // if (!confirm) return // plus.runtime.openURL('app-settings://') // } // }) return } const event = plus.ios.invoke('EKEvent', 'eventWithEventStore:', eventStore) // const date = plus.ios.newObject('NSDate') event.plusSetAttribute('title', data.title) // event.plusSetAttribute('location', 'location') event.plusSetAttribute('notes', data.description) const startDate = plus.ios.invoke('NSDate', 'dateWithTimeIntervalSince1970:', data.dtstart /1000) event.plusSetAttribute('startDate', startDate) const endDate = plus.ios.invoke('NSDate', 'dateWithTimeIntervalSince1970:', data.dtend /1000) event.plusSetAttribute('endDate', endDate) const alarm = plus.ios.invoke('EKAlarm', 'alarmWithRelativeOffset:', -5) plus.ios.invoke(event, 'addAlarm:', alarm) plus.ios.invoke(event, 'setCalendar:', calendar) const res = plus.ios.invoke(eventStore, 'saveEvent:span:error:', event, 0, null) if (res) { return resolve(res) } }) }) }