示例#1
0
        public static TimeSpan DurationOfPauses(this BlockItem This)
        {
            var ret = This.PauseList.Aggregate(TimeSpan.Zero,
                                               (acc, x) => acc + (x.EndedAt - x.StartedAt));

            This.Log().InfoFormat("Pause duration: {0}", ret);
            return(ret);
        }
        public BlockTimerWindow(BlockItem Model)
        {
            this.InitializeComponent();

            ViewModel = new BlockTimerViewModel(Model);

            // N.B: This is a bit of a hack - the completion of this observable
            // happens regardless of the value; I tried to use OnError for this
            // instead but it actually ended up throwing the exception.
            ViewModel.TimerState
                .Where(x => x == BlockTimerViewState.ShouldCancel)
                .Subscribe(
                    _ => Dispatcher.BeginInvoke(new Action(() => Close())),
                    () => Dispatcher.BeginInvoke(new Action(() => Close())));

            Observable.Merge(
                    Observable.FromEvent<SizeChangedEventArgs>(this, "SizeChanged").Select(_ => new Unit()),
                    ViewModel.WhenAny(x => x.ProgressPercentage, _ => new Unit()))
                .Select(_ => progressParentBorder.ActualWidth * ViewModel.ProgressPercentage)
                .Subscribe(x => progressBorder.Width = x);
        }
        public BlockTimerViewModel(BlockItem Model)
        {
            this.Model = Model;

            // Complete the TimerState when the break finishes
            // When the TimerState ends (i.e. the dialog is closed), set the
            // EndedAt time.
            //
            // Note that it's really important that whenever you use any
            // time-based Rx operator like Timer or Delay, that you use an
            // RxApp-based scheduler. If you don't do this (and it's really easy
            // to forget!), your unit tests won't work properly!
            var timer = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1.0), RxApp.DeferredScheduler)
                .TakeWhile(_ => !isFinishedWithBreak())
                .Finally(() => {
                    this.Model.EndedAt = RxApp.DeferredScheduler.Now;
                    _TimerState.OnCompleted();
                });

            // COOLSTUFF: Defining a Command-based State Machine
            //
            // Here, we're creating our Commands - let's look a bit further into
            // how this actually works. Looking back to our problem definition,
            // we can see that many of these buttons' CanExecute() are tied to
            // each other (i.e. Hitting Pause means I can't hit it again until I
            // hit Start).
            //
            // Here, we're defining the conditions when each of these buttons
            // are allowed to be clicked. In the next block, we convert each of
            // the Execute() Observables (each command is an Observable who
            // fires whenever it is Executed), into the state that we *should*
            // move to once the button is hit. We then subscribe to it, and move
            // to the new state.

            // Define when we can click each of these commands
            Start = new ReactiveCommand(
                _TimerState.Select(x => x == BlockTimerViewState.Initialized || x == BlockTimerViewState.Paused));
            Pause = new ReactiveCommand(
                _TimerState.Select(x => x == BlockTimerViewState.Started || x == BlockTimerViewState.StartedInBreak));
            Cancel = new ReactiveCommand(
                _TimerState.Select(x => x != BlockTimerViewState.Initialized));

            // Move to the next state appropriately when the Start, Pause, and Cancel buttons are clicked
            Observable.Merge(
                Start.Select(_ => isInBreak() ? BlockTimerViewState.StartedInBreak : BlockTimerViewState.Started),
                Pause.Select(_ => BlockTimerViewState.Paused),
                Cancel.Select(_ => BlockTimerViewState.ShouldCancel)
            ).Subscribe(_TimerState.OnNext);

            // Set the StartedAt time on the Model when we click the Start
            // button. It's also important here to use RxApp's Now instead of
            // DateTime.Now, or else our tests will be very confused.
            Start.Subscribe(_ => this.Model.StartedAt = this.Model.StartedAt ?? RxApp.DeferredScheduler.Now);

            // Create an Observable who yields the current State whenever the
            // timer fires - it has the Rate of the Timer but the Value of the
            // TimerState.
            var timerAsState = timer.CombineLatest(_TimerState, (_, ts) => ts);

            // Take the timer and derive a version that only notifies us when
            // the TimerState has changed
            var distinctTimerAsState = timerAsState.DistinctUntilChanged();

            // Invalidate the object when they hit Cancel
            Cancel.Subscribe(x => {
                Model.StartedAt = null; Model.EndedAt = null;
            });

            // When someone hits the Start button, then Pause, then the Start
            // button, we create a record of how long they pause.
            //
            // This code is a bit tricky - the key is BufferWithCount; if we
            // have a sequence [1,2,3,4,5], BufferWithCount(3,1) will return:
            // [[1,2,3], [2,3,4], [3,4,5], ...]: advancing forward one at a
            // time, returning the last three items. This is important, since
            // we're looking for the pattern, "Start, Pause, Start"
            //
            // Also, we again see here, whenever we use a Time-based operator
            // like Timestamp, that we use an RxApp scheduler. This is a very
            // common yet annoying-to-debug mistake!
            distinctTimerAsState
                .Timestamp(RxApp.DeferredScheduler)
                .Buffer(3 /*items in buffer*/, 1 /*at a time*/)
                .Where(isStateSequenceAPause)
                .Subscribe(x => this.Model.AddRecordOfPause(new PauseRecord() {
                    StartedAt = x[1].Timestamp,
                    EndedAt = x[2].Timestamp,
                }));

            // Move to the Break when the normal timer expires
            timerAsState
                .Where(ts => isInBreak() && ts == BlockTimerViewState.Started)
                .Subscribe(_ => _TimerState.OnNext(BlockTimerViewState.StartedInBreak));

            /* COOLSTUFF: Superpowered Value Converters
             *
             * Here we're binding the application state Observables we've
             * created above into real WPF properties that we can bind to (since
             * WPF can't bind directly to Observables). However, unlike
             * IValueConverters which are very simple, we can do much more
             * interesting things here like filtering.
             */

            //
            // Set up our output properties
            //

            // When the state is Started, move the timer display forward
            _TimeRemaining = timerAsState
                .Where(isTimerCurrentlyRunning)
                .Select(_ => currentTimeRemaining()).StartWith(currentTimeRemaining())
                .ToProperty(this, x => x.TimeRemaining);

            // Take the TimeSpan and convert it to the Caption (basically an
            // IValueConverter).
            //
            // The WhenAny() operator allows us to easily bind one or more
            // properties to another property, while still pulling in the
            // Initial value (whereas ObservableForProperty would only update
            // the property once it had changed at least once)
            _TimeRemainingCaption = this
                .WhenAny(x => x.TimeRemaining, x => String.Format("{0:mm}:{0:ss}", x.Value))
                .ToProperty(this, x => x.TimeRemainingCaption);

            _ProgressPercentage = timerAsState
                .Where(isTimerCurrentlyRunning)
                .Select(_ => progressBarPercentage())
                .ToProperty(this, x => x.ProgressPercentage);

            // Map states to background colors
            var colorLookupTable = new Dictionary<BlockTimerViewState, Brush> {
                {BlockTimerViewState.Initialized, null},
                {BlockTimerViewState.Paused, new SolidColorBrush(Color.FromRgb(0xff, 0xff, 0x66))},
                {BlockTimerViewState.Started, new SolidColorBrush(Color.FromRgb(0x99, 0xff, 0x66))},
                {BlockTimerViewState.StartedInBreak, new SolidColorBrush(Color.FromRgb(0x99, 0xff, 0xff))},
                {BlockTimerViewState.ShouldCancel, null},
            };

            // Every time the state changes on a Timer beat, update the
            // background color.
            _ProgressBackgroundBrush = distinctTimerAsState.Select(x => colorLookupTable[x])
                .ToProperty(this, x => x.ProgressBackgroundBrush);

            //
            // Everything's set up! Kick off everything by moving to the
            // Initialized state
            //

            _TimerState.OnNext(BlockTimerViewState.Initialized);
        }
示例#4
0
 public BlockItemTileViewModel(BlockItem model)
 {
     Model = model;
 }