Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion app/src/main/java/org/librefit/db/AppDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import org.librefit.db.entity.Workout

@Database(
entities = [Workout::class, Exercise::class, Set::class, Measurement::class, ExerciseDC::class],
version = 3,
version = 4,
exportSchema = true,
autoMigrations = [
AutoMigration(from = 1, to = 2)
Expand All @@ -38,6 +38,31 @@ abstract class AppDatabase : RoomDatabase() {
companion object {
const val NAME = "librefit_database"

val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
// Add HIIT countdown fields to exercises table
db.execSQL(
"""
ALTER TABLE exercises
ADD COLUMN targetDuration INTEGER NOT NULL DEFAULT 0
""".trimIndent()
)
db.execSQL(
"""
ALTER TABLE exercises
ADD COLUMN autoAdvanceSets INTEGER NOT NULL DEFAULT 0
""".trimIndent()
)
// Add cautions field to dataset (ExerciseDC) table
db.execSQL(
"""
ALTER TABLE dataset
ADD COLUMN cautions TEXT NOT NULL DEFAULT ''
""".trimIndent()
)
}
}

val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/java/org/librefit/db/entity/Exercise.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,9 @@ data class Exercise(
val setMode: SetMode = SetMode.LOAD,
val restTime: Int = 0,
val position: Int = 0,
val workoutId: Long = 0// Foreign key reference to Workout
val workoutId: Long = 0,// Foreign key reference to Workout
/** Target duration in seconds for DURATION sets used in countdown mode (HIIT). 0 = use stopwatch instead. */
val targetDuration: Int = 0,
/** When true, sets auto-advance: countdown → rest → next set countdown, without user input. */
val autoAdvanceSets: Boolean = false
)
4 changes: 3 additions & 1 deletion app/src/main/java/org/librefit/db/entity/ExerciseDC.kt
Original file line number Diff line number Diff line change
Expand Up @@ -199,5 +199,7 @@ data class ExerciseDC(
val instructions: List<String> = listOf(),
val category: Category = Category.POWERLIFTING,
val images: List<String> = listOf(),
val isCustomExercise: Boolean = false
val isCustomExercise: Boolean = false,
/** Common mistakes and cautions for the exercise (user-facing text). */
val cautions: String = ""
)
2 changes: 1 addition & 1 deletion app/src/main/java/org/librefit/di/DatabaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ object DatabaseModule {
AppDatabase::class.java,
AppDatabase.NAME
)
.addMigrations(AppDatabase.MIGRATION_2_3)
.addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4)
.build()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ enum class WorkoutServiceActions(val string: String) {
MODIFY_REST_TIMER("PAUSE_REST_TIMER"),
WORKOUT_FOCUS("WORKOUT_FOCUS"),
STOP_SERVICE("STOP_SERVICE"),
SET_ELAPSED_TIME("SET_ELAPSED_TIME")
SET_ELAPSED_TIME("SET_ELAPSED_TIME"),
/** Start a HIIT countdown timer (counts down from targetDuration to 0). */
START_COUNTDOWN("START_COUNTDOWN"),
/** Cancel an active countdown without advancing to rest. */
CANCEL_COUNTDOWN("CANCEL_COUNTDOWN")
}
77 changes: 77 additions & 0 deletions app/src/main/java/org/librefit/services/WorkoutService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,39 @@ class WorkoutService : Service() {
private val _restTime = MutableStateFlow(0)
val restTime: StateFlow<Int> = _restTime

/** Countdown timer for HIIT sets (counts down to 0). */
private val _countdownTime = MutableStateFlow(0)
val countdownTime: StateFlow<Int> = _countdownTime

/** Whether a HIIT countdown is actively running. */
private val _isCountdownActive = MutableStateFlow(false)
val isCountdownActive: StateFlow<Boolean> = _isCountdownActive

/** Total duration for the current countdown (used for progress calculation). */
private val _countdownTotal = MutableStateFlow(0)
val countdownTotal: StateFlow<Int> = _countdownTotal

/** Emits true once when a countdown finishes — consumed by the ViewModel to auto-start rest. */
private val _countdownFinished = MutableStateFlow(false)
val countdownFinished: StateFlow<Boolean> = _countdownFinished

const val EXTRA_INITIAL_REST_TIME = "EXTRA_INITIAL_REST_TIME"
const val EXTRA_ADD_TEN_SECONDS = "EXTRA_ADD_TEN_SECONDS"
const val EXTRA_IS_FOCUSED = "EXTRA_IS_FOCUSED"
const val EXTRA_SET_ELAPSED_TIME = "EXTRA_SET_ELAPSED_TIME"
const val EXTRA_COUNTDOWN_DURATION = "EXTRA_COUNTDOWN_DURATION"
/** Rest duration to auto-start after countdown finishes (0 = skip rest). */
const val EXTRA_COUNTDOWN_REST_DURATION = "EXTRA_COUNTDOWN_REST_DURATION"

/** Acknowledge the [countdownFinished] signal (call from ViewModel after reading it). */
fun clearCountdownFinished() {
_countdownFinished.update { false }
}
}

private var initialRestTime = 0
private var isFocused = true
private var countdownRestDuration = 0

@Inject
lateinit var notificationHelper: NotificationHelper
Expand Down Expand Up @@ -145,6 +170,24 @@ class WorkoutService : Service() {
it + (intent?.getIntExtra(EXTRA_SET_ELAPSED_TIME, 0) ?: 0)
}
}

