Commit da7fc11f authored by 12people's avatar 12people

Merge branch 'severalworkouts' into 'master'

Severalworkouts

See merge request !6
parents 69130e26 6a285f54
......@@ -9,8 +9,8 @@ android {
applicationId "com.enjoyingfoss.feeel"
minSdkVersion 15
targetSdkVersion 27
versionCode 2
versionName "1.1"
versionCode 3
versionName "1.9"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
......
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.enjoyingfoss.feeel"
android:installLocation="auto"
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">
<application
android:allowBackup="true"
......@@ -10,7 +11,7 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.enjoyingfoss.feeel.view.CoverActivity">
<activity android:name="com.enjoyingfoss.feeel.view.OverviewActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
......@@ -18,6 +19,9 @@
</intent-filter>
</activity>
<activity
android:name="com.enjoyingfoss.feeel.view.CoverActivity"/>
<activity
android:name="com.enjoyingfoss.feeel.view.WorkoutActivity"
android:launchMode="singleTop"/>
......
......@@ -9,7 +9,7 @@ import com.enjoyingfoss.feeel.model.Workout
*/
interface WorkoutContract {
interface Presenter {
fun setView(callback: View, savedState: Parcelable?, ttsEnabled: Boolean)
fun setHostView(callback: View, savedState: Parcelable?, workout: Workout, ttsEnabled: Boolean)
fun saveState(): Parcelable?
fun disconnect(view: WorkoutContract.View) //todo test that this is always called; OR use weakReference instead
......@@ -33,6 +33,6 @@ interface WorkoutContract {
}
interface Model {
fun retrieveWorkout(): Workout
fun retrieveAll(): List<Workout>
}
}
\ No newline at end of file
......@@ -31,4 +31,4 @@ data class Exercise(val titleResource: Int, val imageResource: Int, val descReso
return arrayOfNulls(size)
}
}
}
\ No newline at end of file
}
......@@ -7,18 +7,26 @@ import android.os.Parcelable
@author Miroslav Mazel
*/
class Workout(val titleResource: Int,
val customColor: Long,
val exerciseMetas: Array<ExerciseMeta>,
val breakLength: Int) : Parcelable {
/**
* customColor encoded as (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 8 | (B & 0xff), can reference with e.g. 0xff0000ff
*/
val size: Int
get() = exerciseMetas.size
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readLong(),
parcel.createTypedArray(ExerciseMeta),
parcel.readInt())
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(titleResource)
parcel.writeLong(customColor)
parcel.writeTypedArray(exerciseMetas, flags)
parcel.writeInt(breakLength)
}
......
......@@ -6,10 +6,13 @@ import com.enjoyingfoss.feeel.WorkoutContract
/**
@author Miroslav Mazel
*/
class WorkoutRepository : WorkoutContract.Model {
override fun retrieveWorkout(): Workout {
return Workout(
object WorkoutRepository : WorkoutContract.Model {
private const val NULL_RESOURCE = android.R.color.transparent
override fun retrieveAll(): List<Workout> {
return listOf(Workout(
titleResource = R.string.workout_title_7minute,
customColor = 0xff0B65DB,
exerciseMetas = arrayOf(
ExerciseMeta(
Exercise(
......@@ -91,6 +94,68 @@ class WorkoutRepository : WorkoutContract.Model {
), 30, isFlipped = true)
),
breakLength = 10
),
Workout(
titleResource = R.string.workout_title_7minadvanced,
customColor = 0xffff5f00,
exerciseMetas = arrayOf(
ExerciseMeta(
Exercise(
titleResource = R.string.exercise_title_reverselungerotaiton,
descResource = R.string.tbd,
imageResource = NULL_RESOURCE
), 30),
ExerciseMeta(
Exercise(
titleResource = R.string.exercise_title_sideplank_l,
descResource = R.string.exercise_desc_sideplank_l,
imageResource = R.drawable.exercise_sideplank
), 30),
ExerciseMeta(
Exercise(
titleResource = R.string.exercise_title_pushuprowburpee,
descResource = R.string.tbd,
imageResource = NULL_RESOURCE
), 60),
ExerciseMeta(
Exercise(
titleResource = R.string.exercise_title_sideplank_r,
descResource = R.string.exercise_desc_sideplank_r,
imageResource = R.drawable.exercise_sideplank
), 30, isFlipped = true),
ExerciseMeta(
Exercise(
titleResource = R.string.exercise_title_romaniancurlpress_l,
descResource = R.string.tbd,
imageResource = NULL_RESOURCE
), 60),
ExerciseMeta(
Exercise(
titleResource = R.string.exercise_title_romaniancurlpress_r,
descResource = R.string.tbd,
imageResource = NULL_RESOURCE
), 60),
ExerciseMeta(
Exercise(
titleResource = R.string.exercise_title_plankarmlift,
descResource = R.string.tbd,
imageResource = NULL_RESOURCE
), 30),
ExerciseMeta(
Exercise(
titleResource = R.string.exercise_title_laterallungetotricepsextension,
descResource = R.string.tbd,
imageResource = NULL_RESOURCE
), 60),
ExerciseMeta(
Exercise(
titleResource = R.string.exercise_title_bentoverrow,
descResource = R.string.tbd,
imageResource = NULL_RESOURCE
), 60)
),
breakLength = 10
)
)
}
}
\ No newline at end of file
......@@ -84,9 +84,11 @@ internal class WorkoutPresenter : WorkoutTimer.TimerCallback {
views.add(view)
}
timer = WorkoutTimer(this, prepLength)
renderStage(views)
renderSeconds(views)
timer = WorkoutTimer(this, prepLength)
timer.start()
}
......@@ -101,10 +103,10 @@ internal class WorkoutPresenter : WorkoutTimer.TimerCallback {
timer = WorkoutTimer(this, savedState.timeRemaining)
rerender(views)
if (savedState.isTimerRunning) timer.start()
else timer.stop()
rerender(views)
}
//
......
......@@ -10,7 +10,7 @@ import android.os.IBinder
import android.os.Parcelable
import com.enjoyingfoss.feeel.R
import com.enjoyingfoss.feeel.WorkoutContract
import com.enjoyingfoss.feeel.model.WorkoutRepository
import com.enjoyingfoss.feeel.model.Workout
import com.enjoyingfoss.feeel.view.WorkoutActivity
import com.enjoyingfoss.feeel.view.WorkoutChime
import com.enjoyingfoss.feeel.view.WorkoutNotification
......@@ -37,7 +37,7 @@ class WorkoutService : Service(), WorkoutContract.Presenter {
get() = WeakReference(this@WorkoutService)
}
//todo check that workout.size > 0 before starting workout
//todo check that workout.size > 0 before starting workout, otherwise show empty state
override fun onCreate() {
super.onCreate()
......@@ -54,7 +54,7 @@ class WorkoutService : Service(), WorkoutContract.Presenter {
super.onDestroy()
}
override fun setView(callback: WorkoutContract.View, savedState: Parcelable?, ttsEnabled: Boolean) { //todo restore view state here!! //todo figure out how to deal with audio in general -- no effect when restoring service here
override fun setHostView(callback: WorkoutContract.View, savedState: Parcelable?, workout: Workout, ttsEnabled: Boolean) { //todo restore view state here!! //todo figure out how to deal with audio in general -- no effect when restoring service here
if (presenter == null) {
if (audioView != null)
throw IllegalStateException("audio view can't be null when presenter is null")
......@@ -67,7 +67,7 @@ class WorkoutService : Service(), WorkoutContract.Presenter {
presenter =
if (savedState != null) WorkoutPresenter(savedState, WeakReference(audioView!!), WeakReference(callback))
else WorkoutPresenter(WorkoutRepository().retrieveWorkout(), WeakReference(audioView!!), WeakReference(callback))
else WorkoutPresenter(workout, WeakReference(audioView!!), WeakReference(callback))
} else {
presenter?.addViews(WeakReference(callback))
}
......
......@@ -15,7 +15,7 @@ class WorkoutTimer(val callback: TimerCallback, initTime: Int) {
fun onTimerZero()
}
var timeRemaining = initTime + 1
var timeRemaining = initTime
private val exerciseExecutor = Executors.newScheduledThreadPool(1)
private var timerFuture: Future<*>? = null
var running = false
......
......@@ -4,6 +4,7 @@ import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import com.enjoyingfoss.feeel.R
import com.enjoyingfoss.feeel.model.Workout
import kotlinx.android.synthetic.main.activity_cover.*
......@@ -15,10 +16,17 @@ import kotlinx.android.synthetic.main.activity_cover.*
//todo add license info
class CoverActivity : AppCompatActivity() {
companion object {
const val WORKOUT_KEY = "workout"
}
override fun onCreate(savedInstanceState: Bundle?) { //todo connect service and preload here
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cover)
val workout = intent.getParcelableExtra(WORKOUT_KEY) as Workout
titleTV.setText(workout.titleResource)
// todo val editor = sharedpreferences.edit()
// editor.putString("key", "value")
// editor.commit()
......@@ -26,6 +34,7 @@ class CoverActivity : AppCompatActivity() {
startExerciseButton.setOnClickListener {
val startIntent = Intent(this, WorkoutActivity::class.java)
startIntent.putExtra(WorkoutActivity.TTS_KEY, enableTTS.isChecked)
startIntent.putExtra(WorkoutActivity.WORKOUT_KEY, workout)
startActivity(startIntent)
}
}
......
......@@ -19,6 +19,7 @@ import android.view.WindowManager
import com.enjoyingfoss.feeel.R
import com.enjoyingfoss.feeel.WorkoutContract
import com.enjoyingfoss.feeel.model.ExerciseMeta
import com.enjoyingfoss.feeel.model.Workout
import com.enjoyingfoss.feeel.presenter.WorkoutService
import kotlinx.android.synthetic.main.activity_workout.*
import java.lang.ref.WeakReference
......@@ -40,7 +41,8 @@ class WorkoutActivity : AppCompatActivity(), ServiceConnection, WorkoutContract.
//todo implement transition based on https://www.thedroidsonroids.com/blog/android/meaningful-motion-with-shared-element-transition-and-circular-reveal-animation/ or https://guides.codepath.com/android/Circular-Reveal-Animation
companion object {
val TTS_KEY = "audio"
const val TTS_KEY = "audio"
const val WORKOUT_KEY = "workout"
}
//todo do a pager view, preload
......@@ -53,6 +55,7 @@ class WorkoutActivity : AppCompatActivity(), ServiceConnection, WorkoutContract.
private val grayscaleFilter: ColorMatrixColorFilter
private var isImageFlipped = false
private var ttsEnabled = false
private var workout: Workout? = null
init {
grayscaleMatrix.setSaturation(0f)
......@@ -66,12 +69,14 @@ class WorkoutActivity : AppCompatActivity(), ServiceConnection, WorkoutContract.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//todo fails when running in background, but activity card swiped away
volumeControlStream = AudioManager.STREAM_MUSIC
setContentView(R.layout.activity_workout)
val intent = getIntent()
ttsEnabled = intent.getBooleanExtra(TTS_KEY, false)
workout = intent.getParcelableExtra(WORKOUT_KEY)
// workout?.customColor?.toInt()?.let { headerBox.setBackgroundColor(it) } //todo make sure color is applied on restore as well
if (savedInstanceState != null) {
restoredState = savedInstanceState.get(STATE_KEY) as Parcelable?
......@@ -124,7 +129,7 @@ class WorkoutActivity : AppCompatActivity(), ServiceConnection, WorkoutContract.
override fun onServiceConnected(p0: ComponentName?, binder: IBinder) {
presenterService = (binder as WorkoutService.WorkoutBinder).service
binder.service.get()!!.setView(callback = this, savedState = restoredState, ttsEnabled = ttsEnabled)
binder.service.get()!!.setHostView(callback = this, savedState = restoredState, workout = workout!!, ttsEnabled = ttsEnabled)
setOnClickListeners(binder.service.get())
}
......
......@@ -4,11 +4,11 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.graphics.BitmapFactory
import android.os.Build
import android.support.annotation.RequiresApi
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationManagerCompat
import android.support.v4.content.ContextCompat
import com.enjoyingfoss.feeel.R
import com.enjoyingfoss.feeel.WorkoutContract
import com.enjoyingfoss.feeel.model.ExerciseMeta
......@@ -35,6 +35,7 @@ class WorkoutNotification(initService: Service, activityIntent: PendingIntent) :
private val notificationBuilder = NotificationCompat.Builder(initService, channelID)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // lock screen controls, with sensitive content
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(initService, R.color.colorPrimary))
.setContentIntent(activityIntent)
.setUsesChronometer(true)
.setShowWhen(false)
......@@ -56,7 +57,6 @@ class WorkoutNotification(initService: Service, activityIntent: PendingIntent) :
.setContentTitle(String.format(exerciseTitleTemplate,
service.get()?.getString(exercise.titleResource),
curSecondLimit))
.setLargeIcon(BitmapFactory.decodeResource(service.get()?.resources, exercise.imageResource))
.build()
notificationManagerCompat.notify(notificationID, notification)
}
......@@ -73,10 +73,6 @@ class WorkoutNotification(initService: Service, activityIntent: PendingIntent) :
String.format(
breakTitleTemplate,
service.getString(nextExercise.titleResource)))
.setLargeIcon(
BitmapFactory.decodeResource(
service.resources,
nextExerciseMeta.exercise.imageResource))
.build()
notificationManagerCompat.notify(notificationID, notification)
}
......
......@@ -22,7 +22,7 @@
app:srcCompat="@android:drawable/ic_media_play"/>
<TextView
android:id="@+id/textView"
android:id="@+id/titleTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
......
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/workoutLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.enjoyingfoss.feeel.view.WorkoutActivity">
<android.support.constraint.ConstraintLayout android:id="@+id/workoutLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.enjoyingfoss.feeel.view.WorkoutActivity">
<!--TODO use coordinatorLayout, also serving as headerBox? -->
......@@ -16,7 +16,7 @@
android:background="@color/colorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/tapIndicator"
......@@ -29,7 +29,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/timeText" />
app:layout_constraintTop_toBottomOf="@+id/timeText"/>
<TextView
android:id="@+id/timeText"
......@@ -41,7 +41,7 @@
android:textAppearance="@style/TimeHeadline"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"/>
<Button
android:id="@+id/titleButton"
......@@ -61,7 +61,7 @@
app:layout_constraintBottom_toTopOf="@+id/descriptionFrame"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
app:layout_constraintRight_toRightOf="parent"/>
<ImageView
android:id="@+id/exerciseImage"
......@@ -72,7 +72,7 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/guidelineImageTop"
tools:ignore="ContentDescription" />
tools:ignore="ContentDescription"/>
<ImageButton
android:id="@+id/playPauseButton"
......@@ -86,7 +86,7 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/timeText"
app:srcCompat="@drawable/ic64_play" />
app:srcCompat="@drawable/ic64_play"/>
<ImageButton
android:id="@+id/nextButton"
......@@ -100,7 +100,7 @@
app:layout_constraintBottom_toBottomOf="@+id/playPauseButton"
app:layout_constraintStart_toEndOf="@+id/playPauseButton"
app:layout_constraintTop_toTopOf="@+id/playPauseButton"
app:srcCompat="@drawable/ic32_next" />
app:srcCompat="@drawable/ic32_next"/>
<ImageButton
android:id="@+id/previousButton"
......@@ -114,7 +114,7 @@
app:layout_constraintBottom_toBottomOf="@+id/playPauseButton"
app:layout_constraintEnd_toStartOf="@+id/playPauseButton"
app:layout_constraintTop_toTopOf="@+id/playPauseButton"
app:srcCompat="@drawable/ic32_previous" />
app:srcCompat="@drawable/ic32_previous"/>
<ScrollView
android:id="@+id/descriptionFrame"
......@@ -135,6 +135,7 @@
android:id="@+id/descriptionText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
......@@ -148,7 +149,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.75" />
app:layout_constraintGuide_percent="0.75"/>
<android.support.constraint.Guideline
android:id="@+id/guidelineImageTop"
......@@ -157,7 +158,7 @@
android:layout_marginBottom="8dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="@id/headerBox"
app:layout_constraintGuide_begin="138dp" />
app:layout_constraintGuide_begin="138dp"/>
</android.support.constraint.ConstraintLayout>
\ No newline at end of file
......@@ -4,4 +4,7 @@
<color name="colorPrimaryDark">#08329D</color>
<color name="colorAccent">#EEFF41</color>
<color name="darkGrayBackground">#2C2C2C</color>
<!-- NIGHT/DAY -->
<color name="text">#000000</color>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="cover_headline">60dp</dimen>
<dimen name="countdown">144dp</dimen>
<dimen name="time_headline">72dp</dimen>
<dimen name="time_headline_small">24dp</dimen>
<dimen name="description_text">16dp</dimen>
<dimen name="time_headline">72sp</dimen>
<dimen name="time_headline_small">24sp</dimen>
<dimen name="header_height">168dp</dimen>
<dimen name="timeText_margin_top">24dp</dimen>
</resources>
\ No newline at end of file
......@@ -129,4 +129,14 @@
<string name="workout_notification_title">Workouts</string>
<string name="workout_notification_description">Appears when a workout is in progress while the app is in the background</string>
<string name="notification_title">%s (%d s.)</string>
<string name="workout_title_7minadvanced">Advanced 7 minute workout</string>
<string name="exercise_title_reverselungerotaiton">Reverse lunge with rotation</string>
<string name="exercise_title_pushuprowburpee">Push-up to row to burpee</string>
<string name="exercise_title_romaniancurlpress_l">Left-leg Romanian deadlift to curl to press</string>
<string name="exercise_title_romaniancurlpress_r">Right-leg Romanian deadlift to curl to press</string>
<string name="exercise_title_plankarmlift">Plank with arm lift</string>
<string name="exercise_title_laterallungetotricepsextension">Lateral lunge to overhead triceps extension</string>
<string name="exercise_title_bentoverrow">Bent-over row</string>
<string name="tbd">No description yet. Contribute one at https://gitlab.com/enjoyingfoss/feeel</string>
</resources>
\ No newline at end of file
......@@ -22,14 +22,6 @@
<item name="android:alpha">0.87</item>
</style>
<style name="Countdown">
<item name="android:textColor">#FFFFFF</item>
<item name="android:textSize">@dimen/countdown</item>
<item name="android:textStyle">bold</item>
<item name="android:fontFamily" tools:targetApi="jelly_bean">sans-serif-black</item>
<item name="android:alpha">0.87</item>
</style>
<style name="CoverHeadline">
<item name="android:textColor">#FFFFFF</item>
<item name="android:textSize">@dimen/cover_headline</item>
......@@ -46,7 +38,12 @@
<style name="DescriptionText">
<item name="android:textColor">#FFFFFF</item>
<item name="android:textSize">@dimen/description_text</item>
<item name="android:textSize">16sp</item>
</style>
<style name="ListTitle_DayNight">
<item name="android:textColor">@color/text</item>
<item name="android:textSize">16sp</item>
</style>
</resources>
......@@ -12,7 +12,7 @@ public class WorkoutServiceTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
workoutService = new WorkoutService();
workoutService.setView(view, null);
workoutService.setHostView(view, null);
}
@Test
......
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.2.41'
ext.kotlin_version = '1.2.50'
repositories {
google()
jcenter()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment