Beispiel #1
0
        public ProtocolsPage()
        {
            Title = "Your Studies";

            _protocolsList = new ListView(ListViewCachingStrategy.RecycleElement);
            _protocolsList.ItemTemplate = new DataTemplate(typeof(TextCell));
            _protocolsList.ItemTemplate.SetBinding(TextCell.TextProperty, nameof(Protocol.Caption));
            _protocolsList.ItemsSource = SensusServiceHelper.Get()?.RegisteredProtocols;
            _protocolsList.ItemTapped += async(o, e) =>
            {
                if (_protocolsList.SelectedItem == null)
                {
                    return;
                }

                Protocol selectedProtocol = _protocolsList.SelectedItem as Protocol;

                #region add actions
                List <string> actions = new List <string>();

                actions.Add(selectedProtocol.Running ? "Stop" : "Start");

                if (!string.IsNullOrWhiteSpace(selectedProtocol.ContactEmail))
                {
                    actions.Add("Email Study Manager for Help");
                }

                if (selectedProtocol.Running)
                {
                    actions.Add("Status");
                }

                actions.Add("View Data");

                if (selectedProtocol.Running)
                {
                    actions.Add("Display Participation");
                }

                if (selectedProtocol.RemoteDataStore?.CanRetrieveWrittenData ?? false)
                {
                    actions.Add("Scan Participation Barcode");
                }

                actions.AddRange(new string[] { "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 && selectedProtocol.ScheduledStartCallback != null)
                {
                    actions.Remove("Start");
                    actions.Insert(0, "Cancel Scheduled Start");
                }

                actions.Add("Delete");
                #endregion

                #region process selected action
                string selectedAction = await DisplayActionSheet(selectedProtocol.Name, "Cancel", null, actions.ToArray());

                // must reset the protocol selection manually
                SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                {
                    _protocolsList.SelectedItem = null;
                });

                if (selectedAction == "Status")
                {
                    List <Tuple <string, Dictionary <string, string> > > events = await selectedProtocol.TestHealthAsync(true);

                    await Navigation.PushAsync(new ViewTextLinesPage("Status", events.SelectMany(healthEventNameProperties =>
                    {
                        return(healthEventNameProperties.Item2.Select(propertyValue => healthEventNameProperties.Item1 + ":  " + propertyValue.Key + "=" + propertyValue.Value));
                    }).ToList(), null, null));
                }
                else if (selectedAction == "Start")
                {
                    selectedProtocol.StartWithUserAgreementAsync(null);
                }
                else if (selectedAction == "Cancel Scheduled Start")
                {
                    if (await DisplayAlert("Confirm Cancel", "Are you sure you want to cancel " + selectedProtocol.Name + "?", "Yes", "No"))
                    {
                        selectedProtocol.CancelScheduledStart();
                    }
                }
                else if (selectedAction == "Stop")
                {
                    if (await DisplayAlert("Confirm Stop", "Are you sure you want to stop " + selectedProtocol.Name + "?", "Yes", "No"))
                    {
                        await selectedProtocol.StopAsync();
                    }
                }
                else if (selectedAction == "Email Study Manager for Help")
                {
                    await SensusServiceHelper.Get().SendEmailAsync(selectedProtocol.ContactEmail, "Help with Sensus study:  " + selectedProtocol.Name,
                                                                   "Hello - " + Environment.NewLine +
                                                                   Environment.NewLine +
                                                                   "I am having trouble with a Sensus study. The name of the study is \"" + selectedProtocol.Name + "\"." + Environment.NewLine +
                                                                   Environment.NewLine +
                                                                   "Here is why I am sending this email:  ");
                }
                else if (selectedAction == "View Data")
                {
                    await Navigation.PushAsync(new ProbesViewPage(selectedProtocol));
                }
                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[] { new InputGroup {
                                    Name = "Please Wait", Inputs = { new LabelOnlyInput("Submitting participation information.", false) }
                                } },
                        cancellationTokenSource.Token,
                        false,
                        "Cancel",
                        null,
                        null,
                        null,
                        false,
                        async() =>
                    {
                        ParticipationRewardDatum participationRewardDatum = new ParticipationRewardDatum(DateTimeOffset.UtcNow, selectedProtocol.Participation);
                        participationRewardDatum.ProtocolId = selectedProtocol.Id;

                        bool writeFailed = false;
                        try
                        {
                            await selectedProtocol.RemoteDataStore.WriteDatumAsync(participationRewardDatum, cancellationTokenSource.Token);
                        }
                        catch (Exception)
                        {
                            writeFailed = true;
                        }

                        if (writeFailed)
                        {
                            await 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();
                        }

                        await SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() =>
                        {
                            // only show the QR code for the reward datum if the datum was written to the remote data store and if the data store can retrieve it.
                            await Navigation.PushAsync(new ParticipationReportPage(selectedProtocol, participationRewardDatum, !writeFailed && (selectedProtocol.RemoteDataStore?.CanRetrieveWrittenData ?? false)));
                        });
                    },
                        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 write. if the prompt was closed by the termination of the remote
                        // data store write (i.e., by the canceled token), then don't cancel the token again.
                        if (!cancellationTokenSource.IsCancellationRequested)
                        {
                            cancellationTokenSource.Cancel();
                        }
                    });
                }
                else if (selectedAction == "Scan Participation Barcode")
                {
                    try
                    {
                        Result barcodeResult = await SensusServiceHelper.Get().ScanQrCodeAsync(Navigation);

                        if (barcodeResult == null)
                        {
                            return;
                        }

                        SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                        {
                            CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

                            // pop up wait screen while we get the participation reward datum
                            SensusServiceHelper.Get().PromptForInputsAsync(
                                null,
                                new[] { new InputGroup {
                                            Name = "Please Wait", Inputs = { new LabelOnlyInput("Retrieving participation information.", false) }
                                        } },
                                cancellationTokenSource.Token,
                                false,
                                "Cancel",
                                null,
                                null,
                                null,
                                false,
                                async() =>
                            {
                                // after the page shows up, attempt to retrieve the participation reward datum.
                                try
                                {
                                    ParticipationRewardDatum participationRewardDatum = await selectedProtocol.RemoteDataStore.GetDatumAsync <ParticipationRewardDatum>(barcodeResult.Text, cancellationTokenSource.Token);

                                    // cancel the token to close the input above, but only if the token hasn't already been canceled by the user.
                                    if (!cancellationTokenSource.IsCancellationRequested)
                                    {
                                        cancellationTokenSource.Cancel();
                                    }

                                    // ensure that the participation datum has not expired
                                    if (participationRewardDatum.Timestamp > DateTimeOffset.UtcNow.AddSeconds(-SensusServiceHelper.PARTICIPATION_VERIFICATION_TIMEOUT_SECONDS))
                                    {
                                        await SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() =>
                                        {
                                            await Navigation.PushAsync(new VerifiedParticipationPage(selectedProtocol, participationRewardDatum));
                                        });
                                    }
                                    else
                                    {
                                        await SensusServiceHelper.Get().FlashNotificationAsync("Participation barcode has expired. The participant needs to regenerate the barcode.");
                                    }
                                }
                                catch (Exception)
                                {
                                    await 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 by the user. 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();
                                }
                            });
                        });
                    }
                    catch (Exception ex)
                    {
                        string message = "Failed to scan barcode:  " + ex.Message;
                        SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType());
                        await SensusServiceHelper.Get().FlashNotificationAsync(message);
                    }
                }
                else if (selectedAction == "Edit")
                {
                    ExecuteActionUponProtocolAuthentication(selectedProtocol, () =>
                    {
                        SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() =>
                        {
                            ProtocolPage protocolPage = new ProtocolPage(selectedProtocol);
                            await Navigation.PushAsync(protocolPage);
                        });
                    });
                }
                else if (selectedAction == "Copy")
                {
                    // reset the protocol id, as we're creating a new study
                    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. don't reset the id of the protocol to keep
                        // it in the same study. also do not register the copy since we're just going to send it off.
                        selectedProtocol.CopyAsync(false, false, async selectedProtocolCopy =>
                        {
                            // write protocol to file and share
                            string sharePath = SensusServiceHelper.Get().GetSharePath(".json");
                            selectedProtocolCopy.Save(sharePath);
                            await 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 == "Delete")
                {
                    if (await DisplayAlert("Delete " + selectedProtocol.Name + "?", "This action cannot be undone.", "Delete", "Cancel"))
                    {
                        await selectedProtocol.DeleteAsync();
                    }
                }
            };
            #endregion

            Content = _protocolsList;

            #region add toolbar items
            ToolbarItems.Add(new ToolbarItem(null, "plus.png", async() =>
            {
                List <string> buttons = new string[] { "From QR Code", "From URL", "New" }.ToList();

                string action = await DisplayActionSheet("Add Study", "Back", null, buttons.ToArray());

                if (action == "From QR Code")
                {
                    Result result = await SensusServiceHelper.Get().ScanQrCodeAsync(Navigation);

                    if (result != null)
                    {
                        Protocol.DeserializeAsync(new Uri(result.Text), Protocol.DisplayAndStartAsync);
                    }
                }
                else if (action == "From URL")
                {
                    SensusServiceHelper.Get().PromptForInputAsync(
                        "Download Protocol",
                        new SingleLineTextInput("Protocol URL:", Keyboard.Url),
                        null, true, null, null, null, null, false, input =>
                    {
                        // input might be null (user cancelled), or the value might be null (blank input submitted)
                        if (!string.IsNullOrEmpty(input?.Value?.ToString()))
                        {
                            Protocol.DeserializeAsync(new Uri(input.Value.ToString()), Protocol.DisplayAndStartAsync);
                        }
                    });
                }
                else if (action == "New")
                {
                    Protocol.Create("New Protocol");
                }
            }));

            ToolbarItems.Add(new ToolbarItem("ID", null, async() =>
            {
                await DisplayAlert("Device ID", SensusServiceHelper.Get().DeviceId, "Close");
            }, ToolbarItemOrder.Secondary));

            ToolbarItems.Add(new ToolbarItem("Log", null, async() =>
            {
                await Navigation.PushAsync(new ViewTextLinesPage("Log", SensusServiceHelper.Get().Logger.Read(200, true),

                                                                 async() =>
                {
                    string sharePath = null;
                    try
                    {
                        sharePath = SensusServiceHelper.Get().GetSharePath(".txt");
                        SensusServiceHelper.Get().Logger.CopyTo(sharePath);
                    }
                    catch (Exception)
                    {
                        sharePath = null;
                    }

                    if (sharePath != null)
                    {
                        await SensusServiceHelper.Get().ShareFileAsync(sharePath, "Log:  " + Path.GetFileName(sharePath), "text/plain");
                    }
                },

                                                                 () => SensusServiceHelper.Get().Logger.Clear()));
            }, ToolbarItemOrder.Secondary));

#if __ANDROID__
            ToolbarItems.Add(new ToolbarItem("Stop", null, async() =>
            {
                if (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().StopProtocols();

                    (SensusServiceHelper.Get() as Android.IAndroidSensusServiceHelper)?.StopAndroidSensusService();
                }
            }, ToolbarItemOrder.Secondary));
#endif

            ToolbarItems.Add(new ToolbarItem("About", null, async() =>
            {
                await DisplayAlert("About Sensus", "Version:  " + SensusServiceHelper.Get().Version, "OK");
            }, ToolbarItemOrder.Secondary));
            #endregion
        }
