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]: iOS 12 Expose new NSUserActivity APIs for Siri Intents #10288

Merged
merged 7 commits into from
Aug 28, 2018
Merged
Show file tree
Hide file tree
Changes from 6 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
127 changes: 124 additions & 3 deletions apidoc/Titanium/App/iOS/UserActivity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ description: |
* Connect all devices to the same Wi-Fi network.

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For more information, see [the Apple docs](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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not only the new methods but all methods ;-). Including code-docs and params-descriptions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All method exposing is fine. Do we use code-docs and params-description in header file in Titanium SDK?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Ti core-developers, it will help knowing which params are used in the "boxed" arguments. But it looks fine for now.

#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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make sure all public API's are also in the interface header? It makes it easier to maintain.

{
if ([TiUtils isIOSVersionLower:@"12.0"]) {
return NUMBOOL(NO);
}

return @(_userActivity.isEligibleForPrediction);
}

- (void)setEligibleForPrediction:(NSNumber *)value
{
ENSURE_UI_THREAD(setEligibleForSearch, value);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UI-thread should be validated before the type-checks.

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;
});

});