├── .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 | 
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 | 
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 | 
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 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/views/detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/views/index.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/views/list.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/docs/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/docs/diagram.png
--------------------------------------------------------------------------------
/docs/handoff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/docs/handoff.png
--------------------------------------------------------------------------------
/docs/screenshots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-appsearch/252d27e4d64bfd75c1d5ce4cc9ce0d5dd74d0f76/docs/screenshots.png
--------------------------------------------------------------------------------
/plugins/ti.alloy/hooks/alloy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Alloy
3 | * Copyright (c) 2012 by Appcelerator, Inc. All Rights Reserved.
4 | * See LICENSE for more information on licensing.
5 | */
6 |
7 | exports.cliVersion = '>=3.X';
8 | exports.version = '1.0.0';
9 | var SILENT = true;
10 |
11 | exports.init = function (logger, config, cli, appc) {
12 | var path = require('path'),
13 | fs = require('fs'),
14 | afs = appc.fs,
15 | i18n = appc.i18n(__dirname),
16 | __ = i18n.__,
17 | __n = i18n.__n,
18 | pkginfo = appc.pkginfo.package(module),
19 | exec = require('child_process').exec,
20 | spawn = require('child_process').spawn,
21 | parallel = appc.async.parallel;
22 |
23 | if(!process.env.sdk) {
24 | process.env.sdk = cli.sdk.name;
25 | }
26 |
27 | function run(deviceFamily, deployType, target, finished, silent) {
28 | var appDir = path.join(cli.argv['project-dir'], 'app');
29 | if (!afs.exists(appDir)) {
30 | logger.info(__('Project not an Alloy app, continuing'));
31 | finished();
32 | return;
33 | }
34 | logger.info(__('Found Alloy app in %s', appDir.cyan));
35 |
36 | // TODO: Make this check specific to a TiSDK version
37 | // create a .alloynewcli file to tell old plugins not to run
38 | var buildDir = path.join(cli.argv['project-dir'], 'build');
39 | if (!afs.exists(buildDir)) {
40 | fs.mkdirSync(buildDir);
41 | }
42 | fs.writeFileSync(path.join(buildDir, '.alloynewcli'), '');
43 |
44 | var cRequire = afs.resolvePath(__dirname, '..', 'Alloy', 'commands', 'compile', 'index.js'),
45 | config = {
46 | platform: /(?:iphone|ipad)/.test(cli.argv.platform) ? 'ios' : cli.argv.platform,
47 | version: '0',
48 | simtype: 'none',
49 | devicefamily: /(?:iphone|ios)/.test(cli.argv.platform) ? deviceFamily : 'none',
50 | deploytype: deployType || cli.argv['deploy-type'] || 'development',
51 | target: target
52 | };
53 | if(silent) {
54 | // turn off all logging output for code analyzer build hook
55 | config.noBanner = 'true';
56 | config.logLevel = '-1';
57 | }
58 |
59 | config = Object.keys(config).map(function (c) {
60 | return c + '=' + config[c];
61 | }).join(',');
62 |
63 | if (afs.exists(cRequire)) {
64 | // we're being invoked from the actual alloy directory!
65 | // no need to subprocess, just require() and run
66 | var origLimit = Error.stackTraceLimit;
67 | Error.stackTraceLimit = Infinity;
68 | try {
69 | require(cRequire)({}, {
70 | config: config,
71 | outputPath: cli.argv['project-dir'],
72 | _version: pkginfo.version
73 | });
74 | } catch (e) {
75 | logger.error(__('Alloy compiler failed'));
76 | e.toString().split('\n').forEach(function (line) {
77 | if (line) { logger.error(line); }
78 | });
79 | process.exit(1);
80 | }
81 | Error.stackTraceLimit = origLimit;
82 | finished();
83 | } else {
84 | // we have no clue where alloy is installed, so we're going to subprocess
85 | // alloy and hope it's in the system path or a well known place
86 | var paths = {};
87 | var locatorCmd = process.platform === 'win32' ? 'where' : 'which';
88 | parallel(this, ['alloy', 'node'].map(function (bin) {
89 | return function (done) {
90 | var envName = 'ALLOY_' + (bin === 'node' ? 'NODE_' : '') + 'PATH';
91 |
92 | paths[bin] = process.env[envName];
93 | if (paths[bin]) {
94 | done();
95 | } else if (process.platform === 'win32' && bin === 'alloy') {
96 | paths.alloy = 'alloy.cmd';
97 | done();
98 | } else {
99 | exec(locatorCmd + ' ' + bin, function (err, stdout, strerr) {
100 | if (!err) {
101 | paths[bin] = stdout.trim();
102 | done();
103 | } else {
104 | parallel(this, [
105 | '/usr/local/bin/' + bin,
106 | '/opt/local/bin/' + bin,
107 | path.join(process.env.HOME, 'local/bin', bin),
108 | '/opt/bin/' + bin,
109 | '/usr/bin/' + bin
110 | ].map(function (p) {
111 | return function (cb) {
112 | if (afs.exists(p)) { paths[bin] = p; }
113 | cb();
114 | };
115 | }), done);
116 | }
117 | });
118 | }
119 | };
120 | }), function () {
121 |
122 | // compose alloy command execution
123 | var cmd = [paths.node, paths.alloy, 'compile', appDir, '--config', config];
124 | if (cli.argv['no-colors'] || cli.argv['color'] === false) { cmd.push('--no-colors'); }
125 |
126 | // process each line of output from alloy
127 | function checkLine(line) {
128 | var re = new RegExp(
129 | '(?:\u001b\\[\\d+m)?\\[?(' +
130 | logger.getLevels().join('|') +
131 | ')\\]?\s*(?:\u001b\\[\\d+m)?(.*)', 'i'
132 | );
133 | if (line) {
134 | var m = line.match(re);
135 | if (m) {
136 | logger[m[1].toLowerCase()](m[2].trim());
137 | } else {
138 | logger.debug(line);
139 | }
140 | }
141 | }
142 |
143 | // execute alloy in os-specific manner
144 | var child;
145 | if (process.platform === 'win32' && paths.alloy === 'alloy.cmd') {
146 | cmd.shift();
147 | logger.info(__('Executing Alloy compile: %s',
148 | ['cmd','/s','/c'].concat(cmd).join(' ').cyan));
149 |
150 | // arg processing from https://github.com/MarcDiethelm/superspawn
151 | child = spawn('cmd', [['/s', '/c', '"' +
152 | cmd.map(function(a) {
153 | if (/^[^"].* .*[^"]/.test(a)) return '"'+a+'"'; return a;
154 | }).join(" ") + '"'].join(" ")], {
155 | stdio: 'inherit',
156 | windowsVerbatimArguments: true
157 | }
158 | );
159 | } else {
160 | logger.info(__('Executing Alloy compile: %s', cmd.join(' ').cyan));
161 | child = spawn(cmd.shift(), cmd);
162 | child.stdout.on('data', function (data) {
163 | data.toString().split('\n').forEach(checkLine);
164 | });
165 | child.stderr.on('data', function (data) {
166 | data.toString().split('\n').forEach(checkLine);
167 | });
168 | }
169 |
170 | // handle the completion of alloy, success or otherwise
171 | child.on('exit', function (code) {
172 | if (code) {
173 | logger.error(__('Alloy compiler failed'));
174 | process.exit(1);
175 | } else {
176 | logger.info(__('Alloy compiler completed successfully'));
177 |
178 | afs.exists(path.join(cli.argv["project-dir"], 'build', 'i18n')) && process.argv.push('--i18n-dir', 'build');
179 | afs.exists(path.join(cli.argv["project-dir"], 'build', 'platform')) && (cli.argv['platform-dir'] = 'build/platform');
180 | }
181 | finished();
182 | });
183 |
184 | });
185 | }
186 | }
187 |
188 | cli.addHook('build.pre.compile', function (build, finished) {
189 | var deployType = build.deployType,
190 | target = build.target;
191 |
192 | run(build.deviceFamily, deployType, target, finished);
193 | });
194 |
195 | cli.addHook('codeprocessor.pre.run', function (build, finished) {
196 | run('none', 'development', undefined, finished, SILENT);
197 | });
198 | };
199 |
--------------------------------------------------------------------------------
/plugins/ti.alloy/hooks/deepclean.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Alloy
3 | * Copyright (c) 2014 by Appcelerator, Inc. All Rights Reserved.
4 | * See LICENSE for more information on licensing.
5 | */
6 |
7 | exports.cliVersion = '>=3.X';
8 | var SILENT = true;
9 |
10 | exports.init = function (logger, config, cli, appc) {
11 | var path = require('path'),
12 | fs = require('fs'),
13 | afs = appc.fs;
14 |
15 | function run() {
16 | if(cli.argv['shallow'] === '') {
17 | logger.info('Not cleaning the Resources directory');
18 | return;
19 | }
20 | var appDir = path.join(cli.argv['project-dir'], 'app');
21 | if (!afs.exists(appDir)) {
22 | logger.debug('Project not an Alloy app, exiting.');
23 | finished();
24 | return;
25 | }
26 |
27 | var resourcesDir = path.join(cli.argv['project-dir'], 'Resources');
28 | if (!afs.exists(resourcesDir)) {
29 | logger.debug('Resources directory does not exist.');
30 | return;
31 | }
32 | rmdir(resourcesDir, fs, path, logger);
33 | logger.debug('Resources directory of %s has been emptied', appDir.cyan);
34 | }
35 |
36 | cli.addHook('clean.post', function (build, finished) {
37 | run();
38 | });
39 |
40 | };
41 |
42 | function rmdir(dirPath, fs, path, logger, removeSelf) {
43 | var files;
44 | try {
45 | files = fs.readdirSync(dirPath);
46 | }
47 | catch(e) {
48 | return;
49 | }
50 | if (files.length > 0) {
51 | for (var i = 0; i < files.length; i++) {
52 | var filePath = path.join(dirPath, files[i]);
53 | if (fs.statSync(filePath).isFile()) {
54 | fs.unlinkSync(filePath);
55 | } else {
56 | rmdir(filePath, fs, path, logger, true);
57 | }
58 | }
59 | }
60 | if (removeSelf) {
61 | fs.rmdirSync(dirPath);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/plugins/ti.alloy/plugin.py:
--------------------------------------------------------------------------------
1 | import os, sys, subprocess, hashlib
2 |
3 | import subprocess
4 |
5 | def check_output(*popenargs, **kwargs):
6 | r"""Run command with arguments and return its output as a byte string.
7 |
8 | Backported from Python 2.7 as it's implemented as pure python on stdlib.
9 |
10 | >>> check_output(['/usr/bin/python', '--version'])
11 | Python 2.6.2
12 | """
13 | process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
14 | output, unused_err = process.communicate()
15 | retcode = process.poll()
16 | if retcode:
17 | cmd = kwargs.get("args")
18 | if cmd is None:
19 | cmd = popenargs[0]
20 | error = subprocess.CalledProcessError(retcode, cmd)
21 | error.output = output
22 | raise error
23 | return output
24 |
25 | def compile(config):
26 | paths = {}
27 | binaries = ["alloy","node"]
28 |
29 | dotAlloy = os.path.abspath(os.path.join(config['project_dir'], 'build', '.alloynewcli'))
30 | if os.path.exists(dotAlloy):
31 | print "[DEBUG] build/.alloynewcli file found, skipping plugin..."
32 | os.remove(dotAlloy)
33 | else:
34 | for binary in binaries:
35 | try:
36 | # see if the environment variable is defined
37 | paths[binary] = os.environ["ALLOY_" + ("NODE_" if binary == "node" else "") + "PATH"]
38 | except KeyError as ex:
39 | # next try PATH, and then our guess paths
40 | if sys.platform == "darwin" or sys.platform.startswith('linux'):
41 | userPath = os.environ["HOME"]
42 | guessPaths = [
43 | "/usr/local/bin/"+binary,
44 | "/opt/local/bin/"+binary,
45 | userPath+"/local/bin/"+binary,
46 | "/opt/bin/"+binary,
47 | "/usr/bin/"+binary,
48 | "/usr/local/share/npm/bin/"+binary
49 | ]
50 |
51 | try:
52 | binaryPath = check_output(["which",binary], stderr=subprocess.STDOUT).strip()
53 | print "[DEBUG] %s installed at '%s'" % (binary,binaryPath)
54 | except:
55 | print "[WARN] Couldn't find %s on your PATH:" % binary
56 | print "[WARN] %s" % os.environ["PATH"]
57 | print "[WARN]"
58 | print "[WARN] Checking for %s in a few default locations:" % binary
59 | for p in guessPaths:
60 | sys.stdout.write("[WARN] %s -> " % p)
61 | if os.path.exists(p):
62 | binaryPath = p
63 | print "FOUND"
64 | break
65 | else:
66 | print "not found"
67 | binaryPath = None
68 |
69 | if binaryPath is None:
70 | print "[ERROR] Couldn't find %s" % binary
71 | sys.exit(1)
72 | else:
73 | paths[binary] = binaryPath
74 |
75 | # no guesses on windows, just use the PATH
76 | elif sys.platform == "win32":
77 | paths["alloy"] = "alloy.cmd"
78 |
79 | f = os.path.abspath(os.path.join(config['project_dir'], 'app'))
80 | if os.path.exists(f):
81 | print "[INFO] alloy app found at %s" % f
82 | rd = os.path.abspath(os.path.join(config['project_dir'], 'Resources'))
83 |
84 | devicefamily = 'none'
85 | simtype = 'none'
86 | version = '0'
87 | deploytype = 'development'
88 |
89 | if config['platform']==u'ios':
90 | version = config['iphone_version']
91 | devicefamily = config['devicefamily']
92 | deploytype = config['deploytype']
93 | if config['platform']==u'android':
94 | builder = config['android_builder']
95 | version = builder.tool_api_level
96 | deploytype = config['deploy_type']
97 | if config['platform']==u'mobileweb':
98 | builder = config['mobileweb_builder']
99 | deploytype = config['deploytype']
100 |
101 | cfg = "platform=%s,version=%s,simtype=%s,devicefamily=%s,deploytype=%s," % (config['platform'],version,simtype,devicefamily,deploytype)
102 |
103 | if sys.platform == "win32":
104 | cmd = [paths["alloy"], "compile", f, "--no-colors", "--config", cfg]
105 | else:
106 | cmd = [paths["node"], paths["alloy"], "compile", f, "--no-colors", "--config", cfg]
107 |
108 | print "[INFO] Executing Alloy compile:"
109 | print "[INFO] %s" % " ".join(cmd)
110 |
111 | try:
112 | print check_output(cmd, stderr=subprocess.STDOUT)
113 | except subprocess.CalledProcessError as ex:
114 | if hasattr(ex, 'output'):
115 | print ex.output
116 | print "[ERROR] Alloy compile failed"
117 | retcode = 1
118 | if hasattr(ex, 'returncode'):
119 | retcode = ex.returncode
120 | sys.exit(retcode)
121 | except EnvironmentError as ex:
122 | print "[ERROR] Unexpected error with Alloy compiler plugin: %s" % ex.strerror
123 | sys.exit(2)
124 |
--------------------------------------------------------------------------------
/tiapp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | com.appcelerator.sample.appsearch
4 | App Search
5 | 1.0
6 | Appcelerator
7 | http://www.appcelerator.com
8 | This app demonstrates Core Spotlight and UserActivity support in Titanium 5.0.0
9 | 2015 by Appcelerator, Inc.
10 | appicon.png
11 | false
12 | false
13 | false
14 | 11111111-1111-1111-1111-111111111111
15 | dp
16 |
17 |
18 |
19 |
20 |
21 | NSUserActivityTypes
22 |
23 | com.appcelerator.sample.spotlight.list
24 | com.appcelerator.sample.spotlight.detail
25 |
26 |
27 | UISupportedInterfaceOrientations~iphone
28 |
29 | UIInterfaceOrientationPortrait
30 |
31 | UISupportedInterfaceOrientations~ipad
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationPortraitUpsideDown
35 | UIInterfaceOrientationLandscapeLeft
36 | UIInterfaceOrientationLandscapeRight
37 |
38 | UIRequiresPersistentWiFi
39 |
40 | UIPrerenderedIcon
41 |
42 | UIStatusBarHidden
43 |
44 | UIStatusBarStyle
45 | UIStatusBarStyleLightContent
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | true
55 | true
56 |
57 | default
58 |
59 |
60 |
61 | false
62 | false
63 | true
64 | false
65 | false
66 |
67 | 5.0.2.GA
68 |
69 | ti.alloy
70 |
71 |
72 |
--------------------------------------------------------------------------------