package edu.mit.mitmobile2.dining; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.preference.PreferenceManager; import android.text.Html; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; import android.widget.FrameLayout; import android.widget.TextView; import edu.mit.mitmobile2.AttributesParser; import edu.mit.mitmobile2.MobileWebApi; import edu.mit.mitmobile2.NaturalSort; import edu.mit.mitmobile2.R; import edu.mit.mitmobile2.objs.MapItem; import edu.mit.mitmobile2.objs.MapPoint; public class DiningModel { protected static final String PREFS_NAME = "dining_data"; protected static final String JSON = "json"; protected static final String JSON_SAVED_TIME_KEY = "last_saved"; protected static long sCacheLifetime = 10 * 60 * 1000; protected static long sLastRefreshTime; protected static DiningVenues sVenues; protected static ArrayList<DiningLink> sLinks; protected static final boolean sUseTestData = false; public static void fetchDiningData(final Context context, boolean forceRefresh, final Handler uiHandler) { if (!sUseTestData) { // check memory cache if (!forceRefresh && sLastRefreshTime > (System.currentTimeMillis() - sCacheLifetime)) { // nothing to do uiHandler.post(new Runnable() { @Override public void run() { MobileWebApi.sendSuccessMessage(uiHandler); } }); return; } // check persistent cache final SharedPreferences sharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); long lastSaved = sharedPreferences.getLong(JSON_SAVED_TIME_KEY, 0); if (!forceRefresh && lastSaved > (System.currentTimeMillis() - sCacheLifetime)) { if (fetchPersistantCache(sharedPreferences, uiHandler)) { return; } } HashMap<String, String> parameters = new HashMap<String, String>(); parameters.put("module", "dining"); MobileWebApi webApi = new MobileWebApi(false, true, "Dining", context, uiHandler); webApi.requestJSONObject(parameters, new MobileWebApi.JSONObjectResponseListener( new MobileWebApi.ErrorResponseListener() { @Override public void onError() { boolean success = fetchPersistantCache(sharedPreferences, uiHandler); if (!success) { MobileWebApi.sendErrorMessage(uiHandler); } } }, new MobileWebApi.DefaultCancelRequestListener(uiHandler) ) { @Override public void onResponse(JSONObject object) throws JSONException { sVenues = new DiningVenues(object); sLinks = parseDiningLinks(object.getJSONArray("links")); sLastRefreshTime = System.currentTimeMillis(); Editor editor = sharedPreferences.edit(); editor.putString(JSON, object.toString()); editor.putLong(JSON_SAVED_TIME_KEY, sLastRefreshTime); editor.commit(); MobileWebApi.sendSuccessMessage(uiHandler); } }); } else { uiHandler.postDelayed(new Runnable() { @Override public void run() { try { InputStream istream = context.getResources().getAssets().open("dining/data.json"); String jsonString = convertStreamToString(istream); JSONObject jsonObject = new JSONObject(jsonString); sVenues = new DiningVenues(jsonObject); sLinks = parseDiningLinks(jsonObject.getJSONArray("links")); MobileWebApi.sendSuccessMessage(uiHandler); } catch (IOException e) { e.printStackTrace(); MobileWebApi.sendErrorMessage(uiHandler); } catch (JSONException e) { e.printStackTrace(); MobileWebApi.sendErrorMessage(uiHandler); } } }, 500); } } private static boolean fetchPersistantCache(SharedPreferences sharedPreferences, final Handler uiHandler) { String jsonData = sharedPreferences.getString(JSON, null); if (jsonData == null) { return false; } try { JSONObject object = new JSONObject(jsonData); sVenues = new DiningVenues(object); sLinks = parseDiningLinks(object.getJSONArray("links")); uiHandler.post(new Runnable() { @Override public void run() { MobileWebApi.sendSuccessMessage(uiHandler); } }); return true; } catch (JSONException e) { Log.d("dining", "Cached dining json in invalid"); e.printStackTrace(); return false; } } private static ArrayList<DiningLink> parseDiningLinks(JSONArray linkArray) { ArrayList<DiningLink> links = new ArrayList<DiningLink>(); for (int i = 0; i < linkArray.length(); i++) { try { JSONObject link = linkArray.getJSONObject(i); links.add(new DiningLink(link.getString("name"), link.getString("url"))); } catch (JSONException e) { Log.e("Dining JSON", "Error parsing link at index ["+ i +"] in json : " + linkArray.toString()); } } return links; } public static DiningVenues getDiningVenues() { return sVenues; } public static List<DiningLink> getDiningLinks() { return sLinks; } private static String convertStreamToString(InputStream is) throws IOException { StringBuilder builder = new StringBuilder(); char[] buffer = new char[2048]; try { Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); int n; while ((n = reader.read(buffer)) != -1) { builder.append(buffer, 0, n); } } finally { is.close(); } return builder.toString(); } private static Calendar getCalendarDate(String date, String time) { GregorianCalendar calendar = new GregorianCalendar(); String[] dateParts = date.split("\\-"); int year = Integer.parseInt(dateParts[0]); int month = Integer.parseInt(dateParts[1]) - 1; int day = Integer.parseInt(dateParts[2]); if (time == null) { calendar.set(year, month, day, 0, 0); } else { String[] timeParts = time.split(":"); int hourOfDay = Integer.parseInt(timeParts[0]); int minute = Integer.parseInt(timeParts[1]); calendar.set(year, month, day, hourOfDay, minute, 0); } return calendar; } public static class DiningVenues { private String mAnnouncementsHtml; private ArrayList<HouseDiningHall> mHouseVenues = new ArrayList<HouseDiningHall>(); private HashMap<String, List<? extends DiningHall>> mRetailVenues = new HashMap<String, List<? extends DiningHall>>(); public DiningVenues(JSONObject object) throws JSONException { mAnnouncementsHtml = object.getString("announcements_html"); JSONArray jsonHouse = object.getJSONObject("venues").getJSONArray("house"); for (int i = 0; i < jsonHouse.length(); i++) { mHouseVenues.add(new HouseDiningHall(jsonHouse.getJSONObject(i))); } // sort by short name Collections.sort(mHouseVenues, new Comparator<HouseDiningHall>() { @Override public int compare(HouseDiningHall hall1, HouseDiningHall hall2) { String shortName1 = hall1.getShortName(); String shortName2 = hall2.getShortName(); return shortName1.compareTo(shortName2); } }); JSONArray jsonRetail = object.getJSONObject("venues").getJSONArray("retail"); for (int i = 0; i < jsonRetail.length(); i++) { RetailDiningHall retailDiningHall = new RetailDiningHall(jsonRetail.getJSONObject(i)); String buildingNumber = retailDiningHall.getLocation().getBuildingNumber(); if (buildingNumber == null) { buildingNumber = "other"; } if (!mRetailVenues.containsKey(buildingNumber)) { mRetailVenues.put(buildingNumber, new ArrayList<RetailDiningHall>()); } @SuppressWarnings("unchecked") ArrayList<RetailDiningHall> retailVenues = (ArrayList<RetailDiningHall>) mRetailVenues.get(buildingNumber); retailVenues.add(new RetailDiningHall(jsonRetail.getJSONObject(i))); } // alphabetize the retail halls within buildings. for (List<? extends DiningHall> halls: mRetailVenues.values()) { Collections.sort(halls, new Comparator<DiningHall>() { @Override public int compare(DiningHall lhs, DiningHall rhs) { return lhs.getName().compareTo(rhs.getName()); } }); } } public List<? extends DiningHall> getHouses() { return mHouseVenues; } public Map<String, List<? extends DiningHall>> getRetail() { return mRetailVenues; } public List<String> getRetailBuildingNumbers() { ArrayList<String> buildingNumbers = new ArrayList<String>(); for (String key : mRetailVenues.keySet()) { buildingNumbers.add(key); } Collections.sort(buildingNumbers, new Comparator<String>() { @Override public int compare(String lhs, String rhs) { return NaturalSort.compare(lhs, rhs); } }); return buildingNumbers; } public HouseDiningHall getHouseDiningHall(String id) { return (HouseDiningHall) findDiningHall(mHouseVenues, id); } public RetailDiningHall getRetailDiningHall(String id) { RetailDiningHall hall = findRetailDiningHall(id); if (hall != null) { return hall; } throw new RuntimeException("Dining hall with id=" + id + " not found"); } public RetailDiningHall findRetailDiningHall(String id) { for (List<? extends DiningHall> buildingDiningHalls : mRetailVenues.values()) { for (DiningHall hall : buildingDiningHalls) { if (hall.getID().equals(id)) { return (RetailDiningHall) hall; } } } return null; } private DiningHall findDiningHall(List<? extends DiningHall> halls, String id) { for (DiningHall hall : halls) { if (hall.getID().equals(id)) { return hall; } } throw new RuntimeException("Dining hall with id=" + id + " not found"); } public String getAnnouncementsPlainText() { return Html.fromHtml(mAnnouncementsHtml).toString(); } public String getAnnouncementsHtml() { return mAnnouncementsHtml; } } public static abstract class DiningHall { private String mId; private String mName; private String mShortName; private String mUrl; private String mIconUrl; private List<String> mPaymentOptions = Collections.emptyList(); private DiningHallLocation mLocation; public DiningHall(JSONObject object) throws JSONException { mId = object.getString("id"); mUrl = object.getString("url"); if (object.has("icon_url")) { mIconUrl = object.getString("icon_url"); } mName = object.getString("name"); mShortName = object.getString("short_name"); JSONObject locationJSON; if (object.has("location")) { locationJSON = object.getJSONObject("location"); } else { locationJSON = new JSONObject(); } mLocation = new DiningHallLocation(this, locationJSON); if (object.has("payment")) { JSONArray jPayArray = object.getJSONArray("payment"); ArrayList<String> tempList = new ArrayList<String>(); for (int i = 0; i < jPayArray.length(); i++) { tempList.add(jPayArray.getString(i)); } mPaymentOptions = tempList; } } public enum Status { CLOSED, OPEN } public String getID() { return mId; } public String getName() { return mName; } public String getShortName() { return mShortName; } public String getUrl() { return mUrl; } public String getIconUrl() { return mIconUrl; } public List<String> getPaymentOptions() { return mPaymentOptions; } public String getPaymentOptionString() { String options =""; for (String s : mPaymentOptions) { options+= s + ", "; } return options.substring(0, options.length() - 2); } public DiningHallLocation getLocation() { return mLocation; } public abstract String getTodaysHoursSummary(long currentTime); public abstract Status getCurrentStatus(long currentTime); public abstract String getCurrentStatusSummary(long currentTime); } public static class HouseDiningHall extends DiningHall { DailyMealsSchedule mSchedule; public HouseDiningHall(JSONObject object) throws JSONException { super(object); JSONArray jsonSchedule = object.getJSONArray("meals_by_day"); ArrayList<DailyMeals> dailyMealsList = new ArrayList<DailyMeals>(); for (int i = 0; i < jsonSchedule.length(); i++) { dailyMealsList.add(new DailyMeals(jsonSchedule.optJSONObject(i))); } mSchedule = new DailyMealsSchedule(dailyMealsList); } private DailyMeals getCurrentDailyMeals(long currentTime) { Calendar currentDay = new GregorianCalendar(); currentDay.setTimeInMillis(currentTime); for (DailyMeals dailyMeals : mSchedule.mDailyMealsList) { if (compareDates(currentDay, dailyMeals.getDay()) == 0) { return dailyMeals; } } return null; } @Override public String getTodaysHoursSummary(long currentTime) { DailyMeals dailyMeals = getCurrentDailyMeals(currentTime); if (dailyMeals != null) { return dailyMeals.getScheduleSummary(); } return ""; } @Override public Status getCurrentStatus(long currentTime) { DailyMeals dailyMeals = getCurrentDailyMeals(currentTime); if (dailyMeals != null) { for (Meal meal : dailyMeals.mMeals.values()) { Calendar currentDay = new GregorianCalendar(); currentDay.setTimeInMillis(currentTime); if (meal.isInProgress(currentDay)) { return Status.OPEN; } } } return Status.CLOSED; } @Override public String getCurrentStatusSummary(long currentTime) { Calendar currentDay = new GregorianCalendar(); currentDay.setTimeInMillis(currentTime); DailyMeals dailyMeals = getCurrentDailyMeals(currentTime); if (dailyMeals != null) { String mealKey = DailyMeals.getFirstMealName(); while (mealKey != null) { Meal meal = dailyMeals.getMeal(mealKey); if (meal != null) { if (meal.isInProgress(currentDay)) { return openUntil(meal.mEnd); } if (meal.isUpcoming(currentDay)) { return opensAt(meal.mStart); } } mealKey = DailyMeals.getNextMealName(mealKey); } } return "Closed for the day"; } public DailyMealsSchedule getSchedule() { return mSchedule; } } public static class RetailDiningHall extends DiningHall { static class DailyHours { String mMessage; Calendar mDay; Calendar mStartTime; Calendar mEndTime; static String[] days = new String[] { "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", }; @Override public String toString() { return getDayAbbreviation() + " " + getScheduleSpan(); } DailyHours(JSONObject object) throws JSONException { mDay = getCalendarDate(object.getString("date"), null); if (!object.isNull("message")) { mMessage = object.getString("message"); } if (!object.isNull("start_time") && !object.isNull("end_time")) { mStartTime = getCalendarDate(object.getString("date"), object.getString("start_time")); mEndTime = getCalendarDate(object.getString("date"), object.getString("end_time")); // make sure event ends after it starts if (mEndTime.getTimeInMillis() < mStartTime.getTimeInMillis()) { mEndTime.add(Calendar.DATE, 1); } } } private static SimpleDateFormat sHourFormat = new SimpleDateFormat("h", Locale.US); public Calendar getDay() { return mDay; } public String getDayAbbreviation() { SimpleDateFormat df = new SimpleDateFormat("EEE", Locale.US); return df.format(mDay.getTime()).toLowerCase(); } public String getScheduleSpan() { if (mStartTime == null && mMessage != null) { return mMessage; } SimpleDateFormat startFormat = (mStartTime.get(Calendar.MINUTE) > 0) ? sHourMinuteFormat : sHourFormat; SimpleDateFormat endFormat = (mEndTime.get(Calendar.MINUTE) > 0) ? sHourMinuteFormat : sHourFormat; String start = startFormat.format(mStartTime.getTime()) + sAmPmFormat.format(mStartTime.getTime()).toLowerCase(Locale.US); String end = endFormat.format(mEndTime.getTime()) + sAmPmFormat.format(mEndTime.getTime()).toLowerCase(Locale.US); return start + " - " + end; } } String mDescriptionHtml; ArrayList<DailyHours> mHours = new ArrayList<DailyHours>(); String mMenuHtml; String mMenuUrl; String mHomepageUrl; ArrayList<String> mCuisine = new ArrayList<String>(); private static String RETAIL_BOOKMARK_KEY = "retail.bookmarks"; public static void saveBookmarks(Context context, List<RetailDiningHall> list) { // serialize hall Ids into comma separated string, save string String nameString = ""; for (RetailDiningHall hall : list) { if (list.indexOf(hall) == list.size() - 1) { nameString+= hall.getID(); } else { nameString+= hall.getID() + ","; } } SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = prefs.edit(); editor.putString(RETAIL_BOOKMARK_KEY, nameString); editor.apply(); } public static List<RetailDiningHall> getBookmarks(Context context) { // load string from preferences, unserialize string into flag names, lookup flags from static map // returns list of flags ArrayList<RetailDiningHall> bookmarkedHalls = new ArrayList<RetailDiningHall>(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String nameString = prefs.getString(RETAIL_BOOKMARK_KEY, ""); if (nameString.isEmpty()) return bookmarkedHalls; // return empty list if no preference saved String [] idSet = nameString.split(","); DiningVenues venues = DiningModel.getDiningVenues(); for (String id : idSet) { RetailDiningHall hall = venues.findRetailDiningHall(id); if (hall != null) bookmarkedHalls.add(hall); } return bookmarkedHalls; } public RetailDiningHall(JSONObject object) throws JSONException { super(object); if (!object.isNull("description_html")) { mDescriptionHtml = object.getString("description_html"); } if (!object.isNull("menu_html")) { mMenuHtml = object.getString("menu_html"); } if (!object.isNull("hours")) { JSONArray jsonHours = object.getJSONArray("hours"); for (int i = 0; i < jsonHours.length(); i++) { mHours.add(new DailyHours(jsonHours.getJSONObject(i))); } } if (!object.isNull("menu_url")) { mMenuUrl = object.getString("menu_url"); if (!mMenuUrl.startsWith("http://") && !mMenuUrl.startsWith("https://")) { mMenuUrl = "http://"+mMenuUrl; //prepend scheme when necessary } } if (!object.isNull("homepage_url")) { mHomepageUrl = object.getString("homepage_url"); } if (object.has("cuisine")) { JSONArray jsonCuisine = object.getJSONArray("cuisine"); for (int i = 0; i < jsonCuisine.length(); i++) { mCuisine.add(jsonCuisine.getString(i)); } } } public String getDescriptionHtml() { return mDescriptionHtml; } public String getMenuHtml() { return mMenuHtml; } public String getMenuUrl() { return mMenuUrl; } public String getHomePageUrl() { return mHomepageUrl; } public String getCuisineString() { String cuisineString = ""; boolean first = true; for (String cuisine : mCuisine) { if (!first) { cuisineString += ", "; } else { first = false; } cuisineString += cuisine; } return cuisineString; } public List<DailyHours> getDailyHours() { return mHours; } @Override public String getTodaysHoursSummary(long currentTime) { Calendar currentDate = new GregorianCalendar(); currentDate.setTimeInMillis(currentTime); for (DailyHours hours: mHours) { if (compareDates(hours.mDay, currentDate) == 0) { if (hours.mMessage != null) { return hours.mMessage; } else { return scheduleSummary(hours.mStartTime, hours.mEndTime); } } } return null; } private static boolean isToday(Calendar currentDate, Calendar predicateDate) { return (compareDates(currentDate, predicateDate) == 0); } private static boolean isYesterday(Calendar currentDate, Calendar predicateDate) { GregorianCalendar previousDate = new GregorianCalendar(); previousDate.setTime(currentDate.getTime()); previousDate.add(Calendar.DATE, -1); String previousDay = sFormat.format(previousDate.getTime()); String predicateDay = sFormat.format(predicateDate.getTime()); return previousDay.equals(predicateDay); } @Override public Status getCurrentStatus(long currentTime) { Calendar currentDate = new GregorianCalendar(); currentDate.setTimeInMillis(currentTime); for (DailyHours hours: mHours) { if (isToday(currentDate, hours.mDay) || isYesterday(currentDate, hours.mDay)) { if (hours.mStartTime != null && hours.mEndTime != null) { if (currentTime >= hours.mStartTime.getTimeInMillis()) { if (currentTime <= hours.mEndTime.getTimeInMillis()) { return Status.OPEN; } } } } } return Status.CLOSED; } @Override public String getCurrentStatusSummary(long currentTime) { Calendar currentDate = new GregorianCalendar(); currentDate.setTimeInMillis(currentTime); for (DailyHours hours: mHours) { if (isYesterday(currentDate, hours.mDay)) { if (hours.mStartTime != null && hours.mEndTime != null) { if (currentTime >= hours.mStartTime.getTimeInMillis()) { if (currentTime <= hours.mEndTime.getTimeInMillis()) { return openUntil(hours.mEndTime); } } } } if (isToday(currentDate, hours.mDay)) { if (hours.mStartTime != null && hours.mEndTime != null) { if (currentTime >= hours.mStartTime.getTimeInMillis()) { if (currentTime <= hours.mEndTime.getTimeInMillis()) { return openUntil(hours.mEndTime); } } else { return opensAt(hours.mStartTime); } } } } return "Closed for the day"; } } public static class DailyMealsSchedule { private List<DailyMeals> mDailyMealsList; public DailyMealsSchedule(List<DailyMeals> dailyMealsList) { mDailyMealsList = dailyMealsList; } public DailyMeals getDailyMeals(Calendar day) { String dayString = sFormat.format(day.getTime()); for (DailyMeals dailyMeals : mDailyMealsList) { String dailyMealsDayString = sFormat.format(dailyMeals.mDay.getTime()); if (dailyMealsDayString.equals(dayString)) { return dailyMeals; } } return null; } public List<DailyMeals> getDailyMealsForCurrentWeek(Calendar day) { int dayOfWeek = (day.get(Calendar.DAY_OF_WEEK) - 2 + 7) % 7; //monday should be zero long millisInDay = 60 * 60 * 24 * 1000; long millisFromStartOfWeek = dayOfWeek * millisInDay; //number of millis in day times day of week long millisToEndOfWeek = (7 - dayOfWeek) * millisInDay; GregorianCalendar startOfWeek = new GregorianCalendar(); GregorianCalendar endOfWeek = new GregorianCalendar(); startOfWeek.setTimeInMillis(day.getTimeInMillis() - millisFromStartOfWeek); endOfWeek.setTimeInMillis(day.getTimeInMillis() + millisToEndOfWeek); ArrayList<DailyMeals> mealsInWeek = new ArrayList<DailyMeals>(); for (DailyMeals mealDay : mDailyMealsList) { if ( compareDates(mealDay.getDay(), startOfWeek) >= 0 && // compare startOfWeek compareDates(mealDay.getDay(), endOfWeek) <= 0 ) { // compare endOfWeek // if within this week add to weekly list mealsInWeek.add(mealDay); } } return mealsInWeek; } public boolean isBeforeAllDays(Calendar day) { if (mDailyMealsList.size() > 0) { Calendar firstDay = mDailyMealsList.get(0).mDay; return (compareDates(firstDay, day) > 0); } else { return true; } } public boolean isAfterAllDays(Calendar day) { if (mDailyMealsList.size() > 0) { Calendar lastDay = mDailyMealsList.get(mDailyMealsList.size()-1).mDay; return (compareDates(lastDay, day) < 0); } else { return true; } } } public static class DailyMeals { Calendar mDay; String mMessage; HashMap<String, Meal> mMeals = new HashMap<String, Meal>(); static String[] sMealNames = new String[] {"breakfast", "brunch", "lunch", "dinner"}; public DailyMeals(JSONObject object) throws JSONException { String date = object.getString("date"); mDay = getCalendarDate(date, null); if (!object.isNull("message")) { mMessage = object.getString("message"); } if (!object.isNull("meals")) { JSONArray jsonMeals = object.getJSONArray("meals"); for (int i = 0; i < jsonMeals.length(); i++) { Meal meal = new Meal(jsonMeals.getJSONObject(i), date); mMeals.put(meal.getName(), meal); } } } public String getMessage() { return mMessage; } public Calendar getDay() { return mDay; } public int getMealCount() { if (mMeals != null) { return mMeals.size(); } return 0; } public Meal getMeal(String name) { return mMeals.get(name); } public HashMap<String, String> getMealTimes() { // return HashMap of format <Meal Name, Times Summary> both of type String. HashMap<String, String> schedule = new HashMap<String, String>(); Set<String> mealKeys = mMeals.keySet(); for (String key : mealKeys) { Meal meal = mMeals.get(key); if (meal.getMessage() != null && !meal.getMessage().isEmpty()) { schedule.put(meal.getCapitalizedName(), meal.getMessage()); // use message if we have it } else { schedule.put(meal.getCapitalizedName(), meal.getScheduleSummary()); } } return schedule; } public String getScheduleSummary() { String summaryString = ""; boolean first = true; for (String key : sMealNames) { if (mMeals.containsKey(key)) { Meal meal = mMeals.get(key); if (meal.getScheduleSummary() != null) { if (!first) { summaryString += ", "; } first = false; summaryString += meal.getScheduleSummary(); } } } return summaryString; } private static int getMealIndex(String name) { for(int i = 0; i < sMealNames.length; i++) { if (sMealNames[i].equals(name)) { return i; } } throw new RuntimeException("'" + name + "' is not a valid meal name"); } public static String getPreviousMealName(String name) { if (name != null) { int index = getMealIndex(name) - 1; if (index >= 0) { return sMealNames[index]; } } return null; } public static String getNextMealName(String name) { if (name != null) { int index = getMealIndex(name) + 1; if (index < sMealNames.length) { return sMealNames[index]; } } return null; } public static String getFirstMealName() { return sMealNames[0]; } public static String getLastMealName() { return sMealNames[sMealNames.length-1]; } } public static class Meal { String mName; String mMessage; Calendar mStart; Calendar mEnd; ArrayList<MenuItem> mMenuItems = new ArrayList<MenuItem>(); public Meal(JSONObject object, String date) throws JSONException { mName = object.getString("name").toLowerCase(Locale.US); if (!object.isNull("message")) { mMessage = object.getString("message"); } if (!object.isNull("start_time") && !object.isNull("end_time")) { mStart = getCalendarDate(date, object.getString("start_time")); mEnd = getCalendarDate(date, object.getString("end_time")); } if (!object.isNull("items")) { mMenuItems = new ArrayList<MenuItem>(); JSONArray jsonItems = object.getJSONArray("items"); for (int i = 0; i < jsonItems.length(); i++) { mMenuItems.add(new MenuItem(jsonItems.getJSONObject(i))); } } } public String getCapitalizedName() { return mName.substring(0, 1).toUpperCase(Locale.US) + mName.substring(1); } public String getName() { return mName; } public String getMessage() { return mMessage; } public Calendar getStart() { return mStart; } public Calendar getEnd() { return mEnd; } public String getScheduleSummaryForColumns() { if (mStart != null && mEnd != null) { return sHourMinuteFormat.format(mStart.getTime()) + " - " + sHourMinuteFormat.format(mEnd.getTime()) + " " + sAmPmFormat.format(mEnd.getTime()).toLowerCase(Locale.US); } else { return null; } } public String getScheduleSummary() { return scheduleSummary(mStart, mEnd); } public List<MenuItem> getMenuItems() { return mMenuItems; } public boolean isFinished(Calendar day) { if (mEnd != null) { return (day.getTimeInMillis() > mEnd.getTimeInMillis()); } return false; } public boolean isUpcoming(Calendar day) { if (mStart != null) { return (day.getTimeInMillis() < mStart.getTimeInMillis()); } return false; } public boolean isInProgress(Calendar day) { if (mStart != null && mEnd != null) { return (day.getTimeInMillis() >= mStart.getTimeInMillis()) && (day.getTimeInMillis() <= mEnd.getTimeInMillis()); } return false; } } public static class MenuItem { String mName; String mDescription; String mStation; ArrayList<DiningDietaryFlag> mDietaryFlags = new ArrayList<DiningDietaryFlag>(); public MenuItem(JSONObject object) throws JSONException { mName = object.getString("name"); mDescription = object.optString("description", null); mStation = object.getString("station"); if (object.has("dietary_flags")) { JSONArray flags = object.getJSONArray("dietary_flags"); for (int i = 0; i < flags.length(); i++) { mDietaryFlags.add(DiningDietaryFlag.flagsByName().get(flags.getString(i))); } } } public String getStation() { return mStation; } public String getName() { return mName; } public String getDescription() { return mDescription; } public List<DiningDietaryFlag> getDietaryFlags() { return mDietaryFlags; } } public static class DiningHallLocation extends MapItem { DiningHall mDiningHall; // required String mStreet; String mCity; String mState; String mZipcode; // optional String mDescription; String mRoomNumber; float mLatitude; float mLongitude; public DiningHallLocation(DiningHall hall, JSONObject object) throws JSONException { mDiningHall = hall; if (object.has("string")) { mStreet = object.getString("street"); } if (object.has("city")) { mCity = object.getString("city"); } if (object.has("state")) { mState = object.getString("state"); } if (object.has("zipcode")) { mZipcode = object.getString("zipcode"); } if (object.has("description")) { mDescription = object.getString("description"); } if (object.has("mit_room_number")) { mRoomNumber = object.getString("mit_room_number"); } if (object.has("latitude") && object.has("longitude")) { mLatitude = (float) object.getDouble("latitude"); mLongitude = (float) object.getDouble("longitude"); getMapPoints().add(new MapPoint(mLatitude, mLongitude)); } } public String getBuildingNumber() { if (mRoomNumber != null) { if (mRoomNumber.matches("^(N|NW|NE|W|WW|E)?(\\d+).*")) { String[] parts = mRoomNumber.split("\\-"); return parts[0]; } } return null; } @Override public View getCallout(Context context) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View row = inflater.inflate(R.layout.dining_hall_row, null); View iconWrapper = row.findViewById(R.id.diningHallRowImageWrapper); iconWrapper.setVisibility(View.GONE); TextView titleView = (TextView) row.findViewById(R.id.diningHallRowTitle); TextView subtitleView = (TextView) row.findViewById(R.id.diningHallRowSubtitle); TextView statusView = (TextView) row.findViewById(R.id.diningHallRowStatus); long currentTime = DiningModel.currentTimeMillis(); titleView.setText(mDiningHall.getName()); subtitleView.setText(mDiningHall.getTodaysHoursSummary(currentTime)); switch (mDiningHall.getCurrentStatus(currentTime)) { case OPEN: statusView.setText("Open"); statusView.setTextColor(context.getResources().getColor(R.color.dining_open)); break; case CLOSED: statusView.setText("Closed"); statusView.setTextColor(context.getResources().getColor(R.color.dining_closed)); break; } final Context rowContext = context; row.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mDiningHall instanceof HouseDiningHall) { DiningScheduleActivity.launch(rowContext, mDiningHall); } else { DiningRetailInfoActivity.launch(rowContext, mDiningHall); } } }); FrameLayout rowWrapper = new FrameLayout(context); int bubbleFixedWidth = AttributesParser.parseDimension("250dip", context); rowWrapper.addView(row, new FrameLayout.LayoutParams(bubbleFixedWidth, LayoutParams.WRAP_CONTENT)); return rowWrapper; } @Override public View getCallout(Context context, ArrayList<? extends MapItem> mapItems) { return null; } @Override public View getCallout(Context context, ArrayList<? extends MapItem> mapItems, int position) { DiningHallLocation item = (DiningHallLocation) mapItems.get(position); return item.getCallout(context); } @Override public View getCallout(Context context, MapItem mapItem) { DiningHallLocation item = (DiningHallLocation) mapItem; return item.getCallout(context); } } public static class DiningDietaryFlag implements Parcelable, Comparable<DiningDietaryFlag> { private static String FILTER_PREFERENCE_KEY = "filter.preference"; private static HashMap<String, DiningDietaryFlag> sFlagsMap; private String mName; private String mDisplayName; private int mIconId; public static Collection<DiningDietaryFlag> allFlags() { return flagsByName().values(); } public static HashMap<String, DiningDietaryFlag> flagsByName() { if (sFlagsMap == null) { HashMap<String, DiningDietaryFlag> map = new HashMap<String, DiningDietaryFlag>(); map.put("farm to fork", new DiningDietaryFlag("farm to fork", "Farm to Fork", R.drawable.dining_farm_to_fork)); map.put("organic", new DiningDietaryFlag("organic", "Organic", R.drawable.dining_organic)); map.put("seafood watch", new DiningDietaryFlag("seafood watch", "Seafood Watch", R.drawable.dining_seafood_watch)); map.put("vegan", new DiningDietaryFlag("vegan", "Vegan", R.drawable.dining_vegan)); map.put("vegetarian", new DiningDietaryFlag("vegetarian", "Vegetarian", R.drawable.dining_vegetarian)); map.put("for your well-being", new DiningDietaryFlag("for your well-being", "For Your Well-Being", R.drawable.dining_well_being)); map.put("made without gluten", new DiningDietaryFlag("made without gluten", "Made Without Gluten", R.drawable.dining_made_without_gluten)); map.put("halal", new DiningDietaryFlag("halal", "Halal", R.drawable.dining_halal)); map.put("kosher", new DiningDietaryFlag("kosher", "Kosher", R.drawable.dining_kosher)); map.put("humane", new DiningDietaryFlag("humane", "Humane", R.drawable.dining_humane)); map.put("in balance", new DiningDietaryFlag("in balance", "In Balance", R.drawable.dining_in_balance)); sFlagsMap = map; } return sFlagsMap; } public static void saveFilters(Context context, List<DiningDietaryFlag> list) { // serialize flag names into comma separated string, save string String nameString = ""; for (DiningDietaryFlag flag : list) { if (list.indexOf(flag) == list.size() - 1) { nameString+= flag.getName(); } else { nameString+= flag.getName() + ","; } } SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = prefs.edit(); editor.putString(FILTER_PREFERENCE_KEY, nameString); editor.apply(); } public static List<DiningDietaryFlag> loadFilters(Context context) { // load string from preferences, unserialize string into flag names, lookup flags from static map // returns list of flags SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String nameString = prefs.getString(FILTER_PREFERENCE_KEY, ""); String [] nameSet = nameString.split(","); ArrayList<DiningDietaryFlag> flagList = new ArrayList<DiningDietaryFlag>(); for (String name : nameSet) { DiningDietaryFlag flag = DiningDietaryFlag.flagsByName().get(name); if (flag != null) flagList.add(flag); } return flagList; } private DiningDietaryFlag(String name, String display, int resource) { mName = name; mDisplayName= display; mIconId = resource; } @Override public String toString() { return "DiningDietaryFlag:" + this.hashCode() + " name: " + mName; } @Override public boolean equals(Object obj) { if (obj instanceof DiningDietaryFlag) { DiningDietaryFlag o = (DiningDietaryFlag)obj; if (this.getName().equals(o.getName())) { return true; } } return false; } public String getName() { return mName; } public String getDisplayName() { return mDisplayName; } public int getIconId() { return mIconId; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mName); dest.writeString(mDisplayName); dest.writeInt(mIconId); } public static final Parcelable.Creator<DiningDietaryFlag> CREATOR = new Parcelable.Creator<DiningDietaryFlag>() { @Override public DiningDietaryFlag createFromParcel(Parcel in) { return new DiningDietaryFlag(in.readString(), in.readString(), in.readInt()); } @Override public DiningDietaryFlag[] newArray(int size) { return new DiningDietaryFlag[size]; } }; @Override public int compareTo(DiningDietaryFlag another) { return this.getName().compareTo(another.getName()); } public static Comparator<DiningDietaryFlag> NameDescendingComparator = new Comparator<DiningDietaryFlag>() { @Override public int compare(DiningDietaryFlag flag1, DiningDietaryFlag flag2) { String name1 = flag1.getName().toUpperCase(); String name2 = flag2.getName().toUpperCase(); return name2.compareTo(name1); } }; } public static class DiningLink { private String mTitle; //private String mSubtitle; private String mUrl; public DiningLink(String title, String url) { mTitle = title; mUrl = url; } public String getTitle() { return mTitle; } public String getUrl() { return mUrl; } } /* * Some convenience methods for handling dates */ static SimpleDateFormat sFormat = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); static SimpleDateFormat sHourMinuteFormat = new SimpleDateFormat("h:mm", Locale.US); static SimpleDateFormat sAmPmFormat = new SimpleDateFormat("a", Locale.US); /* * Compare only the date (ignore hours/minutes/seconds etc...) */ static long compareDates(Calendar a, Calendar b) { String aString = sFormat.format(a.getTime()); String bString = sFormat.format(b.getTime()); if (aString.equals(bString)) { return 0; } return a.getTimeInMillis() - b.getTimeInMillis(); } static String formatTimeForScheduleSpan(Calendar cal) { SimpleDateFormat shortTimeFormat = new SimpleDateFormat("h", Locale.US); SimpleDateFormat df = null; if (cal.get(Calendar.MINUTE) == 0) { df = shortTimeFormat; } else { df = sHourMinuteFormat; } String timeString = df.format(cal.getTime()) + sAmPmFormat.format(cal.getTime()).toLowerCase(); return timeString; } static String scheduleSummary(Calendar start, Calendar end) { if (start != null && end != null) { return formatTimeForScheduleSpan(start) + " - " + formatTimeForScheduleSpan(end); } else { return null; } } static String openUntil(Calendar end) { return "Open until " + sHourMinuteFormat.format(end.getTime()) + sAmPmFormat.format(end.getTime()).toLowerCase(Locale.US); } static String opensAt(Calendar start) { return "Opens at " + sHourMinuteFormat.format(start.getTime()) + sAmPmFormat.format(start.getTime()).toLowerCase(Locale.US); } static long currentTimeMillis() { if (!sUseTestData) { return System.currentTimeMillis(); } else { long currentTime = 1367351565000L; return currentTime; } } }