Beispiel #2
0
        public ProtocolsPage()
        {
            Title = "Your 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 (!string.IsNullOrWhiteSpace(selectedProtocol.ContactEmail))
                {
                    actions.Add("Email Study Manager for Help");
                }

                actions.Add("View Data");

                if (selectedProtocol.Running)
                {
                    actions.Add("Display Participation");
                }

                if (selectedProtocol.RemoteDataStore?.CanRetrieveCommittedData ?? false)
                {
                    actions.Add("Scan Participation Barcode");
                }

                actions.AddRange(new string[] { "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");
                }

                if (!selectedProtocol.Running && selectedProtocol.ScheduledStartCallback != null)
                {
                    actions.Remove("Start");
                    actions.Insert(0, "Cancel Scheduled Start");
                }

                actions.Add("Delete");

                string selectedAction = await DisplayActionSheet(selectedProtocol.Name, "Cancel", null, actions.ToArray());

                // must reset the protocol selection manually
                Device.BeginInvokeOnMainThread(() =>
                {
                    _protocolsList.SelectedItem = null;
                });

                if (selectedAction == "Start")
                {
                    selectedProtocol.StartWithUserAgreementAsync(null, () =>
                    {
                        // rebind to pick up color and running status changes
                        Refresh();
                    });
                }
                else if (selectedAction == "Cancel Scheduled Start")
                {
                    if (await DisplayAlert("Confirm Cancel", "Are you sure you want to cancel " + selectedProtocol.Name + "?", "Yes", "No"))
                    {
                        selectedProtocol.CancelScheduledStart();

                        // rebind to pick up color and running status changes
                        Refresh();
                    }
                }
                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
                            Refresh();
                        });
                    }
                }
                else if (selectedAction == "Email Study Manager for Help")
                {
                    SensusServiceHelper.Get().SendEmailAsync(selectedProtocol.ContactEmail, "Help with Sensus study:  " + selectedProtocol.Name,
                                                             "Hello - " + Environment.NewLine +
                                                             Environment.NewLine +
                                                             "I am having trouble with a Sensus study. The name of the study is \"" + selectedProtocol.Name + "\"." + Environment.NewLine +
                                                             Environment.NewLine +
                                                             "Here is why I am sending this email:  ");
                }
                else if (selectedAction == "View Data")
                {
                    await Navigation.PushAsync(new ProbesViewPage(selectedProtocol));
                }
                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[] { new InputGroup {
                                    Name = "Please Wait", Inputs = { 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);

                        bool commitFailed;

                        try
                        {
                            commitFailed = !await selectedProtocol.RemoteDataStore.CommitAsync(participationRewardDatum, cancellationTokenSource.Token);
                        }
                        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 and if the data store can retrieve it.
                            await Navigation.PushAsync(new ParticipationReportPage(selectedProtocol, participationRewardDatum, !commitFailed && (selectedProtocol.RemoteDataStore?.CanRetrieveCommittedData ?? false)));
                        });
                    },
                        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")
                {
                    try
                    {
                        Result barcodeResult = await SensusServiceHelper.Get().ScanQrCodeAsync(Navigation);

                        if (barcodeResult == null)
                        {
                            return;
                        }

                        SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                        {
                            CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

                            // pop up wait screen while we get the participation reward datum
                            SensusServiceHelper.Get().PromptForInputsAsync(
                                null,
                                new[] { new InputGroup {
                                            Name = "Please Wait", Inputs = { new LabelOnlyInput("Retrieving participation information.", false) }
                                        } },
                                cancellationTokenSource.Token,
                                false,
                                "Cancel",
                                null,
                                null,
                                null,
                                false,
                                async() =>
                            {
                                // after the page shows up, attempt to retrieve the participation reward datum.
                                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 by the user.
                                    if (!cancellationTokenSource.IsCancellationRequested)
                                    {
                                        cancellationTokenSource.Cancel();
                                    }

                                    // ensure that the participation datum has not expired
                                    if (participationRewardDatum.Timestamp > DateTimeOffset.UtcNow.AddSeconds(-SensusServiceHelper.PARTICIPATION_VERIFICATION_TIMEOUT_SECONDS))
                                    {
                                        await SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(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 by the user. 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();
                                }
                            });
                        });
                    }
                    catch (Exception ex)
                    {
                        string message = "Failed to scan barcode:  " + ex.Message;
                        SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType());
                        SensusServiceHelper.Get().FlashNotificationAsync(message);
                    }
                }
                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")
                {
                    // reset the protocol id, as we're creating a new study
                    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. don't reset the id of the protocol to keep
                        // it in the same study. also do not register the copy since we're just going to send it off.
                        selectedProtocol.CopyAsync(false, false, selectedProtocolCopy =>
                        {
                            // 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))
                    {
                        await 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, "plus.png", async() =>
            {
                List <string> buttons = new string[] { "From QR Code", "From URL", "From Scratch" }.ToList();

                string action = await DisplayActionSheet("Add Study", "Back", null, buttons.ToArray());

                if (action == "From QR Code")
                {
                    Result result = await SensusServiceHelper.Get().ScanQrCodeAsync(Navigation);

                    if (result != null)
                    {
                        Protocol.DeserializeAsync(new Uri(result.Text), Protocol.DisplayAndStartAsync);
                    }
                }
                else if (action == "From URL")
                {
                    SensusServiceHelper.Get().PromptForInputAsync(
                        "Download Protocol",
                        new SingleLineTextInput("Protocol URL:", Keyboard.Url),
                        null, true, null, null, null, null, false, input =>
                    {
                        // input might be null (user cancelled), or the value might be null (blank input submitted)
                        if (!string.IsNullOrEmpty(input?.Value?.ToString()))
                        {
                            Protocol.DeserializeAsync(new Uri(input.Value.ToString()), Protocol.DisplayAndStartAsync);
                        }
                    });
                }
                else if (action == "From Scratch")
                {
                    Protocol.Create("New Protocol");
                }
            }));

            Bind();

            System.Timers.Timer refreshTimer = new System.Timers.Timer(1000);

            refreshTimer.Elapsed += (sender, e) =>
            {
                Refresh();
            };

            Appearing += (sender, e) =>
            {
                refreshTimer.Start();
            };

            Disappearing += (sender, e) =>
            {
                refreshTimer.Stop();
            };
        }
Beispiel #3
0
        public ProtocolsPage()
        {
            Title = "Your 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");
                actions.Add("View Data");

                if (selectedProtocol.Running)
                {
                    actions.Add("Display Participation");
                }

                if (selectedProtocol.RemoteDataStore?.CanRetrieveCommittedData ?? false)
                {
                    actions.Add("Scan Participation Barcode");
                }

                actions.AddRange(new string[] { "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");
                }

                if (!selectedProtocol.Running && selectedProtocol.ScheduledStartCallbackId != null)
                {
                    actions.Remove("Start");
                    actions.Insert(0, "Cancel Scheduled Start");
                }

                actions.Add("Delete");

                string selectedAction = await DisplayActionSheet(selectedProtocol.Name, "Cancel", null, actions.ToArray());

                // must reset the protocol selection manually
                Device.BeginInvokeOnMainThread(() =>
                {
                    _protocolsList.SelectedItem = null;
                });

                if (selectedAction == "Start")
                {
                    selectedProtocol.StartWithUserAgreementAsync(null, () =>
                    {
                        // rebind to pick up color and running status changes
                        Refresh();
                    });
                }
                else if (selectedAction == "Cancel Scheduled Start")
                {
                    if (await DisplayAlert("Confirm Cancel", "Are you sure you want to cancel " + selectedProtocol.Name + "?", "Yes", "No"))
                    {
                        selectedProtocol.CancelScheduledStart();

                        // rebind to pick up color and running status changes
                        Refresh();
                    }
                }
                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
                            Refresh();
                        });
                    }
                }
                else if (selectedAction == "View Data")
                {
                    await Navigation.PushAsync(new ProbesViewPage(selectedProtocol));
                }
                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 [] { new InputGroup {
                                     Name = "Please Wait", Inputs = { 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);

                        bool commitFailed;

                        try
                        {
                            commitFailed = !await selectedProtocol.RemoteDataStore.CommitAsync(participationRewardDatum, cancellationTokenSource.Token);
                        }
                        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 and if the data store can retrieve it.
                            await Navigation.PushAsync(new ParticipationReportPage(selectedProtocol, participationRewardDatum, !commitFailed && (selectedProtocol.RemoteDataStore?.CanRetrieveCommittedData ?? false)));
                        });
                    },
                        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.");
                        }

