diff --git a/README.md b/README.md
index 7ec6252e..fdd6b6ad 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ An android application created for /r/bodyweightfitness subreddit.
Simple interface for accessing multiple exercise videos and up-to-date beginner routine with a handy timer helpful to finish the exercise.
-
+
### LICENSE
diff --git a/app/build.gradle b/app/build.gradle
index 0443834f..88390b86 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -33,14 +33,14 @@ android {
productFlavors {
pro {
applicationId 'io.mazur.fit.pro'
- versionCode 100
- versionName "1.0.0"
+ versionCode 101
+ versionName "1.0.1"
}
free {
applicationId 'io.mazur.fit.free'
- versionCode 100
- versionName "1.0.0"
+ versionCode 101
+ versionName "1.0.1"
}
}
@@ -52,6 +52,9 @@ android {
}
dependencies {
+ testCompile 'junit:junit:4.12'
+ testCompile 'org.mockito:mockito-core:1.9.5'
+
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:support-v4:22.2.0'
@@ -60,7 +63,7 @@ dependencies {
compile 'com.android.support:design:22.2.0'
compile 'com.google.code.gson:gson:2.3.1'
-
+ compile 'com.github.mazurio:glacier:0.0.3'
compile 'net.danlew:android.joda:2.7.1'
compile 'commons-io:commons-io:2.4'
diff --git a/app/src/main/java/io/mazur/fit/App.java b/app/src/main/java/io/mazur/fit/App.java
index 3d69d9e7..d4fbf7b9 100644
--- a/app/src/main/java/io/mazur/fit/App.java
+++ b/app/src/main/java/io/mazur/fit/App.java
@@ -4,9 +4,9 @@
import android.content.Context;
import com.crashlytics.android.Crashlytics;
-
import net.danlew.android.joda.JodaTimeAndroid;
+import io.mazur.glacier.Glacier;
import io.fabric.sdk.android.Fabric;
public class App extends Application {
@@ -16,7 +16,9 @@ public class App extends Application {
public void onCreate() {
super.onCreate();
- JodaTimeAndroid.init(this);
+ Glacier.init(getApplicationContext());
+ JodaTimeAndroid.init(getApplicationContext());
+
if(!BuildConfig.DEBUG) {
Fabric.with(this, new Crashlytics());
}
diff --git a/app/src/main/java/io/mazur/fit/Constants.java b/app/src/main/java/io/mazur/fit/Constants.java
index 814db934..d64063a7 100644
--- a/app/src/main/java/io/mazur/fit/Constants.java
+++ b/app/src/main/java/io/mazur/fit/Constants.java
@@ -1,5 +1,7 @@
package io.mazur.fit;
public class Constants {
+ public static final String PREFERENCE_PLAY_SOUND_WHEN_TIMER_STOPS = "PREFERENCE_PLAY_SOUND_WHEN_TIMER_STOPS";
+ public static final String PREFERENCE_KEEP_SCREEN_ON = "PREFERENCE_KEEP_SCREEN_ON";
public static final String PREFERENCE_TIMER_KEY = "PREFERENCE_TIMER_KEY";
}
diff --git a/app/src/main/java/io/mazur/fit/adapter/RoutineAdapter.java b/app/src/main/java/io/mazur/fit/adapter/RoutineAdapter.java
index 1c32fb3c..d0c70162 100644
--- a/app/src/main/java/io/mazur/fit/adapter/RoutineAdapter.java
+++ b/app/src/main/java/io/mazur/fit/adapter/RoutineAdapter.java
@@ -8,9 +8,14 @@
import butterknife.ButterKnife;
import butterknife.InjectView;
+
import io.mazur.fit.R;
+import io.mazur.fit.model.Category;
import io.mazur.fit.model.Routine;
+import io.mazur.fit.model.Exercise;
+import io.mazur.fit.model.LinkedRoutine;
import io.mazur.fit.model.RoutineType;
+import io.mazur.fit.model.Section;
import io.mazur.fit.stream.RoutineStream;
public class RoutineAdapter extends RecyclerView.Adapter {
@@ -35,7 +40,7 @@ public RoutinePresenter onCreateViewHolder(ViewGroup parent, int viewType) {
@Override
public void onBindViewHolder(RoutinePresenter holder, int position) {
- holder.onBindView(mRoutine.getPartRoutines().get(position));
+ holder.onBindView(mRoutine.getLinkedRoutine().get(position));
}
@Override
@@ -44,12 +49,12 @@ public int getItemCount() {
return 0;
}
- return mRoutine.getSize();
+ return mRoutine.getLinkedRoutine().size();
}
@Override
public int getItemViewType(int position) {
- return mRoutine.getPartRoutines().get(position).getType().ordinal();
+ return mRoutine.getLinkedRoutine().get(position).getType().ordinal();
}
public void setRoutine(Routine routine) {
@@ -58,16 +63,17 @@ public void setRoutine(Routine routine) {
notifyDataSetChanged();
}
- public abstract class RoutinePresenter extends RecyclerView.ViewHolder {
+ public abstract class RoutinePresenter extends RecyclerView.ViewHolder {
public RoutinePresenter(View itemView) {
super(itemView);
+
ButterKnife.inject(this, itemView);
}
- public abstract void onBindView(Routine.PartRoutine partRoutine);
+ public abstract void onBindView(T linkedRoutine);
}
- public class RoutineCategoryPresenter extends RoutinePresenter {
+ public class RoutineCategoryPresenter extends RoutinePresenter {
@InjectView(R.id.routine_category_text_view) TextView mRoutineCategoryTextView;
public RoutineCategoryPresenter(View itemView) {
@@ -75,25 +81,28 @@ public RoutineCategoryPresenter(View itemView) {
}
@Override
- public void onBindView(Routine.PartRoutine partRoutine) {
- mRoutineCategoryTextView.setText(partRoutine.getTitle());
+ public void onBindView(Category category) {
+ mRoutineCategoryTextView.setText(category.getTitle());
}
}
- public class RoutineSectionPresenter extends RoutinePresenter {
+ public class RoutineSectionPresenter extends RoutinePresenter {
@InjectView(R.id.routine_section_text_view) TextView mRoutineSectionTextView;
+ @InjectView(R.id.routine_section_description_text_view) TextView mRoutineSectionDescriptionTextView;
public RoutineSectionPresenter(View itemView) {
super(itemView);
}
@Override
- public void onBindView(Routine.PartRoutine partRoutine) {
- mRoutineSectionTextView.setText(partRoutine.getTitle());
+ public void onBindView(Section section) {
+ mRoutineSectionTextView.setText(section.getTitle());
+ mRoutineSectionDescriptionTextView.setText(section.getDescription());
}
}
- public class RoutineExercisePresenter extends RoutinePresenter {
+ public class RoutineExercisePresenter extends RoutinePresenter {
+ @InjectView(R.id.routine_exercise_level_text_view) TextView mRoutineExerciseLevelTextView;
@InjectView(R.id.routine_exercise_text_view) TextView mRoutineExerciseTextView;
public RoutineExercisePresenter(View itemView) {
@@ -101,23 +110,30 @@ public RoutineExercisePresenter(View itemView) {
}
@Override
- public void onBindView(Routine.PartRoutine partRoutine) {
- mRoutineExerciseTextView.setText(partRoutine.getTitle());
+ public void onBindView(Exercise exercise) {
+ if(exercise.getLevel() == null) {
+ mRoutineExerciseLevelTextView.setVisibility(View.GONE);
+ } else {
+ mRoutineExerciseLevelTextView.setVisibility(View.VISIBLE);
+ mRoutineExerciseLevelTextView.setText(exercise.getLevel());
+ }
+
+ mRoutineExerciseTextView.setText(exercise.getTitle());
- itemView.setOnClickListener(new OnItemClickListener(partRoutine));
+ itemView.setOnClickListener(new OnItemClickListener(exercise));
}
}
public class OnItemClickListener implements View.OnClickListener {
- private Routine.PartRoutine mPartRoutine;
+ private LinkedRoutine mLinkedRoutine;
- public OnItemClickListener(Routine.PartRoutine partRoutine) {
- mPartRoutine = partRoutine;
+ public OnItemClickListener(LinkedRoutine linkedRoutine) {
+ mLinkedRoutine = linkedRoutine;
}
@Override
public void onClick(View v) {
- RoutineStream.getInstance().setExercise(mPartRoutine);
+ RoutineStream.getInstance().setExercise((Exercise) mLinkedRoutine);
}
}
}
diff --git a/app/src/main/java/io/mazur/fit/model/ActivityState.java b/app/src/main/java/io/mazur/fit/model/ActivityState.java
new file mode 100644
index 00000000..3cb95cda
--- /dev/null
+++ b/app/src/main/java/io/mazur/fit/model/ActivityState.java
@@ -0,0 +1,7 @@
+package io.mazur.fit.model;
+
+public enum ActivityState {
+ OnCreate,
+ OnPause,
+ OnStop
+}
diff --git a/app/src/main/java/io/mazur/fit/model/Category.java b/app/src/main/java/io/mazur/fit/model/Category.java
new file mode 100644
index 00000000..4686fc55
--- /dev/null
+++ b/app/src/main/java/io/mazur/fit/model/Category.java
@@ -0,0 +1,32 @@
+package io.mazur.fit.model;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+public class Category extends LinkedRoutine implements Serializable {
+ private String mTitle;
+
+ private ArrayList mSections = new ArrayList<>();
+
+ public Category(String title) {
+ mTitle = title;
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public RoutineType getType() {
+ return RoutineType.CATEGORY;
+ }
+
+ public void insertSection(Section section) {
+ section.setCategory(this);
+
+ mSections.add(section);
+ }
+
+ public ArrayList getSections() {
+ return mSections;
+ }
+}
diff --git a/app/src/main/java/io/mazur/fit/model/Exercise.java b/app/src/main/java/io/mazur/fit/model/Exercise.java
new file mode 100644
index 00000000..3d91b4de
--- /dev/null
+++ b/app/src/main/java/io/mazur/fit/model/Exercise.java
@@ -0,0 +1,83 @@
+package io.mazur.fit.model;
+
+import java.io.Serializable;
+
+public class Exercise extends LinkedRoutine implements Serializable {
+ private String mId;
+ private String mLevel;
+ private String mTitle;
+ private String mDescription;
+
+ private Category mCategory;
+ private Section mSection;
+
+ private Exercise mPrevious;
+ private Exercise mNext;
+
+ public Exercise(String id, String level, String title, String description) {
+ mId = id;
+ mLevel = level;
+ mTitle = title;
+ mDescription = description;
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ public String getLevel() {
+ return mLevel;
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public RoutineType getType() {
+ return RoutineType.EXERCISE;
+ }
+
+ public String getDescription() {
+ return mDescription;
+ }
+
+ public void setCategory(Category category) {
+ mCategory = category;
+ }
+
+ public Category getCategory() {
+ return mCategory;
+ }
+
+ public void setSection(Section section) {
+ mSection = section;
+ }
+
+ public Section getSection() {
+ return mSection;
+ }
+
+ public boolean isPrevious() {
+ return mPrevious != null;
+ }
+
+ public void setPrevious(Exercise previous) {
+ mPrevious = previous;
+ }
+
+ public Exercise getPrevious() {
+ return mPrevious;
+ }
+
+ public boolean isNext() {
+ return mNext != null;
+ }
+
+ public void setNext(Exercise next) {
+ mNext = next;
+ }
+
+ public Exercise getNext() {
+ return mNext;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/mazur/fit/model/JSONRoutine.java b/app/src/main/java/io/mazur/fit/model/JSONRoutine.java
new file mode 100644
index 00000000..aa649bb4
--- /dev/null
+++ b/app/src/main/java/io/mazur/fit/model/JSONRoutine.java
@@ -0,0 +1,81 @@
+package io.mazur.fit.model;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.ArrayList;
+
+public class JSONRoutine {
+ @SerializedName("routine")
+ private ArrayList mJSONLinkedRoutines = new ArrayList<>();
+
+ public ArrayList getPartRoutines() {
+ return mJSONLinkedRoutines;
+ }
+
+ public int getSize() {
+ if(mJSONLinkedRoutines != null) {
+ return mJSONLinkedRoutines.size();
+ }
+
+ return 0;
+ }
+
+ public class JSONLinkedRoutine {
+ @SerializedName("id")
+ private String mId;
+
+ @SerializedName("level")
+ private String mLevel;
+
+ @SerializedName("title")
+ private String mTitle;
+
+ @SerializedName("description")
+ private String mDescription;
+
+ @SerializedName("type")
+ private String mType;
+
+ /**
+ * Mode can be one of those: All, Pick, Levels
+ */
+ @SerializedName("mode")
+ private String mMode;
+
+ public String getId() {
+ return mId;
+ }
+
+ public String getLevel() {
+ return mLevel;
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public String getDescription() {
+ return mDescription;
+ }
+
+ public RoutineType getType() {
+ if(mType.matches("category")) {
+ return RoutineType.CATEGORY;
+ } else if(mType.matches("section")) {
+ return RoutineType.SECTION;
+ } else {
+ return RoutineType.EXERCISE;
+ }
+ }
+
+ public SectionMode getMode() {
+ if(mMode.matches("all")) {
+ return SectionMode.ALL;
+ } else if(mMode.matches("pick")) {
+ return SectionMode.PICK;
+ } else {
+ return SectionMode.LEVELS;
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/io/mazur/fit/model/LinkedRoutine.java b/app/src/main/java/io/mazur/fit/model/LinkedRoutine.java
new file mode 100644
index 00000000..dbbec545
--- /dev/null
+++ b/app/src/main/java/io/mazur/fit/model/LinkedRoutine.java
@@ -0,0 +1,8 @@
+package io.mazur.fit.model;
+
+import java.io.Serializable;
+
+public abstract class LinkedRoutine implements Serializable {
+ public abstract String getTitle();
+ public abstract RoutineType getType();
+}
diff --git a/app/src/main/java/io/mazur/fit/model/Routine.java b/app/src/main/java/io/mazur/fit/model/Routine.java
index 34220e12..873e27b6 100644
--- a/app/src/main/java/io/mazur/fit/model/Routine.java
+++ b/app/src/main/java/io/mazur/fit/model/Routine.java
@@ -1,105 +1,184 @@
package io.mazur.fit.model;
-import com.google.gson.annotations.SerializedName;
-
+import java.io.Serializable;
import java.util.ArrayList;
-import java.util.LinkedList;
-public class Routine {
- @SerializedName("routine")
- private ArrayList mPartRoutines = new ArrayList<>();
+import io.mazur.glacier.Glacier;
+
+public class Routine implements Serializable {
+ /**
+ * Categories are treated as top level parent for the sections and exercises.
+ */
+ private ArrayList mCategories = new ArrayList<>();
+
+ /**
+ * All sections put together inside a list.
+ */
+ private ArrayList mSections = new ArrayList<>();
+
+ /**
+ * All exercises put together inside a list, used to link exercises after changes.
+ */
+ private ArrayList mExercises = new ArrayList<>();
+
+ /**
+ * Linked exercises for switching between them. This could and should be implemented as an
+ * independent screen manager later which allows linking different views together
+ * (e.g. rest view or summary).
+ */
+ private ArrayList mLinkedExercises = new ArrayList<>();
+
+ /**
+ * Linked routine for displaying views in the recycler view as we can just request
+ * type of the object.
+ */
+ private ArrayList mLinkedRoutine = new ArrayList<>();
+
+ public Routine(JSONRoutine JSONRoutine) {
+ Category currentCategory = null;
+ Section currentSection = null;
+ Exercise currentExercise = null;
+
+ for(io.mazur.fit.model.JSONRoutine.JSONLinkedRoutine JSONLinkedRoutine : JSONRoutine.getPartRoutines()) {
+ /**
+ * Update categories in both lists.
+ */
+ if(JSONLinkedRoutine.getType() == RoutineType.CATEGORY) {
+ currentCategory = new Category(JSONLinkedRoutine.getTitle());
+
+ mCategories.add(currentCategory);
+ mLinkedRoutine.add(currentCategory);
+ }
- public ArrayList getPartRoutines() {
- return mPartRoutines;
- }
+ /**
+ * Updated sections for each category and add into linked routine.
+ */
+ else if(JSONLinkedRoutine.getType() == RoutineType.SECTION) {
+ currentSection = new Section(
+ JSONLinkedRoutine.getTitle(),
+ JSONLinkedRoutine.getDescription(),
+ JSONLinkedRoutine.getMode());
- public PartRoutine getFirstExercise() {
- for(PartRoutine partRoutine : getPartRoutines()) {
- if(partRoutine.getType() == RoutineType.EXERCISE) {
- return partRoutine;
- }
- }
+ currentCategory.insertSection(currentSection);
- return new PartRoutine();
- }
+ mSections.add(currentSection);
+ mLinkedRoutine.add(currentSection);
+ }
- public int getSize() {
- if(mPartRoutines != null) {
- return mPartRoutines.size();
+ /**
+ * Update exercises for each section, link them together.
+ */
+ else if(JSONLinkedRoutine.getType() == RoutineType.EXERCISE) {
+ Exercise exercise = new Exercise(
+ JSONLinkedRoutine.getId(),
+ JSONLinkedRoutine.getLevel(),
+ JSONLinkedRoutine.getTitle(),
+ JSONLinkedRoutine.getDescription());
+
+ /**
+ * Add exercise into a section.
+ */
+ currentSection.insertExercise(exercise);
+
+ /**
+ * List contains all exercises, no matter what level.
+ */
+ mExercises.add(exercise);
+
+ /**
+ * Add only first level of exercise when linking.
+ */
+ if(currentSection.getSectionMode() == SectionMode.LEVELS || currentSection.getSectionMode() == SectionMode.PICK) {
+ String currentExerciseId = Glacier.get(currentSection.getTitle(), String.class);
+
+ if(currentExerciseId != null) {
+ if(exercise.getId().matches(currentExerciseId)) {
+ mLinkedExercises.add(exercise);
+
+ exercise.setPrevious(currentExercise);
+
+ if(currentExercise != null) {
+ currentExercise.setNext(exercise);
+ }
+
+ currentExercise = exercise;
+ currentSection.setCurrentLevel(exercise);
+ }
+ } else {
+ if(currentSection.getExercises().size() == 1) {
+ mLinkedExercises.add(exercise);
+
+ exercise.setPrevious(currentExercise);
+
+ if(currentExercise != null) {
+ currentExercise.setNext(exercise);
+ }
+
+ currentExercise = exercise;
+ }
+ }
+ } else {
+ exercise.setPrevious(currentExercise);
+
+ if(currentExercise != null) {
+ currentExercise.setNext(exercise);
+ }
+
+ currentExercise = exercise;
+
+ mLinkedExercises.add(exercise);
+ }
+
+ /**
+ * Linked Routine contains all exercises.
+ */
+ mLinkedRoutine.add(exercise);
+ }
}
-
- return 0;
}
- public class PartRoutine {
- @SerializedName("id")
- private String mId;
-
- @SerializedName("title")
- private String mTitle;
-
- @SerializedName("description")
- private String mDescription;
-
- @SerializedName("type")
- private String mType;
-
- private PartRoutine mPrevious;
+ public ArrayList getCategories() {
+ return mCategories;
+ }
- private PartRoutine mNext;
+ public ArrayList getSections() {
+ return mSections;
+ }
- public String getId() {
- return mId;
- }
+ public ArrayList getExercises() {
+ return mExercises;
+ }
- public String getTitle() {
- return mTitle;
- }
+ public ArrayList getLinkedExercises() {
+ return mLinkedExercises;
+ }
- public String getDescription() {
- return mDescription;
- }
+ /**
+ * Set level of the exercise in given section.
+ */
+ public void setLevel(Exercise exercise, int level) {
+ Exercise currentSectionExercise = exercise.getSection().getCurrentExercise();
- public RoutineType getType() {
- if(mType.matches("category")) {
- return RoutineType.CATEGORY;
- } else if(mType.matches("section")) {
- return RoutineType.SECTION;
- } else {
- return RoutineType.EXERCISE;
+ if(currentSectionExercise != exercise) {
+ if(currentSectionExercise.getPrevious() != null) {
+ currentSectionExercise.getPrevious().setNext(exercise);
}
- }
-
- public void setPrevious(PartRoutine mPrevious) {
- this.mPrevious = mPrevious;
- }
-
- public PartRoutine getPrevious() {
- return mPrevious;
- }
- public void setNext(PartRoutine mNext) {
- this.mNext = mNext;
- }
-
- public PartRoutine getNext() {
- return mNext;
- }
-
- public boolean isPrevious() {
- if(mPrevious == null) {
- return false;
+ if(currentSectionExercise.getNext() != null) {
+ currentSectionExercise.getNext().setPrevious(exercise);
}
- return true;
- }
+ exercise.setPrevious(currentSectionExercise.getPrevious());
+ exercise.setNext(currentSectionExercise.getNext());
- public boolean isNext() {
- if(mNext == null) {
- return false;
- }
+ exercise.getSection().setCurrentLevel(level);
- return true;
+ currentSectionExercise.setPrevious(null);
+ currentSectionExercise.setNext(null);
}
}
+
+ public ArrayList getLinkedRoutine() {
+ return mLinkedRoutine;
+ }
}
diff --git a/app/src/main/java/io/mazur/fit/model/RoutineType.java b/app/src/main/java/io/mazur/fit/model/RoutineType.java
index d6efaf6c..86419050 100644
--- a/app/src/main/java/io/mazur/fit/model/RoutineType.java
+++ b/app/src/main/java/io/mazur/fit/model/RoutineType.java
@@ -1,6 +1,8 @@
package io.mazur.fit.model;
-public enum RoutineType {
+import java.io.Serializable;
+
+public enum RoutineType implements Serializable {
CATEGORY,
SECTION,
EXERCISE;
diff --git a/app/src/main/java/io/mazur/fit/model/Section.java b/app/src/main/java/io/mazur/fit/model/Section.java
new file mode 100644
index 00000000..9ca37983
--- /dev/null
+++ b/app/src/main/java/io/mazur/fit/model/Section.java
@@ -0,0 +1,94 @@
+package io.mazur.fit.model;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+public class Section extends LinkedRoutine implements Serializable {
+ private String mTitle;
+ private String mDescription;
+ private SectionMode mSectionMode;
+
+ private Category mCategory;
+ private int mCurrentLevel = 0;
+
+ private ArrayList mExercises = new ArrayList<>();
+
+ public Section(String title, String description, SectionMode sectionMode) {
+ mTitle = title;
+ mDescription = description;
+ mSectionMode = sectionMode;
+ }
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public String getDescription() {
+ return mDescription;
+ }
+
+ public RoutineType getType() {
+ return RoutineType.SECTION;
+ }
+
+ public SectionMode getSectionMode() {
+ return mSectionMode;
+ }
+
+ public void setCategory(Category category) {
+ mCategory = category;
+ }
+
+ public Category getCategory() {
+ return mCategory;
+ }
+
+ public void setCurrentLevel(int level) {
+ if(getSectionMode() == SectionMode.LEVELS || getSectionMode() == SectionMode.PICK) {
+ if(level < 0 || level >= getExercises().size()) {
+ return;
+ }
+
+ mCurrentLevel = level;
+ }
+ }
+
+ public void setCurrentLevel(Exercise exercise) {
+ int current = 0;
+ for(Exercise ex : getExercises()) {
+ if(ex.equals(exercise)) {
+ mCurrentLevel = current;
+ return;
+ }
+
+ current++;
+ }
+ }
+
+ public int getCurrentLevel() {
+ return mCurrentLevel;
+ }
+
+ public int getAvailableLevels() {
+ if(getSectionMode() == SectionMode.LEVELS || getSectionMode() == SectionMode.PICK) {
+ return getExercises().size();
+ } else {
+ return 0;
+ }
+ }
+
+ public void insertExercise(Exercise exercise) {
+ exercise.setCategory(mCategory);
+ exercise.setSection(this);
+
+ mExercises.add(exercise);
+ }
+
+ public Exercise getCurrentExercise() {
+ return mExercises.get(mCurrentLevel);
+ }
+
+ public ArrayList getExercises() {
+ return mExercises;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/mazur/fit/model/SectionMode.java b/app/src/main/java/io/mazur/fit/model/SectionMode.java
new file mode 100644
index 00000000..464564b9
--- /dev/null
+++ b/app/src/main/java/io/mazur/fit/model/SectionMode.java
@@ -0,0 +1,15 @@
+package io.mazur.fit.model;
+
+import java.io.Serializable;
+
+public enum SectionMode implements Serializable {
+ PICK,
+ ALL,
+ LEVELS;
+
+ private static SectionMode[] values = SectionMode.values();
+
+ public static SectionMode fromInt(int value) {
+ return values[value];
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/mazur/fit/model/TimerState.java b/app/src/main/java/io/mazur/fit/model/TimerState.java
index 1fae82ea..94edafac 100644
--- a/app/src/main/java/io/mazur/fit/model/TimerState.java
+++ b/app/src/main/java/io/mazur/fit/model/TimerState.java
@@ -2,5 +2,6 @@
public enum TimerState {
STARTED,
- PAUSED
+ PAUSED,
+ FORCE_STOPPED
}
diff --git a/app/src/main/java/io/mazur/fit/presenter/TimerPresenter.java b/app/src/main/java/io/mazur/fit/presenter/TimerPresenter.java
index ca99212b..09fee967 100644
--- a/app/src/main/java/io/mazur/fit/presenter/TimerPresenter.java
+++ b/app/src/main/java/io/mazur/fit/presenter/TimerPresenter.java
@@ -1,6 +1,7 @@
package io.mazur.fit.presenter;
import android.app.TimePickerDialog;
+import android.media.MediaPlayer;
import android.os.CountDownTimer;
import android.view.View;
@@ -12,7 +13,9 @@
import io.mazur.fit.R;
import io.mazur.fit.model.DialogState;
import io.mazur.fit.model.TimerState;
+import io.mazur.fit.stream.ActivityStream;
import io.mazur.fit.stream.RoutineStream;
+import io.mazur.fit.utils.Logger;
import io.mazur.fit.utils.PreferenceUtil;
import io.mazur.fit.view.TimerView;
@@ -39,20 +42,47 @@ public class TimerPresenter implements Serializable {
public void onCreateView(TimerView timerView) {
mTimerView = timerView;
- RoutineStream.getInstance().getExerciseObservable().subscribe(routine -> {
- if(routine.isPrevious()) {
+ if(mTimerState == TimerState.FORCE_STOPPED) {
+ mTimerState = TimerState.STARTED;
+ }
+
+ /**
+ * Allows to cancel the timer when activity is paused.
+ */
+ ActivityStream.getInstance().getObservable().subscribe(activityState -> {
+ switch(mTimerState) {
+ case STARTED: {
+ mTimerState = TimerState.FORCE_STOPPED;
+
+ mTimerView.getStartStopTimerButton().setImageDrawable(
+ mTimerView.getResources().getDrawable(R.drawable.ic_play)
+ );
+
+ mTimeInMillis = mTime.getMillis();
+
+ if(mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ }
+
+ break;
+ }
+ }
+ });
+
+ RoutineStream.getInstance().getExerciseObservable().subscribe(exercise -> {
+ if(exercise.isPrevious()) {
mTimerView.getPrevExerciseButton().setVisibility(View.VISIBLE);
mTimerView.getPrevExerciseButton().setOnClickListener(v -> {
- RoutineStream.getInstance().setExercise(routine.getPrevious());
+ RoutineStream.getInstance().setExercise(exercise.getPrevious());
});
} else {
mTimerView.getPrevExerciseButton().setVisibility(View.INVISIBLE);
}
- if(routine.isNext()) {
+ if(exercise.isNext()) {
mTimerView.getNextExerciseButton().setVisibility(View.VISIBLE);
mTimerView.getNextExerciseButton().setOnClickListener(v -> {
- RoutineStream.getInstance().setExercise(routine.getNext());
+ RoutineStream.getInstance().setExercise(exercise.getNext());
});
} else {
mTimerView.getNextExerciseButton().setVisibility(View.INVISIBLE);
@@ -65,6 +95,13 @@ public void onCreateView(TimerView timerView) {
long minutes = ONE_MINUTE * hourOfDay;
long seconds = ONE_SECOND * minute;
+ /**
+ * If user chooses 0 minutes and 0 seconds, let's set default value to 15 seconds.
+ */
+ if(minutes == 0 && seconds <= 15000) {
+ seconds = 15000;
+ }
+
mTimerState = TimerState.PAUSED;
mTimeInMillis = minutes + seconds;
mTime = new DateTime(mTimeInMillis);
@@ -81,10 +118,9 @@ public void onCreateView(TimerView timerView) {
if(mCountDownTimer != null) {
mCountDownTimer.cancel();
}
-
- // set those (chosen values) in prefences using PreferenceUtil.getInstance()
}, mTime.getMinuteOfHour(), mTime.getSecondOfMinute(), true);
+ mTimePickerDialog.setOnDismissListener(d -> mDialogState = DialogState.HIDDEN);
mTimePickerDialog.setOnCancelListener(d -> mDialogState = DialogState.HIDDEN);
RoutineStream.getInstance().getExerciseChangedObservable().subscribe(exercise -> {
@@ -104,6 +140,7 @@ public void onCreateView(TimerView timerView) {
mTimerView.getIncreaseTimerButton().setOnClickListener(v -> {
switch(mTimerState) {
+ case FORCE_STOPPED:
case PAUSED: {
mTimerState = TimerState.PAUSED;
mTimeInMillis = mTimeInMillis + (ONE_SECOND * 5);
@@ -143,6 +180,7 @@ public void onCreateView(TimerView timerView) {
mTimerView.getStartStopTimerButton().setOnClickListener(v -> {
switch(mTimerState) {
+ case FORCE_STOPPED:
case PAUSED: {
mTimerState = TimerState.STARTED;
@@ -180,6 +218,7 @@ public void onCreateView(TimerView timerView) {
mTimerView.getRestartTimerButton().setOnClickListener(v -> restartTimer());
switch(mTimerState) {
+ case FORCE_STOPPED:
case PAUSED: {
mTimerView.getStartStopTimerButton().setImageDrawable(
mTimerView.getResources().getDrawable(R.drawable.ic_play)
@@ -243,6 +282,12 @@ public void onFinish() {
mTimerView.getTimerMinutesTextView().setText(DateTimeFormat.forPattern("mm").print(mTime));
mTimerView.getTimerSecondsTextView().setText(DateTimeFormat.forPattern("ss").print(mTime));
+
+ if(PreferenceUtil.getInstance().playSoundWhenTimerStops()) {
+ MediaPlayer p = MediaPlayer.create(mTimerView.getContext(), R.raw.finished);
+ p.setLooping(false);
+ p.start();
+ }
}
};
}
diff --git a/app/src/main/java/io/mazur/fit/stream/ActivityStream.java b/app/src/main/java/io/mazur/fit/stream/ActivityStream.java
new file mode 100644
index 00000000..659b7867
--- /dev/null
+++ b/app/src/main/java/io/mazur/fit/stream/ActivityStream.java
@@ -0,0 +1,29 @@
+package io.mazur.fit.stream;
+
+import io.mazur.fit.model.ActivityState;
+import rx.Observable;
+import rx.subjects.PublishSubject;
+
+public class ActivityStream {
+ private static ActivityStream sInstance;
+
+ private final PublishSubject mActivitySubject = PublishSubject.create();
+
+ private ActivityStream() {}
+
+ public static ActivityStream getInstance() {
+ if(sInstance == null) {
+ sInstance = new ActivityStream();
+ }
+
+ return sInstance;
+ }
+
+ public void setActivityState(ActivityState activityState) {
+ mActivitySubject.onNext(activityState);
+ }
+
+ public Observable getObservable() {
+ return mActivitySubject;
+ }
+}
diff --git a/app/src/main/java/io/mazur/fit/stream/RoutineStream.java b/app/src/main/java/io/mazur/fit/stream/RoutineStream.java
index 7d29b680..fe975806 100644
--- a/app/src/main/java/io/mazur/fit/stream/RoutineStream.java
+++ b/app/src/main/java/io/mazur/fit/stream/RoutineStream.java
@@ -7,11 +7,15 @@
import java.io.IOException;
import io.mazur.fit.App;
+import io.mazur.fit.Constants;
import io.mazur.fit.R;
import io.mazur.fit.model.Routine;
-import io.mazur.fit.model.RoutineType;
+import io.mazur.fit.model.Exercise;
+import io.mazur.fit.model.JSONRoutine;
import io.mazur.fit.utils.Logger;
+import io.mazur.glacier.Duration;
+import io.mazur.glacier.Glacier;
import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
@@ -21,45 +25,31 @@ public class RoutineStream {
private static RoutineStream sInstance;
private Routine mRoutine;
- private Routine.PartRoutine mExercise;
+ private Exercise mExercise;
private final PublishSubject mRoutineSubject = PublishSubject.create();
- private final PublishSubject mExerciseSubject = PublishSubject.create();
+ private final PublishSubject mExerciseSubject = PublishSubject.create();
private RoutineStream() {
+ /**
+ * We should save levels only, not the whole object
+ * as we will never get any updates when JSON is udpated.
+ */
try {
- mRoutine = new Gson().fromJson(IOUtils.toString(App.getContext()
- .getResources()
- .openRawResource(R.raw.beginner_routine)
- ), Routine.class);
-
- /**
- * TODO: Link Routine, I don't like how this is done at the moment.
- * TODO: There is definitely a better way.
- *
- * Allows to link objects inside the Array List together so we can have previous
- * and next buttons inside the application.
- */
- Routine.PartRoutine prev = null;
- for(Routine.PartRoutine partRoutine : mRoutine.getPartRoutines()) {
- if(partRoutine.getType() == RoutineType.EXERCISE) {
- if(prev != null) {
- partRoutine.setPrevious(prev);
-
- prev.setNext(partRoutine);
- }
-
- prev = partRoutine;
- }
- }
+ JSONRoutine jsonRoutine = new Gson().fromJson(IOUtils.toString(App.getContext()
+ .getResources()
+ .openRawResource(R.raw.beginner_routine)
+ ), JSONRoutine.class);
+
+ mRoutine = new Routine(jsonRoutine);
+ } catch(IOException e) {
+ Logger.e("Exception when loading Beginner Routine from JSON file: " + e.getMessage());
+ }
- mExercise = mRoutine.getFirstExercise();
+ mExercise = mRoutine.getLinkedExercises().get(0);
- mRoutineSubject.onNext(mRoutine);
- mExerciseSubject.onNext(mExercise);
- } catch (IOException e) {
- Logger.e("Exception when loading Beginner Routine from JSON file: " + e);
- }
+ mRoutineSubject.onNext(mRoutine);
+ mExerciseSubject.onNext(mExercise);
}
public static RoutineStream getInstance() {
@@ -84,22 +74,31 @@ public void call(Subscriber super Routine> subscriber) {
return Observable.merge(mRoutineSubject, routineObservable);
}
- public void setExercise(Routine.PartRoutine exercise) {
+ public void setExercise(Exercise exercise) {
mExercise = exercise;
mExerciseSubject.onNext(exercise);
}
- public Observable getExerciseChangedObservable() {
+ public void setLevel(Exercise exercise, int level) {
+ mRoutine.setLevel(exercise, level);
+ setExercise(exercise);
+
+ // We save our current exercise for given section
+ // TODO: This should use section id (not title).
+ Glacier.put(exercise.getSection().getTitle(), exercise.getSection().getCurrentExercise().getId());
+ }
+
+ public Observable getExerciseChangedObservable() {
return mExerciseSubject;
}
/**
* @return Observable that allows to subscribe when only exercise has changed.
*/
- public Observable getExerciseObservable() {
- Observable exerciseObservable = Observable.create(new Observable.OnSubscribe() {
+ public Observable getExerciseObservable() {
+ Observable exerciseObservable = Observable.create(new Observable.OnSubscribe() {
@Override
- public void call(Subscriber super Routine.PartRoutine> subscriber) {
+ public void call(Subscriber super Exercise> subscriber) {
subscriber.onNext(mExercise);
}
}).observeOn(AndroidSchedulers.mainThread()).publish().refCount();
diff --git a/app/src/main/java/io/mazur/fit/ui/MainActivity.java b/app/src/main/java/io/mazur/fit/ui/MainActivity.java
index 95c16c8b..01799373 100644
--- a/app/src/main/java/io/mazur/fit/ui/MainActivity.java
+++ b/app/src/main/java/io/mazur/fit/ui/MainActivity.java
@@ -1,9 +1,10 @@
package io.mazur.fit.ui;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
+import android.net.Uri;
import android.os.Bundle;
-import android.support.design.widget.TabLayout;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
@@ -11,15 +12,21 @@
import android.view.Menu;
import android.view.MenuItem;
+import android.view.WindowManager;
import io.mazur.fit.R;
import io.mazur.fit.fragment.NavigationDrawerFragment;
+import io.mazur.fit.model.ActivityState;
+import io.mazur.fit.model.Exercise;
+import io.mazur.fit.model.SectionMode;
+import io.mazur.fit.stream.ActivityStream;
import io.mazur.fit.stream.RoutineStream;
-import io.mazur.fit.utils.Logger;
-import rx.Subscription;
+import io.mazur.fit.utils.PreferenceUtil;
+import io.mazur.fit.view.ProgressDialog;
-public class MainActivity extends AppCompatActivity {
+public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
private NavigationDrawerFragment mNavigationDrawerFragment;
+ private Exercise mExercise;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -29,8 +36,29 @@ protected void onCreate(Bundle savedInstanceState) {
setToolbar();
setDrawer();
+
+ RoutineStream.getInstance().getExerciseObservable().subscribe(exercise -> {
+ mExercise = exercise;
+ invalidateOptionsMenu();
+ });
+
+ keepScreenOnWhenAppIsRunning();
}
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ ActivityStream.getInstance().setActivityState(ActivityState.OnPause);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ clearFlagKeepScreenOn();
+ }
+
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
@@ -52,23 +80,63 @@ public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
- @Override
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem progressionMenuItem = menu.findItem(R.id.action_progression);
+
+ if(mExercise != null) {
+ if(mExercise.getSection().getSectionMode() == SectionMode.LEVELS || mExercise.getSection().getSectionMode() == SectionMode.PICK) {
+ progressionMenuItem.setVisible(true);
+ } else {
+ progressionMenuItem.setVisible(false);
+ }
+ } else {
+ progressionMenuItem.setVisible(false);
+ }
+
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
public boolean onOptionsItemSelected(MenuItem item) {
if(mNavigationDrawerFragment.getActionBarDrawerToggle().onOptionsItemSelected(item)) {
return true;
}
switch(item.getItemId()) {
- case (R.id.action_settings):
- startActivity(new Intent(getApplicationContext(), SettingsActivity.class));
+ case(R.id.action_progression): {
+ ProgressDialog progressDialog = new ProgressDialog(this, mExercise);
+ progressDialog.show();
+
+ return true;
+ }
+
+ case (R.id.action_faq): {
+ startActivity(new Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse("http://www.reddit.com/r/bodyweightfitness/wiki/faq")));
- return true;
+ return true;
+ }
+
+ case (R.id.action_settings): {
+ startActivity(new Intent(
+ getApplicationContext(),
+ SettingsActivity.class));
+
+ return true;
+ }
}
return super.onOptionsItemSelected(item);
}
- private void setToolbar() {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ keepScreenOnWhenAppIsRunning();
+ }
+
+ private void setToolbar() {
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
@@ -76,6 +144,7 @@ private void setToolbar() {
if(actionBar != null) {
RoutineStream.getInstance().getExerciseObservable().subscribe(exercise -> {
actionBar.setTitle(exercise.getTitle());
+ actionBar.setSubtitle(exercise.getDescription());
});
actionBar.setElevation(0);
@@ -89,4 +158,16 @@ private void setDrawer() {
mNavigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager().findFragmentById(R.id.drawer_fragment);
mNavigationDrawerFragment.setDrawer(R.id.drawer_fragment, (DrawerLayout) findViewById(R.id.drawer_layout));
}
+
+ private void keepScreenOnWhenAppIsRunning() {
+ if(PreferenceUtil.getInstance().keepScreenOnWhenAppIsRunning()) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ clearFlagKeepScreenOn();
+ }
+ }
+
+ private void clearFlagKeepScreenOn() {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
}
diff --git a/app/src/main/java/io/mazur/fit/ui/SettingsActivity.java b/app/src/main/java/io/mazur/fit/ui/SettingsActivity.java
index 3e5389a3..162b81c7 100644
--- a/app/src/main/java/io/mazur/fit/ui/SettingsActivity.java
+++ b/app/src/main/java/io/mazur/fit/ui/SettingsActivity.java
@@ -11,7 +11,8 @@ public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setupActionBar();
+
+ setActionBar();
if(getFragmentManager().findFragmentById(android.R.id.content) == null) {
getFragmentManager().beginTransaction().add(android.R.id.content, new SettingsFragment()).commit();
@@ -29,7 +30,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
- public void setupActionBar() {
+ public void setActionBar() {
ActionBar actionBar = getSupportActionBar();
if(actionBar != null) {
diff --git a/app/src/main/java/io/mazur/fit/utils/PreferenceUtil.java b/app/src/main/java/io/mazur/fit/utils/PreferenceUtil.java
index 3758af7c..78ff81a5 100644
--- a/app/src/main/java/io/mazur/fit/utils/PreferenceUtil.java
+++ b/app/src/main/java/io/mazur/fit/utils/PreferenceUtil.java
@@ -19,6 +19,16 @@ private PreferenceUtil() {
PreferenceManager.setDefaultValues(App.getContext(), R.xml.settings, false);
}
+ public boolean playSoundWhenTimerStops() {
+ return PreferenceManager.getDefaultSharedPreferences(App.getContext())
+ .getBoolean(Constants.PREFERENCE_PLAY_SOUND_WHEN_TIMER_STOPS, true);
+ }
+
+ public boolean keepScreenOnWhenAppIsRunning() {
+ return PreferenceManager.getDefaultSharedPreferences(App.getContext())
+ .getBoolean(Constants.PREFERENCE_KEEP_SCREEN_ON, true);
+ }
+
public void setTimerValue(long value) {
PreferenceManager.getDefaultSharedPreferences(App.getContext()).edit()
.putLong(Constants.PREFERENCE_TIMER_KEY, value).commit();
diff --git a/app/src/main/java/io/mazur/fit/view/CircularProgressBar.java b/app/src/main/java/io/mazur/fit/view/CircularProgressBar.java
new file mode 100644
index 00000000..e61b86ba
--- /dev/null
+++ b/app/src/main/java/io/mazur/fit/view/CircularProgressBar.java
@@ -0,0 +1,334 @@
+package io.mazur.fit.view;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+
+import io.mazur.fit.R;
+
+public class CircularProgressBar extends View {
+ private static final String INSTANCE_STATE_SAVEDSTATE = "saved_state";
+ private static final String INSTANCE_STATE_PROGRESS = "progress";
+ private static final String INSTANCE_STATE_MARKER_PROGRESS = "marker_progress";
+ private static final String INSTANCE_STATE_PROGRESS_BACKGROUND_COLOR = "progress_background_color";
+ private static final String INSTANCE_STATE_PROGRESS_COLOR = "progress_color";
+ private static final String INSTANCE_STATE_THUMB_VISIBLE = "thumb_visible";
+ private static final String INSTANCE_STATE_MARKER_VISIBLE = "marker_visible";
+
+ private final RectF mCircleBounds = new RectF();
+ private Paint mBackgroundColorPaint = new Paint();
+
+ private int mCircleStrokeWidth = 10;
+ private int mGravity = Gravity.CENTER;
+ private int mHorizontalInset = 0;
+
+ private boolean mIsInitializing = true;
+ private boolean mIsThumbEnabled = true;
+
+ private Paint mMarkerColorPaint;
+ private Paint mProgressColorPaint;
+ private Paint mThumbColorPaint = new Paint();
+
+ private float mMarkerProgress = 0.0f;
+ private boolean mOverrdraw = false;
+ private float mProgress = 0.3f;
+ private int mProgressBackgroundColor;
+ private int mProgressColor;
+
+ private float mRadius;
+ private float mThumbPosX;
+ private float mThumbPosY;
+ private int mThumbRadius = 20;
+ private float mTranslationOffsetX;
+ private float mTranslationOffsetY;
+ private int mVerticalInset = 0;
+
+ public CircularProgressBar(final Context context) {
+ this(context, null);
+ }
+
+ public CircularProgressBar(final Context context, final AttributeSet attrs) {
+ this(context, attrs, R.attr.CircularProgressBarStyle);
+ }
+
+ public CircularProgressBar(final Context context, final AttributeSet attrs, final int defStyle) {
+ super(context, attrs, defStyle);
+
+ final TypedArray attributes = context
+ .obtainStyledAttributes(attrs, R.styleable.CircularProgressBar,
+ defStyle, 0);
+ if (attributes != null)
+ {
+ try
+ {
+ setProgressColor(attributes.getColor(R.styleable.CircularProgressBar_progress_color, Color.CYAN));
+ setProgressBackgroundColor(attributes.getColor(R.styleable.CircularProgressBar_progress_background_color,Color.GREEN));
+ setProgress(attributes.getFloat(R.styleable.CircularProgressBar_progress, 0.0f));
+ setWheelSize((int) attributes.getDimension(R.styleable.CircularProgressBar_stroke_width, 10));
+ setThumbEnabled(attributes.getBoolean(R.styleable.CircularProgressBar_thumb_visible, true));
+
+ mGravity = attributes.getInt(R.styleable.CircularProgressBar_android_gravity,Gravity.CENTER);
+ }
+ finally
+ {
+ attributes.recycle();
+ }
+ }
+
+ mThumbRadius = mCircleStrokeWidth * 2;
+ updateBackgroundColor();
+ updateMarkerColor();
+ updateProgressColor();
+
+ // the view has now all properties and can be drawn
+ mIsInitializing = false;
+
+ }
+
+ @Override
+ protected void onDraw(final Canvas canvas) {
+ canvas.translate(mTranslationOffsetX, mTranslationOffsetY);
+ final float progressRotation = getCurrentRotation();
+ // draw the background
+ if (!mOverrdraw)
+ {
+ canvas.drawArc(mCircleBounds, 270, -(360 - progressRotation), false,mBackgroundColorPaint);
+ }
+ // draw the progress or a full circle if overdraw is true
+ canvas.drawArc(mCircleBounds, 270, mOverrdraw ? 360 : progressRotation, false,mProgressColorPaint);
+ // draw the marker at the correct rotated position
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ final int height = getDefaultSize(getSuggestedMinimumHeight() + getPaddingTop() + getPaddingBottom(),heightMeasureSpec);
+ final int width = getDefaultSize(getSuggestedMinimumWidth() + getPaddingLeft() + getPaddingRight(), widthMeasureSpec);
+
+ final int diameter;
+ if (heightMeasureSpec == MeasureSpec.UNSPECIFIED)
+ {
+ // ScrollView
+ diameter = width;
+ computeInsets(0, 0);
+ }
+ else if (widthMeasureSpec == MeasureSpec.UNSPECIFIED)
+ {
+ // HorizontalScrollView
+ diameter = height;
+ computeInsets(0, 0);
+ }
+ else
+ {
+ // Default
+ diameter = Math.min(width, height);
+ computeInsets(width - diameter, height - diameter);
+ }
+
+ setMeasuredDimension(diameter, diameter);
+
+ final float halfWidth = diameter * 0.5f;
+ // width of the drawed circle (+ the drawedThumb)
+ final float drawedWith;
+ if (isThumbEnabled())
+ {
+ drawedWith = mThumbRadius * (5f / 6f);
+ }
+ else
+ {
+ drawedWith = mCircleStrokeWidth / 2f;
+ }
+ // -0.5f for pixel perfect fit inside the viewbounds
+ mRadius = halfWidth - drawedWith - 0.5f;
+
+ mCircleBounds.set(-mRadius, -mRadius, mRadius, mRadius);
+
+ mThumbPosX = (float) (mRadius * Math.cos(0));
+ mThumbPosY = (float) (mRadius * Math.sin(0));
+
+ mTranslationOffsetX = halfWidth + mHorizontalInset;
+ mTranslationOffsetY = halfWidth + mVerticalInset;
+
+ }
+
+ @Override
+ protected void onRestoreInstanceState(final Parcelable state) {
+ if (state instanceof Bundle) {
+ final Bundle bundle = (Bundle) state;
+ setProgress(bundle.getFloat(INSTANCE_STATE_PROGRESS));
+
+ final int progressColor = bundle.getInt(INSTANCE_STATE_PROGRESS_COLOR);
+ if (progressColor != mProgressColor) {
+ mProgressColor = progressColor;
+ updateProgressColor();
+ }
+
+ final int progressBackgroundColor = bundle
+ .getInt(INSTANCE_STATE_PROGRESS_BACKGROUND_COLOR);
+ if (progressBackgroundColor != mProgressBackgroundColor) {
+ mProgressBackgroundColor = progressBackgroundColor;
+ updateBackgroundColor();
+ }
+
+ mIsThumbEnabled = bundle.getBoolean(INSTANCE_STATE_THUMB_VISIBLE);
+
+
+ super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATE_SAVEDSTATE));
+ return;
+ }
+
+ super.onRestoreInstanceState(state);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(INSTANCE_STATE_SAVEDSTATE, super.onSaveInstanceState());
+ bundle.putFloat(INSTANCE_STATE_PROGRESS, mProgress);
+ bundle.putFloat(INSTANCE_STATE_MARKER_PROGRESS, mMarkerProgress);
+ bundle.putInt(INSTANCE_STATE_PROGRESS_COLOR, mProgressColor);
+ bundle.putInt(INSTANCE_STATE_PROGRESS_BACKGROUND_COLOR, mProgressBackgroundColor);
+ bundle.putBoolean(INSTANCE_STATE_THUMB_VISIBLE, mIsThumbEnabled);
+ return bundle;
+ }
+
+ public boolean isThumbEnabled() {
+ return mIsThumbEnabled;
+ }
+
+ public void setProgress(final float progress) {
+ if (progress == mProgress) {
+ mProgress = 0.3f;
+ }
+
+ if (progress == 1) {
+ mOverrdraw = false;
+ mProgress = 1;
+ } else {
+
+ if (progress >= 1) {
+ mOverrdraw = true;
+ } else {
+ mOverrdraw = false;
+ }
+
+ mProgress = progress % 1.0f;
+ }
+
+ if (!mIsInitializing) {
+ invalidate();
+ }
+ }
+
+ public void setProgressBackgroundColor(final int color) {
+ mProgressBackgroundColor = color;
+
+ updateMarkerColor();
+ updateBackgroundColor();
+ }
+
+ public void setProgressColor(final int color) {
+ mProgressColor = color;
+
+ updateProgressColor();
+ }
+
+ public void setThumbEnabled(final boolean enabled) {
+ mIsThumbEnabled = enabled;
+ }
+
+
+ public void setWheelSize(final int dimension) {
+ mCircleStrokeWidth = dimension;
+
+ // update the paints
+ updateBackgroundColor();
+ updateMarkerColor();
+ updateProgressColor();
+ }
+
+ @SuppressLint("NewApi")
+ private void computeInsets(final int dx, final int dy) {
+ int absoluteGravity = mGravity;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ absoluteGravity = Gravity.getAbsoluteGravity(mGravity, getLayoutDirection());
+ }
+
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.LEFT:
+ mHorizontalInset = 0;
+ break;
+ case Gravity.RIGHT:
+ mHorizontalInset = dx;
+ break;
+ case Gravity.CENTER_HORIZONTAL:
+ default:
+ mHorizontalInset = dx / 2;
+ break;
+ }
+ switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
+ case Gravity.TOP:
+ mVerticalInset = 0;
+ break;
+ case Gravity.BOTTOM:
+ mVerticalInset = dy;
+ break;
+ case Gravity.CENTER_VERTICAL:
+ default:
+ mVerticalInset = dy / 2;
+ break;
+ }
+ }
+
+ private float getCurrentRotation() {
+ return 360 * mProgress;
+ }
+
+ private float getMarkerRotation() {
+ return 360 * mMarkerProgress;
+ }
+
+ private void updateBackgroundColor() {
+ mBackgroundColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mBackgroundColorPaint.setColor(mProgressBackgroundColor);
+ mBackgroundColorPaint.setStyle(Paint.Style.STROKE);
+ mBackgroundColorPaint.setStrokeWidth(mCircleStrokeWidth);
+
+ invalidate();
+ }
+
+
+ private void updateMarkerColor() {
+ mMarkerColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mMarkerColorPaint.setColor(mProgressBackgroundColor);
+ mMarkerColorPaint.setStyle(Paint.Style.STROKE);
+ mMarkerColorPaint.setStrokeWidth(mCircleStrokeWidth / 2);
+
+ invalidate();
+ }
+
+ private void updateProgressColor() {
+ mProgressColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mProgressColorPaint.setColor(mProgressColor);
+ mProgressColorPaint.setStyle(Paint.Style.STROKE);
+ mProgressColorPaint.setStrokeWidth(mCircleStrokeWidth);
+
+ mThumbColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mThumbColorPaint.setColor(mProgressColor);
+ mThumbColorPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ mThumbColorPaint.setStrokeWidth(mCircleStrokeWidth);
+
+ invalidate();
+ }
+
+
+}
diff --git a/app/src/main/java/io/mazur/fit/view/ProgressDialog.java b/app/src/main/java/io/mazur/fit/view/ProgressDialog.java
new file mode 100644
index 00000000..9635873f
--- /dev/null
+++ b/app/src/main/java/io/mazur/fit/view/ProgressDialog.java
@@ -0,0 +1,105 @@
+package io.mazur.fit.view;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.Color;
+import android.support.design.widget.FloatingActionButton;
+import android.view.View;
+import android.view.Window;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import butterknife.ButterKnife;
+import butterknife.InjectView;
+
+import io.mazur.fit.R;
+import io.mazur.fit.model.Exercise;
+import io.mazur.fit.model.SectionMode;
+import io.mazur.fit.stream.RoutineStream;
+
+public class ProgressDialog {
+ @InjectView(R.id.level_title_text_view) TextView mLevelTitleTextView;
+ @InjectView(R.id.level_previous_button) ImageButton mLevelPreviousButton;
+ @InjectView(R.id.level_next_button) ImageButton mLevelNextButton;
+ @InjectView(R.id.level_progress_bar) CircularProgressBar mLevelProgressBar;
+ @InjectView(R.id.level_text_view) TextView mLevelTextView;
+ @InjectView(R.id.level_confirm_button) FloatingActionButton mLevelConfirmButton;
+
+ private Dialog mDialog;
+
+ private Exercise mExercise;
+
+ private int mAvailableLevels;
+ private int mChosenLevel = 0;
+
+ public ProgressDialog(Context context, Exercise exercise) {
+ mExercise = exercise;
+ mAvailableLevels = mExercise.getSection().getAvailableLevels();
+ mChosenLevel = mExercise.getSection().getCurrentLevel();
+
+ mDialog = new Dialog(context);
+ mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ mDialog.setContentView(R.layout.view_dialog_level);
+ mDialog.setCanceledOnTouchOutside(true);
+ }
+
+ public void show() {
+ ButterKnife.inject(this, mDialog);
+
+ mLevelPreviousButton.setOnClickListener(v -> {
+ mChosenLevel = mChosenLevel - 1;
+ updateDialog();
+ });
+
+ mLevelNextButton.setOnClickListener(v -> {
+ mChosenLevel = mChosenLevel + 1;
+ updateDialog();
+ });
+
+ mLevelConfirmButton.setOnClickListener(v -> {
+ Exercise chosenExercise = mExercise.getSection().getExercises().get(mChosenLevel);
+
+ RoutineStream.getInstance().setLevel(chosenExercise, mChosenLevel);
+
+ mDialog.dismiss();
+ });
+
+ mLevelProgressBar.setWheelSize(12);
+ mLevelProgressBar.setProgressColor(Color.parseColor("#009688")); // 00453E
+ mLevelProgressBar.setProgressBackgroundColor(Color.parseColor("#00453E"));
+
+ updateDialog();
+
+ mDialog.show();
+ }
+
+ private void updateDialog() {
+ Exercise chosenExercise = mExercise.getSection().getExercises().get(mChosenLevel);
+
+ if(mExercise.getSection().getSectionMode() == SectionMode.LEVELS) {
+ mLevelTitleTextView.setText(chosenExercise.getTitle());
+ mLevelTextView.setText(chosenExercise.getLevel());
+ } else {
+ mLevelTitleTextView.setText(chosenExercise.getTitle());
+ mLevelTextView.setText("Pick One");
+ }
+
+ if(mChosenLevel == 0) {
+ mLevelPreviousButton.setVisibility(View.INVISIBLE);
+ } else {
+ mLevelPreviousButton.setVisibility(View.VISIBLE);
+ }
+
+ if(mChosenLevel >= (mAvailableLevels - 1)) {
+ mLevelNextButton.setVisibility(View.INVISIBLE);
+ } else {
+ mLevelNextButton.setVisibility(View.VISIBLE);
+ }
+
+ updateProgressBar();
+ }
+
+ private void updateProgressBar() {
+ mLevelProgressBar.setProgress((1f / mAvailableLevels) * (mChosenLevel + 1));
+ }
+}
diff --git a/app/src/main/java/io/mazur/fit/view/TimerView.java b/app/src/main/java/io/mazur/fit/view/TimerView.java
index 14047b6e..35d05a43 100644
--- a/app/src/main/java/io/mazur/fit/view/TimerView.java
+++ b/app/src/main/java/io/mazur/fit/view/TimerView.java
@@ -1,6 +1,7 @@
package io.mazur.fit.view;
import android.content.Context;
+import android.os.Build;
import android.os.Parcelable;
import android.support.design.widget.FloatingActionButton;
import android.util.AttributeSet;
@@ -56,6 +57,10 @@ public void onCreate() {
public void onCreateView() {
ButterKnife.inject(this);
+ resetFloatingActionButtonMargin(getIncreaseTimerButton());
+ resetFloatingActionButtonMargin(getStartStopTimerButton());
+ resetFloatingActionButtonMargin(getRestartTimerButton());
+
mTimerPresenter.onCreateView(this);
}
@@ -78,6 +83,14 @@ public void setSaveEnabled(boolean enabled) {
super.setSaveEnabled(true);
}
+ private void resetFloatingActionButtonMargin(FloatingActionButton floatingActionButton) {
+ if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) floatingActionButton.getLayoutParams();
+ layoutParams.setMargins(0, 0, 0, 0);
+ floatingActionButton.setLayoutParams(layoutParams);
+ }
+ }
+
public RelativeLayout getTimerLayout() {
return mTimerLayout;
}
diff --git a/app/src/main/res/drawable-hdpi/level_1.png b/app/src/main/res/drawable-hdpi/level_1.png
new file mode 100644
index 00000000..225c26e4
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/level_1.png differ
diff --git a/app/src/main/res/drawable-mdpi/level_1.png b/app/src/main/res/drawable-mdpi/level_1.png
new file mode 100644
index 00000000..cf95fb0e
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/level_1.png differ
diff --git a/app/src/main/res/drawable-v21/view_dialog_level_drawable.xml b/app/src/main/res/drawable-v21/view_dialog_level_drawable.xml
new file mode 100644
index 00000000..b5e9cb9b
--- /dev/null
+++ b/app/src/main/res/drawable-v21/view_dialog_level_drawable.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-xhdpi/level_1.png b/app/src/main/res/drawable-xhdpi/level_1.png
new file mode 100644
index 00000000..1d32f539
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/level_1.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/level_1.png b/app/src/main/res/drawable-xxhdpi/level_1.png
new file mode 100644
index 00000000..7fc84e22
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/level_1.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_dropdown.png b/app/src/main/res/drawable-xxxhdpi/ic_dropdown.png
new file mode 100755
index 00000000..7b39373b
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_dropdown.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/level_1.png b/app/src/main/res/drawable-xxxhdpi/level_1.png
new file mode 100644
index 00000000..8938fc81
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/level_1.png differ
diff --git a/app/src/main/res/drawable/view_dialog_level_drawable.xml b/app/src/main/res/drawable/view_dialog_level_drawable.xml
new file mode 100644
index 00000000..b5e9cb9b
--- /dev/null
+++ b/app/src/main/res/drawable/view_dialog_level_drawable.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-port/activity_main.xml b/app/src/main/res/layout-port/activity_main.xml
index 8d94b09e..5a236504 100644
--- a/app/src/main/res/layout-port/activity_main.xml
+++ b/app/src/main/res/layout-port/activity_main.xml
@@ -17,7 +17,7 @@
@@ -110,5 +108,4 @@
android:background="@drawable/view_action_exercise_drawable"
android:scaleType="center"
android:src="@drawable/ic_next"/>
-
\ No newline at end of file
diff --git a/app/src/main/res/layout-port/view_toolbar.xml b/app/src/main/res/layout-port/view_toolbar.xml
index 3e2d1ed6..a0100465 100644
--- a/app/src/main/res/layout-port/view_toolbar.xml
+++ b/app/src/main/res/layout-port/view_toolbar.xml
@@ -10,6 +10,6 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
- android:background="?attr/colorPrimary"
+ android:theme="@style/AppTheme.Toolbar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index a4508cdb..0cf5fdb6 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -18,7 +18,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_routine_category.xml b/app/src/main/res/layout/view_routine_category.xml
index 26a2111e..8e795d23 100644
--- a/app/src/main/res/layout/view_routine_category.xml
+++ b/app/src/main/res/layout/view_routine_category.xml
@@ -4,9 +4,11 @@
android:id="@+id/routine_category_text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:minHeight="?attr/actionBarSize"
+ android:paddingTop="16dp"
+ android:paddingLeft="16dp"
+ android:paddingBottom="16dp"
android:background="@color/white"
- android:gravity="center"
- android:textColor="@color/primaryDark"
+ android:gravity="left|center"
+ android:textColor="@color/primary"
android:textSize="16sp"
- app:typeface="roboto_light" />
\ No newline at end of file
+ app:typeface="roboto_black" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_routine_exercise.xml b/app/src/main/res/layout/view_routine_exercise.xml
index 6a28495b..0bb178bd 100644
--- a/app/src/main/res/layout/view_routine_exercise.xml
+++ b/app/src/main/res/layout/view_routine_exercise.xml
@@ -5,19 +5,29 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/view_routine_exercise_drawable"
- android:clickable="true">
+ android:clickable="true"
+ android:paddingTop="12dp"
+ android:paddingLeft="16dp"
+ android:paddingBottom="12dp">
+ android:textSize="13sp"
+ android:text="Level 1"
+ app:typeface="roboto_black" />
-
+ android:layout_height="0dp"
+ android:layout_weight=".70"
+ android:textColor="@color/black"
+ android:textSize="16sp"
+ app:typeface="roboto_light"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_routine_section.xml b/app/src/main/res/layout/view_routine_section.xml
index cb9a2d00..d394fa77 100644
--- a/app/src/main/res/layout/view_routine_section.xml
+++ b/app/src/main/res/layout/view_routine_section.xml
@@ -1,12 +1,35 @@
-
\ No newline at end of file
+ android:orientation="vertical">
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_timer.xml b/app/src/main/res/layout/view_timer.xml
index 1ed1c930..9887d93a 100644
--- a/app/src/main/res/layout/view_timer.xml
+++ b/app/src/main/res/layout/view_timer.xml
@@ -33,7 +33,6 @@
android:layout_toLeftOf="@+id/timer_spacing"
android:gravity="center"
android:textSize="60sp"
- android:text="00"
app:typeface="roboto_bold" />
@@ -69,7 +67,6 @@
android:src="@drawable/ic_add"
app:borderWidth="0dp"
app:elevation="12dp"
- app:pressedTranslationZ="12dp"
app:fabSize="mini"/>
+ app:elevation="12dp"/>
diff --git a/app/src/main/res/layout/view_toolbar.xml b/app/src/main/res/layout/view_toolbar.xml
index 3e2d1ed6..a0100465 100644
--- a/app/src/main/res/layout/view_toolbar.xml
+++ b/app/src/main/res/layout/view_toolbar.xml
@@ -10,6 +10,6 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
- android:background="?attr/colorPrimary"
+ android:theme="@style/AppTheme.Toolbar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
\ No newline at end of file
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
index 0e882061..fb657f62 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/main.xml
@@ -3,8 +3,19 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity">
+
+
+
+
+ android:title="@string/action_settings"
+ android:orderInCategory="110"
+ app:showAsAction="never"/>
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
index 2cdf41c1..b606d5e1 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
index a8460380..8559de3a 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index eadea51e..8d53a6a8 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index 04cdba55..ea10c306 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index cf920fde..71e3183e 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/raw/beginner_routine.json b/app/src/main/res/raw/beginner_routine.json
index 5c14d894..1a241a41 100644
--- a/app/src/main/res/raw/beginner_routine.json
+++ b/app/src/main/res/raw/beginner_routine.json
@@ -5,8 +5,10 @@
"type": "category"
},
{
- "title": "Dynamic Stretches (Do all, 5-10 reps each)",
- "type": "section"
+ "title": "Dynamic Stretches",
+ "description": "Do all, 5-10 reps each",
+ "type": "section",
+ "mode": "all"
},
{
"id": "wall_extensions",
@@ -51,8 +53,10 @@
"type": "exercise"
},
{
- "title": "Bodyline Drills (Do all, 10-60s hold each)",
- "type": "section"
+ "title": "Bodyline Drills",
+ "description": "Do all, 10-60s hold each",
+ "type": "section",
+ "mode": "all"
},
{
"id": "plank",
@@ -62,7 +66,7 @@
},
{
"id": "side_plank",
- "title": "Side Plank (Do both sides)",
+ "title": "Side Plank (Both sides)",
"description": "10-60s hold",
"type": "exercise"
},
@@ -85,8 +89,10 @@
"type": "exercise"
},
{
- "title": "Activity (Pick one, 10-20 reps)",
- "type": "section"
+ "title": "Activity",
+ "description": "Pick one, 10-20 reps",
+ "type": "section",
+ "mode": "pick"
},
{
"id": "squat_jumps",
@@ -105,46 +111,56 @@
"type": "category"
},
{
- "title": "Handstand (5 min)",
- "type": "section"
+ "title": "Handstand",
+ "description": "5 min",
+ "type": "section",
+ "mode": "levels"
},
{
"id": "wall_plank",
- "title": "Level 1: Wall Plank",
+ "level": "Level 1",
+ "title": "Wall Plank",
"description": "5 minutes",
"type": "exercise"
},
{
"id": "wall_handstand",
- "title": "Level 2: Stomach-to-Wall Handstand",
+ "level": "Level 2",
+ "title": "Stomach-to-Wall Handstand",
"description": "5 minutes",
"type": "exercise"
},
{
"id": "freestanding_handstand",
- "title": "Level 3: Freestanding Handstand",
+ "level": "Level 3",
+ "title": "Freestanding Handstand",
"description": "5 minutes",
"type": "exercise"
},
{
- "title": "Support Practice (2-3 min)",
- "type": "section"
+ "title": "Support Practice",
+ "description": "2-3 min",
+ "type": "section",
+ "mode": "levels"
},
{
"id": "parallel_bar_hold",
- "title": "Level 1: Parallel Bar support",
+ "level": "Level 1",
+ "title": "Parallel Bar support",
"description": "2-3 minutes",
"type": "exercise"
},
{
"id": "ring_support",
- "title": "Level 2: Ring Support",
+ "level": "Level 2",
+ "title": "Ring Support",
"description": "2-3 minutes",
"type": "exercise"
},
{
"id": "rings_turned_out_support",
- "title": "Level 3: Rings Turned Out Support Hold",
+ "level": "Level 3",
+ "title": "Rings Turned Out Support Hold",
"description": "2-3 minutes",
"type": "exercise"
},
@@ -153,212 +169,248 @@
"type": "category"
},
{
- "title": "Pullup Progression 3x(5-8)",
- "type": "section"
+ "title": "Pullup Progression",
+ "description": "3x(5-8)",
+ "type": "section",
+ "mode": "levels"
},
{
"id": "negative_pullup",
- "title": "Level 1: Negative Pullups",
+ "level": "Level 1",
+ "title": "Negative Pullups",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "pullup",
- "title": "Level 2: Pull Up",
+ "level": "Level 2",
+ "title": "Pull Up",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "lsit_pullup",
- "title": "Level 3: L-sit Pull Up",
+ "level": "Level 3",
+ "title": "L-sit Pull Up",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "pullover",
- "title": "Level 4: Pull Over",
+ "level": "Level 4",
+ "title": "Pull Over",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
- "title": "Dipping Progression 3x(5-8)",
- "type": "section"
+ "title": "Dipping Progression",
+ "description": "3x(5-8)",
+ "type": "section",
+ "mode": "levels"
},
{
"id": "parallel_bar_dips",
- "title": "Level 1: Parallel Bar Dips",
+ "level": "Level 1",
+ "title": "Parallel Bar Dips",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "ring_dips",
- "title": "Level 2: Ring Dips",
+ "level": "Level 2",
+ "title": "Ring Dips",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "l_ring_dips",
- "title": "Level 3: Ring Dips in L-sit",
+ "level": "Level 3",
+ "title": "Ring Dips in L-sit",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
- "title": "Squat Progression 3x(5-8)",
- "type": "section"
+ "title": "Squat Progression",
+ "description": "3x(5-8)",
+ "type": "section",
+ "mode": "levels"
},
{
"id": "assisted_squat",
- "title": "Level 1: Assisted Bodyweight Squat",
+ "level": "Level 1",
+ "title": "Assisted Bodyweight Squat",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "bodyweight_squat",
- "title": "Level 2: Bodyweight Squat",
+ "level": "Level 2",
+ "title": "Bodyweight Squat",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "deep_stepup",
- "title": "Level 3: Deep Step-up",
+ "level": "Level 3",
+ "title": "Deep Step-up",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
- "title": "L-sit Progression 3x(10-30s)",
- "type": "section"
+ "title": "L-sit Progression",
+ "description": "3x(10-30s)",
+ "type": "section",
+ "mode": "levels"
},
{
"id": "floor_l_sit",
- "title": "Level 1: Foot-supported L-sit",
+ "level": "Level 1",
+ "title": "Foot-supported L-sit",
"description": "3x(10-30s)",
"type": "exercise"
},
{
"id": "one_foot_l_sit",
- "title": "Level 2: One-Leg Foot Supported L-sit",
+ "level": "Level 2",
+ "title": "One-Leg Foot Supported L-sit",
"description": "3x(10-30s)",
"type": "exercise"
},
{
"id": "tuck_l_sit",
- "title": "Level 3: Tuck L-sit",
- "description": "3x(10-30s)",
- "type": "exercise"
- },
- {
- "id": "l_sit",
- "title": "Level 4: One-leg L-sit",
+ "level": "Level 3",
+ "title": "Tuck L-sit",
"description": "3x(10-30s)",
"type": "exercise"
},
{
"id": "l_sit",
- "title": "Level 5: L-sit",
+ "level": "Level 4",
+ "title": "L-sit",
"description": "3x(10-30s)",
"type": "exercise"
},
{
- "title": "Pushing 3x(5-8)",
- "type": "section"
+ "title": "Pushing",
+ "description": "3x(5-8)",
+ "type": "section",
+ "mode": "levels"
},
{
"id": "wall_pushup",
- "title": "Level 1: Wall Pushup",
+ "level": "Level 1",
+ "title": "Wall Pushup",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "incline_pushup",
- "title": "Level 2: Incline Pushup",
+ "level": "Level 2",
+ "title": "Incline Pushup",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "pushup",
- "title": "Level 3: Pushup",
+ "level": "Level 3",
+ "title": "Pushup",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "diamond_pushup",
- "title": "Level 4: Diamond Pushup",
+ "level": "Level 4",
+ "title": "Diamond Pushup",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "ring_pushup",
- "title": "Level 5: Rings Push Ups",
+ "level": "Level 5",
+ "title": "Rings Push Ups",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "rings_turned_out_pushup",
- "title": "Level 5: Rings Turned Out Push Up",
+ "level": "Level 6",
+ "title": "Rings Turned Out Push Up",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "pseudo_planche_pushup",
- "title": "Level 7: Rings Turned Out PPPU",
+ "level": "Level 7",
+ "title": "Rings Turned Out PPPU",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "pseudo_planche",
- "title": "Level 5-7 (No Rings): Pseudo Planche Push Ups",
+ "level": "Level 5-7 (No Rings)",
+ "title": "Pseudo Planche Push Ups",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
- "title": "Row Progression 3x(5-8)",
- "type": "section"
+ "title": "Row Progression",
+ "description": "3x(5-8)",
+ "type": "section",
+ "mode": "levels"
},
{
"id": "vertical_row",
- "title": "Level 1: Vertical Row",
+ "level": "Level 1",
+ "title": "Vertical Row",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "incline_row",
- "title": "Level 2: Incline Row",
+ "level": "Level 2",
+ "title": "Incline Row",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "horizontal_row",
- "title": "Level 3: Horizontal Row",
+ "level": "Level 3",
+ "title": "Horizontal Row",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "wide_row",
- "title": "Level 4: Wide Row",
+ "level": "Level 4",
+ "title": "Wide Row",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "tuck_front_lever",
- "title": "Level 5: Tuck Front Lever",
+ "level": "Level 5",
+ "title": "Tuck Front Lever",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "tuck_ice_cream_maker",
- "title": "Level 6: Tuck Ice Cream Maker",
+ "level": "Level 6",
+ "title": "Tuck Ice Cream Maker",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "tuck_front_lever_row",
- "title": "Level 7: Tuck Front Lever Row",
+ "level": "Level 7",
+ "title": "Tuck Front Lever Row",
"description": "3 sets of 5-8 reps",
"type": "exercise"
},
{
"id": "advanced_tuck_front_lever_row",
- "title": "Level 8: Advanced Tuck Front Lever Row",
+ "level": "Level 8",
+ "title": "Advanced Tuck Front Lever Row",
"description": "3 sets of 5-8 reps",
"type": "exercise"
}
diff --git a/app/src/main/res/raw/finished.mp3 b/app/src/main/res/raw/finished.mp3
new file mode 100644
index 00000000..d9d03101
Binary files /dev/null and b/app/src/main/res/raw/finished.mp3 differ
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
index 76fef9c4..22556540 100644
--- a/app/src/main/res/values-v21/styles.xml
+++ b/app/src/main/res/values-v21/styles.xml
@@ -11,6 +11,13 @@
- @android:color/transparent
+
+
+
+