/// <summary> /// Initializes a new instance of the <see cref="SensusUI.DataStorePage"/> class. /// </summary> /// <param name="protocol">Protocol to which data store is to be bound.</param> /// <param name="dataStore">Data store to display.</param> /// <param name="local">If set to <c>true</c>, the data store will be treated as a local data store.</param> /// <param name="newDataStore">If set to <c>true</c>, the data store will be treated as a new data store.</param> public DataStorePage(Protocol protocol, DataStore dataStore, bool local, bool newDataStore) { Title = (local ? "Local" : "Remote") + " Data Store"; // property stacks all come from the data store passed in (i.e., a copy of the original on the protocol, if there is one) List <StackLayout> stacks = UiProperty.GetPropertyStacks(dataStore); StackLayout buttonStack = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; stacks.Add(buttonStack); // clearing only applies to local data stores that already exist on protocols and are clearable. new local data stores don't have this option. if (local && !newDataStore && protocol.LocalDataStore.Clearable) { Button clearButton = new Button { Text = "Clear", HorizontalOptions = LayoutOptions.FillAndExpand, FontSize = 20 }; clearButton.Clicked += async(o, e) => { // display the name as it's currently shown on the form (i.e., in the passed-in data store rather than the protocols) if (await DisplayAlert("Clear data from " + dataStore.Name + "?", "This action cannot be undone.", "Clear", "Cancel")) { protocol.LocalDataStore.Clear(); // clear the protocol's local data store } }; buttonStack.Children.Add(clearButton); } // sharing only applies to local data stores that already exist on protocols. new local data stores don't have this option. if (local && !newDataStore) { Button shareLocalDataButton = new Button { Text = "Share", HorizontalOptions = LayoutOptions.FillAndExpand, FontSize = 20 }; shareLocalDataButton.Clicked += async(o, e) => { // share the protocol's local data store if it has data in it if (protocol.LocalDataStore.DataCount > 0) { await Navigation.PushAsync(new ShareLocalDataStorePage(protocol.LocalDataStore)); } else { UiBoundSensusServiceHelper.Get(true).FlashNotificationAsync("Local data store contains no data to share."); } }; buttonStack.Children.Add(shareLocalDataButton); } Button okayButton = new Button { Text = "OK", HorizontalOptions = LayoutOptions.FillAndExpand, FontSize = 20 }; okayButton.Clicked += async(o, e) => { if (local) { protocol.LocalDataStore = dataStore as LocalDataStore; } else { protocol.RemoteDataStore = dataStore as RemoteDataStore; } await Navigation.PopAsync(); }; buttonStack.Children.Add(okayButton); StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; foreach (StackLayout stack in stacks) { contentLayout.Children.Add(stack); } Content = new ScrollView { Content = contentLayout }; }
public SensusMainPage() { Title = "Sensus"; StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; Button viewProtocolsButton = new Button { Text = "View Protocols", FontSize = 20 }; viewProtocolsButton.Clicked += async(o, e) => { await Navigation.PushAsync(new ProtocolsPage()); }; contentLayout.Children.Add(viewProtocolsButton); Button viewLogButton = new Button { Text = "View Log", FontSize = 20 }; viewLogButton.Clicked += async(o, e) => { await Navigation.PushAsync(new ViewTextLinesPage("Log", UiBoundSensusServiceHelper.Get(true).Logger.Read(int.MaxValue), () => UiBoundSensusServiceHelper.Get(true).Logger.Clear())); }; contentLayout.Children.Add(viewLogButton); Button stopSensusButton = new Button { Text = "Stop Sensus", FontSize = 20 }; stopSensusButton.Clicked += async(o, e) => { if (await DisplayAlert("Stop Sensus?", "Are you sure you want to stop Sensus?", "OK", "Cancel")) { UiBoundSensusServiceHelper.Get(true).StopAsync(); } }; contentLayout.Children.Add(stopSensusButton); Content = new ScrollView { Content = contentLayout }; }
public ShareLocalDataStorePage(LocalDataStore localDataStore) { _cancel = false; Title = "Sharing Local Data Store"; StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; Label statusLabel = new Label { FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; contentLayout.Children.Add(statusLabel); ProgressBar progressBar = new ProgressBar { Progress = 0, HorizontalOptions = LayoutOptions.FillAndExpand }; contentLayout.Children.Add(progressBar); Button cancelButton = new Button { Text = "Cancel", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; cancelButton.Clicked += async(o, e) => { await Navigation.PopAsync(); }; contentLayout.Children.Add(cancelButton); new Thread(async() => { string sharePath = UiBoundSensusServiceHelper.Get(true).GetSharePath(".json"); bool errorWritingShareFile = false; try { Device.BeginInvokeOnMainThread(() => statusLabel.Text = "Gathering data..."); List <Datum> localData = localDataStore.GetDataForRemoteDataStore(progress => { Device.BeginInvokeOnMainThread(() => { progressBar.ProgressTo(progress, 250, Easing.Linear); }); }, () => { return(_cancel); }); Device.BeginInvokeOnMainThread(() => { progressBar.ProgressTo(0, 0, Easing.Linear); statusLabel.Text = "Writing data to file..."; }); using (StreamWriter shareFile = new StreamWriter(sharePath)) { int dataWritten = 0; foreach (Datum datum in localData) { shareFile.WriteLine(datum.GetJSON(localDataStore.Protocol.JsonAnonymizer)); if ((++dataWritten % (localData.Count / 10)) == 0) { Device.BeginInvokeOnMainThread(() => progressBar.ProgressTo(dataWritten / (double)localData.Count, 250, Easing.Linear)); } } shareFile.Close(); } } catch (Exception ex) { errorWritingShareFile = true; string message = "Error writing share file: " + ex.Message; SensusServiceHelper.Get().FlashNotificationAsync(message); SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType()); await Navigation.PopAsync(); } if (!_cancel && !errorWritingShareFile) { Device.BeginInvokeOnMainThread(async() => await Navigation.PopAsync()); SensusServiceHelper.Get().ShareFileAsync(sharePath, "Sensus Data"); } }).Start(); Content = new ScrollView { Content = contentLayout }; }
/// <summary> /// Initializes a new instance of the <see cref="SensusUI.AddScriptTriggerPage"/> class. /// </summary> /// <param name="scriptRunner">Script runner to add trigger to.</param> public AddScriptTriggerPage(ScriptRunner scriptRunner) { _scriptRunner = scriptRunner; Title = "Add Trigger"; List <Probe> enabledProbes = _scriptRunner.Probe.Protocol.Probes.Where(p => p != _scriptRunner.Probe && p.Enabled).ToList(); if (enabledProbes.Count == 0) { Content = new Label { Text = "No enabled probes. Please enable them before creating triggers.", FontSize = 20 }; return; } StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; Label probeLabel = new Label { Text = "Probe:", FontSize = 20 }; Picker probePicker = new Picker { Title = "Select Probe", HorizontalOptions = LayoutOptions.FillAndExpand }; foreach (Probe enabledProbe in enabledProbes) { probePicker.Items.Add(enabledProbe.DisplayName); } contentLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { probeLabel, probePicker } }); StackLayout triggerDefinitionLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; contentLayout.Children.Add(triggerDefinitionLayout); Switch changeSwitch = new Switch(); Switch regexSwitch = new Switch(); Switch fireRepeatedlySwitch = new Switch(); Switch ignoreFirstDatumSwitch = new Switch(); TimePicker startTimePicker = new TimePicker { HorizontalOptions = LayoutOptions.FillAndExpand }; TimePicker endTimePicker = new TimePicker { HorizontalOptions = LayoutOptions.FillAndExpand }; probePicker.SelectedIndexChanged += (o, e) => { _selectedProbe = null; _selectedDatumProperty = null; _conditionValue = null; triggerDefinitionLayout.Children.Clear(); if (probePicker.SelectedIndex < 0) { return; } _selectedProbe = enabledProbes[probePicker.SelectedIndex]; PropertyInfo[] datumProperties = _selectedProbe.DatumType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttributes <ProbeTriggerProperty>().Count() > 0).ToArray(); if (datumProperties.Length == 0) { return; } #region datum property picker Label datumPropertyLabel = new Label { Text = "Property:", FontSize = 20 }; Picker datumPropertyPicker = new Picker { Title = "Select Datum Property", HorizontalOptions = LayoutOptions.FillAndExpand }; foreach (PropertyInfo datumProperty in datumProperties) { ProbeTriggerProperty triggerProperty = datumProperty.GetCustomAttributes <ProbeTriggerProperty>().First(); datumPropertyPicker.Items.Add(triggerProperty.Name ?? datumProperty.Name); } triggerDefinitionLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { datumPropertyLabel, datumPropertyPicker } }); #endregion #region condition picker (same for all datum types) Label conditionLabel = new Label { Text = "Condition:", FontSize = 20 }; Picker conditionPicker = new Picker { Title = "Select Condition", HorizontalOptions = LayoutOptions.FillAndExpand }; TriggerValueCondition[] conditions = Enum.GetValues(typeof(TriggerValueCondition)) as TriggerValueCondition[]; foreach (TriggerValueCondition condition in conditions) { conditionPicker.Items.Add(condition.ToString()); } conditionPicker.SelectedIndexChanged += (oo, ee) => { if (conditionPicker.SelectedIndex < 0) { return; } _selectedCondition = conditions[conditionPicker.SelectedIndex]; }; triggerDefinitionLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { conditionLabel, conditionPicker } }); #endregion #region condition value for comparison, based on selected datum property -- includes change calculation (for double datum) and regex (for string datum) StackLayout conditionValueStack = new StackLayout { Orientation = StackOrientation.Vertical, HorizontalOptions = LayoutOptions.FillAndExpand }; triggerDefinitionLayout.Children.Add(conditionValueStack); datumPropertyPicker.SelectedIndexChanged += (oo, ee) => { _selectedDatumProperty = null; _conditionValue = null; conditionValueStack.Children.Clear(); if (datumPropertyPicker.SelectedIndex < 0) { return; } _selectedDatumProperty = datumProperties[datumPropertyPicker.SelectedIndex]; ProbeTriggerProperty datumTriggerAttribute = _selectedDatumProperty.GetCustomAttribute <ProbeTriggerProperty>(); View conditionValueStackView = null; bool allowChangeCalculation = false; bool allowRegularExpression = false; if (datumTriggerAttribute is ListProbeTriggerProperty) { Picker conditionValuePicker = new Picker { Title = "Select Condition Value", HorizontalOptions = LayoutOptions.FillAndExpand }; object[] items = (datumTriggerAttribute as ListProbeTriggerProperty).Items; foreach (object item in items) { conditionValuePicker.Items.Add(item.ToString()); } conditionValuePicker.SelectedIndexChanged += (ooo, eee) => { if (conditionValuePicker.SelectedIndex < 0) { return; } _conditionValue = items[conditionValuePicker.SelectedIndex]; }; conditionValueStackView = conditionValuePicker; } else if (datumTriggerAttribute is NumberProbeTriggerProperty) { Entry entry = new Entry { Keyboard = Keyboard.Numeric, HorizontalOptions = LayoutOptions.FillAndExpand }; entry.TextChanged += (ooo, eee) => { double value; if (double.TryParse(eee.NewTextValue, out value)) { _conditionValue = value; } }; conditionValueStackView = entry; allowChangeCalculation = true; } else if (datumTriggerAttribute is TextProbeTriggerProperty) { Entry entry = new Entry { Keyboard = Keyboard.Default, HorizontalOptions = LayoutOptions.FillAndExpand }; entry.TextChanged += (ooo, eee) => _conditionValue = eee.NewTextValue; conditionValueStackView = entry; allowRegularExpression = true; } else if (datumTriggerAttribute is BooleanProbeTriggerProperty) { Switch booleanSwitch = new Switch(); booleanSwitch.Toggled += (ooo, eee) => _conditionValue = eee.Value; conditionValueStackView = booleanSwitch; } Label conditionValueStackLabel = new Label { Text = "Value:", FontSize = 20 }; conditionValueStack.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { conditionValueStackLabel, conditionValueStackView } }); #region change calculation if (allowChangeCalculation) { Label changeLabel = new Label { Text = "Change:", FontSize = 20 }; changeSwitch.IsToggled = false; conditionValueStack.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { changeLabel, changeSwitch } }); } #endregion #region regular expression if (allowRegularExpression) { Label regexLabel = new Label { Text = "Regular Expression:", FontSize = 20 }; regexSwitch.IsToggled = false; conditionValueStack.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { regexLabel, regexSwitch } }); } #endregion }; datumPropertyPicker.SelectedIndex = 0; #endregion #region fire repeatedly Label fireRepeatedlyLabel = new Label { Text = "Fire Repeatedly:", FontSize = 20 }; fireRepeatedlySwitch.IsToggled = false; triggerDefinitionLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { fireRepeatedlyLabel, fireRepeatedlySwitch } }); #endregion #region ignore first datum Label ignoreFirstDatumLabel = new Label { Text = "Ignore First Datum:", FontSize = 20 }; ignoreFirstDatumSwitch.IsToggled = false; triggerDefinitionLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { ignoreFirstDatumLabel, ignoreFirstDatumSwitch } }); #endregion #region start/end times Label startTimeLabel = new Label { Text = "Start Time:", FontSize = 20 }; startTimePicker.Time = new TimeSpan(8, 0, 0); triggerDefinitionLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { startTimeLabel, startTimePicker } }); Label endTimeLabel = new Label { Text = "End Time:", FontSize = 20 }; endTimePicker.Time = new TimeSpan(21, 0, 0); triggerDefinitionLayout.Children.Add(new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { endTimeLabel, endTimePicker } }); #endregion }; probePicker.SelectedIndex = 0; Button okButton = new Button { Text = "OK", FontSize = 20 }; okButton.Clicked += async(o, e) => { try { _scriptRunner.Triggers.Add(new SensusService.Probes.User.Trigger(_selectedProbe, _selectedDatumProperty, _selectedCondition, _conditionValue, changeSwitch.IsToggled, fireRepeatedlySwitch.IsToggled, regexSwitch.IsToggled, ignoreFirstDatumSwitch.IsToggled, startTimePicker.Time, endTimePicker.Time)); await Navigation.PopAsync(); } catch (Exception ex) { string message = "Failed to add trigger: " + ex.Message; UiBoundSensusServiceHelper.Get(true).FlashNotificationAsync(message); UiBoundSensusServiceHelper.Get(true).Logger.Log(message, LoggingLevel.Normal, GetType()); } }; contentLayout.Children.Add(okButton); Content = new ScrollView { Content = contentLayout }; }
/// <summary> /// Initializes a new instance of the <see cref="SensusUI.ViewTextLinesPage"/> class. /// </summary> /// <param name="title">Title of page.</param> /// <param name="lines">Lines to display.</param> /// <param name="clearCallback">Called when the user clicks the Clear button.</param> public ViewTextLinesPage(string title, List <string> lines, Action clearCallback) { Title = title; ListView messageList = new ListView(); messageList.ItemTemplate = new DataTemplate(typeof(TextCell)); messageList.ItemTemplate.SetBinding(TextCell.TextProperty, new Binding(".", mode: BindingMode.OneWay)); messageList.ItemsSource = new ObservableCollection <string>(lines); Button shareButton = new Button { Text = "Share", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; shareButton.Clicked += (o, e) => { string path = null; try { path = UiBoundSensusServiceHelper.Get(true).GetSharePath(".txt"); using (StreamWriter file = new StreamWriter(path)) { foreach (string line in lines) { file.WriteLine(line); } file.Close(); } } catch (Exception ex) { UiBoundSensusServiceHelper.Get(true).Logger.Log("Failed to write lines to temp file for sharing: " + ex.Message, SensusService.LoggingLevel.Normal, GetType()); path = null; } if (path != null) { UiBoundSensusServiceHelper.Get(true).ShareFileAsync(path, title + ": " + Path.GetFileName(path)); } }; Button clearButton = new Button { Text = "Clear", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand, IsEnabled = clearCallback != null }; if (clearCallback != null) { clearButton.Clicked += async(o, e) => { if (await DisplayAlert("Confirm", "Do you wish to clear the list? This cannot be undone.", "OK", "Cancel")) { clearCallback(); messageList.ItemsSource = null; } } } ; StackLayout shareClearStack = new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { shareButton, clearButton } }; Content = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand, Children = { messageList, shareClearStack } }; } }
public SensusMainPage() { Title = "Sensus"; StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; Button protocolsButton = new Button { Text = "Protocols", FontSize = 20 }; protocolsButton.Clicked += async(o, e) => { await Navigation.PushAsync(new ProtocolsPage()); }; contentLayout.Children.Add(protocolsButton); Button studyParticipationButton = new Button { Text = "Study Participation", FontSize = 20 }; studyParticipationButton.Clicked += async(o, e) => { if (UiBoundSensusServiceHelper.Get(true).RegisteredProtocols.Count == 0) { UiBoundSensusServiceHelper.Get(true).FlashNotificationAsync("You have not yet added any studies to Sensus."); } else { string[] protocolNames = UiBoundSensusServiceHelper.Get(true).RegisteredProtocols.Select((protocol, index) => (index + 1) + ") " + protocol.Name).ToArray(); string cancelButtonName = "Cancel"; string selectedProtocolName = await DisplayActionSheet("Select Study", cancelButtonName, null, protocolNames); if (!string.IsNullOrWhiteSpace(selectedProtocolName) && selectedProtocolName != cancelButtonName) { Protocol selectedProtocol = UiBoundSensusServiceHelper.Get(true).RegisteredProtocols[int.Parse(selectedProtocolName.Substring(0, selectedProtocolName.IndexOf(")"))) - 1]; if (selectedProtocol.Running) { await Navigation.PushAsync(new ParticipationReportPage(selectedProtocol)); } else if (await DisplayAlert("Begin Study", "You are not currently participating in this study. Would you like to begin participating?", "Yes", "No")) { selectedProtocol.StartWithUserAgreement(null); } } } }; contentLayout.Children.Add(studyParticipationButton); Button pointsOfInterestButton = new Button { Text = "Points of Interest", FontSize = 20 }; pointsOfInterestButton.Clicked += async(o, e) => { await Navigation.PushAsync(new PointsOfInterestPage( UiBoundSensusServiceHelper.Get(true).PointsOfInterest, () => UiBoundSensusServiceHelper.Get(true).SaveAsync())); }; contentLayout.Children.Add(pointsOfInterestButton); Button logButton = new Button { Text = "Log", FontSize = 20 }; logButton.Clicked += async(o, e) => { await Navigation.PushAsync(new ViewTextLinesPage("Log", UiBoundSensusServiceHelper.Get(true).Logger.Read(int.MaxValue), () => UiBoundSensusServiceHelper.Get(true).Logger.Clear())); }; contentLayout.Children.Add(logButton); Button stopSensusButton = new Button { Text = "Stop Sensus", FontSize = 20 }; stopSensusButton.Clicked += async(o, e) => { if (await DisplayAlert("Stop Sensus?", "Are you sure you want to stop Sensus?", "OK", "Cancel")) { UiBoundSensusServiceHelper.Get(true).StopAsync(); } }; contentLayout.Children.Add(stopSensusButton); Content = new ScrollView { Content = contentLayout }; }
public ParticipationReportPage(Protocol protocol) { Title = protocol.Name; #if __IOS__ string howToIncreaseScore = "You can increase your score by opening Sensus more often and responding to questions that Sensus asks you."; #elif __ANDROID__ string howToIncreaseScore = "You can increase your score by allowing Sensus to run continuously and responding to questions that Sensus asks you."; #elif WINDOWS_PHONE string userNotificationMessage = null; // TODO: How to increase score? #else #error "Unrecognized platform." #endif Button helpButton = null; if (!string.IsNullOrWhiteSpace(protocol.ContactEmail)) { helpButton = new Button { Text = "Email Study Manager", FontSize = 20 }; helpButton.Clicked += (o, e) => { UiBoundSensusServiceHelper.Get(true).SendEmailAsync(protocol.ContactEmail, "Help with study: " + protocol.Name, "Hello - " + Environment.NewLine + Environment.NewLine + "I am having the following problem:" + Environment.NewLine + Environment.NewLine + "[DESCRIBE YOUR PROBLEM HERE]"); }; } StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand, Padding = new Thickness(0, 50, 0, 0), Children = { new Label { Text = "Participation Level", FontSize = 20, HorizontalOptions = LayoutOptions.CenterAndExpand }, new Label { Text = Math.Round(protocol.OverallParticipationLevel * 100, 0) + "%", FontSize = 75, HorizontalOptions = LayoutOptions.CenterAndExpand }, new Label { Text = "This score reflects your overall participation level in the \"" + protocol.Name + "\" study over the past " + (protocol.ParticipationHorizonDays == 1 ? "day" : protocol.ParticipationHorizonDays + " days") + ". " + howToIncreaseScore + (helpButton == null ? "" : " If you have questions, please click the button below to email the study manager."), FontSize = 20, HorizontalOptions = LayoutOptions.CenterAndExpand } } }; if (helpButton != null) { contentLayout.Children.Add(helpButton); } Content = new ScrollView { Content = contentLayout }; }
public ProtocolsPage() { Title = "Protocols"; _protocolsList = new ListView(); _protocolsList.ItemTemplate = new DataTemplate(typeof(TextCell)); _protocolsList.ItemTemplate.SetBinding(TextCell.TextProperty, "Name"); _protocolsList.ItemTapped += async(o, e) => { if (_protocolsList.SelectedItem == null) { return; } Protocol selectedProtocol = _protocolsList.SelectedItem as Protocol; string selectedAction = await DisplayActionSheet(selectedProtocol.Name, "Cancel", null, selectedProtocol.Running? "Stop" : "Start", "Edit", "Status", "Share", "Delete"); if (selectedAction == "Start") { selectedProtocol.StartWithUserAgreement(null); } else if (selectedAction == "Stop") { if (await DisplayAlert("Confirm Stop", "Are you sure you want to stop " + selectedProtocol.Name + "?", "Yes", "No")) { selectedProtocol.Running = false; } } else if (selectedAction == "Edit") { Action editAction = new Action(async() => { ProtocolPage protocolPage = new ProtocolPage(selectedProtocol); protocolPage.Disappearing += (oo, ee) => Bind(); // rebind to pick up name changes await Navigation.PushAsync(protocolPage); _protocolsList.SelectedItem = null; }); ExecuteActionUponProtocolAuthentication(selectedProtocol, editAction); } else if (selectedAction == "Status") { if (UiBoundSensusServiceHelper.Get(true).ProtocolShouldBeRunning(selectedProtocol)) { selectedProtocol.TestHealthAsync(true, () => { Device.BeginInvokeOnMainThread(async() => { if (selectedProtocol.MostRecentReport == null) { await DisplayAlert("No Report", "Status check failed.", "OK"); } else { await Navigation.PushAsync(new ViewTextLinesPage("Protocol Report", selectedProtocol.MostRecentReport.ToString().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).ToList(), null)); } }); }); } else { await DisplayAlert("Protocol Not Running", "Cannot check status of protocol when protocol is not running.", "OK"); } } else if (selectedAction == "Share") { Action shareAction = new Action(() => { string path = null; try { path = UiBoundSensusServiceHelper.Get(true).GetSharePath(".sensus"); selectedProtocol.Save(path); } catch (Exception ex) { UiBoundSensusServiceHelper.Get(true).Logger.Log("Failed to save protocol to file for sharing: " + ex.Message, LoggingLevel.Normal, GetType()); path = null; } if (path != null) { UiBoundSensusServiceHelper.Get(true).ShareFileAsync(path, "Sensus Protocol: " + selectedProtocol.Name); } }); // don't authenticate if the protocol was declared shareable -- participants might require the ability to share without the password. if (selectedProtocol.Shareable) { shareAction(); } // if the protocol isn't declared shareable, require authentication, since sharing is equivalent to editing the protocol. else { ExecuteActionUponProtocolAuthentication(selectedProtocol, shareAction); } } else if (selectedAction == "Delete") { if (await DisplayAlert("Delete " + selectedProtocol.Name + "?", "This action cannot be undone.", "Delete", "Cancel")) { selectedProtocol.StopAsync(() => { UiBoundSensusServiceHelper.Get(true).UnregisterProtocol(selectedProtocol); try { Directory.Delete(selectedProtocol.StorageDirectory, true); } catch (Exception ex) { UiBoundSensusServiceHelper.Get(true).Logger.Log("Failed to delete protocol storage directory \"" + selectedProtocol.StorageDirectory + "\": " + ex.Message, LoggingLevel.Normal, GetType()); } Device.BeginInvokeOnMainThread(() => { _protocolsList.SelectedItem = null; // must reset this manually, since it isn't reset automatically }); }); } } }; Bind(); Content = _protocolsList; ToolbarItems.Add(new ToolbarItem(null, "plus.png", () => { Protocol.CreateAsync("New Protocol", protocol => { UiBoundSensusServiceHelper.Get(true).RegisterProtocol(protocol); }); })); }
public ProtocolsPage() { Title = "Protocols"; _protocolsList = new ListView(); _protocolsList.ItemTemplate = new DataTemplate(typeof(TextCell)); _protocolsList.ItemTemplate.SetBinding(TextCell.TextProperty, "Name"); Bind(); Content = _protocolsList; #region toolbar ToolbarItems.Add(new ToolbarItem("Open", null, () => { if (_protocolsList.SelectedItem != null) { Protocol protocol = _protocolsList.SelectedItem as Protocol; Action openProtocol = new Action(async() => { ProtocolPage protocolPage = new ProtocolPage(protocol); protocolPage.Disappearing += (o, e) => Bind(); await Navigation.PushAsync(protocolPage); _protocolsList.SelectedItem = null; }); if (protocol.LockPasswordHash == "") { openProtocol(); } else { UiBoundSensusServiceHelper.Get(true).PromptForInputAsync("Enter protocol password to open:", false, password => { if (password == null) { return; } if (UiBoundSensusServiceHelper.Get(true).GetMd5Hash(password) == protocol.LockPasswordHash) { Device.BeginInvokeOnMainThread(openProtocol); } else { UiBoundSensusServiceHelper.Get(true).FlashNotificationAsync("Incorrect password"); } }); } } })); ToolbarItems.Add(new ToolbarItem("+", null, () => { UiBoundSensusServiceHelper.Get(true).RegisterProtocol(new Protocol("New Protocol")); _protocolsList.ItemsSource = null; _protocolsList.ItemsSource = UiBoundSensusServiceHelper.Get(true).RegisteredProtocols; })); ToolbarItems.Add(new ToolbarItem("-", null, () => { if (_protocolsList.SelectedItem != null) { Protocol protocolToDelete = _protocolsList.SelectedItem as Protocol; Action deleteProtocol = new Action(async() => { if (await DisplayAlert("Delete " + protocolToDelete.Name + "?", "This action cannot be undone.", "Delete", "Cancel")) { protocolToDelete.StopAsync(() => { UiBoundSensusServiceHelper.Get(true).UnregisterProtocol(protocolToDelete); try { Directory.Delete(protocolToDelete.StorageDirectory, true); } catch (Exception ex) { UiBoundSensusServiceHelper.Get(true).Logger.Log("Failed to delete protocol storage directory \"" + protocolToDelete.StorageDirectory + "\": " + ex.Message, LoggingLevel.Normal, GetType()); } Device.BeginInvokeOnMainThread(() => { _protocolsList.ItemsSource = _protocolsList.ItemsSource.Cast <Protocol>().Where(p => p != protocolToDelete); _protocolsList.SelectedItem = null; }); }); } }); if (protocolToDelete.LockPasswordHash == "") { deleteProtocol(); } else { UiBoundSensusServiceHelper.Get(true).PromptForInputAsync("Enter protocol password to delete:", false, password => { if (password == null) { return; } if (UiBoundSensusServiceHelper.Get(true).GetMd5Hash(password) == protocolToDelete.LockPasswordHash) { Device.BeginInvokeOnMainThread(deleteProtocol); } else { UiBoundSensusServiceHelper.Get(true).FlashNotificationAsync("Incorrect password"); } }); } } })); #endregion }
/// <summary> /// Initializes a new instance of the <see cref="SensusUI.ProbePage"/> class. /// </summary> /// <param name="probe">Probe to display.</param> public ProbePage(Probe probe) { Title = "Probe"; StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; foreach (StackLayout stack in UiProperty.GetPropertyStacks(probe)) { contentLayout.Children.Add(stack); } #region script probes if (probe is ScriptProbe) { ScriptProbe scriptProbe = probe as ScriptProbe; Button editScriptsButton = new Button { Text = "Edit Scripts", FontSize = 20 }; contentLayout.Children.Add(editScriptsButton); editScriptsButton.Clicked += async(o, e) => { await Navigation.PushAsync(new ScriptRunnersPage(scriptProbe)); }; Button shareScriptButton = new Button { Text = "Share Definition", FontSize = 20 }; contentLayout.Children.Add(shareScriptButton); shareScriptButton.Clicked += (o, e) => { string sharePath = UiBoundSensusServiceHelper.Get(true).GetSharePath(".json"); using (StreamWriter shareFile = new StreamWriter(sharePath)) { shareFile.WriteLine(JsonConvert.SerializeObject(probe, SensusServiceHelper.JSON_SERIALIZER_SETTINGS)); shareFile.Close(); } UiBoundSensusServiceHelper.Get(true).ShareFileAsync(sharePath, "Probe Definition"); }; } #endregion #region proximity probe if (probe is IPointsOfInterestProximityProbe) { Button editTriggersButton = new Button { Text = "Edit Triggers", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; contentLayout.Children.Add(editTriggersButton); editTriggersButton.Clicked += async(o, e) => { await Navigation.PushAsync(new ProximityTriggersPage(probe as IPointsOfInterestProximityProbe)); }; } #endregion #region anonymization List <PropertyInfo> anonymizableProperties = probe.DatumType.GetProperties().Where(property => property.GetCustomAttribute <Anonymizable>() != null).ToList(); if (anonymizableProperties.Count > 0) { contentLayout.Children.Add(new Label { Text = "Anonymization", FontSize = 20, FontAttributes = FontAttributes.Italic, TextColor = Color.Accent, HorizontalOptions = LayoutOptions.Center }); List <StackLayout> anonymizablePropertyStacks = new List <StackLayout>(); foreach (PropertyInfo anonymizableProperty in anonymizableProperties) { Anonymizable anonymizableAttribute = anonymizableProperty.GetCustomAttribute <Anonymizable>(true); Label propertyLabel = new Label { Text = anonymizableAttribute.PropertyDisplayName ?? anonymizableProperty.Name + ":", FontSize = 20, HorizontalOptions = LayoutOptions.Start }; // populate a picker of anonymizers for the current property Picker anonymizerPicker = new Picker { Title = "Select Anonymizer", HorizontalOptions = LayoutOptions.FillAndExpand }; anonymizerPicker.Items.Add("Do Not Anonymize"); foreach (Anonymizer anonymizer in anonymizableAttribute.AvailableAnonymizers) { anonymizerPicker.Items.Add(anonymizer.DisplayText); } anonymizerPicker.SelectedIndexChanged += (o, e) => { Anonymizer selectedAnonymizer = null; if (anonymizerPicker.SelectedIndex > 0) { selectedAnonymizer = anonymizableAttribute.AvailableAnonymizers[anonymizerPicker.SelectedIndex - 1]; // subtract one from the selected index since the JsonAnonymizer's collection of anonymizers start after the "None" option within the picker. } probe.Protocol.JsonAnonymizer.SetAnonymizer(anonymizableProperty, selectedAnonymizer); }; // set the picker's index to the current anonymizer (or "Do Not Anonymize" if there is no current) Anonymizer currentAnonymizer = probe.Protocol.JsonAnonymizer.GetAnonymizer(anonymizableProperty); int currentIndex = 0; if (currentAnonymizer != null) { currentIndex = anonymizableAttribute.AvailableAnonymizers.IndexOf(currentAnonymizer) + 1; } anonymizerPicker.SelectedIndex = currentIndex; StackLayout anonymizablePropertyStack = new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { propertyLabel, anonymizerPicker } }; anonymizablePropertyStacks.Add(anonymizablePropertyStack); } foreach (StackLayout anonymizablePropertyStack in anonymizablePropertyStacks.OrderBy(s => (s.Children[0] as Label).Text)) { contentLayout.Children.Add(anonymizablePropertyStack); } } #endregion Content = new ScrollView { Content = contentLayout }; }
public void Bind() { _protocolsList.ItemsSource = null; _protocolsList.ItemsSource = UiBoundSensusServiceHelper.Get(true).RegisteredProtocols; }
private MapPage(string newPinName) { _map = new MapExtend { IsShowingUser = true, VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand, }; ((ObservableCollection <Pin>)_map.Pins).CollectionChanged += (o, e) => { // reset pin names to be the provided name if (e.NewItems != null) { foreach (Pin pin in e.NewItems) { pin.Label = newPinName; } } }; #region search Label searchLabel = new Label { Text = "Search:", FontSize = 20 }; _searchEntry = new Entry { HorizontalOptions = LayoutOptions.FillAndExpand }; Button searchGoButton = new Button { Text = "Go", FontSize = 20 }; searchGoButton.Clicked += (o, e) => { if (!string.IsNullOrWhiteSpace(_searchEntry.Text)) { try { _map.SearchAdress(_searchEntry.Text); } catch (Exception ex) { try { string errorMessage = "Failed to search for address: " + ex.Message; UiBoundSensusServiceHelper.Get(true).Logger.Log(errorMessage, SensusService.LoggingLevel.Normal, GetType()); UiBoundSensusServiceHelper.Get(true).FlashNotificationAsync(errorMessage); } catch (Exception) { } finally { Insights.Report(ex, Insights.Severity.Warning); } } } }; #endregion Button clearPinsButton = new Button { Text = "Clear Pins", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; clearPinsButton.Clicked += (o, e) => { _map.Pins.Clear(); }; Button okButton = new Button { Text = "OK", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; okButton.Clicked += async(o, e) => { await Navigation.PopModalAsync(); }; Content = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand, Children = { new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { searchLabel, _searchEntry, searchGoButton } }, new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { clearPinsButton, okButton } }, _map, } }; }
/// <summary> /// Initializes a new instance of the <see cref="SensusUI.PointsOfInterestPage"/> class. /// </summary> /// <param name="pointsOfInterest">Points of interest to display.</param> /// <param name="changeCallback">Called when a POI is added/deleted.</param> public PointsOfInterestPage(List <PointOfInterest> pointsOfInterest, Action changeCallback) { _pointsOfInterest = pointsOfInterest; Title = "Points of Interest"; _pointsOfInterestList = new ListView(); _pointsOfInterestList.ItemTemplate = new DataTemplate(typeof(TextCell)); _pointsOfInterestList.ItemTemplate.SetBinding(TextCell.TextProperty, new Binding(".", stringFormat: "{0}")); _pointsOfInterestList.ItemTapped += async(o, e) => { if (_pointsOfInterestList.SelectedItem == null) { return; } PointOfInterest selectedPointOfInterest = _pointsOfInterestList.SelectedItem as PointOfInterest; string selectedAction = await DisplayActionSheet(selectedPointOfInterest.ToString(), "Cancel", null, "Delete"); if (selectedAction == "Delete") { if (await DisplayAlert("Delete " + selectedPointOfInterest.Name + "?", "This action cannot be undone.", "Delete", "Cancel")) { _pointsOfInterest.Remove(selectedPointOfInterest); _pointsOfInterestList.SelectedItem = null; // reset it manually, since it isn't done automatically. if (changeCallback != null) { changeCallback(); } Bind(); } } }; Bind(); Content = _pointsOfInterestList; ToolbarItems.Add(new ToolbarItem(null, "plus.png", () => { UiBoundSensusServiceHelper.Get(true).PromptForInputsAsync( "Define Point Of Interest", new Input[] { new TextInput("POI Name:"), new TextInput("POI Type:"), new TextInput("Address:"), new YesNoInput("View Map:") }, null, inputs => { if (inputs == null) { return; } string name = inputs[0].Value as string; string type = inputs[1].Value as string; string address = inputs[2].Value as string; bool viewMap = (bool)inputs[3].Value; if (string.IsNullOrWhiteSpace(name) && string.IsNullOrWhiteSpace(type)) { UiBoundSensusServiceHelper.Get(true).FlashNotificationAsync("You must enter either a name or type (or both)."); } else { Action <List <Position> > addPOI = new Action <List <Position> >(poiPositions => { Device.BeginInvokeOnMainThread(async() => { if (poiPositions != null && poiPositions.Count > 0 && await DisplayAlert("Add POI?", "Would you like to add " + poiPositions.Count + " point(s) of interest?", "Yes", "No")) { foreach (Position poiPosition in poiPositions) { _pointsOfInterest.Add(new PointOfInterest(name, type, poiPosition.ToGeolocationPosition())); if (changeCallback != null) { changeCallback(); } Bind(); } } }); }); string newPinName = name + (string.IsNullOrWhiteSpace(type) ? "" : " (" + type + ")"); if (string.IsNullOrWhiteSpace(address)) { // cancel existing token source if we have one if (_gpsCancellationTokenSource != null && !_gpsCancellationTokenSource.IsCancellationRequested) { _gpsCancellationTokenSource.Cancel(); } _gpsCancellationTokenSource = new CancellationTokenSource(); Xamarin.Geolocation.Position gpsPosition = GpsReceiver.Get().GetReading(_gpsCancellationTokenSource.Token); if (gpsPosition != null) { Position formsPosition = gpsPosition.ToFormsPosition(); if (viewMap) { UiBoundSensusServiceHelper.Get(true).GetPositionsFromMapAsync(formsPosition, newPinName, addPOI); } else { addPOI(new Position[] { formsPosition }.ToList()); } } } else { UiBoundSensusServiceHelper.Get(true).GetPositionsFromMapAsync(address, newPinName, addPOI); } } }); })); Disappearing += (o, e) => { if (_gpsCancellationTokenSource != null && !_gpsCancellationTokenSource.IsCancellationRequested) { _gpsCancellationTokenSource.Cancel(); } }; }
/// <summary> /// Initializes a new instance of the <see cref="SensusUI.ProtocolPage"/> class. /// </summary> /// <param name="protocol">Protocol to display.</param> public ProtocolPage(Protocol protocol) { _protocol = protocol; Title = "Protocol"; List <View> views = new List <View>(); views.AddRange(UiProperty.GetPropertyStacks(_protocol)); #region data stores Button editLocalDataStoreButton = new Button { Text = "Local Data Store", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand, IsEnabled = !_protocol.Running }; editLocalDataStoreButton.Clicked += async(o, e) => { if (_protocol.LocalDataStore != null) { await Navigation.PushAsync(new DataStorePage(_protocol, _protocol.LocalDataStore.Copy(), true, false)); } }; Button createLocalDataStoreButton = new Button { Text = "+", FontSize = 20, HorizontalOptions = LayoutOptions.End, IsEnabled = !_protocol.Running }; createLocalDataStoreButton.Clicked += (o, e) => CreateDataStore(true); StackLayout localDataStoreStack = new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { editLocalDataStoreButton, createLocalDataStoreButton } }; views.Add(localDataStoreStack); Button editRemoteDataStoreButton = new Button { Text = "Remote Data Store", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand, IsEnabled = !_protocol.Running }; editRemoteDataStoreButton.Clicked += async(o, e) => { if (_protocol.RemoteDataStore != null) { await Navigation.PushAsync(new DataStorePage(_protocol, _protocol.RemoteDataStore.Copy(), false, false)); } }; Button createRemoteDataStoreButton = new Button { Text = "+", FontSize = 20, HorizontalOptions = LayoutOptions.End, IsEnabled = !_protocol.Running }; createRemoteDataStoreButton.Clicked += (o, e) => CreateDataStore(false); StackLayout remoteDataStoreStack = new StackLayout { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { editRemoteDataStoreButton, createRemoteDataStoreButton } }; views.Add(remoteDataStoreStack); #endregion #region points of interest Button pointsOfInterestButton = new Button { Text = "Points of Interest", FontSize = 20 }; pointsOfInterestButton.Clicked += async(o, e) => { await Navigation.PushAsync(new PointsOfInterestPage( _protocol.PointsOfInterest, () => UiBoundSensusServiceHelper.Get(true).SaveAsync())); }; views.Add(pointsOfInterestButton); #endregion #region view probes Button viewProbesButton = new Button { Text = "Probes", FontSize = 20 }; viewProbesButton.Clicked += async(o, e) => { await Navigation.PushAsync(new ProbesPage(_protocol)); }; views.Add(viewProbesButton); #endregion _protocolRunningChangedAction = (o, running) => { Device.BeginInvokeOnMainThread(() => { editLocalDataStoreButton.IsEnabled = createLocalDataStoreButton.IsEnabled = editRemoteDataStoreButton.IsEnabled = createRemoteDataStoreButton.IsEnabled = !running; }); }; StackLayout stack = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; foreach (View view in views) { stack.Children.Add(view); } Button lockButton = new Button { Text = _protocol.LockPasswordHash == "" ? "Lock" : "Unlock", FontSize = 20, HorizontalOptions = LayoutOptions.FillAndExpand }; lockButton.Clicked += (o, e) => { if (lockButton.Text == "Lock") { UiBoundSensusServiceHelper.Get(true).PromptForInputAsync( "Lock Protocol", new TextInput("Password:"******"Please enter a non-empty password."); } else { _protocol.LockPasswordHash = UiBoundSensusServiceHelper.Get(true).GetHash(password); Device.BeginInvokeOnMainThread(() => lockButton.Text = "Unlock"); } }); } else if (lockButton.Text == "Unlock") { _protocol.LockPasswordHash = ""; lockButton.Text = "Lock"; } }; stack.Children.Add(lockButton); Content = new ScrollView { Content = stack }; }
public DataStorePage(Protocol protocol, DataStore dataStore, bool local) { Title = (local ? "Local" : "Remote") + " Data Store"; List <StackLayout> stacks = UiProperty.GetPropertyStacks(dataStore); StackLayout buttonStack = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; stacks.Add(buttonStack); if (dataStore.Clearable) { Button clearButton = new Button { Text = "Clear", HorizontalOptions = LayoutOptions.FillAndExpand, FontSize = 20 }; clearButton.Clicked += async(o, e) => { if (await DisplayAlert("Clear data from " + dataStore.Name + "?", "This action cannot be undone.", "Clear", "Cancel")) { dataStore.Clear(); } }; buttonStack.Children.Add(clearButton); } if (local) { Button shareLocalDataButton = new Button { Text = "Share", HorizontalOptions = LayoutOptions.FillAndExpand, FontSize = 20 }; shareLocalDataButton.Clicked += async(o, e) => { LocalDataStore localDataStore = dataStore as LocalDataStore; if (localDataStore.DataCount > 0) { await Navigation.PushAsync(new ShareLocalDataStorePage(dataStore as LocalDataStore)); } else { UiBoundSensusServiceHelper.Get(true).FlashNotificationAsync("Local data store contains no data to share."); } }; buttonStack.Children.Add(shareLocalDataButton); } Button okayButton = new Button { Text = "OK", HorizontalOptions = LayoutOptions.FillAndExpand, FontSize = 20 }; okayButton.Clicked += async(o, e) => { if (local) { protocol.LocalDataStore = dataStore as LocalDataStore; } else { protocol.RemoteDataStore = dataStore as RemoteDataStore; } await Navigation.PopAsync(); }; buttonStack.Children.Add(okayButton); StackLayout contentLayout = new StackLayout { Orientation = StackOrientation.Vertical, VerticalOptions = LayoutOptions.FillAndExpand }; foreach (StackLayout stack in stacks) { contentLayout.Children.Add(stack); } Content = new ScrollView { Content = contentLayout }; }