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);
        }
Exemple #2
0
        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);
        }
Exemple #7
0
        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);
        }