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); }