public VerifiedParticipationPage(Protocol protocol, ParticipationRewardDatum participationRewardDatum)
        {
            Title = "Participation Verification";

            StackLayout contentLayout = new StackLayout
            {
                Orientation = StackOrientation.Vertical,
                VerticalOptions = LayoutOptions.FillAndExpand,
                Padding = new Thickness(0, 25, 0, 0)
            };

            string requiredParticipationPercentage = Math.Round(protocol.RewardThreshold.GetValueOrDefault() * 100, 0) + "%";
            string participationPercentage = Math.Round(participationRewardDatum.Participation * 100, 0) + "%";
            
            if (protocol.RewardThreshold == null)
            {
                contentLayout.Children.Add(
                    new Label
                    {
                        Text = "Verified Participation Level",
                        FontSize = 20,
                        HorizontalOptions = LayoutOptions.CenterAndExpand
                    });

                contentLayout.Children.Add(
                    new Label
                    {
                        Text = participationPercentage,
                        FontSize = 50,
                        HorizontalOptions = LayoutOptions.CenterAndExpand
                    }
                );
            }
            else
            {
                bool reward = participationRewardDatum.Participation >= protocol.RewardThreshold.GetValueOrDefault();

                contentLayout.Children.Add(
                    new Image
                    {
                        HorizontalOptions = LayoutOptions.FillAndExpand,
                        Source = ImageSource.FromFile(reward ? "check.png" : "x.png")
                    });

                contentLayout.Children.Add(
                    new Label
                    {
                        Text = "Participant should " + (reward ? "" : "not ") + "be rewarded. This study requires " + requiredParticipationPercentage + " participation, and the participant is at " + participationPercentage + ".",
                        FontSize = 20,
                        HorizontalOptions = LayoutOptions.CenterAndExpand
                    });
            }

            Content = new ScrollView
            {
                Content = contentLayout
            };
        }
