From 114ef838472a1280b5e6eeae52ecdb615f581557 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Thu, 1 Aug 2019 23:39:35 +0200 Subject: [PATCH 01/15] RepeatStep: Avoid exception if no steps --- .../runnerup/workout/EndOfLapSuppression.java | 2 +- .../main/org/runnerup/workout/RepeatStep.java | 55 +- app/src/main/org/runnerup/workout/Step.java | 891 +++++++++--------- .../main/org/runnerup/workout/Workout.java | 20 +- 4 files changed, 495 insertions(+), 473 deletions(-) diff --git a/app/src/main/org/runnerup/workout/EndOfLapSuppression.java b/app/src/main/org/runnerup/workout/EndOfLapSuppression.java index 859b24813..530c35fd8 100644 --- a/app/src/main/org/runnerup/workout/EndOfLapSuppression.java +++ b/app/src/main/org/runnerup/workout/EndOfLapSuppression.java @@ -119,7 +119,7 @@ private boolean suppressEndOfLap(Trigger trigger, Workout w) { return false; Step s = w.getCurrentStep(); - if (s.getDurationType() == null) + if (s == null || s.getDurationType() == null) return false; switch (s.getDurationType()) { diff --git a/app/src/main/org/runnerup/workout/RepeatStep.java b/app/src/main/org/runnerup/workout/RepeatStep.java index ed573756b..cd646c96d 100644 --- a/app/src/main/org/runnerup/workout/RepeatStep.java +++ b/app/src/main/org/runnerup/workout/RepeatStep.java @@ -24,7 +24,7 @@ public class RepeatStep extends Step { - int repeatCount = 8; + int repeatCount = 0; public ArrayList getSteps() { return steps; @@ -87,7 +87,9 @@ public void onRepeat(int current, int count) { @Override public void onStart(Scope what, Workout s) { - steps.get(currentStep).onStart(what, s); + if (steps.size() > currentStep) { + steps.get(currentStep).onStart(what, s); + } } @Override @@ -102,22 +104,34 @@ public void onPause(Workout s) { @Override public boolean onTick(Workout w) { - return currentRepeat >= repeatCount || steps.get(currentStep).onTick(w); + return currentStep >= steps.size() || currentRepeat >= repeatCount || steps.get(currentStep).onTick(w); } + /** + * Return true when the step cannot be increased within this repeat step + * @param w + * @return + */ @Override public boolean onNextStep(Workout w) { - if (steps.get(currentStep).onNextStep(w)) { - currentStep++; - if (currentStep >= steps.size()) { - currentStep = 0; - currentRepeat++; - if (currentRepeat >= repeatCount) { - return true; - } - for (Step s : steps) { - s.onRepeat(currentRepeat, repeatCount); - } + if (steps.size() <= currentStep) { + // Incorrect handling or repeat 0, move to next + return true; + } + if (!steps.get(currentStep).onNextStep(w)) { + // current step is another repeat step + return false; + } + + currentStep++; + if (currentStep >= steps.size()) { + currentStep = 0; + currentRepeat++; + if (currentRepeat >= repeatCount) { + return true; + } + for (Step s : steps) { + s.onRepeat(currentRepeat, repeatCount); } } return false; @@ -130,7 +144,9 @@ public void onResume(Workout s) { @Override public void onComplete(Scope scope, Workout s) { - steps.get(currentStep).onComplete(scope, s); + if (steps.size() > currentStep) { + steps.get(currentStep).onComplete(scope, s); + } } @Override @@ -182,10 +198,13 @@ public Step getCurrentStep() { @Override public boolean isLastStep() { - if (currentRepeat >= repeatCount) - return true; - if (currentStep + 1 < steps.size()) + if (currentRepeat + 1 < repeatCount) { return false; + } + if (currentStep >= steps.size()) { + return true; + } + return steps.get(currentStep).isLastStep(); } diff --git a/app/src/main/org/runnerup/workout/Step.java b/app/src/main/org/runnerup/workout/Step.java index 3f45ecff1..918917d94 100644 --- a/app/src/main/org/runnerup/workout/Step.java +++ b/app/src/main/org/runnerup/workout/Step.java @@ -1,445 +1,446 @@ -/* - * Copyright (C) 2012 - 2013 jonas.oreland@gmail.com - * - * 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 . - */ - -package org.runnerup.workout; - -import android.content.ContentValues; - -import org.runnerup.BuildConfig; -import org.runnerup.common.util.Constants.DB; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - - -public class Step implements TickComponent { - - private String name = null; - - /** - * Intensity - */ - Intensity intensity = Intensity.ACTIVE; - - /** - * Duration - */ - Dimension durationType = null; - double durationValue = 0; - - /** - * Target - */ - Dimension targetType = null; - Range targetValue = null; - - /** - * Autolap (m) - */ - private double autolap = 0; - - /** - * Triggers - */ - final ArrayList triggers = new ArrayList<>(); - - /** - * @return the name - */ - public String getName() { - return name; - } - - /** - * @param name the name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * @return the durationType - */ - public Dimension getDurationType() { - return durationType; - } - - /** - * @param durationType the durationType to set - */ - public void setDurationType(Dimension durationType) { - this.durationType = durationType; - } - - /** - * @return the durationValue - */ - public double getDurationValue() { - return durationValue; - } - - /** - * @param durationValue the durationValue to set - */ - public void setDurationValue(double durationValue) { - this.durationValue = durationValue; - } - - /** - * @return the targetType - */ - public Dimension getTargetType() { - return targetType; - } - - /** - * @param targetType the targetType to set - */ - public void setTargetType(Dimension targetType) { - this.targetType = targetType; - } - - /** - * @return the targetValue - */ - public Range getTargetValue() { - return targetValue; - } - - /** - * @param targetValue the targetValue to set - */ - public void setTargetValue(double targetValue) { - this.targetValue = new Range(targetValue, targetValue); - } - - public void setTargetValue(double min, double max) { - this.targetValue = new Range(min, max); - } - - public Intensity getIntensity() { - return intensity; - } - public void setIntensity(Intensity intensity) { - this.intensity = intensity; - } - - /** - * @return the autolap - */ - public double getAutolap() { - return autolap; - } - - /** - * @param val the autolap to set - */ - public void setAutolap(double val) { - this.autolap = val; - } - - @Override - public void onInit(Workout s) { - for (Trigger t : triggers) { - t.onInit(s); - } - } - - @Override - public void onBind(Workout s, HashMap bindValues) { - for (Trigger t : triggers) { - t.onBind(s, bindValues); - } - } - - @Override - public void onEnd(Workout s) { - for (Trigger t : triggers) { - t.onEnd(s); - } - } - - public void onRepeat(int current, int count) { - for (Trigger t : triggers) { - t.onRepeat(current, count); - } - } - - private double stepStartTime = 0; - private double stepStartDistance = 0; - private double stepStartHeartbeats = 0; - private double lapStartTime = 0; - private double lapStartDistance = 0; - private double lapStartHeartbeats = 0; - - @Override - public void onStart(Scope what, Workout s) { - double time = s.getTime(Scope.ACTIVITY); - double dist = s.getDistance(Scope.ACTIVITY); - double beats = s.getHeartbeats(Scope.ACTIVITY); - - if (what == Scope.STEP) { - stepStartTime = time; - stepStartDistance = dist; - stepStartHeartbeats = beats; - if (s.isPaused()) - s.tracker.pause(); - else - s.tracker.resume(); - } else if (what == Scope.LAP) { - lapStartTime = time; - lapStartDistance = dist; - lapStartHeartbeats = beats; - ContentValues tmp = new ContentValues(); - tmp.put(DB.LAP.INTENSITY, intensity.getValue()); - if (durationType != null) { - switch (durationType) { - case TIME: - tmp.put(DB.LAP.PLANNED_TIME, (long) durationValue); - break; - case DISTANCE: - tmp.put(DB.LAP.PLANNED_DISTANCE, (long) durationValue); - break; - case PACE: - case SPEED: - case HR: - case HRZ: - case CAD: - case TEMPERATURE: - case PRESSURE: - break; - } - } - if (targetType != null) { - switch (targetType) { - case PACE: - tmp.put(DB.LAP.PLANNED_PACE, targetValue.maxValue); - break; - case SPEED: - if (targetValue.maxValue != 0) { - tmp.put(DB.LAP.PLANNED_PACE, 1.0d / targetValue.maxValue); - } - break; - case DISTANCE: - case TIME: - case HR: - case HRZ: - case CAD: - case TEMPERATURE: - case PRESSURE: - break; - } - } - s.newLap(tmp); - } - - for (Trigger t : triggers) { - t.onStart(what, s); - } - } - - @Override - public void onStop(Workout s) { - s.tracker.stop(); - for (Trigger t : triggers) { - t.onStop(s); - } - - /* - * Save current lap so that it shows in DetailActivity - */ - long distance = Math.round(s.getDistance(Scope.LAP)); - long time = Math.round(s.getTime(Scope.LAP)); - long hr = Math.round(s.getHeartRate(Scope.LAP)); - if (distance > 0 || time > 0) { - ContentValues tmp = new ContentValues(); - tmp.put(DB.LAP.DISTANCE, distance); - tmp.put(DB.LAP.TIME, time); - tmp.put(DB.LAP.AVG_HR, Math.round(hr)); - s.saveLap(tmp, /* next lap */ - false); - } - } - - @Override - public void onPause(Workout s) { - s.tracker.pause(); - for (Trigger t : triggers) { - t.onPause(s); - } - } - - private double mPrevTickLapDistance = 0; - private double mPrevTickLapTime = 0; - /** - * @return true if finished - */ - public boolean onTick(Workout s) { - if (checkFinished(s)) { - return true; - } - - for (Trigger t : triggers) { - t.onTick(s); - } - - if (this.autolap > 0) { - double lapDistance = s.getDistance(Scope.LAP); - double lapTime = s.getTime(Scope.LAP); - if (lapDistance >= this.autolap || - // autolap if this point is closer to the limit then next point - // (assuming the time/speed is is the same to next tick, but this should even out) - mPrevTickLapDistance > 0 && lapTime > mPrevTickLapTime && - (lapDistance + (lapDistance - mPrevTickLapDistance)/2) >= this.autolap) { - s.onNewLap(); - lapDistance = 0; - } - mPrevTickLapDistance = lapDistance; - mPrevTickLapTime = lapTime; - } - return false; - } - - public boolean onNextStep(Workout w) { - return true; // move to next step - } - - private boolean checkFinished(Workout s) { - if (durationType == null) - return false; - - return s.get(Scope.STEP, durationType) >= this.durationValue; - } - - @Override - public void onResume(Workout s) { - for (Trigger t : triggers) { - t.onResume(s); - } - s.tracker.resume(); - } - - @Override - public void onComplete(Scope scope, Workout s) { - if (scope == Scope.LAP) { - double distance = s.getDistance(scope); - long time = Math.round(s.getTime(scope)); - if (distance > 0 || time > 0) { - ContentValues tmp = new ContentValues(); - tmp.put(DB.LAP.DISTANCE, distance); - tmp.put(DB.LAP.TIME, time); - long hr = Math.round(s.getHeartRate(scope)); - tmp.put(DB.LAP.AVG_HR, hr); - s.saveLap(tmp, /* next lap */ - true); - } - } - for (Trigger t : triggers) { - t.onComplete(scope, s); - } - - if (scope == Scope.STEP) { - for (Trigger t : triggers) { - t.onEnd(s); - } - } - } - - public double getDistance(Workout w, Scope s) { - double d = w.getDistance(Scope.ACTIVITY); - if (s == Scope.STEP) { - return d - stepStartDistance; - } else if (s == Scope.LAP) { - return d - lapStartDistance; - } - if (BuildConfig.DEBUG) { throw new AssertionError(); } - return 0; - } - - public double getTime(Workout w, Scope s) { - double t = w.getTime(Scope.ACTIVITY); - if (s == Scope.STEP) { - return t - stepStartTime; - } else if (s == Scope.LAP) { - return t - lapStartTime; - } - if (org.runnerup.BuildConfig.DEBUG) { throw new AssertionError(); } - return 0; - } - - public double getSpeed(Workout w, Scope s) { - double t = getTime(w, s); - double d = getDistance(w, s); - if (t != 0) { - return d / t; - } - return 0; - } - - public double getHeartbeats(Workout w, Scope s) { - double t = w.getHeartbeats(Scope.ACTIVITY); - if (s == Scope.STEP) { - return t - stepStartHeartbeats; - } else if (s == Scope.LAP) { - return t - lapStartHeartbeats; - } - return 0; - } - - public double getDuration(Dimension dimension) { - if (durationType == dimension) - return durationValue; - return 0; - } - - public static Step createPauseStep(Dimension dim, double duration) { - Step step; - if (dim == null || dim == Dimension.TIME) - step = new PauseStep(); - else - step = new Step(); - - step.intensity = Intensity.RESTING; - step.durationType = dim; - step.durationValue = duration; - return step; - } - - public void getSteps(Step parent, int i, List list) { - list.add(new Workout.StepListEntry(list.size(), this, i, parent)); - } - - public Step getCurrentStep() { - return this; - } - - public int getRepeatCount() { - return 0; - } - public void setRepeatCount(int val) { } - - public int getCurrentRepeat() { - return 0; - } - - public boolean isLastStep() { - return true; - } - - public boolean isPauseStep() { return false; } -} +/* + * Copyright (C) 2012 - 2013 jonas.oreland@gmail.com + * + * 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 . + */ + +package org.runnerup.workout; + +import android.content.ContentValues; + +import org.runnerup.BuildConfig; +import org.runnerup.common.util.Constants.DB; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + + +public class Step implements TickComponent { + + private String name = null; + + /** + * Intensity + */ + Intensity intensity = Intensity.ACTIVE; + + /** + * DurationType + * Dimension.TIME, Dimension.DISTANCE, null (keypress) + */ + Dimension durationType = null; + double durationValue = 0; + + /** + * Target + */ + Dimension targetType = null; + Range targetValue = null; + + /** + * Autolap (m) + */ + private double autolap = 0; + + /** + * Triggers + */ + final ArrayList triggers = new ArrayList<>(); + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the durationType + */ + public Dimension getDurationType() { + return durationType; + } + + /** + * @param durationType the durationType to set + */ + public void setDurationType(Dimension durationType) { + this.durationType = durationType; + } + + /** + * @return the durationValue + */ + public double getDurationValue() { + return durationValue; + } + + /** + * @param durationValue the durationValue to set + */ + public void setDurationValue(double durationValue) { + this.durationValue = durationValue; + } + + /** + * @return the targetType + */ + public Dimension getTargetType() { + return targetType; + } + + /** + * @param targetType the targetType to set + */ + public void setTargetType(Dimension targetType) { + this.targetType = targetType; + } + + /** + * @return the targetValue + */ + public Range getTargetValue() { + return targetValue; + } + + /** + * @param targetValue the targetValue to set + */ + public void setTargetValue(double targetValue) { + this.targetValue = new Range(targetValue, targetValue); + } + + public void setTargetValue(double min, double max) { + this.targetValue = new Range(min, max); + } + + public Intensity getIntensity() { + return intensity; + } + public void setIntensity(Intensity intensity) { + this.intensity = intensity; + } + + /** + * @return the autolap + */ + public double getAutolap() { + return autolap; + } + + /** + * @param val the autolap to set + */ + public void setAutolap(double val) { + this.autolap = val; + } + + @Override + public void onInit(Workout s) { + for (Trigger t : triggers) { + t.onInit(s); + } + } + + @Override + public void onBind(Workout s, HashMap bindValues) { + for (Trigger t : triggers) { + t.onBind(s, bindValues); + } + } + + @Override + public void onEnd(Workout s) { + for (Trigger t : triggers) { + t.onEnd(s); + } + } + + public void onRepeat(int current, int count) { + for (Trigger t : triggers) { + t.onRepeat(current, count); + } + } + + private double stepStartTime = 0; + private double stepStartDistance = 0; + private double stepStartHeartbeats = 0; + private double lapStartTime = 0; + private double lapStartDistance = 0; + private double lapStartHeartbeats = 0; + + @Override + public void onStart(Scope what, Workout s) { + double time = s.getTime(Scope.ACTIVITY); + double dist = s.getDistance(Scope.ACTIVITY); + double beats = s.getHeartbeats(Scope.ACTIVITY); + + if (what == Scope.STEP) { + stepStartTime = time; + stepStartDistance = dist; + stepStartHeartbeats = beats; + if (s.isPaused()) + s.tracker.pause(); + else + s.tracker.resume(); + } else if (what == Scope.LAP) { + lapStartTime = time; + lapStartDistance = dist; + lapStartHeartbeats = beats; + ContentValues tmp = new ContentValues(); + tmp.put(DB.LAP.INTENSITY, intensity.getValue()); + if (durationType != null) { + switch (durationType) { + case TIME: + tmp.put(DB.LAP.PLANNED_TIME, (long) durationValue); + break; + case DISTANCE: + tmp.put(DB.LAP.PLANNED_DISTANCE, (long) durationValue); + break; + case PACE: + case SPEED: + case HR: + case HRZ: + case CAD: + case TEMPERATURE: + case PRESSURE: + break; + } + } + if (targetType != null) { + switch (targetType) { + case PACE: + tmp.put(DB.LAP.PLANNED_PACE, targetValue.maxValue); + break; + case SPEED: + if (targetValue.maxValue != 0) { + tmp.put(DB.LAP.PLANNED_PACE, 1.0d / targetValue.maxValue); + } + break; + case DISTANCE: + case TIME: + case HR: + case HRZ: + case CAD: + case TEMPERATURE: + case PRESSURE: + break; + } + } + s.newLap(tmp); + } + + for (Trigger t : triggers) { + t.onStart(what, s); + } + } + + @Override + public void onStop(Workout s) { + s.tracker.stop(); + for (Trigger t : triggers) { + t.onStop(s); + } + + /* + * Save current lap so that it shows in DetailActivity + */ + long distance = Math.round(s.getDistance(Scope.LAP)); + long time = Math.round(s.getTime(Scope.LAP)); + long hr = Math.round(s.getHeartRate(Scope.LAP)); + if (distance > 0 || time > 0) { + ContentValues tmp = new ContentValues(); + tmp.put(DB.LAP.DISTANCE, distance); + tmp.put(DB.LAP.TIME, time); + tmp.put(DB.LAP.AVG_HR, Math.round(hr)); + s.saveLap(tmp, /* next lap */ + false); + } + } + + @Override + public void onPause(Workout s) { + s.tracker.pause(); + for (Trigger t : triggers) { + t.onPause(s); + } + } + + private double mPrevTickLapDistance = 0; + private double mPrevTickLapTime = 0; + /** + * @return true if finished + */ + public boolean onTick(Workout s) { + if (checkFinished(s)) { + return true; + } + + for (Trigger t : triggers) { + t.onTick(s); + } + + if (this.autolap > 0) { + double lapDistance = s.getDistance(Scope.LAP); + double lapTime = s.getTime(Scope.LAP); + if (lapDistance >= this.autolap || + // autolap if this point is closer to the limit then next point + // (assuming the time/speed is is the same to next tick, but this should even out) + mPrevTickLapDistance > 0 && lapTime > mPrevTickLapTime && + (lapDistance + (lapDistance - mPrevTickLapDistance)/2) >= this.autolap) { + s.onNewLap(); + lapDistance = 0; + } + mPrevTickLapDistance = lapDistance; + mPrevTickLapTime = lapTime; + } + return false; + } + + public boolean onNextStep(Workout w) { + return true; // move to next step + } + + private boolean checkFinished(Workout s) { + if (durationType == null) + return false; + + return s.get(Scope.STEP, durationType) >= this.durationValue; + } + + @Override + public void onResume(Workout s) { + for (Trigger t : triggers) { + t.onResume(s); + } + s.tracker.resume(); + } + + @Override + public void onComplete(Scope scope, Workout s) { + if (scope == Scope.LAP) { + double distance = s.getDistance(scope); + long time = Math.round(s.getTime(scope)); + if (distance > 0 || time > 0) { + ContentValues tmp = new ContentValues(); + tmp.put(DB.LAP.DISTANCE, distance); + tmp.put(DB.LAP.TIME, time); + long hr = Math.round(s.getHeartRate(scope)); + tmp.put(DB.LAP.AVG_HR, hr); + s.saveLap(tmp, /* next lap */ + true); + } + } + for (Trigger t : triggers) { + t.onComplete(scope, s); + } + + if (scope == Scope.STEP) { + for (Trigger t : triggers) { + t.onEnd(s); + } + } + } + + public double getDistance(Workout w, Scope s) { + double d = w.getDistance(Scope.ACTIVITY); + if (s == Scope.STEP) { + return d - stepStartDistance; + } else if (s == Scope.LAP) { + return d - lapStartDistance; + } + if (BuildConfig.DEBUG) { throw new AssertionError(); } + return 0; + } + + public double getTime(Workout w, Scope s) { + double t = w.getTime(Scope.ACTIVITY); + if (s == Scope.STEP) { + return t - stepStartTime; + } else if (s == Scope.LAP) { + return t - lapStartTime; + } + if (org.runnerup.BuildConfig.DEBUG) { throw new AssertionError(); } + return 0; + } + + public double getSpeed(Workout w, Scope s) { + double t = getTime(w, s); + double d = getDistance(w, s); + if (t != 0) { + return d / t; + } + return 0; + } + + public double getHeartbeats(Workout w, Scope s) { + double t = w.getHeartbeats(Scope.ACTIVITY); + if (s == Scope.STEP) { + return t - stepStartHeartbeats; + } else if (s == Scope.LAP) { + return t - lapStartHeartbeats; + } + return 0; + } + + public double getDuration(Dimension dimension) { + if (durationType == dimension) + return durationValue; + return 0; + } + + public static Step createPauseStep(Dimension dim, double duration) { + Step step; + if (dim == null || dim == Dimension.TIME) + step = new PauseStep(); + else + step = new Step(); + + step.intensity = Intensity.RESTING; + step.durationType = dim; + step.durationValue = duration; + return step; + } + + public void getSteps(Step parent, int i, List list) { + list.add(new Workout.StepListEntry(list.size(), this, i, parent)); + } + + public Step getCurrentStep() { + return this; + } + + public int getRepeatCount() { + return 0; + } + public void setRepeatCount(int val) { } + + public int getCurrentRepeat() { + return 0; + } + + public boolean isLastStep() { + return true; + } + + public boolean isPauseStep() { return false; } +} diff --git a/app/src/main/org/runnerup/workout/Workout.java b/app/src/main/org/runnerup/workout/Workout.java index 04f031192..c5956d760 100644 --- a/app/src/main/org/runnerup/workout/Workout.java +++ b/app/src/main/org/runnerup/workout/Workout.java @@ -191,12 +191,12 @@ public void onTick() { public void onNextStep() { if (currentStep == null) { - // There is no current step return; } currentStep.onComplete(Scope.LAP, this); currentStep.onComplete(Scope.STEP, this); + // Increase the step counter unless this is a repeat step not yet finished if (currentStep.onNextStep(this)) currentStepNo++; @@ -205,6 +205,7 @@ public void onNextStep() { currentStep.onStart(Scope.STEP, this); currentStep.onStart(Scope.LAP, this); } else { + // End the workout currentStep.onComplete(Scope.ACTIVITY, this); setCurrentStep(null); tracker.stop(); @@ -245,7 +246,6 @@ public void onNewLapOrNextStep() { } public void onStop(Workout w) { - initFeedback(); if (currentStep != null) { currentStep.onStop(this); @@ -288,6 +288,9 @@ public boolean isPaused() { @Override public double get(Scope scope, Dimension d) { + if (d == null) { + return 0; + } switch (d) { case DISTANCE: return getDistance(scope); @@ -566,16 +569,15 @@ void saveLap(ContentValues tmp, boolean next) { } } - //public int getStepCount() { - // return steps.size(); - //} - public boolean isLastStep() { + if (currentStepNo >= steps.size()) + // Incorrect workout + return true; + if (currentStepNo + 1 < steps.size()) return false; - if (currentStepNo < steps.size()) - return steps.get(currentStepNo).isLastStep(); - return true; + + return steps.get(currentStepNo).isLastStep(); } /** From bbfaf4981668733a532d8090e82cd38fd1c4e002 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Mon, 29 Jul 2019 00:15:32 +0200 Subject: [PATCH 02/15] Countdown: Enable for all targets with duration Previously only available for rest/recovery targets Also add countdown in the GUI (done in later commit) --- .../runnerup/workout/EndOfLapSuppression.java | 14 ++- .../org/runnerup/workout/ListTrigger.java | 52 +++++---- .../org/runnerup/workout/WorkoutBuilder.java | 104 ++++++------------ .../feedback/AudioCountdownFeedback.java | 6 +- .../workout/feedback/AudioFeedback.java | 36 +++--- 5 files changed, 85 insertions(+), 127 deletions(-) diff --git a/app/src/main/org/runnerup/workout/EndOfLapSuppression.java b/app/src/main/org/runnerup/workout/EndOfLapSuppression.java index 530c35fd8..c1efe226e 100644 --- a/app/src/main/org/runnerup/workout/EndOfLapSuppression.java +++ b/app/src/main/org/runnerup/workout/EndOfLapSuppression.java @@ -91,20 +91,22 @@ private boolean suppressEmpty(Trigger trigger, Workout w) { private boolean suppressInterval(Trigger trigger, Workout w) { - if (!(trigger instanceof IntervalTrigger)) + if (!(trigger instanceof IntervalTrigger)) { return false; + } IntervalTrigger it = (IntervalTrigger) trigger; - if (it.dimension == Dimension.DISTANCE) { - double distance = w.getDistance(Scope.LAP); - if (Math.abs(distance - lapDuration) > lapDistanceLimit) - return false; + if (it.dimension != Dimension.DISTANCE) { + return false; + } + double distance = w.getDistance(Scope.LAP); + if ((distance - lapDuration) == lapDistanceLimit) { Log.e(getClass().getName(), "suppressing trigger! distance: " + distance + ", lapDistance: " + lapDuration); - return true; } + return false; } diff --git a/app/src/main/org/runnerup/workout/ListTrigger.java b/app/src/main/org/runnerup/workout/ListTrigger.java index 75f4c0786..90ed68f40 100644 --- a/app/src/main/org/runnerup/workout/ListTrigger.java +++ b/app/src/main/org/runnerup/workout/ListTrigger.java @@ -22,42 +22,40 @@ public class ListTrigger extends Trigger { - boolean remaining = false; - Scope scope = Scope.ACTIVITY; - Dimension dimension = Dimension.TIME; + private Scope scope; + private Dimension dimension; - private int pos = Integer.MAX_VALUE; - ArrayList triggerTimes = new ArrayList<>(); + private int pos; + private ArrayList triggerTimes; + + ListTrigger(Dimension d, Scope s, ArrayList triggerTimes){ + this.dimension = d; + this.scope = s; + + if (triggerTimes == null) { + triggerTimes = new ArrayList<>(); + } + this.triggerTimes = triggerTimes; + pos = 0; + } @Override public boolean onTick(Workout w) { - if (pos < triggerTimes.size()) { - if (!remaining) { - double now = w.get(scope, dimension); - if (now >= triggerTimes.get(pos)) { - fire(w); - scheduleNext(w, now); - } - } else { - double now = w.getRemaining(scope, dimension); - if (now <= triggerTimes.get(pos)) { - fire(w); - scheduleNext(w, now); - } - } + // add a bit of margin, NOTE: less than 0.5s + // For distance 4:00 /km is just over 4 m/s + final double margin = dimension == Dimension.TIME ? 0.4d : 2d; + + double now = w.getRemaining(scope, dimension) - margin; + if (pos < triggerTimes.size() && now <= triggerTimes.get(pos)) { + scheduleNext(w, now); + fire(w); } return false; } private void scheduleNext(Workout w, double now) { - if (!remaining) { - while (pos < triggerTimes.size() && now >= triggerTimes.get(pos)) { - pos++; - } - } else { - while (pos < triggerTimes.size() && now <= triggerTimes.get(pos)) { - pos++; - } + while (pos < triggerTimes.size() && now <= triggerTimes.get(pos)) { + pos++; } if (pos >= triggerTimes.size()) { diff --git a/app/src/main/org/runnerup/workout/WorkoutBuilder.java b/app/src/main/org/runnerup/workout/WorkoutBuilder.java index d9f9fdb68..f2d55aa4f 100644 --- a/app/src/main/org/runnerup/workout/WorkoutBuilder.java +++ b/app/src/main/org/runnerup/workout/WorkoutBuilder.java @@ -115,7 +115,7 @@ private static void addAutoPauseTrigger(Resources res, Step step, SharedPreferen return; float autoPauseMinSpeed = 0; - float autoPauseAfterSeconds = 4f; + float autoPauseAfterSeconds = 5f; String val = prefs.getString(res.getString(R.string.pref_autopause_minpace), "20"); try { @@ -306,6 +306,7 @@ private static void addAudioCuesToWorkout(Resources res, ArrayList steps, ev.event = Event.COMPLETED; ev.scope = Scope.STEP; ev.triggerAction.add(new AudioFeedback(R.string.cue_lap_completed)); + ev.triggerAction.add(new CountdownFeedback(Scope.STEP, step.durationType)); step.triggers.add(ev); Trigger elt = hasEndOfLapTrigger(triggers); @@ -319,7 +320,6 @@ private static void addAudioCuesToWorkout(Resources res, ArrayList steps, elt.triggerSuppression.add(EndOfLapSuppression.EndOfLapSuppression); } } - checkDuplicateTriggers(step); // { // Log.e("WorkoutBuilder", "triggers: "); // for (Trigger t : step.triggers) { @@ -342,9 +342,6 @@ private static void addAudioCuesToWorkout(Resources res, ArrayList steps, } addPauseStopResumeTriggers(res, step.triggers, prefs); - if (!silent) { - createAudioCountdown(step); - } break; } case WARMUP: @@ -361,6 +358,11 @@ private static void addAudioCuesToWorkout(Resources res, ArrayList steps, break; } + if (!silent && step.getIntensity() != Intensity.REPEAT) { + createAudioCountdown(step); + } + checkDuplicateTriggers(step); + if (coaching && step.getTargetType() != null) { Range range = step.getTargetValue(); int averageSeconds = SafeParse.parseInt(prefs.getString( @@ -411,11 +413,10 @@ private static void checkDuplicateTriggers(Step step) { if (hasEndOfLapTrigger(step.triggers) != null) { Log.e("WorkoutBuilder", "hasEndOfLapTrigger()"); /* - * The end of lap trigger can be a duplicate of a distance based - * interval trigger 1) in a step with distance duration, that is a - * multiple of the interval-distance e.g interval-trigger-distance = - * 100m duration = 1000m, then set max count = 9 2) in a step with - * autolap 500m and interval-trigger-distance 1000 then remove the + * The end of lap trigger can be a duplicate of a distance based interval trigger + * 1) in a step with distance duration, that is a multiple of the interval-distance + * e.g interval-trigger-distance = 100m duration = 1000m, then set max count = 9 + * 2) in a step with autolap 500m and interval-trigger-distance 1000 then remove the * trigger */ ArrayList list = new ArrayList<>(); @@ -524,22 +525,22 @@ private static void addPauseStopResumeTriggers(Resources res, ArrayList private static void createAudioCountdown(Step step) { if (step.getDurationType() == null) { + // Only for time/distance return; } - double first; ArrayList list = new ArrayList<>(); switch (step.getDurationType()) { case TIME: - first = 60; // 1 minute - Double tmp0[] = { + // seconds + Double[] tmp0 = { 60d, 30d, 10d, 5d, 3d, 2d, 1d }; list.addAll(Arrays.asList(tmp0)); break; case DISTANCE: - first = 100; // 100 meters - Double tmp1[] = { + // meters + Double[] tmp1 = { 100d, 50d, 20d, 10d }; list.addAll(Arrays.asList(tmp1)); @@ -548,55 +549,16 @@ private static void createAudioCountdown(Step step) { return; } - if (step.getDurationValue() > first) { - /* - * If longer than limit...create a Interval trigger for ">" part - */ - IntervalTrigger trigger = new IntervalTrigger(); - trigger.dimension = step.getDurationType(); - trigger.scope = Scope.STEP; - trigger.first = first; - trigger.interval = first; - trigger.triggerAction - .add(new AudioCountdownFeedback(Scope.STEP, step.getDurationType())); - step.triggers.add(trigger); + // Remove all values in list longer than the step + while (list.size() > 0 && step.getDurationValue() > list.get(0) * 1.1d) { + list.remove(0); } - /* - * Then create a list trigger for reminder... - */ - ArrayList triggerTimes = new ArrayList<>(); - for (Double d : list) { - if (d >= step.getDurationValue()) - continue; - double val = step.getDurationValue() - d; - if ((val % first) == 0) { - continue; // handled by interval trigger - } - double margin = 0.4d; // add a bit of margin, NOTE: less than 0.5 - triggerTimes.add(d + margin); - } - - { - ListTrigger trigger = new ListTrigger(); - trigger.remaining = true; - trigger.dimension = step.getDurationType(); - trigger.scope = Scope.STEP; - trigger.triggerTimes = triggerTimes; - trigger.triggerAction - .add(new AudioCountdownFeedback(Scope.STEP, step.getDurationType())); - step.triggers.add(trigger); - } - - - /* - * Add add information just when pause step starts... - */ - EventTrigger ev = new EventTrigger(); - ev.event = Event.STARTED; - ev.scope = Scope.STEP; - ev.triggerAction.add(new AudioCountdownFeedback(Scope.STEP, step.getDurationType())); - step.triggers.add(ev); + // create a list trigger for the values + ListTrigger trigger = new ListTrigger(step.getDurationType(), Scope.STEP, list); + trigger.triggerAction + .add(new AudioCountdownFeedback(Scope.STEP, step.getDurationType())); + step.triggers.add(trigger); } public static void prepareWorkout(Resources res, SharedPreferences prefs, Workout w) { @@ -619,7 +581,6 @@ public static void prepareWorkout(Resources res, SharedPreferences prefs, Workou } Log.i("WorkoutBuilder", "setAutolap(" + val + ")"); for (StepListEntry s : steps) { - s.step.setAutolap(0); // reset switch (s.step.getIntensity()) { case ACTIVE: s.step.setAutolap(val); @@ -629,6 +590,8 @@ public static void prepareWorkout(Resources res, SharedPreferences prefs, Workou case WARMUP: case COOLDOWN: case REPEAT: + default: + s.step.setAutolap(0); // reset break; } } @@ -767,16 +730,15 @@ public static void addFeedbackFromPreferences(SharedPreferences prefs, feedback.add(new AudioFeedback(Scope.CURRENT, Dimension.CAD)); } - // Insert Scope - for (int i=feedbackStart; i 0) { if (msgTxt == null) { msgTxt = formatter.getCueString(msgId); } From 46d5ae2813fa73d54d90414a21c965d8cd0edf01 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Mon, 29 Jul 2019 00:16:15 +0200 Subject: [PATCH 03/15] Step complettion: Use current point if closer to the target Previously, the target were reached only when exceeding the target. So if distance was 1000 m, the step was only completed if over 1000m Similar for time, so average could be 1:01. Time/distance targets are now handled similar to autolap changed recently, so the current point is used if it is expected to be closer to the target than next point, based on the previous point. --- app/src/main/org/runnerup/workout/Step.java | 76 +++++++++++++++------ 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/app/src/main/org/runnerup/workout/Step.java b/app/src/main/org/runnerup/workout/Step.java index 918917d94..954e358a2 100644 --- a/app/src/main/org/runnerup/workout/Step.java +++ b/app/src/main/org/runnerup/workout/Step.java @@ -141,16 +141,16 @@ public void setIntensity(Intensity intensity) { } /** - * @return the autolap + * @return the autolap distance (may be set in workouts too) */ - public double getAutolap() { + double getAutolap() { return autolap; } /** * @param val the autolap to set */ - public void setAutolap(double val) { + void setAutolap(double val) { this.autolap = val; } @@ -286,7 +286,8 @@ public void onPause(Workout s) { } private double mPrevTickLapDistance = 0; - private double mPrevTickLapTime = 0; + private double mPrevTickStepDistance = 0; + private double mPrevTickStepTime = 0; /** * @return true if finished */ @@ -299,32 +300,63 @@ public boolean onTick(Workout s) { t.onTick(s); } - if (this.autolap > 0) { - double lapDistance = s.getDistance(Scope.LAP); - double lapTime = s.getTime(Scope.LAP); - if (lapDistance >= this.autolap || - // autolap if this point is closer to the limit then next point - // (assuming the time/speed is is the same to next tick, but this should even out) - mPrevTickLapDistance > 0 && lapTime > mPrevTickLapTime && - (lapDistance + (lapDistance - mPrevTickLapDistance)/2) >= this.autolap) { - s.onNewLap(); - lapDistance = 0; - } - mPrevTickLapDistance = lapDistance; - mPrevTickLapTime = lapTime; - } return false; } - public boolean onNextStep(Workout w) { - return true; // move to next step + private boolean exceedDistance(double distance, double prevDistance, double refDist, double time) { + return distance >= refDist || + // autolap if this point is closer to the limit then next point + // (assuming the time/speed is is the same to next tick, but this should even out) + prevDistance > 0 && time > mPrevTickStepTime && + (distance + (distance - prevDistance) / 2) >= refDist; } private boolean checkFinished(Workout s) { - if (durationType == null) + if (this.getAutolap() == 0 && durationType == null) { return false; + } + + boolean newStep = false; + boolean newLap = false; + double time = s.getTime(Scope.STEP); - return s.get(Scope.STEP, durationType) >= this.durationValue; + // Special handling for distance targets to end on (likely) closest + if (durationType == Dimension.DISTANCE) { + double distance = s.get(Scope.STEP, durationType); + double lapDist = this.durationValue; + if (exceedDistance(distance, mPrevTickStepDistance, lapDist, time)) { + newStep = true; + } + mPrevTickStepDistance = distance; + } else if (durationType == Dimension.TIME) { + double diff = (time - mPrevTickStepTime) / 2; + mPrevTickStepTime = time; + // This point is more likely than next + newStep = s.get(Scope.STEP, durationType) + diff >= this.durationValue; + } + + if (!newStep && this.getAutolap() > 0) { + double distance = s.getDistance(Scope.LAP); + double lapDist = getAutolap(); + if (exceedDistance(distance, mPrevTickLapDistance, lapDist, time)) { + newLap = true; + } + mPrevTickLapDistance = distance; + } + mPrevTickStepTime = time; + + if (newStep) { + return true; + } + if (newLap) { + s.onNewLap(); + } + + return false; + } + + public boolean onNextStep(Workout w) { + return true; // move to next step } @Override From 9f9ee64fa6f1269d7cce12f45e87e7b4a482efc4 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Tue, 30 Jul 2019 00:55:17 +0200 Subject: [PATCH 04/15] Interval: Use recovery also for Time Tweak to defaults for intervals Use recovery also for countdown steps --- app/res/layout/start_interval.xml | 3 +- app/res/xml/settings.xml | 2 +- .../main/org/runnerup/workout/PauseStep.java | 5 ++- app/src/main/org/runnerup/workout/Step.java | 6 +-- .../org/runnerup/workout/WorkoutBuilder.java | 43 ++++++++----------- .../runnerup/workout/WorkoutSerializer.java | 11 +++-- common/src/main/res/values-sv/strings.xml | 8 ++-- common/src/main/res/values/strings.xml | 8 ++-- 8 files changed, 41 insertions(+), 45 deletions(-) diff --git a/app/res/layout/start_interval.xml b/app/res/layout/start_interval.xml index d8c64228c..ab4e936df 100644 --- a/app/res/layout/start_interval.xml +++ b/app/res/layout/start_interval.xml @@ -41,7 +41,7 @@ diff --git a/app/res/xml/settings.xml b/app/res/xml/settings.xml index 65354030f..a2964f91f 100644 --- a/app/res/xml/settings.xml +++ b/app/res/xml/settings.xml @@ -285,7 +285,7 @@ android:summary="@string/Convert_reststep_with_type_distance_to_recoverystep_for_Intervaltab" /> 0) { - Step step = Step.createPauseStep(Dimension.TIME, val); + Step step = Step.createRestStep(Dimension.TIME, val, false); w.steps.add(step); } } @@ -152,18 +153,18 @@ public static Workout createDefaultIntervalWorkout(Resources res, SharedPreferen w.steps.add(step); } - int repetitions = (int) SafeParse.parseDouble(prefs.getString(res.getString(R.string.pref_interval_repetitions), "1"), + int repetitions = (int) SafeParse.parseDouble(prefs.getString(res.getString(R.string.pref_interval_repetitions), "8"), 1); - int intervalType = prefs.getInt(res.getString(R.string.pref_interval_type), 0); + int intervalType = prefs.getInt(res.getString(R.string.pref_interval_type), 1); // Distance long intervalTime = SafeParse.parseSeconds(prefs.getString(res.getString(R.string.pref_interval_time), "00:04:00"), 4 * 60); - double intevalDistance = SafeParse.parseDouble(prefs.getString(res.getString(R.string.pref_interval_distance), "1000"), + double intervalDistance = SafeParse.parseDouble(prefs.getString(res.getString(R.string.pref_interval_distance), "1000"), 1000); - int intervalRestType = prefs.getInt(res.getString(R.string.pref_interval_rest_type), 0); + int intervalRestType = prefs.getInt(res.getString(R.string.pref_interval_rest_type), 0); // Time long intervalRestTime = SafeParse.parseSeconds( prefs.getString(res.getString(R.string.pref_interval_rest_time), "00:01:00"), 60); - double intevalRestDistance = SafeParse.parseDouble( + double intervalRestDistance = SafeParse.parseDouble( prefs.getString(res.getString(R.string.pref_interval_rest_distance), "200"), 200); RepeatStep repeat = new RepeatStep(); @@ -177,30 +178,21 @@ public static Workout createDefaultIntervalWorkout(Resources res, SharedPreferen break; case 1: // Distance step.durationType = Dimension.DISTANCE; - step.durationValue = intevalDistance; + step.durationValue = intervalDistance; break; } repeat.steps.add(step); - //if (true) { Step rest = null; switch (intervalRestType) { case 0: // Time - rest = Step.createPauseStep(Dimension.TIME, intervalRestTime); + rest = Step.createRestStep(Dimension.TIME, intervalRestTime, convertRestToRecovery); break; case 1: // Distance - if (!convertRestToRecovery) { - rest = Step.createPauseStep(Dimension.DISTANCE, intevalRestDistance); - } else { - rest = new Step(); - rest.intensity = Intensity.RECOVERY; - rest.durationType = Dimension.DISTANCE; - rest.durationValue = intevalRestDistance; - } + rest = Step.createRestStep(Dimension.DISTANCE, intervalRestDistance, convertRestToRecovery); break; } repeat.steps.add(rest); - //} } w.steps.add(repeat); @@ -591,7 +583,7 @@ public static void prepareWorkout(Resources res, SharedPreferences prefs, Workou case COOLDOWN: case REPEAT: default: - s.step.setAutolap(0); // reset + s.step.setAutolap(0); // reset break; } } @@ -604,7 +596,9 @@ public static void prepareWorkout(Resources res, SharedPreferences prefs, Workou */ if (prefs.getBoolean(res.getString(R.string.pref_step_countdown_active), true)) { - long val = 15; // default 15s + final boolean convertRestToRecovery = prefs.getBoolean(res.getString( + R.string.pref_convert_interval_distance_rest_to_recovery), true); + long val = 15; // default String vals = prefs.getString(res.getString(R.string.pref_step_countdown_time), "15"); try { val = Long.parseLong(vals); @@ -614,9 +608,7 @@ public static void prepareWorkout(Resources res, SharedPreferences prefs, Workou StepListEntry stepArr[] = new StepListEntry[steps.size()]; steps.toArray(stepArr); for (int i = 0; i < stepArr.length; i++) { - // Step prev = i == 0 ? null : stepArr[i-1]; Step step = stepArr[i].step; - Step next = i + 1 == stepArr.length ? null : stepArr[i + 1].step; if (step.durationType != null) continue; @@ -624,13 +616,14 @@ public static void prepareWorkout(Resources res, SharedPreferences prefs, Workou if (step.intensity == Intensity.REPEAT || step.intensity == Intensity.RESTING) continue; - if (next == null) + if (i + 1 >= stepArr.length) continue; + Step next = stepArr[i + 1].step; if (next.durationType == Dimension.TIME && next.intensity == Intensity.RESTING) continue; - Step s = Step.createPauseStep(Dimension.TIME, val); + Step s = Step.createRestStep(Dimension.TIME, val, convertRestToRecovery); if (stepArr[i].parent == null) { w.steps.add(i + 1, s); Log.e("WorkoutBuilder", "Added step at index: " + (i + 1)); diff --git a/app/src/main/org/runnerup/workout/WorkoutSerializer.java b/app/src/main/org/runnerup/workout/WorkoutSerializer.java index 2b9253fdc..9b234bde6 100644 --- a/app/src/main/org/runnerup/workout/WorkoutSerializer.java +++ b/app/src/main/org/runnerup/workout/WorkoutSerializer.java @@ -433,11 +433,10 @@ private static jsonstep parseStep(JSONObject obj, boolean convertRestToRecovery) break; } case RESTING: - if (!convertRestToRecovery || duration.first != Dimension.DISTANCE || - duration.second == null) { - js.step = Step.createPauseStep(duration.first, duration.second); - break; - } + boolean rest = !convertRestToRecovery || duration.first != Dimension.DISTANCE || + duration.second == null; + js.step = Step.createRestStep(duration.first, duration.second, !rest); + break; case ACTIVE: case WARMUP: case COOLDOWN: @@ -467,7 +466,7 @@ public static Workout readFile(Context ctx, String name) throws FileNotFoundExce File fin = getFile(ctx, name); Log.e("WorkoutSerializer", "reading " + fin.getPath()); final boolean convertRestToRecovery = prefs.getBoolean(ctx.getResources().getString( - R.string.pref_convert_advanced_distance_rest_to_recovery), false); + R.string.pref_convert_advanced_distance_rest_to_recovery), true); Workout w = readJSON(new FileReader(fin), convertRestToRecovery); w.sport = prefs.getInt(ctx.getResources().getString(R.string.pref_sport), Constants.DB.ACTIVITY.SPORT_RUNNING); diff --git a/common/src/main/res/values-sv/strings.xml b/common/src/main/res/values-sv/strings.xml index 6abcb06c7..83a24057a 100644 --- a/common/src/main/res/values-sv/strings.xml +++ b/common/src/main/res/values-sv/strings.xml @@ -169,8 +169,8 @@ Ladda ned/redigera/ta bort träningspass Obs, du måste ansluta till kontot också Använd dina lurar för att starta/pausa/återuppta RunnerUp - Konvertera vilosteg med distans typ till återhämtningssteg för Interval-tabben - Konvertera vilosteg med distans typ till återhämtningssteg för Avancerat-tabben + För Interval-tabben och nedräkningssteg, använd \"Återhämting\" för \"Vila\" (paus) steg + Använd \"Återhämting\" för \"Vila\" (paus) steg för träningspass Exportera databas till sdkort (t.ex. före uppgradering) Importera databas från sdkort (t.ex. efter uppgradering) Coach för att hjälpa dig nå målet (om mål är satt) @@ -194,8 +194,8 @@ RunnerUpLive adress Autopausa efter (s) Autopaus tempo (min/km) - Konvertera vila i Interval-tabben - Konvertera vila i Avancerat-tabben + Interval vilosteg + Vilosteg i pass Måltempo flytande medelvärde (s) Måltempo anståndstid Autovarv vid intervaller diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 8cbbea69c..5795ca0db 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -170,8 +170,8 @@ Download/edit/remove workouts You need to connect to a RunnerUp Live account Use your headset to start/pause/resume RunnerUp - On the Interval tab, convert distance-based \"Rest\" steps to \"Recovery\" steps - On the Advanced tab, convert distance-based \"Rest\" steps to \"Recovery\" steps + On the Interval tab and countdown steps, use \"Recovery\" instead of \"Rest\" (pause) steps + For advanced workouts, use \"Recovery\" instead of \"Rest\" (pause) steps Export database to storage Import database from storage Coach to help you reach target (if having set target) @@ -195,8 +195,8 @@ RunnerUp Live address Autopause after (s) Autopause min pace (min/km) - Convert rest on Interval tab - Convert rest on Advanced tab + Interval rest steps + Workout rest steps Target pace moving average seconds Target pace grace seconds Autolap during intervals From bacee6ed81c847529cf4c90b43cbdf5bf4d708dd Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Wed, 31 Jul 2019 01:03:09 +0200 Subject: [PATCH 05/15] Periodic feedback: Allow in all steps Autolap: Allow in activity last steps Add summary for Activity_countdown settings --- app/res/xml/settings.xml | 13 +- .../org/runnerup/workout/WorkoutBuilder.java | 289 +++++++++--------- common/src/main/res/values-sv/strings.xml | 19 +- common/src/main/res/values/strings.xml | 19 +- 4 files changed, 179 insertions(+), 161 deletions(-) diff --git a/app/res/xml/settings.xml b/app/res/xml/settings.xml index a2964f91f..68d00b814 100644 --- a/app/res/xml/settings.xml +++ b/app/res/xml/settings.xml @@ -119,11 +119,11 @@ android:defaultValue="true" android:key="@string/pref_autolap_active" android:persistent="true" - android:title="@string/Autolap" /> + android:title="@string/Autolap" + android:summary="@string/Autolap_basic_summary" /> + android:title="@string/Activity_countdown" + android:summary="@string/Activity_countdown_summary" /> + android:title="@string/Autolap_during_intervals" + android:summary="@string/Autolap_during_intervals_summary" /> + android:title="@string/Add_countdown_after_step_that_ends_with_user_press" + android:summary="@string/Add_countdown_after_step_that_ends_with_user_press_summary" /> steps, SharedPreferences prefs) { final boolean skip_startstop_cue = prefs.getBoolean( res.getString(R.string.cueinfo_skip_startstop), false); - ArrayList triggers = createDefaultTriggers(res, prefs); - boolean silent = triggers.size() == 0; - final boolean coaching = prefs.getBoolean(res.getString(R.string.cueinfo_target_coaching), - true); - if (silent && coaching) { - for (Step s : steps) { - if (s.getTargetType() != null) { - silent = false; - break; - } + final boolean isLapStartedCue = prefs.getBoolean(res.getString(R.string.pref_lap_started), true); + + Step[] stepArr = new Step[steps.size()]; + steps.toArray(stepArr); + for (int i = 0; i < stepArr.length; i++) { + Step step = stepArr[i]; + Step next = i + 1 == stepArr.length ? null : stepArr[i + 1]; + + if (step.getIntensity() == Intensity.REPEAT) { + addAudioCuesToWorkout(res, ((RepeatStep) step).steps, prefs); + continue; } - } - addPauseStopResumeTriggers(res, triggers, prefs); - if (!silent) - { - EventTrigger ev = new EventTrigger(); - ev.event = Event.STARTED; - ev.scope = Scope.STEP; - ev.maxCounter = 1; - ev.triggerAction.add(new AudioFeedback(R.string.cue_lap_started)); - triggers.add(ev); + if (step.getIntensity() != Intensity.RESTING) { + // Periodic distance/time and end of lap/step + // Some suppressions for each intensity for related lap started/completed + // endOfLap is related to the autolap + boolean endOfLap = step.getIntensity() == Intensity.ACTIVE || + step.getAutolap() > 0; + ArrayList defaultTriggers = createDefaultTriggers(res, prefs, endOfLap); - if (prefs.getBoolean(res.getString(R.string.pref_lap_started), false)) { - EventTrigger ev2 = new EventTrigger(); // for autolap - ev2.event = Event.STARTED; - ev2.scope = Scope.LAP; - ev2.skipCounter = 1; // skip above - ev2.triggerAction.add(new AudioFeedback(R.string.cue_lap_started)); - triggers.add(ev2); + step.triggers.addAll(defaultTriggers); + } + + if (!skip_startstop_cue) { + addPauseStopResumeTriggers(step.triggers); } if (prefs.getBoolean(res.getString(R.string.pref_cue_hrm_connection), false)) { HRMStateTrigger hrmState = new HRMStateTrigger(); hrmState.triggerAction.add(new HRMStateChangeFeedback(hrmState)); - triggers.add(hrmState); + step.triggers.add(hrmState); + } + + if (step.durationType != null) { + // GUI countdown + IntervalTrigger trigger = new IntervalTrigger(); + trigger.dimension = step.durationType; + trigger.first = 1; + trigger.interval = 1; + trigger.scope = Scope.STEP; + trigger.triggerAction.add(new CountdownFeedback(Scope.STEP, step.durationType)); + step.triggers.add(trigger); + + // Audio feedback + createAudioCountdown(step); + } + + final boolean coaching = prefs.getBoolean(res.getString(R.string.cueinfo_target_coaching),true); + if (coaching && step.getTargetType() != null) { + final Range range = step.getTargetValue(); + final int averageSeconds = SafeParse.parseInt(prefs.getString( + res.getString(R.string.pref_target_pace_moving_average_seconds), "20"), 20); + final int graceSeconds = SafeParse.parseInt( + prefs.getString(res.getString(R.string.pref_target_pace_grace_seconds), "30"), + 30); + + TargetTrigger tr = new TargetTrigger(step.getTargetType(), averageSeconds, + graceSeconds); + tr.scope = Scope.STEP; + tr.range = range; + tr.triggerAction.add(new CoachFeedback(tr)); + step.triggers.add(tr); } - } - Step stepArr[] = new Step[steps.size()]; - steps.toArray(stepArr); - for (int i = 0; i < stepArr.length; i++) { - // Step prev = i == 0 ? null : stepArr[i-1]; - Step step = stepArr[i]; - Step next = i + 1 == stepArr.length ? null : stepArr[i + 1]; switch (step.getIntensity()) { - case REPEAT: - addAudioCuesToWorkout(res, ((RepeatStep) step).steps, prefs); - break; case ACTIVE: - step.triggers.addAll(triggers); - if (!silent && (next == null || next.getIntensity() != step.getIntensity())) - { + if (isLapStartedCue) { EventTrigger ev = new EventTrigger(); - ev.event = Event.COMPLETED; + ev.event = Event.STARTED; ev.scope = Scope.STEP; - ev.triggerAction.add(new AudioFeedback(R.string.cue_lap_completed)); - ev.triggerAction.add(new CountdownFeedback(Scope.STEP, step.durationType)); + ev.maxCounter = 1; + ev.triggerAction.add(new AudioFeedback(R.string.cue_lap_started)); step.triggers.add(ev); - Trigger elt = hasEndOfLapTrigger(triggers); - if (elt != null) { - /* Add feedback after "end of lap" */ - ev.triggerAction.addAll(elt.triggerAction); - /* suppress empty STEP COMPLETED */ - ev.triggerSuppression.add(EndOfLapSuppression.EmptyLapSuppression); - - /* And suppress last end of lap trigger */ - elt.triggerSuppression.add(EndOfLapSuppression.EndOfLapSuppression); + EventTrigger ev2 = new EventTrigger(); // for autolap + ev2.event = Event.STARTED; + ev2.scope = Scope.LAP; + ev2.skipCounter = 1; // skip above + ev2.triggerAction.add(new AudioFeedback(R.string.cue_lap_started)); + step.triggers.add(ev2); + + if (next == null || next.getIntensity() != step.getIntensity()) { + EventTrigger ev3 = new EventTrigger(); + ev3.event = Event.COMPLETED; + ev3.scope = Scope.STEP; + ev3.triggerAction.add(new AudioFeedback(R.string.cue_lap_completed)); + + // Add after "end of lap" default audio cue + Trigger elt = hasEndOfLapTrigger(step.triggers); + if (elt != null) { + ev3.triggerAction.addAll(elt.triggerAction); + /* suppress empty STEP COMPLETED */ + ev3.triggerSuppression.add(EndOfLapSuppression.EmptyLapSuppression); + /* And suppress last end of lap trigger */ + elt.triggerSuppression.add(EndOfLapSuppression.EndOfLapSuppression); + } + step.triggers.add(ev3); } } - // { - // Log.e("WorkoutBuilder", "triggers: "); - // for (Trigger t : step.triggers) { - // System.err.print(t + " "); - // } - // Log.e("WorkoutBuilder", ""); - // } - break; - case RECOVERY: - case RESTING: { - if (step.durationType != null) { - IntervalTrigger trigger = new IntervalTrigger(); - trigger.dimension = step.durationType; - trigger.first = 1; - trigger.interval = 1; - trigger.scope = Scope.STEP; - trigger.triggerAction.add(new CountdownFeedback(Scope.STEP, step.durationType)); - step.triggers.add(trigger); - } - addPauseStopResumeTriggers(res, step.triggers, prefs); - break; - } case WARMUP: case COOLDOWN: - addPauseStopResumeTriggers(res, step.triggers, prefs); - if (!skip_startstop_cue) { + if (isLapStartedCue) { EventTrigger ev = new EventTrigger(); ev.event = Event.STARTED; ev.scope = Scope.STEP; @@ -348,28 +359,14 @@ private static void addAudioCuesToWorkout(Resources res, ArrayList steps, step.triggers.add(ev); } break; - } - if (!silent && step.getIntensity() != Intensity.REPEAT) { - createAudioCountdown(step); + case RECOVERY: + case RESTING: + // No action + break; } - checkDuplicateTriggers(step); - if (coaching && step.getTargetType() != null) { - Range range = step.getTargetValue(); - int averageSeconds = SafeParse.parseInt(prefs.getString( - res.getString(R.string.pref_target_pace_moving_average_seconds), "20"), 20); - int graceSeconds = SafeParse.parseInt( - prefs.getString(res.getString(R.string.pref_target_pace_grace_seconds), "30"), - 30); - TargetTrigger tr = new TargetTrigger(step.getTargetType(), averageSeconds, - graceSeconds); - tr.scope = Scope.STEP; - tr.range = range; - tr.triggerAction.add(new CoachFeedback(Scope.ACTIVITY, step.getTargetType(), range, - tr)); - step.triggers.add(tr); - } + checkDuplicateTriggers(step); } } @@ -430,7 +427,16 @@ private static void checkDuplicateTriggers(Step step) { } } - private static ArrayList createDefaultTriggers(Resources res, SharedPreferences prefs) { + /** + * Add the default triggers, with the configurable feedback (activity/lap etc time/distance etc): + * * periodic time/distance + * * end of lap + * + * @param res + * @param prefs + * @return + */ + private static ArrayList createDefaultTriggers(Resources res, SharedPreferences prefs, boolean endOfLap) { ArrayList feedback = new ArrayList<>(); ArrayList triggers = new ArrayList<>(); @@ -468,7 +474,7 @@ private static ArrayList createDefaultTriggers(Resources res, SharedPre } } - if (prefs.getBoolean(res.getString(R.string.cue_end_of_lap), false)) { + if (endOfLap && prefs.getBoolean(res.getString(R.string.cue_end_of_lap), false)) { EventTrigger ev = new EventTrigger(); ev.event = Event.COMPLETED; ev.scope = Scope.LAP; @@ -553,38 +559,35 @@ private static void createAudioCountdown(Step step) { step.triggers.add(trigger); } + /** + * Add autolap, autopause and countdown to workouts + * @param res + * @param prefs + * @param w + */ public static void prepareWorkout(Resources res, SharedPreferences prefs, Workout w) { List steps = w.getStepList(); boolean basic = w.getWorkoutType() == Constants.WORKOUT_TYPE.BASIC; - /* - * Add/remove autolap - */ - boolean autolap = prefs.getBoolean(res.getString(R.string.pref_autolap_active), false); - if (!basic) { - autolap = prefs.getBoolean(res.getString(R.string.pref_step_autolap_active), autolap); - } + // autolap + boolean autolap = basic ? + prefs.getBoolean(res.getString(R.string.pref_autolap_active), false) : + prefs.getBoolean(res.getString(R.string.pref_step_autolap_active), false); if (autolap) { - double val = 0; + double val; String vals = prefs.getString(res.getString(R.string.pref_autolap), "1000"); try { val = Double.parseDouble(vals); } catch (NumberFormatException e) { + val = 0; } - Log.i("WorkoutBuilder", "setAutolap(" + val + ")"); + Log.d("WorkoutBuilder", "setAutolap(" + val + ")"); for (StepListEntry s : steps) { - switch (s.step.getIntensity()) { - case ACTIVE: - s.step.setAutolap(val); - break; - case RECOVERY: - case RESTING: - case WARMUP: - case COOLDOWN: - case REPEAT: - default: - s.step.setAutolap(0); // reset - break; + if (basic || + s.step.getIntensity() == Intensity.ACTIVE || + // Also set on the last in the flat list + s == steps.get(steps.size() - 1)) { + s.step.setAutolap(val); } } } @@ -605,33 +608,31 @@ public static void prepareWorkout(Resources res, SharedPreferences prefs, Workou } catch (NumberFormatException e) { } if (val > 0) { - StepListEntry stepArr[] = new StepListEntry[steps.size()]; + StepListEntry[] stepArr = new StepListEntry[steps.size()]; steps.toArray(stepArr); for (int i = 0; i < stepArr.length; i++) { Step step = stepArr[i].step; - if (step.durationType != null) + if (step.durationType != null || + step.intensity == Intensity.REPEAT || + step.intensity == Intensity.RESTING || + i + 1 >= stepArr.length) continue; - if (step.intensity == Intensity.REPEAT || step.intensity == Intensity.RESTING) - continue; - - if (i + 1 >= stepArr.length) - continue; Step next = stepArr[i + 1].step; - if (next.durationType == Dimension.TIME && next.intensity == Intensity.RESTING) + if (next.intensity == Intensity.RESTING) continue; Step s = Step.createRestStep(Dimension.TIME, val, convertRestToRecovery); if (stepArr[i].parent == null) { w.steps.add(i + 1, s); - Log.e("WorkoutBuilder", "Added step at index: " + (i + 1)); + Log.d("WorkoutBuilder", "Added step at index: " + (i + 1)); } else { RepeatStep rs = (RepeatStep) stepArr[i].parent; int idx = rs.steps.indexOf(step); rs.steps.add(idx, s); - Log.e("WorkoutBuilder", "Added step at index: " + (i + 1) + " repeat index: " + Log.d("WorkoutBuilder", "Added step at index: " + (i + 1) + " repeat index: " + (idx + 1)); } } diff --git a/common/src/main/res/values-sv/strings.xml b/common/src/main/res/values-sv/strings.xml index 83a24057a..db06fbd59 100644 --- a/common/src/main/res/values-sv/strings.xml +++ b/common/src/main/res/values-sv/strings.xml @@ -185,7 +185,8 @@ Autopaus Aktivera Live Hörlursknapp start/stopp - Nedräkning till aktivitet + Nedräkning till aktivitet start + Lägg till nedräkning till aktivitetsstart för standard pass Nedräkningstid (s) Måttenhet Autostarta GPS @@ -198,8 +199,11 @@ Vilosteg i pass Måltempo flytande medelvärde (s) Måltempo anståndstid - Autovarv vid intervaller - Lägg till nedräkning efter knapptryckningssteg + Autovarv i pass + Autovarv i standard pass + Autovarv i sista steget samt \"Aktiv\" steg + Nedräkning efter knapptryckning + Lägg till nedräknings \"Återhämtning\" eller \"Vila\" steg efter steg som avslutas med knapptryckning Nedräkningssteg (s) Utjämning tempograf Utjämningsfilter tempo (s) @@ -213,11 +217,14 @@ Sken-pulsmätare Om RunnerUp Starta ljud vid - Meddelande triggat av tid + Tid trigger + Periodisk återkoppling Meddelande intervall (s) - Meddelande triggat av distans + Distans trigger + Periodisk återkoppling Meddelande intervall (m) - Meddelande vid varvslut + Varvslut + Autovarvslut samt slut på \"Aktiv\" steg Tysta musik under ljudmeddelanden Information i ljudmeddelanden: Total distans diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 5795ca0db..cecba2112 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -186,7 +186,8 @@ Autopause Enable RunnerUp Live Headset key start/stop - Activity countdown + Countdown to activity start + Add countdown to activity start for basic workouts Countdown time (s) Unit preference Autostart GPS @@ -199,8 +200,11 @@ Workout rest steps Target pace moving average seconds Target pace grace seconds - Autolap during intervals - Add countdown after step that ends with user press + Autolap in workouts + Autolap in basic workouts + Autolap in last step and \"Active\" steps + Countdown after key press + Add \"Recovery\" or \"Rest\" countdown step after steps that ends with user key press Step countdown time (s) Smooth pace graph Smooth pace filter (s) @@ -214,11 +218,14 @@ Mock HRM devices About RunnerUp Triggers - Time triggered audio cue + Time trigger + Periodic feedback Cue interval (s) - Distance triggered audio cue + Distance trigger + Periodic feedback Cue intervall (m) - End of lap audio cue + End of lap + End of autolap and \"Active\" steps Mute music during audio cues Cue information Total distance From 4679b4b33d77693eb48f78996e095485977481e3 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Wed, 31 Jul 2019 01:03:49 +0200 Subject: [PATCH 06/15] Autopause: Allow in warmup/cooldown steps --- app/res/xml/settings.xml | 3 +- .../org/runnerup/workout/WorkoutBuilder.java | 68 +++++++++++-------- common/src/main/res/values-sv/strings.xml | 1 + common/src/main/res/values/strings.xml | 1 + 4 files changed, 45 insertions(+), 28 deletions(-) diff --git a/app/res/xml/settings.xml b/app/res/xml/settings.xml index 68d00b814..0971aa869 100644 --- a/app/res/xml/settings.xml +++ b/app/res/xml/settings.xml @@ -134,7 +134,8 @@ android:defaultValue="false" android:key="@string/pref_autopause_active" android:persistent="true" - android:title="@string/Autopause"/> + android:title="@string/Autopause" + android:summary="@string/Autopause_summary"/> createDefaultTriggers(Resources res, SharedPre return triggers; } - private static void addPauseStopResumeTriggers(Resources res, ArrayList list, - SharedPreferences prefs) { - if (!prefs.getBoolean(res.getString(R.string.cueinfo_skip_startstop), false)) { - { - EventTrigger p = new EventTrigger(); - p.event = Event.PAUSED; - p.scope = Scope.STEP; - p.triggerAction.add(new AudioFeedback(R.string.cue_activity_paused)); - list.add(p); - } + private static void addPauseStopResumeTriggers(ArrayList list) { + { + EventTrigger p = new EventTrigger(); + p.event = Event.PAUSED; + p.scope = Scope.STEP; + p.triggerAction.add(new AudioFeedback(R.string.cue_activity_paused)); + list.add(p); + } - { - EventTrigger r = new EventTrigger(); - r.event = Event.RESUMED; - r.scope = Scope.STEP; - r.triggerAction.add(new AudioFeedback(R.string.cue_activity_resumed)); - list.add(r); - } + { + EventTrigger r = new EventTrigger(); + r.event = Event.RESUMED; + r.scope = Scope.STEP; + r.triggerAction.add(new AudioFeedback(R.string.cue_activity_resumed)); + list.add(r); + } - { - EventTrigger ev = new EventTrigger(); - ev.event = Event.STOPPED; - ev.scope = Scope.STEP; - ev.triggerAction.add(new AudioFeedback(R.string.cue_activity_stopped)); - list.add(ev); - } + { + EventTrigger ev = new EventTrigger(); + ev.event = Event.STOPPED; + ev.scope = Scope.STEP; + ev.triggerAction.add(new AudioFeedback(R.string.cue_activity_stopped)); + list.add(ev); } } @@ -592,6 +586,26 @@ public static void prepareWorkout(Resources res, SharedPreferences prefs, Workou } } + // Autopause + for (StepListEntry s : steps) { + if (basic) { + addAutoPauseTrigger(res, s.step, prefs); + continue; + } + switch (s.step.getIntensity()) { + case WARMUP: + case COOLDOWN: + addAutoPauseTrigger(res, s.step, prefs); + break; + case ACTIVE: + case RECOVERY: + case RESTING: + case REPEAT: + default: + break; + } + } + /* * Add countdowns after steps with duration "until pressed" * - if next is not a countdown diff --git a/common/src/main/res/values-sv/strings.xml b/common/src/main/res/values-sv/strings.xml index db06fbd59..6022ed510 100644 --- a/common/src/main/res/values-sv/strings.xml +++ b/common/src/main/res/values-sv/strings.xml @@ -183,6 +183,7 @@ Autovarv Autovarv (m) Autopaus + Autopaus i standard pass samt \"Uppvärmning\" och \"Nedvarvning\" steg Aktivera Live Hörlursknapp start/stopp Nedräkning till aktivitet start diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index cecba2112..b62fc2805 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -184,6 +184,7 @@ Autolap Autolap (m) Autopause + Autopause in basic workouts as well as \"Warmup\" and "Cooldown\" steps Enable RunnerUp Live Headset key start/stop Countdown to activity start From 353d9be7ec7ca4a3941e469b301925c3708956bc Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Fri, 2 Aug 2019 01:31:12 +0200 Subject: [PATCH 07/15] Settings: Reorder options Group workout options together and reorder the groups --- app/res/xml/settings.xml | 202 +++++++++++++++++++-------------------- 1 file changed, 101 insertions(+), 101 deletions(-) diff --git a/app/res/xml/settings.xml b/app/res/xml/settings.xml index 0971aa869..2b445395a 100644 --- a/app/res/xml/settings.xml +++ b/app/res/xml/settings.xml @@ -115,6 +115,54 @@ android:title="@string/Recording" android:key="recording_preferencescreen"> + + + + + + + + + + + + + + + + + + + + + android:key="@string/pref_step_countdown_active" + android:title="@string/Add_countdown_after_step_that_ends_with_user_press" + android:summary="@string/Add_countdown_after_step_that_ends_with_user_press_summary" /> + android:title="@string/Step_countdown_time_s" /> - + android:key="@string/pref_convert_interval_distance_rest_to_recovery" + android:title="@string/Convert_rest_on_Interval_tab" + android:summary="@string/Convert_reststep_with_type_distance_to_recoverystep_for_Intervaltab" /> + android:key="@string/pref_convert_advanced_distance_rest_to_recovery" + android:title="@string/Convert_rest_on_Advanced_tab" + android:summary="@string/Convert_reststep_with_type_distance_to_recoverystep_for_Advancedtab" /> - + android:title="@string/Target_pace_moving_average_seconds" /> + + + + + + + + + + @@ -241,15 +324,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From bf832d1dd8442bc063efb592a56a99daf3797bf4 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Fri, 2 Aug 2019 02:15:13 +0200 Subject: [PATCH 08/15] Add summary for time triggered steps --- app/res/xml/audio_cue_settings.xml | 27 ++++++++++++++++++++--- common/src/main/res/values-sv/strings.xml | 1 + common/src/main/res/values/strings.xml | 1 + 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/res/xml/audio_cue_settings.xml b/app/res/xml/audio_cue_settings.xml index b4b923b28..315ea2290 100644 --- a/app/res/xml/audio_cue_settings.xml +++ b/app/res/xml/audio_cue_settings.xml @@ -33,7 +33,8 @@ android:defaultValue="false" android:persistent="true" android:key="@string/cue_time" - android:title="@string/Time_triggered_audio_cue" /> + android:title="@string/Time_triggered_audio_cue" + android:summary="@string/Time_triggered_audio_cue_summary" /> + android:title="@string/Distance_triggered_audio_cue" + android:summary="@string/Distance_triggered_audio_cue_summary" /> + android:title="@string/End_of_lap_audio_cue" + android:summary="@string/End_of_lap_audio_cue_summary" /> + + + + + diff --git a/common/src/main/res/values-sv/strings.xml b/common/src/main/res/values-sv/strings.xml index 6022ed510..e898ebfff 100644 --- a/common/src/main/res/values-sv/strings.xml +++ b/common/src/main/res/values-sv/strings.xml @@ -334,6 +334,7 @@ Använd GPS-höjd vid start för att sätta starthöjd RunnerUp GPS och aktivtets notifikationer Varv start + Meddelande vid start av varv och \"Aktiv\", \"Uppvärmning\" och \"Nedvarvning\" steg Lås aktivitetsknappar Lås aktivitetsknappar genom att knacka snabbt på toppfälten diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index b62fc2805..8d17b2fd7 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -335,6 +335,7 @@ Use the initial GPS elevation to average out the barometric elevation RunnerUp GPS and activity notifications Lap start + Cue for start of laps and \"Active\", \"Warmup\" and \"Cooldown\" steps Lock activity buttons Lock activity buttons by fast clicking the header From 0394938671913c316f63e09de46b8876033f286b Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Fri, 2 Aug 2019 02:26:04 +0200 Subject: [PATCH 09/15] Target Feedback: Incorrect use of Activity --- app/src/main/org/runnerup/workout/TargetTrigger.java | 6 ++++++ .../main/org/runnerup/workout/feedback/CoachFeedback.java | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/org/runnerup/workout/TargetTrigger.java b/app/src/main/org/runnerup/workout/TargetTrigger.java index 14d780fbb..4b1985bc4 100644 --- a/app/src/main/org/runnerup/workout/TargetTrigger.java +++ b/app/src/main/org/runnerup/workout/TargetTrigger.java @@ -138,6 +138,12 @@ private void addObservation(double val_now) { cntMeasures++; } + public Dimension getDimension() { return dimension; } + + public Scope getScope() { return scope; } + + public Range getRange() { return range; } + public double getValue() { if (cntMeasures == lastValCnt) return lastVal; diff --git a/app/src/main/org/runnerup/workout/feedback/CoachFeedback.java b/app/src/main/org/runnerup/workout/feedback/CoachFeedback.java index e74904e03..7377d4ce9 100644 --- a/app/src/main/org/runnerup/workout/feedback/CoachFeedback.java +++ b/app/src/main/org/runnerup/workout/feedback/CoachFeedback.java @@ -36,9 +36,9 @@ public class CoachFeedback extends AudioFeedback { private Range range = null; private TargetTrigger trigger = null; - public CoachFeedback(Scope scope, Dimension dimension, Range range, TargetTrigger trigger) { - super(scope, dimension); - this.range = range; + public CoachFeedback(TargetTrigger trigger) { + super(Scope.CURRENT, trigger.getDimension()); + this.range = trigger.getRange(); this.trigger = trigger; if (dimension == Dimension.PACE) { From 906b27beb0dfbcf02c486fb6c17190dac3fd0049 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Fri, 2 Aug 2019 11:11:04 +0200 Subject: [PATCH 10/15] Audio cue settings: Reorder --- app/res/xml/audio_cue_settings.xml | 38 +++++-------------- .../tracker/component/TrackerTTS.java | 5 --- .../main/org/runnerup/view/MainLayout.java | 2 +- common/src/main/res/values-sv/strings.xml | 4 +- common/src/main/res/values/strings.xml | 2 +- 5 files changed, 13 insertions(+), 38 deletions(-) diff --git a/app/res/xml/audio_cue_settings.xml b/app/res/xml/audio_cue_settings.xml index 315ea2290..9086d3fe4 100644 --- a/app/res/xml/audio_cue_settings.xml +++ b/app/res/xml/audio_cue_settings.xml @@ -35,7 +35,6 @@ android:key="@string/cue_time" android:title="@string/Time_triggered_audio_cue" android:summary="@string/Time_triggered_audio_cue_summary" /> - - - - - - - - - - - + + - Exportera databas till sdkort (t.ex. före uppgradering) Importera databas från sdkort (t.ex. efter uppgradering) Coach för att hjälpa dig nå målet (om mål är satt) - Tyst när träningspass startas/pausas/återupptas/stoppas + Skippa meddelande att träningspass startas/pausas/återupptas/stoppas Testa ljudmeddelande Konton Ljudmeddelanden @@ -227,7 +227,7 @@ Varvslut Autovarvslut samt slut på \"Aktiv\" steg Tysta musik under ljudmeddelanden - Information i ljudmeddelanden: + Information i ljudmeddelanden Total distans Total tid Total hastighet diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 8d17b2fd7..44a3a24ae 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -175,7 +175,7 @@ Export database to storage Import database from storage Coach to help you reach target (if having set target) - Silent start/pause/resume/stop of workouts + Suppress cue for start/pause/resume/stop Test audio cue Accounts Audio cues From 694847afbb074438c236097c88d45633dfc9ed13 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Fri, 2 Aug 2019 11:11:37 +0200 Subject: [PATCH 11/15] ManageWorkouts: Expand by default --- app/src/main/org/runnerup/view/ManageWorkoutsActivity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/org/runnerup/view/ManageWorkoutsActivity.java b/app/src/main/org/runnerup/view/ManageWorkoutsActivity.java index b1af07f64..14b041710 100644 --- a/app/src/main/org/runnerup/view/ManageWorkoutsActivity.java +++ b/app/src/main/org/runnerup/view/ManageWorkoutsActivity.java @@ -114,6 +114,7 @@ public void onCreate(Bundle savedInstanceState) { adapter = new WorkoutAccountListAdapter(this); ExpandableListView list = (ExpandableListView) findViewById(R.id.expandable_list_view); list.setAdapter(adapter); + downloadButton = (Button) findViewById(R.id.download_workout_button); downloadButton.setOnClickListener(downloadButtonClick); // No download provider currently exists @@ -130,6 +131,7 @@ public void onCreate(Bundle savedInstanceState) { requery(); listLocal(); + list.expandGroup(0); Uri data = getIntent().getData(); if (data != null) { From d98137f6239e208b59d81a7d11f1d124ec3b7355 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sat, 3 Aug 2019 01:05:17 +0200 Subject: [PATCH 12/15] GraceTime: Reset when conditions are OK also if no feedback --- app/src/main/org/runnerup/workout/TargetTrigger.java | 1 + common/src/main/res/values-sv/strings.xml | 4 ++-- common/src/main/res/values/strings.xml | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/org/runnerup/workout/TargetTrigger.java b/app/src/main/org/runnerup/workout/TargetTrigger.java index 4b1985bc4..3a8f13897 100644 --- a/app/src/main/org/runnerup/workout/TargetTrigger.java +++ b/app/src/main/org/runnerup/workout/TargetTrigger.java @@ -121,6 +121,7 @@ public boolean onTick(Workout w) { double cmp = range.compare(avg); // Log.e(getName(), " => avg: " + avg + " => cmp: " + cmp); if (cmp == 0) { + graceCount = minGraceCount; return false; } fire(w); diff --git a/common/src/main/res/values-sv/strings.xml b/common/src/main/res/values-sv/strings.xml index 4268a583e..0cce5f374 100644 --- a/common/src/main/res/values-sv/strings.xml +++ b/common/src/main/res/values-sv/strings.xml @@ -198,8 +198,8 @@ Autopaus tempo (min/km) Interval vilosteg Vilosteg i pass - Måltempo flytande medelvärde (s) - Måltempo anståndstid + Målåterkoppling flytande medelvärde (s) + Målåterkoppling anståndstid (s) Autovarv i pass Autovarv i standard pass Autovarv i sista steget samt \"Aktiv\" steg diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 44a3a24ae..0d3ed1e07 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -199,8 +199,8 @@ Autopause min pace (min/km) Interval rest steps Workout rest steps - Target pace moving average seconds - Target pace grace seconds + Target feedback moving average (s) + Target feedback grace period (s) Autolap in workouts Autolap in basic workouts Autolap in last step and \"Active\" steps From 12d49bea6b18b93d376fa746848618f9f7b05f31 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sat, 3 Aug 2019 13:25:50 +0200 Subject: [PATCH 13/15] Refactor: Remove space and : in translation strings --- app/src/main/org/runnerup/view/FeedActivity.java | 2 +- .../org/runnerup/view/ManageWorkoutsActivity.java | 6 +++--- common/src/main/res/values-sv/strings.xml | 10 +++++----- common/src/main/res/values/strings.xml | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/main/org/runnerup/view/FeedActivity.java b/app/src/main/org/runnerup/view/FeedActivity.java index 552418972..002bc7889 100644 --- a/app/src/main/org/runnerup/view/FeedActivity.java +++ b/app/src/main/org/runnerup/view/FeedActivity.java @@ -312,7 +312,7 @@ public void update(Observable observable, Object data) { feedAdapter.notifyDataSetChanged(); } else { String synchronizerName = (String) data; - feedProgressLabel.setText(String.format(Locale.getDefault(), "%s %s", getString(R.string.Synchronizing), synchronizerName)); //TODO parameter + feedProgressLabel.setText(String.format(Locale.getDefault(), "%s: %s", getString(R.string.Synchronizing), synchronizerName)); //TODO parameter } } } diff --git a/app/src/main/org/runnerup/view/ManageWorkoutsActivity.java b/app/src/main/org/runnerup/view/ManageWorkoutsActivity.java index 14b041710..af4ae841e 100644 --- a/app/src/main/org/runnerup/view/ManageWorkoutsActivity.java +++ b/app/src/main/org/runnerup/view/ManageWorkoutsActivity.java @@ -145,7 +145,7 @@ public void onCreate(Bundle savedInstanceState) { } catch (Exception e) { AlertDialog.Builder builder = new AlertDialog.Builder(this) .setTitle(getString(R.string.Problem)) - .setMessage(getString(R.string.Failed_to_import) + " " + fileName) + .setMessage(getString(R.string.Failed_to_import) + ": " + fileName) .setPositiveButton(getString(R.string.OK_darn), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { @@ -199,7 +199,7 @@ private void importData(final String fileName, final Uri data) throws Exception }; AlertDialog.Builder builder = new AlertDialog.Builder(this) - .setTitle(getString(R.string.Import_workout) + " " + fileName) + .setTitle(getString(R.string.Import_workout) + ": " + fileName) .setPositiveButton(getString(R.string.Yes), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { @@ -580,7 +580,7 @@ public void onClick(View v) { final String name = selected.workoutName; final Intent intent = new Intent(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.RunnerUp_workout) + " " + name); + intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.RunnerUp_workout) + ": " + name); intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.HinHere_is_a_workout_I_think_you_might_like)); intent.setType(WorkoutFileProvider.MIME); diff --git a/common/src/main/res/values-sv/strings.xml b/common/src/main/res/values-sv/strings.xml index 0cce5f374..0c62e6bfc 100644 --- a/common/src/main/res/values-sv/strings.xml +++ b/common/src/main/res/values-sv/strings.xml @@ -120,7 +120,7 @@ Konfigurera konton Uppdatera Ålder - Synkroniserar: + Synkroniserar synkroniserar flöde… OK Hantera kopplingar @@ -291,15 +291,15 @@ Inkludera karta i post Ta bort aktiviteter (från telefon) Min telefon - Kunde inte importera: - Importera träningspass: + Kunde inte importera + Importera träningspass Sparar som - Skriv över existerande: + Skriv över existerande Skapa nytt träningspass Ange namn för träningspass Ladda ner %1$s kommer skriva över existerande träningpass %2$s Ta bort träningspass - RunnerUp träningspass: + RunnerUp träningspass Hej\nHär är ett träningspass jag tror du skulle gilla. Dela träningspass… Nytt ljudschema diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 0d3ed1e07..93cd0a46e 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -120,7 +120,7 @@ Edit accounts Refresh Age - Synchronizing: + Synchronizing synchronizing feed… OK Manage connections @@ -292,15 +292,15 @@ Include map in post Clear uploads (from phone) My phone - Failed to import: - Import workout: - Saving as + Failed to import + Import workout + Saving as Overwrite existing Create new workout Set workout name Downloading %1$s will overwrite %2$s workout with same name - Delete workout - RunnerUp workout: + Delete workout + RunnerUp workout Hi\nHere is a workout I think you might like. Share workout… New audio scheme From 8b0bdd72479c4f564f4c28e22bf31ee04efc2461 Mon Sep 17 00:00:00 2001 From: Gerhard Olsson Date: Sun, 4 Aug 2019 23:38:42 +0200 Subject: [PATCH 14/15] Remove commented out cue_silence --- app/res/xml/audio_cue_settings.xml | 8 ---- .../view/AudioCueSettingsActivity.java | 47 ------------------- 2 files changed, 55 deletions(-) diff --git a/app/res/xml/audio_cue_settings.xml b/app/res/xml/audio_cue_settings.xml index 9086d3fe4..7478e378e 100644 --- a/app/res/xml/audio_cue_settings.xml +++ b/app/res/xml/audio_cue_settings.xml @@ -19,14 +19,6 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" > - Date: Mon, 5 Aug 2019 16:39:13 +0200 Subject: [PATCH 15/15] Countdown steps: Separate from Interval rest step No auto conversion for Resting steps --- app/res/xml/settings.xml | 13 +++++++------ .../org/runnerup/view/ManageWorkoutsActivity.java | 4 +--- .../main/org/runnerup/workout/WorkoutBuilder.java | 2 +- .../org/runnerup/workout/WorkoutSerializer.java | 13 +++++-------- common/src/main/res/values-sv/strings.xml | 6 +++--- common/src/main/res/values/strings.xml | 8 ++++---- 6 files changed, 21 insertions(+), 25 deletions(-) diff --git a/app/res/xml/settings.xml b/app/res/xml/settings.xml index 2b445395a..2f4d76d0a 100644 --- a/app/res/xml/settings.xml +++ b/app/res/xml/settings.xml @@ -242,17 +242,18 @@ + android:key="@string/pref_convert_advanced_distance_rest_to_recovery" + android:title="@string/Convert_rest_on_Advanced_tab" + android:summary="@string/Convert_reststep_with_type_distance_to_recoverystep_for_Advancedtab" /> + android:key="@string/pref_convert_interval_distance_rest_to_recovery" + android:title="@string/Convert_rest_on_Interval_tab" + android:summary="@string/Convert_reststep_with_type_distance_to_recoverystep_for_Intervaltab" /> list = new ArrayList<>(4); while ((step = steps.optJSONObject(stepNo)) != null) { - jsonstep js = parseStep(step, convertRestToRecovery); + jsonstep js = parseStep(step); list.add(js); stepNo++; } @@ -417,7 +417,7 @@ else if (unit.equalsIgnoreCase("metersPerMillisecond")) { } } - private static jsonstep parseStep(JSONObject obj, boolean convertRestToRecovery) throws JSONException { + private static jsonstep parseStep(JSONObject obj) throws JSONException { jsonstep js = new jsonstep(); js.order = obj.getInt("stepOrder"); js.group = getInt(obj, "groupId"); @@ -433,8 +433,7 @@ private static jsonstep parseStep(JSONObject obj, boolean convertRestToRecovery) break; } case RESTING: - boolean rest = !convertRestToRecovery || duration.first != Dimension.DISTANCE || - duration.second == null; + boolean rest = duration.first != Dimension.DISTANCE || duration.second == null; js.step = Step.createRestStep(duration.first, duration.second, !rest); break; case ACTIVE: @@ -465,10 +464,8 @@ public static Workout readFile(Context ctx, String name) throws FileNotFoundExce SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); File fin = getFile(ctx, name); Log.e("WorkoutSerializer", "reading " + fin.getPath()); - final boolean convertRestToRecovery = prefs.getBoolean(ctx.getResources().getString( - R.string.pref_convert_advanced_distance_rest_to_recovery), true); - Workout w = readJSON(new FileReader(fin), convertRestToRecovery); + Workout w = readJSON(new FileReader(fin)); w.sport = prefs.getInt(ctx.getResources().getString(R.string.pref_sport), Constants.DB.ACTIVITY.SPORT_RUNNING); w.setWorkoutType(Constants.WORKOUT_TYPE.ADVANCED); return w; diff --git a/common/src/main/res/values-sv/strings.xml b/common/src/main/res/values-sv/strings.xml index 0c62e6bfc..96c6a4bd0 100644 --- a/common/src/main/res/values-sv/strings.xml +++ b/common/src/main/res/values-sv/strings.xml @@ -169,8 +169,8 @@ Ladda ned/redigera/ta bort träningspass Obs, du måste ansluta till kontot också Använd dina lurar för att starta/pausa/återuppta RunnerUp - För Interval-tabben och nedräkningssteg, använd \"Återhämting\" för \"Vila\" (paus) steg - Använd \"Återhämting\" för \"Vila\" (paus) steg för träningspass + För Interval-tabben, använd \"Återhämting\" för \"Vila\" (paus) steg + För inlagda nedräkningssteg, använd \"Återhämting\" för \"Vila\" (paus) steg Exportera databas till sdkort (t.ex. före uppgradering) Importera databas från sdkort (t.ex. efter uppgradering) Coach för att hjälpa dig nå målet (om mål är satt) @@ -197,7 +197,7 @@ Autopausa efter (s) Autopaus tempo (min/km) Interval vilosteg - Vilosteg i pass + Återhämtning i nedräkningssteg Målåterkoppling flytande medelvärde (s) Målåterkoppling anståndstid (s) Autovarv i pass diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 93cd0a46e..fea5dcdf4 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -170,8 +170,8 @@ Download/edit/remove workouts You need to connect to a RunnerUp Live account Use your headset to start/pause/resume RunnerUp - On the Interval tab and countdown steps, use \"Recovery\" instead of \"Rest\" (pause) steps - For advanced workouts, use \"Recovery\" instead of \"Rest\" (pause) steps + On the Interval tab, use \"Recovery\" instead of \"Rest\" (pause) steps + For inserted countdown steps, use \"Recovery\" instead of \"Rest\" (pause) steps Export database to storage Import database from storage Coach to help you reach target (if having set target) @@ -198,14 +198,14 @@ Autopause after (s) Autopause min pace (min/km) Interval rest steps - Workout rest steps + Recovery in countdown steps Target feedback moving average (s) Target feedback grace period (s) Autolap in workouts Autolap in basic workouts Autolap in last step and \"Active\" steps Countdown after key press - Add \"Recovery\" or \"Rest\" countdown step after steps that ends with user key press + Add a countdown \"Recovery\" or \"Rest\" step after a step that ends with user key press Step countdown time (s) Smooth pace graph Smooth pace filter (s)