package gui // settingsGui.go implements pages to manage a user's settings and user data import "fyne.io/fyne/v2" 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/currencies" import "seekia/internal/appMemory" import "seekia/internal/encoding" import "seekia/internal/globalSettings" import "seekia/internal/helpers" import "seekia/internal/identity" import "seekia/internal/localFilesystem" import "seekia/internal/logger" import "seekia/internal/messaging/chatMessageStorage" import "seekia/internal/messaging/myChatMessages" import "seekia/internal/moderation/reportStorage" import "seekia/internal/moderation/reviewStorage" import "seekia/internal/myIdentity" import "seekia/internal/mySeedPhrases" import "seekia/internal/mySettings" import "seekia/internal/network/appNetworkType/getAppNetworkType" import "seekia/internal/network/appNetworkType/setAppNetworkType" import "seekia/internal/network/myBroadcasts" import "seekia/internal/profiles/profileStorage" import "seekia/internal/seedPhrase" import "errors" func setSettingsPage(window fyne.Window){ appMemory.SetMemoryEntry("CurrentViewedPage", "Settings") currentPage := func(){setSettingsPage(window)} title := getPageTitleCentered("Settings") description := getLabelCentered("Manage your Seekia settings.") myDataButton := widget.NewButton("My Data", func(){ setSettingsPage_MyData(window, currentPage) }) themesButton := widget.NewButton("Themes", func(){ setChooseAppThemePage(window, currentPage) }) navigationButton := widget.NewButton("Navigation", func(){ setNavigationSettingsPage(window, currentPage) }) contentFilteringButton := widget.NewButton("Content Filtering", func(){ setContentFilteringSettingsPage(window, currentPage) }) networkSettingsButton := widget.NewButton("Network", func(){ setManageNetworkSettingsPage(window, currentPage) }) storageSettingsButton := widget.NewButton("Storage", func(){ setManageStoragePage(window, currentPage) }) toolsButton := widget.NewButton("Tools", func(){ setToolsPage(window, currentPage) }) languageButton := widget.NewButton("Language", func(){ setSelectLanguagePage(window, true, currentPage) }) logsButton := widget.NewButton("Logs", func(){ setViewLogsPage(window, "General", currentPage) }) developerButton := widget.NewButton("Developer", func(){ setViewDeveloperSettingsPage(window, currentPage) }) buttonGrid := getContainerCentered(container.NewGridWithColumns(1, myDataButton, themesButton, navigationButton, contentFilteringButton, networkSettingsButton, storageSettingsButton, toolsButton, languageButton, logsButton, developerButton)) pageContent := container.NewVBox(title, widget.NewSeparator(), description, widget.NewSeparator(), buttonGrid) setPageContent(pageContent, window) } func getMetricImperialSwitchButton(window fyne.Window, currentPage func())(fyne.Widget, error){ currentUnitsExist, currentUnits, err := globalSettings.GetSetting("MetricOrImperial") if (err != nil){ return nil, err } getSwitchButton := func()fyne.Widget{ if (currentUnitsExist == false || currentUnits == "Metric"){ button := widget.NewButton(translate("Metric"), func(){ err := globalSettings.SetSetting("MetricOrImperial", "Imperial") if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) return button } button := widget.NewButton(translate("Imperial"), func(){ err := globalSettings.SetSetting("MetricOrImperial", "Metric") if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) return button } switchButton := getSwitchButton() return switchButton, nil } func setSettingsPage_MyData(window fyne.Window, previousPage func()){ currentPage := func(){setSettingsPage_MyData(window, previousPage)} title := getPageTitleCentered("My Data") backButton := getBackButtonCentered(previousPage) myIdentityHashesButton := widget.NewButton("My Identity Hashes", func(){ setViewMyIdentityHashesPage(window, "Mate", currentPage) }) mySeedPhrasesButton := widget.NewButton("My Seed Phrases", func(){ setViewMySeedPhrasesPage(window, "Mate", currentPage) }) deleteIdentityButton := widget.NewButton("Delete Identity", func(){ setDeleteMyIdentityPage(window, "Mate", currentPage) }) importIdentityButton := widget.NewButton("Import Identity", func(){ setImportMyIdentityPage(window, "Mate", currentPage) }) exportDataButton := widget.NewButton("Export Data", func(){ //TODO // Page to export user data // This should export the UserData folder(s) to a folder, and that data should be able to be imported to a new device // The GUI should give options on which data to export showUnderConstructionDialog(window) }) importDataButton := widget.NewButton("Import Data", func(){ //TODO // On import, user should be able to select which elements to import, and any conflicts with existing data should be shown // When importing chat keys, we must make sure to prune the user's undecryptable message hashes list // The new keys should be used to attempt to decrypt those messages again (if they still exist on the network) showUnderConstructionDialog(window) }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, myIdentityHashesButton, mySeedPhrasesButton, deleteIdentityButton, importIdentityButton, exportDataButton, importDataButton)) page := container.NewVBox(title, backButton, widget.NewSeparator(), buttonsGrid) setPageContent(page, window) } func setViewMySeedPhrasesPage(window fyne.Window, myIdentityType string, previousPage func()){ title := getPageTitleCentered(translate("My Seed Phrase")) currentPage := func(){setViewMySeedPhrasesPage(window, myIdentityType, previousPage)} backButton := getBackButtonCentered(previousPage) description1 := getLabelCentered("Your seed phrase can recover your " + myIdentityType + " identity on any device.") description2 := getLabelCentered("Write it down to prevent the loss of your identity.") description3 := getBoldLabelCentered("Do not share your seed phrases!") identityTypeIcon, err := getIdentityTypeIcon(myIdentityType, -10) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } getIdentityTypeLabelOrChangeButton := func()(fyne.Widget, error){ getHostModeEnabledBool := func()(bool, error){ exists, hostModeOnOffStatus, err := mySettings.GetSetting("HostModeOnOffStatus") if (err != nil) { return false, err } if (exists == true && hostModeOnOffStatus == "On"){ return true, nil } return false, nil } getModeratorModeEnabledBool := func()(bool, error){ exists, moderatorModeOnOffStatus, err := mySettings.GetSetting("ModeratorModeOnOffStatus") if (err != nil) { return false, err } if (exists == true && moderatorModeOnOffStatus == "On"){ return true, nil } return false, nil } hostModeEnabled, err := getHostModeEnabledBool() if (err != nil) { return nil, err } moderatorModeEnabled, err := getModeratorModeEnabledBool() if (err != nil) { return nil, err } hostIdentityExists, _, err := mySeedPhrases.GetMySeedPhrase("Host") if (err != nil) { return nil, err } moderatorIdentityExists, _, err := mySeedPhrases.GetMySeedPhrase("Moderator") if (err != nil) { return nil, err } if (hostModeEnabled == false && hostIdentityExists == false && moderatorModeEnabled == false && moderatorIdentityExists == false){ // Mate identity is the only identity to show mateLabel := getBoldLabel("Mate") return mateLabel, nil } getNextIdentityType := func()string{ if (myIdentityType == "Mate"){ if (hostModeEnabled == true || hostIdentityExists == true){ return "Host" } return "Moderator" } if (myIdentityType == "Host"){ if (moderatorModeEnabled == true || moderatorIdentityExists == true){ return "Moderator" } return "Mate" } return "Mate" } nextIdentityType := getNextIdentityType() changeIdentityTypeButton := widget.NewButton(myIdentityType, func(){ setViewMySeedPhrasesPage(window, nextIdentityType, previousPage) }) return changeIdentityTypeButton, nil } identityTypeLabelOrChangeButton, err := getIdentityTypeLabelOrChangeButton() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } identityTypeLabelOrChangeButtonWithIcon := getContainerCentered(container.NewGridWithColumns(1, identityTypeIcon, identityTypeLabelOrChangeButton)) currentSeedPhraseExists, currentSeedPhrase, err := mySeedPhrases.GetMySeedPhrase(myIdentityType) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (currentSeedPhraseExists == false) { identityDoesNotExistLabel := getBoldLabelCentered("Your " + myIdentityType + " identity does not exist.") createIdentityButton := getWidgetCentered(widget.NewButtonWithIcon("Create Identity", theme.NavigateNextIcon(), func(){ setChooseNewIdentityHashPage(window, myIdentityType, currentPage, currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), identityTypeLabelOrChangeButtonWithIcon, identityDoesNotExistLabel, createIdentityButton, widget.NewSeparator()) setPageContent(page, window) return } seedPhraseLabel := widget.NewMultiLineEntry() seedPhraseLabel.Wrapping = 3 seedPhraseLabel.SetText(currentSeedPhrase) seedPhraseLabel.OnChanged = func(_ string){ seedPhraseLabel.SetText(currentSeedPhrase) } seedPhraseLabelBoxed := getWidgetBoxed(seedPhraseLabel) widener := widget.NewLabel(" ") seedPhraseBoxWidened := getContainerCentered(container.NewGridWithColumns(1, seedPhraseLabelBoxed, widener)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), identityTypeLabelOrChangeButtonWithIcon, seedPhraseBoxWidened) setPageContent(page, window) } func setViewMyIdentityHashesPage(window fyne.Window, myIdentityType string, previousPage func()){ currentPage := func(){setViewMyIdentityHashesPage(window, myIdentityType, previousPage)} title := getPageTitleCentered(translate("My Identity Hashes")) backButton := getBackButtonCentered(previousPage) description1 := getLabelCentered("All users of Seekia have a unique identity hash.") description2 := getLabelCentered("Share your identity hash to advertise your identity.") identityTypeIcon, err := getIdentityTypeIcon(myIdentityType, -10) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } getIdentityTypeLabelOrChangeButton := func()(fyne.Widget, error){ getHostModeEnabledBool := func()(bool, error){ exists, hostModeOnOffStatus, err := mySettings.GetSetting("HostModeOnOffStatus") if (err != nil) { return false, err } if (exists == true && hostModeOnOffStatus == "On"){ return true, nil } return false, nil } getModeratorModeEnabledBool := func()(bool, error){ exists, moderatorModeOnOffStatus, err := mySettings.GetSetting("ModeratorModeOnOffStatus") if (err != nil) { return false, err } if (exists == true && moderatorModeOnOffStatus == "On"){ return true, nil } return false, nil } hostModeEnabled, err := getHostModeEnabledBool() if (err != nil) { return nil, err } moderatorModeEnabled, err := getModeratorModeEnabledBool() if (err != nil) { return nil, err } hostIdentityExists, _, err := mySeedPhrases.GetMySeedPhrase("Host") if (err != nil) { return nil, err } moderatorIdentityExists, _, err := mySeedPhrases.GetMySeedPhrase("Moderator") if (err != nil) { return nil, err } if (hostModeEnabled == false && hostIdentityExists == false && moderatorModeEnabled == false && moderatorIdentityExists == false){ // Mate identity is the only identity to show mateLabel := getBoldLabel("Mate") return mateLabel, nil } getNextIdentityType := func()string{ if (myIdentityType == "Mate"){ if (hostModeEnabled == true || hostIdentityExists == true){ return "Host" } return "Moderator" } if (myIdentityType == "Host"){ if (moderatorModeEnabled == true || moderatorIdentityExists == true){ return "Moderator" } return "Mate" } return "Mate" } nextIdentityType := getNextIdentityType() changeIdentityTypeButton := widget.NewButton(myIdentityType, func(){ setViewMyIdentityHashesPage(window, nextIdentityType, previousPage) }) return changeIdentityTypeButton, nil } identityTypeLabelOrChangeButton, err := getIdentityTypeLabelOrChangeButton() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } identityTypeLabelOrChangeButtonWithIcon := getContainerCentered(container.NewGridWithColumns(1, identityTypeIcon, identityTypeLabelOrChangeButton)) currentIdentityHashExists, currentIdentityHash, err := myIdentity.GetMyIdentityHash(myIdentityType) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (currentIdentityHashExists == false) { identityDoesNotExistLabel := getBoldLabelCentered("Your " + myIdentityType + " identity does not exist.") createIdentityButton := getWidgetCentered(widget.NewButtonWithIcon("Create Identity", theme.NavigateNextIcon(), func(){ setChooseNewIdentityHashPage(window, myIdentityType, currentPage, currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, widget.NewSeparator(), identityTypeLabelOrChangeButtonWithIcon, identityDoesNotExistLabel, createIdentityButton, widget.NewSeparator()) setPageContent(page, window) return } currentIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(currentIdentityHash) if (err != nil){ currentIdentityHashHex := encoding.EncodeBytesToHexString(currentIdentityHash[:]) setErrorEncounteredPage(window, errors.New("GetMyIdentityHash returning invalid myIdentityHash: " + currentIdentityHashHex), previousPage) return } identityHashLabel := widget.NewMultiLineEntry() identityHashLabel.SetText(currentIdentityHashString) identityHashLabel.OnChanged = func(_ string){ identityHashLabel.SetText(currentIdentityHashString) } identityHashLabelBoxed := getWidgetBoxed(identityHashLabel) boxWidener := widget.NewLabel(" ") identityHashBoxWidened := getContainerCentered(container.NewGridWithColumns(1, identityHashLabelBoxed, boxWidener)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, widget.NewSeparator(), identityTypeLabelOrChangeButtonWithIcon, identityHashBoxWidened) setPageContent(page, window) } func setDeleteMyIdentityPage(window fyne.Window, myIdentityType string, previousPage func()){ currentPage := func(){setDeleteMyIdentityPage(window, myIdentityType, previousPage)} if (myIdentityType != "Mate" && myIdentityType != "Host" && myIdentityType != "Moderator"){ setErrorEncounteredPage(window, errors.New("setDeleteMyIdentityPage called with invalid identity type: " + myIdentityType), previousPage) return } title := getPageTitleCentered(translate("Delete Identity")) backButton := getBackButtonCentered(previousPage) getDescriptionSection := func()*fyne.Container{ description1 := getLabelCentered("This page allows you to delete your " + myIdentityType + " identity.") if (myIdentityType == "Host"){ description2 := getLabelCentered("You will lose your host identity balance and account credit.") description3 := getLabelCentered("Write down your seed phrase to keep access to your identity and credit.") description4 := getLabelCentered("Disable your profile before doing this.") descriptionSection := container.NewVBox(description1, description2, description3, description4) return descriptionSection } if (myIdentityType == "Moderator"){ description2 := getLabelCentered("You will lose your identity score and account credit.") description3 := getLabelCentered("This will also delete your message history.") description4 := getLabelCentered("Write down your seed phrase to keep access to your identity and credit.") descriptionSection := container.NewVBox(description1, description2, description3, description4) return descriptionSection } description2 := getLabelCentered("This will delete your messages, identity balance, and account credit.") description3 := getLabelCentered("Write down your seed phrase to keep access to your identity and credit.") description4 := getLabelCentered("Disable your profile before doing this.") descriptionSection := container.NewVBox(description1, description2, description3, description4) return descriptionSection } descriptionSection := getDescriptionSection() getIdentityTypeLabelOrChangeButton := func()(fyne.Widget, error){ hostIdentityExists, _, err := mySeedPhrases.GetMySeedPhrase("Host") if (err != nil) { return nil, err } moderatorIdentityExists, _, err := mySeedPhrases.GetMySeedPhrase("Moderator") if (err != nil) { return nil, err } if (myIdentityType == "Mate" && hostIdentityExists == false && moderatorIdentityExists == false){ mateLabel := getBoldLabel("Mate") return mateLabel, nil } getNextIdentityType := func()string{ if (myIdentityType == "Mate"){ if (hostIdentityExists == true){ return "Host" } return "Moderator" } if (myIdentityType == "Host"){ if (moderatorIdentityExists == true){ return "Moderator" } } return "Mate" } nextIdentityType := getNextIdentityType() changeIdentityTypeButton := widget.NewButton(myIdentityType, func(){ setDeleteMyIdentityPage(window, nextIdentityType, previousPage) }) return changeIdentityTypeButton, nil } identityTypeLabelOrChangeButton, err := getIdentityTypeLabelOrChangeButton() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } identityTypeIcon, err := getIdentityTypeIcon(myIdentityType, -10) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } identityTypeLabelOrChangeButtonWithIcon := getContainerCentered(container.NewGridWithColumns(1, identityTypeIcon, identityTypeLabelOrChangeButton)) identityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(myIdentityType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (identityExists == false){ // This is only reached if the user has just deleted this identity description4 := getBoldLabelCentered("Your " + myIdentityType + " identity does not exist.") description5 := getLabelCentered("There is no identity to delete.") page := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionSection, widget.NewSeparator(), identityTypeLabelOrChangeButtonWithIcon, description4, description5, widget.NewSeparator()) setPageContent(page, window) return } myIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(myIdentityHash) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } identityHashTrimmed, _, err := helpers.TrimAndFlattenString(myIdentityHashString, 10) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } currentIdentityHashText := widget.NewLabel("Current Identity:") identityHashLabel := getBoldLabel(identityHashTrimmed) myIdentityHashRow := getContainerCentered(container.NewHBox(currentIdentityHashText, identityHashLabel)) deleteIdentityButton := getWidgetCentered(widget.NewButtonWithIcon("Delete Identity", theme.DeleteIcon(), func(){ setConfirmDeleteMyIdentityPage(window, myIdentityType, currentPage, currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionSection, widget.NewSeparator(), identityTypeLabelOrChangeButtonWithIcon, myIdentityHashRow, deleteIdentityButton, widget.NewSeparator()) setPageContent(page, window) } func setConfirmDeleteMyIdentityPage(window fyne.Window, myIdentityType string, previousPage func(), nextPage func()){ title := getPageTitleCentered("Confirm Delete " + myIdentityType + " Identity") backButton := getBackButtonCentered(previousPage) identityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(myIdentityType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (identityExists == false){ setErrorEncounteredPage(window, errors.New("setConfirmDeleteMyIdentityPage called with missing identity."), previousPage) return } subtitle := getPageSubtitleCentered("Delete your " + myIdentityType + " identity?") page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator()) if (myIdentityType == "Mate"){ description2 := getBoldLabelCentered(translate("You will lose access to your identity balance and account credit.")) description3 := getBoldLabelCentered(translate("This will delete your chat messages and decryption keys.")) description4 := getLabelCentered(translate("Write down your seed phrase to retain your identity and account credit.")) description5 := getLabelCentered(translate("Export your data to save your chat messages and decryption keys.")) page.Add(description2) page.Add(description3) page.Add(description4) page.Add(description5) } else if (myIdentityType == "Host"){ description2 := getBoldLabelCentered(translate("You will lose access to your identity balance and account credit.")) description3 := getLabelCentered(translate("Write down your seed phrase to retain your identity and account credit.")) page.Add(description2) page.Add(description3) } else if (myIdentityType == "Moderator"){ description2 := getBoldLabelCentered(translate("You will lose access to your identity score and account credit.")) description3 := getBoldLabelCentered(translate("This will delete your chat messages and decryption keys.")) description4 := getLabelCentered(translate("Write down your seed phrase to retain your identity and account credit.")) description5 := getLabelCentered(translate("Export your data to save your chat messages and decryption keys.")) page.Add(description2) page.Add(description3) page.Add(description4) page.Add(description5) } description5 := getLabelCentered("This will not delete your local profile.") page.Add(description5) page.Add(widget.NewSeparator()) if (myIdentityType != "Host"){ //Outputs: // -int: Number of messages to delete // -int: Number of conversations to delete getMessagesToDeleteInfo := func(networkType byte)(int, int, error){ updateProgressFunction := func(_ int)error{ return nil } myChatMessagesMapList, err := myChatMessages.GetUpdatedMyChatMessagesMapList(myIdentityType, networkType, updateProgressFunction) if (err != nil) { return 0, 0, err } numberOfMessagesToDelete := len(myChatMessagesMapList) //Map Structure: Recipient Identity Hash -> Nothing conversationRecipientsMap := make(map[string]struct{}) for _, messageMap := range myChatMessagesMapList{ theirIdentityHash, exists := messageMap["TheirIdentityHash"] if (exists == false) { return 0, 0, errors.New("Malformed myChatMessages map list: Item missing TheirIdentityHash") } conversationRecipientsMap[theirIdentityHash] = struct{}{} } numberOfConversationsToDelete := len(conversationRecipientsMap) return numberOfMessagesToDelete, numberOfConversationsToDelete, nil } numberOfMessagesToDelete_Network1, numberOfConversationsToDelete_Network1, err := getMessagesToDeleteInfo(1) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } numberOfMessagesToDelete_Network2, numberOfConversationsToDelete_Network2, err := getMessagesToDeleteInfo(2) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } numberOfMessagesToDelete := numberOfMessagesToDelete_Network1 + numberOfMessagesToDelete_Network2 numberOfConversationsToDelete := numberOfConversationsToDelete_Network1 + numberOfConversationsToDelete_Network2 if (numberOfMessagesToDelete != 0){ thisWillDeleteLabel := widget.NewLabel("This will delete") numberOfMessagesString := helpers.ConvertIntToString(numberOfMessagesToDelete) numberOfMessagesLabel := getBoldLabel(numberOfMessagesString) getMessageOrMessagesText := func()string{ if (numberOfMessagesToDelete == 1){ return "message" } return "messages" } messageOrMessagesText := getMessageOrMessagesText() messagesAndLabel := widget.NewLabel(messageOrMessagesText + " and") numberOfConversationsString := helpers.ConvertIntToString(numberOfConversationsToDelete) numberOfConversationsLabel := getBoldLabel(numberOfConversationsString) getConversationOrConversationsText := func()string{ if (numberOfConversationsToDelete == 1){ return "conversation" } return "conversations" } conversationOrConversationsText := getConversationOrConversationsText() conversationsLabel := widget.NewLabel(conversationOrConversationsText) chatDeleteWarningRow := container.NewHBox(layout.NewSpacer(), thisWillDeleteLabel, numberOfMessagesLabel, messagesAndLabel, numberOfConversationsLabel, conversationsLabel, layout.NewSpacer()) page.Add(chatDeleteWarningRow) page.Add(widget.NewSeparator()) } } //TODO: Show account credit balance if (myIdentityType == "Mate"){ description6 := getLabelCentered("Disable your profile before doing this on the Profile - Broadcast page.") description7 := getLabelCentered("Otherwise, it will automatically expire from the network in 3 months.") page.Add(description6) page.Add(description7) page.Add(widget.NewSeparator()) } else if (myIdentityType == "Host"){ description6 := getLabelCentered("Disable your profile before doing this on the Profile - Broadcast page.") description7 := getLabelCentered("Otherwise, users will keep trying to connect to your inactive host address.") //TODO: Retrieve true expiration time from parameters description8 := getLabelCentered("This should stop after 6 hours, when your profile expires.") page.Add(description6) page.Add(description7) page.Add(description8) page.Add(widget.NewSeparator()) } deleteIdentityFunction := func()error{ err := myBroadcasts.DeleteMyBroadcastProfiles(myIdentityHash) if (err != nil) { return err } if (myIdentityType != "Host"){ err := myChatMessages.DeleteMyChatMessagesMapList(myIdentityType) if (err != nil) { return err } err = mySettings.SetSetting(myIdentityType + "ChatConversationsGeneratedStatus", "No") if (err != nil) { return err } //TODO: Delete secret inboxes //TODO: Delete saved message cipher keys //TODO: Delete everything else relevant } err = mySeedPhrases.DeleteMySeedPhrase(myIdentityType) if (err != nil) { return err } return nil } confirmDeleteIdentityButton := getWidgetCentered(widget.NewButtonWithIcon("Delete Identity", theme.DeleteIcon(), func(){ err := deleteIdentityFunction() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) } nextPage() })) page.Add(confirmDeleteIdentityButton) setPageContent(page, window) } func setImportMyIdentityPage(window fyne.Window, myIdentityType string, previousPage func()){ if (myIdentityType != "Mate" && myIdentityType != "Host" && myIdentityType != "Moderator"){ setErrorEncounteredPage(window, errors.New("setImportMyIdentityPage called with invalid identity type: " + myIdentityType), previousPage) return } currentPage := func(){setImportMyIdentityPage(window, myIdentityType, previousPage)} title := getPageTitleCentered(translate("Import Identity")) backButton := getBackButtonCentered(previousPage) description := getLabelCentered("Import your " + myIdentityType + " identity from a seed phrase.") getNextIdentityType := func()string{ if (myIdentityType == "Mate"){ return "Host" } if (myIdentityType == "Host"){ return "Moderator" } return "Mate" } nextIdentityType := getNextIdentityType() identityTypeIcon, err := getIdentityTypeIcon(myIdentityType, -10) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } changeIdentityTypeButton := widget.NewButton(myIdentityType, func(){ setImportMyIdentityPage(window, nextIdentityType, previousPage) }) changeIdentityTypeButtonWithIcon := getContainerCentered(container.NewGridWithColumns(1, identityTypeIcon, changeIdentityTypeButton)) identityExists, _, err := mySeedPhrases.GetMySeedPhrase(myIdentityType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (identityExists == true){ description2 := getBoldLabelCentered("Your " + myIdentityType + " identity exists.") description3 := getLabelCentered("You must delete it before restoring a new identity.") description4 := getLabelCentered("Delete your identity on the Settings - My Data - Delete Identity page") page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), changeIdentityTypeButtonWithIcon, description2, description3, description4) setPageContent(page, window) return } seedPhraseEntry := widget.NewMultiLineEntry() seedPhraseEntry.Wrapping = 3 seedPhraseEntry.SetPlaceHolder(translate("Enter seed phrase to import...")) seedPhraseEntryBoxed := getWidgetBoxed(seedPhraseEntry) submitSeedButton := getWidgetCentered(widget.NewButtonWithIcon(translate("Import " + myIdentityType + " Identity"), theme.UploadIcon(), func(){ newSeedPhrase := seedPhraseEntry.Text seedPhraseIsValid := seedPhrase.VerifySeedPhrase(newSeedPhrase) if (seedPhraseIsValid == false){ dialogTitle := translate("Invalid Seed Phrase") dialogMessageA := getLabelCentered("Your seed phrase is invalid.") dialogMessageB := getLabelCentered("A Seekia seed phrase is 15 words long.") dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } err = mySeedPhrases.SetMySeedPhrase(myIdentityType, newSeedPhrase) if (err != nil) { setErrorEncounteredPage(window, err, currentPage) return } setViewMySeedPhrasesPage(window, myIdentityType, previousPage) })) widener := widget.NewLabel(" ") heightener := widget.NewLabel("") submitSeedButtonWithWidener := container.NewVBox(submitSeedButton, widener, heightener) entryWithSubmitSeedButton := getContainerCentered(container.NewGridWithColumns(1, seedPhraseEntryBoxed, submitSeedButtonWithWidener)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), changeIdentityTypeButtonWithIcon, entryWithSubmitSeedButton) setPageContent(page, window) } func setChooseAppThemePage(window fyne.Window, previousPage func()){ currentPage := func(){setChooseAppThemePage(window, previousPage)} title := getPageTitleCentered(translate("App Themes")) backButton := getBackButtonCentered(previousPage) description := getLabelCentered("Choose your Seekia app theme.") getThemeButtonsGrid := func()(*fyne.Container, error){ getCurrentAppTheme := func()(string, error){ exists, currentTheme, err := globalSettings.GetSetting("AppTheme") if (err != nil){ return "", err } if (exists == false){ return "Light", nil } return currentTheme, nil } currentAppTheme, err := getCurrentAppTheme() if (err != nil){ return nil, err } getThemeButtonWithIcon := func(themeName string, themeIconName string)(*fyne.Container, error){ themeIcon, err := getFyneImageIcon(themeIconName) if (err != nil) { return nil, err } chooseThemeButton := widget.NewButton(translate(themeName), func(){ _ = globalSettings.SetSetting("AppTheme", themeName) customTheme, _ := getCustomFyneTheme(themeName) app := fyne.CurrentApp() app.Settings().SetTheme(customTheme) currentPage() }) if (themeName == currentAppTheme){ chooseThemeButton.Importance = widget.HighImportance } buttonWithIcon := getContainerCentered(container.NewGridWithColumns(1, themeIcon, chooseThemeButton)) return buttonWithIcon, nil } lightThemeButton, err := getThemeButtonWithIcon("Light", "Sun") if (err != nil) { return nil, err } darkThemeButton, err := getThemeButtonWithIcon("Dark", "Moon") if (err != nil) { return nil, err } loveThemeButton, err := getThemeButtonWithIcon("Love", "Mate") if (err != nil) { return nil, err } oceanThemeButton, err := getThemeButtonWithIcon("Ocean", "Ocean") if (err != nil) { return nil, err } buttonsGrid := container.NewGridWithColumns(2, lightThemeButton, darkThemeButton, loveThemeButton, oceanThemeButton) return buttonsGrid, nil } themeButtonsGrid, err := getThemeButtonsGrid() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } themeButtonsGridCentered := getContainerCentered(themeButtonsGrid) //TODO: Add a way to create a custom theme within the GUI page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), themeButtonsGridCentered) setPageContent(page, window) } func setNavigationSettingsPage(window fyne.Window, previousPage func()){ currentPage := func(){setNavigationSettingsPage(window, previousPage)} title := getPageTitleCentered(translate("Navigation Settings")) backButton := getBackButtonCentered(previousPage) description := getLabelCentered("Manage your navigation settings.") showButtonTextColumn := container.NewVBox() showButtonChecksColumn := container.NewVBox() addShowButtonCheckRow := func(showButtonText string, settingName string)error{ showButtonTextLabel := getBoldLabelCentered(showButtonText) showButtonCheck := widget.NewCheck("", func(selected bool){ yesOrNoString := helpers.ConvertBoolToYesOrNoString(selected) err := mySettings.SetSetting(settingName, yesOrNoString) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) exists, showButtonCurrent, err := mySettings.GetSetting(settingName) if (err != nil) { return err } if (exists == true && showButtonCurrent == "Yes"){ showButtonCheck.Checked = true } showButtonTextColumn.Add(showButtonTextLabel) showButtonChecksColumn.Add(showButtonCheck) return nil } err := addShowButtonCheckRow("Show Host Button", "ShowHostButtonNavigation") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } showButtonTextColumn.Add(widget.NewSeparator()) showButtonChecksColumn.Add(widget.NewSeparator()) err = addShowButtonCheckRow("Show Moderate Button", "ShowModerateButtonNavigation") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } showButtonChecksGrid := container.NewHBox(layout.NewSpacer(), showButtonTextColumn, showButtonChecksColumn, layout.NewSpacer()) navigationLocationLabel := getLabelCentered("Navigation Bar Location:") getMyCurrentNavigationBarLocation := func()(string, error){ exists, currentNavigationLocation, err := mySettings.GetSetting("NavigationBarLocation") if (err != nil){ return "", err } if (exists == false){ return "Top", nil } if (currentNavigationLocation != "Top" && currentNavigationLocation != "Bottom" && currentNavigationLocation != "Left" && currentNavigationLocation != "Right"){ return "", errors.New("Invalid navigation bar location: " + currentNavigationLocation) } return currentNavigationLocation, nil } currentNavigationLocation, err := getMyCurrentNavigationBarLocation() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } option1Translated := translate("Top") option2Translated := translate("Bottom") option3Translated := translate("Left") option4Translated := translate("Right") untranslatedOptionsMap := map[string]string{ option1Translated: "Top", option2Translated: "Bottom", option3Translated: "Left", option4Translated: "Right", } locationOptionsList := []string{option1Translated, option2Translated, option3Translated, option4Translated} locationSelector := widget.NewSelect(locationOptionsList, func(response string){ responseUntranslated, exists := untranslatedOptionsMap[response] if (exists == false){ setErrorEncounteredPage(window, errors.New("untranslatedOptionsMap missing response: " + response), currentPage) return } if (responseUntranslated == currentNavigationLocation){ return } err = mySettings.SetSetting("NavigationBarLocation", responseUntranslated) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) locationSelector.Selected = currentNavigationLocation locationSelectorCentered := getWidgetCentered(locationSelector) page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), showButtonChecksGrid, widget.NewSeparator(), navigationLocationLabel, locationSelectorCentered) setPageContent(page, window) } func setContentFilteringSettingsPage(window fyne.Window, previousPage func()){ currentPage := func(){setContentFilteringSettingsPage(window, previousPage)} title := getPageTitleCentered(translate("Content Filtering Settings")) backButton := getBackButtonCentered(previousPage) description := getLabelCentered("Manage your content filtering settings.") pixelateImagesButton := getWidgetCentered(widget.NewButton("Pixelate Images", func(){ setManagePixelateImagesSettingPage(window, currentPage) })) // TODO: Add ability to mute/block/censor words? page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), pixelateImagesButton) setPageContent(page, window) } func setManagePixelateImagesSettingPage(window fyne.Window, previousPage func()){ currentPage := func(){setManagePixelateImagesSettingPage(window, previousPage)} title := getPageTitleCentered("Pixelate Images Setting") backButton := getBackButtonCentered(previousPage) description1 := getLabelCentered("Seekia can pixelate the images you receive in messages.") description2 := getLabelCentered("You must slowly reveal the image by incrementally depixelating the image.") description3 := getLabelCentered("If you are not afraid of seeing unreviewed images, you can disable this feature.") getCurrentStatus := func()(string, error){ exists, currentSetting, err := mySettings.GetSetting("PixelateImagesOnOffStatus") if (err != nil) { return "", err } if (exists == false){ return "On", nil } return currentSetting, nil } currentStatus, err := getCurrentStatus() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } currentStatusTitle := widget.NewLabel("Current Status: ") currentStatusLabel := getBoldLabel(currentStatus) currentStatusRow := container.NewHBox(layout.NewSpacer(), currentStatusTitle, currentStatusLabel, layout.NewSpacer()) getEnableOrDisableButton := func()fyne.Widget{ if (currentStatus == "On"){ disableButton := widget.NewButton("Disable", func(){ err := mySettings.SetSetting("PixelateImagesOnOffStatus", "Off") if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) return disableButton } enableButton := widget.NewButton("Enable", func(){ err := mySettings.SetSetting("PixelateImagesOnOffStatus", "On") if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) return enableButton } enableOrDisableButton := getEnableOrDisableButton() enableOrDisableButtonCentered := getWidgetCentered(enableOrDisableButton) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), currentStatusRow, enableOrDisableButtonCentered) setPageContent(page, window) } func setManageNetworkSettingsPage(window fyne.Window, previousPage func()){ appMemory.SetMemoryEntry("CurrentViewedPage", "ManageNetworkConnection") title := getPageTitleCentered("Manage Network Connection") //TODO // Enable download profiles over clearnet mode // This should not be possible for messages, only profiles // Disable Tor mode (if using system-wide tor proxy already, such as Whonix. We can autodetect if user is using Whonix) // Add HiddenServiceOnly mode so users can only download messages over .onion rather than using exit nodes to access clearnet hosts? backButton := getBackButtonCentered(previousPage) description := getBoldLabelCentered("Under Construction") page := container.NewVBox(title, backButton, widget.NewSeparator(), description) setPageContent(page, window) } func setManageStoragePage(window fyne.Window, previousPage func()){ setLoadingScreen(window, "Manage Storage", "Loading Manage Storage Page...") currentPage := func(){setManageStoragePage(window, previousPage)} title := getPageTitleCentered(translate("Manage Storage")) backButton := getBackButtonCentered(previousPage) getAllowedStorageGigabytes := func()(float64, error){ exists, allowedStorageSpaceString, err := globalSettings.GetSetting("AllowedStorageSpace") if (err != nil){ return 0, err } if (exists == false){ //We default to 10 gigabytes return 10, nil } allowedStorageSpaceFloat, err := helpers.ConvertStringToFloat64(allowedStorageSpaceString) if (err != nil){ return 0, errors.New("MySettings contains invalid AllowedStorageSpace: " + allowedStorageSpaceString) } return allowedStorageSpaceFloat, nil } allowedStorageSpace, err := getAllowedStorageGigabytes() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } databaseDirectory, err := localFilesystem.GetAppDatabaseFolderPath() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } databaseSizeBytes, err := localFilesystem.GetFolderSizeInBytes(databaseDirectory) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } // TODO: Change to use actual gigabyte size databaseSizeGigabytes := float64(databaseSizeBytes)/1000000000 allowedStorageSpaceString := helpers.ConvertFloat64ToStringRounded(allowedStorageSpace, 2) databaseSizeString := helpers.ConvertFloat64ToStringRounded(databaseSizeGigabytes, 1) percentageOfSpaceUsed := (databaseSizeGigabytes/allowedStorageSpace)*100 percentageOfSpaceUsedString := helpers.ConvertFloat64ToStringRounded(percentageOfSpaceUsed, 0) spaceUsedTitle := getLabelCentered("Storage Space Used:") spaceUsedLabel := getBoldLabelCentered(databaseSizeString + "/" + allowedStorageSpaceString + " gigabytes" + " (" + percentageOfSpaceUsedString + "%)") numberOfStoredProfiles, err := profileStorage.GetNumberOfStoredProfiles() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } numberOfStoredProfilesString := helpers.ConvertInt64ToString(numberOfStoredProfiles) numberOfStoredProfilesLabel := widget.NewLabel("Stored Profiles:") numberOfStoredProfilesText := getBoldLabel(numberOfStoredProfilesString) numberOfStoredProfilesRow := container.NewHBox(layout.NewSpacer(), numberOfStoredProfilesLabel, numberOfStoredProfilesText, layout.NewSpacer()) numberOfStoredMessages, err := chatMessageStorage.GetNumberOfStoredMessages() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } numberOfStoredMessagesString := helpers.ConvertInt64ToString(numberOfStoredMessages) numberOfStoredMessagesLabel := widget.NewLabel("Stored Messages:") numberOfStoredMessagesText := getBoldLabel(numberOfStoredMessagesString) numberOfStoredMessagesRow := container.NewHBox(layout.NewSpacer(), numberOfStoredMessagesLabel, numberOfStoredMessagesText, layout.NewSpacer()) numberOfStoredReviews, err := reviewStorage.GetNumberOfStoredReviews() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } numberOfStoredReviewsString := helpers.ConvertInt64ToString(numberOfStoredReviews) numberOfStoredReviewsLabel := widget.NewLabel("Stored Reviews:") numberOfStoredReviewsText := getBoldLabel(numberOfStoredReviewsString) numberOfStoredReviewsRow := container.NewHBox(layout.NewSpacer(), numberOfStoredReviewsLabel, numberOfStoredReviewsText, layout.NewSpacer()) numberOfStoredReports, err := reportStorage.GetNumberOfStoredReports() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } numberOfStoredReportsString := helpers.ConvertInt64ToString(numberOfStoredReports) numberOfStoredReportsLabel := widget.NewLabel("Stored Reports:") numberOfStoredReportsText := getBoldLabel(numberOfStoredReportsString) numberOfStoredReportsRow := container.NewHBox(layout.NewSpacer(), numberOfStoredReportsLabel, numberOfStoredReportsText, layout.NewSpacer()) freeUpSpaceButton := widget.NewButton("Free Up Space", func(){ //TODO: A page to manually prune data // This should also be happening automatically by backgroundJobs showUnderConstructionDialog(window) }) allowedSpaceButton := widget.NewButton("Manage Allowed Space", func(){ setManageAllowedStorageSpacePage(window, currentPage) }) changeDownloadsDirectoryButton := widget.NewButton("Change Database Location", func(){ setManageDatabaseLocationPage(window, currentPage) }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, freeUpSpaceButton, allowedSpaceButton, changeDownloadsDirectoryButton)) page := container.NewVBox(title, backButton, widget.NewSeparator(), spaceUsedTitle, spaceUsedLabel, widget.NewSeparator(), numberOfStoredProfilesRow, numberOfStoredMessagesRow, numberOfStoredReviewsRow, numberOfStoredReportsRow, widget.NewSeparator(), buttonsGrid) setPageContent(page, window) } func setManageAllowedStorageSpacePage(window fyne.Window, previousPage func()){ currentPage := func(){setManageAllowedStorageSpacePage(window, previousPage)} title := getPageTitleCentered(translate("Manage Allowed Storage Space")) backButton := getBackButtonCentered(previousPage) description := getLabelCentered("Select the amount of storage space Seekia is allowed to use.") getCurrentAllowedSpace := func()(float64, error){ exists, currentAllowedStorageSpace, err := globalSettings.GetSetting("AllowedStorageSpace") if (err != nil){ return 0, err } if (exists == false){ // Default to 10 GB return 10, nil } currentAllowedSpaceFloat64, err := helpers.ConvertStringToFloat64(currentAllowedStorageSpace) if (err != nil) { return 0, errors.New("MyGlobalSettings Malformed: Contains invalid AllowedStorageSpace: " + currentAllowedStorageSpace) } return currentAllowedSpaceFloat64, nil } currentAllowedSpaceFloat64, err := getCurrentAllowedSpace() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } currentAllowedSpaceString := helpers.ConvertFloat64ToStringRounded(currentAllowedSpaceFloat64, 2) allowedStorageSpaceLabel := getLabelCentered("Allowed storage space:") allowedStorageSpaceText := getBoldLabelCentered(currentAllowedSpaceString + " gigabytes") allowedStorageSpaceGigabytesText := getLabelCentered("Enter a new allowed amount in gigabytes:") allowedStorageSpaceEntry := widget.NewEntry() allowedStorageSpaceEntry.Text = currentAllowedSpaceString allowedStorageSpaceSubmitButton := widget.NewButtonWithIcon("Submit", theme.ConfirmIcon(), func(){ newSize := allowedStorageSpaceEntry.Text if (newSize == ""){ dialogTitle := translate("Invalid Size.") dialogMessage := getLabelCentered(translate("Your must enter a new allowed amount.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } newSizeFloat64, err := helpers.ConvertStringToFloat64(newSize) if (err != nil){ dialogTitle := translate("Invalid Size.") dialogMessage := getLabelCentered(translate("Your new maximum size must be a number.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } if (newSizeFloat64 < 3){ dialogTitle := translate("Invalid Size.") dialogMessage := getLabelCentered(translate("Your new maximum size must be at least 3 GB.")) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } err = globalSettings.SetSetting("AllowedStorageSpace", newSize) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) allowedStorageSpaceEntryRow := getContainerCentered(container.NewGridWithRows(1, allowedStorageSpaceEntry, allowedStorageSpaceSubmitButton)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), allowedStorageSpaceLabel, allowedStorageSpaceText, widget.NewSeparator(), allowedStorageSpaceGigabytesText, allowedStorageSpaceEntryRow) setPageContent(page, window) } func setManageDatabaseLocationPage(window fyne.Window, previousPage func()){ currentPage := func(){setManageDatabaseLocationPage(window, previousPage)} title := getPageTitleCentered(translate("Manage Database Location")) backButton := getBackButtonCentered(previousPage) description1 := getLabelCentered("Use this page to change the location of the Seekia database.") description2 := getLabelCentered("After changing the location, close Seekia.") description3 := getLabelCentered("Then, you must manually move the existing database folder contents to the new location.") description4 := getLabelCentered("Upon starting Seekia again, all existing data should be available.") description5 := getLabelCentered("If you don't copy the folder, you will still retain your user data.") databaseDirectory, err := localFilesystem.GetAppDatabaseFolderPath() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } currentLocationLabel := getLabelCentered("Current Database Directory:") currentLocationText := getBoldLabelCentered(databaseDirectory) resetLocation := widget.NewButtonWithIcon("Reset To Original", theme.ContentUndoIcon(), func(){ confirmDialogCallbackFunction := func(response bool){ if (response == false){ return } err := globalSettings.DeleteSetting("DatabaseFolderpath") if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() } dialogTitle := translate("Confirm Reset Database Location?") dialogMessageA := getLabelCentered("Confirm to reset the database location?") dialogMessageB := getLabelCentered("You must restart Seekia for the change to take effect.") dialogMessageC := getLabelCentered("Move your existing database to the new location before starting Seekia again.") dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC) dialog.ShowCustomConfirm(dialogTitle, translate("Yes"), translate("No"), dialogContent, confirmDialogCallbackFunction, window) }) selectNewLocationButton := widget.NewButtonWithIcon("Select New Location", theme.FolderIcon(), func(){ folderOpenCallbackFunction := func(folderObject fyne.ListableURI, err error){ if (err != nil) { title := translate("Failed To Open Folder Path.") dialogMessage := getLabelCentered(translate("Report this error to Seekia developers: " + err.Error())) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(title, translate("Close"), dialogContent, window) return } if (folderObject == nil){ return } folderPath := folderObject.Path() err = globalSettings.SetSetting("DatabaseFolderpath", folderPath) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() } dialog.ShowFolderOpen(folderOpenCallbackFunction, window) }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, resetLocation, selectNewLocationButton)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, description5, widget.NewSeparator(), currentLocationLabel, currentLocationText, widget.NewSeparator(), buttonsGrid) setPageContent(page, window) } /* func setSyncSettingsPage(window fyne.Window, previousPage func()){ title := getPageTitleCentered(translate("Sync Settings")) backButton := getBackButtonCentered(previousPage) underConstructionLabel := getBoldLabelCentered("Under construction.") //TODO page := container.NewVBox(title, backButton, widget.NewSeparator(), underConstructionLabel) setPageContent(page, window) } */ func setChangeAppCurrencyPage(window fyne.Window, previousPage func()){ getCurrentCurrencyFunction := func()(string, error){ exists, currentAppCurrency, err := globalSettings.GetSetting("Currency") if (err != nil) { return "", err } if (exists == false){ return "USD", nil } return currentAppCurrency, nil } onSelectFunction := func(newCurrencyCode string)error{ err := globalSettings.SetSetting("Currency", newCurrencyCode) if (err != nil){ return err } return nil } setChooseCurrencyPage(window, getCurrentCurrencyFunction, onSelectFunction, previousPage) } func setChooseCurrencyPage(window fyne.Window, getCurrentCurrencyFunction func()(string, error), onSelectFunction func(string)error, previousPage func()){ currentPage := func(){setChooseCurrencyPage(window, getCurrentCurrencyFunction, onSelectFunction, previousPage)} title := getPageTitleCentered(translate("Choose Currency")) backButton := getBackButtonCentered(previousPage) currentCurrencyCode, err := getCurrentCurrencyFunction() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } currentCurrencyName, currentCurrencySymbol, err := currencies.GetCurrencyInfoFromCurrencyCode(currentCurrencyCode) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } currentCurrencyLabel := getLabelCentered("Current Currency:") currentCurrencyText := getBoldLabelCentered(currentCurrencySymbol + " - " + currentCurrencyName + " - " + currentCurrencyCode) chooseCurrencyLabel := getItalicLabelCentered("Choose Currency:") allCurrencyObjectsList, err := currencies.GetCurrencyObjectsList() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } allCurrencyDescriptionsList := make([]string, 0, len(allCurrencyObjectsList)) allCurrencyCodesList := make([]string, 0, len(allCurrencyObjectsList)) for _, currencyObject := range allCurrencyObjectsList{ currencyName := currencyObject.Name currencySymbol := currencyObject.Symbol currencyCode := currencyObject.Code currencyNameTranslated := translate(currencyName) currencyDescription := currencySymbol + " - " + currencyNameTranslated + " - " + currencyCode allCurrencyDescriptionsList = append(allCurrencyDescriptionsList, currencyDescription) allCurrencyCodesList = append(allCurrencyCodesList, currencyCode) } onSelectedFunction := func(currencyIndex int) { selectedCurrencyCode := allCurrencyCodesList[currencyIndex] err := onSelectFunction(selectedCurrencyCode) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() } widgetList, err := getFyneWidgetListFromStringList(allCurrencyDescriptionsList, onSelectedFunction) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } header := container.NewVBox(title, backButton, widget.NewSeparator(), currentCurrencyLabel, currentCurrencyText, widget.NewSeparator(), chooseCurrencyLabel, widget.NewSeparator()) page := container.NewBorder(header, nil, nil, nil, widgetList) setPageContent(page, window) } func setViewLogsPage(window fyne.Window, logType string, previousPage func()){ if (logType != "General" && logType != "Network" && logType != "BackgroundJobs"){ setErrorEncounteredPage(window, errors.New("setViewLogsPage called with invalid logType: " + logType), previousPage) return } currentPage := func(){setViewLogsPage(window, logType, previousPage)} title := getPageTitleCentered("Logs") backButton := getBackButtonCentered(previousPage) description := getLabelCentered("View Seekia logs.") logTypeLabel := getBoldLabelCentered("Log Type:") logTypesList := []string{translate("General"), translate("Network"), translate("Background Jobs")} logTypeSelector := widget.NewSelect(logTypesList, func(selection string){ if (selection == translate("General")){ setViewLogsPage(window, "General", previousPage) } else if (selection == translate("Network")){ setViewLogsPage(window, "Network", previousPage) } else if (selection == translate("Background Jobs")){ setViewLogsPage(window, "BackgroundJobs", previousPage) } }) if (logType == "General"){ logTypeSelector.Selected = translate("General") } else if (logType == "Network"){ logTypeSelector.Selected = translate("Network") } else if (logType == "BackgroundJobs"){ logTypeSelector.Selected = translate("Background Jobs") } logTypeSelectorCentered := getWidgetCentered(logTypeSelector) logList, err := logger.GetLogList(logType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (len(logList) == 0){ noLogsExistLabel := getBoldLabelCentered("No log entries exist.") refreshButton := getWidgetCentered(widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), currentPage)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), logTypeLabel, logTypeSelectorCentered, widget.NewSeparator(), noLogsExistLabel, refreshButton) setPageContent(page, window) return } trimmedLogList := make([]string, 0, len(logList)) for _, logText := range logList{ trimmedText, _, err := helpers.TrimAndFlattenString(logText, 30) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } trimmedLogList = append(trimmedLogList, trimmedText) } onClickedFunction := func(selectedIndex int){ logText := logList[selectedIndex] setViewTextPage(window, "Viewing Log", logText, false, currentPage) } logsWidgetList, err := getFyneWidgetListFromStringList(logList, onClickedFunction) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } refreshButton := getWidgetCentered(widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), currentPage)) header := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), logTypeLabel, logTypeSelectorCentered, widget.NewSeparator()) page := container.NewBorder(header, refreshButton, nil, nil, logsWidgetList) setPageContent(page, window) } func setViewDeveloperSettingsPage(window fyne.Window, previousPage func()){ currentPage := func(){setViewDeveloperSettingsPage(window, previousPage)} title := getPageTitleCentered("Developer Settings") backButton := getBackButtonCentered(previousPage) networkTypeButton := getWidgetCentered(widget.NewButton("Network Type", func(){ setViewAppNetworkTypePage(window, currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), networkTypeButton) setPageContent(page, window) } func setViewAppNetworkTypePage(window fyne.Window, previousPage func()){ currentPage := func(){setViewAppNetworkTypePage(window, previousPage)} title := getPageTitleCentered("Network Type") backButton := getBackButtonCentered(previousPage) description1 := getLabelCentered("Your Seekia application can interact with different network types.") description2 := getLabelCentered("There are 2 network types: Mainnet and Testnet 1.") description3 := getLabelCentered("You can change your app network type to interact with a different network.") description4 := getLabelCentered("You should use Testnet 1 when testing new releases of Seekia before they are versioned.") currentNetworkType, err := getAppNetworkType.GetAppNetworkType() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } getCurrentNetworkTypeString := func()(string, error){ if (currentNetworkType == 1){ return "Mainnet", nil } if (currentNetworkType == 2){ return "Testnet 1", nil } currentNetworkTypeString := helpers.ConvertByteToString(currentNetworkType) return "", errors.New("GetAppNetworkType returning invalid network type: " + currentNetworkTypeString) } currentNetworkTypeString, err := getCurrentNetworkTypeString() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } currentNetworkTypeLabel := widget.NewLabel("Current Network Type:") currentNetworkTypeText := getBoldLabel(currentNetworkTypeString) currentNetworkTypeRow := container.NewHBox(layout.NewSpacer(), currentNetworkTypeLabel, currentNetworkTypeText, layout.NewSpacer()) changeNetworkTypeButton := getWidgetCentered(widget.NewButtonWithIcon("Change Network Type", theme.NavigateNextIcon(), func(){ setChooseNewAppNetworkTypePage(window, currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, widget.NewSeparator(), currentNetworkTypeRow, widget.NewSeparator(), changeNetworkTypeButton) setPageContent(page, window) } func setChooseNewAppNetworkTypePage(window fyne.Window, previousPage func()){ title := getPageTitleCentered("Choose Network Type") backButton := getBackButtonCentered(previousPage) description1 := getLabelCentered("Choose your new network type.") description2 := getLabelCentered("This change will impact your matches and chat conversations.") description3 := getLabelCentered("Your client will automatically delete content for other network types from the database.") description4 := getLabelCentered("Your broadcasted content and messages will not be deleted.") description5 := getLabelCentered("This change will take effect for all of your app users.") currentNetworkType, err := getAppNetworkType.GetAppNetworkType() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } chooseMainnetButton := widget.NewButton("Mainnet", func(){ if (currentNetworkType == 1){ dialogTitle := translate("Cannot Change Network Type") dialogMessageA := getLabelCentered("This network type is already your current app network type.") dialogContent := container.NewVBox(dialogMessageA) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } setChangeAppNetworkTypePage(window, 1, previousPage) }) if (currentNetworkType == 1){ chooseMainnetButton.Importance = widget.HighImportance } chooseTestnet1Button := widget.NewButton("Testnet 1", func(){ if (currentNetworkType == 2){ dialogTitle := translate("Cannot Change Network Type") dialogMessageA := getLabelCentered("This network type is already your current app network type.") dialogContent := container.NewVBox(dialogMessageA) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } setChangeAppNetworkTypePage(window, 2, previousPage) }) if (currentNetworkType == 2){ chooseTestnet1Button.Importance = widget.HighImportance } chooseNetworkTypesGrid := getContainerCentered(container.NewGridWithColumns(1, chooseMainnetButton, chooseTestnet1Button)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, description5, widget.NewSeparator(), chooseNetworkTypesGrid) setPageContent(page, window) } func setChangeAppNetworkTypePage(window fyne.Window, newAppNetworkType byte, nextPage func()){ isValid := helpers.VerifyNetworkType(newAppNetworkType) if (isValid == false){ newAppNetworkTypeString := helpers.ConvertByteToString(newAppNetworkType) setErrorEncounteredPage(window, errors.New("setChangeAppNetworkTypePage called with invalid newAppNetworkType: " + newAppNetworkTypeString), nextPage) return } title := getPageTitleCentered("Changing Network Type") description := getLabelCentered("Changing network type...") progressBar := getWidgetCentered(widget.NewProgressBarInfinite()) page := container.NewVBox(title, widget.NewSeparator(), description, progressBar) window.SetContent(page) err := setAppNetworkType.SetAppNetworkType(newAppNetworkType) if (err != nil){ setErrorEncounteredPage(window, err, nextPage) return } nextPage() }