示例#2
0
        public ParticipationReportPage(Protocol protocol, ParticipationRewardDatum participationRewardDatum, bool displayDatumQrCode)
        {
            Title = protocol.Name;

            #if __IOS__
            string howToIncreaseScore = "You can increase your score by opening Sensus more often and responding to questions that Sensus asks you.";
            #elif __ANDROID__
            string howToIncreaseScore = "You can increase your score by allowing Sensus to run continuously and responding to questions that Sensus asks you.";
            #elif WINDOWS_PHONE
            string userNotificationMessage = null; // TODO:  How to increase score?
            #else
            #error "Unrecognized platform."
            #endif

            StackLayout contentLayout = new StackLayout
            {
                Orientation = StackOrientation.Vertical,
                VerticalOptions = LayoutOptions.FillAndExpand,
                Padding = new Thickness(0, 25, 0, 0),
                Children =
                {
                    new Label
                    {
                        Text = "Participation Level",
                        FontSize = 20,
                        HorizontalOptions = LayoutOptions.CenterAndExpand
                    },
                    new Label
                    {
                        Text = Math.Round(participationRewardDatum.Participation * 100, 0) + "%",
                        FontSize = 50,
                        HorizontalOptions = LayoutOptions.CenterAndExpand
                    },
                    new Label
                    {
                        Text = "This score reflects your participation level over the past " + (protocol.ParticipationHorizonDays == 1 ? "day" : protocol.ParticipationHorizonDays + " days") + "." +
                        (displayDatumQrCode ? " Anyone can verify your participation by tapping \"Scan Participation Barcode\" on their device and scanning the following barcode:" : ""),
                        FontSize = 20,
                        HorizontalOptions = LayoutOptions.CenterAndExpand
                    }
                }
            };

            if (displayDatumQrCode)
            {
                Label expirationLabel = new Label
                {
                    FontSize = 15,
                    HorizontalOptions = LayoutOptions.CenterAndExpand
                };

                contentLayout.Children.Add(expirationLabel);

                Timer timer = new Timer(1000);

                timer.Elapsed += (o, e) =>
                {
                    Device.BeginInvokeOnMainThread(() =>
                        {
                            int secondsLeftBeforeBarcodeExpiration = (int)(SensusServiceHelper.PARTICIPATION_VERIFICATION_TIMEOUT_SECONDS - (DateTimeOffset.UtcNow - participationRewardDatum.Timestamp).TotalSeconds);

                            if (secondsLeftBeforeBarcodeExpiration <= 0)
                            {
                                expirationLabel.TextColor = Color.Red;
                                expirationLabel.Text = "Barcode has expired. Please reopen this page to renew it.";
                                timer.Stop();
                            }
                            else
                            {
                                --secondsLeftBeforeBarcodeExpiration;
                                expirationLabel.Text = "Barcode will expire in " + secondsLeftBeforeBarcodeExpiration + " second" + (secondsLeftBeforeBarcodeExpiration == 1 ? "" : "s") + ".";
                            }
                        });
                };

                timer.Start();

                Disappearing += (o, e) =>
                {
                    timer.Stop();
                };

                contentLayout.Children.Add(new Image
                    {
                        Source = SensusServiceHelper.Get().GetQrCodeImageSource(protocol.RemoteDataStore.GetDatumKey(participationRewardDatum)),
                        HorizontalOptions = LayoutOptions.CenterAndExpand
                    });
            }

            contentLayout.Children.Add(new Label
                {
                    Text = howToIncreaseScore,
                    FontSize = 20,
                    HorizontalOptions = LayoutOptions.CenterAndExpand
                });

            if (!string.IsNullOrWhiteSpace(protocol.ContactEmail))
            {
                Button emailStudyManagerButton = new Button
                {
                    Text = "Email Study Manager",
                    FontSize = 20
                };

                emailStudyManagerButton.Clicked += (o, e) =>
                {
                    SensusServiceHelper.Get().SendEmailAsync(protocol.ContactEmail, "Help with Sensus study:  " + protocol.Name,
                        "Hello - " + Environment.NewLine +
                        Environment.NewLine +
                        "I am having trouble with a Sensus study. The name of the study is \"" + protocol.Name + "\"." + Environment.NewLine +
                        Environment.NewLine +
                        "Here is why I am sending this email:  ");
                };

                contentLayout.Children.Add(emailStudyManagerButton);
            }

            Button viewParticipationDetailsButton = new Button
            {
                Text = "View Participation Details",
                FontSize = 20
            };

            viewParticipationDetailsButton.Clicked += async (o, e) =>
            {
                await Navigation.PushAsync(new ParticipationReportDetailsPage(protocol));
            };

            contentLayout.Children.Add(viewParticipationDetailsButton);

            Content = new ScrollView
            {
                Content = contentLayout
            };
        }