#if __ANDROID__ || __IOS__
                        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()
                        });
#endif
                    }
                    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 [] { new InputGroup {
                                         Name = "Please Wait", Inputs = { 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. don't reset the id of the protocol to keep
                        // it in the same study. also do not register the copy since we're just going to send it off.
                        selectedProtocol.CopyAsync(false, false, selectedProtocolCopy =>
                        {
                            // 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))
                    {
                        await 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

                buttons.Add("About Sensus");

                string action = await DisplayActionSheet("Other Actions", "Back", null, buttons.ToArray());

                if (action == "New Protocol")
                {
                    Protocol.Create("New Protocol");
                }
                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().StopProtocols();
                    (SensusServiceHelper.Get() as Android.IAndroidSensusServiceHelper)?.StopAndroidSensusService();
                }
#endif
                else if (action == "About Sensus")
                {
                    await DisplayAlert("About Sensus", "Version:  " + SensusServiceHelper.Get().Version, "OK");
                }
            }));

            Bind();

            System.Timers.Timer refreshTimer = new System.Timers.Timer(1000);

            refreshTimer.Elapsed += (sender, e) =>
            {
                Refresh();
            };

            Appearing += (sender, e) =>
            {
                refreshTimer.Start();
            };

            Disappearing += (sender, e) =>
            {
                refreshTimer.Stop();
            };
        }
Beispiel #4
0
        private async Task SetAgentButton_Clicked(Button setAgentButton)
        {
            List <Input> agentSelectionInputs = new List <Input>();

            // show any existing agents
            List <SensingAgent> currentAgents = null;

            // android allows us to dynamically load code assemblies, but iOS does not. so, the current approach
            // is to only support dynamic loading on android and force compile-time assembly inclusion on ios.
#if __ANDROID__
            // try to extract agents from a previously loaded assembly
            try
            {
                currentAgents = _protocol.GetAgents(_protocol.AgentAssemblyBytes);
            }
            catch (Exception)
            { }
#elif __IOS__
            currentAgents = _protocol.GetAgents();

            // display warning message, as there is no other option to load agents.
            if (currentAgents.Count == 0)
            {
                await SensusServiceHelper.Get().FlashNotificationAsync("No agents available.");

                return;
            }
#endif

            // let the user pick from currently available agents
            ItemPickerPageInput currentAgentsPicker = null;
            if (currentAgents != null && currentAgents.Count > 0)
            {
                currentAgentsPicker = new ItemPickerPageInput("Available agent" + (currentAgents.Count > 1 ? "s" : "") + ":", currentAgents.Select(agent => agent.Id).Cast <object>().ToList())
                {
                    Required = false
                };

                agentSelectionInputs.Add(currentAgentsPicker);
            }

#if __ANDROID__
            // add option to scan qr code to import a new assembly
            QrCodeInput agentAssemblyUrlQrCodeInput = new QrCodeInput(QrCodePrefix.SENSING_AGENT, "URL:", false, "Agent URL:")
            {
                Required = false
            };

            agentSelectionInputs.Add(agentAssemblyUrlQrCodeInput);
#endif

            List <Input> completedInputs = await SensusServiceHelper.Get().PromptForInputsAsync("Sensing Agent", agentSelectionInputs, null, true, "Set", null, null, null, false);

            if (completedInputs == null)
            {
                return;
            }

            // check for QR code on android. this doesn't exist on ios.
            string agentURL = null;

#if __ANDROID__
            agentURL = agentAssemblyUrlQrCodeInput.Value?.ToString();
#endif

            // if there is no URL, check if the user has selected an agent.
            if (string.IsNullOrWhiteSpace(agentURL))
            {
                if (currentAgentsPicker != null)
                {
                    string selectedAgentId = (currentAgentsPicker.Value as List <object>).FirstOrDefault() as string;

                    SensingAgent selectedAgent = null;

                    if (selectedAgentId != null)
                    {
                        selectedAgent = currentAgents.First(currentAgent => currentAgent.Id == selectedAgentId);
                    }

                    // set the selected agent, watching out for a null (clearing) selection that needs to be confirmed
                    if (selectedAgentId != null || await DisplayAlert("Confirm", "Are you sure you wish to clear the sensing agent?", "Yes", "No"))
                    {
                        _protocol.Agent = selectedAgent;

                        setAgentButton.Text = "Set Agent" + (_protocol.Agent == null ? "" : ":  " + _protocol.Agent.Id);

                        if (_protocol.Agent == null)
                        {
                            await SensusServiceHelper.Get().FlashNotificationAsync("Sensing agent cleared.");
                        }
                    }
                }
            }
#if __ANDROID__
            else
            {
                // download agent assembly from scanned QR code
                byte[] downloadedBytes      = null;
                string downloadErrorMessage = null;
                try
                {
                    // download the assembly and extract agents
                    downloadedBytes = _protocol.AgentAssemblyBytes = await new WebClient().DownloadDataTaskAsync(new Uri(agentURL));
                    List <SensingAgent> qrCodeAgents = _protocol.GetAgents(downloadedBytes);

                    if (qrCodeAgents.Count == 0)
                    {
                        throw new Exception("No agents were present in the specified file.");
                    }
                }
                catch (Exception ex)
                {
                    downloadErrorMessage = ex.Message;
                }

                // if error message is null, then we have 1 or more agents in the downloaded assembly.
                if (downloadErrorMessage == null)
                {
                    // redisplay the current input prompt including the agents we just downloaded
                    _protocol.AgentAssemblyBytes = downloadedBytes;
                    await SetAgentButton_Clicked(setAgentButton);
                }
                else
                {
                    SensusServiceHelper.Get().Logger.Log(downloadErrorMessage, LoggingLevel.Normal, GetType());
                    await SensusServiceHelper.Get().FlashNotificationAsync(downloadErrorMessage);
                }
            }
#endif
        }
Beispiel #5
0
        public ProtocolsPage()
        {
            Title = "Your Studies";

            _protocolsList = new ListView(ListViewCachingStrategy.RecycleElement);
            _protocolsList.ItemTemplate = new DataTemplate(typeof(TextCell));
            _protocolsList.ItemTemplate.SetBinding(TextCell.TextProperty, nameof(Protocol.Caption));
            _protocolsList.ItemTemplate.SetBinding(TextCell.DetailProperty, nameof(Protocol.SubCaption));
            _protocolsList.ItemsSource = SensusServiceHelper.Get()?.RegisteredProtocols;
            _protocolsList.ItemTapped += async(o, e) =>
            {
                if (_protocolsList.SelectedItem == null)
                {
                    return;
                }

                Protocol selectedProtocol = _protocolsList.SelectedItem as Protocol;

                #region add protocol actions
                List <string> actions = new List <string>();

                if (selectedProtocol.State == ProtocolState.Running)
                {
                    actions.Add("Stop");

                    if (selectedProtocol.AllowPause)
                    {
                        actions.Add("Pause");
                    }
                }
                else if (selectedProtocol.State == ProtocolState.Stopped)
                {
                    actions.Add("Start");
                }
                else if (selectedProtocol.State == ProtocolState.Paused)
                {
                    actions.Add("Resume");
                }

                if (selectedProtocol.AllowTagging)
                {
                    actions.Add("Tag Data");
                }

                if (selectedProtocol.State == ProtocolState.Stopped && selectedProtocol.AllowParticipantIdReset)
                {
                    actions.Add("Reset ID");
                }

                if (!string.IsNullOrWhiteSpace(selectedProtocol.ContactEmail))
                {
                    actions.Add("Email Study Manager for Help");
                }

                if (selectedProtocol.State == ProtocolState.Running && selectedProtocol.AllowViewStatus)
                {
                    actions.Add("Status");
                }

                if (selectedProtocol.AllowViewData)
                {
                    actions.Add("View Data");
                }

                if (selectedProtocol.State == ProtocolState.Running)
                {
                    if (selectedProtocol.AllowSubmitData)
                    {
                        actions.Add("Submit Data");
                    }

                    if (selectedProtocol.AllowParticipationScanning)
                    {
                        actions.Add("Display Participation");
                    }
                }

                if (selectedProtocol.AllowParticipationScanning && (selectedProtocol.RemoteDataStore?.CanRetrieveWrittenData ?? false))
                {
                    actions.Add("Scan Participation Barcode");
                }

                actions.Add("Edit");

                if (selectedProtocol.AllowCopy)
                {
                    actions.Add("Copy");
                }

                if (selectedProtocol.Shareable)
                {
                    actions.Add("Share Protocol");
                }

                if (selectedProtocol.AllowLocalDataShare && (selectedProtocol.LocalDataStore?.HasDataToShare ?? false))
                {
                    actions.Add("Share Local Data");
                }

                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.State == ProtocolState.Stopped && selectedProtocol.StartIsScheduled)
                {
                    actions.Remove("Start");
                    actions.Insert(0, "Cancel Scheduled Start");
                }

                if (selectedProtocol.State == ProtocolState.Running && selectedProtocol.AllowTestPushNotification)
                {
                    actions.Add("Request Test Push Notification");
                }

                actions.Add("Delete");
                #endregion

                #region process selected protocol action
                string selectedAction = await DisplayActionSheet(selectedProtocol.Name, "Cancel", null, actions.ToArray());

                // must reset the protocol selection manually
                _protocolsList.SelectedItem = null;

                if (selectedAction == "Start")
                {
                    await selectedProtocol.StartWithUserAgreementAsync();
                }
                else if (selectedAction == "Cancel Scheduled Start")
                {
                    if (await DisplayAlert("Confirm Cancel", "Are you sure you want to cancel " + selectedProtocol.Name + "?", "Yes", "No"))
                    {
                        await selectedProtocol.CancelScheduledStartAsync();
                    }
                }
                else if (selectedAction == "Stop")
                {
                    if (await DisplayAlert("Confirm Stop", "Are you sure you want to stop " + selectedProtocol.Name + "?", "Yes", "No"))
                    {
                        await selectedProtocol.StopAsync();
                    }
                }
                else if (selectedAction == "Pause")
                {
                    await selectedProtocol.PauseAsync();
                }
                else if (selectedAction == "Resume")
                {
                    await selectedProtocol.ResumeAsync();
                }
                else if (selectedAction == "Tag Data")
                {
                    await Navigation.PushAsync(new TaggingPage(selectedProtocol));
                }
                else if (selectedAction == "Reset ID")
                {
                    selectedProtocol.ParticipantId = null;
                    await SensusServiceHelper.Get().FlashNotificationAsync("Your ID has been reset.");
                }
                else if (selectedAction == "Status")
                {
                    List <AnalyticsTrackedEvent> trackedEvents = await selectedProtocol.TestHealthAsync(true, CancellationToken.None);

                    await Navigation.PushAsync(new ViewTextLinesPage("Status", trackedEvents.SelectMany(trackedEvent =>
                    {
                        return(trackedEvent.Properties.Select(propertyValue => trackedEvent.Name + ":  " + propertyValue.Key + "=" + propertyValue.Value));
                    }).ToList()));
                }
                else if (selectedAction == "Email Study Manager for Help")
                {
                    await SensusServiceHelper.Get().SendEmailAsync(selectedProtocol.ContactEmail, "Help with Sensus study:  " + selectedProtocol.Name,
                                                                   "Hello - " + Environment.NewLine +
                                                                   Environment.NewLine +
                                                                   "I am having trouble with a Sensus study. The name of the study is \"" + selectedProtocol.Name + "\"." + Environment.NewLine +
                                                                   Environment.NewLine +
                                                                   "Here is why I am sending this email:  ");
                }
                else if (selectedAction == "View Data")
                {
                    await Navigation.PushAsync(new ProbesViewPage(selectedProtocol));
                }
                else if (selectedAction == "Submit Data")
                {
                    try
                    {
                        if (await selectedProtocol.RemoteDataStore?.WriteLocalDataStoreAsync(CancellationToken.None))
                        {
                            await SensusServiceHelper.Get().FlashNotificationAsync("Data submitted.");
                        }
                        else
                        {
                            throw new Exception("Failed to submit data.");
                        }
                    }
                    catch (Exception ex)
                    {
                        await SensusServiceHelper.Get().FlashNotificationAsync("Error:  " + ex.Message);
                    }
                }
                else if (selectedAction == "Display Participation")
                {
                    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

                    // pop up wait screen while we submit the participation reward datum
                    IEnumerable <InputGroup> inputGroups = await SensusServiceHelper.Get().PromptForInputsAsync(
                        null,
                        new InputGroup[] { new InputGroup {
                                               Name = "Please Wait", Inputs = { new LabelOnlyInput("Submitting participation information.", false) }
                                           } },
                        cancellationTokenSource.Token,
                        false,
                        "Cancel",
                        null,
                        null,
                        null,
                        false,
                        async() =>
                    {
                        ParticipationRewardDatum participationRewardDatum = new ParticipationRewardDatum(DateTimeOffset.UtcNow, selectedProtocol.Participation)
                        {
                            ProtocolId    = selectedProtocol.Id,
                            ParticipantId = selectedProtocol.ParticipantId
                        };

                        bool writeFailed = false;
                        try
                        {
                            await selectedProtocol.RemoteDataStore.WriteDatumAsync(participationRewardDatum, cancellationTokenSource.Token);
                        }
                        catch (Exception)
                        {
                            writeFailed = true;
                        }

                        if (writeFailed)
                        {
                            await 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();
                        }

                        await SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() =>
                        {
                            // only show the QR code for the reward datum if the datum was written to the remote data store and if the data store can retrieve it.
                            await Navigation.PushAsync(new ParticipationReportPage(selectedProtocol, participationRewardDatum, !writeFailed && (selectedProtocol.RemoteDataStore?.CanRetrieveWrittenData ?? false)));
                        });
                    });

                    // if the prompt was closed by the user instead of the cancellation token, cancel the token in order
                    // to cancel the remote data store write. if the prompt was closed by the termination of the remote
                    // data store write (i.e., by the canceled token), then don't cancel the token again.
                    if (!cancellationTokenSource.IsCancellationRequested)
                    {
                        cancellationTokenSource.Cancel();
                    }
                }
                else if (selectedAction == "Scan Participation Barcode")
                {
                    try
                    {
                        string barcodeResult = await SensusServiceHelper.Get().ScanQrCodeAsync(QrCodePrefix.SENSUS_PARTICIPATION);

                        if (barcodeResult == null)
                        {
                            return;
                        }

                        await SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() =>
                        {
                            CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

                            // pop up wait screen while we get the participation reward datum
                            IEnumerable <InputGroup> inputGroups = await SensusServiceHelper.Get().PromptForInputsAsync(
                                null,
                                new InputGroup[] { new InputGroup {
                                                       Name = "Please Wait", Inputs = { new LabelOnlyInput("Retrieving participation information.", false) }
                                                   } },
                                cancellationTokenSource.Token,
                                false,
                                "Cancel",
                                null,
                                null,
                                null,
                                false,
                                async() =>
                            {
                                // after the page shows up, attempt to retrieve the participation reward datum.
                                try
                                {
                                    ParticipationRewardDatum participationRewardDatum = await selectedProtocol.RemoteDataStore.GetDatumAsync <ParticipationRewardDatum>(barcodeResult, cancellationTokenSource.Token);

                                    // cancel the token to close the input above, but only if the token hasn't already been canceled by the user.
                                    if (!cancellationTokenSource.IsCancellationRequested)
                                    {
                                        cancellationTokenSource.Cancel();
                                    }

                                    // ensure that the participation datum has not expired
                                    if (participationRewardDatum.Timestamp > DateTimeOffset.UtcNow.AddSeconds(-SensusServiceHelper.PARTICIPATION_VERIFICATION_TIMEOUT_SECONDS))
                                    {
                                        await SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() =>
                                        {
                                            await Navigation.PushAsync(new VerifiedParticipationPage(selectedProtocol, participationRewardDatum));
                                        });
                                    }
                                    else
                                    {
                                        await SensusServiceHelper.Get().FlashNotificationAsync("Participation barcode has expired. The participant needs to regenerate the barcode.");
                                    }
                                }
                                catch (Exception)
                                {
                                    await 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 by the user. this will be
                                    // used if an exception is thrown while getting the participation reward datum.
                                    if (!cancellationTokenSource.IsCancellationRequested)
                                    {
                                        cancellationTokenSource.Cancel();
                                    }
                                }
                            });

                            // 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();
                            }
                        });
                    }
                    catch (Exception ex)
                    {
                        string message = "Failed to scan barcode:  " + ex.Message;
                        SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType());
                        await SensusServiceHelper.Get().FlashNotificationAsync(message);
                    }
                }
                else if (selectedAction == "Edit")
                {
                    if (await AuthenticateProtocolAsync(selectedProtocol))
                    {
                        await SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() =>
                        {
                            ProtocolPage protocolPage = new ProtocolPage(selectedProtocol);
                            await Navigation.PushAsync(protocolPage);
                        });
                    }
                }
                else if (selectedAction == "Copy")
                {
                    // reset the protocol id, as we're creating a new study
                    await selectedProtocol.CopyAsync(true, true);
                }
                else if (selectedAction == "Share Protocol")
                {
                    await selectedProtocol.ShareAsync();
                }
                else if (selectedAction == "Share Local Data")
                {
                    await selectedProtocol.LocalDataStore?.ShareLocalDataAsync();
                }
                else if (selectedAction == "Group")
                {
                    Input input = await SensusServiceHelper.Get().PromptForInputAsync("Group", new ItemPickerPageInput("Select Protocols", groupableProtocols.Cast <object>().ToList(), textBindingPropertyPath: nameof(Protocol.Name))
                    {
                        Multiselect = true
                    }, null, true, "Group", null, null, null, false);

                    if (input == null)
                    {
                        await 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)
                    {
                        await SensusServiceHelper.Get().FlashNotificationAsync("No protocols grouped.");
                    }
                    else
                    {
                        selectedProtocol.GroupedProtocols.AddRange(selectedProtocols);
                        await 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 == "Request Test Push Notification")
                {
                    try
                    {
                        PushNotificationRequest request = new PushNotificationRequest(SensusServiceHelper.Get().DeviceId + ".test", SensusServiceHelper.Get().DeviceId, selectedProtocol, "Test", "Your test push notification has been delivered.", "default", PushNotificationRequest.LocalFormat, DateTimeOffset.UtcNow, Guid.NewGuid());
                        await SensusContext.Current.Notifier.SendPushNotificationRequestAsync(request, CancellationToken.None);
                        await DisplayAlert("Pending", "Your test push notification was sent and is pending delivery. It should come back within 5 minutes.", "OK");
                    }
                    catch (Exception ex)
                    {
                        await DisplayAlert("Error", "Failed to send test push notification:  " + ex.Message, "OK");
                    }
                }
                else if (selectedAction == "Delete")
                {
                    if (await DisplayAlert("Delete " + selectedProtocol.Name + "?", "This action cannot be undone.", "Delete", "Cancel"))
                    {
                        await selectedProtocol.DeleteAsync();
                    }
                }
                #endregion
            };

            Content = _protocolsList;

            #region add toolbar items
            ToolbarItems.Add(new ToolbarItem(null, "plus.png", async() =>
            {
                string action = await DisplayActionSheet("Add Study", "Back", null, new[] { "From QR Code", "From URL", "New" });

                if (action == "New")
                {
                    await Protocol.CreateAsync("New Study");
                }
                else
                {
                    string url = null;

                    if (action == "From QR Code")
                    {
                        url = await SensusServiceHelper.Get().ScanQrCodeAsync(QrCodePrefix.SENSUS_PROTOCOL);
                    }
                    else if (action == "From URL")
                    {
                        Input input = await SensusServiceHelper.Get().PromptForInputAsync("Download Study", new SingleLineTextInput("Study URL:", Keyboard.Url), null, true, null, null, null, null, false);

                        // input might be null (user cancelled), or the value might be null (blank input submitted)
                        url = input?.Value?.ToString();
                    }

                    if (url != null)
                    {
                        Protocol protocol       = null;
                        Exception loadException = null;

                        // handle managed studies...handshake with authentication service.
                        if (url.StartsWith(Protocol.MANAGED_URL_STRING))
                        {
                            ProgressPage loadProgressPage = null;

                            try
                            {
                                Tuple <string, string> baseUrlParticipantId = ParseManagedProtocolURL(url);

                                AuthenticationService authenticationService = new AuthenticationService(baseUrlParticipantId.Item1);

                                // get account and credentials. this can take a while, so show the user something fun to look at.
                                CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
                                loadProgressPage = new ProgressPage("Configuring study. Please wait...", cancellationTokenSource);
                                await loadProgressPage.DisplayAsync(Navigation);

                                await loadProgressPage.SetProgressAsync(0, "creating account");
                                Account account = await authenticationService.CreateAccountAsync(baseUrlParticipantId.Item2);
                                cancellationTokenSource.Token.ThrowIfCancellationRequested();

                                await loadProgressPage.SetProgressAsync(0.3, "getting credentials");
                                AmazonS3Credentials credentials = await authenticationService.GetCredentialsAsync();
                                cancellationTokenSource.Token.ThrowIfCancellationRequested();

                                await loadProgressPage.SetProgressAsync(0.6, "downloading study");
                                protocol = await Protocol.DeserializeAsync(new Uri(credentials.ProtocolURL), true, credentials);
                                await loadProgressPage.SetProgressAsync(1, null);

                                // don't throw for cancellation here as doing so will leave the protocol partially configured. if
                                // the download succeeds, ensure that the properties get set below before throwing any exceptions.
                                protocol.ParticipantId         = account.ParticipantId;
                                protocol.AuthenticationService = authenticationService;

                                // make sure protocol has the id that we expect
                                if (protocol.Id != credentials.ProtocolId)
                                {
                                    throw new Exception("The identifier of the study does not match that of the credentials.");
                                }
                            }
                            catch (Exception ex)
                            {
                                loadException = ex;
                            }
                            finally
                            {
                                // ensure the progress page is closed
                                await(loadProgressPage?.CloseAsync() ?? Task.CompletedTask);
                            }
                        }
                        // handle unmanaged studies...direct download from URL.
                        else
                        {
                            try
                            {
                                protocol = await Protocol.DeserializeAsync(new Uri(url), true);
                            }
                            catch (Exception ex)
                            {
                                loadException = ex;
                            }
                        }

                        // show load exception to user
                        if (loadException != null)
                        {
                            await SensusServiceHelper.Get().FlashNotificationAsync("Failed to get study:  " + loadException.Message);
                            protocol = null;
                        }

                        // start protocol if we have one
                        if (protocol != null)
                        {
                            // save app state to hang on to protocol, authentication information, etc.
                            await SensusServiceHelper.Get().SaveAsync();

                            // show the protocol to the user and start
                            await Protocol.DisplayAndStartAsync(protocol);
                        }
                    }
                }
            }));

            ToolbarItems.Add(new ToolbarItem("ID", null, async() =>
            {
                await DisplayAlert("Device ID", SensusServiceHelper.Get().DeviceId, "Close");
            }, ToolbarItemOrder.Secondary));

            ToolbarItems.Add(new ToolbarItem("Log", null, async() =>
            {
                Logger logger = SensusServiceHelper.Get().Logger as Logger;
                await Navigation.PushAsync(new ViewTextLinesPage("Log", logger.Read(500, true), logger.Clear));
            }, ToolbarItemOrder.Secondary));

