public ScriptInputGroupPage(InputGroup inputGroup, List<InputGroup> previousInputGroups) { Title = "Input Group"; StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; foreach (StackLayout stack in UiProperty.GetPropertyStacks(inputGroup)) contentLayout.Children.Add(stack); Button editInputsButton = new Button { Text = "Edit Inputs", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; editInputsButton.Clicked += async (o, e) => { await Navigation.PushAsync(new ScriptInputsPage(inputGroup, previousInputGroups)); }; contentLayout.Children.Add(editInputsButton); Content = new ScrollView { Content = contentLayout }; }
public ScriptInputsPage(InputGroup inputGroup) { _inputGroup = inputGroup; Title = "Inputs"; _inputsList = new ListView(); _inputsList.ItemTemplate = new DataTemplate(typeof(TextCell)); _inputsList.ItemTemplate.SetBinding(TextCell.TextProperty, new Binding(".", stringFormat: "{0}")); _inputsList.ItemTapped += async (o, e) => { if (_inputsList.SelectedItem == null) return; Input selectedInput = _inputsList.SelectedItem as Input; int selectedIndex = inputGroup.Inputs.IndexOf(selectedInput); List<string> actions = new string[] { "Edit", "Delete" }.ToList(); if (selectedIndex < inputGroup.Inputs.Count - 1) actions.Insert(0, "Move Down"); if (selectedIndex > 0) actions.Insert(0, "Move Up"); string selectedAction = await DisplayActionSheet(selectedInput.Name, "Cancel", null, actions.ToArray()); if (selectedAction == "Move Up") inputGroup.Inputs.Move(selectedIndex, selectedIndex - 1); else if (selectedAction == "Move Down") inputGroup.Inputs.Move(selectedIndex, selectedIndex + 1); else if (selectedAction == "Edit") { ScriptInputPage inputPage = new ScriptInputPage(selectedInput); inputPage.Disappearing += (oo, ee) => { Bind(); }; await Navigation.PushAsync(inputPage); _inputsList.SelectedItem = null; } else if (selectedAction == "Delete") { if (await DisplayAlert("Delete " + selectedInput.Name + "?", "This action cannot be undone.", "Delete", "Cancel")) { _inputGroup.Inputs.Remove(selectedInput); _inputsList.SelectedItem = null; // manually reset, since it isn't done automatically. } } }; ToolbarItems.Add(new ToolbarItem(null, "plus.png", async () => { List<Input> inputs = Assembly.GetExecutingAssembly() .GetTypes() .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(Input))) .Select(t => Activator.CreateInstance(t)) .Cast<Input>() .OrderBy(i => i.Name) .ToList(); string cancelButtonName = "Cancel"; string selected = await DisplayActionSheet("Select Input Type", cancelButtonName, null, inputs.Select((input, index) => (index + 1) + ") " + input.Name).ToArray()); if (!string.IsNullOrWhiteSpace(selected) && selected != cancelButtonName) { Input input = inputs[int.Parse(selected.Substring(0, selected.IndexOf(")"))) - 1]; if (input is VoiceInput && inputGroup.Inputs.Count > 0 || !(input is VoiceInput) && inputGroup.Inputs.Any(i => i is VoiceInput)) UiBoundSensusServiceHelper.Get(true).FlashNotificationAsync("Voice inputs must reside in groups by themselves."); else { inputGroup.Inputs.Add(input); ScriptInputPage inputPage = new ScriptInputPage(input); inputPage.Disappearing += (o, e) => { Bind(); }; await Navigation.PushAsync(inputPage); } } })); Bind(); Content = _inputsList; }
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 void PromptForInputsAsync(string windowTitle, IEnumerable<Input> inputs, CancellationToken? cancellationToken, bool showCancelButton, string nextButtonText, string cancelConfirmation, string incompleteSubmissionConfirmation, string submitConfirmation, bool displayProgress, Action<List<Input>> callback) { InputGroup inputGroup = new InputGroup(windowTitle); foreach (Input input in inputs) inputGroup.Inputs.Add(input); PromptForInputsAsync(false, DateTimeOffset.MinValue, new InputGroup[] { inputGroup }, cancellationToken, showCancelButton, nextButtonText, cancelConfirmation, incompleteSubmissionConfirmation, submitConfirmation, displayProgress, null, inputGroups => { if (inputGroups == null) callback(null); else callback(inputGroups.SelectMany(g => g.Inputs).ToList()); }); }
public ScriptInputsPage(InputGroup inputGroup, List<InputGroup> previousInputGroups) { _inputGroup = inputGroup; Title = "Inputs"; _inputsList = new ListView(); _inputsList.ItemTemplate = new DataTemplate(typeof(TextCell)); _inputsList.ItemTemplate.SetBinding(TextCell.TextProperty, new Binding(".", stringFormat: "{0}")); _inputsList.ItemTapped += async (o, e) => { if (_inputsList.SelectedItem == null) return; Input selectedInput = _inputsList.SelectedItem as Input; int selectedIndex = inputGroup.Inputs.IndexOf(selectedInput); List<string> actions = new List<string>(); if (selectedIndex > 0) actions.Add("Move Up"); if (selectedIndex < inputGroup.Inputs.Count - 1) actions.Add("Move Down"); actions.Add("Edit"); if (previousInputGroups != null && previousInputGroups.Select(previousInputGroup => previousInputGroup.Inputs.Count).Sum() > 0) actions.Add("Add Display Condition"); if (selectedInput.DisplayConditions.Count > 0) actions.AddRange(new string[]{ "View Display Conditions" }); actions.Add("Delete"); string selectedAction = await DisplayActionSheet(selectedInput.Name, "Cancel", null, actions.ToArray()); if (selectedAction == "Move Up") inputGroup.Inputs.Move(selectedIndex, selectedIndex - 1); else if (selectedAction == "Move Down") inputGroup.Inputs.Move(selectedIndex, selectedIndex + 1); else if (selectedAction == "Edit") { ScriptInputPage inputPage = new ScriptInputPage(selectedInput); inputPage.Disappearing += (oo, ee) => { Bind(); }; await Navigation.PushAsync(inputPage); _inputsList.SelectedItem = null; } else if (selectedAction == "Add Display Condition") { string abortMessage = "Condition is not complete. Abort?"; SensusServiceHelper.Get().PromptForInputsAsync("Display Condition", new Input[] { new ItemPickerPageInput("Input:", previousInputGroups.SelectMany(previousInputGroup => previousInputGroup.Inputs.Cast<object>()).ToList()), new ItemPickerPageInput("Condition:", Enum.GetValues(typeof(InputValueCondition)).Cast<object>().ToList()), new ItemPickerPageInput("Conjunctive/Disjunctive:", new object[] { "Conjunctive", "Disjunctive" }.ToList()) }, null, true, null, null, abortMessage, null, false, inputs => { if (inputs == null) return; if (inputs.All(input => input.Valid)) { Input conditionInput = ((inputs[0] as ItemPickerPageInput).Value as IEnumerable<object>).First() as Input; InputValueCondition condition = (InputValueCondition)((inputs[1] as ItemPickerPageInput).Value as IEnumerable<object>).First(); bool conjunctive = ((inputs[2] as ItemPickerPageInput).Value as IEnumerable<object>).First().Equals("Conjunctive"); if (condition == InputValueCondition.IsComplete) selectedInput.DisplayConditions.Add(new InputDisplayCondition(conditionInput, condition, null, conjunctive)); else { Regex uppercaseSplitter = new Regex(@" (?<=[A-Z])(?=[A-Z][a-z]) | (?<=[^A-Z])(?=[A-Z]) | (?<=[A-Za-z])(?=[^A-Za-z])", RegexOptions.IgnorePatternWhitespace); // show the user a required copy of the condition input and prompt for the condition value Input conditionInputCopy = conditionInput.Copy(); conditionInputCopy.DisplayConditions.Clear(); conditionInputCopy.LabelText = "Value that " + conditionInputCopy.Name + " " + uppercaseSplitter.Replace(condition.ToString(), " ").ToLower() + ":"; conditionInputCopy.Required = true; SensusServiceHelper.Get().PromptForInputAsync("Display Condition", conditionInputCopy, null, true, "OK", null, abortMessage, null, false, input => { if (input == null) return; if (input.Valid) selectedInput.DisplayConditions.Add(new InputDisplayCondition(conditionInput, condition, input.Value, conjunctive)); }); } } }); } else if (selectedAction == "View Display Conditions") { await Navigation.PushAsync(new ViewTextLinesPage("Display Conditions", selectedInput.DisplayConditions.Select(displayCondition => displayCondition.ToString()).ToList(), null, async () => { selectedInput.DisplayConditions.Clear(); })); } else if (selectedAction == "Delete") { if (await DisplayAlert("Delete " + selectedInput.Name + "?", "This action cannot be undone.", "Delete", "Cancel")) { _inputGroup.Inputs.Remove(selectedInput); _inputsList.SelectedItem = null; // manually reset, since it isn't done automatically. } } }; ToolbarItems.Add(new ToolbarItem(null, "plus.png", async () => { List<Input> inputs = Assembly.GetExecutingAssembly() .GetTypes() .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(Input))) .Select(t => Activator.CreateInstance(t)) .Cast<Input>() .OrderBy(i => i.Name) .ToList(); string cancelButtonName = "Cancel"; string selected = await DisplayActionSheet("Select Input Type", cancelButtonName, null, inputs.Select((input, index) => (index + 1) + ") " + input.Name).ToArray()); if (!string.IsNullOrWhiteSpace(selected) && selected != cancelButtonName) { Input input = inputs[int.Parse(selected.Substring(0, selected.IndexOf(")"))) - 1]; if (input is VoiceInput && inputGroup.Inputs.Count > 0 || !(input is VoiceInput) && inputGroup.Inputs.Any(i => i is VoiceInput)) SensusServiceHelper.Get().FlashNotificationAsync("Voice inputs must reside in groups by themselves."); else { inputGroup.Inputs.Add(input); ScriptInputPage inputPage = new ScriptInputPage(input); inputPage.Disappearing += (o, e) => { Bind(); }; await Navigation.PushAsync(inputPage); } } })); Bind(); Content = _inputsList; }
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 PromptForInputsPage(InputGroup inputGroup, int stepNumber, int totalSteps, Action<Result> callback) { Title = inputGroup.Name; StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand, Padding = new Thickness(0, 20, 0, 0) }; contentLayout.Children.Add(new Label { Text = "Step " + stepNumber + " of " + totalSteps, FontSize = 15, HorizontalOptions = LayoutOptions.CenterAndExpand }); contentLayout.Children.Add(new ProgressBar { Progress = stepNumber / (double)totalSteps, HorizontalOptions = LayoutOptions.FillAndExpand }); foreach (Input input in inputGroup.Inputs) if (input.View != null) contentLayout.Children.Add(input.View); StackLayout navigationStack = new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand }; 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); }; } Button cancelButton = new Button { HorizontalOptions = LayoutOptions.FillAndExpand, FontSize = 20, Text = "Cancel" }; navigationStack.Children.Add(cancelButton); bool cancelButtonTapped = false; cancelButton.Clicked += async (o, e) => { cancelButtonTapped = true; await Navigation.PopModalAsync(true); }; Button nextButton = new Button { HorizontalOptions = LayoutOptions.FillAndExpand, FontSize = 20, Text = stepNumber < totalSteps ? "Next" : "Submit" }; navigationStack.Children.Add(nextButton); bool nextButtonTapped = false; nextButton.Clicked += async (o, e) => { nextButtonTapped = true; await Navigation.PopModalAsync(stepNumber == totalSteps); }; contentLayout.Children.Add(navigationStack); Disappearing += (o, e) => { if (previousButtonTapped) callback(Result.NavigateBackward); else if (cancelButtonTapped) 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 }; }