示例#3
0
        public ProtocolsPage()
        {
            Title = "Your Sensus Studies";

            _protocolsList = new ListView();
            _protocolsList.ItemTemplate = new DataTemplate(typeof(TextCell));
            _protocolsList.ItemTemplate.SetBinding(TextCell.TextProperty, new Binding(".", converter: new ProtocolNameValueConverter()));
            _protocolsList.ItemTemplate.SetBinding(TextCell.TextColorProperty, new Binding(".", converter: new ProtocolColorValueConverter()));
            _protocolsList.ItemTapped += async (o, e) =>
            {
                if (_protocolsList.SelectedItem == null)
                    return;

                Protocol selectedProtocol = _protocolsList.SelectedItem as Protocol;

                List<string> actions = new List<string>();

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

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

                actions.AddRange(new string[] { "Scan Participation Barcode", "Edit", "Copy", "Share" });

                List<Protocol> groupableProtocols = SensusServiceHelper.Get().RegisteredProtocols.Where(registeredProtocol => registeredProtocol != selectedProtocol && registeredProtocol.Groupable && registeredProtocol.GroupedProtocols.Count == 0).ToList();
                if (selectedProtocol.Groupable)
                {
                    if (selectedProtocol.GroupedProtocols.Count == 0 && groupableProtocols.Count > 0)
                        actions.Add("Group");
                    else if (selectedProtocol.GroupedProtocols.Count > 0)
                        actions.Add("Ungroup");
                }

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

                actions.Add("Delete");

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

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

                if (selectedAction == "Start")
                {
                    selectedProtocol.StartWithUserAgreementAsync(null, () =>
                        {
                            // rebind to pick up color and running status changes
                            Device.BeginInvokeOnMainThread(Bind);
                        });
                }
                else if (selectedAction == "Stop")
                {
                    if (await DisplayAlert("Confirm Stop", "Are you sure you want to stop " + selectedProtocol.Name + "?", "Yes", "No"))
                    {
                        selectedProtocol.StopAsync(() =>
                            {
                                // rebind to pick up color and running status changes
                                Device.BeginInvokeOnMainThread(Bind);
                            });
                    }
                }
                else if (selectedAction == "Display Participation")
                {
                    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

                    // pop up wait screen while we submit the participation reward datum
                    SensusServiceHelper.Get().PromptForInputsAsync(
                        null,
                        new InputGroup[]
                        {
                            new InputGroup("Please Wait", new LabelOnlyInput("Submitting participation information.", false))
                        },
                        cancellationTokenSource.Token,
                        false,
                        "Cancel",
                        null,
                        null,
                        null,
                        false,
                        async () =>
                        {
                            // add participation reward datum to remote data store and commit immediately
                            ParticipationRewardDatum participationRewardDatum = new ParticipationRewardDatum(DateTimeOffset.UtcNow, selectedProtocol.Participation);
                            selectedProtocol.RemoteDataStore.AddNonProbeDatum(participationRewardDatum);

                            bool commitFailed;

                            try
                            {
                                await selectedProtocol.RemoteDataStore.CommitAsync(cancellationTokenSource.Token);

                                // we should not have any remaining non-probe data
                                commitFailed = selectedProtocol.RemoteDataStore.HasNonProbeDatumToCommit(participationRewardDatum.Id);
                            }
                            catch (Exception)
                            {
                                commitFailed = true;
                            }

                            if (commitFailed)
                                SensusServiceHelper.Get().FlashNotificationAsync("Failed to submit participation information to remote server. You will not be able to verify your participation at this time.");

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

                            Device.BeginInvokeOnMainThread(async() =>
                                {
                                    // only show the QR code for the reward datum if the datum was committed to the remote data store
                                    await Navigation.PushAsync(new ParticipationReportPage(selectedProtocol, participationRewardDatum, !commitFailed));
                                });
                        },
                        inputs =>
                        {
                            // if the prompt was closed by the user instead of the cancellation token, cancel the token in order
                            // to cancel the remote data store commit. if the prompt was closed by the termination of the remote
                            // data store commit (i.e., by the canceled token), then don't cancel the token again.
                            if (!cancellationTokenSource.IsCancellationRequested)
                                cancellationTokenSource.Cancel();
                        });
                }
                else if (selectedAction == "Scan Participation Barcode")
                {
                    Result barcodeResult = null;

                    try
                    {
                        if (await SensusServiceHelper.Get().ObtainPermissionAsync(Permission.Camera) != PermissionStatus.Granted)
                            throw new Exception("Could not access camera.");

                        ZXing.Mobile.MobileBarcodeScanner scanner = SensusServiceHelper.Get().BarcodeScanner;

                        if (scanner == null)
                            throw new Exception("Barcode scanner not present.");

                        scanner.TopText = "Position a Sensus participation barcode in the window below, with the red line across the middle of the barcode.";
                        scanner.BottomText = "Sensus is not recording any of these images. Sensus is only trying to find a barcode.";
                        scanner.CameraUnsupportedMessage = "There is not a supported camera on this phone. Cannot scan barcode.";

                        barcodeResult = await scanner.Scan(new ZXing.Mobile.MobileBarcodeScanningOptions
                            {
                                PossibleFormats = new BarcodeFormat[] { BarcodeFormat.QR_CODE }.ToList()
                            });
                    }
                    catch (Exception ex)
                    {
                        string message = "Failed to scan barcode:  " + ex.Message;
                        SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType());
                        SensusServiceHelper.Get().FlashNotificationAsync(message);
                    }

                    if (barcodeResult != null)
                    {
                        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

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

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

                                    // ensure that the participation datum has not expired
                                    if (participationRewardDatum.Timestamp > DateTimeOffset.UtcNow.AddSeconds(-SensusServiceHelper.PARTICIPATION_VERIFICATION_TIMEOUT_SECONDS))
                                    {
                                        Device.BeginInvokeOnMainThread(async() =>
                                            {
                                                await Navigation.PushAsync(new VerifiedParticipationPage(selectedProtocol, participationRewardDatum));
                                            });
                                    }
                                    else
                                        SensusServiceHelper.Get().FlashNotificationAsync("Participation barcode has expired. The participant needs to regenerate the barcode.");
                                }
                                catch (Exception)
                                {
                                    SensusServiceHelper.Get().FlashNotificationAsync("Failed to retrieve participation information.");
                                }
                                finally
                                {
                                    // cancel the token to close the input above, but only if the token hasn't already been canceled. this will be
                                    // used if an exception is thrown while getting the participation reward datum.
                                    if (!cancellationTokenSource.IsCancellationRequested)
                                        cancellationTokenSource.Cancel();
                                }
                            },
                            inputs =>
                            {
                                // if the prompt was closed by the user instead of the cancellation token, cancel the token in order
                                // to cancel the datum retrieval. if the prompt was closed by the termination of the remote
                                // data store get (i.e., by the canceled token), then don't cancel the token again.
                                if (!cancellationTokenSource.IsCancellationRequested)
                                    cancellationTokenSource.Cancel();
                            });
                    }
                }
                else if (selectedAction == "Edit")
                {
                    ExecuteActionUponProtocolAuthentication(selectedProtocol, () =>
                        {
                            Device.BeginInvokeOnMainThread(async () =>
                                {
                                    ProtocolPage protocolPage = new ProtocolPage(selectedProtocol);
                                    protocolPage.Disappearing += (oo, ee) => Bind();  // rebind to pick up name changes
                                    await Navigation.PushAsync(protocolPage);
                                });
                        }
                    );
                }
                else if (selectedAction == "Copy")
                    selectedProtocol.CopyAsync(true, true);
                else if (selectedAction == "Share")
                {
                    Action ShareSelectedProtocol = new Action(() =>
                        {
                            // make a deep copy of the selected protocol so we can reset it for sharing
                            selectedProtocol.CopyAsync(false, false, selectedProtocolCopy =>
                                {
                                    selectedProtocolCopy.ResetForSharing();

                                    // write protocol to file and share
                                    string sharePath = SensusServiceHelper.Get().GetSharePath(".json");
                                    selectedProtocolCopy.Save(sharePath);
                                    SensusServiceHelper.Get().ShareFileAsync(sharePath, "Sensus Protocol:  " + selectedProtocolCopy.Name, "application/json");

                                });
                        });

                    if (selectedProtocol.Shareable)
                        ShareSelectedProtocol();
                    else
                        ExecuteActionUponProtocolAuthentication(selectedProtocol, ShareSelectedProtocol);
                }
                else if (selectedAction == "Group")
                {
                    SensusServiceHelper.Get().PromptForInputAsync("Group",
                        new ItemPickerPageInput("Select Protocols", groupableProtocols.Cast<object>().ToList(), "Name")
                        {
                            Multiselect = true
                        },
                        null, true, "Group", null, null, null, false,
                        input =>
                        {
                            if (input == null)
                            {
                                SensusServiceHelper.Get().FlashNotificationAsync("No protocols grouped.");
                                return;
                            }

                            ItemPickerPageInput itemPickerPageInput = input as ItemPickerPageInput;

                            List<Protocol> selectedProtocols = (itemPickerPageInput.Value as List<object>).Cast<Protocol>().ToList();

                            if (selectedProtocols.Count == 0)
                                SensusServiceHelper.Get().FlashNotificationAsync("No protocols grouped.");
                            else
                            {
                                selectedProtocol.GroupedProtocols.AddRange(selectedProtocols);
                                SensusServiceHelper.Get().FlashNotificationAsync("Grouped \"" + selectedProtocol.Name + "\" with " + selectedProtocols.Count + " other protocol" + (selectedProtocols.Count == 1 ? "" : "s") + ".");
                            }
                        });
                }
                else if (selectedAction == "Ungroup")
                {
                    if (await DisplayAlert("Ungroup " + selectedProtocol.Name + "?", "This protocol is currently grouped with the following other protocols:" + Environment.NewLine + Environment.NewLine + string.Concat(selectedProtocol.GroupedProtocols.Select(protocol => protocol.Name + Environment.NewLine)), "Ungroup", "Cancel"))
                        selectedProtocol.GroupedProtocols.Clear();
                }
                else if (selectedAction == "Status")
                {
                    if (SensusServiceHelper.Get().ProtocolShouldBeRunning(selectedProtocol))
                    {
                        selectedProtocol.TestHealthAsync(true, () =>
                            {
                                Device.BeginInvokeOnMainThread(async () =>
                                    {
                                        if (selectedProtocol.MostRecentReport == null)
                                            await DisplayAlert("No Report", "Status check failed.", "OK");
                                        else
                                            await Navigation.PushAsync(new ViewTextLinesPage("Protocol Status", selectedProtocol.MostRecentReport.ToString().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).ToList(), null, null));
                                    });
                            });
                    }
                    else
                        await DisplayAlert("Protocol Not Running", "Cannot check status of protocol when protocol is not running.", "OK");
                }
                else if (selectedAction == "Delete")
                {
                    if (await DisplayAlert("Delete " + selectedProtocol.Name + "?", "This action cannot be undone.", "Delete", "Cancel"))
                        selectedProtocol.DeleteAsync();
                }
            };

            Content = _protocolsList;

            ToolbarItems.Add(new ToolbarItem(null, "gear_wrench.png", async () =>
                    {
                        double shareDirectoryMB = SensusServiceHelper.GetDirectorySizeMB(SensusServiceHelper.SHARE_DIRECTORY);
                        string clearShareDirectoryAction = "Clear Share Directory (" + Math.Round(shareDirectoryMB, 1) + " MB)";

                        List<string> buttons = new string[] { "New Protocol", "View Log", "View Points of Interest", clearShareDirectoryAction }.ToList();

                        // stopping only makes sense on android, where we use a background service. on ios, there is no concept
                        // of stopping the app other than the user or system terminating the app.
                        #if __ANDROID__
                        buttons.Add("Stop Sensus");
                        #endif

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

                        if (action == "New Protocol")
                            Protocol.CreateAsync("New Protocol", null);
                        else if (action == "View Log")
                        {
                            await Navigation.PushAsync(new ViewTextLinesPage("Log", SensusServiceHelper.Get().Logger.Read(200, true),
                                    () =>
                                    {
                                        string sharePath = null;
                                        try
                                        {
                                            sharePath = SensusServiceHelper.Get().GetSharePath(".txt");
                                            SensusServiceHelper.Get().Logger.CopyTo(sharePath);
                                        }
                                        catch (Exception)
                                        {
                                            sharePath = null;
                                        }

                                        if (sharePath != null)
                                            SensusServiceHelper.Get().ShareFileAsync(sharePath, "Log:  " + Path.GetFileName(sharePath), "text/plain");
                                    },
                                    () => SensusServiceHelper.Get().Logger.Clear()));
                        }
                        else if (action == "View Points of Interest")
                            await Navigation.PushAsync(new PointsOfInterestPage(SensusServiceHelper.Get().PointsOfInterest));
                        else if (action == clearShareDirectoryAction)
                        {
                            foreach (string sharePath in Directory.GetFiles(SensusServiceHelper.SHARE_DIRECTORY))
                            {
                                try
                                {
                                    File.Delete(sharePath);
                                }
                                catch (Exception ex)
                                {
                                    string errorMessage = "Failed to delete shared file \"" + Path.GetFileName(sharePath) + "\":  " + ex.Message;
                                    SensusServiceHelper.Get().FlashNotificationAsync(errorMessage);
                                    SensusServiceHelper.Get().Logger.Log(errorMessage, LoggingLevel.Normal, GetType());
                                }
                            }
                        }
                        #if __ANDROID__
                        else if (action == "Stop Sensus" && await DisplayAlert("Confirm", "Are you sure you want to stop Sensus? This will end your participation in all studies.", "Stop Sensus", "Go Back"))
                            SensusServiceHelper.Get().Stop();
                        #endif
                    }));
        }