Esempio n. 1
0
        private IEnumerable<PathElement> DiscoverPath(ChainedHeader fromBlock, ChainedHeader toBlock, Func<UInt256, ChainedHeader> getChainedHeader, CancellationToken? cancelToken)
        {
            // find height difference between chains
            var heightDelta = toBlock.Height - fromBlock.Height;

            var currentFromBlock = fromBlock;
            var currentToBlock = toBlock;

            while (currentFromBlock.Hash != currentToBlock.Hash)
            {
                // cooperative loop
                cancelToken.GetValueOrDefault(CancellationToken.None).ThrowIfCancellationRequested();

                // from chain is longer, rewind it
                if (currentFromBlock.Height > currentToBlock.Height)
                {
                    // if no further rollback is possible, chain mismatch
                    if (currentFromBlock.Height == 0)
                        throw new InvalidOperationException();

                    yield return new PathElement(PathChain.From, currentFromBlock);
                    currentFromBlock = getChainedHeader(currentFromBlock.PreviousBlockHash);
                }
                // to chain is longer, rewind it
                else if (currentToBlock.Height > currentFromBlock.Height)
                {
                    // if no further rollback is possible, chain mismatch
                    if (currentToBlock.Height == 0)
                        throw new InvalidOperationException();

                    yield return new PathElement(PathChain.To, currentToBlock);
                    currentToBlock = getChainedHeader(currentToBlock.PreviousBlockHash);
                }
                // chains are same height, rewind both
                else
                {
                    // if no further rollback is possible, chain mismatch
                    if (currentFromBlock.Height == 0 || currentToBlock.Height == 0)
                        throw new InvalidOperationException();

                    yield return new PathElement(PathChain.From, currentFromBlock);
                    yield return new PathElement(PathChain.To, currentToBlock);

                    currentFromBlock = getChainedHeader(currentFromBlock.PreviousBlockHash);
                    currentToBlock = getChainedHeader(currentToBlock.PreviousBlockHash);
                }
            }

            // return last common block
            yield return new PathElement(PathChain.LastCommon, currentFromBlock);
        }
        public void Start(CancellationToken? cancellationToken)
        {
            var startLogMessage = new StringBuilder();
            startLogMessage.AppendFormat("Starting MessengerServer with {0} processes.", _maxProcessCount).AppendLine();
            startLogMessage.AppendFormat("  Batch size: {0}", _messageBatchSize).AppendLine();
            startLogMessage.AppendFormat("  Check interval: {0}", _checkInterval).AppendLine();
            startLogMessage.AppendFormat("  Archive messages: {0}", _completedMessages.Archive).AppendLine();
            startLogMessage.AppendFormat("  Cleanup old messages: {0}", _completedMessages.Cleanup).AppendLine();
            Logger.Info(startLogMessage.ToString());

            var token = cancellationToken.GetValueOrDefault(CancellationToken.None);
            Task.Factory.StartNew(() => StartMonitor(token), TaskCreationOptions.LongRunning);

            if (_completedMessages.Cleanup)
            {
                CleanupService cleanupService = new CleanupService(Logger, _repositoryFactory.Create());
                cleanupService.Start(_completedMessages.CleanOlderThanUtc, token);
            }
        }
