/* * Copyright (c) 2016 Washington State Department of Transportation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> * */ package gov.wa.wsdot.mobile.client.activities.home; import com.google.gwt.aria.client.Id; import com.google.gwt.aria.client.Roles; import com.google.gwt.aria.client.SelectedValue; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.*; import com.googlecode.mgwt.dom.client.event.tap.TapEvent; import com.googlecode.mgwt.dom.client.event.tap.TapHandler; import com.googlecode.mgwt.ui.client.MGWT; import com.googlecode.mgwt.ui.client.widget.base.HasRefresh; import com.googlecode.mgwt.ui.client.widget.button.Button; import com.googlecode.mgwt.ui.client.widget.button.image.AboutImageButton; import com.googlecode.mgwt.ui.client.widget.carousel.Carousel; import com.googlecode.mgwt.ui.client.widget.dialog.Dialogs; import com.googlecode.mgwt.ui.client.widget.dialog.Dialogs.ButtonType; import com.googlecode.mgwt.ui.client.widget.dialog.Dialogs.OptionCallback; import com.googlecode.mgwt.ui.client.widget.dialog.Dialogs.OptionsDialogEntry; import com.googlecode.mgwt.ui.client.widget.header.HeaderTitle; import com.googlecode.mgwt.ui.client.widget.list.celllist.CellList; import com.googlecode.mgwt.ui.client.widget.list.celllist.CellSelectedEvent; import com.googlecode.mgwt.ui.client.widget.list.widgetlist.WidgetList; import com.googlecode.mgwt.ui.client.widget.panel.flex.FixedSpacer; import com.googlecode.mgwt.ui.client.widget.panel.flex.FlexSpacer; import com.googlecode.mgwt.ui.client.widget.panel.flex.RootFlexPanel; import com.googlecode.mgwt.ui.client.widget.panel.pull.PullArrowHeader; import com.googlecode.mgwt.ui.client.widget.panel.pull.PullArrowWidget; import com.googlecode.mgwt.ui.client.widget.panel.pull.PullPanel; import com.googlecode.mgwt.ui.client.widget.panel.pull.PullPanel.Pullhandler; import com.googlecode.mgwt.ui.client.widget.panel.scroll.ScrollPanel; import com.googlecode.mgwt.ui.client.widget.progress.ProgressIndicator; import com.googlecode.mgwt.ui.client.widget.tabbar.TabPanel; import gov.wa.wsdot.mobile.client.activities.ferries.schedules.FerriesRouteSchedulesCell; import gov.wa.wsdot.mobile.client.activities.trafficmap.menu.traveltimes.TravelTimesCell; import gov.wa.wsdot.mobile.client.css.AppBundle; import gov.wa.wsdot.mobile.client.util.ParserUtils; import gov.wa.wsdot.mobile.client.widget.CellDetailsWithIcon; import gov.wa.wsdot.mobile.client.widget.celllist.MyBasicCell; import gov.wa.wsdot.mobile.client.widget.tabbar.FavoritesTabBarButton; import gov.wa.wsdot.mobile.client.widget.tabbar.HomeTabBarButton; import gov.wa.wsdot.mobile.shared.*; import java.util.ArrayList; import java.util.List; public class HomeViewGwtImpl extends Composite implements HomeView { /** * The UiBinder interface. */ interface HomeViewGwtImplUiBinder extends UiBinder<Widget, HomeViewGwtImpl> { } /** * The UiBinder used to generate the view. */ private static HomeViewGwtImplUiBinder uiBinder = GWT .create(HomeViewGwtImplUiBinder.class); @UiField HeaderTitle heading; @UiField AboutImageButton aboutButton; @UiField Button trafficButton; @UiField Button ferriesButton; @UiField Button passesButton; @UiField Button socialButton; @UiField Button tollingButton; @UiField Button borderButton; @UiField Button amtrakButton; @UiField HTMLPanel trafficTitle; @UiField HTMLPanel ferriesTitle; @UiField HTMLPanel passesTitle; @UiField HTMLPanel socialTitle; @UiField HTMLPanel tollingTitle; @UiField HTMLPanel borderTitle; @UiField HTMLPanel amtrakTitle; @UiField HTMLPanel highImpactAlertsPanel; @UiField ProgressIndicator progressIndicator; @UiField(provided = true) PullPanel pullToRefresh; @UiField ScrollPanel scrollPanel; @UiField FixedSpacer leftFixedSpacer; @UiField FlexSpacer leftFlexSpacer; @UiField HTML locationsHeader; @UiField HTML camerasHeader; @UiField HTML ferriesHeader; @UiField HTML mountainPassesHeader; @UiField HTML travelTimesHeader; @UiField HTMLPanel emptyFavorites; @UiField Image star; @UiField HTML colorOfStar; @UiField(provided = true) WidgetList locationsWidgetList; @UiField(provided = true) CellList<CameraItem> camerasCellList; @UiField(provided = true) CellList<FerriesRouteItem> ferriesCellList; @UiField(provided = true) CellList<MountainPassItem> mountainPassesCellList; @UiField(provided = true) CellList<TravelTimesItem> travelTimesCellList; @UiField(provided = true) Carousel alertsCarousel; @UiField TabPanel tabPanel; @UiField HomeTabBarButton homeTab; @UiField FavoritesTabBarButton favoritesTab; @UiField RootFlexPanel home; @UiField RootFlexPanel favorites; private Presenter presenter; private PullArrowHeader pullArrowHeader; public HomeViewGwtImpl() { pullToRefresh = new PullPanel(); pullArrowHeader = new PullArrowHeader(); pullToRefresh.setHeader(pullArrowHeader); alertsCarousel = new Carousel(); locationsWidgetList = new WidgetList(); camerasCellList = new CellList<CameraItem>(new MyBasicCell<CameraItem>() { @Override public String getDisplayString(CameraItem model) { return model.getTitle(); } @Override public boolean canBeSelected(CameraItem model) { return true; } }); ferriesCellList = new CellList<FerriesRouteItem>( new FerriesRouteSchedulesCell<FerriesRouteItem>() { private ImageResourceRenderer imageRenderer = new ImageResourceRenderer(); @Override public String getDescription(FerriesRouteItem model) { return model.getDescription(); } @Override public String getLastUpdated(FerriesRouteItem model) { return ParserUtils.relativeTime(model.getCacheDate(), "MMMM d, yyyy h:mm a", false); } @Override public SafeHtml getAlertImage(FerriesRouteItem model) { boolean hasAlerts = false; if (!model.getRouteAlert().equals("[]")) hasAlerts = true; SafeHtml image = imageRenderer.render(AppBundle.INSTANCE.btnAlertPNG()); return hasAlerts ? image : SafeHtmlUtils.fromString(""); } @Override public String getCrossingTime(FerriesRouteItem model) { try { if (model.getCrossingTime().equalsIgnoreCase("null")) { return ""; } else { return "Crossing Time: ~ " + model.getCrossingTime() + " min"; } } catch (Exception e) { return ""; } } }); mountainPassesCellList = new CellList<MountainPassItem>( new CellDetailsWithIcon<MountainPassItem>() { @Override public String getDisplayString(MountainPassItem model) { return model.getMountainPassName(); } @Override public String getDisplayImage(MountainPassItem model) { return model.getWeatherIcon(); } @Override public String getDisplayDescription(MountainPassItem model) { return model.getWeatherCondition(); } @Override public String getDisplayLastUpdated(MountainPassItem model) { return ParserUtils.relativeTime(model.getDateUpdated(), "MMMM d, yyyy h:mm a", false); } @Override public boolean canBeSelected(MountainPassItem model) { return true; } }); travelTimesCellList = new CellList<TravelTimesItem>( new TravelTimesCell<TravelTimesItem>() { @Override public String getDisplayTitle(TravelTimesItem model) { return model.getTitle(); } @Override public String getDisplayDistanceAverageTime(TravelTimesItem model) { int average = model.getAverageTime(); String averageTime; if (average == 0) { averageTime = "Not Available"; } else { averageTime = average + " min"; } return model.getDistance() + " miles / " + averageTime; } @Override public String getDisplayLastUpdated(TravelTimesItem model) { return ParserUtils.relativeTime(model.getUpdated(), "yyyy-MM-dd h:mm a", false); } @Override public String getDisplayCurrentTime(TravelTimesItem model) { return model.getCurrentTime() + " min"; } @Override public String getDisplayCurrentTimeColor(TravelTimesItem model) { int average = model.getAverageTime(); int current = model.getCurrentTime(); String textColor; if (current < average) { textColor = AppBundle.INSTANCE.css().colorGreen(); } else if ((current > average) && (average != 0)) { textColor = AppBundle.INSTANCE.css().colorRed(); } else { textColor = AppBundle.INSTANCE.css().colorBlack(); } return textColor; } }); initWidget(uiBinder.createAndBindUi(this)); tabPanel.tabContainer.addSelectionHandler(new SelectionHandler<Integer>() { @Override public void onSelection(SelectionEvent<Integer> event) { if (presenter != null) { int index = event.getSelectedItem(); presenter.onTabSelected(index); } } }); accessibilityPrepare(); if (MGWT.getOsDetection().isAndroid()) { leftFixedSpacer.setWidth("12px"); leftFlexSpacer.setVisible(false); scrollPanel.setBounce(false); colorOfStar.setHTML("icon to turn it white."); } } @UiHandler("homeTab") protected void onHomeTabPressed(TapEvent event) { accessibilityShowHome(); } @UiHandler("favoritesTab") protected void onFavoritesTabPressed(TapEvent event) { accessibilityShowFavorites(); } @UiHandler("aboutButton") protected void onAboutButtonPressed(TapEvent event) { if (presenter != null) { presenter.onAboutButtonPressed(); } } @UiHandler("trafficButton") protected void onTrafficButtonPressed(TapEvent event) { if (presenter != null) { presenter.onTrafficMapButtonPressed(); } } @UiHandler("ferriesButton") protected void onFerriesButtonPressed(TapEvent event) { if (presenter != null) { presenter.onFerriesButtonPressed(); } } @UiHandler("passesButton") protected void onMountainPassesButtonPressed(TapEvent event) { if (presenter != null) { presenter.onMountainPassesButtonPressed(); } } @UiHandler("socialButton") protected void onSocialMediaButtonPressed(TapEvent event) { if (presenter != null) { presenter.onSocialMediaButtonPressed(); } } @UiHandler("tollingButton") protected void onTollRatesButtonPressed(TapEvent event) { if (presenter != null) { presenter.onTollRatesButtonPressed(); } } @UiHandler("borderButton") protected void onBorderWaitButtonPressed(TapEvent event) { if (presenter != null) { presenter.onBorderWaitButtonPressed(); } } @UiHandler("amtrakButton") protected void onAmtrakButtonPressed(TapEvent event) { if (presenter != null) { presenter.onAmtrakButtonPressed(); } } @UiHandler("camerasCellList") protected void onCameraCellSelected(CellSelectedEvent event) { if (presenter != null) { int index = event.getIndex(); presenter.onCameraSelected(index); } } @UiHandler("ferriesCellList") protected void onFerriesCellSelected(CellSelectedEvent event) { if (presenter != null) { int index = event.getIndex(); presenter.onFerriesSelected(index); } } @UiHandler("mountainPassesCellList") protected void onMountainPassCellSelected(CellSelectedEvent event) { if (presenter != null) { int index = event.getIndex(); presenter.onMountainPassSelected(index); } } @UiHandler("travelTimesCellList") protected void onTravelTimeCellSelected(CellSelectedEvent event) { if (presenter != null) { int index = event.getIndex(); presenter.onTravelTimeSelected(index); } } @Override public void setPresenter(Presenter presenter) { this.presenter = presenter; } @Override public void render(List<HighwayAlertItem> alerts) { int length = alerts.size(); if (!alerts.isEmpty()) { for (final HighwayAlertItem alert: alerts) { HTMLPanel alertPanel = new HTMLPanel(""); String text = alert.getHeadlineDescription(); String shortenedAlert = ParserUtils.ellipsis(text, 128); HTML html = new HTML("<div><p>" + shortenedAlert + "</p></div><div></div>"); if (length == 1) { alertsCarousel.setShowCarouselIndicator(false); if (alert.getAlertId() == -1) { html.addStyleName(AppBundle.INSTANCE.css().noHighImpactAlerts()); } else { html.addStyleName(AppBundle.INSTANCE.css().highImpactAlert()); if (alert.getAlertId() != -1) { html.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { presenter.onHighImpactAlertSelected(alert.getAlertId()); } }); } } alertPanel.add(html); alertsCarousel.add(alertPanel); } else { alertsCarousel.setShowCarouselIndicator(true); html.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { presenter.onHighImpactAlertSelected(alert.getAlertId()); } }); html.addStyleName(AppBundle.INSTANCE.css().highImpactAlert()); alertPanel.add(html); alertsCarousel.add(alertPanel); } } alertsCarousel.setHeight("75px"); alertsCarousel.refresh(); } } @Override public void refresh() { tabPanel.tabContainer.refresh(); scrollPanel.refresh(); pullToRefresh.refresh(); } @Override public void clear() { alertsCarousel.clear(); alertsCarousel.refresh(); alertsCarousel.setHeight("0px"); } @Override public void showProgressIndicator() { progressIndicator.setVisible(true); } @Override public void hideProgressIndicator() { progressIndicator.setVisible(false); } @Override public void setHeaderPullHandler(Pullhandler pullHandler) { pullToRefresh.setHeaderPullHandler(pullHandler); } @Override public PullArrowWidget getPullHeader() { return pullArrowHeader; } @Override public HasRefresh getPullPanel() { return pullToRefresh; } @Override public TabPanel getTabPanel() { return this.tabPanel; } @Override public void renderLocations(List<LocationItem> createLocationList) { locationsWidgetList.clear(); int i = 0; for (LocationItem item : createLocationList) { FlowPanel cellPanel = new FlowPanel(); cellPanel.setStyleName(AppBundle.INSTANCE.css().cellLocation()); HTML html = new HTML(item.getTitle()); html.setWordWrap(true); html.setStyleName(AppBundle.INSTANCE.css().cellLocationTitle()); final int index = i; cellPanel.sinkEvents(Event.ONCLICK); cellPanel.addHandler(new ClickHandler() { public void onClick(ClickEvent event) { // handle the click event if (presenter != null){ presenter.onLocationSelected(index); } } }, ClickEvent.getType()); Button b = new Button(); b.setStyleName(AppBundle.INSTANCE.css().gearIcon()); b.addTapHandler(new TapHandler() { @Override public void onTap(TapEvent event) { List<OptionsDialogEntry> list = new ArrayList<>(); list.add(new OptionsDialogEntry("Delete Location", ButtonType.IMPORTANT)); list.add(new OptionsDialogEntry("Edit Name", ButtonType.NORMAL)); list.add(new OptionsDialogEntry("Cancel", ButtonType.NORMAL)); Dialogs.options(list, new OptionCallback() { @Override public void onOptionSelected(int position) { switch(position){ case 1: presenter.onLocationRemove(index); break; case 2: presenter.onLocationEdit(index); break; default: break; } } }); } }); cellPanel.add(html); cellPanel.add(b); locationsWidgetList.add(cellPanel); i++; } } @Override public void showLocationsHeader(){ locationsHeader.setVisible(true); } @Override public void hideLocationsHeader(){ locationsHeader.setVisible(false); } @Override public void showLocationsList(){ locationsWidgetList.setVisible(true); } @Override public void hideLocationsList(){ locationsWidgetList.setVisible(false); } @Override public void renderCameras(List<CameraItem> createCameraList) { camerasCellList.render(createCameraList); } @Override public void showCamerasHeader() { camerasHeader.setVisible(true); } @Override public void hideCamerasHeader() { camerasHeader.setVisible(false); } @Override public void showCamerasList() { camerasCellList.setVisible(true); } @Override public void hideCamerasList() { camerasCellList.setVisible(false); } @Override public void renderFerries(List<FerriesRouteItem> createFerriesList) { ferriesCellList.render(createFerriesList); } @Override public void showFerriesHeader() { ferriesHeader.setVisible(true); } @Override public void hideFerriesHeader() { ferriesHeader.setVisible(false); } @Override public void showFerriesList() { ferriesCellList.setVisible(true); } @Override public void hideFerriesList() { ferriesCellList.setVisible(false); } @Override public void renderMountainPasses(List<MountainPassItem> createMountainPassList) { mountainPassesCellList.render(createMountainPassList); } @Override public void showMountainPassesHeader() { mountainPassesHeader.setVisible(true); } @Override public void hideMountainPassesHeader() { mountainPassesHeader.setVisible(false); } @Override public void showMountainPassesList() { mountainPassesCellList.setVisible(true); } @Override public void hideMountainPassesList() { mountainPassesCellList.setVisible(false); } @Override public void renderTravelTimes(List<TravelTimesItem> createTravelTimesList) { travelTimesCellList.render(createTravelTimesList); } @Override public void showTravelTimesHeader() { travelTimesHeader.setVisible(true); } @Override public void hideTravelTimesHeader() { travelTimesHeader.setVisible(false); } @Override public void showTravelTimesList() { travelTimesCellList.setVisible(true); } @Override public void hideTravelTimesList() { travelTimesCellList.setVisible(false); } @Override public void showEmptyFavoritesMessage() { emptyFavorites.setVisible(true); } @Override public void hideEmptyFavoritesMessage() { emptyFavorites.setVisible(false); } private void accessibilityShowHome(){ Roles.getMainRole().setAriaHiddenState(home.getElement(), false); Roles.getMainRole().setAriaHiddenState(favorites.getElement(), true); Roles.getTabRole().setAriaSelectedState(homeTab.getElement(), SelectedValue.TRUE); Roles.getTabRole().setAriaSelectedState(favoritesTab.getElement(), SelectedValue.FALSE); } private void accessibilityShowFavorites(){ Roles.getMainRole().setAriaHiddenState(home.getElement(), true); Roles.getMainRole().setAriaHiddenState(favorites.getElement(), false); Roles.getTabRole().setAriaSelectedState(homeTab.getElement(), SelectedValue.FALSE); Roles.getTabRole().setAriaSelectedState(favoritesTab.getElement(), SelectedValue.TRUE); } private void accessibilityPrepare(){ // Set UI labels for accessibility trafficButton.setTitle("traffic Map"); ferriesButton.setTitle("ferries"); passesButton.setTitle("mountain passes"); socialButton.setTitle("social media"); tollingButton.setTitle("toll rates"); borderButton.setTitle("border waits"); amtrakButton.setTitle("amtrak cascades"); aboutButton.setTitle("about the app"); alertsCarousel.setTitle("high impact alerts"); star.setAltText("star"); // Set ARIA roles for accessibility Roles.getButtonRole().set(trafficButton.getElement()); Roles.getButtonRole().set(ferriesButton.getElement()); Roles.getButtonRole().set(passesButton.getElement()); Roles.getButtonRole().set(socialButton.getElement()); Roles.getButtonRole().set(tollingButton.getElement()); Roles.getButtonRole().set(borderButton.getElement()); Roles.getButtonRole().set(amtrakButton.getElement()); Roles.getButtonRole().set(aboutButton.getElement()); Roles.getHeadingRole().set(highImpactAlertsPanel.getElement()); Roles.getHeadingRole().set(heading.getElement()); Roles.getTabRole().set(homeTab.getElement()); Roles.getTabRole().setAriaSelectedState(homeTab.getElement(), SelectedValue.TRUE); Roles.getTabRole().setAriaLabelProperty(homeTab.getElement(), "home"); Roles.getTabRole().set(favoritesTab.getElement()); Roles.getTabRole().setAriaSelectedState(favoritesTab.getElement(), SelectedValue.FALSE); Roles.getTabRole().setAriaLabelProperty(favoritesTab.getElement(), "favorites"); Roles.getProgressbarRole().set(progressIndicator.getElement()); Roles.getProgressbarRole().setAriaLabelProperty(progressIndicator.getElement(), "loading indicator"); // Define flow Roles.getHeadingRole().setAriaFlowtoProperty(heading.getElement(), Id.of(trafficButton.getElement())); // Hide redundant content from VoiceOver Roles.getHeadingRole().setAriaHiddenState(trafficTitle.getElement(), true); Roles.getHeadingRole().setAriaHiddenState(ferriesTitle.getElement(), true); Roles.getHeadingRole().setAriaHiddenState(passesTitle.getElement(), true); Roles.getHeadingRole().setAriaHiddenState(socialTitle.getElement(), true); Roles.getHeadingRole().setAriaHiddenState(tollingTitle.getElement(), true); Roles.getHeadingRole().setAriaHiddenState(borderTitle.getElement(), true); Roles.getHeadingRole().setAriaHiddenState(amtrakTitle.getElement(), true); // TODO Hide pull down until we can figure out how to get VoiceOver to work with it Roles.getButtonRole().setAriaHiddenState(pullArrowHeader.getElement(), true); accessibilityShowHome(); } }