├── .actionScriptProperties ├── .flexLibProperties ├── .gitignore ├── .project ├── LICENSE ├── README.md └── src └── main └── actionscript ├── assets ├── images │ ├── close.png │ ├── close_over.png │ ├── m6d-magnum-sidearm-16x16.png │ └── m6d-magnum-sidearm-50x50.png ├── sounds │ └── drop.mp3 └── style │ ├── dark.css │ ├── dark.swf │ ├── light.css │ └── light.swf └── com └── charlesbihis └── engine └── notification ├── NotificationConst.as ├── NotificationManager.as ├── event └── NotificationEvent.as ├── style └── style.css └── ui ├── Notification.as └── NotificationWindow.mxml /.actionScriptProperties: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.flexLibProperties: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .settings/* 2 | bin/* 3 | docs/* -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | actionscript-notification-engine 4 | 5 | 6 | 7 | 8 | 9 | com.adobe.flexbuilder.project.flexbuilder 10 | 11 | 12 | 13 | 14 | 15 | com.adobe.flexbuilder.project.flexlibnature 16 | com.adobe.flexbuilder.project.actionscriptnature 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 Charles Bihis 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ActionScript Notification Engine 2 | 3 | A powerful cross-platform notification engine. 4 | 5 | ## Overview 6 | 7 | M6D is a notification engine built on top of Adobe AIR. With a very simple interface, you can drop it into your own desktop AIR project and use it to deliver messenger-style notifications to your users! Think Growl, but for all platforms :) 8 | 9 | This project was first mentioned in my blog post at http://blogs.adobe.com/charles/2012/02/actionscript-notification-engine-open-sourced.html. 10 | 11 | ### Codename 12 | 13 | Project M6D Magnum Sidearm 14 | 15 | ### Features 16 | 17 | M6D supports the following features... 18 | 19 | * Ability to display messenger-style toast notifications as well as compact notifications. 20 | * Variable display length for notifications. 21 | * User-presence logic that detects when the user is at the computer. If the user is away, notifications are held on-screen and queued for when the user returns. 22 | * Ability to replay most recent five notifications. 23 | * Individual notification post settings, such as sticky, replayable, custom image, click URL, compact, etc. 24 | * Smart repositioning logic for sticky posts. 25 | * Ability to see a summary notification if user is away for an extended period of time. 26 | * Support for changing the notification images. 27 | * Support for custom styling as well as changing styles on the fly. 28 | 29 | ### Dependencies 30 | None 31 | 32 | ## Reference 33 | 34 | ### Usage 35 | 36 | To use the library, simply drop in the SWC (or the source) into your project and follow the patterns below... 37 | 38 | // create engine with default settings 39 | var notificationManager:NotificationManager = new NotificationManager("/assets/style/dark.swf", // default style 40 | "/assets/m6d-magnum-sidearm-50x50.png", // default notification image 41 | "/assets/m6d-magnum-sidearm-16x16.png", // default compact notification image 42 | "/assets/sounds/drop.mp3" // (optional) default notification sound 43 | NotificationConst.DISPLAY_LENGTH_DEFAULT, // (optional) default display length 44 | NotificationConst.DISPLAY_LOCATION_AUTO); // (optional) default display location 45 | 46 | // now that we have an engine, let's create a notification and show it 47 | var notification:Notification = new Notification(); 48 | notification.title = "Derek ► Jacobim"; 49 | notification.message = "What is this? A center for ANTS?!"; 50 | notification.image = "/assets/images/profile/derek/avatar.png"; 51 | notification.link = "http://www.youtube.com/watch?v=_6GqqIvfSVQ"; 52 | notification.isCompact = false; 53 | notification.isSticky = false; 54 | notification.isReplayable = true; 55 | notificationManager.showNotification(notification); 56 | 57 | // we can also show notifications quickly using this API too 58 | notificationManager.show("Derek Zoolander Foundation", "Now open!", "/assets/images/dzf-logo-50x50.png"); 59 | 60 | You can also change the engine's default settings on the fly too! 61 | 62 | // let's change the default images, display length, and display location 63 | notificationManager.defaultNotificationImage = "/assets/images/dzf-logo-50x50.png"; 64 | notificationManager.defaultCompactNotificationImage = "/assets/images/dzf-logo-16x16.png"; 65 | notificationManager.displayLength = NotificationConst.DISPLAY_LENGTH_SHORT; 66 | notificationManager.displayLocation = NotificationConst.DISPLAY_LOCATION_TOP_RIGHT; 67 | 68 | // we can even change the style and sound settings on the fly too! 69 | notificationManager.loadStyle("/assets/style/light.swf"); 70 | notificationManager.loadSound("/assets/sounds/bing.mp3"); 71 | 72 | ### Demo 73 | 74 | * Live demo: http://blogs.adobe.com/charles/2012/02/actionscript-notification-engine-open-sourced.html 75 | * Demo source: https://github.com/charlesbihis/sandbox/tree/master/actionscript/actionscript-notification-engine-demo 76 | 77 | ### Documentation 78 | 79 | You can find the full ASDocs for the project [here](http://charlesbihis.github.com/actionscript-notification-engine/docs/). 80 | 81 | ## Special Notes 82 | 83 | ### Note on Assets 84 | 85 | There are assets included in the project under the path /src/main/actionscript/assets/. However, since library projects cannot include assets that aren't embedded, these will have to be included in your main project and referenced accordingly. That is, if you try and reference them from the location in the library project, it will fail. You must put them in your own containing project alongside your own assets and reference them as you do your other assets. This includes all non-embedded images, sounds, and stylesheets. 86 | 87 | ## Author 88 | 89 | * Created by Charles Bihis 90 | * Website: [www.whoischarles.com](http://www.whoischarles.com) 91 | * E-mail: [charles@whoischarles.com](mailto:charles@whoischarles.com) 92 | * Twitter: [@charlesbihis](http://www.twitter.com/charlesbihis) 93 | 94 | ## License 95 | 96 | The ActionScript Notification Engine (a.k.a. Project M6D Magnum Sidearm) is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). -------------------------------------------------------------------------------- /src/main/actionscript/assets/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlesbihis/actionscript-notification-engine/87bb2793d9e58fb19a0eac5647113d661b5c3e29/src/main/actionscript/assets/images/close.png -------------------------------------------------------------------------------- /src/main/actionscript/assets/images/close_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlesbihis/actionscript-notification-engine/87bb2793d9e58fb19a0eac5647113d661b5c3e29/src/main/actionscript/assets/images/close_over.png -------------------------------------------------------------------------------- /src/main/actionscript/assets/images/m6d-magnum-sidearm-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlesbihis/actionscript-notification-engine/87bb2793d9e58fb19a0eac5647113d661b5c3e29/src/main/actionscript/assets/images/m6d-magnum-sidearm-16x16.png -------------------------------------------------------------------------------- /src/main/actionscript/assets/images/m6d-magnum-sidearm-50x50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlesbihis/actionscript-notification-engine/87bb2793d9e58fb19a0eac5647113d661b5c3e29/src/main/actionscript/assets/images/m6d-magnum-sidearm-50x50.png -------------------------------------------------------------------------------- /src/main/actionscript/assets/sounds/drop.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlesbihis/actionscript-notification-engine/87bb2793d9e58fb19a0eac5647113d661b5c3e29/src/main/actionscript/assets/sounds/drop.mp3 -------------------------------------------------------------------------------- /src/main/actionscript/assets/style/dark.css: -------------------------------------------------------------------------------- 1 | /* CSS file */ 2 | @namespace s "library://ns.adobe.com/flex/spark"; 3 | @namespace mx "library://ns.adobe.com/flex/mx"; 4 | 5 | .notificationContainer 6 | { 7 | backgroundAlpha: 0.0; 8 | } 9 | 10 | .notification 11 | { 12 | cornerRadius: 8; 13 | backgroundColor: #000; 14 | backgroundAlpha: 0.8; 15 | borderStyle: none; 16 | borderColor: #000; 17 | paddingTop: 10; 18 | paddingBottom: 10; 19 | paddingLeft: 15; 20 | paddingRight: 15; 21 | } 22 | 23 | .notificationText 24 | { 25 | color: #FFF; 26 | } -------------------------------------------------------------------------------- /src/main/actionscript/assets/style/dark.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlesbihis/actionscript-notification-engine/87bb2793d9e58fb19a0eac5647113d661b5c3e29/src/main/actionscript/assets/style/dark.swf -------------------------------------------------------------------------------- /src/main/actionscript/assets/style/light.css: -------------------------------------------------------------------------------- 1 | /* CSS file */ 2 | @namespace s "library://ns.adobe.com/flex/spark"; 3 | @namespace mx "library://ns.adobe.com/flex/mx"; 4 | 5 | .notificationContainer 6 | { 7 | backgroundAlpha: 0.0; 8 | } 9 | 10 | .notification 11 | { 12 | cornerRadius: 8; 13 | backgroundColor: #F5F5F5; 14 | backgroundAlpha: 1.0; 15 | borderStyle: solid; 16 | borderColor: #b9b9b9; 17 | paddingTop: 10; 18 | paddingBottom: 10; 19 | paddingLeft: 15; 20 | paddingRight: 15; 21 | } 22 | 23 | .notificationText 24 | { 25 | color: #000; 26 | } -------------------------------------------------------------------------------- /src/main/actionscript/assets/style/light.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlesbihis/actionscript-notification-engine/87bb2793d9e58fb19a0eac5647113d661b5c3e29/src/main/actionscript/assets/style/light.swf -------------------------------------------------------------------------------- /src/main/actionscript/com/charlesbihis/engine/notification/NotificationConst.as: -------------------------------------------------------------------------------- 1 | package com.charlesbihis.engine.notification 2 | { 3 | /** 4 | * Class containing frequently used constants. 5 | * 6 | * @langversion ActionScript 3.0 7 | * @playerversion Flash 10 8 | * 9 | * @author Charles Bihis (www.whoischarles.com) 10 | */ 11 | public class NotificationConst 12 | { 13 | ////////////// 14 | // DEFAULTS // 15 | ////////////// 16 | 17 | /** 18 | * Default display length is set to DISPLAY_LENGTH_MEDIUM. 19 | */ 20 | public static const DISPLAY_LENGTH_DEFAULT:int = DISPLAY_LENGTH_MEDIUM; 21 | 22 | /** 23 | * Default display location is set to DISPLAY_LOCATION_AUTO. 24 | */ 25 | public static const DISPLAY_LOCATION_DEFAULT:String = DISPLAY_LOCATION_AUTO; 26 | 27 | 28 | //////////////////// 29 | // DISPLAY LENGTH // 30 | //////////////////// 31 | 32 | /** 33 | * Short display length displays a notification for a duration of 4 seconds. 34 | */ 35 | public static const DISPLAY_LENGTH_SHORT:int = 4; 36 | 37 | /** 38 | * Medium display length displays a notification for a duration of 8 seconds. 39 | */ 40 | public static const DISPLAY_LENGTH_MEDIUM:int= 8; 41 | 42 | /** 43 | * Long display length displays a notification for a duration of 12 seconds. 44 | */ 45 | public static const DISPLAY_LENGTH_LONG:int = 12; 46 | 47 | /** 48 | * Duration of time inbetween repositioning of posts. Is a prime number larger 49 | * than the DISPLAY_LENGTH_LONG value, so as not to collide with any new notifications. 50 | */ 51 | public static const REPOSITION_LENGTH:int = 13; 52 | 53 | 54 | ////////////////////// 55 | // DISPLAY LOCATION // 56 | ////////////////////// 57 | 58 | /** 59 | * Displays notifications in the top-left corner of the users main screen. 60 | */ 61 | public static const DISPLAY_LOCATION_TOP_LEFT:String = "topLeft"; 62 | 63 | /** 64 | * Displays notifications in the top-right corner of the users main screen. 65 | */ 66 | public static const DISPLAY_LOCATION_TOP_RIGHT:String = "topRight"; 67 | 68 | /** 69 | * Displays notifications in the bottom-left corner of the users main screen. 70 | */ 71 | public static const DISPLAY_LOCATION_BOTTOM_LEFT:String = "bottomLeft"; 72 | 73 | /** 74 | * Displays notifications in the bottom-right corner of the users main screen. 75 | */ 76 | public static const DISPLAY_LOCATION_BOTTOM_RIGHT:String = "bottomRight"; 77 | 78 | /** 79 | * Auto-detects the users operating system and attempts to set a logical 80 | * default for display location. If the user is on a Mac, this will set 81 | * the display location to DISPLAY_LOCATION_TOP_RIGHT. Otherwise, if 82 | * the user is on another system (e.g. Windows or Linux), the display 83 | * location is set to DISPLAY_LOCATION_BOTTOM_RIGHT. 84 | */ 85 | public static const DISPLAY_LOCATION_AUTO:String = "auto"; 86 | } // class declaration 87 | } // package -------------------------------------------------------------------------------- /src/main/actionscript/com/charlesbihis/engine/notification/NotificationManager.as: -------------------------------------------------------------------------------- 1 | package com.charlesbihis.engine.notification 2 | { 3 | import com.charlesbihis.engine.notification.event.NotificationEvent; 4 | import com.charlesbihis.engine.notification.ui.Notification; 5 | 6 | import flash.desktop.NativeApplication; 7 | import flash.events.Event; 8 | import flash.events.EventDispatcher; 9 | import flash.events.IEventDispatcher; 10 | import flash.events.IOErrorEvent; 11 | import flash.media.Sound; 12 | import flash.net.URLRequest; 13 | import flash.system.Capabilities; 14 | import flash.utils.setTimeout; 15 | 16 | import mx.collections.ArrayCollection; 17 | import mx.core.FlexGlobals; 18 | import mx.events.StyleEvent; 19 | import mx.logging.ILogger; 20 | import mx.logging.Log; 21 | import mx.logging.LogEventLevel; 22 | import mx.logging.targets.TraceTarget; 23 | import mx.utils.ObjectUtil; 24 | 25 | /** 26 | * Event broadcast when an active notification has just been closed. 27 | * 28 | * @eventType com.charlesbihis.engine.notification.NotificationEvent.NOTIFICATION_CLOSE 29 | * 30 | * @see com.charlesbihis.engine.notification.NotificationEvent 31 | */ 32 | [Event(name="notificationCloseEvent", type="com.charlesbihis.engine.notification.event.NotificationEvent")] 33 | 34 | /** 35 | * Event broadcast when an action to close all active notifications has been invoked. 36 | * 37 | * @eventType com.charlesbihis.engine.notification.NotificationEvent.CLOSE_ALL_NOTIFICATIONS 38 | * 39 | * @see com.charlesbihis.engine.notification.NotificationEvent 40 | */ 41 | [Event(name="closeAllNotificationsEvent", type="com.charlesbihis.engine.notification.event.NotificationEvent")] 42 | 43 | /** 44 | * Event broadcast when the user has gone idle. 45 | * 46 | * @eventType com.charlesbihis.engine.notification.NotificationEvent.USER_IS_IDLE 47 | * 48 | * @see com.charlesbihis.engine.notification.NotificationEvent 49 | */ 50 | [Event(name="userIsIdleEvent", type="com.charlesbihis.engine.notification.event.NotificationEvent")] 51 | 52 | /** 53 | * Event broadcast when the user has returned from idle. 54 | * 55 | * @eventType com.charlesbihis.engine.notification.NotificationEvent.USER_IS_PRESENT 56 | * 57 | * @see com.charlesbihis.engine.notification.NotificationEvent 58 | */ 59 | [Event(name="userIsPresentEvent", type="com.charlesbihis.engine.notification.event.NotificationEvent")] 60 | 61 | /** 62 | * Main service class for Notification Engine. 63 | * 64 | * @langversion ActionScript 3.0 65 | * @playerversion Flash 10 66 | * 67 | * @author Charles Bihis (www.whoischarles.com) 68 | */ 69 | public class NotificationManager extends EventDispatcher 70 | { 71 | /** 72 | * Array of strings containing title names of other AIR windows that should be noted 73 | * when displaying notifications. Any title names in this array will be compared against 74 | * our notification display locations to avoid any overlapping. 75 | */ 76 | public static var otherWindowsToAvoid:Array = new Array(); 77 | 78 | private static const NOTIFICATION_THROTTLE_TIME:int = 500; 79 | private static const NOTIFICATION_IDLE_THRESHOLD:int = 15; 80 | private static const NOTIFICATION_MAX_REPLAY_COUNT:int = 5; 81 | private static const MAX_ACTIVE_TOASTS:int = 5; 82 | private static const MINIMUM_TIME_BETWEEN_NOTIFICATION_SOUNDS:int = 10000; // 10 seconds 83 | 84 | private var log:ILogger = Log.getLogger("com.charlesbihis.engine.notification.NotificationManager"); 85 | private var queue:ArrayCollection; 86 | private var previousQueue:ArrayCollection; 87 | private var latestNotificationDisplay:Number; 88 | private var latestNotificationSound:Number; 89 | private var suppressedNotificationCount:int; 90 | private var activeToasts:int; 91 | private var notificationSound:Sound; 92 | private var soundLoaded:Boolean; 93 | private var themeLoaded:Boolean; 94 | 95 | private var _defaultNotificationImage:String; 96 | private var _defaultCompactNotificationImage:String; 97 | private var _displayLocation:String; 98 | private var _displayLength:int; 99 | private var _isUserIdle:Boolean; 100 | 101 | /** 102 | * Constructor to create a valid NotificationManager object. Will 103 | * create a notification engine with the passed in values as defaults settings. 104 | * Can also change these settings afterwards, but the first three at least (defaultStyle, 105 | * defaultNotificationImage, and defaultCompactNotificationImage) are required. The 106 | * rest are optional and can be omitted or left null. 107 | * 108 | * @param defaultStyle Location of compiled stylesheet to use as default style. 109 | * @param defaultNotificationImage Location of 50x50 image to use as default notification image when one isn't specified. 110 | * @param defaultCompactNotificationImage Location of 16x16 image to use as default compact notification image when one isn't specified. 111 | * @param notificationSound (Optional) Location of notification sound to use when displaying a notification. Leave null if no sound desired. Defaults to null. 112 | * @param displayLength (Optional) Length, in seconds, that notifications will stay on the screen. Defaults to NotificationConst.DISPLAY_LENGTH_MEDIUM. 113 | * @param displayLocation (Optional) Location on-screen (i.e. top-left, top-right, bottom-left, or bottom-right) where notifications are to appear. Defaults to NotificationConst.DISPLAY_LOCATION_AUTO. 114 | * 115 | * @see com.charlesbihis.engine.notification.NotificationConst 116 | */ 117 | public function NotificationManager(defaultStyle:String, defaultNotificationImage:String, defaultCompactNotificationImage:String, notificationSound:String = null, displayLength:int = NotificationConst.DISPLAY_LENGTH_MEDIUM, displayLocation:String = NotificationConst.DISPLAY_LOCATION_AUTO) 118 | { 119 | // initialize 120 | this.queue = new ArrayCollection(); 121 | this.previousQueue = new ArrayCollection(); 122 | this.latestNotificationDisplay = 0; 123 | this.latestNotificationSound = 0; 124 | this.suppressedNotificationCount = 0; 125 | this.activeToasts = 0; 126 | this.notificationSound = null; 127 | this.soundLoaded = false; 128 | this.themeLoaded = false; 129 | 130 | // set up logging 131 | var logTarget:TraceTarget = new TraceTarget(); 132 | logTarget.level = LogEventLevel.ALL; 133 | logTarget.includeDate = true; 134 | logTarget.includeTime = true; 135 | logTarget.includeCategory = true; 136 | logTarget.includeLevel = true; 137 | Log.addTarget(logTarget); 138 | 139 | // configure default settings 140 | _defaultNotificationImage = defaultNotificationImage; 141 | _defaultCompactNotificationImage = defaultCompactNotificationImage; 142 | _displayLength = displayLength; 143 | _displayLocation = displayLocation; 144 | 145 | // listen for important events 146 | addEventListener(NotificationEvent.NOTIFICATION_CLOSE, notificationCloseHandler); 147 | NativeApplication.nativeApplication.idleThreshold = NOTIFICATION_IDLE_THRESHOLD; 148 | NativeApplication.nativeApplication.addEventListener(Event.USER_IDLE, userIdleHandler); 149 | NativeApplication.nativeApplication.addEventListener(Event.USER_PRESENT, userPresentHandler); 150 | 151 | // if display location is set to "auto", configure default display location based on detected operating system 152 | if (_displayLocation == NotificationConst.DISPLAY_LOCATION_AUTO) 153 | { 154 | log.info("Display location has been set to \"auto\". Configuring based on detected operating system"); 155 | 156 | var os:String = flash.system.Capabilities.os; 157 | if (os.indexOf("Mac") >= 0) 158 | { 159 | log.info("Display location has detected an operating system of \"{0}\". Setting default display location to top right.", os); 160 | _displayLocation = NotificationConst.DISPLAY_LOCATION_TOP_RIGHT; 161 | } // if statement 162 | else 163 | { 164 | log.info("Display location has detected an operating system of \"{0}\". Setting default display location to bottom right.", os); 165 | _displayLocation = NotificationConst.DISPLAY_LOCATION_BOTTOM_RIGHT; 166 | } // else statement 167 | } // if statement 168 | 169 | // load default style 170 | var loadStyleEvent:IEventDispatcher = FlexGlobals.topLevelApplication.styleManager.loadStyleDeclarations2(defaultStyle); 171 | loadStyleEvent.addEventListener(StyleEvent.COMPLETE, loadStyleHandler); 172 | 173 | // load notification sound 174 | if (notificationSound != null) 175 | { 176 | this.notificationSound = new Sound(new URLRequest(notificationSound)); 177 | this.notificationSound.addEventListener(Event.COMPLETE, loadSoundHandler); 178 | this.notificationSound.addEventListener(IOErrorEvent.IO_ERROR, loadSoundHandler); 179 | } // if statement 180 | 181 | function notificationCloseHandler(event:Event):void 182 | { 183 | activeToasts--; 184 | log.debug("Notification closed. There are now {0} active toasts", activeToasts); 185 | } // notificationCloseHandler 186 | } // NotificationManager 187 | 188 | /** 189 | * Method to show a notification. 190 | * 191 | * @param notification A notification object for which to display 192 | * 193 | * @see com.charlesbihis.engine.notification.Notification 194 | */ 195 | public function showNotification(notification:Notification):void 196 | { 197 | log.debug("showNotification() called with notification object: {0}", ObjectUtil.toString(notification)); 198 | 199 | // set image to default if none provided 200 | if (notification.image == null || notification.image.length == 0) 201 | { 202 | notification.image = (notification.isCompact ? _defaultCompactNotificationImage : _defaultNotificationImage); 203 | } // if statement 204 | 205 | // place it in the previousQueue for possibly replaying later, 206 | if (notification.isReplayable) 207 | { 208 | previousQueue.addItem(notification); 209 | } // if statement 210 | 211 | // make sure we only store a max of 5 notifications 212 | while (previousQueue.length > NOTIFICATION_MAX_REPLAY_COUNT) 213 | { 214 | previousQueue.removeItemAt(0); 215 | } // while loop 216 | 217 | // queue it so we avoid overlapping notification windows 218 | queue.addItem(notification); 219 | 220 | // start processing the queue 221 | showAll(); 222 | } // showNotification 223 | 224 | /** 225 | * Method to show notification using raw, passed-in values. This is really 226 | * a convenience method for quickly showing a notification. Behind the scenes, 227 | * it simply creates a notification object and calls the showNotification() API 228 | * with that object. 229 | * 230 | * @param notificationTitle The title for the notification pop-up. 231 | * @param notificationMessage The message for the notification pop-up. 232 | * @param notificationLink (Optional) The URL, if any, to direct the user to when the notification is clicked. If null, there will be no action on notification click. Defaults to null. 233 | * @param isCompact (Optional) Parameter to set whether or not the notification should be displayed as a compact notification. If this is true, the notification message will not be displayed. Defaults to false. 234 | * @param isSticky (Optional) Parameter to set whether or not the notification is sticky and should remain on-screen until the user manually closes it. Defaults to false. 235 | * @param isReplayable (Optional) Parameter to set whether or not the notification is replayable, meaning whether or not it will show up when the replayLatestFiveNotifications() API is invoked. This is most often set to true, but would be set to false for certain types of notifications such as system notifications. Defaults to true. 236 | */ 237 | public function show(notificationTitle:String, notificationMessage:String, notificationImage:String, notificationLink:String = null, isCompact:Boolean = false, isSticky:Boolean = false, isReplayable:Boolean = true):void 238 | { 239 | var notification:Notification = new Notification(); 240 | notification.title = notificationTitle; 241 | notification.message = notificationMessage; 242 | notification.image = notificationImage; 243 | notification.link = notificationLink; 244 | notification.isCompact = isCompact; 245 | notification.isSticky = isSticky; 246 | notification.isReplayable = isReplayable; 247 | 248 | log.debug("show() called with values producing notification object: {0}", ObjectUtil.toString(notification)); 249 | showNotification(notification); 250 | } // show 251 | 252 | /** 253 | * Method to replay the latest 5 updates. 254 | */ 255 | public function replayLatestFiveUpdates():void 256 | { 257 | log.info("Replaying latest five updates"); 258 | 259 | // if there are no recent messages, tell the user 260 | if (previousQueue.length == 0) 261 | { 262 | log.info("No updates to display"); 263 | var noUpdatesNotification:Notification = new Notification(); 264 | noUpdatesNotification.title = "No Updates to Show"; 265 | noUpdatesNotification.isReplayable = false; 266 | showNotification(noUpdatesNotification); 267 | 268 | return; 269 | } // if statement 270 | 271 | for (var i:int = 0; i < previousQueue.length; i++) 272 | { 273 | // must create new notification with same properties since previously closed windows cannot be re-opened 274 | var previousNotification:Notification = previousQueue.getItemAt(i) as Notification; 275 | var notification:Notification = new Notification(); 276 | notification.title = previousNotification.title; 277 | notification.message = previousNotification.message; 278 | notification.image = previousNotification.image; 279 | notification.link = previousNotification.link; 280 | notification.isCompact = previousNotification.isCompact; 281 | notification.isSticky = previousNotification.isSticky; 282 | notification.isReplayable = previousNotification.isReplayable; 283 | 284 | log.debug("Replaying notification {0} with values: {1}", i, ObjectUtil.toString(notification)); 285 | queue.addItem(notification); 286 | } // for loop 287 | 288 | showAll(); 289 | } // replayLatestFiveUpdates 290 | 291 | /** 292 | * Change the style at runtime by caling this API and passing in the location 293 | * of a compiled stylesheet. 294 | * 295 | * @param style Location of a compiled stylesheet to use as the current style for any further notifications. 296 | */ 297 | public function loadStyle(style:String):void 298 | { 299 | themeLoaded = false; 300 | log.info("Loading style sheet at location: {0}", style); 301 | var loadStyleEvent:IEventDispatcher = FlexGlobals.topLevelApplication.styleManager.loadStyleDeclarations2(style); 302 | loadStyleEvent.addEventListener(StyleEvent.COMPLETE, loadStyleHandler); 303 | } // loadStyle 304 | 305 | /** 306 | * Load a sound to play when a notification is displayed. If null is passed in, no notification sound 307 | * will be played at all. 308 | * 309 | * @param sound Location of sound file to use as the sound played when a notification is displayed. 310 | */ 311 | public function loadSound(sound:String):void 312 | { 313 | // add event listeners 314 | if (notificationSound != null && !notificationSound.hasEventListener(Event.COMPLETE)) 315 | { 316 | notificationSound.addEventListener(Event.COMPLETE, loadSoundHandler); 317 | } // if statement 318 | if (notificationSound != null && !notificationSound.hasEventListener(IOErrorEvent.IO_ERROR)) 319 | { 320 | this.notificationSound.addEventListener(IOErrorEvent.IO_ERROR, loadSoundHandler); 321 | } // if statement 322 | 323 | // load the sound 324 | soundLoaded = false; 325 | if (sound != null) 326 | { 327 | log.info("Loading sound file at location: {0}", sound); 328 | notificationSound = new Sound(new URLRequest(sound)); 329 | } // if statement 330 | else 331 | { 332 | log.info("Disabling sound on notifications"); 333 | notificationSound = null; 334 | } // else statement 335 | } // loadSound 336 | 337 | /** 338 | * Clears all notifications from history. Calling replayLatestFiveUpdates 339 | * after calling this method will show no notifications. 340 | */ 341 | public function clearLatestFiveUpdates():void 342 | { 343 | log.info("Clearing latest five updates queue"); 344 | previousQueue.removeAll(); 345 | } // clearLatestFiveUpdates 346 | 347 | /** 348 | * Closes all active, on-screen notifications. Dispatches a 349 | * NotificationEvent.CLOSE_ALL_NOTIFICATIONS event to do so. 350 | * 351 | * @see com.charlesbihis.engine.notification.event.NotificationEvent#CLOSE_ALL_NOTIFICATIONS 352 | */ 353 | public function closeAllNotifications():void 354 | { 355 | log.info("Dispatching NotificationEvent.CLOSE_ALL_NOTIFICATIONS event"); 356 | var notificationEvent:NotificationEvent = new NotificationEvent(NotificationEvent.CLOSE_ALL_NOTIFICATIONS); 357 | dispatchEvent(notificationEvent); 358 | } // closeAllNotifications 359 | 360 | /** 361 | * Default image to use in notifications when no image is specified in 362 | * the actual notification object. 363 | */ 364 | public function get defaultNotificationImage():String 365 | { 366 | return _defaultNotificationImage; 367 | } // defaultNotificationImage 368 | 369 | /** 370 | * @private 371 | */ 372 | public function set defaultNotificationImage(defaultNotificationImage:String):void 373 | { 374 | _defaultNotificationImage = defaultNotificationImage; 375 | } // defaultNotificationImage 376 | 377 | /** 378 | * Default compact image to use in compact notifications when no compact image is 379 | * specified in the actual notification object. 380 | */ 381 | public function get defaultCompactNotificationImage():String 382 | { 383 | return _defaultCompactNotificationImage; 384 | } // defaultCompactNotificationImage 385 | 386 | /** 387 | * @private 388 | */ 389 | public function set defaultCompactNotificationImage(defaultCompactNotificationImage:String):void 390 | { 391 | _defaultCompactNotificationImage = defaultCompactNotificationImage; 392 | } // defaultCompactNotificationImage 393 | 394 | /** 395 | * Default display length, in seconds, that the notifications will stay on screen for. 396 | */ 397 | public function get displayLength():int 398 | { 399 | return _displayLength; 400 | } // displayLength 401 | 402 | /** 403 | * @private 404 | */ 405 | public function set displayLength(displayLength:int):void 406 | { 407 | _displayLength = displayLength; 408 | } // displayLength 409 | 410 | /** 411 | * Default location on-screen (i.e. top-left, top-right, bottom-left, or bottom-right) 412 | * where notifications are to appear. 413 | */ 414 | public function get displayLocation():String 415 | { 416 | return _displayLocation; 417 | } // displayLocation 418 | 419 | /** 420 | * @private 421 | */ 422 | public function set displayLocation(displayLocation:String):void 423 | { 424 | _displayLocation = displayLocation; 425 | } // displayLocation 426 | 427 | /** 428 | * Convenience method for notifications that indicates whether or not the 429 | * user is idle. 430 | */ 431 | public function get isUserIdle():Boolean 432 | { 433 | return _isUserIdle; 434 | } // isUserIdle 435 | 436 | /** 437 | * @private 438 | */ 439 | private function showAll():void 440 | { 441 | // throttle the notifications! 442 | if (!themeLoaded || (notificationSound != null && !soundLoaded) || new Date().time - latestNotificationDisplay <= NOTIFICATION_THROTTLE_TIME) 443 | { 444 | setTimeout(showAll, NOTIFICATION_THROTTLE_TIME); 445 | 446 | return; 447 | } // if statement 448 | 449 | // maintain only 5 active toasts 450 | if (isUserIdle && queue.length > 0 && (activeToasts >= MAX_ACTIVE_TOASTS || suppressedNotificationCount > 0)) 451 | { 452 | // close all active notifications 453 | if (activeToasts >= MAX_ACTIVE_TOASTS && suppressedNotificationCount == 0) 454 | { 455 | log.info("User is idle and max active notification limit has been reached ({0}). Closing all active toasts and queueing all incoming.", MAX_ACTIVE_TOASTS); 456 | var notificationEvent:NotificationEvent = new NotificationEvent(NotificationEvent.CLOSE_ALL_NOTIFICATIONS); 457 | dispatchEvent(notificationEvent); 458 | } // if statement 459 | 460 | // increment the suppressed notification count, remove the notification from the queue, rinse, repeat 461 | suppressedNotificationCount++; 462 | queue.removeItemAt(0); 463 | showAll(); 464 | 465 | return; 466 | } // if statement 467 | 468 | // start emptying the queue, one notification at a time 469 | if (queue.length > 0) 470 | { 471 | // show it 472 | var notification:Notification = queue.getItemAt(0) as Notification; 473 | notification.open(this); 474 | log.debug("Showing notification: {0}", ObjectUtil.toString(notification)); 475 | 476 | // play sound 477 | if (soundLoaded && notificationSound != null && (new Date().time - latestNotificationSound > MINIMUM_TIME_BETWEEN_NOTIFICATION_SOUNDS)) 478 | { 479 | notificationSound.play(); 480 | latestNotificationSound = new Date().time; 481 | } // if statement 482 | 483 | // keep track of it 484 | activeToasts++; 485 | log.debug("There are now {0} active toasts", activeToasts); 486 | 487 | // update the latest notification time 488 | latestNotificationDisplay = new Date().time; 489 | 490 | // remove item from the queue 491 | queue.removeItemAt(0); 492 | 493 | // recursively call showAll() until the queue is empty 494 | setTimeout(showAll, NOTIFICATION_THROTTLE_TIME); 495 | } // if statement 496 | } // showAll 497 | 498 | /** 499 | * @private 500 | */ 501 | private function userPresentHandler(event:Event):void 502 | { 503 | log.debug("User is back"); 504 | _isUserIdle = false; 505 | dispatchEvent(new NotificationEvent(NotificationEvent.USER_IS_PRESENT)); 506 | 507 | if (suppressedNotificationCount > 0) 508 | { 509 | // build summary notification 510 | var summaryNotification:Notification = new Notification(); 511 | summaryNotification.title = "There were " + (suppressedNotificationCount + MAX_ACTIVE_TOASTS) + " stories posted while you were away"; 512 | summaryNotification.isReplayable = false; 513 | log.info("Showing summary notification of {0} missed notifications", (suppressedNotificationCount + MAX_ACTIVE_TOASTS)); 514 | 515 | // must reset suppressedNotificationCount back to 0 so that this upcoming 516 | // summary notification will not also be suppressed when shown 517 | suppressedNotificationCount = 0; 518 | 519 | // show it 520 | showNotification(summaryNotification); 521 | } // if statement 522 | } // onPresence 523 | 524 | /** 525 | * @private 526 | */ 527 | private function userIdleHandler(event:Event):void 528 | { 529 | log.debug("User is idle"); 530 | _isUserIdle = true; 531 | dispatchEvent(new NotificationEvent(NotificationEvent.USER_IS_IDLE)); 532 | } // onIdle 533 | 534 | /** 535 | * @private 536 | */ 537 | private function loadStyleHandler(event:StyleEvent):void 538 | { 539 | themeLoaded = true; 540 | } // styleLoadHandler 541 | 542 | /** 543 | * @private 544 | */ 545 | private function loadSoundHandler(event:Event):void 546 | { 547 | if (event is IOErrorEvent) 548 | { 549 | log.error("Unable to load sound file \"{0}\". Please verify its location", notificationSound); 550 | soundLoaded = false; 551 | } // if statement 552 | else 553 | { 554 | soundLoaded = true; 555 | } // else statement 556 | } // loadSoundHandler 557 | } // class declaration 558 | } // package 559 | -------------------------------------------------------------------------------- /src/main/actionscript/com/charlesbihis/engine/notification/event/NotificationEvent.as: -------------------------------------------------------------------------------- 1 | package com.charlesbihis.engine.notification.event 2 | { 3 | import flash.events.Event; 4 | 5 | /** 6 | * Event class for all events in notification engine. 7 | * 8 | * @langversion ActionScript 3.0 9 | * @playerversion Flash 10 10 | * 11 | * @author Charles Bihis (www.whoischarles.com) 12 | */ 13 | public class NotificationEvent extends Event 14 | { 15 | /** 16 | * Event broadcast when an active notification has just been closed. 17 | * 18 | * @eventType notificationCloseEvent 19 | */ 20 | public static const NOTIFICATION_CLOSE:String = "notificationCloseEvent"; 21 | 22 | /** 23 | * Event broadcast when an action to close all active notifications has been invoked. 24 | * 25 | * @eventType closeAllNotificationsEvent 26 | */ 27 | public static const CLOSE_ALL_NOTIFICATIONS:String = "closeAllNotificationsEvent"; 28 | 29 | /** 30 | * Event broadcast when the user has gone idle. 31 | * 32 | * @eventType userIsIdleEvent 33 | */ 34 | public static const USER_IS_IDLE:String = "userIsIdleEvent"; 35 | 36 | /** 37 | * Event broadcast when the user has returned from idle. 38 | * 39 | * @eventType userIsPresentEvent 40 | */ 41 | public static const USER_IS_PRESENT:String = "userIsPresentEvent"; 42 | 43 | public function NotificationEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false):void 44 | { 45 | super(type, bubbles, cancelable); 46 | } // NotificationEvent 47 | 48 | public override function clone():Event 49 | { 50 | return new NotificationEvent(type); 51 | } // clone 52 | } // class declaration 53 | } // package -------------------------------------------------------------------------------- /src/main/actionscript/com/charlesbihis/engine/notification/style/style.css: -------------------------------------------------------------------------------- 1 | /* CSS file */ 2 | @namespace s "library://ns.adobe.com/flex/spark"; 3 | @namespace mx "library://ns.adobe.com/flex/mx"; 4 | 5 | .notificationContainer 6 | { 7 | backgroundAlpha: 0.0; 8 | } 9 | 10 | .notification 11 | { 12 | cornerRadius: 8; 13 | backgroundColor: #000; 14 | backgroundAlpha: 0.8; 15 | borderStyle: none; 16 | borderColor: #000; 17 | paddingTop: 10; 18 | paddingBottom: 10; 19 | paddingLeft: 15; 20 | paddingRight: 15; 21 | } 22 | 23 | .notificationText 24 | { 25 | color: #FFF; 26 | } -------------------------------------------------------------------------------- /src/main/actionscript/com/charlesbihis/engine/notification/ui/Notification.as: -------------------------------------------------------------------------------- 1 | package com.charlesbihis.engine.notification.ui 2 | { 3 | import com.charlesbihis.engine.notification.NotificationManager; 4 | 5 | import flash.events.EventDispatcher; 6 | 7 | /** 8 | * Wrapper class for a notification object. 9 | * 10 | * @langversion ActionScript 3.0 11 | * @playerversion Flash 10 12 | * @author Charles Bihis (www.whoischarles.com) 13 | */ 14 | public class Notification extends EventDispatcher 15 | { 16 | private var _title:String; 17 | private var _message:String; 18 | private var _image:String; 19 | private var _link:String; 20 | private var _isCompact:Boolean; 21 | private var _isSticky:Boolean; 22 | private var _isReplayable:Boolean; 23 | 24 | private var notificationWindow:NotificationWindow; 25 | 26 | /** 27 | * Constructor to create a valid Notification object. 28 | * 29 | * @param title The title that appears in bold at the top of the notification. This is always displayed, whether it be a regular notification or a compact notification. 30 | * @param message The message that appears in the body of the notification. This is only displayed in regular notifications and is not used in compact notifications. 31 | * @param image Location of image to use as the image for the notification. If notification is set to compact (i.e. isCompact=true), image should be 16x16 pixels. Otherwise, image should be 50x50 pixels. 32 | * @param link The URL, if any, to direct the user to when the notification is clicked. If null, there will be no action on notification click. 33 | * @param isCompact Sets whether the notification is regular or compact. 34 | * @param isSticky Sets whether the notification is sticky and should remain on-screen until the user manually closes it. 35 | * @param isReplayable Sets whether the notification is replayable, meaning whether or not it will show up when the replayLatestFiveNotifications() API is invoked. This is most often set to true, but would be set to false for certain types of notifications such as system notifications. 36 | */ 37 | public function Notification(title:String = null, message:String = null, image:String = null, link:String = null, isCompact:Boolean = false, isSticky:Boolean = false, isReplayable:Boolean = true) 38 | { 39 | notificationWindow = new NotificationWindow(); 40 | notificationWindow.notificationTitle = title; 41 | notificationWindow.notificationMessage = message; 42 | notificationWindow.notificationImage = image; 43 | notificationWindow.notificationLink = link; 44 | notificationWindow.isCompact = isCompact; 45 | notificationWindow.isSticky = isSticky; 46 | notificationWindow.isReplayable = isReplayable; 47 | 48 | notificationWindow.title = NotificationWindow.NOTIFICATION_IDENTIFIER; 49 | } // Notification 50 | 51 | /** 52 | * Opens the notification. This will make the notification appear on-screen 53 | * with the values specified. If certain values aren't specified and this 54 | * is called, the default engine values will be used. 55 | */ 56 | public function open(notificationManager:NotificationManager):void 57 | { 58 | notificationWindow.notificationManager = notificationManager; 59 | notificationWindow.open(false); 60 | } // open 61 | 62 | /** 63 | * The title that appears in bold at the top of the notification. This is always displayed, whether it be a regular notification or a compact notification. 64 | */ 65 | public function get title():String 66 | { 67 | return notificationWindow.notificationTitle; 68 | } // title 69 | 70 | /** 71 | * @private 72 | */ 73 | public function set title(title:String):void 74 | { 75 | notificationWindow.notificationTitle = title; 76 | } // title 77 | 78 | /** 79 | * The message that appears in the body of the notification. This is only 80 | * displayed in regular notifications and is not used in compact notifications. 81 | */ 82 | public function get message():String 83 | { 84 | return notificationWindow.notificationMessage; 85 | } // message 86 | 87 | /** 88 | * @private 89 | */ 90 | public function set message(message:String):void 91 | { 92 | notificationWindow.notificationMessage = message; 93 | } // message 94 | 95 | /** 96 | * Location of image to use as the image for the notification. If notification 97 | * is set to compact (i.e. isCompact=true), image should be 16x16 98 | * pixels. Otherwise, image should be 50x50 pixels. Other sizes are allowed, 99 | * but they will be scaled to fit one of the sizes, and scaling may result in 100 | * less than optimized display of the images. 101 | */ 102 | public function get image():String 103 | { 104 | return notificationWindow.notificationImage; 105 | } // image 106 | 107 | /** 108 | * @private 109 | */ 110 | public function set image(image:String):void 111 | { 112 | notificationWindow.notificationImage = image; 113 | } // image 114 | 115 | /** 116 | * The URL, if any, to direct the user to when the notification is clicked. If null, there will be no action on notification click. 117 | */ 118 | public function get link():String 119 | { 120 | return notificationWindow.notificationLink; 121 | } // link 122 | 123 | /** 124 | * @private 125 | */ 126 | public function set link(link:String):void 127 | { 128 | notificationWindow.notificationLink = link; 129 | } // link 130 | 131 | /** 132 | * Sets whether the notification is regular or compact. Regular notifications display a 133 | * 50x50 pixel image, with the notification title in bold at the top, followed by 134 | * the notification message in the body of the notification. Compact notifications display 135 | * a 16x16 pixel image with only the notification title in bold next to it. The notification 136 | * message in compact notifications is not used. 137 | */ 138 | public function get isCompact():Boolean 139 | { 140 | return notificationWindow.isCompact; 141 | } // isCompact 142 | 143 | /** 144 | * @private 145 | */ 146 | public function set isCompact(isCompact:Boolean):void 147 | { 148 | notificationWindow.isCompact = isCompact; 149 | } // isCompact 150 | 151 | /** 152 | * Sets whether the notification is sticky and should remain on-screen until the user manually closes it. 153 | */ 154 | public function get isSticky():Boolean 155 | { 156 | return notificationWindow.isSticky; 157 | } // isSticky 158 | 159 | /** 160 | * @private 161 | */ 162 | public function set isSticky(isSticky:Boolean):void 163 | { 164 | notificationWindow.isSticky = isSticky; 165 | } // isSticky 166 | 167 | /** 168 | * Sets whether the notification is replayable, meaning whether or not it will show up when 169 | * the replayLatestFiveNotifications() API is invoked. This is most often set 170 | * to true, but would be set to false for certain types of 171 | * notifications such as system notifications. 172 | */ 173 | public function get isReplayable():Boolean 174 | { 175 | return notificationWindow.isReplayable; 176 | } // isReplayable 177 | 178 | /** 179 | * @private 180 | */ 181 | public function set isReplayable(isReplayable:Boolean):void 182 | { 183 | notificationWindow.isReplayable = isReplayable; 184 | } // isReplayable 185 | } // class declaration 186 | } // package -------------------------------------------------------------------------------- /src/main/actionscript/com/charlesbihis/engine/notification/ui/NotificationWindow.mxml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | = screen.visibleBounds.x); 136 | x -= (isLeft ? -(size.width + TOP_MARGIN) : size.width + TOP_MARGIN)) 137 | { 138 | for (y = (isTop ? screen.visibleBounds.y + TOP_MARGIN + TOP_MARGIN : screen.visibleBounds.y + screen.visibleBounds.height - size.height - TOP_MARGIN); 139 | (isTop ? y <= screen.visibleBounds.y + screen.visibleBounds.height - size.height - TOP_MARGIN : y >= screen.visibleBounds.y); 140 | y += (isTop ? 10 : -10)) 141 | { 142 | rectangle = new Rectangle(x, isTop ? y - TOP_MARGIN : y, size.width + TOP_MARGIN, size.height + TOP_MARGIN); 143 | 144 | if(!isOccupied(rectangle)) 145 | { 146 | spot.x = x; 147 | spot.y = y; 148 | done = true; 149 | break; 150 | } // if statement 151 | } // for loop 152 | 153 | if (done) 154 | { 155 | break; 156 | } // if statement 157 | } // for loop 158 | 159 | return spot; 160 | } // findSpotForMessage 161 | 162 | private function isOccupied(rectangle:Rectangle):Boolean 163 | { 164 | var occupied:Boolean = false; 165 | var isOtherWindow:Boolean = false; 166 | for each (var window:NativeWindow in NativeApplication.nativeApplication.openedWindows) 167 | { 168 | for (var i:int = 0; i < NotificationManager.otherWindowsToAvoid.length; i++) 169 | { 170 | if (window.title.indexOf(NotificationManager.otherWindowsToAvoid[i]) >= 0) 171 | { 172 | isOtherWindow = true; 173 | break; 174 | } // if statement 175 | } // for loop 176 | occupied = occupied || (((window.title.indexOf(NOTIFICATION_IDENTIFIER) >= 0 && window.title.indexOf(notificationId) < 0) || isOtherWindow) && window.bounds.intersects(rectangle) && window.visible); 177 | } // for loop 178 | 179 | return occupied; 180 | } // isOccupied 181 | 182 | private function clickHandler():void 183 | { 184 | if (notificationLink != null && notificationLink.length > 0 && !closeButton.visible) 185 | { 186 | flash.net.navigateToURL(new URLRequest(notificationLink)); 187 | } // if statement 188 | } // clickHandler 189 | 190 | private function closeToast(event:Event = null):void 191 | { 192 | // clean up the event listeners 193 | notificationManager.removeEventListener(NotificationEvent.CLOSE_ALL_NOTIFICATIONS, closeToast); 194 | notificationManager.removeEventListener(NotificationEvent.USER_IS_PRESENT, userPresentHandler); 195 | removeEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler); 196 | removeEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler); 197 | 198 | // If 'close' button was pushed, let's close it 199 | // immediately. Otherwise, let's start the 200 | // fade-out effect and close after it's done playing. 201 | if (event is MouseEvent) 202 | { 203 | event.preventDefault(); 204 | finishClose(); 205 | } // if statement 206 | else 207 | { 208 | fadeout.addEventListener(EffectEvent.EFFECT_END, finishClose); 209 | fadeout.play(); 210 | } // else statement 211 | 212 | function finishClose(event:Event = null):void 213 | { 214 | if (event is EffectEvent) 215 | { 216 | fadeout.removeEventListener(EffectEvent.EFFECT_END, finishClose); 217 | } // if statement 218 | 219 | notificationManager.dispatchEvent(new NotificationEvent(NotificationEvent.NOTIFICATION_CLOSE)); 220 | lifetimeTimer.stop(); 221 | repositionTimer.stop(); 222 | close(); 223 | } // finishClose 224 | } // closeToast 225 | 226 | private function mouseOverHandler(event:MouseEvent = null):void 227 | { 228 | // reset lifetime 229 | if (ticks < LIFETIME_RESET_VALUE) 230 | { 231 | ticks = LIFETIME_RESET_VALUE; 232 | } // if statement 233 | 234 | lifetimeTimer.stop(); 235 | } // mouseOverHandler 236 | 237 | private function mouseOutHandler(event:MouseEvent = null):void 238 | { 239 | if (!isSticky) 240 | { 241 | lifetimeTimer.start(); 242 | } // if statement 243 | } // mouseOutHandler 244 | 245 | private function userPresentHandler(event:NotificationEvent):void 246 | { 247 | // reset lifetime if too low - don't want notifications disappearing immediately after the user returns 248 | if (ticks < LIFETIME_RESET_VALUE) 249 | { 250 | ticks = LIFETIME_RESET_VALUE; 251 | } // if statement 252 | } // userPresentHandler 253 | ]]> 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | --------------------------------------------------------------------------------