You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VNotification.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.ui;
  17. import java.util.ArrayList;
  18. import java.util.EventObject;
  19. import java.util.List;
  20. import com.google.gwt.aria.client.Roles;
  21. import com.google.gwt.core.client.GWT;
  22. import com.google.gwt.dom.client.Element;
  23. import com.google.gwt.event.dom.client.KeyCodes;
  24. import com.google.gwt.user.client.DOM;
  25. import com.google.gwt.user.client.Event;
  26. import com.google.gwt.user.client.Event.NativePreviewEvent;
  27. import com.google.gwt.user.client.Timer;
  28. import com.google.gwt.user.client.ui.FlowPanel;
  29. import com.google.gwt.user.client.ui.HTML;
  30. import com.google.gwt.user.client.ui.Label;
  31. import com.google.gwt.user.client.ui.Widget;
  32. import com.vaadin.client.AnimationUtil;
  33. import com.vaadin.client.ApplicationConnection;
  34. import com.vaadin.client.BrowserInfo;
  35. import com.vaadin.client.WidgetUtil;
  36. import com.vaadin.client.ui.aria.AriaHelper;
  37. import com.vaadin.shared.Position;
  38. import com.vaadin.shared.ui.ui.NotificationRole;
  39. import com.vaadin.shared.ui.ui.UIState.NotificationTypeConfiguration;
  40. public class VNotification extends VOverlay {
  41. public static final Position CENTERED = Position.MIDDLE_CENTER;
  42. public static final Position CENTERED_TOP = Position.TOP_CENTER;
  43. public static final Position CENTERED_BOTTOM = Position.BOTTOM_CENTER;
  44. public static final Position TOP_LEFT = Position.TOP_LEFT;
  45. public static final Position TOP_RIGHT = Position.TOP_RIGHT;
  46. public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT;
  47. public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT;
  48. private static final String STYLENAME_POSITION_TOP = "v-position-top";
  49. private static final String STYLENAME_POSITION_RIGHT = "v-position-right";
  50. private static final String STYLENAME_POSITION_BOTTOM = "v-position-bottom";
  51. private static final String STYLENAME_POSITION_LEFT = "v-position-left";
  52. private static final String STYLENAME_POSITION_MIDDLE = "v-position-middle";
  53. private static final String STYLENAME_POSITION_CENTER = "v-position-center";
  54. private static final String STYLENAME_POSITION_ASSISTIVE = "v-position-assistive";
  55. public static final String CAPTION = "caption";
  56. public static final String DESCRIPTION = "description";
  57. public static final String DETAILS = "details";
  58. /**
  59. * Position that is only accessible for assistive devices, invisible for
  60. * visual users.
  61. */
  62. public static final Position ASSISTIVE = Position.ASSISTIVE;
  63. public static final int DELAY_FOREVER = -1;
  64. public static final int DELAY_NONE = 0;
  65. private static final String STYLENAME = "v-Notification";
  66. private static final int MOUSE_MOVE_THRESHOLD = 7;
  67. private static final int Z_INDEX_BASE = 20000;
  68. public static final String STYLE_SYSTEM = "system";
  69. private static final List<VNotification> NOTIFICATIONS = new ArrayList<>();
  70. private boolean infiniteDelay = false;
  71. private int hideDelay = 0;
  72. private Timer delay;
  73. private int x = -1;
  74. private int y = -1;
  75. private String temporaryStyle;
  76. private List<EventListener> listeners;
  77. private static final int TOUCH_DEVICE_IDLE_DELAY = 1000;
  78. /**
  79. * Default constructor. You should use GWT.create instead.
  80. */
  81. public VNotification() {
  82. setStyleName(STYLENAME);
  83. sinkEvents(Event.ONCLICK);
  84. getElement().getStyle().setZIndex(Z_INDEX_BASE);
  85. }
  86. /**
  87. * @deprecated Use static {@link #createNotification(int)} instead to enable
  88. * GWT deferred binding.
  89. *
  90. * @param delayMsec
  91. */
  92. @Deprecated
  93. public VNotification(int delayMsec) {
  94. this();
  95. setDelay(delayMsec);
  96. if (BrowserInfo.get().isTouchDevice()) {
  97. new Timer() {
  98. @Override
  99. public void run() {
  100. if (isAttached()) {
  101. hide();
  102. }
  103. }
  104. }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
  105. }
  106. }
  107. /**
  108. * @deprecated Use static {@link #createNotification(int, int, int)} instead
  109. * to enable GWT deferred binding.
  110. *
  111. * @param delayMsec
  112. * @param fadeMsec
  113. * @param startOpacity
  114. */
  115. @Deprecated
  116. public VNotification(int delayMsec, int fadeMsec, int startOpacity) {
  117. this(delayMsec);
  118. AnimationUtil.setAnimationDuration(getElement(), fadeMsec + "ms");
  119. getElement().getStyle().setOpacity(startOpacity / 100);
  120. }
  121. private void setDelay(int delayMsec) {
  122. if (delayMsec < 0) {
  123. infiniteDelay = true;
  124. hideDelay = 0;
  125. } else {
  126. infiniteDelay = false;
  127. hideDelay = delayMsec;
  128. }
  129. }
  130. @Override
  131. public void show() {
  132. show(CENTERED);
  133. }
  134. public void show(String style) {
  135. show(CENTERED, style);
  136. }
  137. public void show(com.vaadin.shared.Position position) {
  138. show(position, null);
  139. }
  140. public void show(Widget widget, Position position, String style) {
  141. NotificationTypeConfiguration styleSetup = getUiState(style);
  142. setWaiAriaRole(styleSetup);
  143. FlowPanel panel = new FlowPanel();
  144. if (hasPrefix(styleSetup)) {
  145. panel.add(new Label(styleSetup.prefix));
  146. AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(),
  147. true);
  148. }
  149. panel.add(widget);
  150. if (hasPostfix(styleSetup)) {
  151. panel.add(new Label(styleSetup.postfix));
  152. AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(),
  153. true);
  154. }
  155. setWidget(panel);
  156. show(position, style);
  157. }
  158. private boolean hasPostfix(NotificationTypeConfiguration styleSetup) {
  159. return styleSetup != null && styleSetup.postfix != null
  160. && !styleSetup.postfix.isEmpty();
  161. }
  162. private boolean hasPrefix(NotificationTypeConfiguration styleSetup) {
  163. return styleSetup != null && styleSetup.prefix != null
  164. && !styleSetup.prefix.isEmpty();
  165. }
  166. public void show(String html, Position position, String styleName) {
  167. NotificationTypeConfiguration styleSetup = getUiState(styleName);
  168. String assistiveDeviceOnlyStyle = AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE;
  169. setWaiAriaRole(styleSetup);
  170. String type = "";
  171. String usage = "";
  172. if (hasPrefix(styleSetup)) {
  173. type = "<span class='" + assistiveDeviceOnlyStyle + "'>"
  174. + styleSetup.prefix + "</span>";
  175. }
  176. if (hasPostfix(styleSetup)) {
  177. usage = "<span class='" + assistiveDeviceOnlyStyle + "'>"
  178. + styleSetup.postfix + "</span>";
  179. }
  180. setWidget(new HTML(type + html + usage));
  181. show(position, styleName);
  182. }
  183. private NotificationTypeConfiguration getUiState(String style) {
  184. if (getApplicationConnection() == null
  185. || getApplicationConnection().getUIConnector() == null) {
  186. return null;
  187. }
  188. return getApplicationConnection().getUIConnector()
  189. .getState().notificationConfigurations.get(style);
  190. }
  191. private void setWaiAriaRole(NotificationTypeConfiguration styleSetup) {
  192. Roles.getAlertRole().set(getElement());
  193. if (styleSetup != null && styleSetup.notificationRole != null) {
  194. if (NotificationRole.STATUS == styleSetup.notificationRole) {
  195. Roles.getStatusRole().set(getElement());
  196. }
  197. }
  198. }
  199. public void show(Position position, String style) {
  200. if (temporaryStyle != null) {
  201. removeStyleName(temporaryStyle);
  202. removeStyleDependentName(temporaryStyle);
  203. temporaryStyle = null;
  204. }
  205. if (style != null && !style.isEmpty()) {
  206. temporaryStyle = style;
  207. addStyleName(style);
  208. addStyleDependentName(style);
  209. }
  210. setPosition(position);
  211. super.show();
  212. updatePositionOffsets(position);
  213. NOTIFICATIONS.add(this);
  214. positionOrSizeUpdated();
  215. /**
  216. * Android 4 fails to render notifications correctly without a little
  217. * nudge (#8551) Chrome 41 now requires this too (#17252)
  218. */
  219. if (BrowserInfo.get().isAndroid() || isChrome41OrHigher()) {
  220. WidgetUtil.setStyleTemporarily(getElement(), "display", "none");
  221. }
  222. }
  223. private boolean isChrome41OrHigher() {
  224. return BrowserInfo.get().isChrome()
  225. && BrowserInfo.get().getBrowserMajorVersion() >= 41;
  226. }
  227. protected void hideAfterDelay() {
  228. if (delay == null) {
  229. delay = new Timer() {
  230. @Override
  231. public void run() {
  232. VNotification.super.hide();
  233. }
  234. };
  235. delay.schedule(hideDelay);
  236. }
  237. }
  238. @Override
  239. public void hide() {
  240. if (delay != null) {
  241. delay.cancel();
  242. }
  243. // Run only once
  244. if (NOTIFICATIONS.contains(this)) {
  245. DOM.removeEventPreview(this);
  246. // Still animating in, wait for it to finish before touching
  247. // the animation delay (which would restart the animation-in
  248. // in some browsers)
  249. if (getStyleName()
  250. .contains(VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) {
  251. AnimationUtil.addAnimationEndListener(getElement(),
  252. event -> {
  253. if (AnimationUtil.getAnimationName(event).contains(
  254. VOverlay.ADDITIONAL_CLASSNAME_ANIMATE_IN)) {
  255. VNotification.this.hide();
  256. }
  257. });
  258. } else {
  259. VNotification.super.hide();
  260. fireEvent(new HideEvent(this));
  261. NOTIFICATIONS.remove(this);
  262. }
  263. }
  264. }
  265. private void updatePositionOffsets(com.vaadin.shared.Position position) {
  266. final Element el = getElement();
  267. // Remove all offsets (GWT PopupPanel defaults)
  268. el.getStyle().clearTop();
  269. el.getStyle().clearLeft();
  270. switch (position) {
  271. case MIDDLE_LEFT:
  272. case MIDDLE_RIGHT:
  273. center();
  274. el.getStyle().clearLeft();
  275. break;
  276. case TOP_CENTER:
  277. case BOTTOM_CENTER:
  278. center();
  279. el.getStyle().clearTop();
  280. break;
  281. case MIDDLE_CENTER:
  282. center();
  283. break;
  284. }
  285. }
  286. public void setPosition(com.vaadin.shared.Position position) {
  287. final Element el = getElement();
  288. // Remove any previous positions
  289. el.removeClassName(STYLENAME_POSITION_TOP);
  290. el.removeClassName(STYLENAME_POSITION_RIGHT);
  291. el.removeClassName(STYLENAME_POSITION_BOTTOM);
  292. el.removeClassName(STYLENAME_POSITION_LEFT);
  293. el.removeClassName(STYLENAME_POSITION_MIDDLE);
  294. el.removeClassName(STYLENAME_POSITION_CENTER);
  295. el.removeClassName(STYLENAME_POSITION_ASSISTIVE);
  296. switch (position) {
  297. case TOP_LEFT:
  298. el.addClassName(STYLENAME_POSITION_TOP);
  299. el.addClassName(STYLENAME_POSITION_LEFT);
  300. break;
  301. case TOP_RIGHT:
  302. el.addClassName(STYLENAME_POSITION_TOP);
  303. el.addClassName(STYLENAME_POSITION_RIGHT);
  304. break;
  305. case MIDDLE_LEFT:
  306. el.addClassName(STYLENAME_POSITION_MIDDLE);
  307. el.addClassName(STYLENAME_POSITION_LEFT);
  308. break;
  309. case MIDDLE_RIGHT:
  310. el.addClassName(STYLENAME_POSITION_MIDDLE);
  311. el.addClassName(STYLENAME_POSITION_RIGHT);
  312. break;
  313. case BOTTOM_RIGHT:
  314. el.addClassName(STYLENAME_POSITION_BOTTOM);
  315. el.addClassName(STYLENAME_POSITION_RIGHT);
  316. break;
  317. case BOTTOM_LEFT:
  318. el.addClassName(STYLENAME_POSITION_BOTTOM);
  319. el.addClassName(STYLENAME_POSITION_LEFT);
  320. break;
  321. case TOP_CENTER:
  322. el.addClassName(STYLENAME_POSITION_TOP);
  323. el.addClassName(STYLENAME_POSITION_CENTER);
  324. break;
  325. case BOTTOM_CENTER:
  326. el.addClassName(STYLENAME_POSITION_BOTTOM);
  327. el.addClassName(STYLENAME_POSITION_CENTER);
  328. break;
  329. case ASSISTIVE:
  330. el.addClassName(STYLENAME_POSITION_ASSISTIVE);
  331. break;
  332. }
  333. }
  334. @Override
  335. public void onBrowserEvent(Event event) {
  336. hide();
  337. }
  338. @Override
  339. /*
  340. * Fix for #14689: {@link #onEventPreview(Event)} method is deprecated and
  341. * it's called now only for the very first handler (see super impl). We need
  342. * it to work for any handler. So let's call old {@link
  343. * #onEventPreview(Event)} method explicitly with updated logic for {@link
  344. * #onPreviewNativeEvent(Event)}.
  345. */
  346. protected void onPreviewNativeEvent(NativePreviewEvent event) {
  347. if (!onEventPreview(Event.as(event.getNativeEvent()))) {
  348. event.cancel();
  349. }
  350. }
  351. @Override
  352. public boolean onEventPreview(Event event) {
  353. int type = DOM.eventGetType(event);
  354. // "modal"
  355. if (infiniteDelay || temporaryStyle == STYLE_SYSTEM) {
  356. if (type == Event.ONCLICK || type == Event.ONTOUCHEND) {
  357. if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
  358. hide();
  359. return false;
  360. }
  361. } else if (type == Event.ONKEYDOWN
  362. && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
  363. hide();
  364. return false;
  365. }
  366. return temporaryStyle == STYLE_SYSTEM;
  367. }
  368. // default
  369. switch (type) {
  370. case Event.ONMOUSEMOVE:
  371. if (x < 0) {
  372. x = DOM.eventGetClientX(event);
  373. y = DOM.eventGetClientY(event);
  374. } else if (Math
  375. .abs(DOM.eventGetClientX(event) - x) > MOUSE_MOVE_THRESHOLD
  376. || Math.abs(DOM.eventGetClientY(event)
  377. - y) > MOUSE_MOVE_THRESHOLD) {
  378. hideAfterDelay();
  379. }
  380. break;
  381. case Event.ONMOUSEDOWN:
  382. case Event.ONMOUSEWHEEL:
  383. case Event.ONSCROLL:
  384. hideAfterDelay();
  385. break;
  386. case Event.ONKEYDOWN:
  387. if (event.getRepeat()) {
  388. return true;
  389. }
  390. hideAfterDelay();
  391. break;
  392. default:
  393. break;
  394. }
  395. return true;
  396. }
  397. public void addEventListener(EventListener listener) {
  398. if (listeners == null) {
  399. listeners = new ArrayList<>();
  400. }
  401. listeners.add(listener);
  402. }
  403. public void removeEventListener(EventListener listener) {
  404. if (listeners != null) {
  405. listeners.remove(listener);
  406. }
  407. }
  408. private void fireEvent(HideEvent event) {
  409. if (listeners != null) {
  410. for (EventListener l : listeners) {
  411. l.notificationHidden(event);
  412. }
  413. }
  414. }
  415. /**
  416. * Creates and shows a {@code Notification} with the specified parameters.
  417. *
  418. * @param client
  419. * The client connection, cannot be {@code null}.
  420. * @param caption
  421. * The Notification caption, can be {@code null}.
  422. * @param description
  423. * The Notification description, can be {@code null}.
  424. * @param htmlContentAllowed
  425. * Whether {@code caption} and {@code description}
  426. * are interpreted as HTML or not.
  427. * @param iconUri
  428. * The icon URI, can be {@code null}.
  429. * @param styleName
  430. * The Notification style name, can be {@code null}.
  431. * @param position
  432. * The desired {@link Position}, can not be {@code null}.
  433. * @param delayMsec
  434. * The delay in milliseconds before disappearing, -1 for forever.
  435. *
  436. * @since 8.2
  437. */
  438. public static VNotification showNotification(ApplicationConnection client,
  439. String caption, String description, boolean htmlContentAllowed,
  440. String iconUri, String styleName, Position position, int delayMsec) {
  441. String html = "";
  442. if (iconUri != null) {
  443. html += client.getIcon(iconUri).getElement().getString();
  444. }
  445. if (caption != null) {
  446. if (!htmlContentAllowed) {
  447. caption = WidgetUtil.escapeHTML(caption);
  448. caption = caption.replaceAll("\\n", "<br />");
  449. }
  450. html += "<h1 class='" + getDependentStyle(client, CAPTION) + "'>"
  451. + caption + "</h1>";
  452. }
  453. if (description != null) {
  454. if (!htmlContentAllowed) {
  455. description = WidgetUtil.escapeHTML(description);
  456. description = description.replaceAll("\\n", "<br />");
  457. }
  458. html += "<p class='" + getDependentStyle(client, DESCRIPTION) + "'>"
  459. + description + "</p>";
  460. }
  461. VNotification vNotification = createNotification(delayMsec,
  462. client.getUIConnector().getWidget());
  463. vNotification.show(html, position, styleName);
  464. return vNotification;
  465. }
  466. /**
  467. * Meant for internal usage only.
  468. *
  469. * @since 7.5.0
  470. * @param client
  471. * application connection
  472. * @param style
  473. * the dependent style name
  474. * @return the given dependent style name prefixed with current notification
  475. * primary style
  476. */
  477. public static String getDependentStyle(ApplicationConnection client,
  478. String style) {
  479. VNotification notification = createNotification(-1,
  480. client.getUIConnector().getWidget());
  481. String styleName = notification.getStyleName();
  482. notification.addStyleDependentName(style);
  483. String extendedStyle = notification.getStyleName();
  484. return extendedStyle.substring(styleName.length()).trim();
  485. }
  486. public static VNotification createNotification(int delayMsec,
  487. Widget owner) {
  488. final VNotification notification = GWT.create(VNotification.class);
  489. notification.setWaiAriaRole(null);
  490. notification.setDelay(delayMsec);
  491. if (!notification.infiniteDelay && BrowserInfo.get().isTouchDevice()) {
  492. new Timer() {
  493. @Override
  494. public void run() {
  495. if (notification.isAttached()) {
  496. notification.hide();
  497. }
  498. }
  499. }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
  500. }
  501. notification.setOwner(owner);
  502. return notification;
  503. }
  504. public class HideEvent extends EventObject {
  505. public HideEvent(Object source) {
  506. super(source);
  507. }
  508. }
  509. public interface EventListener extends java.util.EventListener {
  510. public void notificationHidden(HideEvent event);
  511. }
  512. /**
  513. * Moves currently visible notifications to the top of the event preview
  514. * stack. Can be called when opening other overlays such as subwindows to
  515. * ensure the notifications receive the events they need and don't linger
  516. * indefinitely. See #7136.
  517. *
  518. * TODO Should this be a generic Overlay feature instead?
  519. */
  520. public static void bringNotificationsToFront() {
  521. for (VNotification notification : NOTIFICATIONS) {
  522. DOM.removeEventPreview(notification);
  523. DOM.addEventPreview(notification);
  524. }
  525. }
  526. /**
  527. * Shows an error notification and redirects the user to the given URL when
  528. * she clicks on the notification.
  529. *
  530. * If both message and caption are null, redirects the user to the url
  531. * immediately
  532. *
  533. * @since 7.5.1
  534. * @param connection
  535. * A reference to the ApplicationConnection
  536. * @param caption
  537. * The caption for the error or null to exclude the caption
  538. * @param message
  539. * The message for the error or null to exclude the message
  540. * @param details
  541. * A details message or null to exclude the details
  542. * @param url
  543. * A url to redirect to after the user clicks the error
  544. * notification
  545. */
  546. public static void showError(ApplicationConnection connection,
  547. String caption, String message, String details, String url) {
  548. StringBuilder html = new StringBuilder();
  549. if (caption != null) {
  550. html.append("<h1 class='");
  551. html.append(getDependentStyle(connection, CAPTION));
  552. html.append("'>");
  553. html.append(caption);
  554. html.append("</h1>");
  555. }
  556. if (message != null) {
  557. html.append("<p class='");
  558. html.append(getDependentStyle(connection, DESCRIPTION));
  559. html.append("'>");
  560. html.append(message);
  561. html.append("</p>");
  562. }
  563. if (html.length() != 0) {
  564. // Add error description
  565. if (details != null) {
  566. html.append("<p class='");
  567. html.append(getDependentStyle(connection, DETAILS));
  568. html.append("'>");
  569. html.append("<i style=\"font-size:0.7em\">");
  570. html.append(details);
  571. html.append("</i></p>");
  572. }
  573. VNotification n = VNotification.createNotification(1000 * 60 * 45,
  574. connection.getUIConnector().getWidget());
  575. n.addEventListener(new NotificationRedirect(url));
  576. n.show(html.toString(), VNotification.CENTERED_TOP,
  577. VNotification.STYLE_SYSTEM);
  578. } else {
  579. WidgetUtil.redirect(url);
  580. }
  581. }
  582. /**
  583. * Listens for Notification hide event, and redirects. Used for system
  584. * messages, such as session expired.
  585. *
  586. */
  587. private static class NotificationRedirect
  588. implements VNotification.EventListener {
  589. String url;
  590. NotificationRedirect(String url) {
  591. this.url = url;
  592. }
  593. @Override
  594. public void notificationHidden(HideEvent event) {
  595. WidgetUtil.redirect(url);
  596. }
  597. }
  598. }