WorkoutServiceActions.START_COUNTDOWN -> {
val duration = intent?.getIntExtra(EXTRA_COUNTDOWN_DURATION, 0) ?: 0
countdownRestDuration =
intent?.getIntExtra(EXTRA_COUNTDOWN_REST_DURATION, 0) ?: 0
countdownJob?.cancel()
_countdownTotal.update { duration }
_countdownTime.update { duration }
_isCountdownActive.update { true }
_countdownFinished.update { false }
startCountdown()
}

WorkoutServiceActions.CANCEL_COUNTDOWN -> {
countdownJob?.cancel()
_isCountdownActive.update { false }
_countdownTime.update { 0 }
}
}

return START_STICKY
Expand All @@ -153,9 +196,15 @@ class WorkoutService : Service() {
fun stopService() {
stopwatchJob?.cancel()
restTimerJob?.cancel()
countdownJob?.cancel()
_timeElapsed.update { 0 }
_restTime.update { 0 }
_countdownTime.update { 0 }
_isCountdownActive.update { false }
_countdownFinished.update { false }
_countdownTotal.update { 0 }
initialRestTime = 0
countdownRestDuration = 0
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
}
Expand Down Expand Up @@ -200,6 +249,34 @@ class WorkoutService : Service() {
}


private var countdownJob: Job? = null

/**
* HIIT countdown: counts from [countdownTime] down to 0, then:
* 1. Signals [countdownFinished] for the ViewModel
* 2. Auto-starts the rest timer if [countdownRestDuration] > 0
*/
private fun startCountdown() {
countdownJob = serviceScope.launch {
while (countdownTime.value > 0) {
delay(1000)
_countdownTime.update { (it - 1).coerceAtLeast(0) }
}
_isCountdownActive.update { false }
_countdownFinished.update { true }

// Auto-start rest if configured
if (countdownRestDuration > 0) {
restTimerJob?.cancel()
initialRestTime = countdownRestDuration
_restTime.update { countdownRestDuration }
startRestTimer()
}
}
}



private var restTimerJob: Job? = null

private fun startRestTimer() {
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/org/librefit/services/WorkoutServiceManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import android.content.Intent
import dagger.hilt.android.qualifiers.ApplicationContext
import org.librefit.enums.WorkoutServiceActions
import org.librefit.services.WorkoutService.Companion.EXTRA_ADD_TEN_SECONDS
import org.librefit.services.WorkoutService.Companion.EXTRA_COUNTDOWN_DURATION
import org.librefit.services.WorkoutService.Companion.EXTRA_COUNTDOWN_REST_DURATION
import org.librefit.services.WorkoutService.Companion.EXTRA_INITIAL_REST_TIME
import org.librefit.services.WorkoutService.Companion.EXTRA_IS_FOCUSED
import org.librefit.services.WorkoutService.Companion.EXTRA_SET_ELAPSED_TIME
Expand Down Expand Up @@ -73,6 +75,26 @@ class WorkoutServiceManager @Inject constructor(
context.startForegroundService(serviceIntent)
}

/**
* Start a HIIT countdown timer that counts down from [durationSeconds] to 0,
* then auto-starts rest timer for [restDurationSeconds] if > 0.
*/
fun startCountdown(durationSeconds: Int, restDurationSeconds: Int) {
val serviceIntent = workoutServiceIntent.apply {
action = WorkoutServiceActions.START_COUNTDOWN.string
putExtra(EXTRA_COUNTDOWN_DURATION, durationSeconds)
putExtra(EXTRA_COUNTDOWN_REST_DURATION, restDurationSeconds)
}
context.startForegroundService(serviceIntent)
}

fun cancelCountdown() {
val serviceIntent = workoutServiceIntent.apply {
action = WorkoutServiceActions.CANCEL_COUNTDOWN.string
}
context.startForegroundService(serviceIntent)
}

fun stopService() {
val serviceIntent = workoutServiceIntent.apply {
action = WorkoutServiceActions.STOP_SERVICE.string
Expand Down
Loading
Loading