Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TIMOB-26312] (7_4_X):iOS 12 Expose new NSUserActivity APIs for Siri Intents #10292

Merged
merged 6 commits into from
Aug 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
129 changes: 125 additions & 4 deletions apidoc/Titanium/App/iOS/UserActivity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ description: |
* Handoff broadcasts activities via Bluetooth LE signals, so both the broadcasting and receiving devices must have Bluetooth LE 4.0 support.
* Connect all devices to the same Wi-Fi network.

Make sure you have two Handoff compatible devices running iOS 8 or later that are logged onto the same iCloud account.
Make sure you have two devices that are logged onto the same iCloud account.

Since iOS 12, you can also configure the UserActivity API for handling Siri Shortcuts. See the
below API's and example or refer to the [Apple Siri Shortcuts Docs](https://developer.apple.com/documentation/sirikit/donating_shortcuts?language=objc)
for details.
extends: Titanium.Proxy
platforms: [iphone,ipad]
since: "5.0.0"
Expand Down Expand Up @@ -69,6 +73,29 @@ properties:
default: true
availability: creation

- name: eligibleForPrediction
summary: |
A Boolean value that determines whether Siri can suggest the user activity as a shortcut to the user.
description: |
To donate a user activity to Siri Shortcuts, set eligibleForPrediction to `true` and make the
user activity current. To make the user activity current, call the becomeCurrent method of activity.
For more information, see https://developer.apple.com/documentation/sirikit/donating_shortcuts?language=objc.
type: Boolean
osver: { ios: { min: "12.0" } }
default: false
since: "7.4.0"
availability: creation

- name: persistentIdentifier
summary: A value used to identify the user activity.
description: |
Set this property to a unique value that identifies the user activity so you can later delete it with
Ti.App.iOS.UserActivity.deleteSavedUserActivitiesForPersistentIdentifiers method.
type: String
osver: { ios: { min: "12.0" } }
since: "7.4.0"
availability: creation

- name: expirationDate
summary: Absolute date after which the activity is no longer eligible to be indexed or handed off.
description: |
Expand Down Expand Up @@ -159,6 +186,32 @@ methods:
summary: Returns `true` if the device supports user activity.
platforms: [iphone, ipad]
osver: {ios: {min: "8.0"}}

- name: deleteSavedUserActivitiesForPersistentIdentifiers
summary: |
Deletes user activities created by your app that have the specified persistent identifiers.
description: |
The <Titanium.App.iOS.UserActivity.useractivitydeleted> event is fired after deteting the
user activities. Listen and wait for this event to fired to ensure that the system deletes
the activities (or marks them for deletion).
parameters:
- name: persistentIdentifiers
summary: Array of persistent identifiers of user activity.
type: Array<String>
platforms: [iphone, ipad]
osver: { ios: { min: "12.0" } }
since: "7.4.0"

- name: deleteAllSavedUserActivities
summary: Deletes all user activities created by your app.
description: |
The <Titanium.App.iOS.UserActivity.useractivitydeleted> event is fired after deteting the
user activities. Listen and wait for this event to fired to ensure that the system deletes
the activities (or marks them for deletion).
platforms: [iphone, ipad]
osver: { ios: { min: "12.0" } }
since: "7.4.0"

events:
- name: useractivitywillsave
summary: |
Expand Down Expand Up @@ -216,8 +269,17 @@ events:
summary: Dictionary object containing the userInfo data of the User Activity.
type: Dictionary
platforms: [iphone, ipad]

- name: useractivitydeleted
summary: |
Fired when the user activity get deleted using the <Titanium.App.iOS.UserActivity.deleteAllSavedUserActivities> or
<Titanium.App.iOS.UserActivity.deleteSavedUserActivitiesForPersistentIdentifiers> methods.
platforms: [iphone, ipad]
osver: { ios: { min: "12.0" } }
since: "7.4.0"

examples:
- title: Creating a new UserActivity Example
- title: Creating a new User Activity
example: |
The following example demonstrates how to create a new UserActivity and mark the activity as
the current activity Handoff should be using when switching between devices.
Expand All @@ -236,12 +298,12 @@ examples:
}
});