#if __ANDROID__
            ToolbarItems.Add(new ToolbarItem("Stop", null, async() =>
            {
                if (await DisplayAlert("Confirm", "Are you sure you want to stop Sensus? This will end your participation in all studies.", "Stop Sensus", "Go Back"))
                {
                    // stop all protocols and then stop the service. stopping the service alone does not stop
                    // the service, as we want to cover the case when the os stops/destroys the service. in this
                    // case we do not want to mark the protocols as stopped, as we'd like them to start back
                    // up when the os (or a push notification) starts the service again.
                    await SensusServiceHelper.Get().StopAsync();

                    global::Android.App.Application.Context.StopService(AndroidSensusService.GetServiceIntent(false));
                }
            }, ToolbarItemOrder.Secondary));
#endif

            ToolbarItems.Add(new ToolbarItem("About", null, async() =>
            {
                await DisplayAlert("About Sensus", "Version:  " + SensusServiceHelper.Get().Version, "OK");
            }, ToolbarItemOrder.Secondary));
            #endregion
        }
Beispiel #6
0
        public TaggingPage(Protocol protocol)
        {
            Title = "Tagging";

            Button selectTagsButton = new Button
            {
                Text              = GetSelectTagButtonText(protocol),
                FontSize          = 20,
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            selectTagsButton.Clicked += async(sender, e) =>
            {
                List <object> availableTags = protocol.AvailableTags.Cast <object>().ToList();
                availableTags.Sort();

                ItemPickerPageInput tagPickerInput = await SensusServiceHelper.Get().PromptForInputAsync("Select Tag(s)",
                                                                                                         new ItemPickerPageInput("Available Tags", availableTags)
                {
                    Multiselect = true,
                    Required    = true
                },
                                                                                                         null, true, "OK", null, null, null, false) as ItemPickerPageInput;

                if (tagPickerInput != null)
                {
                    protocol.TaggedEventTags = (tagPickerInput.Value as List <object>).Cast <string>().ToList();
                    protocol.TaggedEventTags.Sort();
                    selectTagsButton.Text = GetSelectTagButtonText(protocol);
                }
            };

            Button addTagButton = new Button
            {
                Text              = "Add Tag",
                FontSize          = 20,
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            addTagButton.Clicked += async(sender, e) =>
            {
                SingleLineTextInput tagInput = await SensusServiceHelper.Get().PromptForInputAsync("Add Tag",
                                                                                                   new SingleLineTextInput("Tag:", Keyboard.Text)
                {
                    Required = true
                },
                                                                                                   null, true, "Add", null, null, null, false) as SingleLineTextInput;

                string tagToAdd = tagInput?.Value?.ToString().Trim();

                if (!string.IsNullOrWhiteSpace(tagToAdd) && !protocol.AvailableTags.Contains(tagToAdd))
                {
                    protocol.AvailableTags.Add(tagToAdd);
                }
            };

            Button startStopButton = new Button
            {
                Text              = protocol.TaggedEventId == null ? "Start" : "Stop",
                FontSize          = 50,
                HorizontalOptions = LayoutOptions.FillAndExpand,
                VerticalOptions   = LayoutOptions.FillAndExpand
            };

            Button exportTaggingsButton = new Button
            {
                Text              = "Export Taggings",
                FontSize          = 20,
                HorizontalOptions = LayoutOptions.FillAndExpand
            };

            exportTaggingsButton.Clicked += async(sender, e) =>
            {
                try
                {
                    if (protocol.TaggingsToExport.Count == 0)
                    {
                        await SensusServiceHelper.Get().FlashNotificationAsync("There are no taggings to export. Select tags and tap Start to generate a tagging.");
                    }
                    else
                    {
                        string path = SensusServiceHelper.Get().GetSharePath(".csv");
                        File.WriteAllText(path, "tagged-event-id,start,end,tags" + Environment.NewLine + string.Concat(protocol.TaggingsToExport.Select(tagging => tagging + Environment.NewLine)));
                        await SensusServiceHelper.Get().ShareFileAsync(path, protocol.Name + ":  Taggings", "text/plain");
                    }
                }
                catch (Exception ex)
                {
                    await SensusServiceHelper.Get().FlashNotificationAsync("Failed to export taggings:  " + ex.Message);
                }
            };

            startStopButton.Clicked += async(sender, e) =>
            {
                // start tagging
                if (startStopButton.Text == "Start")
                {
                    if (protocol.TaggedEventTags.Count > 0)
                    {
                        // mark the start of the tagging
                        protocol.TaggingStartTimestamp = DateTimeOffset.UtcNow;

                        // set the tagged event id, which signals the data storage routines to apply tags to all data.
                        protocol.TaggedEventId = Guid.NewGuid().ToString();

                        startStopButton.Text       = "Stop";
                        selectTagsButton.IsEnabled = addTagButton.IsEnabled = exportTaggingsButton.IsEnabled = false;
                    }
                    else
                    {
                        await SensusServiceHelper.Get().FlashNotificationAsync("Must select 1 or more tags before starting.");
                    }
                }
                // stop tagging
                else
                {
                    // hang on to the id, as we're about to clear it.
                    string taggedEventId = protocol.TaggedEventId;

                    // clear the tagged event id, which signals the data storage routines to stop applying tags to data.
                    protocol.TaggedEventId = null;

                    // mark the end of the tagging
                    protocol.TaggingEndTimestamp = DateTimeOffset.UtcNow;

                    if (await DisplayAlert("Confirm Tagging", "Would you like to keep the following tagging?" + Environment.NewLine + Environment.NewLine +
                                           "Start:  " + protocol.TaggingStartTimestamp + Environment.NewLine +
                                           "End:  " + protocol.TaggingEndTimestamp + Environment.NewLine +
                                           "Tags:  " + string.Concat(protocol.TaggedEventTags.Select(tag => tag + ", ")).Trim(',', ' '), "Yes", "No"))
                    {
                        protocol.TaggingsToExport.Add(taggedEventId + "," + protocol.TaggingStartTimestamp + "," + protocol.TaggingEndTimestamp + "," + string.Concat(protocol.TaggedEventTags.Select(tag => tag + "|")).Trim('|'));
                        await SensusServiceHelper.Get().FlashNotificationAsync("Tagging kept. Tap the export button when finished with all taggings.");
                    }
                    else
                    {
                        await SensusServiceHelper.Get().FlashNotificationAsync("Tagging discarded.");
                    }

                    // reset start and end. don't reset the currently set tags, as the user likely wishes to use them again.
                    protocol.TaggingStartTimestamp = protocol.TaggingEndTimestamp = null;

                    startStopButton.Text       = "Start";
                    selectTagsButton.IsEnabled = addTagButton.IsEnabled = exportTaggingsButton.IsEnabled = true;
                }
            };

            // a tagging might have been started on a previous display of this page. enabled buttons if a tagging is not ongoing.
            selectTagsButton.IsEnabled = addTagButton.IsEnabled = exportTaggingsButton.IsEnabled = protocol.TaggedEventId == null;

            Content = new ScrollView
            {
                Content = new StackLayout
                {
                    Orientation       = StackOrientation.Vertical,
                    HorizontalOptions = LayoutOptions.FillAndExpand,
                    Children          =
                    {
                        selectTagsButton,
                        addTagButton,
                        startStopButton,
                        exportTaggingsButton
                    }
                }
            };
        }