public ExerciseViewModel(ISchedulerService schedulerService, Exercise model, IObservable<ExecutionContext> executionContext) { schedulerService.AssertNotNull(nameof(schedulerService)); model.AssertNotNull(nameof(model)); executionContext.AssertNotNull(nameof(executionContext)); this.disposables = new CompositeDisposable(); this.model = model; executionContext .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.ExecutionContext = x) .AddTo(this.disposables); Observable .CombineLatest( this .WhenAnyValue(x => x.ExecutionContext) .Select(ec => ec == null ? Observable.Never<TimeSpan>() : ec.WhenAnyValue(x => x.SkipAhead)) .Switch(), this .WhenAnyValue(x => x.ExecutionContext) .Select(ec => ec == null ? Observable.Never<Exercise>() : ec.WhenAnyValue(x => x.CurrentExercise)) .Switch(), (skip, current) => skip == TimeSpan.Zero && current == this.model) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.IsActive = x) .AddTo(this.disposables); this .WhenAnyValue(x => x.ExecutionContext) .Select( ec => ec == null ? Observable.Return(TimeSpan.Zero) : ec .WhenAnyValue(x => x.CurrentExerciseProgress) .Where(_ => ec.CurrentExercise == this.model) .StartWith(TimeSpan.Zero)) .Switch() .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.ProgressTimeSpan = x) .AddTo(this.disposables); this .WhenAny( x => x.Duration, x => x.ProgressTimeSpan, (duration, progressTimeSpan) => progressTimeSpan.Value.TotalMilliseconds / duration.Value.TotalMilliseconds) .Select(progressRatio => double.IsNaN(progressRatio) || double.IsInfinity(progressRatio) ? 0d : progressRatio) .Select(progressRatio => Math.Min(1d, progressRatio)) .Select(progressRatio => Math.Max(0d, progressRatio)) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.Progress = x) .AddTo(this.disposables); }
public ExerciseViewModel(ISchedulerService schedulerService, Exercise model, IObservable <ExecutionContext> executionContext) { schedulerService.AssertNotNull(nameof(schedulerService)); model.AssertNotNull(nameof(model)); executionContext.AssertNotNull(nameof(executionContext)); this.disposables = new CompositeDisposable(); this.model = model; executionContext .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.ExecutionContext = x) .AddTo(this.disposables); Observable .CombineLatest( this .WhenAnyValue(x => x.ExecutionContext) .Select(ec => ec == null ? Observable.Never <TimeSpan>() : ec.WhenAnyValue(x => x.SkipAhead)) .Switch(), this .WhenAnyValue(x => x.ExecutionContext) .Select(ec => ec == null ? Observable.Never <Exercise>() : ec.WhenAnyValue(x => x.CurrentExercise)) .Switch(), (skip, current) => skip == TimeSpan.Zero && current == this.model) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.IsActive = x) .AddTo(this.disposables); this .WhenAnyValue(x => x.ExecutionContext) .Select( ec => ec == null ? Observable.Return(TimeSpan.Zero) : ec .WhenAnyValue(x => x.CurrentExerciseProgress) .Where(_ => ec.CurrentExercise == this.model) .StartWith(TimeSpan.Zero)) .Switch() .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.ProgressTimeSpan = x) .AddTo(this.disposables); this .WhenAny( x => x.Duration, x => x.ProgressTimeSpan, (duration, progressTimeSpan) => progressTimeSpan.Value.TotalMilliseconds / duration.Value.TotalMilliseconds) .Select(progressRatio => double.IsNaN(progressRatio) || double.IsInfinity(progressRatio) ? 0d : progressRatio) .Select(progressRatio => Math.Min(1d, progressRatio)) .Select(progressRatio => Math.Max(0d, progressRatio)) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.Progress = x) .AddTo(this.disposables); }
public AudioService(ISchedulerService schedulerService) { schedulerService.AssertNotNull(nameof(schedulerService)); this.schedulerService = schedulerService; }
public ExerciseProgramViewModel( ILoggerService loggerService, ISchedulerService schedulerService, IScreen hostScreen, ExerciseProgram model) { loggerService.AssertNotNull(nameof(loggerService)); schedulerService.AssertNotNull(nameof(schedulerService)); hostScreen.AssertNotNull(nameof(hostScreen)); model.AssertNotNull(nameof(model)); this.logger = loggerService.GetLogger(this.GetType()); this.schedulerService = schedulerService; this.model = model; this.hostScreen = hostScreen; this.disposables = new CompositeDisposable(); this.exercises = this.model.Exercises.CreateDerivedCollection(x => new ExerciseViewModel(schedulerService, x, this.WhenAnyValue(y => y.ExecutionContext))); this .WhenAnyValue( x => x.ExecutionContext, x => x.ExecutionContext.IsCancelled, (ec, isCancelled) => ec != null && !isCancelled) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.IsStarted = x) .AddTo(this.disposables); this .WhenAnyValue(x => x.ExecutionContext) .Select(x => x == null ? Observable.Return(false) : x.WhenAnyValue(y => y.IsPaused)) .Switch() .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.IsPaused = x) .AddTo(this.disposables); this .WhenAnyValue(x => x.ExecutionContext) .Select(x => x == null ? Observable.Return(TimeSpan.Zero) : x.WhenAnyValue(y => y.Progress)) .Switch() .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.ProgressTimeSpan = x) .AddTo(this.disposables); this .WhenAnyValue(x => x.ProgressTimeSpan) .Select(x => x.TotalMilliseconds / this.model.Duration.TotalMilliseconds) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.Progress = x) .AddTo(this.disposables); this .WhenAnyValue( x => x.ExecutionContext, x => x.ExecutionContext.CurrentExercise, (ec, currentExercise) => ec == null ? null : currentExercise) .Select(x => this.Exercises.SingleOrDefault(y => y.Model == x)) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.CurrentExercise = x) .AddTo(this.disposables); var canStart = this .WhenAnyValue(x => x.IsStarted) .Select(x => !x) .ObserveOn(schedulerService.MainScheduler) .Do(x => System.Diagnostics.Debug.WriteLine("CanStart changing to " + x)); this.startCommand = ReactiveCommand .CreateAsyncObservable(canStart, this.OnStartAsync, schedulerService.MainScheduler) .AddTo(this.disposables); var canPause = this .WhenAnyValue(x => x.IsStarted) .CombineLatest(this.WhenAnyValue(x => x.ExecutionContext.IsPaused), (isStarted, isPaused) => isStarted && !isPaused) .ObserveOn(schedulerService.MainScheduler); this.pauseCommand = ReactiveCommand .CreateAsyncObservable(canPause, this.OnPauseAsync, schedulerService.MainScheduler) .AddTo(this.disposables); var canResume = this .WhenAnyValue(x => x.IsStarted) .CombineLatest(this.WhenAnyValue(x => x.ExecutionContext.IsPaused), (isStarted, isPaused) => isStarted && isPaused) .ObserveOn(schedulerService.MainScheduler); this.resumeCommand = ReactiveCommand .CreateAsyncObservable(canResume, this.OnResumeAsync, schedulerService.MainScheduler) .AddTo(this.disposables); var canSkipBackwards = this .WhenAnyValue( x => x.ExecutionContext, x => x.ProgressTimeSpan, (ec, progress) => new { ExecutionContext = ec, Progress = progress }) .Select(x => x.ExecutionContext != null && x.Progress >= skipBackwardsThreshold) .ObserveOn(schedulerService.MainScheduler); this.skipBackwardsCommand = ReactiveCommand .CreateAsyncObservable(canSkipBackwards, this.OnSkipBackwardsAsync, schedulerService.MainScheduler) .AddTo(this.disposables); var canSkipForwards = this .WhenAnyValue( x => x.ExecutionContext, x => x.CurrentExercise, (ec, currentExercise) => new { ExecutionContext = ec, CurrentExercise = currentExercise }) .Select(x => x.ExecutionContext != null && x.CurrentExercise != null && x.CurrentExercise != this.exercises.LastOrDefault()) .ObserveOn(schedulerService.MainScheduler); this.skipForwardsCommand = ReactiveCommand .CreateAsyncObservable(canSkipForwards, this.OnSkipForwardsAsync, schedulerService.MainScheduler) .AddTo(this.disposables); this.startCommand .CanExecuteObservable .Subscribe(x => this.IsStartVisible = x) .AddTo(this.disposables); this.pauseCommand .CanExecuteObservable .Subscribe(x => this.IsPauseVisible = x) .AddTo(this.disposables); this.resumeCommand .CanExecuteObservable .Subscribe(x => this.IsResumeVisible = x) .AddTo(this.disposables); this.startCommand .ThrownExceptions .Subscribe(ex => this.OnThrownException("start", ex)) .AddTo(this.disposables); this.pauseCommand .ThrownExceptions .Subscribe(ex => this.OnThrownException("pause", ex)) .AddTo(this.disposables); this.resumeCommand .ThrownExceptions .Subscribe(ex => this.OnThrownException("resume", ex)) .AddTo(this.disposables); this.skipBackwardsCommand .ThrownExceptions .Subscribe(ex => this.OnThrownException("skip backwards", ex)) .AddTo(this.disposables); this.skipForwardsCommand .ThrownExceptions .Subscribe(ex => this.OnThrownException("skip forwards", ex)) .AddTo(this.disposables); // we don't use a reactive command here because switching in different commands causes it to get confused and // command binding leaves the target button disabled. We could also have not used command binding to get around // this problem this.playbackCommand = new PlaybackCommandImpl(this); // cancel the exercise program if the user navigates away this .hostScreen .Router .NavigationStack .ItemsRemoved .OfType<ExerciseProgramViewModel>() .SelectMany(x => x.StopAsync()) .Subscribe() .AddTo(this.disposables); }
public DelayService(ISchedulerService schedulerService) { schedulerService.AssertNotNull(nameof(schedulerService)); this.schedulerService = schedulerService; }
public ExerciseProgramsViewModel( IAudioService audioService, IDelayService delayService, IExerciseDocumentService exerciseDocumentService, ILoggerService loggerService, ISchedulerService schedulerService, ISpeechService speechService, IStateService stateService, IScreen hostScreen, ExerciseProgramViewModelFactory exerciseProgramViewModelFactory) { audioService.AssertNotNull(nameof(audioService)); delayService.AssertNotNull(nameof(delayService)); exerciseDocumentService.AssertNotNull(nameof(exerciseDocumentService)); loggerService.AssertNotNull(nameof(loggerService)); schedulerService.AssertNotNull(nameof(schedulerService)); speechService.AssertNotNull(nameof(speechService)); stateService.AssertNotNull(nameof(stateService)); hostScreen.AssertNotNull(nameof(hostScreen)); exerciseProgramViewModelFactory.AssertNotNull(nameof(exerciseProgramViewModelFactory)); this.exerciseDocumentService = exerciseDocumentService; this.stateService = stateService; this.logger = loggerService.GetLogger(this.GetType()); this.hostScreen = hostScreen; this.disposables = new CompositeDisposable(); var documentsFromCache = this .stateService .GetAsync <string>(exerciseProgramsCacheKey) .Where(x => x != null) .Select(x => new DocumentSourceWith <string>(DocumentSource.Cache, x)); var documentsFromService = this .exerciseDocumentService .ExerciseDocument .Where(x => x != null) .Select(x => new DocumentSourceWith <string>(DocumentSource.Service, x)); var documents = documentsFromCache .Catch((Exception ex) => Observable.Empty <DocumentSourceWith <string> >()) .Concat(documentsFromService) .Do(x => this.logger.Debug("Received document from {0}.", x.Source)) .Publish(); var safeDocuments = documents .Catch((Exception ex) => Observable.Empty <DocumentSourceWith <string> >()); var results = documents .ObserveOn(schedulerService.TaskPoolScheduler) .Select( x => { IResult <ExercisePrograms> parsedExercisePrograms; using (this.logger.Perf("Parsing exercise programs from {0}.", x.Source)) { parsedExercisePrograms = ExercisePrograms.TryParse(x.Item, audioService, delayService, loggerService, speechService); } return(new DocumentSourceWith <IResult <ExercisePrograms> >(x.Source, parsedExercisePrograms)); }) .Publish(); var safeResults = results .Catch((Exception ex) => Observable.Empty <DocumentSourceWith <IResult <ExercisePrograms> > >()); safeResults .Select(x => x.Item.WasSuccessful ? null : x.Item.ToString()) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.ParseErrorMessage = x) .AddTo(this.disposables); results .Select(x => !x.Item.WasSuccessful ? ExerciseProgramsViewModelStatus.ParseFailed : x.Source == DocumentSource.Cache ? ExerciseProgramsViewModelStatus.LoadedFromCache : ExerciseProgramsViewModelStatus.LoadedFromService) .Catch((Exception ex) => Observable.Return(ExerciseProgramsViewModelStatus.LoadFailed)) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.Status = x) .AddTo(this.disposables); safeResults .Select(x => x.Item.WasSuccessful ? x.Item.Value : null) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.Model = x) .AddTo(this.disposables); this.WhenAnyValue(x => x.Model) .Select(x => x == null ? null : x.Programs.CreateDerivedCollection(y => exerciseProgramViewModelFactory(y))) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.Programs = x) .AddTo(this.disposables); safeDocuments .Where(x => x.Source == DocumentSource.Service) .SelectMany(x => this.stateService.SetAsync(exerciseProgramsCacheKey, x.Item)) .Subscribe() .AddTo(this.disposables); results .Connect() .AddTo(this.disposables); documents .Connect() .AddTo(this.disposables); this .WhenAnyValue(x => x.SelectedProgram) .Where(x => x != null) .Subscribe(x => this.hostScreen.Router.Navigate.Execute(x)) .AddTo(this.disposables); this .hostScreen .Router .CurrentViewModel .OfType <ExerciseProgramsViewModel>() .Subscribe(x => x.SelectedProgram = null) .AddTo(this.disposables); }
public ExerciseProgramViewModel( ILoggerService loggerService, ISchedulerService schedulerService, IScreen hostScreen, ExerciseProgram model) { loggerService.AssertNotNull(nameof(loggerService)); schedulerService.AssertNotNull(nameof(schedulerService)); hostScreen.AssertNotNull(nameof(hostScreen)); model.AssertNotNull(nameof(model)); this.logger = loggerService.GetLogger(this.GetType()); this.schedulerService = schedulerService; this.model = model; this.hostScreen = hostScreen; this.disposables = new CompositeDisposable(); this.exercises = this.model.Exercises.CreateDerivedCollection(x => new ExerciseViewModel(schedulerService, x, this.WhenAnyValue(y => y.ExecutionContext))); this .WhenAnyValue( x => x.ExecutionContext, x => x.ExecutionContext.IsCancelled, (ec, isCancelled) => ec != null && !isCancelled) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.IsStarted = x) .AddTo(this.disposables); this .WhenAnyValue(x => x.ExecutionContext) .Select(x => x == null ? Observable.Return(false) : x.WhenAnyValue(y => y.IsPaused)) .Switch() .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.IsPaused = x) .AddTo(this.disposables); this .WhenAnyValue(x => x.ExecutionContext) .Select(x => x == null ? Observable.Return(TimeSpan.Zero) : x.WhenAnyValue(y => y.Progress)) .Switch() .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.ProgressTimeSpan = x) .AddTo(this.disposables); this .WhenAnyValue(x => x.ProgressTimeSpan) .Select(x => x.TotalMilliseconds / this.model.Duration.TotalMilliseconds) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.Progress = x) .AddTo(this.disposables); this .WhenAnyValue( x => x.ExecutionContext, x => x.ExecutionContext.CurrentExercise, (ec, currentExercise) => ec == null ? null : currentExercise) .Select(x => this.Exercises.SingleOrDefault(y => y.Model == x)) .ObserveOn(schedulerService.MainScheduler) .Subscribe(x => this.CurrentExercise = x) .AddTo(this.disposables); var canStart = this .WhenAnyValue(x => x.IsStarted) .Select(x => !x) .ObserveOn(schedulerService.MainScheduler) .Do(x => System.Diagnostics.Debug.WriteLine("CanStart changing to " + x)); this.startCommand = ReactiveCommand .CreateAsyncObservable(canStart, this.OnStartAsync, schedulerService.MainScheduler) .AddTo(this.disposables); var canPause = this .WhenAnyValue(x => x.IsStarted) .CombineLatest(this.WhenAnyValue(x => x.ExecutionContext.IsPaused), (isStarted, isPaused) => isStarted && !isPaused) .ObserveOn(schedulerService.MainScheduler); this.pauseCommand = ReactiveCommand .CreateAsyncObservable(canPause, this.OnPauseAsync, schedulerService.MainScheduler) .AddTo(this.disposables); var canResume = this .WhenAnyValue(x => x.IsStarted) .CombineLatest(this.WhenAnyValue(x => x.ExecutionContext.IsPaused), (isStarted, isPaused) => isStarted && isPaused) .ObserveOn(schedulerService.MainScheduler); this.resumeCommand = ReactiveCommand .CreateAsyncObservable(canResume, this.OnResumeAsync, schedulerService.MainScheduler) .AddTo(this.disposables); var canSkipBackwards = this .WhenAnyValue( x => x.ExecutionContext, x => x.ProgressTimeSpan, (ec, progress) => new { ExecutionContext = ec, Progress = progress }) .Select(x => x.ExecutionContext != null && x.Progress >= skipBackwardsThreshold) .ObserveOn(schedulerService.MainScheduler); this.skipBackwardsCommand = ReactiveCommand .CreateAsyncObservable(canSkipBackwards, this.OnSkipBackwardsAsync, schedulerService.MainScheduler) .AddTo(this.disposables); var canSkipForwards = this .WhenAnyValue( x => x.ExecutionContext, x => x.CurrentExercise, (ec, currentExercise) => new { ExecutionContext = ec, CurrentExercise = currentExercise }) .Select(x => x.ExecutionContext != null && x.CurrentExercise != null && x.CurrentExercise != this.exercises.LastOrDefault()) .ObserveOn(schedulerService.MainScheduler); this.skipForwardsCommand = ReactiveCommand .CreateAsyncObservable(canSkipForwards, this.OnSkipForwardsAsync, schedulerService.MainScheduler) .AddTo(this.disposables); this.startCommand .CanExecuteObservable .Subscribe(x => this.IsStartVisible = x) .AddTo(this.disposables); this.pauseCommand .CanExecuteObservable .Subscribe(x => this.IsPauseVisible = x) .AddTo(this.disposables); this.resumeCommand .CanExecuteObservable .Subscribe(x => this.IsResumeVisible = x) .AddTo(this.disposables); this.startCommand .ThrownExceptions .Subscribe(ex => this.OnThrownException("start", ex)) .AddTo(this.disposables); this.pauseCommand .ThrownExceptions .Subscribe(ex => this.OnThrownException("pause", ex)) .AddTo(this.disposables); this.resumeCommand .ThrownExceptions .Subscribe(ex => this.OnThrownException("resume", ex)) .AddTo(this.disposables); this.skipBackwardsCommand .ThrownExceptions .Subscribe(ex => this.OnThrownException("skip backwards", ex)) .AddTo(this.disposables); this.skipForwardsCommand .ThrownExceptions .Subscribe(ex => this.OnThrownException("skip forwards", ex)) .AddTo(this.disposables); // we don't use a reactive command here because switching in different commands causes it to get confused and // command binding leaves the target button disabled. We could also have not used command binding to get around // this problem this.playbackCommand = new PlaybackCommandImpl(this); // cancel the exercise program if the user navigates away this .hostScreen .Router .NavigationStack .ItemsRemoved .OfType <ExerciseProgramViewModel>() .SelectMany(x => x.StopAsync()) .Subscribe() .AddTo(this.disposables); }