private static string GetDataVisualState(IDataLoaderState state)
 {
     if (state.IsInitial)
     {
         return(InitialVisualStateName);
     }
     else if (state.IsEmpty)
     {
         return(EmptyVisualStateName);
     }
     else
     {
         return(DataVisualStateName);
     }
 }
        private void UpdateUI(IDataLoaderState newState)
        {
            var safeState = newState ?? DataLoaderState.Default;
            var view      = Delegate.GetView();
            var viewState = CreateDataLoaderViewState(safeState, view);

            Delegate.SetState(viewState);

            if (view != null)
            {
                var dataVisualState = GetDataVisualState(safeState);
                GoToState(view, DataVisualGroup, dataVisualState);

                var errorVisualState = GetErrorVisualState(safeState);
                GoToState(view, ErrorVisualGroup, errorVisualState);

                var loadingVisualState = GetLoadingStateVisualState(safeState);
                GoToState(view, LoadingVisualGroup, loadingVisualState);

                var combinedVisualState = $"{dataVisualState}_{errorVisualState}_{loadingVisualState}";
                GoToState(view, CombinedVisualGroup, combinedVisualState);

                if (_isLoaded)
                {
                    _isVisualStateRefreshRequired = false;

                    if (_logger.IsEnabled(LogLevel.Information))
                    {
                        _logger.LogInformation($"Updated VisualStates to '{combinedVisualState}'.");
                    }
                }
                else
                {
                    // If the control isn't loaded, the visual states are not applied.
                    _isVisualStateRefreshRequired = true;

                    if (_logger.IsEnabled(LogLevel.Debug))
                    {
                        _logger.LogTrace($"Saved VisualStates to '{combinedVisualState}'. (This control isn't loaded yet.)");
                    }
                }
            }

            _lastUpdate    = DateTime.Now;
            _lastState     = safeState;
            _lastViewState = viewState;
        }
 private static string GetErrorVisualState(IDataLoaderState state)
 {
     return(state.Error != null
                         ? ErrorVisualStateName
                         : NoErrorVisualStateName);
 }
 private static string GetLoadingStateVisualState(IDataLoaderState state)
 {
     return(state.IsLoading
                         ? LoadingVisualStateName
                         : NotLoadingVisualStateName);
 }
        /// <summary>
        /// This method is reponsible for the wait mechanism (for <see cref="StateChangingThrottleDelay"/> and <see cref="StateMinimumDuration"/>).
        /// It will be called concurrently because of that reason.
        /// </summary>
        /// <param name="newState">The new state to update to.</param>
        /// <param name="source">The method caller name (used for tracing only).</param>
        private async void Update(IDataLoaderState newState, [CallerMemberName] string source = null)
        {
            try
            {
                var updateId = Interlocked.Increment(ref _updateId);

                if (_logger.IsEnabled(LogLevel.Trace))
                {
                    _logger.LogTrace($"Got update request '#{updateId}' to update to '{newState}' from '{source}'.");
                }

                // Update the _nextState before the LockAsync.
                // This allows a previous execution to update to this newer state.
                _nextState = newState;

                // We use a lock async so that we can only have 1 execution changing the view properies (this.State and VisualStates) at a time.
                // This is very necessary because of the waiting mechanism. There will be fewer DataLoaderView visual updates than DataLoader updates.
                using (await LockAsync())
                {
                    // Early-out condition 1.
                    if (updateId < _updateId)
                    {
                        // When the local requestId is less than the _requestId of the control, it means that this request is obsolete.
                        if (_logger.IsEnabled(LogLevel.Trace))
                        {
                            _logger.LogTrace($"Skipping update '#{updateId}' because it's no longer relevant.");
                        }
                        return;
                    }

                    // Early-out condition 2.
                    if (Object.Equals(_nextState, _lastState))
                    {
                        // When the states are equal, we usually skip the update, unless a visual state refresh is required. (That happens when the DataLoader changes state before the DataLoaderView is Loaded.)
                        if (_isVisualStateRefreshRequired && _isLoaded)
                        {
                            await RunOnDispatcher(() =>
                            {
                                RefreshVisualStates();
                            });

                            if (_logger.IsEnabled(LogLevel.Trace))
                            {
                                _logger.LogTrace($"Refreshed visual states now that the control is loaded.");
                            }
                        }
                        else
                        {
                            if (_logger.IsEnabled(LogLevel.Trace))
                            {
                                _logger.LogTrace($"Skipping update '#{updateId}:{newState}' because we're already in that state.");
                            }
                        }

                        // Leave the update because the processing is already done.
                        return;
                    }

                    var elapsedSinceLastUpdate = DateTimeOffset.Now - _lastUpdate;
                    var throttleDelay          = StateChangingThrottleDelay;
                    var minStateDelay          = StateMinimumDuration - elapsedSinceLastUpdate;

                    // We check whether waiting is necessary (either from the Throttle or MinState properties).
                    var isWaitRequired = throttleDelay > TimeSpan.Zero || minStateDelay > TimeSpan.Zero;

                    if (isWaitRequired)
                    {
                        TimeSpan waitDuration;
                        if (minStateDelay > TimeSpan.Zero && minStateDelay > throttleDelay)
                        {
                            if (_logger.IsEnabled(LogLevel.Trace))
                            {
                                _logger.LogTrace($"Starting '{nameof(minStateDelay)}' for update from '#{updateId}:{newState}' because the last update was {elapsedSinceLastUpdate.TotalMilliseconds}ms ago.");
                            }

                            waitDuration = minStateDelay;
                        }
                        else
                        {
                            if (_logger.IsEnabled(LogLevel.Trace))
                            {
                                _logger.LogTrace($"Starting '{nameof(throttleDelay)}' for update from '#{updateId}:{newState}'.");
                            }

                            waitDuration = throttleDelay;
                        }

                        await Task.Delay(waitDuration);
                    }

                    if (_logger.IsEnabled(LogLevel.Trace))
                    {
                        _logger.LogTrace($"Updating VisualStates for '#{_updateId}(from update #{updateId}):{_nextState}'.");
                    }

                    await RunOnDispatcher(() =>
                    {
                        UpdateUI(_nextState);
                    });
                }
            }
            catch (Exception e)
            {
                if (_logger.IsEnabled(LogLevel.Error))
                {
                    _logger.LogError(e, "Caught unhandled exception in Update.");
                }
            }
        }
 private void OnDataLoaderStateChanged(IDataLoader dataLoader, IDataLoaderState newState)
 {
     Update(newState);
 }
 private static bool DefaultEmptySelector(IDataLoaderState state)
 {
     return(false);
 }