├── .gitignore ├── .project ├── .settings └── com.aptana.editor.common.prefs ├── LICENSE ├── README.md ├── android ├── build.properties ├── build.xml ├── dist │ └── androidcollectionview.jar ├── example │ ├── alloy │ │ ├── config.json │ │ ├── controllers │ │ │ └── index.js │ │ ├── lib │ │ │ └── CollectionView.js │ │ ├── styles │ │ │ └── index.tss │ │ ├── views │ │ │ └── index.xml │ │ └── widgets │ │ │ └── nl.fokkezb.pullToRefresh │ │ │ ├── README.md │ │ │ ├── controllers │ │ │ └── widget.js │ │ │ ├── views │ │ │ └── widget.xml │ │ │ └── widget.json │ ├── app.js │ ├── images │ │ ├── TrashIcon.png │ │ ├── TrashIcon@2x.png │ │ ├── TrashIconSelected.png │ │ ├── TrashIconSelected@2x.png │ │ ├── UploadIcon.png │ │ ├── UploadIcon@2x.png │ │ ├── UploadIconSelected.png │ │ └── UploadIconSelected@2x.png │ └── lib │ │ └── CollectionView.js ├── java-sources.txt ├── manifest ├── platform │ ├── README │ └── android │ │ └── res │ │ ├── layout │ │ ├── swipe_refresh.xml │ │ ├── ticollection_ui_list_header_or_footer.xml │ │ └── titanium_ui_collection_item.xml │ │ └── values │ │ └── colors.xml ├── src │ └── de │ │ └── marcelpociot │ │ └── collectionview │ │ ├── AndroidcollectionviewModule.java │ │ ├── BakedBezierInterpolator.java │ │ ├── BaseCollectionViewItem.java │ │ ├── CollectionItem.java │ │ ├── CollectionItemProxy.java │ │ ├── CollectionSectionProxy.java │ │ ├── CollectionSwipeRefreshLayout.java │ │ ├── CollectionView.java │ │ ├── CollectionViewProxy.java │ │ ├── CollectionViewTemplate.java │ │ ├── DefaultCollectionViewTemplate.java │ │ ├── ExampleProxy.java │ │ ├── SwipeProgressBar.java │ │ ├── SwipeRefreshLayout.java │ │ ├── TiGridView.java │ │ └── ViewItem.java └── timodule.xml ├── dist ├── de.marcelpociot.collectionview-android-1.0.0.zip ├── de.marcelpociot.collectionview-android-1.1.1.zip ├── de.marcelpociot.collectionview-android-1.2.0.zip ├── de.marcelpociot.collectionview-android-1.3.0.zip ├── de.marcelpociot.collectionview-android-1.3.1.zip ├── de.marcelpociot.collectionview-iphone-1.0.0.zip ├── de.marcelpociot.collectionview-iphone-1.1.0.zip ├── de.marcelpociot.collectionview-iphone-1.1.1.zip ├── de.marcelpociot.collectionview-iphone-1.1.2.zip ├── de.marcelpociot.collectionview-iphone-1.2.0.zip ├── de.marcelpociot.collectionview-iphone-1.3.0.zip ├── de.marcelpociot.collectionview-iphone-1.3.1.zip ├── de.marcelpociot.collectionview-iphone-1.4.0.zip ├── de.marcelpociot.collectionview-iphone-1.4.1.zip ├── de.marcelpociot.collectionview-iphone-1.4.2.zip ├── ti.collectionview-android-2.0.0.zip ├── ti.collectionview-android-2.0.1.zip ├── ti.collectionview-android-3.0.0.zip └── ti.collectionview-iphone-2.0.1.zip ├── documentation ├── contextmenu.gif ├── grid.png └── waterfall.png ├── example ├── SampleProjectAlloy │ └── app │ │ ├── config.json │ │ ├── controllers │ │ └── index.js │ │ ├── lib │ │ └── CollectionView.js │ │ ├── styles │ │ └── index.tss │ │ ├── views │ │ └── index.xml │ │ └── widgets │ │ └── nl.fokkezb.pullToRefresh │ │ ├── README.md │ │ ├── controllers │ │ └── widget.js │ │ ├── views │ │ └── widget.xml │ │ └── widget.json └── SampleProjectClassic │ └── app.js ├── ios ├── Classes │ ├── CHTCollectionViewWaterfallLayout │ │ ├── CHTCollectionViewWaterfallLayout.h │ │ └── CHTCollectionViewWaterfallLayout.m │ ├── TiCollectionviewCollectionItem.h │ ├── TiCollectionviewCollectionItem.m │ ├── TiCollectionviewCollectionItemProxy.h │ ├── TiCollectionviewCollectionItemProxy.m │ ├── TiCollectionviewCollectionSectionProxy.h │ ├── TiCollectionviewCollectionSectionProxy.m │ ├── TiCollectionviewCollectionView.h │ ├── TiCollectionviewCollectionView.m │ ├── TiCollectionviewCollectionViewProxy.h │ ├── TiCollectionviewCollectionViewProxy.m │ ├── TiCollectionviewHeaderFooterReusableView.h │ ├── TiCollectionviewHeaderFooterReusableView.m │ ├── TiCollectionviewModule.h │ ├── TiCollectionviewModule.m │ ├── TiCollectionviewModuleAssets.h │ ├── TiCollectionviewModuleAssets.m │ ├── TiSearchDisplayController.h │ └── TiSearchDisplayController.m ├── TiCollectionView.xcodeproj │ └── project.pbxproj ├── TiCollectionViewWorkspace.xcworkspace │ └── contents.xcworkspacedata ├── TiCollectionview_Prefix.pch ├── manifest ├── metadata.json ├── module.xcconfig ├── timodule.xml └── titanium.xcconfig └── lib └── CollectionView.js /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | bin 3 | build 4 | *.class 5 | *.pyo 6 | .DS_Store 7 | Index 8 | # Xcode 9 | *.pbxuser 10 | *.mode1v3 11 | *.mode2v3 12 | *.perspectivev3 13 | *.xcuserstate 14 | project.xcworkspace/ 15 | xcuserdata/ 16 | xcshareddata 17 | 18 | # Generated files 19 | *.o 20 | *.pyc 21 | 22 | 23 | #Python modules 24 | #MANIFEST 25 | build/ 26 | 27 | # Backup files 28 | *~.nib 29 | 30 | #*/titanium.xcconfig 31 | */build.py 32 | android/dist/ 33 | android/build.properties 34 | android/libs/ 35 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | TiCollectionView 4 | 5 | 6 | 7 | 8 | 9 | com.appcelerator.titanium.core.builder 10 | 11 | 12 | 13 | 14 | com.aptana.ide.core.unifiedBuilder 15 | 16 | 17 | 18 | 19 | 20 | com.appcelerator.titanium.mobile.module.nature 21 | com.aptana.projects.webnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/com.aptana.editor.common.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | selectUserAgents=com.appcelerator.titanium.mobile.module.nature\:iphone 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014-2015 Marcel Pociot 2 | Copyright 2015-Present Nuno Costa 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ti.CollectionView 2 | 3 | [![gittio](https://img.shields.io/badge/gittio-2.0.0-00B4CC.svg)](http://gitt.io/component/ti.collectionview) 4 | [![License](http://img.shields.io/badge/license-MIT-orange.svg)](http://mit-license.org) 5 | [![Issues](https://img.shields.io/github/issues/nuno/TiCollectionView.svg)](https://github.com/nuno/TiCollectionView/issues) 6 | 7 | ## ⚠️ IMPORTANT TO NOTE: 8 | Since version 2.0 the module id is ```ti.collectionview``` 9 | and NOT ```de.marcelpociot.CollectionView``` 10 | 11 | The original work was done by [Marcel Pociot](https://github.com/mpociot). He passed the module 12 | to me for me continue his GREAT work, he had not the time to continue! I will need all the help 13 | possible to keep that module great! 14 | 15 | ## Overview 16 | 17 | This module allows you to use a collection / grid view with the Appcelerator Titanium SDK. 18 | 19 | It uses the Titanium `ItemTemplate` objects for the best performance. 20 | 21 | ### Grid layout 22 | ![example](documentation/grid.png) 23 | 24 | ### Waterfall layout 25 | ![example](documentation/waterfall.png) 26 | 27 | ### Context menu 28 | ![example](documentation/contextmenu.gif) 29 | 30 | ## Installation 31 | ### Get it [![gitTio](http://gitt.io/badge.png)](http://gitt.io/component/ti.collectionview) 32 | Download the latest distribution ZIP-file and consult the [Titanium Documentation](http://docs.appcelerator.com/titanium/latest/#!/guide/Using_a_Module) on how install it, or simply use the [gitTio CLI](http://gitt.io/cli): 33 | 34 | `$ gittio install ti.collectionview` 35 | 36 | ### Important notes for Android 37 | In order to make this module work for Android, you need to use the provided "[CollectionView.js](lib/CollectionView.js)" CommonJS library. 38 | 39 | ## API 40 | 41 | This module uses the [Ti.UI.ListView API](http://docs.appcelerator.com/titanium/latest/#!/api/Titanium.UI.ListView). 42 | 43 | ## Additional parameters 44 | 45 | The ListView API gets extended by these custom parameters: 46 | 47 | * `layout` _(LAYOUT_WATERFALL | LAYOUT_GRID)_ - sets the layout to use for the collection view. You can select between the waterfall layout (like Pinterest) or the standard grid layout which is the default value. 48 | 49 | ### Waterfall layout specific configuration 50 | 51 | * `columnCount` _(Number)_ The number of columns to use. Default: 3 52 | * `minimumColumnSpacing` _(Number)_ The minimum spacing between each columns 53 | * `minimumInteritemSpacing` _(Number)_ The minimum spacing between each items (vertically) 54 | * `renderDirection` _(DIRECTION_LEFT_TO_RIGHT | DIRECTION_RIGHT_TO_LEFT | DIRECTION_SHORTEST_FIRST)_ The render direction to use. Default: DIRECTION_LEFT_TO_RIGHT 55 | 56 | 57 | ### iOS specific configuration (Creation Only!) 58 | * `scrollDirection` _(SCROLL_HORIZONTAL | SCROLL_VERTICAL )_ Default: SCROLL_VERTICAL 59 | * `showContextMenu` _Boolean_ - Should we show a contextual menu on longpress? Default: NO 60 | * `contextMenuStrokeColor` _Color_ - The stroke color of the context Menu indicator 61 | * `contextMenuItems` _Array_ - An array of context menu items each item is an object with the following attributes 62 | * `tintColor` _Color_ - The color used to tint the icon 63 | * `selected` _Image_ - The image for the selected state 64 | * `unselected` _Image_ - The image for the unselected state 65 | 66 | #### Events 67 | * `contextMenuClick` - Fired when a context menu item gets selected 68 | * `index` _Number_ - The selected menu item index 69 | * `itemIndex` _Number_ - The selected collection view item index 70 | * `sectionIndex` _Number_ - The selected collection view section index 71 | 72 | --- 73 | 74 | ### Android specific configuration 75 | 76 | * `columnWidth` _(Number)_ - Defines the width of each column. The Android module will fit as many columns in a row as possible 77 | * `verticalSpacing` _(Number)_ - Defines the vertical column spacing 78 | * `horizontalSpacing` _(Number)_ - Defines the horizontal column spacing 79 | 80 | ## Usage 81 | 82 | Alloy: 83 | ```xml 84 | 85 | 86 | 87 | 88 | 89 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | ``` 105 | 106 | Vanilla JS: 107 | 108 | ```js 109 | var collectionView = require("ti.collectionview"); 110 | 111 | var win = Ti.UI.createWindow({backgroundColor: 'white'}); 112 | 113 | // Create a custom template that displays an image on the left, 114 | // then a title next to it with a subtitle below it. 115 | var myTemplate = { 116 | childTemplates: [ 117 | { // Title 118 | type: 'Ti.UI.Label', // Use a label for the title 119 | bindId: 'info', // Maps to a custom info property of the item data 120 | properties: { // Sets the label properties 121 | color: 'black', 122 | font: { fontFamily:'Arial', fontSize: '20dp', fontWeight:'bold' }, 123 | left: '60dp', top: 0, 124 | } 125 | }, 126 | { // Subtitle 127 | type: 'Ti.UI.Label', // Use a label for the subtitle 128 | bindId: 'es_info', // Maps to a custom es_info property of the item data 129 | properties: { // Sets the label properties 130 | color: 'gray', 131 | font: { fontFamily:'Arial', fontSize: '14dp' }, 132 | left: '60dp', top: '25dp', 133 | } 134 | } 135 | ] 136 | }; 137 | 138 | var listView = require("ti.collectionview").createCollectionView({ 139 | backgroundColor: "white", 140 | top: 0, 141 | left: 0, 142 | width: Ti.UI.FILL, 143 | height: Ti.UI.FILL, 144 | // Maps myTemplate dictionary to 'template' string 145 | templates: { 'template': myTemplate }, 146 | // Use 'template', that is, the myTemplate dict created earlier 147 | // for all items as long as the template property is not defined for an item. 148 | defaultItemTemplate: 'template', 149 | 150 | // Context menu options 151 | showContextMenu : true, 152 | contextMenuStrokeColor : "red", 153 | contextMenuItems : [ 154 | { 155 | tintColor: "red", 156 | selected: "/images/UploadIconSelected.png", 157 | unselected:"/images/UploadIcon.png", 158 | }, 159 | { 160 | tintColor: "red", 161 | selected: "/images/TrashIconSelected.png", 162 | unselected:"/images/TrashIcon.png", 163 | } 164 | ], 165 | 166 | // ANDROID ONLY 167 | columnWidth: 150, 168 | verticalSpacing: 10, 169 | horizontalSpacing: 10 170 | }); 171 | var sections = []; 172 | 173 | var fruitSection = collectionView.createCollectionSection({ headerTitle: 'Fruits / Frutas'}); 174 | var fruitDataSet = [ 175 | // the text property of info maps to the text property of the title label 176 | // the text property of es_info maps to text property of the subtitle label 177 | // the image property of pic maps to the image property of the image view 178 | { info: {text: 'Apple'}, es_info: {text: 'Manzana'}, properties: {height:150,width:150}}, 179 | { info: {text: 'Apple'}, es_info: {text: 'Manzana'}, properties: {height:150,width:150}}, 180 | ]; 181 | fruitSection.setItems(fruitDataSet); 182 | sections.push(fruitSection); 183 | 184 | listView.setSections(sections); 185 | win.add(listView); 186 | win.open(); 187 | ``` 188 | 189 | 190 | ## Changelog 191 | 192 | All Releases are located in the **"/dist/*"** folder: https://github.com/nuno/TiCollectionView/tree/master/dist 193 | 194 | * v2.0.0 195 | * _iOS only_ Move project to ARC, fix all warnings and compile errors 196 | * _iOS only_ Fix main-thread execution 197 | * _iOS only_ Rebuild with 6.0.3.GA 198 | * _iOS only_ Rename project to "Ti.CollectionView" 199 | * _iOS only_ Support for the "allowsMultipleSelection" property 200 | * _iOS only_ Support for the "contentOffset" property 201 | * _iOS only_ Support for the "scrollstart" and "scrollend" event 202 | * v1.4.1 203 | * _iOS only_ Fix pull-to-refresh 204 | * v1.4.0 205 | * _iOS only_ Added support for horizontal scrolling 206 | * v1.3.0 207 | * _iOS only_ Added support for header and footer views 208 | * v1.2.0 209 | * _iOS only_ Added support for longtouch context menus 210 | * v1.1.1 211 | * Added support for Pull To Refresh 212 | * v1.1.0 213 | * Added waterfall layout for iOS 214 | * v1.0.0 215 | * Initial release with Android support added 216 | 217 | 218 | ## License 219 | 220 | Copyright 2014-2015 Marcel Pociot 221 | 222 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 223 | 224 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 225 | 226 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 227 | 228 | ## Contextmenu License 229 | Copyright 2014 Brandon McQuilkin 230 | 231 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 232 | 233 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 234 | 235 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 236 | -------------------------------------------------------------------------------- /android/build.properties: -------------------------------------------------------------------------------- 1 | titanium.platform=/Users/marcelpociot/Library/Application Support/Titanium/mobilesdk/osx/3.4.1.GA/android 2 | android.platform=/Users/marcelpociot/Library/android-sdk-macosx/platforms/android-16 3 | google.apis=/Users/marcelpociot/Library/android-sdk-macosx/add-ons/addon-google_apis-google-16 4 | android.ndk=/Users/marcelpociot/Downloads/android-ndk-r9d -------------------------------------------------------------------------------- /android/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ant build script for Titanium Android module androidcollectionview 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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /android/dist/androidcollectionview.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/android/dist/androidcollectionview.jar -------------------------------------------------------------------------------- /android/example/alloy/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": {}, 3 | "env:development": {}, 4 | "env:test": {}, 5 | "env:production": {}, 6 | "os:android": {}, 7 | "os:blackberry": {}, 8 | "os:ios": {}, 9 | "os:mobileweb": {}, 10 | "dependencies": { 11 | "nl.fokkezb.pullToRefresh": "2.0.1" 12 | } 13 | } -------------------------------------------------------------------------------- /android/example/alloy/controllers/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $.listView.addEventListener("pull", function(e){ 5 | Ti.API.info(e); 6 | }); 7 | 8 | $.listView.addEventListener("pullend", function(e){ 9 | Ti.API.info(e); 10 | }); 11 | 12 | function myRefresher(e) { 13 | 14 | Ti.API.info("myRefresher"); 15 | 16 | // fake a remote fetch 17 | setTimeout(function(){ 18 | Ti.API.info("myRefresher callback"); 19 | e.hide(); 20 | }, 3000); 21 | } 22 | 23 | // init 24 | $.ptr.refresh(); 25 | 26 | $.win.open(); -------------------------------------------------------------------------------- /android/example/alloy/lib/CollectionView.js: -------------------------------------------------------------------------------- 1 | function createCollectionView(options) { 2 | if( OS_IOS ) 3 | { 4 | return require("ti.collectionview").createCollectionView(options); 5 | } 6 | var templates = options.templates; 7 | for (var binding in templates) { 8 | var currentTemplate = templates[binding]; 9 | //process template 10 | processTemplate(currentTemplate); 11 | //process child templates 12 | processChildTemplates(currentTemplate); 13 | } 14 | Ti.API.info( JSON.stringify(options) ); 15 | var listView = require("ti.collectionview").createCollectionView(options); 16 | 17 | return listView; 18 | } 19 | 20 | //Create ListItemProxy, add events, then store it in 'tiProxy' property 21 | function processTemplate(properties) { 22 | var cellProxy = require("ti.collectionview").createCollectionItem(); 23 | properties.tiProxy = cellProxy; 24 | var events = properties.events; 25 | addEventListeners(events, cellProxy); 26 | } 27 | 28 | //Recursive function that process childTemplates and append corresponding proxies to 29 | //property 'tiProxy'. I.e: type: "Titanium.UI.Label" -> tiProxy: LabelProxy object 30 | function processChildTemplates(properties) { 31 | if (!properties.hasOwnProperty('childTemplates')) return; 32 | 33 | var childProperties = properties.childTemplates; 34 | if (childProperties === void 0 || childProperties === null) return; 35 | 36 | for (var i = 0; i < childProperties.length; i++) { 37 | var child = childProperties[i]; 38 | var proxyType = child.type; 39 | if (proxyType !== void 0) { 40 | var creationProperties = child.properties; 41 | var creationFunction = lookup(proxyType); 42 | var childProxy; 43 | //create the proxy 44 | if (creationProperties !== void 0) { 45 | childProxy = creationFunction(creationProperties); 46 | } else { 47 | childProxy = creationFunction(); 48 | } 49 | //add event listeners 50 | var events = child.events; 51 | addEventListeners(events, childProxy); 52 | //append proxy to tiProxy property 53 | child.tiProxy = childProxy; 54 | } 55 | 56 | processChildTemplates(child); 57 | 58 | } 59 | 60 | 61 | } 62 | 63 | //add event listeners 64 | function addEventListeners(events, proxy) { 65 | if (events !== void 0) { 66 | for (var eventName in events) { 67 | proxy.addEventListener(eventName, events[eventName]); 68 | } 69 | } 70 | } 71 | 72 | //convert name of UI elements into a constructor function. 73 | //I.e: lookup("Titanium.UI.Label") returns Titanium.UI.createLabel function 74 | function lookup(name) { 75 | var lastDotIndex = name.lastIndexOf('.'); 76 | var proxy = eval(name.substring(0, lastDotIndex)); 77 | if (typeof(proxy) == undefined) return; 78 | 79 | var proxyName = name.slice(lastDotIndex + 1); 80 | return proxy['create' + proxyName]; 81 | } 82 | 83 | exports.createCollectionView = createCollectionView; 84 | -------------------------------------------------------------------------------- /android/example/alloy/styles/index.tss: -------------------------------------------------------------------------------- 1 | 2 | "Label": { 3 | width: Ti.UI.SIZE, 4 | height: Ti.UI.SIZE, 5 | color: "#000", 6 | font: { 7 | fontSize: 12 8 | } 9 | } 10 | 11 | "ListSection":{ 12 | itemSpacing: 40, 13 | lineSpacing: 14 14 | } 15 | -------------------------------------------------------------------------------- /android/example/alloy/views/index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /android/example/alloy/widgets/nl.fokkezb.pullToRefresh/README.md: -------------------------------------------------------------------------------- 1 | # Alloy *Pull to Refresh* Widget 2 | 3 | You can find the README at [https://github.com/fokkezb/nl.fokkezb.drawer](https://github.com/fokkezb/nl.fokkezb.pullToRefresh) 4 | 5 | ## License 6 | 7 |
 8 | Copyright 2013-2014 Fokke Zandbergen
 9 | 
