diff --git a/app/build.gradle b/app/build.gradle index ab1a3b8..b1759e2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'io.fabric' android { compileSdkVersion 25 - buildToolsVersion "25.0.0" + buildToolsVersion '27.0.3' defaultConfig { applicationId "com.michaelcarrano.seven_min_workout" minSdkVersion 16 @@ -23,18 +23,20 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + implementation fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:25.0.1' - compile 'com.android.support:design:25.0.1' - - compile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') { + implementation 'com.android.support:appcompat-v7:25.0.1' + implementation 'com.android.support:design:25.3.1' + implementation 'com.github.ohoussein.playpauseview:playpauseview:1.0.2' + implementation 'com.google.code.gson:gson:2.8.2' + implementation('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') { transitive = true; } // Testing Dependencies - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + //implementation 'com.android.support:support-v4:25.4.0' + androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - testCompile 'junit:junit:4.12' + testImplementation 'junit:junit:4.12' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2760477..ace9123 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,7 +10,10 @@ android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme"> - + + @@ -18,25 +21,27 @@ + android:name=".WorkoutDetailActivity" + android:parentActivityName=".WorkoutListActivity" + android:screenOrientation="portrait"> - + android:parentActivityName=".WorkoutListActivity" + android:screenOrientation="portrait"> @@ -45,6 +50,10 @@ + + \ No newline at end of file diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/AllExerciseStatsFragment.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/AllExerciseStatsFragment.java new file mode 100644 index 0000000..1fbe9d0 --- /dev/null +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/AllExerciseStatsFragment.java @@ -0,0 +1,22 @@ +package com.michaelcarrano.seven_min_workout; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class AllExerciseStatsFragment extends Fragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_all_exercise_stats, container, false); + return rootView; + } +} diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/App.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/App.java index b1bb816..6286895 100644 --- a/app/src/main/java/com/michaelcarrano/seven_min_workout/App.java +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/App.java @@ -26,7 +26,7 @@ public void onCreate() { final int[] darkColors = resources.getIntArray(R.array.darkColors); final int[] lightColors = resources.getIntArray(R.array.lightColors); - if (WorkoutContent.WORKOUTS.isEmpty()) { + if (WorkoutContent.MENU_ITEMS.isEmpty()) { for (int i = 0; i < workoutNames.length; i++) { WorkoutContent.addWorkout( new WorkoutContent.Workout( @@ -39,7 +39,6 @@ public void onCreate() { ) ); } - } } } diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/BaseActivity.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/BaseActivity.java index 1c33eb8..f33c9c6 100644 --- a/app/src/main/java/com/michaelcarrano/seven_min_workout/BaseActivity.java +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/BaseActivity.java @@ -10,6 +10,7 @@ import android.support.v7.widget.Toolbar; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; /** * Created by michaelcarrano on 10/1/16. @@ -94,4 +95,40 @@ public FloatingActionButton setShowFab(boolean show) { return fab; } + @NonNull + public Button addResumeFab() { + Button button = (Button) findViewById(R.id.fabResume); + + if (button == null) { + getLayoutInflater().inflate(R.layout.fab_resume, getCoordinatorLayout()); + button = (Button) findViewById(R.id.fabResume); + } + + setShowResumeFab(true); + + return button; + } + + @Nullable + public Button setShowResumeFab(boolean show) { + Button fab = (Button) findViewById(R.id.fabResume); + + if (fab == null || fab.getVisibility() == View.VISIBLE && show || fab.getVisibility() == View.GONE && !show) { + return fab; + } + + fab.clearAnimation(); + + CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) fab.getLayoutParams(); + if (show) { +// params.setBehavior(new FloatingActionButtonBehavior(this)); + fab.setVisibility(View.VISIBLE); + } else { + params.setBehavior(null); + fab.setVisibility(View.GONE); + } + + return fab; + } + } diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/Utils/RuntimeTypeAdapterFactory.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/Utils/RuntimeTypeAdapterFactory.java new file mode 100644 index 0000000..211226a --- /dev/null +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/Utils/RuntimeTypeAdapterFactory.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Not our code, using tool from github + * https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java + */ + +package com.michaelcarrano.seven_min_workout.Utils; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.Streams; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * Adapts values whose runtime type may differ from their declaration type. This + * is necessary when a field's type is not the same type that GSON should create + * when deserializing that field. For example, consider these types: + *
   {@code
+ *   abstract class Shape {
+ *     int x;
+ *     int y;
+ *   }
+ *   class Circle extends Shape {
+ *     int radius;
+ *   }
+ *   class Rectangle extends Shape {
+ *     int width;
+ *     int height;
+ *   }
+ *   class Diamond extends Shape {
+ *     int width;
+ *     int height;
+ *   }
+ *   class Drawing {
+ *     Shape bottomShape;
+ *     Shape topShape;
+ *   }
+ * }
+ *

Without additional type information, the serialized JSON is ambiguous. Is + * the bottom shape in this drawing a rectangle or a diamond?

   {@code
+ *   {
+ *     "bottomShape": {
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * This class addresses this problem by adding type information to the + * serialized JSON and honoring that type information when the JSON is + * deserialized:
   {@code
+ *   {
+ *     "bottomShape": {
+ *       "type": "Diamond",
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "type": "Circle",
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * Both the type field name ({@code "type"}) and the type labels ({@code + * "Rectangle"}) are configurable. + * + *

Registering Types

+ * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field + * name to the {@link #of} factory method. If you don't supply an explicit type + * field name, {@code "type"} will be used.
   {@code
+ *   RuntimeTypeAdapterFactory shapeAdapterFactory
+ *       = RuntimeTypeAdapterFactory.of(Shape.class, "type");
+ * }
+ * Next register all of your subtypes. Every subtype must be explicitly + * registered. This protects your application from injection attacks. If you + * don't supply an explicit type label, the type's simple name will be used. + *
   {@code
+ *   shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
+ *   shapeAdapter.registerSubtype(Circle.class, "Circle");
+ *   shapeAdapter.registerSubtype(Diamond.class, "Diamond");
+ * }
+ * Finally, register the type adapter factory in your application's GSON builder: + *
   {@code
+ *   Gson gson = new GsonBuilder()
+ *       .registerTypeAdapterFactory(shapeAdapterFactory)
+ *       .create();
+ * }
+ * Like {@code GsonBuilder}, this API supports chaining:
   {@code
+ *   RuntimeTypeAdapterFactory shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
+ *       .registerSubtype(Rectangle.class)
+ *       .registerSubtype(Circle.class)
+ *       .registerSubtype(Diamond.class);
+ * }
+ */ +public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { + private final Class baseType; + private final String typeFieldName; + private final Map> labelToSubtype = new LinkedHashMap>(); + private final Map, String> subtypeToLabel = new LinkedHashMap, String>(); + private final boolean maintainType; + + private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName, boolean maintainType) { + if (typeFieldName == null || baseType == null) { + throw new NullPointerException(); + } + this.baseType = baseType; + this.typeFieldName = typeFieldName; + this.maintainType = maintainType; + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code + * typeFieldName} as the type field name. Type field names are case sensitive. + * {@code maintainType} flag decide if the type will be stored in pojo or not. + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName, boolean maintainType) { + return new RuntimeTypeAdapterFactory(baseType, typeFieldName, maintainType); + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code + * typeFieldName} as the type field name. Type field names are case sensitive. + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { + return new RuntimeTypeAdapterFactory(baseType, typeFieldName, false); + } + + /** + * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as + * the type field name. + */ + public static RuntimeTypeAdapterFactory of(Class baseType) { + return new RuntimeTypeAdapterFactory(baseType, "type", false); + } + + /** + * Registers {@code type} identified by {@code label}. Labels are case + * sensitive. + * + * @throws IllegalArgumentException if either {@code type} or {@code label} + * have already been registered on this type adapter. + */ + public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { + if (type == null || label == null) { + throw new NullPointerException(); + } + if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { + throw new IllegalArgumentException("types and labels must be unique"); + } + labelToSubtype.put(label, type); + subtypeToLabel.put(type, label); + return this; + } + + /** + * Registers {@code type} identified by its {@link Class#getSimpleName simple + * name}. Labels are case sensitive. + * + * @throws IllegalArgumentException if either {@code type} or its simple name + * have already been registered on this type adapter. + */ + public RuntimeTypeAdapterFactory registerSubtype(Class type) { + return registerSubtype(type, type.getSimpleName()); + } + + public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getRawType() != baseType) { + return null; + } + + final Map> labelToDelegate + = new LinkedHashMap>(); + final Map, TypeAdapter> subtypeToDelegate + = new LinkedHashMap, TypeAdapter>(); + for (Map.Entry> entry : labelToSubtype.entrySet()) { + TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); + labelToDelegate.put(entry.getKey(), delegate); + subtypeToDelegate.put(entry.getValue(), delegate); + } + + return new TypeAdapter() { + @Override public R read(JsonReader in) throws IOException { + JsonElement jsonElement = Streams.parse(in); + JsonElement labelJsonElement; + if (maintainType) { + labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); + } else { + labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); + } + + if (labelJsonElement == null) { + throw new JsonParseException("cannot deserialize " + baseType + + " because it does not define a field named " + typeFieldName); + } + String label = labelJsonElement.getAsString(); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); + if (delegate == null) { + throw new JsonParseException("cannot deserialize " + baseType + " subtype named " + + label + "; did you forget to register a subtype?"); + } + return delegate.fromJsonTree(jsonElement); + } + + @Override public void write(JsonWriter out, R value) throws IOException { + Class srcType = value.getClass(); + String label = subtypeToLabel.get(srcType); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); + if (delegate == null) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + "; did you forget to register a subtype?"); + } + JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); + + if (maintainType) { + Streams.write(jsonObject, out); + return; + } + + JsonObject clone = new JsonObject(); + + if (jsonObject.has(typeFieldName)) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + " because it already defines a field named " + typeFieldName); + } + clone.add(typeFieldName, new JsonPrimitive(label)); + + for (Map.Entry e : jsonObject.entrySet()) { + clone.add(e.getKey(), e.getValue()); + } + Streams.write(clone, out); + } + }.nullSafe(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutCompleteActivity.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutCompleteActivity.java new file mode 100644 index 0000000..afc34f3 --- /dev/null +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutCompleteActivity.java @@ -0,0 +1,142 @@ +package com.michaelcarrano.seven_min_workout; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.michaelcarrano.seven_min_workout.Utils.RuntimeTypeAdapterFactory; +import com.michaelcarrano.seven_min_workout.data.ExerciseStats; +import com.michaelcarrano.seven_min_workout.data.RepExercise; +import com.michaelcarrano.seven_min_workout.data.ExerciseData; +import com.michaelcarrano.seven_min_workout.data.TimeExercise; + +import java.util.Arrays; + +public class WorkoutCompleteActivity extends AppCompatActivity { + + private ExerciseData exerciseData = new ExerciseData(); + private ExerciseStats[] eList; + private View[] views = null; + private RuntimeTypeAdapterFactory runtimeTypeAdapterFactory; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_workout_complete); + + //get json string of stats array + String json = getIntent().getStringExtra("stats_array"); + + // type adapter to remember the type of each ExerciseStats in the array + runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory + .of(ExerciseStats.class, "type") + .registerSubtype(RepExercise.class, "rep") + .registerSubtype(TimeExercise.class, "time"); + // Use Gson library to get array from string in intent + Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create(); + eList = gson.fromJson(json, ExerciseStats[].class); + + // set the array of the ExerciseData object + exerciseData.setExerciseStats(eList); + + // get the views for each exercise + views = new View[] { + findViewById(R.id.jumpingJackRepsEditText), + findViewById(R.id.wallsitsCompletedCheckBox), + findViewById(R.id.pushupsRepsEditText), + findViewById(R.id.crunchesRepsEditText), + findViewById(R.id.stepupsRepsEditText), + findViewById(R.id.squatsRepsEditText), + findViewById(R.id.tricepdipsRepsEditText), + findViewById(R.id.planksCompletedCheckBox), + findViewById(R.id.highkneesRepsEditText), + findViewById(R.id.lungeRepsEditText), + findViewById(R.id.pushuprotationsRepsEditText), + findViewById(R.id.sideplanksCompletedCheckBox) + }; + + EditText et; + RepExercise re; + + CheckBox cb; + TimeExercise te; + + for (int i = 0; i < eList.length; i++) { + //1,7,11 + if (eList[i] instanceof TimeExercise) { // set checkbox for each time exercise + cb = (CheckBox) views[i]; + te = (TimeExercise) eList[i]; + cb.setChecked(te.isCurrentStatus()); + } else { // set the number EditText to the value of reps entered during workout + et = (EditText) views[i]; + re = (RepExercise) eList[i]; + et.setText(re.getCurrentReps() + ""); + } + } + + // set on click for Complete Workout button + Button completeBtn = (Button) findViewById(R.id.completeBtn); + completeBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + //Time + //Completed last time + //total completed + //complete percentage + + //Reps + //reps last time + //total reps + //average + //best + for (ExerciseStats e: + eList) { + e.incrementWorkoutsCompleted(); + if (e instanceof RepExercise) { + RepExercise re = (RepExercise) e; + EditText repEditText = (EditText) views[Arrays.asList(eList).indexOf(e)]; + // set previous stat + re.setCompletedLastTime(Integer.valueOf(repEditText.getText() + "")); + // add to total reps completed + re.setTotalReps(re.getTotalReps()+re.getCompletedLastTime()); + // calculate average reps for exercise per workout with total reps done and workouts completed + re.setPersoanlAvg(re.getTotalReps()/re.getWorkoutsCompleted()); + // set best if most recent is higher + if (re.getCompletedLastTime() > re.getPersonalBest()) { + re.setPersonalBest(re.getCompletedLastTime()); + } + } else if (e instanceof TimeExercise) { + TimeExercise te = (TimeExercise) e; + CheckBox isCompleteCheckBox = (CheckBox) views[Arrays.asList(eList).indexOf(e)]; + // set complete last time + te.setCompletedLastTime(isCompleteCheckBox.isChecked()); + // increment total successes if current is success + if (te.isCompletedLastTime()) { + te.setTotalCompleted(te.getTotalCompleted() + 1); + } + // calculate success rate with total successes and total workouts completed + te.setCompletedPercentage((int)(((double)te.getTotalCompleted() / (double)te.getWorkoutsCompleted()) * 100)); + } + } + + //shared preferences + SharedPreferences mPrefs = getSharedPreferences("exercise_stats", MODE_PRIVATE); + SharedPreferences.Editor prefsEditor = mPrefs.edit(); + // use Gson to turn array of stats into Json string + Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create(); + String json = gson.toJson(exerciseData.getExerciseStats()); + prefsEditor.putString("stats", json); + prefsEditor.apply(); + + finish(); + } + }); + } +} diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutCountdownActivity.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutCountdownActivity.java index 09687cb..dcee1c3 100644 --- a/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutCountdownActivity.java +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutCountdownActivity.java @@ -1,8 +1,29 @@ package com.michaelcarrano.seven_min_workout; +import android.animation.LayoutTransition; +import android.animation.ObjectAnimator; +import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; +import android.text.Layout; +import android.view.Gravity; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TableLayout; +import android.widget.TextView; import android.widget.Toast; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.michaelcarrano.seven_min_workout.Utils.RuntimeTypeAdapterFactory; +import com.michaelcarrano.seven_min_workout.data.ExerciseData; +import com.michaelcarrano.seven_min_workout.data.ExerciseStats; +import com.michaelcarrano.seven_min_workout.data.RepExercise; +import com.michaelcarrano.seven_min_workout.data.TimeExercise; + /** * An Activity that represents the Workout timer. Each workout is 30 seconds long with 10 seconds of * rest in between. @@ -13,21 +34,186 @@ */ public class WorkoutCountdownActivity extends BaseActivity { + WorkoutCountdownFragment fragment; + private boolean textDisplayed = false; + private LinearLayout workout_countdown_info_container; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_workout_countdown); + boolean resumePressed = getIntent().getBooleanExtra("ResumePressed", false); if (savedInstanceState == null) { - WorkoutCountdownFragment fragment = new WorkoutCountdownFragment(); + fragment = new WorkoutCountdownFragment(); + + // Pass ResumePressed to fragment + Bundle bundle = new Bundle(); + bundle.putBoolean("ResumePressed", resumePressed); + fragment.setArguments(bundle); + + // Start fragment getSupportFragmentManager().beginTransaction().add(R.id.workout_countdown_container, fragment) .commit(); } else { Toast.makeText(this, "Countdown Activity", Toast.LENGTH_LONG).show(); } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle presses on the action bar items + switch (item.getItemId()) { + case android.R.id.home: + saveWorkout(); + return super.onOptionsItemSelected(item); + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onBackPressed() { + saveWorkout(); + super.onBackPressed(); + } + + public void saveWorkout() { + if (fragment.getWorkoutInProgress()) { + // Setup + SharedPreferences mPrefs = getSharedPreferences("PausedWorkout", MODE_PRIVATE); + SharedPreferences.Editor prefsEditor = mPrefs.edit(); + + // Save boolean indicating workout is paused + prefsEditor.putBoolean("WorkoutIsPaused", true); + + // Save current stats + RuntimeTypeAdapterFactory runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory + .of(ExerciseStats.class, "type") + .registerSubtype(RepExercise.class, "rep") + .registerSubtype(TimeExercise.class, "time"); + Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create(); + String json = gson.toJson(fragment.getStats().getExerciseStats()); + prefsEditor.putString("CurrentStats", json); + + // Save current exercise + int workoutPos = fragment.getmWorkoutPos(); + boolean isResting = fragment.getIsResting(); + boolean isRep = fragment.isRep(); + prefsEditor.putBoolean("IsResting", isResting); + prefsEditor.putBoolean("IsRep", isRep); + prefsEditor.putInt("CurrentExercise", workoutPos); + + // Save current time remaining + float remainingTimeInSeconds = fragment.getRemainingTime(); + prefsEditor.putFloat("SecondsRemaining", remainingTimeInSeconds); + + // Apply changes to shared preference + prefsEditor.apply(); + } + } + + /** + * Called when the bottom bar layout in the WorkoutCountdownFragment is tapped and animates up or down depending on the current position. + * + * @param view + */ + public void layoutClicked(View view) { + // Initialize the layout reference, params, and reference to the workout name textview + workout_countdown_info_container = (LinearLayout) findViewById(R.id.workout_countdown_info_container); + ViewGroup.LayoutParams params = workout_countdown_info_container.getLayoutParams(); + String text = ((TextView) findViewById(R.id.workout_countdown_name)).getText().toString(); + if (!textDisplayed && !(text.equals("Get Ready") || text.equals("Rest"))) { // App is in exercise and bar has not been tapped + // Increases the height of the layout + params.height = params.height + 900; + workout_countdown_info_container.setLayoutParams(params); + + // Sets the margins for the new textview + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, 600, 1f); + layoutParams.setMargins(40, 0, 40, 0); + + // Creates and applies styling to the new textview + TextView textView = new TextView(this); + applyText(textView, text); + textView.setLayoutParams(layoutParams); + textView.setTextSize(20); + textView.setTextColor(getResources().getColor(R.color.colorPrimaryDark)); + textView.setGravity(Gravity.CENTER); + + // Adds the textview to the layout + workout_countdown_info_container.addView(textView); + textDisplayed = true; + } else if (text.equals("Get Ready") || text.equals("Rest")) { // App is in rest or get ready + // Lock bar + } else { // Bar is raised + closeBar(); + } + } + + /** + * Closes the bottom bar in the WorkoutCountdownFragment when entering a rest stage. + */ + public void closeBar() { + ViewGroup.LayoutParams params = workout_countdown_info_container.getLayoutParams(); + workout_countdown_info_container.removeView(workout_countdown_info_container.getChildAt(workout_countdown_info_container.getChildCount() - 1)); + params.height = params.height - 900; + workout_countdown_info_container.setLayoutParams(params); + textDisplayed = false; + } + + /** + * Helper method that applies exercise instruction text inside the bottom bar in the WorkoutCountdownFragment. + * + * @param textView + * @param workoutName + */ + private void applyText(TextView textView, String workoutName) { + String text = ""; + switch (workoutName) { + case "Jumping jacks": + text = getString(R.string.jumping_jacks_desc); + break; + case "Wall sits": + text = getString(R.string.wall_sits_desc); + break; + case "Push-ups": + text = getString(R.string.push_ups_desc); + break; + case "Abdominal crunches": + text = getString(R.string.abdominal_crunches_desc); + break; + case "Step-ups onto a chair": + text = getString(R.string.step_ups_onto_a_chair_desc); + break; + case "Squats": + text = getString(R.string.squats_desc); + break; + case "Triceps dips on a chair": + text = getString(R.string.triceps_dips_on_a_chair_desc); + break; + case "Planks": + text = getString(R.string.planks_desc); + break; + case "High knees running in place": + text = getString(R.string.high_knees_running_in_place_desc); + break; + case "Lunges": + text = getString(R.string.lunges_desc); + break; + case "Push-ups and rotations": + text = getString(R.string.push_ups_and_rotations_desc); + break; + case "Side planks": + text = getString(R.string.side_planks_desc); + break; + } + textView.setText(text); + } + public boolean isTextDisplayed() { + return textDisplayed; } } diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutCountdownFragment.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutCountdownFragment.java index 8b6faf9..fd9fff2 100644 --- a/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutCountdownFragment.java +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutCountdownFragment.java @@ -1,30 +1,85 @@ package com.michaelcarrano.seven_min_workout; +import android.content.Intent; +import android.content.SharedPreferences; +import android.media.MediaPlayer; +import android.animation.LayoutTransition; import android.os.Bundle; import android.os.CountDownTimer; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; + +import android.widget.CheckBox; +import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.michaelcarrano.seven_min_workout.Utils.RuntimeTypeAdapterFactory; +import com.michaelcarrano.seven_min_workout.data.ExerciseStats; +import com.michaelcarrano.seven_min_workout.data.RepExercise; +import com.michaelcarrano.seven_min_workout.data.ExerciseData; +import com.michaelcarrano.seven_min_workout.data.TimeExercise; import com.michaelcarrano.seven_min_workout.data.WorkoutContent; import com.michaelcarrano.seven_min_workout.widget.CircularProgressBar; +import com.ohoussein.playpause.PlayPauseView; + +import static android.content.Context.MODE_PRIVATE; /** * Created by michaelcarrano on 12/6/13. */ public class WorkoutCountdownFragment extends Fragment { - private static int REMAINING_TIME; // Time remaining (ie: device rotation) + /** + * The fields used to track the current workout + */ + private boolean isPaused = false; + private boolean isResting = true; + private boolean isRep = false; + private boolean mWorkoutComplete = false; + private boolean countdownMediaPlayerIsPaused = false; + private MediaPlayer countdownMediaPlayer = new MediaPlayer(); + /** + * Tracks if the workout has started + */ + private boolean workoutInProgress = false; + + + /** + * The views modified in the class + */ + private TextView lastExerciseTextView; + private EditText repsCompletedPlainText; + private CheckBox isCompletedCheckBox; + private PlayPauseView playPauseView; + private TextView workoutCountdownId; + private TextView workoutCountdownName; + private LinearLayout statsContainer; + private LinearLayout completedCheckLayout; + private LinearLayout repCompLayout; + private LinearLayout repExerciseStats; + private TextView avgStatTextView; + private TextView bestStatTextView; + private TextView completedLastStatTextView; + private LinearLayout timeExerciseStats; + private TextView completePercentageStatTextView; + private LinearLayout statsLayout = null; /** * The time spent for each activity (exercise or rest) */ - private final int EXERCISE_TIME = 30000; // 30 seconds + private final int EXERCISE_TIME = 30000; // 30 seconds + + private final int REST_TIME = 10000; // 10 seconds - private final int REST_TIME = 10000; // 10 seconds + private final int COUNTDOWN_INTERVAL = 10; // 10 milliseconds between updates + + private static float REMAINING_TIME; // Time remaining (ie: device rotation) /** * Keeps track of the current workout @@ -47,9 +102,11 @@ public class WorkoutCountdownFragment extends Fragment { private CircularProgressBar mCircularProgressBar; /** - * Tracks if the workout has started + * Used to store exercise stats using Gson */ - private boolean workoutInProgress = false; + private RuntimeTypeAdapterFactory runtimeTypeAdapterFactory; + + private ExerciseData stats = new ExerciseData(); @Override public void onCreate(Bundle savedInstanceState) { @@ -57,8 +114,26 @@ public void onCreate(Bundle savedInstanceState) { // Retain instance on device rotation setRetainInstance(true); + // Remove all descriptions from the workout list + WorkoutContent.removeDescriptions(); + // Set mWorkout to the first workout - mWorkout = WorkoutContent.WORKOUTS.get(mWorkoutPos); + mWorkout = (WorkoutContent.Workout) WorkoutContent.MENU_ITEMS.get(mWorkoutPos); + + runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory + .of(ExerciseStats.class, "type") + .registerSubtype(RepExercise.class, "rep") + .registerSubtype(TimeExercise.class, "time"); + + SharedPreferences mPrefs = getActivity().getSharedPreferences("exercise_stats", MODE_PRIVATE); + if (mPrefs.contains("stats")) { //try to get stats from shared pref + Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create(); + String json = mPrefs.getString("stats", ""); + getStats().setExerciseStats(gson.fromJson(json, ExerciseStats[].class)); + } else { + // Initialize the stats variable + setStats(new ExerciseData(getActivity())); + } } @Override @@ -66,12 +141,37 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View rootView = inflater .inflate(R.layout.fragment_workout_countdown, container, false); - mCircularProgressBar = (CircularProgressBar) rootView - .findViewById(R.id.workout_countdown_time); -// rootView.setBackgroundColor(Color.parseColor(mWorkout.light)); + // Initialize Views + mCircularProgressBar = (CircularProgressBar) rootView.findViewById(R.id.workout_countdown_time); + lastExerciseTextView = (TextView) rootView.findViewById(R.id.lastExerciseTextview); + repsCompletedPlainText = (EditText) rootView.findViewById(R.id.repsCompletedPlainText); + isCompletedCheckBox = (CheckBox) rootView.findViewById(R.id.isCompletedCheckBox); + playPauseView = (PlayPauseView) rootView.findViewById(R.id.play_pause_view); + workoutCountdownId = (TextView) rootView.findViewById(R.id.workout_countdown_id); + workoutCountdownName = (TextView) rootView.findViewById(R.id.workout_countdown_name); + statsContainer = (LinearLayout) rootView.findViewById(R.id.stats_container); + completedCheckLayout = (LinearLayout) rootView.findViewById(R.id.completedCheckLayout); + repCompLayout = (LinearLayout) rootView.findViewById(R.id.repCompLayout); + repExerciseStats = (LinearLayout) rootView.findViewById(R.id.repExerciseStats); + avgStatTextView = (TextView) rootView.findViewById(R.id.avgStatTextView); + bestStatTextView = (TextView) rootView.findViewById(R.id.bestStatTextView); + completedLastStatTextView = (TextView) rootView.findViewById(R.id.completedLastStatTextView); + timeExerciseStats = (LinearLayout) rootView.findViewById(R.id.timeExerciseStats); + completePercentageStatTextView = (TextView) rootView.findViewById(R.id.completePercentageStatTextView); + + + ((ViewGroup) rootView.findViewById(R.id.workout_countdown_info_container)).getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); + ((ViewGroup) rootView.findViewById(R.id.countdown)).getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); + pauseAndPlayButtonSetUp(rootView); + + // Resume workout if selected by user + boolean resumePressed = getArguments().getBoolean("ResumePressed", false); + if (resumePressed) { + resumeWorkout(); + } // Start off with 10 second rest, then alternate - if (!workoutInProgress) { + else if (!workoutInProgress) { rest(rootView); } else { exercise(rootView); @@ -80,91 +180,333 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, return rootView; } + private void resumeWorkout() { + // Setup + SharedPreferences prefs = getActivity().getSharedPreferences("PausedWorkout", MODE_PRIVATE); + + // Get current stats + if (prefs.contains("CurrentStats")) { + Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create(); + String json = prefs.getString("CurrentStats", ""); + stats.setExerciseStats(gson.fromJson(json, ExerciseStats[].class)); + } + + // Get current exercise + isResting = prefs.getBoolean("IsResting", true); + mWorkoutPos = prefs.getInt("CurrentExercise", -1); + isRep = prefs.getBoolean("IsRep", true); + mWorkout = (WorkoutContent.Workout) WorkoutContent.MENU_ITEMS.get(mWorkoutPos); + + // Get current time remaining + REMAINING_TIME = prefs.getFloat("SecondsRemaining", 10000); + + // Resume the workout + if (isResting) { + rest(getView()); + } else { + isRep = (stats.getExerciseStats()[mWorkoutPos] instanceof RepExercise); + exercise(getView()); + } + } + @Override - public void onDestroyView() { - super.onDestroyView(); + public void onStop() { + super.onStop(); // Cancel the countdown mCountDownTimer.cancel(); + + if(!mWorkoutComplete) { + WorkoutCountdownActivity activity = (WorkoutCountdownActivity) getActivity(); + if (activity != null) { + activity.saveWorkout(); + } + } } - private void rest(final View rootView) { - mCountDownTimer = new CountDownTimer(REST_TIME, 1000) { + /** + * Creates a CountDownTimer that will begin at millisInFuture. + * Updates the CircularProgressBar every time the onTick is called while the timer is not paused. + * onFinish() will shift the View based on which is currently shown. + * + * @param millisInFuture The number of milliseconds remaining on the timer + * @param progressBarMax The number of milliseconds the CicularProgressBar holds at the original start of the timer + */ + private void setupCountDownTimer(final int millisInFuture, final int progressBarMax) { + mCircularProgressBar.setMax(progressBarMax / 1000); + mCountDownTimer = new CountDownTimer(millisInFuture, COUNTDOWN_INTERVAL) { @Override public void onTick(long millisUntilFinished) { - REMAINING_TIME = (int) (millisUntilFinished / 1000); - - TextView name = (TextView) rootView.findViewById(R.id.workout_countdown_name); - if (!workoutInProgress) { - name.setText(R.string.get_ready); - } else { - name.setText(R.string.rest); + if (isResting && REMAINING_TIME < 3.05 && REMAINING_TIME > 2.94) { + countdownMediaPlayer = MediaPlayer.create(getActivity().getApplicationContext(), R.raw.exercise_start); + countdownMediaPlayer.start(); } - TextView id = (TextView) rootView.findViewById(R.id.workout_countdown_id); - id.setText(mWorkout.id); - - id.setBackgroundColor(mWorkout.dark); - name.setBackgroundColor(mWorkout.light); - -// TextView time = (TextView) rootView.findViewById(R.id.workout_countdown_time); -// time.setText("" + millisUntilFinished / 1000); - mCircularProgressBar.setMax(REST_TIME / 1000); + REMAINING_TIME = (millisUntilFinished / 1000.0f); mCircularProgressBar.setProgress(REMAINING_TIME); - } @Override public void onFinish() { - workoutInProgress = true; - exercise(rootView); + if (isResting) { + workoutInProgress = true; + exercise(getView()); + if (mWorkoutPos != 0) { + lastExerciseTextView.setText(""); + ExerciseStats exercise = stats.getExerciseStats()[mWorkoutPos - 1]; + if (isRep) { + RepExercise re = (RepExercise) exercise; + if (!repsCompletedPlainText.getText().toString().equals("")) { + int reps = Integer.valueOf(repsCompletedPlainText.getText().toString()); + re.setCurrentReps(reps); + //do this stuff upon completion +// re.setCompletedLastTime(reps); +// if (reps > re.getPersonalBest()) { +// re.setPersonalBest(reps); +// } +// re.addToTotalReps(reps); +// re.setPersonalAvg(re.getTotalReps() / re.getWorkoutsCompleted()); + } else { + re.setCurrentReps(0); + } + } else { + TimeExercise te = (TimeExercise) exercise; + te.setCurrentStatus(isCompletedCheckBox.isChecked()); + //do upon completion +// if (cb.isChecked()) { +// te.setTotalCompleted(te.getTotalCompleted() + 1); +// } +// te.setCompletedLastTime(cb.isChecked()); +// te.setCompletedPercentage(te.getWorkoutsCompleted() / te.getTotalCompleted()); + } + } + } else { + if (++mWorkoutPos < WorkoutContent.MENU_ITEMS.size()) { + MediaPlayer mediaPlayer = MediaPlayer.create(getActivity().getApplicationContext(), R.raw.ting); + mediaPlayer.start(); + mWorkout = (WorkoutContent.Workout) WorkoutContent.MENU_ITEMS.get(mWorkoutPos); + rest(getView()); + } else { + finish(getView()); + } + } } }; - mCountDownTimer.start(); } - private void exercise(final View rootView) { - mCountDownTimer = new CountDownTimer(EXERCISE_TIME, 1000) { + /** + * Edits the pauseAndPlayTimer to begin in a playing state. + * When clicked and timer is currenly playing, the animation for the circularPrograsBar will stop and the timer as well. + * If clicked and the timer is paused setupCountDownTimer() will be called. + * Handles sound if pressed while sound is active. + * + * @param rootView The parent view for the pauseAndPlayButton + */ + private void pauseAndPlayButtonSetUp(View rootView) { + playPauseView.toggle(); + playPauseView.setOnClickListener(new View.OnClickListener() { @Override - public void onTick(long millisUntilFinished) { - REMAINING_TIME = (int) (millisUntilFinished / 1000); + public void onClick(View view) { + playPauseView.toggle(); + if (isPaused) { + isPaused = false; + + int progressBarMax = isResting ? REST_TIME : EXERCISE_TIME; + setupCountDownTimer((int) (REMAINING_TIME * 1000), progressBarMax); + mCountDownTimer.start(); + if (countdownMediaPlayerIsPaused) { + countdownMediaPlayer.start(); + countdownMediaPlayerIsPaused = false; + } - TextView ready = (TextView) rootView.findViewById(R.id.workout_countdown_name); - ready.setText(mWorkout.name); + } else { + if (countdownMediaPlayer.isPlaying()) { + countdownMediaPlayer.pause(); + countdownMediaPlayerIsPaused = true; + } + if (mCountDownTimer != null) { + mCountDownTimer.cancel(); + } + isPaused = true; + } + } + }); + } - TextView id = (TextView) rootView.findViewById(R.id.workout_countdown_id); - id.setText(mWorkout.id); -// TextView time = (TextView) rootView.findViewById(R.id.workout_countdown_time); -// time.setText("" + millisUntilFinished / 1000); - mCircularProgressBar.setMax(EXERCISE_TIME / 1000); - mCircularProgressBar.setProgress(REMAINING_TIME); - } - - @Override - public void onFinish() { - if (++mWorkoutPos < WorkoutContent.WORKOUTS.size()) { - mWorkout = WorkoutContent.WORKOUTS.get(mWorkoutPos); -// rootView.setBackgroundColor(Color.parseColor(mWorkout.light)); - rest(rootView); - } else { - finish(rootView); + private void setStatsPanel(View rootView, boolean isRestCalling) { + ExerciseStats usingStat; + if (isRestCalling) { + if (mWorkoutPos != 0) { + usingStat = getStats().getExerciseStats()[mWorkoutPos - 1]; + lastExerciseTextView.setVisibility(View.VISIBLE); + lastExerciseTextView.setText(usingStat.getExerciseName()); + if (usingStat instanceof TimeExercise) { + setStatsPanelHelper(rootView, usingStat, true, true); + } else if (usingStat instanceof RepExercise) { + setStatsPanelHelper(rootView, usingStat, false, true); } } - }; + } else { + usingStat = getStats().getExerciseStats()[mWorkoutPos]; + if (usingStat instanceof TimeExercise) { + setStatsPanelHelper(rootView, usingStat, true, false); + } else if (usingStat instanceof RepExercise) { + setStatsPanelHelper(rootView, usingStat, false, false); + } + } + statsLayout.setVisibility(View.VISIBLE); + } + + private void setStatsPanelHelper(View rootView, ExerciseStats usingStat, boolean isTimeExercise, boolean isRestCalling) { + if (isRestCalling && isTimeExercise) { + timeExerciseStatsPopulator(rootView, usingStat, true); + + completedCheckLayout.setVisibility(View.VISIBLE); + isRep = false; + } else if (isRestCalling && !isTimeExercise) { + repExerciseStatsPopulator(rootView, usingStat, true); + + repCompLayout.setVisibility(View.VISIBLE); + isRep = true; + } else if (!isRestCalling && isTimeExercise) { + timeExerciseStatsPopulator(rootView, usingStat, false); + + completedCheckLayout.setVisibility(View.GONE); + } else if (!isRestCalling && !isTimeExercise) { + repExerciseStatsPopulator(rootView, usingStat, false); + + repCompLayout.setVisibility(View.GONE); + } + } + + private void repExerciseStatsPopulator(View rootView, ExerciseStats usingStat, boolean isRestCalling) { + if (isRestCalling) { + repsCompletedPlainText.setText(""); + } + RepExercise re = (RepExercise) usingStat; + statsLayout = repExerciseStats; + avgStatTextView.setText(re.getPersoanlAvg() + ""); + bestStatTextView.setText(re.getPersonalBest() + ""); + completedLastStatTextView.setText(re.getCompletedLastTime() + ""); + } + + private void timeExerciseStatsPopulator(View rootView, ExerciseStats usingStat, boolean isRestCalling) { + if (isRestCalling) { + isCompletedCheckBox.setChecked(false); + } + TimeExercise te = (TimeExercise) usingStat; + statsLayout = timeExerciseStats; + completePercentageStatTextView.setText(te.getCompletedPercentage() + "%"); + } + + private void rest(final View rootView) { + isResting = true; + workoutCountdownId.setText(mWorkout.id); + workoutCountdownId.setBackgroundColor(mWorkout.dark); + + workoutCountdownName.setBackgroundColor(mWorkout.light); + if (!workoutInProgress) { + workoutCountdownName.setText(R.string.get_ready); + statsContainer.setVisibility(View.GONE); + } else { + statsContainer.setVisibility(View.VISIBLE); + workoutCountdownName.setText(R.string.rest); + setStatsPanel(rootView, true); + } + REMAINING_TIME = REST_TIME / 1000.0f; + setupCountDownTimer(REST_TIME, REST_TIME); + mCountDownTimer.start(); + + if (((WorkoutCountdownActivity) getActivity()).isTextDisplayed()) { + ((WorkoutCountdownActivity) getActivity()).closeBar(); + } + } + + private void exercise(final View rootView) { + statsContainer.setVisibility(View.VISIBLE); + repExerciseStats.setVisibility(View.GONE); + timeExerciseStats.setVisibility(View.GONE); + lastExerciseTextView.setVisibility(View.GONE); + repCompLayout.setVisibility(View.GONE); + isResting = false; + + workoutCountdownName.setText(mWorkout.name); + workoutCountdownId.setText(mWorkout.id); + workoutCountdownName.setBackgroundColor(mWorkout.light); + workoutCountdownId.setBackgroundColor(mWorkout.dark); + + setStatsPanel(rootView, false); + + REMAINING_TIME = EXERCISE_TIME / 1000.0f; + setupCountDownTimer(EXERCISE_TIME, EXERCISE_TIME); mCountDownTimer.start(); } private void finish(View rootView) { mCountDownTimer.cancel(); - // hide the current views - LinearLayout info = (LinearLayout) rootView.findViewById(R.id.workout_countdown_info); - info.setVisibility(View.GONE); - mCircularProgressBar.setVisibility(View.GONE); + mWorkoutComplete = true; + + // Play workout completed audio + MediaPlayer mediaPlayer = MediaPlayer.create(getActivity().getApplicationContext(), R.raw.cheer3); + mediaPlayer.start(); + + // Store stats from workout + Gson gson = new GsonBuilder().registerTypeAdapterFactory(runtimeTypeAdapterFactory).create(); + String json = gson.toJson(getStats().getExerciseStats()); + + // Save boolean indicating workout is not paused + SharedPreferences mPrefs = getActivity().getSharedPreferences("PausedWorkout", MODE_PRIVATE); + SharedPreferences.Editor prefsEditor = mPrefs.edit(); + prefsEditor.putBoolean("WorkoutIsPaused", false); + prefsEditor.apply(); + + // Start workoutCompleteActivity + Intent intent = new Intent(this.getActivity(), WorkoutCompleteActivity.class); + intent.putExtra("stats_array", json); + startActivity(intent); + getActivity().finish(); + } + + // Getters and Setters + + public ExerciseData getStats() { + return stats; + } + + public void setStats(ExerciseData stats) { + this.stats = stats; + } - // display "finished" - TextView textView = (TextView) rootView.findViewById(R.id.workout_countdown_finished); - textView.setVisibility(View.VISIBLE); + public int getmWorkoutPos() { + return mWorkoutPos; } + public void setmWorkoutPos(int mWorkoutPos) { + this.mWorkoutPos = mWorkoutPos; + } + + public boolean getWorkoutInProgress() { + return workoutInProgress; + } + + public boolean getIsResting() { + return isResting; + } + + public float getRemainingTime() { + return REMAINING_TIME; + } + + public PlayPauseView getPlayPauseView() { + return playPauseView; + } + + public boolean isRep() { + return isRep; + } + + public boolean isPaused() { + return isPaused; + } } diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutDetailActivity.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutDetailActivity.java index d127cfd..86e365d 100644 --- a/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutDetailActivity.java +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutDetailActivity.java @@ -26,7 +26,7 @@ protected void onCreate(Bundle savedInstanceState) { // Get the Workout that was selected int selected = getIntent().getExtras().getInt(WorkoutDetailFragment.ARG_WORKOUT_POS); - WorkoutContent.Workout workout = WorkoutContent.WORKOUTS.get(selected); + WorkoutContent.Workout workout = (WorkoutContent.Workout) WorkoutContent.MENU_ITEMS.get(selected); Log.i("7min", "Selected: " + selected + " " + workout.name); toolbar.setTitle(workout.name); diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutDetailFragment.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutDetailFragment.java index 4d7484d..7d82765 100644 --- a/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutDetailFragment.java +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutDetailFragment.java @@ -1,17 +1,22 @@ package com.michaelcarrano.seven_min_workout; +import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.google.android.youtube.player.YouTubeInitializationResult; import com.google.android.youtube.player.YouTubePlayer; import com.google.android.youtube.player.YouTubePlayerSupportFragment; +import com.google.android.youtube.player.YouTubeStandalonePlayer; +import com.google.android.youtube.player.YouTubeThumbnailView; import com.michaelcarrano.seven_min_workout.data.WorkoutContent; /** @@ -39,7 +44,7 @@ public void onCreate(Bundle savedInstanceState) { // Load the dummy content specified by the fragment // arguments. In a real-world scenario, use a Loader // to load content from a content provider. - mWorkout = WorkoutContent.WORKOUTS.get(getArguments().getInt(ARG_WORKOUT_POS)); + mWorkout = (WorkoutContent.Workout) WorkoutContent.MENU_ITEMS.get(getArguments().getInt(ARG_WORKOUT_POS)); Log.i("7min", "Frag: " + mWorkout.name); } } @@ -55,39 +60,39 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, content.setText(mWorkout.content); } - video(); + // video(); return rootView; } // TODO: Handle rotation so video does not start from beginning. public void video() { - YouTubePlayerSupportFragment youTubePlayerSupportFragment = YouTubePlayerSupportFragment - .newInstance(); - getActivity().getSupportFragmentManager().beginTransaction() - .add(R.id.youtube_fragment, youTubePlayerSupportFragment).commit(); +// YouTubePlayerSupportFragment youTubePlayerSupportFragment = YouTubePlayerSupportFragment +// .newInstance(); +// getActivity().getSupportFragmentManager().beginTransaction() +// .add(R.id.youtube_fragment, youTubePlayerSupportFragment).commit(); - youTubePlayerSupportFragment - .initialize(BuildConfig.YOUTUBE_API_KEY, new YouTubePlayer.OnInitializedListener() { - @Override - public void onInitializationSuccess(YouTubePlayer.Provider provider, - YouTubePlayer youTubePlayer, boolean b) { - if (!b) { - youTubePlayer.cueVideo(mWorkout.video); - } - } - - @Override - public void onInitializationFailure(YouTubePlayer.Provider provider, - YouTubeInitializationResult youTubeInitializationResult) { - if (youTubeInitializationResult.isUserRecoverableError()) { - youTubeInitializationResult.getErrorDialog(getActivity(), 1).show(); - } else { - String errorMessage = String.format(getString(R.string.error_player), - youTubeInitializationResult.toString()); - Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_LONG).show(); - } - } - }); +// youTubePlayerSupportFragment +// .initialize(BuildConfig.YOUTUBE_API_KEY, new YouTubePlayer.OnInitializedListener() { +// @Override +// public void onInitializationSuccess(YouTubePlayer.Provider provider, +// YouTubePlayer youTubePlayer, boolean b) { +// if (!b) { +// youTubePlayer.cueVideo(mWorkout.video); +// } +// } +// +// @Override +// public void onInitializationFailure(YouTubePlayer.Provider provider, +// YouTubeInitializationResult youTubeInitializationResult) { +// if (youTubeInitializationResult.isUserRecoverableError()) { +// youTubeInitializationResult.getErrorDialog(getActivity(), 1).show(); +// } else { +// String errorMessage = String.format(getString(R.string.error_player), +// youTubeInitializationResult.toString()); +// Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_LONG).show(); +// } +// } +// }); } } diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutListActivity.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutListActivity.java index 9806b71..4526a66 100644 --- a/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutListActivity.java +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/WorkoutListActivity.java @@ -1,10 +1,19 @@ package com.michaelcarrano.seven_min_workout; import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources; import android.os.Bundle; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.app.Fragment; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.ListView; + +import com.michaelcarrano.seven_min_workout.data.WorkoutContent; /** @@ -19,6 +28,11 @@ */ public class WorkoutListActivity extends BaseActivity implements WorkoutListFragment.Callbacks { + WorkoutListFragment workoutList; + ListView workoutListView; + boolean isActiveDescription = false; + int activeDescriptionPos = -1; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -32,6 +46,38 @@ public void onClick(View view) { startActivity(workoutIntent); } }); + + // Setup + SharedPreferences prefs = getSharedPreferences("PausedWorkout", MODE_PRIVATE); + + checkForResumeFab(); + workoutListView = (ListView) findViewById(android.R.id.list); + } + + @Override + public void onResume() { + super.onResume(); + checkForResumeFab(); + } + + public void checkForResumeFab() { + // Get boolean indicating if workout is paused + SharedPreferences prefs = getSharedPreferences("PausedWorkout", MODE_PRIVATE); + boolean canResume = prefs.getBoolean("WorkoutIsPaused", false); + + if (canResume) { + addResumeFab().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent workoutIntent = new Intent(WorkoutListActivity.this, WorkoutCountdownActivity.class); + workoutIntent.putExtra("ResumePressed", true); + startActivity(workoutIntent); + } + }); + } + else { + setShowResumeFab(false); + } } /** @@ -40,10 +86,46 @@ public void onClick(View view) { */ @Override public void onItemSelected(int position) { - // Start the detail activity for the selected workout ID. - Intent detailIntent = new Intent(this, WorkoutDetailActivity.class); - detailIntent.putExtra(WorkoutDetailFragment.ARG_WORKOUT_POS, position); - startActivity(detailIntent); + + if (WorkoutContent.MENU_ITEMS.get(position) instanceof WorkoutContent.Workout) { + if (!isActiveDescription) { + isActiveDescription = true; + activeDescriptionPos = position; + addDescription(position, false); + } else if (position == activeDescriptionPos) { + isActiveDescription = false; + WorkoutContent.removeDescriptions(); + } else if (position > activeDescriptionPos) { + activeDescriptionPos = position; + WorkoutContent.removeDescriptions(); + addDescription(position, true); + } else { + activeDescriptionPos = position; + WorkoutContent.removeDescriptions(); + addDescription(position, false); + } + BaseAdapter adapter = (BaseAdapter) workoutListView.getAdapter(); + adapter.notifyDataSetChanged(); + } + } + + private void addDescription(int position, boolean offsetNeeded) { + Resources resources = getResources(); + final String[] workoutNames = resources.getStringArray(R.array.workout_names); + final String[] workoutDescriptions = resources.getStringArray(R.array.workout_descriptions); + final String[] workoutVideos = resources.getStringArray(R.array.workout_videos); + final int[] darkColors = resources.getIntArray(R.array.darkColors); + final int[] lightColors = resources.getIntArray(R.array.lightColors); + + int offsetPos = offsetNeeded ? 0 : 1; + int offsetData = offsetNeeded ? -1 : 0; + WorkoutContent.insertDescription(new WorkoutContent.Description( + String.valueOf(position + offsetPos), + workoutNames[position + offsetData], + workoutDescriptions[position + offsetData], + workoutVideos[position + offsetData], + darkColors[position + offsetData], + lightColors[position + offsetData]), position + offsetPos); } /** diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/adapter/WorkoutListAdapter.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/adapter/WorkoutListAdapter.java index 91fb208..fcb560f 100644 --- a/app/src/main/java/com/michaelcarrano/seven_min_workout/adapter/WorkoutListAdapter.java +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/adapter/WorkoutListAdapter.java @@ -1,14 +1,40 @@ package com.michaelcarrano.seven_min_workout.adapter; +import android.animation.LayoutTransition; import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; import android.widget.BaseAdapter; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; - +import android.widget.Toast; + +import com.google.android.youtube.player.YouTubeInitializationResult; +import com.google.android.youtube.player.YouTubePlayer; +import com.google.android.youtube.player.YouTubePlayerSupportFragment; +import com.google.android.youtube.player.YouTubeStandalonePlayer; +import com.google.android.youtube.player.YouTubeThumbnailLoader; +import com.google.android.youtube.player.YouTubeThumbnailView; +import com.michaelcarrano.seven_min_workout.BuildConfig; import com.michaelcarrano.seven_min_workout.R; +import com.michaelcarrano.seven_min_workout.WorkoutDetailFragment; import com.michaelcarrano.seven_min_workout.data.WorkoutContent; +import com.michaelcarrano.seven_min_workout.data.WorkoutContent.MenuItem; +import com.michaelcarrano.seven_min_workout.data.WorkoutContent.Description; import com.michaelcarrano.seven_min_workout.data.WorkoutContent.Workout; /** @@ -20,19 +46,26 @@ public class WorkoutListAdapter extends BaseAdapter { private static LayoutInflater mLayoutInflater = null; + private Activity activity; public WorkoutListAdapter(Activity ctx) { + activity = ctx; this.mLayoutInflater = ctx.getLayoutInflater(); + addListenerToButton(); + } + + private void addListenerToButton() { + } @Override public int getCount() { - return WorkoutContent.WORKOUTS.size(); + return WorkoutContent.MENU_ITEMS.size(); } @Override public Object getItem(int position) { - return WorkoutContent.WORKOUTS.get(position); + return WorkoutContent.MENU_ITEMS.get(position); } @Override @@ -42,30 +75,120 @@ public long getItemId(int position) { @Override public View getView(int position, View convertView, ViewGroup parent) { - ViewHolder holder; - Workout workout = (Workout) getItem(position); + MenuItem menuItem = (MenuItem) getItem(position); + if (menuItem instanceof Workout) { - if (convertView == null) { - convertView = mLayoutInflater.inflate(R.layout.adapter_workout_row, parent, false); + ViewHolder holder; + Workout workout = (Workout) menuItem; - holder = new ViewHolder(); - holder.id = (TextView) convertView.findViewById(R.id.workout_id); - holder.name = (TextView) convertView.findViewById(R.id.workout_name); + //if (convertView == null) { + convertView = mLayoutInflater.inflate(R.layout.adapter_workout_row, parent, false); - convertView.setTag(holder); - } else { - holder = (ViewHolder) convertView.getTag(); + holder = new ViewHolder(); + holder.id = (TextView) convertView.findViewById(R.id.workout_id); + holder.name = (TextView) convertView.findViewById(R.id.workout_name); + + convertView.setTag(holder); + //} else { + //holder = (ViewHolder) convertView.getTag(); + //} + + // Set the content for the ListView row + holder.id.setText(workout.id); + holder.name.setText(workout.name); + + // Set the color for the ListView row + holder.id.setBackgroundColor(workout.dark); + holder.name.setBackgroundColor(workout.light); + + return convertView; } + else { + final DescriptionViewHolder holder; + final Description description = (Description) menuItem; + + //if (convertView == null) { + convertView = mLayoutInflater.inflate(R.layout.fragment_workout_detail, parent, false); + + holder = new DescriptionViewHolder(); - // Set the content for the ListView row - holder.id.setText(workout.id); - holder.name.setText(workout.name); + createYoutubeThumbnailView(description); + manageYoutubePlayButtonOnThumbnail(convertView, parent); - // Set the color for the ListView row - holder.id.setBackgroundColor(workout.dark); - holder.name.setBackgroundColor(workout.light); - return convertView; + holder.workout_detail = (TextView) convertView.findViewById(R.id.workout_detail); + + convertView.setTag(holder); + //} else { + // holder = (DescriptionViewHolder) convertView.getTag(); + //} + holder.workout_detail.setText(description.content); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + convertView.setTranslationZ(-1f); + } + Animation animation = AnimationUtils + .loadAnimation(convertView.getContext(), R.anim.down_from_top); + convertView.startAnimation(animation); + return convertView; + } + } + + private void manageYoutubePlayButtonOnThumbnail(View convertView, ViewGroup parent) { + ImageButton playButton = (ImageButton) convertView.findViewById(R.id.playVideoButton); + + final View finalConvertView = convertView; + playButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + YouTubeThumbnailView thumbnailView = (YouTubeThumbnailView) ((RelativeLayout) ((LinearLayout) finalConvertView.findViewById(R.id.detailLinearLayout)).getChildAt(0)).getChildAt(0); + Intent intent = YouTubeStandalonePlayer.createVideoIntent(activity, + BuildConfig.YOUTUBE_API_KEY, + thumbnailView.getTag().toString(),//video id + 0, //after this time, video will start automatically + true, //autoplay or not + false); //lightbox mode or not; show the video in a small box + activity.startActivity(intent); + } + }); + } + + private void createYoutubeThumbnailView(Description description) { + final YouTubeThumbnailView ytthumbnail = new YouTubeThumbnailView(activity); + ytthumbnail.setTag(description.getVideo()); + ytthumbnail.initialize(BuildConfig.YOUTUBE_API_KEY, new YouTubeThumbnailView.OnInitializedListener() { + @Override + public void onInitializationSuccess(YouTubeThumbnailView youTubeThumbnailView, YouTubeThumbnailLoader youTubeThumbnailLoader) { + + youTubeThumbnailLoader.setOnThumbnailLoadedListener(new YouTubeThumbnailLoader.OnThumbnailLoadedListener() { + @Override + public void onThumbnailLoaded(YouTubeThumbnailView youTubeThumbnailView, String s) { + RelativeLayout youtubeThumbnailLayoutParent = (RelativeLayout) activity.findViewById(R.id.relWithButton); + if(youtubeThumbnailLayoutParent != null){ + + if( youtubeThumbnailLayoutParent.getChildCount() > 1){ + youtubeThumbnailLayoutParent.getChildAt(0).setVisibility(View.GONE); + youtubeThumbnailLayoutParent.removeViewAt(0); + } + ytthumbnail.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); + youtubeThumbnailLayoutParent.addView(ytthumbnail,0); + youtubeThumbnailLayoutParent.findViewById(R.id.relWithButton).setVisibility(View.VISIBLE); + } + + } + + @Override + public void onThumbnailError(YouTubeThumbnailView youTubeThumbnailView, YouTubeThumbnailLoader.ErrorReason errorReason) { + + } + }); + youTubeThumbnailLoader.setVideo(ytthumbnail.getTag().toString()); + } + + @Override + public void onInitializationFailure(YouTubeThumbnailView youTubeThumbnailView, YouTubeInitializationResult youTubeInitializationResult) { + + } + }); } private static class ViewHolder { @@ -74,4 +197,11 @@ private static class ViewHolder { public TextView name; } + + private static class DescriptionViewHolder { + + public FrameLayout youtube_fragment; + + public TextView workout_detail; + } } diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/data/ExerciseData.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/data/ExerciseData.java new file mode 100644 index 0000000..37c1deb --- /dev/null +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/data/ExerciseData.java @@ -0,0 +1,37 @@ +package com.michaelcarrano.seven_min_workout.data; + +import android.content.Context; + +import com.michaelcarrano.seven_min_workout.R; + +public class ExerciseData { + private ExerciseStats[] exerciseStats = new ExerciseStats[12]; + + public ExerciseData() { + } + + public ExerciseData(Context context) { + + final String[] workoutNames = context.getResources().getStringArray(R.array.workout_names); + exerciseStats[0] = new RepExercise(workoutNames[0]); + exerciseStats[1] = new TimeExercise(workoutNames[1]); + exerciseStats[2] = new RepExercise(workoutNames[2]); + exerciseStats[3] = new RepExercise(workoutNames[3]); + exerciseStats[4] = new RepExercise(workoutNames[4]); + exerciseStats[5] = new RepExercise(workoutNames[5]); + exerciseStats[6] = new RepExercise(workoutNames[6]); + exerciseStats[7] = new TimeExercise(workoutNames[7]); + exerciseStats[8] = new RepExercise(workoutNames[8]); + exerciseStats[9] = new RepExercise(workoutNames[9]); + exerciseStats[10] = new RepExercise(workoutNames[10]); + exerciseStats[11] = new TimeExercise(workoutNames[11]); + } + + public ExerciseStats[] getExerciseStats() { + return exerciseStats; + } + + public void setExerciseStats(ExerciseStats[] exerciseStats) { + this.exerciseStats = exerciseStats; + } +} diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/data/ExerciseStats.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/data/ExerciseStats.java new file mode 100644 index 0000000..889d9fa --- /dev/null +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/data/ExerciseStats.java @@ -0,0 +1,29 @@ +package com.michaelcarrano.seven_min_workout.data; + +public class ExerciseStats { + private int workoutsCompleted; + private String exerciseName = ""; + + public ExerciseStats() { + } + + public int getWorkoutsCompleted() { + return workoutsCompleted; + } + + public void setWorkoutsCompleted(int workoutsCompleted) { + this.workoutsCompleted = workoutsCompleted; + } + + public String getExerciseName() { + return exerciseName; + } + + public void setExerciseName(String exerciseName) { + this.exerciseName = exerciseName; + } + + public void incrementWorkoutsCompleted() { + workoutsCompleted++; + } +} diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/data/RepExercise.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/data/RepExercise.java new file mode 100644 index 0000000..b6c7c7b --- /dev/null +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/data/RepExercise.java @@ -0,0 +1,60 @@ +package com.michaelcarrano.seven_min_workout.data; + +public class RepExercise extends ExerciseStats { + private int personalBest = 0; + private int persoanlAvg = 0; + private int completedLastTime = 0; + private int totalReps = 0; + private int currentReps = 0; + + public RepExercise() { + } + + public RepExercise(String exerciseName) { + this.setExerciseName(exerciseName); + } + + public void addToTotalReps(int reps) { + totalReps += reps; + } + + public int getPersonalBest() { + return personalBest; + } + + public void setPersonalBest(int personalBest) { + this.personalBest = personalBest; + } + + public int getPersoanlAvg() { + return persoanlAvg; + } + + public void setPersoanlAvg(int persoanlAvg) { + this.persoanlAvg = persoanlAvg; + } + + public int getCompletedLastTime() { + return completedLastTime; + } + + public void setCompletedLastTime(int completedLastTime) { + this.completedLastTime = completedLastTime; + } + + public int getTotalReps() { + return totalReps; + } + + public void setTotalReps(int totalReps) { + this.totalReps = totalReps; + } + + public int getCurrentReps() { + return currentReps; + } + + public void setCurrentReps(int currentReps) { + this.currentReps = currentReps; + } +} diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/data/TimeExercise.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/data/TimeExercise.java new file mode 100644 index 0000000..26a2313 --- /dev/null +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/data/TimeExercise.java @@ -0,0 +1,44 @@ +package com.michaelcarrano.seven_min_workout.data; + +public class TimeExercise extends ExerciseStats { + private boolean completedLastTime = false; + private int totalCompleted = 0; + private int completedPercentage = 0; + private boolean currentStatus = false; + + public TimeExercise(String exerciseName) { + this.setExerciseName(exerciseName); + } + + public boolean isCompletedLastTime() { + return completedLastTime; + } + + public void setCompletedLastTime(boolean completedLastTime) { + this.completedLastTime = completedLastTime; + } + + public int getCompletedPercentage() { + return completedPercentage; + } + + public void setCompletedPercentage(int completedPercentage) { + this.completedPercentage = completedPercentage; + } + + public int getTotalCompleted() { + return totalCompleted; + } + + public void setTotalCompleted(int totalCompleted) { + this.totalCompleted = totalCompleted; + } + + public boolean isCurrentStatus() { + return currentStatus; + } + + public void setCurrentStatus(boolean currentStatus) { + this.currentStatus = currentStatus; + } +} diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/data/WorkoutContent.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/data/WorkoutContent.java index ce3982a..c1349e5 100644 --- a/app/src/main/java/com/michaelcarrano/seven_min_workout/data/WorkoutContent.java +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/data/WorkoutContent.java @@ -16,16 +16,72 @@ public class WorkoutContent { /** * An array of workouts. */ - public static List WORKOUTS = new ArrayList(); + public static List MENU_ITEMS = new ArrayList(); public static void addWorkout(Workout workout) { - WORKOUTS.add(workout); + MENU_ITEMS.add(workout); + } + + public static void insertDescription(Description workout, int position) { + MENU_ITEMS.add(position, workout); + } + + public static void removeDescriptions() { + for (int i = MENU_ITEMS.size() - 1; i >= 0; i--) { + if (MENU_ITEMS.get(i) instanceof Description) { + MENU_ITEMS.remove(MENU_ITEMS.get(i)); + } + } + } + + /** + * A MenuItem for the base class used in the ListView holding both Workouts and Descriptions + */ + public static abstract class MenuItem { + } /** - * A Workout representing information related to the workout item. + * A Description holding the YouTube video link and description of the item. */ - public static class Workout { + public static class Description extends MenuItem { + + public String id; + + public String name; + + public String content; + + public String video; + + public int dark; + + public int light; + + /** + * A Workout representing information related to the workout item. + */ + + + public Description(@NonNull String id, + @NonNull String name, + @NonNull String content, + @NonNull String video, + @ColorRes int dark, + @ColorRes int light) { + this.id = id; + this.name = name; + this.content = content; + this.video = video; + this.dark = dark; + this.light = light; + } + public String getVideo() { + return video; + } + } + public static class Workout extends MenuItem { + public String id; diff --git a/app/src/main/java/com/michaelcarrano/seven_min_workout/widget/CircularProgressBar.java b/app/src/main/java/com/michaelcarrano/seven_min_workout/widget/CircularProgressBar.java index c3b2b8b..7dc96d4 100644 --- a/app/src/main/java/com/michaelcarrano/seven_min_workout/widget/CircularProgressBar.java +++ b/app/src/main/java/com/michaelcarrano/seven_min_workout/widget/CircularProgressBar.java @@ -41,7 +41,7 @@ public class CircularProgressBar extends View { private int mMax; // current progress between 0 and mMax - private int mProgress; + private float mProgress; // diameter (in dp) of the circle private float mDiameter; @@ -66,7 +66,7 @@ public CircularProgressBar(final Context context, final AttributeSet attrs, // extract params (if provided) final TypedArray args = context - .obtainStyledAttributes(attrs, R.styleable.circularProgressBar); + .obtainStyledAttributes(attrs, R.styleable.CircularProgressBar); final float defaultDiameter = TypedValue .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 64, this.getResources() @@ -83,31 +83,31 @@ public CircularProgressBar(final Context context, final AttributeSet attrs, try { final int bgColor = args - .getColor(R.styleable.circularProgressBar_bgColor, R.color.black); + .getColor(R.styleable.CircularProgressBar_bgColor, getResources().getColor(R.color.black)); final int bgStrokeWidth = args - .getDimensionPixelSize(R.styleable.circularProgressBar_bgStrokeWidth, + .getDimensionPixelSize(R.styleable.CircularProgressBar_bgStrokeWidth, (int) defaultStrokeWidth); final int progressColor = args - .getColor(R.styleable.circularProgressBar_progressColor, R.color.white); + .getColor(R.styleable.CircularProgressBar_progressColor, getResources().getColor(R.color.white)); final int progressStrokeWidth = args.getDimensionPixelSize( - R.styleable.circularProgressBar_progressStrokeWidth, (int) defaultStrokeWidth); + R.styleable.CircularProgressBar_progressStrokeWidth, (int) defaultStrokeWidth); - this.mShowText = args.getBoolean(R.styleable.circularProgressBar_showText, false); + this.mShowText = args.getBoolean(R.styleable.CircularProgressBar_showText, false); final int textSize = args - .getDimensionPixelSize(R.styleable.circularProgressBar_android_textSize, + .getDimensionPixelSize(R.styleable.CircularProgressBar_android_textSize, (int) defaultTextSize); final int textColor = args - .getInt(R.styleable.circularProgressBar_android_textColor, R.color.white); + .getInt(R.styleable.CircularProgressBar_android_textColor, R.color.white); this.mLayoutMargin = args - .getDimensionPixelSize(R.styleable.circularProgressBar_android_layout_margin, + .getDimensionPixelSize(R.styleable.CircularProgressBar_android_layout_margin, (int) defaultMargin); - this.mMax = args.getInt(R.styleable.circularProgressBar_max, DEFAULT_MAX_VALUE); + this.mMax = args.getInt(R.styleable.CircularProgressBar_max, DEFAULT_MAX_VALUE); this.mDiameter = args - .getDimension(R.styleable.circularProgressBar_diameter, defaultDiameter); + .getDimension(R.styleable.CircularProgressBar_diameter, defaultDiameter); // create paint settings based on supplied args this.mBgPaint = new Paint(); @@ -127,6 +127,7 @@ public CircularProgressBar(final Context context, final AttributeSet attrs, this.mTextPaint.setAntiAlias(true); this.mTextPaint.setStyle(Style.STROKE); this.mTextPaint.setTextAlign(Align.CENTER); + this.mTextPaint.setFakeBoldText(true); this.mTextPaint.setTextSize(textSize); } finally { args.recycle(); @@ -168,13 +169,13 @@ protected void onDraw(final Canvas canvas) { } // draw text in the center - canvas.drawText(String.valueOf(this.mProgress), center, + canvas.drawText(String.valueOf((int)(this.mProgress + 1)), center, center + (this.mTextBounds.height() >> 1), this.mTextPaint); } } - public void setProgress(final int progress) { + public void setProgress(final float progress) { this.mProgress = progress; // force redraw @@ -223,5 +224,4 @@ public void setTextColor(final int textColor) { public void setDiameter(final float diameter) { this.mDiameter = diameter; } - } diff --git a/app/src/main/res/anim/down_from_top.xml b/app/src/main/res/anim/down_from_top.xml new file mode 100644 index 0000000..c0c0463 --- /dev/null +++ b/app/src/main/res/anim/down_from_top.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-mdpi/yt_play_button.png b/app/src/main/res/drawable-mdpi/yt_play_button.png new file mode 100644 index 0000000..930c211 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/yt_play_button.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/wood_background.jpg b/app/src/main/res/drawable-xxxhdpi/wood_background.jpg new file mode 100644 index 0000000..f59e6dc Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/wood_background.jpg differ diff --git a/app/src/main/res/drawable/all_stats_container_background.xml b/app/src/main/res/drawable/all_stats_container_background.xml new file mode 100644 index 0000000..548df8b --- /dev/null +++ b/app/src/main/res/drawable/all_stats_container_background.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/all_stats_text_background.xml b/app/src/main/res/drawable/all_stats_text_background.xml new file mode 100644 index 0000000..5a1452e --- /dev/null +++ b/app/src/main/res/drawable/all_stats_text_background.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/complete_workout_btn_background.xml b/app/src/main/res/drawable/complete_workout_btn_background.xml new file mode 100644 index 0000000..5435bbc --- /dev/null +++ b/app/src/main/res/drawable/complete_workout_btn_background.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/fab_resume_background.xml b/app/src/main/res/drawable/fab_resume_background.xml new file mode 100644 index 0000000..25fa709 --- /dev/null +++ b/app/src/main/res/drawable/fab_resume_background.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/stats_background.xml b/app/src/main/res/drawable/stats_background.xml new file mode 100644 index 0000000..5976361 --- /dev/null +++ b/app/src/main/res/drawable/stats_background.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/font/adamina.ttf b/app/src/main/res/font/adamina.ttf new file mode 100644 index 0000000..3c660f8 Binary files /dev/null and b/app/src/main/res/font/adamina.ttf differ diff --git a/app/src/main/res/layout/activity_workout_complete.xml b/app/src/main/res/layout/activity_workout_complete.xml new file mode 100644 index 0000000..059671d --- /dev/null +++ b/app/src/main/res/layout/activity_workout_complete.xml @@ -0,0 +1,48 @@ + + + + + + + + + +