package gui // buildProfileGui_General.go impements pages to build the general portion of a user profile // Some attributes support multiple profileTypes, most attributes are only used for Mate 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/imageFiles" import "seekia/internal/allowedText" import "seekia/internal/globalSettings" import "seekia/internal/helpers" import "seekia/internal/imagery" import "seekia/internal/mateQuestionnaire" import "seekia/internal/profiles/myLocalProfiles" import "strings" import "errors" import "image" import "reflect" import "slices" func setBuildMyMateProfilePage(window fyne.Window, previousPage func()){ currentPage := func(){setBuildMyMateProfilePage(window, previousPage)} title := getPageTitleCentered(translate("Build Mate Profile")) backButton := getBackButtonCentered(previousPage) description1 := getLabelCentered("Build your Mate profile.") description2 := getLabelCentered("All information is optional.") generalIcon, err := getFyneImageIcon("General") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } physicalIcon, err := getFyneImageIcon("Person") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } lifestyleIcon, err := getFyneImageIcon("Lifestyle") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } mentalIcon, err := getFyneImageIcon("Mental") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } generalButton := widget.NewButton(translate("General"), func(){ setBuildMateProfileCategoryPage_General(window, currentPage) }) generalButtonWithIcon := container.NewGridWithRows(2, generalIcon, generalButton) physicalButton := widget.NewButton(translate("Physical"), func(){ setBuildMateProfileCategoryPage_Physical(window, currentPage) }) physicalButtonWithIcon := container.NewGridWithRows(2, physicalIcon, physicalButton) lifestyleButton := widget.NewButton(translate("Lifestyle"), func(){ setBuildMateProfileCategoryPage_Lifestyle(window, currentPage) }) lifestyleButtonWithIcon := container.NewGridWithRows(2, lifestyleIcon, lifestyleButton) mentalButton := widget.NewButton(translate("Mental"), func(){ setBuildMateProfileCategoryPage_Mental(window, currentPage) }) mentalButtonWithIcon := container.NewGridWithRows(2, mentalIcon, mentalButton) categoriesRow := container.NewGridWithRows(1, generalButtonWithIcon, physicalButtonWithIcon, lifestyleButtonWithIcon, mentalButtonWithIcon) categoriesRowCentered := getContainerCentered(categoriesRow) categoriesRowPadded := container.NewPadded(categoriesRowCentered) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, widget.NewSeparator(), categoriesRowPadded) setPageContent(page, window) } func setBuildMateProfileCategoryPage_General(window fyne.Window, previousPage func()){ currentPage := func(){setBuildMateProfileCategoryPage_General(window, previousPage)} title := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) profileLanguageButton := widget.NewButton(translate("Profile Language"), func(){ setBuildProfilePage_ProfileLanguage(window, "Mate", currentPage) }) usernameButton := widget.NewButton(translate("Username"), func(){ setBuildProfilePage_Username(window, "Mate", currentPage) }) locationButton := widget.NewButton(translate("Location"), func(){ setBuildMateProfilePage_Location(window, currentPage) }) descriptionButton := widget.NewButton(translate("Description"), func(){ setBuildProfilePage_Description(window, "Mate", currentPage) }) photosButton := widget.NewButton(translate("Photos"), func(){ setBuildMateProfilePage_Photos(window, 0, currentPage) }) avatarButton := widget.NewButton(translate("Avatar"), func(){ setBuildProfilePage_Avatar(window, "Mate", currentPage) }) sexualityButton := widget.NewButton(translate("Sexuality"), func(){ setBuildMateProfilePage_Sexuality(window, currentPage) }) tagsButton := widget.NewButton(translate("Tags"), func(){ setBuildMateProfilePage_Tags(window, currentPage) }) questionnaireButton := widget.NewButton(translate("Questionnaire"), func(){ setBuildMateProfilePage_Questionnaire(window, currentPage) }) buttonsGrid := container.NewGridWithColumns(1, profileLanguageButton, usernameButton, locationButton, descriptionButton, photosButton, avatarButton, sexualityButton, tagsButton, questionnaireButton) buttonsGridCentered := getContainerCentered(buttonsGrid) buttonsGridPadded := container.NewPadded(buttonsGridCentered) page := container.NewVBox(title, backButton, widget.NewSeparator(), buttonsGridPadded) setPageContent(page, window) } func setBuildProfilePage_ProfileLanguage(window fyne.Window, profileType string, previousPage func()){ currentPage := func(){setBuildProfilePage_ProfileLanguage(window, profileType, previousPage)} title := getPageTitleCentered(translate("Build " + profileType + " Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Profile Language")) description1 := getLabelCentered("Choose your profile language.") description2 := getLabelCentered("This is the language that your profile is written in.") description3 := getLabelCentered("For example, select English if your description/hobbies/... are written in English.") currentLanguageExists, currentLanguageIdentifier, err := myLocalProfiles.GetProfileData(profileType, "ProfileLanguage") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } getCurrentLanguageLabel := func()(fyne.Widget, error){ if (currentLanguageExists == false){ noResponseLabel := getBoldItalicLabel("No Response") return noResponseLabel, nil } currentLanguageIdentifierInt, err := helpers.ConvertStringToInt(currentLanguageIdentifier) if (err != nil){ return nil, errors.New("MyLocalProfile is malformed: Contains invalid ProfileLanguage: " + currentLanguageIdentifier) } currentLanguageObject, err := worldLanguages.GetLanguageObjectFromLanguageIdentifier(currentLanguageIdentifierInt) if (err != nil) { return nil, err } currentLanguageNamesList := currentLanguageObject.NamesList languageDescription := helpers.TranslateAndJoinStringListItems(currentLanguageNamesList, "/") languageNamesLabel := getBoldLabel(languageDescription) return languageNamesLabel, nil } currentLanguageLabel, err := getCurrentLanguageLabel() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } myProfileLanguageLabel := widget.NewLabel("My Profile Language:") myProfileLanguageRow := container.NewHBox(layout.NewSpacer(), myProfileLanguageLabel, currentLanguageLabel, layout.NewSpacer()) getChooseOrEditButtonText := func()string{ if (currentLanguageExists == false){ result := translate("Choose Language") return result } result := translate("Edit Language") return result } chooseOrEditButtonText := getChooseOrEditButtonText() chooseOrEditLanguageButton := widget.NewButtonWithIcon(chooseOrEditButtonText, theme.DocumentCreateIcon(), func(){ setBuildProfilePage_ChooseProfileLanguage(window, profileType, currentPage, currentPage) }) noResponseButton := widget.NewButtonWithIcon("No Response", theme.CancelIcon(), func(){ err := myLocalProfiles.DeleteProfileData(profileType, "ProfileLanguage") if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, chooseOrEditLanguageButton, noResponseButton)) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), myProfileLanguageRow, widget.NewSeparator(), buttonsGrid) setPageContent(page, window) } func setBuildProfilePage_ChooseProfileLanguage(window fyne.Window, profileType string, previousPage func(), nextPage func()){ currentPage := func(){setBuildProfilePage_ChooseProfileLanguage(window, profileType, previousPage, nextPage)} title := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Choose Language") description := getLabelCentered("Choose your profile language.") worldLanguageObjectsList, err := worldLanguages.GetWorldLanguageObjectsList() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } // This list stores the translated language names worldLanguageDescriptionsList := make([]string, 0, len(worldLanguageObjectsList)) // This map will store the language identifiers //Map Structure: Language Description -> Language identifier worldLanguageIdentifiersMap := make(map[string]int) for _, languageObject := range worldLanguageObjectsList{ languageIdentifier := languageObject.Identifier languageNamesList := languageObject.NamesList languageDescription := helpers.TranslateAndJoinStringListItems(languageNamesList, "/") worldLanguageDescriptionsList = append(worldLanguageDescriptionsList, languageDescription) worldLanguageIdentifiersMap[languageDescription] = languageIdentifier } helpers.SortStringListToUnicodeOrder(worldLanguageDescriptionsList) onSelectedFunction := func(itemIndex int){ languageDescription := worldLanguageDescriptionsList[itemIndex] languageIdentifier, exists := worldLanguageIdentifiersMap[languageDescription] if (exists == false){ setErrorEncounteredPage(window, errors.New("worldLanguageIdentifiersMap missing languageDescription: " + languageDescription), currentPage) return } languageIdentifierString := helpers.ConvertIntToString(languageIdentifier) err := myLocalProfiles.SetProfileData(profileType, "ProfileLanguage", languageIdentifierString) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } nextPage() } languagesWidgetList, err := getFyneWidgetListFromStringList(worldLanguageDescriptionsList, onSelectedFunction) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } header := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator()) page := container.NewBorder(header, nil, nil, nil, languagesWidgetList) setPageContent(page, window) } func setBuildProfilePage_Username(window fyne.Window, profileType string, previousPage func()){ currentPage := func(){setBuildProfilePage_Username(window, profileType, previousPage)} pageTitle := getPageTitleCentered(translate("Build " + profileType + " Profile - General")) backButton := getBackButtonCentered(previousPage) pageSubtitle := getPageSubtitleCentered(translate("Username")) description1 := getLabelCentered(translate("Create your username.")) description2 := getLabelCentered(translate("You do not have to enter your real name.")) description3 := getLabelCentered(translate("If you want to protect your privacy, don't share your surname.")) getMyCurrentUsernameRow := func()(*fyne.Container, error){ myUsernameLabel := widget.NewLabel("My Username:") exists, currentUsername, err := myLocalProfiles.GetProfileData(profileType, "Username") if (err != nil) { return nil, err } if (exists == false){ noResponseLabel := getBoldItalicLabel("No Response") currentUsernameRow := container.NewHBox(layout.NewSpacer(), myUsernameLabel, noResponseLabel, layout.NewSpacer()) return currentUsernameRow, nil } isAllowed := allowedText.VerifyStringIsAllowed(currentUsername) if (isAllowed == false){ return nil, errors.New("My current " + profileType + " username is not allowed: Not valid utf8: " + currentUsername) } containsTabsOrNewlines := helpers.CheckIfStringContainsTabsOrNewlines(currentUsername) if (containsTabsOrNewlines == true){ return nil, errors.New("My current " + profileType + " username is not allowed: Contains newline/tab: " + currentUsername) } if( len(currentUsername) > 25){ return nil, errors.New("Current " + profileType + " username is too long: " + currentUsername) } currentUsernameLabel := getBoldLabel(currentUsername) currentUsernameRow := container.NewHBox(layout.NewSpacer(), myUsernameLabel, currentUsernameLabel, layout.NewSpacer()) return currentUsernameRow, nil } currentUsernameRow, err := getMyCurrentUsernameRow() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } editButton := widget.NewButtonWithIcon("Edit", theme.DocumentCreateIcon(), func(){ setBuildProfilePage_EditUsername(window, profileType, currentPage, currentPage) }) noResponseButton := widget.NewButtonWithIcon(translate("No Response"), theme.CancelIcon(), func(){ myLocalProfiles.DeleteProfileData(profileType, "Username") currentPage() }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, editButton, noResponseButton)) page := container.NewVBox(pageTitle, backButton, widget.NewSeparator(), pageSubtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), currentUsernameRow, widget.NewSeparator(), buttonsGrid) setPageContent(page, window) } func setBuildProfilePage_EditUsername(window fyne.Window, profileType string, previousPage func(), nextPage func()){ title := getPageTitleCentered("Edit " + profileType + " Username") backButton := getBackButtonCentered(previousPage) usernameEntry := widget.NewEntry() // Outputs: // -bool: Current username exists // -string: Current username // -error getCurrentUsername := func()(bool, string, error){ exists, currentUsername, err := myLocalProfiles.GetProfileData(profileType, "Username") if (err != nil) { return false, "", err } if (exists == false){ return false, "", nil } isAllowed := allowedText.VerifyStringIsAllowed(currentUsername) if (isAllowed == false){ return false, "", errors.New("My current " + profileType + " username is not allowed: " + currentUsername) } containsTabsOrNewlines := helpers.CheckIfStringContainsTabsOrNewlines(currentUsername) if (containsTabsOrNewlines == true){ return false, "", errors.New("My current " + profileType + " username is not allowed: " + currentUsername) } if( len(currentUsername) > 25){ return false, "", errors.New("My current " + profileType + " username is too long: " + currentUsername) } return true, currentUsername, nil } currentUsernameExists, currentUsername, err := getCurrentUsername() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (currentUsernameExists == false){ usernameEntry.SetPlaceHolder("Enter a username...") } else { usernameEntry.SetText(currentUsername) } submitButton := getWidgetCentered(widget.NewButtonWithIcon(translate("Save"), theme.ConfirmIcon(), func(){ newUsername := usernameEntry.Text isAllowed := allowedText.VerifyStringIsAllowed(newUsername) if (isAllowed == false){ title := translate("Invalid Username") dialogMessageA := getLabelCentered(translate("Your username contains an invalid character.")) dialogMessageB := getLabelCentered(translate("It must be encoded in UTF-8.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } containsTabsOrNewlines := helpers.CheckIfStringContainsTabsOrNewlines(newUsername) if (containsTabsOrNewlines == true){ title := translate("Invalid Username") dialogMessageA := getLabelCentered(translate("Your username contains a tab or a newline character.")) dialogMessageB := getLabelCentered(translate("Remove this character and resubmit.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } currentBytesLength := len(newUsername) if (currentBytesLength > 25){ currentLengthString := helpers.ConvertIntToString(currentBytesLength) title := translate("Invalid Username") dialogMessageA := getLabelCentered(translate("Username is too long.")) dialogMessageB := getLabelCentered(translate("Username cannot be longer than 25 bytes.")) dialogMessageC := getLabelCentered(translate("Your submission length: ") + currentLengthString + " bytes.") dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } if (newUsername == ""){ myLocalProfiles.DeleteProfileData(profileType, "Username") } else { myLocalProfiles.SetProfileData(profileType, "Username", newUsername) } nextPage() })) emptyLabelA := widget.NewLabel("") emptyLabelB := widget.NewLabel("") emptyLabelC := widget.NewLabel("") submitButtonWithSpacers := container.NewVBox(submitButton, emptyLabelA, emptyLabelB, emptyLabelC) usernameEntryBoxed := getWidgetBoxed(usernameEntry) usernameEntryWithSubmitButton := container.NewBorder(nil, submitButtonWithSpacers, nil, nil, usernameEntryBoxed) header := container.NewVBox(title, backButton, widget.NewSeparator()) page := container.NewBorder(header, nil, nil, nil, usernameEntryWithSubmitButton) setPageContent(page, window) } func setBuildProfilePage_Description(window fyne.Window, profileType string, previousPage func()){ currentPage := func(){setBuildProfilePage_Description(window, profileType, previousPage)} pageTitle := getPageTitleCentered(translate("Build " + profileType + " Profile - General")) backButton := getBackButtonCentered(previousPage) pageSubtitle := getPageSubtitleCentered(translate("Description")) pageDescription := getLabelCentered(translate("Create your profile description.")) getMyCurrentDescriptionRow := func()(*fyne.Container, error){ myDescriptionLabel := widget.NewLabel("My Description:") exists, currentDescription, err := myLocalProfiles.GetProfileData(profileType, "Description") if (err != nil) { return nil, err } if (exists == false){ noResponseLabel := getBoldItalicLabel("No Response") currentDescriptionRow := container.NewHBox(layout.NewSpacer(), myDescriptionLabel, noResponseLabel, layout.NewSpacer()) return currentDescriptionRow, nil } isAllowed := allowedText.VerifyStringIsAllowed(currentDescription) if (isAllowed == false){ return nil, errors.New("My current " + profileType + "description is not allowed: Not utf8.") } getSizeLimit := func()int{ if (profileType == "Moderator"){ return 500 } if (profileType == "Host"){ return 300 } return 3000 } sizeLimit := getSizeLimit() if (len(currentDescription) > sizeLimit){ return nil, errors.New("My current " + profileType + " description is too long.") } currentDescriptionTrimmed, _, err := helpers.TrimAndFlattenString(currentDescription, 15) if (err != nil) { return nil, err } currentDescriptionLabel := getBoldLabel(currentDescriptionTrimmed) viewMyDescriptionButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ setViewTextPage(window, "Viewing Description", currentDescription, false, currentPage) }) currentDescriptionRow := container.NewHBox(layout.NewSpacer(), myDescriptionLabel, currentDescriptionLabel, viewMyDescriptionButton, layout.NewSpacer()) return currentDescriptionRow, nil } currentDescriptionRow, err := getMyCurrentDescriptionRow() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } editButton := widget.NewButtonWithIcon("Edit", theme.DocumentCreateIcon(), func(){ setBuildProfilePage_EditDescription(window, profileType, currentPage, currentPage) }) noResponseButton := widget.NewButtonWithIcon(translate("No Response"), theme.CancelIcon(), func(){ setBuildProfilePage_DeleteDescription(window, profileType, currentPage, currentPage) }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, editButton, noResponseButton)) page := container.NewVBox(pageTitle, backButton, widget.NewSeparator(), pageSubtitle, widget.NewSeparator(), pageDescription, widget.NewSeparator(), currentDescriptionRow, widget.NewSeparator(), buttonsGrid) setPageContent(page, window) } func setBuildProfilePage_DeleteDescription(window fyne.Window, profileType string, previousPage func(), nextPage func()){ title := getPageTitleCentered("Delete " + profileType + " Description") backButton := getBackButtonCentered(previousPage) description1 := getBoldLabelCentered("Delete " + profileType + " Description?") description2 := getLabelCentered("Confirm to delete your description?") confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Delete", theme.DeleteIcon(), func(){ myLocalProfiles.DeleteProfileData(profileType, "Description") nextPage() })) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, confirmButton) setPageContent(page, window) } func setBuildProfilePage_EditDescription(window fyne.Window, profileType string, previousPage func(), nextPage func()){ title := getPageTitleCentered("Edit " + profileType + " Description") backButton := getBackButtonCentered(previousPage) // Returns byte length limit for description length getSizeLimit := func()int{ if (profileType == "Moderator"){ return 500 } if (profileType == "Host"){ return 300 } return 3000 } sizeLimit := getSizeLimit() descriptionEntry := widget.NewMultiLineEntry() descriptionEntry.Wrapping = 3 //Outputs: // -bool: Current description exists // -string: Current description // -error getCurrentDescription := func()(bool, string, error){ exists, currentDescription, err := myLocalProfiles.GetProfileData(profileType, "Description") if (err != nil) { return false, "", err } if (exists == false){ return false, "", nil } isAllowed := allowedText.VerifyStringIsAllowed(currentDescription) if (isAllowed == false){ return false, "", errors.New("My current " + profileType + "description is not allowed.") } if (len(currentDescription) > sizeLimit){ return false, "", errors.New("My current " + profileType + " description is too long.") } return true, currentDescription, nil } currentDescriptionExists, currentDescription, err := getCurrentDescription() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (currentDescriptionExists == false){ descriptionEntry.SetPlaceHolder("Enter a description...") } else { descriptionEntry.SetText(currentDescription) } submitButton := getWidgetCentered(widget.NewButtonWithIcon(translate("Save"), theme.ConfirmIcon(), func(){ newDescription := descriptionEntry.Text isAllowed := allowedText.VerifyStringIsAllowed(newDescription) if (isAllowed == false){ title := translate("Invalid Description") dialogMessageA := getLabelCentered(translate("Your description contains an invalid character.")) dialogMessageB := getLabelCentered(translate("Your description must be encoded in UTF-8.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } newDescriptionLength := len(newDescription) if (newDescriptionLength > sizeLimit){ sizeLimitString := helpers.ConvertIntToStringWithCommas(sizeLimit) currentLengthString := helpers.ConvertIntToString(newDescriptionLength) title := translate("Invalid Description") dialogMessageA := getLabelCentered(translate("Description is too long.")) dialogMessageB := getLabelCentered(translate("Description cannot be longer than " + sizeLimitString + " bytes.")) dialogMessageC := getLabelCentered(translate("Your submission is " + currentLengthString + " bytes.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } if (newDescription == ""){ myLocalProfiles.DeleteProfileData(profileType, "Description") } else { myLocalProfiles.SetProfileData(profileType, "Description", newDescription) } nextPage() })) emptyLabel := widget.NewLabel("") submitButtonWithSpacer := container.NewVBox(submitButton, emptyLabel) descriptionEntryBoxed := getWidgetBoxed(descriptionEntry) descriptionEntryWithSubmitButton := container.NewBorder(nil, submitButtonWithSpacer, nil, nil, descriptionEntryBoxed) header := container.NewVBox(title, backButton, widget.NewSeparator()) page := container.NewBorder(header, nil, nil, nil, descriptionEntryWithSubmitButton) setPageContent(page, window) } func setBuildMateProfilePage_Location(window fyne.Window, previousPage func()){ setLoadingScreen(window, "Build Mate Profile - General", "Loading...") currentPage := func(){setBuildMateProfilePage_Location(window, previousPage)} title := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Location")) description1 := getLabelCentered("Add your location to your profile.") description2 := getLabelCentered("You can add a primary and a secondary location.") //Outputs: // -bool: Location exists // -float64: Location latitude // -float64: Location longitude // -bool: Location country exists // -int: Location country identifier // -error getMyLocationInfo := func(rank string)(bool, float64, float64, bool, int, error){ if (rank != "Primary" && rank != "Secondary"){ return false, 0, 0, false, 0, errors.New("getMyLocationInfo called with invalid rank: " + rank) } exists, locationLatitude, err := myLocalProfiles.GetProfileData("Mate", rank + "LocationLatitude") if (err != nil) { return false, 0, 0, false, 0, err } if (exists == false){ return false, 0, 0, false, 0, nil } exists, locationLongitude, err := myLocalProfiles.GetProfileData("Mate", rank + "LocationLongitude") if (err != nil) { return false, 0, 0, false, 0, err } if (exists == false){ return false, 0, 0, false, 0, errors.New("MyLocalProfiles contains " + rank + "LocationLatitude but missing " + rank + "LocationLongitude") } locationLatitudeFloat64, err := helpers.ConvertStringToFloat64(locationLatitude) if (err != nil){ return false, 0, 0, false, 0, errors.New("MyLocalProfiles contains invalid " + rank + "LocationLatitude: " + locationLatitude) } locationLongitudeFloat64, err := helpers.ConvertStringToFloat64(locationLongitude) if (err != nil){ return false, 0, 0, false, 0, errors.New("MyLocalProfiles contains invalid " + rank + "LocationLongitude: " + locationLongitude) } exists, locationCountryIdentifier, err := myLocalProfiles.GetProfileData("Mate", rank + "LocationCountry") if (err != nil) { return false, 0, 0, false, 0, err } if (exists == false){ return true, locationLatitudeFloat64, locationLongitudeFloat64, false, 0, nil } locationCountryIdentifierInt, err := helpers.ConvertStringToInt(locationCountryIdentifier) if (err != nil){ return false, 0, 0, false, 0, errors.New("MyLocalProfiles contains invalid " + rank + "LocationCountry: " + locationCountryIdentifier) } return true, locationLatitudeFloat64, locationLongitudeFloat64, true, locationCountryIdentifierInt, nil } primaryLocationExists, primaryLocationLatitude, primaryLocationLongitude, primaryLocationCountryExists, primaryLocationCountryIdentifier, err := getMyLocationInfo("Primary") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } secondaryLocationExists, secondaryLocationLatitude, secondaryLocationLongitude, secondaryLocationCountryExists, secondaryLocationCountryIdentifier, err := getMyLocationInfo("Secondary") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (primaryLocationExists == false && secondaryLocationExists == true){ setErrorEncounteredPage(window, errors.New("Primary location does not exist, secondary location exists."), previousPage) return } addLocationButton := getWidgetCentered(widget.NewButtonWithIcon("Add Location", theme.ContentAddIcon(), func(){ if (primaryLocationExists == true && secondaryLocationExists == true){ title := translate("Cannot Add Location.") dialogMessageA := getLabelCentered(translate("You can only have 2 locations.")) dialogMessageB := getLabelCentered(translate("Delete a location first.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } setBuildMateProfilePage_AddLocation_ChooseCountry(window, currentPage, currentPage) })) if (primaryLocationExists == false){ noLocationsExistLabel := getBoldLabelCentered("No Locations Exist.") page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), noLocationsExistLabel, addLocationButton) setPageContent(page, window) return } visibilityLabel := getBoldLabel("Visibility:") visibilityOptions := []string{translate("Show On Profile"), translate("Hide From Profile")} visibilitySelector := widget.NewSelect(visibilityOptions, func(newVisibility string){ getNewVisibilityStatus := func()string{ if (newVisibility == translate("Show On Profile")){ return "Yes" } return "No" } newVisibilityStatus := getNewVisibilityStatus() err := myLocalProfiles.SetProfileData("Mate", "VisibilityStatus_Location", newVisibilityStatus) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } }) exists, visibilityStatus, err := myLocalProfiles.GetProfileData("Mate", "VisibilityStatus_Location") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (exists == true && visibilityStatus == "No"){ visibilitySelector.Selected = translate("Hide From Profile") } else { visibilitySelector.Selected = translate("Show On Profile") } profileVisibilityHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setMateProfileAttributeVisibilityExplainerPage(window, currentPage) }) visibilitySelectorRow := container.NewHBox(layout.NewSpacer(), visibilityLabel, visibilitySelector, profileVisibilityHelpButton, layout.NewSpacer()) getLocationsGrid := func()(*fyne.Container, error){ rankLabel := getItalicLabelCentered("Rank") countryLabel := getItalicLabelCentered("Country") locationLabel := getItalicLabelCentered("Location") emptyLabel := widget.NewLabel("") rankColumn := container.NewVBox(rankLabel, widget.NewSeparator()) countryColumn := container.NewVBox(countryLabel, widget.NewSeparator()) locationColumn := container.NewVBox(locationLabel, widget.NewSeparator()) manageButtonsColumn := container.NewVBox(emptyLabel, widget.NewSeparator()) addLocationRow := func(locationRank string, locationLatitude float64, locationLongitude float64, locationCountryExists bool, locationCountryIdentifier int)error{ getCountryText := func()(string, error){ if (locationCountryExists == false){ noneText := translate("None") return noneText, nil } locationObject, err := worldLocations.GetCountryObjectFromCountryIdentifier(locationCountryIdentifier) if (err != nil) { return "", err } locationNamesList := locationObject.NamesList locationDescription := helpers.TranslateAndJoinStringListItems(locationNamesList, "/") locationCountryTranslatedTrimmed, _, err := helpers.TrimAndFlattenString(locationDescription, 20) if (err != nil) { return "", err } return locationCountryTranslatedTrimmed, nil } countryText, err := getCountryText() if (err != nil) { return err } getLocationText := func()(string, error){ locationCityFound, locationCity, locationState, _, err := worldLocations.GetCityFromCoordinates(locationLatitude, locationLongitude) if (err != nil) { return "", err } if (locationCityFound == false){ locationLatitudeString := helpers.ConvertFloat64ToStringRounded(locationLatitude, 5) locationLongitudeString := helpers.ConvertFloat64ToStringRounded(locationLongitude, 5) formattedCoordinates := locationLatitudeString + "°, " + locationLongitudeString + "°" return formattedCoordinates, nil } locationCityFormatted := locationCity + ", " + locationState locationCityTrimmed, _, err := helpers.TrimAndFlattenString(locationCityFormatted, 25) if (err != nil) { return "", err } return locationCityTrimmed, nil } locationText, err := getLocationText() if (err != nil) { return err } locationRankLabel := getBoldLabelCentered(locationRank) locationCountryLabel := getBoldLabelCentered(countryText) locationTextLabel := getBoldLabelCentered(locationText) manageLocationButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ setBuildMateProfilePage_ManageLocation(window, locationRank, currentPage, currentPage) }) rankColumn.Add(locationRankLabel) countryColumn.Add(locationCountryLabel) locationColumn.Add(locationTextLabel) manageButtonsColumn.Add(manageLocationButton) rankColumn.Add(widget.NewSeparator()) countryColumn.Add(widget.NewSeparator()) locationColumn.Add(widget.NewSeparator()) manageButtonsColumn.Add(widget.NewSeparator()) return nil } err = addLocationRow("Primary", primaryLocationLatitude, primaryLocationLongitude, primaryLocationCountryExists, primaryLocationCountryIdentifier) if (err != nil) { return nil, err } if (secondaryLocationExists == true){ err := addLocationRow("Secondary", secondaryLocationLatitude, secondaryLocationLongitude, secondaryLocationCountryExists, secondaryLocationCountryIdentifier) if (err != nil) { return nil, err } } locationsGrid := container.NewHBox(layout.NewSpacer(), rankColumn, countryColumn, locationColumn, manageButtonsColumn, layout.NewSpacer()) if (secondaryLocationExists == false){ locationGridWithAddLocationButton := container.NewVBox(locationsGrid, addLocationButton) return locationGridWithAddLocationButton, nil } swapLocationsFunction := func()error{ primaryLocationLatitudeString := helpers.ConvertFloat64ToString(primaryLocationLatitude) primaryLocationLongitudeString := helpers.ConvertFloat64ToString(primaryLocationLongitude) secondaryLocationLatitudeString := helpers.ConvertFloat64ToString(secondaryLocationLatitude) secondaryLocationLongitudeString := helpers.ConvertFloat64ToString(secondaryLocationLongitude) err = myLocalProfiles.SetProfileData("Mate", "PrimaryLocationLatitude", secondaryLocationLatitudeString) if (err != nil) { return err } err = myLocalProfiles.SetProfileData("Mate", "PrimaryLocationLongitude", secondaryLocationLongitudeString) if (err != nil) { return err } if (secondaryLocationCountryExists == true){ secondaryLocationCountryIdentifierString := helpers.ConvertIntToString(secondaryLocationCountryIdentifier) err := myLocalProfiles.SetProfileData("Mate", "PrimaryLocationCountry", secondaryLocationCountryIdentifierString) if (err != nil) { return err } } else { err := myLocalProfiles.DeleteProfileData("Mate", "PrimaryLocationCountry") if (err != nil) { return err } } err = myLocalProfiles.SetProfileData("Mate", "SecondaryLocationLatitude", primaryLocationLatitudeString) if (err != nil) { return err } err = myLocalProfiles.SetProfileData("Mate", "SecondaryLocationLongitude", primaryLocationLongitudeString) if (err != nil) { return err } if (primaryLocationCountryExists == true){ primaryLocationCountryIdentifierString := helpers.ConvertIntToString(primaryLocationCountryIdentifier) err := myLocalProfiles.SetProfileData("Mate", "SecondaryLocationCountry", primaryLocationCountryIdentifierString) if (err != nil) { return err } } else { err := myLocalProfiles.DeleteProfileData("Mate", "SecondaryLocationCountry") if (err != nil) { return err } } return nil } swapLocationsButton := widget.NewButtonWithIcon("Swap Locations", theme.ContentRedoIcon(), func(){ err := swapLocationsFunction() if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, swapLocationsButton, addLocationButton)) locationGridWithButtons := container.NewVBox(locationsGrid, buttonsGrid) return locationGridWithButtons, nil } locationsGrid, err := getLocationsGrid() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), visibilitySelectorRow, widget.NewSeparator(), locationsGrid) setPageContent(page, window) } func setBuildMateProfilePage_ManageLocation(window fyne.Window, locationRank string, previousPage func(), afterDeletePage func()){ if (locationRank != "Primary" && locationRank != "Secondary"){ setErrorEncounteredPage(window, errors.New("setBuildMateProfilePage_ManageLocation called with invalid locationRank: " + locationRank), previousPage) return } setLoadingScreen(window, "Build Mate Profile - General", "Loading...") currentPage := func(){setBuildMateProfilePage_ManageLocation(window, locationRank, previousPage, afterDeletePage)} title := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Manage Location")) description1 := getLabelCentered("This is your " + locationRank + " location.") //Outputs: // -bool: Location exists // -float64: Location latitude // -float64: Location longitude // -bool: Location country exists // -int: Location country identifier // -error getMyLocationInfo := func(rank string)(bool, float64, float64, bool, int, error){ if (rank != "Primary" && rank != "Secondary"){ return false, 0, 0, false, 0, errors.New("getMyLocationInfo called with invalid rank: " + rank) } exists, locationLatitude, err := myLocalProfiles.GetProfileData("Mate", rank + "LocationLatitude") if (err != nil) { return false, 0, 0, false, 0, err } if (exists == false){ return false, 0, 0, false, 0, nil } exists, locationLongitude, err := myLocalProfiles.GetProfileData("Mate", rank + "LocationLongitude") if (err != nil) { return false, 0, 0, false, 0, err } if (exists == false){ return false, 0, 0, false, 0, errors.New("MyLocalProfiles contains " + rank + "LocationLatitude but missing " + rank + "LocationLongitude") } locationLatitudeFloat64, err := helpers.ConvertStringToFloat64(locationLatitude) if (err != nil){ return false, 0, 0, false, 0, errors.New("MyLocalProfiles contains invalid " + rank + "LocationLatitude: " + locationLatitude) } locationLongitudeFloat64, err := helpers.ConvertStringToFloat64(locationLongitude) if (err != nil){ return false, 0, 0, false, 0, errors.New("MyLocalProfiles contains invalid " + rank + "LocationLongitude: " + locationLongitude) } exists, locationCountryIdentifier, err := myLocalProfiles.GetProfileData("Mate", rank + "LocationCountry") if (err != nil) { return false, 0, 0, false, 0, err } if (exists == false){ return true, locationLatitudeFloat64, locationLongitudeFloat64, false, 0, nil } locationCountryIdentifierInt, err := helpers.ConvertStringToInt(locationCountryIdentifier) if (err != nil){ return false, 0, 0, false, 0, errors.New("MyLocalProfiles contains invalid locationCountryIdentifier: " + locationCountryIdentifier) } return true, locationLatitudeFloat64, locationLongitudeFloat64, true, locationCountryIdentifierInt, nil } locationExists, locationLatitude, locationLongitude, locationCountryExists, locationCountryIdentifier, err := getMyLocationInfo(locationRank) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (locationExists == false){ setErrorEncounteredPage(window, errors.New("setBuildMateProfilePage_ManageLocation called with missing location"), previousPage) return } getCountryText := func()(string, error){ if (locationCountryExists == false){ noneText := translate("None") return noneText, nil } locationObject, err := worldLocations.GetCountryObjectFromCountryIdentifier(locationCountryIdentifier) if (err != nil) { return "", err } locationNamesList := locationObject.NamesList locationDescription := helpers.TranslateAndJoinStringListItems(locationNamesList, "/") return locationDescription, nil } countryText, err := getCountryText() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } countryLabel := getLabelCentered("Country:") locationCountryLabel := getBoldLabel(countryText) coordinatesLabel := getLabelCentered("Coordinates:") locationLatitudeString := helpers.ConvertFloat64ToString(locationLatitude) locationLongitudeString := helpers.ConvertFloat64ToString(locationLongitude) locationCoordinatesLabel := getBoldLabel(locationLatitudeString + "°, " + locationLongitudeString + "°") cityLabel := getLabelCentered("City:") getCityContent := func()(*fyne.Container, error){ // We will either find the exact city, or find the closest city cityName, cityState, cityCountryIdentifier, cityDistanceKilometers, err := worldLocations.GetClosestCityFromCoordinates(locationLatitude, locationLongitude) if (err != nil) { return nil, err } if (cityDistanceKilometers == 0){ locationCityFormatted := cityName + ", " + cityState locationCityLabel := getBoldLabelCentered(locationCityFormatted) return locationCityLabel, nil } getNearbyCityDistanceFormattedString := func()(string, error){ currentUnitsExist, currentUnits, err := globalSettings.GetSetting("MetricOrImperial") if (err != nil){ return "", err } 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 && cityCountryIdentifier == locationCountryIdentifier){ result := cityName + ", " + cityState return result, nil } locationObject, err := worldLocations.GetCountryObjectFromCountryIdentifier(cityCountryIdentifier) if (err != nil) { return "", err } locationPrimaryName := locationObject.NamesList[0] locationPrimaryNameTranslated := translate(locationPrimaryName) result := cityName + ", " + cityState + ", " + locationPrimaryNameTranslated 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){ setErrorEncounteredPage(window, err, previousPage) return } deleteFunction := func()error{ if (locationRank == "Secondary"){ err := myLocalProfiles.DeleteProfileData("Mate", "SecondaryLocationLatitude") if (err != nil) { return err } err = myLocalProfiles.DeleteProfileData("Mate", "SecondaryLocationLongitude") if (err != nil) { return err } err = myLocalProfiles.DeleteProfileData("Mate", "SecondaryLocationCountry") if (err != nil) { return err } return nil } // We want to delete the primary location // If there is a secondary location, we set it as the primary after deleting the primary primaryLocationExists, _, _, _, _, err := getMyLocationInfo("Primary") if (err != nil){ return err } if (primaryLocationExists == false){ return errors.New("Trying to delete the primary location, but the location does not exist.") } err = myLocalProfiles.DeleteProfileData("Mate", "PrimaryLocationLatitude") if (err != nil) { return err } err = myLocalProfiles.DeleteProfileData("Mate", "PrimaryLocationLongitude") if (err != nil) { return err } err = myLocalProfiles.DeleteProfileData("Mate", "PrimaryLocationCountry") if (err != nil) { return err } secondaryLocationExists, secondaryLocationLatitude, secondaryLocationLongitude, secondaryLocationCountryExists, secondaryLocationCountryIdentifier, err := getMyLocationInfo("Secondary") if (err != nil){ return err } if (secondaryLocationExists == false){ return nil } err = myLocalProfiles.DeleteProfileData("Mate", "SecondaryLocationLatitude") if (err != nil) { return err } err = myLocalProfiles.DeleteProfileData("Mate", "SecondaryLocationLongitude") if (err != nil) { return err } err = myLocalProfiles.DeleteProfileData("Mate", "SecondaryLocationCountry") if (err != nil) { return err } secondaryLocationLatitudeString := helpers.ConvertFloat64ToString(secondaryLocationLatitude) secondaryLocationLongitudeString := helpers.ConvertFloat64ToString(secondaryLocationLongitude) err = myLocalProfiles.SetProfileData("Mate", "PrimaryLocationLatitude", secondaryLocationLatitudeString) if (err != nil) { return err } err = myLocalProfiles.SetProfileData("Mate", "PrimaryLocationLongitude", secondaryLocationLongitudeString) if (err != nil) { return err } if (secondaryLocationCountryExists == true){ secondaryLocationCountryIdentifierString := helpers.ConvertIntToString(secondaryLocationCountryIdentifier) err := myLocalProfiles.SetProfileData("Mate", "PrimaryLocationCountry", secondaryLocationCountryIdentifierString) if (err != nil) { return err } } return nil } deleteButton := getWidgetCentered(widget.NewButtonWithIcon("Delete", theme.DeleteIcon(), func(){ err := deleteFunction() if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } afterDeletePage() })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, widget.NewSeparator(), countryLabel, locationCountryLabel, widget.NewSeparator(), coordinatesLabel, locationCoordinatesLabel, widget.NewSeparator(), cityLabel, cityContent, widget.NewSeparator(), deleteButton) setPageContent(page, window) } func setBuildMateProfilePage_AddLocation_ChooseCountry(window fyne.Window, previousPage func(), visitOnCompletePage func()){ currentPage := func(){setBuildMateProfilePage_AddLocation_ChooseCountry(window, previousPage, visitOnCompletePage)} title := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Add Location")) description1 := getLabelCentered("Choose the country of the location to add.") description2 := getLabelCentered("If your country is not listed, select Country Is Missing") allCountryObjectsList, err := worldLocations.GetAllCountryObjectsList() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } allCountryDescriptionsList := make([]string, 0, len(allCountryObjectsList)) // Map Structure: Country Description -> Country identifier countryIdentifiersMap := make(map[string]int) for _, countryObject := range allCountryObjectsList{ countryIdentifier := countryObject.Identifier countryNamesList := countryObject.NamesList countryDescription := helpers.TranslateAndJoinStringListItems(countryNamesList, "/") countryIdentifiersMap[countryDescription] = countryIdentifier allCountryDescriptionsList = append(allCountryDescriptionsList, countryDescription) } helpers.SortStringListToUnicodeOrder(allCountryDescriptionsList) onSelectedFunction := func(selectedCountryIndex int) { selectedCountryDescription := allCountryDescriptionsList[selectedCountryIndex] selectedCountryIdentifier, exists := countryIdentifiersMap[selectedCountryDescription] if (exists == false){ setErrorEncounteredPage(window, errors.New("countryIdentifiersMap missing country description: " + selectedCountryDescription), currentPage) return } setBuildMateProfilePage_AddLocation_ChooseCity(window, selectedCountryIdentifier, false, "", currentPage, visitOnCompletePage) } widgetList, err := getFyneWidgetListFromStringList(allCountryDescriptionsList, onSelectedFunction) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } header := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator()) countryIsMissingButton := getWidgetCentered(widget.NewButton("Country Is Missing", func(){ setBuildMateProfilePage_AddLocation_CustomCoordinates(window, false, 0, currentPage, visitOnCompletePage) })) page := container.NewBorder(header, countryIsMissingButton, nil, nil, widgetList) setPageContent(page, window) } func setBuildMateProfilePage_AddLocation_ChooseCity(window fyne.Window, selectedCountryIdentifier int, searchTermExists bool, searchTerm string, previousPage func(), visitOnCompletePage func()){ if (searchTermExists == true){ setLoadingScreen(window, "Build Mate Profile - General", "Loading Search Results...") } else { setLoadingScreen(window, "Build Mate Profile - General", "Loading...") } currentPage := func(){setBuildMateProfilePage_AddLocation_ChooseCity(window, selectedCountryIdentifier, searchTermExists, searchTerm, previousPage, visitOnCompletePage)} allCountryCityObjectsList, err := worldLocations.GetAllCityObjectsInCountry(selectedCountryIdentifier) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (len(allCountryCityObjectsList) == 0){ // This country has no cities in the database // We go directly to the custom location screen setBuildMateProfilePage_AddLocation_CustomCoordinates(window, true, selectedCountryIdentifier, previousPage, visitOnCompletePage) return } countryObject, err := worldLocations.GetCountryObjectFromCountryIdentifier(selectedCountryIdentifier) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } countryNamesList := countryObject.NamesList countryDescription := helpers.TranslateAndJoinStringListItems(countryNamesList, "/") title := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered("Choose Location") description1 := getLabelCentered("You must now enter a specific location in " + countryDescription + ".") description2 := getLabelCentered("You can search for and choose a city from below.") description3 := getLabelCentered("If your city is not listed, or to add a custom location, select Add Custom") addCustomLocationButton := getWidgetCentered(widget.NewButtonWithIcon("Add Custom", theme.ContentAddIcon(), func(){ setBuildMateProfilePage_AddLocation_CustomCoordinates(window, true, selectedCountryIdentifier, currentPage, visitOnCompletePage) })) enterSearchTermLabel := getBoldLabelCentered("Enter Search Term:") enterSearchTermEntry := widget.NewEntry() if (searchTermExists == true){ enterSearchTermEntry.SetText(searchTerm) } else { enterSearchTermEntry.SetPlaceHolder("Enter search term...") } searchButton := widget.NewButtonWithIcon("Search", theme.SearchIcon(), func(){ newSearchTerm := enterSearchTermEntry.Text if (newSearchTerm == searchTerm){ return } if (newSearchTerm == ""){ setBuildMateProfilePage_AddLocation_ChooseCity(window, selectedCountryIdentifier, false, "", previousPage, visitOnCompletePage) return } setBuildMateProfilePage_AddLocation_ChooseCity(window, selectedCountryIdentifier, true, newSearchTerm, previousPage, visitOnCompletePage) }) enterSearchTermRow := getContainerCentered(container.NewGridWithRows(1, enterSearchTermLabel, enterSearchTermEntry, searchButton)) header := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), addCustomLocationButton, widget.NewSeparator(), enterSearchTermRow, widget.NewSeparator()) getCityObjectsToDisplayList := func()[]worldLocations.CityObject{ if (searchTermExists == false){ return allCountryCityObjectsList } allCityObjectsWithSearchTermList := make([]worldLocations.CityObject, 0) searchTermLowercase := strings.ToLower(searchTerm) for _, cityObject := range allCountryCityObjectsList{ cityName := cityObject.Name cityNameLowercase := strings.ToLower(cityName) containsAny := strings.Contains(cityNameLowercase, searchTermLowercase) if (containsAny == true){ allCityObjectsWithSearchTermList = append(allCityObjectsWithSearchTermList, cityObject) } } return allCityObjectsWithSearchTermList } cityObjectsToDisplayList := getCityObjectsToDisplayList() if (len(cityObjectsToDisplayList) == 0){ noCitiesFoundLabel := getBoldLabelCentered("No cities found.") page := container.NewVBox(header, noCitiesFoundLabel) setPageContent(page, window) return } cityNamesFormattedList := make([]string, 0, len(cityObjectsToDisplayList)) for _, cityObject := range cityObjectsToDisplayList{ stateName := cityObject.State cityName := cityObject.Name nameFormatted := cityName + ", " + stateName nameFormattedAndTrimmed, _, err := helpers.TrimAndFlattenString(nameFormatted, 50) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } cityNamesFormattedList = append(cityNamesFormattedList, nameFormattedAndTrimmed) } onClickedFunction := func(cityIndex int){ selectedCityObject := cityObjectsToDisplayList[cityIndex] selectedCityLatitude := selectedCityObject.Latitude selectedCityLongitude := selectedCityObject.Longitude setBuildMateProfilePage_ConfirmAddLocation(window, true, selectedCountryIdentifier, selectedCityLatitude, selectedCityLongitude, currentPage, visitOnCompletePage) } citySearchResultsWidgetList, err := getFyneWidgetListFromStringList(cityNamesFormattedList, onClickedFunction) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewBorder(header, nil, nil, nil, citySearchResultsWidgetList) setPageContent(page, window) return } func setBuildMateProfilePage_AddLocation_CustomCoordinates(window fyne.Window, countryExists bool, countryIdentifier int, previousPage func(), visitOnCompletePage func()){ currentPage := func(){setBuildMateProfilePage_AddLocation_CustomCoordinates(window, countryExists, countryIdentifier, previousPage, visitOnCompletePage)} title := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Add Custom Location")) getDescription1Text := func()(string, error){ if (countryExists == false){ result := translate("Enter a location.") return result, nil } countryObject, err := worldLocations.GetCountryObjectFromCountryIdentifier(countryIdentifier) if (err != nil){ return "", err } countryNamesList := countryObject.NamesList countryDescription := helpers.TranslateAndJoinStringListItems(countryNamesList, "/") result := translate("Enter a location in ") + countryDescription return result, nil } description1Text, err := getDescription1Text() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } description1 := getLabelCentered(description1Text) description2 := getBoldLabelCentered("We advise against entering your exact location.") description3 := getLabelCentered("Choose a location that is not a house.") description4 := getLabelCentered("Use an online map explorer to find the coordinates.") description5 := getItalicLabelCentered("Latitude Examples: N 31.7619°, S -40.7128°, -65.358579") description6 := getItalicLabelCentered("Longitude Examples: E 74.0060°, W -106.4850°, -14.1592") enterLatitudeLabel := getBoldLabelCentered("Enter Latitude:") enterLongitudeLabel := getBoldLabelCentered("Enter Longitude:") latitudeEntry := widget.NewEntry() longitudeEntry := widget.NewEntry() latitudeEntry.SetPlaceHolder("Enter Latitude...") longitudeEntry.SetPlaceHolder("Enter Longitude...") entryGrid := getContainerCentered(container.NewGridWithColumns(2, enterLatitudeLabel, latitudeEntry, enterLongitudeLabel, longitudeEntry)) confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm", theme.ConfirmIcon(), func(){ latitudeEntered := latitudeEntry.Text longitudeEntered := longitudeEntry.Text latitudeFormattedA := strings.TrimRight(latitudeEntered, "°NS") latitudeFormattedB := strings.TrimLeft(latitudeFormattedA, "NS") latitudeFormattedC := strings.TrimSpace(latitudeFormattedB) longitudeFormattedA := strings.TrimRight(longitudeEntered, "°EW") longitudeFormattedB := strings.TrimLeft(longitudeFormattedA, "EW") longitudeFormattedC := strings.TrimSpace(longitudeFormattedB) latitudeFloat64, err := helpers.ConvertStringToFloat64(latitudeFormattedC) if (err != nil){ title := translate("Invalid Latitude.") dialogMessage := getLabelCentered(translate("Latitude is not a number.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } longitudeFloat64, err := helpers.ConvertStringToFloat64(longitudeFormattedC) if (err != nil){ title := translate("Invalid Longitude.") dialogMessage := getLabelCentered(translate("Longitude is not a number.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } latitudeIsValid := helpers.VerifyLatitude(latitudeFloat64) if (latitudeIsValid == false){ title := translate("Invalid Latitude.") dialogMessage := getLabelCentered(translate("Latitude should be a number between -90 and 90")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } longitudeIsValid := helpers.VerifyLongitude(longitudeFloat64) if (longitudeIsValid == false){ title := translate("Invalid Longitude.") dialogMessage := getLabelCentered(translate("Longitude should be a number between -180 and 180")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } setBuildMateProfilePage_ConfirmAddLocation(window, countryExists, countryIdentifier, latitudeFloat64, longitudeFloat64, currentPage, visitOnCompletePage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, description4, widget.NewSeparator(), description5, description6, widget.NewSeparator(), entryGrid, confirmButton) setPageContent(page, window) } func setBuildMateProfilePage_ConfirmAddLocation(window fyne.Window, newLocationCountryExists bool, newLocationCountryIdentifier int, newLocationLatitude float64, newLocationLongitude float64, previousPage func(), visitOnCompletePage func()){ currentPage := func(){setBuildMateProfilePage_ConfirmAddLocation(window, newLocationCountryExists, newLocationCountryIdentifier, newLocationLatitude, newLocationLongitude, previousPage, visitOnCompletePage)} setLoadingScreen(window, "Build Mate Profile - General", "Loading...") title := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Add Location")) description1 := getBoldLabelCentered("Confirm Add Location?") description2 := getLabelCentered("This location will be publicly displayed on your profile.") countryLabel := getLabelCentered("Country:") getLocationCountryLabelText := func()(string, error){ if (newLocationCountryExists == false){ result := translate("None") return result, nil } countryObject, err := worldLocations.GetCountryObjectFromCountryIdentifier(newLocationCountryIdentifier) if (err != nil){ return "", err } countryNamesList := countryObject.NamesList countryDescription := helpers.TranslateAndJoinStringListItems(countryNamesList, "/") result := translate(countryDescription) return result, nil } locationCountryLabelText, err := getLocationCountryLabelText() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } locationCountryLabel := getBoldLabel(locationCountryLabelText) coordinatesLabel := getLabelCentered("Coordinates:") locationLatitudeString := helpers.ConvertFloat64ToString(newLocationLatitude) locationLongitudeString := helpers.ConvertFloat64ToString(newLocationLongitude) locationCoordinatesLabel := getBoldLabel(locationLatitudeString + "°, " + locationLongitudeString + "°") cityLabel := getLabelCentered("City:") getCityContent := func()(*fyne.Container, error){ // We will either find the exact city, or find the closest city cityName, cityState, cityCountryIdentifier, cityDistanceKilometers, err := worldLocations.GetClosestCityFromCoordinates(newLocationLatitude, newLocationLongitude) if (err != nil) { return nil, err } if (cityDistanceKilometers == 0){ locationCityFormatted := cityName + ", " + cityState locationCityLabel := getBoldLabelCentered(locationCityFormatted) return locationCityLabel, nil } getNearbyCityDistanceFormattedString := func()(string, error){ currentUnitsExist, currentUnits, err := globalSettings.GetSetting("MetricOrImperial") if (err != nil){ return "", err } 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 (cityCountryIdentifier == newLocationCountryIdentifier){ result := cityName + ", " + cityState return result, nil } cityCountryObject, err := worldLocations.GetCountryObjectFromCountryIdentifier(cityCountryIdentifier) if (err != nil){ return "", err } cityCountryNamesList := cityCountryObject.NamesList countryPrimaryName := cityCountryNamesList[0] countryNameTranslated := translate(countryPrimaryName) result := cityName + ", " + cityState + ", " + countryNameTranslated 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){ setErrorEncounteredPage(window, err, previousPage) return } addLocationFunction := func()error{ //Outputs: // -bool: Location exists // -float64: Location latitude // -float64: Location longitude // -bool: Location country exists // -int: Location country identifier // -error getMyLocationInfo := func(rank string)(bool, float64, float64, bool, int, error){ if (rank != "Primary" && rank != "Secondary"){ return false, 0, 0, false, 0, errors.New("getMyLocationInfo called with invalid rank: " + rank) } exists, locationLatitude, err := myLocalProfiles.GetProfileData("Mate", rank + "LocationLatitude") if (err != nil) { return false, 0, 0, false, 0, err } if (exists == false){ return false, 0, 0, false, 0, nil } exists, locationLongitude, err := myLocalProfiles.GetProfileData("Mate", rank + "LocationLongitude") if (err != nil) { return false, 0, 0, false, 0, err } if (exists == false){ return false, 0, 0, false, 0, errors.New("MyLocalProfiles contains " + rank + "LocationLatitude but missing " + rank + "LocationLongitude") } locationLatitudeFloat64, err := helpers.ConvertStringToFloat64(locationLatitude) if (err != nil){ return false, 0, 0, false, 0, errors.New("MyLocalProfiles contains invalid " + rank + "LocationLatitude") } locationLongitudeFloat64, err := helpers.ConvertStringToFloat64(locationLongitude) if (err != nil){ return false, 0, 0, false, 0, errors.New("MyLocalProfiles contains invalid " + rank + "LocationLongitude") } exists, locationCountryIdentifier, err := myLocalProfiles.GetProfileData("Mate", rank + "LocationCountry") if (err != nil) { return false, 0, 0, false, 0, err } if (exists == false){ return true, locationLatitudeFloat64, locationLongitudeFloat64, false, 0, nil } locationCountryIdentifierInt, err := helpers.ConvertStringToInt(locationCountryIdentifier) if (err != nil){ return false, 0, 0, false, 0, errors.New("MyLocalProfiles contains invalid " + rank + "LocationCountry: " + locationCountryIdentifier) } return true, locationLatitudeFloat64, locationLongitudeFloat64, true, locationCountryIdentifierInt, nil } primaryLocationExists, primaryLocationLatitude, primaryLocationLongitude, primaryLocationCountryExists, primaryLocationCountryIdentifier, err := getMyLocationInfo("Primary") if (err != nil){ return err } secondaryLocationExists, _, _, _, _, err := getMyLocationInfo("Secondary") if (err != nil){ return err } if (primaryLocationExists == false && secondaryLocationExists == true){ return errors.New("My Profile has a secondary location, but not a primary location.") } if (primaryLocationExists == true && secondaryLocationExists == true){ return errors.New("Trying to add a location to a profile with 2 locations.") } if (primaryLocationExists == false){ err := myLocalProfiles.SetProfileData("Mate", "PrimaryLocationLatitude", locationLatitudeString) if (err != nil) { return err } err = myLocalProfiles.SetProfileData("Mate", "PrimaryLocationLongitude", locationLongitudeString) if (err != nil) { return err } if (newLocationCountryExists == true){ newLocationCountryIdentifierString := helpers.ConvertIntToString(newLocationCountryIdentifier) err := myLocalProfiles.SetProfileData("Mate", "PrimaryLocationCountry", newLocationCountryIdentifierString) if (err != nil) { return err } } return nil } // Primary location exists // We see if the location already exists if (primaryLocationLatitude == newLocationLatitude && primaryLocationLongitude == newLocationLongitude){ if (newLocationCountryExists == false && primaryLocationCountryExists == false){ // Location is identical, Nothing left to do. return nil } if (newLocationCountryExists == true && primaryLocationCountryExists == true && primaryLocationCountryIdentifier == newLocationCountryIdentifier){ // Location is identical, Nothing left to do. return nil } } err = myLocalProfiles.SetProfileData("Mate", "SecondaryLocationLatitude", locationLatitudeString) if (err != nil) { return err } err = myLocalProfiles.SetProfileData("Mate", "SecondaryLocationLongitude", locationLongitudeString) if (err != nil) { return err } if (newLocationCountryExists == true){ newLocationCountryIdentifierString := helpers.ConvertIntToString(newLocationCountryIdentifier) err := myLocalProfiles.SetProfileData("Mate", "SecondaryLocationCountry", newLocationCountryIdentifierString) if (err != nil) { return err } } return nil } confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm", theme.ConfirmIcon(), func(){ err := addLocationFunction() if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } visitOnCompletePage() })) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), countryLabel, locationCountryLabel, widget.NewSeparator(), coordinatesLabel, locationCoordinatesLabel, widget.NewSeparator(), cityLabel, cityContent, widget.NewSeparator(), confirmButton) setPageContent(page, window) } func setBuildMateProfilePage_Photos(window fyne.Window, currentPhotoIndex int, previousPage func()){ currentPage := func(){setBuildMateProfilePage_Photos(window, currentPhotoIndex, previousPage)} pageTitle := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) pageSubtitle := getBoldLabelCentered(translate("Photos")) description1 := getLabelCentered(translate("Add photos to your Mate profile.")) description2 := getLabelCentered(translate("You can add 5 photos.")) addImageFileCallbackFunction := func(fileObject fyne.URIReadCloser, err error){ if (err != nil) { title := translate("Failed to open image file.") dialogMessage := getLabelCentered(translate("Report this error to Seekia developers: " + err.Error())) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } if (fileObject == nil) { return } setLoadingScreen(window, "Add Image", "Importing image...") filePath := fileObject.URI().String() filePath = strings.TrimPrefix(filePath, "file://") fileExists, ableToReadImage, imageObject, err := imagery.ReadImageFile(filePath) if (err != nil) { currentPage() title := translate("Failed to open image file.") dialogMessage := getLabelCentered(translate("Report this error to Seekia developers:") + " " + err.Error()) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } if (fileExists == false) { currentPage() title := translate("Failed to open image file.") dialogMessage := getLabelCentered(translate("Report this error to Seekia developers: Image file not found.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } if (ableToReadImage == false) { currentPage() title := translate("Failed to import image file.") dialogMessageA := getLabelCentered(translate("Seekia only supports these image file formats:")) dialogMessageB := getLabelCentered("JPG, JPEG, PNG, WEBP") dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } // We resize image to something that will be more managable when we edit it // When we export it, we will downsize it even further imageObjectResized, err := imagery.DownsizeGolangImage(imageObject, 1500) if (err != nil) { currentPage() title := translate("Failed To Process Image File.") dialogMessageA := getLabelCentered(translate("Your file may be too large.")) errorString := err.Error() errorTrimmed, _, err := helpers.TrimAndFlattenString(errorString, 20) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } errorTrimmedLabel := getBoldLabel("Error: " + errorTrimmed) viewFullErrorButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ setViewTextPage(window, "Viewing Error", errorString, false, currentPage) }) errorDescriptionRow := container.NewHBox(layout.NewSpacer(), errorTrimmedLabel, viewFullErrorButton, layout.NewSpacer()) dialogContent := container.NewVBox(dialogMessageA, errorDescriptionRow) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } setSubmitImageToSubmitPageFunction := func(finalImage image.Image, prevPage func()){ setLoadingScreen(window, "Add Image To Mate Profile", "Compressing Image...") newImageBase64String, err := imagery.ConvertImageObjectToStandardWebpBase64String(finalImage) if (err != nil) { setErrorEncounteredPage(window, err, prevPage) return } setConfirmAddImageToMyMateProfilePage(window, newImageBase64String, prevPage, currentPage) } setEditImagePage(window, imageObjectResized, false, nil, imageObjectResized, currentPage, setSubmitImageToSubmitPageFunction) } //Outputs: // -bool: Any images exist // -[]string: Webp Base64 images list // -error getCurrentImagesList := func()(bool, []string, error){ exists, photosAttributeString, err := myLocalProfiles.GetProfileData("Mate", "Photos") if (err != nil){ return false, nil, err } if (exists == false){ return false, nil, nil } webpImagesList := strings.Split(photosAttributeString, "+") if (len(webpImagesList) > 5){ return false, nil, errors.New("My Photos attribute malformed: More than 5 photos") } return true, webpImagesList, nil } anyImagesExist, myCurrentBase64WebpsList, err := getCurrentImagesList() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (anyImagesExist == false){ noImagesExistLabel := getBoldLabelCentered("No Photos Exist.") addImageButton := getWidgetCentered(widget.NewButtonWithIcon("Add Image", theme.ContentAddIcon(), func(){ dialog.ShowFileOpen(addImageFileCallbackFunction, window) })) page := container.NewVBox(pageTitle, backButton, widget.NewSeparator(), pageSubtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), addImageButton, widget.NewSeparator(), noImagesExistLabel) setPageContent(page, window) return } numberOfImages := len(myCurrentBase64WebpsList) addImageButton := getWidgetCentered(widget.NewButtonWithIcon("Add Image", theme.ContentAddIcon(), func(){ if (numberOfImages >= 5){ title := translate("Image Limit Reached.") dialogMessageA := getLabelCentered(translate("Your profile can only contain 5 images.")) dialogMessageB := getLabelCentered(translate("Delete existing images to add more.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } dialog.ShowFileOpen(addImageFileCallbackFunction, window) })) myPhotosLabel := getBoldLabelCentered("My Photos:") // We use this function to fix an out-of-bound currentPhotoIndex getCurrentImageIndex := func()int{ if (currentPhotoIndex < 0){ return 0 } finalIndex := len(myCurrentBase64WebpsList) - 1 if (currentPhotoIndex > finalIndex){ return finalIndex } return currentPhotoIndex } currentImageIndex := getCurrentImageIndex() selectButtonsRow := container.NewHBox(layout.NewSpacer()) for imageIndex, _ := range myCurrentBase64WebpsList{ imageIndexString := helpers.ConvertIntToString(imageIndex+1) selectButton := widget.NewButton(imageIndexString, func(){ setBuildMateProfilePage_Photos(window, imageIndex, previousPage) }) if (imageIndex == currentImageIndex){ selectButton.Importance = widget.HighImportance } selectButtonsRow.Add(selectButton) } selectButtonsRow.Add(layout.NewSpacer()) currentImageBase64 := myCurrentBase64WebpsList[currentImageIndex] imageObject, err := imagery.ConvertWEBPBase64StringToCroppedDownsizedImageObject(currentImageBase64) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } fyneImageObject := canvas.NewImageFromImage(imageObject) fyneImageObject.FillMode = canvas.ImageFillContain fyneImageSize := getCustomFyneSize(50) fyneImageObject.SetMinSize(fyneImageSize) getBackButton := func()fyne.Widget{ if (currentImageIndex <= 0) { button := widget.NewButton("", nil) return button } button := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func(){ newImageIndex := currentImageIndex - 1 setBuildMateProfilePage_Photos(window, newImageIndex, previousPage) }) return button } getNextButton := func()fyne.Widget{ if (currentImageIndex >= numberOfImages-1) { button := widget.NewButton("", nil) return button } button := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func(){ newImageIndex := currentImageIndex + 1 setBuildMateProfilePage_Photos(window, newImageIndex, previousPage) }) return button } navigateBackButton := getBackButton() navigateNextButton := getNextButton() zoomButton := widget.NewButtonWithIcon("", theme.ZoomInIcon(), func(){ setViewFullpageImagePage(window, imageObject, currentPage) }) navAndZoomButtonsRow := getContainerCentered(container.NewGridWithRows(1, navigateBackButton, zoomButton, navigateNextButton)) deleteButton := getWidgetCentered(widget.NewButtonWithIcon("Delete", theme.DeleteIcon(), func(){ confirmDialogCallbackFunction := func(response bool){ if (response == false){ return } if (numberOfImages == 1){ err := myLocalProfiles.DeleteProfileData("Mate", "Photos") if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() return } newList, err := helpers.DeleteIndexFromStringList(myCurrentBase64WebpsList, currentImageIndex) if (err != nil) { setErrorEncounteredPage(window, err, currentPage) return } newPhotosAttribute := strings.Join(newList, "+") err = myLocalProfiles.SetProfileData("Mate", "Photos", newPhotosAttribute) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() } dialogTitle := translate("Confirm Delete Image?") dialogMessage := getLabelCentered("Confirm to delete this image?") dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustomConfirm(dialogTitle, translate("Yes"), translate("No"), dialogContent, confirmDialogCallbackFunction, window) })) page := container.NewVBox(pageTitle, backButton, widget.NewSeparator(), pageSubtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), addImageButton, widget.NewSeparator(), myPhotosLabel, selectButtonsRow, widget.NewSeparator(), fyneImageObject, widget.NewSeparator(), navAndZoomButtonsRow, deleteButton) setPageContent(page, window) } func setConfirmAddImageToMyMateProfilePage(window fyne.Window, newImageBase64String string, previousPage func(), nextPage func()){ currentPage := func(){setConfirmAddImageToMyMateProfilePage(window, newImageBase64String, previousPage, nextPage)} title := getPageTitleCentered(translate("Add Image To Mate Profile")) backButton := getBackButtonCentered(previousPage) description1 := getBoldLabelCentered("Confirm add image to your Mate profile?") description2 := getLabelCentered("This image will be displayed on your profile.") description3 := getLabelCentered("Be aware that the image has been compressed.") submitButton := getWidgetCentered(widget.NewButtonWithIcon("Add Image", theme.ConfirmIcon(), func(){ setLoadingScreen(window, "Add Image To Mate Profile", "Adding Image...") addImageFunction := func()error{ getNewAttributeValue := func()(string, error){ exists, currentPhotosAttributeValue, err := myLocalProfiles.GetProfileData("Mate", "Photos") if (err != nil) { return "", err } if (exists == false){ return newImageBase64String, nil } existingWebpPhotosList := strings.Split(currentPhotosAttributeValue, "+") newList := append(existingWebpPhotosList, newImageBase64String) newListJoined := strings.Join(newList, "+") return newListJoined, nil } newAttributeValue, err := getNewAttributeValue() if (err != nil){ return err } err = myLocalProfiles.SetProfileData("Mate", "Photos", newAttributeValue) if (err != nil){ return err } return nil } err := addImageFunction() if (err != nil) { setErrorEncounteredPage(window, err, currentPage) return } nextPage() })) croppedImageObject, err := imagery.ConvertWEBPBase64StringToCroppedDownsizedImageObject(newImageBase64String) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } currentImageFyne := canvas.NewImageFromImage(croppedImageObject) currentImageFyne.FillMode = canvas.ImageFillContain viewFullpageButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.ZoomInIcon(), func(){ setViewFullpageImagePage(window, croppedImageObject, currentPage) })) emptyLabelA := widget.NewLabel("") zoomButtonWithSpacer := container.NewVBox(viewFullpageButton, emptyLabelA) header := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), submitButton, widget.NewSeparator()) page := container.NewBorder(header, zoomButtonWithSpacer, nil, nil, currentImageFyne) setPageContent(page, window) } func setBuildProfilePage_Avatar(window fyne.Window, profileType string, previousPage func()){ currentPage := func(){setBuildProfilePage_Avatar(window, profileType, previousPage)} title := getPageTitleCentered(translate("Build " + profileType + " Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Avatar")) description := getLabelCentered(translate("Choose your avatar.")) currentAvatarLabel := getBoldLabelCentered("Current Avatar:") getCurrentAvatarIdentifier := func()(int, error){ exists, myAvatarIdentifier, err := myLocalProfiles.GetProfileData(profileType, "Avatar") if (err != nil) { return 0, err } if (exists == false){ return 2929, nil } myAvatarIdentifierInt, err := helpers.ConvertStringToInt(myAvatarIdentifier) if (err != nil) { return 0, errors.New("MyLocalProfile is malformed: Invalid avatar: " + myAvatarIdentifier) } return myAvatarIdentifierInt, nil } myAvatarIdentifier, err := getCurrentAvatarIdentifier() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } emojiImage, err := getEmojiImageObject(myAvatarIdentifier) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } emojiImageFyne := canvas.NewImageFromImage(emojiImage) nextPageFunction := func(newEmojiIdentifier int){ isValid := imageFiles.VerifyEmojiIdentifier(newEmojiIdentifier) if (isValid == false){ setErrorEncounteredPage(window, errors.New("Invalid emoji identifier selected from chooseEmojiPage"), currentPage) return } newEmojiIdentifierString := helpers.ConvertIntToString(newEmojiIdentifier) err := myLocalProfiles.SetProfileData(profileType, "Avatar", newEmojiIdentifierString) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() } chooseAvatarButton := getWidgetCentered(widget.NewButtonWithIcon("Choose Avatar", theme.GridIcon(), func(){ setChooseEmojiPage(window, "Choose Avatar", "People", 0, currentPage, nextPageFunction) })) emptyLabelA := widget.NewLabel("") emptyLabelB := widget.NewLabel("") emptyLabelC := widget.NewLabel("") chooseAvatarButtonHeightened := container.NewVBox(chooseAvatarButton, emptyLabelA, emptyLabelB, emptyLabelC) avatarWithButton := getContainerCentered(container.NewGridWithColumns(1, emojiImageFyne, chooseAvatarButtonHeightened)) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description, widget.NewSeparator(), currentAvatarLabel, avatarWithButton) setPageContent(page, window) } func setBuildMateProfilePage_Sexuality(window fyne.Window, previousPage func()){ currentPage := func(){setBuildMateProfilePage_Sexuality(window, previousPage)} title := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) pageSubtitle := getPageSubtitleCentered(translate("Sexuality")) description := getLabelCentered(translate("What sex(es) are you interested in mating with?")) option1Translated := translate("Male") option2Translated := translate("Female") option3Translated := translate("Male And Female") untranslatedOptionsMap := map[string]string{ option1Translated: "Male", option2Translated: "Female", option3Translated: "Male And Female", } sexualitySelectorOptions := []string{option1Translated, option2Translated, option3Translated} sexualitySelector := widget.NewRadioGroup(sexualitySelectorOptions, func(response string){ if (response == ""){ myLocalProfiles.DeleteProfileData("Mate", "Sexuality") return } responseUntranslated, exists := untranslatedOptionsMap[response] if (exists == false){ setErrorEncounteredPage(window, errors.New("untranslatedOptionsMap missing response: " + response), currentPage) return } myLocalProfiles.SetProfileData("Mate", "Sexuality", responseUntranslated) }) exists, currentSexuality, err := myLocalProfiles.GetProfileData("Mate", "Sexuality") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (exists == true){ if (currentSexuality != "Male" && currentSexuality != "Female" && currentSexuality != "Male And Female"){ setErrorEncounteredPage(window, errors.New("MyLocalProfiles contains invalid sexuality: " + currentSexuality), previousPage) return } sexualitySelector.Selected = translate(currentSexuality) } sexualitySelectorCentered := getWidgetCentered(sexualitySelector) noResponseButton := getWidgetCentered(widget.NewButtonWithIcon(translate("No Response"), theme.CancelIcon(), func(){ myLocalProfiles.DeleteProfileData("Mate", "Sexuality") currentPage() })) page := container.NewVBox(title, backButton, widget.NewSeparator(), pageSubtitle, widget.NewSeparator(), description, widget.NewSeparator(), sexualitySelectorCentered, noResponseButton) setPageContent(page, window) } func setBuildMateProfilePage_Tags(window fyne.Window, previousPage func()){ currentPage := func(){setBuildMateProfilePage_Tags(window, previousPage)} title := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getBoldLabelCentered(translate("Tags")) description1 := getLabelCentered("Add tags to your profile.") description2 := getLabelCentered("Users can search for matches whose tags match custom terms.") addTagButton := getWidgetCentered(widget.NewButtonWithIcon("Add Tag", theme.ContentAddIcon(), func(){ setBuildMateProfilePage_AddTag(window, currentPage, currentPage) })) myTagsExist, myTagsAttributeValue, err := myLocalProfiles.GetProfileData("Mate", "Tags") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (myTagsExist == false){ noTagsExistLabel := getBoldLabelCentered("No tags exist.") page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), addTagButton, widget.NewSeparator(), noTagsExistLabel) setPageContent(page, window) return } getTagsGrid := func()(*fyne.Container, error){ myTagsList := strings.Split(myTagsAttributeValue, "+&") nameLabel := getItalicLabelCentered("Name") emptyLabel := widget.NewLabel("") tagNameColumn := container.NewVBox(nameLabel, widget.NewSeparator()) deleteButtonsColumn := container.NewVBox(emptyLabel, widget.NewSeparator()) for _, tagName := range myTagsList{ tagNameLabel := getBoldLabelCentered(tagName) deleteButton := widget.NewButtonWithIcon("", theme.DeleteIcon(), func(){ newList, deletedAny := helpers.DeleteAllMatchingItemsFromList(myTagsList, tagName) if (deletedAny == false){ setErrorEncounteredPage(window, errors.New("Cannot delete tag: tag not found."), currentPage) return } if (len(newList) == 0){ err := myLocalProfiles.DeleteProfileData("Mate", "Tags") if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() return } newTagsAttributeValue := strings.Join(newList, "+&") err := myLocalProfiles.SetProfileData("Mate", "Tags", newTagsAttributeValue) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) tagNameColumn.Add(tagNameLabel) deleteButtonsColumn.Add(deleteButton) tagNameColumn.Add(widget.NewSeparator()) deleteButtonsColumn.Add(widget.NewSeparator()) } tagsGrid := container.NewHBox(layout.NewSpacer(), tagNameColumn, deleteButtonsColumn, layout.NewSpacer()) return tagsGrid, nil } tagsGrid, err := getTagsGrid() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), addTagButton, widget.NewSeparator(), tagsGrid) setPageContent(page, window) } func setBuildMateProfilePage_AddTag(window fyne.Window, previousPage func(), nextPage func()){ currentPage := func(){setBuildMateProfilePage_AddTag(window, previousPage, nextPage)} title := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) subtitle := getPageSubtitleCentered(translate("Add Tag")) enterTagNameLabel := getBoldLabelCentered(translate("Enter Tag Name:")) enterTagEntry := widget.NewEntry() enterTagEntry.SetPlaceHolder("Enter Tag Name...") myTagsExist, myTagsAttributeValue, err := myLocalProfiles.GetProfileData("Mate", "Tags") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } addTagButton := getWidgetCentered(widget.NewButtonWithIcon("Add Tag", theme.ContentAddIcon(), func(){ newTag := enterTagEntry.Text if (newTag == ""){ title := translate("Cannot Add Tag.") dialogMessage := getLabelCentered(translate("Tag is empty.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } containsDelimiter := strings.Contains(newTag, "+&") if (containsDelimiter == true){ title := translate("Cannot Add Tag.") dialogMessageA := getLabelCentered(translate("Tag contains invalid substring: " + `"+&"`)) dialogMessageB := getLabelCentered(translate("Remove this substring and resubmit.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } newTagBytesLength := len(newTag) if (newTagBytesLength > 40){ newTagBytesLengthString := helpers.ConvertIntToString(newTagBytesLength) title := translate("Cannot Add Tag.") dialogMessageA := getLabelCentered(translate("Tag is too long.")) dialogMessageB := getLabelCentered(translate("Tag cannot be longer than 40 bytes.")) dialogMessageC := getLabelCentered(translate("Your tag length:") + newTagBytesLengthString) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } if (myTagsExist == false){ err := myLocalProfiles.SetProfileData("Mate", "Tags", newTag) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } nextPage() return } existingTagsList := strings.Split(myTagsAttributeValue, "+&") if (len(existingTagsList) >= 30){ title := translate("Cannot Create Tag.") dialogMessageA := getLabelCentered(translate("You cannot have more than 30 tags.")) dialogMessageB := getLabelCentered(translate("You must first delete an existing tag.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } existingTagsTotalByteLength := 0 for _, tagString := range existingTagsList{ if (tagString == ""){ setErrorEncounteredPage(window, errors.New("My tags are invalid: Existing tag is empty."), currentPage) return } tagByteLength := len(tagString) if (tagByteLength > 40){ setErrorEncounteredPage(window, errors.New("My tags are invalid: Existing tag is too long."), currentPage) return } existingTagsTotalByteLength += tagByteLength } newTagListByteLength := existingTagsTotalByteLength + newTagBytesLength if (newTagListByteLength > 500){ title := translate("Cannot Add Tag.") dialogMessageA := getLabelCentered(translate("Byte length of all tags exceeds 500.")) dialogMessageB := getLabelCentered(translate("Enter a shorter tag or delete an existing tag.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } tagAlreadyExists := slices.Contains(existingTagsList, newTag) if (tagAlreadyExists == true){ title := translate("Cannot Add Tag.") dialogMessage := getLabelCentered(translate("Tag already exists.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } isAllowed := allowedText.VerifyStringIsAllowed(newTag) if (isAllowed == false){ title := translate("Invalid Tag") dialogMessageA := getLabelCentered(translate("Your tag contains an invalid character.")) dialogMessageB := getLabelCentered(translate("It must be encoded in UTF-8")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } containsTabOrNewline := helpers.CheckIfStringContainsTabsOrNewlines(newTag) if (containsTabOrNewline == true){ title := translate("Invalid Tag") dialogMessageA := getLabelCentered(translate("Your tag contains a tab or newline character.")) dialogMessageB := getLabelCentered(translate("Remove the character and resubmit.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } newTagsList := append(existingTagsList, newTag) newTagsAttributeValue := strings.Join(newTagsList, "+&") err := myLocalProfiles.SetProfileData("Mate", "Tags", newTagsAttributeValue) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } nextPage() })) enterTagEntryWithLabel := getContainerCentered(container.NewGridWithColumns(1, enterTagNameLabel, enterTagEntry)) page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), enterTagEntryWithLabel, addTagButton) setPageContent(page, window) } func setBuildMateProfilePage_Questionnaire(window fyne.Window, previousPage func()){ currentPage := func(){setBuildMateProfilePage_Questionnaire(window, previousPage)} pageTitle := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) pageSubtitle := getPageSubtitleCentered(translate("Questionnaire")) description1 := getLabelCentered("Create a questionnaire for users to fill out.") description2 := getLabelCentered("You can filter users who choose your desired answers.") description3 := getLabelCentered("Your desired answers are private.") buildQuestionnaireIcon, err := getFyneImageIcon("BuildQuestionnaire") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } desiresIcon, err := getFyneImageIcon("Desires") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } viewQuestionnaireIcon, err := getFyneImageIcon("Questionnaire") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } buildQuestionnaireButton := widget.NewButton("Build Questionnaire", func(){ setBuildMateProfilePage_QuestionnaireQuestions(window, 0, currentPage) }) chooseDesiresButton := widget.NewButton("Choose Desired Answers", func(){ //TODO showUnderConstructionDialog(window) }) viewQuestionnaireButton := widget.NewButton("View My Questionnaire", func(){ exists, myQuestionnaireRaw, err := myLocalProfiles.GetProfileData("Mate", "Questionnaire") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (exists == false){ title := translate("No Questions Exist") dialogMessageA := getLabelCentered("You must add a question before viewing your questionnaire.") dialogMessageB := getLabelCentered("Add a question on the Build Questionnaire page.") dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } myQuestionnaireObject, err := mateQuestionnaire.ReadQuestionnaireString(myQuestionnaireRaw) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } submitPageFunction := func(_ string, _ func()){ currentPage() } emptyMap := make(map[string]string) setTakeQuestionnairePage(window, myQuestionnaireObject, 0, emptyMap, currentPage, submitPageFunction) }) buildQuestionnaireButtonWithIcon := container.NewGridWithColumns(1, buildQuestionnaireIcon, buildQuestionnaireButton) chooseDesiresButtonWithIcon := container.NewGridWithColumns(1, desiresIcon, chooseDesiresButton) viewQuestionnaireButtonWithIcon := container.NewGridWithColumns(1, viewQuestionnaireIcon, viewQuestionnaireButton) buttonsSection := getContainerCentered(container.NewGridWithColumns(1, buildQuestionnaireButtonWithIcon, chooseDesiresButtonWithIcon, viewQuestionnaireButtonWithIcon)) page := container.NewVBox(pageTitle, backButton, widget.NewSeparator(), pageSubtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), buttonsSection) setPageContent(page, window) } func setBuildMateProfilePage_QuestionnaireQuestions(window fyne.Window, pageIndex int, previousPage func()){ setLoadingScreen(window, "View Questionnaire Questions ", "Loading questionnaire questions...") currentPage := func(){setBuildMateProfilePage_QuestionnaireQuestions(window, pageIndex, previousPage)} pageTitle := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) pageSubtitle := getPageSubtitleCentered(translate("Questionnaire Questions")) exists, myQuestionnaireRaw, err := myLocalProfiles.GetProfileData("Mate", "Questionnaire") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (exists == false) { noQuestionsExistLabel := getBoldLabelCentered("No questions exist.") addQuestionButton := getWidgetCentered(widget.NewButtonWithIcon("Add Question", theme.ContentAddIcon(), func(){ setBuildMateProfilePage_AddQuestionnaireQuestion(window, currentPage, currentPage) })) page := container.NewVBox(pageTitle, backButton, widget.NewSeparator(), pageSubtitle, widget.NewSeparator(), noQuestionsExistLabel, addQuestionButton) setPageContent(page, window) return } myQuestionnaireObject, err := mateQuestionnaire.ReadQuestionnaireString(myQuestionnaireRaw) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } deleteQuestionnaireQuestionFunction := func(questionIdentifier string)error{ newQuestionnaireQuestionsList := make([]mateQuestionnaire.QuestionObject, 0) for _, questionObject := range myQuestionnaireObject{ currentQuestionIdentifier := questionObject.Identifier if (currentQuestionIdentifier != questionIdentifier){ newQuestionnaireQuestionsList = append(newQuestionnaireQuestionsList, questionObject) } } if (len(newQuestionnaireQuestionsList) == 0){ err := myLocalProfiles.DeleteProfileData("Mate", "Questionnaire") if (err != nil) { return err } return nil } newQuestionnaireRaw, err := mateQuestionnaire.CreateQuestionnaireString(newQuestionnaireQuestionsList) if (err != nil) { return err } err = myLocalProfiles.SetProfileData("Mate", "Questionnaire", newQuestionnaireRaw) if (err != nil) { return err } return nil } increaseOrDecreaseQuestionIndexFunction := func(questionIdentifier string, increaseOrDecrease string)error{ // Increase: Move question towards the end of questionnaire // Decrease: Move question towards the beginning of questionnaire if (increaseOrDecrease != "Increase" && increaseOrDecrease != "Decrease"){ return errors.New("increaseOrDecreaseQuestionIndexFunction called with invalid increaseOrDecrease: " + increaseOrDecrease) } if (len(myQuestionnaireObject) <= 1){ return errors.New("increaseOrDecreaseQuestionIndexFunction called when questionnaire has <=1 question.") } getQuestionCurrentIndex := func()(int, error){ for index, questionObject := range myQuestionnaireObject{ currentQuestionIdentifier := questionObject.Identifier if (currentQuestionIdentifier == questionIdentifier){ return index, nil } } //Should not happen, buttons will only be shown for existing questions return 0, errors.New("Question to increase/decrease not found.") } questionIndex, err := getQuestionCurrentIndex() if (err != nil) { return err } finalIndex := len(myQuestionnaireObject) - 1 if (questionIndex == 0 && increaseOrDecrease == "Decrease"){ return errors.New("Trying to decrease the index of first question.") } if (questionIndex == finalIndex && increaseOrDecrease == "Increase"){ return errors.New("Trying to increase the index of last question.") } swapFunction := reflect.Swapper(myQuestionnaireObject) if (increaseOrDecrease == "Increase"){ swapFunction(questionIndex, questionIndex+1) } else { swapFunction(questionIndex-1, questionIndex) } newRawQuestionnaire, err := mateQuestionnaire.CreateQuestionnaireString(myQuestionnaireObject) if (err != nil) { return err } err = myLocalProfiles.SetProfileData("Mate", "Questionnaire", newRawQuestionnaire) if (err != nil) { return err } return nil } addQuestionButton := getWidgetCentered(widget.NewButtonWithIcon("Add Question", theme.ContentAddIcon(), func(){ if (len(myQuestionnaireObject) >= 25){ title := translate("Maximum Question Limit Reached") dialogMessageA := getLabelCentered("You have added the maximum of 25 questions.") dialogMessageB := getLabelCentered("You must delete a question to add a new question.") dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } setViewFinalQuestionnaireQuestionsPage := func(){setBuildMateProfilePage_QuestionnaireQuestions(window, 25, previousPage)} setBuildMateProfilePage_AddQuestionnaireQuestion(window, currentPage, setViewFinalQuestionnaireQuestionsPage) })) getQuestionViewIndex := func()int{ if (pageIndex <= 0){ return 0 } numberOfQuestions := len(myQuestionnaireObject) finalQuestionIndex := numberOfQuestions - 1 if (pageIndex > finalQuestionIndex){ // Index is out of range, show last page of questions if (numberOfQuestions <= 5){ return 0 } lastPageNumberOfQuestions := numberOfQuestions % 5 if (lastPageNumberOfQuestions == 0){ lastPageViewIndex := finalQuestionIndex - 4 return lastPageViewIndex } lastPageViewIndex := finalQuestionIndex - lastPageNumberOfQuestions + 1 return lastPageViewIndex } return pageIndex } questionViewIndex := getQuestionViewIndex() getQuestionsGrid := func()(*fyne.Container, error){ emptyLabelA := widget.NewLabel("") typeLabel := getItalicLabelCentered("Type") contentLabel := getItalicLabelCentered("Content") emptyLabelB := widget.NewLabel("") questionIndexColumn := container.NewVBox(emptyLabelA, widget.NewSeparator()) questionTypeColumn := container.NewVBox(typeLabel, widget.NewSeparator()) questionContentColumn := container.NewVBox(contentLabel, widget.NewSeparator()) buttonsColumn := container.NewVBox(emptyLabelB, widget.NewSeparator()) addQuestionRow := func(questionIndex int, questionObject mateQuestionnaire.QuestionObject)error{ questionIdentifier := questionObject.Identifier questionType := questionObject.Type questionContent := questionObject.Content questionOptions := questionObject.Options getQuestionTypeText := func()(string, error){ if (questionType == "Entry"){ questionTypeText := "Entry - " + questionOptions return questionTypeText, nil } if (questionType == "Choice"){ maximumAnswersAllowed, choicesListString, delimiterFound := strings.Cut(questionOptions, "#") if (delimiterFound == false){ return "", errors.New("Invalid choice question options: " + questionOptions) } choicesList := strings.Split(choicesListString, "$¥") numberOfOptionsString := helpers.ConvertIntToString(len(choicesList)) if (len(choicesList) > 6){ return "", errors.New("Invalid choices list: too many choices.") } getMaximumAnswersAllowedAdjusted := func()(int, error){ maximumAnswersAllowedInt, err := helpers.ConvertStringToInt(maximumAnswersAllowed) if (err != nil) { return 0, err } if (maximumAnswersAllowedInt < 1 || maximumAnswersAllowedInt > 6){ return 0, errors.New("Invalid choice maximum answers allowed") } if (maximumAnswersAllowedInt > len(choicesList)){ return len(choicesList), nil } return maximumAnswersAllowedInt, nil } maximumAnswersAllowedAdjusted, err := getMaximumAnswersAllowedAdjusted() if (err != nil) { return "", err } maximumAnswersAllowedAdjustedString := helpers.ConvertIntToString(maximumAnswersAllowedAdjusted) questionTypeText := numberOfOptionsString + " Choices - Max: " + maximumAnswersAllowedAdjustedString return questionTypeText, nil } return "", errors.New("Malformed question map: invalid question type: " + questionType) } questionTypeText, err := getQuestionTypeText() if (err != nil) { return err } questionContentTrimmed, _, err := helpers.TrimAndFlattenString(questionContent, 15) if (err != nil) { return err } emptyLabelA := widget.NewLabel("") questionIndexString := helpers.ConvertIntToString(questionIndex + 1) questionIndexLabel := getBoldLabel(questionIndexString + ".") questionIndexCell := container.NewVBox(questionIndexLabel, emptyLabelA) emptyLabelB := widget.NewLabel("") questionTypeLabel := getBoldLabelCentered(questionTypeText) questionTypeCell := container.NewVBox(questionTypeLabel, emptyLabelB) emptyLabelC := widget.NewLabel("") questionContentLabel := getBoldLabelCentered(questionContentTrimmed) questionContentCell := container.NewVBox(questionContentLabel, emptyLabelC) getQuestionIncreaseDecreaseIndexButtons := func()*fyne.Container{ getUpButton := func()fyne.Widget{ if (questionIndex == 0){ emptyButton := widget.NewButton("", nil) return emptyButton } upButton := widget.NewButtonWithIcon("", theme.MoveUpIcon(), func(){ err := increaseOrDecreaseQuestionIndexFunction(questionIdentifier, "Decrease") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } currentPage() }) return upButton } getDownButton := func()fyne.Widget{ if (questionIndex >= len(myQuestionnaireObject)-1){ emptyButton := widget.NewButton("", nil) return emptyButton } downButton := widget.NewButtonWithIcon("", theme.MoveDownIcon(), func(){ err := increaseOrDecreaseQuestionIndexFunction(questionIdentifier, "Increase") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } currentPage() }) return downButton } upButton := getUpButton() downButton := getDownButton() buttonsGrid := container.NewGridWithColumns(1, upButton, downButton) return buttonsGrid } deleteQuestionButtonFunction := func(){ confirmDialogCallbackFunction := func(response bool){ if (response == false){ return } err := deleteQuestionnaireQuestionFunction(questionIdentifier) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } currentPage() } dialogTitle := translate("Confirm Delete Question?") dialogMessage := getLabelCentered("Confirm to delete this question?") dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustomConfirm(dialogTitle, translate("Yes"), translate("No"), dialogContent, confirmDialogCallbackFunction, window) } deleteQuestionButton := widget.NewButtonWithIcon("Delete", theme.DeleteIcon(), deleteQuestionButtonFunction) previewQuestionButton := widget.NewButtonWithIcon("Preview", theme.VisibilityIcon(), func(){ setPreviewMyQuestionnaireQuestionPage(window, questionObject, currentPage) }) deletePreviewButtons := container.NewVBox(previewQuestionButton, deleteQuestionButton) getButtonsCell := func()*fyne.Container{ if (len(myQuestionnaireObject) < 2){ return deletePreviewButtons } questionIncreaseDecreaseIndexButtons := getQuestionIncreaseDecreaseIndexButtons() buttonsCell := container.NewHBox(questionIncreaseDecreaseIndexButtons, deletePreviewButtons) return buttonsCell } buttonsCell := getButtonsCell() questionIndexColumn.Add(questionIndexCell) questionTypeColumn.Add(questionTypeCell) questionContentColumn.Add(questionContentCell) buttonsColumn.Add(buttonsCell) questionIndexColumn.Add(widget.NewSeparator()) questionTypeColumn.Add(widget.NewSeparator()) questionContentColumn.Add(widget.NewSeparator()) buttonsColumn.Add(widget.NewSeparator()) return nil } pageQuestionsList := myQuestionnaireObject[questionViewIndex:] questionsGrid := container.NewHBox(layout.NewSpacer(), questionIndexColumn, questionTypeColumn, questionContentColumn, buttonsColumn, layout.NewSpacer()) counter := 0 for index, questionObject := range pageQuestionsList{ if (counter >= 5){ break } questionIndex := questionViewIndex + index err := addQuestionRow(questionIndex, questionObject) if (err != nil) { return nil, err } counter += 1 } return questionsGrid, nil } //Outputs: // -bool: Either button exists // -*fyne.Container: Buttons getPageNavigationButtons := func()(bool, *fyne.Container){ finalQuestionIndex := len(myQuestionnaireObject) -1 if (questionViewIndex == 0 && questionViewIndex > (finalQuestionIndex-5)){ return false, nil } getPreviousPageButton := func()fyne.Widget{ if (questionViewIndex == 0){ emptyButton := widget.NewButton("", nil) return emptyButton } previousButton := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func(){ setBuildMateProfilePage_QuestionnaireQuestions(window, questionViewIndex-5, previousPage) }) return previousButton } getNextPageButton := func()fyne.Widget{ if (questionViewIndex > (finalQuestionIndex-5)){ emptyButton := widget.NewButton("", nil) return emptyButton } nextButton := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func(){ setBuildMateProfilePage_QuestionnaireQuestions(window, questionViewIndex+5, previousPage) }) return nextButton } previousPageButton := getPreviousPageButton() nextPageButton := getNextPageButton() buttonsCentered := getContainerCentered(container.NewGridWithColumns(2, previousPageButton, nextPageButton)) return true, buttonsCentered } page := container.NewVBox(pageTitle, backButton, widget.NewSeparator(), pageSubtitle, widget.NewSeparator(), addQuestionButton, widget.NewSeparator()) navigationButtonsNeeded, navigationButtons := getPageNavigationButtons() if (navigationButtonsNeeded == true){ page.Add(navigationButtons) } questionsGrid, err := getQuestionsGrid() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } questionsGridCentered := getContainerCentered(questionsGrid) page.Add(questionsGridCentered) setPageContent(page, window) } func setPreviewMyQuestionnaireQuestionPage(window fyne.Window, questionObject mateQuestionnaire.QuestionObject, previousPage func()){ currentPage := func(){setPreviewMyQuestionnaireQuestionPage(window, questionObject, previousPage)} title := getPageTitleCentered(translate("Preview Question")) backButton := getBackButtonCentered(previousPage) myResponsesMap := make(map[string]string) viewQuestionContainer, err := getViewQuestionnaireQuestionContainer(window, currentPage, questionObject, myResponsesMap) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), viewQuestionContainer) setPageContent(page, window) } func setBuildMateProfilePage_AddQuestionnaireQuestion(window fyne.Window, previousPage func(), afterCreatePage func()){ currentPage := func(){setBuildMateProfilePage_AddQuestionnaireQuestion(window, previousPage, afterCreatePage)} pageTitle := getPageTitleCentered(translate("Build Mate Profile - General")) backButton := getBackButtonCentered(previousPage) pageSubtitle := getPageSubtitleCentered(translate("Add Questionnaire Question")) addQuestionToMyQuestionnaireFunction := func(newQuestionObject mateQuestionnaire.QuestionObject)error{ getNewQuestionnaireObject := func()([]mateQuestionnaire.QuestionObject, error){ exists, myQuestionnaireRaw, err := myLocalProfiles.GetProfileData("Mate", "Questionnaire") if (err != nil) { return nil, err } if (exists == false) { newQuestionnaireObject := []mateQuestionnaire.QuestionObject{newQuestionObject} return newQuestionnaireObject, nil } existingQuestionnaireObject, err := mateQuestionnaire.ReadQuestionnaireString(myQuestionnaireRaw) if (err != nil) { return nil, err } newQuestionnaireObject := append(existingQuestionnaireObject, newQuestionObject) return newQuestionnaireObject, nil } newQuestionnaireObject, err := getNewQuestionnaireObject() if (err != nil) { return err } newQuestionnaireString, err := mateQuestionnaire.CreateQuestionnaireString(newQuestionnaireObject) if (err != nil) { return err } err = myLocalProfiles.SetProfileData("Mate", "Questionnaire", newQuestionnaireString) if (err != nil) { return err } return nil } addQuestionDescription := getLabelCentered("Choose the type of question to add:") iconSize := getCustomFyneSize(10) choiceIcon, err := getFyneImageIcon("Choice") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } choiceIcon.SetMinSize(iconSize) entryIcon, err := getFyneImageIcon("Entry") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } entryIcon.SetMinSize(iconSize) choiceButton := widget.NewButton("Choice", func(){ setBuildMateProfilePage_AddQuestionnaireQuestion_Choice(window, addQuestionToMyQuestionnaireFunction, currentPage, afterCreatePage) }) entryButton := widget.NewButton("Entry", func(){ setBuildMateProfilePage_AddQuestionnaireQuestion_Entry(window, addQuestionToMyQuestionnaireFunction, currentPage, afterCreatePage) }) choiceButtonWithIcon := container.NewVBox(choiceIcon, choiceButton) entryButtonWithIcon := container.NewVBox(entryIcon, entryButton) choicesSection := container.NewHBox(layout.NewSpacer(), choiceButtonWithIcon, entryButtonWithIcon, layout.NewSpacer()) page := container.NewVBox(pageTitle, backButton, widget.NewSeparator(), pageSubtitle, widget.NewSeparator(), addQuestionDescription, choicesSection) setPageContent(page, window) } func setBuildMateProfilePage_AddQuestionnaireQuestion_Choice(window fyne.Window, addQuestionFunction func(mateQuestionnaire.QuestionObject)error, previousPage func(), afterCreatePage func()){ title := getPageTitleCentered("Add Questionnaire Question - Choice") backButton := getBackButtonCentered(previousPage) description := getLabelCentered("A choice question has multiple answers to choose from.") enterQuestionLabel := getBoldLabelCentered(" Enter Question: ") enterQuestionEntry := widget.NewEntry() enterQuestionEntry.SetPlaceHolder("Enter question.") enterQuestionEntryBoxed := getWidgetBoxed(enterQuestionEntry) enterQuestionLabelWithEntry := getContainerCentered(container.NewGridWithColumns(1, enterQuestionLabel, enterQuestionEntryBoxed)) maximumAnswersAllowedLabel := widget.NewLabel("Maximum Answers Allowed:") selectOptions := []string{"1", "2", "3", "4", "5", "6"} maximumAnswersAllowedSelector := widget.NewSelect(selectOptions, nil) maximumAnswersAllowedSelector.Selected = "6" maximumAnswersAllowedInfoButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ dialogTitle := translate("Maximum Answers Allowed") dialogMessageA := getLabelCentered(translate("Choose the maximum number of answers a user can submit.")) dialogMessageB := getLabelCentered(translate("For example, if you select 1, users can only select 1 choice.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) }) maximumAnswersAllowedRow := container.NewHBox(layout.NewSpacer(), maximumAnswersAllowedLabel, maximumAnswersAllowedSelector, maximumAnswersAllowedInfoButton, layout.NewSpacer()) emptyLabelA := widget.NewLabel("") choiceContentLabel := getItalicLabelCentered(" Choice Content ") emptyLabelB := widget.NewLabel("") choiceIndexColumn := container.NewVBox(emptyLabelA, widget.NewSeparator()) choiceEntryColumn := container.NewVBox(choiceContentLabel, widget.NewSeparator()) clearChoiceButtonsColumn := container.NewVBox(emptyLabelB, widget.NewSeparator()) addChoiceRow := func(index string)func()string{ indexLabel := getBoldLabelCentered(index) choiceEntry := widget.NewEntry() choiceEntry.SetPlaceHolder("Enter choice...") clearChoiceButton := widget.NewButtonWithIcon("Clear", theme.CancelIcon(), func(){ choiceEntry.SetText("") choiceEntry.SetPlaceHolder("Enter choice...") }) choiceEntryColumn.Add(choiceEntry) choiceIndexColumn.Add(indexLabel) clearChoiceButtonsColumn.Add(clearChoiceButton) getChoiceFunction := func()string{ return choiceEntry.Text } return getChoiceFunction } getChoice1Function := addChoiceRow("1.") getChoice2Function := addChoiceRow("2.") getChoice3Function := addChoiceRow("3.") getChoice4Function := addChoiceRow("4.") getChoice5Function := addChoiceRow("5.") getChoice6Function := addChoiceRow("6.") choicesGrid := container.NewHBox(layout.NewSpacer(), choiceIndexColumn, choiceEntryColumn, clearChoiceButtonsColumn, layout.NewSpacer()) submitButton := getWidgetCentered(widget.NewButtonWithIcon("Create Question", theme.ConfirmIcon(), func(){ newQuestionContent := enterQuestionEntry.Text if (newQuestionContent == ""){ dialogTitle := translate("No question provided.") dialogMessage := translate("You must enter a question.") dialogContent := getLabelCentered(dialogMessage) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } isAllowed := allowedText.VerifyStringIsAllowed(newQuestionContent) if (isAllowed == false){ dialogTitle := translate("Question Is Invalid.") dialogMessageA := getLabelCentered(translate("Question contains a prohibited character.")) dialogMessageB := getLabelCentered(translate("It must be encoded in UTF-8.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } // The choice cannot contain these strings because we user them for encoding unallowedStringsList := []string{"+&", "%¢"} for _, unallowedString := range unallowedStringsList{ isContained := strings.Contains(newQuestionContent, unallowedString) if (isContained == true){ dialogTitle := translate("Question Is Invalid.") dialogMessageA := getLabelCentered(translate("Question contains prohibited string: ") + unallowedString) dialogMessageB := getLabelCentered(translate("Remove this string and resubmit.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } } if (len(newQuestionContent) > 500){ questionNumberOfBytesString := helpers.ConvertIntToString(len(newQuestionContent)) dialogTitle := translate("Question Is Invalid.") dialogMessageA := getLabelCentered(translate("Question is longer than 500 bytes.")) dialogMessageB := getLabelCentered(translate("Your question byte count: " + questionNumberOfBytesString)) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } choice1 := getChoice1Function() choice2 := getChoice2Function() choice3 := getChoice3Function() choice4 := getChoice4Function() choice5 := getChoice5Function() choice6 := getChoice6Function() choiceEntryValuesList := []string{choice1, choice2, choice3, choice4, choice5, choice6} newChoicesList := make([]string, 0) // We use a map to detect duplicates. choicesMap := make(map[string]struct{}) for index, choiceString := range choiceEntryValuesList{ if (choiceString == ""){ continue } choiceIndexString := helpers.ConvertIntToString(index+1) choiceIsAllowed := allowedText.VerifyStringIsAllowed(choiceString) if (choiceIsAllowed == false){ dialogTitle := translate("Choice " + choiceIndexString + " Is Invalid.") dialogMessageA := getLabelCentered(translate("Choice " + choiceIndexString + " contains a prohibited character.")) dialogMessageB := getLabelCentered(translate("It must be encoded in UTF-8.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } unallowedStringsList := []string{"+&", "%¢", "$¥"} for _, unallowedString := range unallowedStringsList{ isContained := strings.Contains(choiceString, unallowedString) if (isContained == true){ dialogTitle := translate("Question Is Invalid.") dialogMessageA := getLabelCentered(translate("Choice " + choiceIndexString + " contains prohibited string: ") + unallowedString) dialogMessageB := getLabelCentered(translate("Remove this string and resubmit.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } } if (len(choiceString) > 100){ choiceNumberOfBytesString := helpers.ConvertIntToString(len(choiceString)) dialogTitle := translate("Choice Is Invalid.") dialogMessageA := getLabelCentered(translate("Choice " + choiceIndexString + " is longer than 100 bytes.")) dialogMessageB := getLabelCentered(translate("Choice byte count: " + choiceNumberOfBytesString)) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } _, exists := choicesMap[choiceString] if (exists == true){ dialogTitle := translate("Duplicate Choice Exists") dialogMessage := translate("Each choice must be unique.") dialogContent := getLabelCentered(dialogMessage) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } choicesMap[choiceString] = struct{}{} newChoicesList = append(newChoicesList, choiceString) } if (len(newChoicesList) < 2){ dialogTitle := translate("Not enough choices.") dialogMessage := translate("You must enter at least 2 choices.") dialogContent := getLabelCentered(dialogMessage) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } maximumAnswersAllowedString := maximumAnswersAllowedSelector.Selected newQuestionIdentifier, err := helpers.GetNewRandomHexString(9) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } questionOptionsChoicesJoined := strings.Join(newChoicesList, "$¥") questionOptions := maximumAnswersAllowedString + "#" + questionOptionsChoicesJoined newQuestionObject := mateQuestionnaire.QuestionObject{ Identifier: newQuestionIdentifier, Type: "Choice", Content: newQuestionContent, Options: questionOptions, } err = addQuestionFunction(newQuestionObject) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } afterCreatePage() })) page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), enterQuestionLabelWithEntry, widget.NewSeparator(), maximumAnswersAllowedRow, widget.NewSeparator(), choicesGrid, submitButton) setPageContent(page, window) } func setBuildMateProfilePage_AddQuestionnaireQuestion_Entry(window fyne.Window, addQuestionFunction func(mateQuestionnaire.QuestionObject)error, previousPage func(), afterCreatePage func()){ title := getPageTitleCentered(translate("Add Questionnaire Question - Entry")) backButton := getBackButtonCentered(previousPage) description1 := getLabelCentered("Entry questions allow responders to enter any text.") description2 := getLabelCentered("Select Numeric to restrict responses to only allow numbers.") description3 := getLabelCentered("Numeric allows you to filter users who respond within a desired range.") enterQuestionLabel := getBoldLabelCentered("Enter Question:") enterQuestionEntry := widget.NewMultiLineEntry() enterQuestionEntry.Wrapping = 3 enterQuestionEntry.SetPlaceHolder("Enter question.") enterQuestionEntryBoxed := getWidgetBoxed(enterQuestionEntry) numericCheckbox := widget.NewCheck("Numeric Responses Only", nil) submitButton := getWidgetCentered(widget.NewButtonWithIcon("Add Question", theme.ConfirmIcon(), func(){ newQuestionContent := enterQuestionEntry.Text if (newQuestionContent == ""){ dialogTitle := translate("No question provided.") dialogMessage := translate("You must enter a question.") dialogContent := getLabelCentered(dialogMessage) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } if (len(newQuestionContent) > 500){ newQuestionBytesString := helpers.ConvertIntToString(len(newQuestionContent)) dialogTitle := translate("Question Is Invalid.") dialogMessageA := getLabelCentered(translate("Question cannot be longer than 500 bytes.")) dialogMessageB := getLabelCentered(translate("Provided question byte count: " + newQuestionBytesString)) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } isAllowed := allowedText.VerifyStringIsAllowed(newQuestionContent) if (isAllowed == false){ dialogTitle := translate("Question Is Invalid.") dialogMessageA := getLabelCentered(translate("Question contains a prohibited character.")) dialogMessageB := getLabelCentered(translate("It must be encoded in UTF-8.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } // The question cannot contain these strings because we user them for encoding unallowedStringsList := []string{"+&", "%¢"} for _, unallowedString := range unallowedStringsList{ isContained := strings.Contains(newQuestionContent, unallowedString) if (isContained == true){ dialogTitle := translate("Question Is Invalid.") dialogMessageA := getLabelCentered(translate("Question contains prohibited string: ") + unallowedString) dialogMessageB := getLabelCentered(translate("Remove this string and resubmit.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } } isNumeric := numericCheckbox.Checked newQuestionIdentifier, err := helpers.GetNewRandomHexString(10) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } getQuestionOptions := func()string{ if (isNumeric == true){ return "Numeric" } return "Any" } questionOptions := getQuestionOptions() newQuestionObject := mateQuestionnaire.QuestionObject{ Identifier: newQuestionIdentifier, Type: "Entry", Content: newQuestionContent, Options: questionOptions, } err = addQuestionFunction(newQuestionObject) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } afterCreatePage() })) submitButtonWithCheckbox := container.NewVBox(numericCheckbox, submitButton) entryWithCheckBoxAndSubmitButton := getContainerCentered(container.NewGridWithColumns(1, enterQuestionEntryBoxed, submitButtonWithCheckbox)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), enterQuestionLabel, entryWithCheckBoxAndSubmitButton) setPageContent(page, window) }