10 | Licensed under the Apache License, Version 2.0 (the "License");
11 | you may not use this file except in compliance with the License.
12 | You may obtain a copy of the License at
13 | 
14 |    http://www.apache.org/licenses/LICENSE-2.0
15 | 
16 | Unless required by applicable law or agreed to in writing, software
17 | distributed under the License is distributed on an "AS IS" BASIS,
18 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | See the License for the specific language governing permissions and
20 | limitations under the License.
21 | 
-------------------------------------------------------------------------------- /android/example/alloy/widgets/nl.fokkezb.pullToRefresh/controllers/widget.js: -------------------------------------------------------------------------------- 1 | var moment = require('alloy/moment'); 2 | 3 | var refreshControl; 4 | 5 | var isRefreshing = false; 6 | $.refresh = refresh; 7 | 8 | $.hide = hide; 9 | $.show = show; 10 | 11 | (function constructor(args) { 12 | 13 | if (!OS_IOS && !OS_ANDROID) { 14 | console.warn('[pullToRefresh] only supports iOS and Android.'); 15 | return; 16 | } 17 | 18 | if (!_.isArray(args.children) || !_.contains(['Ti.UI.ListView', 'Ti.UI.TableView','de.marcelpociot.CollectionView', 'ti.collectionview'], args.children[0].apiName)) { 19 | console.error('[pullToRefresh] is missing required Ti.UI.ListView or Ti.UI.TableView or de.marcelpociot.CollectionView, or ti.collectionview as first child element.'); 20 | return; 21 | } 22 | 23 | 24 | var list = args.children[0]; 25 | delete args.children; 26 | 27 | _.extend($, args); 28 | 29 | if (OS_IOS) { 30 | refreshControl = Ti.UI.createRefreshControl(); 31 | var attr = Titanium.UI.iOS.createAttributedString({ 32 | text: "Pull to Refresh.", 33 | attributes: [ 34 | 35 | ] 36 | }); 37 | refreshControl.setTitle(attr); 38 | refreshControl.addEventListener('refreshstart', onRefreshstart); 39 | 40 | list.refreshControl = refreshControl; 41 | 42 | $.addTopLevelView(list); 43 | 44 | } else if (OS_ANDROID) { 45 | refreshControl = require('com.rkam.swiperefreshlayout').createSwipeRefresh({ 46 | view: list 47 | }); 48 | 49 | refreshControl.addEventListener('refreshing', onRefreshstart); 50 | 51 | $.addTopLevelView(refreshControl); 52 | } 53 | 54 | })(arguments[0] || {}); 55 | 56 | function refresh() { 57 | if (!isRefreshing) { 58 | isRefreshing = true; 59 | show(); 60 | 61 | onRefreshstart(); 62 | } 63 | } 64 | 65 | function hide() { 66 | isRefreshing = false; 67 | if (OS_IOS) { 68 | var attr = Titanium.UI.iOS.createAttributedString({ 69 | text: "Last Updated: " + new moment().format("MM-DD-YYYY hh:mm:ss a"), 70 | attributes: [ 71 | 72 | ] 73 | }); 74 | refreshControl.setTitle(attr); 75 | refreshControl.endRefreshing(); 76 | 77 | } else if (OS_ANDROID) { 78 | refreshControl.setRefreshing(false); 79 | } 80 | } 81 | 82 | function show() { 83 | 84 | if (OS_IOS) { 85 | refreshControl.beginRefreshing(); 86 | } else if (OS_ANDROID) { 87 | refreshControl.setRefreshing(true); 88 | } 89 | } 90 | 91 | function onRefreshstart() { 92 | 93 | $.trigger('release', { 94 | source: $, 95 | hide: hide 96 | }); 97 | } 98 | -------------------------------------------------------------------------------- /android/example/alloy/widgets/nl.fokkezb.pullToRefresh/views/widget.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/example/alloy/widgets/nl.fokkezb.pullToRefresh/widget.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "nl.fokkezb.pullToRefresh", 3 | "name": "Pull to Refresh Widget", 4 | "description" : "Uniform wrapper for iOS RefreshControl and Android SwipeRefreshLayout.", 5 | "author": "Fokke Zandbergen", 6 | "version": "2.0.1", 7 | "copyright":"Copyright (c) 2013-2015", 8 | "license":"Apache 2.0", 9 | "min-alloy-version": "1.5", 10 | "min-titanium-version":"3.4", 11 | "tags":"tableview,headerpullview,pulltorefresh,swiperefreshlayout,listview,pullview,refreshcontrol", 12 | "platforms":"ios,android" 13 | } -------------------------------------------------------------------------------- /android/example/app.js: -------------------------------------------------------------------------------- 1 | var collectionView = require("ti.collectionview"); 2 | 3 | var win = Ti.UI.createWindow({ 4 | backgroundColor: 'white' 5 | }); 6 | 7 | // Create a custom template that displays an image on the left, 8 | // then a title next to it with a subtitle below it. 9 | var myTemplate = { 10 | childTemplates: [ 11 | { // Title 12 | type: 'Ti.UI.Label', // Use a label for the title 13 | bindId: 'info', // Maps to a custom info property of the item data 14 | properties: { // Sets the label properties 15 | color: 'black', 16 | font: { fontFamily:'Arial', fontSize: '20dp', fontWeight:'bold' }, 17 | left: '60dp', top: 0, 18 | } 19 | }, 20 | { // Subtitle 21 | type: 'Ti.UI.Label', // Use a label for the subtitle 22 | bindId: 'es_info', // Maps to a custom es_info property of the item data 23 | properties: { // Sets the label properties 24 | color: 'gray', 25 | font: { fontFamily:'Arial', fontSize: '14dp' }, 26 | left: '60dp', top: '25dp', 27 | } 28 | } 29 | ] 30 | }; 31 | 32 | var listView = require("CollectionView").createCollectionView({ 33 | backgroundColor: "white", 34 | top: 0, 35 | left: 0, 36 | width: Ti.UI.FILL, 37 | height: Ti.UI.FILL, 38 | // Maps myTemplate dictionary to 'template' string 39 | templates: { 'template': myTemplate }, 40 | // Use 'template', that is, the myTemplate dict created earlier 41 | // for all items as long as the template property is not defined for an item. 42 | defaultItemTemplate: 'template', 43 | 44 | // Context menu options 45 | showContextMenu : true, 46 | contextMenuStrokeColor : "red", 47 | contextMenuItems : [ 48 | { 49 | tintColor: "red", 50 | selected: "/images/UploadIconSelected.png", 51 | unselected:"/images/UploadIcon.png", 52 | }, 53 | { 54 | tintColor: "red", 55 | selected: "/images/TrashIconSelected.png", 56 | unselected:"/images/TrashIcon.png", 57 | } 58 | ], 59 | 60 | // ANDROID ONLY 61 | columnWidth: 150, 62 | verticalSpacing: 10, 63 | horizontalSpacing: 10 64 | }); 65 | 66 | listView.addEventListener("contextMenuClick", function(e) 67 | { 68 | alert( "You clicked on menu item " + e.index + " - CollectionView item " + e.itemIndex ); 69 | }); 70 | 71 | var sections = []; 72 | 73 | var fruitSection = collectionView.createCollectionSection({ headerTitle: 'Fruits / Frutas'}); 74 | var fruitDataSet = [ 75 | // the text property of info maps to the text property of the title label 76 | // the text property of es_info maps to text property of the subtitle label 77 | // the image property of pic maps to the image property of the image view 78 | { info: {text: 'Apple'}, es_info: {text: 'Manzana'}, properties: {height:150,width:150}}, 79 | { info: {text: 'Apple'}, es_info: {text: 'Manzana'}, properties: {height:150,width:150}}, 80 | ]; 81 | fruitSection.setItems(fruitDataSet); 82 | sections.push(fruitSection); 83 | 84 | listView.setSections(sections); 85 | win.add(listView); 86 | 87 | 88 | win.open(); 89 | -------------------------------------------------------------------------------- /android/example/images/TrashIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/android/example/images/TrashIcon.png -------------------------------------------------------------------------------- /android/example/images/TrashIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/android/example/images/TrashIcon@2x.png -------------------------------------------------------------------------------- /android/example/images/TrashIconSelected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/android/example/images/TrashIconSelected.png -------------------------------------------------------------------------------- /android/example/images/TrashIconSelected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/android/example/images/TrashIconSelected@2x.png -------------------------------------------------------------------------------- /android/example/images/UploadIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/android/example/images/UploadIcon.png -------------------------------------------------------------------------------- /android/example/images/UploadIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/android/example/images/UploadIcon@2x.png -------------------------------------------------------------------------------- /android/example/images/UploadIconSelected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/android/example/images/UploadIconSelected.png -------------------------------------------------------------------------------- /android/example/images/UploadIconSelected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/android/example/images/UploadIconSelected@2x.png -------------------------------------------------------------------------------- /android/example/lib/CollectionView.js: -------------------------------------------------------------------------------- 1 | function createCollectionView(options) { 2 | if( OS_IOS ) 3 | { 4 | return require("ti.collectionview").createCollectionView(options); 5 | } 6 | var templates = options.templates; 7 | for (var binding in templates) { 8 | var currentTemplate = templates[binding]; 9 | //process template 10 | processTemplate(currentTemplate); 11 | //process child templates 12 | processChildTemplates(currentTemplate); 13 | } 14 | Ti.API.info( JSON.stringify(options) ); 15 | var listView = require("ti.collectionview").createCollectionView(options); 16 | 17 | return listView; 18 | } 19 | 20 | //Create ListItemProxy, add events, then store it in 'tiProxy' property 21 | function processTemplate(properties) { 22 | var cellProxy = require("ti.collectionview").createCollectionItem(); 23 | properties.tiProxy = cellProxy; 24 | var events = properties.events; 25 | addEventListeners(events, cellProxy); 26 | } 27 | 28 | //Recursive function that process childTemplates and append corresponding proxies to 29 | //property 'tiProxy'. I.e: type: "Titanium.UI.Label" -> tiProxy: LabelProxy object 30 | function processChildTemplates(properties) { 31 | if (!properties.hasOwnProperty('childTemplates')) return; 32 | 33 | var childProperties = properties.childTemplates; 34 | if (childProperties === void 0 || childProperties === null) return; 35 | 36 | for (var i = 0; i < childProperties.length; i++) { 37 | var child = childProperties[i]; 38 | var proxyType = child.type; 39 | if (proxyType !== void 0) { 40 | var creationProperties = child.properties; 41 | var creationFunction = lookup(proxyType); 42 | var childProxy; 43 | //create the proxy 44 | if (creationProperties !== void 0) { 45 | childProxy = creationFunction(creationProperties); 46 | } else { 47 | childProxy = creationFunction(); 48 | } 49 | //add event listeners 50 | var events = child.events; 51 | addEventListeners(events, childProxy); 52 | //append proxy to tiProxy property 53 | child.tiProxy = childProxy; 54 | } 55 | 56 | processChildTemplates(child); 57 | 58 | } 59 | 60 | 61 | } 62 | 63 | //add event listeners 64 | function addEventListeners(events, proxy) { 65 | if (events !== void 0) { 66 | for (var eventName in events) { 67 | proxy.addEventListener(eventName, events[eventName]); 68 | } 69 | } 70 | } 71 | 72 | //convert name of UI elements into a constructor function. 73 | //I.e: lookup("Titanium.UI.Label") returns Titanium.UI.createLabel function 74 | function lookup(name) { 75 | var lastDotIndex = name.lastIndexOf('.'); 76 | var proxy = eval(name.substring(0, lastDotIndex)); 77 | if (typeof(proxy) == undefined) return; 78 | 79 | var proxyName = name.slice(lastDotIndex + 1); 80 | return proxy['create' + proxyName]; 81 | } 82 | 83 | exports.createCollectionView = createCollectionView; 84 | -------------------------------------------------------------------------------- /android/java-sources.txt: -------------------------------------------------------------------------------- 1 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/AndroidcollectionviewModule.java" 2 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/BakedBezierInterpolator.java" 3 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/BaseCollectionViewItem.java" 4 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/CollectionItem.java" 5 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/CollectionItemProxy.java" 6 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/CollectionSectionProxy.java" 7 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/CollectionSwipeRefreshLayout.java" 8 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/CollectionView.java" 9 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/CollectionViewProxy.java" 10 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/CollectionViewTemplate.java" 11 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/DefaultCollectionViewTemplate.java" 12 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/ExampleProxy.java" 13 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/SwipeProgressBar.java" 14 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/SwipeRefreshLayout.java" 15 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/TiGridView.java" 16 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/src/de/marcelpociot/collectionview/ViewItem.java" 17 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/build/generated/java/ti/collectionview/AndroidcollectionviewBootstrap.java" 18 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/build/generated/r/android/support/compat/R.java" 19 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/build/generated/r/android/support/design/R.java" 20 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/build/generated/r/android/support/v7/appcompat/R.java" 21 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/build/generated/r/android/support/v7/cardview/R.java" 22 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/build/generated/r/ti/collectionview/R.java" 23 | "/Users/hknoechel/Documents/appcelerator_modules/TiCollectionView/android/build/generated/r/ti/modules/titanium/ui/R.java" -------------------------------------------------------------------------------- /android/manifest: -------------------------------------------------------------------------------- 1 | # 2 | # this is your module manifest and used by Titanium 3 | # during compilation, packaging, distribution, etc. 4 | # 5 | version: 3.0.0 6 | apiversion: 4 7 | architectures: arm64-v8a armeabi-v7a x86 8 | description: androidcollectionview 9 | author: Marcel Pociot 10 | license: MIT 11 | copyright: Copyright (c) 2014-2015 Marcel Pociot, 2015-Present Nuno Costa 12 | 13 | # these should not be edited 14 | name: androidcollectionview 15 | moduleid: ti.collectionview 16 | guid: 304bc7f8-ad6c-4348-9d32-658adf6f61f4 17 | platform: android 18 | minsdk: 7.0.0 19 | -------------------------------------------------------------------------------- /android/platform/README: -------------------------------------------------------------------------------- 1 | You can place platform-specific files here in sub-folders named "android" and/or "iphone", just as you can with normal Titanium Mobile SDK projects. Any folders and files you place here will be merged with the platform-specific files in a Titanium Mobile project that uses this module. 2 | 3 | When a Titanium Mobile project that uses this module is built, the files from this platform/ folder will be treated the same as files (if any) from the Titanium Mobile project's platform/ folder. 4 | -------------------------------------------------------------------------------- /android/platform/android/res/layout/swipe_refresh.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/platform/android/res/layout/ticollection_ui_list_header_or_footer.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /android/platform/android/res/layout/titanium_ui_collection_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 19 | 20 | 21 | 22 | 34 | 35 | -------------------------------------------------------------------------------- /android/platform/android/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #1996BE 5 | #48B6D9 6 | #80D3ED 7 | #C2F0FF 8 | -------------------------------------------------------------------------------- /android/src/de/marcelpociot/collectionview/AndroidcollectionviewModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by the Titanium Module SDK helper for Android 3 | * Appcelerator Titanium Mobile 4 | * Copyright (c) 2009-2010 by Appcelerator, Inc. All Rights Reserved. 5 | * Licensed under the terms of the Apache Public License 6 | * Please see the LICENSE included with this distribution for details. 7 | * 8 | */ 9 | package de.marcelpociot.collectionview; 10 | 11 | import org.appcelerator.kroll.KrollModule; 12 | import org.appcelerator.kroll.annotations.Kroll; 13 | 14 | import org.appcelerator.titanium.TiApplication; 15 | import org.appcelerator.kroll.common.Log; 16 | import org.appcelerator.kroll.common.TiConfig; 17 | 18 | 19 | @Kroll.module(name="Androidcollectionview", id="ti.collectionview") 20 | public class AndroidcollectionviewModule extends KrollModule 21 | { 22 | 23 | // Standard Debugging variables 24 | private static final String LCAT = "AndroidcollectionviewModule"; 25 | private static final boolean DBG = TiConfig.LOGD; 26 | 27 | // You can define constants with @Kroll.constant, for example: 28 | // @Kroll.constant public static final String EXTERNAL_NAME = value; 29 | 30 | public AndroidcollectionviewModule() 31 | { 32 | super(); 33 | } 34 | 35 | @Kroll.onAppCreate 36 | public static void onAppCreate(TiApplication app) 37 | { 38 | Log.d(LCAT, "inside onAppCreate"); 39 | // put module init code that needs to run when the application is created 40 | } 41 | 42 | // Methods 43 | @Kroll.method 44 | public String example() 45 | { 46 | Log.d(LCAT, "example called"); 47 | return "hello world"; 48 | } 49 | 50 | // Properties 51 | @Kroll.getProperty 52 | public String getExampleProp() 53 | { 54 | Log.d(LCAT, "get example property"); 55 | return "hello world"; 56 | } 57 | 58 | 59 | @Kroll.setProperty 60 | public void setExampleProp(String value) { 61 | Log.d(LCAT, "set example property: " + value); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /android/src/de/marcelpociot/collectionview/BakedBezierInterpolator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marcelpociot.collectionview; 18 | 19 | import android.view.animation.Interpolator; 20 | 21 | /** 22 | * A pre-baked bezier-curved interpolator for indeterminate progress animations. 23 | */ 24 | final class BakedBezierInterpolator implements Interpolator { 25 | private static final BakedBezierInterpolator INSTANCE = new BakedBezierInterpolator(); 26 | 27 | public final static BakedBezierInterpolator getInstance() { 28 | return INSTANCE; 29 | } 30 | 31 | /** 32 | * Use getInstance instead of instantiating. 33 | */ 34 | private BakedBezierInterpolator() { 35 | super(); 36 | } 37 | 38 | /** 39 | * Lookup table values. 40 | * Generated using a Bezier curve from (0,0) to (1,1) with control points: 41 | * P0 (0,0) 42 | * P1 (0.4, 0) 43 | * P2 (0.2, 1.0) 44 | * P3 (1.0, 1.0) 45 | * 46 | * Values sampled with x at regular intervals between 0 and 1. 47 | */ 48 | private static final float[] VALUES = new float[] { 49 | 0.0f, 0.0002f, 0.0009f, 0.0019f, 0.0036f, 0.0059f, 0.0086f, 0.0119f, 0.0157f, 0.0209f, 50 | 0.0257f, 0.0321f, 0.0392f, 0.0469f, 0.0566f, 0.0656f, 0.0768f, 0.0887f, 0.1033f, 0.1186f, 51 | 0.1349f, 0.1519f, 0.1696f, 0.1928f, 0.2121f, 0.237f, 0.2627f, 0.2892f, 0.3109f, 0.3386f, 52 | 0.3667f, 0.3952f, 0.4241f, 0.4474f, 0.4766f, 0.5f, 0.5234f, 0.5468f, 0.5701f, 0.5933f, 53 | 0.6134f, 0.6333f, 0.6531f, 0.6698f, 0.6891f, 0.7054f, 0.7214f, 0.7346f, 0.7502f, 0.763f, 54 | 0.7756f, 0.7879f, 0.8f, 0.8107f, 0.8212f, 0.8326f, 0.8415f, 0.8503f, 0.8588f, 0.8672f, 55 | 0.8754f, 0.8833f, 0.8911f, 0.8977f, 0.9041f, 0.9113f, 0.9165f, 0.9232f, 0.9281f, 0.9328f, 56 | 0.9382f, 0.9434f, 0.9476f, 0.9518f, 0.9557f, 0.9596f, 0.9632f, 0.9662f, 0.9695f, 0.9722f, 57 | 0.9753f, 0.9777f, 0.9805f, 0.9826f, 0.9847f, 0.9866f, 0.9884f, 0.9901f, 0.9917f, 0.9931f, 58 | 0.9944f, 0.9955f, 0.9964f, 0.9973f, 0.9981f, 0.9986f, 0.9992f, 0.9995f, 0.9998f, 1.0f, 1.0f 59 | }; 60 | 61 | private static final float STEP_SIZE = 1.0f / (VALUES.length - 1); 62 | 63 | @Override 64 | public float getInterpolation(float input) { 65 | if (input >= 1.0f) { 66 | return 1.0f; 67 | } 68 | 69 | if (input <= 0f) { 70 | return 0f; 71 | } 72 | 73 | int position = Math.min( 74 | (int)(input * (VALUES.length - 1)), 75 | VALUES.length - 2); 76 | 77 | float quantized = position * STEP_SIZE; 78 | float difference = input - quantized; 79 | float weight = difference / STEP_SIZE; 80 | 81 | return VALUES[position] + weight * (VALUES[position + 1] - VALUES[position]); 82 | } 83 | 84 | } 85 | 86 | -------------------------------------------------------------------------------- /android/src/de/marcelpociot/collectionview/BaseCollectionViewItem.java: -------------------------------------------------------------------------------- 1 | package de.marcelpociot.collectionview; 2 | 3 | import java.util.HashMap; 4 | 5 | import org.appcelerator.kroll.KrollDict; 6 | import org.appcelerator.titanium.TiDimension; 7 | import org.appcelerator.titanium.view.TiCompositeLayout; 8 | import org.appcelerator.titanium.view.TiUIView; 9 | 10 | import android.content.Context; 11 | import android.util.AttributeSet; 12 | import android.view.MotionEvent; 13 | import android.view.View.MeasureSpec; 14 | 15 | public class BaseCollectionViewItem extends TiCompositeLayout{ 16 | 17 | private HashMap viewsMap; 18 | private ViewItem viewItem; 19 | private int minHeight; 20 | public BaseCollectionViewItem(Context context) { 21 | super(context); 22 | viewsMap = new HashMap(); 23 | } 24 | 25 | public BaseCollectionViewItem(Context context, AttributeSet set) { 26 | super(context, set); 27 | setId(CollectionView.listContentId); 28 | TiDimension heightDimension = new TiDimension(CollectionView.MIN_ROW_HEIGHT, TiDimension.TYPE_UNDEFINED); 29 | minHeight = heightDimension.getAsPixels(this); 30 | setMinimumHeight(minHeight); 31 | viewsMap = new HashMap(); 32 | viewItem = new ViewItem(null, new KrollDict()); 33 | } 34 | 35 | public HashMap getViewsMap() { 36 | return viewsMap; 37 | } 38 | 39 | public ViewItem getViewItem() { 40 | return viewItem; 41 | } 42 | 43 | public void bindView(String binding, ViewItem view) { 44 | viewsMap.put(binding, view); 45 | } 46 | 47 | public TiUIView getViewFromBinding(String binding) { 48 | ViewItem viewItem = viewsMap.get(binding); 49 | if (viewItem != null) { 50 | return viewItem.getView(); 51 | } 52 | return null; 53 | } 54 | 55 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 56 | int h = MeasureSpec.getSize(heightMeasureSpec); 57 | int hMode = MeasureSpec.getMode(heightMeasureSpec); 58 | if (h < minHeight && hMode == MeasureSpec.EXACTLY) { 59 | h = minHeight; 60 | } 61 | super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, hMode)); 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /android/src/de/marcelpociot/collectionview/CollectionItem.java: -------------------------------------------------------------------------------- 1 | package de.marcelpociot.collectionview; 2 | 3 | import java.util.HashMap; 4 | 5 | import org.appcelerator.kroll.KrollDict; 6 | import org.appcelerator.titanium.TiC; 7 | import org.appcelerator.titanium.proxy.TiViewProxy; 8 | import org.appcelerator.titanium.util.TiConvert; 9 | import org.appcelerator.titanium.view.TiCompositeLayout.LayoutParams; 10 | import org.appcelerator.titanium.view.TiUIView; 11 | 12 | import ti.modules.titanium.ui.UIModule; 13 | import android.view.View; 14 | import android.view.View.OnClickListener; 15 | import android.widget.ImageView; 16 | 17 | public class CollectionItem extends TiUIView { 18 | 19 | View listItemLayout; 20 | public CollectionItem(TiViewProxy proxy) { 21 | super(proxy); 22 | } 23 | 24 | public CollectionItem(TiViewProxy proxy, LayoutParams p, View v, View item_layout) { 25 | super(proxy); 26 | layoutParams = p; 27 | listItemLayout = item_layout; 28 | setNativeView(v); 29 | registerForTouch(v); 30 | v.setFocusable(false); 31 | } 32 | 33 | public void processProperties(KrollDict d) { 34 | 35 | if (d.containsKey(TiC.PROPERTY_ACCESSORY_TYPE)) { 36 | int accessory = TiConvert.toInt(d.get(TiC.PROPERTY_ACCESSORY_TYPE), -1); 37 | handleAccessory(accessory); 38 | } 39 | if (d.containsKey(TiC.PROPERTY_SELECTED_BACKGROUND_COLOR)) { 40 | d.put(TiC.PROPERTY_BACKGROUND_SELECTED_COLOR, d.get(TiC.PROPERTY_SELECTED_BACKGROUND_COLOR)); 41 | } 42 | if (d.containsKey(TiC.PROPERTY_SELECTED_BACKGROUND_IMAGE)) { 43 | d.put(TiC.PROPERTY_BACKGROUND_SELECTED_IMAGE, d.get(TiC.PROPERTY_SELECTED_BACKGROUND_IMAGE)); 44 | } 45 | super.processProperties(d); 46 | } 47 | 48 | private void handleAccessory(int accessory) { 49 | 50 | ImageView accessoryImage = (ImageView) listItemLayout.findViewById(CollectionView.accessory); 51 | 52 | switch(accessory) { 53 | 54 | case UIModule.LIST_ACCESSORY_TYPE_CHECKMARK: 55 | accessoryImage.setImageResource(CollectionView.isCheck); 56 | break; 57 | case UIModule.LIST_ACCESSORY_TYPE_DETAIL: 58 | accessoryImage.setImageResource(CollectionView.hasChild); 59 | break; 60 | 61 | case UIModule.LIST_ACCESSORY_TYPE_DISCLOSURE: 62 | accessoryImage.setImageResource(CollectionView.disclosure); 63 | break; 64 | 65 | default: 66 | accessoryImage.setImageResource(0); 67 | } 68 | } 69 | 70 | protected void setOnClickListener(View view) 71 | { 72 | view.setOnClickListener(new OnClickListener() 73 | { 74 | public void onClick(View view) 75 | { 76 | KrollDict data = dictFromEvent(lastUpEvent); 77 | handleFireItemClick(new KrollDict(data)); 78 | fireEvent(TiC.EVENT_CLICK, data); 79 | } 80 | }); 81 | } 82 | 83 | protected void handleFireItemClick (KrollDict data) { 84 | TiViewProxy listViewProxy = ((CollectionItemProxy)proxy).getListProxy(); 85 | if (listViewProxy != null) { 86 | TiUIView listView = listViewProxy.peekView(); 87 | if (listView != null) { 88 | KrollDict d = listView.getAdditionalEventData(); 89 | if (d == null) { 90 | listView.setAdditionalEventData(new KrollDict((HashMap) additionalEventData)); 91 | } else { 92 | d.clear(); 93 | d.putAll(additionalEventData); 94 | } 95 | listView.fireEvent(TiC.EVENT_ITEM_CLICK, data); 96 | } 97 | } 98 | } 99 | 100 | public void release() { 101 | if (listItemLayout != null) { 102 | listItemLayout = null; 103 | } 104 | super.release(); 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /android/src/de/marcelpociot/collectionview/CollectionItemProxy.java: -------------------------------------------------------------------------------- 1 | package de.marcelpociot.collectionview; 2 | 3 | import java.lang.ref.WeakReference; 4 | import java.util.HashMap; 5 | 6 | import org.appcelerator.kroll.KrollDict; 7 | import org.appcelerator.kroll.annotations.Kroll; 8 | import org.appcelerator.titanium.TiC; 9 | import org.appcelerator.titanium.proxy.TiViewProxy; 10 | import org.appcelerator.titanium.util.TiConvert; 11 | import org.appcelerator.titanium.view.TiUIView; 12 | import android.app.Activity; 13 | 14 | @Kroll.proxy(creatableInModule = AndroidcollectionviewModule.class) 15 | public class CollectionItemProxy extends TiViewProxy 16 | { 17 | protected WeakReference listProxy; 18 | 19 | public TiUIView createView(Activity activity) 20 | { 21 | return new CollectionItem(this); 22 | } 23 | 24 | public void setListProxy(TiViewProxy list) 25 | { 26 | listProxy = new WeakReference(list); 27 | } 28 | 29 | public TiViewProxy getListProxy() 30 | { 31 | if (listProxy != null) { 32 | return listProxy.get(); 33 | } 34 | return null; 35 | } 36 | 37 | public boolean fireEvent(final String event, final Object data, boolean bubbles) 38 | { 39 | fireItemClick(event, data); 40 | return super.fireEvent(event, data, bubbles); 41 | } 42 | 43 | private void fireItemClick(String event, Object data) 44 | { 45 | if (event.equals(TiC.EVENT_CLICK) && data instanceof HashMap) { 46 | KrollDict eventData = new KrollDict((HashMap) data); 47 | Object source = eventData.get(TiC.EVENT_PROPERTY_SOURCE); 48 | if (source != null && !source.equals(this) && listProxy != null) { 49 | TiViewProxy listViewProxy = listProxy.get(); 50 | if (listViewProxy != null) { 51 | listViewProxy.fireEvent(TiC.EVENT_ITEM_CLICK, eventData); 52 | } 53 | } 54 | } 55 | } 56 | 57 | @Override 58 | public boolean hierarchyHasListener(String event) 59 | { 60 | // In order to fire the "itemclick" event when the children views are clicked, 61 | // the children views' "click" events must be fired and bubbled up. (TIMOB-14901) 62 | if (event.equals(TiC.EVENT_CLICK)) { 63 | return true; 64 | } 65 | return super.hierarchyHasListener(event); 66 | } 67 | 68 | public void release() 69 | { 70 | super.release(); 71 | if (listProxy != null) { 72 | listProxy = null; 73 | } 74 | } 75 | 76 | @Override 77 | public String getApiName() 78 | { 79 | return "ti.CollectionItem"; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /android/src/de/marcelpociot/collectionview/CollectionSwipeRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package de.marcelpociot.collectionview; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.ViewCompat; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.AbsListView; 8 | import android.widget.FrameLayout; 9 | import android.widget.ScrollView; 10 | 11 | /** 12 | * MySwipeRefreshLayout is a modified SwipeRefreshLayout so that Titanium views 13 | * are supported. It overrides the canChildScrollUp method used by Android to 14 | * determine whether the gesture is for refresh or if the user is just scrolling up 15 | * the scrollable view. 16 | */ 17 | public class CollectionSwipeRefreshLayout extends SwipeRefreshLayout { 18 | 19 | private View nativeView; // usually the layout wrapping the listview 20 | private View nativeChildView; // the native android listview 21 | 22 | public CollectionSwipeRefreshLayout(Context context) { 23 | super(context); 24 | } 25 | 26 | public CollectionSwipeRefreshLayout(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | } 29 | 30 | public View getNativeView() { 31 | return nativeView; 32 | } 33 | 34 | public void setNativeView(View view) { 35 | this.nativeView = view; 36 | } 37 | 38 | @Override 39 | public boolean canChildScrollUp() { 40 | // ScrollViews are also an instance of FrameLayouts and we do not want to get 41 | // the ScrollView's child view as it will not work. 42 | if (nativeView instanceof FrameLayout && !(nativeView instanceof ScrollView)) { 43 | // Try to get the native Android ListView inside the FrameLayout 44 | nativeChildView = ((FrameLayout) nativeView).getChildAt(0); 45 | } else { 46 | nativeChildView = nativeView; 47 | } 48 | if (android.os.Build.VERSION.SDK_INT < 14) { 49 | if (nativeChildView instanceof AbsListView) { 50 | final AbsListView absListView = (AbsListView) nativeChildView; 51 | return absListView.getChildCount() > 0 52 | && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) 53 | .getTop() < absListView.getPaddingTop()); 54 | } else { 55 | return nativeChildView.getScrollY() > 0; 56 | } 57 | } else { 58 | return ViewCompat.canScrollVertically(nativeChildView, -1); 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /android/src/de/marcelpociot/collectionview/CollectionViewProxy.java: -------------------------------------------------------------------------------- 1 | package de.marcelpociot.collectionview; 2 | 3 | 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | 7 | import org.appcelerator.kroll.KrollDict; 8 | import org.appcelerator.kroll.KrollModule; 9 | import org.appcelerator.kroll.annotations.Kroll; 10 | import org.appcelerator.kroll.common.AsyncResult; 11 | import org.appcelerator.kroll.common.Log; 12 | import org.appcelerator.kroll.common.TiMessenger; 13 | import org.appcelerator.titanium.TiApplication; 14 | import org.appcelerator.titanium.TiC; 15 | import org.appcelerator.titanium.proxy.TiViewProxy; 16 | import org.appcelerator.titanium.util.TiConvert; 17 | import org.appcelerator.titanium.view.TiUIView; 18 | 19 | import android.app.Activity; 20 | import android.os.Handler; 21 | import android.os.Message; 22 | 23 | @Kroll.proxy(creatableInModule = AndroidcollectionviewModule.class, propertyAccessors = { 24 | TiC.PROPERTY_HEADER_TITLE, 25 | TiC.PROPERTY_FOOTER_TITLE, 26 | TiC.PROPERTY_DEFAULT_ITEM_TEMPLATE, 27 | TiC.PROPERTY_SHOW_VERTICAL_SCROLL_INDICATOR, 28 | TiC.PROPERTY_SEPARATOR_COLOR, 29 | TiC.PROPERTY_SEARCH_TEXT, 30 | TiC.PROPERTY_SEARCH_VIEW, 31 | TiC.PROPERTY_CASE_INSENSITIVE_SEARCH, 32 | TiC.PROPERTY_HEADER_DIVIDERS_ENABLED, 33 | TiC.PROPERTY_FOOTER_DIVIDERS_ENABLED 34 | }) 35 | public class CollectionViewProxy extends TiViewProxy { 36 | 37 | private static final String TAG = "CollectionViewProxy"; 38 | 39 | private static final int MSG_FIRST_ID = TiViewProxy.MSG_LAST_ID + 1; 40 | 41 | private static final int MSG_SECTION_COUNT = MSG_FIRST_ID + 399; 42 | private static final int MSG_SCROLL_TO_ITEM = MSG_FIRST_ID + 400; 43 | private static final int MSG_APPEND_SECTION = MSG_FIRST_ID + 401; 44 | private static final int MSG_INSERT_SECTION_AT = MSG_FIRST_ID + 402; 45 | private static final int MSG_DELETE_SECTION_AT = MSG_FIRST_ID + 403; 46 | private static final int MSG_REPLACE_SECTION_AT = MSG_FIRST_ID + 404; 47 | private static final int MSG_GET_SECTIONS = MSG_FIRST_ID + 405; 48 | private static final int MSG_SET_SECTIONS = MSG_FIRST_ID + 406; 49 | 50 | 51 | 52 | //indicate if user attempts to add/modify/delete sections before TiListView is created 53 | private boolean preload = false; 54 | private ArrayList preloadSections; 55 | private HashMap preloadMarker; 56 | 57 | public CollectionViewProxy() { 58 | super(); 59 | defaultValues.put("columnWidth", 0); 60 | defaultValues.put("verticalSpacing", 0); 61 | defaultValues.put("horizontalSpacing", 0); 62 | } 63 | 64 | public TiUIView createView(Activity activity) { 65 | return new CollectionView(this, activity); 66 | } 67 | 68 | public void handleCreationArgs(KrollModule createdInModule, Object[] args) { 69 | preloadSections = new ArrayList(); 70 | defaultValues.put(TiC.PROPERTY_DEFAULT_ITEM_TEMPLATE, "listDefaultTemplate"); 71 | defaultValues.put(TiC.PROPERTY_CASE_INSENSITIVE_SEARCH, true); 72 | super.handleCreationArgs(createdInModule, args); 73 | 74 | } 75 | public void handleCreationDict(KrollDict options) { 76 | super.handleCreationDict(options); 77 | //Adding sections to preload sections, so we can handle appendSections/insertSection 78 | //accordingly if user call these before TiListView is instantiated. 79 | if (options.containsKey(TiC.PROPERTY_SECTIONS)) { 80 | Object obj = options.get(TiC.PROPERTY_SECTIONS); 81 | if (obj instanceof Object[]) { 82 | addPreloadSections((Object[]) obj, -1, true); 83 | } 84 | } 85 | } 86 | 87 | public void clearPreloadSections() { 88 | if (preloadSections != null) { 89 | preloadSections.clear(); 90 | } 91 | } 92 | 93 | public ArrayList getPreloadSections() { 94 | return preloadSections; 95 | } 96 | 97 | public boolean getPreload() { 98 | return preload; 99 | } 100 | 101 | public void setPreload(boolean pload) 102 | { 103 | preload = pload; 104 | } 105 | 106 | public HashMap getPreloadMarker() 107 | { 108 | return preloadMarker; 109 | } 110 | 111 | private void addPreloadSections(Object secs, int index, boolean arrayOnly) { 112 | if (secs instanceof Object[]) { 113 | Object[] sections = (Object[]) secs; 114 | for (int i = 0; i < sections.length; i++) { 115 | Object section = sections[i]; 116 | addPreloadSection(section, -1); 117 | } 118 | } else if (!arrayOnly) { 119 | addPreloadSection(secs, -1); 120 | } 121 | } 122 | 123 | private void addPreloadSection(Object section, int index) { 124 | if (section instanceof CollectionSectionProxy) { 125 | if (index == -1) { 126 | preloadSections.add((CollectionSectionProxy) section); 127 | } else { 128 | preloadSections.add(index, (CollectionSectionProxy) section); 129 | } 130 | } 131 | } 132 | 133 | @Kroll.method @Kroll.getProperty 134 | public int getSectionCount() { 135 | if (TiApplication.isUIThread()) { 136 | return handleSectionCount(); 137 | } else { 138 | return (Integer) TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_SECTION_COUNT)); 139 | } 140 | } 141 | 142 | public int handleSectionCount () { 143 | TiUIView listView = peekView(); 144 | if (listView != null) { 145 | return ((CollectionView) listView).getSectionCount(); 146 | } 147 | return 0; 148 | } 149 | 150 | @Kroll.method 151 | public void scrollToItem(int sectionIndex, int itemIndex, @SuppressWarnings("rawtypes") @Kroll.argument(optional=true)HashMap options) { 152 | boolean animated = true; 153 | if ( (options != null) && (options instanceof HashMap) ) { 154 | @SuppressWarnings("unchecked") 155 | KrollDict animationargs = new KrollDict(options); 156 | if (animationargs.containsKeyAndNotNull(TiC.PROPERTY_ANIMATED)) { 157 | animated = TiConvert.toBoolean(animationargs.get(TiC.PROPERTY_ANIMATED), true); 158 | } 159 | } 160 | if (TiApplication.isUIThread()) { 161 | handleScrollToItem(sectionIndex, itemIndex, animated); 162 | } else { 163 | KrollDict d = new KrollDict(); 164 | d.put("itemIndex", itemIndex); 165 | d.put("sectionIndex", sectionIndex); 166 | d.put(TiC.PROPERTY_ANIMATED, Boolean.valueOf(animated)); 167 | TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_SCROLL_TO_ITEM), d); 168 | } 169 | } 170 | 171 | @Kroll.method 172 | public void setMarker(Object marker) { 173 | if (marker instanceof HashMap) { 174 | HashMap m = (HashMap) marker; 175 | TiUIView listView = peekView(); 176 | if (listView != null) { 177 | ((CollectionView)listView).setMarker(m); 178 | } else { 179 | preloadMarker = m; 180 | } 181 | } 182 | } 183 | 184 | 185 | @Override 186 | public boolean handleMessage(final Message msg) { 187 | 188 | switch (msg.what) { 189 | 190 | case MSG_SECTION_COUNT: { 191 | AsyncResult result = (AsyncResult)msg.obj; 192 | result.setResult(handleSectionCount()); 193 | return true; 194 | } 195 | 196 | case MSG_SCROLL_TO_ITEM: { 197 | AsyncResult result = (AsyncResult)msg.obj; 198 | KrollDict data = (KrollDict) result.getArg(); 199 | int sectionIndex = data.getInt("sectionIndex"); 200 | int itemIndex = data.getInt("itemIndex"); 201 | boolean animated = data.getBoolean(TiC.PROPERTY_ANIMATED); 202 | handleScrollToItem(sectionIndex, itemIndex, animated); 203 | result.setResult(null); 204 | return true; 205 | } 206 | case MSG_APPEND_SECTION: { 207 | AsyncResult result = (AsyncResult)msg.obj; 208 | handleAppendSection(result.getArg()); 209 | result.setResult(null); 210 | return true; 211 | } 212 | case MSG_DELETE_SECTION_AT: { 213 | AsyncResult result = (AsyncResult)msg.obj; 214 | handleDeleteSectionAt(TiConvert.toInt(result.getArg())); 215 | result.setResult(null); 216 | return true; 217 | } 218 | case MSG_INSERT_SECTION_AT: { 219 | AsyncResult result = (AsyncResult)msg.obj; 220 | KrollDict data = (KrollDict) result.getArg(); 221 | int index = data.getInt("index"); 222 | Object section = data.get("section"); 223 | handleInsertSectionAt(index, section); 224 | result.setResult(null); 225 | return true; 226 | } 227 | case MSG_REPLACE_SECTION_AT: { 228 | AsyncResult result = (AsyncResult)msg.obj; 229 | KrollDict data = (KrollDict) result.getArg(); 230 | int index = data.getInt("index"); 231 | Object section = data.get("section"); 232 | handleReplaceSectionAt(index, section); 233 | result.setResult(null); 234 | return true; 235 | } 236 | 237 | case MSG_GET_SECTIONS: { 238 | AsyncResult result = (AsyncResult)msg.obj; 239 | result.setResult(handleSections()); 240 | return true; 241 | } 242 | 243 | case MSG_SET_SECTIONS: { 244 | AsyncResult result = (AsyncResult)msg.obj; 245 | TiUIView listView = peekView(); 246 | if (listView != null) { 247 | ((CollectionView)listView).processSectionsAndNotify((Object[])result.getArg()); 248 | } else { 249 | Log.e(TAG, "Unable to set sections, listView is null", Log.DEBUG_MODE); 250 | } 251 | result.setResult(null); 252 | return true; 253 | } 254 | 255 | default: 256 | return super.handleMessage(msg); 257 | } 258 | } 259 | private void handleScrollToItem(int sectionIndex, int itemIndex, boolean animated) { 260 | TiUIView listView = peekView(); 261 | if (listView != null) { 262 | ((CollectionView) listView).scrollToItem(sectionIndex, itemIndex, animated); 263 | } 264 | } 265 | 266 | @Kroll.method 267 | public void appendSection(Object section) { 268 | if (TiApplication.isUIThread()) { 269 | handleAppendSection(section); 270 | } else { 271 | TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_APPEND_SECTION), section); 272 | } 273 | } 274 | 275 | private void handleAppendSection(Object section) { 276 | TiUIView listView = peekView(); 277 | if (listView != null) { 278 | ((CollectionView) listView).appendSection(section); 279 | } else { 280 | preload = true; 281 | addPreloadSections(section, -1, false); 282 | } 283 | } 284 | 285 | @Kroll.method 286 | public void deleteSectionAt(int index) { 287 | if (TiApplication.isUIThread()) { 288 | handleDeleteSectionAt(index); 289 | } else { 290 | TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_DELETE_SECTION_AT), index); 291 | } 292 | } 293 | 294 | private void handleDeleteSectionAt(int index) { 295 | TiUIView listView = peekView(); 296 | if (listView != null) { 297 | ((CollectionView) listView).deleteSectionAt(index); 298 | } else { 299 | if (index < 0 || index >= preloadSections.size()) { 300 | Log.e(TAG, "Invalid index to delete section"); 301 | return; 302 | } 303 | preload = true; 304 | preloadSections.remove(index); 305 | } 306 | } 307 | 308 | @Kroll.method 309 | public void insertSectionAt(int index, Object section) { 310 | if (TiApplication.isUIThread()) { 311 | handleInsertSectionAt(index, section); 312 | } else { 313 | sendInsertSectionMessage(index, section); 314 | } 315 | } 316 | 317 | private void sendInsertSectionMessage(int index, Object section) { 318 | KrollDict data = new KrollDict(); 319 | data.put("index", index); 320 | data.put("section", section); 321 | TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_INSERT_SECTION_AT), data); 322 | } 323 | 324 | private void handleInsertSectionAt(int index, Object section) { 325 | TiUIView listView = peekView(); 326 | if (listView != null) { 327 | ((CollectionView) listView).insertSectionAt(index, section); 328 | } else { 329 | if (index < 0 || index > preloadSections.size()) { 330 | Log.e(TAG, "Invalid index to insertSection"); 331 | return; 332 | } 333 | preload = true; 334 | addPreloadSections(section, index, false); 335 | } 336 | } 337 | 338 | @Kroll.method 339 | public void replaceSectionAt(int index, Object section) { 340 | if (TiApplication.isUIThread()) { 341 | handleReplaceSectionAt(index, section); 342 | } else { 343 | sendReplaceSectionMessage(index, section); 344 | } 345 | } 346 | 347 | private void sendReplaceSectionMessage(int index, Object section) { 348 | KrollDict data = new KrollDict(); 349 | data.put("index", index); 350 | data.put("section", section); 351 | TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_REPLACE_SECTION_AT), data); 352 | } 353 | 354 | private void handleReplaceSectionAt(int index, Object section) { 355 | TiUIView listView = peekView(); 356 | if (listView != null) { 357 | ((CollectionView) listView).replaceSectionAt(index, section); 358 | } else { 359 | handleDeleteSectionAt(index); 360 | handleInsertSectionAt(index, section); 361 | 362 | } 363 | } 364 | 365 | @Kroll.method @Kroll.getProperty 366 | public CollectionSectionProxy[] getSections() 367 | { 368 | if (TiApplication.isUIThread()) { 369 | return handleSections(); 370 | } else { 371 | return (CollectionSectionProxy[]) TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_GET_SECTIONS)); 372 | } 373 | } 374 | 375 | @Kroll.setProperty @Kroll.method 376 | public void setSections(Object sections) 377 | { 378 | if (!(sections instanceof Object[])) { 379 | Log.e(TAG, "Invalid argument type to setSection(), needs to be an array", Log.DEBUG_MODE); 380 | return; 381 | } 382 | //Update java and javascript property 383 | setProperty(TiC.PROPERTY_SECTIONS, sections); 384 | 385 | Object[] sectionsArray = (Object[]) sections; 386 | TiUIView listView = peekView(); 387 | //Preload sections if listView is not opened. 388 | if (listView == null) { 389 | preload = true; 390 | clearPreloadSections(); 391 | addPreloadSections(sectionsArray, -1, true); 392 | } else { 393 | if (TiApplication.isUIThread()) { 394 | ((CollectionView)listView).processSectionsAndNotify(sectionsArray); 395 | } else { 396 | TiMessenger.sendBlockingMainMessage(getMainHandler().obtainMessage(MSG_SET_SECTIONS), sectionsArray); 397 | } 398 | 399 | } 400 | } 401 | 402 | private CollectionSectionProxy[] handleSections() 403 | { 404 | TiUIView listView = peekView(); 405 | 406 | if (listView != null) { 407 | return ((CollectionView) listView).getSections(); 408 | } 409 | ArrayList preloadedSections = getPreloadSections(); 410 | return preloadedSections.toArray(new CollectionSectionProxy[preloadedSections.size()]); 411 | } 412 | 413 | @Kroll.method 414 | public void setRefreshing(boolean refreshing) { 415 | TiUIView listView = peekView(); 416 | 417 | if (listView != null) { 418 | ((CollectionView) listView).setRefreshing(refreshing); 419 | } 420 | } 421 | 422 | @Kroll.method @Kroll.getProperty 423 | public boolean isRefreshing() { 424 | TiUIView listView = peekView(); 425 | 426 | if (listView != null) { 427 | return ((CollectionView) listView).isRefreshing(); 428 | } 429 | 430 | return false; 431 | } 432 | 433 | 434 | @Override 435 | public String getApiName() 436 | { 437 | return "ti.collectionview"; 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /android/src/de/marcelpociot/collectionview/CollectionViewTemplate.java: -------------------------------------------------------------------------------- 1 | package de.marcelpociot.collectionview; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.Set; 6 | 7 | import org.appcelerator.kroll.KrollDict; 8 | import org.appcelerator.kroll.common.Log; 9 | import org.appcelerator.titanium.TiC; 10 | import org.appcelerator.titanium.proxy.TiViewProxy; 11 | import org.appcelerator.titanium.util.TiConvert; 12 | 13 | public class CollectionViewTemplate { 14 | 15 | protected static final String TAG = "TiTemplate"; 16 | 17 | protected HashMap dataItems; 18 | 19 | public static final String DEFAULT_TEMPLATE = "defaultTemplate"; 20 | 21 | public static final String GENERATED_BINDING = "generatedBinding:"; 22 | 23 | //Identifier for template, specified in ListView creation dict 24 | private String templateID; 25 | //Internal identifier for template, each template has a unique type 26 | private int templateType; 27 | 28 | protected DataItem rootItem; 29 | 30 | protected String itemID; 31 | //Properties of the template. 32 | private KrollDict properties; 33 | 34 | public class DataItem { 35 | //proxy for the item 36 | TiViewProxy vProxy; 37 | //binding id 38 | String bindId; 39 | DataItem parent; 40 | ArrayList children; 41 | KrollDict defaultProperties; 42 | 43 | public DataItem(TiViewProxy proxy, String id, DataItem parent) { 44 | vProxy = proxy; 45 | bindId = id; 46 | this.parent = parent; 47 | setProxyParent(); 48 | children = new ArrayList(); 49 | defaultProperties = new KrollDict(); 50 | } 51 | 52 | private void setProxyParent() { 53 | 54 | if (vProxy != null && parent != null) { 55 | TiViewProxy parentProxy = parent.getViewProxy(); 56 | if (parentProxy != null) { 57 | vProxy.setParent(parentProxy); 58 | } 59 | } 60 | } 61 | 62 | public TiViewProxy getViewProxy() { 63 | return vProxy; 64 | } 65 | 66 | public String getBindingId() { 67 | return bindId; 68 | } 69 | public void setDefaultProperties(KrollDict d) { 70 | defaultProperties = d; 71 | } 72 | 73 | public KrollDict getDefaultProperties() { 74 | return defaultProperties; 75 | } 76 | 77 | public DataItem getParent() { 78 | return parent; 79 | } 80 | 81 | public ArrayList getChildren() { 82 | return children; 83 | } 84 | 85 | public void addChild(DataItem child) { 86 | children.add(child); 87 | } 88 | 89 | public void release() { 90 | if (vProxy != null) { 91 | vProxy.release(); 92 | vProxy = null; 93 | } 94 | children.clear(); 95 | parent = null; 96 | } 97 | } 98 | 99 | public CollectionViewTemplate(String id, KrollDict properties) { 100 | //Init our binding hashmaps 101 | dataItems = new HashMap(); 102 | 103 | //Set item id. Item binding is always "properties" 104 | itemID = TiC.PROPERTY_PROPERTIES; 105 | //Init vars. 106 | templateID = id; 107 | templateType = -1; 108 | if (properties != null) { 109 | this.properties = properties; 110 | processProperties(this.properties); 111 | } else { 112 | this.properties = new KrollDict(); 113 | } 114 | 115 | } 116 | 117 | private DataItem bindProxiesAndProperties(KrollDict properties, boolean isRootTemplate, DataItem parent) { 118 | Object proxy = null; 119 | String id = null; 120 | Object props = null; 121 | DataItem item = null; 122 | if (properties.containsKey(TiC.PROPERTY_TI_PROXY)) { 123 | proxy = properties.get(TiC.PROPERTY_TI_PROXY); 124 | } 125 | 126 | //Get/generate random bind id 127 | if (isRootTemplate) { 128 | id = itemID; 129 | } else if (properties.containsKey(TiC.PROPERTY_BIND_ID)) { 130 | id = TiConvert.toString(properties, TiC.PROPERTY_BIND_ID); 131 | } else { 132 | id = GENERATED_BINDING + Math.random(); 133 | } 134 | 135 | 136 | if (proxy instanceof TiViewProxy) { 137 | TiViewProxy viewProxy = (TiViewProxy) proxy; 138 | if (isRootTemplate) { 139 | rootItem = item = new DataItem(viewProxy, TiC.PROPERTY_PROPERTIES, null); 140 | } else { 141 | item = new DataItem(viewProxy, id, parent); 142 | parent.addChild(item); 143 | } 144 | dataItems.put(id, item); 145 | } 146 | 147 | if (properties.containsKey(TiC.PROPERTY_PROPERTIES)) { 148 | props = properties.get(TiC.PROPERTY_PROPERTIES); 149 | } 150 | 151 | if (props instanceof HashMap) { 152 | item.setDefaultProperties(new KrollDict((HashMap)props)); 153 | } 154 | 155 | return item; 156 | } 157 | 158 | private void processProperties(KrollDict properties) { 159 | bindProxiesAndProperties(properties, true, null); 160 | if (properties.containsKey(TiC.PROPERTY_CHILD_TEMPLATES)) { 161 | processChildProperties(properties.get(TiC.PROPERTY_CHILD_TEMPLATES), rootItem); 162 | } 163 | 164 | } 165 | 166 | private void processChildProperties(Object childProperties, DataItem parent) { 167 | if (childProperties instanceof Object[]) { 168 | Object[] propertiesArray = (Object[])childProperties; 169 | for (int i = 0; i < propertiesArray.length; i++) { 170 | HashMap properties = (HashMap) propertiesArray[i]; 171 | //bind proxies and default properties 172 | DataItem item = bindProxiesAndProperties(new KrollDict(properties), false, parent); 173 | //Recursively calls for all childTemplates 174 | if (properties.containsKey(TiC.PROPERTY_CHILD_TEMPLATES)) { 175 | if(item == null) { 176 | Log.e(TAG, "Unable to generate valid data from child view", Log.DEBUG_MODE); 177 | } 178 | processChildProperties(properties.get(TiC.PROPERTY_CHILD_TEMPLATES), item); 179 | } 180 | } 181 | } 182 | } 183 | 184 | public String getTemplateID() { 185 | return templateID; 186 | } 187 | 188 | public void setType(int type) { 189 | templateType = type; 190 | } 191 | 192 | public int getType() { 193 | return templateType; 194 | } 195 | 196 | public String getItemID() { 197 | return itemID; 198 | } 199 | 200 | public void setRootParent(TiViewProxy listView) { 201 | CollectionItemProxy rootProxy = (CollectionItemProxy) rootItem.getViewProxy(); 202 | if (rootProxy != null && rootProxy.getListProxy() == null) { 203 | rootProxy.setListProxy(listView); 204 | } 205 | } 206 | 207 | /** 208 | * Returns the bound view proxy if exists. 209 | */ 210 | public DataItem getDataItem(String binding) { 211 | return dataItems.get(binding); 212 | } 213 | 214 | public DataItem getRootItem() { 215 | return rootItem; 216 | } 217 | 218 | public void updateOrMergeWithDefaultProperties(KrollDict data, boolean update) { 219 | for (String binding: data.keySet()) { 220 | DataItem dataItem = dataItems.get(binding); 221 | if (dataItem == null) continue; 222 | 223 | KrollDict defaultProps = dataItem.getDefaultProperties(); 224 | KrollDict props = new KrollDict((HashMap)data.get(binding)); 225 | if (defaultProps != null) { 226 | if (update) { 227 | //update default properties 228 | Set existingKeys = defaultProps.keySet(); 229 | for (String key: props.keySet()) { 230 | if (!existingKeys.contains(key)) { 231 | defaultProps.put(key, null); 232 | } 233 | } 234 | } else { 235 | //merge default properties with new properties and update data 236 | HashMap newData = ((HashMap)defaultProps.clone()); 237 | newData.putAll(props); 238 | data.put(binding, newData); 239 | } 240 | } 241 | } 242 | 243 | } 244 | 245 | public void release () { 246 | for (int i = 0; i < dataItems.size(); i++) { 247 | DataItem item = dataItems.get(i); 248 | if (item != null) { 249 | item.release(); 250 | } 251 | } 252 | dataItems.clear(); 253 | if (rootItem != null) { 254 | rootItem.release(); 255 | rootItem = null; 256 | } 257 | } 258 | } -------------------------------------------------------------------------------- /android/src/de/marcelpociot/collectionview/DefaultCollectionViewTemplate.java: -------------------------------------------------------------------------------- 1 | package de.marcelpociot.collectionview; 2 | 3 | 4 | import java.util.HashMap; 5 | import java.util.Iterator; 6 | 7 | import org.appcelerator.kroll.KrollDict; 8 | import org.appcelerator.kroll.common.Log; 9 | import org.appcelerator.titanium.TiC; 10 | import org.appcelerator.titanium.util.TiConvert; 11 | 12 | import ti.modules.titanium.ui.ImageViewProxy; 13 | import ti.modules.titanium.ui.LabelProxy; 14 | import ti.modules.titanium.ui.widget.listview.TiListViewTemplate; 15 | import android.app.Activity; 16 | 17 | public class DefaultCollectionViewTemplate extends CollectionViewTemplate { 18 | 19 | public DefaultCollectionViewTemplate(String id, KrollDict properties, Activity activity) { 20 | super(id, properties); 21 | generateDefaultProps(activity); 22 | } 23 | 24 | public void generateDefaultProps(Activity activity) { 25 | 26 | //Generate root item data proxy 27 | CollectionItemProxy proxy = new CollectionItemProxy(); 28 | proxy.setActivity(activity); 29 | rootItem = new DataItem(proxy, TiC.PROPERTY_PROPERTIES, null); 30 | dataItems.put(itemID, rootItem); 31 | 32 | //Init default properties for our proxies 33 | KrollDict defaultLabelProperties = new KrollDict(); 34 | KrollDict defaultImageProperties = new KrollDict(); 35 | 36 | //Generate label proxy 37 | LabelProxy labelProxy = new LabelProxy(); 38 | labelProxy.getProperties().put(TiC.PROPERTY_TOUCH_ENABLED, false); 39 | labelProxy.setActivity(activity); 40 | //Generate properties 41 | defaultLabelProperties.put(TiC.PROPERTY_LEFT, "2dp"); 42 | defaultLabelProperties.put(TiC.PROPERTY_WIDTH, "55%"); 43 | defaultLabelProperties.put(TiC.PROPERTY_TEXT, "label"); 44 | //bind the proxy and default propertiess 45 | DataItem labelItem = new DataItem(labelProxy, TiC.PROPERTY_TITLE, rootItem); 46 | dataItems.put(TiC.PROPERTY_TITLE, labelItem); 47 | //set default properties 48 | labelItem.setDefaultProperties(defaultLabelProperties); 49 | //add child 50 | rootItem.addChild(labelItem); 51 | 52 | //Generate image proxy 53 | ImageViewProxy imageProxy = new ImageViewProxy(); 54 | imageProxy.getProperties().put(TiC.PROPERTY_TOUCH_ENABLED, false); 55 | imageProxy.setActivity(activity); 56 | //Generate properties 57 | defaultImageProperties.put(TiC.PROPERTY_RIGHT, "25dp"); 58 | defaultImageProperties.put(TiC.PROPERTY_WIDTH, "15%"); 59 | //bind the proxy and default properties 60 | DataItem imageItem = new DataItem (imageProxy, TiC.PROPERTY_IMAGE, rootItem); 61 | dataItems.put(TiC.PROPERTY_IMAGE, imageItem); 62 | //set default properties 63 | imageItem.setDefaultProperties(defaultImageProperties); 64 | //add child 65 | rootItem.addChild(imageItem); 66 | 67 | 68 | } 69 | private void parseDefaultData(KrollDict data) { 70 | //for built-in template, we only process 'properties' key 71 | Iterator bindings = data.keySet().iterator(); 72 | while (bindings.hasNext()) { 73 | String binding = bindings.next(); 74 | if (!binding.equals(TiC.PROPERTY_PROPERTIES)) { 75 | Log.e(TAG, "Please only use 'properties' key for built-in template", Log.DEBUG_MODE); 76 | bindings.remove(); 77 | } 78 | } 79 | 80 | KrollDict properties = data.getKrollDict(TiC.PROPERTY_PROPERTIES); 81 | KrollDict clone_properties = new KrollDict((HashMap)properties); 82 | if (clone_properties.containsKey(TiC.PROPERTY_TITLE)) { 83 | KrollDict text = new KrollDict(); 84 | text.put(TiC.PROPERTY_TEXT, TiConvert.toString(clone_properties, TiC.PROPERTY_TITLE)); 85 | data.put(TiC.PROPERTY_TITLE, text); 86 | if (clone_properties.containsKey(TiC.PROPERTY_FONT)) { 87 | text.put(TiC.PROPERTY_FONT, clone_properties.getKrollDict(TiC.PROPERTY_FONT).clone()); 88 | clone_properties.remove(TiC.PROPERTY_FONT); 89 | } 90 | if (clone_properties.containsKey(TiC.PROPERTY_COLOR)) { 91 | text.put(TiC.PROPERTY_COLOR, clone_properties.get(TiC.PROPERTY_COLOR)); 92 | clone_properties.remove(TiC.PROPERTY_COLOR); 93 | } 94 | clone_properties.remove(TiC.PROPERTY_TITLE); 95 | } 96 | 97 | if (clone_properties.containsKey(TiC.PROPERTY_IMAGE)) { 98 | KrollDict image = new KrollDict(); 99 | image.put(TiC.PROPERTY_IMAGE, TiConvert.toString(clone_properties, TiC.PROPERTY_IMAGE)); 100 | data.put(TiC.PROPERTY_IMAGE, image); 101 | clone_properties.remove(TiC.PROPERTY_IMAGE); 102 | } 103 | 104 | data.put(TiC.PROPERTY_PROPERTIES, clone_properties); 105 | } 106 | 107 | public void updateOrMergeWithDefaultProperties(KrollDict data, boolean update) { 108 | 109 | if (!data.containsKey(TiC.PROPERTY_PROPERTIES)) { 110 | Log.e(TAG, "Please use 'properties' binding for builtInTemplate"); 111 | if (!update) { 112 | //apply default behavior 113 | data.clear(); 114 | } 115 | return; 116 | } 117 | parseDefaultData(data); 118 | super.updateOrMergeWithDefaultProperties(data, update); 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /android/src/de/marcelpociot/collectionview/ExampleProxy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by the Titanium Module SDK helper for Android 3 | * Appcelerator Titanium Mobile 4 | * Copyright (c) 2009-2010 by Appcelerator, Inc. All Rights Reserved. 5 | * Licensed under the terms of the Apache Public License 6 | * Please see the LICENSE included with this distribution for details. 7 | * 8 | */ 9 | package de.marcelpociot.collectionview; 10 | 11 | import org.appcelerator.kroll.KrollDict; 12 | import org.appcelerator.kroll.KrollProxy; 13 | import org.appcelerator.kroll.annotations.Kroll; 14 | import org.appcelerator.titanium.TiC; 15 | import org.appcelerator.kroll.common.Log; 16 | import org.appcelerator.kroll.common.TiConfig; 17 | import org.appcelerator.titanium.util.TiConvert; 18 | import org.appcelerator.titanium.proxy.TiViewProxy; 19 | import org.appcelerator.titanium.view.TiCompositeLayout; 20 | import org.appcelerator.titanium.view.TiCompositeLayout.LayoutArrangement; 21 | import org.appcelerator.titanium.view.TiUIView; 22 | 23 | import android.app.Activity; 24 | 25 | 26 | // This proxy can be created by calling Androidcollectionview.createExample({message: "hello world"}) 27 | @Kroll.proxy(creatableInModule=AndroidcollectionviewModule.class) 28 | public class ExampleProxy extends TiViewProxy 29 | { 30 | // Standard Debugging variables 31 | private static final String LCAT = "ExampleProxy"; 32 | private static final boolean DBG = TiConfig.LOGD; 33 | 34 | private class ExampleView extends TiUIView 35 | { 36 | public ExampleView(TiViewProxy proxy) { 37 | super(proxy); 38 | LayoutArrangement arrangement = LayoutArrangement.DEFAULT; 39 | 40 | if (proxy.hasProperty(TiC.PROPERTY_LAYOUT)) { 41 | String layoutProperty = TiConvert.toString(proxy.getProperty(TiC.PROPERTY_LAYOUT)); 42 | if (layoutProperty.equals(TiC.LAYOUT_HORIZONTAL)) { 43 | arrangement = LayoutArrangement.HORIZONTAL; 44 | } else if (layoutProperty.equals(TiC.LAYOUT_VERTICAL)) { 45 | arrangement = LayoutArrangement.VERTICAL; 46 | } 47 | } 48 | setNativeView(new TiCompositeLayout(proxy.getActivity(), arrangement)); 49 | } 50 | 51 | @Override 52 | public void processProperties(KrollDict d) 53 | { 54 | super.processProperties(d); 55 | } 56 | } 57 | 58 | 59 | // Constructor 60 | public ExampleProxy() 61 | { 62 | super(); 63 | } 64 | 65 | @Override 66 | public TiUIView createView(Activity activity) 67 | { 68 | TiUIView view = new ExampleView(this); 69 | view.getLayoutParams().autoFillsHeight = true; 70 | view.getLayoutParams().autoFillsWidth = true; 71 | return view; 72 | } 73 | 74 | // Handle creation options 75 | @Override 76 | public void handleCreationDict(KrollDict options) 77 | { 78 | super.handleCreationDict(options); 79 | 80 | if (options.containsKey("message")) { 81 | Log.d(LCAT, "example created with message: " + options.get("message")); 82 | } 83 | } 84 | 85 | // Methods 86 | @Kroll.method 87 | public void printMessage(String message) 88 | { 89 | Log.d(LCAT, "printing message: " + message); 90 | } 91 | 92 | 93 | @Kroll.getProperty @Kroll.method 94 | public String getMessage() 95 | { 96 | return "Hello World from my module"; 97 | } 98 | 99 | @Kroll.setProperty @Kroll.method 100 | public void setMessage(String message) 101 | { 102 | Log.d(LCAT, "Tried setting module message to: " + message); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /android/src/de/marcelpociot/collectionview/SwipeProgressBar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package de.marcelpociot.collectionview; 18 | 19 | import android.graphics.Canvas; 20 | import android.graphics.Paint; 21 | import android.graphics.Rect; 22 | import android.graphics.RectF; 23 | import android.support.v4.view.ViewCompat; 24 | import android.view.View; 25 | import android.view.animation.AnimationUtils; 26 | import android.view.animation.Interpolator; 27 | 28 | 29 | /** 30 | * Custom progress bar that shows a cycle of colors as widening circles that 31 | * overdraw each other. When finished, the bar is cleared from the inside out as 32 | * the main cycle continues. Before running, this can also indicate how close 33 | * the user is to triggering something (e.g. how far they need to pull down to 34 | * trigger a refresh). 35 | */ 36 | final class SwipeProgressBar { 37 | 38 | // Default progress animation colors are grays. 39 | private final static int COLOR1 = 0xB3000000; 40 | private final static int COLOR2 = 0x80000000; 41 | private final static int COLOR3 = 0x4d000000; 42 | private final static int COLOR4 = 0x1a000000; 43 | 44 | // The duration of the animation cycle. 45 | private static final int ANIMATION_DURATION_MS = 2000; 46 | 47 | // The duration of the animation to clear the bar. 48 | private static final int FINISH_ANIMATION_DURATION_MS = 1000; 49 | 50 | // Interpolator for varying the speed of the animation. 51 | private static final Interpolator INTERPOLATOR = BakedBezierInterpolator.getInstance(); 52 | 53 | private final Paint mPaint = new Paint(); 54 | private final RectF mClipRect = new RectF(); 55 | private float mTriggerPercentage; 56 | private long mStartTime; 57 | private long mFinishTime; 58 | private boolean mRunning; 59 | 60 | // Colors used when rendering the animation, 61 | private int mColor1; 62 | private int mColor2; 63 | private int mColor3; 64 | private int mColor4; 65 | private View mParent; 66 | 67 | private Rect mBounds = new Rect(); 68 | 69 | public SwipeProgressBar(View parent) { 70 | mParent = parent; 71 | mColor1 = COLOR1; 72 | mColor2 = COLOR2; 73 | mColor3 = COLOR3; 74 | mColor4 = COLOR4; 75 | } 76 | 77 | /** 78 | * Set the four colors used in the progress animation. The first color will 79 | * also be the color of the bar that grows in response to a user swipe 80 | * gesture. 81 | * 82 | * @param color1 Integer representation of a color. 83 | * @param color2 Integer representation of a color. 84 | * @param color3 Integer representation of a color. 85 | * @param color4 Integer representation of a color. 86 | */ 87 | void setColorScheme(int color1, int color2, int color3, int color4) { 88 | mColor1 = color1; 89 | mColor2 = color2; 90 | mColor3 = color3; 91 | mColor4 = color4; 92 | } 93 | 94 | /** 95 | * Update the progress the user has made toward triggering the swipe 96 | * gesture. and use this value to update the percentage of the trigger that 97 | * is shown. 98 | */ 99 | void setTriggerPercentage(float triggerPercentage) { 100 | mTriggerPercentage = triggerPercentage; 101 | mStartTime = 0; 102 | ViewCompat.postInvalidateOnAnimation(mParent); 103 | } 104 | 105 | /** 106 | * Start showing the progress animation. 107 | */ 108 | void start() { 109 | if (!mRunning) { 110 | mTriggerPercentage = 0; 111 | mStartTime = AnimationUtils.currentAnimationTimeMillis(); 112 | mRunning = true; 113 | mParent.postInvalidate(); 114 | } 115 | } 116 | 117 | /** 118 | * Stop showing the progress animation. 119 | */ 120 | void stop() { 121 | if (mRunning) { 122 | mTriggerPercentage = 0; 123 | mFinishTime = AnimationUtils.currentAnimationTimeMillis(); 124 | mRunning = false; 125 | mParent.postInvalidate(); 126 | } 127 | } 128 | 129 | /** 130 | * @return Return whether the progress animation is currently running. 131 | */ 132 | boolean isRunning() { 133 | return mRunning || mFinishTime > 0; 134 | } 135 | 136 | void draw(Canvas canvas) { 137 | final int width = mBounds.width(); 138 | final int height = mBounds.height(); 139 | final int cx = width / 2; 140 | final int cy = height / 2; 141 | boolean drawTriggerWhileFinishing = false; 142 | int restoreCount = canvas.save(); 143 | canvas.clipRect(mBounds); 144 | 145 | if (mRunning || (mFinishTime > 0)) { 146 | long now = AnimationUtils.currentAnimationTimeMillis(); 147 | long elapsed = (now - mStartTime) % ANIMATION_DURATION_MS; 148 | long iterations = (now - mStartTime) / ANIMATION_DURATION_MS; 149 | float rawProgress = (elapsed / (ANIMATION_DURATION_MS / 100f)); 150 | 151 | // If we're not running anymore, that means we're running through 152 | // the finish animation. 153 | if (!mRunning) { 154 | // If the finish animation is done, don't draw anything, and 155 | // don't repost. 156 | if ((now - mFinishTime) >= FINISH_ANIMATION_DURATION_MS) { 157 | mFinishTime = 0; 158 | return; 159 | } 160 | 161 | // Otherwise, use a 0 opacity alpha layer to clear the animation 162 | // from the inside out. This layer will prevent the circles from 163 | // drawing within its bounds. 164 | long finishElapsed = (now - mFinishTime) % FINISH_ANIMATION_DURATION_MS; 165 | float finishProgress = (finishElapsed / (FINISH_ANIMATION_DURATION_MS / 100f)); 166 | float pct = (finishProgress / 100f); 167 | // Radius of the circle is half of the screen. 168 | float clearRadius = width / 2 * INTERPOLATOR.getInterpolation(pct); 169 | mClipRect.set(cx - clearRadius, 0, cx + clearRadius, height); 170 | canvas.saveLayerAlpha(mClipRect, 0, 0); 171 | // Only draw the trigger if there is a space in the center of 172 | // this refreshing view that needs to be filled in by the 173 | // trigger. If the progress view is just still animating, let it 174 | // continue animating. 175 | drawTriggerWhileFinishing = true; 176 | } 177 | 178 | // First fill in with the last color that would have finished drawing. 179 | if (iterations == 0) { 180 | canvas.drawColor(mColor1); 181 | } else { 182 | if (rawProgress >= 0 && rawProgress < 25) { 183 | canvas.drawColor(mColor4); 184 | } else if (rawProgress >= 25 && rawProgress < 50) { 185 | canvas.drawColor(mColor1); 186 | } else if (rawProgress >= 50 && rawProgress < 75) { 187 | canvas.drawColor(mColor2); 188 | } else { 189 | canvas.drawColor(mColor3); 190 | } 191 | } 192 | 193 | // Then draw up to 4 overlapping concentric circles of varying radii, based on how far 194 | // along we are in the cycle. 195 | // progress 0-50 draw mColor2 196 | // progress 25-75 draw mColor3 197 | // progress 50-100 draw mColor4 198 | // progress 75 (wrap to 25) draw mColor1 199 | if ((rawProgress >= 0 && rawProgress <= 25)) { 200 | float pct = (((rawProgress + 25) * 2) / 100f); 201 | drawCircle(canvas, cx, cy, mColor1, pct); 202 | } 203 | if (rawProgress >= 0 && rawProgress <= 50) { 204 | float pct = ((rawProgress * 2) / 100f); 205 | drawCircle(canvas, cx, cy, mColor2, pct); 206 | } 207 | if (rawProgress >= 25 && rawProgress <= 75) { 208 | float pct = (((rawProgress - 25) * 2) / 100f); 209 | drawCircle(canvas, cx, cy, mColor3, pct); 210 | } 211 | if (rawProgress >= 50 && rawProgress <= 100) { 212 | float pct = (((rawProgress - 50) * 2) / 100f); 213 | drawCircle(canvas, cx, cy, mColor4, pct); 214 | } 215 | if ((rawProgress >= 75 && rawProgress <= 100)) { 216 | float pct = (((rawProgress - 75) * 2) / 100f); 217 | drawCircle(canvas, cx, cy, mColor1, pct); 218 | } 219 | if (mTriggerPercentage > 0 && drawTriggerWhileFinishing) { 220 | // There is some portion of trigger to draw. Restore the canvas, 221 | // then draw the trigger. Otherwise, the trigger does not appear 222 | // until after the bar has finished animating and appears to 223 | // just jump in at a larger width than expected. 224 | canvas.restoreToCount(restoreCount); 225 | restoreCount = canvas.save(); 226 | canvas.clipRect(mBounds); 227 | drawTrigger(canvas, cx, cy); 228 | } 229 | // Keep running until we finish out the last cycle. 230 | ViewCompat.postInvalidateOnAnimation(mParent); 231 | } else { 232 | // Otherwise if we're in the middle of a trigger, draw that. 233 | if (mTriggerPercentage > 0 && mTriggerPercentage <= 1.0) { 234 | drawTrigger(canvas, cx, cy); 235 | } 236 | } 237 | canvas.restoreToCount(restoreCount); 238 | } 239 | 240 | private void drawTrigger(Canvas canvas, int cx, int cy) { 241 | mPaint.setColor(mColor1); 242 | canvas.drawCircle(cx, cy, cx * mTriggerPercentage, mPaint); 243 | } 244 | 245 | /** 246 | * Draws a circle centered in the view. 247 | * 248 | * @param canvas the canvas to draw on 249 | * @param cx the center x coordinate 250 | * @param cy the center y coordinate 251 | * @param color the color to draw 252 | * @param pct the percentage of the view that the circle should cover 253 | */ 254 | private void drawCircle(Canvas canvas, float cx, float cy, int color, float pct) { 255 | mPaint.setColor(color); 256 | canvas.save(); 257 | canvas.translate(cx, cy); 258 | float radiusScale = INTERPOLATOR.getInterpolation(pct); 259 | canvas.scale(radiusScale, radiusScale); 260 | canvas.drawCircle(0, 0, cx, mPaint); 261 | canvas.restore(); 262 | } 263 | 264 | /** 265 | * Set the drawing bounds of this SwipeProgressBar. 266 | */ 267 | void setBounds(int left, int top, int right, int bottom) { 268 | mBounds.left = left; 269 | mBounds.top = top; 270 | mBounds.right = right; 271 | mBounds.bottom = bottom; 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /android/src/de/marcelpociot/collectionview/ViewItem.java: -------------------------------------------------------------------------------- 1 | package de.marcelpociot.collectionview; 2 | 3 | import java.util.HashMap; 4 | 5 | import org.appcelerator.kroll.KrollDict; 6 | import org.appcelerator.titanium.view.TiUIView; 7 | 8 | public class ViewItem { 9 | TiUIView view; 10 | KrollDict properties; 11 | KrollDict diffProperties; 12 | 13 | public ViewItem(TiUIView view, KrollDict props) { 14 | properties = new KrollDict((HashMap)props.clone()); 15 | this.view = view; 16 | diffProperties = new KrollDict(); 17 | } 18 | 19 | public TiUIView getView() { 20 | return view; 21 | } 22 | 23 | /** 24 | * This method compares applied properties of a view and our data model to 25 | * generate a new set of properties we need to set. It is crucial for scrolling performance. 26 | * @param properties The properties from our data model 27 | * @return The difference set of properties to set 28 | */ 29 | public KrollDict generateDiffProperties(KrollDict properties) { 30 | diffProperties.clear(); 31 | 32 | for (String appliedProp : this.properties.keySet()) { 33 | if (!properties.containsKey(appliedProp)) { 34 | applyProperty(appliedProp, null); 35 | } 36 | } 37 | 38 | for (String property : properties.keySet()) { 39 | Object value = properties.get(property); 40 | if (CollectionView.MUST_SET_PROPERTIES.contains(property)) { 41 | applyProperty(property, value); 42 | continue; 43 | } 44 | 45 | Object existingVal = this.properties.get(property); 46 | if (existingVal == null || value == null || !existingVal.equals(value)) { 47 | applyProperty(property, value); 48 | } 49 | } 50 | return diffProperties; 51 | 52 | } 53 | 54 | private void applyProperty(String key, Object value) { 55 | diffProperties.put(key, value); 56 | properties.put(key, value); 57 | } 58 | 59 | 60 | } -------------------------------------------------------------------------------- /android/timodule.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-android-1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-android-1.0.0.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-android-1.1.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-android-1.1.1.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-android-1.2.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-android-1.2.0.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-android-1.3.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-android-1.3.0.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-android-1.3.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-android-1.3.1.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-iphone-1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-iphone-1.0.0.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-iphone-1.1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-iphone-1.1.0.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-iphone-1.1.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-iphone-1.1.1.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-iphone-1.1.2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-iphone-1.1.2.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-iphone-1.2.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-iphone-1.2.0.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-iphone-1.3.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-iphone-1.3.0.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-iphone-1.3.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-iphone-1.3.1.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-iphone-1.4.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-iphone-1.4.0.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-iphone-1.4.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-iphone-1.4.1.zip -------------------------------------------------------------------------------- /dist/de.marcelpociot.collectionview-iphone-1.4.2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/de.marcelpociot.collectionview-iphone-1.4.2.zip -------------------------------------------------------------------------------- /dist/ti.collectionview-android-2.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/ti.collectionview-android-2.0.0.zip -------------------------------------------------------------------------------- /dist/ti.collectionview-android-2.0.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/ti.collectionview-android-2.0.1.zip -------------------------------------------------------------------------------- /dist/ti.collectionview-android-3.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/ti.collectionview-android-3.0.0.zip -------------------------------------------------------------------------------- /dist/ti.collectionview-iphone-2.0.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/dist/ti.collectionview-iphone-2.0.1.zip -------------------------------------------------------------------------------- /documentation/contextmenu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/documentation/contextmenu.gif -------------------------------------------------------------------------------- /documentation/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/documentation/grid.png -------------------------------------------------------------------------------- /documentation/waterfall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuno/TiCollectionView/8ab6d5f8855aba4f21fc3f4c9e6d1c424c08cffc/documentation/waterfall.png -------------------------------------------------------------------------------- /example/SampleProjectAlloy/app/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": {}, 3 | "env:development": {}, 4 | "env:test": {}, 5 | "env:production": {}, 6 | "os:android": {}, 7 | "os:blackberry": {}, 8 | "os:ios": {}, 9 | "os:mobileweb": {}, 10 | "dependencies": { 11 | "nl.fokkezb.pullToRefresh": "2.0.1" 12 | } 13 | } -------------------------------------------------------------------------------- /example/SampleProjectAlloy/app/controllers/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $.listView.addEventListener("pull", function(e){ 5 | Ti.API.info(e); 6 | }); 7 | 8 | $.listView.addEventListener("pullend", function(e){ 9 | Ti.API.info(e); 10 | }); 11 | 12 | function myRefresher(e) { 13 | 14 | Ti.API.info("myRefresher"); 15 | 16 | // fake a remote fetch 17 | setTimeout(function(){ 18 | Ti.API.info("myRefresher callback"); 19 | e.hide(); 20 | }, 3000); 21 | } 22 | 23 | // init 24 | $.ptr.refresh(); 25 | 26 | $.win.open(); -------------------------------------------------------------------------------- /example/SampleProjectAlloy/app/lib/CollectionView.js: -------------------------------------------------------------------------------- 1 | // The two following statements are required so Titanium can detect the 2 | // use of ListView and RefreshControl at compile time. 3 | Ti.UI.createListView(); 4 | Ti.UI.createRefreshControl(); 5 | 6 | function createCollectionView(options) { 7 | if( OS_IOS ) 8 | { 9 | return require("ti.collectionview").createCollectionView(options); 10 | } 11 | var templates = options.templates; 12 | for (var binding in templates) { 13 | var currentTemplate = templates[binding]; 14 | //process template 15 | processTemplate(currentTemplate); 16 | //process child templates 17 | processChildTemplates(currentTemplate); 18 | } 19 | Ti.API.info( JSON.stringify(options) ); 20 | var listView = require("ti.collectionview").createCollectionView(options); 21 | 22 | return listView; 23 | } 24 | 25 | //Create ListItemProxy, add events, then store it in 'tiProxy' property 26 | function processTemplate(properties) { 27 | var cellProxy = require("ti.collectionview").createCollectionItem(); 28 | properties.tiProxy = cellProxy; 29 | var events = properties.events; 30 | addEventListeners(events, cellProxy); 31 | } 32 | 33 | //Recursive function that process childTemplates and append corresponding proxies to 34 | //property 'tiProxy'. I.e: type: "Titanium.UI.Label" -> tiProxy: LabelProxy object 35 | function processChildTemplates(properties) { 36 | if (!properties.hasOwnProperty('childTemplates')) return; 37 | 38 | var childProperties = properties.childTemplates; 39 | if (childProperties === void 0 || childProperties === null) return; 40 | 41 | for (var i = 0; i < childProperties.length; i++) { 42 | var child = childProperties[i]; 43 | var proxyType = child.type; 44 | if (proxyType !== void 0) { 45 | var creationProperties = child.properties; 46 | var creationFunction = lookup(proxyType); 47 | var childProxy; 48 | //create the proxy 49 | if (creationProperties !== void 0) { 50 | childProxy = creationFunction(creationProperties); 51 | } else { 52 | childProxy = creationFunction(); 53 | } 54 | //add event listeners 55 | var events = child.events; 56 | addEventListeners(events, childProxy); 57 | //append proxy to tiProxy property 58 | child.tiProxy = childProxy; 59 | } 60 | 61 | processChildTemplates(child); 62 | 63 | } 64 | 65 | 66 | } 67 | 68 | //add event listeners 69 | function addEventListeners(events, proxy) { 70 | if (events !== void 0) { 71 | for (var eventName in events) { 72 | proxy.addEventListener(eventName, events[eventName]); 73 | } 74 | } 75 | } 76 | 77 | //convert name of UI elements into a constructor function. 78 | //I.e: lookup("Titanium.UI.Label") returns Titanium.UI.createLabel function 79 | function lookup(name) { 80 | var lastDotIndex = name.lastIndexOf('.'); 81 | var proxy = eval(name.substring(0, lastDotIndex)); 82 | if (typeof(proxy) == undefined) return; 83 | 84 | var proxyName = name.slice(lastDotIndex + 1); 85 | return proxy['create' + proxyName]; 86 | } 87 | 88 | exports.createCollectionView = createCollectionView; -------------------------------------------------------------------------------- /example/SampleProjectAlloy/app/styles/index.tss: -------------------------------------------------------------------------------- 1 | 2 | "Label": { 3 | width: Ti.UI.SIZE, 4 | height: Ti.UI.SIZE, 5 | color: "#000", 6 | font: { 7 | fontSize: 12 8 | } 9 | } 10 | 11 | "ListSection":{ 12 | itemSpacing: 40, 13 | lineSpacing: 14 14 | } 15 | -------------------------------------------------------------------------------- /example/SampleProjectAlloy/app/views/index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /example/SampleProjectAlloy/app/widgets/nl.fokkezb.pullToRefresh/README.md: -------------------------------------------------------------------------------- 1 | # Alloy *Pull to Refresh* Widget 2 | 3 | You can find the README at [https://github.com/fokkezb/nl.fokkezb.drawer](https://github.com/fokkezb/nl.fokkezb.pullToRefresh) 4 | 5 | ## License 6 | 7 |
 8 | Copyright 2013-2014 Fokke Zandbergen
 9 | 
