package gui // desiresGui_Physical.go implements pages to manage a user's Physical mate desires 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/internal/allowedText" import "seekia/internal/desires/mateDesires" import "seekia/internal/desires/myLocalDesires" import "seekia/internal/encoding" import "seekia/internal/genetics/companyAnalysis" import "seekia/internal/globalSettings" import "seekia/internal/helpers" import "seekia/internal/profiles/myLocalProfiles" import "strings" import "errors" import "slices" func setChooseDesiresCategoryPage_Physical(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresCategoryPage_Physical(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) sexButton := widget.NewButton(translate("Sex"), func(){ setChooseDesiresPage_Sex(window, currentPage) }) ageButton := widget.NewButton(translate("Age"), func(){ setChooseDesiresPage_Age(window, currentPage) }) ancestryCompositionButton := widget.NewButton("Ancestry Composition", func(){ setChooseDesiresPage_23andMe_AncestryComposition(window, "User", currentPage) }) neanderthalVariantsButton := widget.NewButton("Neanderthal Variants", func(){ setChooseDesiresPage_23andMe_NeanderthalVariants(window, currentPage) }) maternalHaplogroupButton := widget.NewButton("Maternal Haplogroup", func(){ setChooseDesiresPage_23andMe_Haplogroup(window, "Maternal", currentPage) }) paternalHaplogroupButton := widget.NewButton("Paternal Haplogroup", func(){ setChooseDesiresPage_23andMe_Haplogroup(window, "Paternal", currentPage) }) monogenicDiseasesButton := widget.NewButton("Monogenic Diseases", func(){ setChooseDesiresPage_MonogenicDiseases(window, currentPage) }) polygenicDiseasesButton := widget.NewButton("Polygenic Diseases", func(){ //TODO: Filter based on minimum variants tested // Users will be able to sort matches based on lowest overall disease risk showUnderConstructionDialog(window) }) geneticTraitsButton := widget.NewButton("Genetic Traits", func(){ //TODO: Filter based on number of variants tested // Users will be able to sort matches based on highest probability of blue eyes, brown eyes, straight hair, curly hair, etc.. // Also, users should be able to filter based on outcome points for each trait outcome showUnderConstructionDialog(window) }) heightButton := widget.NewButton(translate("Height"), func(){ setChooseDesiresPage_Height(window, currentPage) }) bodyFatButton := widget.NewButton(translate("Body Fat"), func(){ setChooseDesiresPage_BodyFat(window, currentPage) }) bodyMuscleButton := widget.NewButton(translate("Body Muscle"), func(){ setChooseDesiresPage_BodyMuscle(window, currentPage) }) eyeColorButton := widget.NewButton(translate("Eye Color"), func(){ setChooseDesiresPage_EyeColor(window, currentPage) }) hairColorButton := widget.NewButton(translate("Hair Color"), func(){ setChooseDesiresPage_HairColor(window, currentPage) }) hairTextureButton := widget.NewButton(translate("Hair Texture"), func(){ setChooseDesiresPage_HairTexture(window, currentPage) }) skinColorButton := widget.NewButton(translate("Skin Color"), func(){ setChooseDesiresPage_SkinColor(window, currentPage) }) infectionsButton := widget.NewButton(translate("Infections"), func(){ setChooseDesiresPage_Infections(window, currentPage) }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, sexButton, ageButton, ancestryCompositionButton, neanderthalVariantsButton, maternalHaplogroupButton, paternalHaplogroupButton, monogenicDiseasesButton, polygenicDiseasesButton, geneticTraitsButton, heightButton, bodyFatButton, bodyMuscleButton, eyeColorButton, hairColorButton, hairTextureButton, skinColorButton, infectionsButton)) buttonsGridPadded := container.NewPadded(buttonsGrid) page := container.NewVBox(title, backButton, widget.NewSeparator(), buttonsGridPadded) setPageContent(page, window) } func setChooseDesiresPage_Sex(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_Sex(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Sex")) descriptionText := widget.NewLabel("Choose the sex(es) that you desire.") sexHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setSexExplainerPage(window, currentPage) }) descriptionRow := container.NewHBox(layout.NewSpacer(), descriptionText, sexHelpButton, layout.NewSpacer()) optionTitlesList := []string{translate("Male"), translate("Female"), translate("Intersex Male"), translate("Intersex Female"), translate("Intersex")} optionNamesMap := make(map[string][]string) optionNamesMap[translate("Male")] = []string{"Male"} optionNamesMap[translate("Female")] = []string{"Female"} optionNamesMap[translate("Intersex Male")] = []string{"Intersex Male"} optionNamesMap[translate("Intersex Female")] = []string{"Intersex Female"} optionNamesMap[translate("Intersex")] = []string{"Intersex"} desireEditor, err := getDesireEditor_Choice(window, currentPage, "Sex", optionTitlesList, optionNamesMap, false, true, 2) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "Sex", "Sex", true, "Donut", "Sex", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), descriptionRow, widget.NewSeparator(), desireEditor, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_23andMe_AncestryComposition(window fyne.Window, userOrOffspring string, previousPage func()){ currentPage := func(){setChooseDesiresPage_23andMe_AncestryComposition(window, userOrOffspring, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Ancestry Composition") description1 := getLabelCentered("Choose your ancestry composition desires.") description2 := getLabelCentered("Each location is represented by a desired percentage range.") getRestrictiveModeIsEnabledBool := func()(bool, error){ myDesireExists, restrictiveModeEnabled, err := myLocalDesires.GetDesire("23andMe_AncestryComposition_RestrictiveModeEnabled") if (err != nil) { return false, err } if (myDesireExists == true && restrictiveModeEnabled == "Yes"){ return true, nil } return false, nil } restrictiveModeIsEnabled, err := getRestrictiveModeIsEnabledBool() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } restrictiveModeCheck := widget.NewCheck("Restrictive Mode", func(response bool){ newValue := helpers.ConvertBoolToYesOrNoString(response) err := myLocalDesires.SetDesire("23andMe_AncestryComposition_RestrictiveModeEnabled", newValue) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) restrictiveModeCheck.Checked = restrictiveModeIsEnabled restrictiveModeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setAncestryCompositionDesireRestrictiveModeExplainerPage(window, currentPage) }) restrictiveModeRow := container.NewHBox(layout.NewSpacer(), restrictiveModeCheck, restrictiveModeHelpButton, layout.NewSpacer()) chooseLocationDesiresButton := getWidgetCentered(widget.NewButtonWithIcon("Choose Location Desires", theme.NavigateNextIcon(), func(){ setChooseDesiresPage_23andMe_ViewAncestryComposition(window, restrictiveModeIsEnabled, "User", currentPage) })) getDesireName := func()string{ if (restrictiveModeIsEnabled == false){ return "23andMe_AncestryComposition" } return "23andMe_AncestryComposition_Restrictive" } desireName := getDesireName() filterOptionsSection, err := getDesireEditorFilterOptionsSection(window, currentPage, desireName, true) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } getDesireTitle := func()string{ if (restrictiveModeIsEnabled == false){ return "23andMe Ancestry Composition" } return "23andMe Ancestry Composition (Restrictive)" } desireTitle := getDesireTitle() viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, desireTitle, desireName, false, "", "", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), restrictiveModeRow, widget.NewSeparator(), chooseLocationDesiresButton, widget.NewSeparator(), filterOptionsSection, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_23andMe_ViewAncestryComposition(window fyne.Window, restrictiveModeIsEnabled bool, userOrOffspring string, previousPage func()){ currentPage := func(){setChooseDesiresPage_23andMe_ViewAncestryComposition(window, restrictiveModeIsEnabled, userOrOffspring, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) getSubtitleText := func()string{ if (restrictiveModeIsEnabled == false){ return "23andMe - Ancestry Composition" } return "23andMe - Ancestry Composition (Restrictive)" } subtitleText := getSubtitleText() subtitle := getPageSubtitleCentered(subtitleText) description1 := getLabelCentered("You can toggle between viewing your user or offspring desires.") description2Label := widget.NewLabel("Your offspring desires are calculated using your profile ancestry composition.") offspringAncestryCompositionHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setOffspringAncestryCompositionExplainerPage(window, currentPage) }) description2Row := container.NewHBox(layout.NewSpacer(), description2Label, offspringAncestryCompositionHelpButton, layout.NewSpacer()) userOrOffspringList := []string{"User", "Offspring"} userOrOffspringSelector := widget.NewSelect(userOrOffspringList, func(newUserOrOffspring string){ setChooseDesiresPage_23andMe_ViewAncestryComposition(window, restrictiveModeIsEnabled, newUserOrOffspring, previousPage) }) userOrOffspringSelector.Selected = userOrOffspring userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector) getDesireName := func()string{ if (restrictiveModeIsEnabled == false){ return "23andMe_AncestryComposition" } return "23andMe_AncestryComposition_Restrictive" } desireName := getDesireName() modifyDesiresButton := getWidgetCentered(widget.NewButtonWithIcon("Modify Desires", theme.DocumentCreateIcon(), func(){ setChooseDesiresPage_23andMe_EditAncestryComposition(window, restrictiveModeIsEnabled, false, "", false, "", false, "", currentPage) })) myDesireExists, myDesiredAncestryComposition, err := myLocalDesires.GetDesire(desireName) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (myDesireExists == false){ noLocationsAddedLabel := getBoldLabelCentered("No locations added.") page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2Row, widget.NewSeparator(), userOrOffspringSelectorCentered, widget.NewSeparator(), noLocationsAddedLabel, modifyDesiresButton) setPageContent(page, window) return } //Outputs: // -bool: My ancestry composition exists // -map[string]float64: My continent percentages map // -map[string]float64: My region percentages map // -map[string]float64: My subregion percentages map // -error getMyAncestryCompositionMaps := func()(bool, map[string]float64, map[string]float64, map[string]float64, error){ myAncestryCompositionExists, myCurrentAncestryComposition, err := myLocalProfiles.GetProfileData("Mate", "23andMe_AncestryComposition") if (err != nil){ return false, nil, nil, nil, err } if (myAncestryCompositionExists == false){ return false, nil, nil, nil, nil } attributeIsValid, myContinentPercentagesMap, myRegionPercentagesMap, mySubregionPercentagesMap, err := companyAnalysis.ReadAncestryCompositionAttribute_23andMe(true, myCurrentAncestryComposition) if (err != nil) { return false, nil, nil, nil, err } if (attributeIsValid == false){ return false, nil, nil, nil, errors.New("MyLocalProfiles contains invalid 23andMe ancestry composition attribute: " + myCurrentAncestryComposition) } mapsAreValid, myFilledContinentPercentagesMap, myFilledRegionPercentagesMap, myFilledSubregionPercentagesMap, err := companyAnalysis.AddMissingParentsToAncestryCompositionMaps_23andMe(myContinentPercentagesMap, myRegionPercentagesMap, mySubregionPercentagesMap) if (err != nil){ return false, nil, nil, nil, err } if (mapsAreValid == false){ return false, nil, nil, nil, errors.New("ReadAncestryCompositionAttribute_23andMe not verifying maps.") } return true, myFilledContinentPercentagesMap, myFilledRegionPercentagesMap, myFilledSubregionPercentagesMap, nil } myAncestryCompositionExists, myContinentPercentagesMap, myRegionPercentagesMap, mySubregionPercentagesMap, err := getMyAncestryCompositionMaps() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (userOrOffspring == "Offspring" && myAncestryCompositionExists == false){ description3 := getBoldLabelCentered("Your ancestry composition does not exist.") description4 := getLabelCentered("To view your offspring desires, you must add your ancestry composition.") description5 := getLabelCentered("Add it on the Build Profile - Physical - Ancestry Composition page.") page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2Row, widget.NewSeparator(), modifyDesiresButton, widget.NewSeparator(), userOrOffspringSelectorCentered, widget.NewSeparator(), description3, description4, description5) setPageContent(page, window) return } //Outputs: // -map[string][]string: Location description -> List of sub-locations descriptions // -error getDesiredLocationTreeMap := func()(map[string][]string, error){ myContinentMinimumBoundsMap, myContinentMaximumBoundsMap, myRegionMinimumBoundsMap, myRegionMaximumBoundsMap, mySubregionMinimumBoundsMap, mySubregionMaximumBoundsMap, err := mateDesires.ReadAncestryCompositionDesire_23andMe(myDesiredAncestryComposition) if (err != nil){ return nil, errors.New("MyLocalDesires contains invalid ancestry composition desire: " + err.Error()) } // This function will convert the range to an offspring range (if userOrOffspring == "Offspring") getLocationDescription := func(locationType string, locationName string, userMinimumDesiredBound float64, userMaximumDesiredBound float64)(string, error){ if (locationType != "Continent" && locationType != "Region" && locationType != "Subregion"){ return "", errors.New("getLocationDescription called with invalid locationType: " + locationType) } getMinimumAndMaximumBounds := func()(float64, float64){ if (userOrOffspring == "User"){ return userMinimumDesiredBound, userMaximumDesiredBound } // We calculate offspring range getMyLocationPercentage := func()float64{ if (locationType == "Continent"){ myPercentage, exists := myContinentPercentagesMap[locationName] if (exists == false){ return 0 } return myPercentage } if (locationType == "Region"){ myPercentage, exists := myRegionPercentagesMap[locationName] if (exists == false){ return 0 } return myPercentage } // locationType == "Subregion myPercentage, exists := mySubregionPercentagesMap[locationName] if (exists == false){ return 0 } return myPercentage } myLocationPercentage := getMyLocationPercentage() offspringMinimumBound := (userMinimumDesiredBound + myLocationPercentage)/2 offspringMaximumBound := (userMaximumDesiredBound + myLocationPercentage)/2 return offspringMinimumBound, offspringMaximumBound } minimumDesiredBound, maximumDesiredBound := getMinimumAndMaximumBounds() minimumDesiredBoundString := helpers.ConvertFloat64ToStringRounded(minimumDesiredBound, 1) maximumDesiredBoundString := helpers.ConvertFloat64ToStringRounded(maximumDesiredBound, 1) if (minimumDesiredBoundString == maximumDesiredBoundString){ locationDescription := locationName + ": " + minimumDesiredBoundString + "%" return locationDescription, nil } locationDescription := locationName + ": " + minimumDesiredBoundString + "-" + maximumDesiredBoundString + "%" return locationDescription, nil } // We now build the tree map // This is used to display a fyne tree widget // Each item is mapped to a list of child items // The "" item represents the root of the tree treeMap := make(map[string][]string) // This list will store the desired continent descriptions desiredContinentsList := make([]string, 0) allContinentsList := companyAnalysis.GetAncestryContinentsList_23andMe() for _, continentName := range allContinentsList{ continentRegionsList, err := companyAnalysis.GetAncestryContinentRegionsList_23andMe(continentName) if (err != nil){ return nil, errors.New("GetAncestryContinentRegionsList_23andMe missing continent regions: " + continentName) } if (len(continentRegionsList) == 0){ // Continent has no sublocations // We see if we desire it minimumDesiredRange, exists := myContinentMinimumBoundsMap[continentName] if (exists == false){ // We do not desire this continent continue } maximumDesiredRange, exists := myContinentMaximumBoundsMap[continentName] if (exists == false){ return nil, errors.New("myContinentMinimumBoundsMap contains continent, myContinentMaximumBoundsMap does not.") } continentDescription, err := getLocationDescription("Continent", continentName, minimumDesiredRange, maximumDesiredRange) if (err != nil) { return nil, err } desiredContinentsList = append(desiredContinentsList, continentDescription) continue } // These represent the minimum and maximum desired ranges for this continent // For example, if the user desires Japanese between 0-50%, and Korean between 50-100%, we say they desire East Asian from 0-100% // This list will store the desired region descriptions for this continent desiredContinentRegionsList := make([]string, 0) continentMinimumDesiredBound := float64(100) continentMaximumDesiredBound := float64(0) for _, regionName := range continentRegionsList{ regionSubregionsList, err := companyAnalysis.GetAncestryRegionSubregionsList_23andMe(continentName, regionName) if (err != nil){ return nil, errors.New("GetAncestryRegionSubregionsList_23andMe missing region subregions: " + regionName) } if (len(regionSubregionsList) == 0){ // This region has no sublocations // Check if we desire it regionMinimumDesiredBound, exists := myRegionMinimumBoundsMap[regionName] if (exists == false){ // We do not desire this region continue } regionMaximumDesiredBound, exists := myRegionMaximumBoundsMap[regionName] if (exists == false){ return nil, errors.New("myRegionMinimumBoundsMap contains region name, myRegionMaximumBoundsMap does not.") } if (regionMinimumDesiredBound < continentMinimumDesiredBound){ continentMinimumDesiredBound = regionMinimumDesiredBound } if (regionMaximumDesiredBound > continentMaximumDesiredBound){ continentMaximumDesiredBound = regionMaximumDesiredBound } regionDescription, err := getLocationDescription("Region", regionName, regionMinimumDesiredBound, regionMaximumDesiredBound) if (err != nil) { return nil, err } desiredContinentRegionsList = append(desiredContinentRegionsList, regionDescription) continue } // This list will store the descriptions for our desired subregions of this region desiredRegionSubregionsList := make([]string, 0) regionMinimumDesiredBound := float64(100) regionMaximumDesiredBound := float64(0) for _, subregionName := range regionSubregionsList{ // All subregions have no sublocations // We see if we desire this subregion subregionMinimumDesiredBound, exists := mySubregionMinimumBoundsMap[subregionName] if (exists == false){ // We do not desire this subregion continue } subregionMaximumDesiredBound, exists := mySubregionMaximumBoundsMap[subregionName] if (exists == false){ return nil, errors.New("subregionMinimumBoundsMap contains subregion name, subregionMaximumBoundsMap does not.") } if (subregionMinimumDesiredBound < continentMinimumDesiredBound){ continentMinimumDesiredBound = subregionMinimumDesiredBound } if (subregionMaximumDesiredBound > continentMaximumDesiredBound){ continentMaximumDesiredBound = subregionMaximumDesiredBound } if (subregionMinimumDesiredBound < regionMinimumDesiredBound){ regionMinimumDesiredBound = subregionMinimumDesiredBound } if (subregionMaximumDesiredBound > regionMaximumDesiredBound){ regionMaximumDesiredBound = subregionMaximumDesiredBound } subregionDescription, err := getLocationDescription("Subregion", subregionName, subregionMinimumDesiredBound, subregionMaximumDesiredBound) if (err != nil) { return nil, err } desiredRegionSubregionsList = append(desiredRegionSubregionsList, subregionDescription) } if (len(desiredRegionSubregionsList) != 0){ regionDescription, err := getLocationDescription("Region", regionName, regionMinimumDesiredBound, regionMaximumDesiredBound) if (err != nil) { return nil, err } desiredContinentRegionsList = append(desiredContinentRegionsList, regionDescription) treeMap[regionDescription] = desiredRegionSubregionsList } } if (len(desiredContinentRegionsList) != 0){ continentDescription, err := getLocationDescription("Continent", continentName, continentMinimumDesiredBound, continentMaximumDesiredBound) if (err != nil) { return nil, err } desiredContinentsList = append(desiredContinentsList, continentDescription) treeMap[continentDescription] = desiredContinentRegionsList } } treeMap[""] = desiredContinentsList return treeMap, nil } desiredLocationTreeMap, err := getDesiredLocationTreeMap() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } locationDesireWidgetTree := widget.NewTreeWithStrings(desiredLocationTreeMap) locationDesireWidgetTree.OpenAllBranches() header := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2Row, widget.NewSeparator(), modifyDesiresButton, widget.NewSeparator(), userOrOffspringSelectorCentered, widget.NewSeparator()) page := container.NewBorder(header, nil, nil, nil, locationDesireWidgetTree) setPageContent(page, window) } func setChooseDesiresPage_23andMe_EditAncestryComposition( window fyne.Window, restrictiveModeIsEnabled bool, continentProvided bool, currentContinent string, regionProvided bool, currentRegion string, subregionProvided bool, currentSubregion string, previousPage func()){ currentPage := func(){setChooseDesiresPage_23andMe_EditAncestryComposition(window, restrictiveModeIsEnabled, continentProvided, currentContinent, regionProvided, currentRegion, subregionProvided, currentSubregion, previousPage)} title := getPageTitleCentered("My Mate Desires - Physical") backButton := getBackButtonCentered(previousPage) getSubtitleText := func()string{ if (restrictiveModeIsEnabled == true){ return "23andMe Ancestry Composition (Restrictive)" } return "23andMe Ancestry Composition" } subtitleText := getSubtitleText() subtitle := getPageSubtitleCentered(subtitleText) description := getLabelCentered("Choose the percentage range for each location you desire.") getPageContent := func()(*fyne.Container, error){ getRangeEditor := func(locationType string)(*fyne.Container, error){ if (locationType != "Continent" && locationType != "Region" && locationType != "Subregion"){ return nil, errors.New("getRangeEditor called with invalid locationType: " + locationType) } getDesireName := func()string{ if (restrictiveModeIsEnabled == false){ return "23andMe_AncestryComposition" } return "23andMe_AncestryComposition_Restrictive" } desireName := getDesireName() //Outputs: // -map[string]float64: Continent minimum bounds map // -map[string]float64: Continent maximum bounds map // -map[string]float64: Region minimum bounds map // -map[string]float64: Region maximum bounds map // -map[string]float64: Subregion minimum bounds map // -map[string]float64: Subregion maximum bounds map // -error getMyDesiredAncestryCompositionMaps := func()(map[string]float64, map[string]float64, map[string]float64, map[string]float64, map[string]float64, map[string]float64, error){ myDesireExists, myDesiredAncestryComposition, err := myLocalDesires.GetDesire(desireName) if (err != nil){ return nil, nil, nil, nil, nil, nil, err } if (myDesireExists == false){ emptyMap1 := make(map[string]float64) emptyMap2 := make(map[string]float64) emptyMap3 := make(map[string]float64) emptyMap4 := make(map[string]float64) emptyMap5 := make(map[string]float64) emptyMap6 := make(map[string]float64) return emptyMap1, emptyMap2, emptyMap3, emptyMap4, emptyMap5, emptyMap6, nil } myContinentMinimumBoundsMap, myContinentMaximumBoundsMap, myRegionMinimumBoundsMap, myRegionMaximumBoundsMap, mySubregionMinimumBoundsMap, mySubregionMaximumBoundsMap, err := mateDesires.ReadAncestryCompositionDesire_23andMe(myDesiredAncestryComposition) if (err != nil){ return nil, nil, nil, nil, nil, nil, errors.New("MyLocalDesires contains invalid " + desireName + " desire: " + err.Error()) } return myContinentMinimumBoundsMap, myContinentMaximumBoundsMap, myRegionMinimumBoundsMap, myRegionMaximumBoundsMap, mySubregionMinimumBoundsMap, mySubregionMaximumBoundsMap, nil } myContinentMinimumBoundsMap, myContinentMaximumBoundsMap, myRegionMinimumBoundsMap, myRegionMaximumBoundsMap, mySubregionMinimumBoundsMap, mySubregionMaximumBoundsMap, err := getMyDesiredAncestryCompositionMaps() if (err != nil){ return nil, err } // Outputs: // -bool: Range bounds exist (any desire exists for current location) // -float64: Minimum bound // -float64: Maximum bound // -error getMyDesiredRangeBounds := func()(bool, float64, float64, error){ if (locationType == "Continent"){ continentMinimumBound, exists := myContinentMinimumBoundsMap[currentContinent] if (exists == false){ return false, 0, 0, nil } continentMaximumBound, exists := myContinentMaximumBoundsMap[currentContinent] if (exists == false){ return false, 0, 0, errors.New("myContinentMinimumBoundsMap contains continent, maximumBoundsMap does not: " + currentContinent) } return true, continentMinimumBound, continentMaximumBound, nil } if (locationType == "Region"){ regionMinimumBound, exists := myRegionMinimumBoundsMap[currentRegion] if (exists == false){ return false, 0, 0, nil } regionMaximumBound, exists := myRegionMaximumBoundsMap[currentRegion] if (exists == false){ return false, 0, 0, errors.New("myRegionMinimumBoundsMap contains region, maximumBoundsMap does not: " + currentRegion) } return true, regionMinimumBound, regionMaximumBound, nil } // locationType == "Subregion" subregionMinimumBound, exists := mySubregionMinimumBoundsMap[currentSubregion] if (exists == false){ return false, 0, 0, nil } subregionMaximumBound, exists := mySubregionMaximumBoundsMap[currentSubregion] if (exists == false){ return false, 0, 0, errors.New("mySubregionMinimumBoundsMap contains subregion, maximumBoundsMap does not: " + currentSubregion) } return true, subregionMinimumBound, subregionMaximumBound, nil } myDesireExists, myMinimumBound, myMaximumBound, err := getMyDesiredRangeBounds() if (err != nil){ return nil, err } getMyDesireDisplaySection := func()(*fyne.Container, error){ myDesireTitle := getItalicLabelCentered("My Desire:") getMyDesireText := func()string{ if (myDesireExists == false){ return "None" } myMinimumBoundString := helpers.ConvertFloat64ToStringRounded(myMinimumBound, 1) myMaximumBoundString := helpers.ConvertFloat64ToStringRounded(myMaximumBound, 1) if (myMinimumBoundString == myMaximumBoundString){ result := myMinimumBoundString + "%" return result } result := myMinimumBoundString + "-" + myMaximumBoundString + "%" return result } myDesireText := getMyDesireText() myDesireLabel := getBoldLabelCentered(myDesireText) if (myDesireExists == false){ displaySection := container.NewVBox(myDesireTitle, myDesireLabel) return displaySection, nil } // We check to see if we should display offspring range myAncestryCompositionExists, myCurrentAncestryComposition, err := myLocalProfiles.GetProfileData("Mate", "23andMe_AncestryComposition") if (err != nil){ return nil, err } if (myAncestryCompositionExists == false){ // We have not added our ancestry composition // Thus, we cannot determine the offspring location composition displaySection := container.NewVBox(myDesireTitle, myDesireLabel) return displaySection, nil } // This returns the percentage of the location that we are, as described in our profile getMyLocationPercentage := func()(float64, error){ attributeIsValid, myContinentPercentagesMap, myRegionPercentagesMap, mySubregionPercentagesMap, err := companyAnalysis.ReadAncestryCompositionAttribute_23andMe(true, myCurrentAncestryComposition) if (err != nil) { return 0, err } if (attributeIsValid == false){ return 0, errors.New("MyLocalProfiles contains invalid 23andMe ancestry composition attribute: " + myCurrentAncestryComposition) } if (locationType == "Continent"){ myPercentage, exists := myContinentPercentagesMap[currentContinent] if (exists == false){ return 0, nil } return myPercentage, nil } if (locationType == "Region"){ myPercentage, exists := myRegionPercentagesMap[currentRegion] if (exists == false){ return 0, nil } return myPercentage, nil } // LocationType == "Subregion" myPercentage, exists := mySubregionPercentagesMap[currentSubregion] if (exists == false){ return 0, nil } return myPercentage, nil } myLocationPercentage, err := getMyLocationPercentage() if (err != nil){ return nil, err } offspringMinimumBound := (myLocationPercentage + myMinimumBound)/2 offspringMaximumBound := (myLocationPercentage + myMaximumBound)/2 offspringMinimumBoundString := helpers.ConvertFloat64ToStringRounded(offspringMinimumBound, 1) offspringMaximumBoundString := helpers.ConvertFloat64ToStringRounded(offspringMaximumBound, 1) getOffspringDesiredRangeString := func()string{ if (offspringMinimumBoundString == offspringMaximumBoundString){ result := offspringMinimumBoundString + "%" return result } result := offspringMinimumBoundString + "-" + offspringMaximumBoundString + "%" return result } offspringDesiredRangeString := getOffspringDesiredRangeString() offspringRangeLabel := getItalicLabelCentered("(My Offspring: ~" + offspringDesiredRangeString + ")") displaySection := container.NewVBox(myDesireTitle, myDesireLabel, offspringRangeLabel) return displaySection, nil } myDesireDisplaySection, err := getMyDesireDisplaySection() if (err != nil) { return nil, err } minimumLabel := getBoldLabelCentered("Minimum:") minimumEntry := widget.NewEntry() if (myDesireExists == true){ myMinimumBoundString := helpers.ConvertFloat64ToStringRounded(myMinimumBound, 1) minimumEntry.Text = myMinimumBoundString } else { minimumEntry.SetPlaceHolder("Enter Minimum...") } minimumEntryBoxed := getWidgetBoxed(minimumEntry) minimumEntryColumn := container.NewGridWithColumns(1, minimumLabel, minimumEntryBoxed) maximumLabel := getBoldLabelCentered("Maximum:") maximumEntry := widget.NewEntry() if (myDesireExists == true){ myMaximumBoundString := helpers.ConvertFloat64ToStringRounded(myMaximumBound, 1) maximumEntry.Text = myMaximumBoundString } else { maximumEntry.SetPlaceHolder("Enter Maximum...") } maximumEntryBoxed := getWidgetBoxed(maximumEntry) maximumEntryColumn := container.NewGridWithColumns(1, maximumLabel, maximumEntryBoxed) minimumMaximumEntryColumnsRow := getContainerCentered(container.NewGridWithRows(1, minimumEntryColumn, maximumEntryColumn)) submitButton := getWidgetCentered(widget.NewButtonWithIcon("Submit", theme.ConfirmIcon(), func(){ newMinimum := minimumEntry.Text newMaximum := maximumEntry.Text newMinimumBoundFloat64, err := helpers.ConvertStringToFloat64(newMinimum) if (err != nil) { dialogTitle := translate("Invalid Minimum Bound") dialogMessageA := getLabelCentered(translate("Your minimum bound is invalid.")) dialogMessageB := getLabelCentered(translate("It must be a number between 0 and 100.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } newMaximumBoundFloat64, err := helpers.ConvertStringToFloat64(newMaximum) if (err != nil) { dialogTitle := translate("Invalid Maximum Bound") dialogMessageA := getLabelCentered(translate("Your maximum bound is invalid.")) dialogMessageB := getLabelCentered(translate("It must be a number between 0 and 100.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } if (newMinimumBoundFloat64 < 0 || newMinimumBoundFloat64 > 100){ dialogTitle := translate("Invalid Minimum Bound") dialogMessageA := getLabelCentered(translate("Your minimum bound is invalid.")) dialogMessageB := getLabelCentered(translate("It must be a number between 0 and 100.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } if (newMaximumBoundFloat64 < 0 || newMaximumBoundFloat64 > 100){ dialogTitle := translate("Invalid Maximum Bound") dialogMessageA := getLabelCentered(translate("Your maximum bound is invalid.")) dialogMessageB := getLabelCentered(translate("It must be a number between 0 and 100.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } if (newMinimumBoundFloat64 > newMaximumBoundFloat64){ dialogTitle := translate("Invalid Range") dialogMessageA := getLabelCentered(translate("Your range is invalid.")) dialogMessageB := getLabelCentered(translate("The minimum must be less than the maximum.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } if (newMinimumBoundFloat64 == 0 && newMaximumBoundFloat64 == 100){ dialogTitle := translate("Invalid Range") dialogMessageA := getLabelCentered(translate("Your range is invalid.")) dialogMessageB := getLabelCentered(translate("You cannot set a desired range of 0-100%.")) dialogMessageC := getLabelCentered(translate("All users will pass this range.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } if (locationType == "Continent"){ myContinentMinimumBoundsMap[currentContinent] = newMinimumBoundFloat64 myContinentMaximumBoundsMap[currentContinent] = newMaximumBoundFloat64 } else if (locationType == "Region"){ myRegionMinimumBoundsMap[currentRegion] = newMinimumBoundFloat64 myRegionMaximumBoundsMap[currentRegion] = newMaximumBoundFloat64 } else { // locationType == "Subregion" mySubregionMinimumBoundsMap[currentSubregion] = newMinimumBoundFloat64 mySubregionMaximumBoundsMap[currentSubregion] = newMaximumBoundFloat64 } newDesireValue, err := mateDesires.CreateAncestryCompositionDesire_23andMe(myContinentMinimumBoundsMap, myContinentMaximumBoundsMap, myRegionMinimumBoundsMap, myRegionMaximumBoundsMap, mySubregionMinimumBoundsMap, mySubregionMaximumBoundsMap) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } err = myLocalDesires.SetDesire(desireName, newDesireValue) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() })) noDesireButton := getWidgetCentered(widget.NewButtonWithIcon("No Desire", theme.CancelIcon(), func(){ if (locationType == "Continent"){ delete(myContinentMinimumBoundsMap, currentContinent) delete(myContinentMaximumBoundsMap, currentContinent) } else if (locationType == "Region"){ delete(myRegionMinimumBoundsMap, currentRegion) delete(myRegionMaximumBoundsMap, currentRegion) } else { // locationType == "Subregion" delete(mySubregionMinimumBoundsMap, currentSubregion) delete(mySubregionMaximumBoundsMap, currentSubregion) } if (len(myContinentMinimumBoundsMap) == 0 && len(myContinentMaximumBoundsMap) == 0 && len(myRegionMinimumBoundsMap) == 0 && len(myRegionMaximumBoundsMap) == 0 && len(mySubregionMinimumBoundsMap) == 0 && len(mySubregionMaximumBoundsMap) == 0){ err := myLocalDesires.DeleteDesire(desireName) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() return } newDesireValue, err := mateDesires.CreateAncestryCompositionDesire_23andMe(myContinentMinimumBoundsMap, myContinentMaximumBoundsMap, myRegionMinimumBoundsMap, myRegionMaximumBoundsMap, mySubregionMinimumBoundsMap, mySubregionMaximumBoundsMap) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } err = myLocalDesires.SetDesire(desireName, newDesireValue) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() })) rangeEditor := container.NewVBox(myDesireDisplaySection, widget.NewSeparator(), minimumMaximumEntryColumnsRow, submitButton, noDesireButton) return rangeEditor, nil } continentLabel := getBoldLabel("Continent:") allContinentsList := companyAnalysis.GetAncestryContinentsList_23andMe() handleContinentSelectFunction := func(newContinent string){ if (continentProvided == true && newContinent == currentContinent){ return } setChooseDesiresPage_23andMe_EditAncestryComposition(window, restrictiveModeIsEnabled, true, newContinent, false, "", false, "", previousPage) } continentSelector := widget.NewSelect(allContinentsList, handleContinentSelectFunction) if (continentProvided == true){ continentSelector.Selected = currentContinent } continentRow := container.NewHBox(layout.NewSpacer(), continentLabel, continentSelector, layout.NewSpacer()) if (continentProvided == false){ pageContent := container.NewVBox(continentRow) return pageContent, nil } regionsList, err := companyAnalysis.GetAncestryContinentRegionsList_23andMe(currentContinent) if (err != nil) { return nil, err } if (len(regionsList) == 0){ rangeEditor, err := getRangeEditor("Continent") if (err != nil) { return nil, err } pageContent := container.NewVBox(continentRow, widget.NewSeparator(), rangeEditor) return pageContent, nil } regionLabel := getBoldLabel("Region:") handleRegionSelectFunction := func(newRegion string){ if (regionProvided == true && currentRegion == newRegion){ return } setChooseDesiresPage_23andMe_EditAncestryComposition(window, restrictiveModeIsEnabled, true, currentContinent, true, newRegion, false, "", previousPage) } regionSelector := widget.NewSelect(regionsList, handleRegionSelectFunction) if (regionProvided == true){ regionSelector.Selected = currentRegion } regionRow := container.NewHBox(layout.NewSpacer(), regionLabel, regionSelector, layout.NewSpacer()) if (regionProvided == false){ pageContent := container.NewVBox(continentRow, widget.NewSeparator(), regionRow) return pageContent, nil } subregionsList, err := companyAnalysis.GetAncestryRegionSubregionsList_23andMe(currentContinent, currentRegion) if (err != nil) { return nil, err } if (len(subregionsList) == 0){ rangeEditor, err := getRangeEditor("Region") if (err != nil) { return nil, err } pageContent := container.NewVBox(continentRow, widget.NewSeparator(), regionRow, widget.NewSeparator(), rangeEditor) return pageContent, nil } subregionLabel := getBoldLabel("Subregion:") handleSubregionSelectFunction := func(newSubregion string){ if (subregionProvided == true && currentSubregion == newSubregion){ return } setChooseDesiresPage_23andMe_EditAncestryComposition(window, restrictiveModeIsEnabled, true, currentContinent, true, currentRegion, true, newSubregion, previousPage) } subregionSelector := widget.NewSelect(subregionsList, handleSubregionSelectFunction) if (subregionProvided == true){ subregionSelector.Selected = currentSubregion } subregionRow := container.NewHBox(layout.NewSpacer(), subregionLabel, subregionSelector, layout.NewSpacer()) if (subregionProvided == false){ pageContent := container.NewVBox(continentRow, widget.NewSeparator(), regionRow, widget.NewSeparator(), subregionRow) return pageContent, nil } rangeEditor, err := getRangeEditor("Subregion") if (err != nil) { return nil, err } pageContent := container.NewVBox(continentRow, widget.NewSeparator(), regionRow, widget.NewSeparator(), subregionRow, widget.NewSeparator(), rangeEditor) return pageContent, nil } pageContent, err := getPageContent() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator(), pageContent) setPageContent(page, window) } func setChooseDesiresPage_23andMe_NeanderthalVariants(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_23andMe_NeanderthalVariants(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Neanderthal Variants") description1 := getLabelCentered("Choose your neanderthal variant desires.") description2 := getLabelCentered("Enter the neanderthal variant count you desire in your matches.") description3 := getLabelCentered("The data is provided by 23andMe.") description4 := getLabelCentered("More companies will be added soon.") myVariantCountExists, myCurrentNeanderthalVariants, err := myLocalProfiles.GetProfileData("Mate", "23andMe_NeanderthalVariants") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } getOffspringVariantsCount := func(userVariantsCount float64)(float64, error){ if (myVariantCountExists == false){ return 0, errors.New("Trying to get offspring neanderthal variants count when my count does not exist.") } myVariantsCountFloat64, err := helpers.ConvertStringToFloat64(myCurrentNeanderthalVariants) if (err != nil) { return 0, errors.New("MyLocalProfile neanderthal variants count is invalid: " + myCurrentNeanderthalVariants) } offspringVariantsCount := (userVariantsCount + myVariantsCountFloat64)/2 return offspringVariantsCount, nil } desireEditor, err := getDesireEditor_Numeric(window, currentPage, "23andMe_NeanderthalVariants", 0, 7462, "variants", 0, false, nil, nil, myVariantCountExists, "My Offspring", "~", getOffspringVariantsCount) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "23andMe Neanderthal Variants", "23andMe_NeanderthalVariants", true, "Bar", "23andMe_NeanderthalVariants", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, description4, widget.NewSeparator(), desireEditor, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_23andMe_Haplogroup(window fyne.Window, maternalOrPaternal string, previousPage func()){ if (maternalOrPaternal != "Maternal" && maternalOrPaternal != "Paternal"){ setErrorEncounteredPage(window, errors.New("setChooseDesiresPage_23andMe_Haplogroup called with invalid maternalOrPaternal: " + maternalOrPaternal), previousPage) return } currentPage := func(){setChooseDesiresPage_23andMe_Haplogroup(window, maternalOrPaternal, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(maternalOrPaternal + " Haplogroup") maternalOrPaternalLowercase := strings.ToLower(maternalOrPaternal) description1 := getLabelCentered("Choose your " + maternalOrPaternalLowercase + " haplogroup desires.") description2 := getLabelCentered("Add each " + maternalOrPaternalLowercase + " haplogroup you desire.") description3 := getLabelCentered("You can choose from the known haplogroups, or enter ones that are not listed.") description4 := getLabelCentered("The haplogroups are provided by 23andMe. More companies will be added soon.") desireName := "23andMe_" + maternalOrPaternal + "Haplogroup" getSelectedHaplogroupsSection := func()(*fyne.Container, error){ getCurrentDesiredChoicesList := func()([]string, error){ currentChoicesListExists, currentChoicesList, err := myLocalDesires.GetDesire(desireName) if (err != nil) { return nil, err } if (currentChoicesListExists == false){ emptyList := make([]string, 0) return emptyList, nil } //currentChoicesList is a "+" separated list of choices // Each choice option is encoded in base64 currentDesiredChoicesList := strings.Split(currentChoicesList, "+") return currentDesiredChoicesList, nil } currentDesiredChoicesList, err := getCurrentDesiredChoicesList() if (err != nil) { return nil, err } getKnownHaplogroupsList := func()[]string{ if (maternalOrPaternal == "Maternal"){ maternalHaplogroupsList := companyAnalysis.GetKnownMaternalHaplogroupsList_23andMe() return maternalHaplogroupsList } paternalHaplogroupsList := companyAnalysis.GetKnownPaternalHaplogroupsList_23andMe() return paternalHaplogroupsList } knownHaplogroupsList := getKnownHaplogroupsList() haplogroupEntry := widget.NewSelectEntry(knownHaplogroupsList) haplogroupEntry.SetPlaceHolder("Enter haplogroup...") addHaplogroupButton := getWidgetCentered(widget.NewButtonWithIcon("Add Haplogroup", theme.ContentAddIcon(), func(){ newHaplogroupName := haplogroupEntry.Text if (newHaplogroupName == ""){ return } isAllowed := allowedText.VerifyStringIsAllowed(newHaplogroupName) if (isAllowed == false){ dialogTitle := translate("Invalid Haplogroup Name") dialogMessageA := getLabelCentered(translate("Your haplogroup contains an invalid character.")) dialogMessageB := getLabelCentered(translate("It must be encoded in UTF-8.")) dialogMessageC := getLabelCentered(translate("Remove this character and resubmit.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } containsTabsOrNewlines := helpers.CheckIfStringContainsTabsOrNewlines(newHaplogroupName) if (containsTabsOrNewlines == true){ dialogTitle := translate("Invalid Haplogroup Name") dialogMessageA := getLabelCentered(translate("Your haplogroup contains a tab or a newline.")) dialogMessageB := getLabelCentered(translate("Remove the tab or newline and resubmit.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } if (len(newHaplogroupName) > 25){ currentBytesLengthString := helpers.ConvertIntToString(len(newHaplogroupName)) dialogTitle := translate("Invalid Haplogroup Name") dialogMessageA := getLabelCentered(translate("Your haplogroup name is too long.")) dialogMessageB := getLabelCentered(translate("It cannot exceed 25 bytes.")) dialogMessageC := getLabelCentered(translate("Current Length:") + " " + currentBytesLengthString) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } haplogroupNameBase64 := encoding.EncodeBytesToBase64String([]byte(newHaplogroupName)) newDesireList := helpers.AddItemToStringListAndAvoidDuplicate(currentDesiredChoicesList, haplogroupNameBase64) newDesireAttributeValue := strings.Join(newDesireList, "+") err := myLocalDesires.SetDesire(desireName, newDesireAttributeValue) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() })) addHaplogroupRow := getContainerCentered(container.NewGridWithRows(1, haplogroupEntry, addHaplogroupButton)) otherSelectCheck := widget.NewCheck("Allow Other", func(selection bool){ //Outputs: // -[]string: New desire attribute list (list of base64 option names) getNewDesireAttributeList := func()[]string{ if (selection == false){ if (len(currentDesiredChoicesList) == 0){ // This should not happen, because "Other" had to have been selected for it to be deselected emptyList := make([]string, 0) return emptyList } newAttributeList, _ := helpers.DeleteAllMatchingItemsFromList(currentDesiredChoicesList, "Other") return newAttributeList } //selection == true newAttributeList := helpers.AddItemToStringListAndAvoidDuplicate(currentDesiredChoicesList, "Other") return newAttributeList } newDesireAttributeList := getNewDesireAttributeList() if (len(newDesireAttributeList) == 0){ err := myLocalDesires.DeleteDesire(desireName) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() return } newDesireAttribute := strings.Join(newDesireAttributeList, "+") err := myLocalDesires.SetDesire(desireName, newDesireAttribute) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) otherIsSelected := slices.Contains(currentDesiredChoicesList, "Other") if (otherIsSelected == true){ otherSelectCheck.Checked = true } allowOtherHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setAllowOtherExplainerPage(window, currentPage) }) otherSelectCheckRow := container.NewHBox(layout.NewSpacer(), otherSelectCheck, allowOtherHelpButton, layout.NewSpacer()) checkIfAnyHaplogroupsAreSelected := func()bool{ if (len(currentDesiredChoicesList) == 0){ return false } if (len(currentDesiredChoicesList) == 1){ onlySelection := currentDesiredChoicesList[0] if (onlySelection == "Other"){ return false } } return true } anyHaplogroupsAreSelected := checkIfAnyHaplogroupsAreSelected() if (anyHaplogroupsAreSelected == false){ noHaplogroupsExistLabel := getBoldLabelCentered("No Haplogroups Chosen.") selectedHaplogroupsSection := container.NewVBox(addHaplogroupRow, widget.NewSeparator(), noHaplogroupsExistLabel, widget.NewSeparator(), otherSelectCheckRow) return selectedHaplogroupsSection, nil } myDesiredHaplogroupsLabel := getItalicLabelCentered("My Desired Haplogroups:") haplogroupNameColumn := container.NewVBox(widget.NewSeparator()) deleteButtonsColumn := container.NewVBox(widget.NewSeparator()) for _, haplogroupNameBase64 := range currentDesiredChoicesList{ if (haplogroupNameBase64 == "Other"){ continue } haplogroupName, err := encoding.DecodeBase64StringToUnicodeString(haplogroupNameBase64) if (err != nil){ return nil, errors.New("My current profile " + maternalOrPaternal + " haplogroup desire is malformed: Contains invalid haplogroup: " + haplogroupNameBase64) } haplogroupNameLabel := getBoldLabelCentered(haplogroupName) deleteHaplogroupButton := widget.NewButtonWithIcon("", theme.DeleteIcon(), func(){ newDesiredHaplogroupsList, _ := helpers.DeleteAllMatchingItemsFromList(currentDesiredChoicesList, haplogroupNameBase64) if (len(newDesiredHaplogroupsList) == 0){ err := myLocalDesires.DeleteDesire(desireName) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() return } newDesireValueString := strings.Join(newDesiredHaplogroupsList, "+") err := myLocalDesires.SetDesire(desireName, newDesireValueString) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) haplogroupNameColumn.Add(haplogroupNameLabel) deleteButtonsColumn.Add(deleteHaplogroupButton) haplogroupNameColumn.Add(widget.NewSeparator()) deleteButtonsColumn.Add(widget.NewSeparator()) } haplogroupsGrid := container.NewHBox(layout.NewSpacer(), haplogroupNameColumn, deleteButtonsColumn, layout.NewSpacer()) selectedHaplogroupsSection := container.NewVBox(addHaplogroupRow, widget.NewSeparator(), myDesiredHaplogroupsLabel, haplogroupsGrid, widget.NewSeparator(), otherSelectCheckRow) return selectedHaplogroupsSection, nil } selectedHaplogroupsSection, err := getSelectedHaplogroupsSection() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } filterOptionsSection, err := getDesireEditorFilterOptionsSection(window, currentPage, desireName, true) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "23andMe " + maternalOrPaternal + " Haplogroup", desireName, true, "Bar", desireName, currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, description4, widget.NewSeparator(), selectedHaplogroupsSection, widget.NewSeparator(), filterOptionsSection, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_Age(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_Age(window, previousPage)} pageTitle := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Age")) description := getLabelCentered("Choose your age desires.") desireEditor, err := getDesireEditor_Numeric(window, currentPage, "Age", 18, 200, "Years", 0, false, nil, nil, false, "", "", nil) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "Age", "Age", true, "Bar", "Age", currentPage) })) page := container.NewVBox(pageTitle, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator(), desireEditor, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_MonogenicDiseases(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_MonogenicDiseases(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("My Offspring Monogenic Disease Desires") description1 := getLabelCentered("Choose your offspring monogenic disease probability desires.") description2 := getLabelCentered("You must link your genome person on the Build Profile - Genetic Analysis page.") description3 := getLabelCentered("This desire will filter users based upon the probability that your offspring will have any monogenic disease.") description4 := widget.NewLabel("If you use genetic testing, you can ensure your offspring will not have any monogenic diseases.") geneticTestingHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setGeneticTestingExplainerPage(window, currentPage) }) description4Row := container.NewHBox(layout.NewSpacer(), description4, geneticTestingHelpButton, layout.NewSpacer()) description5 := getBoldLabel("Choose the monogenic disease probability for your offspring that you desire.") desireHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setOffspringMonogenicDiseaseProbabilityDesireExplainerPage(window, currentPage) }) description5Row := container.NewHBox(layout.NewSpacer(), description5, desireHelpButton, layout.NewSpacer()) description6 := getLabelCentered("Be aware that if you select 0%, all users with dominant monogenic diseases will be filtered.") description7 := getLabelCentered("You should not select 0% if you have a dominant monogenic disease.") getCurrentDesireStatus := func()(string, error){ currentValueExists, currentValue, err := myLocalDesires.GetDesire("OffspringProbabilityOfAnyMonogenicDisease_Maximum") if (err != nil){ return "", err } if (currentValueExists == false || currentValue == "100"){ result := translate("0-100% (No Preference)") return result, nil } if (currentValue == "99"){ return "0-99%", nil } if (currentValue != "0"){ return "", errors.New("MyLocalDesires malformed: Invalid OffspringProbabilityOfAnyMonogenicDisease_Maximum: " + currentValue) } return "0%", nil } currentDesireStatus, err := getCurrentDesireStatus() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } myDesireLabel := widget.NewLabel("My Desire:") currentStatusText := getBoldLabel(currentDesireStatus) currentStatusRow := container.NewHBox(layout.NewSpacer(), myDesireLabel, currentStatusText, layout.NewSpacer()) desireOptionsList := []string{"0%", "0-99%", "0-100%"} desireSelector := widget.NewSelect(desireOptionsList, func(newSelection string){ if (newSelection == "0-100%"){ err = myLocalDesires.DeleteDesire("OffspringProbabilityOfAnyMonogenicDisease_Maximum") if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() return } getNewMaximumBound := func()string{ if (newSelection == "0%"){ return "0" } // newSelection == "0-99%" return "99" } newMaximumBound := getNewMaximumBound() err = myLocalDesires.SetDesire("OffspringProbabilityOfAnyMonogenicDisease_Maximum", newMaximumBound) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) if (currentDesireStatus == translate("0-100% (No Preference)")){ desireSelector.Selected = "0-100%" } else { desireSelector.Selected = currentDesireStatus } desireSelectorCentered := getWidgetCentered(desireSelector) filterOptionsSection, err := getDesireEditorFilterOptionsSection(window, currentPage, "OffspringProbabilityOfAnyMonogenicDisease", false) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "Offspring Has Any Monogenic Disease Probability", "OffspringProbabilityOfAnyMonogenicDisease", true, "Donut", "OffspringProbabilityOfAnyMonogenicDisease", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, description4Row, widget.NewSeparator(), description5Row, description6, description7, widget.NewSeparator(), currentStatusRow, desireSelectorCentered, widget.NewSeparator(), filterOptionsSection, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_Height(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_Height(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Height") description := getLabelCentered("Choose your desired height.") metricOrImperialSwitchButton, err := getMetricImperialSwitchButton(window, currentPage) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } currentUnitsLabel := getItalicLabel("Current Units:") currentUnitsRow := container.NewHBox(layout.NewSpacer(), currentUnitsLabel, metricOrImperialSwitchButton, layout.NewSpacer()) getMyMetricOrImperial := func()(string, error){ exists, metricOrImperial, err := globalSettings.GetSetting("MetricOrImperial") if (err != nil) { return "", err } if (exists == false){ return "Metric", nil } if (metricOrImperial != "Metric" && metricOrImperial != "Imperial"){ return "", errors.New("Malformed globalSettings: Invalid metricOrImperial: " + metricOrImperial) } return metricOrImperial, nil } myMetricOrImperial, err := getMyMetricOrImperial() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } getBoundColumn := func(minimumOrMaximum string)(*fyne.Container, error){ if (minimumOrMaximum != "Minimum" && minimumOrMaximum != "Maximum"){ return nil, errors.New("getBoundColumn called with invalid minimumOrMaximum: " + minimumOrMaximum) } desireBoundName := "Height_" + minimumOrMaximum boundLabel := getBoldLabelCentered(minimumOrMaximum) myBoundExists, myCurrentBound, err := myLocalDesires.GetDesire(desireBoundName) if (err != nil) { return nil, err } getMyBoundLabel := func()(*fyne.Container, error){ if (myBoundExists == false){ noneLabel := getBoldItalicLabelCentered(translate("None")) return noneLabel, nil } myCurrentBoundFloat64, err := helpers.ConvertStringToFloat64(myCurrentBound) if (err != nil) { return nil, errors.New("My current height " + minimumOrMaximum + " desire is invalid: " + myCurrentBound) } if (myMetricOrImperial == "Metric"){ myCurrentBoundRounded := helpers.ConvertFloat64ToStringRounded(myCurrentBoundFloat64, 2) myBoundLabel := getBoldLabelCentered(myCurrentBoundRounded + " " + translate("centimeters")) return myBoundLabel, nil } feetInchesString, err := helpers.ConvertCentimetersToFeetInchesTranslatedString(myCurrentBoundFloat64) if (err != nil) { return nil, err } myBoundLabel := getBoldLabelCentered(feetInchesString) return myBoundLabel, nil } myBoundLabel, err := getMyBoundLabel() if (err != nil){ return nil, err } minimumOrMaximumLowercase := strings.ToLower(minimumOrMaximum) //Outputs: // -fyne.Container: Bound entry (either metric or imperial) // -fyne.Widget: Submit button // -error getBoundEntryAndSubmitButton := func()(*fyne.Container, fyne.Widget, error){ if (myMetricOrImperial == "Metric"){ boundEntry := widget.NewEntry() boundEntryBoxed := getWidgetBoxed(boundEntry) if (myBoundExists == true){ currentBoundFloat64, err := helpers.ConvertStringToFloat64(myCurrentBound) if (err != nil) { return nil, nil, err } if (currentBoundFloat64 < 30 || currentBoundFloat64 > 400) { return nil, nil, errors.New("My desires malformed: Contains invalid height desire: " + myCurrentBound) } myCurrentBoundRounded := helpers.ConvertFloat64ToStringRounded(currentBoundFloat64, 2) boundEntry.SetText(myCurrentBoundRounded) } else { boundEntry.SetPlaceHolder(translate("Enter " + minimumOrMaximumLowercase + "...")) } saveBoundButton := widget.NewButtonWithIcon(translate("Save"), theme.ConfirmIcon(), func(){ newHeightBound := boundEntry.Text if (newHeightBound == ""){ err := myLocalDesires.DeleteDesire(desireBoundName) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() return } //TODO: If min is greater than max, delete min newHeightBoundFloat64, err := helpers.ConvertStringToFloat64(newHeightBound) if (err != nil){ title := translate("Invalid " + minimumOrMaximum + " Height") dialogMessage := getLabelCentered(translate("Your height bound must be a number.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } if (newHeightBoundFloat64 < 30 || newHeightBoundFloat64 > 400){ title := translate("Invalid " + minimumOrMaximum + " Height") dialogMessage := getLabelCentered(translate("Your height desire must be a number between 30 and 400.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } err = myLocalDesires.SetDesire(desireBoundName, newHeightBound) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) return boundEntryBoxed, saveBoundButton, nil } feetEntry := widget.NewEntry() inchesEntry := widget.NewEntry() feetLabel := getBoldLabel(translate("feet")) inchesLabel := getBoldLabel(translate("inches")) entryRow := container.NewGridWithRows(1, feetEntry, feetLabel, inchesEntry, inchesLabel) if (myBoundExists == true){ currentBoundFloat64, err := helpers.ConvertStringToFloat64(myCurrentBound) if (err != nil) { return nil, nil, err } if (currentBoundFloat64 < 30 || currentBoundFloat64 > 400) { return nil, nil, errors.New("My desires malformed: Contains invalid height desire: " + myCurrentBound) } feetInt, inchesFloat, err := helpers.ConvertCentimetersToFeetInches(currentBoundFloat64) if (err != nil) { return nil, nil, errors.New("My desires malformed: Contains invalid height desire: " + myCurrentBound) } feetString := helpers.ConvertIntToString(feetInt) inchesString := helpers.ConvertFloat64ToStringRounded(inchesFloat, 1) feetEntry.SetText(feetString) inchesEntry.SetText(inchesString) } else { feetEntry.SetPlaceHolder(translate("Enter " + minimumOrMaximumLowercase + "...")) inchesEntry.SetPlaceHolder(translate("Enter " + minimumOrMaximumLowercase + "...")) } saveBoundButton := widget.NewButtonWithIcon(translate("Save"), theme.ConfirmIcon(), func(){ newHeightFeetBound := feetEntry.Text newHeightInchesBound := inchesEntry.Text if (newHeightFeetBound == "" && newHeightInchesBound == ""){ err := myLocalDesires.DeleteDesire(desireBoundName) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() return } newFeetBoundInt, err := helpers.ConvertStringToInt(newHeightFeetBound) if (err != nil){ title := translate("Invalid " + minimumOrMaximum + " Feet") dialogMessage := getLabelCentered(translate("Your feet bound must be a number.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } newInchesBoundFloat64, err := helpers.ConvertStringToFloat64(newHeightInchesBound) if (err != nil){ title := translate("Invalid " + minimumOrMaximum + " Inches") dialogMessage := getLabelCentered(translate("Your inches bound must be a number.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } newCentimetersBound, err := helpers.ConvertFeetInchesToCentimeters(newFeetBoundInt, newInchesBoundFloat64) if (err != nil){ title := translate("Invalid " + minimumOrMaximum + " Height") dialogMessage := getLabelCentered(translate("Your height bound must be a positive number.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } if (newCentimetersBound < 30 || newCentimetersBound > 400){ title := translate("Invalid " + minimumOrMaximum + " Height") dialogMessage := getLabelCentered(translate("Your height bound must be between 30 and 400 centimeters.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } newCentimetersBoundString := helpers.ConvertFloat64ToString(newCentimetersBound) err = myLocalDesires.SetDesire(desireBoundName, newCentimetersBoundString) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) return entryRow, saveBoundButton, nil } boundEntryRow, saveBoundButton, err := getBoundEntryAndSubmitButton() if (err != nil){ return nil, err } deleteBoundButton := widget.NewButtonWithIcon(translate("No Preference"), theme.CancelIcon(), func(){ if (myBoundExists == false){ return } err := myLocalDesires.DeleteDesire(desireBoundName) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) boundColumn := getContainerCentered(container.NewVBox(boundLabel, widget.NewSeparator(), myBoundLabel, widget.NewSeparator(), boundEntryRow, saveBoundButton, deleteBoundButton)) return boundColumn, nil } minimumColumn, err := getBoundColumn("Minimum") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } maximumColumn, err := getBoundColumn("Maximum") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } boundColumns := getContainerCentered(container.NewGridWithRows(1, minimumColumn, maximumColumn)) filterOptionsSection, err := getDesireEditorFilterOptionsSection(window, currentPage, "Height", true) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "Height", "Height", true, "Bar", "Height", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator(), currentUnitsRow, widget.NewSeparator(), boundColumns, widget.NewSeparator(), filterOptionsSection, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_BodyFat(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_BodyFat(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Body Fat") description1 := getLabelCentered("Choose your body fat desires.") description2 := getLabelCentered("Select each rating which you desire.") description3 := getLabelCentered("1/4 = Least body fat, 4/4 = Most body fat.") optionTitlesList := []string{"1/4", "2/4", "3/4", "4/4"} optionNamesMap := map[string][]string{ "1/4": []string{"1"}, "2/4": []string{"2"}, "3/4": []string{"3"}, "4/4": []string{"4"}, } desireEditor, err := getDesireEditor_Choice(window, currentPage, "BodyFat", optionTitlesList, optionNamesMap, false, true, 1) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "Body Fat", "BodyFat", true, "Donut", "BodyFat", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), desireEditor, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_BodyMuscle(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_BodyMuscle(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Body Muscle") description1 := getLabelCentered("Choose your body muscle desires.") description2 := getLabelCentered("Select each rating which you desire.") description3 := getLabelCentered("1/4 = Least body muscle, 4/4 = Most body muscle.") optionTitlesList := []string{"1/4", "2/4", "3/4", "4/4"} optionNamesMap := map[string][]string{ "1/4": []string{"1"}, "2/4": []string{"2"}, "3/4": []string{"3"}, "4/4": []string{"4"}, } desireEditor, err := getDesireEditor_Choice(window, currentPage, "BodyMuscle", optionTitlesList, optionNamesMap, false, true, 1) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "Body Muscle", "BodyMuscle", true, "Donut", "BodyMuscle", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), desireEditor, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_EyeColor(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_EyeColor(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Eye Color") description1 := getLabelCentered("Choose your eye color desires.") description2 := getLabelCentered("Select each eye color that you desire.") optionTitlesList := []string{"Blue", "Green", "Brown", "Amber", "Blue+Green", "Blue+Brown", "Blue+Amber", "Amber+Brown", "Green+Brown", "Green+Amber", "Green+Amber+Brown", "Blue+Amber+Brown", "Blue+Green+Brown", "Blue+Green+Amber", "Blue+Green+Amber+Brown"} optionNamesMap := make(map[string][]string) optionNamesMap["Blue"] = []string{"Blue"} optionNamesMap["Green"] = []string{"Green"} optionNamesMap["Brown"] = []string{"Brown"} optionNamesMap["Amber"] = []string{"Amber"} optionNamesMap["Blue+Green"] = []string{"Blue+Green", "Green+Blue"} optionNamesMap["Blue+Brown"] = []string{"Blue+Brown", "Brown+Blue"} optionNamesMap["Blue+Amber"] = []string{"Blue+Amber", "Amber+Blue"} optionNamesMap["Amber+Brown"] = []string{"Amber+Brown", "Brown+Amber"} optionNamesMap["Green+Brown"] = []string{"Green+Brown", "Brown+Green"} optionNamesMap["Green+Amber"] = []string{"Green+Amber", "Amber+Green"} optionNamesMap["Green+Amber+Brown"] = []string{"Green+Amber+Brown", "Green+Brown+Amber", "Amber+Green+Brown", "Amber+Brown+Green", "Brown+Amber+Green", "Brown+Green+Amber"} optionNamesMap["Blue+Amber+Brown"] = []string{"Blue+Amber+Brown", "Blue+Brown+Amber", "Amber+Blue+Brown", "Amber+Brown+Blue", "Brown+Amber+Blue", "Brown+Blue+Amber"} optionNamesMap["Blue+Green+Brown"] = []string{"Blue+Green+Brown", "Blue+Brown+Green", "Green+Blue+Brown", "Green+Brown+Blue", "Brown+Green+Blue", "Brown+Blue+Green"} optionNamesMap["Blue+Green+Amber"] = []string{"Blue+Green+Amber", "Blue+Amber+Green", "Green+Blue+Amber", "Green+Amber+Blue", "Amber+Green+Blue", "Amber+Blue+Green"} optionNamesMap["Blue+Green+Amber+Brown"] = []string{"Blue+Green+Brown+Amber", "Blue+Green+Amber+Brown", "Blue+Brown+Green+Amber", "Blue+Brown+Amber+Green", "Blue+Amber+Green+Brown", "Blue+Amber+Brown+Green", "Green+Blue+Brown+Amber", "Green+Blue+Amber+Brown", "Green+Brown+Blue+Amber", "Green+Brown+Amber+Blue", "Green+Amber+Blue+Brown", "Green+Amber+Brown+Blue", "Brown+Blue+Green+Amber", "Brown+Blue+Amber+Green", "Brown+Green+Blue+Amber", "Brown+Green+Amber+Blue", "Brown+Amber+Blue+Green", "Brown+Amber+Green+Blue", "Amber+Blue+Green+Brown", "Amber+Blue+Brown+Green", "Amber+Green+Blue+Brown", "Amber+Green+Brown+Blue", "Amber+Brown+Blue+Green", "Amber+Brown+Green+Blue"} desireEditor, err := getDesireEditor_Choice(window, currentPage, "EyeColor", optionTitlesList, optionNamesMap, false, true, 2) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "Eye Color", "EyeColor", true, "Donut", "EyeColor", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), desireEditor, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_HairColor(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_HairColor(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Hair Color") description1 := getLabelCentered("Choose your natural hair color desires.") description2 := getLabelCentered("Select each natural hair color that you desire.") optionTitlesList := []string{"Black", "Brown", "Blonde", "Orange", "Black+Brown", "Black+Blonde", "Black+Orange", "Brown+Blonde", "Brown+Orange", "Blonde+Orange"} //TODO: Add color guide page, so users can see the colors // We already have this on the Build Profile page for hair/eye color optionNamesMap := make(map[string][]string) optionNamesMap["Black"] = []string{"Black"} optionNamesMap["Brown"] = []string{"Brown"} optionNamesMap["Blonde"] = []string{"Blonde"} optionNamesMap["Orange"] = []string{"Orange"} optionNamesMap["Black+Brown"] = []string{"Black+Brown", "Brown+Black"} optionNamesMap["Black+Blonde"] = []string{"Black+Blonde", "Blonde+Black"} optionNamesMap["Black+Orange"] = []string{"Black+Orange", "Orange+Black"} optionNamesMap["Brown+Blonde"] = []string{"Brown+Blonde", "Blonde+Brown"} optionNamesMap["Brown+Orange"] = []string{"Brown+Orange", "Orange+Brown"} optionNamesMap["Blonde+Orange"] = []string{"Blonde+Orange", "Orange+Blonde"} desireEditor, err := getDesireEditor_Choice(window, currentPage, "HairColor", optionTitlesList, optionNamesMap, false, true, 2) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "Hair Color", "HairColor", true, "Donut", "HairColor", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), desireEditor, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_HairTexture(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_HairTexture(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Hair Texture") description1 := getLabelCentered("Choose your hair texture desires.") description2 := getLabelCentered("Select each hair texture that you desire.") // These descriptions are taken from 23andMe. option1Translated := translate("1 - Straight Hair") option2Translated := translate("2 - Slightly Wavy Hair") option3Translated := translate("3 - Wavy Hair") option4Translated := translate("4 - Big Curls") option5Translated := translate("5 - Small Curls") option6Translated := translate("6 - Very Tight Curls") optionTitlesList := []string{option1Translated, option2Translated, option3Translated, option4Translated, option5Translated, option6Translated} optionNamesMap := map[string][]string{ option1Translated: []string{"1"}, option2Translated: []string{"2"}, option3Translated: []string{"3"}, option4Translated: []string{"4"}, option5Translated: []string{"5"}, option6Translated: []string{"6"}, } desireEditor, err := getDesireEditor_Choice(window, currentPage, "HairTexture", optionTitlesList, optionNamesMap, false, true, 1) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "Hair Texture", "HairTexture", true, "Donut", "HairTexture", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), desireEditor, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_SkinColor(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_SkinColor(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Skin Color") description1 := getLabelCentered("Choose your skin color desires.") description2 := getLabelCentered("Select each skin color that you desire.") description3 := getLabelCentered("Skin color can change under different conditions.") description4 := getLabelCentered("You may want to select multiple options to account for this.") getSkinColorExampleColumn := func(colorIdentifier string, colorCode string)(*fyne.Container, error){ colorSquare, err := getColorSquareAsFyneImage(colorCode) if (err != nil){ return nil, err } colorSquare.FillMode = canvas.ImageFillStretch colorIdentifierLabel := getBoldLabelCentered(colorIdentifier) colorColumn := container.NewGridWithColumns(1, colorSquare, colorIdentifierLabel) return colorColumn, nil } skinColorColumn_1, err := getSkinColorExampleColumn("1", "f4e3da") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } skinColorColumn_2, err := getSkinColorExampleColumn("2", "f5d6b9") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } skinColorColumn_3, err := getSkinColorExampleColumn("3", "dabe91") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } skinColorColumn_4, err := getSkinColorExampleColumn("4", "ba9175") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } skinColorColumn_5, err := getSkinColorExampleColumn("5", "916244") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } skinColorColumn_6, err := getSkinColorExampleColumn("6", "744d2d") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } skinColorExamplesRow := container.NewHBox(layout.NewSpacer(), skinColorColumn_1, skinColorColumn_2, skinColorColumn_3, skinColorColumn_4, skinColorColumn_5, skinColorColumn_6, layout.NewSpacer()) optionTitlesList := []string{"1", "2", "3", "4", "5", "6"} optionNamesMap := map[string][]string{ "1": []string{"1"}, "2": []string{"2"}, "3": []string{"3"}, "4": []string{"4"}, "5": []string{"5"}, "6": []string{"6"}, } desireEditor, err := getDesireEditor_Choice(window, currentPage, "SkinColor", optionTitlesList, optionNamesMap, false, true, 6) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "Skin Color", "SkinColor", true, "Donut", "SkinColor", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, description4, widget.NewSeparator(), skinColorExamplesRow, widget.NewSeparator(), desireEditor, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_Infections(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_Infections(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Infections") description1 := getLabelCentered("Choose your infection desires.") hivButton := widget.NewButton("HIV", func(){ setChooseDesiresPage_HIV(window, currentPage) }) genitalHerpesButton := widget.NewButton("Genital Herpes", func(){ setChooseDesiresPage_GenitalHerpes(window, currentPage) }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, hivButton, genitalHerpesButton)) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, widget.NewSeparator(), buttonsGrid) setPageContent(page, window) } func setChooseDesiresPage_HIV(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_HIV(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("HIV") description1 := getLabelCentered("Choose your HIV desires.") description2 := getLabelCentered("Select each option which you desire.") description3 := getLabelCentered("Positive = Has disease, Negative = Does not have disease.") optionTitlesList := []string{"HIV Positive", "HIV Negative"} optionNamesMap := map[string][]string{ "HIV Positive": []string{"Yes"}, "HIV Negative": []string{"No"}, } desireEditor, err := getDesireEditor_Choice(window, currentPage, "HasHIV", optionTitlesList, optionNamesMap, false, true, 1) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "Has HIV", "HasHIV", true, "Donut", "HasHIV", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), desireEditor, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) } func setChooseDesiresPage_GenitalHerpes(window fyne.Window, previousPage func()){ currentPage := func(){setChooseDesiresPage_GenitalHerpes(window, previousPage)} title := getPageTitleCentered(translate("My Mate Desires - Physical")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Genital Herpes") description1 := getLabelCentered("Choose your Genital Herpes desires.") description2 := getLabelCentered("Select each option which you desire.") description3 := getLabelCentered("Positive = Has disease, Negative = Does not have disease.") optionTitlesList := []string{"Genital Herpes Positive", "Genital Herpes Negative"} optionNamesMap := map[string][]string{ "Genital Herpes Positive": []string{"Yes"}, "Genital Herpes Negative": []string{"No"}, } desireEditor, err := getDesireEditor_Choice(window, currentPage, "HasGenitalHerpes", optionTitlesList, optionNamesMap, false, true, 1) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){ setViewMyMateDesireStatisticsPage(window, "Has Genital Herpes", "HasGenitalHerpes", true, "Donut", "HasGenitalHerpes", currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), desireEditor, widget.NewSeparator(), viewStatisticsButton) setPageContent(page, window) }