Esempio n. 3
0
 public static void Sleep(this TimeSpan timeToDelay, ILogger logger, CancellationToken? cancellationToken = null)
 {
     try
     {
         var token = cancellationToken.GetValueOrDefault(CancellationToken.None);
         if (!token.IsCancellationRequested)
         {
             Task task = Task.Delay(timeToDelay, token);
             task.Wait(token);
         }
     }
     catch (TaskCanceledException)
     {
     }
     catch (OperationCanceledException)
     {
     }
     catch (Exception e)
     {
         logger.Error(e.GetFormattedError("Sleep task failed!"));
     }
 }
 public async Task<ContractorGet.response> ContractorGet(ContractorGet.request request, CancellationToken? token = null)
 {
     return await SendAsync<ContractorGet.response>(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }
 public async Task<CallbackCreate.response> CallbackCreate(CallbackCreate.request request, CancellationToken? token = null)
 {
     return await SendAsync<CallbackCreate.response>(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }
 public async Task<TimeEntryUpdate.response> TimeEntryUpdate(TimeEntryUpdate.request request, CancellationToken? token = null)
 {
     return await SendAsync<TimeEntryUpdate.response>(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }
 public async Task<EstimateMarkAsSent.response> EstimateMarkAsSent(EstimateMarkAsSent.request request, CancellationToken? token = null)
 {
     return await SendAsync<EstimateMarkAsSent.response>(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }
Esempio n. 8
0
        public void PromptForInputsAsync(bool isReprompt, DateTimeOffset firstPromptTimestamp, IEnumerable<InputGroup> inputGroups, CancellationToken? cancellationToken, bool showCancelButton, string nextButtonText, string cancelConfirmation, string incompleteSubmissionConfirmation, string submitConfirmation, bool displayProgress, Action postDisplayCallback, Action<IEnumerable<InputGroup>> callback)
        {
            new Thread(() =>
                {
                    if (inputGroups == null || inputGroups.Count() == 0 || inputGroups.All(inputGroup => inputGroup == null))
                    {
                        callback(inputGroups);
                        return;
                    }

                    // only one prompt can run at a time...enforce that here.
                    lock (PROMPT_FOR_INPUTS_LOCKER)
                    {
                        if (PROMPT_FOR_INPUTS_RUNNING)
                        {
                            _logger.Log("A prompt is already running. Dropping current prompt request.", LoggingLevel.Normal, GetType());
                            callback(inputGroups);
                            return;
                        }
                        else
                            PROMPT_FOR_INPUTS_RUNNING = true;
                    }

                    bool firstPageDisplay = true;

                    Stack<int> inputGroupNumBackStack = new Stack<int>();

                    for (int inputGroupNum = 0; inputGroups != null && inputGroupNum < inputGroups.Count() && !cancellationToken.GetValueOrDefault().IsCancellationRequested; ++inputGroupNum)
                    {
                        InputGroup inputGroup = inputGroups.ElementAt(inputGroupNum);

                        ManualResetEvent responseWait = new ManualResetEvent(false);

                        // run voice inputs by themselves, and only if the input group contains exactly one voice input.
                        if (inputGroup.Inputs.Count == 1 && inputGroup.Inputs[0] is VoiceInput)
                        {
                            VoiceInput voiceInput = inputGroup.Inputs[0] as VoiceInput;

                            if (voiceInput.Enabled && voiceInput.Display)
                            {
                                voiceInput.RunAsync(isReprompt, firstPromptTimestamp, response =>
                                    {                
                                        responseWait.Set();
                                    });
                            }
                            else
                                responseWait.Set();
                        }
                        else
                        {
                            BringToForeground();

                            Device.BeginInvokeOnMainThread(async () =>
                                {
                                    PromptForInputsPage promptForInputsPage = new PromptForInputsPage(inputGroup, inputGroupNum + 1, inputGroups.Count(), showCancelButton, nextButtonText, cancellationToken, cancelConfirmation, incompleteSubmissionConfirmation, submitConfirmation, displayProgress, result =>
                                        {
                                            if (result == PromptForInputsPage.Result.Cancel || result == PromptForInputsPage.Result.NavigateBackward && inputGroupNumBackStack.Count == 0)
                                                inputGroups = null;
                                            else if (result == PromptForInputsPage.Result.NavigateBackward)
                                                inputGroupNum = inputGroupNumBackStack.Pop() - 1;
                                            else
                                                inputGroupNumBackStack.Push(inputGroupNum);

                                            responseWait.Set();
                                        });

                                    // do not display prompts page under the following conditions:  1) there are no inputs displayed on it. 2) the cancellation 
                                    // token has requested a cancellation. if any of these conditions are true, set the wait handle and continue to the next input group.

                                    if (promptForInputsPage.DisplayedInputCount == 0)
                                    {
                                        // if we're on the final input group and no inputs were shown, then we're at the end and we're ready to submit the 
                                        // users' responses. first check that the user is ready to submit. if the user isn't ready, move back to the previous 
                                        // input group if there is one.
                                        if (inputGroupNum >= inputGroups.Count() - 1 &&
                                            !string.IsNullOrWhiteSpace(submitConfirmation) &&
                                            inputGroupNumBackStack.Count > 0 &&
                                            !(await App.Current.MainPage.DisplayAlert("Confirm", submitConfirmation, "Yes", "No")))
                                        {
                                            inputGroupNum = inputGroupNumBackStack.Pop() - 1;
                                        }

                                        responseWait.Set();
                                    }
                                    else if (cancellationToken.GetValueOrDefault().IsCancellationRequested)
                                        responseWait.Set();
                                    else
                                    {
                                        await App.Current.MainPage.Navigation.PushModalAsync(promptForInputsPage, firstPageDisplay);  // only animate the display for the first page

                                        firstPageDisplay = false;

                                        if (postDisplayCallback != null)
                                            postDisplayCallback();
                                    }                                    
                                });
                        }

                        responseWait.WaitOne();    
                    }

                    // at this point we're done showing pages to the user. anything that needs to happen below with GPS tagging or subsequently
                    // in the callback can happen concurrently with any calls that might happen to come into this method. if the callback
                    // calls into this method immediately, there could be a race condition between the call and a call from some other part of 
                    // the system. this is okay, as the latter call is always in a race condition anyway. if the imagined callback is beaten
                    // to its reentrant call of this method by a call from somewhere else in the system, the callback might be prevented from 
                    // executing; however, can't think of a place where this might happen with negative consequences.
                    PROMPT_FOR_INPUTS_RUNNING = false;

                    #region geotag input groups if the user didn't cancel and we've got input groups with inputs that are complete and lacking locations
                    if (inputGroups != null && inputGroups.Any(inputGroup => inputGroup.Geotag && inputGroup.Inputs.Any(input => input.Complete && (input.Latitude == null || input.Longitude == null))))
                    {
                        SensusServiceHelper.Get().Logger.Log("Geotagging input groups.", LoggingLevel.Normal, GetType());

                        try
                        {
                            Position currentPosition = GpsReceiver.Get().GetReading(cancellationToken.GetValueOrDefault());

                            if (currentPosition != null)
                                foreach (InputGroup inputGroup in inputGroups)
                                    if (inputGroup.Geotag)
                                        foreach (Input input in inputGroup.Inputs)
                                            if (input.Complete)
                                            {
                                                bool locationUpdated = false;

                                                if (input.Latitude == null)
                                                {
                                                    input.Latitude = currentPosition.Latitude;
                                                    locationUpdated = true;
                                                }

                                                if (input.Longitude == null)
                                                {
                                                    input.Longitude = currentPosition.Longitude;
                                                    locationUpdated = true;
                                                }

                                                if (locationUpdated)
                                                    input.LocationUpdateTimestamp = currentPosition.Timestamp;
                                            }
                        }
                        catch (Exception ex)
                        {
                            SensusServiceHelper.Get().Logger.Log("Error geotagging input groups:  " + ex.Message, LoggingLevel.Normal, GetType());
                        }
                    }
                    #endregion

                    callback(inputGroups);

                }).Start();
        }
 public async Task<StaffDelete.response> StaffDelete(StaffDelete.request request, CancellationToken? token = null)
 {
     return await SendAsync<StaffDelete.response>(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }
 public async Task<ReportGetProfitDetails.response> ReportGetProfitDetails(ReportGetProfitDetails.request request, CancellationToken? token = null)
 {
     return await SendAsync<ReportGetProfitDetails.response>(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }
 public async Task<RecurringList.response> RecurringList(RecurringList.request request, CancellationToken? token = null)
 {
     return await SendAsync<RecurringList.response>(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }
 public async Task<byte[]> ReceiptGet(ReceiptGet.request request, CancellationToken? token = null)
 {
     return await GetBytesAsync(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }
 public async Task<InvoiceSendBySnailMail.response> InvoiceSendBySnailMail(InvoiceSendBySnailMail.request request, CancellationToken? token = null)
 {
     return await SendAsync<InvoiceSendBySnailMail.response>(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }
 public async Task<byte[]> EstimateSendByEmail(EstimateSendByEmail.request request, CancellationToken? token = null)
 {
     return await GetBytesAsync(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }
 public async Task<SystemCurrent.response> SystemCurrent(SystemCurrent.request request, CancellationToken? token = null)
 {
     return await SendAsync<SystemCurrent.response>(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }
Esempio n. 16
0
        public PromptForInputsPage(InputGroup inputGroup, int stepNumber, int totalSteps, bool showCancelButton, string nextButtonTextOverride, CancellationToken? cancellationToken, string cancelConfirmation, string incompleteSubmissionConfirmation, string submitConfirmation, bool displayProgress, Action<Result> callback)
        {            
            _displayedInputCount = 0;

            StackLayout contentLayout = new StackLayout
            {
                Orientation = StackOrientation.Vertical,
                VerticalOptions = LayoutOptions.FillAndExpand,
                Padding = new Thickness(0, 20, 0, 0),
                Children =
                {
                    new Label
                    {
                        Text = inputGroup.Name,
                        FontSize = 20,
                        HorizontalOptions = LayoutOptions.CenterAndExpand
                    }
                }
            };

            if (displayProgress)
            {
                float progress = (stepNumber - 1) / (float)totalSteps;

                contentLayout.Children.Add(new Label
                    {
                        Text = "Progress:  " + Math.Round(100 * progress) + "%",
                        FontSize = 15,
                        HorizontalOptions = LayoutOptions.CenterAndExpand
                    });

                contentLayout.Children.Add(new ProgressBar
                    {
                        Progress = progress,
                        HorizontalOptions = LayoutOptions.FillAndExpand
                    });
            }

            int inputSeparatorHeight = 10;

            int viewNumber = 1;
            bool anyRequired = false;
            List<Input> displayedInputs = new List<Input>();
            foreach (Input input in inputGroup.Inputs)
                if (input.Display)
                {
                    View inputView = input.GetView(viewNumber);
                    if (inputView != null)
                    {
                        if (input.Enabled && input.Frame)
                        {
                            inputView = new Frame
                            {
                                Content = inputView,
                                OutlineColor = Color.Accent,
                                VerticalOptions = LayoutOptions.Start,
                                HasShadow = true,
                                Padding = new Thickness(10)
                            };
                        }
                        
                        if (_displayedInputCount > 0)
                            contentLayout.Children.Add(new BoxView { Color = Color.Transparent, HeightRequest = inputSeparatorHeight });
                        
                        contentLayout.Children.Add(inputView);
                        displayedInputs.Add(input);

                        if (input.DisplayNumber)
                            ++viewNumber;

                        if (input.Required)
                            anyRequired = true;

                        ++_displayedInputCount;
                    }
                }

            if (_displayedInputCount > 0)
                contentLayout.Children.Add(new BoxView { Color = Color.Transparent, HeightRequest = inputSeparatorHeight });

            if (anyRequired)
                contentLayout.Children.Add(new Label
                    {
                        Text = "* Required Field",
                        FontSize = 15,
                        TextColor = Color.Red,
                        HorizontalOptions = LayoutOptions.FillAndExpand
                    });

            StackLayout navigationStack = new StackLayout
            {
                Orientation = StackOrientation.Horizontal,
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            #region previous button

            bool previousButtonTapped = false;

            // step numbers are 1-based -- if we're beyond the first, provide a previous button
            if (stepNumber > 1)
            {
                Button previousButton = new Button
                {
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    FontSize = 20,
                    Text = "Previous"
                };

                navigationStack.Children.Add(previousButton);

                previousButton.Clicked += async (o, e) =>
                {
                    previousButtonTapped = true;
                    await Navigation.PopModalAsync(false);
                };                      
            }

            #endregion

            #region cancel button

            bool cancelButtonTapped = false;

            if (showCancelButton)
            {
                Button cancelButton = new Button
                {
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    FontSize = 20,
                    Text = "Cancel"
                };

                navigationStack.Children.Add(cancelButton);

                cancelButton.Clicked += async (o, e) =>
                {
                    string confirmationMessage = "";

                    if (!string.IsNullOrWhiteSpace(cancelConfirmation))
                        confirmationMessage += cancelConfirmation;

                    if (string.IsNullOrWhiteSpace(confirmationMessage) || await DisplayAlert("Confirm", confirmationMessage, "Yes", "No"))
                    {
                        // if the cancellation token was cancelled while the dialog was up, then we should ignore the dialog. the token
                        // will have already popped this page off the navigation stack.
                        if (!cancellationToken.GetValueOrDefault().IsCancellationRequested)
                        {
                            cancelButtonTapped = true;
                            await Navigation.PopModalAsync(true);
                        }
                    }
                };
            }

            #endregion

            #region next button

            Button nextButton = new Button
            {
                HorizontalOptions = LayoutOptions.FillAndExpand,
                FontSize = 20,
                Text = stepNumber < totalSteps ? "Next" : "Submit"

                #if UNIT_TESTING
                // set style id so that we can retrieve the button when unit testing
                , StyleId = "NextButton"
                #endif
            };

            if (nextButtonTextOverride != null)
                nextButton.Text = nextButtonTextOverride;

            navigationStack.Children.Add(nextButton);

            bool nextButtonTapped = false;

            nextButton.Clicked += async (o, e) =>
            {
                string confirmationMessage = "";

                if (!string.IsNullOrWhiteSpace(incompleteSubmissionConfirmation) && !inputGroup.Valid)
                    confirmationMessage += incompleteSubmissionConfirmation;
                else if (nextButton.Text == "Submit" && !string.IsNullOrWhiteSpace(submitConfirmation))
                    confirmationMessage += submitConfirmation;
                    
                if (string.IsNullOrWhiteSpace(confirmationMessage) || await DisplayAlert("Confirm", confirmationMessage, "Yes", "No"))
                {
                    // if the cancellation token was cancelled while the dialog was up, then we should ignore the dialog. the token
                    // will have already popped this page off the navigation stack.
                    if (!cancellationToken.GetValueOrDefault().IsCancellationRequested)
                    {
                        nextButtonTapped = true;
                        await Navigation.PopModalAsync(stepNumber == totalSteps);
                    }
                }
            };

            #endregion
                
            contentLayout.Children.Add(navigationStack);

            #region cancellation token

            bool cancellationTokenCanceled = false;

            if (cancellationToken != null)
            {
                // if the cancellation token is cancelled, pop this page off the stack.
                cancellationToken.GetValueOrDefault().Register(() =>
                    {                        
                        cancellationTokenCanceled = true;

                        Device.BeginInvokeOnMainThread(async() =>
                            {
                                if (Navigation.ModalStack.Count > 0 && Navigation.ModalStack.Last() == this)
                                    await Navigation.PopModalAsync(true);
                            });
                    });
            }

            #endregion

            Appearing += (o, e) =>
            {
                foreach (Input input in displayedInputs)
                    input.Viewed = true;
            };
            
            Disappearing += (o, e) =>
            {
                if (previousButtonTapped)
                    callback(Result.NavigateBackward);
                else if (cancelButtonTapped || cancellationTokenCanceled)
                    callback(Result.Cancel);
                else if (nextButtonTapped)
                    callback(Result.NavigateForward);
                else
                    callback(Result.Cancel);  // the user navigated back, or another activity started
            };                    

            Content = new ScrollView
            {
                Content = contentLayout
            };                        
        }
 public async Task<CategoryListHierarchy.response> CategoryListHierarchy(CategoryListHierarchy.request request, CancellationToken? token = null)
 {
     return await SendAsync<CategoryListHierarchy.response>(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }
Esempio n. 18
0
        public PromptForInputsPage(InputGroup inputGroup, int stepNumber, int totalSteps, bool showCancelButton, string nextButtonTextOverride, CancellationToken? cancellationToken, string cancelConfirmation, string incompleteSubmissionConfirmation, string submitConfirmation, bool displayProgress, DateTimeOffset? firstPromptTimestamp, Action<Result> disappearanceCallback)
        {
            _displayedInputCount = 0;

            StackLayout contentLayout = new StackLayout
            {
                Orientation = StackOrientation.Vertical,
                VerticalOptions = LayoutOptions.FillAndExpand,
                Padding = new Thickness(10, 20, 10, 20),
                Children =
                {
                    new Label
                    {
                        Text = inputGroup.Name,
                        FontSize = 20,
                        HorizontalOptions = LayoutOptions.CenterAndExpand
                    }
                }
            };

            if (displayProgress)
            {
                float progress = (stepNumber - 1) / (float)totalSteps;

                contentLayout.Children.Add(new Label
                    {
                        Text = "Progress:  " + Math.Round(100 * progress) + "%",
                        FontSize = 15,
                        HorizontalOptions = LayoutOptions.CenterAndExpand
                    });

                contentLayout.Children.Add(new ProgressBar
                    {
                        Progress = progress,
                        HorizontalOptions = LayoutOptions.FillAndExpand
                    });
            }

            if (firstPromptTimestamp.HasValue)
            {
                DateTime firstDisplayDateTime = firstPromptTimestamp.Value.ToLocalTime().DateTime;

                string displayLapseDayDesc;
                if (firstDisplayDateTime.Date == DateTime.Now.Date)
                    displayLapseDayDesc = "earlier today";
                else if (firstDisplayDateTime.Date == DateTime.Now.AddDays(-1).Date)
                    displayLapseDayDesc = "yesterday";
                else
                    displayLapseDayDesc = ((int)(DateTime.Now - firstDisplayDateTime).TotalDays) + " days ago (" + firstDisplayDateTime.ToShortDateString() + ")";

                contentLayout.Children.Add(new Label
                    {
                        Text = "These fields were first displayed " + displayLapseDayDesc + " at " + firstDisplayDateTime.ToShortTimeString() + ".",
                        FontSize = 20,
                        HorizontalOptions = LayoutOptions.Start
                    });
            }

            if (inputGroup.Inputs.Any(input => input.Display && input.Required))
                contentLayout.Children.Add(new Label
                    {
                        Text = "Required fields are indicated with *",
                        FontSize = 15,
                        TextColor = Color.Red,
                        HorizontalOptions = LayoutOptions.Start
                    });

            List<Input> displayedInputs = new List<Input>();
            int viewNumber = 1;
            int inputSeparatorHeight = 10;
            foreach (Input input in inputGroup.Inputs)
                if (input.Display)
                {
                    View inputView = input.GetView(viewNumber);
                    if (inputView != null)
                    {
                        if (input.Enabled && input.Frame)
                        {
                            inputView = new Frame
                            {
                                Content = inputView,
                                OutlineColor = Color.Accent,
                                VerticalOptions = LayoutOptions.Start,
                                HasShadow = true,
                                Padding = new Thickness(10)
                            };
                        }

                        // add some vertical separation between inputs
                        if (_displayedInputCount > 0)
                            contentLayout.Children.Add(new BoxView { Color = Color.Transparent, HeightRequest = inputSeparatorHeight });

                        contentLayout.Children.Add(inputView);
                        displayedInputs.Add(input);

                        if (input.DisplayNumber)
                            ++viewNumber;

                        ++_displayedInputCount;
                    }
                }

            if (_displayedInputCount > 0)
                contentLayout.Children.Add(new BoxView { Color = Color.Transparent, HeightRequest = inputSeparatorHeight });

            #region previous/next buttons

            StackLayout previousNextStack = new StackLayout
            {
                Orientation = StackOrientation.Horizontal,
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            #region previous button

            bool previousButtonTapped = false;

            if (stepNumber > 1)
            {
                Button previousButton = new Button
                {
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    FontSize = 20,
                    Text = "Previous"
                };

                previousButton.Clicked += async (o, e) =>
                {
                    previousButtonTapped = true;
                    await Navigation.PopModalAsync(false);
                };

                previousNextStack.Children.Add(previousButton);
            }

            #endregion

            #region next button

            Button nextButton = new Button
            {
                HorizontalOptions = LayoutOptions.FillAndExpand,
                FontSize = 20,
                Text = stepNumber < totalSteps ? "Next" : "Submit"

                #if UNIT_TESTING
                // set style id so that we can retrieve the button when unit testing
                , StyleId = "NextButton"
                #endif
            };

            if (nextButtonTextOverride != null)
                nextButton.Text = nextButtonTextOverride;

            bool nextButtonTapped = false;

            nextButton.Clicked += async (o, e) =>
            {
                string confirmationMessage = "";

                if (!string.IsNullOrWhiteSpace(incompleteSubmissionConfirmation) && !inputGroup.Valid)
                    confirmationMessage += incompleteSubmissionConfirmation;
                else if (nextButton.Text == "Submit" && !string.IsNullOrWhiteSpace(submitConfirmation))
                    confirmationMessage += submitConfirmation;

                if (string.IsNullOrWhiteSpace(confirmationMessage) || await DisplayAlert("Confirm", confirmationMessage, "Yes", "No"))
                {
                    // if the cancellation token was cancelled while the dialog was up, then we should ignore the dialog. the token
                    // will have already popped this page off the navigation stack.
                    if (!cancellationToken.GetValueOrDefault().IsCancellationRequested)
                    {
                        nextButtonTapped = true;
                        await Navigation.PopModalAsync(stepNumber == totalSteps);
                    }
                }
            };

            previousNextStack.Children.Add(nextButton);

            #endregion

            #endregion

            StackLayout navigationStack = new StackLayout
            {
                Orientation = StackOrientation.Vertical,
                HorizontalOptions = LayoutOptions.FillAndExpand,
                Children = { previousNextStack }
            };

            #region cancel button

            bool cancelButtonTapped = false;

            if (showCancelButton)
            {
                Button cancelButton = new Button
                {
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    FontSize = 20,
                    Text = "Cancel"
                };

                navigationStack.Children.Add(new BoxView { Color = Color.Gray, HorizontalOptions = LayoutOptions.FillAndExpand, HeightRequest = 0.5 });
                navigationStack.Children.Add(cancelButton);

                cancelButton.Clicked += async (o, e) =>
                {
                    string confirmationMessage = "";

                    if (!string.IsNullOrWhiteSpace(cancelConfirmation))
                        confirmationMessage += cancelConfirmation;

                    if (string.IsNullOrWhiteSpace(confirmationMessage) || await DisplayAlert("Confirm", confirmationMessage, "Yes", "No"))
                    {
                        // if the cancellation token was cancelled while the dialog was up, then we should ignore the dialog. the token
                        // will have already popped this page off the navigation stack (see below).
                        if (!cancellationToken.GetValueOrDefault().IsCancellationRequested)
                        {
                            cancelButtonTapped = true;
                            await Navigation.PopModalAsync(true);
                        }
                    }
                };
            }

            #endregion

            contentLayout.Children.Add(navigationStack);

            #region cancellation token

            bool cancellationTokenCanceled = false;

            if (cancellationToken != null)
            {
                // if the cancellation token is cancelled, pop this page off the stack.
                cancellationToken.GetValueOrDefault().Register(() =>
                    {
                        SensusServiceHelper.Get().Logger.Log("Cancellation token was cancelled. Will pop pages.", LoggingLevel.Normal, GetType());

                        cancellationTokenCanceled = true;

                        Device.BeginInvokeOnMainThread(async() =>
                            {
                                SensusServiceHelper.Get().Logger.Log("On UI thread. Ready to pop page.", LoggingLevel.Normal, GetType());

                                if (Navigation.ModalStack.Count > 0 && Navigation.ModalStack.Last() == this)
                                {
                                    await Navigation.PopModalAsync(true);
                                    SensusServiceHelper.Get().Logger.Log("Popped page.", LoggingLevel.Normal, GetType());
                                }
                                else
                                    SensusServiceHelper.Get().Logger.Log("No page to pop.", LoggingLevel.Normal, GetType());
                            });
                    });
            }

            #endregion

            Appearing += (o, e) =>
            {
                foreach (Input input in displayedInputs)
                    input.Viewed = true;
            };

            Disappearing += (o, e) =>
            {
                if (previousButtonTapped)
                    disappearanceCallback(Result.NavigateBackward);
                else if (cancelButtonTapped || cancellationTokenCanceled)
                    disappearanceCallback(Result.Cancel);
                else if (nextButtonTapped)
                    disappearanceCallback(Result.NavigateForward);
                else
                    disappearanceCallback(Result.Cancel);  // the user navigated back, or another activity started and covered the window
            };

            Content = new ScrollView
            {
                Content = contentLayout
            };
        }
 public async Task<EmailTemplateUpdate.response> EmailTemplateUpdate(EmailTemplateUpdate.request request, CancellationToken? token = null)
 {
     return await SendAsync<EmailTemplateUpdate.response>(request.ToXmlString(), token.GetValueOrDefault(CancellationToken.None));
 }