protected Rules defaultRules( PortionSizeScriptManager scriptManager, CompoundFoodTemplateManager templateManager, RecipeManager recipeManager) { return new Rules( // meal prompts TreePVector.<WithPriority<PromptRule<Meal, MealOperation>>>empty() .plus(AskForMealTime.withPriority(3)) .plus(ShowEditMeal.withPriority(2)) .plus(ShowDrinkReminderPrompt.withPriority(1)) .plus(ShowReadyMealsPrompt.withPriority(0)), // food prompts TreePVector.<WithPriority<PromptRule<FoodEntry, FoodOperation>>>empty() .plus(ShowBrandNamePrompt.withPriority(-1)) .plus(ShowNextPortionSizeStep.withPriority(scriptManager, 0)) .plus(ChoosePortionSizeMethod.withPriority(1)) .plus(AskForMissingFoodDescription.withPriority(2)) .plus(ShowSimpleHomeRecipePrompt.withPriority(2)) .plus(AskIfHomeRecipe.withPriority(3)) .plus(SplitFood.withPriority(4)) .plus(InformFoodComplete.withPriority(-100)), // extended food propmts TreePVector.<WithPriority<PromptRule<Pair<FoodEntry, Meal>, MealOperation>>>empty() .plus(ShowEditIngredientsPrompt.withPriority(3)) .plus(AskToLookupFood.withPriority(3, locale, recipeManager)) .plus( ShowSameAsBeforePrompt.withPriority( 3, getSchemeId(), getDataVersion(), scriptManager, templateManager)) .plus(ShowHomeRecipeServingsPrompt.withPriority(2)) .plus(ShowTemplateRecipeSavePrompt.withPriority(1, recipeManager)) .plus(ShowCompoundFoodPrompt.withPriority(0, locale)) .plus(ShowAssociatedFoodPrompt.withPriority(0, locale)), // global prompts TreePVector.<WithPriority<PromptRule<Survey, SurveyOperation>>>empty() .plus(ConfirmCompletion.withPriority(0)) .plus(ShowEnergyValidationPrompt.withPriority(1, 500.0)) .plus(ShowEmptySurveyPrompt.withPriority(1)) .plus(ShowTimeGapPrompt.withPriority(2, 180, new Time(9, 0), new Time(21, 0))), // selection rules TreePVector.<WithPriority<SelectionRule>>empty() .plus(SelectForPortionSize.withPriority(3)) .plus(SelectRawFood.withPriority(2)) .plus(SelectFoodForAssociatedPrompts.withPriority(1)) .plus(SelectIncompleteFreeEntryMeal.withPriority(1)) .plus(SelectMealWithNoDrink.withPriority(1)) .plus(SelectUnconfirmedMeal.withPriority(1)) .plus(SelectMealForReadyMeals.withPriority(1))); }
public class CompoundFoodPrompt implements Prompt<Pair<FoodEntry, Meal>, MealOperation> { private static final PromptMessages messages = PromptMessages.Util.getInstance(); private static final HelpMessages helpMessages = HelpMessages.Util.getInstance(); private static final PVector<ShepherdTour.Step> tour = TreePVector.<ShepherdTour.Step>empty() .plus( new ShepherdTour.Step( "prompt", "#intake24-compound-food-prompt", helpMessages.compoundFood_promptTitle(), helpMessages.compoundFood_promptDescription()));; private final Meal meal; private final int foodIndex; private final int componentIndex; private final boolean allowSkip; private final String locale; private Panel buttonsPanel; private boolean isFirst; private static Logger log = Logger.getLogger(CompoundFoodPrompt.class.getSimpleName()); private FoodBrowser foodBrowser; public CompoundFoodPrompt( final String locale, final Meal meal, final int foodIndex, final int componentIndex, boolean isFirst, boolean allowSkip) { this.locale = locale; this.meal = meal; this.foodIndex = foodIndex; this.componentIndex = componentIndex; this.isFirst = isFirst; this.allowSkip = allowSkip; } @Override public SurveyStageInterface getInterface( final Callback1<MealOperation> onComplete, Callback1<Function1<Pair<FoodEntry, Meal>, Pair<FoodEntry, Meal>>> updateIntermediateState) { final TemplateFood food = (TemplateFood) meal.foods.get(foodIndex); final ComponentDef component = food.data.template.get(componentIndex); final FlowPanel content = new FlowPanel(); PromptUtil.addBackLink(content); component.headerConstructor.accept( new Option.SideEffectVisitor<Function0<Widget>>() { @Override public void visitSome(Function0<Widget> item) { Widget header = item.apply(); ShepherdTour.makeShepherdTarget(header); content.add(header); } @Override public void visitNone() {} }); SafeHtml promptText; if (isFirst) promptText = SafeHtmlUtils.fromSafeConstant(component.primaryInstancePrompt); else promptText = SafeHtmlUtils.fromSafeConstant(component.secondaryInstancePrompt); final Panel promptPanel = WidgetFactory.createPromptPanel( promptText, WidgetFactory.createHelpButton( new ClickHandler() { @Override public void onClick(ClickEvent arg0) { String promptType = CompoundFoodPrompt.class.getSimpleName() + "." + food.data.template_id; GoogleAnalytics.trackHelpButtonClicked(promptType); ShepherdTour.startTour( tour.plusAll(foodBrowser.getShepherdTourSteps()), promptType); } })); content.add(promptPanel); ShepherdTour.makeShepherdTarget(promptPanel); promptPanel.getElement().setId("intake24-compound-food-prompt"); String skipLabel; if (isFirst) skipLabel = component.primarySkipButtonLabel; else skipLabel = component.secondarySkipButtonLabel; Option<SkipFoodHandler> skipHandler = allowSkip ? Option.some( new SkipFoodHandler( skipLabel, new Callback() { @Override public void call() { onComplete.call( MealOperation.updateTemplateFood( foodIndex, new Function1<TemplateFood, TemplateFood>() { @Override public TemplateFood apply(TemplateFood argument) { return argument.markComponentComplete(componentIndex); } })); } })) : Option.<SkipFoodHandler>none(); foodBrowser = new FoodBrowser( locale, new Callback1<FoodData>() { @Override public void call(final FoodData result) { onComplete.call( MealOperation.update( new Function1<Meal, Meal>() { @Override public Meal apply(Meal argument) { EncodedFood componentFood = new EncodedFood( result, FoodLink.newLinked(food.link.id), "compound food template"); return argument .plusFood(componentFood) .updateFood( foodIndex, food.addComponent(componentIndex, componentFood.link.id)); } })); } }, new Callback1<String>() { @Override public void call(String code) { throw new RuntimeException( "Special foods are not allowed as compound food ingredients"); } }, new Callback() { @Override public void call() { onComplete.call( MealOperation.update( new Function1<Meal, Meal>() { @Override public Meal apply(Meal argument) { MissingFood missingFood = new MissingFood( FoodLink.newLinked(food.link.id), component.name, false) .withFlag(MissingFood.NOT_HOME_RECIPE_FLAG); return argument .plusFood(missingFood) .updateFood( foodIndex, food.addComponent(componentIndex, missingFood.link.id)); } })); } }, skipHandler, false, Option.<Pair<String, String>>none()); foodBrowser.browse( component.categoryCode, component.dataSetLabel, component.foodsLabel, component.categoriesLabel); content.add(foodBrowser); return new SurveyStageInterface.Aligned( content, HasHorizontalAlignment.ALIGN_LEFT, HasVerticalAlignment.ALIGN_TOP, SurveyStageInterface.DEFAULT_OPTIONS); } @Override public String toString() { return "Compound food prompt"; } }
public class DrinkScalePrompt implements SimplePrompt<Double> { private static final PromptMessages messages = PromptMessages.Util.getInstance(); private static final HelpMessages helpMessages = HelpMessages.Util.getInstance(); private static final PVector<ShepherdTour.Step> tour = TreePVector.<ShepherdTour.Step>empty() .plus( new ShepherdTour.Step( "image", "#intake24-sliding-scale-image", helpMessages.drinkScale_imageTitle(), helpMessages.drinkScale_imageDescription())) .plus( new ShepherdTour.Step( "overlay", "#intake24-sliding-scale-overlay", helpMessages.drinkScale_overlayTitle(), helpMessages.drinkScale_overlayDescription())) .plus( new ShepherdTour.Step( "label", "#intake24-sliding-scale-overlay", helpMessages.drinkScale_volumeLabelTitle(), helpMessages.drinkScale_volumeLabelDescription(), "top right", "bottom right")) .plus( new ShepherdTour.Step( "slider", "#intake24-sliding-scale-slider", helpMessages.drinkScale_sliderTitle(), helpMessages.drinkScale_sliderDescription(), "middle right", "middle left")) .plus( new ShepherdTour.Step( "lessButton", "#intake24-sliding-scale-less-button", helpMessages.drinkScale_lessButtonTitle(), helpMessages.drinkScale_lessButtonDescription())) .plus( new ShepherdTour.Step( "moreButton", "#intake24-sliding-scale-more-button", helpMessages.drinkScale_moreButtonTitle(), helpMessages.drinkScale_moreButtonDescription())) .plus( new ShepherdTour.Step( "continueButton", "#intake24-sliding-scale-continue-button", helpMessages.drinkScale_continueButtonTitle(), helpMessages.drinkScale_continueButtonDescription(), "top right", "bottom right")); private final DrinkScalePromptDef def; public DrinkScalePrompt(DrinkScalePromptDef def) { this.def = def; } @Override public FlowPanel getInterface(final Callback1<Double> onComplete) { FlowPanel content = new FlowPanel(); FlowPanel promptPanel = WidgetFactory.createPromptPanel( def.message, ShepherdTour.createTourButton(tour, DrinkScalePrompt.class.getSimpleName())); content.add(promptPanel); SlidingScaleDef ssd = new SlidingScaleDef( def.scaleDef.baseImage, def.scaleDef.overlayImage, def.scaleDef.width, def.scaleDef.height, def.scaleDef.emptyLevel, def.scaleDef.fullLevel); final Function1<Double, String> label = new Function1<Double, String>() { @Override public String apply(Double argument) { double volume = def.scaleDef.calcVolume(argument); int roundedVolume = (int) volume; NumberFormat nf = NumberFormat.getDecimalFormat(); return nf.format(roundedVolume) + " " + messages.drinkScale_volumeUnit(); } }; final SlidingScale scale = new SlidingScale(ssd, def.limit, def.initialLevel, label); content.add(scale); final Button less = WidgetFactory.createButton( def.lessLabel, new ClickHandler() { @Override public void onClick(ClickEvent event) { scale.sliderBar.setValue(scale.sliderBar.getValue() + scale.sliderBar.getStep()); /*if (scale.sliderBar.getValue() > 0.99) less.setEnabled(false); else less.setEnabled(true);*/ } }); less.getElement().setId("intake24-sliding-scale-less-button"); final Button more = WidgetFactory.createButton( def.moreLabel, new ClickHandler() { @Override public void onClick(ClickEvent event) { scale.sliderBar.setValue(scale.sliderBar.getValue() - scale.sliderBar.getStep()); /*if (scale.sliderBar.getValue() < 0.01) more.setEnabled(false); else more.setEnabled(true);*/ } }); more.getElement().setId("intake24-sliding-scale-more-button"); final Button finish = WidgetFactory.createGreenButton( def.acceptLabel, new ClickHandler() { @Override public void onClick(ClickEvent event) { onComplete.call(scale.getValue()); } }); finish.getElement().setId("intake24-sliding-scale-continue-button"); content.add(WidgetFactory.createButtonsPanel(less, more, finish)); ShepherdTour.makeShepherdTarget( promptPanel, scale.image, scale.overlayDiv, scale.sliderBar, less, more, finish); return content; } }
public class AsServedPrompt implements SimplePrompt<Integer> { private final AsServedPromptDef def; public AsServedPrompt(AsServedPromptDef def) { this.def = def; } private static final HelpMessages helpMessages = HelpMessages.Util.getInstance(); private static final PVector<ShepherdTour.Step> tour = TreePVector.<ShepherdTour.Step>empty() .plus( new ShepherdTour.Step( "image", "#intake24-as-served-image-container", helpMessages.asServed_imageTitle(), helpMessages.asServed_imageDescription())) .plus( new ShepherdTour.Step( "label", "#intake24-as-served-image-container", helpMessages.asServed_labelTitle(), helpMessages.asServed_labelDescription(), "top right", "bottom right")) .plus( new ShepherdTour.Step( "thumbs", "#intake24-as-served-thumbs-container", helpMessages.asServed_thumbsTitle(), helpMessages.asServed_thumbsDescription())) .plus( new ShepherdTour.Step( "hadLess", "#intake24-as-served-prev-button", helpMessages.asServed_prevButtonTitle(), helpMessages.asServed_prevButtonDescription(), "bottom left", "top left")) .plus( new ShepherdTour.Step( "hadMore", "#intake24-as-served-next-button", helpMessages.asServed_nextButtonTitle(), helpMessages.asServed_nextButtonDescription(), "bottom left", "top left")) .plus( new ShepherdTour.Step( "hadThisMuch", "#intake24-as-served-confirm-button", helpMessages.asServed_confirmButtonTitle(), helpMessages.asServed_confirmButtonDescription(), "bottom right", "top right")); @Override public FlowPanel getInterface(final Callback1<Integer> onComplete) { final FlowPanel content = new FlowPanel(); FlowPanel promptPanel = WidgetFactory.createPromptPanel( def.promptText, ShepherdTour.createTourButton(tour, AsServedPrompt.class.getSimpleName())); ShepherdTour.makeShepherdTarget(promptPanel); content.add(promptPanel); ImageChooser imageChooser = new ImageChooser( def.images, def.prevLabel, def.nextLabel, def.acceptLabel, def.images.length / 2, new ImageChooser.ResultHandler() { @Override public void handleResult(final int index) { onComplete.call(index); }; }); content.add(imageChooser); ShepherdTour.makeShepherdTarget( imageChooser.imageContainer, imageChooser.thumbsContainer, imageChooser.nextButton, imageChooser.prevButton, imageChooser.confirmButton); return content; } @Override public String toString() { return "As served portion size prompt"; } }
/** * Basic implementation of a survey scheme. Uses the default prompt rules (see <b>defaultRules</b>), * and starting meals (see <b>startingSurveyData</b>). */ public abstract class BasicScheme implements SurveyScheme { static final double MAX_AGE_HOURS = 8.0; protected final SurveyInterfaceManager interfaceManager; protected final StateManager stateManager; protected final PromptManager defaultPromptManager; protected final SelectionManager defaultSelectionManager; protected final PortionSizeScriptManager defaultScriptManager; protected final CompoundFoodTemplateManager defaultTemplateManager; protected final RecipeManager recipeManager; protected final LogRecorder log; protected final String locale; protected final TreePVector<Function1<Survey, Survey>> basicPostProcess = TreePVector.<Function1<Survey, Survey>>empty().plus(new ProcessMilkInHotDrinks()); private static Logger logger = Logger.getLogger("BasicScheme"); protected Survey startingSurveyData() { return new Survey( PredefinedMeals.getStartingMealsForCurrentLocale(), new Selection.EmptySelection(SelectionMode.AUTO_SELECTION), System.currentTimeMillis(), HashTreePSet.<String>empty(), HashTreePMap.<String, String>empty()); } protected Survey postProcess(Survey data, PVector<Function1<Survey, Survey>> functions) { Survey postProcessed = data; for (Function1<Survey, Survey> f : functions) postProcessed = f.apply(postProcessed); return postProcessed; } protected Rules defaultRules( PortionSizeScriptManager scriptManager, CompoundFoodTemplateManager templateManager, RecipeManager recipeManager) { return new Rules( // meal prompts TreePVector.<WithPriority<PromptRule<Meal, MealOperation>>>empty() .plus(AskForMealTime.withPriority(3)) .plus(ShowEditMeal.withPriority(2)) .plus(ShowDrinkReminderPrompt.withPriority(1)) .plus(ShowReadyMealsPrompt.withPriority(0)), // food prompts TreePVector.<WithPriority<PromptRule<FoodEntry, FoodOperation>>>empty() .plus(ShowBrandNamePrompt.withPriority(-1)) .plus(ShowNextPortionSizeStep.withPriority(scriptManager, 0)) .plus(ChoosePortionSizeMethod.withPriority(1)) .plus(AskForMissingFoodDescription.withPriority(2)) .plus(ShowSimpleHomeRecipePrompt.withPriority(2)) .plus(AskIfHomeRecipe.withPriority(3)) .plus(SplitFood.withPriority(4)) .plus(InformFoodComplete.withPriority(-100)), // extended food propmts TreePVector.<WithPriority<PromptRule<Pair<FoodEntry, Meal>, MealOperation>>>empty() .plus(ShowEditIngredientsPrompt.withPriority(3)) .plus(AskToLookupFood.withPriority(3, locale, recipeManager)) .plus( ShowSameAsBeforePrompt.withPriority( 3, getSchemeId(), getDataVersion(), scriptManager, templateManager)) .plus(ShowHomeRecipeServingsPrompt.withPriority(2)) .plus(ShowTemplateRecipeSavePrompt.withPriority(1, recipeManager)) .plus(ShowCompoundFoodPrompt.withPriority(0, locale)) .plus(ShowAssociatedFoodPrompt.withPriority(0, locale)), // global prompts TreePVector.<WithPriority<PromptRule<Survey, SurveyOperation>>>empty() .plus(ConfirmCompletion.withPriority(0)) .plus(ShowEnergyValidationPrompt.withPriority(1, 500.0)) .plus(ShowEmptySurveyPrompt.withPriority(1)) .plus(ShowTimeGapPrompt.withPriority(2, 180, new Time(9, 0), new Time(21, 0))), // selection rules TreePVector.<WithPriority<SelectionRule>>empty() .plus(SelectForPortionSize.withPriority(3)) .plus(SelectRawFood.withPriority(2)) .plus(SelectFoodForAssociatedPrompts.withPriority(1)) .plus(SelectIncompleteFreeEntryMeal.withPriority(1)) .plus(SelectMealWithNoDrink.withPriority(1)) .plus(SelectUnconfirmedMeal.withPriority(1)) .plus(SelectMealForReadyMeals.withPriority(1))); } public BasicScheme(String locale, final SurveyInterfaceManager interfaceManager) { this.log = new LogRecorder(); this.interfaceManager = interfaceManager; this.locale = locale; interfaceManager.setCallbacks( new Callback1<Survey>() { @Override public void call(Survey updatedState) { if (updatedState.flags.contains(Survey.FLAG_SKIP_HISTORY)) stateManager.updateState(updatedState.clearFlag(Survey.FLAG_SKIP_HISTORY), false); else stateManager.updateState(updatedState, true); showNextPage(); } }, new Callback2<Survey, Boolean>() { @Override public void call(Survey updatedState, Boolean makeHistoryEntry) { stateManager.updateState(updatedState, makeHistoryEntry); } }); defaultScriptManager = new PortionSizeScriptManager(DefaultPortionSizeScripts.getCtors()); defaultTemplateManager = new CompoundFoodTemplateManager( HashTreePMap.<String, TemplateFoodData>empty() .plus("sandwich", FoodTemplates.sandwich) .plus("salad", FoodTemplates.salad)); recipeManager = new RecipeManager( getSchemeId(), getDataVersion(), defaultScriptManager, defaultTemplateManager); final Rules rules = defaultRules(defaultScriptManager, defaultTemplateManager, recipeManager); defaultPromptManager = new RuleBasedPromptManager(rules); // final SelectionManager selectionManager = new // RuleBasedSelectionManager(rules.selectionRules); defaultSelectionManager = new PromptAvailabilityBasedSelectionManager(defaultPromptManager); Survey initialState = StateManagerUtil.getLatestState( CurrentUser.getUserInfo().userName, getSchemeId(), getDataVersion(), defaultScriptManager, defaultTemplateManager) .accept( new Option.Visitor<Survey, Survey>() { @Override public Survey visitSome(Survey data) { double age = (System.currentTimeMillis() - data.startTime) / 3600000.0; logger.info("Saved state is " + age + " hours old."); if (age > MAX_AGE_HOURS) { logger.info( "Saved state is older than " + MAX_AGE_HOURS + " hours and has expired."); return startingSurveyData(); } else { return data.clearCompletionConfirmed().clearEnergyValueConfirmed(); } } @Override public Survey visitNone() { // log.info("No saved state, starting new survey."); return startingSurveyData(); } }); stateManager = new StateManager( initialState, getSchemeId(), getDataVersion(), new Callback() { @Override public void call() { showNextPage(); } }, defaultScriptManager); } @Override public abstract void showNextPage(); @Override public abstract String getDataVersion(); @Override public abstract String getSchemeId(); @Override public List<Anchor> navBarLinks() { return Collections.emptyList(); } }