10 | Licensed under the Apache License, Version 2.0 (the "License");
11 | you may not use this file except in compliance with the License.
12 | You may obtain a copy of the License at
13 | 
14 |    http://www.apache.org/licenses/LICENSE-2.0
15 | 
16 | Unless required by applicable law or agreed to in writing, software
17 | distributed under the License is distributed on an "AS IS" BASIS,
18 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 | See the License for the specific language governing permissions and
20 | limitations under the License.
21 | 
-------------------------------------------------------------------------------- /example/SampleProjectAlloy/app/widgets/nl.fokkezb.pullToRefresh/controllers/widget.js: -------------------------------------------------------------------------------- 1 | var moment = require('alloy/moment'); 2 | 3 | var refreshControl; 4 | 5 | var isRefreshing = false; 6 | $.refresh = refresh; 7 | 8 | $.hide = hide; 9 | $.show = show; 10 | 11 | (function constructor(args) { 12 | 13 | if (!OS_IOS && !OS_ANDROID) { 14 | console.warn('[pullToRefresh] only supports iOS and Android.'); 15 | return; 16 | } 17 | 18 | if (!_.isArray(args.children) || !_.contains(['Ti.UI.ListView', 'Ti.UI.TableView','de.marcelpociot.CollectionView'], args.children[0].apiName)) { 19 | console.error('[pullToRefresh] is missing required Ti.UI.ListView or Ti.UI.TableView or de.marcelpociot.CollectionView as first child element.'); 20 | return; 21 | } 22 | 23 | 24 | var list = args.children[0]; 25 | delete args.children; 26 | 27 | _.extend($, args); 28 | 29 | if (OS_IOS) { 30 | refreshControl = Ti.UI.createRefreshControl(); 31 | var attr = Titanium.UI.iOS.createAttributedString({ 32 | text: "Pull to Refresh.", 33 | attributes: [ 34 | 35 | ] 36 | }); 37 | refreshControl.setTitle(attr); 38 | refreshControl.addEventListener('refreshstart', onRefreshstart); 39 | 40 | list.refreshControl = refreshControl; 41 | 42 | $.addTopLevelView(list); 43 | 44 | } else if (OS_ANDROID) { 45 | refreshControl = require('com.rkam.swiperefreshlayout').createSwipeRefresh({ 46 | view: list 47 | }); 48 | 49 | refreshControl.addEventListener('refreshing', onRefreshstart); 50 | 51 | $.addTopLevelView(refreshControl); 52 | } 53 | 54 | })(arguments[0] || {}); 55 | 56 | function refresh() { 57 | if (!isRefreshing) { 58 | isRefreshing = true; 59 | show(); 60 | 61 | onRefreshstart(); 62 | } 63 | } 64 | 65 | function hide() { 66 | isRefreshing = false; 67 | if (OS_IOS) { 68 | var attr = Titanium.UI.iOS.createAttributedString({ 69 | text: "Last Updated: " + new moment().format("MM-DD-YYYY hh:mm:ss a"), 70 | attributes: [ 71 | 72 | ] 73 | }); 74 | refreshControl.setTitle(attr); 75 | refreshControl.endRefreshing(); 76 | 77 | } else if (OS_ANDROID) { 78 | refreshControl.setRefreshing(false); 79 | } 80 | } 81 | 82 | function show() { 83 | 84 | if (OS_IOS) { 85 | refreshControl.beginRefreshing(); 86 | } else if (OS_ANDROID) { 87 | refreshControl.setRefreshing(true); 88 | } 89 | } 90 | 91 | function onRefreshstart() { 92 | 93 | $.trigger('release', { 94 | source: $, 95 | hide: hide 96 | }); 97 | } 98 | -------------------------------------------------------------------------------- /example/SampleProjectAlloy/app/widgets/nl.fokkezb.pullToRefresh/views/widget.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/SampleProjectAlloy/app/widgets/nl.fokkezb.pullToRefresh/widget.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "nl.fokkezb.pullToRefresh", 3 | "name": "Pull to Refresh Widget", 4 | "description" : "Uniform wrapper for iOS RefreshControl and Android SwipeRefreshLayout.", 5 | "author": "Fokke Zandbergen", 6 | "version": "2.0.1", 7 | "copyright":"Copyright (c) 2013-2015", 8 | "license":"Apache 2.0", 9 | "min-alloy-version": "1.5", 10 | "min-titanium-version":"3.4", 11 | "tags":"tableview,headerpullview,pulltorefresh,swiperefreshlayout,listview,pullview,refreshcontrol", 12 | "platforms":"ios,android" 13 | } -------------------------------------------------------------------------------- /example/SampleProjectClassic/app.js: -------------------------------------------------------------------------------- 1 | var TiCollectionView = require('ti.collectionview'); 2 | 3 | var win = Ti.UI.createWindow({ 4 | backgroundColor: 'white' 5 | }); 6 | 7 | // Create a custom template that displays an image on the left, 8 | // then a title next to it with a subtitle below it. 9 | var myTemplate = { 10 | childTemplates: [ 11 | { // Title 12 | type: 'Ti.UI.Label', // Use a label for the title 13 | bindId: 'info', // Maps to a custom info property of the item data 14 | properties: { // Sets the label properties 15 | font: { fontSize: 20, fontWeight:'bold' }, 16 | top: 10, 17 | } 18 | }, { // Subtitle 19 | type: 'Ti.UI.Label', // Use a label for the subtitle 20 | bindId: 'es_info', // Maps to a custom es_info property of the item data 21 | properties: { // Sets the label properties 22 | color: 'gray', 23 | font: { fontSize: 14 }, 24 | top: 40, 25 | } 26 | } 27 | ] 28 | }; 29 | 30 | var collectionView = TiCollectionView.createCollectionView({ 31 | backgroundColor: 'white', 32 | // Maps myTemplate dictionary to 'template' string 33 | templates: { 'template': myTemplate }, 34 | // Use 'template', that is, the myTemplate dict created earlier 35 | // for all items as long as the template property is not defined for an item. 36 | defaultItemTemplate: 'template', 37 | 38 | // ANDROID ONLY 39 | columnWidth: 150, 40 | verticalSpacing: 10, 41 | horizontalSpacing: 10 42 | }); 43 | 44 | collectionView.addEventListener('itemclick', function(e) { 45 | alert('Tapped cell at section = ' + e.sectionIndex + ', item = ' + e.itemIndex); 46 | }); 47 | 48 | var sections = []; 49 | 50 | var fruitSection = TiCollectionView.createCollectionSection({ headerTitle: 'Fruits / Frutas' }); 51 | var fruitDataSet = [ 52 | // the text property of info maps to the text property of the title label 53 | // the text property of es_info maps to text property of the subtitle label 54 | // the image property of pic maps to the image property of the image view 55 | { info: { text: 'Apple 1' }, es_info: { text: 'Manzana 1' }, properties: { height: 150, width: 150 } }, 56 | { info: { text: 'Apple 2' }, es_info: { text: 'Manzana 2' }, properties: { height: 150, width: 150 } }, 57 | { info: { text: 'Apple 3' }, es_info: { text: 'Manzana 3' }, properties: { height: 150, width: 150 } }, 58 | { info: { text: 'Apple 4' }, es_info: { text: 'Manzana 4' }, properties: { height: 150, width: 150 } }, 59 | { info: { text: 'Apple 5' }, es_info: { text: 'Manzana 5' }, properties: { height: 150, width: 150 } }, 60 | { info: { text: 'Apple 6' }, es_info: { text: 'Manzana 6' }, properties: { height: 150, width: 150 } }, 61 | ]; 62 | 63 | fruitSection.setItems(fruitDataSet); 64 | sections.push(fruitSection); 65 | 66 | collectionView.setSections(sections); 67 | 68 | win.add(collectionView); 69 | win.open(); 70 | -------------------------------------------------------------------------------- /ios/Classes/CHTCollectionViewWaterfallLayout/CHTCollectionViewWaterfallLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionViewWaterfallLayout.h 3 | // 4 | // Created by Nelson on 12/11/19. 5 | // Copyright (c) 2012 Nelson Tai. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | /** 11 | * Enumerated structure to define direction in which items can be rendered. 12 | */ 13 | typedef NS_ENUM (NSUInteger, CHTCollectionViewWaterfallLayoutItemRenderDirection) { 14 | CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst, 15 | CHTCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight, 16 | CHTCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft 17 | }; 18 | 19 | /** 20 | * Constants that specify the types of supplementary views that can be presented using a waterfall layout. 21 | */ 22 | 23 | /// A supplementary view that identifies the header for a given section. 24 | extern NSString *const CHTCollectionElementKindSectionHeader; 25 | /// A supplementary view that identifies the footer for a given section. 26 | extern NSString *const CHTCollectionElementKindSectionFooter; 27 | 28 | #pragma mark - CHTCollectionViewDelegateWaterfallLayout 29 | 30 | @class CHTCollectionViewWaterfallLayout; 31 | 32 | /** 33 | * The CHTCollectionViewDelegateWaterfallLayout protocol defines methods that let you coordinate with a 34 | * CHTCollectionViewWaterfallLayout object to implement a waterfall-based layout. 35 | * The methods of this protocol define the size of items. 36 | * 37 | * The waterfall layout object expects the collection view’s delegate object to adopt this protocol. 38 | * Therefore, implement this protocol on object assigned to your collection view’s delegate property. 39 | */ 40 | @protocol CHTCollectionViewDelegateWaterfallLayout 41 | @required 42 | /** 43 | * Asks the delegate for the size of the specified item’s cell. 44 | * 45 | * @param collectionView 46 | * The collection view object displaying the waterfall layout. 47 | * @param collectionViewLayout 48 | * The layout object requesting the information. 49 | * @param indexPath 50 | * The index path of the item. 51 | * 52 | * @return 53 | * The original size of the specified item. Both width and height must be greater than 0. 54 | */ 55 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath; 56 | 57 | @optional 58 | /** 59 | * Asks the delegate for the column count in a section 60 | * 61 | * @param collectionView 62 | * The collection view object displaying the waterfall layout. 63 | * @param collectionViewLayout 64 | * The layout object requesting the information. 65 | * @param section 66 | * The section. 67 | * 68 | * @return 69 | * The original column count for that section. Must be greater than 0. 70 | */ 71 | - (NSInteger)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout columnCountForSection:(NSInteger)section; 72 | 73 | /** 74 | * Asks the delegate for the height of the header view in the specified section. 75 | * 76 | * @param collectionView 77 | * The collection view object displaying the waterfall layout. 78 | * @param collectionViewLayout 79 | * The layout object requesting the information. 80 | * @param section 81 | * The index of the section whose header size is being requested. 82 | * 83 | * @return 84 | * The height of the header. If you return 0, no header is added. 85 | * 86 | * @discussion 87 | * If you do not implement this method, the waterfall layout uses the value in its headerHeight property to set the size of the header. 88 | * 89 | * @see 90 | * headerHeight 91 | */ 92 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout heightForHeaderInSection:(NSInteger)section; 93 | 94 | /** 95 | * Asks the delegate for the height of the footer view in the specified section. 96 | * 97 | * @param collectionView 98 | * The collection view object displaying the waterfall layout. 99 | * @param collectionViewLayout 100 | * The layout object requesting the information. 101 | * @param section 102 | * The index of the section whose header size is being requested. 103 | * 104 | * @return 105 | * The height of the footer. If you return 0, no footer is added. 106 | * 107 | * @discussion 108 | * If you do not implement this method, the waterfall layout uses the value in its footerHeight property to set the size of the footer. 109 | * 110 | * @see 111 | * footerHeight 112 | */ 113 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout heightForFooterInSection:(NSInteger)section; 114 | 115 | /** 116 | * Asks the delegate for the insets in the specified section. 117 | * 118 | * @param collectionView 119 | * The collection view object displaying the waterfall layout. 120 | * @param collectionViewLayout 121 | * The layout object requesting the information. 122 | * @param section 123 | * The index of the section whose insets are being requested. 124 | * 125 | * @discussion 126 | * If you do not implement this method, the waterfall layout uses the value in its sectionInset property. 127 | * 128 | * @return 129 | * The insets for the section. 130 | */ 131 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section; 132 | 133 | /** 134 | * Asks the delegate for the header insets in the specified section. 135 | * 136 | * @param collectionView 137 | * The collection view object displaying the waterfall layout. 138 | * @param collectionViewLayout 139 | * The layout object requesting the information. 140 | * @param section 141 | * The index of the section whose header insets are being requested. 142 | * 143 | * @discussion 144 | * If you do not implement this method, the waterfall layout uses the value in its headerInset property. 145 | * 146 | * @return 147 | * The headerInsets for the section. 148 | */ 149 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForHeaderInSection:(NSInteger)section; 150 | 151 | /** 152 | * Asks the delegate for the footer insets in the specified section. 153 | * 154 | * @param collectionView 155 | * The collection view object displaying the waterfall layout. 156 | * @param collectionViewLayout 157 | * The layout object requesting the information. 158 | * @param section 159 | * The index of the section whose footer insets are being requested. 160 | * 161 | * @discussion 162 | * If you do not implement this method, the waterfall layout uses the value in its footerInset property. 163 | * 164 | * @return 165 | * The footerInsets for the section. 166 | */ 167 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForFooterInSection:(NSInteger)section; 168 | 169 | /** 170 | * Asks the delegate for the minimum spacing between two items in the same column 171 | * in the specified section. If this method is not implemented, the 172 | * minimumInteritemSpacing property is used for all sections. 173 | * 174 | * @param collectionView 175 | * The collection view object displaying the waterfall layout. 176 | * @param collectionViewLayout 177 | * The layout object requesting the information. 178 | * @param section 179 | * The index of the section whose minimum interitem spacing is being requested. 180 | * 181 | * @discussion 182 | * If you do not implement this method, the waterfall layout uses the value in its minimumInteritemSpacing property to determine the amount of space between items in the same column. 183 | * 184 | * @return 185 | * The minimum interitem spacing. 186 | */ 187 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section; 188 | 189 | /** 190 | * Asks the delegate for the minimum spacing between colums in a secified section. If this method is not implemented, the 191 | * minimumColumnSpacing property is used for all sections. 192 | * 193 | * @param collectionView 194 | * The collection view object displaying the waterfall layout. 195 | * @param collectionViewLayout 196 | * The layout object requesting the information. 197 | * @param section 198 | * The index of the section whose minimum interitem spacing is being requested. 199 | * 200 | * @discussion 201 | * If you do not implement this method, the waterfall layout uses the value in its minimumColumnSpacing property to determine the amount of space between columns in each section. 202 | * 203 | * @return 204 | * The minimum spacing between each column. 205 | */ 206 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumColumnSpacingForSectionAtIndex:(NSInteger)section; 207 | 208 | @end 209 | 210 | #pragma mark - CHTCollectionViewWaterfallLayout 211 | 212 | /** 213 | * The CHTCollectionViewWaterfallLayout class is a concrete layout object that organizes items into waterfall-based grids 214 | * with optional header and footer views for each section. 215 | * 216 | * A waterfall layout works with the collection view’s delegate object to determine the size of items, headers, and footers 217 | * in each section. That delegate object must conform to the `CHTCollectionViewDelegateWaterfallLayout` protocol. 218 | * 219 | * Each section in a waterfall layout can have its own custom header and footer. To configure the header or footer for a view, 220 | * you must configure the height of the header or footer to be non zero. You can do this by implementing the appropriate delegate 221 | * methods or by assigning appropriate values to the `headerHeight` and `footerHeight` properties. 222 | * If the header or footer height is 0, the corresponding view is not added to the collection view. 223 | * 224 | * @note CHTCollectionViewWaterfallLayout doesn't support decoration view, and it supports vertical scrolling direction only. 225 | */ 226 | @interface CHTCollectionViewWaterfallLayout : UICollectionViewLayout 227 | 228 | /** 229 | * @brief How many columns for this layout. 230 | * @discussion Default: 2 231 | */ 232 | @property (nonatomic, assign) NSInteger columnCount; 233 | 234 | /** 235 | * @brief The minimum spacing to use between successive columns. 236 | * @discussion Default: 10.0 237 | */ 238 | @property (nonatomic, assign) CGFloat minimumColumnSpacing; 239 | 240 | /** 241 | * @brief The minimum spacing to use between items in the same column. 242 | * @discussion Default: 10.0 243 | * @note This spacing is not applied to the space between header and columns or between columns and footer. 244 | */ 245 | @property (nonatomic, assign) CGFloat minimumInteritemSpacing; 246 | 247 | /** 248 | * @brief Height for section header 249 | * @discussion 250 | * If your collectionView's delegate doesn't implement `collectionView:layout:heightForHeaderInSection:`, 251 | * then this value will be used. 252 | * 253 | * Default: 0 254 | */ 255 | @property (nonatomic, assign) CGFloat headerHeight; 256 | 257 | /** 258 | * @brief Height for section footer 259 | * @discussion 260 | * If your collectionView's delegate doesn't implement `collectionView:layout:heightForFooterInSection:`, 261 | * then this value will be used. 262 | * 263 | * Default: 0 264 | */ 265 | @property (nonatomic, assign) CGFloat footerHeight; 266 | 267 | /** 268 | * @brief The margins that are used to lay out the header for each section. 269 | * @discussion 270 | * These insets are applied to the headers in each section. 271 | * They represent the distance between the top of the collection view and the top of the content items 272 | * They also indicate the spacing on either side of the header. They do not affect the size of the headers or footers themselves. 273 | * 274 | * Default: UIEdgeInsetsZero 275 | */ 276 | @property (nonatomic, assign) UIEdgeInsets headerInset; 277 | 278 | /** 279 | * @brief The margins that are used to lay out the footer for each section. 280 | * @discussion 281 | * These insets are applied to the footers in each section. 282 | * They represent the distance between the top of the collection view and the top of the content items 283 | * They also indicate the spacing on either side of the footer. They do not affect the size of the headers or footers themselves. 284 | * 285 | * Default: UIEdgeInsetsZero 286 | */ 287 | @property (nonatomic, assign) UIEdgeInsets footerInset; 288 | 289 | /** 290 | * @brief The margins that are used to lay out content in each section. 291 | * @discussion 292 | * Section insets are margins applied only to the items in the section. 293 | * They represent the distance between the header view and the columns and between the columns and the footer view. 294 | * They also indicate the spacing on either side of columns. They do not affect the size of the headers or footers themselves. 295 | * 296 | * Default: UIEdgeInsetsZero 297 | */ 298 | @property (nonatomic, assign) UIEdgeInsets sectionInset; 299 | 300 | /** 301 | * @brief The direction in which items will be rendered in subsequent rows. 302 | * @discussion 303 | * The direction in which each item is rendered. This could be left to right (CHTCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight), right to left (CHTCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft), or shortest column fills first (CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst). 304 | * 305 | * Default: CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst 306 | */ 307 | @property (nonatomic, assign) CHTCollectionViewWaterfallLayoutItemRenderDirection itemRenderDirection; 308 | 309 | /** 310 | * @brief The minimum height of the collection view's content. 311 | * @discussion 312 | * The minimum height of the collection view's content. This could be used to allow hidden headers with no content. 313 | * 314 | * Default: 0.f 315 | */ 316 | @property (nonatomic, assign) CGFloat minimumContentHeight; 317 | 318 | /** 319 | * @brief The calculated width of an item in the specified section. 320 | * @discussion 321 | * The width of an item is calculated based on number of columns, the collection view width, and the horizontal insets for that section. 322 | */ 323 | - (CGFloat)itemWidthInSectionAtIndex:(NSInteger)section; 324 | 325 | @end 326 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewCollectionItem.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Appcelerator Titanium Mobile 3 | * Copyright (c) 2013 by Appcelerator, Inc. All Rights Reserved. 4 | * Licensed under the terms of the Apache Public License 5 | * Please see the LICENSE included with this distribution for details. 6 | */ 7 | #import 8 | #import "TiGradient.h" 9 | #import "TiCollectionviewCollectionView.h" 10 | #import "TiCollectionviewCollectionItemProxy.h" 11 | #import "TiSelectedCellbackgroundView.h" 12 | 13 | enum { 14 | TiUIListItemTemplateStyleCustom = -1 15 | }; 16 | 17 | @interface TiCollectionviewCollectionItem : UICollectionViewCell 18 | { 19 | TiGradientLayer * gradientLayer; 20 | TiGradient * backgroundGradient; 21 | TiGradient * selectedBackgroundGradient; 22 | } 23 | 24 | @property (nonatomic, readonly) NSInteger templateStyle; 25 | @property (nonatomic, readonly) TiCollectionviewCollectionItemProxy *proxy; 26 | @property (nonatomic, readwrite, retain) NSDictionary *dataItem; 27 | 28 | - (void)initWithProxy:(TiCollectionviewCollectionItemProxy *)proxy; 29 | 30 | - (BOOL)canApplyDataItem:(NSDictionary *)otherItem; 31 | - (void)setPosition:(int)position isGrouped:(BOOL)grouped; 32 | - (void)configureCellBackground; 33 | @end 34 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewCollectionItem.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Appcelerator Titanium Mobile 3 | * Copyright (c) 2013 by Appcelerator, Inc. All Rights Reserved. 4 | * Licensed under the terms of the Apache Public License 5 | * Please see the LICENSE included with this distribution for details. 6 | */ 7 | #import "TiCollectionviewCollectionItem.h" 8 | #import "TiUtils.h" 9 | #import "TiViewProxy.h" 10 | #import "ImageLoader.h" 11 | #import "Webcolor.h" 12 | 13 | @implementation TiCollectionviewCollectionItem { 14 | TiCollectionviewCollectionItemProxy *_proxy; 15 | NSInteger _templateStyle; 16 | NSMutableDictionary *_initialValues; 17 | NSMutableDictionary *_currentValues; 18 | NSMutableSet *_resetKeys; 19 | NSDictionary *_dataItem; 20 | NSDictionary *_bindings; 21 | int _positionMask; 22 | BOOL _grouped; 23 | UIView* _bgView; 24 | } 25 | 26 | @synthesize templateStyle = _templateStyle; 27 | @synthesize proxy = _proxy; 28 | @synthesize dataItem = _dataItem; 29 | 30 | - (void)initWithProxy:(TiCollectionviewCollectionItemProxy *)proxy 31 | { 32 | _templateStyle = TiUIListItemTemplateStyleCustom; 33 | _initialValues = [[NSMutableDictionary alloc] initWithCapacity:10]; 34 | _currentValues = [[NSMutableDictionary alloc] initWithCapacity:10]; 35 | _resetKeys = [[NSMutableSet alloc] initWithCapacity:10]; 36 | _proxy = proxy; 37 | _proxy.listItem = self; 38 | } 39 | 40 | - (void)dealloc 41 | { 42 | _proxy.listItem = nil; 43 | [_bgView removeFromSuperview]; 44 | } 45 | 46 | - (NSDictionary *)bindings 47 | { 48 | if (_bindings == nil) { 49 | NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:10]; 50 | [[self class] buildBindingsForViewProxy:_proxy intoDictionary:dict]; 51 | _bindings = [dict copy]; 52 | } 53 | return _bindings; 54 | } 55 | 56 | - (void)prepareForReuse 57 | { 58 | [super prepareForReuse]; 59 | } 60 | 61 | - (void)layoutSubviews 62 | { 63 | if (_bgView != nil) { 64 | if ([_bgView superview] == nil) { 65 | [self.backgroundView addSubview:_bgView]; 66 | } 67 | CGRect bounds = [self.backgroundView bounds]; 68 | if ((_positionMask == TiCellBackgroundViewPositionTop) || (_positionMask == TiCellBackgroundViewPositionSingleLine) ) { 69 | [_bgView setFrame:CGRectMake(0, 1, bounds.size.width, bounds.size.height -2)]; 70 | } else { 71 | [_bgView setFrame:bounds]; 72 | } 73 | [_bgView setNeedsDisplay]; 74 | } else if ([self.backgroundView isKindOfClass:[TiSelectedCellBackgroundView class]]) { 75 | [self.backgroundView setNeedsDisplay]; 76 | } 77 | [super layoutSubviews]; 78 | if (_templateStyle == TiUIListItemTemplateStyleCustom) { 79 | // prevent any crashes that could be caused by unsupported layouts 80 | _proxy.layoutProperties->layoutStyle = TiLayoutRuleAbsolute; 81 | [_proxy layoutChildren:NO]; 82 | } 83 | } 84 | 85 | #pragma mark - Background Support 86 | -(BOOL) selectedOrHighlighted 87 | { 88 | return [self isSelected] || [self isHighlighted]; 89 | } 90 | 91 | -(void) updateGradientLayer:(BOOL)useSelected withAnimation:(BOOL)animated 92 | { 93 | TiGradient * currentGradient = useSelected?selectedBackgroundGradient:backgroundGradient; 94 | 95 | if(currentGradient == nil) 96 | { 97 | [gradientLayer removeFromSuperlayer]; 98 | //Because there's the chance that the other state still has the gradient, let's keep it around. 99 | return; 100 | } 101 | 102 | 103 | if(gradientLayer == nil) 104 | { 105 | gradientLayer = [[TiGradientLayer alloc] init]; 106 | [gradientLayer setNeedsDisplayOnBoundsChange:YES]; 107 | [gradientLayer setFrame:[self bounds]]; 108 | } 109 | 110 | [gradientLayer setGradient:currentGradient]; 111 | 112 | CALayer * ourLayer = [[[self contentView] layer] superlayer]; 113 | 114 | if([gradientLayer superlayer] != ourLayer) 115 | { 116 | CALayer* contentLayer = [[self contentView] layer]; 117 | [ourLayer insertSublayer:gradientLayer below:contentLayer]; 118 | } 119 | if (animated) { 120 | CABasicAnimation *flash = [CABasicAnimation animationWithKeyPath:@"opacity"]; 121 | flash.fromValue = [NSNumber numberWithFloat:0.0]; 122 | flash.toValue = [NSNumber numberWithFloat:1.0]; 123 | flash.duration = 1.0; 124 | [gradientLayer addAnimation:flash forKey:@"flashAnimation"]; 125 | } 126 | [gradientLayer setNeedsDisplay]; 127 | } 128 | 129 | -(void) setBackgroundGradient_:(id)value 130 | { 131 | TiGradient * newGradient = [TiGradient gradientFromObject:value proxy:_proxy]; 132 | if(newGradient == backgroundGradient) 133 | { 134 | return; 135 | } 136 | backgroundGradient = newGradient; 137 | 138 | if(![self selectedOrHighlighted]) 139 | { 140 | [self updateGradientLayer:NO withAnimation:NO]; 141 | } 142 | } 143 | 144 | -(void) setSelectedBackgroundGradient_:(id)value 145 | { 146 | TiGradient * newGradient = [TiGradient gradientFromObject:value proxy:_proxy]; 147 | if(newGradient == selectedBackgroundGradient) 148 | { 149 | return; 150 | } 151 | selectedBackgroundGradient = newGradient; 152 | 153 | if([self selectedOrHighlighted]) 154 | { 155 | [self updateGradientLayer:YES withAnimation:NO]; 156 | } 157 | } 158 | 159 | -(void)setPosition:(int)position isGrouped:(BOOL)grouped 160 | { 161 | _positionMask = position; 162 | _grouped = grouped; 163 | } 164 | 165 | 166 | -(BOOL)compareDataItemValue:(NSString*)theKey withItem:(NSDictionary *)otherItem 167 | { 168 | id propertiesValue = [_dataItem objectForKey:@"properties"]; 169 | NSDictionary *properties = ([propertiesValue isKindOfClass:[NSDictionary class]]) ? propertiesValue : nil; 170 | id curValue = [properties objectForKey:theKey]; 171 | 172 | propertiesValue = [otherItem objectForKey:@"properties"]; 173 | properties = ([propertiesValue isKindOfClass:[NSDictionary class]]) ? propertiesValue : nil; 174 | id otherValue = [properties objectForKey:theKey]; 175 | return ( (curValue == otherValue) || [curValue isEqual:otherValue]); 176 | 177 | } 178 | 179 | - (BOOL)canApplyDataItem:(NSDictionary *)otherItem; 180 | { 181 | id template = [_dataItem objectForKey:@"template"]; 182 | id otherTemplate = [otherItem objectForKey:@"template"]; 183 | BOOL same = (template == otherTemplate) || [template isEqual:otherTemplate]; 184 | if (same) { 185 | same = [self compareDataItemValue:@"height" withItem:otherItem]; 186 | } 187 | //These properties are applied in willDisplayCell. So force reload. 188 | if (same) { 189 | same = [self compareDataItemValue:@"backgroundColor" withItem:otherItem]; 190 | } 191 | if (same) { 192 | same = [self compareDataItemValue:@"backgroundImage" withItem:otherItem]; 193 | } 194 | if (same) { 195 | same = [self compareDataItemValue:@"tintColor" withItem:otherItem]; 196 | } 197 | return same; 198 | } 199 | 200 | - (void)configureCellBackground 201 | { 202 | //Ensure that we store the default backgroundColor 203 | if ([_initialValues objectForKey:@"backgroundColor"] == nil) { 204 | id initialValue = nil; 205 | if (_templateStyle == TiUIListItemTemplateStyleCustom) { 206 | initialValue = [[TiUtils colorValue:[_proxy valueForKey:@"backgroundColor"]] color]; 207 | } 208 | if (IS_NULL_OR_NIL(initialValue)) { 209 | initialValue = [self backgroundColor]; 210 | } 211 | [_initialValues setObject:(initialValue != nil ? initialValue : [NSNull null]) forKey:@"backgroundColor"]; 212 | } 213 | id propertiesValue = [_dataItem objectForKey:@"properties"]; 214 | NSDictionary *properties = ([propertiesValue isKindOfClass:[NSDictionary class]]) ? propertiesValue : nil; 215 | id colorValue = [properties objectForKey:@"backgroundColor"]; 216 | UIColor *color = colorValue != nil ? [[TiUtils colorValue:colorValue] _color] : nil; 217 | if (color == nil) { 218 | id initVal = [_initialValues objectForKey:@"backgroundColor"]; 219 | if ([initVal isKindOfClass:[UIColor class]]) { 220 | color = initVal; 221 | } else { 222 | color = [[TiUtils colorValue:initVal] color]; 223 | } 224 | } 225 | self.backgroundColor = color; 226 | 227 | //Ensure that we store the backgroundImage 228 | if ([_initialValues objectForKey:@"backgroundImage"] == nil) { 229 | id initialValue = nil; 230 | if (_templateStyle == TiUIListItemTemplateStyleCustom) { 231 | initialValue = [_proxy valueForKey:@"backgroundImage"]; 232 | } 233 | [_initialValues setObject:(initialValue != nil ? initialValue : [NSNull null]) forKey:@"backgroundImage"]; 234 | } 235 | id backgroundImage = [properties objectForKey:@"backgroundImage"]; 236 | if (IS_NULL_OR_NIL(backgroundImage)) { 237 | backgroundImage = [_initialValues objectForKey:@"backgroundImage"]; 238 | } 239 | UIImage* bgImage = [[ImageLoader sharedLoader] loadImmediateStretchableImage:[TiUtils toURL:backgroundImage proxy:_proxy] withLeftCap:TiDimensionAuto topCap:TiDimensionAuto]; 240 | if (_grouped && ![TiUtils isIOS7OrGreater]) { 241 | UIView* superView = [self backgroundView]; 242 | if (bgImage != nil) { 243 | if (![_bgView isKindOfClass:[UIImageView class]]) { 244 | [_bgView removeFromSuperview]; 245 | _bgView = [[UIImageView alloc] initWithFrame:CGRectZero]; 246 | _bgView.autoresizingMask = UIViewAutoresizingFlexibleWidth; 247 | [superView addSubview:_bgView]; 248 | } 249 | [(UIImageView*)_bgView setImage:bgImage]; 250 | [_bgView setBackgroundColor:[UIColor clearColor]]; 251 | } else { 252 | [_bgView removeFromSuperview]; 253 | } 254 | } else { 255 | if (bgImage != nil) { 256 | //Set the backgroundView to ImageView and set its backgroundColor to bgColor 257 | if ([self.backgroundView isKindOfClass:[UIImageView class]]) { 258 | [(UIImageView*)self.backgroundView setImage:bgImage]; 259 | [(UIImageView*)self.backgroundView setBackgroundColor:[UIColor clearColor]]; 260 | } else { 261 | UIImageView *view_ = [[UIImageView alloc] initWithFrame:CGRectZero]; 262 | [view_ setImage:bgImage]; 263 | [view_ setBackgroundColor:[UIColor clearColor]]; 264 | self.backgroundView = view_; 265 | } 266 | } else { 267 | self.backgroundView = nil; 268 | } 269 | } 270 | 271 | } 272 | 273 | - (void)setDataItem:(NSDictionary *)dataItem 274 | { 275 | _dataItem = dataItem; 276 | [_resetKeys addObjectsFromArray:[_currentValues allKeys]]; 277 | id propertiesValue = [dataItem objectForKey:@"properties"]; 278 | NSDictionary *properties = ([propertiesValue isKindOfClass:[NSDictionary class]]) ? propertiesValue : nil; 279 | switch (_templateStyle) { 280 | default: 281 | [dataItem enumerateKeysAndObjectsUsingBlock:^(NSString *bindId, id dict, BOOL *stop) { 282 | if (![dict isKindOfClass:[NSDictionary class]] || [bindId isEqualToString:@"properties"]) { 283 | return; 284 | } 285 | id bindObject = [self valueForUndefinedKey:bindId]; 286 | if (bindObject != nil) { 287 | BOOL reproxying = NO; 288 | if ([bindObject isKindOfClass:[TiProxy class]]) { 289 | [bindObject setReproxying:YES]; 290 | reproxying = YES; 291 | } 292 | [(NSDictionary *)dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) { 293 | NSString *keyPath = [NSString stringWithFormat:@"%@.%@", bindId, key]; 294 | if ([self shouldUpdateValue:value forKeyPath:keyPath]) { 295 | [self recordChangeValue:value forKeyPath:keyPath withBlock:^{ 296 | [bindObject setValue:value forKey:key]; 297 | }]; 298 | } 299 | }]; 300 | if (reproxying) { 301 | [bindObject setReproxying:NO]; 302 | } 303 | } 304 | }]; 305 | break; 306 | } 307 | 308 | id backgroundGradientValue = [properties objectForKey:@"backgroundGradient"]; 309 | if (IS_NULL_OR_NIL(backgroundGradientValue)) { 310 | backgroundGradientValue = [_proxy valueForKey:@"backgroundGradient"]; 311 | } 312 | [self setBackgroundGradient_:backgroundGradientValue]; 313 | 314 | 315 | id selectedBackgroundGradientValue = [properties objectForKey:@"selectedBackgroundGradient"]; 316 | if (IS_NULL_OR_NIL(selectedBackgroundGradientValue)) { 317 | selectedBackgroundGradientValue = [_proxy valueForKey:@"selectedBackgroundGradient"]; 318 | } 319 | [self setSelectedBackgroundGradient_:selectedBackgroundGradientValue]; 320 | 321 | [_resetKeys enumerateObjectsUsingBlock:^(NSString *keyPath, BOOL *stop) { 322 | id value = [_initialValues objectForKey:keyPath]; 323 | [self setValue:(value != [NSNull null] ? value : nil) forKeyPath:keyPath]; 324 | [_currentValues removeObjectForKey:keyPath]; 325 | }]; 326 | [_resetKeys removeAllObjects]; 327 | } 328 | 329 | - (id)valueForUndefinedKey:(NSString *)key 330 | { 331 | return [self.bindings objectForKey:key]; 332 | } 333 | 334 | - (void)recordChangeValue:(id)value forKeyPath:(NSString *)keyPath withBlock:(void(^)(void))block 335 | { 336 | if ([_initialValues objectForKey:keyPath] == nil) { 337 | id initialValue = [self valueForKeyPath:keyPath]; 338 | [_initialValues setObject:(initialValue != nil ? initialValue : [NSNull null]) forKey:keyPath]; 339 | } 340 | block(); 341 | if (value != nil) { 342 | [_currentValues setObject:value forKey:keyPath]; 343 | } else { 344 | [_currentValues removeObjectForKey:keyPath]; 345 | } 346 | [_resetKeys removeObject:keyPath]; 347 | } 348 | 349 | - (BOOL)shouldUpdateValue:(id)value forKeyPath:(NSString *)keyPath 350 | { 351 | id current = [_currentValues objectForKey:keyPath]; 352 | BOOL sameValue = ((current == value) || [current isEqual:value]); 353 | if (sameValue) { 354 | [_resetKeys removeObject:keyPath]; 355 | } 356 | return !sameValue; 357 | } 358 | 359 | #pragma mark - Static 360 | 361 | + (void)buildBindingsForViewProxy:(TiViewProxy *)viewProxy intoDictionary:(NSMutableDictionary *)dict 362 | { 363 | [viewProxy.children enumerateObjectsUsingBlock:^(TiViewProxy *childViewProxy, NSUInteger idx, BOOL *stop) { 364 | [[self class] buildBindingsForViewProxy:childViewProxy intoDictionary:dict]; 365 | }]; 366 | id bindId = [viewProxy valueForKey:@"bindId"]; 367 | if (bindId != nil) { 368 | [dict setObject:viewProxy forKey:bindId]; 369 | } 370 | } 371 | 372 | @end 373 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewCollectionItemProxy.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Appcelerator Titanium Mobile 3 | * Copyright (c) 2013 by Appcelerator, Inc. All Rights Reserved. 4 | * Licensed under the terms of the Apache Public License 5 | * Please see the LICENSE included with this distribution for details. 6 | */ 7 | 8 | #import "TiViewProxy.h" 9 | 10 | @class TiCollectionviewCollectionItem; 11 | @class TiCollectionviewCollectionViewProxy; 12 | 13 | @interface TiCollectionviewCollectionItemProxy : TiViewProxy 14 | 15 | @property (nonatomic, readwrite, assign) TiCollectionviewCollectionItem *listItem; 16 | @property (nonatomic, readwrite, retain) NSIndexPath *indexPath; 17 | 18 | - (id)initWithListViewProxy:(TiCollectionviewCollectionViewProxy *)listViewProxy inContext:(id)context; 19 | -(void)deregisterProxy:(id)context; 20 | @end 21 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewCollectionItemProxy.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Appcelerator Titanium Mobile 3 | * Copyright (c) 2013 by Appcelerator, Inc. All Rights Reserved. 4 | * Licensed under the terms of the Apache Public License 5 | * Please see the LICENSE included with this distribution for details. 6 | */ 7 | #import "TiCollectionviewCollectionItemProxy.h" 8 | #import "TiUtils.h" 9 | #import "TiCollectionviewCollectionItem.h" 10 | #import "TiCollectionviewCollectionViewProxy.h" 11 | 12 | static void SetEventOverrideDelegateRecursive(NSArray *children, id eventOverrideDelegate); 13 | 14 | @implementation TiCollectionviewCollectionItemProxy { 15 | TiCollectionviewCollectionViewProxy *_listViewProxy; // weak 16 | } 17 | 18 | @synthesize listItem = _listItem; 19 | @synthesize indexPath = _indexPath; 20 | 21 | -(NSString*)apiName 22 | { 23 | return @"Ti.CollectionItem"; 24 | } 25 | 26 | - (id)initWithListViewProxy:(TiCollectionviewCollectionViewProxy *)listViewProxy inContext:(id)context 27 | { 28 | self = [self _initWithPageContext:context]; 29 | if (self) { 30 | _listViewProxy = listViewProxy; 31 | [context.krollContext invokeBlockOnThread:^{ 32 | [context registerProxy:self]; 33 | //Reusable cell will keep native proxy alive. 34 | //This proxy will keep its JS object alive. 35 | [self rememberSelf]; 36 | }]; 37 | self.modelDelegate = self; 38 | } 39 | return self; 40 | } 41 | 42 | +(BOOL)shouldRegisterOnInit 43 | { 44 | //Since this is initialized on main thread, 45 | //there is no need to register on init. Registration 46 | //done later on JS thread (See above) 47 | return NO; 48 | } 49 | 50 | -(void)deregisterProxy:(id)context 51 | { 52 | //Aggressive removal of children on deallocation of cell 53 | [self removeAllChildren:nil]; 54 | [self windowDidClose]; 55 | //Go ahead and unprotect JS object and mark context closed 56 | //(Since cell no longer exists, the proxy is inaccessible) 57 | [context.krollContext invokeBlockOnThread:^{ 58 | [self forgetSelf]; 59 | [self contextShutdown:context]; 60 | }]; 61 | } 62 | 63 | - (id)init 64 | { 65 | self = [super init]; 66 | if (self) { 67 | viewInitialized = YES; 68 | [self windowWillOpen]; 69 | [self windowDidOpen]; 70 | [self willShow]; 71 | } 72 | return self; 73 | } 74 | 75 | - (TiUIView *)view 76 | { 77 | return nil; 78 | } 79 | 80 | -(BOOL)viewAttached 81 | { 82 | return _listItem != nil; 83 | } 84 | 85 | -(UIView *)parentViewForChild:(TiViewProxy *)child 86 | { 87 | return _listItem.contentView; 88 | } 89 | 90 | -(void)propertyChanged:(NSString*)key oldValue:(id)oldValue newValue:(id)newValue proxy:(TiProxy*)proxy_ 91 | { 92 | 93 | } 94 | 95 | - (void)unarchiveFromTemplate:(id)viewTemplate 96 | { 97 | [super unarchiveFromTemplate:viewTemplate]; 98 | SetEventOverrideDelegateRecursive(self.children, self); 99 | } 100 | 101 | -(BOOL)canHaveControllerParent 102 | { 103 | return NO; 104 | } 105 | 106 | #pragma mark - TiViewEventOverrideDelegate 107 | 108 | - (NSDictionary *)overrideEventObject:(NSDictionary *)eventObject forEvent:(NSString *)eventType fromViewProxy:(TiViewProxy *)viewProxy 109 | { 110 | NSMutableDictionary *updatedEventObject = [eventObject mutableCopy]; 111 | [updatedEventObject setObject:NUMINTEGER(_indexPath.section) forKey:@"sectionIndex"]; 112 | [updatedEventObject setObject:NUMINTEGER(_indexPath.row) forKey:@"itemIndex"]; 113 | [updatedEventObject setObject:[_listViewProxy sectionForIndex:_indexPath.section] forKey:@"section"]; 114 | id propertiesValue = [_listItem.dataItem objectForKey:@"properties"]; 115 | NSDictionary *properties = ([propertiesValue isKindOfClass:[NSDictionary class]]) ? propertiesValue : nil; 116 | id itemId = [properties objectForKey:@"itemId"]; 117 | if (itemId != nil) { 118 | [updatedEventObject setObject:itemId forKey:@"itemId"]; 119 | } 120 | id bindId = [viewProxy valueForKey:@"bindId"]; 121 | if (bindId != nil) { 122 | [updatedEventObject setObject:bindId forKey:@"bindId"]; 123 | } 124 | return updatedEventObject; 125 | } 126 | 127 | @end 128 | 129 | static void SetEventOverrideDelegateRecursive(NSArray *children, id eventOverrideDelegate) 130 | { 131 | [children enumerateObjectsUsingBlock:^(TiViewProxy *child, NSUInteger idx, BOOL *stop) { 132 | child.eventOverrideDelegate = eventOverrideDelegate; 133 | SetEventOverrideDelegateRecursive(child.children, eventOverrideDelegate); 134 | }]; 135 | } 136 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewCollectionSectionProxy.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Appcelerator Titanium Mobile 3 | * Copyright (c) 2013 by Appcelerator, Inc. All Rights Reserved. 4 | * Licensed under the terms of the Apache Public License 5 | * Please see the LICENSE included with this distribution for details. 6 | */ 7 | 8 | #import "TiBase.h" 9 | #import "TiProxy.h" 10 | 11 | @protocol TiCollectionviewCollectionViewDelegate 12 | @required 13 | 14 | - (void)dispatchUpdateAction:(void(^)(UICollectionView *tableView))block; 15 | - (id)dispatchBlockWithResult:(id(^)(void))block; 16 | 17 | @end 18 | 19 | @interface TiCollectionviewCollectionSectionProxy : TiProxy < TiCollectionviewCollectionViewDelegate > 20 | 21 | @property (nonatomic, readwrite, assign) id delegate; 22 | @property (nonatomic, readwrite, assign) NSUInteger sectionIndex; 23 | 24 | // Private API. Used by ListView directly. Not for public comsumption 25 | - (NSDictionary *)itemAtIndex:(NSUInteger)index; 26 | - (void) deleteItemAtIndex:(NSUInteger)index; 27 | - (void) addItem:(NSDictionary*)item atIndex:(NSUInteger)index; 28 | 29 | // Public API 30 | @property (nonatomic, readonly) NSUInteger itemCount; 31 | @property (nonatomic, readonly) NSArray *items; 32 | @property (nonatomic, readwrite, copy) NSString *headerTitle; 33 | @property (nonatomic, readwrite, copy) NSString *footerTitle; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewCollectionSectionProxy.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Appcelerator Titanium Mobile 3 | * Copyright (c) 2013 by Appcelerator, Inc. All Rights Reserved. 4 | * Licensed under the terms of the Apache Public License 5 | * Please see the LICENSE included with this distribution for details. 6 | */ 7 | 8 | #import "TiCollectionviewCollectionSectionProxy.h" 9 | #import "TiCollectionviewCollectionViewProxy.h" 10 | #import "TiCollectionviewCollectionView.h" 11 | #import "TiCollectionviewCollectionItem.h" 12 | 13 | @interface TiCollectionviewCollectionSectionProxy () 14 | @property (nonatomic, readonly) id dispatcher; 15 | @end 16 | 17 | @implementation TiCollectionviewCollectionSectionProxy { 18 | NSMutableArray *_items; 19 | } 20 | 21 | @synthesize delegate = _delegate; 22 | @synthesize sectionIndex = _sectionIndex; 23 | @synthesize headerTitle = _headerTitle; 24 | @synthesize footerTitle = _footerTitle; 25 | 26 | -(NSString*)apiName 27 | { 28 | return @"Ti.CollectionSection"; 29 | } 30 | 31 | - (id)init 32 | { 33 | self = [super init]; 34 | if (self) { 35 | _items = [[NSMutableArray alloc] initWithCapacity:20]; 36 | } 37 | return self; 38 | } 39 | 40 | - (id)dispatcher 41 | { 42 | return _delegate != nil ? _delegate : self; 43 | } 44 | 45 | // These API's are used by the ListView directly. Not for public consumption 46 | - (NSDictionary *)itemAtIndex:(NSUInteger)index 47 | { 48 | if (index < [_items count]) { 49 | id item = [_items objectAtIndex:index]; 50 | if ([item isKindOfClass:[NSDictionary class]]) { 51 | return item; 52 | } 53 | } 54 | return nil; 55 | } 56 | 57 | - (void) deleteItemAtIndex:(NSUInteger)index 58 | { 59 | if ([_items count] <= index) { 60 | DLog(@"[WARN] ListSectionProxy: deleteItemAtIndex index is out of range"); 61 | } else { 62 | [_items removeObjectAtIndex:index]; 63 | } 64 | } 65 | 66 | - (void) addItem:(NSDictionary*)item atIndex:(NSUInteger)index 67 | { 68 | if (index > [_items count]) { 69 | DLog(@"[WARN] ListSectionProxy: addItem:atIndex: index is out of range"); 70 | } else { 71 | if (index == [_items count]) { 72 | [_items addObject:item]; 73 | } else { 74 | [_items insertObject:item atIndex:index]; 75 | } 76 | } 77 | } 78 | 79 | 80 | 81 | #pragma mark - Public API 82 | 83 | - (NSArray *)items 84 | { 85 | return [self.dispatcher dispatchBlockWithResult:^() { 86 | return [_items copy]; 87 | }]; 88 | } 89 | 90 | - (NSUInteger)itemCount 91 | { 92 | return [[self.dispatcher dispatchBlockWithResult:^() { 93 | return [NSNumber numberWithUnsignedInteger:[_items count]]; 94 | }] unsignedIntegerValue]; 95 | } 96 | 97 | - (id)getItemAt:(id)args 98 | { 99 | ENSURE_ARG_COUNT(args, 1); 100 | NSUInteger itemIndex = [TiUtils intValue:[args objectAtIndex:0]]; 101 | return [self.dispatcher dispatchBlockWithResult:^() { 102 | return (itemIndex < [_items count]) ? [_items objectAtIndex:itemIndex] : nil; 103 | }]; 104 | } 105 | 106 | - (void)setItems:(id)args 107 | { 108 | [self setItems:args withObject:[NSDictionary dictionaryWithObject:NUMINT(UITableViewRowAnimationNone) forKey:@"animationStyle"]]; 109 | } 110 | 111 | - (void)setItems:(id)args withObject:(id)properties 112 | { 113 | ENSURE_TYPE_OR_NIL(args,NSArray); 114 | NSArray *items = args; 115 | NSUInteger oldCount = [_items count]; 116 | NSUInteger newCount = [items count]; 117 | if ( (oldCount != newCount)) { 118 | NSUInteger minCount = MIN(oldCount, newCount); 119 | NSUInteger maxCount = MAX(oldCount, newCount); 120 | NSUInteger diffCount = maxCount - minCount; 121 | 122 | //Dispath block for difference 123 | [self.dispatcher dispatchUpdateAction:^(UICollectionView *tableView) { 124 | [_items setArray:items]; 125 | NSMutableArray *indexPaths = [[NSMutableArray alloc] initWithCapacity:diffCount]; 126 | for (NSUInteger i = 0; i < diffCount; ++i) { 127 | [indexPaths addObject:[NSIndexPath indexPathForRow:(minCount + i) inSection:_sectionIndex]]; 128 | } 129 | if (newCount > oldCount) { 130 | [tableView insertItemsAtIndexPaths:indexPaths]; 131 | } else { 132 | [tableView deleteItemsAtIndexPaths:indexPaths]; 133 | } 134 | }]; 135 | 136 | //Dispatch block for common items 137 | if (minCount > 0) { 138 | [self.dispatcher dispatchUpdateAction:^(UICollectionView *tableView) { 139 | NSMutableArray *indexPaths = [[NSMutableArray alloc] initWithCapacity:minCount]; 140 | for (NSUInteger i = 0; i < minCount; ++i) { 141 | [indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:_sectionIndex]]; 142 | } 143 | [tableView reloadItemsAtIndexPaths:indexPaths]; 144 | }]; 145 | } 146 | 147 | } else { 148 | [self.dispatcher dispatchUpdateAction:^(UICollectionView *tableView) { 149 | [_items setArray:items]; 150 | [tableView reloadSections:[NSIndexSet indexSetWithIndex:_sectionIndex]]; 151 | }]; 152 | } 153 | } 154 | 155 | - (void)appendItems:(id)args 156 | { 157 | ENSURE_ARG_COUNT(args, 1); 158 | NSArray *items = [args objectAtIndex:0]; 159 | if ([items count] == 0) { 160 | return; 161 | } 162 | ENSURE_TYPE_OR_NIL(items,NSArray); 163 | [self.dispatcher dispatchUpdateAction:^(UICollectionView *tableView) { 164 | NSUInteger insertIndex = [_items count]; 165 | [_items addObjectsFromArray:items]; 166 | NSUInteger count = [items count]; 167 | NSMutableArray *indexPaths = [[NSMutableArray alloc] initWithCapacity:count]; 168 | for (NSUInteger i = 0; i < count; ++i) { 169 | [indexPaths addObject:[NSIndexPath indexPathForRow:insertIndex+i inSection:_sectionIndex]]; 170 | } 171 | [tableView insertItemsAtIndexPaths:indexPaths]; 172 | }]; 173 | } 174 | 175 | - (void)insertItemsAt:(id)args 176 | { 177 | ENSURE_ARG_COUNT(args, 2); 178 | NSUInteger insertIndex = [TiUtils intValue:[args objectAtIndex:0]]; 179 | NSArray *items = [args objectAtIndex:1]; 180 | if ([items count] == 0) { 181 | return; 182 | } 183 | ENSURE_TYPE_OR_NIL(items,NSArray); 184 | 185 | [self.dispatcher dispatchUpdateAction:^(UICollectionView *tableView) { 186 | if ([_items count] < insertIndex) { 187 | DLog(@"[WARN] ListView: Insert item index is out of range"); 188 | return; 189 | } 190 | [_items replaceObjectsInRange:NSMakeRange(insertIndex, 0) withObjectsFromArray:items]; 191 | NSUInteger count = [items count]; 192 | NSMutableArray *indexPaths = [[NSMutableArray alloc] initWithCapacity:count]; 193 | for (NSUInteger i = 0; i < count; ++i) { 194 | [indexPaths addObject:[NSIndexPath indexPathForRow:insertIndex+i inSection:_sectionIndex]]; 195 | } 196 | [tableView insertItemsAtIndexPaths:indexPaths]; 197 | }]; 198 | } 199 | 200 | - (void)replaceItemsAt:(id)args 201 | { 202 | ENSURE_ARG_COUNT(args, 3); 203 | NSUInteger insertIndex = [TiUtils intValue:[args objectAtIndex:0]]; 204 | NSUInteger replaceCount = [TiUtils intValue:[args objectAtIndex:1]]; 205 | NSArray *items = [args objectAtIndex:2]; 206 | ENSURE_TYPE_OR_NIL(items,NSArray); 207 | 208 | [self.dispatcher dispatchUpdateAction:^(UICollectionView *tableView) { 209 | if ([_items count] < insertIndex) { 210 | DLog(@"[WARN] ListView: Replace item index is out of range"); 211 | return; 212 | } 213 | NSUInteger actualReplaceCount = MIN(replaceCount, [_items count]-insertIndex); 214 | [_items replaceObjectsInRange:NSMakeRange(insertIndex, actualReplaceCount) withObjectsFromArray:items]; 215 | NSUInteger count = [items count]; 216 | NSMutableArray *indexPaths = [[NSMutableArray alloc] initWithCapacity:MAX(count, actualReplaceCount)]; 217 | for (NSUInteger i = 0; i < actualReplaceCount; ++i) { 218 | [indexPaths addObject:[NSIndexPath indexPathForRow:insertIndex+i inSection:_sectionIndex]]; 219 | } 220 | if (actualReplaceCount > 0) { 221 | [tableView deleteItemsAtIndexPaths:indexPaths]; 222 | } 223 | [indexPaths removeAllObjects]; 224 | for (NSUInteger i = 0; i < count; ++i) { 225 | [indexPaths addObject:[NSIndexPath indexPathForRow:insertIndex+i inSection:_sectionIndex]]; 226 | } 227 | if (count > 0) { 228 | [tableView insertItemsAtIndexPaths:indexPaths]; 229 | } 230 | }]; 231 | } 232 | 233 | - (void)deleteItemsAt:(id)args 234 | { 235 | ENSURE_ARG_COUNT(args, 2); 236 | NSUInteger deleteIndex = [TiUtils intValue:[args objectAtIndex:0]]; 237 | NSUInteger deleteCount = [TiUtils intValue:[args objectAtIndex:1]]; 238 | if (deleteCount == 0) { 239 | return; 240 | } 241 | 242 | [self.dispatcher dispatchUpdateAction:^(UICollectionView *tableView) { 243 | if ([_items count] <= deleteIndex) { 244 | DLog(@"[WARN] ListView: Delete item index is out of range"); 245 | return; 246 | } 247 | NSUInteger actualDeleteCount = MIN(deleteCount, [_items count]-deleteIndex); 248 | if (actualDeleteCount == 0) { 249 | return; 250 | } 251 | [_items removeObjectsInRange:NSMakeRange(deleteIndex, actualDeleteCount)]; 252 | NSMutableArray *indexPaths = [[NSMutableArray alloc] initWithCapacity:actualDeleteCount]; 253 | for (NSUInteger i = 0; i < actualDeleteCount; ++i) { 254 | [indexPaths addObject:[NSIndexPath indexPathForRow:deleteIndex+i inSection:_sectionIndex]]; 255 | } 256 | [tableView deleteItemsAtIndexPaths:indexPaths]; 257 | }]; 258 | } 259 | 260 | - (void)updateItemAt:(id)args 261 | { 262 | ENSURE_ARG_COUNT(args, 2); 263 | NSUInteger itemIndex = [TiUtils intValue:[args objectAtIndex:0]]; 264 | NSDictionary *item = [args objectAtIndex:1]; 265 | ENSURE_TYPE_OR_NIL(item,NSDictionary); 266 | 267 | [self.dispatcher dispatchUpdateAction:^(UICollectionView *tableView) { 268 | if ([_items count] <= itemIndex) { 269 | DLog(@"[WARN] ListView: Update item index is out of range"); 270 | return; 271 | } 272 | 273 | if (item != nil) { 274 | [_items replaceObjectAtIndex:itemIndex withObject:item]; 275 | } 276 | 277 | NSArray *indexPaths = [[NSArray alloc] initWithObjects:[NSIndexPath indexPathForRow:itemIndex inSection:_sectionIndex], nil]; 278 | BOOL forceReload = NO; 279 | if (!forceReload) { 280 | TiCollectionviewCollectionItem *cell = (TiCollectionviewCollectionItem *)[tableView cellForItemAtIndexPath:[indexPaths objectAtIndex:0]]; 281 | if ((cell != nil) && ([cell canApplyDataItem:item])) { 282 | cell.dataItem = item; 283 | } else { 284 | forceReload = YES; 285 | } 286 | } 287 | if (forceReload) { 288 | [tableView reloadItemsAtIndexPaths:indexPaths]; 289 | } 290 | }]; 291 | } 292 | 293 | #pragma mark - TiUIListViewDelegate 294 | 295 | - (void)dispatchUpdateAction:(void (^)(UICollectionView *))block 296 | { 297 | block(nil); 298 | } 299 | 300 | - (id)dispatchBlockWithResult:(id (^)(void))block 301 | { 302 | return block(); 303 | } 304 | 305 | @end 306 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewCollectionView.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Appcelerator Titanium Mobile 3 | * Copyright (c) 2013 by Appcelerator, Inc. All Rights Reserved. 4 | * Licensed under the terms of the Apache Public License 5 | * Please see the LICENSE included with this distribution for details. 6 | */ 7 | 8 | #import "TiUIView.h" 9 | #import "TiCollectionviewCollectionViewProxy.h" 10 | #import "CHTCollectionViewWaterfallLayout.h" 11 | #import "TiSearchDisplayController.h" 12 | 13 | typedef enum { 14 | kLayoutTypeGrid, 15 | kLayoutTypeWaterfall 16 | } LayoutType; 17 | 18 | typedef enum { 19 | kScrollHorizontal, 20 | kScrollVertical 21 | } ScrollDirection; 22 | 23 | @interface TiCollectionviewCollectionView : TiUIView 24 | 25 | #pragma mark - Private APIs 26 | 27 | @property (nonatomic, readonly) UICollectionView *collectionView; 28 | @property (nonatomic, readonly) BOOL isSearchActive; 29 | 30 | - (void)setDictTemplates_:(id)args; 31 | - (void)setContentInsets_:(id)value withObject:(id)props; 32 | - (void)updateIndicesForVisibleRows; 33 | - (void)updateSearchResults:(id)unused; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewCollectionViewProxy.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Appcelerator Titanium Mobile 3 | * Copyright (c) 2013 by Appcelerator, Inc. All Rights Reserved. 4 | * Licensed under the terms of the Apache Public License 5 | * Please see the LICENSE included with this distribution for details. 6 | */ 7 | 8 | #import "TiViewProxy.h" 9 | #import "TiCollectionviewCollectionSectionProxy.h" 10 | 11 | @interface TiCollectionviewCollectionViewProxy : TiViewProxy < TiCollectionviewCollectionViewDelegate > 12 | 13 | @property (nonatomic, readonly) NSArray *sections; 14 | @property (nonatomic, readonly) NSNumber *sectionCount; 15 | 16 | - (TiCollectionviewCollectionSectionProxy *)sectionForIndex:(NSUInteger)index; 17 | - (void) deleteSectionAtIndex:(NSUInteger)index; 18 | - (void) setMarker:(id)args; 19 | @end 20 | 21 | @interface TiCollectionviewCollectionViewProxy (internal) 22 | -(void)willDisplayCell:(NSIndexPath*)indexPath; 23 | @end 24 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewCollectionViewProxy.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Appcelerator Titanium Mobile 3 | * Copyright (c) 2013 by Appcelerator, Inc. All Rights Reserved. 4 | * Licensed under the terms of the Apache Public License 5 | * Please see the LICENSE included with this distribution for details. 6 | */ 7 | 8 | #import "TiCollectionviewCollectionViewProxy.h" 9 | #import "TiCollectionviewCollectionView.h" 10 | #import "TiUtils.h" 11 | #import "TiViewTemplate.h" 12 | 13 | @interface TiCollectionviewCollectionViewProxy () 14 | @property (nonatomic, readwrite) TiCollectionviewCollectionView *listView; 15 | @end 16 | 17 | @implementation TiCollectionviewCollectionViewProxy { 18 | NSMutableArray *_sections; 19 | NSMutableArray *_operationQueue; 20 | pthread_mutex_t _operationQueueMutex; 21 | pthread_rwlock_t _markerLock; 22 | NSIndexPath *marker; 23 | } 24 | 25 | -(NSString*)apiName 26 | { 27 | return @"Ti.CollectionView"; 28 | } 29 | 30 | - (id)init 31 | { 32 | self = [super init]; 33 | if (self) { 34 | _sections = [[NSMutableArray alloc] initWithCapacity:4]; 35 | _operationQueue = [[NSMutableArray alloc] initWithCapacity:10]; 36 | pthread_mutex_init(&_operationQueueMutex,NULL); 37 | pthread_rwlock_init(&_markerLock,NULL); 38 | } 39 | return self; 40 | } 41 | 42 | -(void)_initWithProperties:(NSDictionary *)properties 43 | { 44 | [self initializeProperty:@"canScroll" defaultValue:NUMBOOL(YES)]; 45 | [super _initWithProperties:properties]; 46 | } 47 | 48 | - (void)dealloc 49 | { 50 | pthread_mutex_destroy(&_operationQueueMutex); 51 | pthread_rwlock_destroy(&_markerLock); 52 | } 53 | 54 | - (TiCollectionviewCollectionView *)listView 55 | { 56 | return (TiCollectionviewCollectionView *)self.view; 57 | } 58 | 59 | - (void)dispatchUpdateAction:(void(^)(UICollectionView *tableView))block 60 | { 61 | if (view == nil) { 62 | block(nil); 63 | return; 64 | } 65 | 66 | if ([self.listView isSearchActive]) { 67 | block(nil); 68 | TiThreadPerformOnMainThread(^{ 69 | [self.listView updateSearchResults:nil]; 70 | }, [NSThread isMainThread]); 71 | return; 72 | } 73 | 74 | BOOL triggerMainThread; 75 | pthread_mutex_lock(&_operationQueueMutex); 76 | triggerMainThread = [_operationQueue count] == 0; 77 | [_operationQueue addObject:(id)block]; 78 | pthread_mutex_unlock(&_operationQueueMutex); 79 | if (triggerMainThread) { 80 | TiThreadPerformOnMainThread(^{ 81 | [self processUpdateActions]; 82 | }, [NSThread isMainThread]); 83 | } 84 | } 85 | 86 | - (void)dispatchBlock:(void(^)(UICollectionView *tableView))block 87 | { 88 | if (view == nil) { 89 | block(nil); 90 | return; 91 | } 92 | if ([NSThread isMainThread]) { 93 | return block(self.listView.collectionView); 94 | } 95 | TiThreadPerformOnMainThread(^{ 96 | block(self.listView.collectionView); 97 | }, YES); 98 | } 99 | 100 | - (id)dispatchBlockWithResult:(id(^)(void))block 101 | { 102 | if ([NSThread isMainThread]) { 103 | return block(); 104 | } 105 | 106 | __block id result = nil; 107 | TiThreadPerformOnMainThread(^{ 108 | result = block(); 109 | }, YES); 110 | return result; 111 | } 112 | 113 | - (void)processUpdateActions 114 | { 115 | UICollectionView *tableView = self.listView.collectionView; 116 | BOOL removeHead = NO; 117 | while (YES) { 118 | void (^block)(UICollectionView *) = nil; 119 | pthread_mutex_lock(&_operationQueueMutex); 120 | if (removeHead) { 121 | [_operationQueue removeObjectAtIndex:0]; 122 | } 123 | if ([_operationQueue count] > 0) { 124 | block = [_operationQueue objectAtIndex:0]; 125 | removeHead = YES; 126 | } 127 | pthread_mutex_unlock(&_operationQueueMutex); 128 | if (block != nil) { 129 | block(tableView); 130 | } else { 131 | [self.listView updateIndicesForVisibleRows]; 132 | [self contentsWillChange]; 133 | return; 134 | } 135 | } 136 | } 137 | 138 | - (TiCollectionviewCollectionSectionProxy *)sectionForIndex:(NSUInteger)index 139 | { 140 | if (index < [_sections count]) { 141 | return [_sections objectAtIndex:index]; 142 | } 143 | return nil; 144 | } 145 | 146 | - (void) deleteSectionAtIndex:(NSUInteger)index 147 | { 148 | if ([_sections count] <= index) { 149 | DLog(@"[WARN] ListViewProxy: Delete section index is out of range"); 150 | return; 151 | } 152 | TiCollectionviewCollectionSectionProxy *section = [_sections objectAtIndex:index]; 153 | [_sections removeObjectAtIndex:index]; 154 | section.delegate = nil; 155 | [_sections enumerateObjectsUsingBlock:^(TiCollectionviewCollectionSectionProxy *section, NSUInteger idx, BOOL *stop) { 156 | section.sectionIndex = idx; 157 | }]; 158 | [self forgetProxy:section]; 159 | } 160 | 161 | - (NSArray *)keySequence 162 | { 163 | static dispatch_once_t onceToken; 164 | static NSArray *keySequence = nil; 165 | dispatch_once(&onceToken, ^{ 166 | keySequence = [[NSArray alloc] initWithObjects:@"style", @"templates", @"defaultItemTemplate", @"sections", @"backgroundColor",nil]; 167 | }); 168 | return keySequence; 169 | } 170 | 171 | - (void)viewDidAttach 172 | { 173 | [self.listView collectionView]; 174 | } 175 | 176 | - (void)willShow 177 | { 178 | [super willShow]; 179 | } 180 | 181 | #pragma mark - Public API 182 | 183 | - (void)setTemplates:(id)args 184 | { 185 | ENSURE_TYPE_OR_NIL(args,NSDictionary); 186 | NSMutableDictionary *templates = [[NSMutableDictionary alloc] initWithCapacity:[args count]]; 187 | [(NSDictionary *)args enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { 188 | TiViewTemplate *template = [TiViewTemplate templateFromViewTemplate:obj]; 189 | if (template != nil) { 190 | [templates setObject:template forKey:key]; 191 | } 192 | }]; 193 | TiThreadPerformOnMainThread(^{ 194 | [self.listView setDictTemplates_:templates]; 195 | }, [NSThread isMainThread]); 196 | } 197 | 198 | - (NSArray *)sections 199 | { 200 | return [self dispatchBlockWithResult:^() { 201 | return [_sections copy]; 202 | }]; 203 | } 204 | 205 | - (NSNumber *)sectionCount 206 | { 207 | return [self dispatchBlockWithResult:^() { 208 | return [NSNumber numberWithUnsignedInteger:[_sections count]]; 209 | }]; 210 | } 211 | 212 | - (void)setSections:(id)args 213 | { 214 | ENSURE_TYPE_OR_NIL(args,NSArray); 215 | NSMutableArray *insertedSections = [args mutableCopy]; 216 | [insertedSections enumerateObjectsUsingBlock:^(TiCollectionviewCollectionSectionProxy *section, NSUInteger idx, BOOL *stop) { 217 | ENSURE_TYPE(section, TiCollectionviewCollectionSectionProxy); 218 | [self rememberProxy:section]; 219 | }]; 220 | [self dispatchBlock:^(UICollectionView *tableView) { 221 | [_sections enumerateObjectsUsingBlock:^(TiCollectionviewCollectionSectionProxy *section, NSUInteger idx, BOOL *stop) { 222 | section.delegate = nil; 223 | if (![insertedSections containsObject:section]) { 224 | [self forgetProxy:section]; 225 | } 226 | }]; 227 | _sections = insertedSections; 228 | [_sections enumerateObjectsUsingBlock:^(TiCollectionviewCollectionSectionProxy *section, NSUInteger idx, BOOL *stop) { 229 | section.delegate = self; 230 | section.sectionIndex = idx; 231 | }]; 232 | [tableView reloadData]; 233 | [self contentsWillChange]; 234 | }]; 235 | } 236 | 237 | - (void)appendSection:(id)args 238 | { 239 | ENSURE_ARG_COUNT(args, 1); 240 | id arg = [args objectAtIndex:0]; 241 | NSArray *appendedSections = [arg isKindOfClass:[NSArray class]] ? arg : [NSArray arrayWithObject:arg]; 242 | if ([appendedSections count] == 0) { 243 | return; 244 | } 245 | 246 | [appendedSections enumerateObjectsUsingBlock:^(TiCollectionviewCollectionSectionProxy *section, NSUInteger idx, BOOL *stop) { 247 | ENSURE_TYPE(section, TiCollectionviewCollectionSectionProxy); 248 | [self rememberProxy:section]; 249 | }]; 250 | [self dispatchUpdateAction:^(UICollectionView *tableView) { 251 | NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init]; 252 | [appendedSections enumerateObjectsUsingBlock:^(TiCollectionviewCollectionSectionProxy *section, NSUInteger idx, BOOL *stop) { 253 | if (![_sections containsObject:section]) { 254 | NSUInteger insertIndex = [_sections count]; 255 | [_sections addObject:section]; 256 | section.delegate = self; 257 | section.sectionIndex = insertIndex; 258 | [indexSet addIndex:insertIndex]; 259 | } else { 260 | DLog(@"[WARN] ListView: Attempt to append exising section"); 261 | } 262 | }]; 263 | if ([indexSet count] > 0) { 264 | [tableView insertSections:indexSet]; 265 | } 266 | }]; 267 | } 268 | 269 | - (void)deleteSectionAt:(id)args 270 | { 271 | ENSURE_ARG_COUNT(args, 1); 272 | NSUInteger deleteIndex = [TiUtils intValue:[args objectAtIndex:0]]; 273 | [self dispatchUpdateAction:^(UICollectionView *tableView) { 274 | if ([_sections count] <= deleteIndex) { 275 | DLog(@"[WARN] ListView: Delete section index is out of range"); 276 | return; 277 | } 278 | TiCollectionviewCollectionSectionProxy *section = [_sections objectAtIndex:deleteIndex]; 279 | [_sections removeObjectAtIndex:deleteIndex]; 280 | section.delegate = nil; 281 | [_sections enumerateObjectsUsingBlock:^(TiCollectionviewCollectionSectionProxy *section, NSUInteger idx, BOOL *stop) { 282 | section.sectionIndex = idx; 283 | }]; 284 | [tableView deleteSections:[NSIndexSet indexSetWithIndex:deleteIndex]]; 285 | [self forgetProxy:section]; 286 | }]; 287 | } 288 | 289 | - (void)insertSectionAt:(id)args 290 | { 291 | ENSURE_ARG_COUNT(args, 2); 292 | NSUInteger insertIndex = [TiUtils intValue:[args objectAtIndex:0]]; 293 | id arg = [args objectAtIndex:1]; 294 | NSArray *insertSections = [arg isKindOfClass:[NSArray class]] ? arg : [NSArray arrayWithObject:arg]; 295 | if ([insertSections count] == 0) { 296 | return; 297 | } 298 | [insertSections enumerateObjectsUsingBlock:^(TiCollectionviewCollectionSectionProxy *section, NSUInteger idx, BOOL *stop) { 299 | ENSURE_TYPE(section, TiCollectionviewCollectionSectionProxy); 300 | [self rememberProxy:section]; 301 | }]; 302 | [self dispatchUpdateAction:^(UICollectionView *tableView) { 303 | if ([_sections count] < insertIndex) { 304 | DLog(@"[WARN] ListView: Insert section index is out of range"); 305 | [insertSections enumerateObjectsUsingBlock:^(TiCollectionviewCollectionSectionProxy *section, NSUInteger idx, BOOL *stop) { 306 | [self forgetProxy:section]; 307 | }]; 308 | return; 309 | } 310 | NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init]; 311 | __block NSUInteger index = insertIndex; 312 | [insertSections enumerateObjectsUsingBlock:^(TiCollectionviewCollectionSectionProxy *section, NSUInteger idx, BOOL *stop) { 313 | if (![_sections containsObject:section]) { 314 | [_sections insertObject:section atIndex:index]; 315 | section.delegate = self; 316 | [indexSet addIndex:index]; 317 | ++index; 318 | } else { 319 | DLog(@"[WARN] ListView: Attempt to insert exising section"); 320 | } 321 | }]; 322 | [_sections enumerateObjectsUsingBlock:^(TiCollectionviewCollectionSectionProxy *section, NSUInteger idx, BOOL *stop) { 323 | section.sectionIndex = idx; 324 | }]; 325 | [tableView insertSections:indexSet]; 326 | }]; 327 | } 328 | 329 | - (void)replaceSectionAt:(id)args 330 | { 331 | ENSURE_ARG_COUNT(args, 2); 332 | NSUInteger replaceIndex = [TiUtils intValue:[args objectAtIndex:0]]; 333 | TiCollectionviewCollectionSectionProxy *section = [args objectAtIndex:1]; 334 | ENSURE_TYPE_OR_NIL(section, TiCollectionviewCollectionSectionProxy); 335 | 336 | [self rememberProxy:section]; 337 | [self dispatchUpdateAction:^(UICollectionView *tableView) { 338 | if ([_sections containsObject:section]) { 339 | DebugLog(@"[WARN] ListView: Attempt to insert exising section"); 340 | return; 341 | } 342 | if ([_sections count] <= replaceIndex) { 343 | DebugLog(@"[WARN] ListView: Replace section index is out of range"); 344 | [self forgetProxy:section]; 345 | return; 346 | } 347 | TiCollectionviewCollectionSectionProxy *prevSection = [_sections objectAtIndex:replaceIndex]; 348 | prevSection.delegate = nil; 349 | if (section != nil) { 350 | [_sections replaceObjectAtIndex:replaceIndex withObject:section]; 351 | section.delegate = self; 352 | section.sectionIndex = replaceIndex; 353 | } 354 | NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:replaceIndex]; 355 | [tableView deleteSections:indexSet]; 356 | [tableView insertSections:indexSet]; 357 | [self forgetProxy:prevSection]; 358 | }]; 359 | } 360 | 361 | - (void)scrollToItem:(id)args 362 | { 363 | if (view != nil) { 364 | ENSURE_ARG_COUNT(args, 2); 365 | NSUInteger sectionIndex = [TiUtils intValue:[args objectAtIndex:0]]; 366 | NSUInteger itemIndex = [TiUtils intValue:[args objectAtIndex:1]]; 367 | NSDictionary *properties = [args count] > 2 ? [args objectAtIndex:2] : nil; 368 | UICollectionViewScrollPosition scrollPosition = [TiUtils intValue:@"position" properties:properties def:UICollectionViewScrollPositionNone]; 369 | BOOL animated = [TiUtils boolValue:@"animated" properties:properties def:YES]; 370 | TiThreadPerformOnMainThread(^{ 371 | if ([_sections count] <= sectionIndex) { 372 | DLog(@"[WARN] ListView: Scroll to section index is out of range"); 373 | return; 374 | } 375 | TiCollectionviewCollectionSectionProxy *section = [_sections objectAtIndex:sectionIndex]; 376 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:MIN(itemIndex, section.itemCount) inSection:sectionIndex]; 377 | [self.listView.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; 378 | }, [NSThread isMainThread]); 379 | } 380 | } 381 | 382 | - (void)selectItem:(id)args 383 | { 384 | if (view != nil) { 385 | ENSURE_ARG_COUNT(args, 2); 386 | NSUInteger sectionIndex = [TiUtils intValue:[args objectAtIndex:0]]; 387 | NSUInteger itemIndex = [TiUtils intValue:[args objectAtIndex:1]]; 388 | TiThreadPerformOnMainThread(^{ 389 | if ([_sections count] <= sectionIndex) { 390 | DLog(@"[WARN] ListView: Select section index is out of range"); 391 | return; 392 | } 393 | TiCollectionviewCollectionSectionProxy *section = [_sections objectAtIndex:sectionIndex]; 394 | if (section.itemCount <= itemIndex) { 395 | DLog(@"[WARN] ListView: Select item index is out of range"); 396 | return; 397 | } 398 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:itemIndex inSection:sectionIndex]; 399 | [self.listView.collectionView selectItemAtIndexPath:indexPath animated:YES scrollPosition:UICollectionViewScrollPositionNone]; 400 | [self.listView.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionNone animated:YES]; 401 | }, NO); 402 | } 403 | } 404 | 405 | - (void)deselectItem:(id)args 406 | { 407 | if (view != nil) { 408 | ENSURE_ARG_COUNT(args, 2); 409 | NSUInteger sectionIndex = [TiUtils intValue:[args objectAtIndex:0]]; 410 | NSUInteger itemIndex = [TiUtils intValue:[args objectAtIndex:1]]; 411 | TiThreadPerformOnMainThread(^{ 412 | if ([_sections count] <= sectionIndex) { 413 | DLog(@"[WARN] ListView: Select section index is out of range"); 414 | return; 415 | } 416 | TiCollectionviewCollectionSectionProxy *section = [_sections objectAtIndex:sectionIndex]; 417 | if (section.itemCount <= itemIndex) { 418 | DLog(@"[WARN] ListView: Select item index is out of range"); 419 | return; 420 | } 421 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:itemIndex inSection:sectionIndex]; 422 | [self.listView.collectionView deselectItemAtIndexPath:indexPath animated:YES]; 423 | }, [NSThread isMainThread]); 424 | } 425 | } 426 | 427 | -(void)setContentInsets:(id)args 428 | { 429 | id arg1; 430 | id arg2; 431 | if ([args isKindOfClass:[NSDictionary class]]) { 432 | arg1 = args; 433 | arg2 = nil; 434 | } 435 | else { 436 | arg1 = [args objectAtIndex:0]; 437 | arg2 = [args count] > 1 ? [args objectAtIndex:1] : nil; 438 | } 439 | TiThreadPerformOnMainThread(^{ 440 | [self.listView setContentInsets_:arg1 withObject:arg2]; 441 | }, NO); 442 | } 443 | 444 | #pragma mark - Marker Support 445 | - (void)setMarker:(id)args; 446 | { 447 | ENSURE_SINGLE_ARG(args, NSDictionary); 448 | pthread_rwlock_wrlock(&_markerLock); 449 | int section = [TiUtils intValue:[args objectForKey:@"sectionIndex"] def:-1]; 450 | int row = [TiUtils intValue:[args objectForKey:@"itemIndex"] def:-1]; 451 | marker = [NSIndexPath indexPathForRow:row inSection:section]; 452 | pthread_rwlock_unlock(&_markerLock); 453 | } 454 | 455 | -(void)willDisplayCell:(NSIndexPath*)indexPath 456 | { 457 | if ((marker != nil) && [self _hasListeners:@"marker"]) { 458 | //Never block the UI thread 459 | int result = pthread_rwlock_tryrdlock(&_markerLock); 460 | if (result != 0) { 461 | return; 462 | } 463 | if ( (indexPath.section > marker.section) || ( (marker.section == indexPath.section) && (indexPath.row >= marker.row) ) ){ 464 | [self fireEvent:@"marker" withObject:nil withSource:self propagate:NO reportSuccess:NO errorCode:0 message:nil]; 465 | } 466 | pthread_rwlock_unlock(&_markerLock); 467 | } 468 | } 469 | 470 | DEFINE_DEF_BOOL_PROP(willScrollOnStatusTap,YES); 471 | USE_VIEW_FOR_CONTENT_HEIGHT 472 | USE_VIEW_FOR_CONTENT_WIDTH 473 | 474 | @end 475 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewHeaderFooterReusableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TiCollectionviewHeaderFooterReusableView.h 3 | // TiCollectionView 4 | // 5 | // Created by Ayorinde Adesugba on 1/28/15. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface TiCollectionviewHeaderFooterReusableView : UICollectionReusableView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewHeaderFooterReusableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TiCollectionviewHeaderFooterReusableView.m 3 | // TiCollectionView 4 | // 5 | // Created by Ayorinde Adesugba on 1/28/15. 6 | // 7 | // 8 | 9 | #import "TiCollectionviewHeaderFooterReusableView.h" 10 | 11 | @implementation TiCollectionviewHeaderFooterReusableView 12 | 13 | - (id)initWithFrame:(CGRect)frame 14 | { 15 | self = [super initWithFrame:frame]; 16 | if (self) { 17 | // Initialization code 18 | //[self setBounds:CGRectMake(0, 0, 320.f, 50.f)]; 19 | //[self setBackgroundColor:[UIColor yellowColor]]; 20 | 21 | } 22 | return self; 23 | } 24 | 25 | - (id)initWithCoder:(NSCoder *)aDecoder 26 | { 27 | self = [super initWithCoder:aDecoder]; 28 | if (self) { 29 | // Initialization code 30 | 31 | } 32 | return self; 33 | } 34 | 35 | - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes 36 | { 37 | 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewModule.h: -------------------------------------------------------------------------------- 1 | /** 2 | * TiCollectionView 3 | * 4 | * Created by Your Name 5 | * Copyright (c) 2014 Your Company. All rights reserved. 6 | */ 7 | 8 | #import "TiModule.h" 9 | 10 | @interface TiCollectionviewModule : TiModule 11 | { 12 | NSNumber* LAYOUT_GRID; 13 | NSNumber* LAYOUT_WATERFALL; 14 | 15 | NSNumber* DIRECTION_LEFT_TO_RIGHT; 16 | NSNumber* DIRECTION_RIGHT_TO_LEFT; 17 | NSNumber* DIRECTION_SHORTEST_FIRST; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewModule.m: -------------------------------------------------------------------------------- 1 | /** 2 | * TiCollectionView 3 | * 4 | * Created by Your Name 5 | * Copyright (c) 2014 Your Company. All rights reserved. 6 | */ 7 | 8 | #import "TiCollectionviewModule.h" 9 | #import "TiBase.h" 10 | #import "TiHost.h" 11 | #import "TiUtils.h" 12 | #import "TiCollectionviewCollectionView.h" 13 | #import "CHTCollectionViewWaterfallLayout.h" 14 | 15 | @implementation TiCollectionviewModule 16 | 17 | 18 | #pragma mark Internal 19 | 20 | // this is generated for your module, please do not change it 21 | -(id)moduleGUID 22 | { 23 | return @"eef8bad5-aef3-49fb-bb8d-7cb781e9e7f5"; 24 | } 25 | 26 | // this is generated for your module, please do not change it 27 | -(NSString*)moduleId 28 | { 29 | return @"Ti.collectionview"; 30 | } 31 | 32 | MAKE_SYSTEM_PROP(LAYOUT_GRID, kLayoutTypeGrid); 33 | MAKE_SYSTEM_PROP(LAYOUT_WATERFALL, kLayoutTypeWaterfall ); 34 | 35 | MAKE_SYSTEM_PROP(DIRECTION_LEFT_TO_RIGHT,CHTCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight); 36 | MAKE_SYSTEM_PROP(DIRECTION_RIGHT_TO_LEFT, CHTCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft); 37 | MAKE_SYSTEM_PROP(DIRECTION_SHORTEST_FIRST, CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst); 38 | 39 | MAKE_SYSTEM_PROP(SCROLL_HORIZONTAL, kScrollHorizontal); 40 | MAKE_SYSTEM_PROP(SCROLL_VERTICAL, kScrollVertical); 41 | 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewModuleAssets.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a generated file. Do not edit or your changes will be lost 3 | */ 4 | 5 | @interface TiCollectionviewModuleAssets : NSObject 6 | { 7 | } 8 | - (NSData*) moduleAsset; 9 | - (NSData*) resolveModuleAsset:(NSString*)path; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /ios/Classes/TiCollectionviewModuleAssets.m: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a generated file. Do not edit or your changes will be lost 3 | */ 4 | #import "TiCollectionviewModuleAssets.h" 5 | 6 | extern NSData* filterDataInRange(NSData* thedata, NSRange range); 7 | 8 | @implementation TiCollectionviewModuleAssets 9 | 10 | - (NSData*) moduleAsset 11 | { 12 | 13 | 14 | return nil; 15 | } 16 | 17 | - (NSData*) resolveModuleAsset:(NSString*)path 18 | { 19 | 20 | 21 | return nil; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /ios/Classes/TiSearchDisplayController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TiSearchDisplayController.h 3 | // TiCollectionView 4 | // 5 | // Created by Ayorinde Adesugba on 1/29/15. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @protocol TiSearchDisplayDelegate; 13 | 14 | @interface TiSearchDisplayController : NSObject 15 | 16 | - (id)initWithSearchBar:(UISearchBar *)searchBar contentsController:(UIViewController *)viewController; 17 | - (void)setActive:(BOOL)visible animated:(BOOL)animated; 18 | 19 | @property(nonatomic,assign) id delegate; 20 | @property(nonatomic, getter = isActive) BOOL active; 21 | @property(nonatomic, readonly) UISearchBar *searchBar; 22 | @property(nonatomic, readonly) UIViewController *searchContentsController; 23 | @property(nonatomic, readonly) UICollectionView *searchResultsCollectionView; 24 | @property(nonatomic, assign) id searchResultsDataSource; 25 | @property(nonatomic, assign) id searchResultsDelegate; 26 | 27 | @end 28 | 29 | 30 | 31 | @protocol TiSearchDisplayDelegate 32 | 33 | @optional 34 | 35 | - (void)searchDisplayControllerWillBeginSearch:(TiSearchDisplayController *)controller; 36 | - (void)searchDisplayControllerDidBeginSearch:(TiSearchDisplayController *)controller; 37 | - (void)searchDisplayControllerWillEndSearch:(TiSearchDisplayController *)controller; 38 | - (void)searchDisplayControllerDidEndSearch:(TiSearchDisplayController *)controller; 39 | - (void)textDidChange:(NSString *)searchText; 40 | - (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ios/Classes/TiSearchDisplayController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TiSearchDisplayController.m 3 | // TiCollectionView 4 | // 5 | // Created by Ayorinde Adesugba on 1/29/15. 6 | // 7 | // 8 | 9 | #import "TiBase.h" 10 | #import "TiSearchDisplayController.h" 11 | 12 | @implementation TiSearchDisplayController 13 | 14 | - (id)initWithSearchBar:(UISearchBar *)searchBar contentsController:(UIViewController *)viewController { 15 | self = [super init]; 16 | 17 | if (self) { 18 | _searchBar = searchBar; 19 | _searchBar.delegate = self; 20 | _searchContentsController = viewController; 21 | 22 | CGFloat y = 64.0f; 23 | CGFloat height = _searchContentsController.view.frame.size.height - y; 24 | 25 | UICollectionViewFlowLayout* layout = [[UICollectionViewFlowLayout alloc] init]; 26 | _searchResultsCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, y, _searchContentsController.view.frame.size.width, height) collectionViewLayout:layout]; 27 | _searchResultsCollectionView.scrollsToTop = NO; 28 | } 29 | 30 | return self; 31 | } 32 | 33 | - (void)setSearchResultsDataSource:(id)searchResultsDataSource { 34 | _searchResultsCollectionView.dataSource = searchResultsDataSource; 35 | } 36 | 37 | - (void)setSearchResultsDelegate:(id)searchResultsDelegate { 38 | _searchResultsCollectionView.delegate = searchResultsDelegate; 39 | } 40 | 41 | - (void)setActive:(BOOL)visible animated:(BOOL)animated { 42 | if (!visible) { 43 | [_searchBar resignFirstResponder]; 44 | _searchBar.text = nil; 45 | _searchBar.showsCancelButton = NO; 46 | } 47 | 48 | if (visible && [self.delegate respondsToSelector:@selector(searchDisplayControllerWillBeginSearch:)]) { 49 | [self.delegate searchDisplayControllerWillBeginSearch:self]; 50 | } else if (!visible && [self.delegate respondsToSelector:@selector(searchDisplayControllerWillEndSearch:)]) { 51 | [self.delegate searchDisplayControllerWillEndSearch:self]; 52 | } 53 | 54 | [_searchContentsController.navigationController setNavigationBarHidden:visible animated:YES]; 55 | 56 | float alpha = 0; 57 | 58 | if (visible) { 59 | [_searchContentsController.view addSubview:_searchResultsCollectionView]; 60 | alpha = 0.2; 61 | } 62 | 63 | #pragma clang diagnostic push 64 | #pragma clang diagnostic ignored "-Wundeclared-selector" 65 | if ([_searchContentsController.view respondsToSelector:@selector(scrollEnabled)]) { 66 | ((UIScrollView *)_searchContentsController.view).scrollEnabled = !visible; 67 | } 68 | #pragma clang diagnostic pop 69 | 70 | if (animated) { 71 | [UIView animateWithDuration:0.2 animations:^{ 72 | _searchResultsCollectionView.alpha = alpha; 73 | } completion:^(BOOL finished) { 74 | self.active = visible; 75 | }]; 76 | } else { 77 | _searchResultsCollectionView.alpha = alpha; 78 | } 79 | } 80 | 81 | #pragma mark - UISearchBarDelegate 82 | 83 | - (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope { 84 | if ([self.delegate respondsToSelector:@selector(searchBar:selectedScopeButtonIndexDidChange:)]) { 85 | [self.delegate searchBar:searchBar selectedScopeButtonIndexDidChange:selectedScope]; 86 | } 87 | } 88 | 89 | - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { 90 | if ([self.delegate respondsToSelector:@selector(textDidChange:)]) { 91 | [self.delegate textDidChange:searchText]; 92 | } 93 | } 94 | 95 | - (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar { 96 | [searchBar setShowsCancelButton:YES animated:YES]; 97 | [self setActive:YES animated:YES]; 98 | [_searchResultsCollectionView reloadData]; 99 | } 100 | 101 | - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { 102 | [_searchResultsCollectionView reloadData]; 103 | } 104 | 105 | - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { 106 | [self setActive:NO animated:YES]; 107 | [self.searchResultsCollectionView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO]; 108 | } 109 | 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /ios/TiCollectionViewWorkspace.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/TiCollectionview_Prefix.pch: -------------------------------------------------------------------------------- 1 | 2 | #ifdef __OBJC__ 3 | #import 4 | 5 | // Debug Logging 6 | //#if defined(DEBUG) || (TARGET_OS_SIMULATOR) 7 | //#define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); 8 | //#else 9 | #define DLog(...) {} 10 | //#endif 11 | 12 | #endif 13 | 14 | 15 | -------------------------------------------------------------------------------- /ios/manifest: -------------------------------------------------------------------------------- 1 | # 2 | # this is your module manifest and used by Titanium 3 | # during compilation, packaging, distribution, etc. 4 | # 5 | version: 3.0.1 6 | apiversion: 2 7 | description: TiCollectionView 8 | author: Marcel Pociot 9 | license: MIT 10 | copyright: Copyright (c) 2014-2015 by Marcel Pociot, 2015-Present by Nuno Costa 11 | architectures: armv7 i386 x86_64 arm64 12 | 13 | # these should not be edited 14 | name: TiCollectionView 15 | moduleid: ti.collectionview 16 | guid: eef8bad5-aef3-49fb-bb8d-7cb781e9e7f5 17 | platform: iphone 18 | minsdk: 5.5.1.GA 19 | -------------------------------------------------------------------------------- /ios/metadata.json: -------------------------------------------------------------------------------- 1 | {"exports":[]} -------------------------------------------------------------------------------- /ios/module.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // PLACE ANY BUILD DEFINITIONS IN THIS FILE AND THEY WILL BE 3 | // PICKED UP DURING THE APP BUILD FOR YOUR MODULE 4 | // 5 | // see the following webpage for instructions on the settings 6 | // for this file: 7 | // http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/XcodeBuildSystem/400-Build_Configurations/build_configs.html 8 | // 9 | 10 | // 11 | // How to add a Framework (example) 12 | // 13 | // OTHER_LDFLAGS=$(inherited) -framework Foo 14 | // 15 | // Adding a framework for a specific version(s) of iPhone: 16 | // 17 | // OTHER_LDFLAGS[sdk=iphoneos4*]=$(inherited) -framework Foo 18 | // OTHER_LDFLAGS[sdk=iphonesimulator4*]=$(inherited) -framework Foo 19 | // 20 | // 21 | // How to add a compiler define: 22 | // 23 | // OTHER_CFLAGS=$(inherited) -DFOO=1 24 | // 25 | // 26 | // IMPORTANT NOTE: always use $(inherited) in your overrides 27 | // 28 | -------------------------------------------------------------------------------- /ios/timodule.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ios/titanium.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // CHANGE THESE VALUES TO REFLECT THE VERSION (AND LOCATION IF DIFFERENT) 4 | // OF YOUR TITANIUM SDK YOU'RE BUILDING FOR 5 | // 6 | // 7 | TITANIUM_SDK_VERSION = 7.0.1.GA 8 | 9 | 10 | // 11 | // THESE SHOULD BE OK GENERALLY AS-IS 12 | // 13 | TITANIUM_SDK = ~/Library/Application Support/Titanium/mobilesdk/osx/$(TITANIUM_SDK_VERSION) 14 | TITANIUM_BASE_SDK = "$(TITANIUM_SDK)/iphone/include" 15 | TITANIUM_BASE_SDK2 = "$(TITANIUM_SDK)/iphone/include/TiCore" 16 | TITANIUM_BASE_SDK3 = "$(TITANIUM_SDK)/iphone/include/JavaScriptCore" 17 | TITANIUM_BASE_SDK4 = "$(TITANIUM_SDK)/iphone/include/APSHTTPClient" 18 | HEADER_SEARCH_PATHS= $(TITANIUM_BASE_SDK) $(TITANIUM_BASE_SDK2) $(TITANIUM_BASE_SDK3) $(TITANIUM_BASE_SDK4) 19 | -------------------------------------------------------------------------------- /lib/CollectionView.js: -------------------------------------------------------------------------------- 1 | function createCollectionView(options) { 2 | if( OS_IOS ) 3 | { 4 | return require("ti.collectionview").createCollectionView(options); 5 | } 6 | var templates = options.templates; 7 | for (var binding in templates) { 8 | var currentTemplate = templates[binding]; 9 | //process template 10 | processTemplate(currentTemplate); 11 | //process child templates 12 | processChildTemplates(currentTemplate); 13 | } 14 | Ti.API.info( JSON.stringify(options) ); 15 | var listView = require("ti.collectionview").createCollectionView(options); 16 | 17 | return listView; 18 | } 19 | 20 | //Create ListItemProxy, add events, then store it in 'tiProxy' property 21 | function processTemplate(properties) { 22 | var cellProxy = require("ti.collectionview").createCollectionItem(); 23 | properties.tiProxy = cellProxy; 24 | var events = properties.events; 25 | addEventListeners(events, cellProxy); 26 | } 27 | 28 | //Recursive function that process childTemplates and append corresponding proxies to 29 | //property 'tiProxy'. I.e: type: "Titanium.UI.Label" -> tiProxy: LabelProxy object 30 | function processChildTemplates(properties) { 31 | if (!properties.hasOwnProperty('childTemplates')) return; 32 | 33 | var childProperties = properties.childTemplates; 34 | if (childProperties === void 0 || childProperties === null) return; 35 | 36 | for (var i = 0; i < childProperties.length; i++) { 37 | var child = childProperties[i]; 38 | var proxyType = child.type; 39 | if (proxyType !== void 0) { 40 | var creationProperties = child.properties; 41 | var creationFunction = lookup(proxyType); 42 | var childProxy; 43 | //create the proxy 44 | if (creationProperties !== void 0) { 45 | childProxy = creationFunction(creationProperties); 46 | } else { 47 | childProxy = creationFunction(); 48 | } 49 | //add event listeners 50 | var events = child.events; 51 | addEventListeners(events, childProxy); 52 | //append proxy to tiProxy property 53 | child.tiProxy = childProxy; 54 | } 55 | 56 | processChildTemplates(child); 57 | 58 | } 59 | 60 | 61 | } 62 | 63 | //add event listeners 64 | function addEventListeners(events, proxy) { 65 | if (events !== void 0) { 66 | for (var eventName in events) { 67 | proxy.addEventListener(eventName, events[eventName]); 68 | } 69 | } 70 | } 71 | 72 | //convert name of UI elements into a constructor function. 73 | //I.e: lookup("Titanium.UI.Label") returns Titanium.UI.createLabel function 74 | function lookup(name) { 75 | var lastDotIndex = name.lastIndexOf('.'); 76 | var proxy = eval(name.substring(0, lastDotIndex)); 77 | if (typeof(proxy) == undefined) return; 78 | 79 | var proxyName = name.slice(lastDotIndex + 1); 80 | return proxy['create' + proxyName]; 81 | } 82 | 83 | exports.createCollectionView = createCollectionView; 84 | --------------------------------------------------------------------------------