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/internal/allowedText"
import "seekia/internal/appMemory"
import "seekia/internal/currencies"
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/myProofFunds"
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
	}

	fundsIcon, err := getFyneImageIcon("Funds")
	if (err != nil){
		setErrorEncounteredPage(window, err, func(){setHomePage(window)})
		return
	}

	fundsButton := widget.NewButton("Funds", func(){
			setViewMyProofFundsPage(window, myIdentityType, currentPage)
		})
	fundsButtonWithIcon := container.NewGridWithRows(2, fundsIcon, fundsButton)

	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, fundsButtonWithIcon, 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) {
				fyne.Do(func(){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)

					fyne.Do(func(){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())
			fyne.Do(func(){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.ConvertJPEGBase64StringToCroppedDownsizedImageObject(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", "DeepAppearanceSimilarity", "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
				messageCreationTime := messageObject.CreationTime
				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.ConvertJPEGBase64StringToCroppedDownsizedImageObject(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 }

				timeSentAgoText, err := helpers.ConvertUnixTimeToTimeFromNowTranslated(messageCreationTime, false)
				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(){
								setViewMessageDetailsButton(window, messageHash, messageCreationTime, currentPage)
							})
						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)
}


func setViewMessageDetailsButton(window fyne.Window, messageHash [26]byte, messageCreationTime int64, previousPage func()){

	currentPage := func(){setViewMessageDetailsButton(window, messageHash, messageCreationTime, previousPage)}

	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())

	timeCreatedLabel := widget.NewLabel("Time Created:")

	messageCreationTimeString := helpers.ConvertUnixTimeToTranslatedTime(messageCreationTime)

	messageCreationTimeLabel := getBoldLabel(messageCreationTimeString)

	creationTimeWarningButton := widget.NewButtonWithIcon("", theme.WarningIcon(), func(){
			title := translate("Creation Time Warning")
			dialogMessageA := getLabelCentered("Creation times are not verified.")
			dialogMessageB := getLabelCentered("They can be faked by the message author.")
			dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
			dialog.ShowCustom(title, translate("Close"), dialogContent, window)
		})

	messageCreationTimeRow := container.NewHBox(layout.NewSpacer(), timeCreatedLabel, messageCreationTimeLabel, creationTimeWarningButton, layout.NewSpacer())

	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

	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), messageHashRow, widget.NewSeparator(), messageCreationTimeRow, widget.NewSeparator(), reportMessageButton)

	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.ConvertImageObjectToStandardJPEGBase64String(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.ConvertJPEGBase64StringToCroppedDownsizedImageObject(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
		}

		currentFundsBalanceLabel := getLabelCentered("My Funds 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, appCurrencyProofFundsBalance, err := myProofFunds.GetMyProofFundsAccountBalanceInAnyCurrency(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 }

		appCurrencyProofFundsBalanceString := helpers.ConvertFloat64ToStringRounded(appCurrencyProofFundsBalance, 3)

		appCurrencySymbolButton := widget.NewButton(appCurrencySymbol, func(){
				setChangeAppCurrencyPage(window, currentPage)	
			})
		currentBalanceLabel := getBoldLabel(appCurrencyProofFundsBalanceString + " " + currentAppCurrencyCode)

		currentBalanceRow := container.NewHBox(layout.NewSpacer(), appCurrencySymbolButton, currentBalanceLabel, layout.NewSpacer())

		manageAccountFundsButton := getWidgetCentered(widget.NewButton("Manage", func(){
				setViewMyProofFundsPage(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 funds exist
			//	-error
			sendMessageFunction := func()(bool, bool, error){

				messageDaysToFund, err := durationDaysBinding.Get()
				if (err != nil){ return false, false, err }

				messageDurationToFund := messageDaysToFund * 86400

				parametersExist, sufficientFundsExist, err := myMessageQueue.AddMessageToMyMessageQueue(myIdentityHash, recipientIdentityHash, appNetworkType, messageDurationToFund, messageCommunication)
				if (err != nil) { return false, false, err }
				if (parametersExist == false){
					return false, false, nil
				}
				if (sufficientFundsExist == 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, sufficientFundsExist, 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 (sufficientFundsExist == false){
				title := translate("Insufficient Funds")
				dialogMessageA := getLabelCentered(translate("You do not have enough funds to send this message."))
				dialogMessageB := getLabelCentered(translate("Add more funds on the Add Funds page."))
				dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
				dialog.ShowCustom(title, translate("Close"), dialogContent, window)
				return
			}

			afterMessageSentPage()
		}))

		sendMessageContent := container.NewVBox(currentFundsBalanceLabel, currentBalanceRow, manageAccountFundsButton, 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){
			fyne.Do(func(){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){
			fyne.Do(func(){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){
				fyne.Do(func(){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){
				fyne.Do(func(){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){
					fyne.Do(func(){setErrorEncounteredPage(window, err, previousPage)})
				}
				return
			}

			statisticsCompleteBoolMutex.Lock()
			statisticsCompleteBool = true
			statisticsCompleteBoolMutex.Unlock()

			pageHasChanged := checkIfPageHasChangedFunction()
			if (pageHasChanged == false){
				fyne.Do(func(){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)
}


