incyclist-services 1.7.81 → 1.7.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -389,6 +389,13 @@ let RouteCard = (() => {
389
389
  showNextOverwrite = false;
390
390
  }
391
391
  }
392
+ if (showNextOverwrite) {
393
+ const nextVideoEnabled = this.isMobile() ? this.getAppState().hasFeature('MOBILE_NEXT_VIDEO') : true;
394
+ if (!nextVideoEnabled) {
395
+ showNextOverwrite = false;
396
+ uiSettings.nextOverwrite = true;
397
+ }
398
+ }
392
399
  }
393
400
  catch (err) {
394
401
  this.logError(err, 'openSettings');
@@ -540,6 +547,7 @@ let RouteCard = (() => {
540
547
  start() {
541
548
  try {
542
549
  const service = (0, service_1.getRouteList)();
550
+ this.enforceNextVideoOverrideOnMobile();
543
551
  service.select(this.route);
544
552
  service.setStartSettings({ type: this.getCardType(), ...this.startSettings });
545
553
  this.route.description.tsLastStart = Date.now();
@@ -556,6 +564,7 @@ let RouteCard = (() => {
556
564
  addWorkout() {
557
565
  try {
558
566
  const service = (0, service_1.getRouteList)();
567
+ this.enforceNextVideoOverrideOnMobile();
559
568
  service.select(this.route);
560
569
  service.setStartSettings({ type: this.getCardType(), ...this.startSettings });
561
570
  this.route.description.tsLastStart = Date.now();
@@ -565,6 +574,21 @@ let RouteCard = (() => {
565
574
  this.logError(err, 'addWorkout');
566
575
  }
567
576
  }
577
+ enforceNextVideoOverrideOnMobile() {
578
+ try {
579
+ if (!this.isMobile())
580
+ return;
581
+ if (!this.startSettings || !(0, route_1.hasNextVideo)(this.route))
582
+ return;
583
+ const nextVideoEnabled = this.getAppState().hasFeature('MOBILE_NEXT_VIDEO');
584
+ if (!nextVideoEnabled) {
585
+ this.startSettings.nextOverwrite = true;
586
+ }
587
+ }
588
+ catch (err) {
589
+ this.logError(err, 'enforceNextVideoOverrideOnMobile');
590
+ }
591
+ }
568
592
  getCurrentDownload() {
569
593
  return this.downloadObserver;
570
594
  }
@@ -145,6 +145,10 @@ let WorkoutListService = (() => {
145
145
  }
146
146
  close() {
147
147
  this.getWorkoutCalendar().setActive(false);
148
+ this.logEvent({ message: 'close workout list' });
149
+ this.emit('closed', this.observer);
150
+ this.observer?.emit('stopped');
151
+ this.observer?.reset();
148
152
  }
149
153
  openSettings() {
150
154
  let workouts;
@@ -43,6 +43,7 @@ const observer_1 = require("../../base/types/observer");
43
43
  const settings_1 = require("../../settings");
44
44
  const calendar_1 = require("../calendar");
45
45
  const service_1 = require("../list/service");
46
+ const devices_1 = require("../../devices");
46
47
  const DEFAULT_IMPORT_GROUP = 'My Workouts';
47
48
  const UPCOMING_COLLAPSED_COUNT = 3;
48
49
  let WorkoutListPageService = (() => {
@@ -52,6 +53,7 @@ let WorkoutListPageService = (() => {
52
53
  let _classThis;
53
54
  let _classSuper = pages_1.IncyclistPageService;
54
55
  let _instanceExtraInitializers = [];
56
+ let _getDevicePairing_decorators;
55
57
  let _getWorkoutList_decorators;
56
58
  let _getWorkoutCalendar_decorators;
57
59
  let _getUserSettings_decorators;
@@ -60,10 +62,12 @@ let WorkoutListPageService = (() => {
60
62
  static { _classThis = this; }
61
63
  static {
62
64
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
65
+ _getDevicePairing_decorators = [decorators_1.Injectable];
63
66
  _getWorkoutList_decorators = [decorators_1.Injectable];
64
67
  _getWorkoutCalendar_decorators = [decorators_1.Injectable];
65
68
  _getUserSettings_decorators = [decorators_1.Injectable];
66
69
  _getAppState_decorators = [decorators_1.Injectable];
70
+ __esDecorate(this, null, _getDevicePairing_decorators, { kind: "method", name: "getDevicePairing", static: false, private: false, access: { has: obj => "getDevicePairing" in obj, get: obj => obj.getDevicePairing }, metadata: _metadata }, null, _instanceExtraInitializers);
67
71
  __esDecorate(this, null, _getWorkoutList_decorators, { kind: "method", name: "getWorkoutList", static: false, private: false, access: { has: obj => "getWorkoutList" in obj, get: obj => obj.getWorkoutList }, metadata: _metadata }, null, _instanceExtraInitializers);
68
72
  __esDecorate(this, null, _getWorkoutCalendar_decorators, { kind: "method", name: "getWorkoutCalendar", static: false, private: false, access: { has: obj => "getWorkoutCalendar" in obj, get: obj => obj.getWorkoutCalendar }, metadata: _metadata }, null, _instanceExtraInitializers);
69
73
  __esDecorate(this, null, _getUserSettings_decorators, { kind: "method", name: "getUserSettings", static: false, private: false, access: { has: obj => "getUserSettings" in obj, get: obj => obj.getUserSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -264,9 +268,15 @@ let WorkoutListPageService = (() => {
264
268
  const card = this.findWorkoutCard(id);
265
269
  if (!card)
266
270
  return;
267
- const settings = this.getWorkoutList().getStartSettings();
271
+ const service = this.getWorkoutList();
272
+ const pairing = this.getDevicePairing();
273
+ const settings = service.getStartSettings() ?? {};
268
274
  card.select({ ...settings, noRoute: opts?.noRoute });
269
- this.getPageObserver()?.emit('start-ride', { id, noRoute: opts?.noRoute });
275
+ this.logEvent({ message: 'Attempting to start a ride', id, ...settings, noRoute: opts?.noRoute, readyToStart: pairing.isReadyToStart(), });
276
+ this.detailWorkoutId = null;
277
+ service.close();
278
+ const next = pairing.isReadyToStart() ? '/rideDeviceOK' : '/pairingStart';
279
+ this.moveTo(next);
270
280
  }
271
281
  catch (err) {
272
282
  this.logError(err, 'onStart');
@@ -455,6 +465,9 @@ let WorkoutListPageService = (() => {
455
465
  this.logError(err, 'emitImportUpdate');
456
466
  }
457
467
  }
468
+ getDevicePairing() {
469
+ return (0, devices_1.useDevicePairing)();
470
+ }
458
471
  getWorkoutList() {
459
472
  return (0, service_1.useWorkoutList)();
460
473
  }
@@ -41,11 +41,13 @@ const types_1 = require("../../../base/types");
41
41
  const pages_1 = require("../../../base/pages");
42
42
  const display_1 = require("../../../ride/display");
43
43
  const activities_1 = require("../../../activities");
44
+ const settings_1 = require("../../../settings");
44
45
  const service_1 = require("../service");
45
46
  const graph_1 = require("../../base/graph");
46
47
  const BACKGROUND_PAUSE_TIMEOUT_MS = 300000;
47
48
  const UPCOMING_STEPS_COUNT = 3;
48
49
  const DEFAULT_LOAD_INCREMENT = 1;
50
+ const HINTS_WORKOUT_GESTURES_KEY = 'hints.workoutRideGestures';
49
51
  let WorkoutRidePageService = (() => {
50
52
  let _classDecorators = [types_1.Singleton];
51
53
  let _classDescriptor;
@@ -56,6 +58,7 @@ let WorkoutRidePageService = (() => {
56
58
  let _getRideDisplay_decorators;
57
59
  let _getWorkoutRide_decorators;
58
60
  let _getActivityRide_decorators;
61
+ let _getUserSettings_decorators;
59
62
  var WorkoutRidePageService = class extends _classSuper {
60
63
  static { _classThis = this; }
61
64
  static {
@@ -63,9 +66,11 @@ let WorkoutRidePageService = (() => {
63
66
  _getRideDisplay_decorators = [decorators_1.Injectable];
64
67
  _getWorkoutRide_decorators = [decorators_1.Injectable];
65
68
  _getActivityRide_decorators = [decorators_1.Injectable];
69
+ _getUserSettings_decorators = [decorators_1.Injectable];
66
70
  __esDecorate(this, null, _getRideDisplay_decorators, { kind: "method", name: "getRideDisplay", static: false, private: false, access: { has: obj => "getRideDisplay" in obj, get: obj => obj.getRideDisplay }, metadata: _metadata }, null, _instanceExtraInitializers);
67
71
  __esDecorate(this, null, _getWorkoutRide_decorators, { kind: "method", name: "getWorkoutRide", static: false, private: false, access: { has: obj => "getWorkoutRide" in obj, get: obj => obj.getWorkoutRide }, metadata: _metadata }, null, _instanceExtraInitializers);
68
72
  __esDecorate(this, null, _getActivityRide_decorators, { kind: "method", name: "getActivityRide", static: false, private: false, access: { has: obj => "getActivityRide" in obj, get: obj => obj.getActivityRide }, metadata: _metadata }, null, _instanceExtraInitializers);
73
+ __esDecorate(this, null, _getUserSettings_decorators, { kind: "method", name: "getUserSettings", static: false, private: false, access: { has: obj => "getUserSettings" in obj, get: obj => obj.getUserSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
69
74
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
70
75
  WorkoutRidePageService = _classThis = _classDescriptor.value;
71
76
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
@@ -77,7 +82,7 @@ let WorkoutRidePageService = (() => {
77
82
  backgroundTimer;
78
83
  backgroundPausedByService = false;
79
84
  menuProps = null;
80
- isInitialized = false;
85
+ gestureHintDismissed = false;
81
86
  constructor() {
82
87
  super('WorkoutRidePage');
83
88
  this.rideEventHandler['state-update'] = this.onRideStateUpdate.bind(this);
@@ -92,13 +97,10 @@ let WorkoutRidePageService = (() => {
92
97
  try {
93
98
  this.logEvent({ message: 'page shown', page: 'WorkoutRide' });
94
99
  gd_eventlog_1.EventLogger.setGlobalConfig('page', 'WorkoutRide');
100
+ this.gestureHintDismissed = false;
95
101
  super.openPage();
96
102
  try {
97
103
  const service = this.getRideDisplay();
98
- if (!this.isInitialized) {
99
- service.init();
100
- this.isInitialized = true;
101
- }
102
104
  this.registerRideEventHandlers();
103
105
  this.subscribeToWorkoutObserver();
104
106
  service.start(simulate);
@@ -120,7 +122,6 @@ let WorkoutRidePageService = (() => {
120
122
  this.unregisterRideEventHandlers();
121
123
  this.unsubscribeFromWorkoutObserver();
122
124
  this.menuProps = null;
123
- this.isInitialized = false;
124
125
  super.closePage();
125
126
  }
126
127
  catch (err) {
@@ -168,7 +169,8 @@ let WorkoutRidePageService = (() => {
168
169
  title: wo.title ?? '',
169
170
  graph: this.buildGraphPlan(current, wo.ftp),
170
171
  steps: this.buildUpcomingSteps(current, wo.ftp),
171
- dashboard: this.buildDashboardLine(wo)
172
+ dashboard: this.buildDashboardLine(wo),
173
+ gestureHint: this.buildGestureHint(base.startOverlayProps === null)
172
174
  };
173
175
  }
174
176
  catch (err) {
@@ -284,6 +286,18 @@ let WorkoutRidePageService = (() => {
284
286
  this.logError(err, 'onIgnoreStart');
285
287
  }
286
288
  }
289
+ onGestureHintDismissed({ dontShowAgain }) {
290
+ try {
291
+ this.gestureHintDismissed = true;
292
+ if (dontShowAgain) {
293
+ this.getUserSettings().set(HINTS_WORKOUT_GESTURES_KEY, true);
294
+ }
295
+ this.updatePageDisplay();
296
+ }
297
+ catch (err) {
298
+ this.logError(err, 'onGestureHintDismissed');
299
+ }
300
+ }
287
301
  onCancelStart() {
288
302
  try {
289
303
  this.rideObserver?.stop();
@@ -314,19 +328,15 @@ let WorkoutRidePageService = (() => {
314
328
  switch (state) {
315
329
  case 'Paused':
316
330
  this.menuProps = { showResume: true, ...this.getStepFlags() };
317
- this.updatePageDisplay();
318
331
  break;
319
332
  case 'Active':
320
333
  this.menuProps = null;
321
- this.updatePageDisplay();
322
334
  break;
323
335
  case 'Finished':
324
336
  this.emitNavigateBack();
325
- break;
326
- case 'Error':
327
- this.updatePageDisplay();
328
- break;
337
+ return;
329
338
  }
339
+ this.updatePageDisplay();
330
340
  }
331
341
  onWorkoutUpdate() {
332
342
  this.updatePageDisplay();
@@ -367,13 +377,26 @@ let WorkoutRidePageService = (() => {
367
377
  startGateProps: null
368
378
  };
369
379
  }
380
+ buildGestureHint(startOverlayCleared) {
381
+ if (this.gestureHintDismissed || !startOverlayCleared)
382
+ return null;
383
+ if (this.getElapsedActivityTime() !== 0 || this.getCurrentCadence() !== 0)
384
+ return null;
385
+ if (this.getUserSettings().get(HINTS_WORKOUT_GESTURES_KEY, false))
386
+ return null;
387
+ return { visible: true };
388
+ }
389
+ getCurrentCadence() {
390
+ return this.getActivityRide().getCurrentValues?.()?.cadence ?? 0;
391
+ }
370
392
  buildGraphPlan(current, ftp) {
371
393
  if (!current) {
372
394
  return { bars: [], ftp: ftp ?? 0, ftpLine: ftp ?? 0, domain: { x: [0, 0], y: [0, 0] } };
373
395
  }
374
396
  const bars = (0, graph_1.getWorkoutGraphSeries)(current, { ftp, absValues: true });
375
397
  const lastBarX = bars.length ? bars.at(-1).x : 0;
376
- const maxX = Math.max(this.getLastLogTime(), lastBarX, current.duration ?? 0);
398
+ const maxXCandidates = [this.getLastLogTime(), lastBarX, current.duration ?? 0].filter(Number.isFinite);
399
+ const maxX = maxXCandidates.length ? Math.max(...maxXCandidates) : 0;
377
400
  const maxBarPower = bars.length ? Math.max(...bars.map(b => b.y)) : 0;
378
401
  return {
379
402
  bars,
@@ -460,7 +483,8 @@ let WorkoutRidePageService = (() => {
460
483
  title: '',
461
484
  graph: { bars: [], ftp: 0, ftpLine: 0, domain: { x: [0, 0], y: [0, 0] } },
462
485
  steps: { previous: null, current: null, upcoming: [], hasMore: false },
463
- dashboard: { text: '', mode: null }
486
+ dashboard: { text: '', mode: null },
487
+ gestureHint: null
464
488
  };
465
489
  }
466
490
  updatePageDisplay() {
@@ -490,6 +514,9 @@ let WorkoutRidePageService = (() => {
490
514
  getActivityRide() {
491
515
  return (0, activities_1.useActivityRide)();
492
516
  }
517
+ getUserSettings() {
518
+ return (0, settings_1.useUserSettings)();
519
+ }
493
520
  };
494
521
  return WorkoutRidePageService = _classThis;
495
522
  })();
@@ -43,7 +43,7 @@ import { valid } from "../../../utils/valid";
43
43
  import { waitNextTick } from "../../../utils";
44
44
  import { useRouteDownload } from "../../download/service";
45
45
  import { EventLogger } from "gd-eventlog";
46
- import { checkIsLoop, getNextVideoId, getPosition, updateSlopes } from "../../base/utils/route";
46
+ import { checkIsLoop, getNextVideoId, hasNextVideo, getPosition, updateSlopes } from "../../base/utils/route";
47
47
  import { getWorkoutList } from "../../../workouts";
48
48
  import { checkIsNew } from "../utils";
49
49
  import { useOnlineStatusMonitoring } from "../../../monitoring";
@@ -386,6 +386,13 @@ let RouteCard = (() => {
386
386
  showNextOverwrite = false;
387
387
  }
388
388
  }
389
+ if (showNextOverwrite) {
390
+ const nextVideoEnabled = this.isMobile() ? this.getAppState().hasFeature('MOBILE_NEXT_VIDEO') : true;
391
+ if (!nextVideoEnabled) {
392
+ showNextOverwrite = false;
393
+ uiSettings.nextOverwrite = true;
394
+ }
395
+ }
389
396
  }
390
397
  catch (err) {
391
398
  this.logError(err, 'openSettings');
@@ -537,6 +544,7 @@ let RouteCard = (() => {
537
544
  start() {
538
545
  try {
539
546
  const service = getRouteList();
547
+ this.enforceNextVideoOverrideOnMobile();
540
548
  service.select(this.route);
541
549
  service.setStartSettings({ type: this.getCardType(), ...this.startSettings });
542
550
  this.route.description.tsLastStart = Date.now();
@@ -553,6 +561,7 @@ let RouteCard = (() => {
553
561
  addWorkout() {
554
562
  try {
555
563
  const service = getRouteList();
564
+ this.enforceNextVideoOverrideOnMobile();
556
565
  service.select(this.route);
557
566
  service.setStartSettings({ type: this.getCardType(), ...this.startSettings });
558
567
  this.route.description.tsLastStart = Date.now();
@@ -562,6 +571,21 @@ let RouteCard = (() => {
562
571
  this.logError(err, 'addWorkout');
563
572
  }
564
573
  }
574
+ enforceNextVideoOverrideOnMobile() {
575
+ try {
576
+ if (!this.isMobile())
577
+ return;
578
+ if (!this.startSettings || !hasNextVideo(this.route))
579
+ return;
580
+ const nextVideoEnabled = this.getAppState().hasFeature('MOBILE_NEXT_VIDEO');
581
+ if (!nextVideoEnabled) {
582
+ this.startSettings.nextOverwrite = true;
583
+ }
584
+ }
585
+ catch (err) {
586
+ this.logError(err, 'enforceNextVideoOverrideOnMobile');
587
+ }
588
+ }
565
589
  getCurrentDownload() {
566
590
  return this.downloadObserver;
567
591
  }
@@ -142,6 +142,10 @@ let WorkoutListService = (() => {
142
142
  }
143
143
  close() {
144
144
  this.getWorkoutCalendar().setActive(false);
145
+ this.logEvent({ message: 'close workout list' });
146
+ this.emit('closed', this.observer);
147
+ this.observer?.emit('stopped');
148
+ this.observer?.reset();
145
149
  }
146
150
  openSettings() {
147
151
  let workouts;
@@ -40,6 +40,7 @@ import { Observer } from "../../base/types/observer";
40
40
  import { useUserSettings } from "../../settings";
41
41
  import { useWorkoutCalendar } from "../calendar";
42
42
  import { useWorkoutList } from "../list/service";
43
+ import { useDevicePairing } from "../../devices";
43
44
  const DEFAULT_IMPORT_GROUP = 'My Workouts';
44
45
  const UPCOMING_COLLAPSED_COUNT = 3;
45
46
  let WorkoutListPageService = (() => {
@@ -49,6 +50,7 @@ let WorkoutListPageService = (() => {
49
50
  let _classThis;
50
51
  let _classSuper = IncyclistPageService;
51
52
  let _instanceExtraInitializers = [];
53
+ let _getDevicePairing_decorators;
52
54
  let _getWorkoutList_decorators;
53
55
  let _getWorkoutCalendar_decorators;
54
56
  let _getUserSettings_decorators;
@@ -57,10 +59,12 @@ let WorkoutListPageService = (() => {
57
59
  static { _classThis = this; }
58
60
  static {
59
61
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
62
+ _getDevicePairing_decorators = [Injectable];
60
63
  _getWorkoutList_decorators = [Injectable];
61
64
  _getWorkoutCalendar_decorators = [Injectable];
62
65
  _getUserSettings_decorators = [Injectable];
63
66
  _getAppState_decorators = [Injectable];
67
+ __esDecorate(this, null, _getDevicePairing_decorators, { kind: "method", name: "getDevicePairing", static: false, private: false, access: { has: obj => "getDevicePairing" in obj, get: obj => obj.getDevicePairing }, metadata: _metadata }, null, _instanceExtraInitializers);
64
68
  __esDecorate(this, null, _getWorkoutList_decorators, { kind: "method", name: "getWorkoutList", static: false, private: false, access: { has: obj => "getWorkoutList" in obj, get: obj => obj.getWorkoutList }, metadata: _metadata }, null, _instanceExtraInitializers);
65
69
  __esDecorate(this, null, _getWorkoutCalendar_decorators, { kind: "method", name: "getWorkoutCalendar", static: false, private: false, access: { has: obj => "getWorkoutCalendar" in obj, get: obj => obj.getWorkoutCalendar }, metadata: _metadata }, null, _instanceExtraInitializers);
66
70
  __esDecorate(this, null, _getUserSettings_decorators, { kind: "method", name: "getUserSettings", static: false, private: false, access: { has: obj => "getUserSettings" in obj, get: obj => obj.getUserSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -261,9 +265,15 @@ let WorkoutListPageService = (() => {
261
265
  const card = this.findWorkoutCard(id);
262
266
  if (!card)
263
267
  return;
264
- const settings = this.getWorkoutList().getStartSettings();
268
+ const service = this.getWorkoutList();
269
+ const pairing = this.getDevicePairing();
270
+ const settings = service.getStartSettings() ?? {};
265
271
  card.select({ ...settings, noRoute: opts?.noRoute });
266
- this.getPageObserver()?.emit('start-ride', { id, noRoute: opts?.noRoute });
272
+ this.logEvent({ message: 'Attempting to start a ride', id, ...settings, noRoute: opts?.noRoute, readyToStart: pairing.isReadyToStart(), });
273
+ this.detailWorkoutId = null;
274
+ service.close();
275
+ const next = pairing.isReadyToStart() ? '/rideDeviceOK' : '/pairingStart';
276
+ this.moveTo(next);
267
277
  }
268
278
  catch (err) {
269
279
  this.logError(err, 'onStart');
@@ -452,6 +462,9 @@ let WorkoutListPageService = (() => {
452
462
  this.logError(err, 'emitImportUpdate');
453
463
  }
454
464
  }
465
+ getDevicePairing() {
466
+ return useDevicePairing();
467
+ }
455
468
  getWorkoutList() {
456
469
  return useWorkoutList();
457
470
  }
@@ -38,11 +38,13 @@ import { Singleton } from "../../../base/types";
38
38
  import { IncyclistPageService } from "../../../base/pages";
39
39
  import { useRideDisplay } from "../../../ride/display";
40
40
  import { useActivityRide } from "../../../activities";
41
+ import { useUserSettings } from "../../../settings";
41
42
  import { useWorkoutRide } from "../service";
42
43
  import { getFlattenedSteps, getStepDuration, getStepTargetText, getWorkoutGraphSeries } from "../../base/graph";
43
44
  const BACKGROUND_PAUSE_TIMEOUT_MS = 300000;
44
45
  const UPCOMING_STEPS_COUNT = 3;
45
46
  const DEFAULT_LOAD_INCREMENT = 1;
47
+ const HINTS_WORKOUT_GESTURES_KEY = 'hints.workoutRideGestures';
46
48
  let WorkoutRidePageService = (() => {
47
49
  let _classDecorators = [Singleton];
48
50
  let _classDescriptor;
@@ -53,6 +55,7 @@ let WorkoutRidePageService = (() => {
53
55
  let _getRideDisplay_decorators;
54
56
  let _getWorkoutRide_decorators;
55
57
  let _getActivityRide_decorators;
58
+ let _getUserSettings_decorators;
56
59
  var WorkoutRidePageService = class extends _classSuper {
57
60
  static { _classThis = this; }
58
61
  static {
@@ -60,9 +63,11 @@ let WorkoutRidePageService = (() => {
60
63
  _getRideDisplay_decorators = [Injectable];
61
64
  _getWorkoutRide_decorators = [Injectable];
62
65
  _getActivityRide_decorators = [Injectable];
66
+ _getUserSettings_decorators = [Injectable];
63
67
  __esDecorate(this, null, _getRideDisplay_decorators, { kind: "method", name: "getRideDisplay", static: false, private: false, access: { has: obj => "getRideDisplay" in obj, get: obj => obj.getRideDisplay }, metadata: _metadata }, null, _instanceExtraInitializers);
64
68
  __esDecorate(this, null, _getWorkoutRide_decorators, { kind: "method", name: "getWorkoutRide", static: false, private: false, access: { has: obj => "getWorkoutRide" in obj, get: obj => obj.getWorkoutRide }, metadata: _metadata }, null, _instanceExtraInitializers);
65
69
  __esDecorate(this, null, _getActivityRide_decorators, { kind: "method", name: "getActivityRide", static: false, private: false, access: { has: obj => "getActivityRide" in obj, get: obj => obj.getActivityRide }, metadata: _metadata }, null, _instanceExtraInitializers);
70
+ __esDecorate(this, null, _getUserSettings_decorators, { kind: "method", name: "getUserSettings", static: false, private: false, access: { has: obj => "getUserSettings" in obj, get: obj => obj.getUserSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
66
71
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
67
72
  WorkoutRidePageService = _classThis = _classDescriptor.value;
68
73
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
@@ -74,7 +79,7 @@ let WorkoutRidePageService = (() => {
74
79
  backgroundTimer;
75
80
  backgroundPausedByService = false;
76
81
  menuProps = null;
77
- isInitialized = false;
82
+ gestureHintDismissed = false;
78
83
  constructor() {
79
84
  super('WorkoutRidePage');
80
85
  this.rideEventHandler['state-update'] = this.onRideStateUpdate.bind(this);
@@ -89,13 +94,10 @@ let WorkoutRidePageService = (() => {
89
94
  try {
90
95
  this.logEvent({ message: 'page shown', page: 'WorkoutRide' });
91
96
  EventLogger.setGlobalConfig('page', 'WorkoutRide');
97
+ this.gestureHintDismissed = false;
92
98
  super.openPage();
93
99
  try {
94
100
  const service = this.getRideDisplay();
95
- if (!this.isInitialized) {
96
- service.init();
97
- this.isInitialized = true;
98
- }
99
101
  this.registerRideEventHandlers();
100
102
  this.subscribeToWorkoutObserver();
101
103
  service.start(simulate);
@@ -117,7 +119,6 @@ let WorkoutRidePageService = (() => {
117
119
  this.unregisterRideEventHandlers();
118
120
  this.unsubscribeFromWorkoutObserver();
119
121
  this.menuProps = null;
120
- this.isInitialized = false;
121
122
  super.closePage();
122
123
  }
123
124
  catch (err) {
@@ -165,7 +166,8 @@ let WorkoutRidePageService = (() => {
165
166
  title: wo.title ?? '',
166
167
  graph: this.buildGraphPlan(current, wo.ftp),
167
168
  steps: this.buildUpcomingSteps(current, wo.ftp),
168
- dashboard: this.buildDashboardLine(wo)
169
+ dashboard: this.buildDashboardLine(wo),
170
+ gestureHint: this.buildGestureHint(base.startOverlayProps === null)
169
171
  };
170
172
  }
171
173
  catch (err) {
@@ -281,6 +283,18 @@ let WorkoutRidePageService = (() => {
281
283
  this.logError(err, 'onIgnoreStart');
282
284
  }
283
285
  }
286
+ onGestureHintDismissed({ dontShowAgain }) {
287
+ try {
288
+ this.gestureHintDismissed = true;
289
+ if (dontShowAgain) {
290
+ this.getUserSettings().set(HINTS_WORKOUT_GESTURES_KEY, true);
291
+ }
292
+ this.updatePageDisplay();
293
+ }
294
+ catch (err) {
295
+ this.logError(err, 'onGestureHintDismissed');
296
+ }
297
+ }
284
298
  onCancelStart() {
285
299
  try {
286
300
  this.rideObserver?.stop();
@@ -311,19 +325,15 @@ let WorkoutRidePageService = (() => {
311
325
  switch (state) {
312
326
  case 'Paused':
313
327
  this.menuProps = { showResume: true, ...this.getStepFlags() };
314
- this.updatePageDisplay();
315
328
  break;
316
329
  case 'Active':
317
330
  this.menuProps = null;
318
- this.updatePageDisplay();
319
331
  break;
320
332
  case 'Finished':
321
333
  this.emitNavigateBack();
322
- break;
323
- case 'Error':
324
- this.updatePageDisplay();
325
- break;
334
+ return;
326
335
  }
336
+ this.updatePageDisplay();
327
337
  }
328
338
  onWorkoutUpdate() {
329
339
  this.updatePageDisplay();
@@ -364,13 +374,26 @@ let WorkoutRidePageService = (() => {
364
374
  startGateProps: null
365
375
  };
366
376
  }
377
+ buildGestureHint(startOverlayCleared) {
378
+ if (this.gestureHintDismissed || !startOverlayCleared)
379
+ return null;
380
+ if (this.getElapsedActivityTime() !== 0 || this.getCurrentCadence() !== 0)
381
+ return null;
382
+ if (this.getUserSettings().get(HINTS_WORKOUT_GESTURES_KEY, false))
383
+ return null;
384
+ return { visible: true };
385
+ }
386
+ getCurrentCadence() {
387
+ return this.getActivityRide().getCurrentValues?.()?.cadence ?? 0;
388
+ }
367
389
  buildGraphPlan(current, ftp) {
368
390
  if (!current) {
369
391
  return { bars: [], ftp: ftp ?? 0, ftpLine: ftp ?? 0, domain: { x: [0, 0], y: [0, 0] } };
370
392
  }
371
393
  const bars = getWorkoutGraphSeries(current, { ftp, absValues: true });
372
394
  const lastBarX = bars.length ? bars.at(-1).x : 0;
373
- const maxX = Math.max(this.getLastLogTime(), lastBarX, current.duration ?? 0);
395
+ const maxXCandidates = [this.getLastLogTime(), lastBarX, current.duration ?? 0].filter(Number.isFinite);
396
+ const maxX = maxXCandidates.length ? Math.max(...maxXCandidates) : 0;
374
397
  const maxBarPower = bars.length ? Math.max(...bars.map(b => b.y)) : 0;
375
398
  return {
376
399
  bars,
@@ -457,7 +480,8 @@ let WorkoutRidePageService = (() => {
457
480
  title: '',
458
481
  graph: { bars: [], ftp: 0, ftpLine: 0, domain: { x: [0, 0], y: [0, 0] } },
459
482
  steps: { previous: null, current: null, upcoming: [], hasMore: false },
460
- dashboard: { text: '', mode: null }
483
+ dashboard: { text: '', mode: null },
484
+ gestureHint: null
461
485
  };
462
486
  }
463
487
  updatePageDisplay() {
@@ -487,6 +511,9 @@ let WorkoutRidePageService = (() => {
487
511
  getActivityRide() {
488
512
  return useActivityRide();
489
513
  }
514
+ getUserSettings() {
515
+ return useUserSettings();
516
+ }
490
517
  };
491
518
  return WorkoutRidePageService = _classThis;
492
519
  })();
@@ -1,4 +1,4 @@
1
- export type FeatureToggle = 'NEW_SEARCH_UI' | 'CONTROLLERS' | 'MOBILE_WORKOUTS' | 'WEBBLE_WINDOWS';
1
+ export type FeatureToggle = 'NEW_SEARCH_UI' | 'CONTROLLERS' | 'MOBILE_WORKOUTS' | 'WEBBLE_WINDOWS' | 'MOBILE_NEXT_VIDEO';
2
2
  export type Interfaces = 'ant' | 'ble' | 'serial' | 'wifi' | 'tcpip';
3
3
  export type BLEFeatures = {
4
4
  services: Array<string>;
@@ -78,6 +78,7 @@ export declare class RouteCard extends BaseCard implements Card<Route> {
78
78
  start(): void;
79
79
  cancel(): void;
80
80
  addWorkout(): void;
81
+ protected enforceNextVideoOverrideOnMobile(): void;
81
82
  getCurrentDownload(): Observer;
82
83
  getVideoDir(): string;
83
84
  setVideoDir(dir: string): void;
@@ -50,6 +50,7 @@ export declare class WorkoutListPageService extends IncyclistPageService impleme
50
50
  protected formatDuration(duration: number): string;
51
51
  protected emitPageUpdate(): void;
52
52
  protected emitImportUpdate(): void;
53
+ protected getDevicePairing(): import("../../devices").DevicePairingService;
53
54
  protected getWorkoutList(): WorkoutListService;
54
55
  protected getWorkoutCalendar(): import("../calendar").WorkoutCalendarService;
55
56
  protected getUserSettings(): import("../../settings").UserSettingsService;
@@ -4,7 +4,7 @@ import type { CurrentRideState, RideType } from "../../../types";
4
4
  import type { WorkoutDisplayProperties } from "../types";
5
5
  import type { Workout } from "../../base/model";
6
6
  import type { StepDefinition } from "../../base/model/types";
7
- import type { IWorkoutRidePageService, WorkoutDashboardLine, WorkoutGraphActuals, WorkoutGraphPlan, WorkoutRideMenuProps, WorkoutRidePageDisplayProps, WorkoutUpcomingSteps } from "./types";
7
+ import type { IWorkoutRidePageService, WorkoutDashboardLine, WorkoutGestureHint, WorkoutGraphActuals, WorkoutGraphPlan, WorkoutRideMenuProps, WorkoutRidePageDisplayProps, WorkoutUpcomingSteps } from "./types";
8
8
  export declare class WorkoutRidePageService extends IncyclistPageService implements IWorkoutRidePageService {
9
9
  protected rideEventHandler: Record<string, any>;
10
10
  protected workoutEventHandler: Record<string, any>;
@@ -12,7 +12,7 @@ export declare class WorkoutRidePageService extends IncyclistPageService impleme
12
12
  protected backgroundTimer: NodeJS.Timeout | undefined;
13
13
  protected backgroundPausedByService: boolean;
14
14
  protected menuProps: WorkoutRideMenuProps | null;
15
- protected isInitialized: boolean;
15
+ protected gestureHintDismissed: boolean;
16
16
  constructor();
17
17
  openPage(simulate?: boolean): IObserver;
18
18
  closePage(): void;
@@ -32,6 +32,9 @@ export declare class WorkoutRidePageService extends IncyclistPageService impleme
32
32
  onDecreaseLoad(): void;
33
33
  onRetryStart(): void;
34
34
  onIgnoreStart(): void;
35
+ onGestureHintDismissed({ dontShowAgain }: {
36
+ dontShowAgain: boolean;
37
+ }): void;
35
38
  onCancelStart(): void;
36
39
  adjustLoad(deltaPct: number): void;
37
40
  protected onRideStateUpdate(state: CurrentRideState): void;
@@ -48,6 +51,8 @@ export declare class WorkoutRidePageService extends IncyclistPageService impleme
48
51
  menuProps: WorkoutRideMenuProps;
49
52
  startGateProps: any;
50
53
  };
54
+ protected buildGestureHint(startOverlayCleared: boolean): WorkoutGestureHint | null;
55
+ protected getCurrentCadence(): number;
51
56
  protected buildGraphPlan(current: Workout | undefined, ftp: number): WorkoutGraphPlan;
52
57
  protected buildUpcomingSteps(current: Workout | undefined, ftp: number): WorkoutUpcomingSteps;
53
58
  protected buildDashboardLine(wo: WorkoutDisplayProperties): WorkoutDashboardLine;
@@ -66,5 +71,6 @@ export declare class WorkoutRidePageService extends IncyclistPageService impleme
66
71
  protected getRideDisplay(): import("../../../ride/display").RideDisplayService;
67
72
  protected getWorkoutRide(): import("../service").WorkoutRide;
68
73
  protected getActivityRide(): import("../../../activities").ActivityRideService;
74
+ protected getUserSettings(): import("../../../settings").UserSettingsService;
69
75
  }
70
76
  export declare const getWorkoutRidePageService: () => WorkoutRidePageService;
@@ -42,12 +42,16 @@ export interface WorkoutRideMenuProps {
42
42
  canStepBack: boolean;
43
43
  canStepForward: boolean;
44
44
  }
45
+ export interface WorkoutGestureHint {
46
+ visible: boolean;
47
+ }
45
48
  export interface WorkoutRidePageDisplayProps extends RidePageDisplayProps {
46
49
  menuProps: WorkoutRideMenuProps | null;
47
50
  graph: WorkoutGraphPlan;
48
51
  steps: WorkoutUpcomingSteps;
49
52
  dashboard: WorkoutDashboardLine;
50
53
  title: string;
54
+ gestureHint: WorkoutGestureHint | null;
51
55
  }
52
56
  export interface WorkoutRidePageCallbacks {
53
57
  onMenuOpen(): void;
@@ -62,6 +66,9 @@ export interface WorkoutRidePageCallbacks {
62
66
  onRetryStart(): void;
63
67
  onIgnoreStart(): void;
64
68
  onCancelStart(): void;
69
+ onGestureHintDismissed(props: {
70
+ dontShowAgain: boolean;
71
+ }): void;
65
72
  }
66
73
  export interface IWorkoutRidePageService extends WorkoutRidePageCallbacks, IPageService {
67
74
  getRideObserver(): IObserver | null;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.7.81",
3
+ "version": "1.7.83",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.27"
6
6
  },
7
7
  "dependencies": {
8
8
  "@garmin/fitsdk": "^21.200.0",
9
9
  "axios": "^1.15.2",
10
- "incyclist-devices": "^3.0.23",
10
+ "incyclist-devices": "^3.0.24",
11
11
  "promise.any": "^2.0.6",
12
12
  "semver": "^7.7.4",
13
13
  "tcx-builder": "^1.1.1",