package gui // gui.go provides miscellaneous GUI functions import "fyne.io/fyne/v2" import "fyne.io/fyne/v2/widget" import "fyne.io/fyne/v2/layout" import "fyne.io/fyne/v2/dialog" import "fyne.io/fyne/v2/theme" import "fyne.io/fyne/v2/canvas" import "fyne.io/fyne/v2/container" import "seekia/internal/appMemory" import "seekia/internal/appUsers" import "seekia/internal/encoding" import "seekia/internal/helpers" import "seekia/internal/identity" import "seekia/internal/mySettings" import "seekia/internal/translation" import "errors" func getCustomFyneSize(shift int)fyne.Size{ normalStyle := fyne.TextStyle{ Bold: false, Italic: false, Monospace: false, } customSize := float32(30 + shift) size := fyne.MeasureText("Standard", customSize, normalStyle) return size } func getBoldLabel(text string) fyne.Widget{ titleStyle := fyne.TextStyle{ Bold: true, Italic: false, Monospace: false, } boldLabel := widget.NewLabelWithStyle(text, fyne.TextAlign(fyne.TextAlignCenter), titleStyle) return boldLabel } func getBoldItalicLabel(text string) fyne.Widget{ titleStyle := fyne.TextStyle{ Bold: true, Italic: true, Monospace: false, } boldItalicLabel := widget.NewLabelWithStyle(text, fyne.TextAlign(fyne.TextAlignCenter), titleStyle) return boldItalicLabel } func getItalicLabel(text string) fyne.Widget{ italicTextStyle := fyne.TextStyle{ Bold: false, Italic: true, Monospace: false, } italicLabel := widget.NewLabelWithStyle(text, fyne.TextAlign(fyne.TextAlignCenter), italicTextStyle) return italicLabel } func getFyneTextStyle_Standard()fyne.TextStyle{ standardStyle := fyne.TextStyle{ Bold: false, Italic: false, Monospace: false, } return standardStyle } func getFyneTextStyle_Bold()fyne.TextStyle{ boldStyle := fyne.TextStyle{ Bold: true, Italic: false, Monospace: false, } return boldStyle } func getFyneTextStyle_Italic()fyne.TextStyle{ italicStyle := fyne.TextStyle{ Bold: false, Italic: true, Monospace: false, } return italicStyle } func getLabelCentered(text string) *fyne.Container{ label := widget.NewLabel(text) labelCentered := container.NewHBox(layout.NewSpacer(), label, layout.NewSpacer()) return labelCentered } func getBoldLabelCentered(inputText string)*fyne.Container{ boldLabel := getBoldLabel(inputText) boldLabelCentered := container.NewHBox(layout.NewSpacer(), boldLabel, layout.NewSpacer()) return boldLabelCentered } func getItalicLabelCentered(inputText string)*fyne.Container{ italicLabel := getItalicLabel(inputText) italicLabelCentered := container.NewHBox(layout.NewSpacer(), italicLabel, layout.NewSpacer()) return italicLabelCentered } func getBoldItalicLabelCentered(inputText string)*fyne.Container{ boldItalicLabel := getBoldItalicLabel(inputText) boldItalicLabelCentered := container.NewHBox(layout.NewSpacer(), boldItalicLabel, layout.NewSpacer()) return boldItalicLabelCentered } func getWidgetCentered(widget fyne.Widget)*fyne.Container{ widgetCentered := container.NewHBox(layout.NewSpacer(), widget, layout.NewSpacer()) return widgetCentered } func getContainerCentered(inputContainer *fyne.Container)*fyne.Container{ containerCentered := container.NewHBox(layout.NewSpacer(), inputContainer, layout.NewSpacer()) return containerCentered } func getFyneImageCentered(inputImage *canvas.Image) *fyne.Container{ imageCentered := container.NewHBox(layout.NewSpacer(), inputImage, layout.NewSpacer()) return imageCentered } func getPageTitleCentered(title string)(*fyne.Container){ textStyle := getFyneTextStyle_Bold() currentApp := fyne.CurrentApp() currentThemeVariant := currentApp.Settings().ThemeVariant() currentThemeObject := currentApp.Settings().Theme() // The .Color function is used to retrieve any specified color // In this case, we are retrieving the current foreground color of the theme // We have to include the theme variant to retrieve the color textColor := currentThemeObject.Color(theme.ColorNameForeground, currentThemeVariant) label := canvas.NewText(title, textColor) label.TextSize = 17 label.TextStyle = textStyle increasedPadding := container.NewPadded(label) increasedPadding2 := container.NewPadded(increasedPadding) pageTitleCentered := container.NewHBox(layout.NewSpacer(), increasedPadding2, layout.NewSpacer()) return pageTitleCentered } func getPageSubtitleCentered(subtitle string) *fyne.Container{ textStyle := getFyneTextStyle_Bold() currentApp := fyne.CurrentApp() currentThemeVariant := currentApp.Settings().ThemeVariant() currentThemeObject := currentApp.Settings().Theme() // The .Color function is used to retrieve any specified color // In this case, we are retrieving the current foreground color of the theme // We have to include the theme variant to retrieve the color textColor := currentThemeObject.Color(theme.ColorNameForeground, currentThemeVariant) label := canvas.NewText(subtitle, textColor) label.TextSize = 16 label.TextStyle = textStyle labelPadded1 := container.NewPadded(label) labelPadded2 := container.NewPadded(labelPadded1) pageSubtitleCentered := container.NewHBox(layout.NewSpacer(), labelPadded2, layout.NewSpacer()) return pageSubtitleCentered } func getBackButtonCentered(previousPage func())*fyne.Container{ backButton := getWidgetCentered(widget.NewButtonWithIcon(translate("Go Back"), theme.NavigateBackIcon(), previousPage)) return backButton } func getFyneImageBoxed(inputImage *canvas.Image) *fyne.Container{ boxedImage := container.NewBorder(widget.NewSeparator(), widget.NewSeparator(), widget.NewSeparator(), widget.NewSeparator(), inputImage) return boxedImage } func getContainerBoxed(inputContainer *fyne.Container) *fyne.Container{ boxedContainer := container.NewBorder(widget.NewSeparator(), widget.NewSeparator(), widget.NewSeparator(), widget.NewSeparator(), inputContainer) return boxedContainer } func getAppTabsBoxed(inputTabs *container.AppTabs) *fyne.Container{ boxedTabs := container.NewBorder(widget.NewSeparator(), widget.NewSeparator(), widget.NewSeparator(), widget.NewSeparator(), inputTabs) return boxedTabs } func getScrollContainerBoxed(inputScrollContainer *container.Scroll) *fyne.Container{ boxedContainer := container.NewBorder(widget.NewSeparator(), widget.NewSeparator(), widget.NewSeparator(), widget.NewSeparator(), inputScrollContainer) return boxedContainer } func getWidgetBoxed(inputWidget fyne.Widget) *fyne.Container{ boxedWidget := container.NewBorder(widget.NewSeparator(), widget.NewSeparator(), widget.NewSeparator(), widget.NewSeparator(), inputWidget) return boxedWidget } func translate(text string) string{ result := translation.TranslateTextFromEnglishToMyLanguage(text) return result } func translateWithContext(text string, context string)string{ //TODO: We will use this to translate text that belongs to a specific context // An example is 23andMe, which will have its own translations for text // For this reason, we may end up having 2 identical texts that have different translations return text } //TODO: Swap content and window parameters func setPageContent(content *fyne.Container, window fyne.Window){ navbar, navbarLocation, err := getNavigationBar(window) if (err != nil){ title := getPageTitleCentered("Failed to read from filesystem.") description := getLabelCentered("Contact the Seekia developers to report this error.") errorString := err.Error() errorLabel := getLabelCentered(errorString) page := container.NewVBox(title, description, errorLabel) window.SetContent(page) return } contentScrollable := container.NewVScroll(content) getPageContentWithNavbar := func()*fyne.Container{ if (navbarLocation == "Bottom"){ content := container.NewBorder(nil, navbar, nil, nil, contentScrollable) return content } if (navbarLocation == "Left"){ content := container.NewBorder(nil, nil, navbar, nil, contentScrollable) return content } if (navbarLocation == "Right"){ content := container.NewBorder(nil, nil, nil, navbar, contentScrollable) return content } content := container.NewBorder(navbar, nil, nil, nil, contentScrollable) return content } pageContentWithNavbar := getPageContentWithNavbar() window.SetContent(pageContentWithNavbar) } //Outputs: // -*fyne.Container: Navigation Bar // -string: Navigation location ("Top"/"Bottom"/"Left"/"Right") // -error func getNavigationBar(window fyne.Window) (*fyne.Container, string, error){ geneticsIcon, err := getFyneImageIcon("Genome") if (err != nil) { return nil, "", err } hostIcon, err := getFyneImageIcon("Host") if (err != nil) { return nil, "", err } desiresIcon, err := getFyneImageIcon("Desires") if (err != nil) { return nil, "", err } matchesIcon, err := getFyneImageIcon("Mate") if (err != nil) { return nil, "", err } homeIcon, err := getFyneImageIcon("Home") if (err != nil) { return nil, "", err } profileIcon, err := getFyneImageIcon("Profile") if (err != nil) { return nil, "", err } moderateIcon, err := getFyneImageIcon("Moderate") if (err != nil) { return nil, "", err } chatIcon, err := getFyneImageIcon("Chat") if (err != nil) { return nil, "", err } settingsIcon, err := getFyneImageIcon("Settings") if (err != nil) { return nil, "", err } geneticsButton := widget.NewButton(translate("Genetics"), func(){ setGeneticsPage(window) }) desiresButton := widget.NewButton(translate("Desires"), func(){ setDesiresPage(window) }) matchesButton := widget.NewButton(translate("Matches"), func(){ setMatchesPage(window) }) homeButton := widget.NewButton(translate("Home"), func(){ setHomePage(window) }) profileButton := widget.NewButton(translate("Profile"), func(){ setProfilePage(window, false, "", false, nil) }) hostButton := widget.NewButton(translate("Host"), func(){ setHostPage(window, false, nil) }) chatButton := widget.NewButton(translate("Chat"), func(){ setChatPage(window) }) moderateButton := widget.NewButton(translate("Moderate"), func(){ setModeratePage(window, false, nil) }) settingsButton := widget.NewButton(translate("Settings"), func(){ setSettingsPage(window) }) geneticsButtonWithIcon := container.NewGridWithRows(2, geneticsIcon, geneticsButton) desiresButtonWithIcon := container.NewGridWithRows(2, desiresIcon, desiresButton) matchesButtonWithIcon := container.NewGridWithRows(2, matchesIcon, matchesButton) homeButtonWithIcon := container.NewGridWithRows(2, homeIcon, homeButton) profileButtonWithIcon := container.NewGridWithRows(2, profileIcon, profileButton) hostButtonWithIcon := container.NewGridWithRows(2, hostIcon, hostButton) chatButtonWithIcon := container.NewGridWithRows(2, chatIcon, chatButton) moderateButtonWithIcon := container.NewGridWithRows(2, moderateIcon, moderateButton) settingsButtonWithIcon := container.NewGridWithRows(2, settingsIcon, settingsButton) getNavigationLocation := func()(string, error){ exists, navigationLocation, err := mySettings.GetSetting("NavigationBarLocation") if (err != nil) { return "", err } if (exists == false){ return "Top", nil } if (navigationLocation != "Top" && navigationLocation != "Bottom" && navigationLocation != "Left" && navigationLocation != "Right"){ return "", errors.New("Invalid NavigationBarLocation: " + navigationLocation) } return navigationLocation, nil } navigationLocation, err := getNavigationLocation() if (err != nil) { return nil, "", err } getNavbarButtonsList := func()([]*fyne.Container, error){ _, showHostButton, err := mySettings.GetSetting("ShowHostButtonNavigation") if (err != nil) { return nil, err } _, showModerateButton, err := mySettings.GetSetting("ShowModerateButtonNavigation") if (err != nil) { return nil, err } if (showHostButton == "Yes" && showModerateButton == "Yes"){ itemsList := []*fyne.Container{homeButtonWithIcon, geneticsButtonWithIcon, profileButtonWithIcon, desiresButtonWithIcon, matchesButtonWithIcon, chatButtonWithIcon, hostButtonWithIcon, moderateButtonWithIcon, settingsButtonWithIcon} return itemsList, nil } if (showHostButton != "Yes" && showModerateButton == "Yes"){ itemsList := []*fyne.Container{homeButtonWithIcon, geneticsButtonWithIcon, profileButtonWithIcon, desiresButtonWithIcon, matchesButtonWithIcon, chatButtonWithIcon, moderateButtonWithIcon, settingsButtonWithIcon } return itemsList, nil } if (showHostButton == "Yes" && showModerateButton != "Yes"){ itemsList := []*fyne.Container{homeButtonWithIcon, geneticsButtonWithIcon, profileButtonWithIcon, desiresButtonWithIcon, matchesButtonWithIcon, chatButtonWithIcon, hostButtonWithIcon, settingsButtonWithIcon } return itemsList, nil } // showHostButton != "Yes" && showModerateButton != "Yes" itemsList := []*fyne.Container{homeButtonWithIcon, geneticsButtonWithIcon, profileButtonWithIcon, desiresButtonWithIcon, matchesButtonWithIcon, chatButtonWithIcon, settingsButtonWithIcon } return itemsList, nil } navbarButtonsList, err := getNavbarButtonsList() if (err != nil) { return nil, "", err } if (navigationLocation == "Top" || navigationLocation == "Bottom"){ navbar := container.NewHBox() navbar.Add(layout.NewSpacer()) for _, element := range navbarButtonsList{ navbar.Add(element) } navbar.Add(layout.NewSpacer()) navbarScrollable := container.NewHScroll(navbar) if (navigationLocation == "Top"){ navbarWithSeparator := container.NewVBox(navbarScrollable, widget.NewSeparator()) return navbarWithSeparator, navigationLocation, nil } navbarWithSeparator := container.NewVBox(widget.NewSeparator(), navbarScrollable) return navbarWithSeparator, navigationLocation, nil } if (navigationLocation == "Left" || navigationLocation == "Right"){ navbar := container.NewVBox() navbar.Add(layout.NewSpacer()) for _, element := range navbarButtonsList{ navbar.Add(element) } navbar.Add(layout.NewSpacer()) navbarScrollable := container.NewVScroll(navbar) if (navigationLocation == "Left"){ navbarWithSeparator := container.NewHBox(navbarScrollable, widget.NewSeparator()) return navbarWithSeparator, navigationLocation, nil } navbarWithSeparator := container.NewHBox(widget.NewSeparator(), navbarScrollable) return navbarWithSeparator, navigationLocation, nil } return nil, "", errors.New("Invalid Navigation bar location: " + navigationLocation) } // This page is used when a new GUI page is being loaded, and concurrency is not being used // When concurrency is being used, we give the user a GUI page where they can exit the page instead of waiting for the process to complete // This screen should only be used when the time it takes to load will only be a few seconds func setLoadingScreen(window fyne.Window, pageTitle string, loadingText string){ title := getPageTitleCentered(pageTitle) loadingLabel := getWidgetCentered(getItalicLabel(loadingText)) progressBar := getWidgetCentered(widget.NewProgressBarInfinite()) pageContent := container.NewVBox(title, loadingLabel, progressBar) page := container.NewCenter(pageContent) window.SetContent(page) } // This will only be used within the startup gui func setErrorEncounteredPage_NoNavBar(window fyne.Window, err error, showBackButton bool, previousPage func()){ title := getPageTitleCentered("Error Encountered") header := container.NewVBox(title) if (showBackButton == true){ backButton := getBackButtonCentered(previousPage) header.Add(backButton) } header.Add(widget.NewSeparator()) description1 := getLabelCentered("Something went wrong. Report this error to Seekia developers.") description2 := getBoldLabelCentered("Be aware that this error may contain sensitive information.") description3 := getLabelCentered("Censor any sensitive information manually before sharing the error.") header.Add(description1) header.Add(description2) header.Add(description3) header.Add(widget.NewSeparator()) getErrorString := func()string{ if (err == nil){ return "No nav bar error encountered page called with nil error." } errorString := err.Error() return errorString } errorString := getErrorString() errorLabel := widget.NewLabel(errorString) errorLabel.Wrapping = 3 errorLabel.Alignment = 1 errorLabel.TextStyle = getFyneTextStyle_Bold() //TODO: Add copyable toggle page := container.NewBorder(header, nil, nil, nil, errorLabel) window.SetContent(page) } func setErrorEncounteredPage(window fyne.Window, err error, previousPage func()){ appMemory.SetMemoryEntry("CurrentViewedPage", "ErrorEncounteredPage") currentPage := func(){setErrorEncounteredPage(window, err, previousPage)} title := getPageTitleCentered("Error Encountered") backButton := getBackButtonCentered(previousPage) description1 := getLabelCentered("Something went wrong. Report this error to Seekia developers.") description2 := getBoldLabelCentered("Be aware that this error may contain sensitive information.") description3 := getLabelCentered("Censor any sensitive information manually before sharing the error.") getErrorString := func()string{ if (err == nil){ return "Error encountered page called with nil error." } errorString := err.Error() return errorString } errorString := getErrorString() errorStringTrimmed, _, _ := helpers.TrimAndFlattenString(errorString, 40) errorLabel := getBoldLabelCentered(errorStringTrimmed) viewTextButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ setViewTextPage(window, "Viewing Error", errorString, false, currentPage) }) errorRow := container.NewHBox(layout.NewSpacer(), errorLabel, viewTextButton, layout.NewSpacer()) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), errorRow, widget.NewSeparator()) setPageContent(page, window) } func showUnderConstructionDialog(window fyne.Window){ dialogTitle := translate("Under Construction") dialogMessageA := getLabelCentered(translate("Seekia is under construction.")) dialogMessageB := getLabelCentered(translate("This page/feature needs to be built.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) } func setHomePage(window fyne.Window){ appMemory.SetMemoryEntry("CurrentViewedPage", "Home") currentPage := func(){setHomePage(window)} title := getPageTitleCentered("Home") welcomeTitle := getBoldLabelCentered("Welcome to Seekia!") welcomeMessage := getLabelCentered("Seekia is a genetics aware mate discovery network.") exists, currentUserName := appUsers.GetCurrentAppUserName() if (exists == false){ setErrorEncounteredPage(window, errors.New("setHomePage called with no signed-in user."), func(){setChooseAppUserPage(window)}) return } currentUserLabel := getBoldItalicLabel("Current User:") changeUserButton := getWidgetCentered(widget.NewButtonWithIcon(currentUserName, theme.AccountIcon(), func(){ setLoadingScreen(window, "Signing Out", "Signing out...") err := appUsers.SignOutOfAppUser() if (err != nil){ setErrorEncounteredPage_NoNavBar(window, err, true, func(){setChooseAppUserPage(window)}) return } setChooseAppUserPage(window) })) currentUserRow := container.NewHBox(layout.NewSpacer(), currentUserLabel, changeUserButton, layout.NewSpacer()) helpIcon, err := getFyneImageIcon("Info") if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } helpButton := widget.NewButton(translate("Help"), func(){ setHelpPage(window, currentPage) }) helpButtonWithIcon := container.NewGridWithColumns(1, helpIcon, helpButton) rulesIcon, err := getFyneImageIcon("Questionnaire") if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } rulesButton := widget.NewButton("Rules", func(){ setViewSeekiaRulesPage(window, currentPage) }) rulesButtonWithIcon := container.NewGridWithColumns(1, rulesIcon, rulesButton) syncIcon, err := getFyneImageIcon("Sync") if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } syncButton := widget.NewButton(translate("Sync"), func(){ setSyncPage(window, currentPage) }) syncButtonWithIcon := container.NewGridWithRows(2, syncIcon, syncButton) buttonsRow := getContainerCentered(container.NewGridWithRows(1, helpButtonWithIcon, rulesButtonWithIcon, syncButtonWithIcon)) description1 := getBoldLabelCentered("Learn how to use Seekia on the Help page.") description2 := getLabelCentered("View the Seekia rules on the Rules page.") description3 := getLabelCentered("Manage your connection to the Seekia network on the Sync page.") seekiaLinkTitle := widget.NewLabel("Stay updated at:") //TODO: Retrieve URL from parameters // URL may need to be changed if it is lost or stolen // Also add a page that shows .eth URL seekiaLink := getBoldLabel("Seekia.net") seekiaVersion := getLabelCentered("Seekia Version 0.71") seekiaLinkWithTitle := container.NewHBox(layout.NewSpacer(), seekiaLinkTitle, seekiaLink, layout.NewSpacer()) page := container.NewVBox(title, widget.NewSeparator(), welcomeTitle, welcomeMessage, widget.NewSeparator(), currentUserRow, widget.NewSeparator(), buttonsRow, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), seekiaLinkWithTitle, seekiaVersion) setPageContent(page, window) } func setSelectLanguagePage(window fyne.Window, showNavigationBar bool, previousPage func()){ title := getPageTitleCentered("Select Language") backButton := getBackButtonCentered(previousPage) description := getLabelCentered("Choose your language.") currentLanguage := translation.GetMyLanguage() currentLanguageLabel := getBoldLabelCentered("Current Language:") currentLanguageTextLabel := getLabelCentered(currentLanguage) //TODO comingSoonLabel := getBoldLabelCentered("More languages coming soon.") page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), currentLanguageLabel, currentLanguageTextLabel, widget.NewSeparator(), comingSoonLabel) if (showNavigationBar == true){ setPageContent(page, window) } else { window.SetContent(page) } } func setViewSeekiaRulesPage(window fyne.Window, previousPage func()){ title := getPageTitleCentered("Seekia Rules") backButton := getBackButtonCentered(previousPage) description1 := getBoldLabelCentered("Unruleful content will be banned by the Seekia moderators.") description2 := getLabelCentered("You can report rulebreaking content within the app.") description3 := getLabelCentered("Identities, profiles and messages can be reported and banned.") //TODO: Add more rules, and improve these rules. // There should be a more descriptive page for moderators that has specific examples spamDescription := widget.NewLabel("Selling or trading. Advertising or soliciting anything except yourself.") spamAccordionItem := widget.NewAccordionItem("Spam", spamDescription) nudityDescription := widget.NewLabel("Nudity and pornography.") nudityAccordionItem := widget.NewAccordionItem("Nudity", nudityDescription) dangerousDescription := widget.NewLabel("Threats, harrasment, and other dangerous content.") dangerousAccordionItem := widget.NewAccordionItem("Dangerous Content", dangerousDescription) illegalDescription := widget.NewLabel("Prostitution, drug trading, copyrighted content, and other unlawful content.") illegalAccordionItem := widget.NewAccordionItem("Illegal Content", illegalDescription) rulesAccordion := getContainerCentered(getWidgetBoxed(widget.NewAccordion(spamAccordionItem, nudityAccordionItem, dangerousAccordionItem, illegalAccordionItem))) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, rulesAccordion) setPageContent(page, window) } // If profileTypeProvided == true, there is no ability to navigate between profile types // If profileTypeProvided == false, you can navigate between profile types, and the result is saved in ProfilePageProfileType func setProfilePage(window fyne.Window, profileTypeProvided bool, profileType string, previousPageProvided bool, previousPage func()){ currentPage := func(){setProfilePage(window, profileTypeProvided, profileType, previousPageProvided, previousPage)} appMemory.SetMemoryEntry("CurrentViewedPage", "Profile") getModeratorModeEnabledStatus := func()(bool, error){ exists, moderatorModeStatus, err := mySettings.GetSetting("ModeratorModeOnOffStatus") if (err != nil) { return false, err } if (exists == true && moderatorModeStatus == "On"){ return true, nil } return false, nil } getHostModeEnabledStatus := func()(bool, error){ exists, hostModeStatus, err := mySettings.GetSetting("HostModeOnOffStatus") if (err != nil) { return false, err } if (exists == true && hostModeStatus == "On"){ return true, nil } return false, nil } moderatorModeEnabled, err := getModeratorModeEnabledStatus() if (err != nil) { setErrorEncounteredPage(window, err, func(){setHomePage(window)}) return } hostModeEnabled, err := getHostModeEnabledStatus() if (err != nil) { setErrorEncounteredPage(window, err, func(){setHomePage(window)}) return } getMyProfileType := func()(string, error){ if (profileTypeProvided == true){ if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){ return "", errors.New("setProfilePage called with invalid profileType: " + profileType) } return profileType, nil } exists, myProfileType, err := mySettings.GetSetting("ProfilePageProfileType") if (err != nil) { return "", err } if (exists == false){ return "Mate", nil } if (myProfileType != "Mate" && myProfileType != "Host" && myProfileType != "Moderator"){ return "", errors.New("mySettings malformed: invalid ProfilePageProfileType: " + myProfileType) } if (myProfileType == "Moderator" && moderatorModeEnabled == false){ return "Mate", nil } if (myProfileType == "Host" && hostModeEnabled == false){ return "Mate", nil } return myProfileType, nil } myProfileType, err := getMyProfileType() if (err != nil) { setErrorEncounteredPage(window, err, func(){setHomePage(window)}) return } title := getPageTitleCentered("My Profile") page := container.NewVBox(title) if (previousPageProvided == true){ backButton := getBackButtonCentered(previousPage) page.Add(backButton) } profileTypeIcon, err := getIdentityTypeIcon(myProfileType, -10) if (err != nil){ setErrorEncounteredPage(window, err, func(){setHomePage(window)}) return } getChangeProfileTypeButtonOrText := func()(fyne.Widget, error){ if (profileTypeProvided == true){ // The page does not offer a button to switch between profile types. profileTypeLabel := getBoldLabel(myProfileType + " Profile") return profileTypeLabel, nil } if (moderatorModeEnabled == false && hostModeEnabled == false){ mateLabel := getBoldLabel("Mate Profile") return mateLabel, nil } changeProfileTypeButton := widget.NewButton(myProfileType + " Profile", func(){ getNextProfileType := func()string{ if (myProfileType == "Mate"){ if (moderatorModeEnabled == true){ return "Moderator" } return "Host" } if (myProfileType == "Moderator"){ if (hostModeEnabled == true){ return "Host" } return "Mate" } return "Mate" } nextProfileType := getNextProfileType() err := mySettings.SetSetting("ProfilePageProfileType", nextProfileType) if (err != nil){ setErrorEncounteredPage(window, err, func(){setHomePage(window)}) return } currentPage() }) return changeProfileTypeButton, nil } changeProfileTypeButtonOrText, err := getChangeProfileTypeButtonOrText() if (err != nil){ setErrorEncounteredPage(window, err, func(){setHomePage(window)}) return } changeProfileTypeButtonWithIcon := getContainerCentered(container.NewGridWithRows(2, profileTypeIcon, changeProfileTypeButtonOrText)) getDescriptionSection := func()*fyne.Container{ description1 := getLabelCentered("Manage your " + myProfileType + " profile.") if (myProfileType == "Mate"){ description2 := getLabelCentered("Start by building your profile.") description3 := getLabelCentered("Broadcast your profile once it is ready.") description4 := getLabelCentered("You must rebroadcast your profile whenever you make changes.") descriptionSection := container.NewVBox(description1, description2, description3, description4) return descriptionSection } if (myProfileType == "Moderator"){ description2 := getLabelCentered("Building your Moderator profile is optional.") description3 := getLabelCentered("You must rebroadcast your profile whenever you make changes.") descriptionSection := container.NewVBox(description1, description2, description3) return descriptionSection } // myProfileType = "Host" description2 := getLabelCentered("Building your Host profile is optional.") description3 := getLabelCentered("Your Host profile will be broadcast automatically.") descriptionSection := container.NewVBox(description1, description2, description3) return descriptionSection } descriptionSection := getDescriptionSection() buildProfileIcon, err := getFyneImageIcon("Check") if (err != nil) { setErrorEncounteredPage(window, err, func(){setHomePage(window)}) return } profileIcon, err := getFyneImageIcon("Profile") if (err != nil) { setErrorEncounteredPage(window, err, func(){setHomePage(window)}) return } broadcastIcon, err := getFyneImageIcon("Broadcast") if (err != nil) { setErrorEncounteredPage(window, err, func(){setHomePage(window)}) return } buildProfileButton := widget.NewButton("Build", func(){ if (myProfileType == "Mate"){ setBuildMyMateProfilePage(window, currentPage) return } if (myProfileType == "Moderator"){ setBuildMyModeratorProfilePage(window, currentPage) return } setBuildMyHostProfilePage(window, currentPage) }) buildProfileButtonWithIcon := container.NewGridWithRows(2, buildProfileIcon, buildProfileButton) broadcastButton := widget.NewButton(translate("Broadcast"), func(){ if (myProfileType == "Host"){ //TODO: Add a custom broadcast page for Host where you can monitor the last time it was broadcast and deactivate showUnderConstructionDialog(window) return } setBroadcastPage(window, myProfileType, currentPage) }) broadcastButtonWithIcon := container.NewGridWithRows(2, broadcastIcon, broadcastButton) viewProfileButton := widget.NewButton(translate("View"), func(){ setViewMyLocalOrPublicProfilePage(window, myProfileType, currentPage) }) viewProfileButtonWithIcon := container.NewGridWithRows(2, profileIcon, viewProfileButton) actionsGrid := container.NewGridWithRows(1, buildProfileButtonWithIcon, broadcastButtonWithIcon, viewProfileButtonWithIcon) actionsSection := getContainerCentered(actionsGrid) page.Add(widget.NewSeparator()) page.Add(changeProfileTypeButtonWithIcon) page.Add(widget.NewSeparator()) page.Add(descriptionSection) page.Add(widget.NewSeparator()) page.Add(actionsSection) if (myProfileType == "Moderator"){ page.Add(widget.NewSeparator()) scoreIcon, err := getFyneImageIcon("Score") if (err != nil){ setErrorEncounteredPage(window, err, func(){setHomePage(window)}) return } myScoreButton := widget.NewButton("My Score", func(){ setViewMyModeratorScorePage(window, currentPage) }) myScoreButtonWithIcon := container.NewGridWithColumns(1, scoreIcon, myScoreButton) reviewsIcon, err := getFyneImageIcon("Choice") if (err != nil){ setErrorEncounteredPage(window, err, func(){setHomePage(window)}) return } myReviewsButton := widget.NewButton("My Reviews", func(){ setViewMyReviewsPage(window, "Profile", currentPage) }) myReviewsButtonWithIcon := container.NewGridWithColumns(1, reviewsIcon, myReviewsButton) moderatorButtonsRow := getContainerCentered(container.NewGridWithRows(1, myScoreButtonWithIcon, myReviewsButtonWithIcon)) page.Add(moderatorButtonsRow) page.Add(widget.NewSeparator()) } setPageContent(page, window) } // This is a page to view an identity hash in its entirety, and a text entry to copy it func setViewIdentityHashPage(window fyne.Window, identityHash [16]byte, previousPage func()){ title := getPageTitleCentered("Viewing Identity Hash") backButton := getBackButtonCentered(previousPage) identityHashString, _, err := identity.EncodeIdentityHashBytesToString(identityHash) if (err != nil){ identityHashHex := encoding.EncodeBytesToHexString(identityHash[:]) setErrorEncounteredPage(window, errors.New("setViewIdentityHashPage called with invalid identity hash: " + identityHashHex), previousPage) } identityHashLabel := getBoldLabel(identityHashString) identityHashLabelPadded := container.NewPadded(container.NewPadded(identityHashLabel)) identityHashEntry := widget.NewMultiLineEntry() identityHashEntry.SetText(identityHashString) identityHashEntry.OnChanged = func(_ string){ identityHashEntry.SetText(identityHashString) } identityHashEntryBoxed := getWidgetBoxed(identityHashEntry) widener := widget.NewLabel(" ") identityHashEntryWidened := getContainerCentered(container.NewGridWithColumns(1, identityHashEntryBoxed, widener)) page := container.NewVBox(title, backButton, widget.NewSeparator(), identityHashLabelPadded, identityHashEntryWidened) setPageContent(page, window) } // This is a page to view an message/profile hash in its entirety, and a text entry to copy it func setViewContentHashPage(window fyne.Window, hashType string, contentHash []byte, previousPage func()){ title := getPageTitleCentered("Viewing " + hashType + " Hash") backButton := getBackButtonCentered(previousPage) contentHashHex := encoding.EncodeBytesToHexString(contentHash) contentHashLabel := getBoldLabel(contentHashHex) contentHashLabelPadded := container.NewPadded(container.NewPadded(contentHashLabel)) contentHashEntry := widget.NewMultiLineEntry() contentHashEntry.SetText(contentHashHex) contentHashEntry.OnChanged = func(_ string){ contentHashEntry.SetText(contentHashHex) } contentHashEntry.Wrapping = 3 contentHashEntryBoxed := getWidgetBoxed(contentHashEntry) widener := widget.NewLabel(" ") contentHashEntryWidened := getContainerCentered(container.NewGridWithColumns(1, contentHashEntryBoxed, widener)) page := container.NewVBox(title, backButton, widget.NewSeparator(), contentHashLabelPadded, contentHashEntryWidened) setPageContent(page, window) } //TODO: Make this page able to handle large amounts of text without crashing // In the meantime, trim text that is too long to render func setViewTextPage(window fyne.Window, pageTitle string, textToView string, viewCopyable bool, previousPage func()){ title := getPageTitleCentered(pageTitle) backButton := getBackButtonCentered(previousPage) header := container.NewVBox(title, backButton, widget.NewSeparator()) getTextViewWidget := func()fyne.Widget{ if (viewCopyable == true){ textEntry := widget.NewMultiLineEntry() textEntry.SetText(textToView) textEntry.OnChanged = func(_ string){ textEntry.SetText(textToView) } textEntry.Wrapping = 3 return textEntry } textViewWidget := widget.NewLabel(textToView) textViewWidget.Wrapping = 3 textViewWidget.Alignment = 1 return textViewWidget } textViewWidget := getTextViewWidget() getChangeDisplayTypeButton := func()fyne.Widget{ if (viewCopyable == false){ viewCopyableButton := widget.NewButtonWithIcon("View Copyable", theme.ContentPasteIcon(), func(){ setViewTextPage(window, pageTitle, textToView, true, previousPage) }) return viewCopyableButton } viewUncopyableButton := widget.NewButtonWithIcon("View Uncopyable", theme.VisibilityIcon(), func(){ setViewTextPage(window, pageTitle, textToView, false, previousPage) }) return viewUncopyableButton } changeDisplayTypeButton := getChangeDisplayTypeButton() changeDisplayTypeButtonCentered := getWidgetCentered(changeDisplayTypeButton) page := container.NewBorder(header, changeDisplayTypeButtonCentered, nil, nil, textViewWidget) setPageContent(page, window) } func setViewLinkPage(window fyne.Window, pageTitle string, textToView string, previousPage func()){ title := getPageTitleCentered(pageTitle) backButton := getBackButtonCentered(previousPage) header := container.NewVBox(title, backButton, widget.NewSeparator()) textEntry := widget.NewMultiLineEntry() textEntry.SetText(textToView) textEntry.OnChanged = func(_ string){ textEntry.SetText(textToView) } textEntry.Wrapping = 3 page := container.NewBorder(header, nil, nil, nil, textEntry) setPageContent(page, window) } func getCryptocurrencyAddressLabelWithCopyAndQRButtons(window fyne.Window, cryptocurrency string, cryptoAddress string, currentPage func())(*fyne.Container, error){ if (cryptocurrency != "Ethereum" && cryptocurrency != "Cardano"){ return nil, errors.New("getCryptocurrencyAddressLabelWithCopyAndQRButtons called with invalid cryptocurrency: " + cryptocurrency) } addressLabel := widget.NewMultiLineEntry() addressLabel.SetText(cryptoAddress) addressLabel.OnChanged = func(_ string){ addressLabel.SetText(cryptoAddress) } addressBox := getWidgetBoxed(addressLabel) qrCodeImage, err := getFyneImageIcon("QR") if (err != nil) { return nil, err } qrCodeButton := widget.NewButton("QR Code", func(){ title := translate(cryptocurrency + " QR Code") //TODO: Add QR Code generation dialogMessage := getLabelCentered("Under Construction") dialog.ShowCustom(title, translate("Close"), dialogMessage, window) }) qrCodeButtonWithIcon := getContainerBoxed(container.NewGridWithColumns(1, qrCodeImage, qrCodeButton)) clipboardIcon, err := getFyneImageIcon("Clipboard") if (err != nil){ return nil, err } currentClipboard := window.Clipboard() getClipboardButtonText := func()string{ currentContent := currentClipboard.Content() if (currentContent == cryptoAddress){ return "Copied!" } return "Copy" } clipboardButtonText := getClipboardButtonText() copyToClipboardButton := widget.NewButton(clipboardButtonText, func(){ currentClipboard.SetContent(cryptoAddress) currentPage() }) clipboardButtonWithIcon := getContainerBoxed(container.NewGridWithColumns(1, clipboardIcon, copyToClipboardButton)) addressBoxWithClipboardQRRow := getContainerCentered(container.NewGridWithRows(1, qrCodeButtonWithIcon, addressBox, clipboardButtonWithIcon)) return addressBoxWithClipboardQRRow, nil } // This function creates a widget list from an input list of strings // The onClickedFunction is called if any of the items are selected //Inputs: // -[]string: A list of the items // -func(itemIndex int): The function that is called when an item is selected // Outputs: // -fyne.Widget: Widget list // -error func getFyneWidgetListFromStringList(inputList []string, onClickedFunction func(int))(fyne.Widget, error){ listLength := len(inputList) if (listLength == 0){ return nil, errors.New("getFyneWidgetListFromStringList called with empty list.") } getListLength := func()int{ return listLength } createListItemFunction := func()fyne.CanvasObject{ listItem := container.NewHBox(layout.NewSpacer(), widget.NewLabel(""), layout.NewSpacer()) return listItem } updateListItemFunction := func(id widget.ListItemID, item fyne.CanvasObject){ itemName := inputList[id] itemObjectsList := item.(*fyne.Container).Objects itemLabel := itemObjectsList[1].(*widget.Label) itemLabel.SetText(itemName) } widgetList := widget.NewList(getListLength, createListItemFunction, updateListItemFunction) widgetList.OnSelected = func(itemIndex widget.ListItemID) { onClickedFunction(itemIndex) } return widgetList, nil }