seekia/gui/broadcastGui.go

1828 lines
65 KiB
Go
Raw Permalink Normal View History

package gui
//broadcastGui.go implements pages for a user to broadcast, disable, and enable their profiles, and to monitor manual broadcasts
//TODO: Add review replacement functionality
// When broadcasting a review, check if the existing conflicting review exists for the same reviewedhash
// If the review is reviewing an identity that the user has already reviewed and broadcast, we should show this in the GUI
// The user should confirm to overwrite their previous verdict
// Once they agree, the application should delete their old broadcasted review
// We should also add the ability to update existing identity reviews with more errant profiles, if more unruleful profiles are discovered
import "fyne.io/fyne/v2"
import "fyne.io/fyne/v2/canvas"
import "fyne.io/fyne/v2/container"
import "fyne.io/fyne/v2/data/binding"
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/genetics/myPeople"
import "seekia/internal/globalSettings"
import "seekia/internal/helpers"
import "seekia/internal/imagery"
import "seekia/internal/mateQuestionnaire"
import "seekia/internal/moderation/myIdentityScore"
import "seekia/internal/myIdentity"
import "seekia/internal/network/appNetworkType/getAppNetworkType"
import "seekia/internal/network/manualBroadcasts"
import "seekia/internal/network/myBroadcasts"
import "seekia/internal/network/myIdentityBalance"
import "seekia/internal/parameters/getParameters"
import "seekia/internal/profiles/attributeDisplay"
import "seekia/internal/profiles/myLocalProfiles"
import "seekia/internal/profiles/myProfileExports"
import "seekia/internal/profiles/myProfileStatus"
import "seekia/internal/profiles/profileFormat"
import "seekia/internal/profiles/readProfiles"
import "strings"
import "image"
import "time"
import "errors"
func setBroadcastPage(window fyne.Window, profileType string, previousPage func()){
setLoadingScreen(window, "Broadcast " + profileType + " Profile", "Loading Broadcast Page...")
currentPage := func(){setBroadcastPage(window, profileType, previousPage)}
appMemory.SetMemoryEntry("CurrentViewedPage", "Broadcast")
if (profileType != "Mate" && profileType != "Moderator"){
setErrorEncounteredPage(window, errors.New("setBroadcastPage called with invalid profile type: " + profileType), previousPage)
return
}
title := getPageTitleCentered("Broadcast " + profileType + " Profile")
backButton := getBackButtonCentered(previousPage)
myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(profileType)
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
if (myIdentityExists == false){
profileIcon, err := getFyneImageIcon("Profile")
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
iconSize := getCustomFyneSize(10)
profileIcon.SetMinSize(iconSize)
profileIconCentered := container.NewHBox(layout.NewSpacer(), profileIcon, layout.NewSpacer())
description1 := getBoldLabelCentered("Your " + profileType + " identity does not exist.")
description2 := getLabelCentered("You must create your user identity hash.")
description3 := getLabelCentered("This hash is how other users will identify you.")
createButton := getWidgetCentered(widget.NewButtonWithIcon("Create Identity", theme.NavigateNextIcon(), func(){
setChooseNewIdentityHashPage(window, profileType, currentPage, currentPage)
}))
page := container.NewVBox(title, backButton, widget.NewSeparator(), profileIconCentered, description1, description2, description3, createButton)
setPageContent(page, window)
return
}
description := getLabelCentered("Broadcast your " + profileType + " profile to the world.")
appNetworkType, err := getAppNetworkType.GetAppNetworkType()
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
identityExists, myProfileIsActiveStatus, err := myProfileStatus.GetMyProfileIsActiveStatus(myIdentityHash, appNetworkType)
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
if (identityExists == false) {
setErrorEncounteredPage(window, errors.New("My identity not found after being found already."), previousPage)
return
}
myProfileStatusLabel := getBoldLabelCentered("My Profile Status:")
getProfileStatusSection := func()(*fyne.Container, error){
if (myProfileIsActiveStatus == true){
activeIcon, err := getFyneImageIcon("ToggleOn")
if (err != nil) { return nil, err }
activeLabel := getBoldLabel("Active")
activeLabelWithIcon := container.NewGridWithRows(2, activeIcon, activeLabel)
updateProfileButton := widget.NewButtonWithIcon("Update", theme.ViewRefreshIcon(), func(){
setBroadcastMyProfilePage(window, profileType, currentPage, currentPage)
})
disableButton := widget.NewButtonWithIcon("Disable", theme.CancelIcon(), func(){
setDisableMyProfilePage(window, profileType, currentPage)
})
actionButtonsGrid := getContainerCentered(container.NewGridWithColumns(1, updateProfileButton, disableButton))
profileStatusSection := container.NewVBox(activeLabelWithIcon, widget.NewSeparator(), actionButtonsGrid)
return profileStatusSection, nil
}
// myProfileIsActiveStatus == false
// Profile is not active.
// Possible reasons:
// -Profile was never broadcast
// -Identity is not funded
// -Profile is disabled
// -Profile has expired from network (For Mate profiles only)
inactiveIcon, err := getFyneImageIcon("ToggleOff")
if (err != nil) { return nil, err }
inactiveLabel := getBoldLabel("Inactive")
inactiveLabelWithIcon := container.NewGridWithRows(2, inactiveIcon, inactiveLabel)
exists, localIsDisabled, err := myLocalProfiles.GetProfileData(profileType, "Disabled")
if (err != nil) { return nil, err }
if (exists == true && localIsDisabled == "Yes"){
enableProfileButton := getWidgetCentered(widget.NewButton("Enable", func(){
setEnableMyProfilePage(window, profileType, currentPage, currentPage)
}))
profileStatusSection := container.NewVBox(inactiveLabelWithIcon, widget.NewSeparator(), enableProfileButton)
return profileStatusSection, nil
}
broadcastProfileFunction := func()error{
if (profileType == "Mate"){
myIdentityFound, myIdentityIsActivated, myBalanceIsSufficient, _, _, err := myIdentityBalance.GetMyIdentityBalanceStatus(myIdentityHash, appNetworkType)
if (err != nil){ return err }
if (myIdentityFound == false){
return errors.New("My identity not found after being found already.")
}
if (myIdentityIsActivated == false || myBalanceIsSufficient == false) {
dialogTitle := translate("Identity Balance Is Insufficient.")
dialogMessageA := getLabelCentered(translate("Your identity balance is insufficient."))
dialogMessageB := getLabelCentered(translate("You must spend credit to fund your identity and broadcast a profile."))
dialogMessageC := getLabelCentered(translate("Visit the Manage Identity Balance page to fund your identity."))
dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC)
dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
return nil
}
} else if (profileType == "Moderator"){
myIdentityFound, _, scoreIsSufficient, _, _, err := myIdentityScore.GetMyIdentityScore()
if (err != nil) { return err }
if (myIdentityFound == false){
return errors.New("My moderator identity not found after being found already.")
}
if (scoreIsSufficient == false){
dialogTitle := translate("Identity Score Is Insufficient.")
dialogMessageA := getLabelCentered(translate("Your identity score is insufficient."))
dialogMessageB := getLabelCentered(translate("You must send cryptocurrency to fund your identity."))
dialogMessageC := getLabelCentered(translate("Visit the Manage Identity Score page to fund your identity."))
dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC)
dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window)
return nil
}
}
setBroadcastMyProfilePage(window, profileType, currentPage, currentPage)
return nil
}
broadcastButton := getWidgetCentered(widget.NewButtonWithIcon("Broadcast", theme.RadioButtonCheckedIcon(), func(){
err := broadcastProfileFunction()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
}))
profileStatusSection := container.NewVBox(inactiveLabelWithIcon, broadcastButton)
return profileStatusSection, nil
}
profileStatusSection, err := getProfileStatusSection()
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), myProfileStatusLabel, widget.NewSeparator(), profileStatusSection, widget.NewSeparator())
if (myProfileIsActiveStatus == true && profileType == "Mate"){
addProfileExpirationTimeSection := func()error{
profileExpirationTimeLabel := getItalicLabelCentered("My Profile Expiration Time:")
page.Add(profileExpirationTimeLabel)
// All mate profiles will expire after an expiration duration, which is defined in the network parameters
// They must be updated, or else they will be dropped by the network
// We will determine how long this current profile has until it is expired
// The user has to broadcast a profile again to reset this countdown
myIdentityExists, myProfileExists, _, myProfileAttributeExists, myProfileCreationTime, err := myBroadcasts.GetAnyAttributeFromMyBroadcastProfile(myIdentityHash, appNetworkType, "CreationTime")
if (err != nil) { return err }
if (myIdentityExists == false) {
return errors.New("My identity not found after being found already.")
}
if (myProfileExists == false){
return errors.New("My broadcast profile not found after myProfileStatus has already been determined to be active.")
}
if (myProfileAttributeExists == false){
return errors.New("My Broadcast profile malformed: Missing CreationTime")
}
myProfileCreationTimeInt64, err := helpers.ConvertCreationTimeStringToInt64(myProfileCreationTime)
if (err != nil){
return errors.New("My Broadcast profile malformed: Contains invalid creationTime: " + myProfileCreationTime)
}
_, mateProfileMaximumExistenceDuration, err := getParameters.GetMateProfileMaximumExistenceDuration(appNetworkType)
if (err != nil) { return err }
currentTime := time.Now().Unix()
profileExistenceTime := currentTime - myProfileCreationTimeInt64
if (profileExistenceTime >= mateProfileMaximumExistenceDuration){
return errors.New("My Broadcast profile is expired, but myProfileStatus says it is active")
}
timeUntilExpiration := mateProfileMaximumExistenceDuration - profileExistenceTime
timeUntilExpirationString, err := helpers.ConvertUnixTimeDurationToUnitsTimeTranslated(timeUntilExpiration, true)
if (err != nil) { return err }
timeUntilExpirationLabel := getBoldLabel("Expires in " + timeUntilExpirationString)
profileExpirationTimeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
setMateProfileExpirationExplainerPage(window, currentPage)
})
timeUntilExpirationRow := container.NewHBox(layout.NewSpacer(), timeUntilExpirationLabel, profileExpirationTimeHelpButton, layout.NewSpacer())
page.Add(timeUntilExpirationRow)
page.Add(widget.NewSeparator())
return nil
}
err := addProfileExpirationTimeSection()
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
}
if (profileType == "Mate"){
getMyIdentityBalanceSection := func()(*fyne.Container, error){
myIdentityBalanceLabel := getItalicLabelCentered("My Identity Balance:")
getMyIdentityBalanceStatus := func()(string, error){
myIdentityFound, myIdentityIsActivated, myBalanceIsSufficient, _, myBalanceExpirationTime, err := myIdentityBalance.GetMyIdentityBalanceStatus(myIdentityHash, appNetworkType)
if (err != nil){ return "", err }
if (myIdentityFound == false){
return "", errors.New("My identity not found after being found already.")
}
if (myIdentityIsActivated == false || myBalanceIsSufficient == false){
result := translate("Insufficient")
return result, nil
}
currentTime := time.Now().Unix()
timeLeft := myBalanceExpirationTime - currentTime
timeTranslated, err := helpers.ConvertUnixTimeDurationToUnitsTimeTranslated(timeLeft, true)
if (err != nil) { return "", err }
identityBalanceExpirationTime := "Expires in " + timeTranslated
return identityBalanceExpirationTime, nil
}
myIdentityBalanceStatus, err := getMyIdentityBalanceStatus()
if (err != nil){ return nil, err }
myIdentityBalanceStatusLabel := getBoldLabelCentered(myIdentityBalanceStatus)
manageBalanceButton := getWidgetCentered(widget.NewButtonWithIcon("Manage", theme.VisibilityIcon(), func(){
setViewMyIdentityBalancePage(window, profileType, currentPage)
}))
identityBalanceSection := container.NewVBox(myIdentityBalanceLabel, myIdentityBalanceStatusLabel, manageBalanceButton)
return identityBalanceSection, nil
}
myIdentityBalanceSection, err := getMyIdentityBalanceSection()
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
page.Add(myIdentityBalanceSection)
} else if (profileType == "Moderator"){
getIdentityScoreSection := func()(*fyne.Container, error){
myIdentityScoreLabel := getItalicLabelCentered("My Identity Score:")
myIdentityFound, _, scoreIsSufficient, _, _, err := myIdentityScore.GetMyIdentityScore()
if (err != nil) { return nil, err }
if (myIdentityFound == false){
return nil, errors.New("My identity not found after being found already.")
}
getIdentityScoreStatus := func()string{
if (scoreIsSufficient == false){
result := translate("Insufficient")
return result
}
result := translate("Sufficient")
return result
}
identityScoreStatus := getIdentityScoreStatus()
identityScoreStatusLabel := getBoldLabelCentered(identityScoreStatus)
manageIdentityScoreButton := getWidgetCentered(widget.NewButtonWithIcon("View Identity Score", theme.VisibilityIcon(), func(){
setViewMyModeratorScorePage(window, currentPage)
}))
identityScoreSection := container.NewVBox(myIdentityScoreLabel, identityScoreStatusLabel, manageIdentityScoreButton)
return identityScoreSection, nil
}
identityScoreSection, err := getIdentityScoreSection()
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
page.Add(identityScoreSection)
}
setPageContent(page, window)
}
func setBroadcastMyProfilePage(window fyne.Window, profileType string, previousPage func(), pageToVisitAfter func()) {
currentPage := func(){setBroadcastMyProfilePage(window, profileType, previousPage, pageToVisitAfter)}
title := getPageTitleCentered(translate("Broadcast " + profileType + " Profile"))
backButton := getBackButtonCentered(previousPage)
if (profileType == "Mate"){
myGenomePersonIdentifierExists, myGenomePersonIdentifier, err := myLocalProfiles.GetProfileData("Mate", "GenomePersonIdentifier")
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
if (myGenomePersonIdentifierExists == true){
anyGenomesExist, personAnalysisIsReady, _, err := myPeople.CheckIfPersonAnalysisIsReady(myGenomePersonIdentifier)
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
if (anyGenomesExist == true && personAnalysisIsReady == false){
description1 := getBoldLabelCentered(translate("Your profile contains a linked genome person."))
description2 := getLabelCentered(translate("You need to perform your genetic analysis."))
description3 := getLabelCentered(translate("Only the information you choose will be shared in your profile."))
performAnalysisButton := getWidgetCentered(widget.NewButtonWithIcon(translate("Perform Analysis"), theme.NavigateNextIcon(), func(){
setConfirmPerformPersonAnalysisPage(window, myGenomePersonIdentifier, currentPage, currentPage)
}))
page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, performAnalysisButton)
setPageContent(page, window)
return
}
}
}
appNetworkType, err := getAppNetworkType.GetAppNetworkType()
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
err = myProfileExports.UpdateMyExportedProfile(profileType, appNetworkType)
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(profileType)
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
if (myIdentityExists == false){
setErrorEncounteredPage(window, errors.New("My identity not found after being found already."), previousPage)
return
}
existingBroadcastProfileExists, _, _, _, existingBroadcastProfileRawMap, err := myBroadcasts.GetMyNewestBroadcastProfile(myIdentityHash, appNetworkType)
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
page := container.NewVBox(title, backButton, widget.NewSeparator())
if (existingBroadcastProfileExists == true){
description1 := getBoldLabelCentered("Are you sure you want to update your " + profileType + " profile?")
page.Add(description1)
} else {
description1 := getBoldLabelCentered("Are you sure you want to broadcast your " + profileType + " profile?")
page.Add(description1)
}
viewProfileButton := getWidgetCentered(widget.NewButtonWithIcon("View Profile", theme.VisibilityIcon(), func(){
setViewMyProfilePage(window, profileType, "Local", currentPage)
}))
page.Add(viewProfileButton)
page.Add(widget.NewSeparator())
if (existingBroadcastProfileExists == true){
getLastUpdatedTimeAgo := func()(string, error){
exists, lastCreationTimeString, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(existingBroadcastProfileRawMap, "CreationTime")
if (err != nil){ return "", err }
if (exists == false){
return "", errors.New("My broadcast profile missing CreationTime")
}
lastCreationTimeInt64, err := helpers.ConvertStringToInt64(lastCreationTimeString)
if (err != nil){
return "", errors.New("My broadcast profile contains invalid CreationTime: " + lastCreationTimeString)
}
lastUpdatedTimeAgo, err := helpers.ConvertUnixTimeToTimeAgoTranslated(lastCreationTimeInt64, true)
if (err != nil){ return "", err }
return lastUpdatedTimeAgo, nil
}
lastUpdatedTimeAgo, err := getLastUpdatedTimeAgo()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
lastUpdatedTitle := widget.NewLabel("Last Updated:")
lastUpdatedLabel := getBoldLabel(lastUpdatedTimeAgo)
lastUpdatedRow := container.NewHBox(layout.NewSpacer(), lastUpdatedTitle, lastUpdatedLabel, layout.NewSpacer())
page.Add(lastUpdatedRow)
getNumberOfChangesMade := func()(int, error){
profileFound, _, _, exportProfileRawMap, err := myProfileExports.GetMyExportedProfile(profileType, appNetworkType)
if (err != nil) { return 0, err }
if (profileFound == false){
return 0, errors.New("My exported profile not found after profile was exported.")
}
allAttributesMap := make(map[int]struct{})
for attributeIdentifier, _ := range existingBroadcastProfileRawMap{
allAttributesMap[attributeIdentifier] = struct{}{}
}
for attributeIdentifier, _ := range exportProfileRawMap{
allAttributesMap[attributeIdentifier] = struct{}{}
}
changesMade := 0
// This will add the number of changed fields
for attributeIdentifier, _ := range allAttributesMap{
attributeName, err := profileFormat.GetAttributeNameFromAttributeIdentifier(attributeIdentifier)
if (err != nil) { return 0, err }
if (attributeName == "CreationTime" || attributeName == "ChatKeysLatestUpdateTime" || attributeName == "NaclKey" || attributeName == "KyberKey"){
continue
}
existingValueExists, existingValue, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(existingBroadcastProfileRawMap, attributeName)
if (err != nil) { return 0, err }
exportValueExists, exportValue, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(exportProfileRawMap, attributeName)
if (err != nil) { return 0, err }
if (existingValueExists != exportValueExists || existingValue != exportValue){
// The value has changed between the old and new profile
changesMade += 1
}
}
return changesMade, nil
}
numberOfChangesMade, err := getNumberOfChangesMade()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
numberOfChangesMadeString := helpers.ConvertIntToString(numberOfChangesMade)
changesMadeTitle := getItalicLabel("Changes Made:")
numberOfChangesMadeLabel := getBoldLabel(numberOfChangesMadeString)
numberOfChangesMadeRow := container.NewHBox(layout.NewSpacer(), changesMadeTitle, numberOfChangesMadeLabel, layout.NewSpacer())
page.Add(numberOfChangesMadeRow)
if (numberOfChangesMade != 0){
viewChangesButton := getWidgetCentered(widget.NewButtonWithIcon("View Changes", theme.VisibilityIcon(), func(){
setViewNewProfileChangesPage(window, profileType, currentPage)
}))
page.Add(viewChangesButton)
}
page.Add(widget.NewSeparator())
}
if (profileType == "Mate"){
// Mate profiles must be funded with each broadcast
getCurrentAppCurrency := func()(string, error){
exists, currentAppCurrency, err := globalSettings.GetSetting("Currency")
if (err != nil) { return "", err }
if (exists == false){
return "USD", nil
}
return currentAppCurrency, nil
}
currentAppCurrencyCode, err := getCurrentAppCurrency()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
_, appCurrencySymbol, err := currencies.GetCurrencyInfoFromCurrencyCode(currentAppCurrencyCode)
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
costTitle := widget.NewLabel("Cost:")
appCurrencySymbolButton := widget.NewButton(appCurrencySymbol, func(){
setChangeAppCurrencyPage(window, currentPage)
})
//TODO: Fix this to actually calculate cost (get cost from parameters)
costLabel := getBoldLabel("0.1 " + currentAppCurrencyCode)
costRow := container.NewHBox(layout.NewSpacer(), costTitle, appCurrencySymbolButton, costLabel, layout.NewSpacer())
page.Add(costRow)
page.Add(widget.NewSeparator())
}
broadcastIcon, err := getFyneImageIcon("Broadcast")
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
confirmButton := widget.NewButtonWithIcon("Broadcast", theme.ConfirmIcon(), func(){
//TODO: Make sure credit is available
setConfirmBroadcastMyProfilePage(window, profileType, currentPage)
})
confirmButtonWithIcon := getContainerCentered(container.NewGridWithRows(2, broadcastIcon, confirmButton))
page.Add(confirmButtonWithIcon)
page.Add(widget.NewSeparator())
setPageContent(page, window)
}
func setConfirmBroadcastMyProfilePage(window fyne.Window, profileType string, previousPage func()){
if (profileType != "Mate" && profileType != "Moderator"){
setErrorEncounteredPage(window, errors.New("setConfirmBroadcastMyProfilePage called with invalid profileType: " + profileType), previousPage)
return
}
title := getPageTitleCentered("Confirm Broadcast Profile")
backButton := getBackButtonCentered(previousPage)
//TODO: Improve wording of below:
description1 := getBoldLabelCentered("Confirm broadcast your " + profileType + " profile?")
description2 := getLabelCentered("This will upload your profile to the Seekia network.")
description3 := getLabelCentered("The whole world will be able to see it.")
description4 := getLabelCentered("Make sure you are comfortable sharing your profile with the world.")
confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm", theme.ConfirmIcon(), func(){
profilePage := func(){setProfilePage(window, true, profileType, false, nil)}
afterCompletionPage := func(){setBroadcastPage(window, profileType, profilePage)}
if (profileType == "Mate"){
setStartAndMonitorMateProfileFundingAndBroadcastPage(window, afterCompletionPage)
} else if (profileType == "Moderator"){
setStartAndMonitorMyModeratorProfileBroadcastPage(window, afterCompletionPage)
}
}))
page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, confirmButton)
setPageContent(page, window)
}
// This function will show the changes between the existing broadcast profile and the new exported profile
func setViewNewProfileChangesPage(window fyne.Window, myProfileType string, previousPage func()){
setLoadingScreen(window, "View Profile Changes", "Loading profile changes...")
currentPage := func(){setViewNewProfileChangesPage(window, myProfileType, previousPage)}
title := getPageTitleCentered("View Profile Changes")
backButton := getBackButtonCentered(previousPage)
description := getLabelCentered("Below are the changes between your old and new profile.")
getChangesGrid := func()(*fyne.Container, error){
myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(myProfileType)
if (err != nil){ return nil, err }
if (myIdentityExists == false){
return nil, errors.New("setViewNewProfileChangesPage called with missing identity.")
}
appNetworkType, err := getAppNetworkType.GetAppNetworkType()
if (err != nil) { return nil, err }
oldProfileExists, _, _, _, oldProfileRawMap, err := myBroadcasts.GetMyNewestBroadcastProfile(myIdentityHash, appNetworkType)
if (err != nil) { return nil, err }
if (oldProfileExists == false){
return nil, errors.New("setViewNewProfileChangesPage called when no broadcast profile exists.")
}
profileFound, _, _, newProfileRawMap, err := myProfileExports.GetMyExportedProfile(myProfileType, appNetworkType)
if (err != nil) { return nil, err }
if (profileFound == false){
return nil, errors.New("setViewNewProfileChangesPage called when exportedProfile is missing.")
}
// We use this map to avoid duplicates
allAttributesIdentifiersMap := make(map[int]struct{})
for attributeIdentifier, _ := range oldProfileRawMap{
allAttributesIdentifiersMap[attributeIdentifier] = struct{}{}
}
for attributeIdentifier, _ := range newProfileRawMap{
allAttributesIdentifiersMap[attributeIdentifier] = struct{}{}
}
allAttributeNamesList := make([]string, 0, len(allAttributesIdentifiersMap))
for attributeIdentifier, _ := range allAttributesIdentifiersMap{
attributeName, err := profileFormat.GetAttributeNameFromAttributeIdentifier(attributeIdentifier)
if (err != nil) { return nil, err }
allAttributeNamesList = append(allAttributeNamesList, attributeName)
}
// We sort attributes so they show up in the same order each time
helpers.SortStringListToUnicodeOrder(allAttributeNamesList)
attributeLabel := getItalicLabelCentered("Attribute")
oldProfileTitle := getItalicLabelCentered("Old Profile")
newProfileTitle := getItalicLabelCentered("New Profile")
attributeTitleColumn := container.NewVBox(attributeLabel, widget.NewSeparator())
oldProfileColumn := container.NewVBox(oldProfileTitle, widget.NewSeparator())
newProfileColumn := container.NewVBox(newProfileTitle, widget.NewSeparator())
for _, attributeName := range allAttributeNamesList{
if (attributeName == "CreationTime" || attributeName == "Disabled" || attributeName == "ChatKeysLatestUpdateTime" || attributeName == "NaclKey" || attributeName == "KyberKey"){
continue
}
oldValueExists, oldValue, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(oldProfileRawMap, attributeName)
if (err != nil) { return nil, err }
newValueExists, newValue, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(newProfileRawMap, attributeName)
if (err != nil) { return nil, err }
if (oldValueExists == false && newValueExists == false){
// This should never happen. Probably cosmic ray bit flip or faulty hardware.
return nil, errors.New("oldProfileRawMap and newProfileRawMap are missing the attribute.")
}
if (oldValueExists == true && newValueExists == true && oldValue == newValue){
// The value is the same between both profiles
continue
}
attributeTitle, _, formatValueFunction, _, _, err := attributeDisplay.GetProfileAttributeDisplayInfo(attributeName)
if (err != nil){ return nil, err }
attributeTitleLabel := getBoldLabelCentered(attributeTitle)
getProfileValueCell := func(valueExists bool, profileValue string)(*fyne.Container, error){
if (valueExists == false){
noneLabel := getItalicLabelCentered(translate("None"))
return noneLabel, nil
}
switch attributeName{
case "Photos":{
photosBase64List := strings.Split(profileValue, "+")
photosList := make([]image.Image, 0, len(photosBase64List))
for _, photoBase64 := range photosBase64List{
goImage, err := imagery.ConvertWEBPBase64StringToCroppedDownsizedImageObject(photoBase64)
if (err != nil){
return nil, errors.New("setViewNewProfileChangesPage called with profile containing invalid Photos attribute: " + profileValue)
}
photosList = append(photosList, goImage)
}
if (len(photosList) > 5){
return nil, errors.New("setViewNewProfileChangesPage called with profile containing invalid Photos attribute: Too many photos: " + profileValue)
}
viewAttributeButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
setViewFullpageImagesWithNavigationPage(window, photosList, 0, currentPage)
}))
return viewAttributeButton, nil
}
case "Avatar":{
avatarIdentifier, err := helpers.ConvertStringToInt(profileValue)
if (err != nil) {
return nil, errors.New("setViewNewProfileChangesPage called with profile containing invalid Avatar attribute: " + profileValue)
}
emojiImage, err := getEmojiImageObject(avatarIdentifier)
if (err != nil){
return nil, errors.New("setViewNewProfileChangesPage called with profile containing invalid Avatar attribute: " + profileValue)
}
viewAttributeButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
setViewFullpageImagePage(window, emojiImage, currentPage)
}))
return viewAttributeButton, nil
}
case "23andMe_AncestryComposition":{
viewAttributeButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
setViewUser23andMeAncestryCompositionPage(window, profileValue, currentPage)
}))
return viewAttributeButton, nil
}
case "Questionnaire":{
questionnaireObject, err := mateQuestionnaire.ReadQuestionnaireString(profileValue)
if (err != nil) {
return nil, errors.New("setViewNewProfileChangesPage called with profile containing invalid Questionnaire attribute: " + profileValue + ". Reason: " + err.Error())
}
myResponsesMap := make(map[string]string)
submitQuestionnairePage := func(_ string, _ func()){
currentPage()
}
viewAttributeButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
setTakeQuestionnairePage(window, questionnaireObject, 0, myResponsesMap, currentPage, submitQuestionnairePage)
}))
return viewAttributeButton, nil
}
//TODO: Format more values that cannot be displayed as a string in their raw form (Tags, Location, Language, etc...)
}
valueFormatted, err := formatValueFunction(profileValue)
if (err != nil) { return nil, err }
valueTrimmed, anyChangesOccurred, err := helpers.TrimAndFlattenString(valueFormatted, 20)
if (err != nil) { return nil, err }
if (anyChangesOccurred == false){
valueLabel := getBoldLabelCentered(valueFormatted)
return valueLabel, nil
}
trimmedValueLabel := getBoldLabel(valueTrimmed)
viewFullValueButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
setViewTextPage(window, "Viewing Attribute", valueFormatted, false, currentPage)
})
profileValueCell := container.NewHBox(layout.NewSpacer(), trimmedValueLabel, viewFullValueButton, layout.NewSpacer())
return profileValueCell, nil
}
oldValueCell, err := getProfileValueCell(oldValueExists, oldValue)
if (err != nil) { return nil, err }
newValueCell, err := getProfileValueCell(newValueExists, newValue)
if (err != nil) { return nil, err }
attributeTitleColumn.Add(attributeTitleLabel)
oldProfileColumn.Add(oldValueCell)
newProfileColumn.Add(newValueCell)
attributeTitleColumn.Add(widget.NewSeparator())
oldProfileColumn.Add(widget.NewSeparator())
newProfileColumn.Add(widget.NewSeparator())
}
displayGrid := container.NewHBox(layout.NewSpacer(), attributeTitleColumn, oldProfileColumn, newProfileColumn, layout.NewSpacer())
return displayGrid, nil
}
displayGrid, err := getChangesGrid()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), displayGrid)
setPageContent(page, window)
}
func setEnableMyProfilePage(window fyne.Window, myProfileType string, previousPage func(), pageToVisitAfter func()){
if (myProfileType != "Mate" && myProfileType != "Moderator"){
setErrorEncounteredPage(window, errors.New("setEnableMyProfilePage called with invalid profileType: " + myProfileType), previousPage)
return
}
myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(myProfileType)
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
if (myIdentityExists == false){
// This should not happen, this page should only be reached if identity exists
setErrorEncounteredPage(window, errors.New("My identity not found after being found already."), previousPage)
return
}
attributeExists, localProfileIsDisabled, err := myLocalProfiles.GetProfileData(myProfileType, "Disabled")
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
if (attributeExists == false || localProfileIsDisabled != "Yes"){
// This should not happen, as this page should only be viewed if profile is disabled
setErrorEncounteredPage(window, errors.New("setEnableMyProfilePage called when your profile is not disabled."), previousPage)
return
}
title := getPageTitleCentered("Enable " + myProfileType + " Profile")
backButton := getBackButtonCentered(previousPage)
description1 := getBoldLabelCentered("Are you sure you want to re-enable your profile?")
description2 := getLabelCentered("You must broadcast your profile after this step.")
confirmFunction := func(){
err = myBroadcasts.DeleteMyBroadcastProfiles(myIdentityHash)
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
err := myLocalProfiles.SetProfileData(myProfileType, "Disabled", "No")
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
pageToVisitAfter()
}
confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm", theme.ConfirmIcon(), confirmFunction))
page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, confirmButton)
setPageContent(page, window)
}
func setDisableMyProfilePage(window fyne.Window, myProfileType string, previousPage func()){
if (myProfileType != "Mate" && myProfileType != "Moderator"){
setErrorEncounteredPage(window, errors.New("setDisableMyProfilePage called with invalid profile type: " + myProfileType), previousPage)
return
}
myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(myProfileType)
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
if (myIdentityExists == false){
// This should not happen, this page should only be reached if identity exists
setErrorEncounteredPage(window, errors.New("My identity not found on setDisableMyProfilePage."), previousPage)
return
}
currentPage := func(){setDisableMyProfilePage(window, myProfileType, previousPage)}
title := getPageTitleCentered("Disable My " + myProfileType + " Profile")
backButton := getBackButtonCentered(previousPage)
getLocalProfileIsDisabledStatus := func()(bool, error){
attributeExists, localProfileIsDisabled, err := myLocalProfiles.GetProfileData(myProfileType, "Disabled")
if (err != nil){ return false, err }
if (attributeExists == true && localProfileIsDisabled == "Yes"){
return true, nil
}
return false, nil
}
appNetworkType, err := getAppNetworkType.GetAppNetworkType()
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
getBroadcastProfileIsDisabledStatus := func()(bool, error){
identityExists, profileExists, _, attributeExists, broadcastProfileIsDisabled, err := myBroadcasts.GetAnyAttributeFromMyBroadcastProfile(myIdentityHash, appNetworkType, "Disabled")
if (err != nil) { return false, err }
if (identityExists == false) {
return false, errors.New("My identity not found after being found already.")
}
if (profileExists == false){
return false, nil
}
if (attributeExists == true && broadcastProfileIsDisabled == "Yes"){
return true, nil
}
return false, nil
}
localProfileIsDisabled, err := getLocalProfileIsDisabledStatus()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
broadcastProfileIsDisabled, err := getBroadcastProfileIsDisabledStatus()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
if (localProfileIsDisabled == true && broadcastProfileIsDisabled == true){
// This should not happen, as this page should only be viewed if profile is enabled
setErrorEncounteredPage(window, errors.New("setDisableMyProfilePage accessed with enabled/missing profile."), previousPage)
return
}
description1 := getBoldLabelCentered("Confirm to disable your " + myProfileType + " Profile?")
description2 := getLabelCentered("Other users will see your profile as disabled.")
description3 := getLabelCentered("You will stop receiving " + myProfileType + " messages.")
description4 := getLabelCentered("You can always enable your profile later.")
page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4)
if (myProfileType == "Mate"){
description5 := getLabelCentered("Your identity balance will be retained, but will continue to expire.")
page.Add(description5)
} else if (myProfileType == "Moderator"){
description5 := getLabelCentered("Your identity score will be retained.")
page.Add(description5)
}
if (myProfileType == "Mate"){
// Mate profiles must be funded with each broadcast
page.Add(widget.NewSeparator())
getCurrentAppCurrency := func()(string, error){
exists, currentAppCurrency, err := globalSettings.GetSetting("Currency")
if (err != nil) { return "", err }
if (exists == false){
return "USD", nil
}
return currentAppCurrency, nil
}
currentAppCurrencyCode, err := getCurrentAppCurrency()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
_, appCurrencySymbol, err := currencies.GetCurrencyInfoFromCurrencyCode(currentAppCurrencyCode)
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
costTitle := widget.NewLabel("Cost:")
appCurrencySymbolButton := widget.NewButton(appCurrencySymbol, func(){
setChangeAppCurrencyPage(window, currentPage)
})
//TODO: Fix this to actually calculate cost (get cost from parameters)
costLabel := getBoldLabel("0.10 " + currentAppCurrencyCode)
costRow := container.NewHBox(layout.NewSpacer(), costTitle, appCurrencySymbolButton, costLabel, layout.NewSpacer())
page.Add(costRow)
page.Add(widget.NewSeparator())
}
confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm", theme.ConfirmIcon(), func(){
//TODO: Make sure credit is sufficient (if profileType == "Mate")
err := myLocalProfiles.SetProfileData(myProfileType, "Disabled", "Yes")
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
err = myProfileExports.UpdateMyExportedProfile(myProfileType, appNetworkType)
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
if (myProfileType == "Mate"){
setStartAndMonitorMateProfileFundingAndBroadcastPage(window, previousPage)
} else if (myProfileType == "Moderator"){
setStartAndMonitorMyModeratorProfileBroadcastPage(window, previousPage)
}
}))
// TODO: Add a warning that you should not disable multiple identity's profiles at the same time
// Correlation between the identities is possible
page.Add(confirmButton)
setPageContent(page, window)
}
// This page will first attempt to fund a mate profile, and if it succeeds, it will initiate a manual profile broadcast
func setStartAndMonitorMateProfileFundingAndBroadcastPage(window fyne.Window, afterCompletionPage func()){
pageIdentifier, err := helpers.GetNewRandomHexString(16)
if (err != nil) {
setErrorEncounteredPage(window, err, afterCompletionPage)
return
}
appMemory.SetMemoryEntry("CurrentViewedPage", pageIdentifier)
checkIfPageHasChangedFunction := func()bool{
exists, currentViewedPage := appMemory.GetMemoryEntry("CurrentViewedPage")
if (exists == true && currentViewedPage == pageIdentifier){
return false
}
return true
}
title := getPageTitleCentered("Funding Mate Profile")
progressBinding := binding.NewString()
updateProgressBindings := func(){
startTime := time.Now().Unix()
for {
//TODO: Add details that describe the steps (Example: contacting server, making transaction)
currentTime := time.Now().Unix()
secondsElapsed := currentTime - startTime
if (secondsElapsed % 3 == 0){
progressBinding.Set("Funding profile.")
} else if (secondsElapsed % 3 == 1){
progressBinding.Set("Funding profile..")
} else {
progressBinding.Set("Funding profile...")
}
pageHasChanged := checkIfPageHasChangedFunction()
if (pageHasChanged == true){
return
}
time.Sleep(time.Second/2)
}
}
appNetworkType, err := getAppNetworkType.GetAppNetworkType()
if (err != nil) {
setErrorEncounteredPage(window, err, afterCompletionPage)
return
}
fundProfileFunction := func(){
// We fund the export profile before we broadcast it
newProfileFound, newProfileHash, _, _, err := myProfileExports.GetMyExportedProfile("Mate", appNetworkType)
if (err != nil) {
setErrorEncounteredPage(window, err, afterCompletionPage)
return
}
if (newProfileFound == false){
setErrorEncounteredPage(window, errors.New("setStartAndMonitorMateProfileFundingAndBroadcastPage called when export profile is missing."), afterCompletionPage)
return
}
//Outputs:
// -bool: Fund successful
// -error
fundProfile := func(profileHashToFund [28]byte)(bool, error){
//TODO: Add function to fund profile
time.Sleep(time.Second * 3)
return true, nil
}
fundSuccessful, err := fundProfile(newProfileHash)
if (err != nil){
pageHasChanged := checkIfPageHasChangedFunction()
if (pageHasChanged == true){
return
}
setErrorEncounteredPage(window, errors.New("Profile fund encountered error: " + err.Error()), afterCompletionPage)
return
}
if (fundSuccessful == false){
pageHasChanged := checkIfPageHasChangedFunction()
if (pageHasChanged == true){
return
}
description1 := getBoldLabelCentered("Seekia failed to fund the profile.")
description2 := getLabelCentered("The account credit server we contacted may be down.")
description3 := getLabelCentered("Your internet connection may also be broken.")
retryFunction := func(){setStartAndMonitorMateProfileFundingAndBroadcastPage(window, afterCompletionPage)}
retryButton := getWidgetCentered(widget.NewButtonWithIcon("Retry", theme.ViewRefreshIcon(), retryFunction))
exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), afterCompletionPage))
manageConnectionDescription := getLabelCentered("Check if your internet connection is working below.")
manageConnectionButton := getWidgetCentered(widget.NewButtonWithIcon("Manage Connection", theme.DownloadIcon(), func(){
setManageNetworkConnectionPage(window, retryFunction)
}))
page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), retryButton, exitButton, widget.NewSeparator(), manageConnectionDescription, manageConnectionButton)
setPageContent(page, window)
return
}
// Fund is complete. We update the broadcast profile
myIdentityExists, newBroadcastProfileHash, err := myBroadcasts.UpdateMyBroadcastProfile("Mate", appNetworkType)
if (err != nil) {
pageHasChanged := checkIfPageHasChangedFunction()
if (pageHasChanged == true){
return
}
setErrorEncounteredPage(window, err, afterCompletionPage)
return
}
if (myIdentityExists == false){
pageHasChanged := checkIfPageHasChangedFunction()
if (pageHasChanged == true){
return
}
setErrorEncounteredPage(window, errors.New("My identity not found after being found already."), afterCompletionPage)
return
}
if (newBroadcastProfileHash != newProfileHash){
pageHasChanged := checkIfPageHasChangedFunction()
if (pageHasChanged == true){
return
}
// This should not happen. This means we funded the wrong profile.
// MyBroadcasts broadcasts the newest exported profile
setErrorEncounteredPage(window, errors.New("Exported profile is not the same as broadcasted profile"), afterCompletionPage)
return
}
// Now we start a new broadcast
//Outputs:
// -bool: Any hosts found
// -[22]byte: New process identifier
// -error
startNewBroadcastFunction := func()(bool, [22]byte, error){
myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash("Mate")
if (err != nil){ return false, [22]byte{}, err }
if (myIdentityExists == false){
return false, [22]byte{}, errors.New("My identity not found after being found already.")
}
// We get the user's newest profile each time
// It is unlikely, but the user could update their broadcast profile before this manual broadcast process completes
// In this case, we will just be broadcasting the user's newest profile in multiple manual processes
profileExists, _, profileHash, profileBytes, _, err := myBroadcasts.GetMyNewestBroadcastProfile(myIdentityHash, appNetworkType)
if (err != nil) { return false, [22]byte{}, err }
if (profileExists == false){
return false, [22]byte{}, errors.New("Cannot get profile to broadcast on setStartAndMonitorMateProfileFundingAndBroadcastPage")
}
if (profileHash != newProfileHash){
return false, [22]byte{}, errors.New("GetMyNewestBroadcastProfile returning different profile during Mate profile broadcast")
}
profileToBroadcastList := [][]byte{profileBytes}
anyHostsFound, processIdentifier, err := manualBroadcasts.StartContentBroadcast("Profile", appNetworkType, profileToBroadcastList, 3)
if (err != nil) { return false, [22]byte{}, err }
if (anyHostsFound == false){
return false, [22]byte{}, nil
}
return true, processIdentifier, nil
}
anyHostsFound, newProcessIdentifier, err := startNewBroadcastFunction()
if (err != nil){
pageHasChanged := checkIfPageHasChangedFunction()
if (pageHasChanged == true){
return
}
setErrorEncounteredPage(window, err, afterCompletionPage)
return
}
pageHasChanged := checkIfPageHasChangedFunction()
if (pageHasChanged == true){
// Whether the process started or not, we will not show the user in the GUI
// If no hosts were found, their profile should still be broadcast in the background
// Seekia will automatically download enough hosts to contact
return
}
noHostsFound := !anyHostsFound
nextPageTitle := "Broadcasting Mate Profile"
nextPageDescription := "Seekia is broadcasting your Mate profile."
setMonitorManualBroadcastPage(window, nextPageTitle, "Profile", nextPageDescription, noHostsFound, newProcessIdentifier, startNewBroadcastFunction, afterCompletionPage)
}
description1 := getBoldLabelCentered("Seekia is funding your new Mate profile.")
description2 := getLabelCentered("You can leave this page.")
progressLabel := widget.NewLabelWithData(progressBinding)
progressLabel.TextStyle = getFyneTextStyle_Bold()
progressLabelCentered := getWidgetCentered(progressLabel)
exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), afterCompletionPage))
page := container.NewVBox(title, widget.NewSeparator(), description1, description2, widget.NewSeparator(), progressLabelCentered, widget.NewSeparator(), exitButton)
setPageContent(page, window)
go updateProgressBindings()
go fundProfileFunction()
}
// Starts broadcast and allows user to monitor progress
func setStartAndMonitorMyModeratorProfileBroadcastPage(window fyne.Window, afterCompletionPage func()){
myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash("Moderator")
if (err != nil){
setErrorEncounteredPage(window, err, afterCompletionPage)
return
}
if (myIdentityExists == false){
setErrorEncounteredPage(window, errors.New("Identity does not exist on setStartAndMonitorMyModeratorProfileBroadcastPage"), afterCompletionPage)
return
}
// We have to update the broadcast profile
appNetworkType, err := getAppNetworkType.GetAppNetworkType()
if (err != nil) {
setErrorEncounteredPage(window, err, afterCompletionPage)
return
}
identityExists, _, err := myBroadcasts.UpdateMyBroadcastProfile("Moderator", appNetworkType)
if (err != nil) {
setErrorEncounteredPage(window, err, afterCompletionPage)
return
}
if (identityExists == false){
setErrorEncounteredPage(window, errors.New("Identity not found after being found already."), afterCompletionPage)
return
}
//Outputs:
// -bool: Any hosts found
// -[22]byte: New process identifier
// -error
startNewBroadcastFunction := func()(bool, [22]byte, error){
// We get the user's newest profile each time
// It is unlikely, but the user could update their broadcast profile before this manual broadcast process completes
// In this case, we will just be broadcasting the user's newest profile in multiple manual processes
profileExists, _, _, profileBytes, _, err := myBroadcasts.GetMyNewestBroadcastProfile(myIdentityHash, appNetworkType)
if (err != nil) { return false, [22]byte{}, err }
if (profileExists == false){
return false, [22]byte{}, errors.New("Cannot get profile to broadcast on setStartAndMonitorMyModeratorProfileBroadcastPage")
}
profileToBroadcastList := [][]byte{profileBytes}
anyHostsFound, processIdentifier, err := manualBroadcasts.StartContentBroadcast("Profile", appNetworkType, profileToBroadcastList, 3)
if (err != nil) { return false, [22]byte{}, err }
if (anyHostsFound == false){
return false, [22]byte{}, nil
}
return true, processIdentifier, nil
}
anyHostsFound, newProcessIdentifier, err := startNewBroadcastFunction()
if (err != nil){
setErrorEncounteredPage(window, err, afterCompletionPage)
return
}
noHostsFound := !anyHostsFound
nextPageTitle := "Broadcasting Moderator Profile"
nextPageDescription := "Seekia is broadcasting your Moderator profile."
setMonitorManualBroadcastPage(window, nextPageTitle, "Profile", nextPageDescription, noHostsFound, newProcessIdentifier, startNewBroadcastFunction, afterCompletionPage)
}
func setMonitorManualBroadcastPage(window fyne.Window, pageTitleText string, broadcastType string, description1Text string, noHostsFound bool, processIdentifier [22]byte, startNewBroadcastFunction func()(bool, [22]byte, error), afterCompletionPage func()){
currentPage := func(){setMonitorManualBroadcastPage(window, pageTitleText, broadcastType, description1Text, noHostsFound, processIdentifier, startNewBroadcastFunction, afterCompletionPage)}
pageIdentifier, err := helpers.GetNewRandomHexString(16)
if (err != nil) {
setErrorEncounteredPage(window, err, afterCompletionPage)
return
}
appMemory.SetMemoryEntry("CurrentViewedPage", pageIdentifier)
checkIfPageHasChangedFunction := func()bool{
exists, currentViewedPage := appMemory.GetMemoryEntry("CurrentViewedPage")
if (exists == true && currentViewedPage == pageIdentifier){
return false
}
return true
}
title := getPageTitleCentered(pageTitleText)
retryFunction := func(){
newBroadcastAnyHostsFound, newProcessIdentifier, err := startNewBroadcastFunction()
if (err != nil){
setErrorEncounteredPage(window, err, afterCompletionPage)
return
}
newBroadcastNoHostsFound := !newBroadcastAnyHostsFound
setMonitorManualBroadcastPage(window, pageTitleText, broadcastType, description1Text, newBroadcastNoHostsFound, newProcessIdentifier, startNewBroadcastFunction, afterCompletionPage)
}
if (noHostsFound == true){
description1 := getLabelCentered("No available hosts were found.")
description2 := getLabelCentered("Please wait for Seekia to find more hosts.")
description3 := getLabelCentered("This should take less than 1 minute.")
description4 := getLabelCentered("You can leave this page and the broadcast will still happen automatically.")
retryingInSecondsBinding := binding.NewString()
startRetryCountdownFunction := func(){
secondsRemaining := 30
for {
secondsRemainingString := helpers.ConvertIntToString(secondsRemaining)
if (secondsRemaining != 1){
retryingInSecondsBinding.Set("Retrying in " + secondsRemainingString + " seconds...")
} else {
retryingInSecondsBinding.Set("Retrying in " + secondsRemainingString + " second...")
}
time.Sleep(time.Second)
secondsRemaining -= 1
if (secondsRemaining <= 0){
pageHasChanged := checkIfPageHasChangedFunction()
if (pageHasChanged == true){
return
}
retryFunction()
return
}
}
}
retryingInLabel := widget.NewLabelWithData(retryingInSecondsBinding)
retryingInLabel.TextStyle = getFyneTextStyle_Bold()
retryingInLabelCentered := getWidgetCentered(retryingInLabel)
retryButton := getWidgetCentered(widget.NewButtonWithIcon("Retry", theme.ViewRefreshIcon(), retryFunction))
exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), afterCompletionPage))
descriptionD := getLabelCentered("Check if your internet connection is working below.")
manageConnectionButton := getWidgetCentered(widget.NewButtonWithIcon("Manage Connection", theme.DownloadIcon(), func(){
setManageNetworkConnectionPage(window, currentPage)
}))
page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, description4, widget.NewSeparator(), retryingInLabelCentered, widget.NewSeparator(), retryButton, exitButton, widget.NewSeparator(), descriptionD, manageConnectionButton)
setPageContent(page, window)
go startRetryCountdownFunction()
return
}
broadcastProgressStatusBinding := binding.NewString()
broadcastProgressDetailsBinding := binding.NewString()
updateBindingsFunction := func(){
startTime := time.Now().Unix()
setBroadcastProgressStatus := func(processComplete bool, newStatus string){
getProgressEllipsis := func()string{
if (processComplete == true){
return ""
}
currentTime := time.Now().Unix()
secondsElapsed := currentTime - startTime
if (secondsElapsed % 3 == 0){
return "."
}
if (secondsElapsed % 3 == 1){
return ".."
}
return "..."
}
progressEllipsis := getProgressEllipsis()
broadcastProgressStatusBinding.Set(newStatus + progressEllipsis)
}
for {
processFound, processIsCompleteBool, processEncounteredError, processError, numberOfCompletedBroadcasts, processProgressDetails := manualBroadcasts.GetProcessInfo(processIdentifier)
if (processFound == false){
// This should not happen
processIdentifierHex := encoding.EncodeBytesToHexString(processIdentifier[:])
setBroadcastProgressStatus(true, "ERROR: manualBroadcasts process not found: " + processIdentifierHex)
broadcastProgressDetailsBinding.Set("Report this error to the Seekia developers.")
return
}
numberOfCompletedBroadcastsString := helpers.ConvertIntToString(numberOfCompletedBroadcasts)
if (processIsCompleteBool == true){
if (processEncounteredError == true){
setBroadcastProgressStatus(true, "ERROR:" + processError.Error())
broadcastProgressDetailsBinding.Set("Report this error to the Seekia developers.")
return
}
pageHasChanged := checkIfPageHasChangedFunction()
if (pageHasChanged == true){
return
}
if (numberOfCompletedBroadcasts == 3){
// We broadcasted to all 3 hosts. Broadcast is complete.
afterCompletionPage()
return
}
// Broadcast did not complete all required hosts.
// We will show user option to retry.
retryButton := getWidgetCentered(widget.NewButtonWithIcon("Retry", theme.ViewRefreshIcon(), retryFunction))
exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), afterCompletionPage))
if (numberOfCompletedBroadcasts != 0){
// Process is complete, and at least 1 host was broadcasted to, but not all hosts.
// This means that we ran out of hosts.
// Seekia will keep broadcasting the content(s) in the background, so nothing needs to be done by the user.
description1 := getBoldLabelCentered("The broadcast was successful to " + numberOfCompletedBroadcastsString + "/3 hosts.")
description2 := getLabelCentered("We ran out of hosts to contact.")
description3 := getLabelCentered("You can exit or wait for more hosts to be found and retry.")
description4 := getLabelCentered("Seekia will broadcast the " + broadcastType + " in the background either way.")
page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, description4, retryButton, exitButton)
setPageContent(page, window)
return
}
// Broadcast completed, but 0 hosts were successfully contacted
// Now we will show them a "Broadcast Failed" page and an option to retry.
description1 := getBoldLabelCentered("The broadcast was unsuccessful.")
description2 := getLabelCentered("Retry the broadcast?")
descriptionC := getLabelCentered("Check if your internet connection is working below.")
manageConnectionButton := getWidgetCentered(widget.NewButtonWithIcon("Manage Connection", theme.DownloadIcon(), func(){
setManageNetworkConnectionPage(window, currentPage)
}))
page := container.NewVBox(title, widget.NewSeparator(), description1, description2, retryButton, exitButton, widget.NewSeparator(), descriptionC, manageConnectionButton)
setPageContent(page, window)
return
}
// Broadcast is not complete
progressProgressStatusString := "Broadcasted to " + numberOfCompletedBroadcastsString + "/3 hosts."
setBroadcastProgressStatus(processIsCompleteBool, progressProgressStatusString)
broadcastProgressDetailsBinding.Set(processProgressDetails)
time.Sleep(100 * time.Millisecond)
}
}
description1 := getBoldLabelCentered(description1Text)
description2 := getLabelCentered("This process will run in the background.")
description3 := getLabelCentered("You can leave this page.")
broadcastProgressStatusLabel := widget.NewLabelWithData(broadcastProgressStatusBinding)
broadcastProgressStatusLabel.TextStyle = getFyneTextStyle_Bold()
broadcastProgressStatusLabelCentered := getWidgetCentered(broadcastProgressStatusLabel)
broadcastProgressDetailsLabel := getWidgetCentered(widget.NewLabelWithData(broadcastProgressDetailsBinding))
exitPageButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.MediaSkipNextIcon(), func(){
appMemory.DeleteMemoryEntry("CurrentViewedPage")
afterCompletionPage()
}))
page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), broadcastProgressStatusLabelCentered, broadcastProgressDetailsLabel, widget.NewSeparator(), exitPageButton)
setPageContent(page, window)
go updateBindingsFunction()
}
func setViewMyIdentityBalancePage(window fyne.Window, myIdentityType string, previousPage func()){
if (myIdentityType != "Mate" && myIdentityType != "Host"){
setErrorEncounteredPage(window, errors.New("setViewMyIdentityBalancePage called with invalid identity type: " + myIdentityType), previousPage)
return
}
currentPage := func(){setViewMyIdentityBalancePage(window, myIdentityType, previousPage)}
title := getPageTitleCentered("My " + myIdentityType + " Identity Balance")
backButton := getBackButtonCentered(previousPage)
myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(myIdentityType)
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
if (myIdentityExists == false){
// This should not occur, this page should only be reached if identity exists.
setErrorEncounteredPage(window, errors.New("Identity not found."), previousPage)
return
}
getDescriptionSection := func()*fyne.Container{
if (myIdentityType == "Mate"){
description1 := getLabelCentered("Below is your Mate identity balance.")
description2 := getLabelCentered("It must be sufficient for your profile to be broadcast.")
description3 := getLabelCentered("Spend credit to increase your identity balance.")
description4 := getLabelCentered("You can be gifted credit or buy some with cryptocurrency.")
descriptionSection := container.NewVBox(description1, description2, description3, description4)
return descriptionSection
}
// myIdentityType == "Host"
description1 := getLabelCentered("Below is your Host identity balance.")
description2 := getLabelCentered("It must be sufficient for you to be a Seekia host.")
description3 := getLabelCentered("Spend credit to increase your identity balance.")
description4 := getLabelCentered("You can be gifted credit or buy some with cryptocurrency.")
descriptionSection := container.NewVBox(description1, description2, description3, description4)
return descriptionSection
}
descriptionSection := getDescriptionSection()
getMyIdentityExpirationTimeDisplaySection := func()(*fyne.Container, error){
appNetworkType, err := getAppNetworkType.GetAppNetworkType()
if (err != nil) { return nil, err }
identityFound, identityIsActivated, balanceIsSufficient, _, balanceExpirationTime, err := myIdentityBalance.GetMyIdentityBalanceStatus(myIdentityHash, appNetworkType)
if (err != nil) { return nil, err }
if (identityFound == false) {
return nil, errors.New("Identity found not found after being found already.")
}
currentBalanceStatusText := getBoldLabelCentered("My Balance Status:")
getBalanceStatusIcon := func()(*canvas.Image, error){
iconSize := getCustomFyneSize(0)
if (identityIsActivated == false || balanceIsSufficient == false){
insufficientIcon, err := getFyneImageIcon("Insufficient")
if (err != nil) { return nil, err }
insufficientIcon.SetMinSize(iconSize)
return insufficientIcon, nil
}
sufficientIcon, err := getFyneImageIcon("Sufficient")
if (err != nil) { return nil, err }
sufficientIcon.SetMinSize(iconSize)
return sufficientIcon, nil
}
balanceStatusIcon, err := getBalanceStatusIcon()
if (err != nil){ return nil, err }
balanceStatusIconCentered := getFyneImageCentered(balanceStatusIcon)
getBalanceStatusText := func()string{
if (balanceIsSufficient == true){
return "Sufficient"
}
return "Insufficient"
}
balanceStatusText := getBalanceStatusText()
balanceStatusLabel := getBoldLabelCentered(balanceStatusText)
displaySection := container.NewVBox(currentBalanceStatusText, balanceStatusIconCentered, balanceStatusLabel, widget.NewSeparator())
if (balanceIsSufficient == true){
timeRemainingTitle := getLabelCentered("Time Until Expiration:")
currentTime := time.Now().Unix()
if (currentTime > balanceExpirationTime){
return nil, errors.New("Balance expiration time is less than current time while Balance is sufficient = true")
}
timeLeft := balanceExpirationTime - currentTime
timeTranslated, err := helpers.ConvertUnixTimeDurationToUnitsTimeTranslated(timeLeft, true)
if (err != nil) { return nil, err }
timeRemainingLabel := getBoldLabelCentered(timeTranslated)
displaySection.Add(timeRemainingTitle)
displaySection.Add(timeRemainingLabel)
}
refreshBalanceButton := widget.NewButtonWithIcon(translate("Refresh"), theme.ViewRefreshIcon(), func(){
//TODO: Add manualDownloads download and page to monitor it
showUnderConstructionDialog(window)
})
addTimeButton := widget.NewButtonWithIcon(translate("Add Time"), theme.MoveUpIcon(), func(){
setIncreaseMyIdentityBalancePage(window, myIdentityType, 30, currentPage)
})
buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, refreshBalanceButton, addTimeButton))
displaySection.Add(buttonsGrid)
return displaySection, nil
}
identityExpirationTimeSection, err := getMyIdentityExpirationTimeDisplaySection()
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
page := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionSection, widget.NewSeparator(), identityExpirationTimeSection)
setPageContent(page, window)
}