2024-04-11 15:51:56 +02:00
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
2024-06-11 06:59:06 +02:00
myIdentityExists , myProfileExists , _ , myProfileAttributeExists , myProfileCreationTime , err := myBroadcasts . GetAnyAttributeFromMyBroadcastProfile ( myIdentityHash , appNetworkType , "CreationTime" )
2024-04-11 15:51:56 +02:00
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 ) {
2024-06-11 06:59:06 +02:00
return errors . New ( "My Broadcast profile malformed: Missing CreationTime" )
2024-04-11 15:51:56 +02:00
}
2024-06-11 06:59:06 +02:00
myProfileCreationTimeInt64 , err := helpers . ConvertCreationTimeStringToInt64 ( myProfileCreationTime )
2024-04-11 15:51:56 +02:00
if ( err != nil ) {
2024-06-11 06:59:06 +02:00
return errors . New ( "My Broadcast profile malformed: Contains invalid creationTime: " + myProfileCreationTime )
2024-04-11 15:51:56 +02:00
}
_ , mateProfileMaximumExistenceDuration , err := getParameters . GetMateProfileMaximumExistenceDuration ( appNetworkType )
if ( err != nil ) { return err }
currentTime := time . Now ( ) . Unix ( )
2024-06-11 06:59:06 +02:00
profileExistenceTime := currentTime - myProfileCreationTimeInt64
2024-04-11 15:51:56 +02:00
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 ) {
2024-06-11 06:59:06 +02:00
exists , lastCreationTimeString , err := readProfiles . GetFormattedProfileAttributeFromRawProfileMap ( existingBroadcastProfileRawMap , "CreationTime" )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return "" , err }
if ( exists == false ) {
2024-06-11 06:59:06 +02:00
return "" , errors . New ( "My broadcast profile missing CreationTime" )
2024-04-11 15:51:56 +02:00
}
2024-06-11 06:59:06 +02:00
lastCreationTimeInt64 , err := helpers . ConvertStringToInt64 ( lastCreationTimeString )
2024-04-11 15:51:56 +02:00
if ( err != nil ) {
2024-06-11 06:59:06 +02:00
return "" , errors . New ( "My broadcast profile contains invalid CreationTime: " + lastCreationTimeString )
2024-04-11 15:51:56 +02:00
}
2024-06-11 06:59:06 +02:00
lastUpdatedTimeAgo , err := helpers . ConvertUnixTimeToTimeAgoTranslated ( lastCreationTimeInt64 , true )
2024-04-11 15:51:56 +02:00
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 }
2024-06-11 06:59:06 +02:00
if ( attributeName == "CreationTime" || attributeName == "ChatKeysLatestUpdateTime" || attributeName == "NaclKey" || attributeName == "KyberKey" ) {
2024-04-11 15:51:56 +02:00
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 {
2024-06-11 06:59:06 +02:00
if ( attributeName == "CreationTime" || attributeName == "Disabled" || attributeName == "ChatKeysLatestUpdateTime" || attributeName == "NaclKey" || attributeName == "KyberKey" ) {
2024-04-11 15:51:56 +02:00
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 )
}