/* * 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.mountainpasses; import com.google.code.gwt.database.client.GenericRow; import com.google.code.gwt.database.client.service.DataServiceException; import com.google.code.gwt.database.client.service.ListCallback; import com.google.code.gwt.database.client.service.RowIdListCallback; import com.google.code.gwt.database.client.service.VoidCallback; import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.JsArrayInteger; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.json.client.JSONArray; import com.google.gwt.jsonp.client.JsonpRequestBuilder; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AbstractImagePrototype; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.web.bindery.event.shared.EventBus; import com.googlecode.gwtphonegap.client.PhoneGap; import com.googlecode.gwtphonegap.client.notification.AlertCallback; import com.googlecode.mgwt.mvp.client.MGWTAbstractActivity; import com.googlecode.mgwt.ui.client.widget.panel.pull.PullArrowStandardHandler; import com.googlecode.mgwt.ui.client.widget.panel.pull.PullArrowStandardHandler.PullActionHandler; import gov.wa.wsdot.mobile.client.ClientFactory; import gov.wa.wsdot.mobile.client.css.AppBundle; import gov.wa.wsdot.mobile.client.event.ActionEvent; import gov.wa.wsdot.mobile.client.event.ActionNames; import gov.wa.wsdot.mobile.client.plugins.accessibility.Accessibility; import gov.wa.wsdot.mobile.client.plugins.analytics.Analytics; import gov.wa.wsdot.mobile.client.service.WSDOTContract.CachesColumns; import gov.wa.wsdot.mobile.client.service.WSDOTContract.MountainPassesColumns; import gov.wa.wsdot.mobile.client.service.WSDOTDataService; import gov.wa.wsdot.mobile.client.service.WSDOTDataService.Tables; import gov.wa.wsdot.mobile.client.util.Consts; import gov.wa.wsdot.mobile.shared.CacheItem; import gov.wa.wsdot.mobile.shared.MountainPassConditions; import gov.wa.wsdot.mobile.shared.MountainPassConditions.Forecast; import gov.wa.wsdot.mobile.shared.MountainPassItem; import java.util.*; import java.util.Map.Entry; public class MountainPassesActivity extends MGWTAbstractActivity implements MountainPassesView.Presenter { private final ClientFactory clientFactory; private MountainPassesView view; private EventBus eventBus; private WSDOTDataService dbService; private PhoneGap phoneGap; private Analytics analytics; private Accessibility accessibility; private static boolean alertShowing = false; private static HashMap<String, String[]> weatherPhrases = new HashMap<String, String[]>(); private static HashMap<String, String[]> weatherPhrasesNight = new HashMap<String, String[]>(); private static DateTimeFormat parseDateFormat = DateTimeFormat.getFormat("yyyy,M,d,H,m"); //e.g. [2010, 11, 2, 8, 22] private static DateTimeFormat displayDateFormat = DateTimeFormat.getFormat("MMMM d, yyyy h:mm a"); private static final String MOUNTAIN_PASS_URL = Consts.HOST_URL + "/traveler/api/mountainpassconditions"; private static List<MountainPassItem> mountainPassItems = new ArrayList<MountainPassItem>(); private static List<Integer> starred = new ArrayList<Integer>(); public MountainPassesActivity(ClientFactory clientFactory) { this.clientFactory = clientFactory; } @Override public void start(AcceptsOneWidget panel, EventBus eventBus) { view = clientFactory.getMountainPassesView(); dbService = clientFactory.getDbService(); phoneGap = clientFactory.getPhoneGap(); analytics = clientFactory.getAnalytics(); accessibility = clientFactory.getAccessibility(); this.eventBus = eventBus; view.setPresenter(this); view.getPullHeader().setHTML("pull down"); PullArrowStandardHandler headerHandler = new PullArrowStandardHandler( view.getPullHeader(), view.getPullPanel()); headerHandler.setErrorText("Error"); headerHandler.setLoadingText("Loading"); headerHandler.setNormalText("pull down"); headerHandler.setPulledText("release to load"); headerHandler.setPullActionHandler(new PullActionHandler() { @Override public void onPullAction(final AsyncCallback<Void> callback) { new Timer() { @Override public void run() { createTopicsList(true); view.refresh(); callback.onSuccess(null); } }.schedule(1); } }); view.setHeaderPullHandler(headerHandler); buildWeatherPhrases(); createTopicsList(false); if (Consts.ANALYTICS_ENABLED) { analytics.trackScreen("/Mountain Passes"); } panel.setWidget(view); accessibility.postScreenChangeNotification(); } @Override public void onStop() { view.setPresenter(null); } @Override public void onItemSelected(int index) { MountainPassItem item = mountainPassItems.get(index); if (Consts.ANALYTICS_ENABLED) { analytics.trackScreen("/Mountain Passes/Pass/Report"); } clientFactory.getPlaceController().goTo( new MountainPassDetailsPlace(Integer.toString(item .getMountainPassId()))); } @Override public void onBackButtonPressed() { ActionEvent.fire(eventBus, ActionNames.BACK); } private void createTopicsList(final boolean forceUpdate) { /** * Check the cache table for the last time data was downloaded. If we are within * the allowed time period, don't sync, otherwise get fresh data from the server. */ dbService.getCacheLastUpdated(Tables.MOUNTAIN_PASSES, new ListCallback<GenericRow>() { @Override public void onFailure(DataServiceException error) { } @Override public void onSuccess(List<GenericRow> result) { boolean shouldUpdate = true; if (!result.isEmpty()) { double now = System.currentTimeMillis(); double lastUpdated = result.get(0).getDouble(CachesColumns.CACHE_LAST_UPDATED); shouldUpdate = (Math.abs(now - lastUpdated) > (15 * 60000)); // Refresh every 15 minutes. } view.showProgressIndicator(); if (shouldUpdate || forceUpdate) { /** * Check the mountain passes table for any starred entries. If we find some, * save them to a list so we can re-star those after we flush the database. */ dbService.getStarredMountainPasses(new ListCallback<GenericRow>() { @Override public void onFailure(DataServiceException error) { } @Override public void onSuccess(List<GenericRow> result) { starred.clear(); if (!result.isEmpty()) { for (GenericRow row: result) { starred.add(row.getInt(MountainPassesColumns.MOUNTAIN_PASS_ID)); } } JsonpRequestBuilder jsonp = new JsonpRequestBuilder(); // Set timeout for 30 seconds (30000 milliseconds) jsonp.setTimeout(30000); jsonp.requestObject( MOUNTAIN_PASS_URL, new AsyncCallback<MountainPassConditions>() { @Override public void onFailure(Throwable caught) { view.hideProgressIndicator(); if (!alertShowing){ alertShowing = true; phoneGap.getNotification() .alert("Can't load data. Check your connection.", new AlertCallback() { @Override public void onOkButtonClicked() { alertShowing = false; } }, "Connection Error"); } } @Override public void onSuccess(MountainPassConditions result) { mountainPassItems.clear(); if (result.getMountainPassConditionsResult() != null) { MountainPassItem item; String weatherCondition; String weatherImage; String forecast_weather_image; String mDateUpdated = ""; int numConditions = result.getMountainPassConditionsResult().getPassCondition().length(); for (int i = 0; i < numConditions; i++) { item = new MountainPassItem(); weatherCondition = result.getMountainPassConditionsResult().getPassCondition().get(i).getWeatherCondition(); weatherImage = getWeatherImage(weatherPhrases, weatherCondition); JsArrayInteger dateUpdated = result.getMountainPassConditionsResult().getPassCondition().get(i).getDateUpdated(); try { StringBuilder sb = new StringBuilder(); for (int j=0; j < 5; j++) { sb.append(dateUpdated.get(j)); sb.append(","); } String tempDate = sb.toString().trim(); tempDate = tempDate.substring(0, tempDate.length()-1); Date date = parseDateFormat.parse(tempDate); mDateUpdated = displayDateFormat.format(date); } catch (Exception e) { mDateUpdated = "N/A"; } JsArray<Forecast> forecasts = result.getMountainPassConditionsResult().getPassCondition().get(i).getForecast(); int numForecasts = forecasts.length(); for (int k=0; k < numForecasts; k++) { Forecast forecast = forecasts.get(k); if (isNight(forecast.getDay())) { forecast_weather_image = getWeatherImage(weatherPhrasesNight, forecast.getForecastText()); } else { forecast_weather_image = getWeatherImage(weatherPhrases, forecast.getForecastText()); } forecast.setWeatherIcon(forecast_weather_image); if (k == 0) { if (weatherCondition.equals("")) { weatherCondition = forecast.getForecastText().split("\\.")[0] + "."; weatherImage = forecast_weather_image; } } forecasts.set(k, forecast); } item.setWeatherCondition(weatherCondition); item.setElevationInFeet(result.getMountainPassConditionsResult().getPassCondition().get(i).getElevationInFeet()); item.setMountainPassId(result.getMountainPassConditionsResult().getPassCondition().get(i).getMountainPassId()); item.setRoadCondition(result.getMountainPassConditionsResult().getPassCondition().get(i).getRoadCondition()); item.setTemperatureInFahrenheit(result.getMountainPassConditionsResult().getPassCondition().get(i).getTemperatureInFahrenheit()); item.setDateUpdated(mDateUpdated); item.setMountainPassName(result.getMountainPassConditionsResult().getPassCondition().get(i).getMountainPassName()); item.setWeatherIcon(weatherImage); item.setRestrictionOneText(result.getMountainPassConditionsResult().getPassCondition().get(i).getRestrictionOne().getRestrictionText()); item.setRestrictionOneTravelDirection(result.getMountainPassConditionsResult().getPassCondition().get(i).getRestrictionOne().getTravelDirection()); item.setRestrictionTwoText(result.getMountainPassConditionsResult().getPassCondition().get(i).getRestrictionTwo().getRestrictionText()); item.setRestrictionTwoTravelDirection(result.getMountainPassConditionsResult().getPassCondition().get(i).getRestrictionTwo().getTravelDirection()); item.setCamera(new JSONArray(result.getMountainPassConditionsResult().getPassCondition().get(i).getCameras()).toString()); item.setForecast(new JSONArray(forecasts).toString()); if (starred.contains(result.getMountainPassConditionsResult().getPassCondition().get(i).getMountainPassId())) { item.setIsStarred(1); } mountainPassItems.add(item); } // Purge existing mountain passes covered by incoming data dbService.deleteMountainPasses(new VoidCallback() { @Override public void onFailure(DataServiceException error) { } @Override public void onSuccess() { // Bulk insert all the new mountain passes dbService.insertMountainPasses(mountainPassItems, new RowIdListCallback() { @Override public void onFailure(DataServiceException error) { } @Override public void onSuccess(List<Integer> rowIds) { // Update the cache table with the time we did the update ArrayList<CacheItem> cacheItems = new ArrayList<CacheItem>(); cacheItems.add(new CacheItem(Tables.MOUNTAIN_PASSES, System.currentTimeMillis())); dbService.updateCachesTable(cacheItems, new VoidCallback() { @Override public void onFailure(DataServiceException error) { } @Override public void onSuccess() { dbService.getMountainPasses(new ListCallback<GenericRow>() { @Override public void onFailure(DataServiceException error) { } @Override public void onSuccess(List<GenericRow> result) { getMountainPasses(result); } }); } }); } }); } }); } } }); } }); } else { dbService.getMountainPasses(new ListCallback<GenericRow>() { @Override public void onFailure(DataServiceException error) { } @Override public void onSuccess(List<GenericRow> result) { getMountainPasses(result); } }); } } }); } /** * Get the lastest mountain pass conditions from the database. * * @param result */ private void getMountainPasses(List<GenericRow> result) { mountainPassItems.clear(); MountainPassItem item; int numResults = result.size(); for (int i = 0; i < numResults; i++) { item = new MountainPassItem(); item.setMountainPassId(result.get(i).getInt(MountainPassesColumns.MOUNTAIN_PASS_ID)); item.setMountainPassName(result.get(i).getString(MountainPassesColumns.MOUNTAIN_PASS_NAME)); item.setWeatherIcon(result.get(i).getString(MountainPassesColumns.MOUNTAIN_PASS_WEATHER_ICON)); item.setWeatherCondition(result.get(i).getString(MountainPassesColumns.MOUNTAIN_PASS_WEATHER_CONDITION)); item.setDateUpdated(result.get(i).getString(MountainPassesColumns.MOUNTAIN_PASS_DATE_UPDATED)); mountainPassItems.add(item); } view.hideProgressIndicator(); view.render(mountainPassItems); view.refresh(); accessibility.postScreenChangeNotification(); } private static String makeImage(ImageResource resource) { AbstractImagePrototype image = AbstractImagePrototype.create(resource); String html = image.getHTML(); return html; } private void buildWeatherPhrases() { String[] weather_clear = {"fair", "sunny", "clear"}; String[] weather_few_clouds = {"few clouds", "scattered clouds", "mostly sunny", "mostly clear"}; String[] weather_partly_cloudy = {"partly cloudy", "partly sunny"}; String[] weather_cloudy = {"cloudy"}; String[] weather_mostly_cloudy = {"broken", "mostly cloudy"}; String[] weather_overcast = {"overcast"}; String[] weather_light_rain = {"occasional light rain", "light rain", "showers"}; String[] weather_rain = {"rain", "heavy rain", "raining"}; String[] weather_snow = {"snow", "snowing", "light snow", "heavy snow"}; String[] weather_fog = {"areas of fog", "fog"}; String[] weather_sleet = {"rain snow", "light rain snow", "heavy rain snow", "rain and snow"}; String[] weather_hail = {"ice pellets", "light ice pellets", "heavy ice pellets", "hail"}; String[] weather_thunderstorm = {"thunderstorm", "thunderstorms"}; weatherPhrases.clear(); weatherPhrasesNight.clear(); weatherPhrases.put(AppBundle.INSTANCE.css().sunnyIcon(), weather_clear); weatherPhrases.put(AppBundle.INSTANCE.css().cloudy_1Icon(), weather_few_clouds); weatherPhrases.put(AppBundle.INSTANCE.css().cloudy_2Icon(), weather_partly_cloudy); weatherPhrases.put(AppBundle.INSTANCE.css().cloudy_3Icon(), weather_cloudy); weatherPhrases.put(AppBundle.INSTANCE.css().cloudy_4Icon(), weather_mostly_cloudy); weatherPhrases.put(AppBundle.INSTANCE.css().overcastIcon(), weather_overcast); weatherPhrases.put(AppBundle.INSTANCE.css().light_rainIcon(), weather_light_rain); weatherPhrases.put(AppBundle.INSTANCE.css().shower_3Icon(), weather_rain); weatherPhrases.put(AppBundle.INSTANCE.css().snow_4Icon(), weather_snow); weatherPhrases.put(AppBundle.INSTANCE.css().fogIcon(), weather_fog); weatherPhrases.put(AppBundle.INSTANCE.css().sleetIcon(), weather_sleet); weatherPhrases.put(AppBundle.INSTANCE.css().hailIcon(), weather_hail); weatherPhrases.put(AppBundle.INSTANCE.css().tstorm_3Icon(), weather_thunderstorm); weatherPhrasesNight.put(AppBundle.INSTANCE.css().sunny_nightIcon(), weather_clear); weatherPhrasesNight.put(AppBundle.INSTANCE.css().cloudy_1_nightIcon(), weather_few_clouds); weatherPhrasesNight.put(AppBundle.INSTANCE.css().cloudy_2_nightIcon(), weather_partly_cloudy); weatherPhrasesNight.put(AppBundle.INSTANCE.css().cloudy_3_nightIcon(), weather_cloudy); weatherPhrasesNight.put(AppBundle.INSTANCE.css().cloudy_4_nightIcon(), weather_mostly_cloudy); weatherPhrasesNight.put(AppBundle.INSTANCE.css().overcastIcon(), weather_overcast); weatherPhrasesNight.put(AppBundle.INSTANCE.css().light_rainIcon(), weather_light_rain); weatherPhrasesNight.put(AppBundle.INSTANCE.css().shower_3Icon(), weather_rain); weatherPhrasesNight.put(AppBundle.INSTANCE.css().snow_4Icon(), weather_snow); weatherPhrasesNight.put(AppBundle.INSTANCE.css().fog_nightIcon(), weather_fog); weatherPhrasesNight.put(AppBundle.INSTANCE.css().sleetIcon(), weather_sleet); weatherPhrasesNight.put(AppBundle.INSTANCE.css().hailIcon(), weather_hail); weatherPhrasesNight.put(AppBundle.INSTANCE.css().tstorm_3Icon(), weather_thunderstorm); return; } private static String getWeatherImage(HashMap<String, String[]> weatherPhrases, String weather) { String image = AppBundle.INSTANCE.css().weather_naIcon(); Set<Entry<String, String[]>> set = weatherPhrases.entrySet(); Iterator<Entry<String, String[]>> i = set.iterator(); if (weather.equals("")) return image; String s0 = weather.split("\\.")[0]; // Pattern match on first sentence only. while(i.hasNext()) { Entry<String, String[]> me = i.next(); for (String phrase: (String[])me.getValue()) { if (s0.toLowerCase().startsWith(phrase)) { image = (String)me.getKey(); return image; } } } return image; } private static boolean isNight(String text) { String patternStr = "night|tonight"; RegExp pattern = RegExp.compile(patternStr, "i"); MatchResult matcher = pattern.exec(text); boolean matchFound = matcher != null; return matchFound; } }