├── .gitignore ├── DefaultIcon.png ├── LICENSE ├── README.md ├── app ├── README ├── alloy.js ├── assets │ └── iphone │ │ ├── Default-568h@2x.png │ │ ├── Default-667h@2x.png │ │ ├── Default-Landscape-736h@3x.png │ │ ├── Default-Portrait-736h@3x.png │ │ ├── Default.png │ │ ├── Default@2x.png │ │ └── images │ │ ├── beatles.jpg │ │ ├── beatles@2x.jpg │ │ ├── george.jpg │ │ ├── george@2x.jpg │ │ ├── john.jpg │ │ ├── john@2x.jpg │ │ ├── paul.jpg │ │ ├── paul@2x.jpg │ │ ├── ringo.jpg │ │ ├── ringo@2x.jpg │ │ ├── tabIcon.png │ │ ├── tabIcon@2x.png │ │ └── tabIcon@3x.png ├── config.json ├── controllers │ ├── console.js │ ├── detail.js │ ├── index.js │ └── list.js ├── lib │ └── log.js ├── models │ └── array.js ├── styles │ ├── app.tss │ ├── console.tss │ ├── detail.tss │ └── list.tss └── views │ ├── console.xml │ ├── detail.xml │ ├── index.xml │ └── list.xml ├── docs ├── diagram.png ├── handoff.png └── screenshots.png ├── plugins └── ti.alloy │ ├── hooks │ ├── alloy.js │ └── deepclean.js │ └── plugin.py └── tiapp.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Resources 3 | build.log 4 | build 5 | npm-debug.log 6 | tmp 7 | .map 8 | .project 9 | .settings 10 | Thumbs.db 11 | _assets -------------------------------------------------------------------------------- /DefaultIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/DefaultIcon.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2008-2013 Appcelerator, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | (or the full text of the license is below) 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | 18 | 19 | Apache License 20 | Version 2.0, January 2004 21 | http://www.apache.org/licenses/ 22 | 23 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 24 | 25 | 1. Definitions. 26 | 27 | "License" shall mean the terms and conditions for use, reproduction, 28 | and distribution as defined by Sections 1 through 9 of this document. 29 | 30 | "Licensor" shall mean the copyright owner or entity authorized by 31 | the copyright owner that is granting the License. 32 | 33 | "Legal Entity" shall mean the union of the acting entity and all 34 | other entities that control, are controlled by, or are under common 35 | control with that entity. For the purposes of this definition, 36 | "control" means (i) the power, direct or indirect, to cause the 37 | direction or management of such entity, whether by contract or 38 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 39 | outstanding shares, or (iii) beneficial ownership of such entity. 40 | 41 | "You" (or "Your") shall mean an individual or Legal Entity 42 | exercising permissions granted by this License. 43 | 44 | "Source" form shall mean the preferred form for making modifications, 45 | including but not limited to software source code, documentation 46 | source, and configuration files. 47 | 48 | "Object" form shall mean any form resulting from mechanical 49 | transformation or translation of a Source form, including but 50 | not limited to compiled object code, generated documentation, 51 | and conversions to other media types. 52 | 53 | "Work" shall mean the work of authorship, whether in Source or 54 | Object form, made available under the License, as indicated by a 55 | copyright notice that is included in or attached to the work 56 | (an example is provided in the Appendix below). 57 | 58 | "Derivative Works" shall mean any work, whether in Source or Object 59 | form, that is based on (or derived from) the Work and for which the 60 | editorial revisions, annotations, elaborations, or other modifications 61 | represent, as a whole, an original work of authorship. For the purposes 62 | of this License, Derivative Works shall not include works that remain 63 | separable from, or merely link (or bind by name) to the interfaces of, 64 | the Work and Derivative Works thereof. 65 | 66 | "Contribution" shall mean any work of authorship, including 67 | the original version of the Work and any modifications or additions 68 | to that Work or Derivative Works thereof, that is intentionally 69 | submitted to Licensor for inclusion in the Work by the copyright owner 70 | or by an individual or Legal Entity authorized to submit on behalf of 71 | the copyright owner. For the purposes of this definition, "submitted" 72 | means any form of electronic, verbal, or written communication sent 73 | to the Licensor or its representatives, including but not limited to 74 | communication on electronic mailing lists, source code control systems, 75 | and issue tracking systems that are managed by, or on behalf of, the 76 | Licensor for the purpose of discussing and improving the Work, but 77 | excluding communication that is conspicuously marked or otherwise 78 | designated in writing by the copyright owner as "Not a Contribution." 79 | 80 | "Contributor" shall mean Licensor and any individual or Legal Entity 81 | on behalf of whom a Contribution has been received by Licensor and 82 | subsequently incorporated within the Work. 83 | 84 | 2. Grant of Copyright License. Subject to the terms and conditions of 85 | this License, each Contributor hereby grants to You a perpetual, 86 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 87 | copyright license to reproduce, prepare Derivative Works of, 88 | publicly display, publicly perform, sublicense, and distribute the 89 | Work and such Derivative Works in Source or Object form. 90 | 91 | 3. Grant of Patent License. Subject to the terms and conditions of 92 | this License, each Contributor hereby grants to You a perpetual, 93 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 94 | (except as stated in this section) patent license to make, have made, 95 | use, offer to sell, sell, import, and otherwise transfer the Work, 96 | where such license applies only to those patent claims licensable 97 | by such Contributor that are necessarily infringed by their 98 | Contribution(s) alone or by combination of their Contribution(s) 99 | with the Work to which such Contribution(s) was submitted. If You 100 | institute patent litigation against any entity (including a 101 | cross-claim or counterclaim in a lawsuit) alleging that the Work 102 | or a Contribution incorporated within the Work constitutes direct 103 | or contributory patent infringement, then any patent licenses 104 | granted to You under this License for that Work shall terminate 105 | as of the date such litigation is filed. 106 | 107 | 4. Redistribution. You may reproduce and distribute copies of the 108 | Work or Derivative Works thereof in any medium, with or without 109 | modifications, and in Source or Object form, provided that You 110 | meet the following conditions: 111 | 112 | (a) You must give any other recipients of the Work or 113 | Derivative Works a copy of this License; and 114 | 115 | (b) You must cause any modified files to carry prominent notices 116 | stating that You changed the files; and 117 | 118 | (c) You must retain, in the Source form of any Derivative Works 119 | that You distribute, all copyright, patent, trademark, and 120 | attribution notices from the Source form of the Work, 121 | excluding those notices that do not pertain to any part of 122 | the Derivative Works; and 123 | 124 | (d) If the Work includes a "NOTICE" text file as part of its 125 | distribution, then any Derivative Works that You distribute must 126 | include a readable copy of the attribution notices contained 127 | within such NOTICE file, excluding those notices that do not 128 | pertain to any part of the Derivative Works, in at least one 129 | of the following places: within a NOTICE text file distributed 130 | as part of the Derivative Works; within the Source form or 131 | documentation, if provided along with the Derivative Works; or, 132 | within a display generated by the Derivative Works, if and 133 | wherever such third-party notices normally appear. The contents 134 | of the NOTICE file are for informational purposes only and 135 | do not modify the License. You may add Your own attribution 136 | notices within Derivative Works that You distribute, alongside 137 | or as an addendum to the NOTICE text from the Work, provided 138 | that such additional attribution notices cannot be construed 139 | as modifying the License. 140 | 141 | You may add Your own copyright statement to Your modifications and 142 | may provide additional or different license terms and conditions 143 | for use, reproduction, or distribution of Your modifications, or 144 | for any such Derivative Works as a whole, provided Your use, 145 | reproduction, and distribution of the Work otherwise complies with 146 | the conditions stated in this License. 147 | 148 | 5. Submission of Contributions. Unless You explicitly state otherwise, 149 | any Contribution intentionally submitted for inclusion in the Work 150 | by You to the Licensor shall be under the terms and conditions of 151 | this License, without any additional terms or conditions. 152 | Notwithstanding the above, nothing herein shall supersede or modify 153 | the terms of any separate license agreement you may have executed 154 | with Licensor regarding such Contributions. 155 | 156 | 6. Trademarks. This License does not grant permission to use the trade 157 | names, trademarks, service marks, or product names of the Licensor, 158 | except as required for reasonable and customary use in describing the 159 | origin of the Work and reproducing the content of the NOTICE file. 160 | 161 | 7. Disclaimer of Warranty. Unless required by applicable law or 162 | agreed to in writing, Licensor provides the Work (and each 163 | Contributor provides its Contributions) on an "AS IS" BASIS, 164 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 165 | implied, including, without limitation, any warranties or conditions 166 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 167 | PARTICULAR PURPOSE. You are solely responsible for determining the 168 | appropriateness of using or redistributing the Work and assume any 169 | risks associated with Your exercise of permissions under this License. 170 | 171 | 8. Limitation of Liability. In no event and under no legal theory, 172 | whether in tort (including negligence), contract, or otherwise, 173 | unless required by applicable law (such as deliberate and grossly 174 | negligent acts) or agreed to in writing, shall any Contributor be 175 | liable to You for damages, including any direct, indirect, special, 176 | incidental, or consequential damages of any character arising as a 177 | result of this License or out of the use or inability to use the 178 | Work (including but not limited to damages for loss of goodwill, 179 | work stoppage, computer failure or malfunction, or any and all 180 | other commercial damages or losses), even if such Contributor 181 | has been advised of the possibility of such damages. 182 | 183 | 9. Accepting Warranty or Additional Liability. While redistributing 184 | the Work or Derivative Works thereof, You may choose to offer, 185 | and charge a fee for, acceptance of support, warranty, indemnity, 186 | or other liability obligations and/or rights consistent with this 187 | License. However, in accepting such obligations, You may act only 188 | on Your own behalf and on Your sole responsibility, not on behalf 189 | of any other Contributor, and only if You agree to indemnify, 190 | defend, and hold each Contributor harmless for any liability 191 | incurred by, or claims asserted against, such Contributor by reason 192 | of your accepting any such warranty or additional liability. 193 | 194 | END OF TERMS AND CONDITIONS 195 | 196 | APPENDIX: How to apply the Apache License to your work. 197 | 198 | To apply the Apache License to your work, attach the following 199 | boilerplate notice, with the fields enclosed by brackets "[]" 200 | replaced with your own identifying information. (Don't include 201 | the brackets!) The text should be enclosed in the appropriate 202 | comment syntax for the file format. We also recommend that a 203 | file or class name and description of purpose be included on the 204 | same "printed page" as the copyright notice for easier 205 | identification within third-party archives. 206 | 207 | Copyright [yyyy] [name of copyright owner] 208 | 209 | Licensed under the Apache License, Version 2.0 (the "License"); 210 | you may not use this file except in compliance with the License. 211 | You may obtain a copy of the License at 212 | 213 | http://www.apache.org/licenses/LICENSE-2.0 214 | 215 | Unless required by applicable law or agreed to in writing, software 216 | distributed under the License is distributed on an "AS IS" BASIS, 217 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 218 | See the License for the specific language governing permissions and 219 | limitations under the License. 220 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS 9 App Search Sample App 2 | 3 | This sample app demonstrates how to make the activities and content of your app searchable via Spotlight, Safari and Siri by using new API's introduced in iOS 9 and supported by Titanium 5.0.0. 4 | 5 | ![screenshots](docs/screenshots.png) 6 | 7 | ## The Big Picture 8 | I highly recommend reading through all of our new [Spotlight Search Guide](http://docs.appcelerator.com/platform/latest/#!/guide/Spotlight_Search) as well as Apple's [App Search Programming Guide](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/index.html) and related documentation but here's the gist of it: 9 | 10 | * Use [NSUserActivity](https://developer.apple.com/library/prerelease/ios/documentation/Foundation/Reference/NSUserActivity_Class/index.html) to [Index Activities and Navigation Points](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/Activities.html) on-device and make them available for private search, Siri and [Handoff](https://developer.apple.com/library/prerelease/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html). 11 | * Use the [Core Spotlight Framework](https://developer.apple.com/library/prerelease/ios/documentation/CoreSpotlight/Reference/CoreSpotlight_Framework/index.html) to [Index App Content](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/AppContent.html) on-device and make it available for private search. 12 | * [Mark Up Web Content](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/WebContent.html) to index content on web pages and make it available for public search. 13 | * Use [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html) and [Smart App Banners](https://developer.apple.com/library/prerelease/ios/documentation/AppleApplications/Reference/SafariWebContent/PromotingAppswithAppBanners/PromotingAppswithAppBanners.html) to enable users to open the current content or activity in your app. 14 | * [Combine APIs](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/CombiningAPIs.html) for NSUserActivity, Core Spotlight and Web Content Mark Up for the same content to increase coverage and [ranking](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/SearchUserExperience.html#//apple_ref/doc/uid/TP40016308-CH11-SW1). 15 | 16 | They say a picture says more then 5 bullets: 17 | 18 | ![picture](docs/diagram.png) 19 | 20 | As you can see Apple wants users to seamlessly move between apps (via search), devices (via handoff) as well as between native apps and websites (via Safari search, universal links and handoff). Apple's programming guide has a nice list of [Example Implementations](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/Choosing.html) for different types of apps to give an idea of how this might work for your app. 21 | 22 | ## The Sample 23 | To show the APIs in action I've created the [iOS App Search Sample App](https://github.com/appcelerator-developer-relations/appc-sample-appsearch). 24 | 25 | ## Spotlight 26 | 27 | The first tab in the sample app shows you a list of The Beatles. The four individual band members will be indexed by SpotLight. The list itself is a user activity, but we'll come back to that later. 28 | 29 | > **Quick Tip:** I use a local instance of an [definition-less model/collection](app/models/array.js) which means I can [populate (reset) the collection](app/controllers/list.js#L25) to use Alloy's data-binding on any array of objects. 30 | 31 | ### Adding items to the Spotlight index 32 | Scroll down to [line 53](app/controllers/list.js#L53) of `app/controllers/list.js` to see how I add the Beatles to the Spotlight index. There are three parts to it: 33 | 34 | * [Ti.App.iOS.SearchableItemAttributeSet](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS.SearchableItemAttributeSet) to create meta data for a.. 35 | * [Ti.App.iOS.SearchableItem](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS.SearchableItem) which I add to an instance of.. 36 | * [Ti.App.iOS.SearchableIndex](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS.SearchableIndex) 37 | 38 | The attribute set has a [huge amount of properties](https://developer.apple.com/library/prerelease/ios/documentation/CoreSpotlight/Reference/CSSearchableItemAttributeSet_Class/index.html#//apple_ref/doc/uid/TP40016247-CH1-DontLinkElementID_170) you can use to describe the item. Some let iOS play a song, call a phone number or navigate to an address directly from the Spotlight results without even opening your app. 39 | 40 | In short, this is how you'd index a single item: 41 | 42 | var index = Ti.App.iOS.createSearchableIndex(); 43 | 44 | index.addToDefaultSearchableIndex([ 45 | 46 | Ti.App.iOS.createSearchableItem({ 47 | uniqueIdentifier: 'my-id', 48 | domainIdentifier: 'my.content.type', 49 | attributeSet: Ti.App.iOS.createSearchableItemAttributeSet({ 50 | title: 'My Item' 51 | }) 52 | }) 53 | 54 | ], function (e) { 55 | e.success || alert('Oops!'); 56 | }); 57 | 58 | 59 | ### Deleting items from the Spotlight index 60 | Indexed items by default expire after one month unless you have set [Ti.App.iOS.SearchableItem.expirationDate](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS.SearchableItem-property-expirationDate). You can also manually delete all items, items with a shared `domainIdentifier` or specific items by `uniqueIdentifier`. 61 | 62 | The list in the sample app has a Trash/Add icon as the left navigation button to [delete all items](app/controllers/list.js#L121) for the Beatles domain or re-index them. Search for `appsearch` before and after to verify the change is effective immediately. 63 | 64 | var index = Ti.App.iOS.createSearchableIndex(); 65 | 66 | index.deleteAllSearchableItemByDomainIdenifiers(['content.type'], 67 | function (e) { 68 | e.success || alert('Oops!'); 69 | } 70 | ); 71 | 72 | ### Opening a Spotlight search result 73 | When a user taps on a Spotlight search result, your app will open and receive the [continueactivity](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS-event-continueactivity) event. 74 | 75 | Be aware that this event is also fired when a User Activity is opened from the search results or handed off from another device. In the case of a Spotlight search result the event's `activityType` property will be `com.apple.corespotlightitem` and `searchableItemActivityIdentifier` will have the `uniqueIdentifier` you've set on the indexed item. 76 | 77 | From [line 141](app/controllers/list.js#L141) you can see how to use this information to navigate your app to the content the user requested. In our case we share an [openDetail()](app/controllers/list.js#L197) helper function with the ListView's `itemclick` listener to look up the model and open the detail window. 78 | 79 | In short: 80 | 81 | Ti.App.iOS.addEventListener('continueactivity', function(e) { 82 | 83 | // Not for us 84 | if (e.activityType !== 'com.apple.corespotlightitem') { 85 | return 86 | } 87 | 88 | var uniqueIdentifier = e.searchableItemActivityIdentifier; 89 | 90 | // Navigate to the content 91 | }); 92 | 93 | ## User Activities 94 | 95 | The [NSUserActivity](https://developer.apple.com/library/prerelease/ios/documentation/Foundation/Reference/NSUserActivity_Class/index.html#//apple_ref/occ/cl/NSUserActivity) class introduced in iOS 8 to enable [Handoff](https://developer.apple.com/library/prerelease/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html) can now also be included in both private and even public search results. The new [Ti.App.iOS.UserActivity](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS.UserActivity) API in Titanium 5.0 gives you access to all these features. 96 | 97 | ### Spotlight vs UserActivity 98 | There's a very subtle difference between indexing for on-device search using Core Spotlight and User Activities. Think of indexing User Actives as tracking the pages users *have* visited, where Core Spotlight allows you to index the actual content that might be on one or more of these pages. We'll come back to how they work together later. 99 | 100 | ### Managing User Activities 101 | In the app we track two user activities. The first is the activity of viewing the list of Beatles and the other is the activity of viewing a individual Beatle's details. 102 | 103 | Both are created in the same way in [list.js from line 233](app/controllers/list.js#L233) and most of [detail.js](app/controllers/detail.js). 104 | 105 | As you can see we listen to the `focus` and `blur` events of both windows to create and invalidate (end) the related activity. Once invalidated the activity cannot become current again and must be re-created. 106 | 107 | The activity itself is uniquely identified by a reverse-domain `activityType`. The current state of the activity is saved to `userInfo`. In case of the detail window this is where we save the model ID of the Beatle we're viewing. While handoff is enabled by default, we need to set `eligibleForSearch:true` to have Spotlight index the activity. 108 | 109 | If you search for *appsearch* you should find the list activity as *The Beatles*. 110 | 111 | When you compare lines 73-88 in `list.js` and lines 61-79 in `detail.js` you will see that we can describe both a Spotlight item and a User Activity using the same `Ti.UI.SearchableItemAttributeSet`. In `detail.js` we also set the `relatedUniqueIdentifier` property. This prevents duplicate search results and makes that every time the activity becomes current it will count as a *pageview* for the related Spotlight item and improve its ranking. 112 | 113 | ### Continue a User Activity from Spotlight or Handoff 114 | From [line 141 of list.js](app/controllers/list.js#L141) you can see we handle a user activity search result in almost the same way as for other Spotlight items. We use the `activityType` to identify it and act accordingly. 115 | 116 | The exact same `continueactivity` event is also what handoff will fire so we get that for free! Install the app on two devices and double-tap home to try it out: 117 | 118 | ![screenshot](docs/handoff.png) 119 | 120 | ## Public Indexing & Web Content 121 | Since the sample is not in the App Store I cannot fully demonstrate [how to combine](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/CombiningAPIs.html#//apple_ref/doc/uid/TP40016308-CH10-SW1) the NSUserActivity and Core Spotlight APIs with Web Content Mark Up and Universal Links. Fortunately, Apple has a [excellent guide](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/WebContent.html) on this topic. 122 | 123 | ### Linking Web Content 124 | I already showed you how to combine NSUserActivity and Core Spotlight APIs for the same content. In the same way you can link Web Content as well. 125 | 126 | 1. First, set `Ti.App.iOS.UserActivity.webpageURL` to the related webpage URL and use the same value for the Spotlight item's `uniqueIdentifier` which in turn is linked to the activity via `relatedUniqueIdentifier`. 127 | 128 | 2. Then set the activity's `eligibleForPublicIndexing:true` as well as the required `requiredUserInfoKeys` property. *Eligible* means that a user activity on its own will never show up in search results. It will [Enhance Your Search Results](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/SearchUserExperience.html#//apple_ref/doc/uid/TP40016308-CH11-SW1) for Web Content by counting as *pageviews* for the related content. 129 | 130 | 3. Last but not least follow Apple's guide to [Mark Up Web Content](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/WebContent.html) and use the [App Search API Validation Tool](https://search.developer.apple.com/appsearch-validation-tool) to verify if you've set it all up correctly. 131 | 132 | Another benefit of setting `webpageURL` is that you can now handoff a user activity to a device (including desktops) that doesn't have the app, in which case it will open the website instead. We will come back to that in a separate Handoff sample. 133 | 134 | ## Links 135 | 136 | * [Appcelerator Spotlight Search Guide](http://docs.appcelerator.com/platform/latest/#!/guide/Spotlight_Search) 137 | * [Apple App Search Programming Guide](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/index.html) 138 | 139 | ## Known Issues 140 | 141 | * [TIMOB-19467: 142 | Ti.App.iOS.SearchableItemAttributeSet.thumbnailURL is not working](https://jira.appcelerator.org/browse/TIMOB-19467) 143 | -------------------------------------------------------------------------------- /app/README: -------------------------------------------------------------------------------- 1 | Welcome to Alloy. Prepare to be amazed. 2 | ======================================= 3 | 4 | Titanium Alloys are metals which contain a mixture of Titanium and other chemical elements. Such Alloys have very high tensile strength and toughness (even at extreme temperatures). They are light weight, have extraordinary corrosion resistance and the ability to withstand extreme temperatures [1]. 5 | 6 | Alloy for Titanium provides you, the developer, with the ability to run fast, jump high and generally code like an amazing superstar. 7 | 8 | Codestrong! 9 | 10 | [1] http://en.wikipedia.org/wiki/Titanium_alloy 11 | 12 | ------------------------- 13 | Now to the serious stuff. 14 | ------------------------- 15 | 16 | Here's how your Alloy directory is laid out. 17 | 18 | models your model files go here 19 | controllers your controllers files go here 20 | views yep, the views go here. you're getting it 21 | styles your style (.tss) files for your views go here 22 | assets All files here will be deployed into Resources 23 | 24 | Folders not generated by Alloy automatically, but the developer can create and use. 25 | 26 | lib put your own libraries here and use require('name') to load it 27 | migrations generated model migrations go here 28 | widgets pre-built, reusable components for your Ally apps. 29 | 30 | Also, in the root is the alloy.jmk file and config.json. Alloy.jmk acts like a makefile and can be used to hook into the Alloy compiler to customize the build process. The config.json file is where you can declare runtime contstants, and widget dependencies. 31 | 32 | -------------------------------------------------------------------------------- /app/alloy.js: -------------------------------------------------------------------------------- 1 | // The contents of this file will be executed before any of 2 | // your view controllers are ever executed, including the index. 3 | // You have access to all functionality on the `Alloy` namespace. 4 | // 5 | // This is a great place to do any initialization for your app 6 | // or create any global variables/functions that you'd like to 7 | // make available throughout your app. You can easily make things 8 | // accessible globally by attaching them to the `Alloy.Globals` 9 | // object. For example: 10 | // 11 | // Alloy.Globals.someGlobalFunction = function(){}; 12 | 13 | (function (global) { 14 | 15 | Alloy.Globals.TI_VERSION = parseInt(Ti.version.split('.')[0], 10); 16 | 17 | })(this); -------------------------------------------------------------------------------- /app/assets/iphone/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/Default-568h@2x.png -------------------------------------------------------------------------------- /app/assets/iphone/Default-667h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/Default-667h@2x.png -------------------------------------------------------------------------------- /app/assets/iphone/Default-Landscape-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/Default-Landscape-736h@3x.png -------------------------------------------------------------------------------- /app/assets/iphone/Default-Portrait-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/Default-Portrait-736h@3x.png -------------------------------------------------------------------------------- /app/assets/iphone/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/Default.png -------------------------------------------------------------------------------- /app/assets/iphone/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/Default@2x.png -------------------------------------------------------------------------------- /app/assets/iphone/images/beatles.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/beatles.jpg -------------------------------------------------------------------------------- /app/assets/iphone/images/beatles@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/beatles@2x.jpg -------------------------------------------------------------------------------- /app/assets/iphone/images/george.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/george.jpg -------------------------------------------------------------------------------- /app/assets/iphone/images/george@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/george@2x.jpg -------------------------------------------------------------------------------- /app/assets/iphone/images/john.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/john.jpg -------------------------------------------------------------------------------- /app/assets/iphone/images/john@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/john@2x.jpg -------------------------------------------------------------------------------- /app/assets/iphone/images/paul.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/paul.jpg -------------------------------------------------------------------------------- /app/assets/iphone/images/paul@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/paul@2x.jpg -------------------------------------------------------------------------------- /app/assets/iphone/images/ringo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/ringo.jpg -------------------------------------------------------------------------------- /app/assets/iphone/images/ringo@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/ringo@2x.jpg -------------------------------------------------------------------------------- /app/assets/iphone/images/tabIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/tabIcon.png -------------------------------------------------------------------------------- /app/assets/iphone/images/tabIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/tabIcon@2x.png -------------------------------------------------------------------------------- /app/assets/iphone/images/tabIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/app/assets/iphone/images/tabIcon@3x.png -------------------------------------------------------------------------------- /app/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "brandPrimary": "#CD1625" 4 | }, 5 | "env:development": {}, 6 | "env:test": {}, 7 | "env:production": {}, 8 | "os:android": {}, 9 | "os:blackberry": {}, 10 | "os:ios": {}, 11 | "os:mobileweb": {}, 12 | "os:windows": {}, 13 | "dependencies": {} 14 | } -------------------------------------------------------------------------------- /app/controllers/console.js: -------------------------------------------------------------------------------- 1 | var log = require('log'); 2 | 3 | /** 4 | * I wrap code that executes on creation in a self-executing function just to 5 | * keep it organised, not to protect global scope like it would in alloy.js 6 | */ 7 | (function constructor(args) { 8 | 9 | // Show logs from before this controller was created 10 | showLogs(); 11 | 12 | // Listen to changes 13 | log.on('change', showLogs); 14 | 15 | })(arguments[0] || {}); 16 | 17 | function showLogs() { 18 | $.log.value = log.history; 19 | } 20 | 21 | function clearLogs() { 22 | log.history = ''; 23 | 24 | showLogs(); 25 | } 26 | -------------------------------------------------------------------------------- /app/controllers/detail.js: -------------------------------------------------------------------------------- 1 | /* global $model */ 2 | 3 | var log = require('log'); 4 | 5 | // Reference to the activity created so we can invalidate it 6 | var activity; 7 | 8 | function onWindowFocus(e) { 9 | createUserActivity(); 10 | } 11 | 12 | function onWindowBlur(e) { 13 | invalidateActivity(); 14 | } 15 | 16 | function openWebsite() { 17 | Ti.Platform.openURL($model.get('id')); 18 | } 19 | 20 | /** 21 | * Called when the detail window receives focus to create the user activity and 22 | * make it the current by calling becomeCurrent() 23 | */ 24 | function createUserActivity() { 25 | 26 | // Make sure any existing activity is invalidated 27 | invalidateActivity(); 28 | 29 | // Parameters for Ti.App.iOS.createUserActivity() 30 | var parameters = { 31 | 32 | // This value needs to be defined in tiapp.xml 33 | activityType: 'com.appcelerator.sample.spotlight.detail', 34 | 35 | // We'll receive this information when the activity is continued via handoff 36 | userInfo: { 37 | id: $model.get('id') 38 | }, 39 | 40 | // This activity can be continued on another device 41 | eligibleForHandoff: true, 42 | 43 | // Index this activity for Spotlight as well as Siri suggestions 44 | eligibleForSearch: true, 45 | 46 | // Count this activity as a 'pageview' toward the related web/app content 47 | eligibleForPublicIndexing: true, 48 | 49 | // Required for eligibleForPublicIndexing, allowing users to open the result in the app 50 | requiredUserInfoKeys: ['id'], 51 | 52 | // Required for eligibleForPublicIndexing, allowing users to open the result on the website 53 | // The website should use Markup pointing back to this app for this to work 54 | webpageURL: $model.get('id') 55 | }; 56 | 57 | activity = Ti.App.iOS.createUserActivity(parameters); 58 | 59 | log.args('Ti.App.iOS.createUserActivity()', parameters); 60 | 61 | // Add a content attribute set, just like we did for Spotlight items 62 | activity.addContentAttributeSet(Ti.App.iOS.createSearchableItemAttributeSet({ 63 | 64 | // In particular useful for music which can be played straight from Spotlight search results 65 | itemContentType: Ti.App.iOS.UTTYPE_PLAIN_TEXT, 66 | 67 | // The information shown in Spotlight search results 68 | title: $model.get('name'), 69 | contentDescription: $model.get('bio'), 70 | 71 | // FIXME: It is more efficient to use thumbnailURL (https://jira.appcelerator.org/browse/TIMOB-19467) 72 | thumbnailData: Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, $model.get('image')).read(), 73 | 74 | // Not shown, but used to match results (try 'appsearch') 75 | keywords: ['beatle', 'spotlight', 'appsearch', 'sample', 'alloy', 'titanium', 'appcelerator'], 76 | 77 | // The ID of the related Spotlight item to enhance result and prevent duplicates 78 | relatedUniqueIdentifier: $model.get('id') 79 | })); 80 | 81 | // Check if the user's OS version supports user activities 82 | if (activity.supported) { 83 | 84 | // Make it the current activity 85 | activity.becomeCurrent(); 86 | 87 | } else { 88 | alert('UserActivity is not supported'); 89 | } 90 | } 91 | 92 | /** 93 | * Called when the user moves away from the detail window so we can invalidate 94 | * the user activity. Once invalidated it cannot become current again! 95 | */ 96 | function invalidateActivity() { 97 | 98 | if (!activity) { 99 | return; 100 | } 101 | 102 | activity.invalidate(); 103 | activity = null; 104 | 105 | log.args('Ti.App.iOS.UserActivity#detail.invalidate()'); 106 | } -------------------------------------------------------------------------------- /app/controllers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * I wrap code that executes on creation in a self-executing function just to 3 | * keep it organised, not to protect global scope like it would in alloy.js 4 | */ 5 | (function constructor(args) { 6 | 7 | if (Alloy.Globals.TI_VERSION < 5) { 8 | return alert('This sample requires Titanium 5 or later.'); 9 | } 10 | 11 | $.index.open(); 12 | 13 | })(arguments[0] || {}); -------------------------------------------------------------------------------- /app/controllers/list.js: -------------------------------------------------------------------------------- 1 | var log = require('log'); 2 | 3 | // Reference to the current detail window so we can close it 4 | var currentDetailWindow; 5 | 6 | // Reference to our activity so we can invalidate it 7 | var activity; 8 | 9 | /** 10 | * I wrap code that executes on creation in a self-executing function just to 11 | * keep it organised, not to protect global scope like it would in alloy.js 12 | */ 13 | (function constructor(args) { 14 | 15 | if (Alloy.Globals.TI_VERSION < 5) { 16 | return; 17 | } 18 | 19 | populateCollection(); 20 | 21 | // Add the Beatles to Spotlight 22 | addToIndex(); 23 | 24 | // Listen to the event that will fire when uses handoff or opens a Spotlight search result 25 | Ti.App.iOS.addEventListener('continueactivity', onContinueactivity); 26 | 27 | })(arguments[0] || {}); 28 | 29 | /** 30 | * Helper function to populate the collection. We need to do this every time we 31 | * open the app because its a model with no sync adapter, just used for data-binding. 32 | */ 33 | function populateCollection() { 34 | $.collection.reset([{ 35 | id: 'https://en.wikipedia.org/wiki/John_Lennon', 36 | name: 'John Lennon', 37 | bio: 'John Winston Ono Lennon MBE (born John Winston Lennon; 9 October 1940 – 8 December 1980) was an English singer and songwriter who rose to worldwide fame as a co-founder of the band the Beatles, the most commercially successful band in the history of popular music. With Paul McCartney, he formed a celebrated songwriting partnership.', 38 | image: '/images/john.jpg' 39 | }, { 40 | id: 'https://en.wikipedia.org/wiki/Paul_McCartney', 41 | name: 'Paul McCartney', 42 | bio: 'Sir James Paul McCartney MBE (born 18 June 1942) is an English singer-songwriter, multi-instrumentalist, and composer. With John Lennon, George Harrison, and Ringo Starr, he gained worldwide fame as the bassist of the English rock band the Beatles, one of the most popular and influential groups in the history of pop music; his songwriting partnership with Lennon is one of the most celebrated of the 20th century. After the band\'s break-up, he pursued a solo career and formed Wings with his first wife, Linda, and Denny Laine.', 43 | image: '/images/paul.jpg' 44 | }, { 45 | id: 'https://en.wikipedia.org/wiki/George_Harrison', 46 | name: 'George Harrison', 47 | bio: 'George Harrison,[nb 1] MBE (25 February 1943 – 29 November 2001) was an English musician, multi-instrumentalist, singer and songwriter and music and film producer who achieved international fame as the lead guitarist of the Beatles. Although John Lennon and Paul McCartney were the band\'s primary songwriters, most of their albums included at least one Harrison composition, including "While My Guitar Gently Weeps", "Here Comes the Sun" and "Something", which became the Beatles\' second-most-covered song.', 48 | image: '/images/george.jpg' 49 | }, { 50 | id: 'https://en.wikipedia.org/wiki/Ringo_Starr', 51 | name: 'Ringo Starr', 52 | bio: 'Richard Starkey,[2]` MBE (born 7 July 1940), known professionally as Ringo Starr, is an English musician and actor who gained worldwide fame as the drummer for the Beatles. He occasionally sang lead vocals, usually for one song on an album, including "With a Little Help from My Friends", "Yellow Submarine" and their cover of "Act Naturally". He also wrote the Beatles\' songs "Don\'t Pass Me By" and "Octopus\'s Garden", and is credited as a co-writer of others, such as "What Goes On" and "Flying".', 53 | image: '/images/ringo.jpg' 54 | }]); 55 | } 56 | 57 | /** 58 | * Helper function and event listener to the leftNav-add-Button. 59 | * It will add all individual Beatles to the Spotlight index. 60 | */ 61 | function addToIndex() { 62 | 63 | // Create an instance of the index 64 | var searchableIndex = Ti.App.iOS.createSearchableIndex(); 65 | 66 | // Check if Spotlight is supported (since iOS 9) 67 | if (!searchableIndex.isSupported()) { 68 | return alert('Ti.App.iOS.SearchableIndex requires iOS 9'); 69 | } 70 | 71 | // Collect all items we'll create 72 | var searchableItems = []; 73 | 74 | // Walk through all Beatle models 75 | $.collection.each(function (model) { 76 | 77 | // Create a set of attributes describing this item 78 | var searchableItemAttributeSet = Ti.App.iOS.createSearchableItemAttributeSet({ 79 | 80 | // In particular useful for music which can be played straight from Spotlight search results 81 | itemContentType: Ti.App.iOS.UTTYPE_PLAIN_TEXT, 82 | 83 | // The information shown in Spotlight search results 84 | title: model.get('name'), 85 | contentDescription: model.get('bio'), 86 | 87 | // FIXME: It is more efficient to use thumbnailURL (https://jira.appcelerator.org/browse/TIMOB-19467) 88 | thumbnailData: Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, model.get('image')).read(), 89 | 90 | // Not shown, but used to match results (try 'appsearch') 91 | keywords: ['beatle', 'appsearch', 'sample', 'alloy', 'titanium', 'appcelerator'] 92 | }); 93 | 94 | // Create the actual item we want to index 95 | var searchableItem = Ti.App.iOS.createSearchableItem({ 96 | 97 | // Used to uniquely identify this item within the app (group) 98 | uniqueIdentifier: model.get('id'), 99 | 100 | // Optional identifier for a group which you might want to delete all at once 101 | // Should be a reserve domain-type content type 102 | domainIdentifier: 'com.appcelerator.sample.spotlight.beatle', 103 | 104 | // The set of attributes we created above 105 | attributeSet: searchableItemAttributeSet 106 | }); 107 | 108 | searchableItems.push(searchableItem); 109 | }); 110 | 111 | // Add the searchable items to the index and provide a callback to check if it worked 112 | searchableIndex.addToDefaultSearchableIndex(searchableItems, function (e) { 113 | log.args('Ti.App.iOS.SearchableIndex.addToDefaultSearchableIndex', e); 114 | }); 115 | 116 | // Unless this is the initial call to addIndex, inform the user 117 | if ($.win.leftNavButton) { 118 | alert('Use spotlight and search for \'appsearch\' to find the individual beatles.'); 119 | } 120 | 121 | // Replace the leftNavButton with the button to delete the items from the index 122 | $.win.leftNavButton = $.deleteFromIndex; 123 | } 124 | 125 | /** 126 | * Event listener for the leftNav-trash-button. It will delete the 127 | * individual Beatles from Spotlight by their shared domainIdentifier. 128 | */ 129 | function deleteFromIndex() { 130 | 131 | // Create an instance of the index 132 | var searchableIndex = Ti.App.iOS.createSearchableIndex(); 133 | 134 | // Delete all Spotlight items with the given domain identifiers. 135 | searchableIndex.deleteAllSearchableItemByDomainIdenifiers(['com.appcelerator.sample.spotlight.beatle'], function (e) { 136 | log.args('Ti.App.iOS.SearchableIndex.deleteAllSearchableItemByDomainIdenifiers', e); 137 | }); 138 | 139 | alert('Use spotlight and search for \'appsearch\' to verify you can no longer find the individual beatles, but still find the activity of viewing this tab.'); 140 | 141 | // Replace the leftNavButton with the button to re-index the items 142 | $.win.leftNavButton = $.addToIndex; 143 | } 144 | 145 | /** 146 | * Event listener for the continueactivity event which will fire when a user 147 | * taps on the handoff icon or the Core Spotlight search item. 148 | */ 149 | function onContinueactivity(e) { 150 | var modelId; 151 | 152 | log.args('Ti.App.iOS:continueactivity', e); 153 | 154 | // A Spotlight search result was opened 155 | if (e.activityType === 'com.apple.corespotlightitem') { 156 | 157 | // The model ID is what we've set via Ti.App.iOS.SearchableItem.identifier 158 | modelId = e.searchableItemActivityIdentifier; 159 | 160 | // The UserActivity for the detail window was continued 161 | } else if (e.activityType === 'com.appcelerator.sample.spotlight.detail') { 162 | 163 | // The model ID is what we've set via Ti.App.iOS.UserActivity.userInfo.id 164 | modelId = e.userInfo.id; 165 | 166 | // The UserActivity for the list was continued 167 | } else if (e.activityType === 'com.appcelerator.sample.spotlight.list') { 168 | 169 | closeDetail(); 170 | 171 | } else { 172 | 173 | // Don't select our tab 174 | return; 175 | } 176 | 177 | // Select our tab 178 | $.tab.active = true; 179 | 180 | if (modelId) { 181 | 182 | // Pass the model ID to openDetail to handle opening the detail window 183 | openDetail(modelId); 184 | } 185 | } 186 | 187 | /** 188 | * Helper function to close the current detail window, if any 189 | */ 190 | function closeDetail() { 191 | 192 | if (!currentDetailWindow) { 193 | return; 194 | } 195 | 196 | $.tab.close(currentDetailWindow); 197 | 198 | currentDetailWindow = null; 199 | } 200 | 201 | /** 202 | * Helper function to lookup a model by its ID, close the current detail 203 | * window if needed and then open a new one for the model. 204 | */ 205 | function openDetail(id) { 206 | 207 | // Look up the model by the given ID 208 | var model = $.collection.get(id); 209 | 210 | // We somehow have an unknown ID, perhaps an old search result 211 | if (!model) { 212 | return alert('That\'s no Beatle!'); 213 | } 214 | 215 | closeDetail(); 216 | 217 | // Replace the current detail window by one for this model 218 | currentDetailWindow = Alloy.createController('detail', { 219 | 220 | // Pass the model as $model to use data binding 221 | $model: model 222 | 223 | }).getView(); 224 | 225 | // Open the new detail window via the tab 226 | $.tab.open(currentDetailWindow); 227 | } 228 | 229 | function onListViewItemclick(e) { 230 | 231 | // The model ID is what we've set via the ListItem's special itemId property 232 | var modelId = e.itemId; 233 | 234 | openDetail(modelId); 235 | } 236 | 237 | function onWindowFocus(e) { 238 | createUserActivity(); 239 | } 240 | 241 | function onWindowBlur(e) { 242 | invalidateActivity(); 243 | } 244 | 245 | function openWebsite() { 246 | Ti.Platform.openURL('https://en.wikipedia.org/wiki/The_Beatles'); 247 | } 248 | 249 | /** 250 | * Called when the list window receives focus to create the user activity and 251 | * make it the current by calling becomeCurrent() 252 | */ 253 | function createUserActivity() { 254 | 255 | if (activity) { 256 | return; 257 | } 258 | 259 | var parameters = { 260 | 261 | // This value needs to be defined in tiapp.xml 262 | activityType: 'com.appcelerator.sample.spotlight.list', 263 | 264 | // This activity can be continued on another device 265 | eligibleForHandoff: true, 266 | 267 | // Index this activity for Spotlight as well as Siri suggestions 268 | eligibleForSearch: true, 269 | 270 | // Count this activity as a 'pageview' toward the related web/app content 271 | eligibleForPublicIndexing: true, 272 | 273 | // Required for eligibleForPublicIndexing 274 | webpageURL: 'https://en.wikipedia.org/wiki/The_Beatles' 275 | }; 276 | 277 | activity = Ti.App.iOS.createUserActivity(parameters); 278 | 279 | log.args('Ti.App.iOS.createUserActivity()', parameters); 280 | 281 | // Add a set of attributes describing this user activity 282 | activity.addContentAttributeSet(Ti.App.iOS.createSearchableItemAttributeSet({ 283 | itemContentType: Ti.App.iOS.UTTYPE_PLAIN_TEXT, 284 | 285 | title: 'The Beatles', 286 | contentDescription: 'The Beatles were an English rock band, formed in Liverpool in 1960. With members John Lennon, Paul McCartney, George Harrison and Ringo Starr, they became widely regarded as the greatest and most influential act of the rock era.', 287 | thumbnailData: Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'images/beatles.jpg').read(), 288 | 289 | keywords: ['beatles', 'appsearch', 'sample', 'alloy', 'titanium', 'appcelerator'] 290 | })); 291 | 292 | // Check if the user's OS version supports user activities 293 | if (activity.supported) { 294 | 295 | // Make it the current activity 296 | activity.becomeCurrent(); 297 | 298 | } else { 299 | log.args('Did not call becomeCurrent() because activity.supported is:', activity.supported); 300 | } 301 | } 302 | 303 | /** 304 | * Called when the user moves away from the list window so we can invalidate 305 | * the user activity. Once invalidated it cannot become current again! 306 | */ 307 | function invalidateActivity() { 308 | 309 | if (!activity) { 310 | return; 311 | } 312 | 313 | activity.invalidate(); 314 | activity = null; 315 | 316 | log.args('Ti.App.iOS.UserActivity#list.invalidate()'); 317 | } 318 | -------------------------------------------------------------------------------- /app/lib/log.js: -------------------------------------------------------------------------------- 1 | /* global ENV_PROD */ 2 | 3 | var moment = require('alloy/moment'); 4 | 5 | var Log = module.exports = _.extend({}, Backbone.Events); 6 | 7 | Log.history = ''; 8 | 9 | Log.args = function () { 10 | var args = Array.prototype.slice.call(arguments); 11 | 12 | // Stringify non-strings 13 | args = args.map(function (arg) { 14 | return (typeof arg === 'string') ? arg : JSON.stringify(arg, null, 2); 15 | }); 16 | 17 | var message = args.join(' '); 18 | 19 | // Use error-level for production or they will not show in Xcode console 20 | console[ENV_PROD ? 'error' : 'info'](message); 21 | 22 | // Add the message to a global variable for controllers/console.js to use 23 | Log.history = (Log.history || '') + '[' + moment().format('HH:mm:ss.SS') + '] ' + message + '\n\n'; 24 | 25 | // Trigger an event for controllers/console.js to listen to and display the log 26 | Log.trigger('change'); 27 | }; 28 | -------------------------------------------------------------------------------- /app/models/array.js: -------------------------------------------------------------------------------- 1 | // An empty definition effectively gives you Backbone collection without a 2 | // sync-adapter. This means we have to fill (reset) the collection every 3 | // time the app starts and changes do not persist between sessions. 4 | // Ideal if all you want is Alloy's data-binding for any array of objects. 5 | exports.definition = {}; 6 | -------------------------------------------------------------------------------- /app/styles/app.tss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This is your global styles file. Selectors and rules you define 4 | here will be applied throughout your app. However, these rules 5 | have the lowest priority of any style settings. 6 | 7 | For more information, see the 'Style Priorities' section of 8 | http://docs.appcelerator.com/titanium/latest/#!/guide/Alloy_Styles_and_Themes 9 | 10 | For example, the following would apply to all labels, windows, 11 | and text fields (depending on platform) in your app unless you 12 | overrode the settings with other TSS, XML, or JS settings: 13 | 14 | 'Label[platform=android,windows]': { 15 | color: '#000' // all platforms except Android and Windows default to black 16 | } 17 | 18 | 'TextField[platform=android]': { 19 | height: Ti.UI.SIZE 20 | } 21 | 22 | */ 23 | 24 | 'TabGroup': { 25 | tabsBackgroundColor: 'white', 26 | tabsTintColor: Alloy.CFG.brandPrimary 27 | } 28 | 29 | 'Tab': { 30 | icon: '/images/tabIcon.png' 31 | } 32 | 33 | 'Window': { 34 | backgroundColor: '#fff' // white background instead of default transparent or black 35 | } 36 | 37 | 'Window[platform=ios]': { 38 | barColor: Alloy.CFG.brandPrimary, 39 | navTintColor: '#FFF', 40 | translucent: false, 41 | titleAttributes: { 42 | color: '#FFF' 43 | } 44 | } 45 | 46 | // All our ScrollViews are layed out and scroll vertical 47 | 'ScrollView': { 48 | layout: 'vertical', 49 | contentWidth: Ti.UI.FILE, 50 | contentHeight: Ti.UI.SIZE 51 | } 52 | 53 | '.help': { 54 | color: '#999', 55 | font: { 56 | fontSize: 15 57 | } 58 | } -------------------------------------------------------------------------------- /app/styles/console.tss: -------------------------------------------------------------------------------- 1 | 'TextArea': { 2 | 3 | // We just want to display text in a way that it can scroll 4 | // We don't want the user to actually be able to edit text 5 | editable: false, 6 | 7 | width: Ti.UI.FILL, 8 | height: Ti.UI.FILL, 9 | font: { 10 | fontFamily: 'Courier New' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/styles/detail.tss: -------------------------------------------------------------------------------- 1 | '.image': { 2 | top: 10, 3 | 4 | borderWidth: 5, 5 | borderColor: '#eee' 6 | } 7 | 8 | '.name': { 9 | top: 10, 10 | left: 10, 11 | right: 10, 12 | 13 | font: { 14 | fontWeight: 'bold', 15 | fontSize: 24 16 | }, 17 | textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER 18 | } 19 | 20 | '.bio': { 21 | top: 10, 22 | left: 10, 23 | right: 10 24 | } 25 | 26 | '.help': { 27 | top: 25, 28 | left: 10, 29 | right: 10 30 | } -------------------------------------------------------------------------------- /app/styles/list.tss: -------------------------------------------------------------------------------- 1 | 'ListView': { 2 | style: Ti.UI.iPhone.ListViewStyle.GROUPED 3 | } 4 | 5 | '.help': { 6 | top: 15, 7 | right: 15, 8 | left: 15 9 | } -------------------------------------------------------------------------------- /app/views/console.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |