2024-04-11 15:51:56 +02:00
package gui
// chatGui.go implements pages for a user to view and sort their chat conversations, send chat messages, and view their chat statistics
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/allowedText"
import "seekia/internal/appMemory"
import "seekia/internal/encoding"
import "seekia/internal/globalSettings"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/imagery"
import "seekia/internal/messaging/myChatConversations"
import "seekia/internal/messaging/myChatFilters"
import "seekia/internal/messaging/myChatFilterStatistics"
import "seekia/internal/messaging/myChatMessages"
import "seekia/internal/messaging/myConversationIndexes"
import "seekia/internal/messaging/myMessageQueue"
import "seekia/internal/messaging/myReadStatus"
import "seekia/internal/messaging/peerChatKeys"
import "seekia/internal/messaging/readMessages"
import "seekia/internal/messaging/sendMessages"
import "seekia/internal/myBlockedUsers"
import "seekia/internal/myContacts"
import "seekia/internal/myIdentity"
import "seekia/internal/mySettings"
import "seekia/internal/network/appNetworkType/getAppNetworkType"
import "seekia/internal/network/myAccountCredit"
import "seekia/internal/parameters/getParameters"
import "seekia/internal/profiles/attributeDisplay"
import "seekia/internal/profiles/myLocalProfiles"
import "seekia/internal/profiles/myProfileStatus"
import "seekia/internal/profiles/profileStorage"
import "seekia/internal/profiles/readProfiles"
import "seekia/internal/profiles/viewableProfiles"
import "time"
import "image"
import "errors"
import "strings"
import "slices"
import "sync"
//TODO: Page to prune Chat Messages, which allows deletion of messages older than X date
func setChatPage ( window fyne . Window ) {
setLoadingScreen ( window , "Chat" , "Loading chat page..." )
currentPage := func ( ) { setChatPage ( window ) }
appMemory . SetMemoryEntry ( "CurrentViewedPage" , "Chat" )
title := getPageTitleCentered ( "Chat" )
getChatPageIdentityType := func ( ) ( string , error ) {
exists , myIdentityType , err := mySettings . GetSetting ( "ChatPageIdentityType" )
if ( err != nil ) { return "" , err }
if ( exists == false ) {
return "Mate" , nil
}
if ( myIdentityType != "Mate" && myIdentityType != "Moderator" ) {
return "" , errors . New ( "Invalid ChatPageIdentityType: " + myIdentityType )
}
return myIdentityType , nil
}
myIdentityType , err := getChatPageIdentityType ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
checkIfPageHasChangedFunction := func ( ) ( bool , error ) {
exists , currentViewedPage := appMemory . GetMemoryEntry ( "CurrentViewedPage" )
if ( exists == false || currentViewedPage != "Chat" ) {
return true , nil
}
exists , pageIdentityType , err := mySettings . GetSetting ( "ChatPageIdentityType" )
if ( err != nil ) { return false , err }
if ( exists == false ) {
if ( myIdentityType == "Mate" ) {
return false , nil
}
return true , nil
}
if ( myIdentityType != pageIdentityType ) {
return true , nil
}
return false , nil
}
creditIcon , err := getFyneImageIcon ( "Funds" )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
creditButton := widget . NewButton ( "Credit" , func ( ) {
setViewMyAccountCreditPage ( window , myIdentityType , currentPage )
} )
creditButtonWithIcon := container . NewGridWithRows ( 2 , creditIcon , creditButton )
filtersIcon , err := getFyneImageIcon ( "Desires" )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
filtersButton := widget . NewButton ( "Filters" , func ( ) {
setMyChatFiltersPage ( window , myIdentityType )
} )
filtersButtonWithIcon := container . NewGridWithRows ( 2 , filtersIcon , filtersButton )
statsIcon , err := getFyneImageIcon ( "Stats" )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
statsButton := widget . NewButton ( "Stats" , func ( ) {
setChatStatisticsPage ( window , myIdentityType , currentPage )
} )
statsButtonWithIcon := container . NewGridWithRows ( 2 , statsIcon , statsButton )
contactsIcon , err := getFyneImageIcon ( "Contacts" )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
contactsButton := widget . NewButton ( "Contacts" , func ( ) {
setMyContactsPage ( window , myIdentityType , currentPage )
} )
contactsButtonWithIcon := container . NewGridWithRows ( 2 , contactsIcon , contactsButton )
getIdentityTypeLabelOrChangeButton := func ( ) ( fyne . Widget , error ) {
getModeratorModeEnabledBool := func ( ) ( bool , error ) {
exists , moderatorModeOnOffStatus , err := mySettings . GetSetting ( "ModeratorModeOnOffStatus" )
if ( err != nil ) { return false , err }
if ( exists == true && moderatorModeOnOffStatus == "On" ) {
return true , nil
}
return false , nil
}
moderatorModeEnabled , err := getModeratorModeEnabledBool ( )
if ( err != nil ) { return nil , err }
moderatorIdentityExists , _ , err := myIdentity . GetMyIdentityHash ( "Moderator" )
if ( err != nil ) { return nil , err }
if ( moderatorIdentityExists == false && moderatorModeEnabled == false ) {
mateLabel := getBoldLabel ( "Mate" )
return mateLabel , nil
}
getNextIdentityType := func ( ) string {
if ( myIdentityType == "Mate" ) {
return "Moderator"
}
return "Mate"
}
nextIdentityType := getNextIdentityType ( )
changeIdentityTypeButton := widget . NewButton ( myIdentityType , func ( ) {
err := mySettings . SetSetting ( "ChatPageIdentityType" , nextIdentityType )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setChatPage ( window ) } )
return
}
currentPage ( )
} )
return changeIdentityTypeButton , nil
}
identityTypeLabelOrChangeButton , err := getIdentityTypeLabelOrChangeButton ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
identityTypeIcon , err := getIdentityTypeIcon ( myIdentityType , - 20 )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
identityTypeLabelOrChangeButtonWithIcon := container . NewGridWithColumns ( 1 , identityTypeIcon , identityTypeLabelOrChangeButton )
pageButtonsRow := getContainerCentered ( container . NewGridWithRows ( 1 , creditButtonWithIcon , filtersButtonWithIcon , identityTypeLabelOrChangeButtonWithIcon , statsButtonWithIcon , contactsButtonWithIcon ) )
currentSortByAttribute , err := myChatConversations . GetConversationsSortByAttribute ( myIdentityType )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
sortingByLabel := getBoldLabel ( "Sorting By:" )
sortByAttributeTitle , _ , formatSortByAttributeValuesFunction , sortByAttributeUnits , unknownSortByAttributeText , err := attributeDisplay . GetProfileAttributeDisplayInfo ( currentSortByAttribute )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
sortByButton := widget . NewButton ( sortByAttributeTitle , func ( ) {
setSelectMyConversationsSortByAttributePage ( window , myIdentityType , currentPage )
} )
getSortDirectionButtonWithIcon := func ( ) ( fyne . Widget , error ) {
currentSortDirection , err := myChatConversations . GetConversationsSortDirection ( myIdentityType )
if ( err != nil ) { return nil , err }
if ( currentSortDirection == "Ascending" ) {
button := widget . NewButtonWithIcon ( translate ( "Ascending" ) , theme . MoveUpIcon ( ) , func ( ) {
appMemory . SetMemoryEntry ( "StopConversationsGenerationYesNo" , "Yes" )
_ = mySettings . SetSetting ( myIdentityType + "ChatConversations_SortDirection" , "Descending" )
_ = mySettings . SetSetting ( myIdentityType + "ChatConversationsSortedStatus" , "No" )
_ = mySettings . SetSetting ( myIdentityType + "ChatConversations_ViewIndex" , "0" )
currentPage ( )
} )
return button , nil
}
button := widget . NewButtonWithIcon ( translate ( "Descending" ) , theme . MoveDownIcon ( ) , func ( ) {
appMemory . SetMemoryEntry ( "StopConversationsGenerationYesNo" , "Yes" )
_ = mySettings . SetSetting ( myIdentityType + "ChatConversations_SortDirection" , "Ascending" )
_ = mySettings . SetSetting ( myIdentityType + "ChatConversationsSortedStatus" , "No" )
_ = mySettings . SetSetting ( myIdentityType + "ChatConversations_ViewIndex" , "0" )
currentPage ( )
} )
return button , nil
}
sortByDirectionButton , err := getSortDirectionButtonWithIcon ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
sortByRow := container . NewHBox ( layout . NewSpacer ( ) , sortingByLabel , sortByButton , sortByDirectionButton , layout . NewSpacer ( ) )
appNetworkType , err := getAppNetworkType . GetAppNetworkType ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
messagesReady , err := myChatConversations . GetMyChatConversationsReadyStatus ( myIdentityType , appNetworkType )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
if ( messagesReady == false ) {
progressPercentageBinding := binding . NewFloat ( )
progressDescriptionBinding := binding . NewString ( )
updateConversationsAndLoadingBarFunction := func ( ) {
err = myChatConversations . StartUpdatingMyConversations ( myIdentityType , appNetworkType )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , currentPage )
return
}
var encounteredError error
for {
pageHasChanged , err := checkIfPageHasChangedFunction ( )
if ( err != nil ) {
//TODO: Log error in logger
return
}
if ( pageHasChanged == true ) {
appMemory . SetMemoryEntry ( "StopBuildMyConversationsYesNo" , "Yes" )
return
}
buildEncounteredError , errorEncounteredString , buildIsStopped , conversationsAreReady , currentPercentageProgress , err := myChatConversations . GetChatConversationsBuildStatus ( myIdentityType , appNetworkType )
if ( err != nil ) {
encounteredError = err
break
}
if ( buildEncounteredError == true ) {
encounteredError = errors . New ( errorEncounteredString )
break
}
if ( buildIsStopped == true ) {
return
}
if ( conversationsAreReady == true ) {
progressPercentageBinding . Set ( 1 )
// We wait so that the loading bar will appear complete.
time . Sleep ( 100 * time . Millisecond )
setChatPage ( window )
return
}
progressPercentageBinding . Set ( currentPercentageProgress )
if ( currentPercentageProgress >= 0.50 ) {
progressDescriptionBinding . Set ( "Sorting Conversations..." )
}
time . Sleep ( 100 * time . Millisecond )
}
// This should only be reached if an error is encountered
errorToShow := errors . New ( "Error encountered while generating conversations: " + encounteredError . Error ( ) )
setErrorEncounteredPage ( window , errorToShow , currentPage )
}
loadingLabel := getBoldLabelCentered ( "Loading conversations..." )
loadingBar := getWidgetCentered ( widget . NewProgressBarWithData ( progressPercentageBinding ) )
loadingDetailsLabel := widget . NewLabelWithData ( progressDescriptionBinding )
loadingDetailsLabel . TextStyle = getFyneTextStyle_Italic ( )
loadingDetailsLabelCentered := getWidgetCentered ( loadingDetailsLabel )
page := container . NewVBox ( title , widget . NewSeparator ( ) , pageButtonsRow , widget . NewSeparator ( ) , sortByRow , widget . NewSeparator ( ) , loadingLabel , loadingBar , loadingDetailsLabelCentered )
setPageContent ( page , window )
go updateConversationsAndLoadingBarFunction ( )
return
}
getConversationsContainer := func ( ) ( * fyne . Container , error ) {
conversationsAreReady , conversationsList , err := myChatConversations . GetMyChatConversationsMapList ( myIdentityType , appNetworkType )
if ( err != nil ) { return nil , err }
if ( conversationsAreReady == false ) {
return nil , errors . New ( "Chat conversations not ready after being ready already." )
}
getRefreshResultsButtonText := func ( ) ( string , error ) {
needsRefresh , err := myChatConversations . CheckIfMyChatConversationsNeedRefresh ( myIdentityType )
if ( err != nil ) { return "" , err }
if ( needsRefresh == false ) {
return "Refresh Results" , nil
}
return "Refresh Results - Updates Available!" , nil
}
refreshButtonText , err := getRefreshResultsButtonText ( )
if ( err != nil ) { return nil , err }
refreshResultsButton := getWidgetCentered ( widget . NewButtonWithIcon ( refreshButtonText , theme . ViewRefreshIcon ( ) , func ( ) {
_ = mySettings . SetSetting ( myIdentityType + "ChatMessagesReadyStatus" , "No" )
_ = mySettings . SetSetting ( myIdentityType + "ChatConversationsGeneratedStatus" , "No" )
_ = mySettings . SetSetting ( myIdentityType + "ChatConversations_ViewIndex" , "0" )
currentPage ( )
} ) )
numberOfConversations := len ( conversationsList )
numberOfConversationsString := helpers . ConvertIntToString ( numberOfConversations )
if ( numberOfConversations == 0 ) {
lowercaseIdentityType := strings . ToLower ( myIdentityType )
noConversationsFoundText := getBoldLabelCentered ( "No " + lowercaseIdentityType + " conversations found." )
numberOfFilters , err := myChatFilters . GetNumberOfEnabledChatFilters ( myIdentityType )
if ( err != nil ) { return nil , err }
if ( numberOfFilters == 0 ) {
noConversationsFoundLabelWithRefreshButton := container . NewVBox ( noConversationsFoundText , refreshResultsButton )
return noConversationsFoundLabelWithRefreshButton , nil
}
numberOfFiltersString := helpers . ConvertIntToString ( numberOfFilters )
getActiveFiltersText := func ( ) string {
if ( numberOfFilters == 1 ) {
return "active filter"
}
return "active filters"
}
activeFiltersText := getActiveFiltersText ( )
activeFiltersLabel := getItalicLabelCentered ( numberOfFiltersString + " " + activeFiltersText )
noConversationsFoundTextWithFilters := container . NewVBox ( noConversationsFoundText , activeFiltersLabel , refreshResultsButton )
return noConversationsFoundTextWithFilters , nil
}
getViewIndex := func ( ) ( int , error ) {
exists , viewIndexString , err := mySettings . GetSetting ( myIdentityType + "ChatConversations_ViewIndex" )
if ( err != nil ) { return 0 , err }
if ( exists == false ) {
return 0 , nil
}
viewIndex , err := helpers . ConvertStringToInt ( viewIndexString )
if ( err != nil ) {
return 0 , errors . New ( "Invalid chat conversations view index: " + viewIndexString )
}
if ( viewIndex < 0 ) {
return 0 , nil
}
maximumViewIndex := numberOfConversations - 1
if ( viewIndex > maximumViewIndex ) {
return maximumViewIndex , nil
}
return viewIndex , nil
}
viewIndex , err := getViewIndex ( )
if ( err != nil ) { return nil , err }
getNavigateToBeginningButton := func ( ) fyne . Widget {
if ( numberOfConversations <= 5 || viewIndex == 0 ) {
emptyButton := widget . NewButton ( " " , nil )
return emptyButton
}
goToBeginningButton := widget . NewButtonWithIcon ( "" , theme . MediaSkipPreviousIcon ( ) , func ( ) {
mySettings . SetSetting ( myIdentityType + "ChatConversations_ViewIndex" , "0" )
currentPage ( )
} )
return goToBeginningButton
}
getNavigateToEndButton := func ( ) fyne . Widget {
emptyButton := widget . NewButton ( " " , nil )
if ( numberOfConversations <= 5 ) {
return emptyButton
}
finalPageMinimumIndex := numberOfConversations - 5
if ( viewIndex >= finalPageMinimumIndex ) {
return emptyButton
}
goToEndButton := widget . NewButtonWithIcon ( "" , theme . MediaSkipNextIcon ( ) , func ( ) {
finalPageIndexString := helpers . ConvertIntToString ( finalPageMinimumIndex )
_ = mySettings . SetSetting ( myIdentityType + "ChatConversations_ViewIndex" , finalPageIndexString )
currentPage ( )
} )
return goToEndButton
}
getNavigateLeftButton := func ( ) fyne . Widget {
if ( numberOfConversations <= 5 || viewIndex == 0 ) {
emptyButton := widget . NewButton ( " " , nil )
return emptyButton
}
button := widget . NewButtonWithIcon ( "" , theme . NavigateBackIcon ( ) , func ( ) {
newIndex := helpers . ConvertIntToString ( viewIndex - 5 )
_ = mySettings . SetSetting ( myIdentityType + "ChatConversations_ViewIndex" , newIndex )
currentPage ( )
} )
return button
}
getNavigateRightButton := func ( ) fyne . Widget {
emptyButton := widget . NewButton ( " " , nil )
if ( numberOfConversations <= 5 ) {
return emptyButton
}
finalPageMinimumIndex := numberOfConversations - 5
if ( viewIndex >= finalPageMinimumIndex ) {
return emptyButton
}
button := widget . NewButtonWithIcon ( "" , theme . NavigateNextIcon ( ) , func ( ) {
newIndex := helpers . ConvertIntToString ( viewIndex + 5 )
_ = mySettings . SetSetting ( myIdentityType + "ChatConversations_ViewIndex" , newIndex )
currentPage ( )
} )
return button
}
getViewingConversationsRow := func ( ) * fyne . Container {
getConversationOrConversationsText := func ( ) string {
if ( numberOfConversationsString == "1" ) {
return "Conversation"
}
return "Conversations"
}
conversationOrConversationsText := getConversationOrConversationsText ( )
viewingConversationsText := getBoldLabel ( "Viewing " + numberOfConversationsString + " " + conversationOrConversationsText )
if ( numberOfConversations <= 5 ) {
viewingConversationsRow := getWidgetCentered ( viewingConversationsText )
return viewingConversationsRow
}
navigateToBeginningButton := getNavigateToBeginningButton ( )
navigateToEndButton := getNavigateToEndButton ( )
navigateLeftButton := getNavigateLeftButton ( )
navigateRightButton := getNavigateRightButton ( )
viewingConversationsRow := container . NewHBox ( layout . NewSpacer ( ) , navigateToBeginningButton , navigateLeftButton , viewingConversationsText , navigateRightButton , navigateToEndButton , layout . NewSpacer ( ) )
return viewingConversationsRow
}
viewingConversationsRow := getViewingConversationsRow ( )
viewIndexOnwardsConversationsList := conversationsList [ viewIndex : ]
conversationsContainer := container . NewVBox ( )
if ( viewIndex == 0 ) {
conversationsContainer . Add ( refreshResultsButton )
conversationsContainer . Add ( widget . NewSeparator ( ) )
}
for index , conversationMap := range viewIndexOnwardsConversationsList {
resultIndex := viewIndex + index + 1
resultIndexString := helpers . ConvertIntToString ( resultIndex )
myIdentityHashString , exists := conversationMap [ "MyIdentityHash" ]
if ( exists == false ) {
return nil , errors . New ( "Malformed conversation map: Missing MyIdentityHash" )
}
theirIdentityHashString , exists := conversationMap [ "TheirIdentityHash" ]
if ( exists == false ) {
return nil , errors . New ( "Malformed conversation map: Missing TheirIdentityHash" )
}
myIdentityHash , _ , err := identity . ReadIdentityHashString ( myIdentityHashString )
if ( err != nil ) {
return nil , errors . New ( "Malformed conversation map: Contains invalid MyIdentityHash: " + myIdentityHashString )
}
theirIdentityHash , _ , err := identity . ReadIdentityHashString ( theirIdentityHashString )
if ( err != nil ) {
return nil , errors . New ( "Malformed conversation map: Contains invalid TheirIdentityHash: " + theirIdentityHashString )
}
getAllowUnknownViewableStatusBool := func ( ) bool {
if ( myIdentityType == "Mate" ) {
return false
}
return true
}
allowUnknownViewableStatusBool := getAllowUnknownViewableStatusBool ( )
theirProfileExists , _ , getAnyAttributeFromTheirProfileFunction , err := viewableProfiles . GetRetrieveAnyNewestViewableUserProfileAttributeFunction ( theirIdentityHash , appNetworkType , true , allowUnknownViewableStatusBool , true )
if ( err != nil ) { return nil , err }
getAvatarOrImage := func ( ) ( image . Image , error ) {
if ( theirProfileExists == true ) {
attributeExists , _ , photosAttributeValue , err := getAnyAttributeFromTheirProfileFunction ( "Photos" )
if ( err != nil ) { return nil , err }
if ( attributeExists == true ) {
base64PhotosList := strings . Split ( photosAttributeValue , "+" )
firstPhotoBase64 := base64PhotosList [ 0 ]
userImageObject , err := imagery . ConvertWEBPBase64StringToCroppedDownsizedImageObject ( firstPhotoBase64 )
if ( err != nil ) {
return nil , errors . New ( "Database corrupt: Contains profile with invalid photos attribute." )
}
return userImageObject , nil
}
}
getContactEmojiIdentifier := func ( ) ( int , error ) {
if ( theirProfileExists == false ) {
return 2929 , nil
}
attributeExists , _ , avatarAttributeValue , err := getAnyAttributeFromTheirProfileFunction ( "Avatar" )
if ( err != nil ) { return 0 , err }
if ( attributeExists == false ) {
return 2929 , nil
}
userEmojiIdentifier , err := helpers . ConvertStringToInt ( avatarAttributeValue )
if ( err != nil ) {
return 0 , errors . New ( "Database corrupt: Contains profile with invalid emoji attribute: " + avatarAttributeValue )
}
return userEmojiIdentifier , nil
}
contactEmojiIdentifier , err := getContactEmojiIdentifier ( )
if ( err != nil ) { return nil , err }
emojiImageObject , err := getEmojiImageObject ( contactEmojiIdentifier )
if ( err != nil ) { return nil , err }
return emojiImageObject , nil
}
getSortByAttributeBox := func ( ) ( * fyne . Container , error ) {
sortByAttributeTitleLabel := getLabelCentered ( sortByAttributeTitle )
getSortByAttributeValueText := func ( ) ( string , error ) {
if ( theirProfileExists == false ) {
return unknownSortByAttributeText , nil
}
exists , _ , attributeValue , err := getAnyAttributeFromTheirProfileFunction ( currentSortByAttribute )
if ( err != nil ) { return "" , err }
if ( exists == false ) {
return unknownSortByAttributeText , nil
}
attributeValueFormatted , err := formatSortByAttributeValuesFunction ( attributeValue )
if ( err != nil ) { return "" , err }
result := attributeValueFormatted + sortByAttributeUnits
return result , nil
}
sortByAttributeValueText , err := getSortByAttributeValueText ( )
if ( err != nil ) { return nil , err }
attributeValueLabel := getBoldLabelCentered ( sortByAttributeValueText )
sortByAttributeBox := getContainerBoxed ( container . NewVBox ( sortByAttributeTitleLabel , attributeValueLabel ) )
return sortByAttributeBox , nil
}
getReadUnreadStatusButton := func ( ) ( fyne . Widget , error ) {
conversationReadUnreadStatus , err := myReadStatus . GetConversationReadUnreadStatus ( myIdentityHash , theirIdentityHash , appNetworkType )
if ( err != nil ) { return nil , err }
if ( conversationReadUnreadStatus == "Unread" ) {
unreadStatusButton := widget . NewButtonWithIcon ( "Unread" , theme . WarningIcon ( ) , func ( ) {
dialogTitle := translate ( "Conversation Is Unread" )
dialogMessageA := getLabelCentered ( translate ( "This conversation is unread." ) )
dialogMessageB := getLabelCentered ( translate ( "It contains new messages for you to read." ) )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB )
dialog . ShowCustom ( dialogTitle , translate ( "Close" ) , dialogContent , window )
} )
unreadStatusButton . Importance = widget . HighImportance
return unreadStatusButton , nil
}
readStatusButton := widget . NewButtonWithIcon ( "Read" , theme . VisibilityIcon ( ) , func ( ) {
dialogTitle := translate ( "Conversation Is Read" )
dialogMessageA := getLabelCentered ( translate ( "This conversation is read." ) )
dialogMessageB := getLabelCentered ( translate ( "It does not contain any new messages for you to read." ) )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB )
dialog . ShowCustom ( dialogTitle , translate ( "Close" ) , dialogContent , window )
} )
return readStatusButton , nil
}
getUserName := func ( ) ( string , error ) {
if ( theirProfileExists == false ) {
// We haven't downloaded their profile yet
// Their profile may not exist on the network
return "Unknown" , nil
}
exists , _ , username , err := getAnyAttributeFromTheirProfileFunction ( "Username" )
if ( err != nil ) { return "" , err }
if ( exists == false ) {
return "Anonymous" , nil
}
theirUsernameTrimmed , _ , err := helpers . TrimAndFlattenString ( username , 15 )
if ( err != nil ) { return "" , err }
return theirUsernameTrimmed , nil
}
avatarOrImage , err := getAvatarOrImage ( )
if ( err != nil ) { return nil , err }
userFyneImage := canvas . NewImageFromImage ( avatarOrImage )
userFyneImage . FillMode = canvas . ImageFillContain
imageSize := getCustomFyneSize ( 10 )
userFyneImage . SetMinSize ( imageSize )
userImageBoxed := getFyneImageBoxed ( userFyneImage )
currentTheirUsername , err := getUserName ( )
if ( err != nil ) { return nil , err }
userNameLabel := getBoldLabelCentered ( currentTheirUsername )
theirIdentityHashTrimmed , _ , err := helpers . TrimAndFlattenString ( theirIdentityHashString , 15 )
if ( err != nil ) { return nil , err }
theirIdentityHashLabel := widget . NewLabel ( theirIdentityHashTrimmed )
userNameColumn := getContainerBoxed ( container . NewVBox ( userNameLabel , theirIdentityHashLabel ) )
sortByAttributeBox , err := getSortByAttributeBox ( )
if ( err != nil ) { return nil , err }
readUnreadStatusButton , err := getReadUnreadStatusButton ( )
if ( err != nil ) { return nil , err }
chatButton := widget . NewButtonWithIcon ( "Chat" , theme . MailComposeIcon ( ) , func ( ) {
setViewAConversationPage ( window , theirIdentityHash , true , currentPage )
} )
resultIndexBoldLabel := getBoldLabel ( resultIndexString + "." )
conversationRow := container . NewHBox ( layout . NewSpacer ( ) , resultIndexBoldLabel , userImageBoxed , userNameColumn , sortByAttributeBox , readUnreadStatusButton , chatButton , layout . NewSpacer ( ) )
conversationsContainer . Add ( conversationRow )
if ( index >= 4 ) {
break
}
conversationsContainer . Add ( widget . NewSeparator ( ) )
}
conversationsContainerScrollable := container . NewVScroll ( conversationsContainer )
conversationsContainerBoxed := getWidgetBoxed ( conversationsContainerScrollable )
resultsContainer := container . NewBorder ( viewingConversationsRow , nil , nil , nil , conversationsContainerBoxed )
return resultsContainer , nil
}
conversationsContent , err := getConversationsContainer ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , func ( ) { setHomePage ( window ) } )
return
}
pageHeader := container . NewVBox ( title , widget . NewSeparator ( ) , pageButtonsRow , widget . NewSeparator ( ) , sortByRow , widget . NewSeparator ( ) )
content := container . NewBorder ( pageHeader , nil , nil , nil , conversationsContent )
setPageContent ( content , window )
}
func setSelectMyConversationsSortByAttributePage ( window fyne . Window , identityType string , previousPage func ( ) ) {
appMemory . SetMemoryEntry ( "CurrentViewedPage" , "SortBySelectPage_Chat" )
title := getPageTitleCentered ( "Select Sort By Attribute" )
backButton := getBackButtonCentered ( previousPage )
description := getLabelCentered ( "Select the attribute to sort your chat conversations by." )
getPageContent := func ( ) ( * fyne . Container , error ) {
if ( identityType == "Mate" ) {
generalAttributeButtonsGrid := container . NewGridWithColumns ( 1 )
physicalAttributeButtonsGrid := container . NewGridWithColumns ( 1 )
lifestyleAttributeButtonsGrid := container . NewGridWithColumns ( 1 )
mentalAttributeButtonsGrid := container . NewGridWithColumns ( 1 )
addAttributeSelectButton := func ( attributeType string , attributeName string , sortDirection string ) error {
attributeTitle , _ , _ , _ , _ , err := attributeDisplay . GetProfileAttributeDisplayInfo ( attributeName )
if ( err != nil ) { return err }
attributeButton := widget . NewButton ( attributeTitle , func ( ) {
_ = mySettings . SetSetting ( identityType + "ChatConversationsSortedStatus" , "No" )
_ = mySettings . SetSetting ( identityType + "ChatConversations_SortByAttribute" , attributeName )
_ = mySettings . SetSetting ( identityType + "ChatConversations_SortDirection" , sortDirection )
_ = mySettings . SetSetting ( identityType + "ChatConversations_ViewIndex" , "0" )
previousPage ( )
} )
if ( attributeType == "General" ) {
generalAttributeButtonsGrid . Add ( attributeButton )
} else if ( attributeType == "Physical" ) {
physicalAttributeButtonsGrid . Add ( attributeButton )
} else if ( attributeType == "Lifestyle" ) {
lifestyleAttributeButtonsGrid . Add ( attributeButton )
} else if ( attributeType == "Mental" ) {
mentalAttributeButtonsGrid . Add ( attributeButton )
} else {
return errors . New ( "addSelectButton called with invalid attributeType: " + attributeType )
}
return nil
}
generalLabel := getBoldLabelCentered ( "General" )
err := addAttributeSelectButton ( "General" , "MatchScore" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "General" , "Distance" , "Ascending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "General" , "SearchTermsCount" , "Descending" )
if ( err != nil ) { return nil , err }
physicalLabel := getBoldLabelCentered ( "Physical" )
err = addAttributeSelectButton ( "Physical" , "Age" , "Ascending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "Height" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "BodyFat" , "Ascending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "BodyMuscle" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "SkinColor" , "Ascending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "HairTexture" , "Ascending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "RacialSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "EyeColorSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "EyeColorGenesSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "HairColorSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "HairColorGenesSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "SkinColorSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "SkinColorGenesSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "HairTextureSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "HairTextureGenesSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "FacialStructureGenesSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "23andMe_AncestralSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "23andMe_MaternalHaplogroupSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "23andMe_PaternalHaplogroupSimilarity" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "OffspringProbabilityOfAnyMonogenicDisease" , "Ascending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "TotalPolygenicDiseaseRiskScore" , "Ascending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Physical" , "OffspringTotalPolygenicDiseaseRiskScore" , "Ascending" )
if ( err != nil ) { return nil , err }
offspringLactoseToleranceProbabilityButton := widget . NewButton ( "Offspring Lactose Tolerance Probability" , func ( ) {
//TODO
showUnderConstructionDialog ( window )
} )
physicalAttributeButtonsGrid . Add ( offspringLactoseToleranceProbabilityButton )
offspringCurlyHairProbabilityButton := widget . NewButton ( "Offspring Curly Hair Probability" , func ( ) {
//TODO
showUnderConstructionDialog ( window )
} )
physicalAttributeButtonsGrid . Add ( offspringCurlyHairProbabilityButton )
offspringStraightHairProbabilityButton := widget . NewButton ( "Offspring Straight Hair Probability" , func ( ) {
//TODO
showUnderConstructionDialog ( window )
} )
physicalAttributeButtonsGrid . Add ( offspringStraightHairProbabilityButton )
err = addAttributeSelectButton ( "Physical" , "23andMe_NeanderthalVariants" , "Descending" )
if ( err != nil ) { return nil , err }
lifestyleLabel := getBoldLabelCentered ( "Lifestyle" )
err = addAttributeSelectButton ( "Lifestyle" , "WealthInGold" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "Fame" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "FruitRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "VegetablesRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "NutsRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "GrainsRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "DairyRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "SeafoodRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "BeefRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "PorkRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "PoultryRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "EggsRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "BeansRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "AlcoholFrequency" , "Ascending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "TobaccoFrequency" , "Ascending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Lifestyle" , "CannabisFrequency" , "Ascending" )
if ( err != nil ) { return nil , err }
mentalLabel := getBoldLabelCentered ( "Mental" )
err = addAttributeSelectButton ( "Mental" , "PetsRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Mental" , "CatsRating" , "Descending" )
if ( err != nil ) { return nil , err }
err = addAttributeSelectButton ( "Mental" , "DogsRating" , "Descending" )
if ( err != nil ) { return nil , err }
generalAttributeButtonsGridCentered := getContainerCentered ( generalAttributeButtonsGrid )
physicalAttributeButtonsGridCentered := getContainerCentered ( physicalAttributeButtonsGrid )
lifestyleAttributeButtonsGridCentered := getContainerCentered ( lifestyleAttributeButtonsGrid )
mentalAttributeButtonsGridCentered := getContainerCentered ( mentalAttributeButtonsGrid )
pageContent := container . NewVBox ( generalLabel , widget . NewSeparator ( ) , generalAttributeButtonsGridCentered , widget . NewSeparator ( ) , physicalLabel , widget . NewSeparator ( ) , physicalAttributeButtonsGridCentered , widget . NewSeparator ( ) , lifestyleLabel , widget . NewSeparator ( ) , lifestyleAttributeButtonsGridCentered , widget . NewSeparator ( ) , mentalLabel , widget . NewSeparator ( ) , mentalAttributeButtonsGridCentered )
return pageContent , nil
} else if ( identityType == "Moderator" ) {
getSelectButton := func ( attributeTitle string , attributeName string , sortDirection string ) fyne . Widget {
button := widget . NewButton ( translate ( attributeTitle ) , func ( ) {
_ = mySettings . SetSetting ( "ModeratorChatConversationsSortedStatus" , "No" )
_ = mySettings . SetSetting ( "ModeratorChatConversations_SortByAttribute" , attributeName )
_ = mySettings . SetSetting ( "ModeratorChatConversations_SortDirection" , sortDirection )
_ = mySettings . SetSetting ( "ModeratorChatConversations_ViewIndex" , "0" )
previousPage ( )
} )
return button
}
identityScoreButton := getSelectButton ( "Identity Score" , "IdentityScore" , "Descending" )
buttonsGrid := container . NewGridWithColumns ( 1 , identityScoreButton )
pageContent := getContainerCentered ( buttonsGrid )
return pageContent , nil
}
return nil , errors . New ( "setSelectMyConversationsSortByAttributePage called with invalid identityType: " + identityType )
}
pageContent , err := getPageContent ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , description , widget . NewSeparator ( ) , pageContent )
setPageContent ( page , window )
}
func setMyChatFiltersPage ( window fyne . Window , identityType string ) {
appMemory . SetMemoryEntry ( "CurrentViewedPage" , "ChatFilters" )
title := getPageTitleCentered ( identityType + " Chat Filters" )
previousPage := func ( ) { setChatPage ( window ) }
backButton := getBackButtonCentered ( previousPage )
filtersDescription := getLabelCentered ( "Choose your chat conversation filters." )
getChatFiltersGrid := func ( ) ( * fyne . Container , error ) {
filterDescriptionsColumn := container . NewVBox ( )
filterChecksColumn := container . NewVBox ( )
addChatFilterRow := func ( chatFilterDescription string , chatFilterName string ) error {
currentStatus , err := myChatFilters . GetChatFilterOnOffStatus ( identityType , chatFilterName )
if ( err != nil ) { return err }
chatFilterDescriptionLabel := getBoldLabelCentered ( chatFilterDescription )
chatFilterCheck := widget . NewCheck ( "" , func ( response bool ) {
err := myChatFilters . SetChatFilterOnOffStatus ( identityType , chatFilterName , response )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
}
} )
if ( currentStatus == true ) {
chatFilterCheck . Checked = true
}
filterDescriptionsColumn . Add ( chatFilterDescriptionLabel )
filterChecksColumn . Add ( chatFilterCheck )
filterDescriptionsColumn . Add ( widget . NewSeparator ( ) )
filterChecksColumn . Add ( widget . NewSeparator ( ) )
return nil
}
err := addChatFilterRow ( "Only show conversations with my contacts." , "ShowMyContactsOnly" )
if ( err != nil ) { return nil , err }
if ( identityType == "Mate" ) {
err := addChatFilterRow ( "Only show conversations with my matches." , "ShowMyMatchesOnly" )
if ( err != nil ) { return nil , err }
}
err = addChatFilterRow ( "Only show conversations with users who have messaged me." , "ShowHasMessagedMeOnly" )
if ( err != nil ) { return nil , err }
if ( identityType == "Mate" ) {
err = addChatFilterRow ( "Only show conversations with users I have liked." , "OnlyShowLikedUsers" )
if ( err != nil ) { return nil , err }
err = addChatFilterRow ( "Hide conversations with users I have ignored." , "HideIgnoredUsers" )
if ( err != nil ) { return nil , err }
}
//TODO: Hide conversations with users who are banned
chatFiltersGrid := container . NewHBox ( layout . NewSpacer ( ) , filterDescriptionsColumn , filterChecksColumn , layout . NewSpacer ( ) )
return chatFiltersGrid , nil
}
chatFiltersGrid , err := getChatFiltersGrid ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , filtersDescription , widget . NewSeparator ( ) , chatFiltersGrid )
setPageContent ( page , window )
}
func setViewAConversationPage ( window fyne . Window , theirIdentityHash [ 16 ] byte , resetConversationIndex bool , previousPage func ( ) ) {
setLoadingScreen ( window , "View Conversation" , "Loading conversation..." )
currentPage := func ( ) { setViewAConversationPage ( window , theirIdentityHash , false , previousPage ) }
currentPageWithNewestView := func ( ) { setViewAConversationPage ( window , theirIdentityHash , true , previousPage ) }
title := getPageTitleCentered ( "Viewing Conversation" )
backButton := getBackButtonCentered ( previousPage )
theirIdentityHashString , theirIdentityType , err := identity . EncodeIdentityHashBytesToString ( theirIdentityHash )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
if ( theirIdentityType == "Host" ) {
title := getPageTitleCentered ( "Chat" )
description1 := getBoldLabelCentered ( "Recipient is a Host profile." )
description2 := getLabelCentered ( "They cannot be chatted with." )
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , description1 , description2 )
setPageContent ( page , window )
return
}
myIdentityExists , myIdentityHash , err := myIdentity . GetMyIdentityHash ( theirIdentityType )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
if ( myIdentityExists == false ) {
// This should not happen, because conversations are generated from user's existing identitites.
// A user's chat conversations should be regenerated whenever a user deletes/changes their identity
err := mySettings . SetSetting ( theirIdentityType + "ChatConversationsGeneratedStatus" , "No" )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
title := getPageTitleCentered ( "Create Chat Identity" )
description1 := getBoldLabelCentered ( "Recipient " + theirIdentityHashString + " is a " + theirIdentityType + " identity." )
description2 := getLabelCentered ( "You do not have a " + theirIdentityType + " identity." )
description3 := getLabelCentered ( "Create your " + theirIdentityType + " identity to chat from?" )
createIdentityButton := getWidgetCentered ( widget . NewButtonWithIcon ( "Create " + theirIdentityType + " Identity" , theme . NavigateNextIcon ( ) , func ( ) {
setChooseNewIdentityHashPage ( window , theirIdentityType , currentPage , currentPage )
} ) )
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , description1 , description2 , description3 , createIdentityButton )
setPageContent ( page , window )
return
}
if ( myIdentityHash == theirIdentityHash ) {
// This should not happen
setErrorEncounteredPage ( window , errors . New ( "Trying to chat with myself." ) , previousPage )
return
}
myIdentityType := theirIdentityType
appNetworkType , err := getAppNetworkType . GetAppNetworkType ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
getTheyAreDisabledStatus := func ( ) ( bool , error ) {
theirProfileExists , _ , _ , _ , _ , theirNewestProfileRawMap , err := profileStorage . GetNewestUserProfile ( theirIdentityHash , appNetworkType )
if ( err != nil ) { return false , err }
if ( theirProfileExists == false ) {
return false , nil
}
theyAreDisabled , _ , err := readProfiles . GetFormattedProfileAttributeFromRawProfileMap ( theirNewestProfileRawMap , "Disabled" )
if ( err != nil ) { return false , err }
if ( theyAreDisabled == true ) {
return true , nil
}
return false , nil
}
theyAreDisabled , err := getTheyAreDisabledStatus ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
getIAmDisabledStatus := func ( ) ( bool , error ) {
exists , iAmDisabled , err := myLocalProfiles . GetProfileData ( myIdentityType , "Disabled" )
if ( err != nil ) { return false , err }
if ( exists == true && iAmDisabled == "Yes" ) {
return true , nil
}
return false , nil
}
iAmDisabled , err := getIAmDisabledStatus ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
getRecipientInfoColumn := func ( ) ( * fyne . Container , error ) {
chattingWithTitle := getBoldLabelCentered ( "Chatting With:" )
getRecipientUserName := func ( ) ( string , error ) {
theirProfileExists , _ , _ , _ , _ , theirRawProfileMap , err := profileStorage . GetNewestUserProfile ( theirIdentityHash , appNetworkType )
if ( err != nil ) { return "" , err }
if ( theirProfileExists == false ) {
result := translate ( "Unknown" )
return result , nil
}
exists , username , err := readProfiles . GetFormattedProfileAttributeFromRawProfileMap ( theirRawProfileMap , "Username" )
if ( err != nil ) { return "" , err }
if ( exists == false ) {
result := translate ( "Anonymous" )
return result , nil
}
trimmedUsername , _ , err := helpers . TrimAndFlattenString ( username , 15 )
if ( err != nil ) { return "" , err }
return trimmedUsername , nil
}
recipientUserName , err := getRecipientUserName ( )
if ( err != nil ) { return nil , err }
recipientUsernameLabel := getBoldItalicLabelCentered ( recipientUserName )
trimmedIdentityHash , _ , err := helpers . TrimAndFlattenString ( theirIdentityHashString , 15 )
if ( err != nil ) { return nil , err }
theirIdentityHashLabel := getLabelCentered ( trimmedIdentityHash )
getViewTheirProfileButton := func ( ) ( * fyne . Container , error ) {
if ( theyAreDisabled == true ) {
disabledButton := getWidgetCentered ( widget . NewButtonWithIcon ( "Disabled" , theme . VisibilityOffIcon ( ) , func ( ) {
dialogTitle := translate ( "User Is Disabled" )
dialogMessageA := getLabelCentered ( translate ( "This user has disabled their profile." ) )
dialogMessageB := getLabelCentered ( translate ( "They have no profile to view." ) )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB )
dialog . ShowCustom ( dialogTitle , translate ( "Close" ) , dialogContent , window )
} ) )
return disabledButton , nil
}
viewProfileButton := getWidgetCentered ( widget . NewButtonWithIcon ( "View Profile" , theme . VisibilityIcon ( ) , func ( ) {
setViewPeerProfilePageFromIdentityHash ( window , theirIdentityHash , currentPage )
} ) )
return viewProfileButton , nil
}
viewRecipientProfileButton , err := getViewTheirProfileButton ( )
if ( err != nil ) { return nil , err }
getMyContactSection := func ( ) ( * fyne . Container , error ) {
// Will show add to my contacts if not a contact
// will show contact name if is already a contact
contactExists , contactName , _ , _ , _ , err := myContacts . GetMyContactDetails ( theirIdentityHash )
if ( err != nil ) { return nil , err }
if ( contactExists == false ) {
addContactButton := container . NewVBox ( widget . NewButtonWithIcon ( "Add Contact" , theme . ContentAddIcon ( ) , func ( ) {
setAddContactFromIdentityHashPage ( window , theirIdentityHash , currentPage , currentPage )
} ) )
return addContactButton , nil
}
trimmedContactName , _ , err := helpers . TrimAndFlattenString ( contactName , 15 )
if ( err != nil ) { return nil , err }
contactNameLabel := getItalicLabelCentered ( "Contact Name:" )
contactNameText := getWidgetCentered ( getBoldItalicLabel ( trimmedContactName ) )
contactSection := container . NewVBox ( contactNameLabel , contactNameText )
return contactSection , nil
}
myContactSection , err := getMyContactSection ( )
if ( err != nil ) { return nil , err }
actionsButton := widget . NewButtonWithIcon ( "Actions" , theme . ContentRedoIcon ( ) , func ( ) {
setViewPeerActionsPage ( window , theirIdentityHash , currentPage )
} )
recipientInfoColumn := getContainerBoxed ( container . NewVBox ( chattingWithTitle , widget . NewSeparator ( ) , recipientUsernameLabel , theirIdentityHashLabel , viewRecipientProfileButton , widget . NewSeparator ( ) , myContactSection , widget . NewSeparator ( ) , actionsButton ) )
return recipientInfoColumn , nil
}
recipientInfoColumn , err := getRecipientInfoColumn ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
getMyInfoColumn := func ( ) ( * fyne . Container , error ) {
myIdentityLabel := getBoldLabelCentered ( "My Identity:" )
getMyUserName := func ( ) ( string , error ) {
exists , username , err := myLocalProfiles . GetProfileData ( myIdentityType , "Username" )
if ( err != nil ) { return "" , err }
if ( exists == false ) {
return "Anonymous" , nil
}
trimmedUsername , _ , err := helpers . TrimAndFlattenString ( username , 15 )
if ( err != nil ) { return "" , err }
return trimmedUsername , nil
}
myUserName , err := getMyUserName ( )
if ( err != nil ) { return nil , err }
myUsernameLabel := getWidgetCentered ( getBoldItalicLabel ( myUserName ) )
myIdentityHashString , _ , err := identity . EncodeIdentityHashBytesToString ( myIdentityHash )
if ( err != nil ) { return nil , err }
trimmedIdentityHash , _ , err := helpers . TrimAndFlattenString ( myIdentityHashString , 15 )
if ( err != nil ) { return nil , err }
myIdentityHashLabel := getLabelCentered ( trimmedIdentityHash )
getViewMyProfileButton := func ( ) ( * fyne . Container , error ) {
if ( iAmDisabled == true ) {
disabledButton := getWidgetCentered ( widget . NewButtonWithIcon ( "Disabled" , theme . VisibilityOffIcon ( ) , func ( ) {
title := translate ( "Your Profile Is Disabled" )
dialogMessageA := getLabelCentered ( translate ( "You have disabled your " + myIdentityType + " profile." ) )
dialogMessageB := getLabelCentered ( translate ( "You have no profile to view." ) )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB )
dialog . ShowCustom ( title , translate ( "Close" ) , dialogContent , window )
} ) )
return disabledButton , nil
}
viewProfileButton := getWidgetCentered ( widget . NewButtonWithIcon ( "View Profile" , theme . VisibilityIcon ( ) , func ( ) {
setViewMyProfilePage ( window , myIdentityType , "Public" , currentPage )
} ) )
return viewProfileButton , nil
}
viewMyProfileButton , err := getViewMyProfileButton ( )
if ( err != nil ) { return nil , err }
myIdentityTypeIcon , err := getIdentityTypeIcon ( myIdentityType , 0 )
if ( err != nil ) { return nil , err }
myIdentityTypeIconCentered := getFyneImageCentered ( myIdentityTypeIcon )
myIdentityTypeLabel := getBoldLabelCentered ( myIdentityType )
myInfoColumn := getContainerBoxed ( container . NewVBox ( myIdentityLabel , widget . NewSeparator ( ) , myUsernameLabel , myIdentityHashLabel , viewMyProfileButton , widget . NewSeparator ( ) , myIdentityTypeIconCentered , myIdentityTypeLabel ) )
return myInfoColumn , nil
}
myInfoColumn , err := getMyInfoColumn ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
getConversationContainer := func ( ) ( * fyne . Container , error ) {
myIdentityFound , conversationExists , messagesList , _ , _ , iHaveRejectedThem , _ , _ , theyHaveRejectedMe , _ , err := myChatMessages . GetMyConversationInfoAndSortedMessagesList ( myIdentityHash , theirIdentityHash , appNetworkType )
if ( err != nil ) { return nil , err }
if ( myIdentityFound == false ) {
return nil , errors . New ( "My identity not found after being found already." )
}
// We use this list to store the non seen indicating messages
// Seen messages are messages that are used to indicate that a user has seen another user's message
// They are not shown to the user as messages, but are instead are shown as a color status next to each message
nonSeenIndicatingMessagesList := make ( [ ] myChatMessages . ConversationMessage , 0 )
// We have to split seen indicating messages into two maps: Ones which I have seen, and ones which they have seen
// Otherwise, a malicious conversator could make us falsely believe
// we had sent a seen indicating message for one of the messages they sent us.
//
// This map stores the message hashes which have been seen by me
mySeenMessagesMap := make ( map [ [ 26 ] byte ] struct { } )
// This map stores the message hashes which they have seen
theirSeenMessagesMap := make ( map [ [ 26 ] byte ] struct { } )
for _ , messageObject := range messagesList {
currentMessageContent := messageObject . Communication
seenMessageHashHex , isSeenMessage := strings . CutPrefix ( currentMessageContent , ">!>Seen=" )
if ( isSeenMessage == false ) {
// This message is not a seen indicating message
nonSeenIndicatingMessagesList = append ( nonSeenIndicatingMessagesList , messageObject )
continue
}
seenMessageHash , err := readMessages . ReadMessageHashHex ( seenMessageHashHex )
if ( err != nil ) {
// This should not happen
// Seekia should prevent anyone from sending a message with this prefix that does not contain a valid message hash
// The myChatMessages package should also reject any invalid messages from being added.
return nil , errors . New ( "GetMyConversationInfoAndSortedMessagesList returning message which contains invalid seen indicating message: " + seenMessageHashHex )
}
currentMessageIAmSender := messageObject . IAmSender
if ( currentMessageIAmSender == true ) {
mySeenMessagesMap [ seenMessageHash ] = struct { } { }
} else {
theirSeenMessagesMap [ seenMessageHash ] = struct { } { }
}
}
// We use this function to determine if a message in the conversation has been seen by the user it was sent to
// Inputs:
// -bool: This is the IAmSender for the message which we are trying to get the Seen status of
// -[26]byte: This is the message hash we are trying to get the Seen status of
// Output:
// -bool: Message is seen
getMessageIsSeenStatus := func ( iAmSender bool , messageHash [ 26 ] byte ) bool {
if ( iAmSender == true ) {
_ , exists := theirSeenMessagesMap [ messageHash ]
return exists
}
_ , exists := mySeenMessagesMap [ messageHash ]
return exists
}
// Conversation view index is the index of the first message to display (ignoring seen-indicating messages)
// Will show the next 5 messages after the view index, unless less than 5 messages exist, in which case it will show whatever is left
// If more than five messages exist, the first page will show the remainder of messages, which may not be 5, because every other page displays exactly 5 messages
//TODO: Conversation view index should actually be calculated from a message hash/message identifier
// Otherwise, new messages will shift the current conversation view index
// For example, 5 messages are sent, so the conversation view index is now shifted by 1 page
numberOfMessages := len ( nonSeenIndicatingMessagesList )
getMaximumViewIndex := func ( ) int {
if ( numberOfMessages <= 5 ) {
return 0
}
// Maximum view index = index of first message for the newest page of messages
maximumViewIndex := numberOfMessages - 5
return maximumViewIndex
}
maximumViewIndex := getMaximumViewIndex ( )
getConversationViewIndex := func ( ) ( int , error ) {
// We show 5 messages per page
if ( numberOfMessages <= 5 ) {
return 0 , nil
}
cacheConversationIndexExists , cacheConversationIndex , err := myConversationIndexes . GetConversationMessageViewIndex ( myIdentityHash , theirIdentityHash , appNetworkType )
if ( err != nil ) { return 0 , err }
if ( cacheConversationIndexExists == false || cacheConversationIndex > maximumViewIndex || resetConversationIndex == true ) {
err := myConversationIndexes . SetConversationMessageViewIndex ( myIdentityHash , theirIdentityHash , appNetworkType , maximumViewIndex )
if ( err != nil ) { return 0 , err }
return maximumViewIndex , nil
}
if ( cacheConversationIndex <= 0 ) {
return 0 , nil
}
return cacheConversationIndex , nil
}
conversationViewIndex , err := getConversationViewIndex ( )
if ( err != nil ) { return nil , err }
err = myConversationIndexes . SetConversationMessageViewIndex ( myIdentityHash , theirIdentityHash , appNetworkType , conversationViewIndex )
if ( err != nil ) { return nil , err }
// Last Message Index is the index of the last message on display
getLastMessageIndex := func ( ) int {
if ( numberOfMessages == 0 ) {
return 0
}
if ( numberOfMessages <= 5 ) {
finalMessageIndex := numberOfMessages - 1
return finalMessageIndex
}
if ( conversationViewIndex == 0 ) {
// First page will not show next five messages
// It will show the remainder of messages, because all other pages show 5 messages each
dividedFiveRemainder := numberOfMessages % 5
if ( dividedFiveRemainder != 0 ) {
return dividedFiveRemainder - 1
}
}
return conversationViewIndex + 4
}
lastMessageIndex := getLastMessageIndex ( )
getConversationTopBar := func ( ) ( * fyne . Container , error ) {
theyAreBlocked , _ , _ , _ , err := myBlockedUsers . CheckIfUserIsBlocked ( theirIdentityHash )
if ( err != nil ) { return nil , err }
if ( theyAreBlocked == true ) {
blockedDescription := getBoldLabel ( "You Have Blocked This User." )
blockedHelpButton := widget . NewButtonWithIcon ( "" , theme . InfoIcon ( ) , func ( ) {
setViewPeerActionsPage ( window , theirIdentityHash , currentPage )
} )
blockedDescriptionRow := container . NewHBox ( layout . NewSpacer ( ) , blockedDescription , blockedHelpButton , layout . NewSpacer ( ) )
return blockedDescriptionRow , nil
}
if ( conversationExists == false ) {
topBar := getBoldLabelCentered ( "No messages exist." )
return topBar , nil
}
getTopBar := func ( ) * fyne . Container {
getMessagesViewingText := func ( ) string {
if ( numberOfMessages == 1 || lastMessageIndex == 0 ) {
return "Viewing 1"
}
totalMessagesString := helpers . ConvertIntToString ( numberOfMessages )
if ( numberOfMessages <= 5 ) {
return "Viewing 1 - " + totalMessagesString
}
startNumberString := helpers . ConvertIntToString ( conversationViewIndex + 1 )
lastMessageViewingString := helpers . ConvertIntToString ( lastMessageIndex + 1 )
return "Viewing " + startNumberString + " - " + lastMessageViewingString
}
messagesViewingText := getMessagesViewingText ( )
numberOfMessagesTotalString := helpers . ConvertIntToString ( numberOfMessages )
numberOfMessagesText := messagesViewingText + " of " + numberOfMessagesTotalString + " messages"
if ( numberOfMessages <= 5 ) {
// No navigation buttons needed
topBar := getBoldLabelCentered ( numberOfMessagesText )
return topBar
}
numberOfMessagesLabel := getBoldLabel ( numberOfMessagesText )
getNavigateToBeginningButton := func ( ) fyne . Widget {
if ( conversationViewIndex <= 0 ) {
blankButton := widget . NewButton ( " " , nil )
return blankButton
}
viewBeginningButton := widget . NewButtonWithIcon ( "" , theme . MediaFastRewindIcon ( ) , func ( ) {
err := myConversationIndexes . SetConversationMessageViewIndex ( myIdentityHash , theirIdentityHash , appNetworkType , 0 )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
currentPage ( )
} )
return viewBeginningButton
}
getViewOlderMessagesButton := func ( ) fyne . Widget {
if ( conversationViewIndex <= 0 ) {
blankButton := widget . NewButton ( " " , nil )
return blankButton
}
viewOlderButton := widget . NewButtonWithIcon ( "" , theme . NavigateBackIcon ( ) , func ( ) {
newViewIndex := conversationViewIndex - 5
err := myConversationIndexes . SetConversationMessageViewIndex ( myIdentityHash , theirIdentityHash , appNetworkType , newViewIndex )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
currentPage ( )
} )
return viewOlderButton
}
getViewNewerMessagesButton := func ( ) fyne . Widget {
if ( conversationViewIndex >= maximumViewIndex ) {
blankButton := widget . NewButton ( " " , nil )
return blankButton
}
viewNewerButton := widget . NewButtonWithIcon ( "" , theme . NavigateNextIcon ( ) , func ( ) {
newViewIndex := lastMessageIndex + 1
err := myConversationIndexes . SetConversationMessageViewIndex ( myIdentityHash , theirIdentityHash , appNetworkType , newViewIndex )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
currentPage ( )
} )
return viewNewerButton
}
getNavigateToEndButton := func ( ) fyne . Widget {
if ( conversationViewIndex >= maximumViewIndex ) {
blankButton := widget . NewButton ( " " , nil )
return blankButton
}
navigateToEndButton := widget . NewButtonWithIcon ( "" , theme . MediaFastForwardIcon ( ) , currentPageWithNewestView )
return navigateToEndButton
}
navigateToBeginningButton := getNavigateToBeginningButton ( )
viewOlderMessagesButton := getViewOlderMessagesButton ( )
viewNewerMessagesButton := getViewNewerMessagesButton ( )
navigateToEndButton := getNavigateToEndButton ( )
leftSideNavigationButtons := container . NewGridWithRows ( 1 , navigateToBeginningButton , viewOlderMessagesButton )
rightSideNavigationButton := container . NewGridWithRows ( 1 , viewNewerMessagesButton , navigateToEndButton )
topBar := container . NewHBox ( layout . NewSpacer ( ) , leftSideNavigationButtons , numberOfMessagesLabel , rightSideNavigationButton , layout . NewSpacer ( ) )
return topBar
}
topBar := getTopBar ( )
if ( iHaveRejectedThem == false && theyHaveRejectedMe == false ) {
return topBar , nil
}
getRejectionInfoDescription := func ( ) string {
if ( iHaveRejectedThem == true && theyHaveRejectedMe == false ) {
return "You have rejected this user."
}
if ( iHaveRejectedThem == false && theyHaveRejectedMe == true ) {
return "This user has rejected you."
}
return "You have rejected eachother."
}
rejectionInfoDescription := getRejectionInfoDescription ( )
rejectionInfoButton := widget . NewButtonWithIcon ( "" , theme . InfoIcon ( ) , func ( ) {
setGreetAndRejectExplainerPage ( window , currentPage )
} )
rejectionInfoLabel := getBoldLabel ( rejectionInfoDescription )
rejectionInfoRow := container . NewHBox ( layout . NewSpacer ( ) , rejectionInfoLabel , rejectionInfoButton , layout . NewSpacer ( ) )
topBarWithDescription := container . NewVBox ( topBar , widget . NewSeparator ( ) , rejectionInfoRow )
return topBarWithDescription , nil
}
getMessagesContainer := func ( ) ( * fyne . Container , error ) {
if ( conversationExists == false ) {
emptyBox := getContainerBoxed ( container . NewHBox ( ) )
return emptyBox , nil
}
// This will return true if we should pixelate received images
// We will never pixelate images that we sent
getPixelateReceivedImagesBool := func ( ) ( bool , error ) {
exists , pixelateStatus , err := mySettings . GetSetting ( "PixelateImagesOnOffStatus" )
if ( err != nil ) { return false , err }
if ( exists == false ) {
return true , nil
}
if ( pixelateStatus == "Off" ) {
return false , nil
}
return true , nil
}
pixelateReceivedImagesBool , err := getPixelateReceivedImagesBool ( )
if ( err != nil ) { return nil , err }
if ( lastMessageIndex > ( len ( nonSeenIndicatingMessagesList ) - 1 ) ) {
return nil , errors . New ( "Maximum index out of range." )
}
messagesToDisplayList := nonSeenIndicatingMessagesList [ conversationViewIndex : lastMessageIndex + 1 ]
messageRowsContainer := container . NewVBox ( )
for _ , messageObject := range messagesToDisplayList {
messageStatus := messageObject . MessageStatus
2024-06-11 06:59:06 +02:00
messageCreationTime := messageObject . CreationTime
2024-04-11 15:51:56 +02:00
messageCommunication := messageObject . Communication
iAmSender := messageObject . IAmSender
getMessageContentBox := func ( ) ( * fyne . Container , error ) {
isAPhoto := strings . HasPrefix ( messageCommunication , ">!>Photo=" )
if ( isAPhoto == true ) {
imageBase64 := strings . TrimPrefix ( messageCommunication , ">!>Photo=" )
imageObject , err := imagery . ConvertWEBPBase64StringToCroppedDownsizedImageObject ( imageBase64 )
if ( err != nil ) {
return nil , errors . New ( "MyChatMessages contains photo message with invalid photo: " + err . Error ( ) )
}
getThumbnail := func ( ) ( * canvas . Image , error ) {
if ( iAmSender == true || pixelateReceivedImagesBool == false ) {
fyneImageObject := canvas . NewImageFromImage ( imageObject )
return fyneImageObject , nil
}
photoIcon , err := getFyneImageIcon ( "Photo" )
if ( err != nil ) { return nil , err }
return photoIcon , nil
}
imageThumbnail , err := getThumbnail ( )
if ( err != nil ) { return nil , err }
imageThumbnail . FillMode = canvas . ImageFillContain
viewImageButton := getWidgetCentered ( widget . NewButtonWithIcon ( "View Image" , theme . VisibilityIcon ( ) , func ( ) {
if ( iAmSender == true || pixelateReceivedImagesBool == false ) {
setViewFullpageImagePage ( window , imageObject , currentPage )
return
}
setSlowlyRevealImagePage ( window , imageObject , 0 , currentPage )
} ) )
imageWithButton := container . NewGridWithColumns ( 1 , imageThumbnail , viewImageButton )
imageWithButtonBoxed := getContainerBoxed ( imageWithButton )
return imageWithButtonBoxed , nil
}
isGreet := strings . HasPrefix ( messageCommunication , ">!>Greet" )
if ( isGreet == true ) {
greetIcon , err := getFyneImageIcon ( "Greet" )
if ( err != nil ) { return nil , err }
emojiSize := getCustomFyneSize ( 20 )
greetIcon . SetMinSize ( emojiSize )
greetDescription := getBoldLabel ( "I Greet You." )
greetHelpButton := widget . NewButtonWithIcon ( "" , theme . QuestionIcon ( ) , func ( ) {
setGreetAndRejectExplainerPage ( window , currentPage )
} )
greetDescriptionRow := container . NewHBox ( layout . NewSpacer ( ) , greetDescription , greetHelpButton , layout . NewSpacer ( ) )
greetMessageBox := getContainerBoxed ( container . NewVBox ( greetIcon , greetDescriptionRow ) )
return greetMessageBox , nil
}
isReject := strings . HasPrefix ( messageCommunication , ">!>Reject" )
if ( isReject == true ) {
rejectIcon , err := getFyneImageIcon ( "Reject" )
if ( err != nil ) { return nil , err }
emojiSize := getCustomFyneSize ( 20 )
rejectIcon . SetMinSize ( emojiSize )
rejectDescription := getBoldLabel ( "I Reject You." )
rejectHelpButton := widget . NewButtonWithIcon ( "" , theme . QuestionIcon ( ) , func ( ) {
setGreetAndRejectExplainerPage ( window , currentPage )
} )
rejectDescriptionRow := container . NewHBox ( layout . NewSpacer ( ) , rejectDescription , rejectHelpButton , layout . NewSpacer ( ) )
rejectMessageBox := getContainerBoxed ( container . NewVBox ( rejectIcon , rejectDescriptionRow ) )
return rejectMessageBox , nil
}
isEmoji := strings . HasPrefix ( messageCommunication , ">!>Emoji=" )
if ( isEmoji == true ) {
emojiIdentifierString := strings . TrimPrefix ( messageCommunication , ">!>Emoji=" )
emojiIdentifierInt , err := helpers . ConvertStringToInt ( emojiIdentifierString )
if ( err != nil ) {
return nil , errors . New ( "MyChatMessages contains invalid message: " + messageCommunication )
}
emojiImageObject , err := getEmojiImageObject ( emojiIdentifierInt )
if ( err != nil ) { return nil , err }
emojiFyneImage := canvas . NewImageFromImage ( emojiImageObject )
emojiFyneImage . FillMode = canvas . ImageFillContain
emojiSize := getCustomFyneSize ( 30 )
emojiFyneImage . SetMinSize ( emojiSize )
emojiImageBoxed := getFyneImageBoxed ( emojiFyneImage )
return emojiImageBoxed , nil
}
//TODO: Add Questionnaire
// This will return true if we can display the unflattened and untrimmed message
checkIfMessageTrimmingAndFlatteningIsNeeded := func ( ) bool {
numberOfNewlines := strings . Count ( messageCommunication , "\n" )
if ( numberOfNewlines > 15 ) {
// Message is too tall to show in full
return true
}
// We need to count tabs as 5 runes
numberOfTabs := strings . Count ( messageCommunication , "\t" )
numberOfRunes := len ( [ ] rune ( messageCommunication ) ) + ( numberOfTabs * 4 )
if ( numberOfRunes > 200 ) {
return true
}
return false
}
messageTrimmingAndFlatteningIsNeeded := checkIfMessageTrimmingAndFlatteningIsNeeded ( )
if ( messageTrimmingAndFlatteningIsNeeded == true ) {
messagePreviewText , _ , err := helpers . TrimAndFlattenString ( messageCommunication , 200 )
if ( err != nil ) { return nil , err }
messagePreviewLabel := widget . NewLabel ( messagePreviewText )
messagePreviewLabel . TextStyle = getFyneTextStyle_Bold ( )
messagePreviewLabel . Wrapping = 3
viewFullMessageButton := getWidgetCentered ( widget . NewButtonWithIcon ( "Read All" , theme . VisibilityIcon ( ) , func ( ) {
setViewTextPage ( window , "Viewing Message" , messageCommunication , false , currentPage )
} ) )
messageContentBoxed := getContainerBoxed ( container . NewVBox ( messagePreviewLabel , viewFullMessageButton ) )
return messageContentBoxed , nil
}
messageContentTextLabel := widget . NewLabel ( messageCommunication )
messageContentTextLabel . TextStyle = getFyneTextStyle_Bold ( )
messageContentTextLabel . Wrapping = 3
messageContentTextBoxed := getWidgetBoxed ( messageContentTextLabel )
return messageContentTextBoxed , nil
}
// This will show a button to represent Queued, Failed, Seen, or Unseen
getMessageStatusButton := func ( ) ( fyne . Widget , error ) {
if ( messageStatus == "Queued" ) {
queuedButton := widget . NewButtonWithIcon ( "Queued" , theme . HistoryIcon ( ) , func ( ) {
dialogTitle := translate ( "Message Is Queued" )
dialogMessageA := getLabelCentered ( translate ( "This message is queued." ) )
dialogMessageB := getLabelCentered ( translate ( "Seekia is still trying to send the message." ) )
dialogMessageC := getLabelCentered ( translate ( "This could take a while if the recipient's chat keys are missing." ) )
dialogMessageD := getLabelCentered ( translate ( "In that case, Seekia will try to download the recipient's chat keys." ) )
dialogMessageE := getLabelCentered ( translate ( "If Seekia cannot find the user's chat keys, it will eventually stop trying." ) )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB , dialogMessageC , dialogMessageD , dialogMessageE )
dialog . ShowCustom ( dialogTitle , translate ( "Close" ) , dialogContent , window )
} )
queuedButton . Importance = widget . HighImportance
return queuedButton , nil
}
if ( messageStatus == "Failed" ) {
failedButton := widget . NewButtonWithIcon ( "Failed" , theme . CancelIcon ( ) , func ( ) {
dialogTitle := translate ( "Message Failed" )
dialogMessageA := getLabelCentered ( translate ( "This message failed to send." ) )
dialogMessageB := getLabelCentered ( translate ( "This is probably due to the user's profile being missing or disabled." ) )
dialogMessageC := getLabelCentered ( translate ( "You can try again." ) )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB , dialogMessageC )
dialog . ShowCustom ( dialogTitle , translate ( "Close" ) , dialogContent , window )
} )
failedButton . Importance = widget . DangerImportance
return failedButton , nil
}
// messageStatus == "Sent"
messageHash := messageObject . MessageHash
messageSeenUnseenStatus := getMessageIsSeenStatus ( iAmSender , messageHash )
if ( messageSeenUnseenStatus == false && iAmSender == true ) {
unseenButton := widget . NewButtonWithIcon ( "Unseen" , theme . VisibilityOffIcon ( ) , func ( ) {
title := translate ( "Message is Unseen" )
dialogMessageA := getLabelCentered ( translate ( "This message is unseen." ) )
dialogMessageB := getLabelCentered ( translate ( "The recipient has not indicated that they have seen the message." ) )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB )
dialog . ShowCustom ( title , translate ( "Close" ) , dialogContent , window )
} )
return unseenButton , nil
}
if ( messageSeenUnseenStatus == false && iAmSender == false ) {
unseenButton := widget . NewButtonWithIcon ( "Unseen" , theme . VisibilityOffIcon ( ) , func ( ) {
setConfirmSendSeenMessagePage ( window , myIdentityHash , theirIdentityHash , messageHash , currentPage , currentPage )
} )
return unseenButton , nil
}
if ( messageSeenUnseenStatus == true && iAmSender == true ) {
seenButton := widget . NewButtonWithIcon ( "Seen" , theme . VisibilityIcon ( ) , func ( ) {
title := translate ( "Message Is Seen" )
dialogMessage := getLabelCentered ( translate ( "This message has been seen by the recipient." ) )
dialogContent := container . NewVBox ( dialogMessage )
dialog . ShowCustom ( title , translate ( "Close" ) , dialogContent , window )
} )
seenButton . Importance = widget . HighImportance
return seenButton , nil
}
// messageSeenUnseenStatus == true && iAmSender == false
seenButton := widget . NewButtonWithIcon ( "Seen" , theme . VisibilityIcon ( ) , func ( ) {
title := translate ( "Message Is Seen" )
dialogMessage := getLabelCentered ( translate ( "You have notified the recipient that you have seen this message." ) )
dialogContent := container . NewVBox ( dialogMessage )
dialog . ShowCustom ( title , translate ( "Close" ) , dialogContent , window )
} )
seenButton . Importance = widget . HighImportance
return seenButton , nil
}
messageContentBox , err := getMessageContentBox ( )
if ( err != nil ) { return nil , err }
2024-06-11 06:59:06 +02:00
timeSentAgoText , err := helpers . ConvertUnixTimeToTimeFromNowTranslated ( messageCreationTime , false )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return nil , err }
timeSentAgoLabel := getItalicLabel ( "Sent " + timeSentAgoText )
getViewMessageDetailsButton := func ( ) ( fyne . Widget , error ) {
if ( messageStatus == "Sent" ) {
messageHash := messageObject . MessageHash
viewMessageDetailsButton := widget . NewButtonWithIcon ( "" , theme . InfoIcon ( ) , func ( ) {
2024-06-11 06:59:06 +02:00
setViewMessageDetailsButton ( window , messageHash , messageCreationTime , currentPage )
2024-04-11 15:51:56 +02:00
} )
return viewMessageDetailsButton , nil
}
if ( messageStatus == "Failed" ) {
showFailedDialog := func ( ) {
//TODO: Add a page where user can see why message failed
dialogTitle := translate ( "Message Failed" )
dialogMessageA := getLabelCentered ( translate ( "This message failed to send." ) )
dialogMessageB := getLabelCentered ( translate ( "This is probably due to the user's profile being missing or disabled." ) )
dialogMessageC := getLabelCentered ( translate ( "You can try again." ) )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB , dialogMessageC )
dialog . ShowCustom ( dialogTitle , translate ( "Close" ) , dialogContent , window )
}
viewMessageDetailsButton := widget . NewButtonWithIcon ( "" , theme . InfoIcon ( ) , showFailedDialog )
return viewMessageDetailsButton , nil
}
if ( messageStatus == "Queued" ) {
showQueuedDialog := func ( ) {
//TODO: Add a page where user can cancel message, and see why message is queued
dialogTitle := translate ( "Message Is Queued" )
dialogMessageA := getLabelCentered ( translate ( "This message is queued." ) )
dialogMessageB := getLabelCentered ( translate ( "Seekia is still trying to send the message." ) )
dialogMessageC := getLabelCentered ( translate ( "This could take a while if the recipient's chat keys are missing." ) )
dialogMessageD := getLabelCentered ( translate ( "In that case, Seekia will try to download the recipient's chat keys." ) )
dialogMessageE := getLabelCentered ( translate ( "If Seekia cannot find the user's chat keys, it will eventually stop trying." ) )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB , dialogMessageC , dialogMessageD , dialogMessageE )
dialog . ShowCustom ( dialogTitle , translate ( "Close" ) , dialogContent , window )
}
viewMessageDetailsButton := widget . NewButtonWithIcon ( "" , theme . InfoIcon ( ) , showQueuedDialog )
return viewMessageDetailsButton , nil
}
return nil , errors . New ( "messagesToDisplayList contains messageMap with invalid messageStatus: " + messageStatus )
}
viewMessageDetailsButton , err := getViewMessageDetailsButton ( )
if ( err != nil ) { return nil , err }
messageDetailsRow := container . NewHBox ( layout . NewSpacer ( ) , timeSentAgoLabel , viewMessageDetailsButton , layout . NewSpacer ( ) )
messageStatusButton , err := getMessageStatusButton ( )
if ( err != nil ) { return nil , err }
//messageDetailsBox := getContainerBoxed(container.NewVBox(messageStatusButton, messageDetailsRow))
getMessageRow := func ( ) * fyne . Container {
if ( iAmSender == true ) {
messageBox := getContainerBoxed ( container . NewBorder ( nil , messageDetailsRow , messageStatusButton , nil , messageContentBox ) )
emptyLabel := widget . NewLabel ( "" )
messageRow := container . NewBorder ( nil , nil , emptyLabel , nil , messageBox )
return messageRow
}
messageBox := getContainerBoxed ( container . NewBorder ( nil , messageDetailsRow , nil , messageStatusButton , messageContentBox ) )
emptyLabel := widget . NewLabel ( "" )
messageRow := container . NewBorder ( nil , nil , nil , emptyLabel , messageBox )
return messageRow
}
messageRow := getMessageRow ( )
messageRowsContainer . Add ( messageRow )
}
if ( conversationViewIndex >= maximumViewIndex ) {
refreshMessagesButton := getWidgetCentered ( widget . NewButtonWithIcon ( "Refresh" , theme . ViewRefreshIcon ( ) , currentPageWithNewestView ) )
messageRowsContainer . Add ( refreshMessagesButton )
}
messageRowsScrollable := container . NewVScroll ( messageRowsContainer )
boxedMessagesContainer := getScrollContainerBoxed ( messageRowsScrollable )
return boxedMessagesContainer , nil
}
getChooseMessageTypeToSendRow := func ( ) ( * fyne . Container , error ) {
// Outputs:
// -bool: Able to send
showDialogIfUnableToSend := func ( ) bool {
if ( iAmDisabled == true ) {
dialogTitle := translate ( "Unable To Chat" )
dialogMessageA := getLabelCentered ( translate ( "Cannot respond: Your identity is disabled." ) )
dialogMessageB := getLabelCentered ( translate ( "Enable your profile on the Profiles - Broadcast page." ) )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB )
dialog . ShowCustom ( dialogTitle , translate ( "Close" ) , dialogContent , window )
return false
}
if ( theyAreDisabled == true ) {
dialogTitle := translate ( "Unable To Chat" )
dialogMessageA := getLabelCentered ( translate ( "Cannot respond: Their identity is disabled." ) )
dialogMessageB := getLabelCentered ( translate ( "This user has disabled their Seekia profile." ) )
//TODO: Add button to attempt to download their profile
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB )
dialog . ShowCustom ( dialogTitle , translate ( "Close" ) , dialogContent , window )
return false
}
return true
}
sendImageButton := widget . NewButtonWithIcon ( "Send Image" , theme . FileImageIcon ( ) , func ( ) {
ableToSend := showDialogIfUnableToSend ( )
if ( ableToSend == false ) {
return
}
setSendChatImagePage ( window , myIdentityHash , theirIdentityHash , currentPage , currentPageWithNewestView )
} )
sendTextButton := widget . NewButtonWithIcon ( "Send Text" , theme . DocumentCreateIcon ( ) , func ( ) {
ableToSend := showDialogIfUnableToSend ( )
if ( ableToSend == false ) {
return
}
setWriteMessageToSendPage ( window , myIdentityHash , theirIdentityHash , "" , currentPage , currentPageWithNewestView )
} )
sendEmojiButton := widget . NewButtonWithIcon ( "Send Emoji" , theme . AccountIcon ( ) , func ( ) {
ableToSend := showDialogIfUnableToSend ( )
if ( ableToSend == false ) {
return
}
submitEmojiFunction := func ( emojiIdentifier int ) {
emojiIdentifierString := helpers . ConvertIntToString ( emojiIdentifier )
newMessageCommunication := ">!>Emoji=" + emojiIdentifierString
setFundAndSendChatMessagePage ( window , myIdentityHash , theirIdentityHash , newMessageCommunication , false , currentPage , currentPageWithNewestView )
}
setChooseEmojiPage ( window , "Choose Emoji" , "Circle Face" , 0 , currentPage , submitEmojiFunction )
} )
sendMessageRow := getContainerCentered ( container . NewGridWithRows ( 1 , sendImageButton , sendTextButton , sendEmojiButton ) )
return sendMessageRow , nil
}
topbar , err := getConversationTopBar ( )
if ( err != nil ) { return nil , err }
messagesContainer , err := getMessagesContainer ( )
if ( err != nil ) { return nil , err }
chooseMessageTypeToSendRow , err := getChooseMessageTypeToSendRow ( )
if ( err != nil ) { return nil , err }
conversationMessagesColumn := container . NewBorder ( topbar , chooseMessageTypeToSendRow , nil , nil , messagesContainer )
err = myReadStatus . SetConversationReadUnreadStatus ( myIdentityHash , theirIdentityHash , appNetworkType , "Read" )
if ( err != nil ) { return nil , err }
return conversationMessagesColumn , nil
}
conversationContainer , err := getConversationContainer ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
pageHeader := container . NewVBox ( title , backButton , widget . NewSeparator ( ) )
pageContent := container . NewBorder ( pageHeader , nil , recipientInfoColumn , myInfoColumn , conversationContainer )
setPageContent ( pageContent , window )
}
2024-06-11 06:59:06 +02:00
func setViewMessageDetailsButton ( window fyne . Window , messageHash [ 26 ] byte , messageCreationTime int64 , previousPage func ( ) ) {
2024-04-11 15:51:56 +02:00
2024-06-11 06:59:06 +02:00
currentPage := func ( ) { setViewMessageDetailsButton ( window , messageHash , messageCreationTime , previousPage ) }
2024-04-11 15:51:56 +02:00
title := getPageTitleCentered ( "View Message Details" )
backButton := getBackButtonCentered ( previousPage )
subtitle := getPageSubtitleCentered ( "Message Details" )
messageHashLabel := widget . NewLabel ( "Message Hash:" )
messageHashHex := encoding . EncodeBytesToHexString ( messageHash [ : ] )
messageHashTrimmed , _ , err := helpers . TrimAndFlattenString ( messageHashHex , 10 )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
messageHashText := getBoldLabel ( messageHashTrimmed )
viewMessageHashButton := widget . NewButtonWithIcon ( "" , theme . VisibilityIcon ( ) , func ( ) {
setViewContentHashPage ( window , "Message" , messageHash [ : ] , currentPage )
} )
messageHashRow := container . NewHBox ( layout . NewSpacer ( ) , messageHashLabel , messageHashText , viewMessageHashButton , layout . NewSpacer ( ) )
2024-06-11 06:59:06 +02:00
timeCreatedLabel := widget . NewLabel ( "Time Created:" )
2024-04-11 15:51:56 +02:00
2024-06-11 06:59:06 +02:00
messageCreationTimeString := helpers . ConvertUnixTimeToTranslatedTime ( messageCreationTime )
2024-04-11 15:51:56 +02:00
2024-06-11 06:59:06 +02:00
messageCreationTimeLabel := getBoldLabel ( messageCreationTimeString )
2024-04-11 15:51:56 +02:00
2024-06-11 06:59:06 +02:00
creationTimeWarningButton := widget . NewButtonWithIcon ( "" , theme . WarningIcon ( ) , func ( ) {
title := translate ( "Creation Time Warning" )
dialogMessageA := getLabelCentered ( "Creation times are not verified." )
2024-04-11 15:51:56 +02:00
dialogMessageB := getLabelCentered ( "They can be faked by the message author." )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB )
dialog . ShowCustom ( title , translate ( "Close" ) , dialogContent , window )
} )
2024-06-11 06:59:06 +02:00
messageCreationTimeRow := container . NewHBox ( layout . NewSpacer ( ) , timeCreatedLabel , messageCreationTimeLabel , creationTimeWarningButton , layout . NewSpacer ( ) )
2024-04-11 15:51:56 +02:00
reportMessageButton := getWidgetCentered ( widget . NewButtonWithIcon ( "Report Message" , theme . WarningIcon ( ) , func ( ) {
//TODO
// This will go to a page where the user can report the message
// They will have to pay for their report, and be warned that the report may contain their identity hash
// It will not be directly linked to their identity hash if the message was sent to a secret inbox.
// We should let the user know if the message was sent to a secret inbox or not.
// The sender will also be able to tell that their message was reported
// If the message contains illegal content, reporting the message may be legally risky for the recipient, because it acknowledges
// that the user saw the message
// We must warn the user of this too.
showUnderConstructionDialog ( window )
} ) )
//TODO: Show other information: Message size, message moderation status, and when message will expire from network
2024-06-11 06:59:06 +02:00
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , subtitle , widget . NewSeparator ( ) , messageHashRow , widget . NewSeparator ( ) , messageCreationTimeRow , widget . NewSeparator ( ) , reportMessageButton )
2024-04-11 15:51:56 +02:00
setPageContent ( page , window )
}
func setConfirmSendSeenMessagePage ( window fyne . Window , myIdentityHash [ 16 ] byte , recipientIdentityHash [ 16 ] byte , seenMessageHash [ 26 ] byte , previousPage func ( ) , afterMessageSentPage func ( ) ) {
currentPage := func ( ) { setConfirmSendSeenMessagePage ( window , myIdentityHash , recipientIdentityHash , seenMessageHash , previousPage , afterMessageSentPage ) }
title := getPageTitleCentered ( translate ( "Send Seen Message" ) )
backButton := getBackButtonCentered ( previousPage )
subtitle := getPageSubtitleCentered ( "Confirm Send Seen Message?" )
description1 := getLabelCentered ( "Are you sure you want to send a seen message?" )
description2 := getLabelCentered ( "This will let the user know you received and saw their message." )
description3 := getLabelCentered ( "This is informative for the recipient." )
description4 := getLabelCentered ( "You will pay for the message on the next page." )
confirmButton := getWidgetCentered ( widget . NewButtonWithIcon ( "Confirm" , theme . ConfirmIcon ( ) , func ( ) {
seenMessageHashHex := encoding . EncodeBytesToHexString ( seenMessageHash [ : ] )
seenMessageCommunication := ">!>Seen=" + seenMessageHashHex
setFundAndSendChatMessagePage ( window , myIdentityHash , recipientIdentityHash , seenMessageCommunication , false , currentPage , afterMessageSentPage )
} ) )
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , subtitle , widget . NewSeparator ( ) , description1 , description2 , description3 , description4 , confirmButton )
setPageContent ( page , window )
}
func setWriteMessageToSendPage ( window fyne . Window , myIdentityHash [ 16 ] byte , recipientIdentityHash [ 16 ] byte , messageCommunication string , previousPage func ( ) , afterMessageSentPage func ( ) ) {
title := getPageTitleCentered ( "Write Message" )
backButton := getBackButtonCentered ( previousPage )
messageEntry := widget . NewMultiLineEntry ( )
messageEntry . Wrapping = 3
messageEntry . SetPlaceHolder ( translate ( "Enter message..." ) )
messageEntry . Text = messageCommunication
// We use this function so user does not lose their written message if they view Rules/SendMessage page and return back
currentPageWithEntryContent := func ( ) {
currentEntryText := messageEntry . Text
setWriteMessageToSendPage ( window , myIdentityHash , recipientIdentityHash , currentEntryText , previousPage , afterMessageSentPage )
}
description1 := getBoldLabelCentered ( "Write your message to send." )
description2 := widget . NewLabel ( "Your message must follow the Seekia rules." )
seekiaRulesButton := widget . NewButtonWithIcon ( "" , theme . QuestionIcon ( ) , func ( ) {
setViewSeekiaRulesPage ( window , currentPageWithEntryContent )
} )
description2Row := container . NewHBox ( layout . NewSpacer ( ) , description2 , seekiaRulesButton , layout . NewSpacer ( ) )
sendButton := widget . NewButtonWithIcon ( "Send" , theme . NavigateNextIcon ( ) , func ( ) {
//TODO: Length limit
newMessageCommunication := messageEntry . Text
if ( newMessageCommunication == "" ) {
return
}
textIsAllowed := allowedText . VerifyStringIsAllowed ( newMessageCommunication )
if ( textIsAllowed == false ) {
dialogTitle := translate ( "Message Is Not Allowed" )
dialogMessageA := getLabelCentered ( "Text contains unallowed text." )
dialogMessageB := getLabelCentered ( "It must be encoded in UTF-8." )
dialogMessageC := getLabelCentered ( "Remove unallowed text and resend message." )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB , dialogMessageC )
dialog . ShowCustom ( dialogTitle , translate ( "Close" ) , dialogContent , window )
return
}
// We use this prefix for special messages (emojis, questionnaire responses, images, seen messages)
isSpecial := strings . HasPrefix ( newMessageCommunication , ">!>" )
if ( isSpecial == true ) {
dialogTitle := translate ( "Message Prefix Is Not Allowed" )
dialogMessageA := getLabelCentered ( "Your message cannot start with >!>." )
dialogMessageB := getLabelCentered ( "Remove this prefix and resend message." )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB )
dialog . ShowCustom ( dialogTitle , translate ( "Close" ) , dialogContent , window )
return
}
setFundAndSendChatMessagePage ( window , myIdentityHash , recipientIdentityHash , newMessageCommunication , false , currentPageWithEntryContent , afterMessageSentPage )
} )
sendButtonCentered := getWidgetCentered ( sendButton )
spacer := widget . NewLabel ( "" )
sendButtonWithSpacer := container . NewVBox ( sendButtonCentered , spacer )
header := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , description1 , description2Row , widget . NewSeparator ( ) )
page := container . NewBorder ( header , sendButtonWithSpacer , nil , nil , messageEntry )
setPageContent ( page , window )
}
func setSendChatImagePage ( window fyne . Window , myIdentityHash [ 16 ] byte , recipientIdentityHash [ 16 ] byte , previousPage func ( ) , afterSendPage func ( ) ) {
currentPage := func ( ) { setSendChatImagePage ( window , myIdentityHash , recipientIdentityHash , previousPage , afterSendPage ) }
title := getPageTitleCentered ( "Send Chat Image" )
backButton := getBackButtonCentered ( previousPage )
subtitle := getPageSubtitleCentered ( "Send Image" )
description1 := getBoldLabelCentered ( "Select an image file to send." )
description2 := getLabelCentered ( "You can apply image effects on the next page." )
description3 := getLabelCentered ( "JPEG, PNG and WEBP files are supported." )
openFileCallbackFunction := func ( fileObject fyne . URIReadCloser , err error ) {
if ( err != nil ) {
title := translate ( "Failed to open image file." )
dialogMessage := getLabelCentered ( translate ( "Report this error to Seekia developers: " + err . Error ( ) ) )
dialogContent := container . NewVBox ( dialogMessage )
dialog . ShowCustom ( title , translate ( "Close" ) , dialogContent , window )
return
}
if ( fileObject == nil ) {
return
}
setLoadingScreen ( window , "Add Image" , "Importing image..." )
filePath := fileObject . URI ( ) . String ( )
filePath = strings . TrimPrefix ( filePath , "file://" )
fileExists , ableToReadImage , imageObject , err := imagery . ReadImageFile ( filePath )
if ( err != nil ) {
currentPage ( )
title := translate ( "Failed to open image file." )
dialogMessage := getLabelCentered ( translate ( "Report this error to Seekia developers: " + err . Error ( ) ) )
dialogContent := container . NewVBox ( dialogMessage )
dialog . ShowCustom ( title , translate ( "Close" ) , dialogContent , window )
return
}
if ( fileExists == false ) {
currentPage ( )
title := translate ( "Failed to open image file." )
dialogMessage := getLabelCentered ( translate ( "Report this error to Seekia developers: Image file not found." ) )
dialogContent := container . NewVBox ( dialogMessage )
dialog . ShowCustom ( title , translate ( "Close" ) , dialogContent , window )
return
}
if ( ableToReadImage == false ) {
currentPage ( )
title := translate ( "Failed to import image file." )
dialogMessageA := getLabelCentered ( translate ( "Seekia only supports these image file formats:" ) )
dialogMessageB := getLabelCentered ( "JPG, JPEG, PNG, WEBP" )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB )
dialog . ShowCustom ( title , translate ( "Close" ) , dialogContent , window )
return
}
// We resize image to something that will be more managable when we edit it
// When we export it, we will downsize it even further
imageObjectResized , err := imagery . DownsizeGolangImage ( imageObject , 1500 )
if ( err != nil ) {
currentPage ( )
title := translate ( "Failed To Process Image File" )
dialogMessageA := getLabelCentered ( translate ( "Your file may be too large." ) )
errorString := err . Error ( )
errorTrimmed , _ , err := helpers . TrimAndFlattenString ( errorString , 20 )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , currentPage )
return
}
errorTrimmedLabel := getBoldLabel ( "Error: " + errorTrimmed )
viewFullErrorButton := widget . NewButtonWithIcon ( "" , theme . VisibilityIcon ( ) , func ( ) {
setViewTextPage ( window , "Viewing Error" , errorString , false , currentPage )
} )
errorDescriptionRow := container . NewHBox ( layout . NewSpacer ( ) , errorTrimmedLabel , viewFullErrorButton , layout . NewSpacer ( ) )
dialogContent := container . NewVBox ( dialogMessageA , errorDescriptionRow )
dialog . ShowCustom ( title , translate ( "Close" ) , dialogContent , window )
return
}
submitImageToSendFunction := func ( newImageObject image . Image , newPreviousPage func ( ) ) {
setLoadingScreen ( window , "Send Chat Image" , "Compressing Image..." )
newImageBase64String , err := imagery . ConvertImageObjectToStandardWebpBase64String ( newImageObject )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , newPreviousPage )
return
}
setConfirmSendChatImagePage ( window , myIdentityHash , recipientIdentityHash , newImageBase64String , newPreviousPage , afterSendPage )
}
setEditImagePage ( window , imageObject , false , nil , imageObjectResized , currentPage , submitImageToSendFunction )
}
selectImageFileButton := getWidgetCentered ( widget . NewButtonWithIcon ( "Select Image File" , theme . FileImageIcon ( ) , func ( ) {
dialog . ShowFileOpen ( openFileCallbackFunction , window )
} ) )
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , subtitle , widget . NewSeparator ( ) , description1 , description2 , description3 , selectImageFileButton )
setPageContent ( page , window )
}
func setConfirmSendChatImagePage ( window fyne . Window , myIdentityHash [ 16 ] byte , recipientIdentityHash [ 16 ] byte , newImageBase64String string , previousPage func ( ) , afterSendPage func ( ) ) {
currentPage := func ( ) { setConfirmSendChatImagePage ( window , myIdentityHash , recipientIdentityHash , newImageBase64String , previousPage , afterSendPage ) }
title := getPageTitleCentered ( "Send Chat Image" )
backButton := getBackButtonCentered ( previousPage )
subtitle := getPageSubtitleCentered ( "Confirm Send Image?" )
description1 := getLabelCentered ( "Are you sure you want to send this image?" )
description2 := getLabelCentered ( "Be aware that the image has been compressed." )
description3 := getLabelCentered ( "You will pay for the message on the next page." )
submitButton := getWidgetCentered ( widget . NewButtonWithIcon ( "Send Image" , theme . ConfirmIcon ( ) , func ( ) {
newMessageCommunication := ">!>Photo=" + newImageBase64String
setFundAndSendChatMessagePage ( window , myIdentityHash , recipientIdentityHash , newMessageCommunication , false , currentPage , afterSendPage )
} ) )
croppedImageObject , err := imagery . ConvertWEBPBase64StringToCroppedDownsizedImageObject ( newImageBase64String )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
newFyneImage := canvas . NewImageFromImage ( croppedImageObject )
newFyneImage . FillMode = canvas . ImageFillContain
viewFullpageButton := getWidgetCentered ( widget . NewButtonWithIcon ( "" , theme . ZoomInIcon ( ) , func ( ) {
setViewFullpageImagePage ( window , croppedImageObject , currentPage )
} ) )
emptyLabel := widget . NewLabel ( "" )
zoomButtonWithSpacer := container . NewVBox ( viewFullpageButton , emptyLabel )
header := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , subtitle , widget . NewSeparator ( ) , description1 , description2 , description3 , widget . NewSeparator ( ) , submitButton , widget . NewSeparator ( ) )
page := container . NewBorder ( header , zoomButtonWithSpacer , nil , nil , newFyneImage )
setPageContent ( page , window )
}
// Network duration time is the time the message should exist on the network before expiring
func setFundAndSendChatMessagePage ( window fyne . Window , myIdentityHash [ 16 ] byte , recipientIdentityHash [ 16 ] byte , messageCommunication string , acceptedMessageQueueWarning bool , previousPage func ( ) , afterMessageSentPage func ( ) ) {
setLoadingScreen ( window , "Send Message" , "Loading..." )
currentPage := func ( ) { setFundAndSendChatMessagePage ( window , myIdentityHash , recipientIdentityHash , messageCommunication , acceptedMessageQueueWarning , previousPage , afterMessageSentPage ) }
isMine , myIdentityType , err := myIdentity . CheckIfIdentityHashIsMine ( myIdentityHash )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
if ( isMine == false ) {
// should not occur, as send chat message page will restrict user from sending message if their identity does not exist
setErrorEncounteredPage ( window , errors . New ( "Your identity no longer exists." ) , previousPage )
return
}
if ( myIdentityType != "Mate" && myIdentityType != "Moderator" ) {
// This should never occur
setErrorEncounteredPage ( window , errors . New ( "Attempting to send from invalid identity type: " + myIdentityType ) , previousPage )
return
}
title := getPageTitleCentered ( "Send Message" )
backButton := getBackButtonCentered ( previousPage )
exists , iAmDisabled , err := myLocalProfiles . GetProfileData ( myIdentityType , "Disabled" )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
if ( exists == true && iAmDisabled == "Yes" ) {
description1 := getBoldLabelCentered ( "Your profile is disabled." )
description2 := getLabelCentered ( "You must enable your profile to send messages." )
description3 := getLabelCentered ( "Enable your profile on the Profile - Broadcast page." )
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , description1 , description2 , description3 )
setPageContent ( page , window )
return
}
appNetworkType , err := getAppNetworkType . GetAppNetworkType ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
myIdentityFound , myProfileIsActiveStatus , err := myProfileStatus . GetMyProfileIsActiveStatus ( myIdentityHash , appNetworkType )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
if ( myIdentityFound == false ) {
setErrorEncounteredPage ( window , errors . New ( "My identity not found after being found already." ) , previousPage )
return
}
if ( myProfileIsActiveStatus == false ) {
description1 := getBoldLabelCentered ( "Your profile is not active." )
description2 := getLabelCentered ( "You must broadcast your profile to chat with users." )
description3 := getLabelCentered ( "Broadcast your " + myIdentityType + " profile on the Broadcast page." )
broadcastPageButton := getWidgetCentered ( widget . NewButton ( "Visit Broadcast Page" , func ( ) {
setBroadcastPage ( window , myIdentityType , currentPage )
} ) )
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , description1 , description2 , description3 , broadcastPageButton )
setPageContent ( page , window )
return
}
recipientIdentityType , err := identity . GetIdentityTypeFromIdentityHash ( recipientIdentityHash )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
if ( recipientIdentityType == "Host" ) {
description1 := getBoldLabelCentered ( "Recipient is a Host." )
description2 := getLabelCentered ( "They cannot be chatted with." )
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , description1 , description2 )
setPageContent ( page , window )
return
}
// We check if the recipient is disabled
getRecipientIsDisabledBool := func ( ) ( bool , error ) {
recipientProfileExists , _ , _ , _ , _ , recipientRawProfileMap , err := profileStorage . GetNewestUserProfile ( recipientIdentityHash , appNetworkType )
if ( err != nil ) { return false , err }
if ( recipientProfileExists == false ) {
return false , nil
}
recipientIsDisabled , _ , err := readProfiles . GetFormattedProfileAttributeFromRawProfileMap ( recipientRawProfileMap , "Disabled" )
if ( err != nil ) { return false , err }
return recipientIsDisabled , nil
}
recipientIsDisabled , err := getRecipientIsDisabledBool ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
showPeerIsDisabledPage := func ( ) {
userIsDisabledDescriptionA := getBoldLabelCentered ( "The recipient has disabled their profile." )
userIsDisabledDescriptionB := getLabelCentered ( "You cannot send them a message." )
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , userIsDisabledDescriptionA , userIsDisabledDescriptionB )
setPageContent ( page , window )
}
if ( recipientIsDisabled == true ) {
showPeerIsDisabledPage ( )
return
}
peerIsDisabled , peerChatKeysExist , _ , _ , err := peerChatKeys . GetPeerNewestActiveChatKeys ( recipientIdentityHash , appNetworkType )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
if ( peerIsDisabled == true ) {
showPeerIsDisabledPage ( )
return
}
if ( peerChatKeysExist == false && acceptedMessageQueueWarning == false ) {
description1 := getBoldLabelCentered ( "You do not have the recipient's chat keys." )
description2 := getLabelCentered ( "You can add your message to the message queue." )
description3 := getLabelCentered ( "Seekia will try to download their chat keys in the background." )
description4 := getLabelCentered ( "You can also try to download their profile immediately." )
description5 := getLabelCentered ( "Add message to message queue, or try to download chat keys?" )
addToMessageQueueButton := getWidgetCentered ( widget . NewButtonWithIcon ( "Add Message To Message Queue" , theme . NavigateNextIcon ( ) , func ( ) {
setFundAndSendChatMessagePage ( window , myIdentityHash , recipientIdentityHash , messageCommunication , true , previousPage , afterMessageSentPage )
} ) )
downloadProfileButton := getWidgetCentered ( widget . NewButtonWithIcon ( "Download Profile" , theme . DownloadIcon ( ) , func ( ) {
setDownloadMissingUserProfilePage ( window , recipientIdentityHash , true , false , previousPage , currentPage , previousPage )
} ) )
buttonsGrid := getContainerCentered ( container . NewGridWithColumns ( 1 , addToMessageQueueButton , downloadProfileButton ) )
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , description1 , description2 , description3 , description4 , description5 , buttonsGrid )
setPageContent ( page , window )
return
}
//Outputs:
// -bool: Parameters Exist
// -*fyne.Container:
// -error
getSendMessageContent := func ( ) ( bool , * fyne . Container , error ) {
parametersExist , err := getParameters . CheckIfChatParametersExist ( appNetworkType )
if ( err != nil ) { return false , nil , err }
if ( parametersExist == false ) {
return false , nil , nil
}
currentCreditBalanceLabel := getLabelCentered ( "My Credit Balance:" )
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 ) { return false , nil , err }
parametersExist , appCurrencyAccountCreditBalance , err := myAccountCredit . GetMyCreditAccountBalanceInAnyCurrency ( myIdentityType , appNetworkType , currentAppCurrencyCode )
if ( err != nil ) { return false , nil , err }
if ( parametersExist == false ) {
return false , nil , nil
}
_ , appCurrencySymbol , err := currencies . GetCurrencyInfoFromCurrencyCode ( currentAppCurrencyCode )
if ( err != nil ) { return false , nil , err }
appCurrencyCreditBalanceString := helpers . ConvertFloat64ToStringRounded ( appCurrencyAccountCreditBalance , 3 )
appCurrencySymbolButton := widget . NewButton ( appCurrencySymbol , func ( ) {
setChangeAppCurrencyPage ( window , currentPage )
} )
currentBalanceLabel := getBoldLabel ( appCurrencyCreditBalanceString + " " + currentAppCurrencyCode )
currentBalanceRow := container . NewHBox ( layout . NewSpacer ( ) , appCurrencySymbolButton , currentBalanceLabel , layout . NewSpacer ( ) )
manageAccountCreditButton := getWidgetCentered ( widget . NewButton ( "Manage" , func ( ) {
setViewMyAccountCreditPage ( window , myIdentityType , currentPage )
} ) )
estimatedMessageSize , err := sendMessages . GetEstimatedMessageSize ( recipientIdentityHash , messageCommunication )
if ( err != nil ) { return false , nil , err }
messageCostLabel := getBoldLabelCentered ( "Message Cost:" )
appCurrencyCostBinding := binding . NewString ( )
durationDaysBinding := binding . NewInt ( )
appCurrencyCostLabel := widget . NewLabelWithData ( appCurrencyCostBinding )
appCurrencyCostLabel . TextStyle = getFyneTextStyle_Bold ( )
appCurrencyCostLabelCentered := container . NewHBox ( layout . NewSpacer ( ) , appCurrencyCostLabel , layout . NewSpacer ( ) )
// Fewer options improves anonymity, as a user who selects the same option every time will stand out
//Outputs:
// -bool: Parameters exist
// -error
networkExpirationSelectFunction := func ( response string ) ( bool , error ) {
getDurationDays := func ( ) int {
if ( response == "2 Days" ) {
return 2
}
// response == "2 Weeks"
return 14
}
desiredDurationDays := getDurationDays ( )
desiredDurationSeconds := desiredDurationDays * 86400
parametersExist , appCurrencyCost , err := sendMessages . GetMessageNetworkCurrencyCostForProvidedDuration ( appNetworkType , estimatedMessageSize , desiredDurationSeconds , currentAppCurrencyCode )
if ( err != nil ) { return false , err }
if ( parametersExist == false ) {
return false , nil
}
currencyCostString := helpers . ConvertFloat64ToStringRounded ( appCurrencyCost , 3 )
err = durationDaysBinding . Set ( desiredDurationDays )
if ( err != nil ) { return false , err }
err = appCurrencyCostBinding . Set ( appCurrencySymbol + currencyCostString + " " + currentAppCurrencyCode )
if ( err != nil ) { return false , err }
return true , nil
}
// We initialize the bindings
parametersExist , err = networkExpirationSelectFunction ( "2 Days" )
if ( err != nil ) { return false , nil , err }
if ( parametersExist == false ) {
return false , nil , nil
}
durationToFundText := getBoldLabelCentered ( "Duration To fund:" )
expirationOptionsList := [ ] string { "2 Days" , "2 Weeks" }
networkDurationSelector := widget . NewSelect ( expirationOptionsList , func ( response string ) {
parametersExist , err := networkExpirationSelectFunction ( response )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , currentPage )
}
if ( parametersExist == false ) {
// Parameter permissions must have been updated and now existing parameters are invalid
// We will refresh the page
// We will show a loading screen so if this causes an infinite loop, we can detect it
setLoadingScreen ( window , "Send Chat Message" , "Trying to find parameters..." )
time . Sleep ( time . Second )
currentPage ( )
}
} )
networkDurationSelector . Selected = "2 Days"
networkDurationSelectorCentered := getWidgetCentered ( networkDurationSelector )
payAndSendButton := getWidgetCentered ( widget . NewButtonWithIcon ( "Pay and Send" , theme . ConfirmIcon ( ) , func ( ) {
// Outputs:
// -bool: Parameters exist
// -bool: Sufficient credit exist
// -error
sendMessageFunction := func ( ) ( bool , bool , error ) {
messageDaysToFund , err := durationDaysBinding . Get ( )
if ( err != nil ) { return false , false , err }
messageDurationToFund := messageDaysToFund * 86400
parametersExist , sufficientCreditExist , err := myMessageQueue . AddMessageToMyMessageQueue ( myIdentityHash , recipientIdentityHash , appNetworkType , messageDurationToFund , messageCommunication )
if ( err != nil ) { return false , false , err }
if ( parametersExist == false ) {
return false , false , nil
}
if ( sufficientCreditExist == false ) {
return true , false , nil
}
//TODO: Check to see if their identity balance is known to be expired, here and within the send message code
return true , true , nil
}
parametersExist , sufficientCreditExist , err := sendMessageFunction ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , currentPage )
return
}
if ( parametersExist == false ) {
// Parameter permissions must have been updated and now existing parameters are invalid
// We will refresh the page
// We will show a loading screen so if this causes an infinite loop, we can detect it
setLoadingScreen ( window , "Send Chat Message" , "Trying to find parameters." )
time . Sleep ( time . Second )
currentPage ( )
return
}
if ( sufficientCreditExist == false ) {
title := translate ( "Insufficient Credit" )
dialogMessageA := getLabelCentered ( translate ( "You do not have enough credit to send this message." ) )
dialogMessageB := getLabelCentered ( translate ( "Add more credit on the Add Credit page." ) )
dialogContent := container . NewVBox ( dialogMessageA , dialogMessageB )
dialog . ShowCustom ( title , translate ( "Close" ) , dialogContent , window )
return
}
afterMessageSentPage ( )
} ) )
sendMessageContent := container . NewVBox ( currentCreditBalanceLabel , currentBalanceRow , manageAccountCreditButton , widget . NewSeparator ( ) , messageCostLabel , appCurrencyCostLabelCentered , widget . NewSeparator ( ) , durationToFundText , networkDurationSelectorCentered , payAndSendButton )
return true , sendMessageContent , nil
}
parametersExist , sendMessageContent , err := getSendMessageContent ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
if ( parametersExist == false ) {
//TODO: Add button to view download progress and check network connection
missingParametersLabelA := getBoldLabelCentered ( "Network parameters are not downloaded." )
missingParametersLabelB := getLabelCentered ( "Please wait for them to download." )
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , missingParametersLabelA , missingParametersLabelB )
setPageContent ( page , window )
return
}
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , sendMessageContent )
setPageContent ( page , window )
}
func setChatStatisticsPage ( window fyne . Window , identityType string , previousPage func ( ) ) {
pageIdentifier , err := helpers . GetNewRandomHexString ( 16 )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
appMemory . SetMemoryEntry ( "CurrentViewedPage" , pageIdentifier )
checkIfPageHasChangedFunction := func ( ) bool {
exists , currentViewedPage := appMemory . GetMemoryEntry ( "CurrentViewedPage" )
if ( exists == true && currentViewedPage == pageIdentifier ) {
return false
}
return true
}
currentPage := func ( ) { setChatStatisticsPage ( window , identityType , previousPage ) }
title := getPageTitleCentered ( identityType + " Chat Statistics" )
backButton := getBackButtonCentered ( previousPage )
subtitle := getPageSubtitleCentered ( "My " + identityType + " Chat Statistics" )
numberOfInboxMessagesBinding := binding . NewString ( )
numberOfConversationsBinding := binding . NewString ( )
numberOfUnreadConversationsBinding := binding . NewString ( )
numberOfInboxMessagesTitle := widget . NewLabel ( "Number Of Inbox Messages:" )
numberOfInboxMessagesLabel := widget . NewLabelWithData ( numberOfInboxMessagesBinding )
numberOfInboxMessagesLabel . TextStyle = getFyneTextStyle_Bold ( )
numberOfInboxMessagesRow := container . NewHBox ( layout . NewSpacer ( ) , numberOfInboxMessagesTitle , numberOfInboxMessagesLabel , layout . NewSpacer ( ) )
numberOfConversationsTitle := widget . NewLabel ( "Number Of Conversations:" )
numberOfConversationsLabel := widget . NewLabelWithData ( numberOfConversationsBinding )
numberOfConversationsLabel . TextStyle = getFyneTextStyle_Bold ( )
numberOfConversationsRow := container . NewHBox ( layout . NewSpacer ( ) , numberOfConversationsTitle , numberOfConversationsLabel , layout . NewSpacer ( ) )
numberOfUnreadConversationsTitle := widget . NewLabel ( "Number Of Unread Conversations:" )
numberOfUnreadConversationsLabel := widget . NewLabelWithData ( numberOfUnreadConversationsBinding )
numberOfUnreadConversationsLabel . TextStyle = getFyneTextStyle_Bold ( )
numberOfUnreadConversationsRow := container . NewHBox ( layout . NewSpacer ( ) , numberOfUnreadConversationsTitle , numberOfUnreadConversationsLabel , layout . NewSpacer ( ) )
appNetworkType , err := getAppNetworkType . GetAppNetworkType ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
updateBindingsFunction := func ( ) {
err := myChatConversations . StartUpdatingMyConversations ( identityType , appNetworkType )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
var resultsReadyBoolMutex sync . RWMutex
resultsReadyBool := false
updateLoadingProgress := func ( ) {
secondsElapsed := 0
for {
resultsReadyBoolMutex . RLock ( )
resultsReady := resultsReadyBool
resultsReadyBoolMutex . RUnlock ( )
if ( resultsReady == true ) {
return
}
pageHasChanged := checkIfPageHasChangedFunction ( )
if ( pageHasChanged == true ) {
return
}
if ( secondsElapsed % 3 == 0 ) {
numberOfInboxMessagesBinding . Set ( "Loading." )
numberOfConversationsBinding . Set ( "Loading." )
numberOfUnreadConversationsBinding . Set ( "Loading." )
} else if ( secondsElapsed % 3 == 1 ) {
numberOfInboxMessagesBinding . Set ( "Loading.." )
numberOfConversationsBinding . Set ( "Loading.." )
numberOfUnreadConversationsBinding . Set ( "Loading.." )
} else {
numberOfInboxMessagesBinding . Set ( "Loading..." )
numberOfConversationsBinding . Set ( "Loading..." )
numberOfUnreadConversationsBinding . Set ( "Loading..." )
}
time . Sleep ( time . Second )
secondsElapsed += 1
}
}
go updateLoadingProgress ( )
numberOfInboxMessages , err := myChatMessages . GetNumberOfMessagesInMyInbox ( identityType , appNetworkType )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
numberOfInboxMessagesString := helpers . ConvertIntToString ( numberOfInboxMessages )
numberOfInboxMessagesBinding . Set ( numberOfInboxMessagesString )
for {
pageHasChanged := checkIfPageHasChangedFunction ( )
if ( pageHasChanged == true ) {
return
}
conversationsReady , numberOfConversations , err := myChatConversations . GetNumberOfConversations ( identityType , appNetworkType )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
if ( conversationsReady == false ) {
continue
}
numberOfConversationsString := helpers . ConvertIntToString ( numberOfConversations )
numberOfConversationsBinding . Set ( numberOfConversationsString )
conversationsReady , numberOfUnreadConversations , err := myChatConversations . GetNumberOfUnreadConversations ( identityType , appNetworkType )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
if ( conversationsReady == false ) {
continue
}
numberOfUnreadConversationsString := helpers . ConvertIntToString ( numberOfUnreadConversations )
numberOfUnreadConversationsBinding . Set ( numberOfUnreadConversationsString )
resultsReadyBoolMutex . Lock ( )
resultsReadyBool = true
resultsReadyBoolMutex . Unlock ( )
return
}
}
viewChatFilterStatisticsButton := getWidgetCentered ( widget . NewButtonWithIcon ( "View My Chat Filter Statistics" , theme . VisibilityIcon ( ) , func ( ) {
setViewMyChatFilterStatisticsPage ( window , identityType , false , 0 , 0 , nil , currentPage )
} ) )
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , subtitle , widget . NewSeparator ( ) , numberOfInboxMessagesRow , numberOfConversationsRow , numberOfUnreadConversationsRow , widget . NewSeparator ( ) , viewChatFilterStatisticsButton )
setPageContent ( page , window )
go updateBindingsFunction ( )
}
func setViewMyChatFilterStatisticsPage ( window fyne . Window , myIdentityType string , statisticsReady bool , numberOfConversations int , numberOfFilteredConversations int , statisticsItemsList [ ] myChatFilterStatistics . ChatFilterStatisticsItem , previousPage func ( ) ) {
pageIdentifier , err := helpers . GetNewRandomHexString ( 16 )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
appMemory . SetMemoryEntry ( "CurrentViewedPage" , pageIdentifier )
checkIfPageHasChangedFunction := func ( ) bool {
exists , currentViewedPage := appMemory . GetMemoryEntry ( "CurrentViewedPage" )
if ( exists == true && currentViewedPage == pageIdentifier ) {
return false
}
return true
}
title := getPageTitleCentered ( "My " + myIdentityType + " Chat Filter Statistics" )
backButton := getBackButtonCentered ( previousPage )
if ( statisticsReady == false ) {
appNetworkType , err := getAppNetworkType . GetAppNetworkType ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
loadingTextBinding := binding . NewString ( )
loadingTextBinding . Set ( "Loading..." )
loadingTextLabel := widget . NewLabelWithData ( loadingTextBinding )
loadingTextLabel . TextStyle = getFyneTextStyle_Bold ( )
loadingTextLabelCentered := getWidgetCentered ( loadingTextLabel )
calculateStatisticsAndRefreshPageFunction := func ( ) {
var statisticsCompleteBoolMutex sync . RWMutex
statisticsCompleteBool := false
updateLoadingBindingFunction := func ( ) {
secondsElapsed := 0
for {
if ( secondsElapsed % 3 == 0 ) {
loadingTextBinding . Set ( "Loading." )
} else if ( secondsElapsed % 3 == 1 ) {
loadingTextBinding . Set ( "Loading.." )
} else {
loadingTextBinding . Set ( "Loading..." )
}
pageHasChanged := checkIfPageHasChangedFunction ( )
if ( pageHasChanged == true ) {
return
}
statisticsCompleteBoolMutex . RLock ( )
statisticsComplete := statisticsCompleteBool
statisticsCompleteBoolMutex . RUnlock ( )
if ( statisticsComplete == true ) {
return
}
time . Sleep ( time . Second )
secondsElapsed += 1
}
}
go updateLoadingBindingFunction ( )
numberOfConversations , numberOfFilteredConversations , myChatFilterStatisticsItemsList , err := myChatFilterStatistics . GetAllMyChatFilterStatistics ( myIdentityType , appNetworkType )
if ( err != nil ) {
statisticsCompleteBoolMutex . Lock ( )
statisticsCompleteBool = true
statisticsCompleteBoolMutex . Unlock ( )
pageHasChanged := checkIfPageHasChangedFunction ( )
if ( pageHasChanged == false ) {
setErrorEncounteredPage ( window , err , previousPage )
}
return
}
statisticsCompleteBoolMutex . Lock ( )
statisticsCompleteBool = true
statisticsCompleteBoolMutex . Unlock ( )
pageHasChanged := checkIfPageHasChangedFunction ( )
if ( pageHasChanged == false ) {
setViewMyChatFilterStatisticsPage ( window , myIdentityType , true , numberOfConversations , numberOfFilteredConversations , myChatFilterStatisticsItemsList , previousPage )
}
}
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , loadingTextLabelCentered )
setPageContent ( page , window )
go calculateStatisticsAndRefreshPageFunction ( )
return
}
myEnabledChatFiltersList , err := myChatFilters . GetAllMyEnabledChatFiltersList ( myIdentityType )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
numberOfConversationsString := helpers . ConvertIntToString ( numberOfConversations )
numberOfFilteredConversationsString := helpers . ConvertIntToString ( numberOfFilteredConversations )
if ( len ( myEnabledChatFiltersList ) == 0 ) {
noFiltersEnabledDescriptionA := getBoldLabelCentered ( "You have not enabled any chat filters." )
noFiltersEnabledDescriptionB := getLabelCentered ( "All " + numberOfConversationsString + " of your conversations will be shown." )
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , noFiltersEnabledDescriptionA , noFiltersEnabledDescriptionB )
setPageContent ( page , window )
return
}
description1 := getLabelCentered ( "Below are the percentages of conversations that pass your chat filters." )
description2 := getLabelCentered ( "Filtered Conversations divides by the number of conversations you would see without the filter." )
getStatisticsDisplayContainer := func ( ) ( * fyne . Container , error ) {
statisticsDisplayContainer := container . NewVBox ( )
for _ , chatFilterStatisticsItem := range statisticsItemsList {
filterName := chatFilterStatisticsItem . FilterName
numberOfRecipientsWhoPassFilter := chatFilterStatisticsItem . NumberOfRecipientsWhoPassFilter
percentageOfRecipientsWhoPassFilter := chatFilterStatisticsItem . PercentageOfRecipientsWhoPassFilter
numberOfFilterExcludedRecipients := chatFilterStatisticsItem . NumberOfFilterExcludedRecipients
percentageOfFilterExcludedRecipientsWhoPassFilter := chatFilterStatisticsItem . PercentageOfFilterExcludedRecipientsWhoPassFilter
filterIsEnabled := slices . Contains ( myEnabledChatFiltersList , filterName )
if ( filterIsEnabled == false ) {
// If the filter is not enabled, all conversations will pass the filter.
// We will not show the filter.
continue
}
getFilterTitle := func ( ) string {
if ( filterName == "ShowMyContactsOnly" ) {
return "Only show conversations with my contacts."
}
if ( filterName == "ShowMyMatchesOnly" ) {
return "Only show conversations with my matches."
}
if ( filterName == "ShowHasMessagedMeOnly" ) {
return "Only show conversations with users who have messaged me."
}
if ( filterName == "OnlyShowLikedUsers" ) {
return "Only show conversations with users I have liked."
}
if ( filterName == "HideIgnoredUsers" ) {
return "Hide conversations with users I have ignored."
}
return filterName
}
filterTitle := getFilterTitle ( )
filterTitleLabel := getItalicLabelCentered ( filterTitle )
allConversationsLabel := getBoldLabel ( "All Conversations" )
filteredConversationsLabel := getBoldLabel ( "Filtered Conversations" )
numberOfRecipientsWhoPassFilterString := helpers . ConvertIntToString ( numberOfRecipientsWhoPassFilter )
percentageOfRecipientsWhoPassFilterString := helpers . ConvertFloat64ToStringRounded ( percentageOfRecipientsWhoPassFilter , 2 )
numberOfFilterExcludedRecipientsString := helpers . ConvertIntToString ( numberOfFilterExcludedRecipients )
percentageOfFilterExcludedRecipientsWhoPassFilterString := helpers . ConvertFloat64ToStringRounded ( percentageOfFilterExcludedRecipientsWhoPassFilter , 2 )
allConversationsPercentageLabel := getLabelCentered ( numberOfRecipientsWhoPassFilterString + "/" + numberOfConversationsString + " = " + percentageOfRecipientsWhoPassFilterString + "%" )
filteredConversationsPercentageLabel := getLabelCentered ( numberOfFilteredConversationsString + "/" + numberOfFilterExcludedRecipientsString + " = " + percentageOfFilterExcludedRecipientsWhoPassFilterString + "%" )
filterStatisticsDisplayGrid := getContainerCentered ( container . NewGridWithColumns ( 2 , allConversationsLabel , filteredConversationsLabel , allConversationsPercentageLabel , filteredConversationsPercentageLabel ) )
statisticsDisplayContainer . Add ( filterTitleLabel )
statisticsDisplayContainer . Add ( filterStatisticsDisplayGrid )
statisticsDisplayContainer . Add ( widget . NewSeparator ( ) )
}
return statisticsDisplayContainer , nil
}
statisticsDisplayContainer , err := getStatisticsDisplayContainer ( )
if ( err != nil ) {
setErrorEncounteredPage ( window , err , previousPage )
return
}
statisticsDisplayContainerCentered := statisticsDisplayContainer
page := container . NewVBox ( title , backButton , widget . NewSeparator ( ) , description1 , description2 , widget . NewSeparator ( ) , statisticsDisplayContainerCentered )
setPageContent ( page , window )
}