├── LICENSE ├── README.md └── src ├── CustomTheme.as ├── Firebase.as ├── Main.as ├── TodoApp-app.xml ├── TodoApp.as ├── assets ├── icons │ ├── account.png │ ├── add.png │ ├── back-arrow.png │ ├── cancel.png │ ├── check.png │ ├── delete.png │ ├── done.png │ ├── person.png │ ├── rounded.png │ ├── save.png │ └── warning.png └── launchicons │ ├── icon-100.png │ ├── icon-114.png │ ├── icon-120.png │ ├── icon-144.png │ ├── icon-152.png │ ├── icon-40.png │ ├── icon-48.png │ ├── icon-50.png │ ├── icon-512.png │ ├── icon-57.png │ ├── icon-58.png │ ├── icon-72.png │ ├── icon-76.png │ ├── icon-80.png │ └── icon-96.png ├── screens ├── AddTaskScreen.as ├── EditTaskScreen.as ├── HomeScreen.as ├── LoginScreen.as └── RegisterScreen.as └── utils ├── NavigatorData.as ├── ProfileManager.as ├── Responses.as ├── RoundedRect.as └── SliderItemRenderer.as /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Phantom App Development 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 | # ToDo App 1.1.2 2 | 3 | ToDo App is a mobile application developed with Starling Framework and FeathersUI. It showcases how to use Firebase services with ActionScript to create simple and secure CRUD system. 4 | 5 | It shows how to use the following Firebase features: 6 | 7 | * GET, POST, DELETE and PATCH requests to the Firebase Database (JSON) 8 | * User Auth with Email and Password 9 | * User account management (update email, password, recover password and deletion of account) 10 | 11 | This app also has some extra features: 12 | 13 | * Managing the logged in user profile with a Profile Manager class 14 | * Managing the Firebase response errors with a Responses class 15 | * RoundedRect with a Scale9Grid 16 | * Mail app-like ItemRenderer 17 | * Material Design custom theme 18 | * Multi DPI development 19 | 20 | ## Dependencies 21 | 22 | * [Starling Framework 2.1](http://gamua.com/starling/) 23 | * [Feathers UI 3.2.0](https://feathersui.com/) 24 | * [Adobe AIR 25](http://www.adobe.com/devnet/air/air-sdk-download.html) 25 | 26 | To compile this application you require to provide your own 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/ 27 | 28 | ## What is Firebase? 29 | 30 | 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. 31 | 32 | It also offers a user management service which allows your users to register an account in your app and have personalized experiences. 33 | In this app the users can generate private to do's lists that they only can access. 34 | 35 | ## Firebase Rules 36 | 37 | The following rules are used for this app: 38 | 39 | ```json 40 | { 41 | "rules": { 42 | "todos": { 43 | "$uid": { 44 | ".indexOn": ["due_date"], 45 | ".read": "auth != null && auth.uid == $uid", 46 | ".write": "auth != null && auth.uid == $uid" 47 | } 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | These rules mean the following: 54 | 55 | There's a main node named `todos`, inside that node each user will have their own node which only they will be able to read and write. 56 | We add a index to the `due_date` value so we can sort our lists by it. 57 | 58 | Follow these steps to locate your API Key: 59 | 60 | 1. Login to the [Firebase console](https://console.firebase.google.com/) and select a project or create a new one. 61 | 2. In the project home page you will be prompted to add Firebase to `Android`, `iOS` or `Web`. 62 | 3. Select `Web`, a popup will appear. 63 | 4. Copy the `apiKey` from the JavaScript code block. 64 | 5. Open the `Firebase.as` file and set your variables and constants accordingly. 65 | 66 | Don't forget to enable Email and Password authentication from the Auth section in the Firebase console. 67 | 68 | ## Preview 69 | 70 | [![Watch on Youtube](http://i.imgur.com/T1irUWs.png)](https://www.youtube.com/watch?v=WDFwFJYTU9k) 71 | 72 | ## Download 73 | 74 | You can test this app by downloading it directly from Google Play. 75 | 76 | [![Download](http://i.imgur.com/He0deVa.png)](https://play.google.com/store/apps/details?id=air.im.phantom.todo) -------------------------------------------------------------------------------- /src/CustomTheme.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | 4 | import feathers.controls.Alert; 5 | import feathers.controls.Button; 6 | import feathers.controls.ButtonGroup; 7 | import feathers.controls.ButtonState; 8 | import feathers.controls.Header; 9 | import feathers.controls.ImageLoader; 10 | import feathers.controls.Label; 11 | import feathers.controls.List; 12 | import feathers.controls.PanelScreen; 13 | import feathers.controls.SpinnerList; 14 | import feathers.controls.TextInput; 15 | import feathers.controls.renderers.DefaultListItemRenderer; 16 | import feathers.controls.text.StageTextTextEditor; 17 | import feathers.controls.text.TextFieldTextRenderer; 18 | import feathers.core.FeathersControl; 19 | import feathers.core.ITextEditor; 20 | import feathers.core.ITextRenderer; 21 | import feathers.layout.Direction; 22 | import feathers.layout.HorizontalAlign; 23 | import feathers.themes.StyleNameFunctionTheme; 24 | 25 | import starling.display.DisplayObject; 26 | import starling.display.Quad; 27 | import starling.text.TextFormat; 28 | 29 | import utils.RoundedRect; 30 | 31 | public class CustomTheme extends StyleNameFunctionTheme 32 | { 33 | 34 | public function CustomTheme() 35 | { 36 | super(); 37 | this.initialize(); 38 | } 39 | 40 | private function initialize():void 41 | { 42 | Alert.overlayFactory = function ():DisplayObject 43 | { 44 | var quad:Quad = new Quad(3, 3, 0x000000); 45 | quad.alpha = 0.50; 46 | return quad; 47 | }; 48 | 49 | this.initializeGlobals(); 50 | this.initializeStyleProviders(); 51 | } 52 | 53 | private function initializeGlobals():void 54 | { 55 | FeathersControl.defaultTextRendererFactory = function ():ITextRenderer 56 | { 57 | var renderer:TextFieldTextRenderer = new TextFieldTextRenderer(); 58 | renderer.isHTML = true; 59 | return renderer; 60 | } 61 | 62 | FeathersControl.defaultTextEditorFactory = function ():ITextEditor 63 | { 64 | return new StageTextTextEditor(); 65 | } 66 | } 67 | 68 | private function initializeStyleProviders():void 69 | { 70 | this.getStyleProviderForClass(Button).setFunctionForStyleName("alert-button", setAlertButtonStyles); 71 | this.getStyleProviderForClass(Button).setFunctionForStyleName("back-button", setBackButtonStyles); 72 | this.getStyleProviderForClass(Button).setFunctionForStyleName("callout-button", setCalloutButtonStyles); 73 | this.getStyleProviderForClass(Button).setFunctionForStyleName("header-button", setHeaderButtonStyles); 74 | 75 | this.getStyleProviderForClass(Alert).defaultStyleFunction = this.setAlertStyles; 76 | this.getStyleProviderForClass(Button).defaultStyleFunction = this.setButtonStyles; 77 | this.getStyleProviderForClass(DefaultListItemRenderer).defaultStyleFunction = this.setDefaultListItemRendererStyles; 78 | this.getStyleProviderForClass(Header).defaultStyleFunction = this.setHeaderStyles; 79 | this.getStyleProviderForClass(Label).defaultStyleFunction = this.setLabelStyles; 80 | this.getStyleProviderForClass(List).defaultStyleFunction = this.setListStyles; 81 | this.getStyleProviderForClass(PanelScreen).defaultStyleFunction = this.setPanelScreenStyles; 82 | this.getStyleProviderForClass(SpinnerList).defaultStyleFunction = this.setSpinnerListStyles; 83 | this.getStyleProviderForClass(TextInput).defaultStyleFunction = this.setTextInputStyles; 84 | } 85 | 86 | //------------------------- 87 | // Alert 88 | //------------------------- 89 | 90 | private function setAlertStyles(alert:Alert):void 91 | { 92 | alert.backgroundSkin = new Quad(3, 3, 0xE0F2F1); 93 | alert.maxWidth = 250; 94 | alert.minHeight = 50; 95 | alert.padding = 10; 96 | 97 | var icon:ImageLoader = new ImageLoader(); 98 | icon.source = "assets/icons/warning.png"; 99 | icon.width = icon.height = 20; 100 | 101 | alert.headerProperties.paddingLeft = 10; 102 | alert.headerProperties.leftItems = new [icon]; 103 | alert.headerProperties.gap = 10; 104 | alert.headerProperties.titleAlign = HorizontalAlign.LEFT; 105 | 106 | alert.fontStyles = new TextFormat("_sans", 14, 0x000000, "left"); 107 | alert.fontStyles.leading = 7; 108 | 109 | alert.buttonGroupFactory = function ():ButtonGroup 110 | { 111 | var group:ButtonGroup = new ButtonGroup(); 112 | group.customButtonStyleName = "alert-button"; 113 | group.direction = Direction.HORIZONTAL; 114 | group.gap = 10; 115 | group.padding = 10; 116 | return group; 117 | } 118 | 119 | } 120 | 121 | 122 | //------------------------- 123 | // Button 124 | //------------------------- 125 | 126 | private function setButtonStyles(button:Button):void 127 | { 128 | button.height = 50; 129 | button.defaultSkin = RoundedRect.createRoundedRect(0xE0F2F1); 130 | button.downSkin = RoundedRect.createRoundedRect(0xCCCCCC); 131 | button.fontStyles = new TextFormat("_sans", 16, 0x000000); 132 | } 133 | 134 | private function setCalloutButtonStyles(button:Button):void 135 | { 136 | button.width = 150; 137 | button.height = 45; 138 | button.horizontalAlign = HorizontalAlign.LEFT; 139 | button.paddingLeft = 10; 140 | button.defaultSkin = new Quad(3, 3, 0x00796B); 141 | button.downSkin = new Quad(3, 3, 0x004D40); 142 | button.fontStyles = new TextFormat("_sans", 14, 0xFFFFFF, "left") 143 | } 144 | 145 | private function setAlertButtonStyles(button:Button):void 146 | { 147 | button.height = 40; 148 | button.defaultSkin = new Quad(40, 40, 0x00796B); 149 | button.downSkin = new Quad(40, 40, 0x004D40); 150 | button.fontStyles = new TextFormat("_sans", 14, 0xFFFFFF); 151 | } 152 | 153 | private function setBackButtonStyles(button:Button):void 154 | { 155 | var transparentQuad:Quad = new Quad(3, 3, 0xFFFFFF); 156 | transparentQuad.alpha = 0.20; 157 | 158 | var arrowIcon:ImageLoader = new ImageLoader(); 159 | arrowIcon.width = arrowIcon.height = 25; 160 | arrowIcon.source = "assets/icons/back-arrow.png"; 161 | 162 | button.width = button.height = 45; 163 | button.defaultIcon = arrowIcon; 164 | button.downSkin = transparentQuad; 165 | } 166 | 167 | private function setHeaderButtonStyles(button:Button):void 168 | { 169 | var transparentQuad:Quad = new Quad(3, 3, 0xFFFFFF); 170 | transparentQuad.alpha = 0.20; 171 | 172 | button.width = button.height = 45; 173 | button.downSkin = transparentQuad; 174 | } 175 | 176 | //------------------------- 177 | // Header 178 | //------------------------- 179 | 180 | private function setHeaderStyles(header:Header):void 181 | { 182 | var quad:Quad = new Quad(3, 50, 0x00796B); 183 | 184 | header.backgroundSkin = quad; 185 | header.fontStyles = new TextFormat("_sans", 16, 0xFFFFFF); 186 | } 187 | 188 | //------------------------- 189 | // Label 190 | //------------------------- 191 | 192 | private function setLabelStyles(label:Label):void 193 | { 194 | label.fontStyles = new TextFormat("-sans", 16, 0xFFFFFF, "left"); 195 | } 196 | 197 | //------------------------- 198 | // List 199 | //------------------------- 200 | 201 | private function setListStyles(list:List):void 202 | { 203 | list.backgroundSkin = new Quad(3, 3, 0xE0F2F1); 204 | } 205 | 206 | private function setDefaultListItemRendererStyles(renderer:DefaultListItemRenderer):void 207 | { 208 | renderer.defaultSkin = new Quad(3, 3, 0xFFFFFF); 209 | renderer.downSkin = new Quad(3, 3, 0x00796B); 210 | renderer.defaultSelectedSkin = new Quad(3, 3, 0x00796B); 211 | 212 | renderer.horizontalAlign = HorizontalAlign.LEFT; 213 | renderer.paddingLeft = 10; 214 | renderer.paddingRight = 10; 215 | renderer.paddingTop = 10; 216 | renderer.paddingBottom = 10; 217 | renderer.minHeight = 50; 218 | renderer.gap = 10; 219 | 220 | var blackFormat:TextFormat = new TextFormat("_sans", 14, 0x000000, "left"); 221 | blackFormat.leading = 7; 222 | 223 | var whiteFormat:TextFormat = new TextFormat("_sans", 14, 0xFFFFFF, "left"); 224 | whiteFormat.leading = 7; 225 | 226 | renderer.wordWrap = true; 227 | 228 | renderer.setFontStylesForState(ButtonState.UP, blackFormat); 229 | renderer.setFontStylesForState(ButtonState.UP_AND_SELECTED, whiteFormat); 230 | renderer.setFontStylesForState(ButtonState.DOWN, whiteFormat); 231 | renderer.setFontStylesForState(ButtonState.DOWN_AND_SELECTED, whiteFormat); 232 | renderer.setFontStylesForState(ButtonState.HOVER, blackFormat); 233 | renderer.setFontStylesForState(ButtonState.HOVER_AND_SELECTED, whiteFormat); 234 | } 235 | 236 | //------------------------- 237 | // PanelScreen 238 | //------------------------- 239 | 240 | private function setPanelScreenStyles(screen:PanelScreen):void 241 | { 242 | screen.backgroundSkin = new Quad(3, 3, 0xE0F2F1); 243 | } 244 | 245 | //------------------------- 246 | // SpinnerList 247 | //------------------------- 248 | 249 | private function setSpinnerListStyles(spinnerList:SpinnerList):void 250 | { 251 | var overlay:Quad = new Quad(3, 3, 0xCCCCCC); 252 | overlay.alpha = 0.3; 253 | 254 | spinnerList.selectionOverlaySkin = overlay; 255 | spinnerList.backgroundSkin = new Quad(3, 3, 0xFFFFFF); 256 | } 257 | 258 | //------------------------- 259 | // TextInput 260 | //------------------------- 261 | 262 | private function setTextInputStyles(textInput:TextInput):void 263 | { 264 | textInput.backgroundSkin = RoundedRect.createRoundedRect(0x004D40); 265 | textInput.padding = 10; 266 | 267 | textInput.textEditorFactory = function ():ITextEditor 268 | { 269 | var editor:StageTextTextEditor = new StageTextTextEditor(); 270 | editor.fontFamily = "_sans"; 271 | editor.fontSize = 16; 272 | editor.color = 0xFFFFFF; 273 | return editor; 274 | } 275 | 276 | textInput.promptFontStyles = new TextFormat("_sann", 16, 0xCCCCCC, "left", "top"); 277 | } 278 | 279 | } 280 | } -------------------------------------------------------------------------------- /src/Firebase.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | public class Firebase 4 | { 5 | //This will be used as a global variable that holds the logged in user information 6 | 7 | public static var LOGGED_USER_DATA:Object; 8 | public static var FIREBASE_AUTH_TOKEN:String; //Also known as the access_token 9 | 10 | //Your project API Key 11 | 12 | public static const FIREBASE_API_KEY:String = ""; 13 | 14 | //The url of your project 15 | 16 | public static const PROJECT_NAME:String = ""; 17 | 18 | //These URLs allow user registration and management with Email and Password 19 | 20 | private static const AUTH_BASE_URL:String = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/"; 21 | 22 | public static const EMAIL_PASSWORD_LOGIN:String = AUTH_BASE_URL + "verifyPassword?key=" + FIREBASE_API_KEY; 23 | public static const EMAIL_PASSWORD_SIGNUP:String = AUTH_BASE_URL + "signupNewUser?key=" + FIREBASE_API_KEY; 24 | public static const UPDATE_EMAIL:String = AUTH_BASE_URL + "setAccountInfo?key=" + FIREBASE_API_KEY; 25 | public static const RESET_PASSWORD:String = AUTH_BASE_URL + "getOobConfirmationCode?key=" + FIREBASE_API_KEY; 26 | public static const UPDATE_PASSWORD:String = AUTH_BASE_URL + "setAccountInfo?key=" + FIREBASE_API_KEY; 27 | public static const DELETE_ACCOUNT:String = AUTH_BASE_URL + "deleteAccount?key=" + FIREBASE_API_KEY; 28 | public static const FIREBASE_AUTH_TOKEN_URL:String = "https://securetoken.googleapis.com/v1/token?key=" + FIREBASE_API_KEY; 29 | 30 | //The URLs for different methods, in this case all 4 are the same 31 | 32 | public static const FIREBASE_SELECT_URL:String = "https://" + PROJECT_NAME + ".firebaseio.com/todos/"; 33 | public static const FIREBASE_INSERT_URL:String = "https://" + PROJECT_NAME + ".firebaseio.com/todos/"; 34 | public static const FIREBASE_DELETE_URL:String = "https://" + PROJECT_NAME + ".firebaseio.com/todos/"; 35 | public static const FIREBASE_UPDATE_URL:String = "https://" + PROJECT_NAME + ".firebaseio.com/todos/"; 36 | } 37 | } -------------------------------------------------------------------------------- /src/Main.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | 4 | import feathers.controls.StackScreenNavigator; 5 | import feathers.controls.StackScreenNavigatorItem; 6 | import feathers.motion.Cover; 7 | import feathers.motion.Reveal; 8 | import feathers.motion.Slide; 9 | 10 | import flash.events.Event; 11 | import flash.events.IOErrorEvent; 12 | import flash.net.URLLoader; 13 | import flash.net.URLRequest; 14 | import flash.net.URLRequestHeader; 15 | import flash.net.URLRequestMethod; 16 | 17 | import screens.AddTaskScreen; 18 | import screens.EditTaskScreen; 19 | import screens.HomeScreen; 20 | import screens.LoginScreen; 21 | import screens.RegisterScreen; 22 | 23 | import starling.display.Sprite; 24 | import starling.events.Event; 25 | 26 | import utils.NavigatorData; 27 | import utils.ProfileManager; 28 | 29 | public class Main extends Sprite 30 | { 31 | public function Main() 32 | { 33 | this.addEventListener(starling.events.Event.ADDED_TO_STAGE, addedToStageHandler); 34 | } 35 | 36 | private var myNavigator:StackScreenNavigator; 37 | 38 | private static const LOGIN_SCREEN:String = "loginScreen"; 39 | private static const REGISTER_SCREEN:String = "registerScreen"; 40 | private static const HOME_SCREEN:String = "homeScreen"; 41 | private static const ADD_TASK_SCREEN:String = "addTaskScreen"; 42 | private static const EDIT_TASK_SCREEN:String = "editTaskScreen"; 43 | 44 | protected function addedToStageHandler(event:starling.events.Event):void 45 | { 46 | this.removeEventListener(starling.events.Event.ADDED_TO_STAGE, addedToStageHandler); 47 | 48 | new CustomTheme(); 49 | 50 | var NAVIGATOR_DATA:NavigatorData = new NavigatorData(); 51 | 52 | myNavigator = new StackScreenNavigator(); 53 | myNavigator.pushTransition = Slide.createSlideLeftTransition(); 54 | myNavigator.popTransition = Slide.createSlideRightTransition(); 55 | addChild(myNavigator); 56 | 57 | var loginScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(LoginScreen); 58 | loginScreenItem.setScreenIDForPushEvent(LoginScreen.GO_HOME, HOME_SCREEN); 59 | loginScreenItem.setScreenIDForPushEvent(LoginScreen.GO_REGISTER, REGISTER_SCREEN); 60 | myNavigator.addScreen(LOGIN_SCREEN, loginScreenItem); 61 | 62 | var registerScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(RegisterScreen); 63 | registerScreenItem.pushTransition = Cover.createCoverUpTransition(); 64 | registerScreenItem.popTransition = Reveal.createRevealDownTransition(); 65 | registerScreenItem.addPopEvent(starling.events.Event.COMPLETE); 66 | registerScreenItem.setScreenIDForPushEvent(RegisterScreen.GO_HOME, HOME_SCREEN); 67 | myNavigator.addScreen(REGISTER_SCREEN, registerScreenItem); 68 | 69 | var homeScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(HomeScreen); 70 | homeScreenItem.properties.data = NAVIGATOR_DATA; 71 | homeScreenItem.setScreenIDForPushEvent(HomeScreen.GO_ADDTASK, ADD_TASK_SCREEN); 72 | homeScreenItem.setScreenIDForPushEvent(HomeScreen.GO_DETAILS, EDIT_TASK_SCREEN); 73 | myNavigator.addScreen(HOME_SCREEN, homeScreenItem); 74 | 75 | var addTaskScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(AddTaskScreen); 76 | addTaskScreenItem.addPopEvent(starling.events.Event.COMPLETE); 77 | myNavigator.addScreen(ADD_TASK_SCREEN, addTaskScreenItem); 78 | 79 | var editTaskScreenItem:StackScreenNavigatorItem = new StackScreenNavigatorItem(EditTaskScreen); 80 | editTaskScreenItem.properties.data = NAVIGATOR_DATA; 81 | editTaskScreenItem.addPopEvent(starling.events.Event.COMPLETE); 82 | myNavigator.addScreen(EDIT_TASK_SCREEN, editTaskScreenItem); 83 | 84 | //We load the user config and determinate if it's a new user 85 | 86 | if (ProfileManager.isLoggedIn()) { 87 | 88 | Firebase.LOGGED_USER_DATA = ProfileManager.loadProfile(); 89 | 90 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 91 | 92 | var myObject:Object = new Object(); 93 | myObject.grant_type = "refresh_token"; 94 | myObject.refresh_token = Firebase.LOGGED_USER_DATA.refresh_token; 95 | 96 | var request:URLRequest = new URLRequest(Firebase.FIREBASE_AUTH_TOKEN_URL); 97 | request.method = URLRequestMethod.POST; 98 | request.data = JSON.stringify(myObject); 99 | request.requestHeaders.push(header); 100 | 101 | var loader:URLLoader = new URLLoader(); 102 | loader.addEventListener(flash.events.Event.COMPLETE, function ():void 103 | { 104 | var rawData:Object = JSON.parse(loader.data); 105 | Firebase.FIREBASE_AUTH_TOKEN = rawData.access_token; 106 | myNavigator.rootScreenID = HOME_SCREEN; 107 | }); 108 | loader.addEventListener(IOErrorEvent.IO_ERROR, function ():void 109 | { 110 | trace(loader.data); 111 | }); 112 | loader.load(request); 113 | 114 | } else { 115 | myNavigator.rootScreenID = LOGIN_SCREEN; 116 | } 117 | 118 | } 119 | 120 | } 121 | } -------------------------------------------------------------------------------- /src/TodoApp-app.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 18 | im.phantom.todo 19 | 20 | 21 | ToDo 22 | 23 | 25 | ToDo 26 | 27 | 31 | 1.1.2 32 | 33 | 37 | 38 | 39 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | [This value will be overwritten by Flash Builder in the output app.xml] 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | direct 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | true 119 | false 120 | true 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 142 | 143 | 144 | assets/launchicons/icon-40.png 145 | assets/launchicons/icon-48.png 146 | assets/launchicons/icon-50.png 147 | assets/launchicons/icon-57.png 148 | assets/launchicons/icon-58.png 149 | assets/launchicons/icon-72.png 150 | assets/launchicons/icon-76.png 151 | assets/launchicons/icon-80.png 152 | assets/launchicons/icon-96.png 153 | assets/launchicons/icon-100.png 154 | assets/launchicons/icon-114.png 155 | assets/launchicons/icon-120.png 156 | assets/launchicons/icon-144.png 157 | assets/launchicons/icon-152.png 158 | assets/launchicons/icon-512.png 159 | 160 | 161 | 164 | 165 | 166 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 215 | 216 | 225 | 226 | 227 | 228 | 229 | 232 | 233 | 234 | 235 | 236 | 237 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 267 | 268 | 270 | 271 | 272 | 273 | 274 | 276 | 277 | 278 | 279 | 280 | 282 | 283 | 284 | 285 | 286 | ]]> 287 | 288 | 289 | UIDeviceFamily 291 | 292 | 1 293 | 2 294 | 295 | 296 | NSAppTransportSecurity 297 | 298 | NSExceptionDomains 299 | 300 | firebaseio.com 301 | 302 | NSIncludesSubdomains 303 | 304 | NSThirdPartyExceptionRequiresForwardSecrecy 305 | 306 | 307 | 308 | 309 | ]]> 310 | high 311 | 312 | 313 | -------------------------------------------------------------------------------- /src/TodoApp.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | 4 | import feathers.utils.ScreenDensityScaleFactorManager; 5 | 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="#FFFFFF")] 15 | public class TodoApp extends Sprite 16 | { 17 | public function TodoApp() 18 | { 19 | if (this.stage) { 20 | this.stage.scaleMode = StageScaleMode.NO_SCALE; 21 | this.stage.align = StageAlign.TOP_LEFT; 22 | } 23 | this.mouseEnabled = this.mouseChildren = false; 24 | this.loaderInfo.addEventListener(Event.COMPLETE, loaderInfo_completeHandler); 25 | } 26 | 27 | private var myStarling:Starling; 28 | private var myScaler:ScreenDensityScaleFactorManager; 29 | 30 | private function loaderInfo_completeHandler(event:Event):void 31 | { 32 | Starling.multitouchEnabled = true; 33 | 34 | this.myStarling = new Starling(Main, this.stage, null, null, Context3DRenderMode.AUTO, "auto"); 35 | this.myScaler = new ScreenDensityScaleFactorManager(this.myStarling); 36 | this.myStarling.enableErrorChecking = false; 37 | //this.myStarling.showStats = true; 38 | this.myStarling.skipUnchangedFrames = true; 39 | 40 | this.myStarling.start(); 41 | 42 | this.stage.addEventListener(Event.DEACTIVATE, stage_deactivateHandler, false, 0, true); 43 | } 44 | 45 | private function stage_deactivateHandler(event:Event):void 46 | { 47 | this.myStarling.stop(); 48 | this.stage.addEventListener(Event.ACTIVATE, stage_activateHandler, false, 0, true); 49 | } 50 | 51 | private function stage_activateHandler(event:Event):void 52 | { 53 | this.stage.removeEventListener(Event.ACTIVATE, stage_activateHandler); 54 | this.myStarling.start(); 55 | } 56 | 57 | } 58 | } -------------------------------------------------------------------------------- /src/assets/icons/account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/icons/account.png -------------------------------------------------------------------------------- /src/assets/icons/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/icons/add.png -------------------------------------------------------------------------------- /src/assets/icons/back-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/icons/back-arrow.png -------------------------------------------------------------------------------- /src/assets/icons/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/icons/cancel.png -------------------------------------------------------------------------------- /src/assets/icons/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/icons/check.png -------------------------------------------------------------------------------- /src/assets/icons/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/icons/delete.png -------------------------------------------------------------------------------- /src/assets/icons/done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/icons/done.png -------------------------------------------------------------------------------- /src/assets/icons/person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/icons/person.png -------------------------------------------------------------------------------- /src/assets/icons/rounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/icons/rounded.png -------------------------------------------------------------------------------- /src/assets/icons/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/icons/save.png -------------------------------------------------------------------------------- /src/assets/icons/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/icons/warning.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-100.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-114.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-120.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-144.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-152.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-40.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-48.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-50.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-512.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-57.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-58.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-72.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-76.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-80.png -------------------------------------------------------------------------------- /src/assets/launchicons/icon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhantomAppDevelopment/todo-app/3e60dd031284927388124998fb689592d5488022/src/assets/launchicons/icon-96.png -------------------------------------------------------------------------------- /src/screens/AddTaskScreen.as: -------------------------------------------------------------------------------- 1 | package screens 2 | { 3 | import feathers.controls.Alert; 4 | import feathers.controls.Button; 5 | import feathers.controls.DateTimeSpinner; 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.layout.AnchorLayout; 13 | import feathers.layout.AnchorLayoutData; 14 | import feathers.layout.HorizontalAlign; 15 | import feathers.layout.VerticalLayout; 16 | import feathers.layout.VerticalLayoutData; 17 | 18 | import flash.events.Event; 19 | import flash.net.URLLoader; 20 | import flash.net.URLRequest; 21 | import flash.net.URLRequestMethod; 22 | 23 | import starling.display.DisplayObject; 24 | import starling.events.Event; 25 | 26 | import utils.RoundedRect; 27 | 28 | public class AddTaskScreen extends PanelScreen 29 | { 30 | private var alert:Alert; 31 | private var nameInput:TextInput; 32 | private var descriptionInput:TextInput; 33 | private var dueDate:DateTimeSpinner; 34 | 35 | override protected function initialize():void 36 | { 37 | super.initialize(); 38 | 39 | this.layout = new AnchorLayout(); 40 | this.title = "Add Task"; 41 | this.backButtonHandler = goBack; 42 | 43 | var backButton:Button = new Button(); 44 | backButton.addEventListener(starling.events.Event.TRIGGERED, goBack); 45 | backButton.styleNameList.add("back-button"); 46 | 47 | this.headerProperties.leftItems = new [backButton]; 48 | 49 | var doneIcon:ImageLoader = new ImageLoader(); 50 | doneIcon.source = "assets/icons/check.png"; 51 | doneIcon.width = doneIcon.height = 25; 52 | 53 | var doneButton:Button = new Button(); 54 | doneButton.defaultIcon = doneIcon; 55 | doneButton.addEventListener(starling.events.Event.TRIGGERED, saveTask); 56 | doneButton.styleNameList.add("header-button"); 57 | 58 | this.headerProperties.rightItems = new [doneButton]; 59 | 60 | var myLayout:VerticalLayout = new VerticalLayout(); 61 | myLayout.horizontalAlign = HorizontalAlign.CENTER; 62 | myLayout.gap = 12; 63 | 64 | var mainGroup:ScrollContainer = new ScrollContainer(); 65 | mainGroup.layoutData = new AnchorLayoutData(10, 10, 10, 10, NaN, NaN); 66 | mainGroup.layout = myLayout; 67 | mainGroup.padding = 12; 68 | mainGroup.backgroundSkin = RoundedRect.createRoundedRect(0x00695C); 69 | this.addChild(mainGroup); 70 | 71 | nameInput = new TextInput(); 72 | nameInput.layoutData = new VerticalLayoutData(100, NaN); 73 | nameInput.height = 50; 74 | nameInput.prompt = "Task Name"; 75 | mainGroup.addChild(nameInput); 76 | 77 | descriptionInput = new TextInput(); 78 | descriptionInput.layoutData = new VerticalLayoutData(100, NaN); 79 | descriptionInput.height = 100; 80 | descriptionInput.prompt = "Task Description"; 81 | descriptionInput.textEditorProperties.multiline = true; 82 | mainGroup.addChild(descriptionInput); 83 | 84 | var label1:Label = new Label(); 85 | label1.text = "Due Date"; 86 | mainGroup.addChild(label1); 87 | 88 | dueDate = new DateTimeSpinner(); 89 | mainGroup.addChild(dueDate); 90 | 91 | } 92 | 93 | private function saveTask():void 94 | { 95 | if (nameInput.text == "") { 96 | alert = Alert.show("A name is required.", "Error", new ListCollection( 97 | [ 98 | {label: "OK"} 99 | ])); 100 | } else { 101 | var myObject:Object = new Object(); 102 | myObject.title = nameInput.text; 103 | myObject.description = descriptionInput.text; 104 | myObject.due_date = dueDate.value.getTime(); 105 | myObject.start_date = new Date().getTime(); 106 | 107 | var request:URLRequest = new URLRequest(Firebase.FIREBASE_INSERT_URL + Firebase.LOGGED_USER_DATA.user_id + 108 | ".json" + "?auth=" + Firebase.FIREBASE_AUTH_TOKEN); 109 | request.data = JSON.stringify(myObject); 110 | request.method = URLRequestMethod.POST; 111 | 112 | var taskLoader:URLLoader = new URLLoader(); 113 | taskLoader.addEventListener(flash.events.Event.COMPLETE, taskSent); 114 | taskLoader.load(request); 115 | } 116 | } 117 | 118 | private function taskSent(event:flash.events.Event):void 119 | { 120 | goBack(); 121 | } 122 | 123 | private function goBack():void 124 | { 125 | if (alert) { 126 | alert.removeFromParent(true); 127 | } 128 | 129 | this.dispatchEventWith(starling.events.Event.COMPLETE); 130 | } 131 | 132 | } 133 | } -------------------------------------------------------------------------------- /src/screens/EditTaskScreen.as: -------------------------------------------------------------------------------- 1 | package screens 2 | { 3 | import feathers.controls.Alert; 4 | import feathers.controls.Button; 5 | import feathers.controls.DateTimeSpinner; 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.layout.AnchorLayout; 13 | import feathers.layout.AnchorLayoutData; 14 | import feathers.layout.HorizontalAlign; 15 | import feathers.layout.VerticalLayout; 16 | import feathers.layout.VerticalLayoutData; 17 | 18 | import flash.events.Event; 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.events.Event; 26 | 27 | import utils.NavigatorData; 28 | import utils.RoundedRect; 29 | 30 | public class EditTaskScreen extends PanelScreen 31 | { 32 | private var alert:Alert; 33 | private var nameInput:TextInput; 34 | private var descriptionInput:TextInput; 35 | private var dueDate:DateTimeSpinner; 36 | 37 | protected var _data:NavigatorData; 38 | 39 | public function get data():NavigatorData 40 | { 41 | return this._data; 42 | } 43 | 44 | public function set data(value:NavigatorData):void 45 | { 46 | this._data = value; 47 | } 48 | 49 | override protected function initialize():void 50 | { 51 | super.initialize(); 52 | 53 | this.layout = new AnchorLayout(); 54 | this.title = "Edit Task"; 55 | this.backButtonHandler = goBack; 56 | 57 | var backButton:Button = new Button(); 58 | backButton.addEventListener(starling.events.Event.TRIGGERED, goBack); 59 | backButton.styleNameList.add("back-button"); 60 | 61 | this.headerProperties.leftItems = new [backButton]; 62 | 63 | var saveIcon:ImageLoader = new ImageLoader(); 64 | saveIcon.source = "assets/icons/save.png"; 65 | saveIcon.width = saveIcon.height = 25; 66 | 67 | var saveButton:Button = new Button(); 68 | saveButton.defaultIcon = saveIcon; 69 | saveButton.addEventListener(starling.events.Event.TRIGGERED, updateTask); 70 | saveButton.styleNameList.add("header-button"); 71 | 72 | this.headerProperties.rightItems = new [saveButton]; 73 | 74 | var myLayout:VerticalLayout = new VerticalLayout(); 75 | myLayout.horizontalAlign = HorizontalAlign.CENTER; 76 | myLayout.gap = 12; 77 | 78 | var mainGroup:ScrollContainer = new ScrollContainer(); 79 | mainGroup.layoutData = new AnchorLayoutData(10, 10, 10, 10, NaN, NaN); 80 | mainGroup.layout = myLayout; 81 | mainGroup.padding = 12; 82 | mainGroup.backgroundSkin = RoundedRect.createRoundedRect(0x00695C); 83 | this.addChild(mainGroup); 84 | 85 | nameInput = new TextInput(); 86 | nameInput.layoutData = new VerticalLayoutData(100, NaN); 87 | nameInput.height = 50; 88 | nameInput.text = _data.selectedTask.title; 89 | nameInput.prompt = "Task Name"; 90 | mainGroup.addChild(nameInput); 91 | 92 | descriptionInput = new TextInput(); 93 | descriptionInput.layoutData = new VerticalLayoutData(100, NaN); 94 | descriptionInput.height = 100; 95 | descriptionInput.text = _data.selectedTask.description; 96 | descriptionInput.prompt = "Task Description"; 97 | descriptionInput.textEditorProperties.multiline = true; 98 | mainGroup.addChild(descriptionInput); 99 | 100 | var label1:Label = new Label(); 101 | label1.text = "Due Date"; 102 | mainGroup.addChild(label1); 103 | 104 | dueDate = new DateTimeSpinner(); 105 | dueDate.value = new Date(_data.selectedTask.due_date); 106 | mainGroup.addChild(dueDate); 107 | 108 | } 109 | 110 | private function updateTask():void 111 | { 112 | if (nameInput.text == "") { 113 | alert = Alert.show("A name is required.", "Error", new ListCollection( 114 | [ 115 | {label: "OK"} 116 | ])); 117 | } else { 118 | var myObject:Object = new Object(); 119 | myObject.title = nameInput.text; 120 | myObject.description = descriptionInput.text; 121 | myObject.due_date = dueDate.value.getTime(); 122 | 123 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "PATCH"); 124 | var request:URLRequest = new URLRequest(Firebase.FIREBASE_UPDATE_URL + Firebase.LOGGED_USER_DATA.user_id + 125 | "/" + _data.selectedTask.id + ".json?auth=" + Firebase.FIREBASE_AUTH_TOKEN); 126 | request.data = JSON.stringify(myObject); 127 | request.method = URLRequestMethod.POST; 128 | request.requestHeaders.push(header); 129 | 130 | var taskLoader:URLLoader = new URLLoader(); 131 | taskLoader.addEventListener(flash.events.Event.COMPLETE, taskUpdated); 132 | taskLoader.load(request); 133 | } 134 | } 135 | 136 | private function taskUpdated(event:flash.events.Event):void 137 | { 138 | goBack(); 139 | } 140 | 141 | private function goBack():void 142 | { 143 | if (alert) { 144 | alert.removeFromParent(true); 145 | } 146 | 147 | this.dispatchEventWith(starling.events.Event.COMPLETE); 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /src/screens/HomeScreen.as: -------------------------------------------------------------------------------- 1 | package screens 2 | { 3 | import feathers.controls.Alert; 4 | import feathers.controls.Button; 5 | import feathers.controls.Callout; 6 | import feathers.controls.ImageLoader; 7 | import feathers.controls.LayoutGroup; 8 | import feathers.controls.List; 9 | import feathers.controls.Panel; 10 | import feathers.controls.PanelScreen; 11 | import feathers.controls.TextInput; 12 | import feathers.core.PopUpManager; 13 | import feathers.data.ListCollection; 14 | import feathers.events.FeathersEventType; 15 | import feathers.layout.AnchorLayout; 16 | import feathers.layout.AnchorLayoutData; 17 | import feathers.layout.VerticalLayout; 18 | import feathers.layout.VerticalLayoutData; 19 | 20 | import flash.events.Event; 21 | import flash.events.IOErrorEvent; 22 | import flash.filesystem.File; 23 | import flash.filesystem.FileMode; 24 | import flash.filesystem.FileStream; 25 | import flash.net.URLLoader; 26 | import flash.net.URLRequest; 27 | import flash.net.URLRequestHeader; 28 | import flash.net.URLRequestMethod; 29 | 30 | import starling.display.DisplayObject; 31 | import starling.display.Quad; 32 | import starling.events.Event; 33 | 34 | import utils.NavigatorData; 35 | import utils.ProfileManager; 36 | import utils.Responses; 37 | import utils.SliderItemRenderer; 38 | 39 | public class HomeScreen extends PanelScreen 40 | { 41 | 42 | public static const GO_ADDTASK:String = "goAddTask"; 43 | public static const GO_DETAILS:String = "goDetails"; 44 | 45 | private var alert:Alert; 46 | private var tasksList:List; 47 | 48 | protected var _data:NavigatorData; 49 | 50 | public function get data():NavigatorData 51 | { 52 | return this._data; 53 | } 54 | 55 | public function set data(value:NavigatorData):void 56 | { 57 | this._data = value; 58 | } 59 | 60 | override protected function initialize():void 61 | { 62 | super.initialize(); 63 | 64 | this.title = "To Do's"; 65 | this..layout = new AnchorLayout(); 66 | 67 | var personIcon:ImageLoader = new ImageLoader(); 68 | personIcon.source = "assets/icons/person.png"; 69 | personIcon.width = personIcon.height = 25; 70 | 71 | var personButton:Button = new Button(); 72 | personButton.defaultIcon = personIcon; 73 | personButton.styleNameList.add("header-button"); 74 | personButton.addEventListener(starling.events.Event.TRIGGERED, showCallout); 75 | this.headerProperties.leftItems = new [personButton]; 76 | 77 | var addIcon:ImageLoader = new ImageLoader(); 78 | addIcon.source = "assets/icons/add.png"; 79 | addIcon.width = addIcon.height = 25; 80 | 81 | var addButton:Button = new Button(); 82 | addButton.defaultIcon = addIcon; 83 | addButton.styleNameList.add("header-button"); 84 | addButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 85 | { 86 | dispatchEventWith(GO_ADDTASK); 87 | }); 88 | this.headerProperties.rightItems = new [addButton]; 89 | 90 | var layoutForList:VerticalLayout = new VerticalLayout(); 91 | layoutForList.hasVariableItemDimensions = true; 92 | 93 | tasksList = new List(); 94 | tasksList.hasElasticEdges = false; 95 | tasksList.layout = layoutForList; 96 | tasksList.typicalItem = {title: "Task Title"}; 97 | tasksList.itemRendererType = SliderItemRenderer; 98 | tasksList.addEventListener("delete-task", deleteTask); 99 | tasksList.addEventListener("select-task", function ():void 100 | { 101 | _data.selectedTask = tasksList.selectedItem; 102 | dispatchEventWith(GO_DETAILS); 103 | }); 104 | 105 | tasksList.layoutData = new AnchorLayoutData(0, 0, 0, 0, NaN, NaN); 106 | this.addChild(tasksList); 107 | 108 | this.addEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 109 | } 110 | 111 | private function transitionComplete(event:starling.events.Event):void 112 | { 113 | this.removeEventListener(FeathersEventType.TRANSITION_IN_COMPLETE, transitionComplete); 114 | 115 | loadTasks(); 116 | } 117 | 118 | private function loadTasks():void 119 | { 120 | var request:URLRequest = new URLRequest(Firebase.FIREBASE_SELECT_URL + Firebase.LOGGED_USER_DATA.user_id + 121 | '.json?auth=' + Firebase.FIREBASE_AUTH_TOKEN); 122 | var tasksLoader:URLLoader = new URLLoader(); 123 | tasksLoader.addEventListener(flash.events.Event.COMPLETE, tasksLoaded); 124 | tasksLoader.load(request); 125 | } 126 | 127 | private function tasksLoaded(event:flash.events.Event):void 128 | { 129 | event.currentTarget.removeEventListener(flash.events.Event.COMPLETE, tasksLoaded); 130 | 131 | var rawData:Object = JSON.parse(String(event.currentTarget.data)); 132 | var tasksArray:Array = new Array(); 133 | 134 | for (var key:String in rawData) { 135 | tasksArray.push({ 136 | id: key, 137 | title: rawData[key].title, 138 | description: rawData[key].description, 139 | due_date: rawData[key].due_date, 140 | start_date: rawData[key].start_date 141 | }); 142 | } 143 | 144 | tasksList.dataProvider = new ListCollection(tasksArray); 145 | } 146 | 147 | private function deleteTask():void 148 | { 149 | var header:URLRequestHeader = new URLRequestHeader("X-HTTP-Method-Override", "DELETE"); 150 | var request:URLRequest = new URLRequest(Firebase.FIREBASE_DELETE_URL + Firebase.LOGGED_USER_DATA.user_id + 151 | "/" + tasksList.selectedItem.id + ".json?auth=" + Firebase.FIREBASE_AUTH_TOKEN); 152 | request.method = URLRequestMethod.POST; 153 | request.requestHeaders.push(header); 154 | 155 | var deleteTaskLoader:URLLoader = new URLLoader(); 156 | deleteTaskLoader.addEventListener(flash.events.Event.COMPLETE, function ():void 157 | { 158 | //Task Successfully deleted. 159 | }); 160 | deleteTaskLoader.load(request); 161 | } 162 | 163 | private function showCallout(event:starling.events.Event):void 164 | { 165 | var button:Button = Button(event.currentTarget); 166 | var content:LayoutGroup = new LayoutGroup(); 167 | content.layout = new VerticalLayout(); 168 | 169 | var updateEmailButton:Button = new Button(); 170 | updateEmailButton.styleNameList.add("callout-button"); 171 | updateEmailButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 172 | { 173 | alert = Alert.show("Do you want to update your Email Address?", "Update Email", new ListCollection( 174 | [ 175 | {label: "Cancel"}, 176 | {label: "OK", triggered: updateEmail} 177 | ])); 178 | }); 179 | updateEmailButton.label = "Update Email"; 180 | content.addChild(updateEmailButton); 181 | 182 | var updatePasswordButton:Button = new Button(); 183 | updatePasswordButton.styleNameList.add("callout-button"); 184 | updatePasswordButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 185 | { 186 | alert = Alert.show("Do you want to update your Password?", "Update Password", new ListCollection( 187 | [ 188 | {label: "Cancel"}, 189 | {label: "OK", triggered: updatePassword} 190 | ])); 191 | }); 192 | updatePasswordButton.label = "Update Password"; 193 | content.addChild(updatePasswordButton); 194 | 195 | var deleteAccountButton:Button = new Button(); 196 | deleteAccountButton.styleNameList.add("callout-button"); 197 | deleteAccountButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 198 | { 199 | alert = Alert.show("Do you want to permanently delete your account?", "Delete Account", new ListCollection( 200 | [ 201 | {label: "Cancel"}, 202 | {label: "OK", triggered: deleteAccount} 203 | ])); 204 | }); 205 | deleteAccountButton.label = "Delete Account"; 206 | content.addChild(deleteAccountButton); 207 | 208 | var signOutButton:Button = new Button(); 209 | signOutButton.styleNameList.add("callout-button"); 210 | signOutButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 211 | { 212 | alert = Alert.show("Do you want to sign out of your account?", "Sign Out", new ListCollection( 213 | [ 214 | {label: "Cancel"}, 215 | {label: "OK", triggered: signOut} 216 | ])); 217 | }); 218 | signOutButton.label = "Sign Out"; 219 | content.addChild(signOutButton); 220 | 221 | var callout:Callout = Callout.show(content, button); 222 | } 223 | 224 | private function updateEmail():void 225 | { 226 | var layoutForEmailPopUp:VerticalLayout = new VerticalLayout(); 227 | layoutForEmailPopUp.gap = 10; 228 | 229 | var emailPopUp:Panel = new Panel(); 230 | emailPopUp.padding = 10; 231 | emailPopUp.backgroundSkin = new Quad(3, 3, 0xE0F2F1); 232 | emailPopUp.layout = layoutForEmailPopUp; 233 | emailPopUp.width = emailPopUp.maxWidth = 250; 234 | emailPopUp.title = "Updating Email Address"; 235 | 236 | var emailInput:TextInput = new TextInput(); 237 | emailInput.layoutData = new VerticalLayoutData(100, NaN); 238 | emailInput.prompt = "Type your new Email Address."; 239 | emailPopUp.addChild(emailInput); 240 | 241 | var updateButton:Button = new Button(); 242 | updateButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 243 | { 244 | if (emailInput.text == "") { 245 | alert = Alert.show("New Email Address is required.", "Error", new ListCollection([{label: "OK"}])); 246 | } else { 247 | var myObject:Object = new Object(); 248 | myObject.email = emailInput.text; 249 | myObject.idToken = Firebase.FIREBASE_AUTH_TOKEN; 250 | 251 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 252 | 253 | var request:URLRequest = new URLRequest(Firebase.UPDATE_EMAIL); 254 | request.method = URLRequestMethod.POST; 255 | request.data = JSON.stringify(myObject); 256 | request.requestHeaders.push(header); 257 | 258 | var updateEmailLoader:URLLoader = new URLLoader(); 259 | updateEmailLoader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 260 | updateEmailLoader.addEventListener(flash.events.Event.COMPLETE, function ():void 261 | { 262 | PopUpManager.removePopUp(emailPopUp, true); 263 | alert = Alert.show("Email Address successfully updated.", "Success", new ListCollection([{label: "OK"}])); 264 | }); 265 | updateEmailLoader.load(request); 266 | } 267 | 268 | }); 269 | updateButton.layoutData = new VerticalLayoutData(100, NaN); 270 | updateButton.label = "Update Email Address"; 271 | updateButton.styleNameList.add("alert-button"); 272 | emailPopUp.addChild(updateButton); 273 | 274 | PopUpManager.addPopUp(emailPopUp, true, true, function ():DisplayObject 275 | { 276 | var quad:Quad = new Quad(3, 3, 0x000000); 277 | quad.alpha = 0.50; 278 | return quad; 279 | }); 280 | } 281 | 282 | private function updatePassword():void 283 | { 284 | var layoutForPasswordPopUp:VerticalLayout = new VerticalLayout(); 285 | layoutForPasswordPopUp.gap = 10; 286 | 287 | var passwordPopUp:Panel = new Panel(); 288 | passwordPopUp.padding = 10; 289 | passwordPopUp.backgroundSkin = new Quad(3, 3, 0xE0F2F1); 290 | passwordPopUp.layout = layoutForPasswordPopUp; 291 | passwordPopUp.width = passwordPopUp.maxWidth = 250; 292 | passwordPopUp.title = "Updating Password"; 293 | 294 | var passwordInput:TextInput = new TextInput(); 295 | passwordInput.layoutData = new VerticalLayoutData(100, NaN); 296 | passwordInput.displayAsPassword = true; 297 | passwordInput.prompt = "Type your new Password."; 298 | passwordPopUp.addChild(passwordInput); 299 | 300 | var updateButton:Button = new Button(); 301 | updateButton.addEventListener(starling.events.Event.TRIGGERED, function ():void 302 | { 303 | if (passwordInput.text == "") { 304 | alert = Alert.show("New Password is required.", "Error", new ListCollection([{label: "OK"}])); 305 | } else { 306 | var myObject:Object = new Object(); 307 | myObject.password = passwordInput.text; 308 | myObject.idToken = Firebase.FIREBASE_AUTH_TOKEN; 309 | 310 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 311 | 312 | var request:URLRequest = new URLRequest(Firebase.UPDATE_PASSWORD); 313 | request.method = URLRequestMethod.POST; 314 | request.data = JSON.stringify(myObject); 315 | request.requestHeaders.push(header); 316 | 317 | var updatePasswordLoader:URLLoader = new URLLoader(); 318 | updatePasswordLoader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 319 | updatePasswordLoader.addEventListener(flash.events.Event.COMPLETE, function ():void 320 | { 321 | //We update the conf file with the new email address. 322 | Firebase.LOGGED_USER_DATA.password = passwordInput.text; 323 | 324 | var file:File = File.applicationStorageDirectory.resolvePath("prefs.conf"); 325 | 326 | var fileStream:FileStream = new FileStream(); 327 | fileStream.open(file, FileMode.WRITE); 328 | fileStream.writeObject(Firebase.LOGGED_USER_DATA); 329 | fileStream.close(); 330 | 331 | PopUpManager.removePopUp(passwordPopUp, true); 332 | alert = Alert.show("Password successfully updated.", "Success", new ListCollection( 333 | [ 334 | {label: "OK"} 335 | ])); 336 | }); 337 | updatePasswordLoader.load(request); 338 | } 339 | 340 | }); 341 | updateButton.layoutData = new VerticalLayoutData(100, NaN); 342 | updateButton.label = "Update Password"; 343 | updateButton.styleNameList.add("alert-button"); 344 | passwordPopUp.addChild(updateButton); 345 | 346 | PopUpManager.addPopUp(passwordPopUp, true, true, function ():DisplayObject 347 | { 348 | var quad:Quad = new Quad(3, 3, 0x000000); 349 | quad.alpha = 0.50; 350 | return quad; 351 | }); 352 | } 353 | 354 | private function deleteAccount():void 355 | { 356 | var myObject:Object = new Object(); 357 | myObject.idToken = Firebase.FIREBASE_AUTH_TOKEN; 358 | 359 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 360 | 361 | var request:URLRequest = new URLRequest(Firebase.DELETE_ACCOUNT); 362 | request.method = URLRequestMethod.POST; 363 | request.data = JSON.stringify(myObject); 364 | request.requestHeaders.push(header); 365 | 366 | var deleteAccountLoader:URLLoader = new URLLoader(); 367 | deleteAccountLoader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 368 | deleteAccountLoader.addEventListener(flash.events.Event.COMPLETE, function ():void 369 | { 370 | var rawData:Object = JSON.parse(String(deleteAccountLoader.data)); 371 | 372 | if (rawData.kind == "identitytoolkit#DeleteAccountResponse") { 373 | //Account was successfully delete in the server 374 | signOut(); 375 | } 376 | }); 377 | deleteAccountLoader.load(request); 378 | } 379 | 380 | private function signOut():void 381 | { 382 | ProfileManager.signOut(); 383 | this.owner.replaceScreen("loginScreen"); 384 | } 385 | 386 | private function errorHandler(event:IOErrorEvent):void 387 | { 388 | var rawData:Object = JSON.parse(String(event.currentTarget.data)); 389 | alert = Alert.show(Responses[rawData.error.message], "Error", new ListCollection([{label: "OK"}])); 390 | } 391 | 392 | } 393 | } -------------------------------------------------------------------------------- /src/screens/LoginScreen.as: -------------------------------------------------------------------------------- 1 | package screens 2 | { 3 | 4 | import feathers.controls.Alert; 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.controls.text.TextFieldTextRenderer; 12 | import feathers.core.ITextRenderer; 13 | import feathers.data.ListCollection; 14 | import feathers.layout.AnchorLayout; 15 | import feathers.layout.AnchorLayoutData; 16 | import feathers.layout.HorizontalAlign; 17 | import feathers.layout.VerticalLayout; 18 | import feathers.layout.VerticalLayoutData; 19 | 20 | import flash.events.Event; 21 | import flash.events.IOErrorEvent; 22 | import flash.net.URLLoader; 23 | import flash.net.URLRequest; 24 | import flash.net.URLRequestHeader; 25 | import flash.net.URLRequestMethod; 26 | import flash.text.TextFormat; 27 | 28 | import starling.events.Event; 29 | 30 | import utils.ProfileManager; 31 | import utils.Responses; 32 | import utils.RoundedRect; 33 | 34 | public class LoginScreen extends PanelScreen 35 | { 36 | public static const GO_HOME:String = "goHomeScreen"; 37 | public static const GO_REGISTER:String = "goRegister"; 38 | 39 | private var alert:Alert; 40 | private var emailInput:TextInput; 41 | private var passwordInput:TextInput; 42 | 43 | override protected function initialize():void 44 | { 45 | super.initialize(); 46 | 47 | this.layout = new AnchorLayout(); 48 | this.title = "Welcome"; 49 | 50 | var myLayout:VerticalLayout = new VerticalLayout(); 51 | myLayout.horizontalAlign = HorizontalAlign.CENTER; 52 | myLayout.gap = 12; 53 | 54 | var mainGroup:ScrollContainer = new ScrollContainer(); 55 | mainGroup.layoutData = new AnchorLayoutData(10, 10, 10, 10, NaN, NaN); 56 | mainGroup.layout = myLayout; 57 | mainGroup.padding = 12; 58 | mainGroup.backgroundSkin = RoundedRect.createRoundedRect(0x00695C); 59 | this.addChild(mainGroup); 60 | 61 | var icon:ImageLoader = new ImageLoader(); 62 | icon.source = "assets/icons/account.png"; 63 | icon.width = icon.height = 110; 64 | mainGroup.addChild(icon); 65 | 66 | var label1:Label = new Label(); 67 | label1.layoutData = new VerticalLayoutData(100, NaN); 68 | label1.text = "Email"; 69 | mainGroup.addChild(label1); 70 | 71 | emailInput = new TextInput(); 72 | emailInput.layoutData = new VerticalLayoutData(100, NaN); 73 | emailInput.prompt = "Type your Email Address"; 74 | mainGroup.addChild(emailInput); 75 | 76 | var label2:Label = new Label(); 77 | label2.layoutData = new VerticalLayoutData(100, NaN); 78 | label2.text = "Password"; 79 | mainGroup.addChild(label2); 80 | 81 | passwordInput = new TextInput(); 82 | passwordInput.layoutData = new VerticalLayoutData(100, NaN); 83 | passwordInput.prompt = "Type your Password"; 84 | passwordInput.displayAsPassword = true; 85 | mainGroup.addChild(passwordInput); 86 | 87 | var loginBtn:Button = new Button(); 88 | loginBtn.addEventListener(starling.events.Event.TRIGGERED, login); 89 | loginBtn.layoutData = new VerticalLayoutData(100, NaN); 90 | loginBtn.styleNameList.add("white-button"); 91 | loginBtn.label = "Sign In"; 92 | mainGroup.addChild(loginBtn); 93 | 94 | var registerBtn:Button = new Button(); 95 | registerBtn.addEventListener(starling.events.Event.TRIGGERED, function ():void 96 | { 97 | dispatchEventWith(GO_REGISTER); 98 | }); 99 | registerBtn.label = "New User? Register here"; 100 | registerBtn.height = 40; 101 | registerBtn.styleProvider = null; 102 | registerBtn.labelFactory = function ():ITextRenderer 103 | { 104 | var renderer:TextFieldTextRenderer = new TextFieldTextRenderer(); 105 | renderer.isHTML = true; 106 | renderer.textFormat = new TextFormat("_sans", 16, 0xFFFFFF); 107 | return renderer; 108 | }; 109 | mainGroup.addChild(registerBtn); 110 | } 111 | 112 | private function login():void 113 | { 114 | if (emailInput.text == "" || passwordInput.text == "") { 115 | alert = Alert.show("Email and Password are required fields.", "Error", new ListCollection([{label: "OK"}])); 116 | } else { 117 | var myObject:Object = new Object(); 118 | myObject.email = emailInput.text; 119 | myObject.password = passwordInput.text; 120 | myObject.returnSecureToken = true; //We ask for a refreshToken in our response 121 | 122 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 123 | 124 | var request:URLRequest = new URLRequest(Firebase.EMAIL_PASSWORD_LOGIN); 125 | request.method = URLRequestMethod.POST; 126 | request.data = JSON.stringify(myObject); 127 | request.requestHeaders.push(header); 128 | 129 | var loginLoader:URLLoader = new URLLoader(); 130 | loginLoader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 131 | loginLoader.addEventListener(flash.events.Event.COMPLETE, loginComplete); 132 | loginLoader.load(request); 133 | } 134 | } 135 | 136 | private function loginComplete(event:flash.events.Event):void 137 | { 138 | //The user has successfully logged in to our Firebase project, now we are going to get an access_token. 139 | var rawData:Object = JSON.parse(event.currentTarget.data); 140 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 141 | 142 | var myObject:Object = new Object(); 143 | myObject.grant_type = "refresh_token"; 144 | myObject.refresh_token = rawData.refreshToken; //Here we use the refreshToken previously mentioned 145 | 146 | var request:URLRequest = new URLRequest(Firebase.FIREBASE_AUTH_TOKEN_URL); 147 | request.method = URLRequestMethod.POST; 148 | request.data = JSON.stringify(myObject); 149 | request.requestHeaders.push(header); 150 | 151 | var loader:URLLoader = new URLLoader(); 152 | loader.addEventListener(flash.events.Event.COMPLETE, authComplete); 153 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 154 | loader.load(request); 155 | } 156 | 157 | private function authComplete(event:flash.events.Event):void 158 | { 159 | var rawData:Object = JSON.parse(event.currentTarget.data); 160 | 161 | Firebase.LOGGED_USER_DATA = rawData; 162 | Firebase.FIREBASE_AUTH_TOKEN = rawData.access_token; 163 | ProfileManager.saveProfile(rawData); 164 | 165 | this.dispatchEventWith(GO_HOME); 166 | } 167 | 168 | private function resetPassword():void 169 | { 170 | var myObject:Object = new Object(); 171 | myObject.email = emailInput.text; 172 | myObject.requestType = "PASSWORD_RESET"; 173 | 174 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 175 | 176 | var request:URLRequest = new URLRequest(Firebase.RESET_PASSWORD); 177 | request.method = URLRequestMethod.POST; 178 | request.data = JSON.stringify(myObject); 179 | request.requestHeaders.push(header); 180 | 181 | var resetLoader:URLLoader = new URLLoader(); 182 | resetLoader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 183 | resetLoader.addEventListener(flash.events.Event.COMPLETE, function ():void 184 | { 185 | //Recover password email has been sent 186 | alert = Alert.show("A recovery message has been sent to your email address.", "Password Reset", new ListCollection([{label: "OK"}])); 187 | }); 188 | resetLoader.load(request); 189 | } 190 | 191 | private function errorHandler(event:IOErrorEvent):void 192 | { 193 | var rawData:Object = JSON.parse(event.currentTarget.data); 194 | trace(event.currentTarget.data); 195 | if (rawData.error.message == "INVALID_PASSWORD") { 196 | //If the user has an account we offer him to recover his password 197 | alert = Alert.show(rawData.error.message, "Error", new ListCollection( 198 | [ 199 | {label: "Reset Pass", triggered: resetPassword}, 200 | {label: "OK"} 201 | ])); 202 | } else { 203 | alert = Alert.show(Responses[rawData.error.message], "Error", new ListCollection([{label: "OK"}])); 204 | } 205 | } 206 | 207 | 208 | } 209 | } -------------------------------------------------------------------------------- /src/screens/RegisterScreen.as: -------------------------------------------------------------------------------- 1 | package screens 2 | { 3 | import feathers.controls.Alert; 4 | import feathers.controls.Button; 5 | import feathers.controls.ImageLoader; 6 | import feathers.controls.Label; 7 | import feathers.controls.PanelScreen; 8 | import feathers.controls.ScrollContainer; 9 | import feathers.controls.TextInput; 10 | import feathers.data.ListCollection; 11 | import feathers.layout.AnchorLayout; 12 | import feathers.layout.AnchorLayoutData; 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.events.Event; 26 | 27 | import utils.ProfileManager; 28 | import utils.Responses; 29 | import utils.RoundedRect; 30 | 31 | public class RegisterScreen extends PanelScreen 32 | { 33 | 34 | private var alert:Alert; 35 | private var emailInput:TextInput; 36 | private var passwordInput:TextInput; 37 | 38 | public static const GO_HOME:String = "goHomeScreen"; 39 | 40 | override protected function initialize():void 41 | { 42 | super.initialize(); 43 | 44 | this.layout = new AnchorLayout(); 45 | this.title = "Register"; 46 | this.backButtonHandler = goBack; 47 | 48 | var cancelIcon:ImageLoader = new ImageLoader(); 49 | cancelIcon.source = "assets/icons/cancel.png"; 50 | cancelIcon.width = cancelIcon.height = 25; 51 | 52 | var cancelBtn:Button = new Button(); 53 | cancelBtn.defaultIcon = cancelIcon; 54 | cancelBtn.styleNameList.add("header-button"); 55 | cancelBtn.addEventListener(starling.events.Event.TRIGGERED, goBack); 56 | this.headerProperties.rightItems = new [cancelBtn]; 57 | 58 | var myLayout:VerticalLayout = new VerticalLayout(); 59 | myLayout.horizontalAlign = HorizontalAlign.CENTER; 60 | myLayout.gap = 12; 61 | 62 | var mainGroup:ScrollContainer = new ScrollContainer(); 63 | mainGroup.layoutData = new AnchorLayoutData(10, 10, 10, 10, NaN, NaN); 64 | mainGroup.layout = myLayout; 65 | mainGroup.padding = 12; 66 | mainGroup.backgroundSkin = RoundedRect.createRoundedRect(0x00695C); 67 | this.addChild(mainGroup); 68 | 69 | var icon:ImageLoader = new ImageLoader(); 70 | icon.source = "assets/icons/account.png"; 71 | icon.width = icon.height = 110; 72 | mainGroup.addChild(icon); 73 | 74 | var label1:Label = new Label(); 75 | label1.layoutData = new VerticalLayoutData(100, NaN); 76 | label1.text = "Email"; 77 | mainGroup.addChild(label1); 78 | 79 | emailInput = new TextInput(); 80 | emailInput.layoutData = new VerticalLayoutData(100, NaN); 81 | emailInput.prompt = "Type your Email Address"; 82 | mainGroup.addChild(emailInput); 83 | 84 | var label2:Label = new Label(); 85 | label2.layoutData = new VerticalLayoutData(100, NaN); 86 | label2.text = "Password"; 87 | mainGroup.addChild(label2); 88 | 89 | passwordInput = new TextInput(); 90 | passwordInput.layoutData = new VerticalLayoutData(100, NaN); 91 | passwordInput.prompt = "Type your Password"; 92 | passwordInput.displayAsPassword = true; 93 | mainGroup.addChild(passwordInput); 94 | 95 | var registerBtn:Button = new Button(); 96 | registerBtn.addEventListener(starling.events.Event.TRIGGERED, register); 97 | registerBtn.layoutData = new VerticalLayoutData(100, NaN); 98 | registerBtn.styleNameList.add("white-button"); 99 | registerBtn.label = "Sign Up"; 100 | mainGroup.addChild(registerBtn); 101 | } 102 | 103 | private function register():void 104 | { 105 | if (emailInput.text == "" || passwordInput.text == "") { 106 | alert = Alert.show("Email and Password are required fields.", "Error", new ListCollection([{label: "OK"}])); 107 | } else { 108 | var myObject:Object = new Object(); 109 | myObject.email = emailInput.text; 110 | myObject.password = passwordInput.text; 111 | 112 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 113 | 114 | var request:URLRequest = new URLRequest(Firebase.EMAIL_PASSWORD_SIGNUP); 115 | request.method = URLRequestMethod.POST; 116 | request.data = JSON.stringify(myObject); 117 | request.requestHeaders.push(header); 118 | 119 | var registerLoader:URLLoader = new URLLoader(); 120 | registerLoader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 121 | registerLoader.addEventListener(flash.events.Event.COMPLETE, registerComplete); 122 | registerLoader.load(request); 123 | } 124 | } 125 | 126 | private function registerComplete(event:flash.events.Event):void 127 | { 128 | //The user has been registered to our Firebase project, now we are going to log in to get an access_token. 129 | var rawData:Object = JSON.parse(event.currentTarget.data); 130 | var header:URLRequestHeader = new URLRequestHeader("Content-Type", "application/json"); 131 | 132 | var myObject:Object = new Object(); 133 | myObject.grant_type = "refresh_token"; 134 | myObject.refresh_token = rawData.refreshToken; 135 | 136 | var request:URLRequest = new URLRequest(Firebase.FIREBASE_AUTH_TOKEN_URL); 137 | request.method = URLRequestMethod.POST; 138 | request.data = JSON.stringify(myObject); 139 | request.requestHeaders.push(header); 140 | 141 | var loader:URLLoader = new URLLoader(); 142 | loader.addEventListener(flash.events.Event.COMPLETE, authComplete); 143 | loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler); 144 | loader.load(request); 145 | } 146 | 147 | private function authComplete(event:flash.events.Event):void 148 | { 149 | var rawData:Object = JSON.parse(event.currentTarget.data); 150 | 151 | Firebase.LOGGED_USER_DATA = rawData; 152 | Firebase.FIREBASE_AUTH_TOKEN = rawData.access_token; 153 | ProfileManager.saveProfile(rawData); 154 | 155 | this.dispatchEventWith(GO_HOME); 156 | } 157 | 158 | private function errorHandler(event:IOErrorEvent):void 159 | { 160 | var rawData:Object = JSON.parse(String(event.currentTarget.data)); 161 | alert = Alert.show(Responses[rawData.error.message], "Error", new ListCollection([{label: "OK"}])); 162 | } 163 | 164 | private function goBack():void 165 | { 166 | this.dispatchEventWith(starling.events.Event.COMPLETE); 167 | } 168 | 169 | } 170 | } -------------------------------------------------------------------------------- /src/utils/NavigatorData.as: -------------------------------------------------------------------------------- 1 | package utils 2 | { 3 | import flash.utils.Dictionary; 4 | 5 | public dynamic class NavigatorData extends Dictionary 6 | { 7 | public function NavigatorData() 8 | { 9 | super(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /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 | private static const file:File = File.applicationStorageDirectory.resolvePath("profile.data"); 10 | private static var fileStream:FileStream; 11 | 12 | /** 13 | * Saves the data from a logged in user 14 | * 15 | * @param profile An Object containing profile information from Firebase. 16 | */ 17 | public static function saveProfile(profile:Object):void 18 | { 19 | fileStream = new FileStream(); 20 | fileStream.open(file, FileMode.WRITE); 21 | fileStream.writeObject(profile); 22 | fileStream.close(); 23 | } 24 | 25 | /** 26 | * Loads the user data from the profile.data file into an object. 27 | */ 28 | public static function loadProfile():Object 29 | { 30 | var myObject:Object = new Object(); 31 | 32 | if (file.exists) { 33 | fileStream = new FileStream(); 34 | fileStream.open(file, FileMode.READ); 35 | myObject = fileStream.readObject(); 36 | fileStream.close(); 37 | } else { 38 | myObject = {}; 39 | } 40 | 41 | return myObject; 42 | } 43 | 44 | /** 45 | * Checks if the user is logged in 46 | */ 47 | public static function isLoggedIn():Boolean 48 | { 49 | var tempObject:Object = loadProfile(); 50 | 51 | //We check if the profile exists by checking the existence of the localId value 52 | 53 | if (tempObject.user_id == null) { 54 | tempObject = null; 55 | return false; 56 | } else { 57 | tempObject = null; 58 | return true; 59 | } 60 | } 61 | 62 | /** 63 | * Signs out the user from the app by setting the profile.data file into an empty object. 64 | */ 65 | public static function signOut():void 66 | { 67 | fileStream = new FileStream(); 68 | fileStream.open(file, FileMode.WRITE); 69 | fileStream.writeObject({}); 70 | fileStream.close(); 71 | } 72 | 73 | } 74 | } -------------------------------------------------------------------------------- /src/utils/Responses.as: -------------------------------------------------------------------------------- 1 | package utils 2 | { 3 | /* 4 | 5 | This class contains all the error responses the Firebase API may output. 6 | These responses have been taken from the Firebase 3.4.0 JavaScript SDK. 7 | The response strings have been modified for clearer readibility. 8 | 9 | To use this class, you need to convert a Firebase JSON response into an AS3 Object using the JSON.parse() method 10 | 11 | var response:Object = JSON.parse(event.currentTarget.data); 12 | Alert.show(Responses[response.error.message], "Error"); 13 | 14 | */ 15 | public class Responses extends Object 16 | { 17 | Responses["INVALID_CUSTOM_TOKEN"] = "Invalid Custom Token"; 18 | Responses["CREDENTIAL_MISMATCH"] = "Custom Token Mismatch"; 19 | Responses["MISSING_CUSTOM_TOKEN"] = "Missing Custom Token"; 20 | Responses["INVALID_IDENTIFIER"] = "Invalid Email"; 21 | Responses["MISSING_CONTINUE_URI"] = "Missing Continue URI"; 22 | Responses["INVALID_EMAIL"] = "Invalid Email"; 23 | Responses["INVALID_PASSWORD"] = "Wrong Password"; 24 | Responses["USER_DISABLED"] = "User Disabled"; 25 | Responses["MISSING_PASSWORD"] = "Missing Password"; 26 | Responses["EMAIL_EXISTS"] = "Email Already in Use"; 27 | Responses["PASSWORD_LOGIN_DISABLED"] = "Operation Not Allowed"; 28 | Responses["INVALID_IDP_RESPONSE"] = "Invalid Credential"; 29 | Responses["FEDERATED_USER_ID_ALREADY_LINKED"] = "Credential Already in Use"; 30 | Responses["EMAIL_NOT_FOUND"] = "User Not Found"; 31 | Responses["EXPIRED_OOB_CODE"] = "Expired Action Code"; 32 | Responses["INVALID_OOB_CODE"] = "Invalid Action Code"; 33 | Responses["MISSING_OOB_CODE"] = "Missing Action Code"; 34 | Responses["CREDENTIAL_TOO_OLD_LOGIN_AGAIN"] = "Requires Recent Login"; 35 | Responses["INVALID_ID_TOKEN"] = "Invalid User Token"; 36 | Responses["TOKEN_EXPIRED"] = "User Token Expired"; 37 | Responses["USER_NOT_FOUND"] = "User Token Expired"; 38 | Responses["CORS_UNSUPPORTED"] = "Cross-Origin Resource Sharing Unsupported"; 39 | Responses["TOO_MANY_ATTEMPTS_TRY_LATER"] = "Too Many Attempts, Try Later"; 40 | Responses["WEAK_PASSWORD"] = "Weak Password"; 41 | Responses["OPERATION_NOT_ALLOWED"] = "Operation Not Allowed"; 42 | Responses["USER_CANCELLED"] = "User Cancelled" 43 | } 44 | } -------------------------------------------------------------------------------- /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(24, 24, 2, 2); 20 | myImage.color = color; 21 | return myImage; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/utils/SliderItemRenderer.as: -------------------------------------------------------------------------------- 1 | package utils 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.ScrollBarDisplayMode; 9 | import feathers.controls.ScrollContainer; 10 | import feathers.controls.renderers.LayoutGroupListItemRenderer; 11 | import feathers.events.FeathersEventType; 12 | import feathers.layout.AnchorLayout; 13 | import feathers.layout.AnchorLayoutData; 14 | import feathers.layout.HorizontalLayout; 15 | import feathers.layout.RelativePosition; 16 | 17 | import starling.animation.Tween; 18 | import starling.core.Starling; 19 | import starling.display.Quad; 20 | import starling.events.EnterFrameEvent; 21 | import starling.events.Event; 22 | import starling.events.ResizeEvent; 23 | import starling.text.TextFormat; 24 | 25 | public class SliderItemRenderer extends LayoutGroupListItemRenderer 26 | { 27 | protected static const FIXED_HEIGHT:Number = 70; 28 | 29 | protected var _leftThreshold:int = 50; 30 | protected var _rightThreshold:int = 50; 31 | protected var _additionalThreshold:int = 5; 32 | 33 | protected var _scrollerContainer:ScrollContainer; 34 | protected var _leftContent:LayoutGroup; 35 | protected var _middleContent:LayoutGroup; 36 | protected var _rightContent:LayoutGroup; 37 | 38 | protected var _label1:Label; 39 | protected var _label2:Label; 40 | 41 | protected var _transparentButton:BasicButton; 42 | protected var _button1:Button; 43 | protected var _button2:Button; 44 | 45 | 46 | public function SliderItemRenderer() 47 | { 48 | super(); 49 | this.minHeight = FIXED_HEIGHT; 50 | } 51 | 52 | override protected function initialize():void 53 | { 54 | this._scrollerContainer = new ScrollContainer(); 55 | this._scrollerContainer.scrollBarDisplayMode = ScrollBarDisplayMode.NONE; 56 | this._scrollerContainer.layout = new HorizontalLayout(); 57 | this._scrollerContainer.hasElasticEdges = false; 58 | this._scrollerContainer.decelerationRate = 0.3; 59 | this.addChild(_scrollerContainer); 60 | 61 | this._leftContent = new LayoutGroup(); 62 | this._leftContent.height = FIXED_HEIGHT; 63 | this._leftContent.layout = new AnchorLayout(); 64 | this._leftContent.backgroundSkin = new Quad(3, 3, 0x0066FF); 65 | this._scrollerContainer.addChild(this._leftContent); 66 | 67 | this._middleContent = new LayoutGroup(); 68 | this._middleContent.height = FIXED_HEIGHT; 69 | this._middleContent.layout = new AnchorLayout(); 70 | this._middleContent.backgroundSkin = new Quad(3, 3, 0xFFFFFF); 71 | this._scrollerContainer.addChild(this._middleContent); 72 | 73 | this._rightContent = new LayoutGroup(); 74 | this._rightContent.height = FIXED_HEIGHT; 75 | this._rightContent.layout = new AnchorLayout(); 76 | this._rightContent.backgroundSkin = new Quad(3, 3, 0xCC0000); 77 | this._scrollerContainer.addChild(this._rightContent); 78 | 79 | this._label1 = new Label(); 80 | this._label1.styleProvider = null; 81 | this._label1.layoutData = new AnchorLayoutData(12, 10, NaN, 10, NaN, NaN); 82 | this._label1.fontStyles = new TextFormat("_sans", 16, 0x000000, "left"); 83 | this._middleContent.addChild(this._label1); 84 | 85 | this._label2 = new Label(); 86 | this._label2.styleProvider = null; 87 | this._label2.layoutData = new AnchorLayoutData(NaN, 10, 12, 10, NaN, NaN); 88 | this._label2.fontStyles = new TextFormat("_sans", 16, 0x000000, "left"); 89 | this._middleContent.addChild(this._label2); 90 | 91 | this._transparentButton = new BasicButton(); 92 | this._transparentButton.styleProvider = null; 93 | this._transparentButton.addEventListener(Event.TRIGGERED, function ():void 94 | { 95 | owner.selectedIndex = owner.dataProvider.getItemIndex(data); 96 | 97 | _scrollerContainer.scrollToPageIndex(1, 0, 0.2); 98 | _scrollerContainer.snapToPages = true; 99 | 100 | if (!isSelected) { 101 | _scrollerContainer.scrollToPageIndex(1, 0, 0.3); 102 | _scrollerContainer.snapToPages = true; 103 | } 104 | 105 | _middleContent.backgroundSkin = new Quad(3, 3, 0x00796B); 106 | 107 | var bubblingEvent:Event = new Event("select-task", true); 108 | dispatchEvent(bubblingEvent); 109 | 110 | }); 111 | this._transparentButton.layoutData = new AnchorLayoutData(0, 0, 0, 0, NaN, NaN); 112 | this._middleContent.addChild(this._transparentButton); 113 | 114 | var icon1:ImageLoader = new ImageLoader(); 115 | icon1.width = icon1.height = 30; 116 | icon1.source = "assets/icons/done.png"; 117 | 118 | this._button1 = new Button(); 119 | this._button1.styleProvider = null; 120 | this._button1.addEventListener(Event.TRIGGERED, disposeRenderer); 121 | this._button1.gap = 5; 122 | this._button1.visible = false; 123 | this._button1.width = 50; 124 | this._button1.layoutData = new AnchorLayoutData(0, 0, 0, NaN, NaN, 0); 125 | this._button1.defaultIcon = icon1; 126 | this._button1.label = "Done"; 127 | this._button1.iconPosition = RelativePosition.TOP; 128 | this._button1.defaultSkin = new Quad(3, 3, 0x0066FF); 129 | this._button1.fontStyles = new TextFormat("_sans", 12, 0xFFFFFF) 130 | this._leftContent.addChild(this._button1); 131 | 132 | var icon2:ImageLoader = new ImageLoader(); 133 | icon2.width = icon2.height = 30; 134 | icon2.source = "assets/icons/delete.png"; 135 | 136 | this._button2 = new Button(); 137 | this._button2.styleProvider = null; 138 | this._button2.addEventListener(Event.TRIGGERED, disposeRenderer); 139 | this._button2.gap = 5; 140 | this._button2.visible = false; 141 | this._button2.width = 50; 142 | this._button2.layoutData = new AnchorLayoutData(0, NaN, 0, 0, NaN, 0); 143 | this._button2.defaultIcon = icon2; 144 | this._button2.label = "Delete"; 145 | this._button2.iconPosition = RelativePosition.TOP; 146 | this._button2.defaultSkin = new Quad(3, 3, 0xCC0000); 147 | this._button2.fontStyles = new TextFormat("_sans", 12, 0xFFFFFF) 148 | this._rightContent.addChild(this._button2); 149 | 150 | this.addEventListener(EnterFrameEvent.ENTER_FRAME, checkSelectedIndex); 151 | this._scrollerContainer.addEventListener(FeathersEventType.BEGIN_INTERACTION, startDrag); 152 | this._scrollerContainer.addEventListener(FeathersEventType.END_INTERACTION, stopDrag); 153 | 154 | stage.addEventListener(ResizeEvent.RESIZE, reSize); 155 | } 156 | 157 | override protected function commitData():void 158 | { 159 | if (this._data && this._owner) { 160 | this._label1.text = "" + this._data.title + ""; 161 | this._label2.text = new Date(this._data.due_date).toLocaleString(); 162 | 163 | this._button1.visible = false; 164 | this._button2.visible = false 165 | } else { 166 | this._label1.text = ""; 167 | this._label2.text = ""; 168 | } 169 | } 170 | 171 | override protected function postLayout():void 172 | { 173 | this._scrollerContainer.width = this.owner.width; 174 | this._scrollerContainer.pageWidth = this.owner.width; 175 | 176 | this._leftContent.width = this.owner.width; 177 | this._middleContent.width = this.owner.width; 178 | this._rightContent.width = this.owner.width; 179 | 180 | this._scrollerContainer.scrollToPageIndex(1, 0, 0); 181 | this._scrollerContainer.snapToPages = true; 182 | } 183 | 184 | protected function reSize(event:ResizeEvent):void 185 | { 186 | this._scrollerContainer.removeEventListener(Event.SCROLL, scrollingHandler); 187 | 188 | this.width = event.width; 189 | this._scrollerContainer.width = event.width; 190 | this._scrollerContainer.pageWidth = event.width; 191 | 192 | this._leftContent.width = event.width; 193 | this._middleContent.width = event.width; 194 | this._rightContent.width = event.width; 195 | 196 | this._scrollerContainer.scrollToPosition(0, event.width, 0); 197 | this._scrollerContainer.snapToPages = true; 198 | } 199 | 200 | protected function startDrag(event:Event):void 201 | { 202 | this._scrollerContainer.addEventListener(Event.SCROLL, scrollingHandler); 203 | this.owner.selectedIndex = owner.dataProvider.getItemIndex(data); 204 | 205 | //If the scrolling is touched by the user we show the buttons, this is to avoid unnecessary draw calls 206 | this._button1.visible = true; 207 | this._button2.visible = true; 208 | } 209 | 210 | protected function scrollingHandler(event:Event):void 211 | { 212 | //If slider has been dragged to the farright it will return to the middle of the right 213 | 214 | if (this._scrollerContainer.horizontalScrollPosition <= 40) { 215 | this._scrollerContainer.removeEventListener(Event.SCROLL, scrollingHandler); 216 | disposeRenderer(); 217 | } 218 | 219 | if (this._scrollerContainer.horizontalScrollPosition >= this._scrollerContainer.maxHorizontalScrollPosition - 40) { 220 | this._scrollerContainer.removeEventListener(Event.SCROLL, scrollingHandler); 221 | disposeRenderer(); 222 | } 223 | 224 | } 225 | 226 | protected function stopDrag(event:Event):void 227 | { 228 | if (this._scrollerContainer.isScrolling) { 229 | checkScrollPosition(); 230 | } else { 231 | checkSelectedIndex(); 232 | } 233 | } 234 | 235 | protected function checkScrollPosition():void 236 | { 237 | var currentPosition:int = this._scrollerContainer.horizontalScrollPosition; 238 | var tween:Tween; 239 | 240 | if (currentPosition <= Math.round(this._scrollerContainer.maxHorizontalScrollPosition / 2) - this._additionalThreshold && 241 | currentPosition >= 0) { 242 | this._scrollerContainer.snapToPages = false; 243 | 244 | tween = new Tween(this._scrollerContainer, 0.2); 245 | tween.animate("horizontalScrollPosition", (Math.round(this._scrollerContainer.maxHorizontalScrollPosition / 2)) - this._leftThreshold); 246 | Starling.juggler.add(tween); 247 | } 248 | 249 | else if (currentPosition >= Math.round(this._scrollerContainer.maxHorizontalScrollPosition / 2) + this._additionalThreshold && 250 | currentPosition <= this._scrollerContainer.maxHorizontalScrollPosition - this._additionalThreshold) { 251 | this._scrollerContainer.snapToPages = false; 252 | 253 | tween = new Tween(this._scrollerContainer, 0.2); 254 | tween.animate("horizontalScrollPosition", (Math.round(this._scrollerContainer.maxHorizontalScrollPosition / 2)) + this._rightThreshold); 255 | Starling.juggler.add(tween); 256 | } 257 | 258 | 259 | else { 260 | this._scrollerContainer.snapToPages = true; 261 | } 262 | } 263 | 264 | protected function disposeRenderer():void 265 | { 266 | var tween:Tween = new Tween(this, 0.2); 267 | tween.animate("height", 0); 268 | tween.onComplete = function ():void 269 | { 270 | var bubblingEvent:Event = new Event("delete-task", true); 271 | dispatchEvent(bubblingEvent); 272 | 273 | Starling.juggler.remove(tween); 274 | _owner.dataProvider.removeItemAt(_owner.selectedIndex); 275 | }; 276 | Starling.juggler.add(tween); 277 | } 278 | 279 | protected function checkSelectedIndex():void 280 | { 281 | if (!this.isSelected) { 282 | this._scrollerContainer.scrollToPageIndex(1, 0, 0.3); 283 | this._scrollerContainer.snapToPages = true; 284 | } 285 | } 286 | 287 | } 288 | } --------------------------------------------------------------------------------