if(!activity.isSupported()){
if (!activity.isSupported()) {
alert('User Activities are not supported on this device!');
} else {
activity.becomeCurrent();

Ti.App.iOS.addEventListener('continueactivity', function(e) {
Ti.App.iOS.addEventListener('continueactivity', function (e) {
if (e.activityType === 'com.setdirection.home' && e.userInfo.msg) {
alert(e.userInfo.msg);
}
Expand All @@ -263,3 +325,62 @@ examples:
</ios>
</ti:app>

- title: iOS 12+ Siri Shortcuts
example: |
The following example shows how to add and delete a UserActivity for Siri Shortcuts
on iOS 12 and later.

#### app.js

var win = Ti.UI.createWindow({
backgroundColor: '#fff'
});

var btn = Ti.UI.createButton({
top: 200,
title: 'Delete UserActivity'
});

var itemAttr = Ti.App.iOS.createSearchableItemAttributeSet({
itemContentType: Ti.App.iOS.UTTYPE_IMAGE,
title: 'Titanium Siri Shortcut Tutorial',
contentDescription: 'Tech Example \nOn: ' + (new Date().toLocaleString()),
});

var activity = Ti.App.iOS.createUserActivity({
activityType: 'com.appcelerator.titanium',
title: 'Siri shortcut activity',
userInfo: {
msg: 'hello world'
},
eligibleForSearch: true,
eligibleForPrediction: true,
persistentIdentifier: 'titanium_siri_identifier'
});

activity.addContentAttributeSet(itemAttr);

if (!activity.isSupported()) {
alert('User Activities are not supported on this device!');
} else {
activity.becomeCurrent();

Ti.App.iOS.addEventListener('continueactivity', function (e) {
Ti.API.info('continueactivity called');
if (e.activityType === 'com.appcelerator.titanium' && e.userInfo.msg) {
alert(e.userInfo.msg);
}
});
}

activity.addEventListener('useractivitydeleted', function (e) {
Ti.API.info('useractivitydeleted called');
alert('user activity deleted');
});

btn.addEventListener('click', function () {
activity.deleteSavedUserActivitiesForPersistentIdentifiers(['titanium_siri_identifier']);
});

win.add(btn);
win.open();
70 changes: 69 additions & 1 deletion iphone/Classes/TiAppiOSUserActivityProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,74 @@
- (id)initWithOptions:(NSDictionary *)props;
@property (nonatomic, strong) NSUserActivity *userActivity;

- (id)isSupported:(id)unused;

- (NSString *)activityType;

- (void)setActivityType:(id)value;

- (NSString *)title;

- (void)setTitle:(id)value;

- (NSDictionary *)userInfo;

- (void)setUserInfo:(id)info;

- (NSString *)webpageURL;

- (void)setWebpageURL:(id)value;

- (NSNumber *)needsSave;

- (void)setNeedsSave:(id)value;

- (void)becomeCurrent:(id)unused;

- (void)invalidate:(id)unused;

- (void)addContentAttributeSet:(id)contentAttributeSet;

- (NSNumber *)eligibleForPublicIndexing;

- (void)setEligibleForPublicIndexing:(id)value;

- (NSNumber *)eligibleForSearch;

- (void)setEligibleForSearch:(id)value;

- (NSNumber *)eligibleForHandoff;

- (void)setEligibleForHandoff:(id)value;

- (NSString *)expirationDate;

- (void)setExpirationDate:(id)UTCDateFormat;

- (NSArray *)requiredUserInfoKeys;

- (void)setRequiredUserInfoKeys:(id)keys;

- (NSArray *)keywords;

- (void)setKeywords:(id)keys;

- (void)resignCurrent:(id)unused;

#if IS_XCODE_10
- (NSString *)persistentIdentifier;

- (void)setPersistentIdentifier:(NSString *)value;

- (NSNumber *)eligibleForPrediction;

- (void)setEligibleForPrediction:(NSNumber *)value;

- (void)deleteSavedUserActivitiesForPersistentIdentifiers:(id)persistentIdentifiers;

- (void)deleteAllSavedUserActivities:(id)unused;
#endif

@end

#endif
#endif
84 changes: 84 additions & 0 deletions iphone/Classes/TiAppiOSUserActivityProxy.m
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ - (void)buildInitialActivity:(NSDictionary *)props
}
}

#if IS_XCODE_10
if ([TiUtils isIOSVersionOrGreater:@"12.0"]) {
if ([props objectForKey:@"eligibleForPrediction"]) {
[_userActivity setEligibleForPrediction:[TiUtils boolValue:@"eligibleForPrediction" properties:props]];
}

if ([props objectForKey:@"persistentIdentifier"]) {
[_userActivity setPersistentIdentifier:[TiUtils stringValue:@"persistentIdentifier"
properties:props]];
}
}
#endif

_userActivity.delegate = self;
}

Expand Down Expand Up @@ -425,6 +438,77 @@ - (void)resignCurrent:(id)unused
}
[_userActivity resignCurrent];
}

#if IS_XCODE_10
- (NSNumber *)eligibleForPrediction
{
if ([TiUtils isIOSVersionLower:@"12.0"]) {
return NUMBOOL(NO);
}

return @(_userActivity.isEligibleForPrediction);
}

- (void)setEligibleForPrediction:(NSNumber *)value
{
ENSURE_UI_THREAD(setEligibleForSearch, value);
ENSURE_TYPE(value, NSNumber);
if ([TiUtils isIOSVersionLower:@"12.0"]) {
return;
}
[_userActivity setEligibleForPrediction:[TiUtils boolValue:value]];
}

- (NSString *)persistentIdentifier
{
if ([TiUtils isIOSVersionLower:@"12.0"]) {
return nil;
}

return _userActivity.persistentIdentifier;
}

- (void)setPersistentIdentifier:(NSString *)value
{
ENSURE_TYPE(value, NSString);
if ([TiUtils isIOSVersionLower:@"12.0"]) {
return;
}
[_userActivity setPersistentIdentifier:[TiUtils stringValue:value]];
}

- (void)deleteSavedUserActivitiesForPersistentIdentifiers:(id)persistentIdentifiers
{
ENSURE_SINGLE_ARG(persistentIdentifiers, NSArray);

for (id object in persistentIdentifiers) {
ENSURE_TYPE(object, NSString);
}

if ([TiUtils isIOSVersionLower:@"12.0"]) {
return;
}
[NSUserActivity deleteSavedUserActivitiesWithPersistentIdentifiers:persistentIdentifiers
completionHandler:^{
if ([self _hasListeners:@"useractivitydeleted"]) {
[self fireEvent:@"useractivitydeleted" withObject:nil];
}
}];
}

- (void)deleteAllSavedUserActivities:(id)unused
{
if ([TiUtils isIOSVersionLower:@"12.0"]) {
return;
}
[NSUserActivity deleteAllSavedUserActivitiesWithCompletionHandler:^{
if ([self _hasListeners:@"useractivitydeleted"]) {
[self fireEvent:@"useractivitydeleted" withObject:nil];
}
}];
}
#endif

@end

#endif
48 changes: 48 additions & 0 deletions tests/Resources/ti.app.ios.useractivity.addontest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Appcelerator Titanium Mobile
* Copyright (c) 2017-Present by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
/* eslint-env mocha */
/* global Ti */
/* eslint no-unused-expressions: "off" */
'use strict';
var should = require('./utilities/assertions');

describe.ios('Titanium.App.iOS.UserActivity', function () {

var userActivity;

before(function () {
userActivity = Ti.App.iOS.createUserActivity({
activityType: 'com.setdirection.home',
title: 'activity 1',
userInfo: {
msg: 'hello world'
},
eligibleForSearch: true,
eligibleForPrediction: true,
persistentIdentifier: 'titanium_activity_identifier'
});
});

after(function () {
userActivity = null;
});

it('constructor', function () {
should(userActivity).be.an.Object;
should(userActivity).have.readOnlyProperty('apiName').which.is.a.String;
should(userActivity.apiName).be.eql('Ti.App.iOS.UserActivity');
});

it('#deleteSavedUserActivitiesForPersistentIdentifiers()', function () {
should(userActivity.deleteSavedUserActivitiesForPersistentIdentifiers).be.a.Function;
});

it('#deleteAllSavedUserActivities()', function () {
should(userActivity.deleteAllSavedUserActivities).be.a.Function;
});

});