├── LICENSE ├── README.md └── src ├── Constants.as ├── CustomTheme.as ├── Main.as ├── PizzaApp-app.xml ├── PizzaApp.as ├── assets ├── font.otf ├── fonts │ └── playball.ttf ├── icons │ ├── account_circle.png │ ├── back.png │ ├── camera.png │ ├── camera_roll.png │ ├── cancel.png │ ├── chat.png │ ├── check.png │ ├── close.png │ ├── date.png │ ├── directions.png │ ├── download.png │ ├── facebook.png │ ├── gallery.png │ ├── google.png │ ├── gps.png │ ├── home.png │ ├── home_rounded.png │ ├── info.png │ ├── insert_comment.png │ ├── loading.png │ ├── logout.png │ ├── menu.png │ ├── open_browser.png │ ├── overflow.png │ ├── person.png │ ├── person_pin.png │ ├── phone.png │ ├── pin.png │ ├── radio_checked.png │ ├── radio_unchecked.png │ ├── rounded.png │ ├── save.png │ ├── search.png │ ├── send.png │ ├── settings.png │ ├── star.png │ ├── thumb_down.png │ ├── thumb_up.png │ ├── twitter.png │ ├── upload.png │ └── warning.png ├── launchicons │ ├── icon-100.png │ ├── icon-1024.png │ ├── icon-114.png │ ├── icon-120.png │ ├── icon-144.png │ ├── icon-152.png │ ├── icon-167.png │ ├── icon-180.png │ ├── icon-192.png │ ├── icon-29.png │ ├── icon-36.png │ ├── icon-40.png │ ├── icon-48.png │ ├── icon-50.png │ ├── icon-512.png │ ├── icon-57.png │ ├── icon-58.png │ ├── icon-60.png │ ├── icon-72.png │ ├── icon-75.png │ ├── icon-76.png │ ├── icon-80.png │ ├── icon-87.png │ └── icon-96.png ├── rooms │ ├── room1.png │ ├── room2.png │ └── room3.png └── yelp │ ├── 0.png │ ├── 1.5.png │ ├── 1.png │ ├── 2.5.png │ ├── 2.png │ ├── 3.5.png │ ├── 3.png │ ├── 4.5.png │ ├── 4.png │ ├── 5.png │ └── yelp_logo_large.png ├── businessScreens ├── BusinessDetailsScreen.as ├── DirectionsScreen.as ├── FinderScreen.as └── ResultsScreen.as ├── chatScreens ├── ChatScreen.as └── ChatroomsScreen.as ├── cz └── j4w │ └── map │ ├── Map.as │ ├── MapLayer.as │ ├── MapLayerOptions.as │ ├── MapMarker.as │ ├── MapOptions.as │ ├── MapTile.as │ ├── MapTilesBuffer.as │ ├── TouchSheet.as │ ├── events │ └── MapEvent.as │ ├── example │ ├── Main.as │ ├── MainStarling.as │ └── marker.png │ └── geo │ ├── GeoMap.as │ ├── GeoUtils.as │ └── Maps.as ├── galleryScreens ├── GalleryScreen.as ├── ImageDetailsScreen.as └── UploadImageScreen.as ├── newsScreens ├── HomeScreen.as └── NewsDetailsScreen.as ├── renderers ├── BusinessRenderer.as └── NewsRenderer.as ├── screens ├── LoginScreen.as └── SettingsScreen.as └── utils ├── ChatMessageItemRenderer.as ├── NavigatorData.as ├── ProfileManager.as └── RoundedRect.as /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Mr. Phantom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pizza App 1.1.1 2 | 3 | Pizza App is a mobile application developed with Starling Framework and Feathers UI. It showcases how to use Firebase services with ActionScript to create a small social network. 4 | 5 | It uses the following APIs and technologies: 6 | 7 | * REST requests from the Firebase Database (JSON) 8 | * Realtime requests (JSON & URLStream) 9 | * User Auth with Facebook, Twitter and Google using Firebase Auth 10 | * File uploading and downloading with Firebase Storage 11 | * CameraUI & CameraRoll 12 | * Geolocation API 13 | * OpenStreetMaps using the [AS3 Starling/Feathers maps](https://github.com/ZwickTheGreat/feathers-maps) 14 | 15 | Some of the techniques covered are: 16 | 17 | * Creating and managing a file to store user credentials. 18 | * Material Design inspired custom theme. 19 | * Passing data between screens. 20 | * Remembering the app's state between screens. 21 | * Correctly disposing unused objects. 22 | * Multi DPI development. 23 | 24 | ## Dependencies 25 | 26 | * [Starling Framework 2.1](http://gamua.com/starling/) 27 | * [Feathers UI 3.2.0](https://feathersui.com/) 28 | * [Adobe AIR 25](http://www.adobe.com/devnet/air/air-sdk-download.html) 29 | 30 | To compile this application you require to provide your own Yelp Fusion API Key and Secret. You will also need to provide your Firebase API key which can be obtained for free on the Firebase developer console (see below), this project only works with Firebase V3 and its newer console located at https://console.firebase.google.com/ 31 | 32 | ## What is Firebase? 33 | 34 | Firebase is a set of tools and services that are designed to ease the development of server side infrastructure for apps and games. You can easily and securely save and retrieve data from the cloud. 35 | 36 | It also offers a user management service which allows your users to register an account in your app and have personalized experiences. 37 | In this app the users can upload images, post real time messages, post comments and vote on pictures. 38 | 39 | ## Firebase Rules 40 | 41 | The following Database rules are used for this app: 42 | 43 | ```json 44 | { 45 | "rules": { 46 | "images": { 47 | "$other": { 48 | "$views": { 49 | ".write": true 50 | } 51 | }, 52 | ".indexOn": [ 53 | "status", 54 | "views" 55 | ], 56 | ".read": true, 57 | ".write": "auth != null" 58 | }, 59 | "images_comments": { 60 | "$other": { 61 | ".indexOn": [ 62 | "timestamp" 63 | ] 64 | }, 65 | ".read": true, 66 | ".write": "auth != null" 67 | }, 68 | "images_votes": { 69 | "$other": { 70 | ".indexOn": [ 71 | "value" 72 | ] 73 | }, 74 | ".read": true, 75 | ".write": "auth != null" 76 | }, 77 | "rooms": { 78 | ".read": true, 79 | ".write": false 80 | }, 81 | "messages": { 82 | "$other": { 83 | ".indexOn": [ 84 | "timestamp" 85 | ] 86 | }, 87 | ".read": "auth != null", 88 | ".write": "auth != null" 89 | } 90 | } 91 | } 92 | ``` 93 | 94 | There are several nodes that contain information on the app's content: 95 | 96 | * `images` contains the metadata about the uploaded images. 97 | * `images_comments` contains the comments of each uploaded image. 98 | * `images_votes` contains the votes of each uploaded image. 99 | * `rooms` contains the metadata of the chat rooms. 100 | * `messages` contains the messages of each chat room. 101 | 102 | The following rule means that we want all the childs of the node to be indexable by their timestamp. This is used to load the latest messages and comments. 103 | 104 | ```json 105 | "$other": { 106 | ".indexOn": [ 107 | "timestamp" 108 | ] 109 | }, 110 | ``` 111 | 112 | All the data from this app is dynamically generated except for the Chat Rooms. 113 | 114 | Chat rooms JSON structure: 115 | 116 | ```json 117 | { 118 | "room1": { 119 | "description": "Yum, delicious meat.", 120 | "image": "assets/rooms/room1.png", 121 | "internal_id": 1, 122 | "name": "Meat Lovers" 123 | }, 124 | "room2": { 125 | "description": "Don't like meat? No problem!", 126 | "image": "assets/rooms/room2.png", 127 | "internal_id": 2, 128 | "name": "Veggie Lovers" 129 | }, 130 | "room3": { 131 | "description": "Everyone loves cheese, right?", 132 | "image": "assets/rooms/room3.png", 133 | "internal_id": 3, 134 | "name": "Cheese Lovers" 135 | } 136 | } 137 | ``` 138 | 139 | The following Storage rules are used for this app: 140 | 141 | ``` 142 | service firebase.storage { 143 | match /b/.appspot.com/o { 144 | match /images/{allPaths=**} { 145 | allow read; 146 | allow write: if request.auth != null; 147 | } 148 | } 149 | } 150 | ``` 151 | 152 | These rules mean that any user can download the pictures and their respective thumbnails but only registered users are able to create them. 153 | 154 | Follow these steps to locate your Firebase API Key: 155 | 156 | 1. Login to the [Firebase console](https://console.firebase.google.com/) and select a project or create a new one. 157 | 2. In the project home page you will be prompted to add Firebase to `Android`, `iOS` or `Web`. 158 | 3. Select `Web`, a popup will appear. 159 | 4. Copy the `apiKey` from the JavaScript code block. 160 | 5. Open the `Constants.as` file and set your variables and constants accordingly. 161 | 162 | Don't forget to enable Google, Facebook and Twitter authentication from the Auth section in the Firebase console. 163 | 164 | [![Watch on Youtube](http://i.imgur.com/pVtSIr0.png)](https://www.youtube.com/watch?v=klJvYV7Twv8) 165 | 166 | ## Download 167 | 168 | You can test this app by downloading it directly from Google Play. 169 | 170 | [![Download](http://i.imgur.com/He0deVa.png)](https://play.google.com/store/apps/details?id=air.im.phantom.pizza) -------------------------------------------------------------------------------- /src/Constants.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | public class Constants 4 | { 5 | //Customize with your Yelp prooject values 6 | public static const YELP_APP_ID:String = ""; 7 | public static const YELP_APP_SECRET:String = ""; 8 | 9 | //Customize with your Firebase project values 10 | public static const FIREBASE_API_KEY:String = ""; 11 | private static const PROJECT_ID:String = "YOUR-FIREBASE-PROJECT-ID"; 12 | 13 | //Auth and Storage URLs 14 | public static const FIREBASE_STORAGE_URL:String = "https://firebasestorage.googleapis.com/v0/b/" + PROJECT_ID + ".appspot.com/o/"; 15 | public static const FIREBASE_REDIRECT_URL:String = "https://" + PROJECT_ID + ".firebaseapp.com/__/auth/handler"; 16 | 17 | //Database URLs 18 | public static const FIREBASE_IMAGES_GALLERY_URL:String = 'https://' + PROJECT_ID + '.firebaseio.com/images'; 19 | public static const FIREBASE_IMAGES_COMMENTS_BASE_URL:String = 'https://' + PROJECT_ID + '.firebaseio.com/images_comments/'; 20 | public static const FIREBASE_IMAGES_VOTES_BASE_URL:String = 'https://' + PROJECT_ID + '.firebaseio.com/images_votes/'; 21 | public static const FIREBASE_CHATROOMS_URL:String = 'https://' + PROJECT_ID + '.firebaseio.com/rooms.json'; 22 | public static const FIREBASE_CHATROOM_BASE_URL:String = 'https://' + PROJECT_ID + '.firebaseio.com/messages/'; 23 | 24 | //These URLs are used by the Auth service when using Federated login providers 25 | public static const FIREBASE_CREATE_AUTH_URL:String = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/createAuthUri?key=" + FIREBASE_API_KEY; 26 | public static const FIREBASE_VERIFY_ASSERTION_URL:String = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAssertion?key=" + FIREBASE_API_KEY; 27 | public static const FIREBASE_ACCOUNT_SETINFO_URL:String = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccountInfo?key=" + FIREBASE_API_KEY; 28 | //This URL generates an access_token that is used to sign Auth and Storage requests 29 | public static const FIREBASE_AUTH_TOKEN_URL:String = "https://securetoken.googleapis.com/v1/token?key=" + FIREBASE_API_KEY; 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /src/CustomTheme.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import feathers.controls.Alert; 4 | import feathers.controls.Button; 5 | import feathers.controls.ButtonGroup; 6 | import feathers.controls.ButtonState; 7 | import feathers.controls.Header; 8 | import feathers.controls.ImageLoader; 9 | import feathers.controls.Label; 10 | import feathers.controls.List; 11 | import feathers.controls.PanelScreen; 12 | import feathers.controls.TabBar; 13 | import feathers.controls.TextInput; 14 | import feathers.controls.ToggleButton; 15 | import feathers.controls.renderers.BaseDefaultItemRenderer; 16 | import feathers.controls.renderers.DefaultListItemRenderer; 17 | import feathers.controls.text.StageTextTextEditor; 18 | import feathers.controls.text.TextFieldTextRenderer; 19 | import feathers.core.FeathersControl; 20 | import feathers.core.ITextEditor; 21 | import feathers.core.ITextRenderer; 22 | import feathers.layout.Direction; 23 | import feathers.layout.HorizontalAlign; 24 | import feathers.layout.RelativePosition; 25 | import feathers.layout.VerticalAlign; 26 | import feathers.themes.StyleNameFunctionTheme; 27 | 28 | import flash.text.engine.FontDescription; 29 | import flash.text.engine.FontLookup; 30 | 31 | import starling.display.DisplayObject; 32 | import starling.display.Quad; 33 | import starling.text.TextFormat; 34 | import starling.textures.Texture; 35 | 36 | import utils.RoundedRect; 37 | 38 | public class CustomTheme extends StyleNameFunctionTheme 39 | { 40 | [Embed(source="assets/fonts/playball.ttf", fontFamily="MyFont", fontWeight="normal", fontStyle="normal", mimeType="application/x-font", embedAsCFF="false")] 41 | private static const MY_FONT:Class; 42 | 43 | [Embed(source="assets/icons/loading.png")] 44 | private static const LOADING_ASSET:Class; 45 | public static const loadingTexture:Texture = Texture.fromEmbeddedAsset(LOADING_ASSET); 46 | 47 | private var playballFont:FontDescription; 48 | private var transparentQuad:Quad = new Quad(3, 3, 0xFFFFFF); 49 | 50 | public function CustomTheme() 51 | { 52 | super(); 53 | 54 | this.transparentQuad.alpha = 0.0; 55 | this.initialize(); 56 | } 57 | 58 | private function initialize():void 59 | { 60 | Alert.overlayFactory = function ():DisplayObject 61 | { 62 | var quad:Quad = new Quad(3, 3, 0x000000); 63 | quad.alpha = 0.50; 64 | return quad; 65 | }; 66 | 67 | playballFont = new FontDescription("MyFont"); 68 | playballFont.fontLookup = FontLookup.EMBEDDED_CFF; 69 | 70 | this.initializeGlobals(); 71 | this.initializeStyleProviders(); 72 | } 73 | 74 | private function initializeGlobals():void 75 | { 76 | FeathersControl.defaultTextRendererFactory = function ():ITextRenderer 77 | { 78 | var renderer:TextFieldTextRenderer = new TextFieldTextRenderer()(); 79 | renderer.wordWrap = true; 80 | renderer.isHTML = true; 81 | return renderer; 82 | } 83 | 84 | FeathersControl.defaultTextEditorFactory = function ():ITextEditor 85 | { 86 | return new StageTextTextEditor(); 87 | } 88 | } 89 | 90 | private function initializeStyleProviders():void 91 | { 92 | this.getStyleProviderForClass(Button).setFunctionForStyleName("alert-button", setAlertButtonStyles); 93 | this.getStyleProviderForClass(Button).setFunctionForStyleName("horizontal-button", this.setHorizontalButtonStyles); 94 | this.getStyleProviderForClass(Button).setFunctionForStyleName("vertical-button", this.setVerticalButtonStyles); 95 | this.getStyleProviderForClass(Button).setFunctionForStyleName("back-button", this.setBackButtonStyles); 96 | this.getStyleProviderForClass(Button).setFunctionForStyleName("menu-button", this.setMenuButtonStyles); 97 | this.getStyleProviderForClass(Button).setFunctionForStyleName("header-button", this.setHeaderButtonStyles); 98 | this.getStyleProviderForClass(Button).setFunctionForStyleName("rounded-button", this.setRoundedButtonStyles); 99 | this.getStyleProviderForClass(Label).setFunctionForStyleName("big-label", this.setBigLabelStyles); 100 | this.getStyleProviderForClass(Label).setFunctionForStyleName("date-label", this.setDateLabelStyles); 101 | this.getStyleProviderForClass(DefaultListItemRenderer).setFunctionForStyleName("drawer-itemrenderer", this.setDrawerItemRendererStyles); 102 | this.getStyleProviderForClass(ToggleButton).setFunctionForStyleName("custom-tab", setTabStyles); 103 | 104 | 105 | this.getStyleProviderForClass(Alert).defaultStyleFunction = this.setAlertStyles; 106 | this.getStyleProviderForClass(DefaultListItemRenderer).defaultStyleFunction = this.setItemRendererStyles; 107 | this.getStyleProviderForClass(Header).defaultStyleFunction = this.setHeaderStyles; 108 | this.getStyleProviderForClass(Label).defaultStyleFunction = this.setLabelStyles; 109 | this.getStyleProviderForClass(List).defaultStyleFunction = this.setListStyles; 110 | this.getStyleProviderForClass(PanelScreen).defaultStyleFunction = this.setPanelScreenStyles; 111 | this.getStyleProviderForClass(TabBar).defaultStyleFunction = this.setTabBarStyles; 112 | this.getStyleProviderForClass(TextInput).defaultStyleFunction = this.setTextInputStyles; 113 | } 114 | 115 | //------------------------- 116 | // Alert 117 | //------------------------- 118 | 119 | private function setAlertStyles(alert:Alert):void 120 | { 121 | alert.backgroundSkin = new Quad(3, 3, 0xFFFFFF); 122 | alert.maxWidth = 280; 123 | alert.minHeight = 50; 124 | alert.padding = 10; 125 | 126 | alert.headerProperties.paddingLeft = 10; 127 | alert.headerProperties.gap = 10; 128 | alert.headerProperties.titleAlign = HorizontalAlign.LEFT; 129 | alert.fontStyles = new TextFormat("_sans", 14, 0x000000, "left"); 130 | alert.fontStyles.leading = 7; 131 | 132 | alert.buttonGroupFactory = function ():ButtonGroup 133 | { 134 | var group:ButtonGroup = new ButtonGroup(); 135 | group.customButtonStyleName = "alert-button"; 136 | group.direction = Direction.HORIZONTAL; 137 | group.gap = 10; 138 | group.padding = 10; 139 | return group; 140 | } 141 | } 142 | 143 | //------------------------- 144 | // Button 145 | //------------------------- 146 | 147 | private function setAlertButtonStyles(button:Button):void 148 | { 149 | button.defaultSkin = RoundedRect.createRoundedRect(0x1565C0); 150 | button.downSkin = RoundedRect.createRoundedRect(0x0D47A1); 151 | button.gap = 15; 152 | button.height = 40; 153 | button.width = 250; 154 | button.fontStyles = new TextFormat("_sans", 16, 0xFFFFFF); 155 | } 156 | 157 | private function setHeaderButtonStyles(button:Button):void 158 | { 159 | button.height = button.width = 45; 160 | 161 | var quad:Quad = new Quad(45, 45, 0xFFFFFF); 162 | quad.alpha = .3; 163 | 164 | button.downSkin = quad; 165 | } 166 | 167 | private function setBackButtonStyles(button:Button):void 168 | { 169 | button.height = button.width = 45; 170 | 171 | var backButtonIcon:ImageLoader = new ImageLoader(); 172 | backButtonIcon.source = "assets/icons/back.png"; 173 | backButtonIcon.height = backButtonIcon.width = 25; 174 | 175 | button.defaultIcon = backButtonIcon; 176 | 177 | var quad:Quad = new Quad(45, 45, 0xFFFFFF); 178 | quad.alpha = .3; 179 | 180 | button.downSkin = quad; 181 | } 182 | 183 | private function setMenuButtonStyles(button:Button):void 184 | { 185 | button.height = button.width = 45; 186 | 187 | var menuButtonIcon:ImageLoader = new ImageLoader(); 188 | menuButtonIcon.source = "assets/icons/menu.png"; 189 | menuButtonIcon.height = menuButtonIcon.width = 25; 190 | 191 | button.defaultIcon = menuButtonIcon; 192 | 193 | var quad:Quad = new Quad(45, 45, 0xFFFFFF); 194 | quad.alpha = .3; 195 | 196 | button.downSkin = quad; 197 | } 198 | 199 | private function setHorizontalButtonStyles(button:Button):void 200 | { 201 | button.iconPosition = RelativePosition.LEFT; 202 | button.horizontalAlign = HorizontalAlign.LEFT; 203 | button.verticalAlign = VerticalAlign.MIDDLE; 204 | button.gap = 10; 205 | button.fontStyles = new TextFormat("_sans", 14, 0x000000, "left"); 206 | button.fontStyles.leading = 5; 207 | button.defaultLabelProperties.wordWrap = true; 208 | } 209 | 210 | private function setVerticalButtonStyles(button:Button):void 211 | { 212 | button.iconPosition = RelativePosition.TOP; 213 | button.horizontalAlign = HorizontalAlign.CENTER; 214 | button.paddingLeft = 15; 215 | button.fontStyles = new TextFormat("_sans", 16, 0x000000, "center"); 216 | } 217 | 218 | private function setRoundedButtonStyles(button:Button):void 219 | { 220 | button.defaultSkin = RoundedRect.createRoundedRect(0x1565C0); 221 | button.downSkin = RoundedRect.createRoundedRect(0x0D47A1); 222 | button.horizontalAlign = HorizontalAlign.LEFT; 223 | button.paddingLeft = 15; 224 | button.gap = 15; 225 | button.height = 50; 226 | button.minWidth = 100; 227 | button.fontStyles = new TextFormat("_sans", 16, 0xFFFFFF, "left") 228 | } 229 | 230 | //------------------------- 231 | // Header 232 | //------------------------- 233 | 234 | private function setHeaderStyles(header:Header):void 235 | { 236 | header.fontStyles = new TextFormat("_sans", 16, 0xFFFFFF, "left"); 237 | header.gap = 5; 238 | header.paddingLeft = header.paddingRight = 2; 239 | header.titleAlign = HorizontalAlign.LEFT; 240 | 241 | var skin:Quad = new Quad(3, 50); 242 | skin.setVertexColor(0, 0x0277BD); 243 | skin.setVertexColor(1, 0x0277BD); 244 | skin.setVertexColor(2, 0x01579B); 245 | skin.setVertexColor(3, 0x01579B); 246 | header.backgroundSkin = skin; 247 | } 248 | 249 | //------------------------- 250 | // Label 251 | //------------------------- 252 | 253 | private function setLabelStyles(label:Label):void 254 | { 255 | label.fontStyles = new TextFormat("_sans", 14, 0x000000, "left"); 256 | label.fontStyles.leading = 7; 257 | } 258 | 259 | private function setBigLabelStyles(label:Label):void 260 | { 261 | label.fontStyles = new TextFormat("MyFont", 60, 0xFFFFFF); 262 | } 263 | 264 | private function setDateLabelStyles(label:Label):void 265 | { 266 | label.width = 50; 267 | label.height = 25; 268 | label.paddingTop = 3; 269 | label.fontStyles = new TextFormat("_sans", 14, 0xFFFFFF); 270 | } 271 | 272 | //------------------------- 273 | // List 274 | //------------------------- 275 | 276 | private function setListStyles(list:List):void 277 | { 278 | list.backgroundSkin = new Quad(3, 3, 0xFFFFFF); 279 | list.hasElasticEdges = false; 280 | } 281 | 282 | private function setItemRendererStyles(renderer:BaseDefaultItemRenderer):void 283 | { 284 | renderer.defaultSelectedSkin = new Quad(3, 3, 0xD50000); 285 | renderer.downSkin = new Quad(3, 3, 0xD50000); 286 | renderer.horizontalAlign = HorizontalAlign.LEFT; 287 | renderer.paddingLeft = 10; 288 | renderer.paddingRight = 10; 289 | renderer.paddingTop = 5; 290 | renderer.paddingBottom = 5; 291 | renderer.gap = 10; 292 | renderer.minHeight = 50; 293 | renderer.accessoryGap = Number.POSITIVE_INFINITY; 294 | renderer.iconPosition = RelativePosition.LEFT; 295 | renderer.accessoryPosition = RelativePosition.RIGHT; 296 | renderer.isQuickHitAreaEnabled = true; 297 | 298 | var blackFormat:TextFormat = new TextFormat("_sans", 14, 0x000000, "left", "center"); 299 | blackFormat.leading = 7; 300 | 301 | var whiteFormat:TextFormat = new TextFormat("_sans", 14, 0xFFFFFF, "left", "center"); 302 | whiteFormat.leading = 7; 303 | 304 | renderer.setFontStylesForState(ButtonState.UP, blackFormat); 305 | renderer.setFontStylesForState(ButtonState.UP_AND_SELECTED, whiteFormat); 306 | renderer.setFontStylesForState(ButtonState.DOWN, whiteFormat); 307 | renderer.setFontStylesForState(ButtonState.DOWN_AND_SELECTED, whiteFormat); 308 | renderer.setFontStylesForState(ButtonState.HOVER, blackFormat); 309 | renderer.setFontStylesForState(ButtonState.HOVER_AND_SELECTED, whiteFormat); 310 | 311 | renderer.setAccessoryLabelFontStylesForState(ButtonState.UP, blackFormat); 312 | renderer.setAccessoryLabelFontStylesForState(ButtonState.UP_AND_SELECTED, whiteFormat); 313 | renderer.setAccessoryLabelFontStylesForState(ButtonState.DOWN, whiteFormat); 314 | renderer.setAccessoryLabelFontStylesForState(ButtonState.DOWN_AND_SELECTED, whiteFormat); 315 | renderer.setAccessoryLabelFontStylesForState(ButtonState.HOVER, blackFormat); 316 | renderer.setAccessoryLabelFontStylesForState(ButtonState.HOVER_AND_SELECTED, whiteFormat); 317 | } 318 | 319 | private function setDrawerItemRendererStyles(renderer:BaseDefaultItemRenderer):void 320 | { 321 | renderer.defaultSelectedSkin = new Quad(3, 3, 0x01579B); 322 | renderer.downSkin = new Quad(3, 3, 0x01579B); 323 | renderer.horizontalAlign = HorizontalAlign.LEFT; 324 | renderer.verticalAlign = VerticalAlign.MIDDLE; 325 | renderer.paddingLeft = 10; 326 | renderer.paddingRight = 10; 327 | renderer.paddingTop = 5; 328 | renderer.paddingBottom = 5; 329 | renderer.gap = 10; 330 | renderer.minHeight = 45; 331 | renderer.accessoryGap = Number.POSITIVE_INFINITY; 332 | renderer.iconPosition = RelativePosition.LEFT; 333 | renderer.accessoryPosition = RelativePosition.RIGHT; 334 | renderer.isQuickHitAreaEnabled = true; 335 | renderer.fontStyles = new TextFormat("_sans", 14, 0xFFFFFF, "left"); 336 | } 337 | 338 | //------------------------- 339 | // PanelScreen 340 | //------------------------- 341 | 342 | private function setPanelScreenStyles(screen:PanelScreen):void 343 | { 344 | screen.backgroundSkin = new Quad(3, 3, 0xD50000); 345 | screen.hasElasticEdges = false; 346 | } 347 | 348 | //------------------------- 349 | // TabBar 350 | //------------------------- 351 | 352 | private function setTabBarStyles(tabBar:TabBar):void 353 | { 354 | tabBar.customTabStyleName = "custom-tab"; 355 | } 356 | 357 | private function setTabStyles(tab:ToggleButton):void 358 | { 359 | var skin1:Quad = new Quad(3, 50); 360 | skin1.setVertexColor(0, 0x021F34); 361 | skin1.setVertexColor(1, 0x021F34); 362 | skin1.setVertexColor(2, 0x08467C); 363 | skin1.setVertexColor(3, 0x08467C); 364 | 365 | var skin2:Quad = new Quad(3, 50); 366 | skin2.setVertexColor(0, 0x0277BD); 367 | skin2.setVertexColor(1, 0x0277BD); 368 | skin2.setVertexColor(2, 0x01579B); 369 | skin2.setVertexColor(3, 0x01579B); 370 | 371 | tab.defaultSkin = skin2; 372 | tab.downSkin = skin1; 373 | tab.selectedDownSkin = skin1; 374 | tab.defaultSelectedSkin = skin1; 375 | } 376 | 377 | //------------------------- 378 | // TextInput 379 | //--------- 380 | 381 | private function setTextInputStyles(textinput:TextInput):void 382 | { 383 | textinput.padding = 10; 384 | textinput.backgroundSkin = RoundedRect.createRoundedRect(0xD50000); 385 | textinput.fontStyles = new TextFormat("_sans", 16, 0xFFFFFF, "left", "top"); 386 | textinput.promptFontStyles = new TextFormat("_sans", 16, 0xCCCCCC, "left", "top"); 387 | } 388 | 389 | } 390 | } -------------------------------------------------------------------------------- /src/Main.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import businessScreens.BusinessDetailsScreen; 4 | import businessScreens.DirectionsScreen; 5 | import businessScreens.FinderScreen; 6 | import businessScreens.ResultsScreen; 7 | 8 | import chatScreens.ChatScreen; 9 | import chatScreens.ChatroomsScreen; 10 | 11 | import feathers.controls.Drawers; 12 | import feathers.controls.ImageLoader; 13 | import feathers.controls.Label; 14 | import feathers.controls.LayoutGroup; 15 | import feathers.controls.List; 16 | import feathers.controls.StackScreenNavigator; 17 | import feathers.controls.StackScreenNavigatorItem; 18 | import feathers.controls.renderers.DefaultListItemRenderer; 19 | import feathers.controls.text.TextFieldTextRenderer; 20 | import feathers.core.ITextRenderer; 21 | import feathers.data.ListCollection; 22 | import feathers.layout.AnchorLayout; 23 | import feathers.layout.AnchorLayoutData; 24 | import feathers.layout.VerticalLayout; 25 | import feathers.layout.VerticalLayoutData; 26 | import feathers.motion.Cover; 27 | import feathers.motion.Fade; 28 | import feathers.motion.Reveal; 29 | 30 | import flash.text.TextFormat; 31 | 32 | import galleryScreens.GalleryScreen; 33 | import galleryScreens.ImageDetailsScreen; 34 | import galleryScreens.UploadImageScreen; 35 | 36 | import newsScreens.HomeScreen; 37 | import newsScreens.NewsDetailsScreen; 38 | 39 | import screens.LoginScreen; 40 | import screens.SettingsScreen; 41 | 42 | import starling.display.Canvas; 43 | import starling.display.Quad; 44 | import starling.display.Sprite; 45 | import starling.events.Event; 46 | 47 | import utils.NavigatorData; 48 | import utils.ProfileManager; 49 | 50 | public class Main extends Sprite 51 | { 52 | public function Main() 53 | { 54 | this.addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); 55 | } 56 | 57 | private var drawers:Drawers; 58 | private var myNavigator:StackScreenNavigator; 59 | private var list:List; 60 | private var NAVIGATOR_DATA:NavigatorData; 61 | 62 | public static var profile:Object; 63 | public static var avatar:ImageLoader; 64 | public static var displayName:Label; 65 | 66 | public static const OPEN_MENU:String = "openMenu"; 67 | 68 | private static const HOME_SCREEN:String = "homeScreen"; 69 | private static const NEWS_DETAILS_SCREEN:String = "newsDetailsScreen"; 70 | private static const LOGIN_SCREEN:String = "loginScreen"; 71 | private static const FINDER_SCREEN:String = "finderScreen"; 72 | private static const RESULTS_SCREEN:String = "resultsScreen"; 73 | private static const BUSINESS_DETAILS_SCREEN:String = "businessDetailsScreen"; 74 | private static const DIRECTIONS_SCREEN:String = "directionsScreen"; 75 | private static const CHATROOMS_SCREEN:String = "chatroomsScreen"; 76 | private static const CHAT_SCREEN:String = "chatScreen"; 77 | private static const GALLERY_SCREEN:String = "galleryScreen"; 78 | private static const IMAGE_DETAILS_SCREEN:String = "imageDetailsScreen"; 79 | private static const UPLOAD_SCREEN:String = "uploadScreen"; 80 | private static const SETTINGS_SCREEN:String = "settingsScreen"; 81 | 82 | protected function addedToStageHandler(event:Event):void 83 | { 84 | this.removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); 85 | 86 | this.NAVIGATOR_DATA = new NavigatorData(); 87 | 88 | new CustomTheme(); 89 | 90 | createDrawer(); 91 | 92 | //We create the StackScreenNavigator and add the screens 93 | 94 | myNavigator = new StackScreenNavigator(); 95 | myNavigator.pushTransition = Fade.createFadeInTransition(); 96 | myNavigator.popTransition = Fade.createFadeOutTransition(); 97 | drawers.content = myNavigator; 98 | 99 | var homeScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(HomeScreen); 100 | homeScreenItem.properties.data = NAVIGATOR_DATA; 101 | homeScreenItem.setScreenIDForPushEvent(HomeScreen.GO_NEWS_DETAILS, NEWS_DETAILS_SCREEN); 102 | myNavigator.addScreen(HOME_SCREEN, homeScreenItem); 103 | 104 | var newsDetailsScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(NewsDetailsScreen); 105 | newsDetailsScreenItem.properties.data = NAVIGATOR_DATA; 106 | newsDetailsScreenItem.addPopEvent(Event.COMPLETE); 107 | myNavigator.addScreen(NEWS_DETAILS_SCREEN, newsDetailsScreenItem); 108 | 109 | var loginScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(LoginScreen); 110 | loginScreenItem.pushTransition = Cover.createCoverUpTransition(); 111 | loginScreenItem.popTransition = Reveal.createRevealDownTransition(); 112 | loginScreenItem.addPopEvent(Event.COMPLETE); 113 | myNavigator.addScreen(LOGIN_SCREEN, loginScreenItem); 114 | 115 | var finderScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(FinderScreen); 116 | finderScreenItem.properties.data = NAVIGATOR_DATA; 117 | finderScreenItem.addPopEvent(Event.COMPLETE); 118 | finderScreenItem.setScreenIDForPushEvent(FinderScreen.GO_RESULTS, RESULTS_SCREEN); 119 | myNavigator.addScreen(FINDER_SCREEN, finderScreenItem); 120 | 121 | var resultsScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(ResultsScreen); 122 | resultsScreenItem.pushTransition = Fade.createFadeInTransition(0); 123 | resultsScreenItem.properties.data = NAVIGATOR_DATA; 124 | resultsScreenItem.addPopEvent(Event.COMPLETE); 125 | resultsScreenItem.setScreenIDForPushEvent(ResultsScreen.GO_DETAILS, BUSINESS_DETAILS_SCREEN); 126 | myNavigator.addScreen(RESULTS_SCREEN, resultsScreenItem); 127 | 128 | var businessDetailsScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(BusinessDetailsScreen); 129 | businessDetailsScreenItem.properties.data = NAVIGATOR_DATA; 130 | businessDetailsScreenItem.addPopEvent(Event.COMPLETE); 131 | businessDetailsScreenItem.setScreenIDForPushEvent(BusinessDetailsScreen.GO_DIRECTIONS, DIRECTIONS_SCREEN); 132 | myNavigator.addScreen(BUSINESS_DETAILS_SCREEN, businessDetailsScreenItem); 133 | 134 | var directionsScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(DirectionsScreen); 135 | directionsScreenItem.pushTransition = Cover.createCoverUpTransition(); 136 | directionsScreenItem.popTransition = Reveal.createRevealDownTransition(); 137 | directionsScreenItem.properties.data = NAVIGATOR_DATA; 138 | directionsScreenItem.addPopEvent(Event.COMPLETE); 139 | myNavigator.addScreen(DIRECTIONS_SCREEN, directionsScreenItem); 140 | 141 | var chatroomsScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(ChatroomsScreen); 142 | chatroomsScreenItem.properties.data = NAVIGATOR_DATA; 143 | chatroomsScreenItem.addPopEvent(Event.COMPLETE); 144 | chatroomsScreenItem.setScreenIDForPushEvent(ChatroomsScreen.GO_CHAT, CHAT_SCREEN); 145 | chatroomsScreenItem.setScreenIDForPushEvent(ChatroomsScreen.GO_LOGIN, LOGIN_SCREEN); 146 | myNavigator.addScreen(CHATROOMS_SCREEN, chatroomsScreenItem); 147 | 148 | var chatScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(ChatScreen); 149 | chatScreenItem.properties.data = NAVIGATOR_DATA; 150 | chatScreenItem.addPopEvent(Event.COMPLETE); 151 | myNavigator.addScreen(CHAT_SCREEN, chatScreenItem); 152 | 153 | var galleryScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(GalleryScreen); 154 | galleryScreenItem.properties.data = NAVIGATOR_DATA; 155 | galleryScreenItem.setScreenIDForPushEvent(GalleryScreen.GO_IMAGE_DETAILS, IMAGE_DETAILS_SCREEN); 156 | galleryScreenItem.setScreenIDForPushEvent(GalleryScreen.GO_LOGIN, LOGIN_SCREEN); 157 | galleryScreenItem.setScreenIDForPushEvent(GalleryScreen.GO_UPLOAD, UPLOAD_SCREEN); 158 | myNavigator.addScreen(GALLERY_SCREEN, galleryScreenItem); 159 | 160 | var imageDetailsScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(ImageDetailsScreen); 161 | imageDetailsScreenItem.properties.data = NAVIGATOR_DATA; 162 | imageDetailsScreenItem.setScreenIDForPushEvent(ImageDetailsScreen.GO_LOGIN, LOGIN_SCREEN); 163 | imageDetailsScreenItem.addPopEvent(Event.COMPLETE); 164 | myNavigator.addScreen(IMAGE_DETAILS_SCREEN, imageDetailsScreenItem); 165 | 166 | var uploadImageScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(UploadImageScreen); 167 | uploadImageScreenItem.properties.data = NAVIGATOR_DATA; 168 | uploadImageScreenItem.addPopEvent(Event.COMPLETE); 169 | myNavigator.addScreen(UPLOAD_SCREEN, uploadImageScreenItem); 170 | 171 | var settingsScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(SettingsScreen); 172 | settingsScreenItem.addPopEvent(Event.COMPLETE); 173 | settingsScreenItem.setScreenIDForPushEvent(SettingsScreen.GO_LOGIN, LOGIN_SCREEN); 174 | myNavigator.addScreen(SETTINGS_SCREEN, settingsScreenItem); 175 | 176 | myNavigator.rootScreenID = HOME_SCREEN; 177 | 178 | } 179 | 180 | private function createDrawer():void 181 | { 182 | var leftGroup:LayoutGroup = new LayoutGroup(); 183 | leftGroup.layout = new VerticalLayout(); 184 | leftGroup.width = 200; 185 | 186 | var topGroupSkin:Quad = new Quad(3, 50); 187 | topGroupSkin.setVertexColor(0, 0x01579B); 188 | topGroupSkin.setVertexColor(1, 0x01579B); 189 | topGroupSkin.setVertexColor(2, 0x0277BD); 190 | topGroupSkin.setVertexColor(3, 0x0277BD); 191 | 192 | var topGroup:LayoutGroup = new LayoutGroup(); 193 | topGroup.backgroundSkin = topGroupSkin; 194 | topGroup.layout = new AnchorLayout(); 195 | topGroup.layoutData = new VerticalLayoutData(100, NaN); 196 | topGroup.height = 50; 197 | leftGroup.addChild(topGroup); 198 | 199 | var avatarMask:Canvas = new Canvas(); 200 | avatarMask.drawCircle(15, 15, 15); 201 | 202 | avatar = new ImageLoader(); 203 | avatar.width = avatar.height = 30; 204 | avatar.mask = avatarMask; 205 | avatar.layoutData = new AnchorLayoutData(NaN, NaN, NaN, 10, NaN, 0); 206 | topGroup.addChild(avatar); 207 | 208 | displayName = new Label(); 209 | displayName.styleProvider = null; 210 | displayName.layoutData = new AnchorLayoutData(NaN, 10, NaN, 50, NaN, 0); 211 | displayName.textRendererFactory = function ():ITextRenderer 212 | { 213 | var renderer:TextFieldTextRenderer = new TextFieldTextRenderer(); 214 | 215 | var format:TextFormat = new TextFormat("_sans", 14, 0xFFFFFF); 216 | format.leading = 3; 217 | renderer.isHTML = true; 218 | renderer.textFormat = format; 219 | return renderer; 220 | }; 221 | topGroup.addChild(displayName); 222 | 223 | list = new List(); 224 | list.styleProvider = null; 225 | list.hasElasticEdges = false; 226 | list.itemRendererFactory = function ():DefaultListItemRenderer 227 | { 228 | var renderer:DefaultListItemRenderer = new DefaultListItemRenderer(); 229 | renderer.styleNameList.add("drawer-itemrenderer"); 230 | renderer.iconSourceField = "icon"; 231 | 232 | renderer.iconLoaderFactory = function ():ImageLoader 233 | { 234 | var loader:ImageLoader = new ImageLoader(); 235 | loader.width = loader.height = 25; 236 | return loader; 237 | }; 238 | 239 | return renderer; 240 | }; 241 | list.dataProvider = new ListCollection( 242 | [ 243 | {screen: HOME_SCREEN, label: "Home", icon: "assets/icons/home.png"}, 244 | {screen: FINDER_SCREEN, label: "Find Pizza", icon: "assets/icons/search.png"}, 245 | {screen: CHATROOMS_SCREEN, label: "Chat Rooms", icon: "assets/icons/chat.png"}, 246 | {screen: GALLERY_SCREEN, label: "Gallery", icon: "assets/icons/gallery.png"}, 247 | {screen: SETTINGS_SCREEN, label: "Settings", icon: "assets/icons/settings.png"} 248 | ]); 249 | list.backgroundSkin = new Quad(3, 3, 0x37474F); 250 | list.selectedIndex = 0; 251 | list.layoutData = new VerticalLayoutData(100, 100); 252 | list.addEventListener(Event.CHANGE, changeHandler); 253 | leftGroup.addChild(list); 254 | 255 | var overlaySkin:Quad = new Quad(3, 3, 0x000000); 256 | overlaySkin.alpha = 0.50; 257 | 258 | drawers = new Drawers(); 259 | drawers.overlaySkin = overlaySkin; 260 | drawers.leftDrawerToggleEventType = OPEN_MENU; 261 | drawers.leftDrawer = leftGroup; 262 | this.addChild(drawers); 263 | 264 | //Once our drawer and its content is created we load the logged in user profile 265 | 266 | profile = ProfileManager.loadProfile(); 267 | 268 | if (profile.photoUrl != null) { 269 | avatar.source = profile.photoUrl; 270 | } else { 271 | avatar.source = "assets/icons/account_circle.png"; 272 | } 273 | 274 | if (profile.displayName != null) { 275 | displayName.text = "Welcome,\n" + profile.displayName + ""; 276 | } else { 277 | displayName.text = "Welcome Guest"; 278 | } 279 | } 280 | 281 | 282 | private function changeHandler(event:Event):void 283 | { 284 | drawers.toggleLeftDrawer(); 285 | var screen:String = list.selectedItem.screen; 286 | myNavigator.pushScreen(screen); 287 | } 288 | 289 | } 290 | } -------------------------------------------------------------------------------- /src/PizzaApp.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import feathers.utils.ScreenDensityScaleFactorManager; 4 | 5 | import flash.data.SQLConnection; 6 | import flash.display.Sprite; 7 | import flash.display.StageAlign; 8 | import flash.display.StageScaleMode; 9 | import flash.display3D.Context3DRenderMode; 10 | import flash.events.Event; 11 | 12 | import starling.core.Starling; 13 | 14 | [SWF(width="320", height="480", frameRate="60", backgroundColor="#D50000")] 15 | public class PizzaApp extends Sprite 16 | { 17 | public static var conn:SQLConnection; 18 | private var myStarling:Starling; 19 | private var myScaler:ScreenDensityScaleFactorManager; 20 | 21 | public function PizzaApp() 22 | { 23 | if (this.stage) { 24 | 25 | this.stage.scaleMode = StageScaleMode.NO_SCALE; 26 | this.stage.align = StageAlign.TOP_LEFT; 27 | } 28 | this.mouseEnabled = this.mouseChildren = false; 29 | this.loaderInfo.addEventListener(Event.COMPLETE, loaderInfo_completeHandler); 30 | } 31 | 32 | private function loaderInfo_completeHandler(event:Event):void 33 | { 34 | Starling.multitouchEnabled = true; 35 | 36 | this.myStarling = new Starling(Main, this.stage, null, null, Context3DRenderMode.AUTO, "auto"); 37 | this.myScaler = new ScreenDensityScaleFactorManager(this.myStarling); 38 | this.myStarling.enableErrorChecking = false; 39 | this.myStarling.skipUnchangedFrames = true; 40 | //this.myStarling.showStats = true; 41 | this.myStarling.start(); 42 | 43 | this.stage.addEventListener(Event.DEACTIVATE, stage_deactivateHandler, false, 0, true); 44 | } 45 | 46 | private function stage_deactivateHandler(event:Event):void 47 | { 48 | this.myStarling.stop(); 49 | this.stage.addEventListener(Event.ACTIVATE, stage_activateHandler, false, 0, true); 50 | } 51 | 52 | private function stage_activateHandler(event:Event):void 53 | { 54 | this.stage.removeEventListener(Event.ACTIVATE, stage_activateHandler); 55 | this.myStarling.start(); 56 | } 57 | 58 | } 59 | } -------------------------------------------------------------------------------- /src/assets/font.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/font.otf -------------------------------------------------------------------------------- /src/assets/fonts/playball.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/fonts/playball.ttf -------------------------------------------------------------------------------- /src/assets/icons/account_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/account_circle.png -------------------------------------------------------------------------------- /src/assets/icons/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/back.png -------------------------------------------------------------------------------- /src/assets/icons/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/camera.png -------------------------------------------------------------------------------- /src/assets/icons/camera_roll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/camera_roll.png -------------------------------------------------------------------------------- /src/assets/icons/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/cancel.png -------------------------------------------------------------------------------- /src/assets/icons/chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/chat.png -------------------------------------------------------------------------------- /src/assets/icons/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/check.png -------------------------------------------------------------------------------- /src/assets/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/close.png -------------------------------------------------------------------------------- /src/assets/icons/date.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/date.png -------------------------------------------------------------------------------- /src/assets/icons/directions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/directions.png -------------------------------------------------------------------------------- /src/assets/icons/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/download.png -------------------------------------------------------------------------------- /src/assets/icons/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/facebook.png -------------------------------------------------------------------------------- /src/assets/icons/gallery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/gallery.png -------------------------------------------------------------------------------- /src/assets/icons/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/google.png -------------------------------------------------------------------------------- /src/assets/icons/gps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/gps.png -------------------------------------------------------------------------------- /src/assets/icons/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/home.png -------------------------------------------------------------------------------- /src/assets/icons/home_rounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/home_rounded.png -------------------------------------------------------------------------------- /src/assets/icons/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/info.png -------------------------------------------------------------------------------- /src/assets/icons/insert_comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/insert_comment.png -------------------------------------------------------------------------------- /src/assets/icons/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/loading.png -------------------------------------------------------------------------------- /src/assets/icons/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/logout.png -------------------------------------------------------------------------------- /src/assets/icons/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/menu.png -------------------------------------------------------------------------------- /src/assets/icons/open_browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/open_browser.png -------------------------------------------------------------------------------- /src/assets/icons/overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/overflow.png -------------------------------------------------------------------------------- /src/assets/icons/person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/person.png -------------------------------------------------------------------------------- /src/assets/icons/person_pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/person_pin.png -------------------------------------------------------------------------------- /src/assets/icons/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/phone.png -------------------------------------------------------------------------------- /src/assets/icons/pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/pin.png -------------------------------------------------------------------------------- /src/assets/icons/radio_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/radio_checked.png -------------------------------------------------------------------------------- /src/assets/icons/radio_unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/radio_unchecked.png -------------------------------------------------------------------------------- /src/assets/icons/rounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/rounded.png -------------------------------------------------------------------------------- /src/assets/icons/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/save.png -------------------------------------------------------------------------------- /src/assets/icons/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/search.png -------------------------------------------------------------------------------- /src/assets/icons/send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/send.png -------------------------------------------------------------------------------- /src/assets/icons/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/settings.png -------------------------------------------------------------------------------- /src/assets/icons/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/star.png -------------------------------------------------------------------------------- /src/assets/icons/thumb_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/thumb_down.png -------------------------------------------------------------------------------- /src/assets/icons/thumb_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/thumb_up.png -------------------------------------------------------------------------------- /src/assets/icons/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/twitter.png -------------------------------------------------------------------------------- /src/assets/icons/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/upload.png -------------------------------------------------------------------------------- /src/assets/icons/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/icons/warning.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-100.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-1024.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-114.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-120.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-144.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-152.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-167.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-180.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-192.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-29.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-36.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-40.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-48.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-50.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-512.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-57.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-58.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-60.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-72.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-75.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-76.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-80.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-87.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/launchicons/icon-96.png -------------------------------------------------------------------------------- /src/assets/rooms/room1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/rooms/room1.png -------------------------------------------------------------------------------- /src/assets/rooms/room2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/rooms/room2.png -------------------------------------------------------------------------------- /src/assets/rooms/room3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/rooms/room3.png -------------------------------------------------------------------------------- /src/assets/yelp/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/yelp/0.png -------------------------------------------------------------------------------- /src/assets/yelp/1.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/yelp/1.5.png -------------------------------------------------------------------------------- /src/assets/yelp/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/yelp/1.png -------------------------------------------------------------------------------- /src/assets/yelp/2.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/yelp/2.5.png -------------------------------------------------------------------------------- /src/assets/yelp/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/yelp/2.png -------------------------------------------------------------------------------- /src/assets/yelp/3.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/yelp/3.5.png -------------------------------------------------------------------------------- /src/assets/yelp/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/yelp/3.png -------------------------------------------------------------------------------- /src/assets/yelp/4.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/yelp/4.5.png -------------------------------------------------------------------------------- /src/assets/yelp/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/yelp/4.png -------------------------------------------------------------------------------- /src/assets/yelp/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/yelp/5.png -------------------------------------------------------------------------------- /src/assets/yelp/yelp_logo_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/assets/yelp/yelp_logo_large.png -------------------------------------------------------------------------------- /src/businessScreens/BusinessDetailsScreen.as: -------------------------------------------------------------------------------- 1 | package businessScreens 2 | { 3 | import feathers.controls.Button; 4 | import feathers.controls.ImageLoader; 5 | import feathers.controls.Label; 6 | import feathers.controls.LayoutGroup; 7 | import feathers.controls.PanelScreen; 8 | import feathers.controls.ScrollContainer; 9 | import feathers.events.FeathersEventType; 10 | import feathers.layout.AnchorLayout; 11 | import feathers.layout.AnchorLayoutData; 12 | import feathers.layout.HorizontalAlign; 13 | import feathers.layout.VerticalLayout; 14 | import feathers.layout.VerticalLayoutData; 15 | 16 | import flash.events.Event; 17 | import flash.events.IOErrorEvent; 18 | import flash.net.URLLoader; 19 | import flash.net.URLRequest; 20 | import flash.net.URLRequestHeader; 21 | import flash.net.navigateToURL; 22 | 23 | import starling.display.Canvas; 24 | import starling.display.DisplayObject; 25 | import starling.events.Event; 26 | import starling.text.TextFormat; 27 | import starling.utils.ScaleMode; 28 | 29 | import utils.NavigatorData; 30 | import utils.RoundedRect; 31 | 32 | public class BusinessDetailsScreen extends PanelScreen 33 | { 34 | public static const GO_DIRECTIONS:String = "goDirections"; 35 | 36 | private var mainGroup:ScrollContainer; 37 | private var reviewsGroup:LayoutGroup; 38 | 39 | protected var _data:NavigatorData; 40 | 41 | public function get data():NavigatorData 42 | { 43 | return this._data; 44 | } 45 | 46 | public function set data(value:NavigatorData):void 47 | { 48 | this._data = value; 49 | } 50 | 51 | override protected function initialize():void 52 | { 53 | super.initialize(); 54 | 55 | this.layout = new AnchorLayout(); 56 | this.backButtonHandler = goBack; 57 | 58 | this.title = "Business Details"; 59 | 60 | var backButton:Button = new Button(); 61 | backButton.styleNameList.add("back-button"); 62 | backButton.addEventListener(starling.events.Event.TRIGGERED, goBack); 63 | this.headerProperties.leftItems = new [backButton]; 64 | 65 | var browserIcon:ImageLoader = new ImageLoader(); 66 | browserIcon.source = "assets/icons/open_browser.png"; 67 | browserIcon.width = browserIcon.height = 25; 68 | 69 | var browserButton:Button = new Button(); 70 | browserButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 71 | { 72 | navigateToURL(new URLRequest(_data.currentBusiness.url)); 73 | }) 74 | browserButton.styleNameList.add("header-button"); 75 | browserButton.defaultIcon = browserIcon; 76 | this.headerProperties.rightItems = new [browserButton]; 77 | 78 | var layoutForMainGroup:VerticalLayout = new VerticalLayout(); 79 | layoutForMainGroup.horizontalAlign = HorizontalAlign.CENTER; 80 | layoutForMainGroup.padding = 10; 81 | layoutForMainGroup.gap = 10; 82 | 83 | mainGroup = new ScrollContainer(); 84 | mainGroup.hasElasticEdges = false; 85 | mainGroup.layout = layoutForMainGroup; 86 | mainGroup.layoutData = new AnchorLayoutData(10, 10, 10, 10, NaN, NaN); 87 | mainGroup.backgroundSkin = RoundedRect.createRoundedRect(0xFFFFFF); 88 | this.addChild(mainGroup); 89 | 90 | this.addEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 91 | } 92 | 93 | private function transitionComplete(event:starling.events.Event):void 94 | { 95 | this.removeEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 96 | 97 | var businessImage:ImageLoader = new ImageLoader(); 98 | businessImage.layoutData = new VerticalLayoutData(100, NaN); 99 | businessImage.minWidth = businessImage.minHeight = 50; 100 | if (_data.currentBusiness.image_url != "") { 101 | businessImage.source = _data.currentBusiness.image_url; 102 | } else { 103 | businessImage.source = "assets/icons/person.png"; 104 | } 105 | 106 | mainGroup.addChild(businessImage); 107 | 108 | var businessName:Label = new Label(); 109 | businessName.layoutData = new VerticalLayoutData(100, NaN); 110 | businessName.text = _data.currentBusiness.name; 111 | businessName.fontStyles = new TextFormat("_sans", 20, 0x000000); 112 | businessName.fontStyles.bold = true; 113 | businessName.wordWrap = true; 114 | mainGroup.addChild(businessName); 115 | 116 | var mainRatingImage:ImageLoader = new ImageLoader(); 117 | mainRatingImage.source = "assets/yelp/" + _data.currentBusiness.rating + ".png"; 118 | mainRatingImage.height = 20; 119 | mainRatingImage.width = 106; 120 | mainGroup.addChild(mainRatingImage); 121 | 122 | var ratingsCount:Label = new Label(); 123 | ratingsCount.text = _data.currentBusiness.review_count + " review(s)"; 124 | mainGroup.addChild(ratingsCount); 125 | 126 | var addressLabel:Label = new Label(); 127 | addressLabel.fontStyles = new TextFormat("_sans", 18, 0x000000); 128 | addressLabel.layoutData = new VerticalLayoutData(100, NaN); 129 | addressLabel.wordWrap = true; 130 | addressLabel.paddingBottom = 10; 131 | addressLabel.text = _data.currentBusiness.location.address1 + "\n" + _data.currentBusiness.location.city + ", " + _data.currentBusiness.location.state; 132 | mainGroup.addChild(addressLabel); 133 | 134 | reviewsGroup = new LayoutGroup(); 135 | mainGroup.addChild(reviewsGroup); 136 | 137 | var spacer1:LayoutGroup = new LayoutGroup(); 138 | spacer1.layoutData = new VerticalLayoutData(100, 100); 139 | mainGroup.addChild(spacer1); 140 | 141 | if (_data.currentBusiness.phone) { 142 | var phoneIcon:ImageLoader = new ImageLoader(); 143 | phoneIcon.source = "assets/icons/phone.png"; 144 | phoneIcon.width = phoneIcon.height = 25; 145 | 146 | var phoneButton:Button = new Button(); 147 | phoneButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 148 | { 149 | navigateToURL(new URLRequest("tel:" + _data.currentBusiness.phone)); 150 | }) 151 | phoneButton.layoutData = new VerticalLayoutData(100, NaN); 152 | phoneButton.styleNameList.add("rounded-button"); 153 | phoneButton.label = "Call: " + _data.currentBusiness.phone; 154 | phoneButton.defaultIcon = phoneIcon; 155 | mainGroup.addChild(phoneButton); 156 | } 157 | 158 | if (_data.currentBusiness.coordinates.latitude) { 159 | var directionsIcon:ImageLoader = new ImageLoader(); 160 | directionsIcon.source = "assets/icons/directions.png"; 161 | directionsIcon.width = directionsIcon.height = 25; 162 | 163 | var directionsButton:Button = new Button(); 164 | directionsButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 165 | { 166 | dispatchEventWith(GO_DIRECTIONS); 167 | }) 168 | directionsButton.layoutData = new VerticalLayoutData(100, NaN); 169 | directionsButton.styleNameList.add("rounded-button"); 170 | directionsButton.label = "Get Directions"; 171 | directionsButton.defaultIcon = directionsIcon; 172 | mainGroup.addChild(directionsButton); 173 | } 174 | 175 | getReviews(); 176 | } 177 | 178 | private function getReviews():void 179 | { 180 | var header:URLRequestHeader = new URLRequestHeader("Authorization", "Bearer " + _data.yelpAccessToken); 181 | 182 | var request:URLRequest = new URLRequest("https://api.yelp.com/v3/businesses/" + _data.currentBusiness.id + "/reviews"); 183 | request.requestHeaders.push(header); 184 | 185 | var loader:URLLoader = new URLLoader(); 186 | loader.addEventListener(flash.events.Event.COMPLETE, yelpResponse); 187 | loader.addEventListener(IOErrorEvent.IO_ERROR, yelpError); 188 | loader.load(request); 189 | } 190 | 191 | private function yelpResponse(event:flash.events.Event):void 192 | { 193 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, yelpResponse); 194 | 195 | var layoutForReviewsGroup:VerticalLayout = new VerticalLayout(); 196 | layoutForReviewsGroup.gap = 10; 197 | 198 | reviewsGroup.layout = layoutForReviewsGroup; 199 | reviewsGroup.layoutData = new VerticalLayoutData(100, NaN); 200 | 201 | var rawData:Object = JSON.parse(event.currentTarget.data); 202 | 203 | var reviewLabel:Label = new Label(); 204 | reviewLabel.layoutData = new VerticalLayoutData(100, NaN); 205 | reviewLabel.text = "Top Reviews"; 206 | reviewLabel.fontStyles = new TextFormat("_sans", 20, 0x000000); 207 | reviewLabel.fontStyles.bold = true; 208 | reviewsGroup.addChild(reviewLabel); 209 | 210 | for each(var item:Object in rawData.reviews) { 211 | reviewsGroup.addChild(createReviewBlock(item)); 212 | } 213 | } 214 | 215 | private function createReviewBlock(review:Object):DisplayObject 216 | { 217 | var group:LayoutGroup = new LayoutGroup(); 218 | group.layout = new AnchorLayout(); 219 | group.layoutData = new VerticalLayoutData(100, NaN); 220 | 221 | var avatar:ImageLoader = new ImageLoader(); 222 | avatar.scaleMode = ScaleMode.NO_BORDER; 223 | 224 | //IF the user doesn't have an avatar we use a placeholder one 225 | if (review.user.image_url == null) { 226 | avatar.color = 0x6666663; 227 | avatar.source = "assets/icons/account_circle.png"; 228 | } else { 229 | var mask:Canvas = new Canvas(); 230 | mask.drawCircle(25, 25, 25); 231 | 232 | avatar.mask = mask; 233 | avatar.source = review.user.image_url; 234 | } 235 | 236 | avatar.width = avatar.height = 50; 237 | avatar.layoutData = new AnchorLayoutData(5, NaN, NaN, 10, NaN, NaN); 238 | group.addChild(avatar); 239 | 240 | var nameLabel:Label = new Label(); 241 | nameLabel.layoutData = new AnchorLayoutData(5, NaN, NaN, 70, NaN, NaN); 242 | nameLabel.text = "" + review.user.name + ""; 243 | group.addChild(nameLabel); 244 | 245 | var ratingImage:ImageLoader = new ImageLoader(); 246 | ratingImage.width = 106; 247 | ratingImage.height = 20; 248 | ratingImage.source = "assets/yelp/" + review.rating + ".png"; 249 | ratingImage.layoutData = new AnchorLayoutData(30, NaN, NaN, 70, NaN, NaN); 250 | group.addChild(ratingImage); 251 | 252 | var reviewLabel:Label = new Label(); 253 | reviewLabel.layoutData = new AnchorLayoutData(65, 10, 0, 10, NaN, NaN); 254 | reviewLabel.textRendererProperties.wordWrap = true; 255 | reviewLabel.text = review.text; 256 | group.addChild(reviewLabel); 257 | 258 | return group; 259 | } 260 | 261 | private function yelpError(event:flash.events.Event):void 262 | { 263 | trace(event.currentTarget.data); 264 | } 265 | 266 | private function goBack():void 267 | { 268 | this.dispatchEventWith(starling.events.Event.COMPLETE); 269 | } 270 | 271 | } 272 | } -------------------------------------------------------------------------------- /src/businessScreens/DirectionsScreen.as: -------------------------------------------------------------------------------- 1 | package businessScreens 2 | { 3 | import cz.j4w.map.MapLayerOptions; 4 | import cz.j4w.map.MapOptions; 5 | import cz.j4w.map.geo.GeoMap; 6 | import cz.j4w.map.geo.Maps; 7 | 8 | import feathers.controls.Alert; 9 | import feathers.controls.Button; 10 | import feathers.controls.ImageLoader; 11 | import feathers.controls.LayoutGroup; 12 | import feathers.controls.List; 13 | import feathers.controls.PanelScreen; 14 | import feathers.controls.renderers.DefaultListItemRenderer; 15 | import feathers.data.ListCollection; 16 | import feathers.events.FeathersEventType; 17 | import feathers.layout.AnchorLayout; 18 | import feathers.layout.AnchorLayoutData; 19 | import feathers.layout.HorizontalAlign; 20 | import feathers.layout.VerticalAlign; 21 | import feathers.layout.VerticalLayout; 22 | import feathers.layout.VerticalLayoutData; 23 | 24 | import flash.events.Event; 25 | import flash.events.GeolocationEvent; 26 | import flash.events.StatusEvent; 27 | import flash.net.URLLoader; 28 | import flash.net.URLRequest; 29 | import flash.sensors.Geolocation; 30 | 31 | import starling.display.DisplayObject; 32 | import starling.events.Event; 33 | import starling.textures.Texture; 34 | 35 | import utils.NavigatorData; 36 | 37 | public class DirectionsScreen extends PanelScreen 38 | { 39 | [Embed(source="./../assets/icons/pin.png")] 40 | private static const pinAsset:Class; 41 | public static var pinTexture:Texture; 42 | 43 | private var directionsLoader:URLLoader; 44 | private var origin:String; 45 | private var destination:String; 46 | private var geo:Geolocation; 47 | private var alert:Alert; 48 | private var directionsList:List; 49 | private var geoMap:GeoMap; 50 | 51 | protected var _data:NavigatorData; 52 | 53 | public function get data():NavigatorData 54 | { 55 | return this._data; 56 | } 57 | 58 | public function set data(value:NavigatorData):void 59 | { 60 | this._data = value; 61 | } 62 | 63 | override protected function initialize():void 64 | { 65 | super.initialize(); 66 | 67 | this.title = "Directions"; 68 | this.layout = new VerticalLayout(); 69 | this.backButtonHandler = goBack; 70 | this.headerProperties.paddingLeft = 10; 71 | 72 | var doneIcon:ImageLoader = new ImageLoader(); 73 | doneIcon.source = "assets/icons/check.png"; 74 | doneIcon.width = doneIcon.height = 25; 75 | 76 | var doneButton:Button = new Button(); 77 | doneButton.defaultIcon = doneIcon; 78 | doneButton.addEventListener(starling.events.Event.TRIGGERED, goBack); 79 | doneButton.styleNameList.add("header-button"); 80 | this.headerProperties.rightItems = new [doneButton]; 81 | 82 | var mapOptions:MapOptions = new MapOptions(); 83 | mapOptions.initialScale = 16 / 32; 84 | mapOptions.minimumScale = 1 / 32; 85 | mapOptions.maximumScale = 16 / 32; 86 | mapOptions.disableRotation = true; 87 | 88 | var mapContainer:LayoutGroup = new LayoutGroup(); 89 | mapContainer.layoutData = new VerticalLayoutData(100, 50); 90 | mapContainer.layout = new AnchorLayout(); 91 | this.addChild(mapContainer); 92 | 93 | geoMap = new GeoMap(mapOptions); 94 | geoMap.visible = false; 95 | geoMap.layoutData = new AnchorLayoutData(0, 0, 0, 0, NaN, NaN); 96 | geoMap.setSize(1, 1); 97 | mapContainer.addChild(geoMap); 98 | 99 | var osMaps:MapLayerOptions = Maps.OSM; 100 | osMaps.notUsedZoomThreshold = 1; 101 | geoMap.addLayer("osMaps", osMaps); 102 | 103 | directionsList = new List(); 104 | directionsList.layoutData = new VerticalLayoutData(100, 50); 105 | directionsList.itemRendererFactory = function ():DefaultListItemRenderer 106 | { 107 | var renderer:DefaultListItemRenderer = new DefaultListItemRenderer(); 108 | renderer.labelFunction = function (item:Object):String 109 | { 110 | return item.html_instructions; 111 | } 112 | 113 | renderer.accessoryLabelFunction = function (item:Object):String 114 | { 115 | return item.distance.text + "\n" + item.duration.text; 116 | } 117 | 118 | return renderer; 119 | }; 120 | this.addChild(directionsList); 121 | 122 | pinTexture = Texture.fromEmbeddedAsset(pinAsset); 123 | 124 | this.addEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 125 | } 126 | 127 | private function transitionComplete(event:starling.events.Event):void 128 | { 129 | this.removeEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 130 | 131 | destination = _data.currentBusiness.coordinates.latitude + "," + _data.currentBusiness.coordinates.longitude; 132 | 133 | if (Geolocation.isSupported) { 134 | geo = new Geolocation(); 135 | 136 | if (!geo.muted) { 137 | geo.addEventListener(GeolocationEvent.UPDATE, geoUpdateHandler); 138 | } else { 139 | alert = Alert.show("Your GPS is turned off, please turn it ON and try again.", "Error", new ListCollection([{ 140 | label: "OK", 141 | triggered: goBack 142 | }])); 143 | } 144 | geo.addEventListener(StatusEvent.STATUS, geoStatusHandler); 145 | } else { 146 | alert = Alert.show("GPS is not supported on your device.", "Error", new ListCollection([{ 147 | label: "OK", 148 | triggered: goBack 149 | }])); 150 | } 151 | } 152 | 153 | private function geoUpdateHandler(event:GeolocationEvent):void 154 | { 155 | var lat:String = event.latitude.toString(); 156 | var lon:String = event.longitude.toString(); 157 | 158 | geo.removeEventListener(GeolocationEvent.UPDATE, geoUpdateHandler); 159 | geo = null; 160 | 161 | origin = lat + "," + lon; 162 | getDirections(); 163 | } 164 | 165 | private function geoStatusHandler(event:StatusEvent):void 166 | { 167 | if (geo.muted) { 168 | geo.removeEventListener(GeolocationEvent.UPDATE, geoUpdateHandler); 169 | } else { 170 | geo.addEventListener(GeolocationEvent.UPDATE, geoUpdateHandler); 171 | } 172 | } 173 | 174 | private function getDirections():void 175 | { 176 | var request:URLRequest = new URLRequest("https://maps.googleapis.com/maps/api/directions/json?origin=" + origin + "&destination=" + destination + "&units=imperial"); 177 | 178 | directionsLoader = new URLLoader(); 179 | directionsLoader.addEventListener(flash.events.Event.COMPLETE, directionsLoaded); 180 | directionsLoader.load(request); 181 | } 182 | 183 | private function directionsLoaded(event:flash.events.Event):void 184 | { 185 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, directionsLoaded); 186 | 187 | var rawData:Object = JSON.parse(event.currentTarget.data); 188 | 189 | directionsList.dataProvider = new ListCollection(rawData.routes[0].legs[0].steps as Array); 190 | 191 | this.title = "Directions [ " + rawData.routes[0].legs[0].distance.text + " in " + rawData.routes[0].legs[0].duration.text + " ]"; 192 | 193 | if (directionsList.dataProvider != null && directionsList.dataProvider.length >= 1) { 194 | 195 | geoMap.setCenterLongLat(directionsList.dataProvider.getItemAt(0).start_location.lng, directionsList.dataProvider.getItemAt(0).start_location.lat); 196 | 197 | directionsList.addEventListener(starling.events.Event.CHANGE, listHandler); 198 | 199 | geoMap.removeAllMarkers(); 200 | geoMap.visible = true; 201 | 202 | for each(var item:Object in directionsList.dataProvider.data) { 203 | 204 | var marker:ImageLoader = new ImageLoader(); 205 | marker.source = pinTexture; 206 | marker.color = 0xCC0000; 207 | marker.width = marker.height = 50; 208 | marker.alignPivot(HorizontalAlign.CENTER, VerticalAlign.BOTTOM); 209 | 210 | geoMap.addMarkerLongLat(String(Math.random()), 211 | Number(item.end_location.lng), 212 | Number(item.end_location.lat), marker); 213 | } 214 | 215 | var firstMarker:ImageLoader = new ImageLoader(); 216 | firstMarker.source = "assets/icons/person_pin.png"; 217 | firstMarker.width = firstMarker.height = 50; 218 | firstMarker.color = 0x0000FF; 219 | firstMarker.alignPivot(HorizontalAlign.CENTER, VerticalAlign.BOTTOM); 220 | geoMap.addMarkerLongLat("first", directionsList.dataProvider.getItemAt(0).start_location.lng, directionsList.dataProvider.getItemAt(0).start_location.lat, firstMarker); 221 | 222 | var finalMarker:ImageLoader = new ImageLoader(); 223 | finalMarker.source = "assets/icons/person_pin.png"; 224 | finalMarker.width = finalMarker.height = 50; 225 | finalMarker.color = 0x0000FF; 226 | finalMarker.alignPivot(HorizontalAlign.CENTER, VerticalAlign.BOTTOM); 227 | geoMap.addMarkerLongLat("final", _data.currentBusiness.coordinates.longitude, _data.currentBusiness.coordinates.latitude, finalMarker); 228 | } 229 | } 230 | 231 | private function listHandler(event:starling.events.Event):void 232 | { 233 | geoMap.setCenterLongLat(directionsList.selectedItem.end_location.lng, directionsList.selectedItem.end_location.lat); 234 | } 235 | 236 | private function goBack():void 237 | { 238 | if (geo) { 239 | geo.removeEventListener(GeolocationEvent.UPDATE, geoUpdateHandler); 240 | geo = null; 241 | } 242 | 243 | if (alert) { 244 | alert.removeFromParent(true); 245 | } 246 | 247 | this.dispatchEventWith(starling.events.Event.COMPLETE); 248 | } 249 | 250 | } 251 | } -------------------------------------------------------------------------------- /src/businessScreens/FinderScreen.as: -------------------------------------------------------------------------------- 1 | package businessScreens 2 | { 3 | import feathers.controls.Alert; 4 | import feathers.controls.BasicButton; 5 | import feathers.controls.Button; 6 | import feathers.controls.ImageLoader; 7 | import feathers.controls.Label; 8 | import feathers.controls.LayoutGroup; 9 | import feathers.controls.PanelScreen; 10 | import feathers.controls.TextInput; 11 | import feathers.data.ListCollection; 12 | import feathers.events.FeathersEventType; 13 | import feathers.layout.AnchorLayout; 14 | import feathers.layout.AnchorLayoutData; 15 | import feathers.layout.HorizontalAlign; 16 | import feathers.layout.VerticalLayout; 17 | import feathers.layout.VerticalLayoutData; 18 | 19 | import flash.events.Event; 20 | import flash.events.GeolocationEvent; 21 | import flash.events.IOErrorEvent; 22 | import flash.events.StatusEvent; 23 | import flash.net.URLLoader; 24 | import flash.net.URLRequest; 25 | import flash.net.URLRequestHeader; 26 | import flash.net.URLRequestMethod; 27 | import flash.net.URLVariables; 28 | import flash.sensors.Geolocation; 29 | 30 | import starling.animation.Tween; 31 | import starling.core.Starling; 32 | import starling.display.DisplayObject; 33 | import starling.events.Event; 34 | import starling.text.TextFormat; 35 | 36 | import utils.NavigatorData; 37 | import utils.RoundedRect; 38 | 39 | public class FinderScreen extends PanelScreen 40 | { 41 | public static const GO_RESULTS:String = "goResults"; 42 | 43 | private var alert:Alert; 44 | private var findPizzaLabel:Label; 45 | private var innerGroup:LayoutGroup; 46 | private var layoutForInnerGroup:VerticalLayout; 47 | private var locationInput:TextInput; 48 | private var mainGroup:LayoutGroup; 49 | private var latitude:String; 50 | private var longitude:String; 51 | private var geo:Geolocation; 52 | private var searchMode:String; 53 | private var yelpLogo:ImageLoader; 54 | 55 | protected var _data:NavigatorData; 56 | 57 | public function get data():NavigatorData 58 | { 59 | return this._data; 60 | } 61 | 62 | public function set data(value:NavigatorData):void 63 | { 64 | this._data = value; 65 | } 66 | 67 | override protected function initialize():void 68 | { 69 | super.initialize(); 70 | 71 | this.title = "Find Local Businesses"; 72 | this.layout = new AnchorLayout(); 73 | 74 | var menuButton:Button = new Button(); 75 | menuButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 76 | { 77 | dispatchEventWith(Main.OPEN_MENU); 78 | }); 79 | menuButton.styleNameList.add("menu-button"); 80 | this.headerProperties.leftItems = new [menuButton]; 81 | 82 | layoutForInnerGroup = new VerticalLayout(); 83 | layoutForInnerGroup.horizontalAlign = HorizontalAlign.CENTER; 84 | layoutForInnerGroup.gap = 10; 85 | 86 | this.addEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 87 | } 88 | 89 | private function transitionComplete(event:starling.events.Event):void 90 | { 91 | initPanel(); 92 | } 93 | 94 | private function initPanel():void 95 | { 96 | findPizzaLabel = new Label(); 97 | findPizzaLabel.alpha = 0; 98 | findPizzaLabel.styleNameList.add("big-label"); 99 | findPizzaLabel.text = "Find Pizza"; 100 | findPizzaLabel.layoutData = new AnchorLayoutData(NaN, NaN, NaN, NaN, -50, -170); 101 | this.addChild(findPizzaLabel); 102 | 103 | var findPizzaLabelFade:Tween = new Tween(findPizzaLabel, 0.6); 104 | findPizzaLabelFade.animate("alpha", 1); 105 | Starling.juggler.add(findPizzaLabelFade); 106 | 107 | var findPizzaLabelSlide:Tween = new Tween(findPizzaLabel.layoutData, 0.6); 108 | findPizzaLabelSlide.animate("horizontalCenter", 0); 109 | Starling.juggler.add(findPizzaLabelSlide); 110 | 111 | mainGroup = new LayoutGroup(); 112 | mainGroup.layout = new VerticalLayout(); 113 | mainGroup.width = mainGroup.height = 1; 114 | mainGroup.layoutData = new AnchorLayoutData(NaN, NaN, NaN, NaN, 0, 0); 115 | mainGroup.backgroundSkin = RoundedRect.createRoundedRect(0xFFFFFF); 116 | this.addChild(mainGroup); 117 | 118 | innerGroup = new LayoutGroup(); 119 | innerGroup.alpha = 0; 120 | innerGroup.width = innerGroup.height = 250; 121 | innerGroup.layout = layoutForInnerGroup; 122 | mainGroup.addChild(innerGroup); 123 | 124 | var instructionsLabel:Label = new Label(); 125 | instructionsLabel.width = 220; 126 | instructionsLabel.paddingTop = 15; 127 | instructionsLabel.fontStyles = new TextFormat("_sans", 14, 0x000000, "left"); 128 | instructionsLabel.fontStyles.leading = 5; 129 | instructionsLabel.textRendererProperties.wordWrap = true; 130 | instructionsLabel.layoutData = new VerticalLayoutData(NaN, 100); 131 | instructionsLabel.text = "Find pizza near you! Type your desired location or use the GPS."; 132 | innerGroup.addChild(instructionsLabel); 133 | 134 | locationInput = new TextInput(); 135 | locationInput.width = 220; 136 | locationInput.height = 50; 137 | locationInput.prompt = "Location, e.g: Chicago"; 138 | innerGroup.addChild(locationInput); 139 | 140 | var searchIcon:ImageLoader = new ImageLoader(); 141 | searchIcon.source = "assets/icons/search.png"; 142 | searchIcon.width = searchIcon.height = 25; 143 | 144 | var searchButton:Button = new Button(); 145 | searchButton.addEventListener(starling.events.Event.TRIGGERED, initSearch); 146 | searchButton.styleNameList.add("rounded-button"); 147 | searchButton.label = "Search"; 148 | searchButton.defaultIcon = searchIcon; 149 | innerGroup.addChild(searchButton); 150 | searchButton.width = 220; 151 | searchButton.height = 50; 152 | 153 | var gpsIcon:ImageLoader = new ImageLoader(); 154 | gpsIcon.source = "assets/icons/gps.png"; 155 | gpsIcon.width = gpsIcon.height = 25; 156 | 157 | var gpsButton:Button = new Button(); 158 | gpsButton.addEventListener(starling.events.Event.TRIGGERED, initGPS); 159 | gpsButton.styleNameList.add("rounded-button"); 160 | gpsButton.label = "Search with GPS"; 161 | gpsButton.defaultIcon = gpsIcon; 162 | innerGroup.addChild(gpsButton); 163 | gpsButton.width = 220; 164 | gpsButton.height = 50; 165 | 166 | var spacer:BasicButton = new BasicButton(); 167 | spacer.height = 5; 168 | innerGroup.addChild(spacer); 169 | 170 | yelpLogo = new ImageLoader(); 171 | yelpLogo.alpha = 0; 172 | yelpLogo.source = "assets/yelp/yelp_logo_large.png"; 173 | yelpLogo.height = 40; 174 | yelpLogo.layoutData = new AnchorLayoutData(NaN, NaN, NaN, NaN, 0, 160); 175 | this.addChild(yelpLogo); 176 | 177 | var tween1:Tween = new Tween(mainGroup, 0.3); 178 | tween1.animate("height", 250); 179 | tween1.onComplete = function ():void 180 | { 181 | Starling.juggler.remove(tween1); 182 | Starling.juggler.add(tween2); 183 | }; 184 | Starling.juggler.add(tween1); 185 | 186 | var tween2:Tween = new Tween(mainGroup, 0.3); 187 | tween2.animate("width", 250); 188 | tween2.onComplete = function ():void 189 | { 190 | Starling.juggler.remove(tween2); 191 | Starling.juggler.add(tween3); 192 | }; 193 | 194 | var tween3:Tween = new Tween(innerGroup, 0.3); 195 | tween3.fadeTo(1); 196 | tween3.onComplete = function ():void 197 | { 198 | Starling.juggler.remove(tween3); 199 | Starling.juggler.add(tween4); 200 | } 201 | 202 | var tween4:Tween = new Tween(yelpLogo, 0.3); 203 | tween4.fadeTo(1); 204 | tween4.onComplete = function ():void 205 | { 206 | Starling.juggler.remove(tween4); 207 | } 208 | 209 | } 210 | 211 | private function initSearch():void 212 | { 213 | if (locationInput.text != "") { 214 | startFadeOut("location"); 215 | } else { 216 | alert = Alert.show("Please type a location.", "Location Required", new ListCollection([{label: "OK"}])); 217 | } 218 | } 219 | 220 | private function initGPS(event:starling.events.Event):void 221 | { 222 | if (Geolocation.isSupported) { 223 | geo = new Geolocation(); 224 | 225 | if (!geo.muted) { 226 | geo.addEventListener(GeolocationEvent.UPDATE, geoUpdateHandler); 227 | } else { 228 | alert = Alert.show("Your GPS is turned off, please turn it ON and try again.", "Error", new ListCollection([{label: "OK"}])); 229 | } 230 | 231 | geo.addEventListener(StatusEvent.STATUS, geoStatusHandler); 232 | } else { 233 | alert = Alert.show("GPS is not supported on your device.", "Error", new ListCollection([{label: "OK"}])); 234 | } 235 | } 236 | 237 | private function geoUpdateHandler(event:GeolocationEvent):void 238 | { 239 | latitude = event.latitude.toString(); 240 | longitude = event.longitude.toString(); 241 | 242 | geo.removeEventListener(GeolocationEvent.UPDATE, geoUpdateHandler); 243 | geo = null; 244 | 245 | startFadeOut("gps"); 246 | } 247 | 248 | private function geoStatusHandler(event:StatusEvent):void 249 | { 250 | if (geo.muted) { 251 | geo.removeEventListener(GeolocationEvent.UPDATE, geoUpdateHandler); 252 | } else { 253 | geo.addEventListener(GeolocationEvent.UPDATE, geoUpdateHandler); 254 | } 255 | } 256 | 257 | private function startFadeOut(mode:String):void 258 | { 259 | var findPizzaLabelFadeOut:Tween = new Tween(findPizzaLabel, 0.6); 260 | findPizzaLabelFadeOut.fadeTo(0); 261 | Starling.juggler.add(findPizzaLabelFadeOut); 262 | 263 | var yelpLogoFadeOut:Tween = new Tween(yelpLogo, 0.6); 264 | yelpLogoFadeOut.fadeTo(0); 265 | Starling.juggler.add(yelpLogoFadeOut); 266 | 267 | var innerGroupFadeOut:Tween = new Tween(innerGroup, 0.3); 268 | innerGroupFadeOut.fadeTo(0); 269 | innerGroupFadeOut.onComplete = function ():void 270 | { 271 | Starling.juggler.remove(innerGroupFadeOut); 272 | mainGroup.removeChild(innerGroup, true); 273 | Starling.juggler.add(resizeMainGroup); 274 | }; 275 | Starling.juggler.add(innerGroupFadeOut); 276 | 277 | var resizeMainGroup:Tween = new Tween(mainGroup, 0.3); 278 | resizeMainGroup.animate("width", stage.stageWidth + 20); 279 | resizeMainGroup.animate("height", stage.stageHeight - 50); 280 | resizeMainGroup.onComplete = function ():void 281 | { 282 | Starling.juggler.remove(resizeMainGroup); 283 | searchMode = mode; 284 | initYelpAPI(); 285 | }; 286 | } 287 | 288 | private function initYelpAPI():void 289 | { 290 | var myObject:URLVariables = new URLVariables(); 291 | myObject.grant_type = "client_credentials"; 292 | myObject.client_id = Constants.YELP_APP_ID; 293 | myObject.client_secret = Constants.YELP_APP_SECRET; 294 | 295 | var request:URLRequest = new URLRequest("https://api.yelp.com/oauth2/token"); 296 | request.data = myObject; 297 | request.method = URLRequestMethod.POST; 298 | 299 | var accessTokenLoader:URLLoader = new URLLoader(); 300 | accessTokenLoader.addEventListener(flash.events.Event.COMPLETE, yelpAccessTokenLoaded); 301 | accessTokenLoader.load(request); 302 | } 303 | 304 | private function yelpAccessTokenLoaded(event:flash.events.Event):void 305 | { 306 | event.currentTarget.addEventListener(flash.events.Event.COMPLETE, yelpAccessTokenLoaded); 307 | 308 | var rawData:Object = JSON.parse(event.currentTarget.data); 309 | _data.yelpAccessToken = rawData.access_token; 310 | 311 | var header:URLRequestHeader = new URLRequestHeader("Authorization", "Bearer " + _data.yelpAccessToken); 312 | 313 | var myObject:URLVariables = new URLVariables(); 314 | myObject.limit = 50; 315 | myObject.offset = 0; 316 | myObject.term = "Pizza"; 317 | myObject.radius = 8000; 318 | 319 | if (searchMode == "gps") { 320 | myObject.latitude = latitude; 321 | myObject.longitude = longitude; 322 | } else { 323 | myObject.location = locationInput.text; 324 | } 325 | 326 | var request:URLRequest = new URLRequest("https://api.yelp.com/v3/businesses/search"); 327 | request.data = myObject; 328 | request.requestHeaders.push(header); 329 | 330 | _data.searchParameters = myObject; 331 | 332 | var loader:URLLoader = new URLLoader(); 333 | loader.addEventListener(flash.events.Event.COMPLETE, yelpResponse); 334 | loader.addEventListener(IOErrorEvent.IO_ERROR, yelpError); 335 | loader.load(request); 336 | } 337 | 338 | private function yelpResponse(event:flash.events.Event):void 339 | { 340 | event.currentTarget.addEventListener(flash.events.Event.COMPLETE, yelpResponse); 341 | 342 | var rawData:Object = JSON.parse(event.currentTarget.data); 343 | _data.selectedDistance = 0; 344 | _data.totalResults = rawData.total; 345 | _data.savedResults = new ListCollection(rawData.businesses as Array); 346 | rawData = null; 347 | 348 | this.dispatchEventWith(GO_RESULTS); 349 | } 350 | 351 | private function yelpError(event:flash.events.Event):void 352 | { 353 | trace(event.currentTarget.data); 354 | } 355 | 356 | } 357 | } -------------------------------------------------------------------------------- /src/businessScreens/ResultsScreen.as: -------------------------------------------------------------------------------- 1 | package businessScreens 2 | { 3 | import feathers.controls.Button; 4 | import feathers.controls.ImageLoader; 5 | import feathers.controls.Label; 6 | import feathers.controls.List; 7 | import feathers.controls.Panel; 8 | import feathers.controls.PanelScreen; 9 | import feathers.controls.renderers.DefaultListItemRenderer; 10 | import feathers.core.PopUpManager; 11 | import feathers.data.ListCollection; 12 | import feathers.events.FeathersEventType; 13 | import feathers.layout.VerticalLayout; 14 | import feathers.layout.VerticalLayoutData; 15 | 16 | import flash.events.Event; 17 | import flash.net.URLLoader; 18 | import flash.net.URLRequest; 19 | import flash.net.URLRequestHeader; 20 | import flash.net.URLVariables; 21 | 22 | import renderers.BusinessRenderer; 23 | 24 | import starling.display.DisplayObject; 25 | import starling.display.Quad; 26 | import starling.events.Event; 27 | 28 | import utils.NavigatorData; 29 | 30 | public class ResultsScreen extends PanelScreen 31 | { 32 | public static const GO_DETAILS:String = "goBusinessDetails"; 33 | 34 | private var businessList:List; 35 | private var loading:Boolean = false; 36 | private var popup:Panel; 37 | private var isOpen:Boolean = false; 38 | 39 | protected var _data:NavigatorData; 40 | 41 | public function get data():NavigatorData 42 | { 43 | return this._data; 44 | } 45 | 46 | public function set data(value:NavigatorData):void 47 | { 48 | this._data = value; 49 | } 50 | 51 | override protected function initialize():void 52 | { 53 | super.initialize(); 54 | 55 | this.title = _data.totalResults + " Search Results"; 56 | this.layout = new VerticalLayout(); 57 | this.backButtonHandler = goBack; 58 | 59 | var backButton:Button = new Button(); 60 | backButton.styleNameList.add("back-button"); 61 | backButton.addEventListener(starling.events.Event.TRIGGERED, goBack); 62 | this.headerProperties.leftItems = new [backButton]; 63 | 64 | var rightMenuIcon:ImageLoader = new ImageLoader(); 65 | rightMenuIcon.source = "assets/icons/overflow.png"; 66 | rightMenuIcon.width = rightMenuIcon.height = 20; 67 | 68 | var rightMenuButton:Button = new Button(); 69 | rightMenuButton.addEventListener(starling.events.Event.TRIGGERED, openPopUpMenu); 70 | rightMenuButton.styleNameList.add("header-button"); 71 | rightMenuButton.defaultIcon = rightMenuIcon; 72 | this.headerProperties.rightItems = new [rightMenuButton]; 73 | 74 | businessList = new List(); 75 | businessList.layoutData = new VerticalLayoutData(100, 100); 76 | businessList.itemRendererType = BusinessRenderer; 77 | this.addChild(businessList); 78 | 79 | this.addEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 80 | } 81 | 82 | private function transitionComplete(event:starling.events.Event):void 83 | { 84 | this.removeEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 85 | 86 | businessList.dataProvider = _data.savedResults; 87 | 88 | if (_data.selectedIndex != undefined) { 89 | businessList.scrollToDisplayIndex(_data.selectedIndex); 90 | businessList.selectedIndex = _data.selectedIndex; 91 | } 92 | 93 | businessList.addEventListener(starling.events.Event.CHANGE, changeHandler); 94 | businessList.addEventListener(starling.events.Event.SCROLL, scrollHandler) 95 | } 96 | 97 | private function loadMore():void 98 | { 99 | if (!loading) { 100 | loading = true; 101 | 102 | var header:URLRequestHeader = new URLRequestHeader("Authorization", "Bearer " + _data.yelpAccessToken); 103 | 104 | var myObject:URLVariables = _data.searchParameters; 105 | myObject.offset = myObject.offset + 50; 106 | 107 | var request:URLRequest = new URLRequest("https://api.yelp.com/v3/businesses/search"); 108 | request.data = myObject; 109 | request.requestHeaders.push(header); 110 | 111 | var loader:URLLoader = new URLLoader(); 112 | loader.addEventListener(flash.events.Event.COMPLETE, businessLoaded); 113 | loader.load(request); 114 | } 115 | } 116 | 117 | private function businessLoaded(event:flash.events.Event):void 118 | { 119 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, businessLoaded); 120 | 121 | var rawData:Object = JSON.parse(event.currentTarget.data); 122 | trace(event.currentTarget.data); 123 | this.title = rawData.total + " Search Results"; 124 | 125 | for each (var item:* in rawData.businesses) { 126 | businessList.dataProvider.addItem(item); 127 | } 128 | 129 | loading = false; 130 | } 131 | 132 | private function scrollHandler(event:starling.events.Event):void 133 | { 134 | if (businessList.verticalScrollPosition == (businessList.viewPort.height - businessList.height)) { 135 | loadMore(); 136 | } 137 | } 138 | 139 | private function changeHandler(event:starling.events.Event):void 140 | { 141 | _data.savedResults = businessList.dataProvider; 142 | _data.currentBusiness = businessList.selectedItem; 143 | _data.selectedIndex = businessList.selectedIndex; 144 | 145 | this.dispatchEventWith(GO_DETAILS); 146 | } 147 | 148 | private function openPopUpMenu(event:starling.events.Event):void 149 | { 150 | popup = new Panel(); 151 | popup.layout = new VerticalLayout(); 152 | popup.backgroundSkin = new Quad(3, 3, 0xDDDDDD); 153 | popup.headerProperties.paddingLeft = 10; 154 | popup.title = "Select a Distance"; 155 | popup.width = 220; 156 | 157 | var closeIcon:ImageLoader = new ImageLoader(); 158 | closeIcon.source = "assets/icons/close.png"; 159 | closeIcon.width = closeIcon.height = 25; 160 | 161 | var closeButton:Button = new Button(); 162 | closeButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 163 | { 164 | isOpen = false; 165 | PopUpManager.removePopUp(popup, true); 166 | }); 167 | closeButton.styleNameList.add("header-button"); 168 | closeButton.defaultIcon = closeIcon; 169 | popup.headerProperties.rightItems = new [closeButton]; 170 | 171 | var distancesList:List = new List(); 172 | distancesList.itemRendererFactory = function ():DefaultListItemRenderer 173 | { 174 | var renderer:DefaultListItemRenderer = new DefaultListItemRenderer(); 175 | renderer.accessorySourceFunction = function ():String 176 | { 177 | if (distancesList.selectedIndex == renderer.index) { 178 | return "assets/icons/radio_checked.png"; 179 | } else { 180 | return "assets/icons/radio_unchecked.png"; 181 | } 182 | } 183 | 184 | renderer.accessoryLoaderFactory = function ():ImageLoader 185 | { 186 | var loader:ImageLoader = new ImageLoader(); 187 | loader.width = loader.height = 20; 188 | loader.color = 0x000000; 189 | return loader; 190 | } 191 | 192 | return renderer; 193 | } 194 | distancesList.width = 220; 195 | distancesList.dataProvider = new ListCollection( 196 | [ 197 | {label: "5 mi.", value: 8000}, 198 | {label: "10 mi.", value: 16000}, 199 | {label: "15 mi.", value: 24000} 200 | ]); 201 | popup.addChild(distancesList); 202 | 203 | distancesList.selectedIndex = _data.selectedDistance; 204 | 205 | distancesList.addEventListener(starling.events.Event.CHANGE, function ():void 206 | { 207 | //Order of these instructions DOES matter 208 | businessList.removeEventListener(starling.events.Event.CHANGE, changeHandler); 209 | businessList.selectedIndex = -1; 210 | _data.selectedDistance = distancesList.selectedIndex; 211 | _data.searchParameters.radius = distancesList.selectedItem.value; 212 | PopUpManager.removePopUp(popup, true); 213 | isOpen = false; 214 | startNewSearch(); 215 | businessList.addEventListener(starling.events.Event.CHANGE, changeHandler); 216 | }); 217 | 218 | PopUpManager.addPopUp(popup, true, true, function ():DisplayObject 219 | { 220 | var quad:Quad = new Quad(3, 3, 0x000000); 221 | quad.alpha = 0.75; 222 | return quad; 223 | }) 224 | 225 | var instructionsLabel:Label = new Label(); 226 | instructionsLabel.paddingLeft = 10; 227 | instructionsLabel.paddingTop = 5; 228 | instructionsLabel.text = "Works best with GPS."; 229 | popup.addChild(instructionsLabel); 230 | 231 | isOpen = true; 232 | } 233 | 234 | private function startNewSearch():void 235 | { 236 | loading = true; 237 | 238 | businessList.removeEventListener(starling.events.Event.SCROLL, scrollHandler) 239 | businessList.dataProvider = new ListCollection(); 240 | businessList.addEventListener(starling.events.Event.SCROLL, scrollHandler) 241 | 242 | var header:URLRequestHeader = new URLRequestHeader("Authorization", "Bearer " + _data.yelpAccessToken); 243 | 244 | var myObject:URLVariables = _data.searchParameters; 245 | myObject.offset = 0; 246 | 247 | var request:URLRequest = new URLRequest("https://api.yelp.com/v3/businesses/search"); 248 | request.data = myObject; 249 | request.requestHeaders.push(header); 250 | 251 | var loader:URLLoader = new URLLoader(); 252 | loader.addEventListener(flash.events.Event.COMPLETE, businessLoaded); 253 | loader.load(request); 254 | } 255 | 256 | private function goBack():void 257 | { 258 | _data.selectedDistance = null; 259 | _data.selectedIndex = null; 260 | _data.savedResults = null; 261 | _data.currentBusiness = null; 262 | _data.totalResults = null; 263 | _data.searchParameters = null; 264 | 265 | businessList.removeEventListener(starling.events.Event.CHANGE, changeHandler); 266 | businessList.removeEventListener(starling.events.Event.SCROLL, scrollHandler); 267 | businessList.dataProvider = null; 268 | 269 | this.dispatchEventWith(starling.events.Event.COMPLETE); 270 | } 271 | 272 | override public function dispose():void 273 | { 274 | if (isOpen) { 275 | PopUpManager.removePopUp(popup); 276 | } 277 | 278 | businessList.removeEventListener(starling.events.Event.CHANGE, changeHandler); 279 | businessList.removeEventListener(starling.events.Event.SCROLL, scrollHandler); 280 | 281 | super.dispose(); 282 | } 283 | 284 | } 285 | } -------------------------------------------------------------------------------- /src/chatScreens/ChatScreen.as: -------------------------------------------------------------------------------- 1 | package chatScreens 2 | { 3 | import feathers.controls.BasicButton; 4 | import feathers.controls.Button; 5 | import feathers.controls.ImageLoader; 6 | import feathers.controls.LayoutGroup; 7 | import feathers.controls.List; 8 | import feathers.controls.PanelScreen; 9 | import feathers.controls.TextInput; 10 | import feathers.data.ListCollection; 11 | import feathers.events.FeathersEventType; 12 | import feathers.layout.HorizontalAlign; 13 | import feathers.layout.HorizontalLayout; 14 | import feathers.layout.HorizontalLayoutData; 15 | import feathers.layout.VerticalAlign; 16 | import feathers.layout.VerticalLayout; 17 | import feathers.layout.VerticalLayoutData; 18 | 19 | import flash.events.Event; 20 | import flash.events.IOErrorEvent; 21 | import flash.events.ProgressEvent; 22 | import flash.net.URLLoader; 23 | import flash.net.URLRequest; 24 | import flash.net.URLRequestHeader; 25 | import flash.net.URLRequestMethod; 26 | import flash.net.URLStream; 27 | 28 | import starling.display.DisplayObject; 29 | import starling.display.Quad; 30 | import starling.events.Event; 31 | 32 | import utils.ChatMessageItemRenderer; 33 | import utils.NavigatorData; 34 | import utils.RoundedRect; 35 | 36 | public class ChatScreen extends PanelScreen 37 | { 38 | private var isListFocused:Boolean; 39 | 40 | private var messagesArray:Array; 41 | private var messagesList:List; 42 | private var messageInput:TextInput; 43 | private var messagesStream:URLStream; 44 | private var sendButton:Button; 45 | 46 | protected var _data:NavigatorData; 47 | 48 | public function get data():NavigatorData 49 | { 50 | return this._data; 51 | } 52 | 53 | public function set data(value:NavigatorData):void 54 | { 55 | this._data = value; 56 | } 57 | 58 | override protected function initialize():void 59 | { 60 | super.initialize(); 61 | 62 | this.title = _data.selectedRoom.name; 63 | this.layout = new VerticalLayout(); 64 | this.backButtonHandler = goBack; 65 | 66 | var backButton:Button = new Button(); 67 | backButton.styleNameList.add("back-button"); 68 | backButton.addEventListener(starling.events.Event.TRIGGERED, goBack); 69 | this.headerProperties.leftItems = new [backButton]; 70 | 71 | messagesArray = new Array(); 72 | 73 | var layoutForMessagesList:VerticalLayout = new VerticalLayout(); 74 | layoutForMessagesList.hasVariableItemDimensions = true; 75 | layoutForMessagesList.horizontalAlign = HorizontalAlign.JUSTIFY; 76 | layoutForMessagesList.gap = 5; 77 | 78 | messagesList = new List(); 79 | messagesList.paddingTop = messagesList.paddingBottom = 10; 80 | messagesList.layout = layoutForMessagesList; 81 | messagesList.itemRendererType = ChatMessageItemRenderer; 82 | messagesList.layoutData = new VerticalLayoutData(100, 100); 83 | messagesList.dataProvider = new ListCollection(messagesArray); 84 | this.addChild(messagesList); 85 | 86 | var layoutForBottomGroup:HorizontalLayout = new HorizontalLayout(); 87 | layoutForBottomGroup.paddingLeft = layoutForBottomGroup.paddingRight = 10; 88 | layoutForBottomGroup.verticalAlign = VerticalAlign.MIDDLE; 89 | layoutForBottomGroup.gap = 10; 90 | 91 | var bottomGroupSkin:Quad = new Quad(3, 50); 92 | bottomGroupSkin.setVertexColor(0, 0x01579B); 93 | bottomGroupSkin.setVertexColor(1, 0x01579B); 94 | bottomGroupSkin.setVertexColor(2, 0x0277BD); 95 | bottomGroupSkin.setVertexColor(3, 0x0277BD); 96 | 97 | var bottomGroup:LayoutGroup = new LayoutGroup(); 98 | bottomGroup.backgroundSkin = bottomGroupSkin; 99 | bottomGroup.layout = layoutForBottomGroup; 100 | bottomGroup.layoutData = new VerticalLayoutData(100, NaN); 101 | bottomGroup.height = 70; 102 | 103 | this.addChild(bottomGroup); 104 | 105 | var spacer:BasicButton = new BasicButton(); 106 | spacer.width = 10; 107 | spacer.height = 0; 108 | this.addChild(spacer); 109 | 110 | messageInput = new TextInput(); 111 | messageInput.layoutData = new HorizontalLayoutData(100, NaN); 112 | messageInput.height = 50; 113 | messageInput.prompt = "Type your message here"; 114 | messageInput.textEditorProperties.maintainTouchFocus = true; 115 | bottomGroup.addChild(messageInput); 116 | messageInput.backgroundSkin = RoundedRect.createRoundedRect(0x263238); 117 | 118 | var sendIcon:ImageLoader = new ImageLoader(); 119 | sendIcon.source = "assets/icons/send.png"; 120 | sendIcon.width = sendIcon.height = 25; 121 | 122 | sendButton = new Button(); 123 | sendButton.addEventListener(starling.events.Event.TRIGGERED, sendMessage); 124 | sendButton.width = sendButton.height = 50; 125 | sendButton.defaultIcon = sendIcon; 126 | sendButton.defaultSkin = new Quad(50, 50, 0x000000); 127 | bottomGroup.addChild(sendButton); 128 | sendButton.defaultSkin = RoundedRect.createRoundedRect(0x263238); 129 | 130 | this.addEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 131 | } 132 | 133 | private function transitionComplete(event:starling.events.Event):void 134 | { 135 | this.removeEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 136 | loadMessages(); 137 | } 138 | 139 | protected function loadMessages():void 140 | { 141 | var header:URLRequestHeader = new URLRequestHeader("Accept", "text/event-stream"); 142 | var request:URLRequest = new URLRequest(Constants.FIREBASE_CHATROOM_BASE_URL + _data.selectedRoom.id + '.json?auth=' 143 | + _data.FirebaseAuthToken + '&orderBy="timestamp"&limitToLast=100'); //We are always loading the last 100 messages 144 | request.requestHeaders.push(header); 145 | 146 | messagesStream = new URLStream(); 147 | messagesStream.addEventListener(ProgressEvent.PROGRESS, progress); 148 | messagesStream.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 149 | messagesStream.load(request); 150 | } 151 | 152 | private function progress(event:ProgressEvent):void 153 | { 154 | //We are continously listening responses and response codes 155 | 156 | var message:String = messagesStream.readUTFBytes(messagesStream.bytesAvailable); 157 | //trace(message); 158 | 159 | //If a message contains the null value it means nothing happened and we skip it 160 | 161 | if (message.indexOf("null") == -1) { 162 | //Otherwise we take the JSON part of the response and convert it into an Object 163 | message = message.substr(message.indexOf("data:") + 6, message.length); 164 | 165 | var rawData:Object = JSON.parse(message); 166 | 167 | //The first time we connect to the database it will return saved messages in a different structure 168 | //Even if we requested the data sorted by timestamp, the JSON parser scrambles it, so we have to put it in an Array 169 | 170 | var tempArray:Array = new Array(); 171 | 172 | for each(var item:Object in rawData.data) { 173 | if (item.hasOwnProperty("message")) { 174 | tempArray.push(item); 175 | } 176 | } 177 | 178 | //We sort our data by timestamp so it shows the most recent messages at the bottom, like in all messaging apps 179 | tempArray.sortOn("timestamp"); 180 | 181 | for (var i:uint = 0; i < tempArray.length; i++) { 182 | //We add the contents of the sorted array to the dataprovider 183 | messagesList.dataProvider.addItem(tempArray[i]); 184 | } 185 | 186 | //For the individual messages the structure is more simple, we just make sure it contains a message 187 | if (Object(rawData.data).hasOwnProperty("message")) { 188 | messagesList.dataProvider.addItem(rawData.data); 189 | } 190 | 191 | //If the list is not focused we automatically scroll it to the bottom 192 | if (isListFocused == false) { 193 | messagesList.scrollToDisplayIndex(messagesList.dataProvider.data.length - 1); 194 | } 195 | 196 | //Clean up 197 | tempArray = null; 198 | rawData = null; 199 | message = null; 200 | } 201 | } 202 | 203 | private function sendMessage():void 204 | { 205 | if (messageInput.text != "") { 206 | sendButton.isEnabled = false; 207 | 208 | //We prepare the vars to be send to the database, including the logged-in user basic info 209 | var myObject:Object = new Object(); 210 | myObject.message = messageInput.text; 211 | myObject.timestamp = new Date().getTime(); 212 | myObject.senderId = Main.profile.localId; 213 | myObject.senderName = Main.profile.displayName; 214 | 215 | var request:URLRequest = new URLRequest(Constants.FIREBASE_CHATROOM_BASE_URL + _data.selectedRoom.id + ".json?auth=" + _data.FirebaseAuthToken); 216 | request.data = JSON.stringify(myObject); 217 | request.method = URLRequestMethod.POST; 218 | 219 | var loader:URLLoader = new URLLoader(); 220 | loader.addEventListener(flash.events.Event.COMPLETE, messageSent); 221 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 222 | loader.load(request); 223 | } 224 | } 225 | 226 | private function messageSent(event:flash.events.Event):void 227 | { 228 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, messageSent); 229 | messageInput.text = ""; 230 | sendButton.isEnabled = true; 231 | } 232 | 233 | private function errorHandler(event:IOErrorEvent):void 234 | { 235 | trace(event.currentTarget.data); 236 | event.currentTarget.removeEventListener(IOErrorEvent.IO_ERROR, errorHandler); 237 | sendButton.isEnabled = true; 238 | } 239 | 240 | private function goBack():void 241 | { 242 | this.dispatchEventWith(starling.events.Event.COMPLETE); 243 | } 244 | 245 | override public function dispose():void 246 | { 247 | //Remove the event listener or you will be still receiving messages 248 | messagesStream.removeEventListener(ProgressEvent.PROGRESS, progress); 249 | messagesList.dataProvider = null; 250 | 251 | super.dispose(); 252 | } 253 | 254 | } 255 | } -------------------------------------------------------------------------------- /src/chatScreens/ChatroomsScreen.as: -------------------------------------------------------------------------------- 1 | package chatScreens 2 | { 3 | import feathers.controls.Alert; 4 | import feathers.controls.Button; 5 | import feathers.controls.ImageLoader; 6 | import feathers.controls.List; 7 | import feathers.controls.PanelScreen; 8 | import feathers.controls.renderers.DefaultListItemRenderer; 9 | import feathers.data.ListCollection; 10 | import feathers.events.FeathersEventType; 11 | import feathers.layout.VerticalLayout; 12 | import feathers.layout.VerticalLayoutData; 13 | 14 | import flash.events.Event; 15 | import flash.events.IOErrorEvent; 16 | import flash.net.URLLoader; 17 | import flash.net.URLRequest; 18 | import flash.net.URLRequestHeader; 19 | import flash.net.URLRequestMethod; 20 | 21 | import starling.display.DisplayObject; 22 | import starling.events.Event; 23 | 24 | import utils.NavigatorData; 25 | import utils.ProfileManager; 26 | 27 | public class ChatroomsScreen extends PanelScreen 28 | { 29 | public static const GO_CHAT:String = "goChat"; 30 | public static const GO_LOGIN:String = "goLogin"; 31 | 32 | private var alert:Alert; 33 | private var roomsList:List; 34 | 35 | protected var _data:NavigatorData; 36 | 37 | public function get data():NavigatorData 38 | { 39 | return this._data; 40 | } 41 | 42 | public function set data(value:NavigatorData):void 43 | { 44 | this._data = value; 45 | } 46 | 47 | override protected function initialize():void 48 | { 49 | super.initialize(); 50 | 51 | this.title = "Chat Rooms"; 52 | this.layout = new VerticalLayout(); 53 | 54 | var menuButton:Button = new Button(); 55 | menuButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 56 | { 57 | dispatchEventWith(Main.OPEN_MENU); 58 | }); 59 | menuButton.styleNameList.add("menu-button"); 60 | this.headerProperties.leftItems = new [menuButton]; 61 | 62 | roomsList = new List(); 63 | roomsList.addEventListener(starling.events.Event.CHANGE, changeHandler); 64 | roomsList.layoutData = new VerticalLayoutData(100, 100); 65 | roomsList.itemRendererFactory = function ():DefaultListItemRenderer 66 | { 67 | var renderer:DefaultListItemRenderer = new DefaultListItemRenderer(); 68 | renderer.isQuickHitAreaEnabled = true; 69 | renderer.height = 80; 70 | renderer.iconSourceField = "image"; 71 | 72 | renderer.iconLoaderFactory = function ():ImageLoader 73 | { 74 | var loader:ImageLoader = new ImageLoader(); 75 | loader.width = loader.height = 50; 76 | return loader; 77 | } 78 | 79 | renderer.labelFunction = function (item:Object):String 80 | { 81 | return "" + item.name + "" + "\n" + item.description; 82 | }; 83 | 84 | return renderer; 85 | }; 86 | this.addChild(roomsList); 87 | 88 | this.addEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 89 | } 90 | 91 | private function transitionComplete(event:starling.events.Event):void 92 | { 93 | this.removeEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 94 | loadChatRooms(); 95 | } 96 | 97 | private function loadChatRooms():void 98 | { 99 | var request:URLRequest = new URLRequest(Constants.FIREBASE_CHATROOMS_URL); 100 | 101 | var loader:URLLoader = new URLLoader(); 102 | loader.addEventListener(flash.events.Event.COMPLETE, chatRoomsLoaded); 103 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 104 | loader.load(request); 105 | } 106 | 107 | private function chatRoomsLoaded(event:flash.events.Event):void 108 | { 109 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, chatRoomsLoaded); 110 | 111 | //The JSON generated by Firebase contains the id as the node key, we use this function to add it to our Objects 112 | 113 | var rawData:Object = JSON.parse(event.currentTarget.data); 114 | var roomsArray:Array = new Array(); 115 | 116 | for (var parent:String in rawData) { 117 | var tempObject:Object = new Object(); 118 | tempObject.id = parent; 119 | 120 | for (var child:* in rawData[parent]) { 121 | tempObject[child] = rawData[parent][child]; 122 | } 123 | 124 | roomsArray.push(tempObject); 125 | roomsArray.sortOn("internal_id"); 126 | tempObject = null; 127 | } 128 | 129 | roomsList.dataProvider = new ListCollection(roomsArray); 130 | } 131 | 132 | private function changeHandler(event:starling.events.Event):void 133 | { 134 | if (ProfileManager.isLoggedIn() === false) { 135 | alert = Alert.show("This feature requires that you are signed in, proceed to Sign In process?", "Sign In Required", new ListCollection( 136 | [ 137 | {label: "Cancel"}, 138 | {label: "OK"} 139 | ])); 140 | 141 | alert.addEventListener(starling.events.Event.CLOSE, function (event:starling.events.Event, data:Object):void 142 | { 143 | if (data.label == "OK") { 144 | dispatchEventWith(GO_LOGIN); 145 | } else { 146 | //Changing the index triggers a CHANGE event, so we temporarily remove it and add it again 147 | roomsList.removeEventListener(starling.events.Event.CHANGE, changeHandler); 148 | roomsList.selectedIndex = -1; 149 | roomsList.addEventListener(starling.events.Event.CHANGE, changeHandler); 150 | } 151 | }); 152 | } else { 153 | getAccessToken(); 154 | } 155 | } 156 | 157 | private function getAccessToken():void 158 | { 159 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 160 | 161 | var myObject:Object = new Object(); 162 | myObject.grant_type = "refresh_token"; 163 | myObject.refresh_token = Main.profile.refreshToken; 164 | 165 | var request:URLRequest = new URLRequest(Constants.FIREBASE_AUTH_TOKEN_URL); 166 | request.method = URLRequestMethod.POST; 167 | request.data = JSON.stringify(myObject); 168 | request.requestHeaders.push(header); 169 | 170 | var loader:URLLoader = new URLLoader(); 171 | loader.addEventListener(flash.events.Event.COMPLETE, accessTokenLoaded); 172 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 173 | loader.load(request); 174 | } 175 | 176 | private function accessTokenLoaded(event:flash.events.Event):void 177 | { 178 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, accessTokenLoaded); 179 | 180 | var rawData:Object = JSON.parse(event.currentTarget.data); 181 | 182 | //VERY IMPORTANT: Yhis token will be used to authenticate with the Firebase realtime database 183 | _data.FirebaseAuthToken = rawData.access_token; 184 | _data.selectedRoom = roomsList.selectedItem; 185 | this.dispatchEventWith(GO_CHAT); 186 | } 187 | 188 | private function errorHandler(event:IOErrorEvent):void 189 | { 190 | trace(event.currentTarget.data); 191 | } 192 | 193 | override public function dispose():void 194 | { 195 | if (alert) { 196 | alert.removeFromParent(true); 197 | } 198 | 199 | super.dispose(); 200 | } 201 | 202 | } 203 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/Map.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map 2 | { 3 | import cz.j4w.map.events.MapEvent; 4 | 5 | import feathers.core.FeathersControl; 6 | 7 | import flash.events.MouseEvent; 8 | import flash.geom.Point; 9 | import flash.geom.Rectangle; 10 | import flash.utils.Dictionary; 11 | 12 | import starling.animation.Transitions; 13 | import starling.animation.Tween; 14 | import starling.core.Starling; 15 | import starling.display.DisplayObject; 16 | import starling.display.Quad; 17 | import starling.display.Sprite; 18 | import starling.events.EnterFrameEvent; 19 | import starling.events.Touch; 20 | import starling.events.TouchEvent; 21 | import starling.events.TouchPhase; 22 | 23 | /** 24 | * Main Starling class. Provides demo Feathers UI for map controll. 25 | */ 26 | public class Map extends FeathersControl 27 | { 28 | private var currentTween:Tween; 29 | private var tweenTransition:Function = Transitions.getTransition(Transitions.EASE_IN_OUT); 30 | 31 | protected var mapTilesBuffer:MapTilesBuffer; 32 | 33 | protected var mapOptions:MapOptions; 34 | protected var mapContainer:Sprite; 35 | protected var markersContainer:Sprite; 36 | protected var touchSheet:TouchSheet; 37 | protected var markers:Dictionary; 38 | 39 | protected var layers:Array; 40 | protected var mapViewPort:Rectangle; 41 | protected var centerBackup:Point; 42 | 43 | private var _scaleRatio:int; 44 | private var _zoom:int; 45 | 46 | public function Map(mapOptions:MapOptions) 47 | { 48 | this.mapOptions = mapOptions; 49 | } 50 | 51 | override protected function initialize():void 52 | { 53 | layers = []; 54 | 55 | mapTilesBuffer = new MapTilesBuffer(); 56 | markers = new Dictionary(); 57 | centerBackup = new Point(); 58 | mapViewPort = new Rectangle(); 59 | mapContainer = new Sprite(); 60 | markersContainer = new Sprite(); 61 | touchSheet = new TouchSheet(mapContainer, viewPort, mapOptions); 62 | addChild(touchSheet); 63 | mapContainer.addChild(markersContainer); 64 | 65 | touchSheet.scaleX = touchSheet.scaleY = mapOptions.initialScale || 1; 66 | 67 | if (mapOptions.initialCenter) 68 | setCenter(mapOptions.initialCenter); 69 | 70 | addEventListener(EnterFrameEvent.ENTER_FRAME, onEnterFrame); 71 | addEventListener(TouchEvent.TOUCH, onTouch); 72 | Starling.current.nativeStage.addEventListener(MouseEvent.MOUSE_WHEEL, onNativeStageMouseWheel); 73 | } 74 | 75 | override public function dispose():void 76 | { 77 | super.dispose(); 78 | mapTilesBuffer.dispose(); 79 | Starling.current.nativeStage.removeEventListener(MouseEvent.MOUSE_WHEEL, onNativeStageMouseWheel); 80 | } 81 | 82 | override protected function draw():void 83 | { 84 | mask = new Quad(scaledActualWidth, scaledActualHeight); 85 | setCenter(centerBackup); 86 | } 87 | 88 | protected function update():void 89 | { 90 | getBounds(touchSheet, mapViewPort); // calculate mapViewPort before bounds check 91 | touchSheet.applyBounds(); 92 | getBounds(touchSheet, mapViewPort); // calculate mapViewPort after bounds check 93 | updateMarkers(); 94 | updateZoomAndScale(); 95 | } 96 | 97 | protected function updateMarkers():void 98 | { 99 | var n:int = markersContainer.numChildren; 100 | var sx:Number = 1 / touchSheet.scaleX; 101 | for (var i:int = 0; i < n; i++) { 102 | var marker:DisplayObject = markersContainer.getChildAt(i); // scaling markers always to be 1:1 103 | marker.scaleX = marker.scaleY = sx; 104 | marker.visible = mapViewPort.intersects(marker.bounds); 105 | } 106 | } 107 | 108 | public function addLayer(id:String, options:Object = null):MapLayer 109 | { 110 | if (layers[id]) { 111 | trace("Layer", id, "already added.") 112 | return layers[id]; 113 | } 114 | 115 | if (!options) 116 | options = {}; 117 | 118 | var childIndex:uint = options.index >= 0 ? options.index : mapContainer.numChildren; 119 | 120 | var layer:MapLayer = new MapLayer(this, id, options, mapTilesBuffer); 121 | mapContainer.addChildAt(layer, childIndex); 122 | mapContainer.addChild(markersContainer); // markers are always on top 123 | 124 | layers[id] = layer; 125 | 126 | return layer; 127 | } 128 | 129 | public function removeLayer(id:String):void 130 | { 131 | if (layers[id]) { 132 | var layer:MapLayer = layers[id] as MapLayer; 133 | layer.removeFromParent(true); 134 | layers[id] = null; 135 | delete layers[id]; 136 | } 137 | } 138 | 139 | public function removeAllLayers():void 140 | { 141 | for (var layerId:String in layers) { 142 | removeLayer(layerId); 143 | } 144 | } 145 | 146 | public function hasLayer(id:String):Boolean 147 | { 148 | return layers[id]; 149 | } 150 | 151 | public function getLayer(id:String):MapLayer 152 | { 153 | return layers[id]; 154 | } 155 | 156 | public function addMarker(id:String, x:Number, y:Number, displayObject:DisplayObject, data:Object = null):MapMarker 157 | { 158 | displayObject.name = id; 159 | displayObject.x = x; 160 | displayObject.y = y; 161 | markersContainer.addChild(displayObject); 162 | 163 | var mapMarker:MapMarker = createMarker(id, displayObject, data); 164 | markers[id] = mapMarker; 165 | return mapMarker; 166 | } 167 | 168 | protected function createMarker(id:String, displayObject:DisplayObject, data:Object):MapMarker 169 | { 170 | return new MapMarker(id, displayObject, data); 171 | } 172 | 173 | public function getMarker(id:String):MapMarker 174 | { 175 | return markers[id] as MapMarker; 176 | } 177 | 178 | public function removeMarker(id:String):MapMarker 179 | { 180 | var mapMarker:MapMarker = markers[id] as MapMarker; 181 | 182 | if (mapMarker) { 183 | var displayObject:DisplayObject = mapMarker.displayObject; 184 | displayObject.removeFromParent(); 185 | delete markers[id]; 186 | } 187 | 188 | return mapMarker; 189 | } 190 | 191 | public function removeAllMarkers():void 192 | { 193 | markersContainer.removeChildren(); 194 | markers = new Dictionary(); 195 | } 196 | 197 | public function get viewPort():Rectangle 198 | { 199 | return mapViewPort; 200 | } 201 | 202 | public function get zoom():int 203 | { 204 | return _zoom; 205 | } 206 | 207 | public function get scaleRatio():int 208 | { 209 | return _scaleRatio; 210 | } 211 | 212 | private function updateZoomAndScale():void 213 | { 214 | _scaleRatio = 1; 215 | var z:int = int(1 / touchSheet.scaleX); 216 | while (_scaleRatio < z) { 217 | _scaleRatio <<= 1; 218 | } 219 | 220 | var s:uint = _scaleRatio; 221 | _zoom = 1; 222 | while (s > 1) { 223 | s >>= 1; 224 | ++_zoom; 225 | } 226 | } 227 | 228 | public function setCenter(point:Point):void 229 | { 230 | setCenterXY(point.x, point.y); 231 | } 232 | 233 | public function getCenter():Point 234 | { 235 | return new Point(mapViewPort.x + mapViewPort.width / 2, mapViewPort.y + mapViewPort.height / 2); 236 | } 237 | 238 | public function setCenterXY(x:Number, y:Number):void 239 | { 240 | update(); 241 | centerBackup.setTo(x, y); 242 | 243 | var tween:Tween = new Tween(touchSheet, 1.0); 244 | tween.animate("pivotX", x); 245 | tween.animate("pivotY", y); 246 | tween.onComplete = function ():void 247 | { 248 | touchSheet.x = width / 2; 249 | touchSheet.y = height / 2; 250 | update(); 251 | }; 252 | 253 | Starling.juggler.add(tween); 254 | 255 | //touchSheet.pivotX = x; 256 | //touchSheet.pivotY = y; 257 | 258 | } 259 | 260 | public function tweenTo(x:Number, y:Number, scale:Number = 1, time:Number = 3):Tween 261 | { 262 | cancelTween(); 263 | 264 | var center:Point = getCenter(); 265 | 266 | var tweenObject:Object = {ratio: 0, x: center.x, y: center.y, scale: touchSheet.scaleX}; 267 | var tweenTo:Object = {ratio: 1, x: x, y: y, scale: scale}; 268 | currentTween = Starling.juggler.tween(tweenObject, time, { 269 | ratio: 1, 270 | onComplete: tweenComplete, 271 | onUpdate: tweenUpdate, 272 | onUpdateArgs: [tweenObject, tweenTo] 273 | }) as Tween; 274 | 275 | return currentTween; 276 | } 277 | 278 | public function cancelTween():void 279 | { 280 | if (currentTween) { 281 | Starling.juggler.remove(currentTween); 282 | currentTween = null; 283 | } 284 | } 285 | 286 | public function isTweening():Boolean 287 | { 288 | return currentTween != null; 289 | } 290 | 291 | protected function tweenUpdate(tweenObject:Object, tweenTo:Object):void 292 | { 293 | // scale tween is much slower then position 294 | 295 | var ratio:Number = tweenObject.ratio; 296 | var r1:Number = tweenTransition(ratio); 297 | var r2:Number = tweenTransition(ratio * 3 <= 1 ? ratio * 3 : 1); // faster ratio 298 | 299 | var currentScale:Number = tweenObject.scale + (tweenTo.scale - tweenObject.scale) * r1; 300 | var currentX:Number = tweenObject.x + (tweenTo.x - tweenObject.x) * r2; 301 | var currentY:Number = tweenObject.y + (tweenTo.y - tweenObject.y) * r2; 302 | 303 | touchSheet.scaleX = touchSheet.scaleY = currentScale; 304 | setCenterXY(currentX, currentY); 305 | } 306 | 307 | protected function tweenComplete():void 308 | { 309 | Starling.juggler.remove(currentTween); 310 | currentTween = null; 311 | } 312 | 313 | //*************************************************************// 314 | //******************** Event Listeners **********************// 315 | //*************************************************************// 316 | 317 | private function onEnterFrame(e:EnterFrameEvent):void 318 | { 319 | update(); 320 | } 321 | 322 | private function onTouch(e:TouchEvent):void 323 | { 324 | var touch:Touch = e.getTouch(this, TouchPhase.MOVED); 325 | if (touch) 326 | cancelTween(); 327 | 328 | touch = e.getTouch(markersContainer, TouchPhase.ENDED); 329 | if (touch) { 330 | var displayObject:DisplayObject = touch.target; 331 | if (displayObject && displayObject.parent == markersContainer) { 332 | var marker:MapMarker = getMarker(displayObject.name); 333 | dispatchEvent(new MapEvent(MapEvent.MARKER_TRIGGERED, false, marker)); 334 | } 335 | } 336 | } 337 | 338 | private function onNativeStageMouseWheel(e:MouseEvent):void 339 | { 340 | var point:Point = globalToLocal(new Point(Starling.current.nativeStage.mouseX, Starling.current.nativeStage.mouseY)); 341 | 342 | if (bounds.containsPoint(point)) { 343 | point.x -= width / 2; 344 | point.y -= height / 2; 345 | 346 | var center:Point = getCenter(); 347 | center.x += point.x / touchSheet.scaleX; 348 | center.y += point.y / touchSheet.scaleY; 349 | 350 | var newScale:Number = touchSheet.scaleX / (e.delta > 0 ? 0.5 : 2); 351 | newScale = Math.max(mapOptions.minimumScale, newScale); 352 | newScale = Math.min(mapOptions.maximumScale, newScale); 353 | tweenTo(center.x, center.y, newScale, .3); 354 | } 355 | } 356 | 357 | private function sortMarkersFunction(d1:DisplayObject, d2:DisplayObject):int 358 | { 359 | return d1.x > d2.x ? 1 : -1; 360 | } 361 | 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /src/cz/j4w/map/MapLayer.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map 2 | { 3 | import feathers.controls.ImageLoader; 4 | 5 | import flash.geom.Rectangle; 6 | import flash.utils.Dictionary; 7 | 8 | import starling.display.BlendMode; 9 | import starling.display.Sprite; 10 | import starling.events.EnterFrameEvent; 11 | 12 | /** 13 | * ... 14 | * @author Jakub Wagner, J4W 15 | */ 16 | public class MapLayer extends Sprite 17 | { 18 | private var mapTilesBuffer:MapTilesBuffer; 19 | private var _options:Object; 20 | protected var map:Map; 21 | protected var id:String; 22 | protected var urlTemplate:String; 23 | protected var tiles:Vector.; 24 | protected var tilesDictionary:Dictionary = new Dictionary(true); 25 | protected var tileSize:int; 26 | 27 | protected var notUsedZoomThreshold:int; 28 | protected var maximumZoom:int; 29 | 30 | public var debugTrace:Boolean = false; 31 | 32 | public function MapLayer(map:Map, id:String, options:Object, buffer:MapTilesBuffer) 33 | { 34 | super(); 35 | this._options = options; 36 | this.id = id; 37 | this.map = map; 38 | this.mapTilesBuffer = buffer; 39 | this.tiles = new Vector.(); 40 | 41 | this.urlTemplate = options.urlTemplate; 42 | if (!this.urlTemplate) { 43 | throw new Error("urlTemplate option is required"); 44 | } 45 | this.notUsedZoomThreshold = options.notUsedZoomThreshold || 0; 46 | this.blendMode = options.blendMode || BlendMode.NORMAL; 47 | this.tileSize = options.tileSize || 256; 48 | this.maximumZoom = options.maximumZoom || 20; 49 | 50 | addEventListener(EnterFrameEvent.ENTER_FRAME, onEnterFrame); 51 | } 52 | 53 | /** 54 | * Check tiles and create new ones if needed. 55 | */ 56 | protected function checkTiles():void 57 | { 58 | var mapViewPort:Rectangle = map.viewPort; 59 | 60 | var zoom:int = map.zoom; 61 | var scale:int = map.scaleRatio; 62 | var actualTileSize:Number = tileSize * scale; 63 | 64 | var startX:int = Math.floor(mapViewPort.left / actualTileSize); 65 | var endX:int = Math.ceil(mapViewPort.right / actualTileSize); 66 | var startY:int = Math.floor(mapViewPort.top / actualTileSize); 67 | var endY:int = Math.ceil(mapViewPort.bottom / actualTileSize); 68 | 69 | var tilesCreated:int = 0; 70 | for (var i:int = startX; i < endX; i += 1) { 71 | for (var j:int = startY; j < endY; j += 1) { 72 | if (createTile(i, j, actualTileSize, zoom, scale)) 73 | tilesCreated++; 74 | } 75 | } 76 | if (debugTrace && tilesCreated) 77 | trace("Created", tilesCreated, "tiles.") 78 | } 79 | 80 | /** 81 | * Check tiles visibility and removes those not visible. 82 | */ 83 | protected function checkNotUsedTiles():void 84 | { 85 | var mapViewPort:Rectangle = map.viewPort.clone(); 86 | var zoom:int = map.zoom; 87 | 88 | var tilesCount:int = 0; 89 | var tilesRemoved:int = 0; 90 | for each (var tile:MapTile in tilesDictionary) { 91 | tilesCount++; 92 | // its outside viewport or its not current zoom 93 | if (!mapViewPort.intersects(tile.bounds) || Math.abs(map.zoom - tile.zoom) > notUsedZoomThreshold) { 94 | removeTile(tile); 95 | tilesRemoved++; 96 | } 97 | } 98 | if (debugTrace && tilesRemoved) 99 | trace("Removed", tilesRemoved, "tiles.") 100 | } 101 | 102 | protected function createTile(x:int, y:int, actualTileSize:Number, zoom:int, scale:int):Boolean 103 | { 104 | var key:String = getKey(x, y, zoom); 105 | 106 | if (tilesDictionary[key]) { 107 | return false; 108 | } 109 | 110 | var url:String = urlTemplate.replace("${z}", maximumZoom - zoom).replace("${x}", x).replace("${y}", y); 111 | 112 | var tile:ImageLoader = mapTilesBuffer.create(x, y, zoom); 113 | tile.source = url; 114 | tile.setSize(tileSize, tileSize); 115 | tile.x = x * actualTileSize; 116 | tile.y = y * actualTileSize; 117 | tile.scaleX = tile.scaleY = scale; 118 | addChild(tile); 119 | 120 | tilesDictionary[key] = tile; 121 | 122 | return true; 123 | } 124 | 125 | protected function removeTile(tile:MapTile):void 126 | { 127 | mapTilesBuffer.release(tile); 128 | tile.removeFromParent(); 129 | 130 | var key:String = getKey(tile.mapX, tile.mapY, tile.zoom); 131 | tilesDictionary[key] = null; 132 | delete tilesDictionary[key]; 133 | } 134 | 135 | [Inline] 136 | 137 | protected function getKey(x:int, y:int, zoom:int):String 138 | { 139 | return x + "x" + y + "x" + zoom; 140 | } 141 | 142 | //*************************************************************// 143 | //******************** Event Listeners **********************// 144 | //*************************************************************// 145 | 146 | private function onEnterFrame(e:EnterFrameEvent):void 147 | { 148 | checkTiles(); 149 | checkNotUsedTiles(); 150 | } 151 | 152 | public function get options():Object 153 | { 154 | return _options; 155 | } 156 | 157 | } 158 | 159 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/MapLayerOptions.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map 2 | { 3 | 4 | /** 5 | * ... 6 | * @author Jakub Wagner, J4W 7 | */ 8 | public class MapLayerOptions 9 | { 10 | public var blendMode:String; 11 | public var index:int = -1; 12 | public var maximumZoom:int; 13 | public var notUsedZoomThreshold:int; 14 | public var tileSize:int; 15 | public var urlTemplate:String; 16 | 17 | public function MapLayerOptions() 18 | { 19 | 20 | } 21 | 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/MapMarker.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map 2 | { 3 | import starling.display.DisplayObject; 4 | 5 | /** 6 | * MapMarker. 7 | * @author Jakub Wagner, J4W 8 | */ 9 | public class MapMarker 10 | { 11 | private var _id:String; 12 | private var _data:Object; 13 | private var _displayObject:DisplayObject; 14 | 15 | public function MapMarker(id:String, displayObject:DisplayObject, data:Object) 16 | { 17 | this._id = id; 18 | this._displayObject = displayObject; 19 | this._data = data; 20 | } 21 | 22 | public function get id():String 23 | { 24 | return _id; 25 | } 26 | 27 | public function get data():Object 28 | { 29 | return _data; 30 | } 31 | 32 | public function get displayObject():DisplayObject 33 | { 34 | return _displayObject; 35 | } 36 | 37 | /* DELEGATE starling.display.DisplayObject */ 38 | 39 | public function get x():Number 40 | { 41 | return displayObject.x; 42 | } 43 | 44 | public function set x(value:Number):void 45 | { 46 | displayObject.x = value; 47 | } 48 | 49 | public function get y():Number 50 | { 51 | return displayObject.y; 52 | } 53 | 54 | public function set y(value:Number):void 55 | { 56 | displayObject.y = value; 57 | } 58 | 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/MapOptions.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map 2 | { 3 | import flash.geom.Point; 4 | import flash.geom.Rectangle; 5 | 6 | /** 7 | * ... 8 | * @author Jakub Wagner, J4W 9 | */ 10 | public class MapOptions 11 | { 12 | public var disableMovement:Boolean; 13 | public var disableRotation:Boolean; 14 | public var disableZooming:Boolean; 15 | public var initialCenter:Point; 16 | public var initialScale:Number 17 | public var maximumScale:Number = 1; 18 | public var minimumScale:Number = 1 / Math.pow(2, 17); 19 | public var movementBounds:Rectangle; 20 | 21 | public function MapOptions() 22 | { 23 | 24 | } 25 | 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/MapTile.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map 2 | { 3 | import feathers.controls.ImageLoader; 4 | 5 | import flash.display.LoaderInfo; 6 | import flash.events.Event; 7 | import flash.geom.Point; 8 | 9 | import starling.core.Starling; 10 | import starling.display.DisplayObject; 11 | 12 | /** 13 | * ... 14 | * @author Jakub Wagner, J4W 15 | */ 16 | public class MapTile extends ImageLoader 17 | { 18 | protected var buffer:MapTilesBuffer; 19 | 20 | public var sourceBackup:Object; 21 | 22 | public var mapX:int; 23 | public var mapY:int; 24 | public var zoom:int; 25 | 26 | public function MapTile(mapX:int, mapY:int, zoom:int, buffer:MapTilesBuffer) 27 | { 28 | super(); 29 | 30 | this.buffer = buffer; 31 | this.zoom = zoom; 32 | this.mapY = mapY; 33 | this.mapX = mapX; 34 | } 35 | 36 | override public function set source(value:Object):void 37 | { 38 | if (!sourceBackup) { 39 | buffer.currentlyBuffering.push(this); 40 | sourceBackup = value; 41 | return; 42 | } 43 | super.source = value; 44 | } 45 | 46 | public function get isDisposed():Boolean 47 | { 48 | return _isDisposed; 49 | } 50 | 51 | override public function hitTest(localPoint:Point):DisplayObject 52 | { 53 | // all tiles are always able to touch 54 | return this; 55 | } 56 | 57 | //*************************************************************// 58 | //******************** Event Listeners **********************// 59 | //*************************************************************// 60 | 61 | override protected function loader_completeHandler(event:Event):void 62 | { 63 | var loaderInfo:LoaderInfo = event.target as LoaderInfo; 64 | if (loaderInfo.bytesTotal == 1113) // empty tile 65 | visible = false; 66 | else { 67 | super.loader_completeHandler(event); 68 | visible = true; 69 | alpha = 0; 70 | Starling.juggler.tween(this, .2, {alpha: 1}); 71 | } 72 | } 73 | 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/MapTilesBuffer.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map 2 | { 3 | import flash.utils.clearInterval; 4 | import flash.utils.setInterval; 5 | 6 | /** 7 | * MapTiles loading buffer and pool. 8 | * @author Jakub Wagner, J4W 9 | */ 10 | public class MapTilesBuffer 11 | { 12 | public var pool:Vector. = new Vector.(); 13 | public var currentlyBuffering:Vector. = new Vector.(); 14 | private var bufferingInterval:uint; 15 | 16 | public function MapTilesBuffer() 17 | { 18 | bufferingInterval = setInterval(checkCurrentlyCommiting, 1000 / 15); 19 | } 20 | 21 | private function checkCurrentlyCommiting():void 22 | { 23 | for (var i:int = 0; i < 5; i++) { 24 | if (currentlyBuffering.length) { 25 | var mapTile:MapTile = currentlyBuffering.shift(); 26 | if (mapTile.isDisposed || !mapTile.sourceBackup) { 27 | i--; 28 | continue; 29 | } 30 | mapTile.source = mapTile.sourceBackup; 31 | } 32 | } 33 | } 34 | 35 | public function create(mapX:int, mapY:int, zoom:int):MapTile 36 | { 37 | var mapTile:MapTile = pool.pop(); 38 | if (!mapTile) { 39 | mapTile = new MapTile(mapX, mapY, zoom, this); 40 | } else { 41 | mapTile.mapX = mapX; 42 | mapTile.mapY = mapY; 43 | mapTile.zoom = zoom; 44 | } 45 | return mapTile; 46 | } 47 | 48 | public function release(mapTile:MapTile):void 49 | { 50 | if (mapTile.source) 51 | mapTile.source = null; 52 | mapTile.sourceBackup = null; 53 | mapTile.visible = false; 54 | pool.push(mapTile); 55 | } 56 | 57 | public function dispose():void 58 | { 59 | var item:MapTile; 60 | for each (item in pool) { 61 | if (!item.isDisposed) 62 | item.dispose(); 63 | } 64 | for each (item in currentlyBuffering) { 65 | if (!item.isDisposed) 66 | item.dispose(); 67 | } 68 | pool.length = 0; 69 | currentlyBuffering.length = 0; 70 | 71 | clearInterval(bufferingInterval); 72 | } 73 | 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/TouchSheet.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map 2 | { 3 | import flash.geom.Point; 4 | import flash.geom.Rectangle; 5 | 6 | import starling.display.DisplayObject; 7 | import starling.display.Sprite; 8 | import starling.events.EnterFrameEvent; 9 | import starling.events.Touch; 10 | import starling.events.TouchEvent; 11 | import starling.events.TouchPhase; 12 | 13 | /** 14 | * ... 15 | * @author Jakub Wagner, J4W 16 | */ 17 | public class TouchSheet extends Sprite 18 | { 19 | private var _disableMovement:Boolean; 20 | private var _disableRotation:Boolean; 21 | private var _disableZooming:Boolean; 22 | private var _minimumScale:Number; 23 | private var _maximumScale:Number; 24 | private var _movementBounds:Rectangle; 25 | private var viewPort:Rectangle; 26 | private var viewportAutoUpdate:Boolean; 27 | private var originalViewPort:Rectangle; 28 | 29 | private var movement:Point = new Point(); 30 | private var decelerationRatio:Number = 0.95; 31 | private var touching:Boolean; 32 | 33 | /** 34 | * Image 35 | * @param contents DisplayObject to insert immiadiatly as a child. 36 | * @param params Object with params... 37 | * 38 | * disableZooming 39 | * disableRotation 40 | * disableMovement 41 | * movementBounds 42 | */ 43 | public function TouchSheet(contents:DisplayObject, viewPort:Rectangle, params:Object = null) 44 | { 45 | this.viewPort = viewPort; 46 | 47 | if (!params) 48 | params = {}; 49 | 50 | disableZooming = params.disableZooming ? true : false; 51 | disableRotation = params.disableRotation ? true : false; 52 | disableMovement = params.disableMovement ? true : false; 53 | movementBounds = params.movementBounds; 54 | viewportAutoUpdate = params.hasOwnProperty("viewportAutoUpdate") && params.viewportAutoUpdate ? true : false; 55 | minimumScale = params.minimumScale; 56 | maximumScale = params.maximumScale; 57 | 58 | if (viewportAutoUpdate) { 59 | originalViewPort = viewPort.clone(); 60 | } 61 | 62 | addEventListener(TouchEvent.TOUCH, onTouch); 63 | addEventListener(EnterFrameEvent.ENTER_FRAME, onEnterFrame); 64 | addChild(contents); 65 | } 66 | 67 | private function onTouch(event:TouchEvent):void 68 | { 69 | var touches:Vector. = event.getTouches(this, TouchPhase.MOVED); 70 | 71 | if (touches.length == 0) { 72 | if (event.getTouch(this, TouchPhase.ENDED)) 73 | touching = false; 74 | } else if (touches.length == 1) { 75 | // one finger touching -> move 76 | touches[0].getMovement(parent, movement); 77 | if (!disableMovement) { 78 | x += movement.x; 79 | y += movement.y; 80 | } 81 | touching = true; 82 | } else if (touches.length == 2) { 83 | // two fingers touching -> rotate and scale 84 | var touchA:Touch = touches[0]; 85 | var touchB:Touch = touches[1]; 86 | 87 | var currentPosA:Point = touchA.getLocation(parent); 88 | var previousPosA:Point = touchA.getPreviousLocation(parent); 89 | var currentPosB:Point = touchB.getLocation(parent); 90 | var previousPosB:Point = touchB.getPreviousLocation(parent); 91 | 92 | var currentVector:Point = currentPosA.subtract(currentPosB); 93 | var previousVector:Point = previousPosA.subtract(previousPosB); 94 | 95 | var currentAngle:Number = Math.atan2(currentVector.y, currentVector.x); 96 | var previousAngle:Number = Math.atan2(previousVector.y, previousVector.x); 97 | var deltaAngle:Number = currentAngle - previousAngle; 98 | 99 | // update pivot point based on previous center 100 | var previousLocalA:Point = touchA.getPreviousLocation(this); 101 | var previousLocalB:Point = touchB.getPreviousLocation(this); 102 | if (!disableMovement && !disableZooming) { 103 | pivotX = (previousLocalA.x + previousLocalB.x) * 0.5; 104 | pivotY = (previousLocalA.y + previousLocalB.y) * 0.5; 105 | 106 | // update location based on the current center 107 | x = (currentPosA.x + currentPosB.x) * 0.5; 108 | y = (currentPosA.y + currentPosB.y) * 0.5; 109 | } 110 | 111 | // rotate 112 | if (!disableRotation) { 113 | rotation += deltaAngle; 114 | } 115 | 116 | // scale 117 | if (!disableZooming) { 118 | var sizeDiff:Number = currentVector.length / previousVector.length; 119 | scaleX *= sizeDiff; 120 | scaleY *= sizeDiff; 121 | if (minimumScale && minimumScale > scaleX) { 122 | scaleX = scaleY = minimumScale; 123 | } 124 | if (maximumScale && maximumScale < scaleX) { 125 | scaleX = scaleY = maximumScale; 126 | } 127 | } 128 | } 129 | applyBounds(); 130 | } 131 | 132 | public function applyBounds():void 133 | { 134 | if (movementBounds) { 135 | if (viewportAutoUpdate) { 136 | viewPort.x = -x; 137 | viewPort.y = -y; 138 | } 139 | if (viewPort.left < movementBounds.left) { 140 | this.x += (viewPort.left - movementBounds.left) * scaleX; 141 | } else if (viewPort.right > movementBounds.right) { 142 | this.x += (viewPort.right - movementBounds.right) * scaleX; 143 | } 144 | 145 | if (viewPort.top < movementBounds.top) { 146 | this.y += (viewPort.top - movementBounds.top) * scaleY; 147 | } else if (viewPort.bottom > movementBounds.bottom) { 148 | this.y += (viewPort.bottom - movementBounds.bottom) * scaleY; 149 | } 150 | } 151 | } 152 | 153 | public override function dispose():void 154 | { 155 | removeEventListener(TouchEvent.TOUCH, onTouch); 156 | super.dispose(); 157 | } 158 | 159 | public function get movementBounds():Rectangle 160 | { 161 | return _movementBounds; 162 | } 163 | 164 | public function set movementBounds(value:Rectangle):void 165 | { 166 | _movementBounds = value; 167 | applyBounds(); 168 | } 169 | 170 | public function get disableMovement():Boolean 171 | { 172 | return _disableMovement; 173 | } 174 | 175 | public function set disableMovement(value:Boolean):void 176 | { 177 | _disableMovement = value; 178 | } 179 | 180 | public function get disableRotation():Boolean 181 | { 182 | return _disableRotation; 183 | } 184 | 185 | public function set disableRotation(value:Boolean):void 186 | { 187 | _disableRotation = value; 188 | } 189 | 190 | public function get disableZooming():Boolean 191 | { 192 | return _disableZooming; 193 | } 194 | 195 | public function set disableZooming(value:Boolean):void 196 | { 197 | _disableZooming = value; 198 | } 199 | 200 | public function get minimumScale():Number 201 | { 202 | return _minimumScale; 203 | } 204 | 205 | public function set minimumScale(value:Number):void 206 | { 207 | _minimumScale = value; 208 | } 209 | 210 | public function get maximumScale():Number 211 | { 212 | return _maximumScale; 213 | } 214 | 215 | public function set maximumScale(value:Number):void 216 | { 217 | _maximumScale = value; 218 | } 219 | 220 | //*************************************************************// 221 | //******************** Event Listeners **********************// 222 | //*************************************************************// 223 | 224 | private function onEnterFrame(e:EnterFrameEvent):void 225 | { 226 | if (!touching) { 227 | movement.x *= decelerationRatio; 228 | movement.y *= decelerationRatio; 229 | x += movement.x; 230 | y += movement.y; 231 | applyBounds(); 232 | } 233 | } 234 | 235 | } 236 | 237 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/events/MapEvent.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map.events 2 | { 3 | import starling.events.Event; 4 | 5 | /** 6 | * ... 7 | * @author Jakub Wagner, J4W 8 | */ 9 | public class MapEvent extends Event 10 | { 11 | static public const MARKER_TRIGGERED:String = "markerTriggered"; 12 | 13 | public function MapEvent(type:String, bubbles:Boolean = false, data:Object = null) 14 | { 15 | super(type, bubbles, data); 16 | } 17 | 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/example/Main.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map.example 2 | { 3 | import flash.display.Sprite; 4 | import flash.display.StageAlign; 5 | import flash.display.StageScaleMode; 6 | import flash.events.Event; 7 | import flash.geom.Rectangle; 8 | 9 | import starling.core.Starling; 10 | 11 | /** 12 | * ... 13 | * @author Jakub Wagner, J4W 14 | */ 15 | public class Main extends Sprite 16 | { 17 | private var starling:Starling; 18 | 19 | public function Main():void 20 | { 21 | addEventListener(Event.ADDED_TO_STAGE, onAddedToStage) 22 | } 23 | 24 | private function initialize():void 25 | { 26 | stage.scaleMode = StageScaleMode.NO_SCALE; 27 | stage.align = StageAlign.TOP_LEFT; 28 | stage.frameRate = 60; 29 | 30 | initStarling(); 31 | } 32 | 33 | /** 34 | * Initialise the Starling sprites 35 | */ 36 | private function initStarling():void 37 | { 38 | Starling.multitouchEnabled = true; 39 | starling = new Starling(MainStarling, stage, new Rectangle(0, 0, stage.stageWidth, stage.stageHeight)); 40 | starling.simulateMultitouch = true; 41 | starling.start(); 42 | } 43 | 44 | //*************************************************************// 45 | //******************** Event Listeners **********************// 46 | //*************************************************************// 47 | 48 | private function onAddedToStage(e:Event):void 49 | { 50 | removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage); 51 | initialize(); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/example/MainStarling.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map.example 2 | { 3 | import cz.j4w.map.MapLayerOptions; 4 | import cz.j4w.map.MapOptions; 5 | import cz.j4w.map.events.MapEvent; 6 | import cz.j4w.map.geo.GeoMap; 7 | import cz.j4w.map.geo.GeoUtils; 8 | import cz.j4w.map.geo.Maps; 9 | 10 | import feathers.core.FeathersControl; 11 | import feathers.layout.HorizontalAlign; 12 | import feathers.layout.VerticalAlign; 13 | 14 | import flash.geom.Point; 15 | 16 | import starling.core.Starling; 17 | import starling.display.Image; 18 | import starling.display.Quad; 19 | import starling.textures.Texture; 20 | 21 | /** 22 | * Map test. 23 | * @author Jakub Wagner, J4W 24 | */ 25 | public class MainStarling extends FeathersControl 26 | { 27 | [Embed(source="marker.png")] 28 | private var MarkerClass:Class; 29 | 30 | private var background:Quad; 31 | 32 | public function MainStarling() 33 | { 34 | super(); 35 | } 36 | 37 | override protected function initialize():void 38 | { 39 | super.initialize(); 40 | Starling.current.showStats = true; 41 | Starling.current.skipUnchangedFrames = true; 42 | 43 | var mapScale:Number = 2; // use 1 for non-retina displays 44 | GeoUtils.scale = mapScale; 45 | 46 | var mapOptions:MapOptions = new MapOptions(); 47 | mapOptions.initialCenter = new Point(14.4777357, 50.1017711); 48 | mapOptions.initialScale = 1 / 32 / mapScale; 49 | mapOptions.disableRotation = true; 50 | 51 | var geoMap:GeoMap = new GeoMap(mapOptions); 52 | geoMap.setSize(stage.stageWidth - 100, stage.stageHeight - 100); 53 | geoMap.x = geoMap.y = 50; 54 | addChild(geoMap); 55 | 56 | var googleMaps:MapLayerOptions = Maps.GOOGLE_MAPS_SCALED(mapScale); 57 | googleMaps.notUsedZoomThreshold = 1; 58 | geoMap.addLayer("googleMaps", googleMaps); 59 | 60 | var markerTexture:Texture = Texture.fromEmbeddedAsset(MarkerClass); 61 | 62 | for (var i:int = 0; i < 100; i++) { 63 | var image:Image = new Image(markerTexture); 64 | image.alignPivot(HorizontalAlign.CENTER, VerticalAlign.BOTTOM); 65 | 66 | geoMap.addMarkerLongLat("marker" + i, mapOptions.initialCenter.x + .1 - Math.random() * .2, mapOptions.initialCenter.y + .1 - Math.random() * .2, image); 67 | } 68 | geoMap.addEventListener(MapEvent.MARKER_TRIGGERED, onGeoMapMarkerTriggered); 69 | } 70 | 71 | private function onGeoMapMarkerTriggered(e:MapEvent):void 72 | { 73 | trace(e.target); 74 | } 75 | 76 | } 77 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/example/marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/pizza-app/be3a88cbbcc2c86d8e4c51e6771648169f414f15/src/cz/j4w/map/example/marker.png -------------------------------------------------------------------------------- /src/cz/j4w/map/geo/GeoMap.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map.geo 2 | { 3 | import cz.j4w.map.Map; 4 | import cz.j4w.map.MapMarker; 5 | import cz.j4w.map.MapOptions; 6 | 7 | import flash.geom.Point; 8 | import flash.geom.Rectangle; 9 | 10 | import starling.animation.Tween; 11 | import starling.display.DisplayObject; 12 | 13 | /** 14 | * Geo maps. Implements position methods with longitude and latitude. 15 | * @author Jakub Wagner, J4W 16 | */ 17 | public class GeoMap extends Map 18 | { 19 | 20 | public function GeoMap(mapOptions:MapOptions) 21 | { 22 | var m:Rectangle = mapOptions.movementBounds; 23 | if (m) { 24 | // movements bounds are supposed to be lon/lat coords 25 | m.left = GeoUtils.lon2x(m.left); 26 | m.top = GeoUtils.lat2y(m.top); 27 | m.right = GeoUtils.lon2x(m.right); 28 | m.bottom = GeoUtils.lat2y(m.bottom); 29 | } 30 | super(mapOptions); 31 | } 32 | 33 | override protected function initialize():void 34 | { 35 | super.initialize(); 36 | 37 | if (mapOptions.initialCenter) 38 | setCenterLongLat(mapOptions.initialCenter.x, mapOptions.initialCenter.y); 39 | } 40 | 41 | public function addMarkerLongLat(id:String, long:Number, lat:Number, displayObject:DisplayObject, data:Object = null):MapMarker 42 | { 43 | return addMarker(id, GeoUtils.lon2x(long), GeoUtils.lat2y(lat), displayObject, data); 44 | } 45 | 46 | public function getCenterLongLat():Point 47 | { 48 | var center:Point = getCenter(); 49 | center.x = GeoUtils.x2lon(center.x); 50 | center.y = GeoUtils.y2lat(center.y); 51 | return center; 52 | } 53 | 54 | public function setCenterLongLat(long:Number, lat:Number):void 55 | { 56 | setCenterXY(GeoUtils.lon2x(long), GeoUtils.lat2y(lat)); 57 | } 58 | 59 | public function tweenToLongLat(long:Number, lat:Number, scale:Number = 1, time:Number = 3):Tween 60 | { 61 | return tweenTo(GeoUtils.lon2x(long), GeoUtils.lat2y(lat), scale, time); 62 | } 63 | 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/geo/GeoUtils.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map.geo 2 | { 3 | import flash.geom.Point; 4 | 5 | /** 6 | * Geographical utils using mercator projection algorithms. 7 | */ 8 | public class GeoUtils 9 | { 10 | public static const DEG_RAD:Number = PI / 180; 11 | public static const RAD_DEG:Number = 180 / PI; 12 | private static const PI:Number = Math.PI; 13 | 14 | public static const EARTH_RADIUS:uint = 6371000; // in meters 15 | 16 | private static const COS_1_EARTH_RADIUS:Number = Math.cos(1 / EARTH_RADIUS); 17 | private static const SIN_1_EARTH_RADIUS:Number = Math.sin(1 / EARTH_RADIUS); 18 | 19 | public static var MAX_LONGITUDE:uint = 67108864 * 2; 20 | public static var MAX_LATITUDE:uint = 67108864 * 2; 21 | 22 | private static var C_LONGITUDE:Number = 360 / MAX_LONGITUDE; 23 | private static var C_LATITUDE:Number = 2 * PI / MAX_LATITUDE; 24 | private static var C_LATITUDE2:Number = MAX_LATITUDE / 2; 25 | 26 | public static function set scale(value:int):void 27 | { 28 | MAX_LONGITUDE = 67108864 * 2 * value; 29 | MAX_LATITUDE = 67108864 * 2 * value; 30 | C_LONGITUDE = 360 / MAX_LONGITUDE; 31 | C_LATITUDE = 2 * PI / MAX_LATITUDE; 32 | C_LATITUDE2 = MAX_LATITUDE / 2; 33 | } 34 | 35 | /** 36 | * Converts x coordinate to lon. 37 | */ 38 | public static function x2lon(x:Number):Number 39 | { 40 | return x * C_LONGITUDE - 180; 41 | } 42 | 43 | /** 44 | * Converts lon to x coordinate. 45 | */ 46 | public static function lon2x(lon:Number):Number 47 | { 48 | return (lon + 180) / C_LONGITUDE 49 | } 50 | 51 | /** 52 | * Converts y coordinate to lat. 53 | */ 54 | public static function y2lat(y:Number):Number 55 | { 56 | return Math.atan(sinh(PI - (C_LATITUDE * y))) * RAD_DEG; 57 | } 58 | 59 | /** 60 | * Converts lat to y coordinate. 61 | */ 62 | public static function lat2y(lat:Number):Number 63 | { 64 | var latRad:Number = lat * DEG_RAD; 65 | return (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / PI) * C_LATITUDE2; 66 | } 67 | 68 | /** 69 | * Returns the hyperbolic sine of value. 70 | */ 71 | public static function sinh(value:Number):Number 72 | { 73 | return (Math.exp(value) - Math.exp(-value)) / 2; 74 | } 75 | 76 | /** 77 | * Returns distance in meters. 78 | */ 79 | public static function distance(lon1:Number, lat1:Number, lon2:Number, lat2:Number):Number 80 | { 81 | var v:Number = Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1) + Math.sin(lat1) * Math.sin(lat2); 82 | return v > 1 ? 0 : EARTH_RADIUS * Math.acos(v); 83 | } 84 | 85 | /** 86 | * Returns distance using deg parameters. 87 | */ 88 | public static function distanceDeg(lon1:Number, lat1:Number, lon2:Number, lat2:Number):Number 89 | { 90 | return distance(lon1 * DEG_RAD, lat1 * DEG_RAD, lon2 * DEG_RAD, lat2 * DEG_RAD); 91 | } 92 | 93 | /** 94 | * Given a start point, initial bearing, and distance, this will 95 | * calculate the destination point and final bearing travelling along 96 | * a (shortest distance) great circle arc. 97 | * http://www.movable-type.co.uk/scripts/latlong.html 98 | * 99 | * @distance in meters 100 | */ 101 | 102 | public static function destination(lon:Number, lat:Number, bearing:Number, distance:Number):Point 103 | { 104 | var a:Number = distance / EARTH_RADIUS; 105 | var lat2:Number = Math.asin(Math.sin(lat) * Math.cos(a) + Math.cos(lat) * Math.sin(a) * Math.cos(bearing)); 106 | var lon2:Number = lon + Math.atan2(Math.sin(bearing) * Math.sin(a) * Math.cos(lat), Math.cos(a) - Math.sin(lat) * Math.sin(lat2)); 107 | return new Point(lon2, lat2); 108 | } 109 | 110 | /** 111 | * Returns destination point using deg parameters. 112 | */ 113 | public static function destionationDeg(lon:Number, lat:Number, bearing:Number, distance:Number):Point 114 | { 115 | var result:Point = destination(lon * DEG_RAD, lat * DEG_RAD, bearing * DEG_RAD, distance); 116 | result.x *= RAD_DEG; 117 | result.y *= RAD_DEG; 118 | return result; 119 | } 120 | 121 | /** 122 | * Returns lat coordinate at specific distance and 90 degree angle. 123 | */ 124 | public static function lonAtDistanceDeg(lon:Number, lat:Number, distance:Number):Number 125 | { 126 | return destionationDeg(lon, lat, 90, distance).x; 127 | } 128 | 129 | /** 130 | * Returns latitude at distance from original point. 131 | */ 132 | public static function latAtDistanceDeg(lat:Number, distance:Number):Number 133 | { 134 | lat *= DEG_RAD; 135 | var a:Number = distance / EARTH_RADIUS; 136 | return Math.asin(Math.sin(lat) * Math.cos(a) + Math.cos(lat) * Math.sin(a)) * RAD_DEG; 137 | } 138 | 139 | /** 140 | * Returns pixels per meter in specific geo location. 141 | */ 142 | public static function pixelsPerMeter(lat:Number):Number 143 | { 144 | var result:Number = lat2y(lat) - lat2y(Math.asin(Math.sin(lat * DEG_RAD) * COS_1_EARTH_RADIUS + Math.cos(lat * DEG_RAD) * SIN_1_EARTH_RADIUS) * RAD_DEG); 145 | return result < 0 ? -result : result; 146 | } 147 | 148 | /** 149 | * Returns pixels per meter in specific coordinates. 150 | */ 151 | public static function pixelPerMeterByCenter(center:Point):Number 152 | { 153 | return pixelsPerMeter(y2lat(center.y)); 154 | } 155 | 156 | /** 157 | * Returns pixels per meter using destination algorithm. 158 | */ 159 | public static function pixelsPerMeterPrecise(lon:Number, lat:Number):Number 160 | { 161 | var destination:Point = destionationDeg(lon, lat, 45, 1); 162 | var x:Number = lon2x(lon) - lon2x(destination.x); 163 | var y:Number = lat2y(lat) - lat2y(destination.y); 164 | return Math.sqrt(x * x + y * y); 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /src/cz/j4w/map/geo/Maps.as: -------------------------------------------------------------------------------- 1 | package cz.j4w.map.geo 2 | { 3 | import cz.j4w.map.MapLayerOptions; 4 | 5 | /** 6 | * This class provides implementation of some tile providers 7 | * (mapquest, openstreetmaps, cloudmade, arcgis) 8 | */ 9 | public class Maps 10 | { 11 | 12 | public static function get GOOGLE_MAPS():MapLayerOptions 13 | { 14 | return createMapLayerOptions(["http://mt1.google.com/vt/lyrs=r&x=${x}&y=${y}&z=${z}"]); 15 | } 16 | 17 | public static function GOOGLE_MAPS_SCALED(scale:int):MapLayerOptions 18 | { 19 | var googleMaps:MapLayerOptions = createMapLayerOptions(["http://mt1.google.com/vt/lyrs=r&x=${x}&y=${y}&z=${z}"]); 20 | googleMaps.urlTemplate += "&scale=" + scale; 21 | googleMaps.tileSize = 256 * scale; 22 | return googleMaps; 23 | } 24 | 25 | public static function get GOOGLE_MAPS_HYBRID():MapLayerOptions 26 | { 27 | return createMapLayerOptions(["http://mt1.google.com/vt/lyrs=y&x=${x}&y=${y}&z=${z}"]); 28 | } 29 | 30 | public static function get GOOGLE_MAPS_SATELLITE():MapLayerOptions 31 | { 32 | return createMapLayerOptions(["http://mt1.google.com/vt/lyrs=s&x=${x}&y=${y}&z=${z}"]); 33 | } 34 | 35 | public static function get MAPQUEST():MapLayerOptions 36 | { 37 | return createMapLayerOptions(["http://otile1.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png", "http://otile2.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png", "http://otile3.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png", "http://otile4.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png"]); 38 | } 39 | 40 | public static function get OSM():MapLayerOptions 41 | { 42 | return createMapLayerOptions(["http://a.tile.openstreetmap.org/${z}/${x}/${y}.png", "http://b.tile.openstreetmap.org/${z}/${x}/${y}.png", "http://c.tile.openstreetmap.org/${z}/${x}/${y}.png"]); 43 | } 44 | 45 | public static function get MAPBOX():MapLayerOptions 46 | { 47 | return createMapLayerOptions(["http://a.tiles.mapbox.com/v3/examples.map-vyofok3q/${z}/${x}/${y}.png", "http://b.tiles.mapbox.com/v3/examples.map-vyofok3q/${z}/${x}/${y}.png", "http://c.tiles.mapbox.com/v3/examples.map-vyofok3q/${z}/${x}/${y}.png", "http://d.tiles.mapbox.com/v3/examples.map-vyofok3q/${z}/${x}/${y}.png"]); 48 | } 49 | 50 | public static function get CLOUDMADE():MapLayerOptions 51 | { 52 | return createMapLayerOptions(["http://a.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/998/256/${z}/${x}/${y}.png", "http://b.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/998/256/${z}/${x}/${y}.png", "http://c.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/998/256/${z}/${x}/${y}.png"]); 53 | } 54 | 55 | public static function get ARCGIS_IMAGERY():MapLayerOptions 56 | { 57 | return createMapLayerOptions(["http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/${z}/${y}/${x}.png", "http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/${z}/${y}/${x}.png"]); 58 | } 59 | 60 | public static function get ARCGIS_NATIONAL_GEOGRAPHIC():MapLayerOptions 61 | { 62 | return createMapLayerOptions(["http://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/${z}/${y}/${x}.png", "http://services.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/${z}/${y}/${x}.png"]); 63 | } 64 | 65 | public static function get ARCGIS_REFERENCE():MapLayerOptions 66 | { 67 | return createMapLayerOptions(["http://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/${z}/${y}/${x}.png", "http://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/${z}/${y}/${x}.png"]); 68 | } 69 | 70 | private static function createMapLayerOptions(templates:Array):MapLayerOptions 71 | { 72 | var result:MapLayerOptions = new MapLayerOptions; 73 | result.urlTemplate = templates[0]; // TODO: do not use only first url 74 | result.tileSize = 256; 75 | return result; 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/galleryScreens/GalleryScreen.as: -------------------------------------------------------------------------------- 1 | package galleryScreens 2 | { 3 | 4 | import feathers.controls.Alert; 5 | import feathers.controls.Button; 6 | import feathers.controls.ImageLoader; 7 | import feathers.controls.List; 8 | import feathers.controls.PanelScreen; 9 | import feathers.controls.TabBar; 10 | import feathers.controls.renderers.DefaultListItemRenderer; 11 | import feathers.data.ListCollection; 12 | import feathers.events.FeathersEventType; 13 | import feathers.layout.AnchorLayout; 14 | import feathers.layout.AnchorLayoutData; 15 | 16 | import flash.events.Event; 17 | import flash.events.IOErrorEvent; 18 | import flash.net.URLLoader; 19 | import flash.net.URLRequest; 20 | import flash.net.URLRequestHeader; 21 | import flash.net.URLRequestMethod; 22 | 23 | import starling.display.DisplayObject; 24 | import starling.events.Event; 25 | 26 | import utils.NavigatorData; 27 | import utils.ProfileManager; 28 | 29 | public class GalleryScreen extends PanelScreen 30 | { 31 | public static const GO_IMAGE_DETAILS:String = "goImageDetails"; 32 | public static const GO_LOGIN:String = "goLogin"; 33 | public static const GO_UPLOAD:String = "goUpload"; 34 | 35 | private var alert:Alert; 36 | private var imagesList:List; 37 | private var tabBar:TabBar; 38 | 39 | protected var _data:NavigatorData; 40 | 41 | public function get data():NavigatorData 42 | { 43 | return this._data; 44 | } 45 | 46 | public function set data(value:NavigatorData):void 47 | { 48 | this._data = value; 49 | } 50 | 51 | override protected function initialize():void 52 | { 53 | super.initialize(); 54 | 55 | this.layout = new AnchorLayout(); 56 | 57 | var menuButton:Button = new Button(); 58 | menuButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 59 | { 60 | dispatchEventWith(Main.OPEN_MENU); 61 | }); 62 | menuButton.styleNameList.add("menu-button"); 63 | this.headerProperties.leftItems = new [menuButton]; 64 | 65 | var uploadIcon:ImageLoader = new ImageLoader(); 66 | uploadIcon.source = "assets/icons/upload.png"; 67 | uploadIcon.width = uploadIcon.height = 25; 68 | 69 | var uploadButton:Button = new Button(); 70 | uploadButton.addEventListener(starling.events.Event.TRIGGERED, checkProfile); 71 | uploadButton.styleNameList.add("header-button"); 72 | uploadButton.defaultIcon = uploadIcon; 73 | this.headerProperties.rightItems = new [uploadButton]; 74 | 75 | imagesList = new List(); 76 | imagesList.addEventListener(starling.events.Event.CHANGE, changeHandler); 77 | imagesList.layoutData = new AnchorLayoutData(0, 0, 50, 0, NaN, NaN); 78 | imagesList.itemRendererFactory = function ():DefaultListItemRenderer 79 | { 80 | var renderer:DefaultListItemRenderer = new DefaultListItemRenderer(); 81 | renderer.isQuickHitAreaEnabled = true; 82 | 83 | renderer.labelFunction = function (item:Object):String 84 | { 85 | return "" + item.title + "" + "\n" + new Date(Number(item.timestamp)).toLocaleDateString() + "\n" + item.views + " views"; 86 | } 87 | 88 | renderer.iconSourceFunction = function (item:Object):String 89 | { 90 | return Constants.FIREBASE_STORAGE_URL + formatUrl(item.thumb_url) + "?alt=media"; 91 | } 92 | 93 | renderer.iconLoaderFactory = function ():ImageLoader 94 | { 95 | var loader:ImageLoader = new ImageLoader(); 96 | loader.width = loader.height = 70; 97 | return loader; 98 | } 99 | 100 | return renderer; 101 | }; 102 | this.addChild(imagesList); 103 | 104 | var popularIcon:ImageLoader = new ImageLoader(); 105 | popularIcon.width = popularIcon.height = 25; 106 | popularIcon.source = "assets/icons/star.png"; 107 | 108 | var popularIconSelected:ImageLoader = new ImageLoader(); 109 | popularIconSelected.alpha = 0.75; 110 | popularIconSelected.width = popularIconSelected.height = 25; 111 | popularIconSelected.source = "assets/icons/star.png"; 112 | 113 | var recentIcon:ImageLoader = new ImageLoader(); 114 | recentIcon.width = recentIcon.height = 25; 115 | recentIcon.source = "assets/icons/date.png"; 116 | 117 | var recentIconSelected:ImageLoader = new ImageLoader(); 118 | recentIconSelected.alpha = 0.75; 119 | recentIconSelected.width = recentIconSelected.height = 25; 120 | recentIconSelected.source = "assets/icons/date.png"; 121 | 122 | tabBar = new TabBar(); 123 | tabBar.layoutData = new AnchorLayoutData(NaN, 0, 0, 0, NaN, NaN); 124 | tabBar.dataProvider = new ListCollection( 125 | [ 126 | { 127 | label: "", 128 | data: "popular", 129 | defaultIcon: popularIcon, 130 | defaultSelectedIcon: popularIconSelected, 131 | downIcon: popularIconSelected 132 | }, 133 | { 134 | label: "", 135 | data: "latest", 136 | defaultIcon: recentIcon, 137 | defaultSelectedIcon: recentIconSelected, 138 | downIcon: recentIconSelected 139 | } 140 | ]); 141 | tabBar.addEventListener(starling.events.Event.CHANGE, loadGallery); 142 | this.addChild(tabBar); 143 | 144 | this.addEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 145 | } 146 | 147 | private function transitionComplete(event:starling.events.Event):void 148 | { 149 | this.removeEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 150 | loadGallery(); 151 | } 152 | 153 | private function loadGallery():void 154 | { 155 | var request:URLRequest = new URLRequest(Constants.FIREBASE_IMAGES_GALLERY_URL + '.json?orderBy="status"&equalTo="approved"'); 156 | 157 | var loader:URLLoader = new URLLoader(); 158 | loader.addEventListener(flash.events.Event.COMPLETE, galleryLoaded); 159 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 160 | loader.load(request); 161 | } 162 | 163 | private function galleryLoaded(event:flash.events.Event):void 164 | { 165 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, galleryLoaded); 166 | 167 | //The JSON generated by Firebase contains the id as the node key, we use this function to add it to our Objects 168 | 169 | var rawData:Object = JSON.parse(event.currentTarget.data); 170 | var imagesArray:Array = new Array(); 171 | 172 | for (var parent:String in rawData) { 173 | var tempObject:Object = new Object(); 174 | tempObject.id = parent; 175 | 176 | for (var child:* in rawData[parent]) { 177 | tempObject[child] = rawData[parent][child]; 178 | } 179 | 180 | imagesArray.push(tempObject); 181 | tempObject = null; 182 | } 183 | 184 | if (tabBar.selectedItem.data == "popular") { 185 | this.title = "Most Popular"; 186 | imagesArray.sortOn("views", 2); 187 | } else if (tabBar.selectedItem.data == "latest") { 188 | this.title = "Latest Submissions"; 189 | imagesArray.sortOn("timestamp", 2); 190 | } else { 191 | //Nothing 192 | } 193 | 194 | imagesList.dataProvider = new ListCollection(imagesArray); 195 | } 196 | 197 | private function changeHandler(event:starling.events.Event):void 198 | { 199 | _data["selectedImage"] = imagesList.selectedItem; 200 | this.dispatchEventWith(GO_IMAGE_DETAILS); 201 | } 202 | 203 | private function checkProfile(event:starling.events.Event):void 204 | { 205 | if (ProfileManager.isLoggedIn() === false) { 206 | alert = Alert.show("This feature requires that you are signed in, proceed to Sign In process?", "Sign In Required", new ListCollection( 207 | [ 208 | {label: "Cancel"}, 209 | {label: "OK"} 210 | ])); 211 | 212 | alert.addEventListener(starling.events.Event.CLOSE, function (event:starling.events.Event, data:Object):void 213 | { 214 | if (data.label == "OK") { 215 | dispatchEventWith(GO_LOGIN); 216 | } 217 | }); 218 | } else { 219 | getAccessToken(); 220 | } 221 | } 222 | 223 | private function getAccessToken():void 224 | { 225 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 226 | 227 | var myObject:Object = new Object(); 228 | myObject.grant_type = "refresh_token"; 229 | myObject.refresh_token = Main.profile.refreshToken; 230 | 231 | var request:URLRequest = new URLRequest(Constants.FIREBASE_AUTH_TOKEN_URL); 232 | request.method = URLRequestMethod.POST; 233 | request.data = JSON.stringify(myObject); 234 | request.requestHeaders.push(header); 235 | 236 | var loader:URLLoader = new URLLoader(); 237 | loader.addEventListener(flash.events.Event.COMPLETE, accessTokenLoaded); 238 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 239 | loader.load(request); 240 | } 241 | 242 | private function accessTokenLoaded(event:flash.events.Event):void 243 | { 244 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, accessTokenLoaded); 245 | 246 | var rawData:Object = JSON.parse(event.currentTarget.data); 247 | 248 | //VERY IMPORTANT: Yhis token will be used to authenticate with the Firebase realtime database 249 | _data.FirebaseAuthToken = rawData.access_token; 250 | this.dispatchEventWith(GO_UPLOAD); 251 | } 252 | 253 | private function errorHandler(event:IOErrorEvent):void 254 | { 255 | trace(event.currentTarget.data); 256 | } 257 | 258 | private function formatUrl(url:String):String 259 | { 260 | //Firebase Storage / Google Cloud Storage requires that the slashes (/) are URLEncoded 261 | return url.replace(/\//g, "%2F"); 262 | } 263 | 264 | override public function dispose():void 265 | { 266 | if (alert) { 267 | alert.removeFromParent(true); 268 | } 269 | 270 | super.dispose(); 271 | } 272 | 273 | } 274 | } -------------------------------------------------------------------------------- /src/newsScreens/HomeScreen.as: -------------------------------------------------------------------------------- 1 | package newsScreens 2 | { 3 | import feathers.controls.Button; 4 | import feathers.controls.Label; 5 | import feathers.controls.LayoutGroup; 6 | import feathers.controls.List; 7 | import feathers.controls.PanelScreen; 8 | import feathers.data.ListCollection; 9 | import feathers.events.FeathersEventType; 10 | import feathers.layout.AnchorLayout; 11 | import feathers.layout.AnchorLayoutData; 12 | 13 | import flash.events.Event; 14 | import flash.net.URLLoader; 15 | import flash.net.URLRequest; 16 | 17 | import renderers.NewsRenderer; 18 | 19 | import starling.animation.Tween; 20 | import starling.core.Starling; 21 | import starling.display.DisplayObject; 22 | import starling.display.Quad; 23 | import starling.events.Event; 24 | 25 | import utils.NavigatorData; 26 | 27 | public class HomeScreen extends PanelScreen 28 | { 29 | public static const GO_NEWS_DETAILS:String = "goNewsDetails"; 30 | 31 | private var mainGroup:LayoutGroup; 32 | private var pizzaNewsLabel:Label; 33 | private var newsList:List; 34 | 35 | protected var _data:NavigatorData; 36 | 37 | public function get data():NavigatorData 38 | { 39 | return this._data; 40 | } 41 | 42 | public function set data(value:NavigatorData):void 43 | { 44 | this._data = value; 45 | } 46 | 47 | override protected function initialize():void 48 | { 49 | super.initialize(); 50 | 51 | this.title = "Local Pizza News"; 52 | this.layout = new AnchorLayout(); 53 | 54 | var menuButton:Button = new Button(); 55 | menuButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 56 | { 57 | dispatchEventWith(Main.OPEN_MENU); 58 | }); 59 | menuButton.styleNameList.add("menu-button"); 60 | this.headerProperties.leftItems = new [menuButton]; 61 | 62 | mainGroup = new LayoutGroup(); 63 | mainGroup.alpha = 0; 64 | mainGroup.layoutData = new AnchorLayoutData(100, NaN, 10, NaN, 0, 0); 65 | mainGroup.backgroundSkin = new Quad(3, 3, 0xFFFFFF); 66 | this.addChild(mainGroup); 67 | 68 | this.addEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 69 | } 70 | 71 | private function transitionComplete(event:starling.events.Event):void 72 | { 73 | this.removeEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 74 | 75 | pizzaNewsLabel = new Label(); 76 | pizzaNewsLabel.alpha = 0; 77 | pizzaNewsLabel.styleNameList.add("big-label"); 78 | pizzaNewsLabel.text = "Pizza News"; 79 | pizzaNewsLabel.layoutData = new AnchorLayoutData(10, NaN, NaN, NaN, -50, NaN); 80 | this.addChild(pizzaNewsLabel); 81 | 82 | var pizzaNewsLabelFade:Tween = new Tween(pizzaNewsLabel, 0.5); 83 | pizzaNewsLabelFade.animate("alpha", 1); 84 | Starling.juggler.add(pizzaNewsLabelFade); 85 | 86 | var pizzaNewsLabelSlide:Tween = new Tween(pizzaNewsLabel.layoutData, 0.5); 87 | pizzaNewsLabelSlide.animate("horizontalCenter", 0); 88 | Starling.juggler.add(pizzaNewsLabelSlide); 89 | 90 | newsList = new List(); 91 | newsList.addEventListener(starling.events.Event.CHANGE, changeHandler); 92 | newsList.itemRendererType = NewsRenderer; 93 | newsList.layoutData = new AnchorLayoutData(90, 10, 10, 10, 0, NaN); 94 | 95 | var listTween:Tween = new Tween(mainGroup, 0.3); 96 | listTween.animate("alpha", 1.0); 97 | listTween.animate("width", stage.stageWidth - 20); 98 | listTween.onComplete = function ():void 99 | { 100 | Starling.juggler.remove(listTween); 101 | addChild(newsList); 102 | loadNews(); 103 | }; 104 | Starling.juggler.add(listTween); 105 | } 106 | 107 | private function loadNews():void 108 | { 109 | var request:URLRequest = new URLRequest("https://news.google.com/news/feeds?q=pizza"); 110 | 111 | var loader:URLLoader = new URLLoader(); 112 | loader.addEventListener(flash.events.Event.COMPLETE, newsLoaded); 113 | loader.load(request); 114 | } 115 | 116 | private function newsLoaded(event:flash.events.Event):void 117 | { 118 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, newsLoaded); 119 | 120 | removeChild(mainGroup, true); 121 | 122 | var myXMLList:XMLList = new XMLList(event.currentTarget.data); 123 | newsList.dataProvider = new ListCollection(myXMLList.channel.item); 124 | } 125 | 126 | private function changeHandler(event:starling.events.Event):void 127 | { 128 | _data.selectedNews = newsList.selectedItem; 129 | this.dispatchEventWith(GO_NEWS_DETAILS); 130 | } 131 | 132 | override public function dispose():void 133 | { 134 | super.dispose(); 135 | } 136 | 137 | } 138 | } -------------------------------------------------------------------------------- /src/newsScreens/NewsDetailsScreen.as: -------------------------------------------------------------------------------- 1 | package newsScreens 2 | { 3 | import feathers.controls.Button; 4 | import feathers.controls.LayoutGroup; 5 | import feathers.controls.PanelScreen; 6 | import feathers.controls.WebView; 7 | import feathers.events.FeathersEventType; 8 | import feathers.layout.AnchorLayout; 9 | import feathers.layout.AnchorLayoutData; 10 | 11 | import starling.display.DisplayObject; 12 | import starling.events.Event; 13 | 14 | import utils.NavigatorData; 15 | import utils.RoundedRect; 16 | 17 | public class NewsDetailsScreen extends PanelScreen 18 | { 19 | 20 | private var mainGroup:LayoutGroup; 21 | private var webView:WebView; 22 | 23 | protected var _data:NavigatorData; 24 | 25 | public function get data():NavigatorData 26 | { 27 | return this._data; 28 | } 29 | 30 | public function set data(value:NavigatorData):void 31 | { 32 | this._data = value; 33 | } 34 | 35 | override protected function initialize():void 36 | { 37 | super.initialize(); 38 | 39 | this.title = "News Details"; 40 | this.layout = new AnchorLayout(); 41 | 42 | var backButton:Button = new Button(); 43 | backButton.styleNameList.add("back-button"); 44 | backButton.addEventListener(Event.TRIGGERED, goBack); 45 | this.headerProperties.leftItems = new [backButton]; 46 | 47 | mainGroup = new LayoutGroup(); 48 | mainGroup.layoutData = new AnchorLayoutData(10, 10, 10, 10, NaN, NaN); 49 | mainGroup.backgroundSkin = RoundedRect.createRoundedRect(0xFFFFFF); 50 | this.addChild(mainGroup); 51 | 52 | this.addEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 53 | } 54 | 55 | private function transitionComplete(event:Event):void 56 | { 57 | this.removeEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 58 | 59 | webView = new WebView(); 60 | webView.layoutData = new AnchorLayoutData(20, 20, 20, 20, NaN, NaN); 61 | this.addChild(webView); 62 | webView.loadString(_data.selectedNews.description); 63 | } 64 | 65 | private function goBack():void 66 | { 67 | this.removeChild(webView, true); 68 | this.dispatchEventWith(Event.COMPLETE); 69 | } 70 | 71 | } 72 | } -------------------------------------------------------------------------------- /src/renderers/BusinessRenderer.as: -------------------------------------------------------------------------------- 1 | package renderers 2 | { 3 | import feathers.controls.ImageLoader; 4 | import feathers.controls.Label; 5 | import feathers.controls.renderers.LayoutGroupListItemRenderer; 6 | import feathers.layout.AnchorLayout; 7 | import feathers.layout.AnchorLayoutData; 8 | import feathers.utils.touch.DelayedDownTouchToState; 9 | import feathers.utils.touch.TapToSelect; 10 | 11 | import starling.display.Quad; 12 | import starling.text.TextFormat; 13 | 14 | public class BusinessRenderer extends LayoutGroupListItemRenderer 15 | { 16 | private var _ratingLabel:Label; 17 | private var _ratingImage:ImageLoader; 18 | private var _businessLabel:Label; 19 | private var _businessImage:ImageLoader 20 | private var _select:TapToSelect; 21 | private var _delay:DelayedDownTouchToState; 22 | 23 | public function BusinessRenderer() 24 | { 25 | super(); 26 | this._select = new TapToSelect(this); 27 | this._delay = new DelayedDownTouchToState(this, changeState); 28 | } 29 | 30 | private function changeState(currentState:String):void 31 | { 32 | if(this._data) 33 | { 34 | if(currentState == "up") 35 | { 36 | if(this.isSelected) 37 | { 38 | _businessLabel.fontStyles.color = 0xFFFFFF; 39 | _ratingLabel.fontStyles.color = 0xFFFFFF; 40 | } else { 41 | this.backgroundSkin = new Quad(3, 3, 0xFFFFFF); 42 | _businessLabel.fontStyles.color = 0x000000; 43 | _ratingLabel.fontStyles.color = 0x000000; 44 | } 45 | } 46 | 47 | else if(currentState == "down") 48 | { 49 | this.backgroundSkin = new Quad(3, 3, 0xD50000); 50 | _businessLabel.fontStyles.color = 0xFFFFFF; 51 | _ratingLabel.fontStyles.color = 0xFFFFFF; 52 | } 53 | } 54 | } 55 | 56 | override protected function initialize():void 57 | { 58 | super.initialize(); 59 | 60 | this.layout = new AnchorLayout(); 61 | this.height = 80; 62 | this.isQuickHitAreaEnabled = true; 63 | this.backgroundSkin = new Quad(3, 3, 0xFFFFFF); 64 | this.backgroundSelectedSkin = new Quad(3, 3, 0xD50000); 65 | 66 | _ratingImage = new ImageLoader(); 67 | _ratingImage.layoutData = new AnchorLayoutData(NaN, 10, NaN, NaN, NaN, -10); 68 | _ratingImage.width = 65; 69 | _ratingImage.height = 15; 70 | this.addChild(_ratingImage); 71 | 72 | _ratingLabel = new Label(); 73 | _ratingLabel.layoutData = new AnchorLayoutData(NaN, 10, NaN, NaN, NaN, 10); 74 | _ratingLabel.fontStyles = new TextFormat("_sans", 10, 0x00000, "center"); 75 | _ratingLabel.width = 65; 76 | this.addChild(_ratingLabel); 77 | 78 | _businessImage = new ImageLoader(); 79 | _businessImage.layoutData = new AnchorLayoutData(NaN, NaN, NaN, 10, NaN, 0); 80 | _businessImage.width = _businessImage.height = 60; 81 | this.addChild(_businessImage); 82 | 83 | _businessLabel = new Label(); 84 | _businessLabel.wordWrap = true; 85 | _businessLabel.layoutData = new AnchorLayoutData(5, 80, 5, 85); 86 | _businessLabel.fontStyles = new TextFormat("_sans", 14, 0x000000, "left"); 87 | _businessLabel.fontStyles.leading = 5; 88 | this.addChild(_businessLabel); 89 | 90 | } 91 | 92 | override protected function commitData():void 93 | { 94 | if (this._data && this._owner) { 95 | 96 | this.backgroundSkin = new Quad(3, 3, 0xFFFFFF); 97 | 98 | var path:String = _data.image_url; 99 | path = path.substr(0, path.length - 5); 100 | 101 | _businessImage.source = path + "m.jpg"; 102 | _ratingImage.source = "assets/yelp/" + _data.rating + ".png"; 103 | _businessLabel.text = "" + _data.name + "" + "\n" + _data.location.address1; 104 | _ratingLabel.text = _data.review_count + " review(s)"; 105 | 106 | if(this.isSelected) 107 | { 108 | _businessLabel.fontStyles.color = 0xFFFFFF; 109 | _ratingLabel.fontStyles.color = 0xFFFFFF; 110 | } else { 111 | _businessLabel.fontStyles.color = 0x000000; 112 | _ratingLabel.fontStyles.color = 0x000000; 113 | } 114 | 115 | } else { 116 | _businessLabel.text = ""; 117 | _ratingLabel.text = ""; 118 | } 119 | } 120 | 121 | } 122 | } -------------------------------------------------------------------------------- /src/renderers/NewsRenderer.as: -------------------------------------------------------------------------------- 1 | package renderers 2 | { 3 | import feathers.controls.Label; 4 | import feathers.controls.LayoutGroup; 5 | import feathers.controls.renderers.LayoutGroupListItemRenderer; 6 | import feathers.layout.AnchorLayout; 7 | import feathers.layout.AnchorLayoutData; 8 | import feathers.layout.VerticalLayout; 9 | import feathers.utils.touch.DelayedDownTouchToState; 10 | import feathers.utils.touch.TapToSelect; 11 | 12 | import starling.display.Quad; 13 | import starling.text.TextFormat; 14 | 15 | public class NewsRenderer extends LayoutGroupListItemRenderer 16 | { 17 | private var monthNames:Array = new Array("Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec"); 18 | 19 | private var _newsLabel:Label; 20 | private var _dayLabel:Label; 21 | private var _monthLabel:Label; 22 | private var _select:TapToSelect; 23 | private var _delay:DelayedDownTouchToState; 24 | 25 | public function NewsRenderer() 26 | { 27 | super(); 28 | this._select = new TapToSelect(this); 29 | this._delay = new DelayedDownTouchToState(this, changeState); 30 | } 31 | 32 | private function changeState(currentState:String):void 33 | { 34 | if(this._data) 35 | { 36 | if(currentState == "up") 37 | { 38 | this.backgroundSkin = new Quad(3, 3, 0xFFFFFF); 39 | this._newsLabel.fontStyles.color = 0x000000; 40 | } 41 | 42 | else if(currentState == "down") 43 | { 44 | this.backgroundSkin = new Quad(3, 3, 0xD50000); 45 | this._newsLabel.fontStyles.color = 0xFFFFFF; 46 | } 47 | } 48 | } 49 | 50 | override protected function initialize():void 51 | { 52 | super.initialize(); 53 | 54 | this.layout = new AnchorLayout(); 55 | this.height = 80; 56 | this.isQuickHitAreaEnabled = true; 57 | this.backgroundSkin = new Quad(3, 3, 0xFFFFFF); 58 | this.backgroundSelectedSkin = new Quad(3, 3, 0xD50000); 59 | 60 | var dateGroup:LayoutGroup = new LayoutGroup(); 61 | dateGroup.layout = new VerticalLayout(); 62 | dateGroup.layoutData = new AnchorLayoutData(NaN, NaN, NaN, 10, NaN, 0); 63 | this.addChild(dateGroup); 64 | 65 | _monthLabel = new Label(); 66 | _monthLabel.styleNameList.add("date-label"); 67 | _monthLabel.backgroundSkin = new Quad(3, 3, 0x0277BD); 68 | dateGroup.addChild(_monthLabel); 69 | 70 | _dayLabel = new Label(); 71 | _dayLabel.styleNameList.add("date-label"); 72 | _dayLabel.paddingTop = -3; 73 | _dayLabel.backgroundSkin = new Quad(3, 3, 0x000000); 74 | dateGroup.addChild(_dayLabel); 75 | 76 | _newsLabel = new Label(); 77 | _newsLabel.wordWrap = true; 78 | _newsLabel.layoutData = new AnchorLayoutData(5, 10, 5, 75); 79 | _newsLabel.fontStyles = new TextFormat("_sans", 14, 0x000000, "left"); 80 | _newsLabel.fontStyles.leading = 5; 81 | this.addChild(_newsLabel); 82 | } 83 | 84 | override protected function commitData():void 85 | { 86 | if (this._data && this._owner) { 87 | 88 | this.backgroundSkin = new Quad(3, 3, 0xFFFFFF); 89 | this._newsLabel.fontStyles.color = 0x000000; 90 | 91 | var itemDate:Date = new Date(String(_data.pubDate)); 92 | _monthLabel.text = String(monthNames[itemDate.month]); 93 | _dayLabel.text = String(itemDate.date); 94 | 95 | _newsLabel.text = _data.title; 96 | 97 | } else { 98 | _newsLabel.text = ""; 99 | } 100 | } 101 | 102 | } 103 | } -------------------------------------------------------------------------------- /src/screens/LoginScreen.as: -------------------------------------------------------------------------------- 1 | package screens 2 | { 3 | import feathers.controls.BasicButton; 4 | import feathers.controls.Button; 5 | import feathers.controls.ImageLoader; 6 | import feathers.controls.Label; 7 | import feathers.controls.LayoutGroup; 8 | import feathers.controls.Panel; 9 | import feathers.controls.PanelScreen; 10 | import feathers.controls.WebView; 11 | import feathers.core.PopUpManager; 12 | import feathers.events.FeathersEventType; 13 | import feathers.layout.HorizontalAlign; 14 | import feathers.layout.VerticalLayout; 15 | import feathers.layout.VerticalLayoutData; 16 | 17 | import flash.events.Event; 18 | import flash.events.IOErrorEvent; 19 | import flash.net.URLLoader; 20 | import flash.net.URLRequest; 21 | import flash.net.URLRequestHeader; 22 | import flash.net.URLRequestMethod; 23 | 24 | import starling.display.DisplayObject; 25 | import starling.display.Quad; 26 | import starling.events.Event; 27 | 28 | import utils.ProfileManager; 29 | import utils.RoundedRect; 30 | 31 | public class LoginScreen extends PanelScreen 32 | { 33 | private var popup:Panel; 34 | private var webView:WebView; 35 | private var sessionId:String; 36 | private var requestUri:String; 37 | 38 | private var isOpen:Boolean; 39 | 40 | override protected function initialize():void 41 | { 42 | super.initialize(); 43 | 44 | var myLayout:VerticalLayout = new VerticalLayout(); 45 | myLayout.horizontalAlign = HorizontalAlign.CENTER; 46 | myLayout.gap = 15; 47 | 48 | this.title = "Select a Login Provider"; 49 | this.layout = myLayout; 50 | this.backButtonHandler = goBack; 51 | this.headerProperties.paddingLeft = 10; 52 | 53 | var cancelIcon:ImageLoader = new ImageLoader(); 54 | cancelIcon.width = cancelIcon.height = 25; 55 | cancelIcon.source = "assets/icons/cancel.png"; 56 | 57 | var cancelButton:Button = new Button(); 58 | cancelButton.addEventListener(starling.events.Event.TRIGGERED, goBack); 59 | cancelButton.styleNameList.add("header-button"); 60 | cancelButton.defaultIcon = cancelIcon; 61 | this.headerProperties.rightItems = new [cancelButton]; 62 | 63 | isOpen = false; 64 | 65 | var spacer1:BasicButton = new BasicButton(); 66 | spacer1.layoutData = new VerticalLayoutData(100, 100); 67 | this.addChild(spacer1); 68 | 69 | var label1:Label = new Label(); 70 | label1.styleNameList.add("big-label"); 71 | label1.text = "Social Login"; 72 | this.addChild(label1); 73 | 74 | var layoutForButtonsContainer:VerticalLayout = new VerticalLayout(); 75 | layoutForButtonsContainer.gap = 10; 76 | layoutForButtonsContainer.padding = 10; 77 | 78 | var buttonsContainer:LayoutGroup = new LayoutGroup(); 79 | buttonsContainer.width = 280; 80 | buttonsContainer.layout = layoutForButtonsContainer; 81 | buttonsContainer.backgroundSkin = RoundedRect.createRoundedRect(0xFFFFFF); 82 | this.addChild(buttonsContainer); 83 | 84 | var facebookIcon:ImageLoader = new ImageLoader(); 85 | facebookIcon.source = "assets/icons/facebook.png"; 86 | facebookIcon.width = facebookIcon.height = 25; 87 | 88 | var signInFacebookButton:Button = new Button(); 89 | signInFacebookButton.layoutData = new VerticalLayoutData(100, NaN); 90 | signInFacebookButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 91 | { 92 | startAuth("facebook.com"); 93 | }); 94 | signInFacebookButton.styleNameList.add("rounded-button"); 95 | signInFacebookButton.label = "Sign in with Facebook"; 96 | signInFacebookButton.defaultIcon = facebookIcon; 97 | buttonsContainer.addChild(signInFacebookButton); 98 | 99 | signInFacebookButton.defaultSkin = RoundedRect.createRoundedRect(0x3b5998); 100 | signInFacebookButton.downSkin = RoundedRect.createRoundedRect(0x314A7F); 101 | 102 | var twitterIcon:ImageLoader = new ImageLoader(); 103 | twitterIcon.source = "assets/icons/twitter.png"; 104 | twitterIcon.width = twitterIcon.height = 25; 105 | 106 | var signInTwitterButton:Button = new Button(); 107 | signInTwitterButton.layoutData = new VerticalLayoutData(100, NaN); 108 | signInTwitterButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 109 | { 110 | startAuth("twitter.com"); 111 | }); 112 | signInTwitterButton.styleNameList.add("rounded-button"); 113 | signInTwitterButton.label = "Sign in with Twitter"; 114 | signInTwitterButton.defaultIcon = twitterIcon; 115 | buttonsContainer.addChild(signInTwitterButton); 116 | 117 | signInTwitterButton.defaultSkin = RoundedRect.createRoundedRect(0x55ACEE); 118 | signInTwitterButton.downSkin = RoundedRect.createRoundedRect(0x489DD); 119 | 120 | var googleIcon:ImageLoader = new ImageLoader(); 121 | googleIcon.source = "assets/icons/google.png"; 122 | googleIcon.width = googleIcon.height = 25; 123 | 124 | var signInGoogleButton:Button = new Button(); 125 | signInGoogleButton.layoutData = new VerticalLayoutData(100, NaN); 126 | signInGoogleButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 127 | { 128 | startAuth("google.com"); 129 | }); 130 | signInGoogleButton.styleNameList.add("rounded-button"); 131 | signInGoogleButton.label = "Sign in with Google"; 132 | signInGoogleButton.defaultIcon = googleIcon; 133 | buttonsContainer.addChild(signInGoogleButton); 134 | 135 | signInGoogleButton.defaultSkin = RoundedRect.createRoundedRect(0xD34836); 136 | signInGoogleButton.downSkin = RoundedRect.createRoundedRect(0xBA3D2D); 137 | 138 | var spacer2:BasicButton = new BasicButton(); 139 | spacer2.layoutData = new VerticalLayoutData(100, 100); 140 | this.addChild(spacer2); 141 | } 142 | 143 | private function startAuth(provider:String):void 144 | { 145 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 146 | 147 | var myObject:Object = new Object(); 148 | myObject.continueUri = Constants.FIREBASE_REDIRECT_URL; 149 | myObject.providerId = provider; 150 | 151 | var request:URLRequest = new URLRequest(Constants.FIREBASE_CREATE_AUTH_URL); 152 | request.method = URLRequestMethod.POST; 153 | request.data = JSON.stringify(myObject); 154 | request.requestHeaders.push(header); 155 | 156 | var loader:URLLoader = new URLLoader(); 157 | loader.addEventListener(flash.events.Event.COMPLETE, authURLCreated); 158 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 159 | loader.load(request); 160 | } 161 | 162 | private function authURLCreated(event:flash.events.Event):void 163 | { 164 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, authURLCreated); 165 | 166 | var rawData:Object = JSON.parse(event.currentTarget.data); 167 | 168 | //We store the sessionId value from the response 169 | sessionId = rawData.sessionId; 170 | 171 | webView = new WebView(); 172 | webView.addEventListener(FeathersEventType.LOCATION_CHANGE, changeLocation); 173 | webView.layoutData = new VerticalLayoutData(100, 100); 174 | 175 | webView.width = 310; 176 | webView.height = 380; 177 | 178 | //We load the URL from the response, it will automatically contain the client id, scopes and the redirect URL 179 | webView.loadURL(rawData.authUri); 180 | 181 | popup = new Panel(); 182 | popup.headerProperties.paddingLeft = 10; 183 | popup.title = "Sign In"; 184 | popup.addChild(webView); 185 | 186 | var closeIcon:ImageLoader = new ImageLoader(); 187 | closeIcon.source = "assets/icons/close.png"; 188 | closeIcon.width = closeIcon.height = 25; 189 | 190 | var closeButton:Button = new Button(); 191 | closeButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 192 | { 193 | isOpen = false; 194 | webView.dispose(); 195 | PopUpManager.removePopUp(popup, true); 196 | }); 197 | closeButton.styleNameList.add("header-button"); 198 | closeButton.defaultIcon = closeIcon; 199 | popup.headerProperties.rightItems = new [closeButton]; 200 | 201 | PopUpManager.addPopUp(popup, true, true, function ():DisplayObject 202 | { 203 | var quad:Quad = new Quad(3, 3, 0x000000); 204 | quad.alpha = 0.50; 205 | return quad; 206 | }); 207 | 208 | isOpen = true; 209 | } 210 | 211 | private function changeLocation(event:starling.events.Event):void 212 | { 213 | var location:String = webView.location; 214 | 215 | if (location.indexOf("/__/auth/handler?code=") != -1 || location.indexOf("/__/auth/handler?state=") != -1 || location.indexOf("/__/auth/handler#state=") != -1) { 216 | 217 | //We are looking for a code parameter in the URL, once we have it we dispose the webview and prepare the last URLRequest 218 | webView.removeEventListener(FeathersEventType.LOCATION_CHANGE, changeLocation); 219 | webView.dispose(); 220 | PopUpManager.removePopUp(popup, true); 221 | isOpen = false; 222 | 223 | requestUri = location; 224 | getAccountInfo(); 225 | } 226 | } 227 | 228 | private function getAccountInfo():void 229 | { 230 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 231 | 232 | var myObject:Object = new Object(); 233 | myObject.requestUri = requestUri; 234 | myObject.sessionId = sessionId; 235 | myObject.returnSecureToken = true; 236 | 237 | var request:URLRequest = new URLRequest(Constants.FIREBASE_VERIFY_ASSERTION_URL); 238 | request.method = URLRequestMethod.POST; 239 | request.data = JSON.stringify(myObject); 240 | request.requestHeaders.push(header); 241 | 242 | var loader:URLLoader = new URLLoader(); 243 | loader.addEventListener(flash.events.Event.COMPLETE, registerComplete); 244 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 245 | loader.load(request); 246 | } 247 | 248 | 249 | private function registerComplete(event:flash.events.Event):void 250 | { 251 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, registerComplete); 252 | 253 | //The profile data is returned abck from Firebase, we call our ProfileManager and save the data in a local file 254 | 255 | var rawData:Object = JSON.parse(event.currentTarget.data); 256 | trace(event.currentTarget.data); 257 | ProfileManager.saveProfile(rawData); 258 | 259 | Main.profile = rawData; 260 | Main.displayName.text = "Welcome,\n" + rawData.displayName + ""; 261 | Main.avatar.source = rawData.photoUrl; 262 | 263 | this.dispatchEventWith(starling.events.Event.COMPLETE); 264 | } 265 | 266 | private function errorHandler(event:IOErrorEvent):void 267 | { 268 | trace(event.currentTarget.data); 269 | } 270 | 271 | private function goBack():void 272 | { 273 | this.dispatchEventWith(starling.events.Event.COMPLETE); 274 | } 275 | 276 | override public function dispose():void 277 | { 278 | if (isOpen == true) { 279 | webView.dispose(); 280 | PopUpManager.removePopUp(popup, true); 281 | } 282 | 283 | super.dispose(); 284 | } 285 | 286 | } 287 | } -------------------------------------------------------------------------------- /src/screens/SettingsScreen.as: -------------------------------------------------------------------------------- 1 | package screens 2 | { 3 | import feathers.controls.Alert; 4 | import feathers.controls.BasicButton; 5 | import feathers.controls.Button; 6 | import feathers.controls.ImageLoader; 7 | import feathers.controls.Label; 8 | import feathers.controls.PanelScreen; 9 | import feathers.controls.ScrollContainer; 10 | import feathers.controls.TextInput; 11 | import feathers.data.ListCollection; 12 | import feathers.events.FeathersEventType; 13 | import feathers.layout.AnchorLayout; 14 | import feathers.layout.AnchorLayoutData; 15 | import feathers.layout.HorizontalAlign; 16 | import feathers.layout.VerticalLayout; 17 | import feathers.layout.VerticalLayoutData; 18 | 19 | import flash.events.Event; 20 | import flash.events.IOErrorEvent; 21 | import flash.net.URLLoader; 22 | import flash.net.URLRequest; 23 | import flash.net.URLRequestHeader; 24 | import flash.net.URLRequestMethod; 25 | 26 | import starling.display.DisplayObject; 27 | import starling.events.Event; 28 | import starling.text.TextFormat; 29 | 30 | import utils.ProfileManager; 31 | import utils.RoundedRect; 32 | 33 | public class SettingsScreen extends PanelScreen 34 | { 35 | public static const GO_LOGIN:String = "goLogin"; 36 | 37 | private var alert:Alert; 38 | private var mainGroup:ScrollContainer; 39 | private var nameInput:TextInput; 40 | 41 | override protected function initialize():void 42 | { 43 | super.initialize(); 44 | 45 | this.title = "Settings"; 46 | this.layout = new AnchorLayout(); 47 | 48 | var menuButton:Button = new Button(); 49 | menuButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 50 | { 51 | dispatchEventWith(Main.OPEN_MENU); 52 | }); 53 | menuButton.styleNameList.add("menu-button"); 54 | this.headerProperties.leftItems = new [menuButton]; 55 | 56 | var layoutForMainGroup:VerticalLayout = new VerticalLayout(); 57 | layoutForMainGroup.horizontalAlign = HorizontalAlign.CENTER; 58 | layoutForMainGroup.padding = 10; 59 | layoutForMainGroup.gap = 10; 60 | 61 | mainGroup = new ScrollContainer(); 62 | mainGroup.layout = layoutForMainGroup; 63 | mainGroup.layoutData = new AnchorLayoutData(10, 10, 10, 10, NaN, NaN); 64 | mainGroup.backgroundSkin = RoundedRect.createRoundedRect(0xFFFFFF); 65 | this.addChild(mainGroup); 66 | 67 | this.addEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 68 | } 69 | 70 | private function transitionComplete(event:starling.events.Event):void 71 | { 72 | this.removeEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 73 | 74 | if (ProfileManager.isLoggedIn() === false) { 75 | loadLoggedOffUI(); 76 | } else { 77 | loadLoggedInUI(); 78 | } 79 | } 80 | 81 | private function loadLoggedOffUI():void 82 | { 83 | var spacer1:BasicButton = new BasicButton(); 84 | spacer1.layoutData = new VerticalLayoutData(100, 100); 85 | mainGroup.addChild(spacer1); 86 | 87 | var label1:Label = new Label(); 88 | label1.text = "Settings are only available for logged in users."; 89 | label1.fontStyles = new TextFormat("_sans", 18, 0x00000, "center"); 90 | label1.layoutData = new VerticalLayoutData(100, NaN); 91 | label1.wordWrap = true; 92 | mainGroup.addChild(label1); 93 | 94 | var spacer2:BasicButton = new BasicButton(); 95 | spacer2.height = 5 96 | mainGroup.addChild(spacer2); 97 | 98 | var signInButton:Button = new Button(); 99 | signInButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 100 | { 101 | dispatchEventWith(GO_LOGIN); 102 | }) 103 | signInButton.styleNameList.add("rounded-button"); 104 | signInButton.label = "Sign In"; 105 | signInButton.width = 150; 106 | signInButton.paddingLeft = 0; 107 | signInButton.horizontalAlign = HorizontalAlign.CENTER; 108 | mainGroup.addChild(signInButton); 109 | 110 | var spacer3:BasicButton = new BasicButton(); 111 | spacer3.layoutData = new VerticalLayoutData(100, 100); 112 | mainGroup.addChild(spacer3); 113 | } 114 | 115 | private function loadLoggedInUI():void 116 | { 117 | var spacer1:BasicButton = new BasicButton(); 118 | spacer1.height = 1 119 | mainGroup.addChild(spacer1); 120 | 121 | var label1:Label = new Label(); 122 | label1.text = "Change displayed name:"; 123 | label1.layoutData = new VerticalLayoutData(100, NaN); 124 | mainGroup.addChild(label1); 125 | 126 | nameInput = new TextInput(); 127 | nameInput.layoutData = new VerticalLayoutData(100, NaN); 128 | nameInput.text = Main.profile.displayName; 129 | nameInput.prompt = "Type your desired display name"; 130 | mainGroup.addChild(nameInput); 131 | 132 | var spacer2:BasicButton = new BasicButton(); 133 | spacer2.layoutData = new VerticalLayoutData(NaN, 100); 134 | mainGroup.addChild(spacer2); 135 | 136 | var saveIcon:ImageLoader = new ImageLoader(); 137 | saveIcon.source = "assets/icons/save.png"; 138 | saveIcon.width = saveIcon.height = 25; 139 | 140 | var saveButton:Button = new Button(); 141 | saveButton.addEventListener(starling.events.Event.TRIGGERED, getAccessToken); 142 | saveButton.layoutData = new VerticalLayoutData(100, NaN); 143 | saveButton.styleNameList.add("rounded-button"); 144 | saveButton.label = "Save Changes"; 145 | saveButton.defaultIcon = saveIcon; 146 | mainGroup.addChild(saveButton); 147 | 148 | var signOutIcon:ImageLoader = new ImageLoader(); 149 | signOutIcon.source = "assets/icons/logout.png"; 150 | signOutIcon.width = signOutIcon.height = 25; 151 | 152 | var signOutButton:Button = new Button(); 153 | signOutButton.addEventListener(starling.events.Event.TRIGGERED, attemptSingOut); 154 | signOutButton.layoutData = new VerticalLayoutData(100, NaN); 155 | signOutButton.styleNameList.add("rounded-button"); 156 | signOutButton.label = "Sign Out"; 157 | signOutButton.defaultIcon = signOutIcon; 158 | mainGroup.addChild(signOutButton); 159 | } 160 | 161 | private function getAccessToken():void 162 | { 163 | if (nameInput.text != "") { 164 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 165 | 166 | var myObject:Object = new Object(); 167 | myObject.grant_type = "refresh_token"; 168 | myObject.refresh_token = Main.profile.refreshToken; 169 | 170 | var request:URLRequest = new URLRequest(Constants.FIREBASE_AUTH_TOKEN_URL); 171 | request.method = URLRequestMethod.POST; 172 | request.data = JSON.stringify(myObject); 173 | request.requestHeaders.push(header); 174 | 175 | var loader:URLLoader = new URLLoader(); 176 | loader.addEventListener(flash.events.Event.COMPLETE, accessTokenLoaded); 177 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 178 | loader.load(request); 179 | } else { 180 | alert = Alert.show("Display name is a required field.", "Error", new ListCollection([{label: "OK"}])); 181 | } 182 | } 183 | 184 | private function accessTokenLoaded(event:flash.events.Event):void 185 | { 186 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, accessTokenLoaded); 187 | 188 | var rawData:Object = JSON.parse(event.currentTarget.data); 189 | 190 | var myObject:Object = new Object(); 191 | myObject.displayName = nameInput.text; 192 | myObject.idToken = rawData.access_token; 193 | 194 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 195 | 196 | var request:URLRequest = new URLRequest(Constants.FIREBASE_ACCOUNT_SETINFO_URL); 197 | request.method = URLRequestMethod.POST; 198 | request.data = JSON.stringify(myObject); 199 | request.requestHeaders.push(header); 200 | 201 | var loader:URLLoader = new URLLoader(); 202 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 203 | loader.addEventListener(flash.events.Event.COMPLETE, nameUpdated); 204 | loader.load(request); 205 | } 206 | 207 | private function nameUpdated(event:flash.events.Event):void 208 | { 209 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, nameUpdated); 210 | 211 | //After the name was updated in the Firebase Auth service we update it locally 212 | 213 | var rawData:Object = JSON.parse(event.currentTarget.data); 214 | Main.displayName.text = "Welcome,\n" + rawData.displayName + ""; 215 | Main.profile.displayName = rawData.displayName; 216 | ProfileManager.saveProfile(Main.profile); 217 | alert = Alert.show("Display name successfully updated.", "Success", new ListCollection([{label: "OK"}])); 218 | } 219 | 220 | private function attemptSingOut(event:starling.events.Event):void 221 | { 222 | alert = Alert.show("Do you really want to sign out from this app?", "Sign Out", new ListCollection( 223 | [ 224 | {label: "Cancel"}, 225 | {label: "OK"} 226 | ])); 227 | 228 | alert.addEventListener(starling.events.Event.CLOSE, function (event:starling.events.Event, data:Object):void 229 | { 230 | if (data.label == "OK") { 231 | //User profile data will be cleared from the app and logged in information will be cleared 232 | ProfileManager.signOut(); 233 | Main.avatar.source = "assets/icons/account_circle.png"; 234 | Main.displayName.text = "Welcome Guest"; 235 | 236 | mainGroup.removeChildren(); 237 | loadLoggedOffUI(); 238 | } 239 | }); 240 | } 241 | 242 | private function errorHandler(event:IOErrorEvent):void 243 | { 244 | trace(event.currentTarget.data); 245 | } 246 | } 247 | } -------------------------------------------------------------------------------- /src/utils/ChatMessageItemRenderer.as: -------------------------------------------------------------------------------- 1 | package utils 2 | { 3 | import feathers.controls.Label; 4 | import feathers.controls.renderers.LayoutGroupListItemRenderer; 5 | import feathers.layout.AnchorLayout; 6 | import feathers.layout.AnchorLayoutData; 7 | 8 | import starling.display.Image; 9 | import starling.text.TextFormat; 10 | 11 | public class ChatMessageItemRenderer extends LayoutGroupListItemRenderer 12 | { 13 | protected var bubble:Image; 14 | protected var side:String; 15 | protected var _messageLabel:Label; 16 | 17 | public function ChatMessageItemRenderer() 18 | { 19 | super(); 20 | } 21 | 22 | override protected function initialize():void 23 | { 24 | this.layout = new AnchorLayout(); 25 | 26 | bubble = RoundedRect.createRoundedRect(); 27 | 28 | _messageLabel = new Label(); 29 | _messageLabel.minWidth = 5; 30 | _messageLabel.minHeight = 5; 31 | _messageLabel.maxWidth = 250; 32 | _messageLabel.wordWrap = true; 33 | _messageLabel.padding = 10; 34 | _messageLabel.backgroundSkin = bubble; 35 | this.addChild(_messageLabel); 36 | } 37 | 38 | override protected function commitData():void 39 | { 40 | if (this._data && this._owner) { 41 | 42 | var tempData:Date = new Date(Number(this._data.timestamp)); 43 | var hours:String = String(tempData.hours); 44 | 45 | if (hours.length == 1) { 46 | hours = "0" + hours; 47 | } 48 | 49 | var minutes:String = String(tempData.minutes); 50 | 51 | if (minutes.length == 1) { 52 | minutes = "0" + minutes; 53 | } 54 | 55 | _messageLabel.text = this._data.message + 56 | " " + hours + ":" + minutes + " "; 57 | 58 | if (this._data.senderId == Main.profile.localId) { 59 | side = "right"; 60 | } else { 61 | side = "left"; 62 | } 63 | 64 | } 65 | } 66 | 67 | override protected function preLayout():void 68 | { 69 | if (side == "left") { 70 | _messageLabel.fontStyles = new TextFormat("-sans", 14, 0xFFFFFF, "left"); 71 | _messageLabel.layoutData = new AnchorLayoutData(NaN, NaN, NaN, 10, NaN, 0); 72 | bubble.color = 0xD50000; 73 | } else { 74 | 75 | _messageLabel.fontStyles = new TextFormat("_sans", 14, 0xFFFFFF, "left"); 76 | _messageLabel.layoutData = new AnchorLayoutData(NaN, 10, NaN, NaN, NaN, 0); 77 | bubble.color = 0x2979FF; 78 | } 79 | 80 | bubble.width = _messageLabel.width; 81 | bubble.height = _messageLabel.height; 82 | 83 | } 84 | 85 | } 86 | } -------------------------------------------------------------------------------- /src/utils/NavigatorData.as: -------------------------------------------------------------------------------- 1 | package utils 2 | { 3 | public dynamic class NavigatorData extends Object 4 | { 5 | public function NavigatorData() 6 | { 7 | super(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/utils/ProfileManager.as: -------------------------------------------------------------------------------- 1 | package utils 2 | { 3 | import flash.filesystem.File; 4 | import flash.filesystem.FileMode; 5 | import flash.filesystem.FileStream; 6 | 7 | public class ProfileManager 8 | { 9 | 10 | private static const file:File = File.applicationStorageDirectory.resolvePath("profile.data"); 11 | 12 | private static var fileStream:FileStream; 13 | 14 | 15 | /** 16 | * Saves the data from a logged in user 17 | * 18 | * @param profile An Object containing profile information from Firebase. 19 | */ 20 | public static function saveProfile(profile:Object):void 21 | { 22 | fileStream = new FileStream(); 23 | fileStream.open(file, FileMode.WRITE); 24 | fileStream.writeObject(profile); 25 | fileStream.close(); 26 | } 27 | 28 | /** 29 | * Loads the user data from the profile.data file into an object. 30 | */ 31 | public static function loadProfile():Object 32 | { 33 | var myObject:Object = new Object(); 34 | 35 | if (file.exists) { 36 | fileStream = new FileStream(); 37 | fileStream.open(file, FileMode.READ); 38 | myObject = fileStream.readObject(); 39 | fileStream.close(); 40 | } else { 41 | myObject = {}; 42 | } 43 | 44 | return myObject; 45 | } 46 | 47 | /** 48 | * Checks if the user is logged in 49 | */ 50 | public static function isLoggedIn():Boolean 51 | { 52 | var tempObject:Object = loadProfile(); 53 | 54 | //We check if the profile exists by checking the existense of the localId value 55 | 56 | if (tempObject.localId == null) { 57 | tempObject = null; 58 | return false; 59 | } else { 60 | tempObject = null; 61 | return true; 62 | } 63 | } 64 | 65 | /** 66 | * Sign outs the user from the app by setting the profile.data file into an empty object. 67 | */ 68 | public static function signOut():void 69 | { 70 | fileStream = new FileStream(); 71 | fileStream.open(file, FileMode.WRITE); 72 | fileStream.writeObject({}); 73 | fileStream.close(); 74 | } 75 | 76 | } 77 | } -------------------------------------------------------------------------------- /src/utils/RoundedRect.as: -------------------------------------------------------------------------------- 1 | package utils 2 | { 3 | import flash.geom.Rectangle; 4 | 5 | import starling.display.Image; 6 | import starling.textures.Texture; 7 | import starling.textures.TextureSmoothing; 8 | 9 | public class RoundedRect 10 | { 11 | [Embed(source="./../assets/icons/rounded.png")] 12 | private static const myAsset:Class; 13 | 14 | public static function createRoundedRect(color:uint = 0xFF0000):Image 15 | { 16 | var myImage:Image = new Image(Texture.fromEmbeddedAsset(myAsset)); 17 | myImage.pixelSnapping = true; 18 | myImage.textureSmoothing = TextureSmoothing.TRILINEAR; 19 | myImage.scale9Grid = new Rectangle(25, 25, 1, 1); 20 | myImage.color = color; 21 | return myImage; 22 | } 23 | } 24 | } --------------------------------------------------------------------------------