├── .gitignore ├── DefaultIcon.png ├── LICENSE ├── README.md ├── app ├── README ├── alloy.jmk ├── 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 │ │ ├── shortcutItemIcon.png │ │ ├── shortcutItemIcon@2x.png │ │ ├── shortcutItemIcon@3x.png │ │ ├── tabIcon.png │ │ ├── tabIcon@2x.png │ │ └── tabIcon@3x.png ├── config.json ├── controllers │ ├── apis.js │ ├── console.js │ ├── details.js │ ├── index.js │ ├── list.js │ ├── pictures.js │ ├── preview.js │ ├── thumbnail.js │ └── thumbnails.js ├── i18n │ ├── de │ │ └── app.xml │ └── en │ │ └── app.xml ├── lib │ └── log.js ├── models │ └── picture.js ├── styles │ ├── apis.tss │ ├── app.tss │ ├── console.tss │ ├── index.tss │ ├── list.tss │ ├── pictures.tss │ ├── preview.tss │ ├── thumbnail.tss │ └── thumbnails.tss └── views │ ├── apis.xml │ ├── console.xml │ ├── details.xml │ ├── index.xml │ ├── list.xml │ ├── pictures.xml │ ├── preview.xml │ ├── thumbnail.xml │ └── thumbnails.xml ├── docs ├── preview.png ├── screencast.gif └── shortcuts.png ├── manifest ├── 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 12 | /i18n 13 | /platform 14 | -------------------------------------------------------------------------------- /DefaultIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/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 3D Touch Sample App 2 | 3 | > **NOTE:** This Sample App requires Titanium 5.1.0, Alloy 1.7.26 and AppC CLI Core Package 5.1.0 or later. It also requires iOS 9 and an iPhone 6S device or later. 4 | 5 | This sample app demonstrates the new [3D Touch](http://www.apple.com/iphone-6s/3d-touch/) capabilities of the iPhone 6S. Titanium 5.1 implements [Peek and Pop](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/3DTouch.html#//apple_ref/doc/uid/TP40006556-CH71-SW1) and [Home Screen Quick Actions](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/3DTouch.html#//apple_ref/doc/uid/TP40006556-CH71-SW1). The new [force-properties](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITouch_Class/index.html#//apple_ref/occ/instp/UITouch/force) available in touch-events will be [added in a later release](https://jira.appcelerator.org/browse/TIMOB-19492) as well as [support for the related Apple Pencil](https://jira.appcelerator.org/browse/TIMOB-19667). 6 | 7 | ![screenshots](docs/screencast.gif) 8 | 9 | ## Force Touch vs 3D Touch 10 | Force Touch was introduced with the [Apple Watch](https://developer.apple.com/watch/human-interface-guidelines/#force-touch) and has later been added to the MacBook and Magic Trackpad for [OS X](https://developer.apple.com/osx/force-touch/). Still, these devices use two different techniques and 3D Touch is yet another. 11 | 12 | * Apple provides little detail about how Force Touch for Apple Watch exactly works, but as a developer it is important that [you do not have direct access to the touch events](https://forums.developer.apple.com/message/5723#5723) and have to implement [Context Menus](https://developer.apple.com/library/ios/documentation/General/Conceptual/WatchKitProgrammingGuide/Menus.html) to which a touch is either *firm* or not. It was even rumored that the Apple Watch measures how the surface of your finger grows as you press with more force. 13 | 14 | * The Trackpads however [have four force sensors](http://www.apple.com/macbook/design/) and allow developers to [monitor the exact pressure and acceleration](https://developer.apple.com/osx/force-touch/) in OS X. 15 | 16 | * The developer experience for 3D Touch on iPhone 6S and 6S Plus is [similar](https://developer.apple.com/ios/3d-touch/) to Force Touch on OS X, but the technique driving it is again different. The screen is now [one big pressure sensor](http://www.apple.com/iphone-6s/3d-touch/) which can measure the exact pressure anywhere. 17 | 18 | Why Apple uses two different names for three different techniques is a mystery. We can probably expect the 3D Touch technique to come to Apple Watch 2 and new Trackpads at which point *Force Touch* would no longer be used. 19 | 20 | ## Running the Sample 21 | At this moment, the iOS Simulator does not let you to simulate 3D Touch events. So to run and test the sample you will need an iPhone 6S to build to. 22 | 23 | > **NOTE:** There is a [tweak available](https://github.com/DeskConnect/SBShortcutMenuSimulator) to simulate the Quick Actions in iOS Simulator. 24 | 25 | ### Via Appcelerator Studio 26 | 27 | * Import it via *Dashboard* if available. 28 | * Or import it via *File > Import... > Git > Git Repository as New Project* 29 | * Select *URI* and enter: 30 | 31 | https://github.com/appcelerator-developer-relations/appc-sample-3dtouch 32 | 33 | * Select a device to build to via *Run > Run As*. 34 | 35 | ### Via CLI 36 | 37 | 1. Clone the repository: 38 | 39 | git clone https://github.com/appcelerator-developer-relations/appc-sample-ti500 40 | 41 | 2. To run it with `appc run` first import it to the platform: 42 | 43 | appc new --import --no-services 44 | 45 | 3. Build to device: 46 | 47 | [appc run | ti build] -p ios -T device 48 | 49 | ## Quick Actions 50 | 51 | Press firmly on the app icon to reveal the static *Quick Actions* or *Application Shortcuts*. Once you've used the app add and then view a picture, you will also see the dynamic shortcut: 52 | 53 | ![shortcuts](docs/shortcuts.png) 54 | 55 | ### Static shortcuts 56 | 57 | Static shortcuts must be specified in `Info.plist` and work right after the app has installed. In Titanium you will add them to [tiapp.xml](tiapp.xml#L21) under the `ios/plist/dict` element, but apart from that you can just follow the [Apple Reference](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/iPhoneOSKeys.html#//apple_ref/doc/uid/TP40009252-SW36). 58 | 59 | In the sample app we've [added a Quick Action](tiapp.xml#L24) to select a picture from the device photo gallery to add to the app. 60 | 61 | Instead of `UIApplicationShortcutItemIconType` you can also use `UIApplicationShortcutItemIconFile` to use a 35x35dp so-called [Template Icon](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/BarIcons.html#//apple_ref/doc/uid/TP40006556-CH21-SW1). The title and subtitle can be localized by using a name you provide strings for via `i18n//app.xml` - not `strings.xml`. 62 | 63 | > **NOTE:** To use custom template icons the image must be in an asset catalog. This is an optional feature in 5.1.0 that can be enabled by `true` under the `ios` element in [tiapp.xml](tiapp.xml#L18). 64 | 65 | > **NOTE:** It is a [known issue](https://jira.appcelerator.org/browse/CLI-845) that to use a custom template icon for static shortcuts you need to find the corresponding hash of the image found under `build/iphone/Assets.xcassets`. 66 | 67 | We'll come back to how we handle the action later. 68 | 69 | ### Dynamic shortcuts 70 | 71 | Dynamic shortcuts are created via an instance of [Ti.UI.iOS.ApplicationShortcuts](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.ApplicationShortcuts), which means they will only be available once you have used these APIs and they can also be removed. 72 | 73 | In our sample we create a dynamic shortcut for the last-viewed picture in the [details controller](app/controllers/details.js#L11). We first create an instance of the above API and then use [addDynamicShortcut](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.ApplicationShortcuts-method-addDynamicShortcut). The params are similar to the static shortcuts, with the exception of [icon](https://docs.appcelerator.com/platform/latest/#!/api/ShortcutParams-property-icon) which can be either the path to a Template Icon or one of the `Ti.UI.iOS.SHORTCUT_ICON_TYPE_*` constants. 74 | 75 | > **NOTE:** As you can see, we first use [Ti.UI.iOS.forceTouchSupported](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS-property-forceTouchSupported) to test if the OS version and device actually support 3D Touch. The app might crash if you don't! 76 | 77 | When a picture gets deleted from the app we use the different APIs also demonstrated in the [api controller](app/controllers/api.js) to also remove the shortcut if it happened to be the last one we've viewed. You'll find this code in the [thumbnail](app/controllers/thumbnail.js#L65) and [details](app/controllers/details.js#L61) controllers. 78 | 79 | ### Handling Quick Actions 80 | When the user taps a Quick Action, the [Ti.App.iOS:shortcutitemclick](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.App.iOS-event-shortcutitemclick) event is fired. The payload includes all properties you have set static or dynamic shortcut except the icon. Simply use `itemtype` to identify the shortcut and act accordingly. 81 | 82 | In our sample app we're listening to the event in the [pictures controller](app/controllers/pictures.js#L19). For the dynamic details-shortcut you can see we're using the custom `userInfo` payload to get the actual model ID of the last-viewed picture. 83 | 84 | ## Peek & Pop 85 | Press firmly on one of the thumbnails in the sample app to play with Peek and Pop. As you start applying more force the rest of the screen will blur, then a preview will appear and finally the details window will open. Swipe up while you Peek to reveal any quick actions available. As you use it more often you will get a feel for the amount of pressure needed to trigger Peek directly. 86 | 87 | ![preview](docs/preview.png) 88 | 89 | ### PreviewContext 90 | To add Peek & Pop to a individual view or a ListView, create an instance of [Ti.UI.iOS.createPreviewContext](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS-method-createPreviewContext) and set it to the view's [previewContext](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.View-property-previewContext) property. When used in a ListView you need to listen to the PreviewContext's [peek](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.PreviewContext-event-peek) event and update the view set to its `preview` property. 91 | 92 | > **NOTE:** TableView [currently](https://jira.appcelerator.org/browse/TIMOB-20139) does not (fully) support Peek & Pop. 93 | 94 | ### Sample 95 | Use the top-left icon on the *Pictures* tab to switch between thumbnails that each individually have a preview context and a ListView where all items share a preview context. 96 | 97 | For both views we create the previewContext in [preview.xml](app/views/preview.xml). As from Alloy 1.7.25 and AppC CLI Core Package 5.1.0 you can define these in XML. In classic this would look like: 98 | 99 | $.previewContext = Ti.UI.iOS.createPreviewContext({ 100 | contentHeight: 400, 101 | preview: $.preview, 102 | actions: createActions() 103 | }); 104 | $.previewContext.addEventListener('peek', onPeek); 105 | $.previewContext.addEventListener('pop', onPop); 106 | 107 | * The preview showed during Peek is simply a Titanium View you assign to the [preview](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.PreviewContext-property-preview) property. Use the previewContext's [contentHeight](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.PreviewContext-property-contentHeight) property to enable rounded corners and not have the view take up all available height. 108 | 109 | * Listen to the [peek](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.PreviewContext-event-peek) event to update a shared preview when it is about to be displayed for a specific item. The event payload has the `sectionIndex`, `itemIndex` and optional `itemId` you need to do so. 110 | 111 | * To pop you add an event listener to the [pop](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.PreviewContext-event-pop) event. It has the same payload as `peek`. In our sample we just open the [details view](app/views/details.xml) via the helper method exposed in the [list controller](app/controllers/list.js#L26). 112 | 113 | * Finally an array of Quick Actions can be assigned to the [actions](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.PreviewContext-property-actions) property. These can also be [grouped](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.PreviewActionGroup) and the samples demonstrates both, as well as the different styles available. 114 | 115 | ## Credits 116 | 117 | A special thanks to community member Ben Bahrenburg for [his initial implementation](https://github.com/appcelerator/titanium_mobile/pull/7236) of the Quick Actions. Appcelerator engineer Hans Knöchel implemented Peek and Pop and did the initial version of this sample. 118 | 119 | ## Links 120 | 121 | * Titanium API reference: [Ti.UI.iOS.ApplicationShortcuts](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.ApplicationShortcuts) 122 | * Titanium API reference: [Ti.UI.iOS.PreviewContext](https://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.iOS.PreviewContext) 123 | * Apple Human Interface Guidelines: [3D Touch](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/3DTouch.html) 124 | * Apple Documentation: [Getting Started with 3D Touch](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Adopting3DTouchOniPhone/index.html) 125 | -------------------------------------------------------------------------------- /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.jmk: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | task('pre:load', function(event, logger) { 4 | 5 | if (!event.minVersion) { 6 | return; 7 | } 8 | 9 | var pkg = require(path.join(process.mainModule.filename, '..', '..', 'package.json')); 10 | 11 | if (versionStringToInt(pkg.version) < versionStringToInt(event.minVersion)) { 12 | var error = 'This sample requires Alloy ' + minVersion.join('.') + ' or later'; 13 | var line = (new Array(error.length + 1)).join('-'); 14 | 15 | logger.error(line); 16 | logger.error(error); 17 | logger.error(line); 18 | 19 | process.exit(1); 20 | } 21 | }); 22 | 23 | function versionStringToInt(versionStr) { 24 | return versionStr.split(/[^0-9]+/).reverse().reduce(function(previousValue, currentValue, currentIndex) { 25 | return previousValue + Math.pow(100, currentIndex) * parseInt(currentValue, 10); 26 | }, 0); 27 | } 28 | -------------------------------------------------------------------------------- /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 | /** 14 | * It's a best practice to your code in alloy.js in a self-executing function 15 | * since any variable declared here will be in global scope and never garbage 16 | * collected. Use this or global to explicitly define a global, but rather 17 | * use Alloy.Globals or a CommonJS module you require where needed. 18 | */ 19 | (function(global) { 20 | 21 | // Used in controllers/pictures.js to save an optimal detail size for this device 22 | Alloy.Globals.logicalDensityFactor = Ti.Platform.displayCaps.logicalDensityFactor; 23 | Alloy.Globals.detailSize = Ti.Platform.displayCaps.platformWidth * Alloy.Globals.logicalDensityFactor; 24 | 25 | // Used in styles/thumbnail.tss 26 | // FIXME: Using 25% will somehow wrap the 4th view 27 | Alloy.Globals.thumbnailSize = Ti.Platform.displayCaps.platformWidth / 4; 28 | 29 | // Used in views/index.xml 30 | // This sample requires Ti SDK 5.1.0 or later 31 | Alloy.Globals.isSupported = isSupported(); 32 | 33 | })(this); 34 | 35 | function isSupported() { 36 | var version = 0; 37 | 38 | _.each(Ti.version.split('.'), function(_version) { 39 | version += _version; 40 | }); 41 | 42 | return version >= 0510; 43 | } 44 | -------------------------------------------------------------------------------- /app/assets/iphone/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/app/assets/iphone/Default-568h@2x.png -------------------------------------------------------------------------------- /app/assets/iphone/Default-667h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/app/assets/iphone/Default-667h@2x.png -------------------------------------------------------------------------------- /app/assets/iphone/Default-Landscape-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/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-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/app/assets/iphone/Default-Portrait-736h@3x.png -------------------------------------------------------------------------------- /app/assets/iphone/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/app/assets/iphone/Default.png -------------------------------------------------------------------------------- /app/assets/iphone/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/app/assets/iphone/Default@2x.png -------------------------------------------------------------------------------- /app/assets/iphone/images/shortcutItemIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/app/assets/iphone/images/shortcutItemIcon.png -------------------------------------------------------------------------------- /app/assets/iphone/images/shortcutItemIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/app/assets/iphone/images/shortcutItemIcon@2x.png -------------------------------------------------------------------------------- /app/assets/iphone/images/shortcutItemIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/app/assets/iphone/images/shortcutItemIcon@3x.png -------------------------------------------------------------------------------- /app/assets/iphone/images/tabIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/app/assets/iphone/images/tabIcon.png -------------------------------------------------------------------------------- /app/assets/iphone/images/tabIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/app/assets/iphone/images/tabIcon@2x.png -------------------------------------------------------------------------------- /app/assets/iphone/images/tabIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/app/assets/iphone/images/tabIcon@3x.png -------------------------------------------------------------------------------- /app/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "brandPrimary": "#CD1625", 4 | "minVersion": "1.7.25" 5 | }, 6 | "env:development": {}, 7 | "env:test": {}, 8 | "env:production": {}, 9 | "os:android": {}, 10 | "os:blackberry": {}, 11 | "os:ios": {}, 12 | "os:mobileweb": {}, 13 | "os:windows": {}, 14 | "dependencies": {} 15 | } -------------------------------------------------------------------------------- /app/controllers/apis.js: -------------------------------------------------------------------------------- 1 | var log = require('log'); 2 | 3 | var appShortcuts; 4 | 5 | /** 6 | * I wrap code that executes on creation in a self-executing function just to 7 | * keep it organised, not to protect global scope like it would in alloy.js 8 | */ 9 | (function constructor(args) { 10 | 11 | // If supported, create an applicationShortcuts instance 12 | if (Ti.UI.iOS.forceTouchSupported) { 13 | appShortcuts = Ti.UI.iOS.createApplicationShortcuts(); 14 | } 15 | 16 | })(arguments[0] || {}); 17 | 18 | /** 19 | * Event handler set in view to list all static shortcuts 20 | */ 21 | function listStaticShortcuts() { 22 | 23 | if (!appShortcuts) { 24 | return alert('This device does not support Force Touch'); 25 | } 26 | 27 | log.args('Ti.UI.iOS.ApplicationShortcuts.listStaticShortcuts', appShortcuts.listStaticShortcuts()); 28 | } 29 | 30 | /** 31 | * Event handler set in view to list all dynamic shortcuts 32 | */ 33 | function listDynamicShortcuts() { 34 | 35 | if (!appShortcuts) { 36 | return alert('This device does not support Force Touch'); 37 | } 38 | 39 | var res = appShortcuts.listDynamicShortcuts(); 40 | 41 | log.args('Ti.UI.iOS.ApplicationShortcuts.listDynamicShortcuts', res); 42 | 43 | // If don't have any, explain how to create it 44 | if (res.length === 0) { 45 | Ti.UI.createAlertDialog({ 46 | title: 'None', 47 | message: 'Open a picture to create a dynamic shortcut.' 48 | }).show(); 49 | } 50 | } 51 | 52 | /** 53 | * Event handler set in view to check if our dynamic shortcut exists 54 | */ 55 | function dynamicShortcutExists() { 56 | 57 | if (!appShortcuts) { 58 | return alert('This device does not support Force Touch'); 59 | } 60 | 61 | var res = appShortcuts.dynamicShortcutExists('details'); 62 | 63 | log.args('Ti.UI.iOS.ApplicationShortcuts.dynamicShortcutExists', 'details', res); 64 | 65 | // If don't have it, explain how to create it 66 | if (!res) { 67 | Ti.UI.createAlertDialog({ 68 | title: 'Does not exist', 69 | message: 'Open a picture to create a dynamic shortcut.' 70 | }).show(); 71 | } 72 | } 73 | 74 | /** 75 | * Event handler set in view to get our dynamic shortcut 76 | */ 77 | function getDynamicShortcut() { 78 | 79 | if (!appShortcuts) { 80 | return alert('This device does not support Force Touch'); 81 | } 82 | 83 | var res = appShortcuts.getDynamicShortcut('details'); 84 | 85 | log.args('Ti.UI.iOS.ApplicationShortcuts.getDynamicShortcut', 'details', res); 86 | 87 | // If don't have it, explain how to create it 88 | if (!res) { 89 | Ti.UI.createAlertDialog({ 90 | title: 'Does not exist', 91 | message: 'Open a picture to create a dynamic shortcut.' 92 | }).show(); 93 | } 94 | } 95 | 96 | /** 97 | * Event handler set in view to remove our dynamic shortcut 98 | */ 99 | function removeDynamicShortcut() { 100 | 101 | if (!appShortcuts) { 102 | return alert('This device does not support Force Touch'); 103 | } 104 | 105 | appShortcuts.removeDynamicShortcut('details'); 106 | 107 | // Explain how to (re)create it 108 | Ti.UI.createAlertDialog({ 109 | title: 'Removed', 110 | message: 'Open a picture to create a new dynamic shortcut.' 111 | }).show(); 112 | } 113 | 114 | /** 115 | * Event handler set in view to remove all dynamic shortcuts 116 | */ 117 | function removeAllDynamicShortcuts() { 118 | 119 | if (!appShortcuts) { 120 | return alert('This device does not support Force Touch'); 121 | } 122 | 123 | appShortcuts.removeAllDynamicShortcuts(); 124 | 125 | // Explain how to create our dynamic shortcut 126 | Ti.UI.createAlertDialog({ 127 | title: 'Removed', 128 | message: 'Open a picture to create a new dynamic shortcut.' 129 | }).show(); 130 | } -------------------------------------------------------------------------------- /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 to the logs 13 | log.on('change', showLogs); 14 | 15 | })(arguments[0] || {}); 16 | 17 | function showLogs() { 18 | $.log.text = log.history; 19 | 20 | $.scrollView.scrollToBottom(); 21 | } 22 | 23 | function clearLogs() { 24 | 25 | // Clear the public property holding the log history 26 | log.history = ''; 27 | 28 | showLogs(); 29 | } 30 | -------------------------------------------------------------------------------- /app/controllers/details.js: -------------------------------------------------------------------------------- 1 | var log = require('log'); 2 | 3 | var appShortcuts; 4 | 5 | /** 6 | * I wrap code that executes on creation in a self-executing function just to 7 | * keep it organised, not to protect global scope like it would in alloy.js 8 | */ 9 | (function constructor(args) { 10 | 11 | // If supported.. 12 | if (Ti.UI.iOS.forceTouchSupported) { 13 | 14 | // Create an applicationShortcuts instance 15 | appShortcuts = Ti.UI.iOS.createApplicationShortcuts(); 16 | 17 | // Remove the details shortcut 18 | removeDetailsShortcut(); 19 | 20 | // Add the details shortcut 21 | var params = { 22 | 23 | // Must be unique to identify it in the shortcutitemclick-listener in list.js 24 | itemtype: 'details', 25 | 26 | title: 'Open last picture', 27 | subtitle: $model.get('time'), 28 | 29 | // A grey-scale icon of 35x35dp 30 | icon: 'images/shortcutItemIcon.png', 31 | 32 | // Or a system-provided icon 33 | // icon: Ti.UI.iOS.SHORTCUT_ICON_TYPE_LOVE, 34 | 35 | // A custom payload 36 | userInfo: { 37 | filename: $model.get('filename') 38 | } 39 | }; 40 | 41 | appShortcuts.addDynamicShortcut(params); 42 | 43 | log.args('Ti.UI.iOS.ApplicationShortcuts.addDynamicShortcut', params); 44 | } 45 | 46 | })(arguments[0] || {}); 47 | 48 | /** 49 | * Helper used in the constructor and deletePicture() to remove the details shortcut 50 | */ 51 | function removeDetailsShortcut() { 52 | 53 | // Other than in list.js we don't need to check the userInfo.filename 54 | // since we will replace it if we're called from the constructor or it 55 | // is the one we created there and now remove in deletePicture() 56 | appShortcuts.removeDynamicShortcut('details'); 57 | 58 | log.args('Ti.UI.iOS.ApplicationShortcuts.removeDynamicShortcut', 'details'); 59 | } 60 | 61 | /** 62 | * Event listener set in the view to delete this picture 63 | */ 64 | function deletePicture() { 65 | 66 | // If supported, remove the details shortcut we created in the constructor 67 | if (appShortcuts) { 68 | removeDetailsShortcut(); 69 | } 70 | 71 | // Delete the actual file 72 | Ti.Filesystem.getFile($model.transform().filepath).deleteFile(); 73 | 74 | $model.destroy(); 75 | 76 | // Use the global method set in list.js to close our window 77 | Alloy.Globals.closeDetails(); 78 | } 79 | 80 | /** 81 | * Event listener set in the view for when our window is closed 82 | */ 83 | function onWindowClose() { 84 | 85 | // Null the global reference to the current detailsWindow so 86 | // that closeDetails() in list.js won't try to close it twice 87 | Alloy.Globals.detailsWindow = null; 88 | } -------------------------------------------------------------------------------- /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 | $.index.open(); 8 | 9 | })(arguments[0] || {}); -------------------------------------------------------------------------------- /app/controllers/list.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 | // We've allready called fetch() in pictures.js and instead of doing 10 | // this again we simply call the data-binding method we've given a name 11 | // in the view manually. 12 | bindData(); 13 | 14 | // For ListView and TableView the previewContext is added to the list 15 | // instead of every single item/row. 16 | if (Ti.UI.iOS.forceTouchSupported) { 17 | 18 | // Get the previewContext and its controller 19 | var previewCtrl = Alloy.createController('preview'); 20 | var previewContext = previewCtrl.getView(); 21 | 22 | // Since all items share the same preview we need to listen to the peek event 23 | previewContext.addEventListener('peek', function(e) { 24 | 25 | // Which contains the item's sectionIndex, itemIndex and optional itemId 26 | // that we can use to look up the data 27 | var model = Alloy.Collections.picture.get(e.itemId); 28 | 29 | // And update the preview accordingly (see previewContext.js) 30 | previewCtrl.setModel(model); 31 | }); 32 | 33 | $.listView.previewContext = previewContext; 34 | } 35 | 36 | })(arguments[0] || {}); 37 | 38 | /** 39 | * Event listener set in the view for when user taps on an item 40 | */ 41 | function onItemclick(e) { 42 | log.args('Ti.UI.ListView:itemclick', e); 43 | 44 | // The item's special itemId property is the model ID we need 45 | // See pictures.js for the implementation of openDetails() 46 | Alloy.Globals.openDetails(e.itemId); 47 | } -------------------------------------------------------------------------------- /app/controllers/pictures.js: -------------------------------------------------------------------------------- 1 | var moment = require('alloy/moment'); 2 | 3 | var log = require('log'); 4 | 5 | // Prevent edge case where app is moved to background while photo gallery is 6 | // showing and then user opens app via the add-shortcut, which will cause error. 7 | var isPhotoGalleryOpen = false; 8 | 9 | var container; 10 | 11 | /** 12 | * I wrap code that executes on creation in a self-executing function just to 13 | * keep it organised, not to protect global scope like it would in alloy.js 14 | */ 15 | (function constructor(args) { 16 | 17 | toggleContainer(); 18 | 19 | // If supported, listen to the event that fires when a app shortcut has been tapped on 20 | if (Ti.UI.iOS.forceTouchSupported) { 21 | Ti.App.iOS.addEventListener('shortcutitemclick', onShortcutitemclick); 22 | } 23 | 24 | // Listen to collection changes 25 | Alloy.Collections.picture.on('fetch destroy change add remove reset', onCollectionChange); 26 | 27 | // Fetch collection models via its adapter (see models/picture.js) 28 | Alloy.Collections.picture.fetch(); 29 | 30 | // Expose global helper methods to close a previous detailsWindow before we open a new one 31 | // Tabs don't have a pop-to-root method we can use for this 32 | Alloy.Globals.openDetails = openDetails; 33 | Alloy.Globals.closeDetails = closeDetails; 34 | 35 | })(arguments[0] || {}); 36 | 37 | /** 38 | * Event listener for taps on app shortcut items 39 | */ 40 | function onShortcutitemclick(e) { 41 | 42 | log.args('Ti.App.iOS:shortcutitemclick', e); 43 | 44 | // The static shortcut we've set in tiapp.xml 45 | if (e.itemtype === 'add') { 46 | addPicture(); 47 | 48 | // The dynamic shortcut we set in details.js 49 | } else if (e.itemtype === 'details') { 50 | 51 | // Get the modelId from the shortcut item payload 52 | var modelId = e.userInfo.filename; 53 | 54 | // Create the detail window and open it via our helper 55 | Alloy.Globals.openDetails(modelId); 56 | } 57 | 58 | // Activate our 59 | $.tab.active = true; 60 | } 61 | 62 | /** 63 | * Event listener for changes to the collection 64 | */ 65 | function onCollectionChange() { 66 | 67 | // Show the placeholder when the collection is empty (!0 === true) 68 | $.placeholder.visible = !Alloy.Collections.picture.length; 69 | } 70 | 71 | function toggleContainer() { 72 | container = (container === 'thumbnails') ? 'list' : 'thumbnails'; 73 | 74 | $.container.removeAllChildren(); 75 | $.container.add(Alloy.createController(container).getView()); 76 | } 77 | 78 | /** 79 | * Event listener set in view for the '+' button 80 | */ 81 | function addPicture() { 82 | 83 | if (isPhotoGalleryOpen) { 84 | return; 85 | } 86 | 87 | isPhotoGalleryOpen = true; 88 | 89 | Ti.Media.openPhotoGallery({ 90 | mediaTypes: [Ti.Media.MEDIA_TYPE_PHOTO], 91 | success: function(e) { 92 | isPhotoGalleryOpen = false; 93 | 94 | // FIXME: https://jira.appcelerator.org/browse/TIMOB-19764 95 | // We need to wait for the photo gallery to close or our preview actions won't work 96 | setTimeout(function() { 97 | 98 | // Create a unique filename 99 | var filename = Ti.Platform.createUUID() + ((Alloy.Globals.logicalDensityFactor === 1) ? '' : '@' + Alloy.Globals.logicalDensityFactor + 'x') + '.jpg'; 100 | 101 | // Create the file under the applicationDataDirectory 102 | var file = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, filename); 103 | 104 | // Write a square version of the selected media to the file 105 | file.write(e.media.imageAsThumbnail(Alloy.Globals.detailSize)); 106 | 107 | // Add the model to the collection 108 | Alloy.Collections.picture.create({ 109 | 110 | // Set the time the picture was added 111 | time: moment().format(), 112 | 113 | // Set the filename (the full path changes between builds) 114 | filename: filename 115 | }); 116 | 117 | }, 500); 118 | }, 119 | cancel: function(e) { 120 | isPhotoGalleryOpen = false; 121 | }, 122 | error: function(e) { 123 | isPhotoGalleryOpen = false; 124 | 125 | alert(e.error || 'Unknown Error'); 126 | } 127 | }); 128 | } 129 | 130 | /** 131 | * Global helper to open a detailsWindow but first close current. 132 | * Tabs don't have a pop-to-root method we can use for this. 133 | */ 134 | function openDetails(modelId) { 135 | 136 | // Get the model if the argument is not already 137 | var model = _.isObject(modelId) ? modelId : Alloy.Collections.picture.get(modelId); 138 | 139 | // The model no longer exist 140 | if (!model) { 141 | return alert('Picture not found: ' + modelId); 142 | } 143 | 144 | // Close the current detailsWindow, if any 145 | Alloy.Globals.closeDetails(); 146 | 147 | // Create a new details controller and reference its view 148 | Alloy.Globals.detailsWindow = Alloy.createController('details', { 149 | $model: model 150 | }).getView(); 151 | 152 | // Open it under the tab 153 | $.tab.openWindow(Alloy.Globals.detailsWindow); 154 | } 155 | 156 | /** 157 | * Global helper to close the current detailsWindow. 158 | */ 159 | function closeDetails() { 160 | 161 | if (!Alloy.Globals.detailsWindow) { 162 | return; 163 | } 164 | 165 | $.tab.closeWindow(Alloy.Globals.detailsWindow); 166 | 167 | Alloy.Globals.detailsWindow = null; 168 | } 169 | -------------------------------------------------------------------------------- /app/controllers/preview.js: -------------------------------------------------------------------------------- 1 | var log = require('log'); 2 | 3 | // Public interface 4 | $.setModel = setModel; 5 | 6 | var model; 7 | 8 | /** 9 | * I wrap code that executes on creation in a self-executing function just to 10 | * keep it organised, not to protect global scope like it would in alloy.js 11 | */ 12 | (function constructor(args) { 13 | 14 | // A model was passed (see thumbnail.js) 15 | if (args.model) { 16 | setModel(args.model); 17 | } 18 | 19 | })(arguments[0] || {}); 20 | 21 | /** 22 | * Public method to update the preview with model data 23 | */ 24 | function setModel(val) { 25 | model = val; 26 | 27 | $.time.text = model.get('time'); 28 | $.picture.image = model.transform().filepath; 29 | } 30 | 31 | /** 32 | * Helper method to create preview actions. 33 | */ 34 | function createActions() { 35 | 36 | var sendAction = Ti.UI.iOS.createPreviewAction({ 37 | title: 'Send per Mail', 38 | 39 | // Wil render a checkmark on the right 40 | style: Ti.UI.iOS.PREVIEW_ACTION_STYLE_SELECTED 41 | }); 42 | 43 | sendAction.addEventListener('click', sendPicture); 44 | 45 | var confirmAction = Ti.UI.iOS.createPreviewAction({ 46 | title: 'Confirm', 47 | 48 | // Will render in red 49 | style: Ti.UI.iOS.PREVIEW_ACTION_STYLE_DESTRUCTIVE 50 | }); 51 | 52 | confirmAction.addEventListener('click', deletePicture); 53 | 54 | var cancelAction = Ti.UI.iOS.createPreviewAction({ 55 | title: 'Cancel', 56 | 57 | // The default style, so not actually required 58 | style: Ti.UI.iOS.PREVIEW_ACTION_STYLE_DEFAULT 59 | }); 60 | 61 | // Actions can be grouped 62 | var deleteActionGroup = Ti.UI.iOS.createPreviewActionGroup({ 63 | title: 'Delete', 64 | style: Ti.UI.iOS.PREVIEW_ACTION_STYLE_DESTRUCTIVE, 65 | actions: [confirmAction, cancelAction] 66 | }); 67 | 68 | return [sendAction, deleteActionGroup]; 69 | } 70 | 71 | /** 72 | * Event listener for the Send per Mail action 73 | */ 74 | function sendPicture() { 75 | var dialog = Ti.UI.createEmailDialog({ 76 | subject: model.get('time') 77 | }); 78 | dialog.addAttachment(Ti.Filesystem.getFile(model.transform().filepath)); 79 | dialog.open(); 80 | } 81 | 82 | /** 83 | * Event listener for the Delete > Confirm action 84 | */ 85 | function deletePicture() { 86 | 87 | // Create an applicationShortcuts instance 88 | var appShortcuts = Ti.UI.iOS.createApplicationShortcuts(); 89 | 90 | // Find the details shortcut 91 | var detailsShortcut = appShortcuts.getDynamicShortcut('details'); 92 | 93 | // If found, check if it's about the picture we want to delete 94 | if (detailsShortcut && detailsShortcut.userInfo.filename === model.get('filename')) { 95 | 96 | // Remove it 97 | appShortcuts.removeDynamicShortcut('details'); 98 | 99 | log.args('Ti.UI.iOS.ApplicationShortcuts.removeDynamicShortcut', 'details'); 100 | } 101 | 102 | // Delete the actual file 103 | Ti.Filesystem.getFile(model.transform().filepath).deleteFile(); 104 | 105 | // Delete the model (which will update all bound views) 106 | model.destroy(); 107 | } 108 | 109 | /** 110 | * Event listener set in the view for when the user peeks. 111 | */ 112 | function onPeek(e) { 113 | log.args('Ti.UI.iOS.PreviewContext:peek', e); 114 | } 115 | 116 | /** 117 | * Event listener set in the view for when the user pops. 118 | */ 119 | function onPop(e) { 120 | log.args('Ti.UI.iOS.PreviewContext:pop', e); 121 | 122 | // Open the details window for this model 123 | // See pictures.js for the implementation of openDetails() 124 | Alloy.Globals.openDetails(model); 125 | } -------------------------------------------------------------------------------- /app/controllers/thumbnail.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 (Ti.UI.iOS.forceTouchSupported) { 8 | 9 | var previewContext = Alloy.createController('preview', { 10 | 11 | // Because we are required within a data-bound view we are passed 12 | // the model as $model. We pass it on to the previewContext. 13 | // Since we create the context for each thumbnail we don't need 14 | // the peek-event like in list.js to update the view. 15 | model: $model 16 | 17 | }).getView(); 18 | 19 | $.imageView.previewContext = previewContext; 20 | } 21 | 22 | })(arguments[0] || {}); 23 | 24 | function onThumbnailClick() { 25 | 26 | // The item's special itemId property is the model ID we need 27 | // See pictures.js for the implementation of openDetails() 28 | Alloy.Globals.openDetails($model); 29 | } -------------------------------------------------------------------------------- /app/controllers/thumbnails.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 | // We've allready called fetch() in pictures.js and instead of doing 8 | // this again we simply call the data-binding method we've given a name 9 | // in the view manually. 10 | bindData(); 11 | 12 | })(arguments[0] || {}); -------------------------------------------------------------------------------- /app/i18n/de/app.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Bild auswählen 4 | Wählen Sie ein neues Bild 5 | -------------------------------------------------------------------------------- /app/i18n/en/app.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Add Picture 4 | Select a new Picture 5 | -------------------------------------------------------------------------------- /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/picture.js: -------------------------------------------------------------------------------- 1 | exports.definition = { 2 | 3 | config: { 4 | 5 | columns: { 6 | time: 'TEXT', 7 | filename: 'TEXT' 8 | }, 9 | 10 | // Use SQLite to persist the data 11 | adapter: { 12 | type: 'sql', 13 | collection_name: 'pictures', 14 | idAttribute: 'filename' 15 | } 16 | }, 17 | 18 | // Extend the default BackBone model 19 | extendModel: function(Model) { 20 | 21 | _.extend(Model.prototype, { 22 | 23 | // Will be used in data-binding instead of attributes 24 | transform: function transform() { 25 | var transformed = this.toJSON(); 26 | 27 | // We can't store the full path because each build to iOS Simulator results in a new path 28 | transformed.filepath = Ti.Filesystem.applicationDataDirectory + this.get('filename'); 29 | 30 | return transformed; 31 | } 32 | }); 33 | 34 | // FIXME: Required views in data-binding don't support transform() but expect __transform 35 | Object.defineProperty(Model.prototype, '__transform', { 36 | 37 | // So we just link that through to transform() 38 | get: Model.prototype.transform 39 | }); 40 | 41 | return Model; 42 | } 43 | }; -------------------------------------------------------------------------------- /app/styles/apis.tss: -------------------------------------------------------------------------------- 1 | "Button": { 2 | top: 20, 3 | width: 300, 4 | height: 40, 5 | 6 | backgroundColor: Alloy.CFG.brandPrimary, 7 | tintColor: '#fff', 8 | borderRadius: 4 9 | } 10 | -------------------------------------------------------------------------------- /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 | 39 | // Style the TabGroup's navigation bar 40 | barColor: Alloy.CFG.brandPrimary, 41 | navTintColor: '#FFF', 42 | translucent: false, 43 | titleAttributes: { 44 | color: '#FFF' 45 | } 46 | } -------------------------------------------------------------------------------- /app/styles/console.tss: -------------------------------------------------------------------------------- 1 | 'ScrollView': { 2 | contentWidth: Ti.UI.FILL, 3 | contentHeight: Ti.UI.SIZE 4 | } 5 | 6 | 'Label': { 7 | top: 10, 8 | right: 10, 9 | left: 10, 10 | 11 | width: Ti.UI.FILL, 12 | height: Ti.UI.SIZE, 13 | 14 | font: { 15 | fontFamily: 'Courier New' 16 | } 17 | } -------------------------------------------------------------------------------- /app/styles/index.tss: -------------------------------------------------------------------------------- 1 | 'Label': { 2 | 3 | // Add some margin left and right 4 | left: 20, 5 | right: 20, 6 | 7 | textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER 8 | } -------------------------------------------------------------------------------- /app/styles/list.tss: -------------------------------------------------------------------------------- 1 | 'ListView': { 2 | separatorInsets: { 3 | left: 10 4 | } 5 | } 6 | 7 | 'ItemTemplate': { 8 | height: 95 9 | } 10 | 11 | 'ImageView': { 12 | width: 75, 13 | height: 75, 14 | 15 | left: 10, 16 | top: 10 17 | } 18 | 19 | 'Label': { 20 | left: 95 21 | } -------------------------------------------------------------------------------- /app/styles/pictures.tss: -------------------------------------------------------------------------------- 1 | '#placeholder': { 2 | color: '#aaa' 3 | } -------------------------------------------------------------------------------- /app/styles/preview.tss: -------------------------------------------------------------------------------- 1 | 'View': { 2 | height: Ti.UI.SIZE, 3 | 4 | layout: 'vertical', 5 | backgroundColor: '#fff' 6 | } 7 | 8 | 'Label': { 9 | height: 55, 10 | 11 | color: '#333', 12 | font: { 13 | fontWeight: 'bold' 14 | } 15 | } -------------------------------------------------------------------------------- /app/styles/thumbnail.tss: -------------------------------------------------------------------------------- 1 | 'ImageView': { 2 | 3 | // Populate the horizontal layout from the top left 4 | top: 0, 5 | left: 0, 6 | 7 | // Set to 1/4th of our screen width in alloy.js 8 | // Using 25% will somehow wrap the 4th view 9 | width: Alloy.Globals.thumbnailSize, 10 | height: Alloy.Globals.thumbnailSize 11 | } -------------------------------------------------------------------------------- /app/styles/thumbnails.tss: -------------------------------------------------------------------------------- 1 | 'ScrollView': { 2 | layout: 'horizontal', 3 | 4 | // Trigger vertical scroll on iOS by setting contentWidth to that of its 5 | // parent while allowing contentHeight to size to its contents. 6 | contentWidth: Ti.UI.FILL, 7 | contentHeight: Ti.UI.SIZE 8 | } -------------------------------------------------------------------------------- /app/views/apis.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/views/console.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/views/details.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/views/index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/views/list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/views/pictures.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/views/preview.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/views/thumbnail.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/views/thumbnails.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/docs/preview.png -------------------------------------------------------------------------------- /docs/screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/docs/screencast.gif -------------------------------------------------------------------------------- /docs/shortcuts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcelerator-developer-relations/appc-sample-3dtouch/9008bad9057f55fe829a83a1772621a3f2659fcf/docs/shortcuts.png -------------------------------------------------------------------------------- /manifest: -------------------------------------------------------------------------------- 1 | #appname:3D Touch 2 | #publisher:Appcelerator 3 | #url:http://appcelerator.com 4 | #image:appicon.png 5 | #appid:com.appcelerator.sample.3dtouch 6 | #desc:undefined 7 | #type:mobile 8 | #guid:2bf3b591-b090-43aa-9ddd-1fbfe43eb85f 9 | -------------------------------------------------------------------------------- /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(finished) { 16 | if(cli.argv['shallow'] === '') { 17 | logger.info('Not cleaning the Resources directory'); 18 | finished(); 19 | return; 20 | } 21 | var appDir = path.join(cli.argv['project-dir'], 'app'); 22 | if (!afs.exists(appDir)) { 23 | logger.debug('Project not an Alloy app, exiting.'); 24 | finished(); 25 | return; 26 | } 27 | 28 | var resourcesDir = path.join(cli.argv['project-dir'], 'Resources'); 29 | if (!afs.exists(resourcesDir)) { 30 | logger.debug('Resources directory does not exist.'); 31 | finished(); 32 | return; 33 | } 34 | rmdir(resourcesDir, fs, path, logger); 35 | logger.debug('Resources directory of %s has been emptied', appDir.cyan); 36 | finished(); 37 | } 38 | 39 | cli.addHook('clean.post', function (build, finished) { 40 | run(finished); 41 | }); 42 | 43 | }; 44 | 45 | function rmdir(dirPath, fs, path, logger, removeSelf) { 46 | var files; 47 | try { 48 | files = fs.readdirSync(dirPath); 49 | } 50 | catch(e) { 51 | return; 52 | } 53 | if (files.length > 0) { 54 | for (var i = 0; i < files.length; i++) { 55 | var filePath = path.join(dirPath, files[i]); 56 | if (fs.statSync(filePath).isFile()) { 57 | fs.unlinkSync(filePath); 58 | } else { 59 | rmdir(filePath, fs, path, logger, true); 60 | } 61 | } 62 | } 63 | if (removeSelf) { 64 | fs.rmdirSync(dirPath); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /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.3dtouch 4 | 3D Touch 5 | 1.0.0 6 | Appcelerator 7 | http://appcelerator.com 8 | undefined 9 | 2015 by Appcelerator 10 | appicon.png 11 | false 12 | false 13 | true 14 | 11111111-1111-1111-1111-111111111111 15 | dp 16 | 17 | 18 | 19 | 9.1 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | NSPhotoLibraryUsageDescription 29 | Can we access your photo library? 30 | NSCameraUsageDescription 31 | Can we access your camera? 32 | 33 | 34 | UIApplicationShortcutItems 35 | 36 | 37 | 38 | 39 | UIApplicationShortcutItemIconType 40 | UIApplicationShortcutIconTypeAdd 41 | 42 | 48 | 49 | 53 | UIApplicationShortcutItemTitle 54 | add_title 55 | 56 | 60 | UIApplicationShortcutItemSubtitle 61 | add_subtitle 62 | 63 | 64 | UIApplicationShortcutItemType 65 | add 66 | 67 | 68 | UIApplicationShortcutItemUserInfo 69 | 70 | myCustomKey 71 | myCustomValue 72 | 73 | 74 | 75 | 76 | 77 | UIStatusBarStyle 78 | UIStatusBarStyleLightContent 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | true 87 | true 88 | 89 | default 90 | 91 | 92 | 93 | false 94 | false 95 | true 96 | false 97 | 98 | 6.0.0.GA 99 | 100 | ti.alloy 101 | 102 | 103 | --------------------------------------------------------------------------------