Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 0 additions & 8 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
BB0286322FD60C00014F981C /* Pods */,
);
sourceTree = "<group>";
};
Expand Down Expand Up @@ -105,13 +104,6 @@
path = Runner;
sourceTree = "<group>";
};
BB0286322FD60C00014F981C /* Pods */ = {
isa = PBXGroup;
children = (
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down
12 changes: 11 additions & 1 deletion lib/features/routines/providers/gym_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const PREFS_SHOW_TIMER = 'showTimerPrefs';
const PREFS_ALERT_COUNTDOWN = 'alertCountdownPrefs';
const PREFS_USE_COUNTDOWN_BETWEEN_SETS = 'useCountdownBetweenSetsPrefs';
const PREFS_COUNTDOWN_DURATION = 'countdownDurationSecondsPrefs';
const PREFS_LOG_SCOPE_WEEKS = 'logScopeWeeksPrefs';
const PREFS_SHOW_DISTINCT_LOGS = 'showDistinctLogsPrefs';

/// In seconds
const DEFAULT_COUNTDOWN_DURATION = 180;
Expand Down Expand Up @@ -177,6 +179,8 @@ class GymModeState {
final bool alertOnCountdownEnd;
final bool useCountdownBetweenSets;
final Duration countdownDuration;
final int? logScopeWeeks;
final bool showDistinctLogs;

// Routine data
late final int dayId;
Expand All @@ -193,7 +197,8 @@ class GymModeState {
this.alertOnCountdownEnd = true,
this.useCountdownBetweenSets = false,
this.countdownDuration = const Duration(seconds: DEFAULT_COUNTDOWN_DURATION),

this.logScopeWeeks,
this.showDistinctLogs = true,
int? dayId,
int? iteration,
Routine? routine,
Expand Down Expand Up @@ -234,6 +239,9 @@ class GymModeState {
bool? alertOnCountdownEnd,
bool? useCountdownBetweenSets,
int? countdownDuration,
int? logScopeWeeks,
bool clearLogScopeWeeks = false,
bool? showDistinctLogs,
}) {
return GymModeState(
isInitialized: isInitialized ?? this.isInitialized,
Expand All @@ -253,6 +261,8 @@ class GymModeState {
countdownDuration: Duration(
seconds: countdownDuration ?? this.countdownDuration.inSeconds,
),
logScopeWeeks: clearLogScopeWeeks ? null : (logScopeWeeks ?? this.logScopeWeeks),
showDistinctLogs: showDistinctLogs ?? this.showDistinctLogs,
);
}

Expand Down
36 changes: 34 additions & 2 deletions lib/features/routines/providers/gym_state_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,25 @@ class GymStateNotifier extends _$GymStateNotifier {
);
}

final logScopeWeeks = await prefs.getInt(PREFS_LOG_SCOPE_WEEKS);
if (logScopeWeeks != null && logScopeWeeks != state.logScopeWeeks) {
state = state.copyWith(logScopeWeeks: logScopeWeeks);
}

final showDistinctLogs = await prefs.getBool(PREFS_SHOW_DISTINCT_LOGS);
if (showDistinctLogs != null && showDistinctLogs != state.showDistinctLogs) {
state = state.copyWith(showDistinctLogs: showDistinctLogs);
}

_logger.finer(
'Loaded saved preferences: '
'showExercise=$showExercise '
'showTimer=$showTimer '
'alertOnCountdownEnd=$alertOnCountdownEnd '
'useCountdownBetweenSets=$useCountdownBetweenSets '
'defaultCountdownDurationSeconds=$defaultCountdownDurationSeconds',
'defaultCountdownDurationSeconds=$defaultCountdownDurationSeconds'
'logScopeWeeks=$logScopeWeeks '
'showDistinctLogs=$showDistinctLogs ',
);
}

Expand All @@ -92,13 +104,22 @@ class GymStateNotifier extends _$GymStateNotifier {
PREFS_COUNTDOWN_DURATION,
state.countdownDuration.inSeconds,
);
if (state.logScopeWeeks != null) {
await prefs.setInt(PREFS_LOG_SCOPE_WEEKS, state.logScopeWeeks!);
} else {
await prefs.remove(PREFS_LOG_SCOPE_WEEKS);
}
await prefs.setBool(PREFS_SHOW_DISTINCT_LOGS, state.showDistinctLogs);

_logger.finer(
'Saved preferences: '
'showExercise=${state.showExercisePages} '
'showTimer=${state.showTimerPages} '
'alertOnCountdownEnd=${state.alertOnCountdownEnd} '
'useCountdownBetweenSets=${state.useCountdownBetweenSets} '
'defaultCountdownDuration=${state.countdownDuration.inSeconds}',
'defaultCountdownDuration=${state.countdownDuration.inSeconds}'
'logScopeWeeks=${state.logScopeWeeks} '
'showDistinctLogs=${state.showDistinctLogs} ',
);
}

Expand Down Expand Up @@ -312,6 +333,17 @@ class GymStateNotifier extends _$GymStateNotifier {
_savePrefs();
}

/// Passing null resets the scope to the current routine
void setLogScopeWeeks(int? weeks) {
state = state.copyWith(logScopeWeeks: weeks, clearLogScopeWeeks: weeks == null);
_savePrefs();
}

void setShowDistinctLogs(bool value) {
state = state.copyWith(showDistinctLogs: value);
_savePrefs();
}

void markSlotPageAsDone(String uuid, {required bool isDone}) {
final slotPage = state.getSlotPageByUUID(uuid);
if (slotPage == null) {
Expand Down
2 changes: 1 addition & 1 deletion lib/features/routines/providers/gym_state_notifier.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 33 additions & 4 deletions lib/features/routines/providers/workout_logs_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import 'package:clock/clock.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:wger/features/routines/models/log.dart';
Expand All @@ -27,16 +28,44 @@ final workoutLogProvider = Provider<WorkoutLogMutations>((ref) {
return WorkoutLogMutations(ref.read(workoutLogRepositoryProvider));
});

/// Streams the past logs for [exerciseId] within [routineId], newest first.
/// Streams the past logs for [exerciseId], newest first.
///
/// Without [weeksBack] only the logs of [routineId] are returned. Setting it
/// widens the scope to all routines and instead limits the logs to the last
/// [weeksBack] weeks, in which case [routineId] is ignored. With [distinct],
/// only the newest log of each repetition/weight combination is kept.
@riverpod
Stream<List<Log>> pastExerciseLogs(
Ref ref, {
required int routineId,
required int exerciseId,
int? weeksBack,
bool distinct = true,
}) {
return ref
.read(workoutLogRepositoryProvider)
.watchLogsByExerciseDrift(routineId: routineId, exerciseId: exerciseId);
final repo = ref.read(workoutLogRepositoryProvider);
final cutoff = weeksBack != null ? clock.now().subtract(Duration(days: weeksBack * 7)) : null;

return repo
.watchLogsByExerciseDrift(
routineId: weeksBack == null ? routineId : null,
exerciseId: exerciseId,
since: cutoff,
)
.map((logs) => distinct ? _deduplicate(logs) : logs);
}

/// Keeps only the first log of each repetitions/weight combination, units included.
///
/// Expects [logs] to be sorted newest first, so the newest log of each
/// combination survives.
List<Log> _deduplicate(List<Log> logs) {
final seen = <(num?, int?, num?, int?)>{};

return logs
.where(
(log) => seen.add((log.repetitions, log.repetitionsUnitId, log.weight, log.weightUnitId)),
)
.toList();
}

class WorkoutLogMutations {
Expand Down
59 changes: 49 additions & 10 deletions lib/features/routines/providers/workout_logs_notifier.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 35 additions & 23 deletions lib/features/routines/providers/workout_logs_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,32 +41,44 @@ class WorkoutLogRepository {

WorkoutLogRepository(this._db);

/// Streams the logs for a single exercise within a routine, newest first,
/// with their repetition and weight units attached.
/// Streams the logs for a single exercise, newest first, with their
/// repetition and weight units attached.
///
/// Only logs belonging to [routineId] are returned, pass null for all
/// routines. [since] additionally restricts the result to logs on or after
/// that point in time.
Stream<List<Log>> watchLogsByExerciseDrift({
required int routineId,
int? routineId,
required int exerciseId,
DateTime? since,
}) {
_logger.finer('Watching local logs for routine $routineId, exercise $exerciseId');

final query =
_db.select(_db.workoutLogTable).join([
leftOuterJoin(
_db.routineRepetitionUnitTable,
_db.routineRepetitionUnitTable.id.equalsExp(_db.workoutLogTable.repetitionsUnitId),
),
leftOuterJoin(
_db.routineWeightUnitTable,
_db.routineWeightUnitTable.id.equalsExp(_db.workoutLogTable.weightUnitId),
),
])
..where(
_db.workoutLogTable.routineId.equals(routineId) &
_db.workoutLogTable.exerciseId.equals(exerciseId),
)
..orderBy([
OrderingTerm(expression: _db.workoutLogTable.date, mode: OrderingMode.desc),
]);
_logger.finer(
'Watching local logs for exercise $exerciseId, '
'routine ${routineId ?? 'any'}, since ${since ?? 'always'}',
);

final query = _db.select(_db.workoutLogTable).join([
leftOuterJoin(
_db.routineRepetitionUnitTable,
_db.routineRepetitionUnitTable.id.equalsExp(_db.workoutLogTable.repetitionsUnitId),
),
leftOuterJoin(
_db.routineWeightUnitTable,
_db.routineWeightUnitTable.id.equalsExp(_db.workoutLogTable.weightUnitId),
),
]);

var whereCondition = _db.workoutLogTable.exerciseId.equals(exerciseId);
if (routineId != null) {
whereCondition &= _db.workoutLogTable.routineId.equals(routineId);
}
if (since != null) {
whereCondition &= _db.workoutLogTable.date.isBiggerOrEqualValue(since);
}
query.where(whereCondition);
query.orderBy([
OrderingTerm(expression: _db.workoutLogTable.date, mode: OrderingMode.desc),
]);

return query.watch().map((rows) {
return rows.map((row) {
Expand Down
Loading