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"); _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 = "Your Sensus Studies"; _protocolsList = new ListView(); _protocolsList.ItemTemplate = new DataTemplate(typeof(TextCell)); _protocolsList.ItemTemplate.SetBinding(TextCell.TextProperty, new Binding(".", converter: new ProtocolNameValueConverter())); _protocolsList.ItemTemplate.SetBinding(TextCell.TextColorProperty, new Binding(".", converter: new ProtocolColorValueConverter())); _protocolsList.ItemTapped += async (o, e) => { if (_protocolsList.SelectedItem == null) return; Protocol selectedProtocol = _protocolsList.SelectedItem as Protocol; List<string> actions = new List<string>(); actions.Add(selectedProtocol.Running ? "Stop" : "Start"); if (selectedProtocol.Running) actions.Add("Display Participation"); actions.AddRange(new string[] { "Scan Participation Barcode", "Edit", "Copy", "Share" }); List<Protocol> groupableProtocols = SensusServiceHelper.Get().RegisteredProtocols.Where(registeredProtocol => registeredProtocol != selectedProtocol && registeredProtocol.Groupable && registeredProtocol.GroupedProtocols.Count == 0).ToList(); if (selectedProtocol.Groupable) { if (selectedProtocol.GroupedProtocols.Count == 0 && groupableProtocols.Count > 0) actions.Add("Group"); else if (selectedProtocol.GroupedProtocols.Count > 0) actions.Add("Ungroup"); } if (selectedProtocol.Running) actions.Add("Status"); actions.Add("Delete"); string selectedAction = await DisplayActionSheet(selectedProtocol.Name, "Cancel", null, actions.ToArray()); // must reset the protocol select manually Device.BeginInvokeOnMainThread(() => { _protocolsList.SelectedItem = null; }); if (selectedAction == "Start") { selectedProtocol.StartWithUserAgreementAsync(null, () => { // rebind to pick up color and running status changes Device.BeginInvokeOnMainThread(Bind); }); } else if (selectedAction == "Stop") { if (await DisplayAlert("Confirm Stop", "Are you sure you want to stop " + selectedProtocol.Name + "?", "Yes", "No")) { selectedProtocol.StopAsync(() => { // rebind to pick up color and running status changes Device.BeginInvokeOnMainThread(Bind); }); } } else if (selectedAction == "Display Participation") { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); // pop up wait screen while we submit the participation reward datum SensusServiceHelper.Get().PromptForInputsAsync( null, new InputGroup[] { new InputGroup("Please Wait", new LabelOnlyInput("Submitting participation information.", false)) }, cancellationTokenSource.Token, false, "Cancel", null, null, null, false, async () => { // add participation reward datum to remote data store and commit immediately ParticipationRewardDatum participationRewardDatum = new ParticipationRewardDatum(DateTimeOffset.UtcNow, selectedProtocol.Participation); selectedProtocol.RemoteDataStore.AddNonProbeDatum(participationRewardDatum); bool commitFailed; try { await selectedProtocol.RemoteDataStore.CommitAsync(cancellationTokenSource.Token); // we should not have any remaining non-probe data commitFailed = selectedProtocol.RemoteDataStore.HasNonProbeDatumToCommit(participationRewardDatum.Id); } catch (Exception) { commitFailed = true; } if (commitFailed) SensusServiceHelper.Get().FlashNotificationAsync("Failed to submit participation information to remote server. You will not be able to verify your participation at this time."); // cancel the token to close the input above, but only if the token hasn't already been canceled. if (!cancellationTokenSource.IsCancellationRequested) cancellationTokenSource.Cancel(); Device.BeginInvokeOnMainThread(async() => { // only show the QR code for the reward datum if the datum was committed to the remote data store await Navigation.PushAsync(new ParticipationReportPage(selectedProtocol, participationRewardDatum, !commitFailed)); }); }, inputs => { // if the prompt was closed by the user instead of the cancellation token, cancel the token in order // to cancel the remote data store commit. if the prompt was closed by the termination of the remote // data store commit (i.e., by the canceled token), then don't cancel the token again. if (!cancellationTokenSource.IsCancellationRequested) cancellationTokenSource.Cancel(); }); } else if (selectedAction == "Scan Participation Barcode") { Result barcodeResult = null; try { if (await SensusServiceHelper.Get().ObtainPermissionAsync(Permission.Camera) != PermissionStatus.Granted) throw new Exception("Could not access camera."); ZXing.Mobile.MobileBarcodeScanner scanner = SensusServiceHelper.Get().BarcodeScanner; if (scanner == null) throw new Exception("Barcode scanner not present."); scanner.TopText = "Position a Sensus participation barcode in the window below, with the red line across the middle of the barcode."; scanner.BottomText = "Sensus is not recording any of these images. Sensus is only trying to find a barcode."; scanner.CameraUnsupportedMessage = "There is not a supported camera on this phone. Cannot scan barcode."; barcodeResult = await scanner.Scan(new ZXing.Mobile.MobileBarcodeScanningOptions { PossibleFormats = new BarcodeFormat[] { BarcodeFormat.QR_CODE }.ToList() }); } catch (Exception ex) { string message = "Failed to scan barcode: " + ex.Message; SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType()); SensusServiceHelper.Get().FlashNotificationAsync(message); } if (barcodeResult != null) { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); // pop up wait screen while we get the participation reward datum SensusServiceHelper.Get().PromptForInputsAsync( null, new InputGroup[] { new InputGroup("Please Wait", new LabelOnlyInput("Retrieving participation information.", false)) }, cancellationTokenSource.Token, false, "Cancel", null, null, null, false, async () => { try { ParticipationRewardDatum participationRewardDatum = await selectedProtocol.RemoteDataStore.GetDatum<ParticipationRewardDatum>(barcodeResult.Text, cancellationTokenSource.Token); // cancel the token to close the input above, but only if the token hasn't already been canceled. if (!cancellationTokenSource.IsCancellationRequested) cancellationTokenSource.Cancel(); // ensure that the participation datum has not expired if (participationRewardDatum.Timestamp > DateTimeOffset.UtcNow.AddSeconds(-SensusServiceHelper.PARTICIPATION_VERIFICATION_TIMEOUT_SECONDS)) { Device.BeginInvokeOnMainThread(async() => { await Navigation.PushAsync(new VerifiedParticipationPage(selectedProtocol, participationRewardDatum)); }); } else SensusServiceHelper.Get().FlashNotificationAsync("Participation barcode has expired. The participant needs to regenerate the barcode."); } catch (Exception) { SensusServiceHelper.Get().FlashNotificationAsync("Failed to retrieve participation information."); } finally { // cancel the token to close the input above, but only if the token hasn't already been canceled. this will be // used if an exception is thrown while getting the participation reward datum. if (!cancellationTokenSource.IsCancellationRequested) cancellationTokenSource.Cancel(); } }, inputs => { // if the prompt was closed by the user instead of the cancellation token, cancel the token in order // to cancel the datum retrieval. if the prompt was closed by the termination of the remote // data store get (i.e., by the canceled token), then don't cancel the token again. if (!cancellationTokenSource.IsCancellationRequested) cancellationTokenSource.Cancel(); }); } } else if (selectedAction == "Edit") { ExecuteActionUponProtocolAuthentication(selectedProtocol, () => { Device.BeginInvokeOnMainThread(async () => { ProtocolPage protocolPage = new ProtocolPage(selectedProtocol); protocolPage.Disappearing += (oo, ee) => Bind(); // rebind to pick up name changes await Navigation.PushAsync(protocolPage); }); } ); } else if (selectedAction == "Copy") selectedProtocol.CopyAsync(true, true); else if (selectedAction == "Share") { Action ShareSelectedProtocol = new Action(() => { // make a deep copy of the selected protocol so we can reset it for sharing selectedProtocol.CopyAsync(false, false, selectedProtocolCopy => { selectedProtocolCopy.ResetForSharing(); // write protocol to file and share string sharePath = SensusServiceHelper.Get().GetSharePath(".json"); selectedProtocolCopy.Save(sharePath); SensusServiceHelper.Get().ShareFileAsync(sharePath, "Sensus Protocol: " + selectedProtocolCopy.Name, "application/json"); }); }); if (selectedProtocol.Shareable) ShareSelectedProtocol(); else ExecuteActionUponProtocolAuthentication(selectedProtocol, ShareSelectedProtocol); } else if (selectedAction == "Group") { SensusServiceHelper.Get().PromptForInputAsync("Group", new ItemPickerPageInput("Select Protocols", groupableProtocols.Cast<object>().ToList(), "Name") { Multiselect = true }, null, true, "Group", null, null, null, false, input => { if (input == null) { SensusServiceHelper.Get().FlashNotificationAsync("No protocols grouped."); return; } ItemPickerPageInput itemPickerPageInput = input as ItemPickerPageInput; List<Protocol> selectedProtocols = (itemPickerPageInput.Value as List<object>).Cast<Protocol>().ToList(); if (selectedProtocols.Count == 0) SensusServiceHelper.Get().FlashNotificationAsync("No protocols grouped."); else { selectedProtocol.GroupedProtocols.AddRange(selectedProtocols); SensusServiceHelper.Get().FlashNotificationAsync("Grouped \"" + selectedProtocol.Name + "\" with " + selectedProtocols.Count + " other protocol" + (selectedProtocols.Count == 1 ? "" : "s") + "."); } }); } else if (selectedAction == "Ungroup") { if (await DisplayAlert("Ungroup " + selectedProtocol.Name + "?", "This protocol is currently grouped with the following other protocols:" + Environment.NewLine + Environment.NewLine + string.Concat(selectedProtocol.GroupedProtocols.Select(protocol => protocol.Name + Environment.NewLine)), "Ungroup", "Cancel")) selectedProtocol.GroupedProtocols.Clear(); } else if (selectedAction == "Status") { if (SensusServiceHelper.Get().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 Status", selectedProtocol.MostRecentReport.ToString().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).ToList(), null, null)); }); }); } else await DisplayAlert("Protocol Not Running", "Cannot check status of protocol when protocol is not running.", "OK"); } else if (selectedAction == "Delete") { if (await DisplayAlert("Delete " + selectedProtocol.Name + "?", "This action cannot be undone.", "Delete", "Cancel")) selectedProtocol.DeleteAsync(); } }; Content = _protocolsList; ToolbarItems.Add(new ToolbarItem(null, "gear_wrench.png", async () => { double shareDirectoryMB = SensusServiceHelper.GetDirectorySizeMB(SensusServiceHelper.SHARE_DIRECTORY); string clearShareDirectoryAction = "Clear Share Directory (" + Math.Round(shareDirectoryMB, 1) + " MB)"; List<string> buttons = new string[] { "New Protocol", "View Log", "View Points of Interest", clearShareDirectoryAction }.ToList(); // stopping only makes sense on android, where we use a background service. on ios, there is no concept // of stopping the app other than the user or system terminating the app. #if __ANDROID__ buttons.Add("Stop Sensus"); #endif string action = await DisplayActionSheet("Other Actions", "Back", null, buttons.ToArray()); if (action == "New Protocol") Protocol.CreateAsync("New Protocol", null); else if (action == "View Log") { await Navigation.PushAsync(new ViewTextLinesPage("Log", SensusServiceHelper.Get().Logger.Read(200, true), () => { string sharePath = null; try { sharePath = SensusServiceHelper.Get().GetSharePath(".txt"); SensusServiceHelper.Get().Logger.CopyTo(sharePath); } catch (Exception) { sharePath = null; } if (sharePath != null) SensusServiceHelper.Get().ShareFileAsync(sharePath, "Log: " + Path.GetFileName(sharePath), "text/plain"); }, () => SensusServiceHelper.Get().Logger.Clear())); } else if (action == "View Points of Interest") await Navigation.PushAsync(new PointsOfInterestPage(SensusServiceHelper.Get().PointsOfInterest)); else if (action == clearShareDirectoryAction) { foreach (string sharePath in Directory.GetFiles(SensusServiceHelper.SHARE_DIRECTORY)) { try { File.Delete(sharePath); } catch (Exception ex) { string errorMessage = "Failed to delete shared file \"" + Path.GetFileName(sharePath) + "\": " + ex.Message; SensusServiceHelper.Get().FlashNotificationAsync(errorMessage); SensusServiceHelper.Get().Logger.Log(errorMessage, LoggingLevel.Normal, GetType()); } } } #if __ANDROID__ else if (action == "Stop Sensus" && await DisplayAlert("Confirm", "Are you sure you want to stop Sensus? This will end your participation in all studies.", "Stop Sensus", "Go Back")) SensusServiceHelper.Get().Stop(); #endif })); }
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 }