seekia/gui/gui.go
2024-09-06 14:25:30 +00:00

1259 lines
39 KiB
Go

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
}