package gui // viewProfileGui.go implements pages to view user profiles import "fyne.io/fyne/v2" import "fyne.io/fyne/v2/canvas" import "fyne.io/fyne/v2/container" import "fyne.io/fyne/v2/dialog" import "fyne.io/fyne/v2/layout" import "fyne.io/fyne/v2/theme" import "fyne.io/fyne/v2/widget" import "seekia/resources/worldLanguages" import "seekia/resources/worldLocations" import "seekia/resources/trainedPredictionModels" import "seekia/resources/geneticReferences/monogenicDiseases" import "seekia/resources/geneticReferences/polygenicDiseases" import "seekia/resources/geneticReferences/traits" import "seekia/internal/appMemory" import "seekia/internal/encoding" import "seekia/internal/genetics/companyAnalysis" import "seekia/internal/genetics/createCoupleGeneticAnalysis" import "seekia/internal/genetics/createPersonGeneticAnalysis" import "seekia/internal/genetics/locusValue" import "seekia/internal/genetics/myChosenAnalysis" import "seekia/internal/genetics/myPeople" import "seekia/internal/genetics/readGeneticAnalysis" import "seekia/internal/globalSettings" import "seekia/internal/helpers" import "seekia/internal/identity" import "seekia/internal/imagery" import "seekia/internal/mateQuestionnaire" import "seekia/internal/moderation/moderatorRanking" import "seekia/internal/moderation/trustedViewableStatus" import "seekia/internal/moderation/verifiedStickyStatus" import "seekia/internal/myIdentity" import "seekia/internal/network/appNetworkType/getAppNetworkType" import "seekia/internal/network/getFundedStatuses" import "seekia/internal/network/myBroadcasts" import "seekia/internal/profiles/attributeDisplay" import "seekia/internal/profiles/calculatedAttributes" import "seekia/internal/profiles/myLocalProfiles" import "seekia/internal/profiles/myProfileExports" import "seekia/internal/profiles/myProfileStatus" import "seekia/internal/profiles/profileStorage" import "seekia/internal/profiles/readProfiles" import "seekia/internal/profiles/viewableProfiles" import "slices" import "strings" import "image" import "time" import "errors" // This page will show user the difference between local/public and let them choose func setViewMyLocalOrPublicProfilePage(window fyne.Window, myProfileType string, previousPage func()){ setLoadingScreen(window, "Loading My Profile", "Loading...") if (myProfileType != "Mate" && myProfileType != "Host" && myProfileType != "Moderator"){ setErrorEncounteredPage(window, errors.New("setViewMyLocalOrPublicProfilePage called with invalid profile type: " + myProfileType), previousPage) return } currentPage := func(){setViewMyLocalOrPublicProfilePage(window, myProfileType, previousPage)} title := getPageTitleCentered(translate("View My Profile")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("View My " + myProfileType + " Profile") myIdentityExists, _, err := myIdentity.GetMyIdentityHash(myProfileType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (myIdentityExists == false){ description1 := getBoldLabelCentered("Your " + myProfileType + " identity does not exist.") description2 := getLabelCentered("Create your " + myProfileType + " identity below.") createIdentityButton := getWidgetCentered(widget.NewButtonWithIcon("Create Identity", theme.NavigateNextIcon(), func(){ setChooseNewIdentityHashPage(window, myProfileType, currentPage, currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, createIdentityButton) setPageContent(page, window) return } description1 := getLabelCentered("Choose which profile to view.") description2 := getLabelCentered("Your Local profile is the profile stored on your computer.") description3 := getLabelCentered("Your Public profile is the most recent profile that you have broadcasted.") localIcon, err := getFyneImageIcon("Local") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } publicIcon, err := getFyneImageIcon("Broadcast") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewLocalProfileButton := widget.NewButton("Local", func(){ setViewMyProfilePage(window, myProfileType, "Local", currentPage) }) viewPublicProfileButton := widget.NewButton("Public", func(){ setViewMyProfilePage(window, myProfileType, "Public", currentPage) }) viewLocalButtonWithIcon := container.NewGridWithColumns(1, localIcon, viewLocalProfileButton) viewPublicButtonWithIcon := container.NewGridWithColumns(1, publicIcon, viewPublicProfileButton) buttonsRow := container.NewHBox(layout.NewSpacer(), viewLocalButtonWithIcon, viewPublicButtonWithIcon, layout.NewSpacer()) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), buttonsRow, widget.NewSeparator()) setPageContent(page, window) } func setViewMyProfilePage(window fyne.Window, myProfileType string, localOrPublic string, previousPage func()){ if (myProfileType != "Mate" && myProfileType != "Host" && myProfileType != "Moderator"){ setErrorEncounteredPage(window, errors.New("setViewMyProfilePage called with invalid myProfileType: " + myProfileType), previousPage) return } if (localOrPublic != "Local" && localOrPublic != "Public"){ setErrorEncounteredPage(window, errors.New("setViewMyProfilePage called with invalid localOrPublic: " + localOrPublic), previousPage) return } currentPage := func(){setViewMyProfilePage(window, myProfileType, localOrPublic, previousPage)} title := getPageTitleCentered("View My Profile") backButton := getBackButtonCentered(previousPage) myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(myProfileType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (myIdentityExists == false){ description1 := getBoldLabelCentered("Your " + myProfileType + " identity does not exist.") description2 := getLabelCentered("Create your " + myProfileType + " identity below.") createIdentityButton := getWidgetCentered(widget.NewButtonWithIcon("Create Identity", theme.NavigateNextIcon(), func(){ setChooseNewIdentityHashPage(window, myProfileType, currentPage, currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, createIdentityButton) setPageContent(page, window) return } if (myProfileType == "Mate"){ myGenomePersonIdentifierExists, myGenomePersonIdentifier, err := myLocalProfiles.GetProfileData("Mate", "GenomePersonIdentifier") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (myGenomePersonIdentifierExists == true){ anyGenomesExist, personAnalysisIsReady, _, err := myPeople.CheckIfPersonAnalysisIsReady(myGenomePersonIdentifier) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (anyGenomesExist == true && personAnalysisIsReady == false){ description1 := getBoldLabelCentered(translate("Your profile contains a linked genome person.")) description2 := getLabelCentered(translate("You need to perform your genetic analysis.")) description3 := getLabelCentered(translate("Only the information you choose will be shared in your profile.")) performAnalysisButton := getWidgetCentered(widget.NewButtonWithIcon(translate("Perform Analysis"), theme.NavigateNextIcon(), func(){ setConfirmPerformPersonAnalysisPage(window, myGenomePersonIdentifier, currentPage, currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, performAnalysisButton) setPageContent(page, window) return } } } appNetworkType, err := getAppNetworkType.GetAppNetworkType() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } err = myProfileExports.UpdateMyExportedProfile(myProfileType, appNetworkType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } profileFound, profileHash, _, rawProfileMap, err := myProfileExports.GetMyExportedProfile(myProfileType, appNetworkType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (profileFound == false){ setErrorEncounteredPage(window, errors.New("My exported profile not found after exporting."), previousPage) return } getAnyLocalProfileAttributeFunction, err := calculatedAttributes.GetRetrieveAnyProfileAttributeIncludingCalculatedFunction(1, rawProfileMap) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } exists, _, iAmDisabled, err := getAnyLocalProfileAttributeFunction("Disabled") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (exists == true && iAmDisabled == "Yes"){ description1 := getBoldLabelCentered("Your " + myProfileType + " profile is disabled.") description2 := getLabelCentered("Re-enable it on the Broadcast page.") page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2) setPageContent(page, window) return } if (localOrPublic == "Local"){ setViewUserProfilePage(window, true, profileHash, 0, getAnyLocalProfileAttributeFunction, previousPage) return } // localOrPublic == "Public" identityExists, broadcastProfileExists, publicProfileHash, getAnyPublicProfileAttributeFunction, err := myBroadcasts.GetRetrieveAnyAttributeFromMyBroadcastProfileFunction(myIdentityHash, appNetworkType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (identityExists == false){ setErrorEncounteredPage(window, errors.New("My identity not found after being found already."), previousPage) return } if (broadcastProfileExists == false){ description1 := getBoldLabelCentered("Your public profile does not exist.") description2 := getLabelCentered("You have not broadcast your profile.") description3 := getLabelCentered("Broadcast your profile on the Profile - Broadcast page.") page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3) setPageContent(page, window) return } myIdentityExists, myProfileIsActive, err := myProfileStatus.GetMyProfileIsActiveStatus(myIdentityHash, appNetworkType) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (myIdentityExists == false){ setErrorEncounteredPage(window, errors.New("My identity not found after being found already."), previousPage) return } if (myProfileIsActive == false){ description1 := getBoldLabelCentered("Your public profile has expired.") description2 := getLabelCentered("You must broadcast your profile again.") description3 := getLabelCentered("Broadcast your profile on the Profile - Broadcast page.") page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3) setPageContent(page, window) return } setViewUserProfilePage(window, true, publicProfileHash, 0, getAnyPublicProfileAttributeFunction, previousPage) } func setViewPeerProfilePageFromIdentityHash(window fyne.Window, peerIdentityHash [16]byte, previousPage func()){ setLoadingScreen(window, "View Profile", "Loading Profile...") currentPage := func(){setViewPeerProfilePageFromIdentityHash(window, peerIdentityHash, previousPage)} title := getPageTitleCentered(translate("View Profile")) backButton := getBackButtonCentered(previousPage) userIdentityType, err := identity.GetIdentityTypeFromIdentityHash(peerIdentityHash) if (err != nil) { peerIdentityHashHex := encoding.EncodeBytesToHexString(peerIdentityHash[:]) setErrorEncounteredPage(window, errors.New("setViewPeerProfilePageFromIdentityHash called with invalid peerIdentityHash: " + peerIdentityHashHex), previousPage) return } // We only allow unknown viewable status profiles to be viewed if the user profile type is host/moderator getAllowUnknownStatusBool := func()bool{ if (userIdentityType == "Mate"){ return false } return true } allowUnknownStatusBool := getAllowUnknownStatusBool() appNetworkType, err := getAppNetworkType.GetAppNetworkType() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } viewableProfileExists, profileHash, getAnyAttributeFromViewableProfileFunction, err := viewableProfiles.GetRetrieveAnyNewestViewableUserProfileAttributeFunction(peerIdentityHash, appNetworkType, true, allowUnknownStatusBool, true) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (viewableProfileExists == true){ // The user has a viewable profile. // We display it. setViewUserProfilePage(window, false, profileHash, 0, getAnyAttributeFromViewableProfileFunction, previousPage) return } // Viewable profile does not exist // We check to see if any profile exists //Outputs: // -bool: Viewable status is known // -bool: Identity Is Viewable status // -error getIdentityIsViewableStickyStatus := func()(bool, bool, error){ // First try verified status, then try trusted if verified is not available downloadingRequiredReviews, networkParametersExist, stickyConsensusEstablished, identityIsViewableStatus, err := verifiedStickyStatus.GetVerifiedIdentityIsViewableStickyStatus(peerIdentityHash, appNetworkType) if (err != nil) { return false, false, err } if (downloadingRequiredReviews == true && networkParametersExist == true && stickyConsensusEstablished == true){ return true, identityIsViewableStatus, nil } statusIsKnown, identityIsViewable, _, err := trustedViewableStatus.GetTrustedIdentityIsViewableStatus(peerIdentityHash, appNetworkType) if (err != nil) { return false, false, err } if (statusIsKnown == true){ return true, identityIsViewable, nil } return false, false, nil } identityViewableStatusIsKnown, identityIsViewableStatus, err := getIdentityIsViewableStickyStatus() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } theirNewestProfileExists, newestProfileVersion, newestProfileHash, _, _, newestProfileRawMap, err := profileStorage.GetNewestUserProfile(peerIdentityHash, appNetworkType) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (theirNewestProfileExists == false){ // Their profile does not exist. downloadProfileFunction := func(){setDownloadMissingUserProfilePage(window, peerIdentityHash, true, true, previousPage, currentPage, previousPage)} if (identityViewableStatusIsKnown == true && identityIsViewableStatus == false){ description1 := getLabelCentered("This user is banned.") description2 := getLabelCentered("You do not have their profile downloaded.") description3 := getLabelCentered("Do you want to try to download their profile?") downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.ConfirmIcon(), downloadProfileFunction)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, downloadButton) setPageContent(page, window) return } // Identity is not banned. Profile does not exist // We check if user's identity is known to be expired. statusIsKnown, identityIsFunded, _, err := getFundedStatuses.GetIdentityIsFundedStatus(peerIdentityHash, appNetworkType) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (statusIsKnown == true && identityIsFunded == false){ // Identity is known to be expired description1 := getBoldLabelCentered("This user's identity is expired.") description2 := getLabelCentered("Their profile is expired from the network.") description3 := getLabelCentered("You can try to update this status to see if the user's identity is funded.") updateButton := getWidgetCentered(widget.NewButtonWithIcon("Update", theme.ViewRefreshIcon(), func(){ //TODO: // Use manualDownloads to update the user identity's funded status showUnderConstructionDialog(window) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, updateButton) setPageContent(page, window) return } // We will commence profile download without any prompt. downloadProfileFunction() return } // We know their profile exists, but it is not viewable // For host/moderator profiles, this must mean that the profile/identity is banned // For mate profiles, it could also mean that we have not downloaded the profile's sticky viewable status getAnyNewestProfileAttributeFunction, err := calculatedAttributes.GetRetrieveAnyProfileAttributeIncludingCalculatedFunction(newestProfileVersion, newestProfileRawMap) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } viewNewestProfileFunction := func(){setViewUserProfilePage(window, false, newestProfileHash, 0, getAnyNewestProfileAttributeFunction, previousPage)} _, newestProfileIsDisabled, err := readProfiles.ReadProfileHashMetadata(newestProfileHash) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (newestProfileIsDisabled == true){ // Their newest profile is disabled. // The user must be banned. // We can show it to the user. viewNewestProfileFunction() return } if (identityViewableStatusIsKnown == true && identityIsViewableStatus == false){ description1 := getBoldLabelCentered("This user is banned.") description2 := getLabelCentered("Do you want to view their profile anyway?") viewProfileButton := getWidgetCentered(widget.NewButtonWithIcon("View Profile", theme.VisibilityIcon(), viewNewestProfileFunction)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, viewProfileButton) setPageContent(page, window) return } if (userIdentityType == "Host" || userIdentityType == "Moderator"){ // Host/moderator profiles are not considered unviewable until they are banned, or their author is banned. // An unreviewed host/moderator profile is considered viewable. // We already checked to see if the user's identity is banned // Therefore, we know that this profile is banned. description1 := getBoldLabelCentered("This user's profile is banned.") description2 := getLabelCentered("Do you want to view their profile anyway?") viewProfileButton := getWidgetCentered(widget.NewButtonWithIcon("View Profile", theme.VisibilityIcon(), viewNewestProfileFunction)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, viewProfileButton) setPageContent(page, window) return } // userIdentityType == "Mate" // We know the user's identity is either viewable or unknown // We check to see if mate profile is viewable //Outputs: // -bool: Profile metadata found // -bool: Viewable Status is known // -bool: Profile is viewable // -error getNewestProfileIsViewableStatus := func()(bool, bool, bool, error){ profileIsDisabled, profileMetadataIsKnown, profileNetworkType, profileIdentityHash, downloadingRequiredReviews, networkParametersExist, stickyConsensusEstablished, profileIsViewableStatus, err := verifiedStickyStatus.GetVerifiedProfileIsViewableStickyStatus(newestProfileHash) if (err != nil) { return false, false, false, err } if (profileIsDisabled == true){ return false, false, false, errors.New("GetVerifiedProfileIsViewableStickyStatus returning different profileIsDisabled status than ReadProfileHashMetadata.") } if (profileMetadataIsKnown == false){ // Profile metadata must have been deleted after earlier check. return false, false, false, nil } if (profileNetworkType != appNetworkType){ return false, false, false, errors.New("GetVerifiedProfileIsViewableStickyStatus returning different networkType than requested from GetNewestUserProfile.") } if (profileIdentityHash != peerIdentityHash){ return false, false, false, errors.New("GetVerifiedProfileIsViewableStickyStatus returning different profileIdentityHash") } if (downloadingRequiredReviews == true && networkParametersExist == true && stickyConsensusEstablished == true){ return true, true, profileIsViewableStatus, nil } return true, false, false, nil } newestProfileMetadataFound, profileViewableStatusIsKnown, profileIsViewableStatus, err := getNewestProfileIsViewableStatus() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (newestProfileMetadataFound == false){ // This should only happen if the profile metadata was deleted after our earlier check // We show a loading screen so this issue can be identified if it is because of an error setLoadingScreen(window, "Loading Profile", "Looking for profile...") time.Sleep(time.Second) currentPage() return } if (profileViewableStatusIsKnown == true && profileIsViewableStatus == false){ description1 := getBoldLabelCentered("This user's profile is not viewable.") description2 := getLabelCentered("It may be banned, or not yet approved.") description3 := getLabelCentered("Do you still want to view it?") viewProfileButton := getWidgetCentered(widget.NewButtonWithIcon("View Profile", theme.VisibilityIcon(), viewNewestProfileFunction)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, viewProfileButton) setPageContent(page, window) return } // We know the profile is not considered viewable // We know the identity is not banned, or its status is unknown // We know the profile is not known to be unviewable // Therefore, the profile viewable status must be unknown description1 := getBoldLabelCentered("This user's profile has an unknown moderation status.") description2 := getLabelCentered("Do you still want to view their profile?") description3 := getLabelCentered("If not, you must wait for Seekia to download the profile's moderation status.") description4 := getLabelCentered("After the download, you will know if the profile is banned or viewable.") description5 := getLabelCentered("This will happen automatically in the background.") description6 := getLabelCentered("Refresh the page to check if the status is downloaded.") refreshButton := getWidgetCentered(widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), currentPage)) viewProfileButton := widget.NewButtonWithIcon("View Profile", theme.VisibilityIcon(), viewNewestProfileFunction) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, viewProfileButton, widget.NewSeparator(), description3, description4, description5, description6, refreshButton) setPageContent(page, window) } func setViewUserProfilePage(window fyne.Window, profileIsMine bool, profileHash [28]byte, currentImageIndex int, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()) { pageIdentifier, err := helpers.GetNewRandomHexString(16) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } appMemory.SetMemoryEntry("CurrentViewedPage", pageIdentifier) getTitleText := func()string{ if (profileIsMine == true){ return "Viewing My Profile" } return "Viewing Profile" } titleText := getTitleText() setLoadingScreen(window, titleText, "Loading Profile...") currentPage := func(){setViewUserProfilePage(window, profileIsMine, profileHash, currentImageIndex, getAnyUserProfileAttributeFunction, previousPage)} userIdentityHashExists, _, userIdentityHashString, err := getAnyUserProfileAttributeFunction("IdentityHash") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (userIdentityHashExists == false) { setErrorEncounteredPage(window, errors.New("Failed to get user profile IdentityHash"), previousPage) return } userIdentityHash, userProfileType, err := identity.ReadIdentityHashString(userIdentityHashString) if (err != nil){ setErrorEncounteredPage(window, errors.New("getAnyUserProfileAttributeFunction returning invalid IdentityHash: " + userIdentityHashString), previousPage) return } title := getPageTitleCentered(titleText) backButton := getBackButtonCentered(previousPage) getProfileIsDisabledStatus := func()(bool, error){ exists, _, disabledAttribute, err := getAnyUserProfileAttributeFunction("Disabled") if (err != nil) { return false, err } if (exists == true && disabledAttribute == "Yes") { return true, nil } return false, nil } userIsDisabled, err := getProfileIsDisabledStatus() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (userIsDisabled == true){ description1 := getBoldLabelCentered("User Is Disabled.") getDescription2Text := func()string{ if (profileIsMine == true){ result := translate("You have disabled your profile.") return result } result := translate("This user has disabled their profile.") return result } description2Text := getDescription2Text() description2 := getLabelCentered(description2Text) theirIdentityHashLabel := getItalicLabelCentered("User Identity Hash:") theirIdentityHashText := getBoldLabelCentered(userIdentityHashString) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, widget.NewSeparator(), theirIdentityHashLabel, theirIdentityHashText) if (profileIsMine == false){ page.Add(widget.NewSeparator()) actionsButton := getWidgetCentered(widget.NewButtonWithIcon("Actions", theme.ContentRedoIcon(), func(){ setViewPeerActionsPage(window, userIdentityHash, currentPage) })) page.Add(actionsButton) } setPageContent(page, window) return } getProfileContainer := func()(*fyne.Container, error){ chatButton := widget.NewButtonWithIcon("Chat", theme.MailComposeIcon(), func(){ if (profileIsMine == true){ dialogTitle := translate("Chat Inaccessible") dialogMessageA := getLabelCentered(translate("You are viewing your own profile.")) dialogMessageB := getLabelCentered(translate("You cannot chat with yourself.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } setViewAConversationPage(window, userIdentityHash, true, currentPage) }) actionsButton := widget.NewButtonWithIcon("Actions", theme.ContentRedoIcon(), func(){ if (profileIsMine == true){ dialogTitle := translate("Peer Actions Inaccessible") dialogMessageA := getLabelCentered(translate("You are viewing your own profile.")) dialogMessageB := getLabelCentered(translate("You cannot access the peer actions page for your own identity.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } setViewPeerActionsPage(window, userIdentityHash, currentPage) }) topRowLeftButtonsColumn := container.NewGridWithColumns(1, chatButton, actionsButton) getPhotosColumnWithNavigationButtons := func()(*fyne.Container, error){ imageObjectsList := make([]image.Image, 0) if (userProfileType == "Mate"){ exists, _, webpPhotosAttribute, err := getAnyUserProfileAttributeFunction("Photos") if (err != nil) { return nil, err } if (exists == true){ webpPhotosList := strings.Split(webpPhotosAttribute, "+") for _, base64Image := range webpPhotosList{ imageObject, err := imagery.ConvertWEBPBase64StringToCroppedDownsizedImageObject(base64Image) if (err != nil) { return nil, err } imageObjectsList = append(imageObjectsList, imageObject) } } } getAvatarEmojiIdentifier := func()(int, error){ exists, _, emojiIdentifier, err := getAnyUserProfileAttributeFunction("Avatar") if (err != nil){ return 0, err } if (exists == false){ // This is the avatar for users who have not selected an avatar return 2929, nil } emojiIdentifierInt, err := helpers.ConvertStringToInt(emojiIdentifier) if (err != nil) { return 0, errors.New("User profile is malformed: Invalid avatar: " + emojiIdentifier) } return emojiIdentifierInt, nil } avatarEmojiIdentifier, err := getAvatarEmojiIdentifier() if (err != nil) { return nil, err } avatarImage, err := getEmojiImageObject(avatarEmojiIdentifier) if (err != nil){ return nil, err } imageObjectsList = append(imageObjectsList, avatarImage) numberOfImages := len(imageObjectsList) getImageIndex := func()int{ if (currentImageIndex <= 0){ return 0 } finalIndex := numberOfImages-1 if (currentImageIndex >= finalIndex){ return finalIndex } return currentImageIndex } imageIndex := getImageIndex() currentImage := imageObjectsList[imageIndex] currentFyneImage := canvas.NewImageFromImage(currentImage) currentFyneImage.FillMode = canvas.ImageFillContain currentFyneImage.SetMinSize(getCustomFyneSize(30)) getNavigationWithZoomRow := func()*fyne.Container{ zoomButton := widget.NewButtonWithIcon("", theme.ZoomInIcon(), func(){ setViewFullpageImagesWithNavigationPage(window, imageObjectsList, imageIndex, currentPage) }) if (numberOfImages == 1){ zoomButtonRow := getWidgetCentered(zoomButton) return zoomButtonRow } getBackButton := func()fyne.Widget{ if (imageIndex <= 0) { button := widget.NewButton("", nil) return button } button := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func(){ newImageIndex := imageIndex-1 setViewUserProfilePage(window, profileIsMine, profileHash, newImageIndex, getAnyUserProfileAttributeFunction, previousPage) }) return button } getNextButton := func()fyne.Widget{ if (imageIndex >= numberOfImages-1) { button := widget.NewButton("", nil) return button } newImageIndex := imageIndex+1 button := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func(){ setViewUserProfilePage(window, profileIsMine, profileHash, newImageIndex, getAnyUserProfileAttributeFunction, previousPage) }) return button } backButton := getBackButton() nextButton := getNextButton() navigationWithZoomRow := getContainerCentered(container.NewGridWithRows(1, backButton, zoomButton, nextButton)) return navigationWithZoomRow } navigationWithZoomRow := getNavigationWithZoomRow() currentFyneImageBoxed := container.NewHBox(layout.NewSpacer(), currentFyneImage, layout.NewSpacer()) imagesColumn := container.NewVBox(currentFyneImageBoxed, navigationWithZoomRow) return imagesColumn, nil } photosColumnWithNavButtons, err := getPhotosColumnWithNavigationButtons() if (err != nil) { return nil, err } profileDetailsButton := widget.NewButtonWithIcon("Details", theme.InfoIcon(), func(){ setViewUserProfilePage_ProfileDetails(window, profileHash, getAnyUserProfileAttributeFunction, currentPage) }) reportProfileButton := widget.NewButtonWithIcon("Report", theme.WarningIcon(), func(){ if (profileIsMine == true){ dialogTitle := translate("Reporting Inaccessible") dialogMessageA := getLabelCentered(translate("You are viewing your own profile.")) dialogMessageB := getLabelCentered(translate("You cannot report your own profile.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } //TODO showUnderConstructionDialog(window) }) topRowRightButtonsColumn := container.NewGridWithColumns(1, profileDetailsButton, reportProfileButton) topRow := container.NewHBox(layout.NewSpacer(), topRowLeftButtonsColumn, widget.NewSeparator(), photosColumnWithNavButtons, widget.NewSeparator(), topRowRightButtonsColumn, layout.NewSpacer()) identityHashTitle := widget.NewLabel("Identity Hash:") identityHashLabel := getBoldLabel(userIdentityHashString) viewIdentityHashButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ setViewIdentityHashPage(window, userIdentityHash, currentPage) }) identityHashRow := container.NewHBox(layout.NewSpacer(), identityHashTitle, identityHashLabel, viewIdentityHashButton, layout.NewSpacer()) profileContentContainer := container.NewVBox(topRow, widget.NewSeparator(), identityHashRow) usernameTitle := widget.NewLabel("Username:") getUsernameLabel := func()(fyne.Widget, error){ exists, _, usernameString, err := getAnyUserProfileAttributeFunction("Username") if (err != nil) { return nil, err } if (exists == false){ anonymousLabel := getBoldItalicLabel(translate("Anonymous")) return anonymousLabel, nil } usernameLabel := getBoldLabel(usernameString) return usernameLabel, nil } usernameLabel, err := getUsernameLabel() if (err != nil) { return nil, err } usernameRow := container.NewHBox(layout.NewSpacer(), usernameTitle, usernameLabel, layout.NewSpacer()) profileContentContainer.Add(usernameRow) if (profileIsMine == false && userProfileType == "Mate"){ exists, _, matchScore, err := getAnyUserProfileAttributeFunction("MatchScore") if (err != nil) { return nil, err } if (exists == false){ return nil, errors.New("Unable to retrieve mate user match score.") } matchScoreTitle := widget.NewLabel("Match Score:") matchScoreLabel := getBoldLabel(matchScore) viewMatchScoreDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ // This page describes which desires a user fulfills setViewUserMatchScoreBreakdownPage(window, userIdentityHash, currentPage) }) matchScoreRow := container.NewHBox(layout.NewSpacer(), matchScoreTitle, matchScoreLabel, viewMatchScoreDetailsButton, layout.NewSpacer()) profileContentContainer.Add(matchScoreRow) distanceTranslated, _, formatDistanceFunction, distanceUnits, unavailableDistanceText, err := attributeDisplay.GetProfileAttributeDisplayInfo("Distance") if (err != nil) { return nil, err } distanceTitle := getLabelCentered(distanceTranslated + ":") getDistanceLabel := func()(fyne.Widget, error){ exists, _, distanceKilometersString, err := getAnyUserProfileAttributeFunction("Distance") if (err != nil) { return nil, err } if (exists == false){ distanceLabel := getBoldItalicLabel(unavailableDistanceText) return distanceLabel, nil } distanceFormatted, err := formatDistanceFunction(distanceKilometersString) if (err != nil) { return nil, err } distanceLabel := getBoldLabel(distanceFormatted + distanceUnits) return distanceLabel, nil } distanceLabel, err := getDistanceLabel() if (err != nil) { return nil, err } distanceRow := container.NewHBox(layout.NewSpacer(), distanceTitle, distanceLabel, layout.NewSpacer()) profileContentContainer.Add(distanceRow) } if (userProfileType == "Moderator"){ appNetworkType, err := getAppNetworkType.GetAppNetworkType() if (err != nil) { return nil, err } rankingKnown, moderatorRank, totalNumberOfModerators, err := moderatorRanking.GetModeratorRanking(userIdentityHash, appNetworkType) if (err != nil) { return nil, err } getModeratorRankText := func()string{ if (rankingKnown == false){ result := translate("Unknown") return result } moderatorRankString := helpers.ConvertIntToString(moderatorRank) totalNumberOfModeratorsString := helpers.ConvertIntToString(totalNumberOfModerators) moderatorRankText := moderatorRankString + "/" + totalNumberOfModeratorsString return moderatorRankText } moderatorRankText := getModeratorRankText() moderatorRankTitle := widget.NewLabel(translate("Moderator Rank") + ":") moderatorRankLabel := getBoldLabel(moderatorRankText) moderatorRankRow := container.NewHBox(layout.NewSpacer(), moderatorRankTitle, moderatorRankLabel, layout.NewSpacer()) profileContentContainer.Add(moderatorRankRow) } if (userProfileType == "Mate"){ ageTitle := widget.NewLabel("Age:") getAgeLabel := func()(fyne.Widget, error){ exists, _, ageString, err := getAnyUserProfileAttributeFunction("Age") if (err != nil) { return nil, err } if (exists == false){ noResponseLabel := getBoldItalicLabel(translate("No Response")) return noResponseLabel, nil } ageLabel := getBoldLabel(ageString) return ageLabel, nil } ageLabel, err := getAgeLabel() if (err != nil) { return nil, err } ageRow := container.NewHBox(layout.NewSpacer(), ageTitle, ageLabel, layout.NewSpacer()) profileContentContainer.Add(ageRow) } getProfileCategorySelectButtons := func()(*fyne.Container, error){ generalIcon, err := getFyneImageIcon("General") if (err != nil) { return nil, err } generalButton := widget.NewButton("General", func(){ setViewUserProfilePage_Category(window, profileIsMine, "General", getAnyUserProfileAttributeFunction, currentPage) }) generalButtonWithIcon := container.NewGridWithRows(2, generalIcon, generalButton) if (userProfileType == "Host" || userProfileType == "Moderator"){ categorySelectButtonsRow := getContainerCentered(generalButtonWithIcon) return categorySelectButtonsRow, nil } physicalIcon, err := getFyneImageIcon("Person") if (err != nil) { return nil, err } physicalButton := widget.NewButton("Physical", func(){ setViewUserProfilePage_Category(window, profileIsMine, "Physical", getAnyUserProfileAttributeFunction, currentPage) }) physicalButtonWithIcon := container.NewGridWithRows(2, physicalIcon, physicalButton) lifestyleIcon, err := getFyneImageIcon("Lifestyle") if (err != nil) { return nil, err } lifestyleButton := widget.NewButton("Lifestyle", func(){ setViewUserProfilePage_Category(window, profileIsMine, "Lifestyle", getAnyUserProfileAttributeFunction, currentPage) }) lifestyleButtonWithIcon := container.NewGridWithRows(2, lifestyleIcon, lifestyleButton) mentalIcon, err := getFyneImageIcon("Mental") if (err != nil) { return nil, err } mentalButton := widget.NewButton("Mental", func(){ setViewUserProfilePage_Category(window, profileIsMine, "Mental", getAnyUserProfileAttributeFunction, currentPage) }) mentalButtonWithIcon := container.NewGridWithRows(2, mentalIcon, mentalButton) categoriesRow := getContainerCentered(container.NewGridWithRows(1, generalButtonWithIcon, physicalButtonWithIcon, lifestyleButtonWithIcon, mentalButtonWithIcon)) return categoriesRow, nil } selectProfileCategoryButtonsRow, err := getProfileCategorySelectButtons() if (err != nil) { return nil, err } profileContentContainer.Add(widget.NewSeparator()) profileContentContainer.Add(selectProfileCategoryButtonsRow) profileContentContainer.Add(widget.NewSeparator()) return profileContentContainer, nil } profileContent, err := getProfileContainer() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), profileContent) setPageContent(page, window) } func setViewUserProfilePage_ProfileDetails(window fyne.Window, profileHash [28]byte, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()) { currentPage := func(){setViewUserProfilePage_ProfileDetails(window, profileHash, getAnyUserProfileAttributeFunction, previousPage)} title := getPageTitleCentered(translate("View Profile Details")) backButton := getBackButtonCentered(previousPage) getPageContent := func()(*fyne.Container, error){ profileHashLabel := widget.NewLabel("Profile Hash:") profileHashHex := encoding.EncodeBytesToHexString(profileHash[:]) profileHashTrimmed, _, err := helpers.TrimAndFlattenString(profileHashHex, 20) if (err != nil){ return nil, err } profileHashTrimmedLabel := getBoldLabel(profileHashTrimmed) viewProfileHashButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ setViewContentHashPage(window, "Profile", profileHash[:], currentPage) }) profileHashRow := container.NewHBox(layout.NewSpacer(), profileHashLabel, profileHashTrimmedLabel, viewProfileHashButton, layout.NewSpacer()) exists, _, profileCreationTimeString, err := getAnyUserProfileAttributeFunction("CreationTime") if (err != nil){ return nil, err } if (exists == false){ return nil, errors.New("setViewUserProfilePage_ProfileDetails called with profile missing CreationTime") } profileCreationTime, err := helpers.ConvertCreationTimeStringToInt64(profileCreationTimeString) if (err != nil){ return nil, errors.New("setViewUserProfilePage_ProfileDetails called with profile with invalid CreationTime: " + profileCreationTimeString) } creationTimeLabel := widget.NewLabel("Creation Time:") creationTimeString := helpers.ConvertUnixTimeToTranslatedTime(profileCreationTime) creationTimeAgoString, err := helpers.ConvertUnixTimeToTimeFromNowTranslated(profileCreationTime, true) if (err != nil) { return nil, err } creationTimeText := getBoldLabel(creationTimeString + " (" + creationTimeAgoString + ")") creationTimeWarningButton := widget.NewButtonWithIcon("", theme.WarningIcon(), func(){ title := translate("Creation Time Warning") dialogMessageA := getLabelCentered("Profile creation times are not verified.") dialogMessageB := getLabelCentered("They can be faked by the profile author.") dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) }) creationTimeRow := container.NewHBox(layout.NewSpacer(), creationTimeLabel, creationTimeText, creationTimeWarningButton, layout.NewSpacer()) exists, profileVersion, profileType, err := getAnyUserProfileAttributeFunction("ProfileType") if (err != nil) { return nil, err } if (exists == false){ return nil, errors.New("getAnyUserProfileAttributeFunction called and ProfileType not found") } profileTypeLabel := widget.NewLabel("Profile Type:") profileTypeText := getBoldLabel(profileType) profileTypeRow := container.NewHBox(layout.NewSpacer(), profileTypeLabel, profileTypeText, layout.NewSpacer()) profileVersionLabel := widget.NewLabel("Profile Version:") profileVersionString := helpers.ConvertIntToString(profileVersion) profileVersionText := getBoldLabel(profileVersionString) profileVersionRow := container.NewHBox(layout.NewSpacer(), profileVersionLabel, profileVersionText, layout.NewSpacer()) //TODO: Add moderation details (if profile/identity is banned) //TODO: Show when identity/profile will expire, and allow user to send funds to increase the user's identity balance/score //TODO: Save raw profile button (will save it as a txt file) pageContent := container.NewVBox(profileHashRow, widget.NewSeparator(), creationTimeRow, widget.NewSeparator(), profileTypeRow, widget.NewSeparator(), profileVersionRow) return pageContent, nil } pageContent, err := getPageContent() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), pageContent) setPageContent(page, window) } func setViewUserProfilePage_Category(window fyne.Window, profileIsMine bool, categoryName string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()) { if (categoryName != "General" && categoryName != "Physical" && categoryName != "Lifestyle" && categoryName != "Mental"){ setErrorEncounteredPage(window, errors.New("setViewUserProfilePage_Category called with invalid profile category: " + categoryName), previousPage) return } currentPage := func(){setViewUserProfilePage_Category(window, profileIsMine, categoryName, getAnyUserProfileAttributeFunction, previousPage)} title := getPageTitleCentered("View Profile - " + categoryName) backButton := getBackButtonCentered(previousPage) page := container.NewVBox(title, backButton, widget.NewSeparator()) addAttributeRow := func(attributeName string)error{ attributeTitle, _, formatAttributeValue, attributeUnits, missingValueText, err := attributeDisplay.GetProfileAttributeDisplayInfo(attributeName) if (err != nil) { return err } attributeTitleLabel := widget.NewLabel(attributeTitle + ":") exists, _, attributeValue, err := getAnyUserProfileAttributeFunction(attributeName) if (err != nil) { return err } if (exists == false) { attributeValueLabel := getBoldItalicLabel(missingValueText) attributeRow := container.NewHBox(layout.NewSpacer(), attributeTitleLabel, attributeValueLabel, layout.NewSpacer()) page.Add(attributeRow) return nil } attributeValueFormatted, err := formatAttributeValue(attributeValue) if (err != nil) { return err } attributeValueFormattedWithUnits := attributeValueFormatted + attributeUnits attributeValueTrimmed, anyChangesOccurred, err := helpers.TrimAndFlattenString(attributeValueFormattedWithUnits, 25) if (err != nil) { return err } if (anyChangesOccurred == false){ attributeValueLabel := getBoldLabel(attributeValueFormattedWithUnits) attributeRow := container.NewHBox(layout.NewSpacer(), attributeTitleLabel, attributeValueLabel, layout.NewSpacer()) page.Add(attributeRow) return nil } attributeValueTrimmedLabel := getBoldLabel(attributeValueTrimmed) viewAttributeValueButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ setViewTextPage(window, "Viewing " + attributeTitle, attributeValueFormattedWithUnits, false, currentPage) }) attributeValueRow := container.NewHBox(layout.NewSpacer(), attributeTitleLabel, attributeValueTrimmedLabel, viewAttributeValueButton, layout.NewSpacer()) page.Add(attributeValueRow) return nil } addAttributesFunction := func()error{ if (categoryName == "General"){ exists, _, userProfileType, err := getAnyUserProfileAttributeFunction("ProfileType") if (err != nil) { return err } if (exists == false){ return errors.New("getAnyUserProfileAttributeFunction not able to find ProfileType") } if (userProfileType == "Mate"){ locationButton := widget.NewButton("Location", func(){ setViewMateProfilePage_Location(window, getAnyUserProfileAttributeFunction, currentPage) }) questionnaireButton := widget.NewButton("Questionnaire", func(){ setViewMateProfilePage_Questionnaire(window, profileIsMine, getAnyUserProfileAttributeFunction, currentPage) }) tagsButton := widget.NewButton("Tags", func(){ setViewMateProfilePage_Tags(window, getAnyUserProfileAttributeFunction, currentPage) }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, locationButton, questionnaireButton, tagsButton)) page.Add(buttonsGrid) page.Add(widget.NewSeparator()) } err = addAttributeRow("Description") if (err != nil) { return err } err = addAttributeRow("Username") if (err != nil) { return err } if (userProfileType == "Mate"){ err = addAttributeRow("Sexuality") if (err != nil) { return err } } return nil } if (categoryName == "Physical"){ racialSimilarityButton := widget.NewButton("Racial Similarity", func(){ setViewMateProfilePage_RacialSimilarity(window, getAnyUserProfileAttributeFunction, currentPage) }) ancestryCompositionButton := widget.NewButton("Ancestry Composition", func(){ setViewMateProfilePage_AncestryComposition(window, "User", getAnyUserProfileAttributeFunction, currentPage) }) haplogroupsButton := widget.NewButton("Haplogroups", func(){ setViewMateProfilePage_Haplogroups(window, getAnyUserProfileAttributeFunction, currentPage) }) neanderthalVariantsButton := widget.NewButton("Neanderthal Variants", func(){ setViewMateProfilePage_NeanderthalVariants(window, getAnyUserProfileAttributeFunction, currentPage) }) totalDiseaseRiskButton := widget.NewButton("Total Disease Risk", func(){ setViewMateProfilePage_TotalDiseaseRisk(window, getAnyUserProfileAttributeFunction, currentPage) }) monogenicDiseasesButton := widget.NewButton("Monogenic Diseases", func(){ setViewMateProfilePage_MonogenicDiseases(window, "Offspring", getAnyUserProfileAttributeFunction, currentPage) }) polygenicDiseasesButton := widget.NewButton("Polygenic Diseases", func(){ setViewMateProfilePage_PolygenicDiseases(window, "Offspring", getAnyUserProfileAttributeFunction, currentPage) }) geneticTraitsButton := widget.NewButton("Genetic Traits", func(){ setViewMateProfilePage_GeneticTraits(window, getAnyUserProfileAttributeFunction, currentPage) }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(2, racialSimilarityButton, totalDiseaseRiskButton, ancestryCompositionButton, monogenicDiseasesButton, haplogroupsButton, polygenicDiseasesButton, neanderthalVariantsButton, geneticTraitsButton)) page.Add(buttonsGrid) page.Add(widget.NewSeparator()) err := addAttributeRow("Age") if (err != nil) { return err } err = addAttributeRow("Sex") if (err != nil) { return err } err = addAttributeRow("Height") if (err != nil) { return err } page.Add(widget.NewSeparator()) err = addAttributeRow("EyeColor") if (err != nil) { return err } err = addAttributeRow("SkinColor") if (err != nil) { return err } err = addAttributeRow("HairColor") if (err != nil) { return err } err = addAttributeRow("HairTexture") if (err != nil) { return err } page.Add(widget.NewSeparator()) err = addAttributeRow("BodyFat") if (err != nil) { return err } err = addAttributeRow("BodyMuscle") if (err != nil) { return err } page.Add(widget.NewSeparator()) err = addAttributeRow("HasHIV") if (err != nil) { return err } err = addAttributeRow("HasGenitalHerpes") if (err != nil) { return err } return nil } if (categoryName == "Lifestyle"){ dietButton := getWidgetCentered(widget.NewButton("Diet", func(){ setViewMateProfilePage_Diet(window, getAnyUserProfileAttributeFunction, currentPage) })) page.Add(dietButton) page.Add(widget.NewSeparator()) err := addAttributeRow("Hobbies") if (err != nil) { return err } err = addAttributeRow("WealthInGold") if (err != nil) { return err } err = addAttributeRow("Job") if (err != nil) { return err } err = addAttributeRow("Fame") if (err != nil) { return err } page.Add(widget.NewSeparator()) err = addAttributeRow("AlcoholFrequency") if (err != nil) { return err } err = addAttributeRow("TobaccoFrequency") if (err != nil) { return err } err = addAttributeRow("CannabisFrequency") if (err != nil) { return err } return nil } if (categoryName == "Mental"){ languageButton := getWidgetCentered(widget.NewButton("Language", func(){ setViewMateProfilePage_Language(window, getAnyUserProfileAttributeFunction, currentPage) })) page.Add(languageButton) page.Add(widget.NewSeparator()) err := addAttributeRow("Beliefs") if (err != nil) { return err } err = addAttributeRow("GenderIdentity") if (err != nil) { return err } page.Add(widget.NewSeparator()) err = addAttributeRow("PetsRating") if (err != nil) { return err } err = addAttributeRow("DogsRating") if (err != nil) { return err } err = addAttributeRow("CatsRating") if (err != nil) { return err } return nil } return errors.New("setViewUserProfilePage_Category called with invalid profile category: " + categoryName) } err := addAttributesFunction() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page.Add(widget.NewSeparator()) setPageContent(page, window) } func setViewMateProfilePage_Location(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ setLoadingScreen(window, "View Profile - General", "Loading user location...") title := getPageTitleCentered("View Profile - General") backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Location")) //Outputs: // -bool: Location exists // -*fyne.Container // -error getLocationColumn := func(locationRank string)(bool, *fyne.Container, error){ if (locationRank != "Primary" && locationRank != "Secondary"){ return false, nil, errors.New("getLocationColumn called with invalid locationRank: " + locationRank) } exists, _, locationLatitudeString, err := getAnyUserProfileAttributeFunction(locationRank + "LocationLatitude") if (err != nil) { return false, nil, err } if (exists == false){ return false, nil, nil } exists, _, locationLongitudeString, err := getAnyUserProfileAttributeFunction(locationRank + "LocationLongitude") if (err != nil) { return false, nil, err } if (exists == false){ return false, nil, errors.New("setViewMateProfilePage_Location called with profile containing " + locationRank + "LocationLatitude but missing " + locationRank + "LocationLongitude") } locationLatitudeFloat64, err := helpers.ConvertStringToFloat64(locationLatitudeString) if (err != nil){ return false, nil, errors.New("setViewMateProfilePage_Location called with profile containing invalid LocationLatitude: " + locationLatitudeString) } locationLongitudeFloat64, err := helpers.ConvertStringToFloat64(locationLongitudeString) if (err != nil){ return false, nil, errors.New("setViewMateProfilePage_Location called with profile containing invalid LocationLongitude: " + locationLongitudeString) } locationRankLabel := getBoldLabelCentered(locationRank) locationCountryExists, _, locationCountryIdentifier, err := getAnyUserProfileAttributeFunction(locationRank + "LocationCountry") if (err != nil) { return false, nil, err } getCountryTextLabel := func()(fyne.Widget, error){ if (locationCountryExists == false){ noneLabel := getBoldItalicLabel(translate("None")) return noneLabel, nil } locationCountryIdentifierInt, err := helpers.ConvertStringToInt(locationCountryIdentifier) if (err != nil) { return nil, err } countryObject, err := worldLocations.GetCountryObjectFromCountryIdentifier(locationCountryIdentifierInt) if (err != nil){ return nil, err } countryNamesList := countryObject.NamesList countryDescription := helpers.TranslateAndJoinStringListItems(countryNamesList, "/") countryTextLabel := getBoldLabel(translate(countryDescription)) return countryTextLabel, nil } countryTextLabel, err := getCountryTextLabel() if (err != nil) { return false, nil, err } countryLabel := getLabelCentered("Country:") coordinatesLabel := getLabelCentered("Coordinates:") locationCoordinatesLabel := getBoldLabel(locationLatitudeString + "°, " + locationLongitudeString + "°") cityLabel := getLabelCentered("City:") getCityContent := func()(*fyne.Container, error){ // We will either find the exact city or the closest city cityName, cityState, cityCountryIdentifier, cityDistanceKilometers, err := worldLocations.GetClosestCityFromCoordinates(locationLatitudeFloat64, locationLongitudeFloat64) if (err != nil) { return nil, err } if (cityDistanceKilometers == 0){ locationCityFormatted := cityName + ", " + cityState locationCityLabel := getBoldLabelCentered(locationCityFormatted) return locationCityLabel, nil } currentUnitsExist, currentUnits, err := globalSettings.GetSetting("MetricOrImperial") if (err != nil){ return nil, err } getNearbyCityDistanceFormattedString := func()(string, error){ if (currentUnitsExist == true && currentUnits == "Imperial"){ distanceMiles, err := helpers.ConvertKilometersToMiles(cityDistanceKilometers) if (err != nil) { return "", err } distanceMilesString := helpers.ConvertFloat64ToStringRounded(distanceMiles, 1) result := distanceMilesString + " miles" return result, nil } distanceKilometersString := helpers.ConvertFloat64ToStringRounded(cityDistanceKilometers, 1) result := distanceKilometersString + " kilometers" return result, nil } nearbyCityDistanceFormattedString, err := getNearbyCityDistanceFormattedString() if (err != nil) { return nil, err } getNearbyCityNameFormatted := func()(string, error){ if (locationCountryExists == true){ locationCountryIdentifierInt, err := helpers.ConvertStringToInt(locationCountryIdentifier) if (err != nil) { return "", err } if (cityCountryIdentifier == locationCountryIdentifierInt){ result := cityName + ", " + cityState return result, nil } } countryObject, err := worldLocations.GetCountryObjectFromCountryIdentifier(cityCountryIdentifier) if (err != nil){ return "", err } countryNamesList := countryObject.NamesList countryDescription := helpers.TranslateAndJoinStringListItems(countryNamesList, "/") result := cityName + ", " + cityState + ", " + countryDescription return result, nil } nearbyCityNameFormatted, err := getNearbyCityNameFormatted() if (err != nil) { return nil, err } nearbyCityNameFormattedAndTrimmed, _, err := helpers.TrimAndFlattenString(nearbyCityNameFormatted, 40) if (err != nil) { return nil, err } locationDistanceLabel := getBoldLabelCentered(nearbyCityDistanceFormattedString + " from") locationCityNameLabel := getBoldLabelCentered(nearbyCityNameFormattedAndTrimmed) cityInfoContainer := container.NewVBox(locationDistanceLabel, locationCityNameLabel) return cityInfoContainer, nil } cityContent, err := getCityContent() if (err != nil){ return false, nil, err } locationColumn := container.NewVBox(locationRankLabel, widget.NewSeparator(), countryLabel, countryTextLabel, widget.NewSeparator(), coordinatesLabel, locationCoordinatesLabel, widget.NewSeparator(), cityLabel, cityContent) return true, locationColumn, nil } primaryLocationExists, primaryLocationColumn, err := getLocationColumn("Primary") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (primaryLocationExists == false){ noLocationsLabel := getBoldLabelCentered("This user has no locations.") page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), noLocationsLabel) setPageContent(page, window) return } locationColumnsGrid := container.NewGridWithRows(1, primaryLocationColumn) secondaryLocationExists, secondaryLocationColumn, err := getLocationColumn("Secondary") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (secondaryLocationExists == true){ locationColumnsGrid.Add(secondaryLocationColumn) } locationColumnsGridCentered := getContainerCentered(locationColumnsGrid) getDescriptionText := func()string{ if (secondaryLocationExists == false){ result := translate("Below is the user's location.") return result } result := translate("Below are the user's locations.") return result } descriptionText := getDescriptionText() description := getLabelCentered(descriptionText) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator(), locationColumnsGridCentered) setPageContent(page, window) } func setViewMateProfilePage_Questionnaire(window fyne.Window, profileIsMine bool, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ currentPage := func(){setViewMateProfilePage_Questionnaire(window, profileIsMine, getAnyUserProfileAttributeFunction, previousPage)} title := getPageTitleCentered(translate("View Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Questionnaire")) questionnaireExists, _, questionnaireString, err := getAnyUserProfileAttributeFunction("Questionnaire") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (questionnaireExists == false){ description := getBoldLabelCentered("This user does not have a questionnaire.") page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description) setPageContent(page, window) return } description1 := getLabelCentered("This user has a questionnaire.") description2 := getLabelCentered("You can take their questionnaire and send them your responses.") questionnaireObject, err := mateQuestionnaire.ReadQuestionnaireString(questionnaireString) if (err != nil){ setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_Questionnaire called with profile containing invalid questionnaire."), previousPage) return } numberOfQuestions := len(questionnaireObject) numberOfQuestionsString := helpers.ConvertIntToString(numberOfQuestions) numberOfQuestionsLabel := widget.NewLabel("Number of questions:") numberOfQuestionsText := getBoldLabel(numberOfQuestionsString) numberOfQuestionsRow := container.NewHBox(layout.NewSpacer(), numberOfQuestionsLabel, numberOfQuestionsText, layout.NewSpacer()) exists, _, userIdentityHashString, err := getAnyUserProfileAttributeFunction("IdentityHash") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (exists == false){ setErrorEncounteredPage(window, errors.New("getAnyUserProfileAttributeFunction failed to get IdentityHash"), previousPage) return } userIdentityHash, _, err := identity.ReadIdentityHashString(userIdentityHashString) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } //TODO: Show if user has already taken the questionnaire submitQuestionnairePage := func(questionnaireResponse string, prevPage func()){ if (profileIsMine == true){ dialogTitle := translate("Cannot Submit Questionnaire") dialogMessageA := getLabelCentered(translate("You are viewing your own profile.")) dialogMessageB := getLabelCentered(translate("You cannot submit a response to your own questionnaire.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } setSubmitQuestionnairePage(window, userIdentityHash, questionnaireResponse, prevPage, currentPage) } takeQuestionnaireButton := getWidgetCentered(widget.NewButtonWithIcon("Take Questionnaire", theme.DocumentCreateIcon(), func(){ emptyMap := make(map[string]string) setTakeQuestionnairePage(window, questionnaireObject, 0, emptyMap, currentPage, submitQuestionnairePage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, numberOfQuestionsRow, takeQuestionnaireButton) setPageContent(page, window) } func setViewMateProfilePage_Tags(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ title := getPageTitleCentered(translate("View Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Tags")) anyTagsExist, _, tagsAttribute, err := getAnyUserProfileAttributeFunction("Tags") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (anyTagsExist == false){ description := getBoldLabelCentered("This user has no tags.") page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description) setPageContent(page, window) return } description := getLabelCentered("Below are the user's tags.") page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator()) tagsList := strings.Split(tagsAttribute, "+&") for _, tagName := range tagsList{ tagLabel := getBoldLabelCentered(tagName) page.Add(tagLabel) } setPageContent(page, window) } func setViewMateProfilePage_Haplogroups(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ title := getPageTitleCentered(translate("View Profile - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Haplogroups")) description1 := getLabelCentered(translate("Below are the user's haplogroups.")) description2 := getLabelCentered("These are reported by 23andMe. More companies will be added.") userMaternalHaplogroupExists, _, userMaternalHaplogroup_23andMe, err := getAnyUserProfileAttributeFunction("23andMe_MaternalHaplogroup") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } maternalHaplogroupLabel := widget.NewLabel("Maternal Haplogroup:") getMaternalHalogroupText := func()fyne.Widget{ if (userMaternalHaplogroupExists == false){ noResponseLabel := getBoldItalicLabel("No Response") return noResponseLabel } maternalHaplogroupLabel := getBoldLabel(userMaternalHaplogroup_23andMe) return maternalHaplogroupLabel } maternalHaplogroupText := getMaternalHalogroupText() userMaternalHaplogroupRow := container.NewHBox(layout.NewSpacer(), maternalHaplogroupLabel, maternalHaplogroupText, layout.NewSpacer()) userPaternalHaplogroupExists, _, userPaternalHaplogroup_23andMe, err := getAnyUserProfileAttributeFunction("23andMe_PaternalHaplogroup") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } paternalHaplogroupLabel := widget.NewLabel("Paternal Haplogroup:") getPaternalHalogroupText := func()fyne.Widget{ if (userPaternalHaplogroupExists == false){ noResponseLabel := getBoldItalicLabel("No Response") return noResponseLabel } paternalHaplogroupLabel := getBoldLabel(userPaternalHaplogroup_23andMe) return paternalHaplogroupLabel } paternalHaplogroupText := getPaternalHalogroupText() userPaternalHaplogroupRow := container.NewHBox(layout.NewSpacer(), paternalHaplogroupLabel, paternalHaplogroupText, layout.NewSpacer()) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), userMaternalHaplogroupRow, userPaternalHaplogroupRow) //Outputs: // -bool: Offspring maternal haplogroup is known // -string: Offspring maternal haplogroup // -bool: Offspring paternal haplogroup is known // -string: Offspring paternal haplogroup is known // -error getOffspringHaplogroups := func()(bool, string, bool, string, error){ isKnown, _, isSameSex, err := getAnyUserProfileAttributeFunction("IsSameSex") if (err != nil){ return false, "", false, "", err } if (isKnown == true && isSameSex == "Yes"){ return false, "", false, "", nil } mySexExists, mySex, err := myLocalProfiles.GetProfileData("Mate", "Sex") if (err != nil) { return false, "", false, "", err } userSexExists, _, userSex, err := getAnyUserProfileAttributeFunction("Sex") if (err != nil) { return false, "", false, "", err } if (mySexExists == false && userSexExists == false){ return false, "", false, "", nil } //Outputs: // -bool: Haplogroup is known // -string: Offspring maternal haplogroup // -error getOffspringMaternalHaplogroup := func()(bool, string, error){ if (mySexExists == true){ if (mySex == "Female" || mySex == "Intersex Female"){ exists, myMaternalHaplogroup, err := myLocalProfiles.GetProfileData("Mate", "23andMe_MaternalHaplogroup") if (err != nil){ return false, "", err } if (exists == false){ return false, "", nil } return true, myMaternalHaplogroup, nil } } if (userSexExists == true){ if (userSex == "Female" || userSex == "Intersex Female"){ if (userMaternalHaplogroupExists == true){ return true, userMaternalHaplogroup_23andMe, nil } } } return false, "", nil } offspringMaternalHaplogroupIsKnown, offspringMaternalHaplogroup, err := getOffspringMaternalHaplogroup() if (err != nil) { return false, "", false, "", err } //Outputs: // -bool: Haplogroup is known // -string: Offspring paternal haplogroup // -error getOffspringPaternalHaplogroup := func()(bool, string, error){ if (mySexExists == true){ if (mySex == "Male" || mySex == "Intersex Male"){ exists, myPaternalHaplogroup, err := myLocalProfiles.GetProfileData("Mate", "23andMe_PaternalHaplogroup") if (err != nil){ return false, "", err } if (exists == false){ return false, "", nil } return true, myPaternalHaplogroup, nil } } if (userSexExists == true){ if (userSex == "Male" || userSex == "Intersex Male"){ if (userPaternalHaplogroupExists == true){ return true, userPaternalHaplogroup_23andMe, nil } } } return false, "", nil } offspringPaternalHaplogroupIsKnown, offspringPaternalHaplogroup, err := getOffspringPaternalHaplogroup() if (err != nil) { return false, "", false, "", err } return offspringMaternalHaplogroupIsKnown, offspringMaternalHaplogroup, offspringPaternalHaplogroupIsKnown, offspringPaternalHaplogroup, nil } offspringMaternalHaplogroupIsKnown, offspringMaternalHaplogroup, offspringPaternalHaplogroupIsKnown, offspringPaternalHaplogroup, err := getOffspringHaplogroups() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (offspringMaternalHaplogroupIsKnown == true || offspringPaternalHaplogroupIsKnown == true){ page.Add(widget.NewSeparator()) offspringDescription := getLabelCentered("Below are the haplogroups of an offspring created from you and the user.") page.Add(offspringDescription) page.Add(widget.NewSeparator()) offspringMaternalHaplogroupLabel := widget.NewLabel("Offspring Maternal Haplogroup:") getOffspringMaternalHaplogroupText := func()fyne.Widget{ if (offspringMaternalHaplogroupIsKnown == false){ unknownLabel := getBoldItalicLabel("Unknown") return unknownLabel } haplogroupLabel := getBoldLabel(offspringMaternalHaplogroup) return haplogroupLabel } offspringMaternalHaplogroupText := getOffspringMaternalHaplogroupText() offspringMaternalHaplogroupRow := container.NewHBox(layout.NewSpacer(), offspringMaternalHaplogroupLabel, offspringMaternalHaplogroupText, layout.NewSpacer()) page.Add(offspringMaternalHaplogroupRow) offspringPaternalHaplogroupLabel := widget.NewLabel("Offspring Paternal Haplogroup:") getOffspringPaternalHaplogroupText := func()fyne.Widget{ if (offspringPaternalHaplogroupIsKnown == false){ unknownLabel := getBoldItalicLabel("Unknown") return unknownLabel } haplogroupLabel := getBoldLabel(offspringPaternalHaplogroup) return haplogroupLabel } offspringPaternalHaplogroupText := getOffspringPaternalHaplogroupText() offspringPaternalHaplogroupRow := container.NewHBox(layout.NewSpacer(), offspringPaternalHaplogroupLabel, offspringPaternalHaplogroupText, layout.NewSpacer()) page.Add(offspringPaternalHaplogroupRow) } setPageContent(page, window) } func setViewMateProfilePage_NeanderthalVariants(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ title := getPageTitleCentered(translate("View Profile - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Neanderthal Variants")) description1 := getLabelCentered("Below is the user's neanderthal variant count.") description2 := getLabelCentered("The information comes from 23andMe. More companies will be added.") userResponseExists, _, userNeanderthalVariants, err := getAnyUserProfileAttributeFunction("23andMe_NeanderthalVariants") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } getUserNeanderthalVariantsLabel := func()fyne.Widget{ if (userResponseExists == false){ noResponseLabel := getBoldItalicLabel(translate("No Response")) return noResponseLabel } userNeanderthalVariantsLabel := getBoldLabel(userNeanderthalVariants + " variants") return userNeanderthalVariantsLabel } userNeanderthalVariantsLabel := getUserNeanderthalVariantsLabel() neanderthalVariantsLabel := widget.NewLabel("Neanderthal Variants:") neanderthalVariantsRow := container.NewHBox(layout.NewSpacer(), neanderthalVariantsLabel, userNeanderthalVariantsLabel, layout.NewSpacer()) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), neanderthalVariantsRow) isSameSexIsKnown, _, isSameSex, err := getAnyUserProfileAttributeFunction("IsSameSex") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (isSameSexIsKnown == true && isSameSex == "Yes"){ setPageContent(page, window) return } offspringNeanderthalVariantsKnown, _, offspringNeanderthalVariants, err := getAnyUserProfileAttributeFunction("23andMe_OffspringNeanderthalVariants") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (offspringNeanderthalVariantsKnown == false){ setPageContent(page, window) return } page.Add(widget.NewSeparator()) offspringDescription := getLabelCentered("Below is the estimated neanderthal variant count of an offspring created from you and this user.") page.Add(offspringDescription) offspringNeanderthalVariantsLabel := widget.NewLabel("Offspring Neanderthal Variants:") offspringNeanderthalVariantsText := getBoldLabel(offspringNeanderthalVariants + " variants") offspringNeanderthalVariantsRow := container.NewHBox(layout.NewSpacer(), offspringNeanderthalVariantsLabel, offspringNeanderthalVariantsText, layout.NewSpacer()) page.Add(offspringNeanderthalVariantsRow) setPageContent(page, window) } func setViewMateProfilePage_RacialSimilarity(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ title := getPageTitleCentered(translate("View Profile - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Racial Similarity")) description := getLabelCentered("This page describes the racial similarity between you and the user.") //TODO: Add help pages to explain results racialSimilarityTitle := widget.NewLabel("Racial Similarity:") getRacialSimilarityText := func()(string, error){ similarityIsKnown, _, racialSimilarity, err := getAnyUserProfileAttributeFunction("RacialSimilarity") if (err != nil) { return "", err } if (similarityIsKnown == false){ result := translate("Unknown") return result, nil } return racialSimilarity, nil } racialSimilarityText, err := getRacialSimilarityText() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } racialSimilarityLabel := getBoldLabel(racialSimilarityText) racialSimilarityHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ //TODO showUnderConstructionDialog(window) }) racialSimilarityRow := container.NewHBox(layout.NewSpacer(), racialSimilarityTitle, racialSimilarityLabel, racialSimilarityHelpButton, layout.NewSpacer()) getRacialSimilarityBreakdownGrid_Traits := func()(*fyne.Container, error){ //TODO: A button for each trait to see what the our and the user's trait value is traitNameHeader := getItalicLabelCentered("Trait Name") traitSimilarityHeader := getItalicLabelCentered("Trait Similarity") geneticSimilarityHeader := getItalicLabelCentered("Genetic Similarity") numberOfTestedLociHeader := getItalicLabelCentered("Number Of Tested Loci") traitNamesColumn := container.NewVBox(traitNameHeader, widget.NewSeparator()) traitSimilarityColumn := container.NewVBox(traitSimilarityHeader, widget.NewSeparator()) geneticSimilarityColumn := container.NewVBox(geneticSimilarityHeader, widget.NewSeparator()) numberOfTestedLociColumn := container.NewVBox(numberOfTestedLociHeader, widget.NewSeparator()) addSimilarityRow := func(traitName string, traitSimilarityAttributeName string, geneticSimilarityAttributeName string, numberOfSimilarAllelesAttributeName string)error{ traitTitleLabel := getBoldLabelCentered(translate(traitName)) traitNamesColumn.Add(traitTitleLabel) if (traitName == "Facial Structure"){ // This trait's similarity cannot be calculated yet // This feature is planned to be added - it will compare the faces in user profile photos. dashLabel := getItalicLabelCentered("-") traitSimilarityColumn.Add(dashLabel) } else { traitSimilarityIsKnown, _, attributeValue, err := getAnyUserProfileAttributeFunction(traitSimilarityAttributeName) if (err != nil) { return err } if (traitSimilarityIsKnown == false){ unknownLabel := getItalicLabelCentered("Unknown") traitSimilarityColumn.Add(unknownLabel) } else { similarityFormatted := attributeValue + "%" similarityLabel := getBoldLabelCentered(similarityFormatted) traitSimilarityColumn.Add(similarityLabel) } } // We figure out how many loci exist for this trait traitObject, err := traits.GetTraitObject(traitName) if (err != nil) { return err } traitLociList := traitObject.LociList quantityOfTraitLoci := len(traitLociList) quantityOfTraitLociString := helpers.ConvertIntToString(quantityOfTraitLoci) geneticSimilarityIsKnown, _, attributeValue, err := getAnyUserProfileAttributeFunction(geneticSimilarityAttributeName) if (err != nil) { return err } if (geneticSimilarityIsKnown == false){ unknownLabel := getItalicLabelCentered("Unknown") geneticSimilarityColumn.Add(unknownLabel) quantityOfTestedLociText := "0/" + quantityOfTraitLociString quantityOfTestedLociLabel := getBoldLabelCentered(quantityOfTestedLociText) numberOfTestedLociColumn.Add(quantityOfTestedLociLabel) } else { similarityFormatted := attributeValue + "%" similarityLabel := getBoldLabelCentered(similarityFormatted) geneticSimilarityColumn.Add(similarityLabel) attributeIsKnown, _, numberOfSimilarAllelesValue, err := getAnyUserProfileAttributeFunction(numberOfSimilarAllelesAttributeName) if (err != nil) { return err } if (attributeIsKnown == false){ return errors.New("User profile contains " + geneticSimilarityAttributeName + " but is missing " + numberOfSimilarAllelesAttributeName) } // numberOfSimilarAllelesValue is represented by (number of shared alleles)/(number of tested alleles) // Example: "52/60" // We extract the denominator _, numberOfTestedAllelesString, delimeterFound := strings.Cut(numberOfSimilarAllelesValue, "/") if (delimeterFound == false){ return errors.New("User profile contains invalid " + numberOfSimilarAllelesAttributeName + ": " + numberOfSimilarAllelesValue) } numberOfTestedAlleles, err := helpers.ConvertStringToInt(numberOfTestedAllelesString) if (err != nil){ return errors.New("User profile contains invalid " + numberOfSimilarAllelesAttributeName + ": " + numberOfSimilarAllelesValue) } // Each locus has 2 alleles (this will change once we include sex chromosome locations) numberOfTestedLoci := numberOfTestedAlleles/2 numberOfTestedLociString := helpers.ConvertIntToString(numberOfTestedLoci) numberOfTestedLociLabelText := numberOfTestedLociString + "/" + quantityOfTraitLociString numberOfTestedLociLabel := getBoldLabelCentered(numberOfTestedLociLabelText) numberOfTestedLociColumn.Add(numberOfTestedLociLabel) } traitNamesColumn.Add(widget.NewSeparator()) traitSimilarityColumn.Add(widget.NewSeparator()) geneticSimilarityColumn.Add(widget.NewSeparator()) numberOfTestedLociColumn.Add(widget.NewSeparator()) return nil } err = addSimilarityRow("Eye Color", "EyeColorSimilarity", "EyeColorGenesSimilarity", "EyeColorGenesSimilarity_NumberOfSimilarAlleles") if (err != nil) { return nil, err } err = addSimilarityRow("Hair Color", "HairColorSimilarity", "HairColorGenesSimilarity", "HairColorGenesSimilarity_NumberOfSimilarAlleles") if (err != nil) { return nil, err } err = addSimilarityRow("Skin Color", "SkinColorSimilarity", "SkinColorGenesSimilarity", "SkinColorGenesSimilarity_NumberOfSimilarAlleles") if (err != nil) { return nil, err } err = addSimilarityRow("Hair Texture", "HairTextureSimilarity", "HairTextureGenesSimilarity", "HairTextureGenesSimilarity_NumberOfSimilarAlleles") if (err != nil) { return nil, err } err = addSimilarityRow("Facial Structure", "", "FacialStructureGenesSimilarity", "FacialStructureGenesSimilarity_NumberOfSimilarAlleles") if (err != nil) { return nil, err } similarityGrid := container.NewHBox(layout.NewSpacer(), traitNamesColumn, traitSimilarityColumn, geneticSimilarityColumn, numberOfTestedLociColumn, layout.NewSpacer()) return similarityGrid, nil } racialSimilarityBreakdownGrid_Traits, err := getRacialSimilarityBreakdownGrid_Traits() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } getRacialSimilarityBreakdownGrid_GeneticAttributes := func()(*fyne.Container, error){ attributeTitleHeader := getItalicLabelCentered("Genetic Attribute Name") twentyThreeAndMeHeader := getItalicLabelCentered("23andMe") attributeTitleColumn := container.NewVBox(attributeTitleHeader, widget.NewSeparator()) similarityColumn_23andMe := container.NewVBox(twentyThreeAndMeHeader, widget.NewSeparator()) addSimilarityRow := func(attributeTitle string, attributeName_23andMe string)error{ attributeTitleLabel := getBoldLabelCentered(translate(attributeTitle)) attributeTitleColumn.Add(attributeTitleLabel) similarityIsKnown, _, attributeValue, err := getAnyUserProfileAttributeFunction(attributeName_23andMe) if (err != nil) { return err } if (similarityIsKnown == false){ unknownLabel := getItalicLabelCentered("Unknown") similarityColumn_23andMe.Add(unknownLabel) } else { similarityFormatted := attributeValue + "%" similarityLabel := getBoldLabelCentered(similarityFormatted) similarityColumn_23andMe.Add(similarityLabel) } attributeTitleColumn.Add(widget.NewSeparator()) similarityColumn_23andMe.Add(widget.NewSeparator()) return nil } err = addSimilarityRow("Ancestral Similarity", "23andMe_AncestralSimilarity") if (err != nil) { return nil, err } err = addSimilarityRow("Maternal Haplogroup Similarity", "23andMe_MaternalHaplogroupSimilarity") if (err != nil) { return nil, err } err = addSimilarityRow("Paternal Haplogroup Similarity", "23andMe_PaternalHaplogroupSimilarity") if (err != nil) { return nil, err } //TODO: Add neanderthal variants similarity? similarityGrid := container.NewHBox(layout.NewSpacer(), attributeTitleColumn, similarityColumn_23andMe, layout.NewSpacer()) return similarityGrid, nil } racialSimilarityBreakdownGrid_GeneticAttributes, err := getRacialSimilarityBreakdownGrid_GeneticAttributes() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator(), racialSimilarityRow, widget.NewSeparator(), racialSimilarityBreakdownGrid_Traits, widget.NewSeparator(), racialSimilarityBreakdownGrid_GeneticAttributes) setPageContent(page, window) } func setViewMateProfilePage_AncestryComposition(window fyne.Window, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ if (userOrOffspring != "User" && userOrOffspring != "Offspring"){ setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_AncestryComposition called with invalid userOrOffspring: " + userOrOffspring), previousPage) return } currentPage := func(){setViewMateProfilePage_AncestryComposition(window, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)} title := getPageTitleCentered(translate("View Profile - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Ancestry Composition")) description1 := getLabelCentered("Below is the ancestry location composition for this user.") description2 := getLabelCentered("The information is provided by 23andMe. More companies will be added.") userAncestryCompositionExists, _, userAncestryCompositionAttribute, err := getAnyUserProfileAttributeFunction("23andMe_AncestryComposition") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (userAncestryCompositionExists == false){ noAncestryComposition := getBoldLabelCentered("This user has no ancestry composition.") page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), noAncestryComposition) setPageContent(page, window) return } description3Label := getLabelCentered("Choose if you want to view the composition of the user or the offspring between you and the user.") offspringAncestryCompositionHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setOffspringAncestryCompositionExplainerPage(window, currentPage) }) description3Row := container.NewHBox(layout.NewSpacer(), description3Label, offspringAncestryCompositionHelpButton, layout.NewSpacer()) handleSelectFunction := func(newUserOrOffspring string){ if (newUserOrOffspring == userOrOffspring){ return } setViewMateProfilePage_AncestryComposition(window, newUserOrOffspring, getAnyUserProfileAttributeFunction, previousPage) } userOrOffspringList := []string{"User", "Offspring"} userOrOffspringSelector := widget.NewSelect(userOrOffspringList, handleSelectFunction) userOrOffspringSelector.Selected = userOrOffspring userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector) header := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), description3Row, userOrOffspringSelectorCentered, widget.NewSeparator()) if (userOrOffspring == "User"){ attributeIsValid, continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap, err := companyAnalysis.ReadAncestryCompositionAttribute_23andMe(true, userAncestryCompositionAttribute) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (attributeIsValid == false){ setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_AncestryComposition called with getAnyUserProfileAttributeFunction returning invalid userAncestryCompositionAttribute"), previousPage) return } userCompositionDisplay, err := get23andMeAncestryCompositionDisplay(continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewBorder(header, nil, nil, nil, userCompositionDisplay) setPageContent(page, window) return } //userOrOffspring == "Offspring" myAncestryCompositionExists, myAncestryCompositionAttribute, err := myLocalProfiles.GetProfileData("Mate", "23andMe_AncestryComposition") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (myAncestryCompositionExists == false){ description4 := getBoldLabelCentered("Your ancestry location composition does not exist.") description5 := getLabelCentered("Add your ancestry composition to view your offspring's ancestry composition.") description6 := getLabelCentered("You can do this on the Build Profile - Physical - Ancestry Composition page.") page := container.NewVBox(header, description4, description5, description6) setPageContent(page, window) return } offspringContinentPercentagesMap, offspringRegionPercentagesMap, offspringSubregionPercentagesMap, err := companyAnalysis.GetOffspringAncestryComposition_23andMe(myAncestryCompositionAttribute, userAncestryCompositionAttribute) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } offspringAncestryCompositionDisplay, err := get23andMeAncestryCompositionDisplay(offspringContinentPercentagesMap, offspringRegionPercentagesMap, offspringSubregionPercentagesMap) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewBorder(header, nil, nil, nil, offspringAncestryCompositionDisplay) setPageContent(page, window) } // This is a page to view the total monogenic disease risk for a user's offspring func setViewMateProfilePage_TotalDiseaseRisk(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ setLoadingScreen(window, "View Profile - Physical", "Computing Genetic Analysis...") title := getPageTitleCentered(translate("View Profile - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Total Disease Risk")) description1 := getLabelCentered("This page describes the total disease risk for this user.") description2 := getLabelCentered("You must link your genome person in the Build Profile menu to see offspring disease information.") offspringProbabilityOfAnyMonogenicDiseaseTitle := widget.NewLabel("Offspring Probability Of Any Monogenic Disease:") getOffspringProbabilityOfAnyMonogenicDiseaseLabel := func()(fyne.Widget, error){ probabilityIsKnown, _, offspringProbabilityOfAnyMonogenicDisease, err := getAnyUserProfileAttributeFunction("OffspringProbabilityOfAnyMonogenicDisease") if (err != nil) { return nil, err } if (probabilityIsKnown == false){ unknownLabel := getItalicLabel(translate("Unknown")) return unknownLabel, nil } probabilityFormatted := offspringProbabilityOfAnyMonogenicDisease + "%" probabilityLabel := getBoldLabel(probabilityFormatted) return probabilityLabel, nil } offspringProbabilityOfAnyMonogenicDiseaseLabel, err := getOffspringProbabilityOfAnyMonogenicDiseaseLabel() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } offspringProbabilityOfAnyMonogenicDiseaseRow := container.NewHBox(layout.NewSpacer(), offspringProbabilityOfAnyMonogenicDiseaseTitle, offspringProbabilityOfAnyMonogenicDiseaseLabel, layout.NewSpacer()) getNumberOfOffspringMonogenicDiseasesTested := func()(int, error){ attributeIsKnown, _, totalNumberOfMonogenicDiseasesTestedString, err := getAnyUserProfileAttributeFunction("OffspringProbabilityOfAnyMonogenicDisease_NumberOfDiseasesTested") if (err != nil) { return 0, err } if (attributeIsKnown == false){ return 0, nil } totalNumberOfMonogenicDiseasesTested, err := helpers.ConvertStringToInt(totalNumberOfMonogenicDiseasesTestedString) if (err != nil){ return 0, errors.New("getAnyUserProfileAttributeFunction returning invalid OffspringProbabilityOfAnyMonogenicDisease_NumberOfDiseasesTested value: " + totalNumberOfMonogenicDiseasesTestedString) } return totalNumberOfMonogenicDiseasesTested, nil } numberOfOffspringMonogenicDiseasesTested, err := getNumberOfOffspringMonogenicDiseasesTested() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } allMonogenicDiseaseNamesList, err := monogenicDiseases.GetMonogenicDiseaseNamesList() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } totalNumberOfMonogenicDiseases := len(allMonogenicDiseaseNamesList) totalNumberOfMonogenicDiseasesString := helpers.ConvertIntToString(totalNumberOfMonogenicDiseases) numberOfOffspringMonogenicDiseasesTestedString := helpers.ConvertIntToString(numberOfOffspringMonogenicDiseasesTested) numberOfOffspringMonogenicDiseasesTestedLabelText := numberOfOffspringMonogenicDiseasesTestedString + "/" + totalNumberOfMonogenicDiseasesString numberOfOffspringMonogenicDiseasesTestedTitle := widget.NewLabel("Number Of Offspring Monogenic Diseases Tested:") numberOfOffspringMonogenicDiseasesTestedLabel := getBoldLabel(numberOfOffspringMonogenicDiseasesTestedLabelText) numberOfOffspringMonogenicDiseasesTestedRow := container.NewHBox(layout.NewSpacer(), numberOfOffspringMonogenicDiseasesTestedTitle, numberOfOffspringMonogenicDiseasesTestedLabel, layout.NewSpacer()) userTotalPolygenicDiseaseRiskScoreTitle := widget.NewLabel("User Total Polygenic Disease Risk Score:") getUserTotalPolygenicDiseaseRiskScoreLabel := func()(fyne.Widget, error){ riskScoreIsKnown, _, userTotalPolygenicDiseaseRiskScore, err := getAnyUserProfileAttributeFunction("TotalPolygenicDiseaseRiskScore") if (err != nil) { return nil, err } if (riskScoreIsKnown == false){ unknownLabel := getItalicLabel(translate("Unknown")) return unknownLabel, nil } riskScoreFormatted := userTotalPolygenicDiseaseRiskScore + "/100" riskScoreLabel := getBoldLabel(riskScoreFormatted) return riskScoreLabel, nil } userTotalPolygenicDiseaseRiskScoreLabel, err := getUserTotalPolygenicDiseaseRiskScoreLabel() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } userTotalPolygenicDiseaseRiskScoreRow := container.NewHBox(layout.NewSpacer(), userTotalPolygenicDiseaseRiskScoreTitle, userTotalPolygenicDiseaseRiskScoreLabel, layout.NewSpacer()) getNumberOfUserPolygenicDiseasesTested := func()(int, error){ attributeIsKnown, _, totalNumberOfPolygenicDiseasesTestedString, err := getAnyUserProfileAttributeFunction("TotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested") if (err != nil) { return 0, err } if (attributeIsKnown == false){ return 0, nil } totalNumberOfPolygenicDiseasesTested, err := helpers.ConvertStringToInt(totalNumberOfPolygenicDiseasesTestedString) if (err != nil){ return 0, errors.New("getAnyUserProfileAttributeFunction returning invalid TotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested value: " + totalNumberOfPolygenicDiseasesTestedString) } return totalNumberOfPolygenicDiseasesTested, nil } numberOfUserPolygenicDiseasesTested, err := getNumberOfUserPolygenicDiseasesTested() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } allPolygenicDiseaseNamesList, err := polygenicDiseases.GetPolygenicDiseaseNamesList() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } // We count up every disease that has a prediction model totalNumberOfPolygenicDiseases := 0 for _, diseaseName := range allPolygenicDiseaseNamesList{ predictionModelExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(diseaseName) if (predictionModelExists == true){ totalNumberOfPolygenicDiseases += 1 } } totalNumberOfPolygenicDiseasesString := helpers.ConvertIntToString(totalNumberOfPolygenicDiseases) numberOfUserPolygenicDiseasesTestedString := helpers.ConvertIntToString(numberOfUserPolygenicDiseasesTested) numberOfUserPolygenicDiseasesTestedLabelText := numberOfUserPolygenicDiseasesTestedString + "/" + totalNumberOfPolygenicDiseasesString numberOfUserPolygenicDiseasesTestedTitle := widget.NewLabel("Number Of User Polygenic Diseases Tested:") numberOfUserPolygenicDiseasesTestedLabel := getBoldLabel(numberOfUserPolygenicDiseasesTestedLabelText) numberOfUserPolygenicDiseasesTestedRow := container.NewHBox(layout.NewSpacer(), numberOfUserPolygenicDiseasesTestedTitle, numberOfUserPolygenicDiseasesTestedLabel, layout.NewSpacer()) offspringTotalPolygenicDiseaseRiskScoreTitle := widget.NewLabel("Offspring Total Polygenic Disease Risk Score:") getOffspringTotalPolygenicDiseaseRiskScoreLabel := func()(fyne.Widget, error){ riskScoreIsKnown, _, offspringTotalPolygenicDiseaseRiskScore, err := getAnyUserProfileAttributeFunction("OffspringTotalPolygenicDiseaseRiskScore") if (err != nil) { return nil, err } if (riskScoreIsKnown == false){ unknownLabel := getItalicLabel(translate("Unknown")) return unknownLabel, nil } riskScoreFormatted := offspringTotalPolygenicDiseaseRiskScore + "/100" riskScoreLabel := getBoldLabel(riskScoreFormatted) return riskScoreLabel, nil } offspringTotalPolygenicDiseaseRiskScoreLabel, err := getOffspringTotalPolygenicDiseaseRiskScoreLabel() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } offspringTotalPolygenicDiseaseRiskScoreRow := container.NewHBox(layout.NewSpacer(), offspringTotalPolygenicDiseaseRiskScoreTitle, offspringTotalPolygenicDiseaseRiskScoreLabel, layout.NewSpacer()) getNumberOfOffspringPolygenicDiseasesTested := func()(int, error){ attributeIsKnown, _, totalNumberOfPolygenicDiseasesTestedString, err := getAnyUserProfileAttributeFunction("OffspringTotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested") if (err != nil) { return 0, err } if (attributeIsKnown == false){ return 0, nil } totalNumberOfPolygenicDiseasesTested, err := helpers.ConvertStringToInt(totalNumberOfPolygenicDiseasesTestedString) if (err != nil){ return 0, errors.New("getAnyUserProfileAttributeFunction returning invalid OffspringTotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested value: " + totalNumberOfPolygenicDiseasesTestedString) } return totalNumberOfPolygenicDiseasesTested, nil } numberOfOffspringPolygenicDiseasesTested, err := getNumberOfOffspringPolygenicDiseasesTested() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } numberOfOffspringPolygenicDiseasesTestedString := helpers.ConvertIntToString(numberOfOffspringPolygenicDiseasesTested) numberOfOffspringPolygenicDiseasesTestedLabelText := numberOfOffspringPolygenicDiseasesTestedString + "/" + totalNumberOfPolygenicDiseasesString numberOfOffspringPolygenicDiseasesTestedTitle := widget.NewLabel("Number Of Offspring Polygenic Diseases Tested:") numberOfOffspringPolygenicDiseasesTestedLabel := getBoldLabel(numberOfOffspringPolygenicDiseasesTestedLabelText) numberOfOffspringPolygenicDiseasesTestedRow := container.NewHBox(layout.NewSpacer(), numberOfOffspringPolygenicDiseasesTestedTitle, numberOfOffspringPolygenicDiseasesTestedLabel, layout.NewSpacer()) //TODO: Add help buttons page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), offspringProbabilityOfAnyMonogenicDiseaseRow, numberOfOffspringMonogenicDiseasesTestedRow, widget.NewSeparator(), userTotalPolygenicDiseaseRiskScoreRow, numberOfUserPolygenicDiseasesTestedRow, widget.NewSeparator(), offspringTotalPolygenicDiseaseRiskScoreRow, numberOfOffspringPolygenicDiseasesTestedRow) setPageContent(page, window) } func setViewMateProfilePage_MonogenicDiseases(window fyne.Window, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ currentPage := func(){setViewMateProfilePage_MonogenicDiseases(window, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)} title := getPageTitleCentered(translate("View Profile - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Monogenic Diseases")) description1 := getLabelCentered("Below is the monogenic disease analysis for this user.") description2 := getLabelCentered("You can choose to view the analysis of the user or an offspring between you and the user.") description3 := getLabelCentered("You must link your genome person in the Build Profile menu to see full offspring information.") handleSelectButton := func(newUserOrOffspring string){ if (userOrOffspring == newUserOrOffspring){ return } setViewMateProfilePage_MonogenicDiseases(window, newUserOrOffspring, getAnyUserProfileAttributeFunction, previousPage) } userOrOffspringSelector := widget.NewSelect([]string{"User", "Offspring"}, handleSelectButton) userOrOffspringSelector.Selected = userOrOffspring userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector) //TODO: Sort to show highest risk diseases first. All other diseases should be in normal order getDiseasesInfoGrid := func()(*fyne.Container, error){ emptyLabel1 := widget.NewLabel("") emptyLabel2 := widget.NewLabel("") emptyLabel3 := widget.NewLabel("") diseaseNameLabel := getItalicLabelCentered("Disease Name") emptyLabel4 := widget.NewLabel("") userHasDiseaseLabel := getItalicLabelCentered("User Has Disease") userProbabilityOfLabelA := getItalicLabelCentered("User Probability Of") passingVariantLabel := getItalicLabelCentered("Passing Variant") userNumberOfLabel := getItalicLabelCentered("User Number Of") variantsTestedLabelA := getItalicLabelCentered("Variants Tested") offspringProbabilityOfLabelA := getItalicLabelCentered("Offspring Probability Of") havingDiseaseLabel := getItalicLabelCentered("Having Disease") offspringProbabilityOfLabelB := getItalicLabelCentered("Offspring Probability Of") havingVariantLabel := getItalicLabelCentered("Having Variant") offspringNumberOfLabel := getItalicLabelCentered("Offspring Number Of") variantsTestedLabelB := getItalicLabelCentered("Variants Tested") diseaseInfoButtonsColumn := container.NewVBox(emptyLabel1, emptyLabel2, widget.NewSeparator()) diseaseNameColumn := container.NewVBox(emptyLabel3, diseaseNameLabel, widget.NewSeparator()) userHasDiseaseColumn := container.NewVBox(emptyLabel4, userHasDiseaseLabel, widget.NewSeparator()) userProbabilityOfPassingVariantColumn := container.NewVBox(userProbabilityOfLabelA, passingVariantLabel, widget.NewSeparator()) userNumberOfVariantsTestedColumn := container.NewVBox(userNumberOfLabel, variantsTestedLabelA, widget.NewSeparator()) offspringProbabilityOfHavingDiseaseColumn := container.NewVBox(offspringProbabilityOfLabelA, havingDiseaseLabel, widget.NewSeparator()) offspringProbabilityOfHavingAVariantColumn := container.NewVBox(offspringProbabilityOfLabelB, havingVariantLabel, widget.NewSeparator()) offspringNumberOfVariantsTestedColumn := container.NewVBox(offspringNumberOfLabel, variantsTestedLabelB, widget.NewSeparator()) myPersonChosen, myGenomesExist, myAnalysisIsReady, myAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis() if (err != nil) { return nil, err } monogenicDiseaseObjectsList, err := monogenicDiseases.GetMonogenicDiseaseObjectsList() if (err != nil) { return nil, err } for _, diseaseObject := range monogenicDiseaseObjectsList{ monogenicDiseaseName := diseaseObject.DiseaseName diseaseIsDominantOrRecessive := diseaseObject.DominantOrRecessive diseaseVariantsList := diseaseObject.VariantsList numberOfDiseaseVariants := len(diseaseVariantsList) //Outputs: // -bool: User disease info exists // -bool: User has the disease // -int: User probability of passing a disease variant (0-100) // -int: User number of variants tested // -error getUserDiseaseInfo := func()(bool, bool, int, int, error){ diseaseNameWithUnderscores := strings.ReplaceAll(monogenicDiseaseName, " ", "_") probabilityOfPassingAVariantAttributeName := "MonogenicDisease_" + diseaseNameWithUnderscores + "_ProbabilityOfPassingAVariant" numberOfVariantsTestedAttributeName := "MonogenicDisease_" + diseaseNameWithUnderscores + "_NumberOfVariantsTested" userDiseaseInfoExists, _, userProbabilityOfPassingDiseaseVariantString, err := getAnyUserProfileAttributeFunction(probabilityOfPassingAVariantAttributeName) if (err != nil) { return false, false, 0, 0, err } if (userDiseaseInfoExists == false){ return false, false, 0, 0, nil } userProbabilityOfPassingDiseaseVariant, err := helpers.ConvertStringToInt(userProbabilityOfPassingDiseaseVariantString) if (err != nil) { return false, false, 0, 0, errors.New("setViewMateProfilePage_MonogenicDiseases called with profile containing invalid probabilityOfPassingAVariant: " + userProbabilityOfPassingDiseaseVariantString) } getUserHasDiseaseBool := func()bool{ if (diseaseIsDominantOrRecessive == "Dominant"){ if (userProbabilityOfPassingDiseaseVariant != 0){ return true } return false } // diseaseIsDominantOrRecessive == "Recessive" if (userProbabilityOfPassingDiseaseVariant == 100){ return true } return false } userHasDisease := getUserHasDiseaseBool() userVariantsTestedExists, _, userNumberOfVariantsTested, err := getAnyUserProfileAttributeFunction(numberOfVariantsTestedAttributeName) if (err != nil) { return false, false, 0, 0, err } if (userVariantsTestedExists == false){ return false, false, 0, 0, errors.New("setViewMateProfilePage_MonogenicDiseases called with user profile containing probabilityOfPassingAVariant but not numberOfVariantsTested") } userNumberOfVariantsTestedInt, err := helpers.ConvertStringToInt(userNumberOfVariantsTested) if (err != nil) { return false, false, 0, 0, errors.New("setViewMateProfilePage_MonogenicDiseases called with profile containing invalid numberOfVariantsTested: " + userNumberOfVariantsTested) } return true, userHasDisease, userProbabilityOfPassingDiseaseVariant, userNumberOfVariantsTestedInt, nil } userDiseaseInfoExists, userHasDisease, userProbabilityOfPassingAVariant, userNumberOfVariantsTested, err := getUserDiseaseInfo() if (err != nil) { return nil, err } //Outputs: // -bool: My disease info exists // -int: My probability of passing a disease variant // -int: Number of variants tested // -error getMyDiseaseInfo := func()(bool, int, int, error){ if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){ return false, 0, 0, nil } diseaseInfoIsKnown, _, probabilityOfPassingADiseaseVariant, _, numberOfVariantsTested, _, _, _, err := readGeneticAnalysis.GetPersonMonogenicDiseaseInfoFromGeneticAnalysis(myAnalysisObject, monogenicDiseaseName, myGenomeIdentifier) if (err != nil) { return false, 0, 0, err } if (diseaseInfoIsKnown == false){ return false, 0, 0, nil } return true, probabilityOfPassingADiseaseVariant, numberOfVariantsTested, nil } myDiseaseInfoExists, myProbabilityOfPassingAVariant, myNumberOfVariantsTested, err := getMyDiseaseInfo() if (err != nil) { return nil, err } probabilityOffspringHasDiseaseIsKnown, probabilityOffspringHasDisease, probabilityOffspringHasVariantIsKnown, probabilityOffspringHasVariant, err := createCoupleGeneticAnalysis.GetOffspringMonogenicDiseaseProbabilities(diseaseIsDominantOrRecessive, userDiseaseInfoExists, userProbabilityOfPassingAVariant, myDiseaseInfoExists, myProbabilityOfPassingAVariant) if (err != nil) { return nil, err } getUserHasDiseaseString := func()string{ if (userDiseaseInfoExists == false){ return "Unknown" } userHasDiseaseString := helpers.ConvertBoolToYesOrNoString(userHasDisease) return userHasDiseaseString } userHasDiseaseString := getUserHasDiseaseString() getUserProbabilityOfPassingAVariantString := func()string{ if (userDiseaseInfoExists == false){ return "Unknown" } userProbabilityOfPassingAVariantString := helpers.ConvertIntToString(userProbabilityOfPassingAVariant) resultFormatted := userProbabilityOfPassingAVariantString + "%" return resultFormatted } userProbabilityOfPassingAVariantString := getUserProbabilityOfPassingAVariantString() getUserNumberOfVariantsTestedString := func()string{ numberOfDiseaseVariantsString := helpers.ConvertIntToString(numberOfDiseaseVariants) if (userDiseaseInfoExists == false){ result := "0/" + numberOfDiseaseVariantsString return result } userNumberOfVariantsTestedString := helpers.ConvertIntToString(userNumberOfVariantsTested) resultFormatted := userNumberOfVariantsTestedString + "/" + numberOfDiseaseVariantsString return resultFormatted } userNumberOfVariantsTestedString := getUserNumberOfVariantsTestedString() getOffspringProbabilityOfHavingDiseaseString := func()string{ if (probabilityOffspringHasDiseaseIsKnown == false){ result := translate("Unknown") return result } offspringProbabilityOfHavingDiseaseString := helpers.ConvertIntToString(probabilityOffspringHasDisease) resultFormatted := offspringProbabilityOfHavingDiseaseString + "%" return resultFormatted } offspringProbabilityOfHavingDiseaseString := getOffspringProbabilityOfHavingDiseaseString() getOffspringProbabilityOfHavingAVariantFormatted := func()string{ if (probabilityOffspringHasVariantIsKnown == false){ result := translate("Unknown") return result } offspringProbabilityOfHavingAVariantString := helpers.ConvertIntToString(probabilityOffspringHasVariant) resultFormatted := offspringProbabilityOfHavingAVariantString + "%" return resultFormatted } offspringProbabilityOfHavingAVariantFormatted := getOffspringProbabilityOfHavingAVariantFormatted() totalNumberOfOffspringDiseaseVariantsString := helpers.ConvertIntToString(numberOfDiseaseVariants*2) offspringNumberOfVariantsTested := userNumberOfVariantsTested + myNumberOfVariantsTested offspringNumberOfVariantsTestedString := helpers.ConvertIntToString(offspringNumberOfVariantsTested) offspringNumberOfVariantsTestedFormatted := offspringNumberOfVariantsTestedString + "/" + totalNumberOfOffspringDiseaseVariantsString viewDiseaseInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){ setViewMonogenicDiseaseDetailsPage(window, monogenicDiseaseName, currentPage) }) diseaseNameLabel := getBoldLabelCentered(monogenicDiseaseName) userHasDiseaseLabel := getBoldLabelCentered(userHasDiseaseString) userProbabilityOfPassingAVariantLabel := getBoldLabelCentered(userProbabilityOfPassingAVariantString) userNumberOfVariantsTestedLabel := getBoldLabelCentered(userNumberOfVariantsTestedString) offspringProbabilityOfHavingDiseaseLabel := getBoldLabelCentered(offspringProbabilityOfHavingDiseaseString) offspringProbabilityOfHavingAVariantLabel := getBoldLabelCentered(offspringProbabilityOfHavingAVariantFormatted) offspringNumberOfVariantsTestedLabel := getBoldLabelCentered(offspringNumberOfVariantsTestedFormatted) diseaseInfoButtonsColumn.Add(viewDiseaseInfoButton) diseaseNameColumn.Add(diseaseNameLabel) userHasDiseaseColumn.Add(userHasDiseaseLabel) userProbabilityOfPassingVariantColumn.Add(userProbabilityOfPassingAVariantLabel) userNumberOfVariantsTestedColumn.Add(userNumberOfVariantsTestedLabel) offspringProbabilityOfHavingDiseaseColumn.Add(offspringProbabilityOfHavingDiseaseLabel) offspringProbabilityOfHavingAVariantColumn.Add(offspringProbabilityOfHavingAVariantLabel) offspringNumberOfVariantsTestedColumn.Add(offspringNumberOfVariantsTestedLabel) diseaseInfoButtonsColumn.Add(widget.NewSeparator()) diseaseNameColumn.Add(widget.NewSeparator()) userHasDiseaseColumn.Add(widget.NewSeparator()) userProbabilityOfPassingVariantColumn.Add(widget.NewSeparator()) userNumberOfVariantsTestedColumn.Add(widget.NewSeparator()) offspringProbabilityOfHavingDiseaseColumn.Add(widget.NewSeparator()) offspringProbabilityOfHavingAVariantColumn.Add(widget.NewSeparator()) offspringNumberOfVariantsTestedColumn.Add(widget.NewSeparator()) } userHasDiseaseHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setPersonHasMonogenicDiseaseExplainerPage(window, currentPage) }) probabilityOfPassingVariantHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setPersonProbabilityOfPassingVariantExplainerPage(window, currentPage) }) numberOfVariantsTestedHelpButtonA := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setNumberOfTestedVariantsExplainerPage(window, currentPage) }) numberOfVariantsTestedHelpButtonB := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setNumberOfTestedVariantsExplainerPage(window, currentPage) }) probabilityOfHavingDiseaseHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setOffspringProbabilityOfHavingMonogenicDiseaseExplainerPage(window, currentPage) }) probabilityOfHavingAVariantHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setOffspringProbabilityOfHavingVariantExplainerPage(window, currentPage) }) userHasDiseaseColumn.Add(userHasDiseaseHelpButton) userProbabilityOfPassingVariantColumn.Add(probabilityOfPassingVariantHelpButton) userNumberOfVariantsTestedColumn.Add(numberOfVariantsTestedHelpButtonA) offspringProbabilityOfHavingDiseaseColumn.Add(probabilityOfHavingDiseaseHelpButton) offspringProbabilityOfHavingAVariantColumn.Add(probabilityOfHavingAVariantHelpButton) offspringNumberOfVariantsTestedColumn.Add(numberOfVariantsTestedHelpButtonB) if (userOrOffspring == "User"){ diseasesInfoGrid := container.NewHBox(layout.NewSpacer(), diseaseInfoButtonsColumn, diseaseNameColumn, userHasDiseaseColumn, userProbabilityOfPassingVariantColumn, userNumberOfVariantsTestedColumn, layout.NewSpacer()) return diseasesInfoGrid, nil } diseasesInfoGrid := container.NewHBox(layout.NewSpacer(), diseaseInfoButtonsColumn, diseaseNameColumn, offspringProbabilityOfHavingDiseaseColumn, offspringProbabilityOfHavingAVariantColumn, offspringNumberOfVariantsTestedColumn, layout.NewSpacer()) return diseasesInfoGrid, nil } diseasesInfoGrid, err := getDiseasesInfoGrid() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), userOrOffspringSelectorCentered, widget.NewSeparator(), diseasesInfoGrid) setPageContent(page, window) } func setViewMateProfilePage_PolygenicDiseases(window fyne.Window, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ if (userOrOffspring == "Offspring"){ setLoadingScreen(window, "View Profile - Physical", "Computing Genetic Analysis...") } currentPage := func(){setViewMateProfilePage_PolygenicDiseases(window, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)} title := getPageTitleCentered(translate("View Profile - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Polygenic Diseases")) description1 := getLabelCentered("Below is the polygenic disease risk analysis for this user.") description2 := getLabelCentered("You can choose to view the analysis of the user or an offspring between you and the user.") description3 := getLabelCentered("You must link your genome person in the Build Profile menu to see offspring information.") handleSelectButton := func(newUserOrOffspring string){ if (userOrOffspring == newUserOrOffspring){ return } setViewMateProfilePage_PolygenicDiseases(window, newUserOrOffspring, getAnyUserProfileAttributeFunction, previousPage) } userOrOffspringSelector := widget.NewSelect([]string{"User", "Offspring"}, handleSelectButton) userOrOffspringSelector.Selected = userOrOffspring userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector) getDiseaseInfoGrid := func()(*fyne.Container, error){ diseaseNameLabel := getItalicLabelCentered("Disease Name") userRiskScoreLabel := getItalicLabelCentered("User Risk Score") offspringRiskScoreLabel := getItalicLabelCentered("Offspring Risk Score") confidenceRangeLabel := getItalicLabelCentered("Confidence Range") emptyLabel1 := widget.NewLabel("") emptyLabel2 := widget.NewLabel("") diseaseNameColumn := container.NewVBox(diseaseNameLabel, widget.NewSeparator()) userRiskScoreColumn := container.NewVBox(userRiskScoreLabel, widget.NewSeparator()) offspringRiskScoreColumn := container.NewVBox(offspringRiskScoreLabel, widget.NewSeparator()) confidenceRangeColumn := container.NewVBox(confidenceRangeLabel, widget.NewSeparator()) viewSampleOffspringsChartButtonsColumn := container.NewVBox(emptyLabel1, widget.NewSeparator()) viewDiseaseInfoButtonsColumn := container.NewVBox(emptyLabel2, widget.NewSeparator()) //Outputs: // -map[int64]locusValue.LocusValue // -error getMyGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){ if (userOrOffspring == "User"){ // We don't need to retrieve our locus values map emptyMap := make(map[int64]locusValue.LocusValue) return emptyMap, nil } myPersonChosen, myGenomesExist, myAnalysisIsReady, myAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis() if (err != nil) { return nil, err } if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){ emptyMap := make(map[int64]locusValue.LocusValue) return emptyMap, nil } _, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myAnalysisObject) if (err != nil) { return nil, err } myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier] if (exists == false){ return nil, errors.New("GetMyChosenMateGeneticAnalysis returning analysis which is missing genome with myGenomeIdentifier.") } return myGenomeLocusValuesMap, nil } myGenomeLocusValuesMap, err := getMyGenomeLocusValuesMap() if (err != nil) { return nil, err } getConfidenceRangeLabel := func(analysisExists bool, predictionConfidenceRangesMap map[int]float64)(fyne.Widget, error){ if (analysisExists == false){ result := widget.NewLabel("Unknown") return result, nil } // This is a list of the percentage accuracies in the map // For example: 80% == The distance from the prediction you must travel for 80% of the predictions to be // accurate within that range confidenceRangePercentagesList := helpers.GetListOfMapKeys(predictionConfidenceRangesMap) // We sort the list so the percentage is always the same upon refreshing the page slices.Sort(confidenceRangePercentagesList) closestToEightyPercentage, err := helpers.GetClosestIntInList(confidenceRangePercentagesList, 80) if (err != nil) { return nil, err } closestToEightyPercentageConfidenceDistance, exists := predictionConfidenceRangesMap[closestToEightyPercentage] if (exists == false){ return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.") } closestConfidenceDistanceString := helpers.ConvertFloat64ToStringRounded(closestToEightyPercentageConfidenceDistance, 2) closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage) confidenceRangeLabelValueFormatted := "+/- " + closestConfidenceDistanceString + " (" + closestToEightyPercentageString + "%)" confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted) return confidenceRangeLabel, nil } diseaseObjectsList, err := polygenicDiseases.GetPolygenicDiseaseObjectsList() if (err != nil) { return nil, err } for _, diseaseObject := range diseaseObjectsList{ diseaseName := diseaseObject.DiseaseName diseaseLociList := diseaseObject.LociList predictionModelExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(diseaseName) if (predictionModelExists == false){ // Prediction is not possible for this disease continue } diseaseNameLabel := getBoldLabelCentered(diseaseName) userDiseaseLocusValuesMap, err := calculatedAttributes.GetUserGenomeLocusValuesMapFromProfile(diseaseLociList, getAnyUserProfileAttributeFunction) if (err != nil) { return nil, err } if (userOrOffspring == "User"){ neuralNetworkExists, analysisExists, userDiseaseRiskScore, predictionConfidenceRangesMap, _, _, err := createPersonGeneticAnalysis.GetPersonGenomePolygenicDiseaseAnalysis(diseaseObject, userDiseaseLocusValuesMap, true) if (err != nil) { return nil, err } if (neuralNetworkExists == false){ return nil, errors.New("GetPersonGenomePolygenicDiseaseAnalysis claims model doesn't exist when we already checked.") } getUserDiseaseRiskScoreLabel := func()fyne.Widget{ if (analysisExists == false){ result := widget.NewLabel(translate("Unknown")) return result } userRiskScoreString := helpers.ConvertIntToString(userDiseaseRiskScore) riskScoreFormatted := userRiskScoreString + "/10" riskScoreLabel := getBoldLabel(riskScoreFormatted) return riskScoreLabel } userDiseaseRiskScoreLabel := getUserDiseaseRiskScoreLabel() userDiseaseRiskScoreLabelCentered := getWidgetCentered(userDiseaseRiskScoreLabel) userRiskScoreColumn.Add(userDiseaseRiskScoreLabelCentered) confidenceRangeLabel, err := getConfidenceRangeLabel(analysisExists, predictionConfidenceRangesMap) if (err != nil) { return nil, err } confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel) confidenceRangeColumn.Add(confidenceRangeLabelCentered) } else if (userOrOffspring == "Offspring"){ neuralNetworkExists, anyOffspringLociKnown, offspringDiseaseRiskScore, predictionConfidenceRangesMap, offspringSampleRiskScoresList, offspringQuantityOfLociKnown, _, err := createCoupleGeneticAnalysis.GetOffspringPolygenicDiseaseAnalysis(diseaseObject, myGenomeLocusValuesMap, userDiseaseLocusValuesMap) if (err != nil) { return nil, err } if (neuralNetworkExists == false){ return nil, errors.New("GetOffspringPolygenicDiseaseAnalysis claims that neural network doesn't exist when we already checked.") } getOffspringDiseaseRiskScoreLabel := func()fyne.Widget{ if (anyOffspringLociKnown == false){ result := widget.NewLabel(translate("Unknown")) return result } offspringRiskScoreString := helpers.ConvertIntToString(offspringDiseaseRiskScore) riskScoreFormatted := offspringRiskScoreString + "/10" riskScoreLabel := getBoldLabel(riskScoreFormatted) return riskScoreLabel } offspringDiseaseRiskScoreLabel := getOffspringDiseaseRiskScoreLabel() offspringDiseaseRiskScoreLabelCentered := getWidgetCentered(offspringDiseaseRiskScoreLabel) offspringRiskScoreColumn.Add(offspringDiseaseRiskScoreLabelCentered) confidenceRangeLabel, err := getConfidenceRangeLabel(anyOffspringLociKnown, predictionConfidenceRangesMap) if (err != nil) { return nil, err } confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel) confidenceRangeColumn.Add(confidenceRangeLabelCentered) viewSampleOffspringsChartButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){ setViewPolygenicDiseaseSampleOffspringRiskScoresChart(window, diseaseName, offspringSampleRiskScoresList, offspringQuantityOfLociKnown, currentPage) }) viewSampleOffspringsChartButtonsColumn.Add(viewSampleOffspringsChartButton) } viewDiseaseDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ //TODO showUnderConstructionDialog(window) }) diseaseNameColumn.Add(diseaseNameLabel) viewDiseaseInfoButtonsColumn.Add(viewDiseaseDetailsButton) diseaseNameColumn.Add(widget.NewSeparator()) userRiskScoreColumn.Add(widget.NewSeparator()) offspringRiskScoreColumn.Add(widget.NewSeparator()) confidenceRangeColumn.Add(widget.NewSeparator()) viewSampleOffspringsChartButtonsColumn.Add(widget.NewSeparator()) viewDiseaseInfoButtonsColumn.Add(widget.NewSeparator()) } userRiskScoreHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setPolygenicDiseaseRiskScoreExplainerPage(window, currentPage) }) userRiskScoreColumn.Add(userRiskScoreHelpButton) offspringRiskScoreHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setOffspringPolygenicDiseaseRiskScoreExplainerPage(window, currentPage) }) offspringRiskScoreColumn.Add(offspringRiskScoreHelpButton) confidenceRangeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ showUnderConstructionDialog(window) //TODO }) confidenceRangeColumn.Add(confidenceRangeHelpButton) if (userOrOffspring == "User"){ diseaseInfoGrid := container.NewHBox(layout.NewSpacer(), diseaseNameColumn, userRiskScoreColumn, confidenceRangeColumn, viewDiseaseInfoButtonsColumn, layout.NewSpacer()) return diseaseInfoGrid, nil } diseaseInfoGrid := container.NewHBox(layout.NewSpacer(), diseaseNameColumn, offspringRiskScoreColumn, confidenceRangeColumn, viewSampleOffspringsChartButtonsColumn, viewDiseaseInfoButtonsColumn, layout.NewSpacer()) return diseaseInfoGrid, nil } diseaseInfoGrid, err := getDiseaseInfoGrid() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), userOrOffspringSelectorCentered, widget.NewSeparator(), diseaseInfoGrid) setPageContent(page, window) } func setViewMateProfilePage_GeneticTraits(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ currentPage := func(){setViewMateProfilePage_GeneticTraits(window, getAnyUserProfileAttributeFunction, previousPage)} title := getPageTitleCentered(translate("View Profile - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Genetic Traits")) description1 := getLabelCentered("Choose if you want to view Discrete traits or Numeric traits.") description2 := getLabelCentered("Discrete traits are traits which have discrete outcomes, such as Eye Color") description3 := getLabelCentered("Numeric traits are traits with numeric outcomes, such as Height.") discreteTraitsButton := widget.NewButton("Discrete Traits", func(){ setViewMateProfilePage_DiscreteGeneticTraits(window, "Offspring", getAnyUserProfileAttributeFunction, currentPage) }) numericTraitsButton := widget.NewButton("Numeric Traits", func(){ setViewMateProfilePage_NumericGeneticTraits(window, "Offspring", getAnyUserProfileAttributeFunction, currentPage) }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, discreteTraitsButton, numericTraitsButton)) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), buttonsGrid) setPageContent(page, window) } func setViewMateProfilePage_DiscreteGeneticTraits(window fyne.Window, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ if (userOrOffspring != "User" && userOrOffspring != "Offspring"){ setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_DiscreteGeneticTraits called with invalid userOrOffspring: " + userOrOffspring), previousPage) return } if (userOrOffspring == "Offspring"){ setLoadingScreen(window, "View Profile - Physical", "Computing Genetic Analysis...") } currentPage := func(){setViewMateProfilePage_DiscreteGeneticTraits(window, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)} title := getPageTitleCentered(translate("View Profile - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Discrete Genetic Traits")) description1 := getLabelCentered("Below is the discrete genetic trait analysis for this user.") description2 := getLabelCentered("You can choose to view the analysis of the user or an offspring between you and the user.") description3 := getLabelCentered("You must link your genome person in the Build Profile menu to see offspring information.") handleSelectButton := func(newUserOrOffspring string){ if (userOrOffspring == newUserOrOffspring){ return } setViewMateProfilePage_DiscreteGeneticTraits(window, newUserOrOffspring, getAnyUserProfileAttributeFunction, previousPage) } userOrOffspringSelector := widget.NewSelect([]string{"User", "Offspring"}, handleSelectButton) userOrOffspringSelector.Selected = userOrOffspring userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector) getTraitsInfoGrid := func()(*fyne.Container, error){ myPersonChosen, myGenomesExist, myAnalysisIsReady, myAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis() if (err != nil) { return nil, err } //Outputs: // -map[int64]locusValue.LocusValue // -error getMyGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){ if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){ emptyMap := make(map[int64]locusValue.LocusValue) return emptyMap, nil } _, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myAnalysisObject) if (err != nil) { return nil, err } myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier] if (exists == false){ return nil, errors.New("GetMyChosenMateGeneticAnalysis returning analysis which is missing genome with myGenomeIdentifier.") } return myGenomeLocusValuesMap, nil } myGenomeLocusValuesMap, err := getMyGenomeLocusValuesMap() if (err != nil) { return nil, err } emptyLabel1 := widget.NewLabel("") traitNameLabel := getItalicLabelCentered("Trait Name") emptyLabel2 := widget.NewLabel("") userPredictedOutcomeTitle := getItalicLabelCentered("User Predicted Outcome") emptyLabel3 := widget.NewLabel("") offspringOutcomeProbabilitiesLabel := getItalicLabelCentered("Offspring Outcome Probabilities") quantityOfLabel1 := getItalicLabelCentered("Quantity Of") lociKnownLabel := getItalicLabelCentered("Loci Known") emptyLabel4 := widget.NewLabel("") emptyLabel5 := widget.NewLabel("") traitNameColumn := container.NewVBox(emptyLabel1, traitNameLabel, widget.NewSeparator()) userPredictedOutcomeColumn := container.NewVBox(emptyLabel2, userPredictedOutcomeTitle, widget.NewSeparator()) offspringOutcomeProbabilitiesColumn := container.NewVBox(emptyLabel3, offspringOutcomeProbabilitiesLabel, widget.NewSeparator()) quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel1, lociKnownLabel, widget.NewSeparator()) viewTraitDetailsButtonsColumn := container.NewVBox(emptyLabel4, emptyLabel5, widget.NewSeparator()) traitObjectsList, err := traits.GetTraitObjectsList() if (err != nil) { return nil, err } for _, traitObject := range traitObjectsList{ traitName := traitObject.TraitName traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric if (traitIsDiscreteOrNumeric != "Discrete"){ continue } traitRulesList := traitObject.RulesList totalNumberOfTraitRules := len(traitRulesList) traitLociList := traitObject.LociList numberOfTraitLoci := len(traitLociList) if (totalNumberOfTraitRules == 0 && numberOfTraitLoci == 0){ // We are not able to analyze these traits yet continue } traitNeuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(traitName) if (traitNeuralNetworkExists == false && totalNumberOfTraitRules == 0){ // We are not able to analyze these traits yet continue } traitNameText := getBoldLabelCentered(translate(traitName)) traitNameColumn.Add(traitNameText) viewTraitDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ if (traitNeuralNetworkExists == true){ //TODO showUnderConstructionDialog(window) } else { setViewMateProfilePage_DiscreteTraitRules(window, traitName, userOrOffspring, getAnyUserProfileAttributeFunction, currentPage) } }) viewTraitDetailsButtonsColumn.Add(viewTraitDetailsButton) // We construct the user's trait locus values map userTraitLocusValuesMap, err := calculatedAttributes.GetUserGenomeLocusValuesMapFromProfile(traitLociList, getAnyUserProfileAttributeFunction) if (err != nil) { return nil, err } if (userOrOffspring == "User"){ if (traitNeuralNetworkExists == true){ traitNeuralNetworkExists, anyLocusValuesAreKnown, predictedOutcome, _, quantityOfLociKnown, _, err := createPersonGeneticAnalysis.GetGenomeDiscreteTraitAnalysis_NeuralNetwork(traitObject, userTraitLocusValuesMap, true) if (err != nil) { return nil, err } if (traitNeuralNetworkExists == false){ return nil, errors.New("GetGenomeTraitAnalysis_NeuralNetwork claims neural network doesn't exist for trait, but we already checked.") } if (anyLocusValuesAreKnown == false){ unknownLabel := getItalicLabelCentered(translate("Unknown")) userPredictedOutcomeColumn.Add(unknownLabel) } else { predictedOutcomeLabel := getBoldLabelCentered(predictedOutcome) userPredictedOutcomeColumn.Add(predictedOutcomeLabel) } totalNumberOfLociString := helpers.ConvertIntToString(numberOfTraitLoci) quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown) quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalNumberOfLociString quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted) quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel) } else { // We use the rules-based analysis anyRulesExist, _, quantityOfLociKnown_Rules, _, predictedOutcomeIsKnown, predictedOutcome, err := createPersonGeneticAnalysis.GetGenomeDiscreteTraitAnalysis_Rules(traitObject, userTraitLocusValuesMap, true) if (err != nil) { return nil, err } if (anyRulesExist == false){ return nil, errors.New("GetGenomeTraitAnalysis_Rules claims no rules exist when we already checked.") } if (predictedOutcomeIsKnown == false){ unknownLabel := getItalicLabelCentered("Unknown") userPredictedOutcomeColumn.Add(unknownLabel) } else { predictedOutcomeLabel := getBoldLabelCentered(predictedOutcome) userPredictedOutcomeColumn.Add(predictedOutcomeLabel) } traitLociList_Rules := traitObject.LociList_Rules totalQuantityOfLoci := len(traitLociList_Rules) quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown_Rules) totalQuantityOfLociString := helpers.ConvertIntToString(totalQuantityOfLoci) quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted) quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel) } } else { // userOrOffspring == "Offspring" if (traitNeuralNetworkExists == true){ neuralNetworkExists, anyLociKnown, outcomeProbabilitiesMap, _, quantityOfLociKnown, _, err := createCoupleGeneticAnalysis.GetOffspringDiscreteTraitAnalysis_NeuralNetwork(traitObject, userTraitLocusValuesMap, myGenomeLocusValuesMap) if (err != nil) { return nil, err } if (neuralNetworkExists == false){ return nil, errors.New("GetOffspringDiscreteTraitAnalysis_NeuralNetwork claiming that neural network doesn't exist when we already checked.") } totalNumberOfLociString := helpers.ConvertIntToString(numberOfTraitLoci) quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown) quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalNumberOfLociString quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted) quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel) if (anyLociKnown == false){ unknownLabel := getItalicLabelCentered("Unknown") offspringOutcomeProbabilitiesColumn.Add(unknownLabel) } else { outcomesList := helpers.GetListOfMapKeys(outcomeProbabilitiesMap) // We sort the outcomes in alphabetical order so they show up the same way each time slices.Sort(outcomesList) quantityOfAddedItems := 0 for _, outcomeName := range outcomesList{ outcomeProbability, exists := outcomeProbabilitiesMap[outcomeName] if (exists == false){ return nil, errors.New("GetListOfMapKeys returning element which doesn't exist in map.") } if (outcomeProbability == 0){ continue } outcomeProbabilityString := helpers.ConvertIntToString(outcomeProbability) outcomeProbabilityLabelText := outcomeName + ": " + outcomeProbabilityString + "%" outcomeProbabilityLabel := getBoldLabelCentered(outcomeProbabilityLabelText) offspringOutcomeProbabilitiesColumn.Add(outcomeProbabilityLabel) quantityOfAddedItems += 1 if (quantityOfAddedItems > 1){ // We add whitespace for the other columns traitNameColumn.Add(widget.NewLabel("")) quantityOfLociKnownColumn.Add(widget.NewLabel("")) viewTraitDetailsButtonsColumn.Add(widget.NewLabel("")) } } } } else { // We use the rules-based analysis anyRulesExist, rulesAnalysisExists, _, offspringQuantityOfLociKnown, _, outcomeProbabilitiesMap, err := createCoupleGeneticAnalysis.GetOffspringDiscreteTraitAnalysis_Rules(traitObject, myGenomeLocusValuesMap, userTraitLocusValuesMap) if (err != nil) { return nil, err } if (anyRulesExist == false){ return nil, errors.New("GetOffspringDiscreteTraitAnalysis_Rules claiming that no rules exist when we already checked.") } lociList_Rules := traitObject.LociList_Rules totalQuantityOfLoci := len(lociList_Rules) quantityOfLociKnownString := helpers.ConvertIntToString(offspringQuantityOfLociKnown) totalQuantityOfLociString := helpers.ConvertIntToString(totalQuantityOfLoci) quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted) quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel) if (rulesAnalysisExists == false){ unknownLabel := getItalicLabelCentered(translate("Unknown")) offspringOutcomeProbabilitiesColumn.Add(unknownLabel) } else { outcomesList := helpers.GetListOfMapKeys(outcomeProbabilitiesMap) // We sort the outcomes in alphabetical order so they show up the same way each time slices.Sort(outcomesList) quantityOfAddedItems := 0 for _, outcomeName := range outcomesList{ outcomeProbability, exists := outcomeProbabilitiesMap[outcomeName] if (exists == false){ return nil, errors.New("GetListOfMapKeys returning element which doesn't exist in map.") } if (outcomeProbability == 0){ continue } outcomeProbabilityString := helpers.ConvertIntToString(outcomeProbability) outcomeProbabilityLabelText := outcomeName + ": " + outcomeProbabilityString + "%" outcomeProbabilityLabel := getBoldLabelCentered(outcomeProbabilityLabelText) offspringOutcomeProbabilitiesColumn.Add(outcomeProbabilityLabel) quantityOfAddedItems += 1 if (quantityOfAddedItems > 1){ // We add whitespace for the other columns traitNameColumn.Add(widget.NewLabel("")) quantityOfLociKnownColumn.Add(widget.NewLabel("")) viewTraitDetailsButtonsColumn.Add(widget.NewLabel("")) } } } } } traitNameColumn.Add(widget.NewSeparator()) userPredictedOutcomeColumn.Add(widget.NewSeparator()) offspringOutcomeProbabilitiesColumn.Add(widget.NewSeparator()) quantityOfLociKnownColumn.Add(widget.NewSeparator()) viewTraitDetailsButtonsColumn.Add(widget.NewSeparator()) } if (userOrOffspring == "User"){ predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ //TODO showUnderConstructionDialog(window) }) userPredictedOutcomeColumn.Add(predictedOutcomeHelpButton) } else { // userOrOffspring == "Offspring" outcomeProbabilitiesHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ //TODO showUnderConstructionDialog(window) }) offspringOutcomeProbabilitiesColumn.Add(outcomeProbabilitiesHelpButton) } quantityOfLociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ //TODO showUnderConstructionDialog(window) }) quantityOfLociKnownColumn.Add(quantityOfLociKnownHelpButton) traitsInfoGrid := container.NewHBox(layout.NewSpacer(), traitNameColumn) if (userOrOffspring == "User"){ traitsInfoGrid.Add(userPredictedOutcomeColumn) } else { // userOrOffspring == "Offspring" traitsInfoGrid.Add(offspringOutcomeProbabilitiesColumn) } traitsInfoGrid.Add(quantityOfLociKnownColumn) traitsInfoGrid.Add(viewTraitDetailsButtonsColumn) traitsInfoGrid.Add(layout.NewSpacer()) return traitsInfoGrid, nil } traitsInfoGrid, err := getTraitsInfoGrid() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), userOrOffspringSelectorCentered, widget.NewSeparator(), traitsInfoGrid) setPageContent(page, window) } func setViewMateProfilePage_DiscreteTraitRules(window fyne.Window, traitName string, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ currentPage := func(){setViewMateProfilePage_DiscreteTraitRules(window, traitName, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)} title := getPageTitleCentered("View Profile - Physical") backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(userOrOffspring + " Trait Rules") traitNameLabel := widget.NewLabel("Trait Name:") traitNameText := getBoldLabel(traitName) viewTraitInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){ setViewTraitDetailsPage(window, traitName, currentPage) }) traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameLabel, traitNameText, viewTraitInfoButton, layout.NewSpacer()) //Outputs: // -bool: Any trait locus value exists for this myself // -map[int64]locusValue.LocusValue: My locus values map // -error getMyGenomeLocusValuesMap := func()(bool, map[int64]locusValue.LocusValue, error){ myPersonChosen, myGenomesExist, myAnalysisIsReady, myAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis() if (err != nil) { return false, nil, err } if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){ // Without my genome person chosen, all offspring rule probabilities are unknown return false, nil, nil } _, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myAnalysisObject) if (err != nil){ return false, nil, err } myGenomeMap, exists := myGenomesMap[myGenomeIdentifier] if (exists == false){ return false, nil, errors.New("GetMyChosenMateGeneticAnalysis returning analysis which does not contain genome matching myGenomeIdentifier") } return true, myGenomeMap, nil } anyMyLocusValuesExist, myLocusValuesMap, err := getMyGenomeLocusValuesMap() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } traitObject, err := traits.GetTraitObject(traitName) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } traitLociList_Rules := traitObject.LociList_Rules traitRulesList := traitObject.RulesList if (len(traitRulesList) == 0){ setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_DiscreteTraitRules called with trait which has no rules."), previousPage) return } //Outputs: // -bool: Any trait locus value exists for this user // -map[int64]locusValue.LocusValue: User locus values map // -error getUserTraitLocusValuesMap := func()(bool, map[int64]locusValue.LocusValue, error){ // We construct the user's trait locus values map userTraitLocusValuesMap, err := calculatedAttributes.GetUserGenomeLocusValuesMapFromProfile(traitLociList_Rules, getAnyUserProfileAttributeFunction) if (err != nil) { return false, nil, err } if (len(userTraitLocusValuesMap) == 0){ return false, nil, nil } return true, userTraitLocusValuesMap, nil } anyUserTraitLocusValueExists, userTraitLocusValuesMap, err := getUserTraitLocusValuesMap() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } //Outputs: // -bool: Any offspring probability of passing rule is known // -map[[3]byte]int: Offspring probability of passing rules map // Map Structure: Rule Identifier -> Probability offspring will pass rule (0-100%) // -error getOffspringProbabilityOfPassingRulesMap := func()(bool, map[[3]byte]int, error){ if (anyMyLocusValuesExist == false || anyUserTraitLocusValueExists == false){ return false, nil, nil } anyRulesExist, anyOffspringRulesTested, _, _, offspringProbabilityOfPassingRulesMap, _, err := createCoupleGeneticAnalysis.GetOffspringDiscreteTraitAnalysis_Rules(traitObject, myLocusValuesMap, userTraitLocusValuesMap) if (err != nil) { return false, nil, err } if (anyRulesExist == false){ return false, nil, errors.New("GetOffspringDiscreteTraitAnalysis_Rules claiming no trait rules exist when we already checked.") } if (anyOffspringRulesTested == false){ return false, nil, nil } return true, offspringProbabilityOfPassingRulesMap, nil } anyOffspringProbabilityOfPassingRuleIsKnown, offspringProbabilityOfPassingRulesMap, err := getOffspringProbabilityOfPassingRulesMap() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } getNumberOfRulesTested := func()(int, error){ if (userOrOffspring == "Offspring"){ if (anyOffspringProbabilityOfPassingRuleIsKnown == false){ return 0, nil } numberOfRulesTested := len(offspringProbabilityOfPassingRulesMap) return numberOfRulesTested, nil } if (anyUserTraitLocusValueExists == false){ return 0, nil } numberOfRulesTested := 0 for _, ruleObject := range traitRulesList{ ruleLociList := ruleObject.LociList ruleStatusIsKnown, _, err := createPersonGeneticAnalysis.GetGenomePassesDiscreteTraitRuleStatus(ruleLociList, userTraitLocusValuesMap, true) if (err != nil) { return 0, err } if (ruleStatusIsKnown == true){ numberOfRulesTested += 1 } } return numberOfRulesTested, nil } numberOfRulesTested, err := getNumberOfRulesTested() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } rulesTestedLabel := widget.NewLabel("Rules Tested:") totalNumberOfTraitRules := len(traitRulesList) numberOfRulesTestedString := helpers.ConvertIntToString(numberOfRulesTested) totalNumberOfTraitRulesString := helpers.ConvertIntToString(totalNumberOfTraitRules) numberOfRulesTestedFormatted := numberOfRulesTestedString + "/" + totalNumberOfTraitRulesString numberOfRulesTestedLabel := getBoldLabel(numberOfRulesTestedFormatted) numberOfRulesTestedHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ if (userOrOffspring == "User"){ setDiscreteTraitQuantityOfRulesTestedExplainerPage(window, currentPage) } else { setOffspringDiscreteTraitQuantityOfRulesTestedExplainerPage(window, currentPage) } }) numberOfRulesTestedRow := container.NewHBox(layout.NewSpacer(), rulesTestedLabel, numberOfRulesTestedLabel, numberOfRulesTestedHelpButton, layout.NewSpacer()) getRulesGrid := func()(*fyne.Container, error){ //TODO: Sort results viewRuleInfoButtonsColumn := container.NewVBox() ruleIdentifierColumn := container.NewVBox() ruleEffectsColumn := container.NewVBox() userPassesRuleColumn := container.NewVBox() offspringProbabilityOfPassingRuleColumn := container.NewVBox() if (userOrOffspring == "Offspring"){ // We need this because the header row is 2 rows tall when userOrOffspring == "Offspring" // Otherwise, it is 1 row tall emptyLabelA := widget.NewLabel("") emptyLabelB := widget.NewLabel("") emptyLabelC := widget.NewLabel("") emptyLabelD := widget.NewLabel("") viewRuleInfoButtonsColumn.Add(emptyLabelA) ruleIdentifierColumn.Add(emptyLabelB) ruleEffectsColumn.Add(emptyLabelC) userPassesRuleColumn.Add(emptyLabelD) } offspringProbabilityOfTitle := getItalicLabelCentered("Offspring Probability Of") offspringProbabilityOfPassingRuleColumn.Add(offspringProbabilityOfTitle) emptyLabelE := widget.NewLabel("") ruleIdentifierTitle := getItalicLabelCentered("Rule Identifier") ruleEffectsTitle := getItalicLabelCentered("Rule Effects") userPassesRuleTitle := getItalicLabelCentered("User Passes Rule") passingRuleTitle := getItalicLabelCentered("Passing Rule") viewRuleInfoButtonsColumn.Add(emptyLabelE) ruleIdentifierColumn.Add(ruleIdentifierTitle) ruleEffectsColumn.Add(ruleEffectsTitle) userPassesRuleColumn.Add(userPassesRuleTitle) offspringProbabilityOfPassingRuleColumn.Add(passingRuleTitle) viewRuleInfoButtonsColumn.Add(widget.NewSeparator()) ruleIdentifierColumn.Add(widget.NewSeparator()) ruleEffectsColumn.Add(widget.NewSeparator()) userPassesRuleColumn.Add(widget.NewSeparator()) offspringProbabilityOfPassingRuleColumn.Add(widget.NewSeparator()) for _, ruleObject := range traitRulesList{ ruleIdentifierHex := ruleObject.RuleIdentifier ruleIdentifier, err := encoding.DecodeHexStringTo3ByteArray(ruleIdentifierHex) if (err != nil) { return nil, err } ruleLociList := ruleObject.LociList getUserPassesRuleString := func()(string, error){ userRuleStatusIsKnown, userPassesRule, err := createPersonGeneticAnalysis.GetGenomePassesDiscreteTraitRuleStatus(ruleLociList, userTraitLocusValuesMap, true) if (err != nil) { return "", err } if (userRuleStatusIsKnown == false){ result := translate("Unknown") return result, nil } userPassesRuleString := helpers.ConvertBoolToYesOrNoString(userPassesRule) userPassesRuleTranslated := translate(userPassesRuleString) return userPassesRuleTranslated, nil } userPassesRuleString, err := getUserPassesRuleString() if (err != nil) { return nil, err } getOffspringProbabilityOfPassingRuleString := func()(string, error){ if (anyOffspringProbabilityOfPassingRuleIsKnown == false){ result := translate("Unknown") return result, nil } probabilityOfPassingRule, probabilityIsKnown := offspringProbabilityOfPassingRulesMap[ruleIdentifier] if (probabilityIsKnown == false){ result := translate("Unknown") return result, nil } ruleProbabilityString := helpers.ConvertIntToString(probabilityOfPassingRule) ruleProbabilityFormatted := ruleProbabilityString + "%" return ruleProbabilityFormatted, nil } offspringProbabilityOfPassingRuleString, err := getOffspringProbabilityOfPassingRuleString() if (err != nil) { return nil, err } // We add all of the columns except for the rule effects column // We do this because the rule effects column may be multiple rows tall viewRuleInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){ setViewDiscreteTraitRuleDetailsPage(window, traitName, ruleIdentifierHex, currentPage) }) ruleIdentifierLabel := getBoldLabelCentered(ruleIdentifierHex) userPassesRuleLabel := getBoldLabelCentered(userPassesRuleString) offspringProbabilityOfPassingRuleLabel := getBoldLabelCentered(offspringProbabilityOfPassingRuleString) viewRuleInfoButtonsColumn.Add(viewRuleInfoButton) ruleIdentifierColumn.Add(ruleIdentifierLabel) userPassesRuleColumn.Add(userPassesRuleLabel) offspringProbabilityOfPassingRuleColumn.Add(offspringProbabilityOfPassingRuleLabel) traitOutcomesList := traitObject.OutcomesList ruleOutcomePointsMap := ruleObject.OutcomePointsMap // We have to sort the outcome names so they always show up in the same order outcomeNamesListSorted := helpers.CopyAndSortStringListToUnicodeOrder(traitOutcomesList) addedOutcomes := 0 for _, outcomeName := range outcomeNamesListSorted{ outcomeChange, exists := ruleOutcomePointsMap[outcomeName] if (exists == false){ // This rule does not effect this outcome. Skip continue } getOutcomeEffectString := func()string{ outcomeChangeString := helpers.ConvertIntToString(outcomeChange) if (outcomeChange < 0){ return outcomeChangeString } outcomeEffect := "+" + outcomeChangeString return outcomeEffect } outcomeEffect := getOutcomeEffectString() outcomeRow := getBoldLabelCentered(outcomeName + ": " + outcomeEffect) ruleEffectsColumn.Add(outcomeRow) if (addedOutcomes > 0){ emptyLabelA := widget.NewLabel("") emptyLabelB := widget.NewLabel("") emptyLabelC := widget.NewLabel("") emptyLabelD := widget.NewLabel("") viewRuleInfoButtonsColumn.Add(emptyLabelA) ruleIdentifierColumn.Add(emptyLabelB) userPassesRuleColumn.Add(emptyLabelC) offspringProbabilityOfPassingRuleColumn.Add(emptyLabelD) } addedOutcomes += 1 } viewRuleInfoButtonsColumn.Add(widget.NewSeparator()) ruleIdentifierColumn.Add(widget.NewSeparator()) ruleEffectsColumn.Add(widget.NewSeparator()) userPassesRuleColumn.Add(widget.NewSeparator()) offspringProbabilityOfPassingRuleColumn.Add(widget.NewSeparator()) } ruleEffectsHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setDiscreteTraitRuleOutcomeEffectsExplainerPage(window, currentPage) }) userPassesRuleHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setPersonPassesDiscreteTraitRuleExplainerPage(window, currentPage) }) offspringProbabilityOfPassingRuleHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setOffspringProbabilityOfPassingTraitRuleExplainerPage(window, currentPage) }) ruleEffectsColumn.Add(ruleEffectsHelpButton) userPassesRuleColumn.Add(userPassesRuleHelpButton) offspringProbabilityOfPassingRuleColumn.Add(offspringProbabilityOfPassingRuleHelpButton) if (userOrOffspring == "User"){ rulesGrid := container.NewHBox(layout.NewSpacer(), viewRuleInfoButtonsColumn, ruleIdentifierColumn, ruleEffectsColumn, userPassesRuleColumn, layout.NewSpacer()) return rulesGrid, nil } rulesGrid := container.NewHBox(layout.NewSpacer(), viewRuleInfoButtonsColumn, ruleIdentifierColumn, ruleEffectsColumn, offspringProbabilityOfPassingRuleColumn, layout.NewSpacer()) return rulesGrid, nil } rulesGrid, err := getRulesGrid() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), traitNameRow, numberOfRulesTestedRow, widget.NewSeparator(), rulesGrid) setPageContent(page, window) } func setViewMateProfilePage_NumericGeneticTraits(window fyne.Window, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ if (userOrOffspring != "User" && userOrOffspring != "Offspring"){ setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_NumericGeneticTraits called with invalid userOrOffspring: " + userOrOffspring), previousPage) return } if (userOrOffspring == "Offspring"){ setLoadingScreen(window, "View Profile - Physical", "Computing Genetic Analysis...") } currentPage := func(){setViewMateProfilePage_NumericGeneticTraits(window, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)} title := getPageTitleCentered(translate("View Profile - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Numeric Genetic Traits")) description1 := getLabelCentered("Below is the numeric genetic trait analysis for this user.") description2 := getLabelCentered("You can choose to view the analysis of the user or an offspring between you and the user.") description3 := getLabelCentered("You must link your genome person in the Build Profile menu to see offspring information.") handleSelectButton := func(newUserOrOffspring string){ if (userOrOffspring == newUserOrOffspring){ return } setViewMateProfilePage_NumericGeneticTraits(window, newUserOrOffspring, getAnyUserProfileAttributeFunction, previousPage) } userOrOffspringSelector := widget.NewSelect([]string{"User", "Offspring"}, handleSelectButton) userOrOffspringSelector.Selected = userOrOffspring userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector) getTraitsInfoGrid := func()(*fyne.Container, error){ //Outputs: // -map[int64]locusValue.LocusValue // -error getMyGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){ if (userOrOffspring == "User"){ // We don't need to retrieve our locus values map emptyMap := make(map[int64]locusValue.LocusValue) return emptyMap, nil } myPersonChosen, myGenomesExist, myAnalysisIsReady, myAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis() if (err != nil) { return nil, err } if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){ emptyMap := make(map[int64]locusValue.LocusValue) return emptyMap, nil } _, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myAnalysisObject) if (err != nil) { return nil, err } myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier] if (exists == false){ return nil, errors.New("GetMyChosenMateGeneticAnalysis returning analysis which is missing genome with myGenomeIdentifier.") } return myGenomeLocusValuesMap, nil } myGenomeLocusValuesMap, err := getMyGenomeLocusValuesMap() if (err != nil) { return nil, err } traitNameLabel := getItalicLabelCentered("Trait Name") userPredictedOutcomeTitle := getItalicLabelCentered("User Predicted Outcome") offspringPredictedOutcomeTitle := getItalicLabelCentered("Offspring Predicted Outcome") confidenceRangeTitle := getItalicLabelCentered("Confidence Range") emptyLabel1 := widget.NewLabel("") emptyLabel2 := widget.NewLabel("") traitNameColumn := container.NewVBox(traitNameLabel, widget.NewSeparator()) userPredictedOutcomeColumn := container.NewVBox(userPredictedOutcomeTitle, widget.NewSeparator()) offspringPredictedOutcomeColumn := container.NewVBox(offspringPredictedOutcomeTitle, widget.NewSeparator()) predictionConfidenceRangeColumn := container.NewVBox(confidenceRangeTitle, widget.NewSeparator()) viewSampleOffspringsButtonsColumn := container.NewVBox(emptyLabel1, widget.NewSeparator()) viewTraitDetailsButtonsColumn := container.NewVBox(emptyLabel2, widget.NewSeparator()) getConfidenceRangeLabel := func(traitObject traits.Trait, analysisExists bool, predictionConfidenceRangesMap map[int]float64)(fyne.Widget, error){ if (analysisExists == false){ result := widget.NewLabel("Unknown") return result, nil } // This is a list of the percentage accuracies in the map // For example: 80% == The distance from the prediction you must travel for 80% of the predictions to be // accurate within that range confidenceRangePercentagesList := helpers.GetListOfMapKeys(predictionConfidenceRangesMap) // We sort the list so the percentage is always the same upon refreshing the page slices.Sort(confidenceRangePercentagesList) closestToEightyPercentage, err := helpers.GetClosestIntInList(confidenceRangePercentagesList, 80) if (err != nil) { return nil, err } closestToEightyPercentageConfidenceDistance, exists := predictionConfidenceRangesMap[closestToEightyPercentage] if (exists == false){ return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.") } outcomeFormatter := traitObject.NumericValueFormatter closestConfidenceDistanceString, err := outcomeFormatter(closestToEightyPercentageConfidenceDistance, false) if (err != nil) { return nil, err } closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage) confidenceRangeLabelValueFormatted := "+/- " + closestConfidenceDistanceString + " (" + closestToEightyPercentageString + "%)" confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted) return confidenceRangeLabel, nil } traitObjectsList, err := traits.GetTraitObjectsList() if (err != nil) { return nil, err } for _, traitObject := range traitObjectsList{ traitName := traitObject.TraitName traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric if (traitIsDiscreteOrNumeric != "Numeric"){ continue } traitLociList := traitObject.LociList numberOfTraitLoci := len(traitLociList) if (numberOfTraitLoci == 0){ // We are not able to analyze these traits continue } traitNeuralNetworkExists := trainedPredictionModels.CheckIfAttributeNeuralNetworkExists(traitName) if (traitNeuralNetworkExists == false){ // We are not able to analyze these traits yet continue } traitNameText := getBoldLabelCentered(translate(traitName)) // We construct the user's trait locus values map userTraitLocusValuesMap, err := calculatedAttributes.GetUserGenomeLocusValuesMapFromProfile(traitLociList, getAnyUserProfileAttributeFunction) if (err != nil) { return nil, err } getPredictedOutcomeLabel := func(analysisExists bool, predictedOutcome float64)(fyne.Widget, error){ if (analysisExists == false){ unknownLabel := widget.NewLabel("Unknown") return unknownLabel, nil } outcomeFormatter := traitObject.NumericValueFormatter predictedOutcomeFormatted, err := outcomeFormatter(predictedOutcome, true) if (err != nil) { return nil, err } predictedOutcomeLabel := getBoldLabel(predictedOutcomeFormatted) return predictedOutcomeLabel, nil } if (userOrOffspring == "User"){ traitNeuralNetworkExists, anyLocusValuesAreKnown, predictedOutcome, predictionAccuracyRangesMap, _, _, err := createPersonGeneticAnalysis.GetGenomeNumericTraitAnalysis(traitObject, userTraitLocusValuesMap, true) if (err != nil) { return nil, err } if (traitNeuralNetworkExists == false){ return nil, errors.New("GetGenomeNumericTraitAnalysis claims neural network doesn't exist for trait, but we already checked.") } predictedOutcomeLabel, err := getPredictedOutcomeLabel(anyLocusValuesAreKnown, predictedOutcome) if (err != nil) { return nil, err } predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel) userPredictedOutcomeColumn.Add(predictedOutcomeLabelCentered) confidenceRangeLabel, err := getConfidenceRangeLabel(traitObject, anyLocusValuesAreKnown, predictionAccuracyRangesMap) if (err != nil) { return nil, err } confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel) predictionConfidenceRangeColumn.Add(confidenceRangeLabelCentered) } else { // userOrOffspring == "Offspring" neuralNetworkExists, anyLociKnown, predictedOutcome, predictionAccuracyRangesMap, sampleOffspringOutcomesList, quantityOfLociKnown, _, err := createCoupleGeneticAnalysis.GetOffspringNumericTraitAnalysis(traitObject, userTraitLocusValuesMap, myGenomeLocusValuesMap) if (err != nil) { return nil, err } if (neuralNetworkExists == false){ return nil, errors.New("GetOffspringNumericTraitAnalysis claiming that neural network doesn't exist when we already checked.") } predictedOutcomeLabel, err := getPredictedOutcomeLabel(anyLociKnown, predictedOutcome) if (err != nil) { return nil, err } predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel) offspringPredictedOutcomeColumn.Add(predictedOutcomeLabelCentered) confidenceRangeLabel, err := getConfidenceRangeLabel(traitObject, anyLociKnown, predictionAccuracyRangesMap) if (err != nil) { return nil, err } confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel) predictionConfidenceRangeColumn.Add(confidenceRangeLabelCentered) viewSampleOffspringsButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){ setViewNumericTraitSampleOffspringOutcomesChart(window, traitName, sampleOffspringOutcomesList, quantityOfLociKnown, currentPage) }) viewSampleOffspringsButtonsColumn.Add(viewSampleOffspringsButton) } viewTraitDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ //TODO showUnderConstructionDialog(window) }) traitNameColumn.Add(traitNameText) viewTraitDetailsButtonsColumn.Add(viewTraitDetailsButton) traitNameColumn.Add(widget.NewSeparator()) userPredictedOutcomeColumn.Add(widget.NewSeparator()) offspringPredictedOutcomeColumn.Add(widget.NewSeparator()) predictionConfidenceRangeColumn.Add(widget.NewSeparator()) viewSampleOffspringsButtonsColumn.Add(widget.NewSeparator()) viewTraitDetailsButtonsColumn.Add(widget.NewSeparator()) } predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ //TODO showUnderConstructionDialog(window) }) predictionConfidenceRangeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ //TODO showUnderConstructionDialog(window) }) predictionConfidenceRangeColumn.Add(predictionConfidenceRangeHelpButton) traitsInfoGrid := container.NewHBox(layout.NewSpacer(), traitNameColumn) if (userOrOffspring == "User"){ userPredictedOutcomeColumn.Add(predictedOutcomeHelpButton) traitsInfoGrid.Add(userPredictedOutcomeColumn) } else { // userOrOffspring == "Offspring" offspringPredictedOutcomeColumn.Add(predictedOutcomeHelpButton) traitsInfoGrid.Add(offspringPredictedOutcomeColumn) } traitsInfoGrid.Add(predictionConfidenceRangeColumn) if (userOrOffspring == "Offspring"){ traitsInfoGrid.Add(viewSampleOffspringsButtonsColumn) } traitsInfoGrid.Add(viewTraitDetailsButtonsColumn) traitsInfoGrid.Add(layout.NewSpacer()) return traitsInfoGrid, nil } traitsInfoGrid, err := getTraitsInfoGrid() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), userOrOffspringSelectorCentered, widget.NewSeparator(), traitsInfoGrid) setPageContent(page, window) } func setViewMateProfilePage_Diet(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ title := getPageTitleCentered(translate("View Profile - Lifestyle")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Diet") description := getLabelCentered("Below are the user's food ratings.") foodNameTitle := getItalicLabelCentered("Food Name") ratingTitle := getItalicLabelCentered("Rating") foodNameColumn := container.NewVBox(foodNameTitle, widget.NewSeparator()) foodRatingColumn := container.NewVBox(ratingTitle, widget.NewSeparator()) foodsList := []string{"Fruit", "Vegetables", "Nuts", "Grains", "Dairy", "Seafood", "Beef", "Pork", "Poultry", "Eggs", "Beans"} for _, foodName := range foodsList{ foodAttributeName := foodName + "Rating" ratingExists, _, foodRating, err := getAnyUserProfileAttributeFunction(foodAttributeName) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } foodNameLabel := getBoldLabelCentered(foodName) getRatingLabel := func()*fyne.Container{ if (ratingExists == false){ result := getBoldItalicLabelCentered(translate("No Response")) return result } result := getBoldLabelCentered(foodRating + "/10") return result } ratingLabel := getRatingLabel() foodNameColumn.Add(foodNameLabel) foodRatingColumn.Add(ratingLabel) foodNameColumn.Add(widget.NewSeparator()) foodRatingColumn.Add(widget.NewSeparator()) } foodsGrid := container.NewHBox(layout.NewSpacer(), foodNameColumn, foodRatingColumn, layout.NewSpacer()) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator(), foodsGrid) setPageContent(page, window) } func setViewMateProfilePage_Language(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){ title := getPageTitleCentered(translate("View Profile - Mental")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Language")) description := getLabelCentered("Below describes the language(s) the user can speak.") userLanguagesExist, _, userLanguageAttribute, err := getAnyUserProfileAttributeFunction("Language") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (userLanguagesExist == false){ noLanguagesExistLabel := getBoldLabelCentered("This user's profile has no languages listed.") page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator(), noLanguagesExistLabel) setPageContent(page, window) return } getLanguagesGrid := func()(*fyne.Container, error){ //TODO: Sort by fluency languageTitle := getItalicLabelCentered("Language") fluencyTitle := getItalicLabelCentered("Fluency") languageNameColumn := container.NewVBox(languageTitle, widget.NewSeparator()) languageFluencyColumn := container.NewVBox(fluencyTitle, widget.NewSeparator()) languageItemsList := strings.Split(userLanguageAttribute, "+&") worldLanguageObjectsMap, err := worldLanguages.GetWorldLanguageObjectsMap() if (err != nil) { return nil, err } for _, languageItem := range languageItemsList{ languageName, languageRating, delimiterFound := strings.Cut(languageItem, "$") if (delimiterFound == false){ return nil, errors.New("setViewMateProfilePage_Language called with profile containing invalid language attribute item: " + languageItem) } getLanguageNameFormatted := func()string{ // We only translate if language name is canonical _, languageIsCanonical := worldLanguageObjectsMap[languageName] if (languageIsCanonical == false){ return languageName } languageNameTranslated := translate(languageName) return languageNameTranslated } languageNameFormatted := getLanguageNameFormatted() languageFluencyFormatted := languageRating + "/5" languageNameLabel := getBoldLabelCentered(languageNameFormatted) languageFluencyLabel := getBoldLabelCentered(languageFluencyFormatted) languageNameColumn.Add(languageNameLabel) languageFluencyColumn.Add(languageFluencyLabel) languageNameColumn.Add(widget.NewSeparator()) languageFluencyColumn.Add(widget.NewSeparator()) } languagesGrid := container.NewHBox(layout.NewSpacer(), languageNameColumn, languageFluencyColumn, layout.NewSpacer()) return languagesGrid, nil } languagesGrid, err := getLanguagesGrid() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator(), languagesGrid) setPageContent(page, window) } // We use this to view an ancestry composition for moderation/comparing profile changes func setViewUser23andMeAncestryCompositionPage(window fyne.Window, inputAncestryCompositionAttribute string, previousPage func()){ title := getPageTitleCentered("Viewing 23andMe Ancestry Composition") backButton := getBackButtonCentered(previousPage) attributeIsValid, continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap, err := companyAnalysis.ReadAncestryCompositionAttribute_23andMe(true, inputAncestryCompositionAttribute) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (attributeIsValid == false){ setErrorEncounteredPage(window, errors.New("setViewUser23andMeAncestryCompositionPage called with invalid inputAncestryCompositionAttribute"), previousPage) return } userCompositionDisplay, err := get23andMeAncestryCompositionDisplay(continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } header := container.NewVBox(title, backButton, widget.NewSeparator()) page := container.NewBorder(header, nil, nil, nil, userCompositionDisplay) setPageContent(page, window) } func get23andMeAncestryCompositionDisplay(inputContinentPercentagesMap map[string]float64, inputRegionPercentagesMap map[string]float64, inputSubregionPercentagesMap map[string]float64)(fyne.Widget, error){ mapsAreValid, continentPercentagesMap, regionPercentagesMap, subregionPercentagesMap, err := companyAnalysis.AddMissingParentsToAncestryCompositionMaps_23andMe(inputContinentPercentagesMap, inputRegionPercentagesMap, inputSubregionPercentagesMap) if (err != nil){ return nil, err } if (mapsAreValid == false){ return nil, errors.New("get23andMeAncestryCompositionDisplay called with invalid ancestry location maps.") } // This map is used to create a fyne tree widget // Each item maps to a list of child items treeMap := make(map[string][]string) continentsList := make([]string, 0, len(continentPercentagesMap)) // Map Structure: Continent Description -> Continent percentage continentDescriptionPercentagesMap := make(map[string]float64) for continentName, continentPercentage := range continentPercentagesMap{ continentPercentageString := helpers.ConvertFloat64ToStringRounded(continentPercentage, 1) continentDescription := continentName + " - " + continentPercentageString + "%" continentDescriptionPercentagesMap[continentDescription] = continentPercentage continentsList = append(continentsList, continentDescription) currentContinentRegionsList := make([]string, 0) allContinentRegionsList, err := companyAnalysis.GetAncestryContinentRegionsList_23andMe(continentName) if (err != nil) { return nil, errors.New("continentPercentagesMap contains invalid continent.") } // Map Structure: Region Description -> Region percentage regionDescriptionPercentagesMap := make(map[string]float64) for regionName, regionPercentage := range regionPercentagesMap{ regionIsRelevant := slices.Contains(allContinentRegionsList, regionName) if (regionIsRelevant == false){ continue } regionPercentageString := helpers.ConvertFloat64ToStringRounded(regionPercentage, 1) regionDescription := regionName + " - " + regionPercentageString + "%" regionDescriptionPercentagesMap[regionDescription] = regionPercentage currentContinentRegionsList = append(currentContinentRegionsList, regionDescription) allRegionSubregionsList, err := companyAnalysis.GetAncestryRegionSubregionsList_23andMe(continentName, regionName) if (err != nil){ return nil, err } currentRegionSubregionsList := make([]string, 0, len(subregionPercentagesMap)) // Map Structure: Subregion description -> Subregion percentage subregionDescriptionPercentagesMap := make(map[string]float64) for subregionName, subregionPercentage := range subregionPercentagesMap{ subregionIsRelevant := slices.Contains(allRegionSubregionsList, subregionName) if (subregionIsRelevant == false){ continue } subregionPercentageString := helpers.ConvertFloat64ToStringRounded(subregionPercentage, 1) subregionDescription := subregionName + " - " + subregionPercentageString + "%" subregionDescriptionPercentagesMap[subregionDescription] = subregionPercentage currentRegionSubregionsList = append(currentRegionSubregionsList, subregionDescription) } // We sort region subregions list // We sort them from highest to lowest in percentage. compareSubregionsFunction := func(subregion1Description string, subregion2Description string)int{ if (subregion1Description == subregion2Description){ panic("compareSubregionsFunction called with identical subregion descriptions.") } subregion1Percentage, exists := subregionDescriptionPercentagesMap[subregion1Description] if (exists == false){ panic("subregionPercentagesMap missing subregion during sort.") } subregion2Percentage, exists := subregionDescriptionPercentagesMap[subregion2Description] if (exists == false){ panic("subregionPercentagesMap missing subregion during sort.") } if (subregion1Percentage == subregion2Percentage){ // We sort subregions in unicode order if (subregion1Description < subregion2Description){ return -1 } return 1 } if (subregion1Percentage > subregion2Percentage){ return -1 } return 1 } slices.SortFunc(currentRegionSubregionsList, compareSubregionsFunction) treeMap[regionDescription] = currentRegionSubregionsList } // We sort continent regions list by highest to lowest percentage. compareRegionsFunction := func(region1Description string, region2Description string)int{ if (region1Description == region2Description){ panic("compareRegionsFunction called with identical regions.") } region1Percentage, exists := regionDescriptionPercentagesMap[region1Description] if (exists == false){ panic("regionPercentagesMap missing subregion during sort.") } region2Percentage, exists := regionDescriptionPercentagesMap[region2Description] if (exists == false){ panic("regionPercentagesMap missing subregion during sort.") } if (region1Percentage == region2Percentage){ // We sort regions in unicode order if (region1Description < region2Description){ return -1 } return 1 } if (region1Percentage > region2Percentage){ return -1 } return 1 } slices.SortFunc(currentContinentRegionsList, compareRegionsFunction) treeMap[continentDescription] = currentContinentRegionsList } // We sort root list by highest to lowest proportions compareContinentsFunction := func(continent1Description string, continent2Description string)int{ if (continent1Description == continent2Description){ panic("compareContinentsFunction called with identical continents.") } continent1Percentage, exists := continentDescriptionPercentagesMap[continent1Description] if (exists == false){ panic("Continent percentage not found when sorting root list.") } continent2Percentage, exists := continentDescriptionPercentagesMap[continent2Description] if (exists == false){ panic("Continent percentage not found when sorting root list.") } if (continent1Percentage == continent2Percentage){ // We sort continents in unicode order if (continent1Description < continent2Description){ return -1 } return 1 } if (continent1Percentage > continent2Percentage){ return -1 } return 1 } slices.SortFunc(continentsList, compareContinentsFunction) treeMap[""] = continentsList resultTree := widget.NewTreeWithStrings(treeMap) resultTree.OpenAllBranches() return resultTree, nil }