package gui

// moderatorGui.go implements pages for moderators to review and browse content and identities, and to manage their own reviews.

//TODO: Add moderate full profiles page
//TODO: Add page to view my hidden content

import "fyne.io/fyne/v2"
import "fyne.io/fyne/v2/canvas"
import "fyne.io/fyne/v2/container"
import "fyne.io/fyne/v2/dialog"
import "fyne.io/fyne/v2/layout"
import "fyne.io/fyne/v2/theme"
import "fyne.io/fyne/v2/widget"

import "seekia/internal/appMemory"
import "seekia/internal/badgerDatabase"
import "seekia/internal/contentMetadata"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/imagery"
import "seekia/internal/messaging/chatMessageStorage"
import "seekia/internal/moderation/bannedModeratorConsensus"
import "seekia/internal/moderation/moderatorRanking"
import "seekia/internal/moderation/myHiddenContent"
import "seekia/internal/moderation/myIdentityScore"
import "seekia/internal/moderation/myReviews"
import "seekia/internal/moderation/mySkippedContent"
import "seekia/internal/moderation/myUnreviewed"
import "seekia/internal/moderation/readReviews"
import "seekia/internal/moderation/reportStorage"
import "seekia/internal/moderation/reviewStorage"
import "seekia/internal/moderation/verifiedVerdict"
import "seekia/internal/myIdentity"
import "seekia/internal/mySettings"
import "seekia/internal/network/appNetworkType/getAppNetworkType"
import "seekia/internal/network/backgroundDownloads"
import "seekia/internal/profiles/attributeDisplay"
import "seekia/internal/profiles/calculatedAttributes"
import "seekia/internal/profiles/myProfileStatus"
import "seekia/internal/profiles/profileFormat"
import "seekia/internal/profiles/profileStorage"
import "seekia/internal/profiles/readProfiles"

import "slices"
import "bytes"
import "strings"
import "time"
import "errors"

func setModeratePage(window fyne.Window, previousPageExists bool, previousPage func()){

	appMemory.SetMemoryEntry("CurrentViewedPage", "Moderate")

	currentPage := func(){setModeratePage(window, previousPageExists, previousPage)}
	homePage := func(){setHomePage(window)}
	
	title := getPageTitleCentered("Moderate")

	page := container.NewVBox(title)

	if (previousPageExists == true){

		backButton := getBackButtonCentered(previousPage)
		page.Add(backButton)
	}

	page.Add(widget.NewSeparator())

	description := getLabelCentered("Be a Seekia moderator.")
	page.Add(description)
	page.Add(widget.NewSeparator())

	rulesIcon, err := getFyneImageIcon("Info")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}

	profileIcon, err := getFyneImageIcon("Profile")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}

	settingsIcon, err := getFyneImageIcon("Settings")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}

	moderatorsIcon, err := getFyneImageIcon("Users")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}

	statsIcon, err := getFyneImageIcon("Stats")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}

	contentIcon, err := getFyneImageIcon("Choice")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}

	manageProfileButton := widget.NewButton("Profile", func(){
			setProfilePage(window, true, "Moderator", true, currentPage)
		})
	manageProfileButtonWithIcon := container.NewGridWithColumns(1, profileIcon, manageProfileButton)

	moderatorSettingsButton := widget.NewButton("Settings", func(){
			setModeratorSettingsPage(window, currentPage)
		})
	moderatorSettingsButtonWithIcon := container.NewGridWithColumns(1, settingsIcon, moderatorSettingsButton)

	rulesButton := widget.NewButton("Rules", func(){
			setViewSeekiaRulesPage(window, currentPage)
			//TODO: Create a rules page for moderators, explaining how to be a moderator
		})
	rulesButtonWithIcon := container.NewGridWithColumns(1, rulesIcon, rulesButton)

	moderatorsButton := widget.NewButton("Mods", func(){
			setViewModeratorsPage(window, currentPage)
		})
	moderatorsButtonWithIcon := container.NewGridWithColumns(1, moderatorsIcon, moderatorsButton)

	statsButton := widget.NewButton("Stats", func(){
			// TODO: Page to show statistics about the user's moderator reviews, and moderation statistics of the entire network
			// Example: Unreviewed profiles, number of banned profiles, and more
			showUnderConstructionDialog(window)
		})
	statsButtonWithIcon := container.NewGridWithColumns(1, statsIcon, statsButton)

	contentButton := widget.NewButton("Content", func(){
			setBrowseContentPage(window, currentPage)
		})
	contentButtonWithIcon := container.NewGridWithColumns(1, contentIcon, contentButton)

	buttonsRow := getContainerCentered(container.NewGridWithRows(1, rulesButtonWithIcon, manageProfileButtonWithIcon, moderatorSettingsButtonWithIcon, moderatorsButtonWithIcon, statsButtonWithIcon, contentButtonWithIcon))

	page.Add(buttonsRow)
	page.Add(widget.NewSeparator())

	exists, moderatorModeStatus, err := mySettings.GetSetting("ModeratorModeOnOffStatus")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}
	if (exists == false || moderatorModeStatus == "Off"){

		description1 := getBoldLabelCentered("You must enable moderator mode to moderate.")
		description2 := getLabelCentered("Enable it on the Settings page.")

		page.Add(description1)
		page.Add(description2)

		setPageContent(page, window)
		return
	}

	identityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash("Moderator")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}
	if (identityExists == false){

		description1 := getBoldLabelCentered("Your moderator identity does not exist.")
		description2 := getLabelCentered("You must choose an identity.")
		description3 := getLabelCentered("This is how users of Seekia will identify you.")

		chooseIdentityButton := getWidgetCentered(widget.NewButtonWithIcon("Choose Identity", theme.NavigateNextIcon(), func(){
				setChooseNewIdentityHashPage(window, "Moderator", currentPage, currentPage)
			}))

		page.Add(description1)
		page.Add(description2)
		page.Add(description3)
		page.Add(chooseIdentityButton)

		setPageContent(page, window)
		return
	}

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	identityExists, iAmOnlineStatus, err := myProfileStatus.GetMyProfileIsActiveStatus(myIdentityHash, appNetworkType)
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}
	if (identityExists == false) {
		setErrorEncounteredPage(window, errors.New("My identity not found after being found already."), homePage)
		return
	}
	if (iAmOnlineStatus == false){

		description1 := getBoldLabelCentered("Your Moderator profile is offline.")
		description2 := getLabelCentered("Broadcast your profile on the Profile - Broadcast page.")		

		page.Add(description1)
		page.Add(description2)

		setPageContent(page, window)
		return
	}

	//TODO: Check for moderation parameters

	moderateDescriptionA := getBoldLabelCentered("You are ready to moderate.")
	moderateDescriptionB := getLabelCentered("Choose the type of content to moderate.")
	page.Add(moderateDescriptionA)
	page.Add(moderateDescriptionB)
	page.Add(widget.NewSeparator())

	mateIcon, err := getFyneImageIcon("Mate")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}
	mateButton := widget.NewButton("Mate Profiles", func(){
			setChooseProfileContentToModeratePage(window, "Mate", currentPage)
		})
	mateColumn := container.NewGridWithColumns(1, mateIcon, mateButton)

	hostIcon, err := getFyneImageIcon("Host")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}
	hostButton := widget.NewButton("Host Profiles", func(){
			setChooseProfileContentToModeratePage(window, "Host", currentPage)
		})
	hostColumn := container.NewGridWithColumns(1, hostIcon, hostButton)

	moderatorIcon, err := getFyneImageIcon("Moderate")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}
	moderatorButton := widget.NewButton("Moderator Profiles", func(){
			setChooseProfileContentToModeratePage(window, "Moderator", currentPage)
		})
	moderatorColumn := container.NewGridWithColumns(1, moderatorIcon, moderatorButton)

	profilesRow := getContainerCentered(container.NewGridWithRows(1, mateColumn, hostColumn, moderatorColumn))
	page.Add(profilesRow)

	textMessagesImage, err := getFyneImageIcon("InspectText")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}
	textMessagesButton := widget.NewButton("Text Messages", func(){
			setModerateMessagesPage(window, "Text", false, [26]byte{}, currentPage)
		})
	textMessagesColumn := container.NewGridWithColumns(1, textMessagesImage, textMessagesButton)

	imageMessagesIcon, err := getFyneImageIcon("Photo")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}
	imageMessagesButton := widget.NewButton("Image Messages", func(){
			setModerateMessagesPage(window, "Image", false, [26]byte{}, currentPage)
		})
	imageMessagesColumn := container.NewGridWithColumns(1, imageMessagesIcon, imageMessagesButton)

	messageButtonsRow := getContainerCentered(container.NewGridWithRows(1, textMessagesColumn, imageMessagesColumn))
	page.Add(messageButtonsRow)

	identitiesIcon, err := getFyneImageIcon("Profile")
	if (err != nil){
		setErrorEncounteredPage(window, err, homePage)
		return
	}	

	identitiesButton := widget.NewButton("Identities", func(){
			setModerateIdentitiesPage(window, currentPage)
		})

	identitiesColumn := getContainerCentered(container.NewGridWithColumns(1, identitiesIcon, identitiesButton))
	page.Add(identitiesColumn)

	setPageContent(page, window)
}


func setChooseProfileContentToModeratePage(window fyne.Window, profileType string, previousPage func()){

	setLoadingScreen(window, "Moderate " + profileType + " Profiles", "Loading profiles...")

	currentPage := func(){setChooseProfileContentToModeratePage(window, profileType, previousPage)}

	title := getPageTitleCentered("Moderate " + profileType + " Profiles")

	backButton := getBackButtonCentered(previousPage)

	//TODO: Check if we are moderating profiles of this profileType

	description := getLabelCentered("Choose a profile attribute to moderate (under construction).")

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	moderateFullProfilesButton := getWidgetCentered(widget.NewButton("Moderate Full Profiles", func(){
			//TODO: A page to moderate full profiles, which have at least 1 full profile ban.
			// This page will show profiles whose full profile has been banned.
			// This is necessary, because profiles which have been fully banned must be fully approved to undo the effect of the ban.
			// Approving all of the profile's attributes individually is cumbersome, and takes up more space in the form of reviews.
			// Also, viewing a full profile is necessary when the full profile has been banned, because
			//		whatever is unruleful about the profile can't be isolated to a single attribute.
			//		If it could be isolated to a single attribute, the moderator would have banned that attribute, not the full profile
			//		The exception is with malicious moderators, who could abuse this feature to waste 
			//			the time of moderators by banning many full profiles.
			showUnderConstructionDialog(window)
		}))

	nameLabel := getItalicLabelCentered("Name")
	isCanonicalLabel := getItalicLabelCentered("Is Canonical")
	anyUnreviewedLabel := getItalicLabelCentered("Any Unreviewed?")
	emptyLabel := widget.NewLabel("")

	attributeNamesColumn := container.NewVBox(nameLabel, widget.NewSeparator())
	attributeIsCanonicalColumn := container.NewVBox(isCanonicalLabel, widget.NewSeparator())
	anyUnreviewedExistColumn := container.NewVBox(anyUnreviewedLabel, widget.NewSeparator())
	viewAttributeButtonsColumn := container.NewVBox(emptyLabel, widget.NewSeparator())

	addAttributeRow := func(attributeName string, attributeIsCanonical bool, attributeIdentifier int)error{

		attributeIsCanonicalString := helpers.ConvertBoolToYesOrNoString(attributeIsCanonical)

		anyExist, _, _, _, err := myUnreviewed.GetMyHighestPriorityUnreviewedProfileAttributeHash(profileType, attributeIdentifier, appNetworkType)
		if (err != nil) { return err }

		getAnyUnreviewedExistLabel := func()*fyne.Container{

			if (anyExist == false){

				noLabel := getLabelCentered(translate("No"))
				return noLabel
			}

			yesLabel := getBoldLabelCentered(translate("Yes"))

			return yesLabel
		}

		anyUnreviewedExistLabel := getAnyUnreviewedExistLabel()

		attributeNameLabel := getBoldLabelCentered(attributeName)
		attributeIsCanonicalLabel := getBoldLabelCentered(attributeIsCanonicalString)
		viewAttributeButton := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func(){
				setModerateProfileAttributesPage(window, profileType, attributeIdentifier, false, [27]byte{}, [28]byte{}, [16]byte{}, currentPage)
			})

		attributeNamesColumn.Add(attributeNameLabel)
		attributeIsCanonicalColumn.Add(attributeIsCanonicalLabel)
		anyUnreviewedExistColumn.Add(anyUnreviewedExistLabel)
		viewAttributeButtonsColumn.Add(viewAttributeButton)

		attributeNamesColumn.Add(widget.NewSeparator())
		attributeIsCanonicalColumn.Add(widget.NewSeparator())
		anyUnreviewedExistColumn.Add(widget.NewSeparator())
		viewAttributeButtonsColumn.Add(widget.NewSeparator())

		return nil
	}

	addAttributeRows := func()error{

		if (profileType == "Mate"){
			err := addAttributeRow("Photos", false, 18)
			if (err != nil){ return err }			
		}

		err := addAttributeRow("Username", false, 10)
		if (err != nil) { return err }

		err = addAttributeRow("Description", false, 9)
		if (err != nil) { return err }

		if (profileType == "Mate"){
			err := addAttributeRow("Age", true, 8)
			if (err != nil) { return err }

			err = addAttributeRow("Sex", true, 7)
			if (err != nil) { return err }

			err = addAttributeRow("Height", true, 6)
			if (err != nil) { return err }
		}

		//TODO: Add more attributes

		return nil
	}

	err = addAttributeRows()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	isCanonicalHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setAttributeIsCanonicalExplainerPage(window, currentPage)
		})
	anyUnreviewedExistHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
			setAnyUnreviewedAttributesExistExplainerPage(window, currentPage)
		})

	attributeIsCanonicalColumn.Add(isCanonicalHelpButton)
	anyUnreviewedExistColumn.Add(anyUnreviewedExistHelpButton)

	attributeGrid := container.NewHBox(layout.NewSpacer(), attributeNamesColumn, attributeIsCanonicalColumn, anyUnreviewedExistColumn, viewAttributeButtonsColumn, layout.NewSpacer())

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), moderateFullProfilesButton, widget.NewSeparator(), attributeGrid)

	setPageContent(page, window)
}

// This page is used by a moderator to browse their unreviewed profile attributes, review the content, and submit a verdict
// It provides skip and hide buttons, and serve new attributes to review to the moderator 
func setModerateProfileAttributesPage(window fyne.Window, profileType string, attributeIdentifier int, attributeProvided bool, attributeHash [27]byte, attributeProfileHash [28]byte, attributeAuthorIdentityHash [16]byte, previousPage func()){

	setLoadingScreen(window, "Moderating Attributes", "Loading attribute...")

	currentPage := func(){setModerateProfileAttributesPage(window, profileType, attributeIdentifier, attributeProvided, attributeHash, attributeProfileHash, attributeAuthorIdentityHash, previousPage)}

	refreshPageWithNewContent := func(){setModerateProfileAttributesPage(window, profileType, attributeIdentifier, false, [27]byte{}, [28]byte{}, [16]byte{}, previousPage)}

	title := getPageTitleCentered("Moderating Attributes")

	backButton := getBackButtonCentered(previousPage)

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (attributeProvided == false){

		attributeExists, newAttributeHash, newAttributeProfileHash, newAttributeAuthorIdentityHash, err := myUnreviewed.GetMyHighestPriorityUnreviewedProfileAttributeHash(profileType, attributeIdentifier, appNetworkType)
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		if (attributeExists == false){

			attributeName, err := profileFormat.GetAttributeNameFromAttributeIdentifier(attributeIdentifier)
			if (err != nil){
				setErrorEncounteredPage(window, err, previousPage)
				return
			}

			description1 := getBoldLabelCentered("No " + attributeName + " attributes remaining to moderate.")
			description2 := getLabelCentered("Check back later for new attributes to review.")
			//TODO: Add check for if space is full, and add description to say to increase space allowed for moderation content

			page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2)

			setPageContent(page, window)
			return
		}

		setModerateProfileAttributesPage(window, profileType, attributeIdentifier, true, newAttributeHash, newAttributeProfileHash, newAttributeAuthorIdentityHash, previousPage)
		return
	}

	viewAttributeDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("View Attribute Details", theme.VisibilityIcon(), func(){
			setViewAttributeModerationDetailsPage(window, attributeHash, currentPage)
		}))

	profileExists, profileBytes, err := profileStorage.GetStoredProfile(attributeProfileHash)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (profileExists == false){
		// Profile must have been deleted after myUnreviewed found it
		// We will refresh page and show loading screen so we can tell if this is happening
		setLoadingScreen(window, "Moderating Attributes", "Trying to find attribute...")

		time.Sleep(time.Second)

		refreshPageWithNewContent()
		return
	}

	attributeName, err := profileFormat.GetAttributeNameFromAttributeIdentifier(attributeIdentifier)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	attributeTitle, _, _, _, _, err := attributeDisplay.GetProfileAttributeDisplayInfo(attributeName)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	attributeNameLabel := widget.NewLabel("Attribute Name:")
	attributeTitleLabel := getBoldLabel(attributeTitle)
	attributeTitleRow := container.NewHBox(layout.NewSpacer(), attributeNameLabel, attributeTitleLabel, layout.NewSpacer())

	attributeDisplayContainer, err := getProfileAttributeDisplayForModeration(window, attributeName, attributeIdentifier, profileBytes, currentPage)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	appNetworkTypeString := helpers.ConvertByteToString(appNetworkType)
	attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])

	submitAttributeReview := func(verdict string, reasonExists bool, reason string)error{

		newReviewMap := map[string]string{
			"NetworkType": appNetworkTypeString,
			"ReviewedHash": attributeHashHex,
			"Verdict": verdict,
		}

		if (reasonExists == true){
			newReviewMap["Reason"] = reason
		}

		myIdentityExists, err := myReviews.CreateAndBroadcastMyReview(newReviewMap)
		if (err != nil) { return err }
		if (myIdentityExists == false) {
			return errors.New("My moderator identity not found when trying to submit review.")
		}

		return nil
	}

	submitBanIdentityReview := func(reasonExists bool, reason string)error{

		attributeAuthorString, _, err := identity.EncodeIdentityHashBytesToString(attributeAuthorIdentityHash)
		if (err != nil) { return err }

		attributeProfileHashHex := encoding.EncodeBytesToHexString(attributeProfileHash[:])

		identityReviewMap := map[string]string{
			"NetworkType": appNetworkTypeString,
			"ReviewedHash": attributeAuthorString,
			"Verdict": "Ban",
			"ErrantAttributes": attributeHashHex,
			"ErrantProfiles": attributeProfileHashHex,
		}

		if (reasonExists == true){
			identityReviewMap["Reason"] = reason
		}

		myIdentityExists, err := myReviews.CreateAndBroadcastMyReview(identityReviewMap)
		if (err != nil) { return err }
		if (myIdentityExists == false) {
			return errors.New("My moderator identity not found when trying to submit review.")
		}

		return nil
	}

	approveAttributeWithReasonFunction := func(reasonExists bool, reason string)error{

		err := submitAttributeReview("Approve", reasonExists, reason)
		if (err != nil) { return err }

		return nil
	}
	banAttributeWithReasonFunction := func(reasonExists bool, reason string)error{
		err := submitAttributeReview("Ban", reasonExists, reason)
		if (err != nil) { return err }

		return nil
	}
	banAttributeAndIdentityWithReasonFunction := func(reasonExists bool, reason string)error{
		err := submitAttributeReview("Ban", reasonExists, reason)
		if (err != nil) { return err }

		err = submitBanIdentityReview(reasonExists, reason)
		if (err != nil) { return err }

		return nil
	}

	approveAttributeButton := widget.NewButtonWithIcon("Approve Attribute", theme.ConfirmIcon(), func(){

			setEnterReasonAndSubmitReviewPage(window, "Submit Approve Review", "Approve Attribute", "Approve", approveAttributeWithReasonFunction, currentPage, refreshPageWithNewContent)
		})
	banAttributeButton := widget.NewButtonWithIcon("Ban Attribute", theme.CancelIcon(), func(){

			setEnterReasonAndSubmitReviewPage(window, "Submit Ban Review", "Ban Attribute", "Ban", banAttributeWithReasonFunction, currentPage, refreshPageWithNewContent)
		})

	banAttributeAndIdentityButton := widget.NewButtonWithIcon("Ban Attribute And Identity", theme.CancelIcon(), func(){

			setEnterReasonAndSubmitReviewPage(window, "Submit Ban Reviews", "Ban Attribute And Identity", "Ban", banAttributeAndIdentityWithReasonFunction, currentPage, refreshPageWithNewContent)
		})

	reviewButtonsColumn := getContainerCentered(container.NewGridWithColumns(1, approveAttributeButton, banAttributeButton, banAttributeAndIdentityButton))

	skipButton := widget.NewButtonWithIcon("Skip", theme.MediaFastForwardIcon(), func(){

			// This will skip this attribute. It will be moved to the back of the myUnreviewed queue
			err := mySkippedContent.AddAttributeToMySkippedAttributesMap(attributeHash, attributeAuthorIdentityHash)
			if (err != nil){
				setErrorEncounteredPage(window, err, currentPage)
				return
			}
			refreshPageWithNewContent()
		})
	hideButton := widget.NewButtonWithIcon("Hide", theme.VisibilityOffIcon(), func(){
			setConfirmHideContentPage(window, attributeHash[:], attributeAuthorIdentityHash, currentPage, refreshPageWithNewContent)
		})

	skipAndHideButtonsColumn := getContainerCentered(container.NewGridWithColumns(1, skipButton, hideButton))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), viewAttributeDetailsButton, widget.NewSeparator(), attributeTitleRow, widget.NewSeparator(), attributeDisplayContainer, widget.NewSeparator(), reviewButtonsColumn, widget.NewSeparator(), skipAndHideButtonsColumn)

	setPageContent(page, window)
}


// This page is used by a moderator to browse their unreviewed messages, review their content, and submit a verdict
func setModerateMessagesPage(window fyne.Window, imageOrText string, messageProvided bool, messageHash [26]byte, previousPage func()){

	currentPage := func(){setModerateMessagesPage(window, imageOrText, messageProvided, messageHash, previousPage)}
	refreshPageWithNewContent := func(){setModerateMessagesPage(window, imageOrText, false, [26]byte{}, previousPage)}

	title := getPageTitleCentered("Moderating Messages")

	backButton := getBackButtonCentered(previousPage)

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (messageProvided == false){

		exists, moderatingMessages, err := mySettings.GetSetting("ModerateMessagesOnOffStatus")
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}
		if (exists == false || moderatingMessages != "On"){
			
			description1 := getBoldLabelCentered("You are not moderating messages.")
			description2 := getLabelCentered("You must enable message moderation mode.")

			enableButton := getWidgetCentered(widget.NewButtonWithIcon("Enable", theme.NavigateNextIcon(), func(){
					setManageModerateMessagesModePage(window, currentPage)
				}))

			page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, enableButton)

			setPageContent(page, window)
			return
		}

		setLoadingScreen(window, "Moderate Messages", "Loading message...")

		messageExists, newMessageHash, err := myUnreviewed.GetMyHighestPriorityUnreviewedMessageHash(appNetworkType, imageOrText)
		if (err != nil){
			setErrorEncounteredPage(window, err, previousPage)
			return
		}

		if (messageExists == false){

			description1 := getBoldLabelCentered("No messages remain to moderate.")
			description2 := getLabelCentered("Check back later for new messages.")
			//TODO: Add check for if space is full, and add message to say to increase space allowed for moderation content

			page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2)

			setPageContent(page, window)
			return
		}

		setModerateMessagesPage(window, imageOrText, true, newMessageHash, previousPage)
		return
	}

	viewMessageDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("View Message Details", theme.VisibilityIcon(), func(){
			setViewMessageModerationDetailsPage(window, messageHash, currentPage)
		}))

	//Outputs:
	//	-bool: Necessary information found to display message
	//	-[16]byte: Message author
	//	-[32]byte: Message cipher key
	//	-string: Message communication
	//	-error
	getMessageMetadata := func()(bool, [16]byte, [32]byte, string, error){

		messageExists, cipherKeyFound, ableToDecrypt, messageCipherKey, senderIdentityHash, messageCommunication, err := chatMessageStorage.GetDecryptedMessageForModeration(messageHash)
		if (err != nil){ return false, [16]byte{}, [32]byte{}, "", err }
		if (messageExists == false){
			// We cannot review messages which we do not have downloaded
			// Message may have been deleted after earlier check
			return false, [16]byte{}, [32]byte{}, "", nil
		}
		if (cipherKeyFound == false){
			// No valid reviews/reports exist for this message.
			// myUnreviewed should have checked this before.
			// Report could have been deleted.
			return false, [16]byte{}, [32]byte{}, "", nil
		}
		if (ableToDecrypt == false){
			// myUnreviewed should have checked for this
			return false, [16]byte{}, [32]byte{}, "", errors.New("myUnreviewed returning message that cannot be read.")
		}

		return true, senderIdentityHash, messageCipherKey, messageCommunication, nil
	}

	messageInfoFound, senderIdentityHash, messageCipherKey, messageCommunication, err := getMessageMetadata()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (messageInfoFound == false){
		// We will refresh page and show loading screen so we can tell if this is happening
		setLoadingScreen(window, "Moderate Messages", "Trying to find message...")

		time.Sleep(time.Second)

		refreshPageWithNewContent()
		return
	}

	messageContentContainer, err := getMessageDisplayForModeration(window, messageCommunication, currentPage)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	appNetworkTypeString := helpers.ConvertByteToString(appNetworkType)
	messageHashHex := encoding.EncodeBytesToHexString(messageHash[:])

	submitMessageReview := func(verdict string, reasonExists bool, reason string)error{

		messageCipherKeyHex := encoding.EncodeBytesToHexString(messageCipherKey[:])

		newReviewMap := map[string]string{
			"NetworkType": appNetworkTypeString,
			"ReviewedHash": messageHashHex,
			"MessageCipherKey": messageCipherKeyHex,
			"Verdict": verdict,
		}

		if (reasonExists == true){
			newReviewMap["Reason"] = reason
		}

		myIdentityExists, err := myReviews.CreateAndBroadcastMyReview(newReviewMap)
		if (err != nil) { return err }
		if (myIdentityExists == false) {
			return errors.New("My moderator identity not found when trying to submit review.")
		}

		return nil
	}

	submitBanIdentityReview := func(reasonExists bool, reason string)error{

		senderIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(senderIdentityHash)
		if (err != nil) { return err }

		identityReviewMap := map[string]string{
			"NetworkType": appNetworkTypeString,
			"ReviewedHash": senderIdentityHashString,
			"Verdict": "Ban",
			"ErrantMessages": messageHashHex,
		}

		if (reasonExists == true){
			identityReviewMap["Reason"] = reason
		}

		myIdentityExists, err := myReviews.CreateAndBroadcastMyReview(identityReviewMap)
		if (err != nil) { return err }
		if (myIdentityExists == false) {
			return errors.New("My moderator identity not found when trying to submit review.")
		}

		return nil
	}

	approveMessageWithReasonFunction := func(reasonExists bool, reason string)error{

		err := submitMessageReview("Approve", reasonExists, reason)
		if (err != nil) { return err }

		return nil
	}
	banMessageWithReasonFunction := func(reasonExists bool, reason string)error{
		err := submitMessageReview("Ban", reasonExists, reason)
		if (err != nil) { return err }

		return nil
	}
	banMessageAndIdentityWithReasonFunction := func(reasonExists bool, reason string)error{
		err := submitMessageReview("Ban", reasonExists, reason)
		if (err != nil) { return err }

		err = submitBanIdentityReview(reasonExists, reason)
		if (err != nil) { return err }

		return nil
	}

	approveMessageButton := widget.NewButtonWithIcon("Approve Message", theme.ConfirmIcon(), func(){

			setEnterReasonAndSubmitReviewPage(window, "Submit Approve Review", "Approve Message", "Approve", approveMessageWithReasonFunction, currentPage, refreshPageWithNewContent)
		})
	banMessageButton := widget.NewButtonWithIcon("Ban Message", theme.CancelIcon(), func(){

			setEnterReasonAndSubmitReviewPage(window, "Submit Ban Review", "Ban Message", "Ban", banMessageWithReasonFunction, currentPage, refreshPageWithNewContent)
		})

	banMessageAndSenderButton := widget.NewButtonWithIcon("Ban Message And Sender", theme.CancelIcon(), func(){

			setEnterReasonAndSubmitReviewPage(window, "Submit Ban Reviews", "Ban Message and Sender", "Ban", banMessageAndIdentityWithReasonFunction, currentPage, refreshPageWithNewContent)
		})

	reviewButtonsColumn := getContainerCentered(container.NewGridWithColumns(1, approveMessageButton, banMessageButton, banMessageAndSenderButton))

	skipButton := widget.NewButtonWithIcon("Skip", theme.MediaFastForwardIcon(), func(){

			// This will skip this attribute. It will be moved to the back of the myUnreviewed queue
			err := mySkippedContent.AddMessageToMySkippedMessagesMap(messageHash, senderIdentityHash)
			if (err != nil){
				setErrorEncounteredPage(window, err, currentPage)
				return
			}
			refreshPageWithNewContent()
		})
	hideButton := widget.NewButtonWithIcon("Hide", theme.VisibilityOffIcon(), func(){
			setConfirmHideContentPage(window, messageHash[:], senderIdentityHash, currentPage, refreshPageWithNewContent)
		})

	skipAndHideButtonsColumn := getContainerCentered(container.NewGridWithColumns(1, skipButton, hideButton))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), viewMessageDetailsButton, widget.NewSeparator(), messageContentContainer, widget.NewSeparator(), reviewButtonsColumn, widget.NewSeparator(), skipAndHideButtonsColumn)

	setPageContent(page, window)
}

// This will hide the profile/message/attribute permanently so it does not show up anymore
// This is useful if the content is in a language the user does not understand
func setConfirmHideContentPage(window fyne.Window, contentHash []byte, authorIdentityHash [16]byte, previousPage func(), pageToVisitAfter func()){

	currentPage := func(){setConfirmHideContentPage(window, contentHash, authorIdentityHash, previousPage, pageToVisitAfter)}

	contentType, err := helpers.GetContentTypeFromContentHash(contentHash)
	if (err != nil){
		contentHashHex := encoding.EncodeBytesToHexString(contentHash)
		setErrorEncounteredPage(window, errors.New("setConfirmHideContentPage called with invalid contentHash: " + contentHashHex), previousPage)
		return
	}

	if (contentType != "Message" && contentType != "Profile" && contentType != "Attribute"){
		contentHashHex := encoding.EncodeBytesToHexString(contentHash)
		setErrorEncounteredPage(window, errors.New("setConfirmHideContentPage called with invalid contentHash: " + contentHashHex), previousPage)
		return
	}

	title := getPageTitleCentered("Confirm Hide " + contentType)

	backButton := getBackButtonCentered(previousPage)

	description1 := getBoldLabelCentered("Confirm Hide " + contentType + "?")

	description2 := getLabelCentered("This will prevent this " + contentType + " from showing up in your moderation queue.")
	description3 := getLabelCentered("This is useful when you don't want to review the " + contentType + ".")
	description4 := getLabelCentered("For example, the " + contentType + " may be written in a language you don't understand.")

	confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm", theme.ConfirmIcon(), func(){

			if (contentType == "Message"){

				if (len(contentHash) != 26){
					contentHashHex := encoding.EncodeBytesToHexString(contentHash)
					setErrorEncounteredPage(window, errors.New("GetContentTypeFromContentHash returning Message for different length content hash: " + contentHashHex), currentPage)
					return
				}

				messageHash := [26]byte(contentHash)

				err := myHiddenContent.AddMessageToMyHiddenMessagesMap(messageHash, authorIdentityHash)
				if (err != nil){
					setErrorEncounteredPage(window, err, currentPage)
					return
				}
			} else if (contentType == "Profile"){

				if (len(contentHash) != 28){
					contentHashHex := encoding.EncodeBytesToHexString(contentHash)
					setErrorEncounteredPage(window, errors.New("GetContentTypeFromContentHash returning Profile for different length content hash: " + contentHashHex), currentPage)
					return
				}

				profileHash := [28]byte(contentHash)

				err := myHiddenContent.AddProfileToMyHiddenProfilesMap(profileHash, authorIdentityHash)
				if (err != nil){
					setErrorEncounteredPage(window, err, currentPage)
					return
				}

			} else if (contentType == "Attribute"){

				if (len(contentHash) != 27){
					contentHashHex := encoding.EncodeBytesToHexString(contentHash)
					setErrorEncounteredPage(window, errors.New("GetContentTypeFromContentHash returning Attribute for different length content hash: " + contentHashHex), currentPage)
					return
				}

				attributeHash := [27]byte(contentHash)

				err := myHiddenContent.AddAttributeToMyHiddenAttributesMap(attributeHash, authorIdentityHash)
				if (err != nil){
					setErrorEncounteredPage(window, err, currentPage)
					return
				}
			}

			pageToVisitAfter()
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, confirmButton)

	setPageContent(page, window)
}


func setEnterReasonAndSubmitReviewPage(window fyne.Window, pageTitle string, actionName string, verdict string, submitFunctionWithReason func(bool, string)error, previousPage func(), pageToVisitAfter func()){

	title := getPageTitleCentered(pageTitle)

	backButton := getBackButtonCentered(previousPage)

	actionLabel := getItalicLabelCentered("Action:")
	actionDescription := getBoldLabelCentered(actionName)

	enterReasonLabel := getLabelCentered("Enter " + verdict + " Reason (Optional):")

	reasonEntry := widget.NewMultiLineEntry()
	reasonEntry.SetPlaceHolder("Enter reason...")

	reasonEntryBoxed := getWidgetBoxed(reasonEntry)

	submitButton := getWidgetCentered(widget.NewButtonWithIcon("Submit", theme.ConfirmIcon(), func(){

			reason := reasonEntry.Text

			if (reason == ""){
				err := submitFunctionWithReason(false, "")
				if (err != nil) {
					setErrorEncounteredPage(window, err, previousPage)
					return
				}
			} else {
				err := submitFunctionWithReason(true, reason)
				if (err != nil) {
					setErrorEncounteredPage(window, err, previousPage)
					return
				}
			}

			pageToVisitAfter()
		}))

	widener := widget.NewLabel("                                                       ")

	submitButtonWithWidener := container.NewGridWithColumns(1, submitButton, widener)

	entryWithSubmitButton := getContainerCentered(container.NewGridWithColumns(1, reasonEntryBoxed, submitButtonWithWidener))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), actionLabel, actionDescription, widget.NewSeparator(), enterReasonLabel, entryWithSubmitButton)

	setPageContent(page, window)
}

func setViewProfileForModerationPage(window fyne.Window, profileHash [28]byte, previousPage func()){

	currentPage := func(){setViewProfileForModerationPage(window, profileHash, previousPage)}

	title := getPageTitleCentered("Viewing Profile")

	backButton := getBackButtonCentered(previousPage)

	profileHashTitle := widget.NewLabel("Profile Hash:")

	profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])

	profileHashTrimmed, _, err := helpers.TrimAndFlattenString(profileHashHex, 10)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	profileHashLabel := getBoldLabel(profileHashTrimmed)
	viewProfileHashButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
			setViewContentHashPage(window, "Profile", profileHash[:], currentPage)
		})
	profileHashRow := container.NewHBox(layout.NewSpacer(), profileHashTitle, profileHashLabel, viewProfileHashButton, layout.NewSpacer())

	getProfileDisplayContainer := func()(*fyne.Container, error){

		profileExists, profileBytes, err := profileStorage.GetStoredProfile(profileHash)
		if (err != nil) { return nil, err }
		if (profileExists == false){
			description1 := getBoldLabelCentered("Profile not downloaded.")
			description2 := getLabelCentered("Try to download?")

			downloadButton := getWidgetCentered(widget.NewButton("Download", func(){
					setDownloadContentFromHashPage(window, "Profile", profileHash[:], currentPage)
				}))	

			content := container.NewVBox(description1, description2, downloadButton)
			return content, nil
		}

		ableToRead, currentProfileHash, profileVersion, _, profileIdentityHash, _, _, rawProfileMap, err := readProfiles.ReadProfileAndHash(false, profileBytes)
		if (err != nil) { return nil, err }
		if (ableToRead == false){
			return nil, errors.New("Database corrupt: Contains invalid profile.")
		}
		if (profileHash != currentProfileHash) {
			return nil, errors.New("Database corrupt: Profile hash does not match entry key")
		}

		profileAuthorLabel := widget.NewLabel("Profile Author:")

		profileIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(profileIdentityHash)
		if (err != nil){ return nil, err } 

		profileIdentityHashLabel := getBoldLabel(profileIdentityHashString)
		profileIdentityHashRow := container.NewHBox(layout.NewSpacer(), profileAuthorLabel, profileIdentityHashLabel, layout.NewSpacer())

		getAnyProfileAttributeFunction, err := calculatedAttributes.GetRetrieveAnyProfileAttributeIncludingCalculatedFunction(profileVersion, rawProfileMap)
		if (err != nil) { return nil, err }

		viewProfileButton := getWidgetCentered(widget.NewButtonWithIcon("View Profile", theme.VisibilityIcon(), func(){
				setViewUserProfilePage(window, false, profileHash, 0, getAnyProfileAttributeFunction, currentPage)
			}))

		profileDisplayWithInfo := container.NewVBox(profileIdentityHashRow, viewProfileButton)

		return profileDisplayWithInfo, nil
	}

	profileDisplayContainer, err := getProfileDisplayContainer()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), profileHashRow, profileDisplayContainer)

	setPageContent(page, window)
}

// This page allows a moderator to view a profile attribute value
// It is different from setModerateProfileAttributesPage,
//		which allows the moderator to review the attribute, provides skip/hide buttons, and serves
//		the next attribute to review
func setViewProfileAttributeForModerationPage(window fyne.Window, attributeHash [27]byte, previousPage func()){

	currentPage := func(){setViewProfileAttributeForModerationPage(window, attributeHash, previousPage)}

	title := getPageTitleCentered("Viewing Profile Attribute")

	backButton := getBackButtonCentered(previousPage)

	attributeHashTitle := widget.NewLabel("Attribute Hash:")

	attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])

	attributeHashTrimmed, _, err := helpers.TrimAndFlattenString(attributeHashHex, 10)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	attributeHashLabel := getBoldLabel(attributeHashTrimmed)
	viewAttributeHashButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
			setViewContentHashPage(window, "Attribute", attributeHash[:], currentPage)
		})
	attributeHashRow := container.NewHBox(layout.NewSpacer(), attributeHashTitle, attributeHashLabel, viewAttributeHashButton, layout.NewSpacer())

	// We have to find the profile hash that the attribute belongs to
	// If we cannot find any profiles with this attribute, we will offer the user to try to download profiles for this attribute hash

	attributeMetadataFound, attributeIdentifier, _, _, _, profileFound, _, profileBytes, err := profileStorage.GetProfileAttributeMetadataAndProfile(attributeHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (attributeMetadataFound == false || profileFound == false){

		description1 := getBoldLabelCentered("You do not have this profile attribute downloaded.")
		description2 := getLabelCentered("Try to download it?")
		description3 := getLabelCentered("It may not exist on the network anymore.")

		downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.DownloadIcon(), func(){
				//TODO
				showUnderConstructionDialog(window)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), attributeHashRow, widget.NewSeparator(), description1, description2, description3, downloadButton)

		setPageContent(page, window)
		return		
	}

	attributeName, err := profileFormat.GetAttributeNameFromAttributeIdentifier(attributeIdentifier)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	attributeTitle, _, _, _, _, err := attributeDisplay.GetProfileAttributeDisplayInfo(attributeName)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	attributeNameLabel := widget.NewLabel("Attribute Name:")
	attributeTitleLabel := getBoldLabel(attributeTitle)
	attributeTitleRow := container.NewHBox(layout.NewSpacer(), attributeNameLabel, attributeTitleLabel, layout.NewSpacer())

	attributeDisplayContainer, err := getProfileAttributeDisplayForModeration(window, attributeName, attributeIdentifier, profileBytes, currentPage)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), attributeHashRow, widget.NewSeparator(), attributeTitleRow, widget.NewSeparator(), attributeDisplayContainer)
	
	setPageContent(page, window)
}

func setViewMessageForModerationPage(window fyne.Window, messageHash [26]byte, previousPage func()){

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

	title := getPageTitleCentered("Viewing Message")

	backButton := getBackButtonCentered(previousPage)

	messageHashHex := encoding.EncodeBytesToHexString(messageHash[:])

	messageHashTitle := widget.NewLabel("Message Hash:")
	messageHashTrimmed, _, err := helpers.TrimAndFlattenString(messageHashHex, 10)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	messageHashLabel := getBoldLabel(messageHashTrimmed)
	viewMessageHashButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
			setViewContentHashPage(window, "Message", messageHash[:], currentPage)
		})
	messageHashRow := container.NewHBox(layout.NewSpacer(), messageHashTitle, messageHashLabel, viewMessageHashButton, layout.NewSpacer())

	messageExists, cipherKeyFound, ableToDecrypt, _, senderIdentityHash, messageCommunication, err := chatMessageStorage.GetDecryptedMessageForModeration(messageHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (messageExists == false){
		description1 := getBoldLabelCentered("Message not downloaded.")
		description2 := getLabelCentered("Try to download?")

		downloadButton := getWidgetCentered(widget.NewButton("Download", func(){
				setDownloadContentFromHashPage(window, "Message", messageHash[:], currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), messageHashRow, widget.NewSeparator(), description1, description2, downloadButton)

		setPageContent(page, window)
		return
	}

	if (cipherKeyFound == false){
		// No valid reviews/reports exist for message hash. User can try to download
		description1 := getBoldLabelCentered("No reviews or reports for message found.")
		description2 := getLabelCentered("Without a review/report, the contents are encrypted.")
		description3 := getLabelCentered("Try to download reviews/reports?")

		downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.DownloadIcon(), func(){
				setDownloadReviewsAndReportsForReviewedHashPage(window, messageHash[:], currentPage, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), messageHashRow, widget.NewSeparator(), description1, description2, description3, downloadButton)

		setPageContent(page, window)
		return
	}
	if (ableToDecrypt == false){
		description1 := getBoldLabelCentered("Message is corrupt.")
		description2 := getLabelCentered("It was created by a malicious user.")
		description3 := getLabelCentered("It cannot be decrypted.")
		description4 := getLabelCentered("No moderator should be able to approve it.")
		description5 := getLabelCentered("Any moderators who have approved the message will be automatically banned.")

		page := container.NewVBox(title, backButton, widget.NewSeparator(), messageHashRow, widget.NewSeparator(), description1, description2, description3, description4, description5)

		setPageContent(page, window)
		return
	}

	senderIdentityHashTitle := widget.NewLabel("Sender Identity Hash:")

	senderIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(senderIdentityHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	senderIdentityHashTrimmed, _, err := helpers.TrimAndFlattenString(senderIdentityHashString, 10)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	senderIdentityHashLabel := getBoldLabel(senderIdentityHashTrimmed)
	viewSenderIdentityHashButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
			setViewIdentityHashPage(window, senderIdentityHash, currentPage)
		})
	senderIdentityHashRow := container.NewHBox(layout.NewSpacer(), senderIdentityHashTitle, senderIdentityHashLabel, viewSenderIdentityHashButton, layout.NewSpacer())

	messageContentLabel := getBoldItalicLabelCentered("Message Content:")

	messageContentContainer, err := getMessageDisplayForModeration(window, messageCommunication, currentPage)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), messageHashRow, widget.NewSeparator(), senderIdentityHashRow, widget.NewSeparator(), messageContentLabel, messageContentContainer)

	setPageContent(page, window)
}

// This function will return a container to display a profile attribute
func getProfileAttributeDisplayForModeration(window fyne.Window, attributeName string, attributeIdentifier int, profileBytes []byte, currentPage func())(*fyne.Container, error){

	ableToRead, _, _, _, _, profileIsDisabled, rawProfileMap, err := readProfiles.ReadProfile(false, profileBytes)
	if (err != nil) { return nil, err }
	if (ableToRead == false){
		return nil, errors.New("getProfileAttributeDisplayForModeration called with invalid profile.")
	}

	if (profileIsDisabled == true){
		return nil, errors.New("getProfileAttributeDisplayForModeration called with disabled profile.")
	}

	exists, attributeValue, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(rawProfileMap, attributeName)
	if (err != nil) { return nil, err }
	if (exists == false){
		attributeIdentifierString := helpers.ConvertIntToString(attributeIdentifier)
		return nil, errors.New("getProfileAttributeDisplayForModeration called with profile missing attribute: " + attributeIdentifierString)
	}

	_, _, formatValueFunction, attributeUnits, _, err := attributeDisplay.GetProfileAttributeDisplayInfo(attributeName)
	if (err != nil) { return nil, err }

	if (attributeName == "Photos"){

		photosBase64List := strings.Split(attributeValue, "+")
		if (len(photosBase64List) == 0) {
			return nil, errors.New("Verified mate profile has an empty photos list")
		}

		getNumberOfImagesGridColumns := func()int{
			if (len(photosBase64List) == 1){
				return 1			
			}
			if (len(photosBase64List) == 2){
				return 2
			}
			return 3
		}

		numberOfImagesGridColumns := getNumberOfImagesGridColumns()

		imagesGrid := container.NewGridWithColumns(numberOfImagesGridColumns)

		for _, base64Photo := range photosBase64List{

			goImage, err := imagery.ConvertJPEGBase64StringToImageObject(base64Photo)
			if (err != nil) { return nil, err }

			viewImageButton := widget.NewButtonWithIcon("", theme.ZoomInIcon(), func(){
					setViewFullpageImagePage(window, goImage, currentPage)
				})

			fyneImage := canvas.NewImageFromImage(goImage)
			fyneImage.FillMode = canvas.ImageFillContain
			imageSize := getCustomFyneSize(10)
			fyneImage.SetMinSize(imageSize)			

			viewImageButtonCentered := getWidgetCentered(viewImageButton)

			imageWithFullpageButton := container.NewVBox(fyneImage, viewImageButtonCentered)

			imagesGrid.Add(imageWithFullpageButton)
		}

		imagesGridCentered := getContainerCentered(imagesGrid)

		return imagesGridCentered, nil
	}

	//TODO: Add more attributes that need a custom display, such as 23andMe_AncestryComposition

	attributeValueFormatted, err := formatValueFunction(attributeValue)
	if (err != nil) { return nil, err }

	attributeValueTrimmed, _, err := helpers.TrimAndFlattenString(attributeValueFormatted, 20)
	if (err != nil) { return nil, err }

	viewTextButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
			setViewTextPage(window, "Viewing Attribute", attributeValueFormatted, false, currentPage)
		})

	valueLabel := widget.NewLabel("Value:")

	attributeValueLabel := getBoldLabel(attributeValueTrimmed + " " + attributeUnits)

	attributeValueRow := container.NewHBox(layout.NewSpacer(), valueLabel, attributeValueLabel, viewTextButton, layout.NewSpacer())

	return attributeValueRow, nil
}

func getMessageDisplayForModeration(window fyne.Window, messageCommunication string, currentPage func())(*fyne.Container, error){

	isImageMessage := strings.HasPrefix(messageCommunication, ">!>Photo=")
	if (isImageMessage == true){

		imageBase64Jpeg := strings.TrimPrefix(messageCommunication, ">!>Photo=")		

		goImage, err := imagery.ConvertJPEGBase64StringToCroppedDownsizedImageObject(imageBase64Jpeg)
		if (err != nil) { return nil, err }

		viewImageButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.ZoomInIcon(), func(){
				setViewFullpageImagePage(window, goImage, currentPage)
			}))

		fyneImage := canvas.NewImageFromImage(goImage)
		fyneImage.FillMode = canvas.ImageFillContain
		imageSize := getCustomFyneSize(20)
		fyneImage.SetMinSize(imageSize)

		imageCentered := getFyneImageCentered(fyneImage)

		contentDisplayContainer := container.NewVBox(imageCentered, viewImageButton)

		return contentDisplayContainer, nil
	}
	//imageOrText == "Text"

	communicationTrimmed, _, err := helpers.TrimAndFlattenString(messageCommunication, 25)
	if (err != nil) { return nil, err }

	communicationLabel := getLabelCentered(communicationTrimmed)

	viewCommunicationButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
			setViewTextPage(window, "Viewing Message", messageCommunication, false, currentPage)
		})

	viewTextRow := container.NewHBox(layout.NewSpacer(), communicationLabel, viewCommunicationButton, layout.NewSpacer())

	return viewTextRow, nil
}

func setDownloadContentFromHashPage(window fyne.Window, contentType string, contentHash []byte, previousPage func()){

	//TODO: Make sure the content has not expired from the network, and is not banned (in which case it will not exist on the network)
}

func setModerateIdentitiesPage(window fyne.Window, previousPage func()){

	currentPage := func(){setModerateIdentitiesPage(window, previousPage)}

	title := getPageTitleCentered("Moderate Identities")

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Choose a method to moderate user identities.")

	viewControversialContentButton := widget.NewButton("View Controversial Content", func(){

			_ = mySettings.SetSetting("ViewedContentSortByAttribute", "Controversy")
			_ = mySettings.SetSetting("ViewedContentSortDirection", "Descending")
			_ = mySettings.SetSetting("ViewedContentGeneratedStatus", "No")
			_ = mySettings.SetSetting("ViewedContentViewIndex", "0")

			setBrowseContentPage(window, currentPage)
		})

	viewControversialModeratorsButton := widget.NewButton("View Controversial Moderators", func(){

			_ = mySettings.SetSetting("ViewedModeratorsSortByAttribute", "Controversy")
			_ = mySettings.SetSetting("ViewedModeratorsSortDirection", "Descending")
			_ = mySettings.SetSetting("ViewedModeratorsGeneratedStatus", "No")
			_ = mySettings.SetSetting("ViewedModeratorsViewIndex", "0")

			setViewModeratorsPage(window, currentPage)
		})

	viewMostBannedModeratorsButton := widget.NewButton("View Most Banned Moderators", func(){

			_ = mySettings.SetSetting("ViewedModeratorsSortByAttribute", "BanAdvocates")
			_ = mySettings.SetSetting("ViewedModeratorsSortDirection", "Descending")
			_ = mySettings.SetSetting("ViewedModeratorsGeneratedStatus", "No")
			_ = mySettings.SetSetting("ViewedModeratorsViewIndex", "0")

			setViewModeratorsPage(window, currentPage)
		})

	viewMostReportedModeratorsButton := widget.NewButton("View Most Reported Moderators", func(){
			//TODO
			showUnderConstructionDialog(window)
		})

	viewMostBannedHostsButton := widget.NewButton("View Most Banned Hosts", func(){

			_ = mySettings.SetSetting("ViewedHostsSortByAttribute", "BanAdvocates")
			_ = mySettings.SetSetting("ViewedHostsSortDirection", "Descending")
			_ = mySettings.SetSetting("ViewedHostsGeneratedStatus", "No")
			_ = mySettings.SetSetting("ViewedHostsViewIndex", "0")

			setViewHostsPage(window, currentPage)
		})

	buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, viewControversialContentButton, viewControversialModeratorsButton, viewMostBannedModeratorsButton, viewMostReportedModeratorsButton, viewMostBannedHostsButton))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, buttonsGrid)

	setPageContent(page, window)
}


// This page allows moderators to view the moderation status of an identity
func setViewIdentityModerationDetailsPage(window fyne.Window, identityHash [16]byte, previousPage func()){

	setLoadingScreen(window, "View Moderation Details", "Loading details...")

	currentPage := func(){setViewIdentityModerationDetailsPage(window, identityHash, previousPage)}

	title := getPageTitleCentered("Viewing Moderation Details - Identity")

	backButton := getBackButtonCentered(previousPage)

	identityHashLabel := widget.NewLabel("Identity Hash:")

	identityHashString, _, err := identity.EncodeIdentityHashBytesToString(identityHash)
	if (err != nil){
		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		setErrorEncounteredPage(window, errors.New("setViewIdentityModerationDetailsPage called with invalid identity hash: " + identityHashHex), previousPage)
		return
	}

	identityHashTrimmed, _, err := helpers.TrimAndFlattenString(identityHashString, 10)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	viewIdentityHashButton := widget.NewButton(identityHashTrimmed, func(){
			setViewIdentityHashPage(window, identityHash, currentPage)
		})

	identityHashRow := container.NewHBox(layout.NewSpacer(), identityHashLabel, viewIdentityHashButton, layout.NewSpacer())

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	downloadingRequiredReviews, parametersExist, identityIsBannedConsensus, numberOfBanAdvocates, banScoreSum, _, err := verifiedVerdict.GetVerifiedIdentityVerdict(identityHash, appNetworkType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getModeratorConsensusRow := func()*fyne.Container{

		moderatorConsensusLabel := widget.NewLabel("Moderator Consensus:")

		if (downloadingRequiredReviews == false || parametersExist == false){

			verdictConsensusLabel := getBoldLabel("Unknown")
			moderatorConsensusRow := container.NewHBox(layout.NewSpacer(), moderatorConsensusLabel, verdictConsensusLabel, layout.NewSpacer())

			return moderatorConsensusRow
		}

		getConsensusVerdictString := func()string{
			if (identityIsBannedConsensus == false){
				return "Not Banned"
			}
			return "Banned"
		}
		consensusVerdictString := getConsensusVerdictString()

		verdictConsensusLabel := getBoldLabel(consensusVerdictString)
		moderatorConsensusRow := container.NewHBox(layout.NewSpacer(), moderatorConsensusLabel, verdictConsensusLabel, layout.NewSpacer())

		return moderatorConsensusRow
	}

	moderatorConsensusRow := getModeratorConsensusRow()

	getMyVerdict := func()(string, error){

		myIdentityExists, iHaveBanned, err := myReviews.GetMyNewestIdentityModerationVerdict(identityHash, appNetworkType)
		if (err != nil) { return "", err }
		if (myIdentityExists == false || iHaveBanned == false){
			return "None", nil
		}
		return "Ban", nil
	}

	myVerdict, err := getMyVerdict()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	myVerdictLabel := widget.NewLabel("My Verdict:")
	myVerdictText := getBoldLabel(myVerdict)

	//TODO: Add button to change my verdict, and see when my verdict was authored.

	myVerdictRow := container.NewHBox(layout.NewSpacer(), myVerdictLabel, myVerdictText, layout.NewSpacer())

	//TODO: Add button to view identity sticky status and verdict history
	
	if (downloadingRequiredReviews == false){

		description1 := getBoldLabelCentered("This identity is outside of your moderation range.")
		description2 := getLabelCentered("We cannot determine the moderation consensus.")
		description3 := getLabelCentered("Do you want to download the reviews to view the consensus?")

		downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.DownloadIcon(), func(){
				setDownloadReviewsAndReportsForReviewedHashPage(window, identityHash[:], currentPage, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), identityHashRow, widget.NewSeparator(), moderatorConsensusRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), description1, description2, description3, downloadButton)

		setPageContent(page, window)
		return
	}
	if (parametersExist == false){

		description1 := getLabelCentered("Your client is missing the moderation parameters.")
		description2 := getLabelCentered("Please wait for the parameters to be downloaded.")

		viewStatusButton := getWidgetCentered(widget.NewButtonWithIcon("View Status", theme.VisibilityIcon(), func(){
				//TODO
				showUnderConstructionDialog(window)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), identityHashRow, widget.NewSeparator(), moderatorConsensusRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), description1, description2, viewStatusButton)

		setPageContent(page, window)
		return
	}

	numberBanString := helpers.ConvertIntToString(numberOfBanAdvocates)
	banAdvocatesLabel := widget.NewLabel("Ban Advocates:")
	numberBanLabel := getBoldLabel(numberBanString)
	numberBanRow := container.NewHBox(layout.NewSpacer(), banAdvocatesLabel, numberBanLabel, layout.NewSpacer())

	banScoreString := helpers.ConvertFloat64ToStringRounded(banScoreSum, 2)
	banScoreLabel := getLabelCentered("Ban Score:")
	banScoreText := getBoldLabel(banScoreString)
	banScoreRow := container.NewHBox(layout.NewSpacer(), banScoreLabel, banScoreText, layout.NewSpacer())

	viewReviewerVerdictsButton := getWidgetCentered(widget.NewButton("View Reviewers", func(){
			setViewIdentityReviewerVerdictsPage(window, identityHash, 0, currentPage)
		}))

	numberOfReports, err := reportStorage.GetNumberOfReportsForReportedHash(identityHash[:], appNetworkType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	numberOfReportsString := helpers.ConvertInt64ToString(numberOfReports)
	numberOfReportsLabel := widget.NewLabel("Number of reports:")
	numberOfReportsText := getBoldLabel(numberOfReportsString)
	numberOfReportsRow := container.NewHBox(layout.NewSpacer(), numberOfReportsLabel, numberOfReportsText, layout.NewSpacer())

	page := container.NewVBox(title, backButton, widget.NewSeparator(), identityHashRow, widget.NewSeparator(), moderatorConsensusRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), numberBanRow, banScoreRow, viewReviewerVerdictsButton, widget.NewSeparator(), numberOfReportsRow)

	if (numberOfReports != 0){
		viewReportsButton := getWidgetCentered(widget.NewButton("View Reports", func(){
				//TODO
				showUnderConstructionDialog(window)
			}))
		page.Add(viewReportsButton)
	}
	page.Add(widget.NewSeparator())

	updateButton := getWidgetCentered(widget.NewButtonWithIcon("Update", theme.ViewRefreshIcon(), currentPage))

	page.Add(updateButton)

	setPageContent(page, window)
}


// This page allows moderators to view the moderation status of a profile
func setViewProfileModerationDetailsPage(window fyne.Window, profileHash [28]byte, previousPage func()){

	setLoadingScreen(window, "View Moderation Details", "Loading details...")

	currentPage := func(){setViewProfileModerationDetailsPage(window, profileHash, previousPage)}

	title := getPageTitleCentered("Viewing Moderation Details - Profile")
	
	backButton := getBackButtonCentered(previousPage)

	profileHashLabel := widget.NewLabel("Profile Hash:")

	profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])

	profileHashTrimmed, _, err := helpers.TrimAndFlattenString(profileHashHex, 6)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	viewProfileHashButton := widget.NewButton(profileHashTrimmed, func(){
			setViewContentHashPage(window, "Profile", profileHash[:], currentPage)
		})

	profileHashRow := container.NewHBox(layout.NewSpacer(), profileHashLabel, viewProfileHashButton, layout.NewSpacer())

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	profileIsDisabled, profileMetadataIsKnown, _, profileNetworkType, profileIdentityHash, _, downloadingRequiredReviews, parametersExist, profileVerdict, numberOfApproveAdvocates, numberOfBanAdvocates, approveScoreSum, banScoreSum, _, _, _, _, _, _, _, _, _, err := verifiedVerdict.GetVerifiedProfileVerdict(profileHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (profileMetadataIsKnown == true){

		if (profileNetworkType != appNetworkType){
			setErrorEncounteredPage(window, errors.New("setViewProfileModerationDetailsPage called with profile for different network type."), previousPage)
			return
		}
	}

	getProfileAuthorRow := func()(*fyne.Container, error){

		profileAuthorLabel := widget.NewLabel("Profile Author:")

		if (profileMetadataIsKnown == false){

			unknownLabel := getBoldLabel("Unknown")

			profileAuthorRow := container.NewHBox(layout.NewSpacer(), profileAuthorLabel, unknownLabel, layout.NewSpacer())

			return profileAuthorRow, nil
		}

		profileIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(profileIdentityHash)
		if (err != nil){ return nil, err }

		profileIdentityHashTrimmed, _, err := helpers.TrimAndFlattenString(profileIdentityHashString, 10)
		if (err != nil){ return nil, err }

		viewProfileIdentityHashButton := widget.NewButton(profileIdentityHashTrimmed, func(){
				setViewIdentityModerationDetailsPage(window, profileIdentityHash, currentPage)
			})

		profileAuthorRow := container.NewHBox(layout.NewSpacer(), profileAuthorLabel, viewProfileIdentityHashButton, layout.NewSpacer())

		return profileAuthorRow, nil
	}

	profileAuthorRow, err := getProfileAuthorRow()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (profileIsDisabled == true){

		description1 := getBoldLabelCentered("This profile is disabled.")
		description2 := getLabelCentered("It cannot be banned.")

		page := container.NewVBox(title, backButton, widget.NewSeparator(), profileHashRow, widget.NewSeparator(), profileAuthorRow, widget.NewSeparator(), description1, description2)

		setPageContent(page, window)
		return
	}

	getModeratorConsensusRow := func()*fyne.Container{

		moderatorConsensusLabel := widget.NewLabel("Moderator Consensus:")

		if (profileMetadataIsKnown == false || downloadingRequiredReviews == false || parametersExist == false){

			verdictConsensusLabel := getBoldLabel("Unknown")

			moderatorConsensusRow := container.NewHBox(layout.NewSpacer(), moderatorConsensusLabel, verdictConsensusLabel, layout.NewSpacer())

			return moderatorConsensusRow
		}

		verdictConsensusLabel := getBoldLabel(profileVerdict)

		moderatorConsensusRow := container.NewHBox(layout.NewSpacer(), moderatorConsensusLabel, verdictConsensusLabel, layout.NewSpacer())

		return moderatorConsensusRow
	}

	moderatorConsensusRow := getModeratorConsensusRow()

	getMyVerdict := func()(string, error){

		if (profileIsDisabled == true){
			return "None", nil
		}

		myIdentityExists, profileIsDisabledB, profileMetadataExists, myReviewExists, myReviewVerdict, err := myReviews.GetMyNewestProfileModerationVerdict(profileHash, true)
		if (err != nil) { return "", err }
		if (myIdentityExists == false){
			return "None", nil
		}
		if (profileIsDisabled != profileIsDisabledB){
			return "", errors.New("GetMyNewestProfileModerationVerdict returning different profileIsDisabled status than GetVerifiedProfileVerdict.")
		}
		if (profileMetadataExists == false){
			return "Unknown", nil
		}
		if (myReviewExists == false){
			return "None", nil
		}

		return myReviewVerdict, nil
	}

	myVerdict, err := getMyVerdict()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	myVerdictLabel := widget.NewLabel("My Verdict:")
	myVerdictText := getBoldLabel(myVerdict)

	myVerdictRow := container.NewHBox(layout.NewSpacer(), myVerdictLabel, myVerdictText, layout.NewSpacer())

	//TODO: Add button to view profile sticky status and verdict history

	if (profileMetadataIsKnown == false){
		// We do not have the required profile
		// We can attempt to download it.

		description1 := getLabelCentered("This profile is not downloaded.")
		description2 := getLabelCentered("It must be downloaded to know its moderation status.")
		description3 := getLabelCentered("You can try to download it, but it may have been deleted from the network.")

		downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.DownloadIcon(), func(){
				//TODO
				showUnderConstructionDialog(window)
			}))

		//TODO: Add a way to also download profile's reviews/reports at the same time so we can quickly determine verdict

		page := container.NewVBox(title, backButton, widget.NewSeparator(), profileHashRow, widget.NewSeparator(), moderatorConsensusRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), description1, description2, description3, downloadButton)

		setPageContent(page, window)
		return
	}

	viewProfileButton := getWidgetCentered(widget.NewButtonWithIcon("View Profile", theme.VisibilityIcon(), func(){
			setViewProfileForModerationPage(window, profileHash, currentPage)
		}))

	viewAttributesButton := getWidgetCentered(widget.NewButtonWithIcon("View Attributes", theme.VisibilityIcon(), func(){
			//TODO
			showUnderConstructionDialog(window)
		}))

	if (downloadingRequiredReviews == false){

		description1 := getLabelCentered("This profile is outside of your moderation range.")
		description2 := getLabelCentered("We cannot determine the moderation consensus.")
		description3 := getLabelCentered("Do you want to download the reviews to view the consensus?")

		downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.DownloadIcon(), func(){
				setDownloadReviewsAndReportsForReviewedHashPage(window, profileHash[:], currentPage, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), profileHashRow, widget.NewSeparator(), profileAuthorRow, viewProfileButton, widget.NewSeparator(), viewAttributesButton, widget.NewSeparator(), moderatorConsensusRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), description1, description2, description3, downloadButton)

		setPageContent(page, window)
		return
	}
	if (parametersExist == false){

		description1 := getLabelCentered("Your client is missing the moderation parameters.")
		description2 := getLabelCentered("Please wait for the parameters to be downloaded.")
		
		viewStatusButton := getWidgetCentered(widget.NewButtonWithIcon("View Status", theme.VisibilityIcon(), func(){
				//TODO
				showUnderConstructionDialog(window)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), profileHashRow, widget.NewSeparator(), profileAuthorRow, widget.NewSeparator(), viewProfileButton, widget.NewSeparator(), viewAttributesButton, widget.NewSeparator(), moderatorConsensusRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), description1, description2, viewStatusButton)

		setPageContent(page, window)
		return
	}

	numberApproveString := helpers.ConvertIntToString(numberOfApproveAdvocates)
	approveAdvocatesLabel := widget.NewLabel("Approve Advocates:")
	numberApproveLabel := getBoldLabel(numberApproveString)
	numberApproveRow := container.NewHBox(layout.NewSpacer(), approveAdvocatesLabel, numberApproveLabel, layout.NewSpacer())

	numberBanString := helpers.ConvertIntToString(numberOfBanAdvocates)
	banAdvocatesLabel := widget.NewLabel("Ban Advocates:")
	numberBanLabel := getBoldLabel(numberBanString)
	numberBanRow := container.NewHBox(layout.NewSpacer(), banAdvocatesLabel, numberBanLabel, layout.NewSpacer())

	approveScoreString := helpers.ConvertFloat64ToStringRounded(approveScoreSum, 2)
	approveScoreLabel := getLabelCentered("Approve Score:")
	approveScoreText := getBoldLabel(approveScoreString)
	approveScoreRow := container.NewHBox(layout.NewSpacer(), approveScoreLabel, approveScoreText, layout.NewSpacer())

	banScoreString := helpers.ConvertFloat64ToStringRounded(banScoreSum, 2)
	banScoreLabel := getLabelCentered("Ban Score:")
	banScoreText := getBoldLabel(banScoreString)
	banScoreRow := container.NewHBox(layout.NewSpacer(), banScoreLabel, banScoreText, layout.NewSpacer())

	viewReviewerVerdictsButton := getWidgetCentered(widget.NewButton("View Reviewers", func(){
			setViewProfileReviewerVerdictsPage(window, profileHash, 0, currentPage)
		}))

	numberOfReports, err := reportStorage.GetNumberOfReportsForReportedHash(profileHash[:], appNetworkType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	numberOfReportsString := helpers.ConvertInt64ToString(numberOfReports)
	numberOfReportsLabel := widget.NewLabel("Number of reports:")
	numberOfReportsText := getBoldLabel(numberOfReportsString)
	numberOfReportsRow := container.NewHBox(layout.NewSpacer(), numberOfReportsLabel, numberOfReportsText, layout.NewSpacer())

	page := container.NewVBox(title, backButton, widget.NewSeparator(), profileHashRow, widget.NewSeparator(), profileAuthorRow, widget.NewSeparator(), viewProfileButton, widget.NewSeparator(), viewAttributesButton, widget.NewSeparator(), moderatorConsensusRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), numberApproveRow, numberBanRow, approveScoreRow, banScoreRow, viewReviewerVerdictsButton, widget.NewSeparator(), numberOfReportsRow)

	if (numberOfReports != 0){
		viewReportsButton := getWidgetCentered(widget.NewButton("View Reports", func(){
				//TODO
			}))
		page.Add(viewReportsButton)
	}
	page.Add(widget.NewSeparator())
	
	updateButton := getWidgetCentered(widget.NewButtonWithIcon("Update", theme.ViewRefreshIcon(), func(){
			currentPage()
		}))

	page.Add(updateButton)

	setPageContent(page, window)
}


// This page allows moderators to view the moderation status of a profile attribute
func setViewAttributeModerationDetailsPage(window fyne.Window, attributeHash [27]byte, previousPage func()){

	setLoadingScreen(window, "View Moderation Details", "Loading details...")

	currentPage := func(){setViewAttributeModerationDetailsPage(window, attributeHash, previousPage)}

	title := getPageTitleCentered("Viewing Moderation Details - Attribute")

	backButton := getBackButtonCentered(previousPage)

	attributeHashLabel := widget.NewLabel("Attribute Hash:")

	attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])

	attributeHashTrimmed, _, err := helpers.TrimAndFlattenString(attributeHashHex, 6)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	viewAttributeHashButton := widget.NewButton(attributeHashTrimmed, func(){
			setViewContentHashPage(window, "Attribute", attributeHash[:], currentPage)
		})

	attributeHashRow := container.NewHBox(layout.NewSpacer(), attributeHashLabel, viewAttributeHashButton, layout.NewSpacer())

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	attributeMetadataIsKnown, _, attributeAuthor, attributeNetworkType, attributeProfileHashesList, err := profileStorage.GetProfileAttributeMetadata(attributeHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (attributeMetadataIsKnown == true){

		if (appNetworkType != attributeNetworkType){
			setErrorEncounteredPage(window, errors.New("setViewAttributeModerationDetailsPage called with attribute belonging to different appNetworkType."), previousPage)
			return
		}
	}

	getAttributeAuthorRow := func()(*fyne.Container, error){

		attributeAuthorLabel := widget.NewLabel("Attribute Author:")

		if (attributeMetadataIsKnown == false){

			unknownLabel := getBoldLabel("Unknown")

			attributeAuthorRow := container.NewHBox(layout.NewSpacer(), attributeAuthorLabel, unknownLabel, layout.NewSpacer())

			return attributeAuthorRow, nil
		}

		attributeAuthorString, _, err := identity.EncodeIdentityHashBytesToString(attributeAuthor)
		if (err != nil){ return nil, err }

		authorIdentityHashTrimmed, _, err := helpers.TrimAndFlattenString(attributeAuthorString, 10)
		if (err != nil){ return nil, err }

		viewAttributeAuthorButton := widget.NewButton(authorIdentityHashTrimmed, func(){
				setViewIdentityModerationDetailsPage(window, attributeAuthor, currentPage)
			})

		attributeAuthorRow := container.NewHBox(layout.NewSpacer(), attributeAuthorLabel, viewAttributeAuthorButton, layout.NewSpacer())

		return attributeAuthorRow, nil
	}

	attributeAuthorRow, err := getAttributeAuthorRow()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getMyVerdict := func()(string, error){

		myIdentityExists, attributeMetadataKnown, myReviewExists, myReviewVerdict, _, err := myReviews.GetMyNewestProfileAttributeModerationVerdict(attributeHash, true)
		if (err != nil) { return "", err }
		if (myIdentityExists == false){
			return "None", nil
		}
		if (attributeMetadataKnown == false){
			// Metadata must have been recently deleted
			return "Unknown", nil
		}
		if (myReviewExists == false){
			return "None", nil
		}
		return myReviewVerdict, nil
	}

	myVerdict, err := getMyVerdict()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	myVerdictLabel := widget.NewLabel("My Verdict:")
	myVerdictText := getBoldLabel(myVerdict)

	myVerdictRow := container.NewHBox(layout.NewSpacer(), myVerdictLabel, myVerdictText, layout.NewSpacer())

	if (attributeMetadataIsKnown == false){
		// We do not have a profile containing this attribute
		// We can attempt to download it.

		description1 := getLabelCentered("This attribute is not downloaded.")
		description2 := getLabelCentered("It must be downloaded to know its moderation status.")
		description3 := getLabelCentered("You can try to download it, but it may have been deleted from the network.")

		downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.DownloadIcon(), func(){
				//TODO
				showUnderConstructionDialog(window)
			}))

		//TODO: Add a way to also download reviews/reports at the same time so we can determine attribute verdict quickly

		page := container.NewVBox(title, backButton, widget.NewSeparator(), attributeHashRow, widget.NewSeparator(), attributeAuthorRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), description1, description2, description3, downloadButton)

		setPageContent(page, window)
		return
	}

	viewAttributeButton := getWidgetCentered(widget.NewButtonWithIcon("View Attribute", theme.VisibilityIcon(), func(){
			setViewProfileAttributeForModerationPage(window, attributeHash, currentPage)
		}))

	viewAttributeProfilesButton := getWidgetCentered(widget.NewButtonWithIcon("View Attribute Profiles", theme.VisibilityIcon(), func(){
			//TODO: A list of profiles the attribute belongs to.
			showUnderConstructionDialog(window)
		}))

	downloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineIdentityVerdicts(attributeAuthor)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (downloadingRequiredReviews == false){

		description1 := getLabelCentered("This attribute's author is outside of your moderation range.")
		description2 := getLabelCentered("We cannot determine the moderation consensus.")
		description3 := getLabelCentered("Do you want to download the reviews to view the consensus?")

		downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.DownloadIcon(), func(){
				setDownloadReviewsAndReportsForReviewedHashPage(window, attributeAuthor[:], currentPage, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), attributeHashRow, widget.NewSeparator(), attributeAuthorRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), viewAttributeButton, viewAttributeProfilesButton, widget.NewSeparator(), description1, description2, description3, downloadButton)

		setPageContent(page, window)
		return
	}

	//TODO: Check for parameters
	parametersExist := true

	if (parametersExist == false){

		description1 := getLabelCentered("Your client is missing the moderation parameters.")
		description2 := getLabelCentered("Please wait for the parameters to download.")
		description3 := getLabelCentered("Once they download, you will be able to see who has reviewed the attribute.")

		viewStatusButton := getWidgetCentered(widget.NewButtonWithIcon("View Status", theme.VisibilityIcon(), func(){
				//TODO
				showUnderConstructionDialog(window)
			}))

		refreshButton := getWidgetCentered(widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), currentPage))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), attributeHashRow, widget.NewSeparator(), attributeAuthorRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), viewAttributeButton, viewAttributeProfilesButton, widget.NewSeparator(), description1, description2, description3, viewStatusButton, refreshButton)

		setPageContent(page, window)
		return
	}

	attributeApproveAdvocatesMap, attributeBanAdvocatesMap, err := reviewStorage.GetProfileAttributeVerdictMaps(attributeHash, attributeNetworkType, true, attributeProfileHashesList)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	numberOfApproveAdvocates := len(attributeApproveAdvocatesMap)
	numberOfBanAdvocates := len(attributeBanAdvocatesMap)

	numberApproveString := helpers.ConvertIntToString(numberOfApproveAdvocates)
	approveAdvocatesLabel := widget.NewLabel("Approve Advocates:")
	numberApproveLabel := getBoldLabel(numberApproveString)
	numberApproveRow := container.NewHBox(layout.NewSpacer(), approveAdvocatesLabel, numberApproveLabel, layout.NewSpacer())

	numberBanString := helpers.ConvertIntToString(numberOfBanAdvocates)
	banAdvocatesLabel := widget.NewLabel("Ban Advocates:")
	numberBanLabel := getBoldLabel(numberBanString)
	numberBanRow := container.NewHBox(layout.NewSpacer(), banAdvocatesLabel, numberBanLabel, layout.NewSpacer())

	viewReviewerVerdictsButton := getWidgetCentered(widget.NewButton("View Reviewers", func(){
			setViewAttributeReviewerVerdictsPage(window, attributeHash, 0, currentPage)
		}))

	numberOfReports, err := reportStorage.GetNumberOfReportsForReportedHash(attributeHash[:], appNetworkType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	numberOfReportsString := helpers.ConvertInt64ToString(numberOfReports)
	numberOfReportsLabel := widget.NewLabel("Number of reports:")
	numberOfReportsText := getBoldLabel(numberOfReportsString)
	numberOfReportsRow := container.NewHBox(layout.NewSpacer(), numberOfReportsLabel, numberOfReportsText, layout.NewSpacer())

	page := container.NewVBox(title, backButton, widget.NewSeparator(), attributeHashRow, widget.NewSeparator(), attributeAuthorRow, widget.NewSeparator(), viewAttributeButton, viewAttributeProfilesButton, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), numberApproveRow, numberBanRow, viewReviewerVerdictsButton, widget.NewSeparator(), numberOfReportsRow)

	if (numberOfReports != 0){
		viewReportsButton := getWidgetCentered(widget.NewButton("View Reports", func(){
				//TODO
				showUnderConstructionDialog(window)
			}))
		page.Add(viewReportsButton)
	}
	page.Add(widget.NewSeparator())

	updateButton := getWidgetCentered(widget.NewButtonWithIcon("Update", theme.ViewRefreshIcon(), currentPage))

	page.Add(updateButton)

	setPageContent(page, window)
}


// This page allows moderators to view the moderation status of a message
func setViewMessageModerationDetailsPage(window fyne.Window, messageHash [26]byte, previousPage func()){

	setLoadingScreen(window, "View Moderation Details", "Loading details...")

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

	title := getPageTitleCentered("Viewing Moderation Details - Message")

	backButton := getBackButtonCentered(previousPage)

	messageHashLabel := widget.NewLabel("Message Hash:")

	messageHashHex := encoding.EncodeBytesToHexString(messageHash[:])

	messageHashTrimmed, _, err := helpers.TrimAndFlattenString(messageHashHex, 6)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	viewMessageHashButton := widget.NewButton(messageHashTrimmed, func(){
			setViewContentHashPage(window, "Message", messageHash[:], currentPage)
		})

	messageHashRow := container.NewHBox(layout.NewSpacer(), messageHashLabel, viewMessageHashButton, layout.NewSpacer())

	//TODO: Add button to view message sticky status and message verdict history

	getMessageAuthorRow := func()(*fyne.Container, error){

		messageAuthorLabel := widget.NewLabel("Message Author:")

		messageExists, messageCipherKeyFound, messageIsDecryptable, _, messageAuthorIdentityHash, _, err :=  chatMessageStorage.GetDecryptedMessageForModeration(messageHash)
		if (err != nil){ return nil, err }
		if (messageExists == false || messageCipherKeyFound == false || messageIsDecryptable == false){

			//TODO: Show user if message is not decryptable, in which case, author must be malicious

			unknownLabel := getBoldLabel("Unknown")

			messageAuthorRow := container.NewHBox(layout.NewSpacer(), messageAuthorLabel, unknownLabel, layout.NewSpacer())

			return messageAuthorRow, nil
		}

		messageAuthorIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(messageAuthorIdentityHash)
		if (err != nil){ return nil, err }

		messageAuthorIdentityHashTrimmed, _, err := helpers.TrimAndFlattenString(messageAuthorIdentityHashString, 10)
		if (err != nil){ return nil, err }

		viewMessageAuthorIdentityHashButton := widget.NewButton(messageAuthorIdentityHashTrimmed, func(){
				setViewIdentityModerationDetailsPage(window, messageAuthorIdentityHash, currentPage)
			})

		messageAuthorRow := container.NewHBox(layout.NewSpacer(), messageAuthorLabel, viewMessageAuthorIdentityHashButton, layout.NewSpacer())

		return messageAuthorRow, nil
	}

	messageAuthorRow, err := getMessageAuthorRow()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	messageMetadataIsKnown, _, messageNetworkType, _, _, downloadingRequiredReviews, parametersExist, messageVerdict, numberOfApproveAdvocates, numberOfBanAdvocates, approveScoreSum, banScoreSum, _, _, err := verifiedVerdict.GetVerifiedMessageVerdict(messageHash)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (messageMetadataIsKnown == true){

		if (appNetworkType != messageNetworkType){
			setErrorEncounteredPage(window, errors.New("setViewMessageModerationDetailsPage called with message belonging to different appNetworkType."), previousPage)
			return
		}
	}

	getModeratorConsensusRow := func()*fyne.Container{

		moderatorConsensusLabel := widget.NewLabel("Moderator Consensus:")

		if (messageMetadataIsKnown == false || downloadingRequiredReviews == false || parametersExist == false){
			verdictConsensusLabel := getBoldItalicLabel("Unknown")

			moderatorConsensusRow := container.NewHBox(layout.NewSpacer(), moderatorConsensusLabel, verdictConsensusLabel, layout.NewSpacer())

			return moderatorConsensusRow
		}

		verdictConsensusLabel := getBoldLabel(messageVerdict)
		moderatorConsensusRow := container.NewHBox(layout.NewSpacer(), moderatorConsensusLabel, verdictConsensusLabel, layout.NewSpacer())

		return moderatorConsensusRow
	}

	moderatorConsensusRow := getModeratorConsensusRow()

	getMyVerdict := func()(string, error){

		myIdentityExists, messageMetadataExists, myReviewExists, myReviewVerdict, err := myReviews.GetMyNewestMessageModerationVerdict(messageHash)
		if (err != nil) { return "", err }
		if (myIdentityExists == false){
			return "None", nil
		}
		if (messageMetadataExists == false){
			return "Unknown", nil
		}
		if (myReviewExists == false){
			return "None", nil
		}
		return myReviewVerdict, nil
	}

	myVerdict, err := getMyVerdict()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	myVerdictLabel := widget.NewLabel("My Verdict:")
	myVerdictText := getBoldLabel(myVerdict)

	myVerdictRow := container.NewHBox(layout.NewSpacer(), myVerdictLabel, myVerdictText, layout.NewSpacer())

	if (messageMetadataIsKnown == false){
		// We do not have the required message
		// We can attempt to download it.

		description1 := getBoldLabelCentered("This message is not downloaded.")
		description2 := getLabelCentered("It must be downloaded to know its moderation status.")
		description3 := getLabelCentered("You can try to download it, but it may have been deleted from the network.")

		downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.DownloadIcon(), func(){
				//TODO
				showUnderConstructionDialog(window)
			}))

		//TODO: Add a way to also download reviews/reports at the same time, so we can determine message verdict quickly

		page := container.NewVBox(title, backButton, widget.NewSeparator(), messageHashRow, widget.NewSeparator(), messageAuthorRow, widget.NewSeparator(), moderatorConsensusRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), description1, description2, description3, downloadButton)

		setPageContent(page, window)
		return
	}

	viewMessageButton := getWidgetCentered(widget.NewButtonWithIcon("View Message", theme.VisibilityIcon(), func(){
			setViewMessageForModerationPage(window, messageHash, currentPage)
		}))

	if (downloadingRequiredReviews == false){

		description1 := getLabelCentered("This message is outside of your moderation range.")
		description2 := getLabelCentered("We cannot determine the moderation consensus.")
		description3 := getLabelCentered("Do you want to download the reviews to view the consensus?")

		downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.DownloadIcon(), func(){
				setDownloadReviewsAndReportsForReviewedHashPage(window, messageHash[:], currentPage, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), messageHashRow, widget.NewSeparator(), messageAuthorRow, widget.NewSeparator(), viewMessageButton, widget.NewSeparator(), moderatorConsensusRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), description1, description2, description3, downloadButton)

		setPageContent(page, window)
		return
	}
	if (parametersExist == false){

		description1 := getLabelCentered("Your client is missing the moderation parameters.")
		description2 := getLabelCentered("Please wait for the parameters to be downloaded.")

		viewStatusButton := getWidgetCentered(widget.NewButtonWithIcon("View Status", theme.VisibilityIcon(), func(){
				//TODO
				showUnderConstructionDialog(window)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), messageHashRow, widget.NewSeparator(), messageAuthorRow, widget.NewSeparator(), viewMessageButton, widget.NewSeparator(), moderatorConsensusRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), description1, description2, viewStatusButton)

		setPageContent(page, window)
		return
	}

	numberApproveString := helpers.ConvertIntToString(numberOfApproveAdvocates)
	approveAdvocatesLabel := widget.NewLabel("Approve Advocates:")
	numberApproveLabel := getBoldLabel(numberApproveString)
	numberApproveRow := container.NewHBox(layout.NewSpacer(), approveAdvocatesLabel, numberApproveLabel, layout.NewSpacer())

	numberBanString := helpers.ConvertIntToString(numberOfBanAdvocates)
	banAdvocatesLabel := widget.NewLabel("Ban Advocates:")
	numberBanLabel := getBoldLabel(numberBanString)
	numberBanRow := container.NewHBox(layout.NewSpacer(), banAdvocatesLabel, numberBanLabel, layout.NewSpacer())

	approveScoreString := helpers.ConvertFloat64ToStringRounded(approveScoreSum, 2)
	approveScoreLabel := getLabelCentered("Approve Score:")
	approveScoreText := getBoldLabel(approveScoreString)
	approveScoreRow := container.NewHBox(layout.NewSpacer(), approveScoreLabel, approveScoreText, layout.NewSpacer())

	banScoreString := helpers.ConvertFloat64ToStringRounded(banScoreSum, 2)
	banScoreLabel := getLabelCentered("Ban Score:")
	banScoreText := getBoldLabel(banScoreString)
	banScoreRow := container.NewHBox(layout.NewSpacer(), banScoreLabel, banScoreText, layout.NewSpacer())

	viewReviewerVerdictsButton := getWidgetCentered(widget.NewButton("View Reviewers", func(){
			setViewMessageReviewerVerdictsPage(window, messageHash, 0, currentPage)
		}))

	numberOfReports, err := reportStorage.GetNumberOfReportsForReportedHash(messageHash[:], messageNetworkType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	numberOfReportsString := helpers.ConvertInt64ToString(numberOfReports)
	numberOfReportsLabel := widget.NewLabel("Number of reports:")
	numberOfReportsText := getBoldLabel(numberOfReportsString)
	numberOfReportsRow := container.NewHBox(layout.NewSpacer(), numberOfReportsLabel, numberOfReportsText, layout.NewSpacer())

	page := container.NewVBox(title, backButton, widget.NewSeparator(), messageHashRow, widget.NewSeparator(), messageAuthorRow, widget.NewSeparator(), viewMessageButton, widget.NewSeparator(), moderatorConsensusRow, widget.NewSeparator(), myVerdictRow, widget.NewSeparator(), numberApproveRow, numberBanRow, approveScoreRow, banScoreRow, viewReviewerVerdictsButton, widget.NewSeparator(), numberOfReportsRow)

	if (numberOfReports != 0){
		viewReportsButton := getWidgetCentered(widget.NewButton("View Reports", func(){
				//TODO
				showUnderConstructionDialog(window)
			}))
		page.Add(viewReportsButton)
	}
	page.Add(widget.NewSeparator())

	updateButton := getWidgetCentered(widget.NewButtonWithIcon("Update", theme.ViewRefreshIcon(), currentPage))

	page.Add(updateButton)

	setPageContent(page, window)
}

func setDownloadReviewsAndReportsForReviewedHashPage(window fyne.Window, reviewedHash []byte, previousPage func(), pageToVisitAfter func()){


	//TODO: This page must also wait for moderator identity hashes from newly downloaded reviews to be downloaded
	// This page must also make sure that the content is downloaded

	showUnderConstructionDialog(window)
}


// This page will show all moderators who have banned the identity
func setViewIdentityReviewerVerdictsPage(window fyne.Window, identityHash [16]byte, viewIndex int, previousPage func()){

	setLoadingScreen(window, "View Identity Ban Advocates", "Loading verdicts...")

	currentPage := func(){setViewIdentityReviewerVerdictsPage(window, identityHash, viewIndex, previousPage)}

	title := getPageTitleCentered("View Identity Ban Advocates")

	backButton := getBackButtonCentered(previousPage)

	downloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineIdentityVerdicts(identityHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (downloadingRequiredReviews == false){
		description1 := getBoldLabelCentered("This identity is outside of your moderator range.")
		description2 := getLabelCentered("Download the reviews for this identity?")

		downloadButton := getWidgetCentered(widget.NewButton("Download", func(){
				setDownloadReviewsAndReportsForReviewedHashPage(window, identityHash[:], currentPage, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, downloadButton)
		setPageContent(page, window)
		return
	}

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	banAdvocatesMap, err := reviewStorage.GetIdentityBanAdvocatesMap(identityHash, appNetworkType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (len(banAdvocatesMap) == 0){

		noReviewersDescription := getBoldLabelCentered("No moderators have banned this identity.")

		updateButton := getWidgetCentered(widget.NewButtonWithIcon("Update", theme.ViewRefreshIcon(), currentPage))	

		page := container.NewVBox(title, backButton, widget.NewSeparator(), noReviewersDescription, updateButton)

		setPageContent(page, window)
		return
	}

	description := getLabelCentered("Below are the moderators who have banned the identity.")

	//TODO: Navigation buttons and pages

	//Outputs:
	//	-bool: Downloading required reviews/profiles (moderator mode is enabled)
	//	-bool: Parameters exist
	//	-*fyne.Container: Reviewer verdicts grid
	//	-error
	getReviewerVerdictsGrid := func()(bool, bool, *fyne.Container, error){

		notBannedReviewersList := make([][16]byte, 0)
		bannedReviewersList := make([][16]byte, 0)

		for moderatorIdentityHash, _ := range banAdvocatesMap{

			requiredDataIsBeingDownloaded, parametersExist, isBanned, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(false, moderatorIdentityHash, appNetworkType)
			if (err	!= nil) { return false, false, nil, err }
			if (requiredDataIsBeingDownloaded == false){
				return false, parametersExist, nil, nil
			}
			if (parametersExist == false){
				return true, false, nil, nil
			}

			if (isBanned == true){
				bannedReviewersList = append(bannedReviewersList, moderatorIdentityHash)
			} else{
				notBannedReviewersList = append(notBannedReviewersList, moderatorIdentityHash)
			}
		}

		err := helpers.SortIdentityHashListToUnicodeOrder(notBannedReviewersList)
		if (err != nil) { return false, false, nil, err }
		err = helpers.SortIdentityHashListToUnicodeOrder(bannedReviewersList)
		if (err != nil) { return false, false, nil, err }

		allReviewersListSorted := slices.Concat(notBannedReviewersList, bannedReviewersList)

		//TODO: Add pages and navigation

		emptyLabelA := widget.NewLabel("")
		moderatorLabel := getItalicLabelCentered("Moderator")
		isBannedLabel := getItalicLabelCentered("Is Banned")
		reasonLabel := getItalicLabelCentered("Reason")
		emptyLabelC := widget.NewLabel("")

		viewModeratorButtonsColumn := container.NewVBox(emptyLabelA, widget.NewSeparator())
		moderatorIdentityHashColumn := container.NewVBox(moderatorLabel, widget.NewSeparator())
		moderatorIsBannedColumn := container.NewVBox(isBannedLabel, widget.NewSeparator())
		reasonColumn := container.NewVBox(reasonLabel, widget.NewSeparator())
		viewReviewButtonsColumn := container.NewVBox(emptyLabelC, widget.NewSeparator())

		for _, moderatorIdentityHash := range allReviewersListSorted{

			viewModeratorButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewModeratorDetailsPage(window, moderatorIdentityHash, currentPage)
				})

			moderatorIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(moderatorIdentityHash)
			if (err != nil) { return false, false, nil, err }

			identityHashTrimmed, _, err := helpers.TrimAndFlattenString(moderatorIdentityHashString, 10)
			if (err != nil) { return false, false, nil, err }
			identityHashLabel := getBoldLabelCentered(identityHashTrimmed)

			moderatorIsBannedBool := slices.Contains(bannedReviewersList, moderatorIdentityHash)
			moderatorIsBanned := helpers.ConvertBoolToYesOrNoString(moderatorIsBannedBool)
			moderatorIsBannedTranslated := translate(moderatorIsBanned)

			moderatorIsBannedLabel := getBoldLabelCentered(moderatorIsBannedTranslated)

			reviewFound, newestReviewBytes, _, err := reviewStorage.GetModeratorNewestIdentityReview(moderatorIdentityHash, identityHash, appNetworkType)
			if (err != nil) { return false, false, nil, err }
			if (reviewFound == false){
				// Review was deleted or changed after reviewers were retrieved
				continue
			}

			ableToRead, reviewHash, _, reviewNetworkType, retrievedModeratorIdentityHash, _, _, currentReviewedHash, reviewVerdict, reviewMap, err := readReviews.ReadReviewAndHash(false, newestReviewBytes)
			if (err != nil) { return false, false, nil, err }
			if (ableToRead == false){
				return false, false, nil, errors.New("GetModeratorNewestIdentityReview returning invalid review.")
			}
			if (reviewNetworkType != appNetworkType){
				return false, false, nil, errors.New("GetModeratorNewestIdentityReview returning review of different networkType.")
			}
			if (moderatorIdentityHash != retrievedModeratorIdentityHash) {
				return false, false, nil, errors.New("GetModeratorNewestIdentityReview returning review by different moderator.")
			}
			areEqual := bytes.Equal(currentReviewedHash, identityHash[:])
			if (areEqual == false){
				return false, false, nil, errors.New("GetModeratorNewestIdentityReview returning review for different reviewedHash.")
			}
			if (reviewVerdict != "Ban"){
				return false, false, nil, errors.New("GetModeratorNewestIdentityReview returning non-Ban review: " + reviewVerdict)
			}

			getViewReasonButtonOrText := func()(*fyne.Container, error){

				reasonString, exists := reviewMap["Reason"]
				if (exists == false) {
					noneLabel := getBoldLabelCentered(translate("None"))
					return noneLabel, nil
				}

				viewReasonButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
						setViewTextPage(window, "Viewing Verdict Reason", reasonString, false, currentPage)
					}))

				return viewReasonButton, nil
			}
			viewReasonButtonOrText, err := getViewReasonButtonOrText()
			if (err != nil) { return false, false, nil, err }

			viewReviewButton := widget.NewButtonWithIcon("View Review", theme.VisibilityIcon(), func(){
					setViewReviewDetailsPage(window, reviewHash, currentPage)
				})

			viewModeratorButtonsColumn.Add(viewModeratorButton)
			moderatorIdentityHashColumn.Add(identityHashLabel)
			moderatorIsBannedColumn.Add(moderatorIsBannedLabel)
			reasonColumn.Add(viewReasonButtonOrText)
			viewReviewButtonsColumn.Add(viewReviewButton)

			viewModeratorButtonsColumn.Add(widget.NewSeparator())
			moderatorIdentityHashColumn.Add(widget.NewSeparator())
			moderatorIsBannedColumn.Add(widget.NewSeparator())
			reasonColumn.Add(widget.NewSeparator())
			viewReviewButtonsColumn.Add(widget.NewSeparator())
		}

		reviewsGrid := container.NewHBox(layout.NewSpacer(), viewModeratorButtonsColumn, moderatorIdentityHashColumn, moderatorIsBannedColumn, reasonColumn, viewReviewButtonsColumn, layout.NewSpacer())

		return true, true, reviewsGrid, nil
	}

	moderatorModeEnabled, parametersExist, verdictsGrid, err := getReviewerVerdictsGrid()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (moderatorModeEnabled == false){

		description1 := getBoldLabelCentered("Moderator mode is disabled.")
		description2 := getLabelCentered("You must enable moderator mode to view the reviewer verdicts.")

		enableModeratorModeButton := getWidgetCentered(widget.NewButtonWithIcon("Enable Moderator Mode", theme.NavigateNextIcon(), func(){
				setManageModeratorModePage(window, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, enableModeratorModeButton)
		setPageContent(page, window)
		return
	}
	if (parametersExist == false){

		description1 := getBoldLabelCentered("Your client is missing the moderation parameters.")
		description2 := getLabelCentered("Please wait for them to download.")

		refreshButton := getWidgetCentered(widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), currentPage))

		//TODO: Add button to view progress

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, refreshButton)
		setPageContent(page, window)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), verdictsGrid)

	setPageContent(page, window)
}


// This page will show all moderators who have banned/approved the profile
func setViewProfileReviewerVerdictsPage(window fyne.Window, profileHash [28]byte, viewIndex int, previousPage func()){

	setLoadingScreen(window, "View Profile Verdicts", "Loading verdicts...")

	currentPage := func(){setViewProfileReviewerVerdictsPage(window, profileHash, viewIndex, previousPage)}

	title := getPageTitleCentered("View Profile Verdicts")

	backButton := getBackButtonCentered(previousPage)

	_, profileIsDisabled, err := readProfiles.ReadProfileHashMetadata(profileHash)
	if (err != nil){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		setErrorEncounteredPage(window, errors.New("setViewProfileReviewerVerdictsPage called with invalid profileHash: " + profileHashHex), previousPage)
		return
	}
	if (profileIsDisabled == true){
		description1 := getBoldLabelCentered("This profile is disabled.")
		description2 := getLabelCentered("It cannot be reviewed.")

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2)

		setPageContent(page, window)
		return
	}

	profileMetadataIsKnown, _, profileNetworkType, profileIdentityHash, _, _, _, profileAttributeHashesMap, err := contentMetadata.GetProfileMetadata(profileHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (profileMetadataIsKnown == false){

		description1 := getLabelCentered("The profile is not downloaded.")
		description2 := getLabelCentered("You must download it to view its reviews.")
		description3 := getLabelCentered("It may not be possible if the profile has been deleted from the network.")
		description4 := getLabelCentered("Try to download the profile?")

		downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.DownloadIcon(), func(){
				//TODO
				showUnderConstructionDialog(window)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, downloadButton)

		setPageContent(page, window)
		return
	}

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (profileNetworkType != appNetworkType){
		setErrorEncounteredPage(window, errors.New("setViewProfileReviewerVerdictsPage called with profile belonging to different networkType."), previousPage)
		return
	}

	downloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineIdentityVerdicts(profileIdentityHash)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (downloadingRequiredReviews == false){

		description1 := getBoldLabelCentered("This profile's author is outside of your moderator range.")
		description2 := getLabelCentered("Download the reviews for this profile?")

		downloadButton := getWidgetCentered(widget.NewButton("Download", func(){
				setDownloadReviewsAndReportsForReviewedHashPage(window, profileHash[:], currentPage, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, downloadButton)
		setPageContent(page, window)
		return
	}

	profileAttributeHashesList := helpers.GetListOfMapValues(profileAttributeHashesMap)

	approveAdvocatesMap, banAdvocatesMap, err := reviewStorage.GetProfileVerdictMaps(profileHash, profileNetworkType, true, profileAttributeHashesList)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (len(approveAdvocatesMap) == 0 && len(banAdvocatesMap) == 0){

		noReviewersDescription := getLabelCentered("No moderators have reviewed the profile.")

		updateButton := getWidgetCentered(widget.NewButtonWithIcon("Update", theme.ViewRefreshIcon(), currentPage))	

		page := container.NewVBox(title, backButton, widget.NewSeparator(), noReviewersDescription, updateButton)

		setPageContent(page, window)
		return
	}

	description := getLabelCentered("Below are the moderators who have reviewed the profile.")

	//TODO: Navigation pages and buttons

	//Outputs:
	//	-bool: Downloading required reviews/profiles (moderator mode is enabled)
	//	-bool: Parameters exist
	//	-*fyne.Container: Reviewer verdicts grid
	//	-error
	getReviewerVerdictsGrid := func()(bool, bool, *fyne.Container, error){

		approveAdvocatesList := helpers.GetListOfMapKeys(approveAdvocatesMap)
		banAdvocatesList := helpers.GetListOfMapKeys(banAdvocatesMap)

		allReviewersList := slices.Concat(approveAdvocatesList, banAdvocatesList)

		notBannedReviewersList := make([][16]byte, 0)
		bannedReviewersList := make([][16]byte, 0)

		for _, moderatorIdentityHash := range allReviewersList{

			requiredDataIsBeingDownloaded, parametersExist, isBanned, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(false, moderatorIdentityHash, profileNetworkType)
			if (err	!= nil) { return false, false, nil, err }
			if (requiredDataIsBeingDownloaded == false){
				return false, parametersExist, nil, nil
			}
			if (parametersExist == false){
				return true, false, nil, nil
			}

			if (isBanned == true){
				bannedReviewersList = append(bannedReviewersList, moderatorIdentityHash)
			} else{
				notBannedReviewersList = append(notBannedReviewersList, moderatorIdentityHash)
			}
		}

		err := helpers.SortIdentityHashListToUnicodeOrder(notBannedReviewersList)
		if (err != nil) { return false, false, nil, err }
		err = helpers.SortIdentityHashListToUnicodeOrder(bannedReviewersList)
		if (err != nil) { return false, false, nil, err }

		allReviewersListSorted := slices.Concat(notBannedReviewersList, bannedReviewersList)

		//TODO: Add pages and navigation

		emptyLabel1 := widget.NewLabel("")
		emptyLabel2 := widget.NewLabel("")
		emptyLabel3 := widget.NewLabel("")
		emptyLabel4 := widget.NewLabel("")
		emptyLabel5 := widget.NewLabel("")
		approvedLabel := getItalicLabelCentered("Approved")
		bannedLabel := getItalicLabelCentered("Banned")

		emptyLabel6 := widget.NewLabel("")
		authorLabel := getItalicLabelCentered("Author")
		isBannedLabel := getItalicLabelCentered("Is Banned")
		verdictLabel := getItalicLabelCentered("Verdict")
		fullProfileLabel := getItalicLabelCentered("Full Profile")
		attributesLabelA := getItalicLabelCentered("Attributes")
		attributesLabelB := getItalicLabelCentered("Attributes")

		viewModeratorButtonsColumn := container.NewVBox(emptyLabel1, emptyLabel6, widget.NewSeparator())
		moderatorIdentityHashColumn := container.NewVBox(emptyLabel2, authorLabel, widget.NewSeparator())
		moderatorIsBannedColumn := container.NewVBox(emptyLabel3, isBannedLabel, widget.NewSeparator())
		verdictColumn := container.NewVBox(emptyLabel4, verdictLabel, widget.NewSeparator())
		fullProfileReviewColumn := container.NewVBox(emptyLabel5, fullProfileLabel, widget.NewSeparator())
		approvedAttributesColumn := container.NewVBox(approvedLabel, attributesLabelA, widget.NewSeparator())
		bannedAttributesColumn := container.NewVBox(bannedLabel, attributesLabelB, widget.NewSeparator())

		for _, moderatorIdentityHash := range allReviewersListSorted{

			viewModeratorButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewModeratorDetailsPage(window, moderatorIdentityHash, currentPage)
				})

			moderatorIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(moderatorIdentityHash)
			if (err != nil) { return false, false, nil, err }

			identityHashTrimmed, _, err := helpers.TrimAndFlattenString(moderatorIdentityHashString, 10)
			if (err != nil) { return false, false, nil, err }
			identityHashLabel := getBoldLabelCentered(identityHashTrimmed)

			moderatorIsBannedBool := slices.Contains(bannedReviewersList, moderatorIdentityHash)
			moderatorIsBanned := helpers.ConvertBoolToYesOrNoString(moderatorIsBannedBool)
			moderatorIsBannedTranslated := translate(moderatorIsBanned)

			moderatorIsBannedLabel := getBoldLabelCentered(moderatorIsBannedTranslated)

			profileVerdictExists, profileVerdict, fullProfileReviewExists, fullProfileReviewBytes, _, attributeApproveReviewsMap, attributeBanReviewsMap, err := reviewStorage.GetModeratorNewestProfileReviews(moderatorIdentityHash, profileHash, profileNetworkType, profileAttributeHashesList)
			if (err != nil) { return false, false, nil, err }
			if (profileVerdictExists == false){
				// Review was deleted or changed after reviewers were retrieved
				continue
			}

			profileVerdictLabel := getBoldLabelCentered(profileVerdict)

			viewModeratorButtonsColumn.Add(viewModeratorButton)
			moderatorIdentityHashColumn.Add(identityHashLabel)
			moderatorIsBannedColumn.Add(moderatorIsBannedLabel)
			verdictColumn.Add(profileVerdictLabel)

			if (fullProfileReviewExists == false){

				noneLabel := getLabelCentered("None")

				fullProfileReviewColumn.Add(noneLabel)
			} else {

				ableToRead, reviewHash, _, reviewNetworkType, retrievedModeratorIdentityHash, _, _, currentReviewedHash, _, _, err := readReviews.ReadReviewAndHash(false, fullProfileReviewBytes)
				if (err != nil) { return false, false, nil, err }
				if (ableToRead == false){
					return false, false, nil, errors.New("GetModeratorNewestProfileReviews returning invalid full profile review.")
				}
				if (reviewNetworkType != profileNetworkType){
					return false, false, nil, errors.New("GetModeratorNewestProfileReviews returning full profile review belonging to different network type.")
				}
				if (moderatorIdentityHash != retrievedModeratorIdentityHash) {
					return false, false, nil, errors.New("GetModeratorNewestProfileReviews returning full profile review by different moderator.")
				}
				areEqual := bytes.Equal(currentReviewedHash, profileHash[:])
				if (areEqual == false){
					return false, false, nil, errors.New("GetModeratorNewestProfileReviews returning full profile review for different reviewedHash.")
				}

				fullProfileReviewButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
						setViewReviewDetailsPage(window, reviewHash, currentPage)
					})

				fullProfileReviewColumn.Add(fullProfileReviewButton)
			}

			if (len(attributeApproveReviewsMap) == 0){

				noneLabel := getLabelCentered("None")
				approvedAttributesColumn.Add(noneLabel)
			} else {

				numberOfApprovedAttributes := len(attributeApproveReviewsMap)
				numberOfApprovedAttributesString := helpers.ConvertIntToString(numberOfApprovedAttributes)

				viewApprovedAttributesButton := widget.NewButtonWithIcon(numberOfApprovedAttributesString, theme.VisibilityIcon(), func(){
						//TODO: A page to view the attribute approvals
						showUnderConstructionDialog(window)
					})

				approvedAttributesColumn.Add(viewApprovedAttributesButton)
			}

			if (len(attributeBanReviewsMap) == 0){

				noneLabel := getLabelCentered("None")
				bannedAttributesColumn.Add(noneLabel)
			} else {

				numberOfBannedAttributes := len(attributeBanReviewsMap)
				numberOfBannedAttributesString := helpers.ConvertIntToString(numberOfBannedAttributes)

				viewBannedAttributesButton := widget.NewButtonWithIcon(numberOfBannedAttributesString, theme.VisibilityIcon(), func(){
						//TODO: A page to view the attribute bans
						showUnderConstructionDialog(window)
					})

				bannedAttributesColumn.Add(viewBannedAttributesButton)
			}

			viewModeratorButtonsColumn.Add(widget.NewSeparator())
			moderatorIdentityHashColumn.Add(widget.NewSeparator())
			moderatorIsBannedColumn.Add(widget.NewSeparator())
			fullProfileReviewColumn.Add(widget.NewSeparator())
			approvedAttributesColumn.Add(widget.NewSeparator())
			bannedAttributesColumn.Add(widget.NewSeparator())
		}

		reviewsGrid := container.NewHBox(layout.NewSpacer(), viewModeratorButtonsColumn, moderatorIdentityHashColumn, moderatorIsBannedColumn, fullProfileReviewColumn, approvedAttributesColumn, bannedAttributesColumn, layout.NewSpacer())

		return true, true, reviewsGrid, nil
	}

	moderatorModeEnabled, parametersExist, verdictsGrid, err := getReviewerVerdictsGrid()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (moderatorModeEnabled == false){

		description1 := getBoldLabelCentered("Moderator mode is disabled.")
		description2 := getLabelCentered("You must enable moderator mode to view the reviewer verdicts.")

		enableModeratorModeButton := getWidgetCentered(widget.NewButtonWithIcon("Enable Moderator Mode", theme.NavigateNextIcon(), func(){
				setManageModeratorModePage(window, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, enableModeratorModeButton)
		setPageContent(page, window)
		return
	}
	if (parametersExist == false){
		description1 := getBoldLabelCentered("Your client is missing the moderation parameters.")
		description2 := getLabelCentered("Please wait for them to download.")

		refreshButton := getWidgetCentered(widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), currentPage))

		//TODO: Add button to view progress

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, refreshButton)
		setPageContent(page, window)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), verdictsGrid)

	setPageContent(page, window)
}

// This page will show all moderators who have banned/approved the attribute
func setViewAttributeReviewerVerdictsPage(window fyne.Window, attributeHash [27]byte, viewIndex int, previousPage func()){

	setLoadingScreen(window, "View Attribute Verdicts", "Loading verdicts...")

	currentPage := func(){setViewAttributeReviewerVerdictsPage(window, attributeHash, viewIndex, previousPage)}

	title := getPageTitleCentered("View Attribute Verdicts")

	backButton := getBackButtonCentered(previousPage)

	attributeMetadataIsKnown, _, attributeAuthor, attributeNetworkType, attributeProfileHashesList, err := profileStorage.GetProfileAttributeMetadata(attributeHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (attributeMetadataIsKnown == false){

		description1 := getBoldLabelCentered("The attribute is not downloaded.")
		description2 := getLabelCentered("You must download it to view its reviews.")
		description3 := getLabelCentered("It may not be possible if the attribute has been deleted from the network.")
		description4 := getLabelCentered("Try to download the attribute?")

		downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.DownloadIcon(), func(){
				//TODO: Create a way to download profiles which contain an attribute
				showUnderConstructionDialog(window)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, downloadButton)

		setPageContent(page, window)
		return
	}

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (appNetworkType != attributeNetworkType){
		setErrorEncounteredPage(window, errors.New("setViewAttributeReviewerVerdictsPage called with attribute belonging to different network type than app."), previousPage)
		return
	}

	downloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineIdentityVerdicts(attributeAuthor)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (downloadingRequiredReviews == false){

		description1 := getBoldLabelCentered("This attribute's author is outside of your moderator range.")
		description2 := getLabelCentered("Download the reviews for this attribute?")

		downloadButton := getWidgetCentered(widget.NewButton("Download", func(){
				setDownloadReviewsAndReportsForReviewedHashPage(window, attributeHash[:], currentPage, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, downloadButton)
		setPageContent(page, window)
		return
	}

	attributeApproveAdvocatesMap, attributeBanAdvocatesMap, err := reviewStorage.GetProfileAttributeVerdictMaps(attributeHash, attributeNetworkType, true, attributeProfileHashesList)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (len(attributeApproveAdvocatesMap) == 0 && len(attributeBanAdvocatesMap) == 0){

		noReviewersDescription := getBoldLabelCentered("No moderators have reviewed the attribute.")

		updateButton := getWidgetCentered(widget.NewButtonWithIcon("Update", theme.ViewRefreshIcon(), currentPage))	

		page := container.NewVBox(title, backButton, widget.NewSeparator(), noReviewersDescription, updateButton)

		setPageContent(page, window)
		return
	}

	description1 := getLabelCentered("These are the moderators who have reviewed the attribute.")
	description2 := getLabelCentered("This includes moderators who have approved a profile that contains this attribute.")

	//TODO: Navigation pages and buttons

	//Outputs:
	//	-bool: Downloading required reviews/profiles (moderator mode is enabled)
	//	-bool: Parameters exist
	//	-*fyne.Container: Reviewer verdicts grid
	//	-error
	getReviewerVerdictsGrid := func()(bool, bool, *fyne.Container, error){

		approveAdvocatesList := helpers.GetListOfMapKeys(attributeApproveAdvocatesMap)
		banAdvocatesList := helpers.GetListOfMapKeys(attributeApproveAdvocatesMap)

		allReviewersList := slices.Concat(approveAdvocatesList, banAdvocatesList)

		notBannedReviewersList := make([][16]byte, 0)
		bannedReviewersList := make([][16]byte, 0)

		for _, moderatorIdentityHash := range allReviewersList{

			requiredDataIsBeingDownloaded, parametersExist, isBanned, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(false, moderatorIdentityHash, attributeNetworkType)
			if (err	!= nil) { return false, false, nil, err }
			if (requiredDataIsBeingDownloaded == false){
				return false, parametersExist, nil, nil
			}
			if (parametersExist == false){
				return true, false, nil, nil
			}

			if (isBanned == true){
				bannedReviewersList = append(bannedReviewersList, moderatorIdentityHash)
			} else{
				notBannedReviewersList = append(notBannedReviewersList, moderatorIdentityHash)
			}
		}

		err := helpers.SortIdentityHashListToUnicodeOrder(notBannedReviewersList)
		if (err != nil) { return false, false, nil, err }
		err = helpers.SortIdentityHashListToUnicodeOrder(bannedReviewersList)
		if (err != nil) { return false, false, nil, err }

		allReviewersListSorted := slices.Concat(notBannedReviewersList, bannedReviewersList)

		//TODO: Add pages and navigation

		emptyLabelA := widget.NewLabel("")
		authorLabel := getItalicLabelCentered("Author")
		isBannedLabel := getItalicLabelCentered("Is Banned")
		reviewTypeTitle := getItalicLabelCentered("Review Type")
		verdictLabel := getItalicLabelCentered("Verdict")
		reasonLabel := getItalicLabelCentered("Reason")
		emptyLabelC := widget.NewLabel("")

		viewModeratorButtonsColumn := container.NewVBox(emptyLabelA, widget.NewSeparator())
		moderatorIdentityHashColumn := container.NewVBox(authorLabel, widget.NewSeparator())
		moderatorIsBannedColumn := container.NewVBox(isBannedLabel, widget.NewSeparator())
		reviewTypeColumn := container.NewVBox(reviewTypeTitle, widget.NewSeparator())
		verdictColumn := container.NewVBox(verdictLabel, widget.NewSeparator())
		reasonColumn := container.NewVBox(reasonLabel, widget.NewSeparator())
		viewReviewButtonsColumn := container.NewVBox(emptyLabelC, widget.NewSeparator())

		for _, moderatorIdentityHash := range allReviewersListSorted{

			viewModeratorButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewModeratorDetailsPage(window, moderatorIdentityHash, currentPage)
				})

			moderatorIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(moderatorIdentityHash)
			if (err != nil) { return false, false, nil, err }

			identityHashTrimmed, _, err := helpers.TrimAndFlattenString(moderatorIdentityHashString, 10)
			if (err != nil) { return false, false, nil, err }
			identityHashLabel := getBoldLabelCentered(identityHashTrimmed)

			moderatorIsBannedBool := slices.Contains(bannedReviewersList, moderatorIdentityHash)
			moderatorIsBanned := helpers.ConvertBoolToYesOrNoString(moderatorIsBannedBool)
			moderatorIsBannedTranslated := translate(moderatorIsBanned)

			moderatorIsBannedLabel := getBoldLabelCentered(moderatorIsBannedTranslated)

			reviewFound, reviewType, newestReviewBytes, newestReviewMap, reviewVerdict, _, err := reviewStorage.GetModeratorNewestProfileAttributeReview(moderatorIdentityHash, attributeHash, attributeNetworkType, true)
			if (err != nil) { return false, false, nil, err }
			if (reviewFound == false){
				// Review was deleted or changed after reviewers were retrieved
				continue
			}

			reviewTypeLabel := getBoldLabelCentered(reviewType)

			ableToRead, reviewHash, _, reviewNetworkType, retrievedModeratorIdentityHash, _, _, _, _, _, err := readReviews.ReadReviewAndHash(false, newestReviewBytes)
			if (err != nil) { return false, false, nil, err }
			if (ableToRead == false){
				return false, false, nil, errors.New("GetModeratorNewestProfileAttributeReview returning invalid review.")
			}
			if (reviewNetworkType != attributeNetworkType){
				return false, false, nil, errors.New("GetModeratorNewestProfileAttributeReview returning review belonging to different networkType.")
			}
			if (moderatorIdentityHash != retrievedModeratorIdentityHash) {
				return false, false, nil, errors.New("GetModeratorNewestProfileAttributeReview returning review by different moderator.")
			}

			reviewVerdictTranslated := translate(reviewVerdict)
			reviewVerdictLabel := getBoldLabelCentered(reviewVerdictTranslated)

			getViewReasonButtonOrText := func()(*fyne.Container, error){

				reasonString, exists := newestReviewMap["Reason"]
				if (exists == false) {
					noneLabel := getBoldLabelCentered(translate("None"))
					return noneLabel, nil
				}

				viewReasonButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
						setViewTextPage(window, "Viewing Verdict Reason", reasonString, false, currentPage)
					}))

				return viewReasonButton, nil
			}
			viewReasonButtonOrText, err := getViewReasonButtonOrText()
			if (err != nil) { return false, false, nil, err }

			viewReviewButton := widget.NewButtonWithIcon("View Review", theme.VisibilityIcon(), func(){
					setViewReviewDetailsPage(window, reviewHash, currentPage)
				})

			viewModeratorButtonsColumn.Add(viewModeratorButton)
			moderatorIdentityHashColumn.Add(identityHashLabel)
			moderatorIsBannedColumn.Add(moderatorIsBannedLabel)
			reviewTypeColumn.Add(reviewTypeLabel)
			verdictColumn.Add(reviewVerdictLabel)
			reasonColumn.Add(viewReasonButtonOrText)
			viewReviewButtonsColumn.Add(viewReviewButton)

			viewModeratorButtonsColumn.Add(widget.NewSeparator())
			moderatorIdentityHashColumn.Add(widget.NewSeparator())
			moderatorIsBannedColumn.Add(widget.NewSeparator())
			reviewTypeColumn.Add(widget.NewSeparator())
			verdictColumn.Add(widget.NewSeparator())
			reasonColumn.Add(widget.NewSeparator())
			viewReviewButtonsColumn.Add(widget.NewSeparator())
		}

		reviewsGrid := container.NewHBox(layout.NewSpacer(), viewModeratorButtonsColumn, moderatorIdentityHashColumn, moderatorIsBannedColumn, reviewTypeColumn, verdictColumn, reasonColumn, viewReviewButtonsColumn, layout.NewSpacer())

		return true, true, reviewsGrid, nil
	}

	moderatorModeEnabled, parametersExist, verdictsGrid, err := getReviewerVerdictsGrid()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (moderatorModeEnabled == false){

		description1 := getBoldLabelCentered("Moderator mode is disabled.")
		description2 := getLabelCentered("You must enable moderator mode to view the reviewer verdicts.")

		enableModeratorModeButton := getWidgetCentered(widget.NewButtonWithIcon("Enable Moderator Mode", theme.NavigateNextIcon(), func(){
				setManageModeratorModePage(window, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, enableModeratorModeButton)
		setPageContent(page, window)
		return
	}
	if (parametersExist == false){
		description1 := getBoldLabelCentered("Your client is missing the moderation parameters.")
		description2 := getLabelCentered("Please wait for them to download.")

		refreshButton := getWidgetCentered(widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), currentPage))

		//TODO: Add button to view progress

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, refreshButton)
		setPageContent(page, window)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, verdictsGrid)

	setPageContent(page, window)
}


// This page will show all moderators who have banned/approved the message
func setViewMessageReviewerVerdictsPage(window fyne.Window, messageHash [26]byte, viewIndex int, previousPage func()){

	setLoadingScreen(window, "View Message Verdicts", "Loading verdicts...")

	currentPage := func(){setViewMessageReviewerVerdictsPage(window, messageHash, viewIndex, previousPage)}

	title := getPageTitleCentered("View Message Verdicts")

	backButton := getBackButtonCentered(previousPage)

	messageMetadataIsKnown, _, messageNetworkType, _, messageInbox, messageCipherKeyHash, err := contentMetadata.GetMessageMetadata(messageHash)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (messageMetadataIsKnown == false){

		description1 := getBoldLabelCentered("The message is not downloaded.")
		description2 := getLabelCentered("You must download the message to view its reviews.")
		description3 := getLabelCentered("It may not be possible if the message has been deleted from the network.")
		description4 := getLabelCentered("Try to download the message?")

		downloadButton := getWidgetCentered(widget.NewButtonWithIcon("Download", theme.DownloadIcon(), func(){
				//TODO
				showUnderConstructionDialog(window)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, downloadButton)

		setPageContent(page, window)
		return
	}

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (appNetworkType != messageNetworkType){
		// TODO: Show an explanatory page instead of an error. Users may want to view messages by pasting a hash into a text entry lookup, without knowing which network type the message belongs to.
		// Add this explanatory page for all of the relevant pages within the moderatorGui file.
		setErrorEncounteredPage(window, errors.New("setViewMessageReviewerVerdictsPage called with message belonging to different network type than app."), previousPage)
		return
	}

	downloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineMessageVerdict(messageNetworkType, messageInbox, true, messageHash)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (downloadingRequiredReviews == false){

		description1 := getBoldLabelCentered("This message's inbox is outside of your moderator range.")
		description2 := getLabelCentered("Download the reviews for this message?")

		downloadButton := getWidgetCentered(widget.NewButton("Download", func(){
				setDownloadReviewsAndReportsForReviewedHashPage(window, messageHash[:], currentPage, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, downloadButton)
		setPageContent(page, window)
		return
	}

	approveAdvocatesMap, banAdvocatesMap, err := reviewStorage.GetMessageVerdictMaps(messageHash, messageNetworkType, messageCipherKeyHash)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	if (len(approveAdvocatesMap) == 0 && len(banAdvocatesMap) == 0){

		noReviewersDescription := getBoldLabelCentered("No moderators have reviewed the message.")

		updateButton := getWidgetCentered(widget.NewButtonWithIcon("Update", theme.ViewRefreshIcon(), currentPage))	

		page := container.NewVBox(title, backButton, widget.NewSeparator(), noReviewersDescription, updateButton)

		setPageContent(page, window)
		return
	}

	description := getLabelCentered("Below are the moderators who have reviewed the message.")

	//TODO: Navigation button and pages

	//Outputs:
	//	-bool: Downloading required reviews/profiles (moderator mode is enabled)
	//	-bool: Parameters exist
	//	-*fyne.Container: Reviewer verdicts grid
	//	-error
	getReviewerVerdictsGrid := func()(bool, bool, *fyne.Container, error){

		approveAdvocatesList := helpers.GetListOfMapKeys(approveAdvocatesMap)
		banAdvocatesList := helpers.GetListOfMapKeys(banAdvocatesMap)

		allReviewersList := slices.Concat(approveAdvocatesList, banAdvocatesList)

		notBannedReviewersList := make([][16]byte, 0)
		bannedReviewersList := make([][16]byte, 0)

		for _, moderatorIdentityHash := range allReviewersList{

			requiredDataIsBeingDownloaded, parametersExist, isBanned, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(false, moderatorIdentityHash, messageNetworkType)
			if (err	!= nil) { return false, false, nil, err }
			if (requiredDataIsBeingDownloaded == false){
				return false, parametersExist, nil, nil
			}
			if (parametersExist == false){
				return true, false, nil, nil
			}

			if (isBanned == true){
				bannedReviewersList = append(bannedReviewersList, moderatorIdentityHash)
			} else{
				notBannedReviewersList = append(notBannedReviewersList, moderatorIdentityHash)
			}
		}

		err := helpers.SortIdentityHashListToUnicodeOrder(notBannedReviewersList)
		if (err != nil) { return false, false, nil, err }
		err = helpers.SortIdentityHashListToUnicodeOrder(bannedReviewersList)
		if (err != nil) { return false, false, nil, err }

		allReviewersListSorted := slices.Concat(notBannedReviewersList, bannedReviewersList)

		//TODO: Add pages and navigation

		emptyLabelA := widget.NewLabel("")
		authorLabel := getItalicLabelCentered("Author")
		isBannedLabel := getItalicLabelCentered("Is Banned")
		verdictLabel := getItalicLabelCentered("Verdict")
		reasonLabel := getItalicLabelCentered("Reason")
		emptyLabelC := widget.NewLabel("")

		viewModeratorButtonsColumn := container.NewVBox(emptyLabelA, widget.NewSeparator())
		moderatorIdentityHashColumn := container.NewVBox(authorLabel, widget.NewSeparator())
		moderatorIsBannedColumn := container.NewVBox(isBannedLabel, widget.NewSeparator())
		verdictColumn := container.NewVBox(verdictLabel, widget.NewSeparator())
		reasonColumn := container.NewVBox(reasonLabel, widget.NewSeparator())
		viewReviewButtonsColumn := container.NewVBox(emptyLabelC, widget.NewSeparator())

		for _, moderatorIdentityHash := range allReviewersListSorted{

			viewModeratorButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewModeratorDetailsPage(window, moderatorIdentityHash, currentPage)
				})

			moderatorIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(moderatorIdentityHash)
			if (err != nil) { return false, false, nil, err }

			identityHashTrimmed, _, err := helpers.TrimAndFlattenString(moderatorIdentityHashString, 10)
			if (err != nil) { return false, false, nil, err }
			identityHashLabel := getBoldLabelCentered(identityHashTrimmed)

			moderatorIsBannedBool := slices.Contains(bannedReviewersList, moderatorIdentityHash)
			moderatorIsBanned := helpers.ConvertBoolToYesOrNoString(moderatorIsBannedBool)
			moderatorIsBannedTranslated := translate(moderatorIsBanned)

			moderatorIsBannedLabel := getBoldLabelCentered(moderatorIsBannedTranslated)

			reviewFound, newestReviewBytes, _, err := reviewStorage.GetModeratorNewestMessageReview(moderatorIdentityHash, messageHash, messageNetworkType, messageCipherKeyHash)
			if (err != nil) { return false, false, nil, err }
			if (reviewFound == false){
				// Review was deleted or changed after reviewers were retrieved
				continue
			}

			ableToRead, reviewHash, _, reviewNetworkType, retrievedModeratorIdentityHash, _, _, currentReviewedHash, reviewVerdict, reviewMap, err := readReviews.ReadReviewAndHash(false, newestReviewBytes)
			if (err != nil) { return false, false, nil, err }
			if (ableToRead == false){
				return false, false, nil, errors.New("GetModeratorNewestMessageReview returning invalid review.")
			}
			if (reviewNetworkType != messageNetworkType){
				return false, false, nil, errors.New("GetModeratorNewestMessageReview returning review belonging to different networkType.")
			}
			if (moderatorIdentityHash != retrievedModeratorIdentityHash) {
				return false, false, nil, errors.New("GetModeratorNewestMessageReview returning review by different moderator.")
			}
			areEqual := bytes.Equal(currentReviewedHash, messageHash[:])
			if (areEqual == false){
				return false, false, nil, errors.New("GetModeratorNewestMessageReview returning review for different reviewedHash.")
			}

			reviewVerdictTranslated := translate(reviewVerdict)
			reviewVerdictLabel := getBoldLabelCentered(reviewVerdictTranslated)

			getViewReasonButtonOrText := func()(*fyne.Container, error){

				reasonString, exists := reviewMap["Reason"]
				if (exists == false) {
					noneLabel := getBoldLabelCentered(translate("None"))
					return noneLabel, nil
				}

				viewReasonButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
						setViewTextPage(window, "Viewing Verdict Reason", reasonString, false, currentPage)
					}))

				return viewReasonButton, nil
			}
			viewReasonButtonOrText, err := getViewReasonButtonOrText()
			if (err != nil) { return false, false, nil, err }

			viewReviewButton := widget.NewButtonWithIcon("View Review", theme.VisibilityIcon(), func(){
					setViewReviewDetailsPage(window, reviewHash, currentPage)
				})

			viewModeratorButtonsColumn.Add(viewModeratorButton)
			moderatorIdentityHashColumn.Add(identityHashLabel)
			moderatorIsBannedColumn.Add(moderatorIsBannedLabel)
			verdictColumn.Add(reviewVerdictLabel)
			reasonColumn.Add(viewReasonButtonOrText)
			viewReviewButtonsColumn.Add(viewReviewButton)

			viewModeratorButtonsColumn.Add(widget.NewSeparator())
			moderatorIdentityHashColumn.Add(widget.NewSeparator())
			moderatorIsBannedColumn.Add(widget.NewSeparator())
			verdictColumn.Add(widget.NewSeparator())
			reasonColumn.Add(widget.NewSeparator())
			viewReviewButtonsColumn.Add(widget.NewSeparator())
		}

		reviewsGrid := container.NewHBox(layout.NewSpacer(), viewModeratorButtonsColumn, moderatorIdentityHashColumn, moderatorIsBannedColumn, verdictColumn, reasonColumn, viewReviewButtonsColumn, layout.NewSpacer())

		return true, true, reviewsGrid, nil
	}

	moderatorModeEnabled, parametersExist, verdictsGrid, err := getReviewerVerdictsGrid()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (moderatorModeEnabled == false){

		description1 := getBoldLabelCentered("Moderator mode is disabled.")
		description2 := getLabelCentered("You must enable moderator mode to view the reviewer verdicts.")
		
		enableModeratorModeButton := getWidgetCentered(widget.NewButtonWithIcon("Enable Moderator Mode", theme.NavigateNextIcon(), func(){
				setManageModeratorModePage(window, currentPage)
			}))
		
		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, enableModeratorModeButton)
		setPageContent(page, window)
		return
	}
	if (parametersExist == false){
		
		description1 := getBoldLabelCentered("Your app is missing the moderation parameters.")
		description2 := getLabelCentered("Please wait for them to download.")

		refreshButton := getWidgetCentered(widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), currentPage))

		//TODO: Add button to view progress

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, refreshButton)
		setPageContent(page, window)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), verdictsGrid)

	setPageContent(page, window)
}


func setViewModeratorDetailsPage(window fyne.Window, moderatorIdentityHash [16]byte, previousPage func()){

	appMemory.SetMemoryEntry("CurrentViewedPage", "ModeratorDetails")

	currentPage := func(){setViewModeratorDetailsPage(window, moderatorIdentityHash, previousPage)}

	moderatorIdentityHashString, userIdentityType, err := identity.EncodeIdentityHashBytesToString(moderatorIdentityHash)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (userIdentityType != "Moderator"){
		setErrorEncounteredPage(window, errors.New("ViewModeratorDetailsPage called with non-moderator identity: " + moderatorIdentityHashString), previousPage)
		return
	}

	title := getPageTitleCentered("Moderator Details")

	backButton := getBackButtonCentered(previousPage)

	identityHashLabel := widget.NewLabel("Identity Hash:")
	identityHashText := getBoldLabel(moderatorIdentityHashString)
	identityHashRow := container.NewHBox(layout.NewSpacer(), identityHashLabel, identityHashText, layout.NewSpacer())

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	rankingKnown, moderatorRank, totalNumberOfModerators, err := moderatorRanking.GetModeratorRanking(moderatorIdentityHash, appNetworkType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	getModeratorRankText := func()string{
		if (rankingKnown == false){
			result := translate("Unknown")
			return result		
		}

		moderatorRankString := helpers.ConvertIntToString(moderatorRank)
		totalNumberOfModeratorsString := helpers.ConvertIntToString(totalNumberOfModerators)

		moderatorRankText := moderatorRankString + "/" + totalNumberOfModeratorsString

		return moderatorRankText
	}

	moderatorRankText := getModeratorRankText()

	moderatorRankTitle := widget.NewLabel("Moderator Rank:")
	moderatorRankLabel := getBoldLabel(moderatorRankText)

	moderatorRankRow := container.NewHBox(layout.NewSpacer(), moderatorRankTitle, moderatorRankLabel, layout.NewSpacer())

	showModeratorModeIsDisabledPage := func(){

		description1 := getBoldLabelCentered("Moderator mode is disabled.")
		description2 := getLabelCentered("You must enable moderator mode to view the moderator details.")

		enableModeratorModeButton := getWidgetCentered(widget.NewButtonWithIcon("Enable Moderator Mode", theme.NavigateNextIcon(), func(){
				setManageModeratorModePage(window, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, enableModeratorModeButton)
		setPageContent(page, window)
	}

	moderatorModeIsEnabled, parametersExist, isBannedStatus, err := bannedModeratorConsensus.GetModeratorIsBannedStatus(false, moderatorIdentityHash, appNetworkType)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (moderatorModeIsEnabled == false){

		showModeratorModeIsDisabledPage()
		return
	}
	if (parametersExist == false){

		description1 := getBoldLabelCentered("Your client is missing the moderation parameters.")
		description2 := getLabelCentered("Please wait for them to download.")

		refreshButton := getWidgetCentered(widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), currentPage))

		//TODO: Add button to view progress

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, refreshButton)
		setPageContent(page, window)
		return
	}

	getBannedStatusString := func()string{
		if (isBannedStatus == true){
			return "Banned"
		}
		return "Not Banned"
	}
	bannedStatusString := getBannedStatusString()

	isBannedLabel := widget.NewLabel("Moderation Status:")
	bannedStatusLabel := getBoldLabel(bannedStatusString)
	bannedStatusRow := container.NewHBox(layout.NewSpacer(), isBannedLabel, bannedStatusLabel, layout.NewSpacer())

	downloadingRequiredReviews, numberOfBanAdvocates, err := reviewStorage.GetNumberOfBanAdvocatesForIdentity(moderatorIdentityHash, appNetworkType)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (downloadingRequiredReviews == false){
		showModeratorModeIsDisabledPage()
		return
	}

	numberOfBanAdvocatesString := helpers.ConvertIntToString(numberOfBanAdvocates)

	numberOfBanAdvocatesLabel := widget.NewLabel("Number Of Ban Advocates:")
	numberOfBanAdvocatesText := getBoldLabel(numberOfBanAdvocatesString)
	numberOfBanAdvocatesRow := container.NewHBox(layout.NewSpacer(), numberOfBanAdvocatesLabel, numberOfBanAdvocatesText, layout.NewSpacer())

	viewProfileButton := widget.NewButtonWithIcon("View Profile", theme.AccountIcon(), func(){
			setViewPeerProfilePageFromIdentityHash(window, moderatorIdentityHash, currentPage)
		})

	viewReviewsButton := widget.NewButtonWithIcon("View Reviews", theme.ListIcon(), func(){
			setViewAllReviewsCreatedByModeratorPage(window, moderatorIdentityHash, "Profile", 0, currentPage)
		})

	viewBanAdvocatesButton := widget.NewButtonWithIcon("View Ban Advocates", theme.ErrorIcon(), func(){
			setViewIdentityReviewerVerdictsPage(window, moderatorIdentityHash, 0, currentPage)
		})

	viewStatisticsButton := widget.NewButtonWithIcon("View Statistics", theme.InfoIcon(), func(){
			//TODO: A page to view a user's moderation statistics
			// An example is the percentage of profiles/attributes/messages they have banned/approved
			showUnderConstructionDialog(window)
		})

	buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, viewProfileButton, viewReviewsButton, viewBanAdvocatesButton, viewStatisticsButton))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), identityHashRow, widget.NewSeparator(), moderatorRankRow, widget.NewSeparator(), bannedStatusRow, widget.NewSeparator(), numberOfBanAdvocatesRow, widget.NewSeparator(), buttonsGrid)

	setPageContent(page, window)
}

func setViewAllReviewsCreatedByModeratorPage(window fyne.Window, moderatorIdentityHash [16]byte, reviewType string, viewIndex int64, previousPage func()){

	if (reviewType != "Identity" && reviewType != "Profile" && reviewType != "Attribute" && reviewType != "Message"){
		setErrorEncounteredPage(window, errors.New("setViewAllReviewsCreatedByModeratorPage called with invalid reviewType: " + reviewType), previousPage)
		return
	}

	userIdentityType, err := identity.GetIdentityTypeFromIdentityHash(moderatorIdentityHash)
	if (err != nil) {
		moderatorIdentityHashHex := encoding.EncodeBytesToHexString(moderatorIdentityHash[:])
		setErrorEncounteredPage(window, errors.New("setViewAllReviewsCreatedByModeratorPage called with invalid identity hash: " + moderatorIdentityHashHex), previousPage)
		return
	}
	if (userIdentityType != "Moderator"){
		setErrorEncounteredPage(window, errors.New("setViewAllReviewsCreatedByModeratorPage called with non-moderator identity."), previousPage)
		return
	}

	currentPage := func(){setViewAllReviewsCreatedByModeratorPage(window, moderatorIdentityHash, reviewType, viewIndex, previousPage)}

	title := getPageTitleCentered("Viewing Moderator Reviews")

	backButton := getBackButtonCentered(previousPage)

	reviewTypeTitle := getBoldLabelCentered("Review Type:")

	reviewTypesList := []string{"Identity", "Profile", "Attribute", "Message"}
	
	handleSelectFunction := func(newReviewType string){

		setViewAllReviewsCreatedByModeratorPage(window, moderatorIdentityHash, newReviewType, 0, previousPage)
	}

	reviewTypeSelector := widget.NewSelect(reviewTypesList, handleSelectFunction)
	reviewTypeSelector.Selected = reviewType

	reviewTypeSelectorCentered := getWidgetCentered(reviewTypeSelector)

	description := getLabelCentered("These are the " + reviewType + " reviews created by the moderator.")

	getReviewsGrid := func()(*fyne.Container, error){

		appNetworkType, err := getAppNetworkType.GetAppNetworkType()
		if (err != nil) { return nil, err }

		reviewedHashLabel := getItalicLabelCentered("Reviewed Hash")
		verdictLabel := getItalicLabelCentered("Verdict")
		reasonLabel := getItalicLabelCentered("Reason")
		emptyLabelA := widget.NewLabel("")
		
		reviewedHashColumn := container.NewVBox(reviewedHashLabel, widget.NewSeparator())
		verdictColumn := container.NewVBox(verdictLabel, widget.NewSeparator())
		reasonColumn := container.NewVBox(reasonLabel, widget.NewSeparator())
		viewReviewButtonsColumn := container.NewVBox(emptyLabelA, widget.NewSeparator())

		reviewsList, err := reviewStorage.GetAllNewestReviewsCreatedByModerator(moderatorIdentityHash, reviewType, appNetworkType)
		if (err != nil) { return nil, err }

		if (len(reviewsList) == 0){
			noReviewsExistLabel := getBoldLabelCentered("No " + reviewType + " reviews exist.")
			return noReviewsExistLabel, nil
		}

		//TODO: Add sort reviews alphabetically and navigation buttons

		for _, reviewBytes := range reviewsList{

			ableToRead, reviewHash, _, reviewNetworkType, reviewerIdentityHash, _, currentReviewType, reviewedHash, reviewVerdict, reviewMap, err := readReviews.ReadReviewAndHash(false, reviewBytes)
			if (err != nil) { return nil, err }
			if (ableToRead == false){
				return nil, errors.New("GetAllNewestReviewsCreatedByModerator returning invalid review.")
			}
			if (reviewNetworkType != appNetworkType){
				return nil, errors.New("GetAllNewestReviewsCreatedByModerator returning review belonging to different networkType.")
			}
			if (reviewerIdentityHash != moderatorIdentityHash) {
				return nil, errors.New("GetAllNewestReviewsCreatedByModerator returning review by different moderator.")
			}
			if (currentReviewType != reviewType){
				return nil, errors.New("GetAllNewestReviewsCreatedByModerator returning review of different reviewType")
			}

			reviewedHashString, err := helpers.EncodeReviewedHashBytesToString(reviewedHash)
			if (err != nil) { return nil, err }

			reviewedHashTrimmed, _, err := helpers.TrimAndFlattenString(reviewedHashString, 10)
			if (err != nil) { return nil, err }

			reviewedHashLabel := getBoldLabelCentered(reviewedHashTrimmed)

			verdictLabel := getBoldLabelCentered(reviewVerdict)

			getViewReasonButtonOrLabel := func()*fyne.Container{
				reasonString, exists := reviewMap["Reason"]
				if (exists == false) {
					noneLabel := getBoldLabelCentered("None")
					return noneLabel
				}
				viewReasonButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
						setViewTextPage(window, "Viewing Verdict Reason", reasonString, false, currentPage)
					}))

				return viewReasonButton
			}
			viewReasonButtonOrLabel := getViewReasonButtonOrLabel()

			viewReviewButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewReviewDetailsPage(window, reviewHash, currentPage)
				})

			reviewedHashColumn.Add(reviewedHashLabel)
			verdictColumn.Add(verdictLabel)
			reasonColumn.Add(viewReasonButtonOrLabel)
			viewReviewButtonsColumn.Add(viewReviewButton)

			reviewedHashColumn.Add(widget.NewSeparator())
			verdictColumn.Add(widget.NewSeparator())
			reasonColumn.Add(widget.NewSeparator())
			viewReviewButtonsColumn.Add(widget.NewSeparator())
		}

		reviewsGrid := container.NewHBox(layout.NewSpacer(), reviewedHashColumn, verdictColumn, reasonColumn, viewReviewButtonsColumn, layout.NewSpacer())

		return reviewsGrid, nil
	}

	reviewsGrid, err := getReviewsGrid()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), reviewTypeTitle, reviewTypeSelectorCentered, widget.NewSeparator(), description, widget.NewSeparator(), reviewsGrid)

	setPageContent(page, window)
}


func setViewReviewDetailsPage(window fyne.Window, reviewHash [29]byte, previousPage func()){

	currentPage := func(){setViewReviewDetailsPage(window, reviewHash, previousPage)}

	title := getPageTitleCentered("Viewing Review Details")

	backButton := getBackButtonCentered(previousPage)

	exists, reviewBytes, err := badgerDatabase.GetReview(reviewHash)
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (exists == false){

		description := getBoldLabelCentered("Review not found.")

		//TODO: Download review

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description)
		setPageContent(page, window)
		return
	}

	getPageContent := func()(*fyne.Container, error){

		ableToRead, currentReviewHash, _, reviewNetworkType, reviewerIdentityHash, reviewCreationTime, reviewType, reviewedHash, reviewVerdict, reviewMap, err := readReviews.ReadReviewAndHash(false, reviewBytes)
		if (err != nil) { return nil, err }
		if (ableToRead == false){
			return nil, errors.New("Database corrupt: Contains invalid review.")
		}
		if (reviewHash != currentReviewHash) {
			return nil, errors.New("Database corrupt: Review entry key does not match review hash")
		}

		reviewNetworkTypeLabel := widget.NewLabel("Review Network Type:")

		reviewNetworkTypeString := helpers.ConvertByteToString(reviewNetworkType)

		reviewNetworkTypeText := getBoldLabel(reviewNetworkTypeString)

		reviewNetworkTypeRow := container.NewHBox(layout.NewSpacer(), reviewNetworkTypeLabel, reviewNetworkTypeText, layout.NewSpacer())

		reviewAuthorLabel := widget.NewLabel("Review Author:")

		reviewerIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(reviewerIdentityHash)
		if (err != nil) { return nil, err }

		reviewerIdentityHashTrimmed, _, err := helpers.TrimAndFlattenString(reviewerIdentityHashString, 15)
		if (err != nil) { return nil, err }

		viewReviewAuthorButton := widget.NewButtonWithIcon(reviewerIdentityHashTrimmed, theme.VisibilityIcon(), func(){
				setViewModeratorDetailsPage(window, reviewerIdentityHash, currentPage)
			})

		reviewerIdentityRow := container.NewHBox(layout.NewSpacer(), reviewAuthorLabel, viewReviewAuthorButton, layout.NewSpacer())

		reviewHashTitle := widget.NewLabel("Review Hash:")

		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])

		reviewHashTrimmed, _, err := helpers.TrimAndFlattenString(reviewHashHex, 10)
		if (err != nil) { return nil, err }
		reviewHashLabel := getBoldLabel(reviewHashTrimmed)
		viewReviewHashButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
				setViewContentHashPage(window, "Review", reviewHash[:], currentPage)
			})
		reviewHashRow := container.NewHBox(layout.NewSpacer(), reviewHashTitle, reviewHashLabel, viewReviewHashButton, layout.NewSpacer())

		reviewTypeTitle := widget.NewLabel("Review Type:")
		reviewTypeLabel := getBoldLabel(reviewType)
		reviewTypeRow := container.NewHBox(layout.NewSpacer(), reviewTypeTitle, reviewTypeLabel, layout.NewSpacer())

		reviewedHashTitle := widget.NewLabel("Reviewed Hash:")

		reviewedHashString, err := helpers.EncodeReviewedHashBytesToString(reviewedHash)
		if (err != nil) { return nil, err }

		reviewedHashTrimmed, _, err := helpers.TrimAndFlattenString(reviewedHashString, 10)
		if (err != nil) { return nil, err }
		reviewedHashLabel := getBoldLabel(reviewedHashTrimmed)
		viewReviewedHashButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){

				if (reviewType == "Identity"){

					if (len(reviewedHash) != 16){
						setErrorEncounteredPage(window, errors.New("ReadReview returning invalid reviewedHash length."), previousPage)
						return
					}

					reviewedIdentityHash := [16]byte(reviewedHash)

					setViewIdentityHashPage(window, reviewedIdentityHash, currentPage)
				} else {
					setViewContentHashPage(window, reviewType, reviewedHash, currentPage)
				}
			})
		reviewedHashRow := container.NewHBox(layout.NewSpacer(), reviewedHashTitle, reviewedHashLabel, viewReviewedHashButton, layout.NewSpacer())

		creationTimeTranslated, err := helpers.ConvertUnixTimeToTimeFromNowTranslated(reviewCreationTime, true)
		if (err != nil ){ return nil, err }

		creationTimeTitle := widget.NewLabel("Creation Time:")
		creationTimeLabel := getBoldLabel(creationTimeTranslated)
		creationTimeWarningButton := widget.NewButtonWithIcon("", theme.WarningIcon(), func(){
				title := translate("Creation Time Warning")
				dialogMessage := "Creation times are not verified. They can be faked by the reviewer."
				dialogContent := container.NewVBox(widget.NewLabel(dialogMessage))
				dialog.ShowCustom(title, translate("Close"), dialogContent, window)
			})
		creationTimeRow := container.NewHBox(layout.NewSpacer(), creationTimeTitle, creationTimeLabel, creationTimeWarningButton, layout.NewSpacer())

		verdictTitle := widget.NewLabel("Verdict:")
		verdictLabel := getBoldLabel(reviewVerdict)
		verdictRow := container.NewHBox(layout.NewSpacer(), verdictTitle, verdictLabel, layout.NewSpacer())

		getReviewReasonRow := func()(*fyne.Container, error){

			reasonTitle := widget.NewLabel("Reason:")

			reasonString, exists := reviewMap["Reason"]
			if (exists == false){
				noneLabel := getBoldItalicLabel(translate("None"))
				reasonRow := container.NewHBox(layout.NewSpacer(), reasonTitle, noneLabel, layout.NewSpacer())

				return reasonRow, nil
			}

			reasonTrimmed, _, err := helpers.TrimAndFlattenString(reasonString, 20)
			if (err != nil) { return nil, err }
			reasonLabel := getBoldLabel(reasonTrimmed)
			viewReasonButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
					setViewTextPage(window, "Viewing Review Reason", reasonString, false, currentPage)
				})

			reasonRow := container.NewHBox(layout.NewSpacer(), reasonTitle, reasonLabel, viewReasonButton, layout.NewSpacer())

			return reasonRow, nil
		}

		reasonRow, err := getReviewReasonRow()
		if (err != nil) { return nil, err }

		pageContent := container.NewVBox(reviewNetworkTypeRow, widget.NewSeparator(), reviewerIdentityRow, widget.NewSeparator(), reviewHashRow, widget.NewSeparator(), reviewTypeRow, widget.NewSeparator(), reviewedHashRow, widget.NewSeparator(), creationTimeRow, widget.NewSeparator(), verdictRow, widget.NewSeparator(), reasonRow, widget.NewSeparator())

		if (reviewType == "Identity"){
			_, exists := reviewMap["ErrantMessages"]
			if (exists == true){

				errantMessagesTitle := widget.NewLabel("Errant Messages:")
				errantMessagesRow := container.NewHBox(layout.NewSpacer(), errantMessagesTitle, layout.NewSpacer())

				pageContent.Add(errantMessagesRow)
				//TODO: Add errant Profiles and reviews, and add buttons to view each.
			}
		}

		if (reviewType != "Identity"){

			viewReviewedContentOrIdentityButton := getWidgetCentered(widget.NewButtonWithIcon("View Reviewed " + reviewType, theme.VisibilityIcon(), func(){
				if (reviewType == "Message"){

					if (len(reviewedHash) != 26){
						reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
						setErrorEncounteredPage(window, errors.New("ReadReview returning invalid length reviewedHash for Message review: " + reviewedHashHex), currentPage)
						return
					}

					reviewedMessageHash := [26]byte(reviewedHash)

					setViewMessageForModerationPage(window, reviewedMessageHash, currentPage)
				} else if (reviewType == "Profile"){

					if (len(reviewedHash) != 28){
						reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
						setErrorEncounteredPage(window, errors.New("ReadReview returning invalid length reviewedHash for Profile review: " + reviewedHashHex), currentPage)
						return
					}

					reviewedProfileHash := [28]byte(reviewedHash)

					setViewProfileForModerationPage(window, reviewedProfileHash, currentPage)
				} else if (reviewType == "Attribute"){

					if (len(reviewedHash) != 27){
						reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
						setErrorEncounteredPage(window, errors.New("ReadReview returning invalid length reviewedHash for Attribute review: " + reviewedHashHex), currentPage)
						return
					}

					reviewedAttributeHash := [27]byte(reviewedHash)

					setViewProfileAttributeForModerationPage(window, reviewedAttributeHash, currentPage)
				}
			}))

			pageContent.Add(viewReviewedContentOrIdentityButton)		
		}

		viewReviewedHashModerationDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("View Reviewed " + reviewType + " Details", theme.VisibilityIcon(), func(){

				if (reviewType == "Identity"){

					if (len(reviewedHash) != 16){
						reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
						setErrorEncounteredPage(window, errors.New("ReadReview returning invalid length reviewedHash for Identity review: " + reviewedHashHex), currentPage)
						return
					}

					reviewedIdentityHash := [16]byte(reviewedHash)

					setViewIdentityModerationDetailsPage(window, reviewedIdentityHash, currentPage)

				} else if (reviewType == "Profile"){

					if (len(reviewedHash) != 28){
						reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
						setErrorEncounteredPage(window, errors.New("ReadReview returning invalid length reviewedHash for Profile review: " + reviewedHashHex), currentPage)
						return
					}

					reviewedProfileHash := [28]byte(reviewedHash)

					setViewProfileModerationDetailsPage(window, reviewedProfileHash, currentPage)

				} else if (reviewType == "Attribute"){

					if (len(reviewedHash) != 27){
						reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
						setErrorEncounteredPage(window, errors.New("ReadReview returning invalid length reviewedHash for Attribute review: " + reviewedHashHex), currentPage)
						return
					}

					reviewedAttributeHash := [27]byte(reviewedHash)

					setViewAttributeModerationDetailsPage(window, reviewedAttributeHash, currentPage)

				} else if (reviewType == "Message"){

					if (len(reviewedHash) != 26){
						reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
						setErrorEncounteredPage(window, errors.New("ReadReview returning invalid length reviewedHash for Message review: " + reviewedHashHex), currentPage)
						return
					}

					reviewedMessageHash := [26]byte(reviewedHash)

					setViewMessageModerationDetailsPage(window, reviewedMessageHash, currentPage)
				} 
			}))

		pageContent.Add(viewReviewedHashModerationDetailsButton)

		//TODO: Add anything else?

		return pageContent, nil
	}

	pageContent, err := getPageContent()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), pageContent)

	setPageContent(page, window)
}


func setModeratorSettingsPage(window fyne.Window, previousPage func()){

	currentPage := func(){setModeratorSettingsPage(window, previousPage)}

	title := getPageTitleCentered("Moderator Settings")

	backButton := getBackButtonCentered(previousPage)

	description := getLabelCentered("Manage your moderation settings.")

	getSettingsGrid := func()(*fyne.Container, error){

		settingTitleColumn := container.NewVBox()
		settingStatusColumn := container.NewVBox()
		manageSettingButtonsColumn := container.NewVBox()

		addSettingRow := func(addSeparator bool, settingTitle string, settingName string, defaultIsOn bool, manageSettingPage func())error{

			settingTitleLabel := getBoldLabelCentered(settingTitle)

			getSettingOnOffStatus := func()(string, error){

				exists, settingOnOffStatus, err := mySettings.GetSetting(settingName)
				if (err != nil){ return "", err }
				if (exists == false){
					if (defaultIsOn == true){
						return "On", nil
					}
					return "Off", nil
				}
				return settingOnOffStatus, nil
			}

			settingStatus, err := getSettingOnOffStatus()
			if (err != nil) { return err }

			settingStatusLabel := getBoldLabelCentered(settingStatus)

			manageSettingButton := widget.NewButtonWithIcon("Manage", theme.SettingsIcon(), manageSettingPage)

			settingTitleColumn.Add(settingTitleLabel)
			settingStatusColumn.Add(settingStatusLabel)
			manageSettingButtonsColumn.Add(manageSettingButton)

			if (addSeparator == true){

				settingTitleColumn.Add(widget.NewSeparator())
				settingStatusColumn.Add(widget.NewSeparator())
				manageSettingButtonsColumn.Add(widget.NewSeparator())
			}

			return nil
		}

		err := addSettingRow(true, "Moderator Mode", "ModeratorModeOnOffStatus", false, func(){
				setManageModeratorModePage(window, currentPage)
			})
		if (err != nil) { return nil, err }

		err = addSettingRow(true, "Moderate Messages", "ModerateMessagesOnOffStatus", false, func(){
				setManageModerateMessagesModePage(window, currentPage)
			})
		if (err != nil) { return nil, err }

		err = addSettingRow(true, "Moderate Mate Identities", "ModerateMateContentOnOffStatus", true, func(){
				//TODO
				showUnderConstructionDialog(window)
			})
		if (err != nil) { return nil, err }

		err = addSettingRow(true, "Moderate Host Identities", "ModerateHostContentOnOffStatus", true, func(){
				//TODO
				showUnderConstructionDialog(window)
			})
		if (err != nil) { return nil, err }

		err = addSettingRow(true, "Moderate Moderator Identities", "ModerateModeratorContentOnOffStatus", true, func(){
				//TODO
				showUnderConstructionDialog(window)
			})
		if (err != nil) { return nil, err }

		settingsGrid := container.NewHBox(layout.NewSpacer(), settingTitleColumn, settingStatusColumn, manageSettingButtonsColumn, layout.NewSpacer())

		return settingsGrid, nil
	}

	settingsGrid, err := getSettingsGrid()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	
	page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), settingsGrid)
	
	setPageContent(page, window)
}


func setManageModeratorModePage(window fyne.Window, previousPage func()){
	
	currentPage := func(){setManageModeratorModePage(window, previousPage)}

	title := getPageTitleCentered("Manage Moderator Mode")

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Moderator Mode")

	description1 := getLabelCentered("Activate this mode if you want to be a moderator.")
	description2 := getLabelCentered("Seekia will start downloading content for you to moderate.")

	getModeratorModeStatus := func()(string, error){
		exists, moderatorModeStatus, err := mySettings.GetSetting("ModeratorModeOnOffStatus")
		if (err != nil){ return "", err }
		if (exists == false){
			return "Off", nil
		}
		if (moderatorModeStatus != "On" && moderatorModeStatus != "Off"){
			return "", errors.New("MySettings malformed: Invalid moderator mode status: " + moderatorModeStatus)
		}
		return moderatorModeStatus, nil
	}

	moderatorModeStatus, err := getModeratorModeStatus()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	moderatorModeStatusLabel := getLabelCentered("Moderator Mode Status:")
	moderatorModeStatusText := getBoldLabelCentered(moderatorModeStatus)	

	getEnableDisableText := func()string{
		if (moderatorModeStatus == "On"){
			return "Disable"
		}
		return "Enable"
	}

	enableDisableText := getEnableDisableText()

	enableDisableButton := getWidgetCentered(widget.NewButton(enableDisableText, func(){

			if (moderatorModeStatus == "On"){
				err := mySettings.SetSetting("ModeratorModeOnOffStatus", "Off")
				if (err != nil){
					setErrorEncounteredPage(window, err, currentPage)
					return
				}
				currentPage()
				return
			}
			setConfirmEnableModeratorModePage(window, currentPage, currentPage)
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, widget.NewSeparator(), moderatorModeStatusLabel, moderatorModeStatusText, enableDisableButton)

	setPageContent(page, window)
}

func setConfirmEnableModeratorModePage(window fyne.Window, previousPage func(), nextPage func()){

	title := getPageTitleCentered("Enable Moderator Mode")

	backButton := getBackButtonCentered(previousPage)

	description1 := getBoldLabelCentered("Enable Moderator Mode?")

	description2 := getLabelCentered("This mode will download unapproved content for you to moderate.")
	description3 := getLabelCentered("This content may contain illegal and unruleful content.")
	description4 := getLabelCentered("You must accept the legal risks of downloading this content.")

	enableButton := getWidgetCentered(widget.NewButtonWithIcon("Enable", theme.ConfirmIcon(), func(){
			err := mySettings.SetSetting("ModeratorModeOnOffStatus", "On")
			if (err != nil){
				setErrorEncounteredPage(window, err, previousPage)
				return
			}
			nextPage()
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, enableButton)

	setPageContent(page, window)
}



func setManageModerateMessagesModePage(window fyne.Window, previousPage func()){
	
	currentPage := func(){setManageModeratorModePage(window, previousPage)}

	title := getPageTitleCentered("Manage Moderate Messages Mode")

	backButton := getBackButtonCentered(previousPage)

	subtitle := getPageSubtitleCentered("Moderate Messages Mode")

	description1 := getLabelCentered("Activate this mode if you want to moderate messages.")
	description2 := getLabelCentered("Seekia will start downloading messages for you to moderate.")
	description3 := getLabelCentered("You will be reviewing messages which have been reported by users.")
	description4 := getLabelCentered("Be aware that these messages are more likely to contain unruleful content.")

	getModerateMessagesModeStatus := func()(string, error){
		exists, moderateMessagesModeStatus, err := mySettings.GetSetting("ModerateMessagesOnOffStatus")
		if (err != nil){ return "", err }
		if (exists == false){
			return "Off", nil
		}
		if (moderateMessagesModeStatus != "On" && moderateMessagesModeStatus != "Off"){
			return "", errors.New("MySettings malformed: Invalid moderate messages mode status: " + moderateMessagesModeStatus)
		}
		return moderateMessagesModeStatus, nil
	}

	moderateMessagesModeStatus, err := getModerateMessagesModeStatus()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	moderateMessagesModeStatusLabel := getLabelCentered("Moderate Messages Mode Status:")
	moderateMessagesModeStatusText := getBoldLabelCentered(moderateMessagesModeStatus)	

	getEnableDisableText := func()string{
		if (moderateMessagesModeStatus == "On"){
			return "Disable"
		}
		return "Enable"
	}

	enableDisableText := getEnableDisableText()

	enableDisableButton := getWidgetCentered(widget.NewButton(enableDisableText, func(){

			if (moderateMessagesModeStatus == "On"){
				err := mySettings.SetSetting("ModerateMessagesOnOffStatus", "Off")
				if (err != nil){
					setErrorEncounteredPage(window, err, currentPage)
					return
				}
				currentPage()
				return
			}
			setConfirmEnableModerateMessagesModePage(window, currentPage, currentPage)
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, description4, widget.NewSeparator(), moderateMessagesModeStatusLabel, moderateMessagesModeStatusText, enableDisableButton)

	setPageContent(page, window)
}

func setConfirmEnableModerateMessagesModePage(window fyne.Window, previousPage func(), nextPage func()){

	title := getPageTitleCentered("Enable Moderate Messages Mode")

	backButton := getBackButtonCentered(previousPage)

	description1 := getBoldLabelCentered("Enable Moderate Messages Mode?")

	description2 := getLabelCentered("This mode will download reported messages for you to moderate.")
	description3 := getLabelCentered("These are likely to contain illegal and unruleful content.")
	description4 := getLabelCentered("You must accept the legal risks of downloading this content.")

	enableButton := getWidgetCentered(widget.NewButtonWithIcon("Enable", theme.ConfirmIcon(), func(){
			err := mySettings.SetSetting("ModerateMessagesOnOffStatus", "On")
			if (err != nil){
				setErrorEncounteredPage(window, err, previousPage)
				return
			}
			nextPage()
		}))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, enableButton)

	setPageContent(page, window)
}


func setBuildMyModeratorProfilePage(window fyne.Window, previousPage func()){

	currentPage := func(){setBuildMyModeratorProfilePage(window, previousPage)}

	title := getPageTitleCentered("Build Moderator Profile")

	backButton := getBackButtonCentered(previousPage)

	description1 := getLabelCentered("Build your moderator profile.")
	description2 := getLabelCentered("All information is optional.")

	usernameButton := widget.NewButton("Username", func(){
			setBuildProfilePage_Username(window, "Moderator", currentPage)
		})

	avatarButton := widget.NewButton("Avatar", func(){
			setBuildProfilePage_Avatar(window, "Moderator", currentPage)
		})

	descriptionButton := widget.NewButton("Description", func(){
			setBuildProfilePage_Description(window, "Moderator", currentPage)
		})

	profileLanguageButton := widget.NewButton(translate("Profile Language"), func(){
			setBuildProfilePage_ProfileLanguage(window, "Moderator", currentPage)
		})

	languageButton := widget.NewButton("Language", func(){
			//TODO
			// The format of this attribute will be different than the one for mates
			// It will not include a fluency field. It will simply contain a list of understood languages
			showUnderConstructionDialog(window)
		})

	buttonsColumn := getContainerCentered(container.NewGridWithColumns(1, usernameButton, avatarButton, descriptionButton, profileLanguageButton, languageButton))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, widget.NewSeparator(), buttonsColumn)

	setPageContent(page, window)
}


func setViewMyReviewsPage(window fyne.Window, reviewType string, previousPage func()){

	if (reviewType != "Identity" && reviewType != "Profile" && reviewType != "Attribute" && reviewType != "Message"){
		setErrorEncounteredPage(window, errors.New("setViewMyReviewsPage called with invalid reviewType: " + reviewType), previousPage)
		return
	}

	setLoadingScreen(window, "View My Reviews", "Loading my reviews...")

	currentPage := func(){setViewMyReviewsPage(window, reviewType, previousPage)}

	title := getPageTitleCentered("My Reviews")

	backButton := getBackButtonCentered(previousPage)

	myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash("Moderator")
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (myIdentityExists == false){
		description1 := getBoldLabelCentered("Your moderator identity does not exist.")
		description2 := getLabelCentered("Thus, you have no moderator reviews.")
		description3 := getLabelCentered("Create your identity?")

		createIdentityButton := getWidgetCentered(widget.NewButtonWithIcon("Create Identity", theme.NavigateNextIcon(), func(){
				setChooseNewIdentityHashPage(window, "Moderator", currentPage, currentPage)
			}))

		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, createIdentityButton)

		setPageContent(page, window)
		return
	}

	//TODO: Add info about moderator's number of used reviews out of total that have been funded

	reviewTypeLabel := getBoldLabelCentered("Review Type:")

	allReviewTypesList := []string{"Identity", "Profile", "Attribute", "Message"}

	handleSelectFunction := func(newReviewType string){
			setViewMyReviewsPage(window, newReviewType, previousPage)
		}

	reviewTypeSelector := widget.NewSelect(allReviewTypesList, handleSelectFunction)
	reviewTypeSelector.Selected = reviewType

	reviewTypeSelectorCentered := getWidgetCentered(reviewTypeSelector)

	description := getLabelCentered("Below are your " + reviewType + " reviews.")

	getResultsContainer := func()(*fyne.Container, error){

		appNetworkType, err := getAppNetworkType.GetAppNetworkType()
		if (err != nil) { return nil, err }

		myIdentityExists, myReviewsList, err := myReviews.GetMyNewestReviewsListSorted(reviewType, appNetworkType)
		if (err != nil) { return nil, err }
		if (myIdentityExists == false){
			return nil, errors.New("My identity not found after being found already.")
		}

		totalNumberOfReviews := len(myReviewsList)

		if (totalNumberOfReviews == 0){

			reviewTypeLowercase := strings.ToLower(reviewType)
			noReviewsExistText := getBoldLabelCentered("No " + reviewTypeLowercase + " reviews exist.")

			return noReviewsExistText, nil
		}

		getFinalPageIndex := func()int{
			if (totalNumberOfReviews <= 5){
				return 0
			}

			maximumIndex := totalNumberOfReviews -5

			return maximumIndex
		}

		finalPageIndex := getFinalPageIndex()

		getViewIndex := func()(int, error){

			if (totalNumberOfReviews <= 5){
				return 0, nil
			}

			exists, currentViewIndex := appMemory.GetMemoryEntry("MyReviewsPageViewIndex_" + reviewType)
			if (exists == false){
				return 0, nil
			}

			currentViewIndexInt, err := helpers.ConvertStringToInt(currentViewIndex)
			if (err != nil) { return 0, err }

			maximumIndex := totalNumberOfReviews - 1

			if (currentViewIndexInt < 0){ 
				// Index is out of range, restart at 0
				appMemory.SetMemoryEntry("MyReviewsPageViewIndex_" + reviewType, "0")

				return 0, nil
			}
			if (currentViewIndexInt > maximumIndex){
				finalPageIndexString := helpers.ConvertIntToString(finalPageIndex)
				appMemory.SetMemoryEntry("MyReviewsPageViewIndex_" + reviewType, finalPageIndexString)

				return finalPageIndex, nil
			}

			return currentViewIndexInt, nil
		}

		viewIndex, err := getViewIndex()
		if (err != nil) { return nil, err }

		getNavigateToBeginningButton := func()fyne.Widget{

			if (totalNumberOfReviews <= 5 || viewIndex == 0){

				emptyButton := widget.NewButton("  ", nil)
				return emptyButton
			}

			goToBeginningButton := widget.NewButtonWithIcon("", theme.MediaSkipPreviousIcon(), func(){
					appMemory.SetMemoryEntry("MyReviewsPageViewIndex_" + reviewType, "0")
					currentPage()
				})

			return goToBeginningButton
		}

		getNavigateToEndButton := func()fyne.Widget{

			emptyButton := widget.NewButton("  ", nil)

			if (totalNumberOfReviews <= 5 || viewIndex >= finalPageIndex){
				return emptyButton
			}

			goToEndButton := widget.NewButtonWithIcon("", theme.MediaSkipNextIcon(), func(){

					finalPageIndexString := helpers.ConvertIntToString(finalPageIndex)

					appMemory.SetMemoryEntry("MyReviewsPageViewIndex_" + reviewType, finalPageIndexString)

					currentPage()
				})

			return goToEndButton
		}

		getNavigateLeftButton := func()fyne.Widget{

			if (totalNumberOfReviews <= 5 || viewIndex == 0){

				emptyButton := widget.NewButton("  ", nil)
				return emptyButton
			}

			button := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func(){

					newIndex := helpers.ConvertIntToString(viewIndex-5)

					appMemory.SetMemoryEntry("MyReviewsPageViewIndex_" + reviewType, newIndex)

					currentPage()
				})

			return button
		}

		getNavigateRightButton := func()fyne.Widget{

			emptyButton := widget.NewButton("  ", nil)			

			if (totalNumberOfReviews <= 5 || viewIndex >= finalPageIndex){
				
				return emptyButton
			}

			button := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func(){

					newIndex := helpers.ConvertIntToString(viewIndex+5)

					appMemory.SetMemoryEntry("MyReviewsPageViewIndex_" + reviewType, newIndex)

					currentPage()
				})

			return button
		}

		getViewingReviewsLabelWithNavRow := func()*fyne.Container{

			if (totalNumberOfReviews == 1){
				viewingReviewText := getBoldLabelCentered("Viewing 1 Review")

				return viewingReviewText
			}

			totalNumberOfReviewsString := helpers.ConvertIntToString(totalNumberOfReviews)

			if (totalNumberOfReviews <= 5){

				viewingReviewsText := getBoldLabelCentered("Viewing " + totalNumberOfReviewsString + " Reviews")

				return viewingReviewsText			
			}

			// We need to show navigation buttons

			viewingReviewsText := getBoldLabel("Viewing " + totalNumberOfReviewsString + " Reviews")

			navigateToBeginningButton := getNavigateToBeginningButton()
			navigateToEndButton := getNavigateToEndButton()

			navigateLeftButton := getNavigateLeftButton()
			navigateRightButton := getNavigateRightButton()

			navigationButtonsWithNumShowingRow := container.NewHBox(layout.NewSpacer(), navigateToBeginningButton, navigateLeftButton, viewingReviewsText, navigateRightButton, navigateToEndButton, layout.NewSpacer())

			return navigationButtonsWithNumShowingRow
		}

		viewingReviewsLabelWithNavRow := getViewingReviewsLabelWithNavRow()

		reviewedHashLabel := getItalicLabelCentered("Reviewed Hash")		
		creationTimeLabel := getItalicLabelCentered("Creation Time")
		verdictLabel := getItalicLabelCentered("Verdict")
		reasonLabel := getItalicLabelCentered("Reason")
		emptyLabel := widget.NewLabel("")	

		reviewedHashColumn := container.NewVBox(reviewedHashLabel, widget.NewSeparator())
		creationTimeColumn := container.NewVBox(creationTimeLabel, widget.NewSeparator())
		verdictColumn := container.NewVBox(verdictLabel, widget.NewSeparator())
		reasonColumn := container.NewVBox(reasonLabel, widget.NewSeparator())
		viewReviewButtonsColumn := container.NewVBox(emptyLabel, widget.NewSeparator())

		viewIndexOnwardsReviewsList := myReviewsList[viewIndex:]

		for index, reviewBytes := range viewIndexOnwardsReviewsList{

			ableToRead, reviewHash, _, reviewNetworkType, reviewerIdentityHash, reviewCreationTime, currentReviewType, reviewedHash, reviewVerdict, reviewMap, err := readReviews.ReadReviewAndHash(false, reviewBytes)
			if (err != nil) { return nil, err }
			if (ableToRead == false){
				return nil, errors.New("GetMyNewestReviewsListSorted returning invalid review")
			}
			if (reviewNetworkType != appNetworkType){
				return nil, errors.New("GetMyNewestReviewsListSorted returning review belonging to different networkType.")
			}
			if (reviewerIdentityHash != myIdentityHash) {
				return nil, errors.New("GetMyNewestReviewsListSorted returning review for different identity hash.")
			}
			if (currentReviewType != reviewType){
				return nil, errors.New("GetMyNewestReviewsListSorted returning review of a different reviewType")
			}

			reviewedHashString, err := helpers.EncodeReviewedHashBytesToString(reviewedHash)
			if (err != nil) { return nil, err }

			reviewedHashTrimmed, _, err := helpers.TrimAndFlattenString(reviewedHashString, 10)
			if (err != nil) { return nil, err }
			reviewedHashLabel := getBoldLabel(reviewedHashTrimmed)

			creationTimeString, err := helpers.ConvertUnixTimeToTimeFromNowTranslated(reviewCreationTime, false)
			if (err != nil ){ return nil, err }

			creationTimeLabel := getBoldLabelCentered(creationTimeString)

			verdictLabel := getBoldLabelCentered(reviewVerdict)
	
			getReviewReasonCell := func()(*fyne.Container, error){

				reasonString, exists := reviewMap["Reason"]
				if (exists == false){
					noneLabel := getBoldItalicLabelCentered(translate("None"))

					return noneLabel, nil
				}

				reasonTrimmed, _, err := helpers.TrimAndFlattenString(reasonString, 20)
				if (err != nil) { return nil, err }

				reasonLabel := getBoldLabel(reasonTrimmed)

				viewReasonButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
						setViewTextPage(window, "Viewing Review Reason", reasonString, false, currentPage)
					})

				reasonCell := container.NewHBox(layout.NewSpacer(), reasonLabel, viewReasonButton, layout.NewSpacer())

				return reasonCell, nil
			}

			reasonCell, err := getReviewReasonCell()
			if (err != nil) { return nil, err }

			viewReviewButton := widget.NewButtonWithIcon("View", theme.VisibilityIcon(), func(){
					setViewReviewDetailsPage(window, reviewHash, currentPage)
				})

			reviewedHashColumn.Add(reviewedHashLabel)
			creationTimeColumn.Add(creationTimeLabel)
			verdictColumn.Add(verdictLabel)
			reasonColumn.Add(reasonCell)
			viewReviewButtonsColumn.Add(viewReviewButton)

			reviewedHashColumn.Add(widget.NewSeparator())
			creationTimeColumn.Add(widget.NewSeparator())
			verdictColumn.Add(widget.NewSeparator())
			reasonColumn.Add(widget.NewSeparator())
			viewReviewButtonsColumn.Add(widget.NewSeparator())

			if (index >= 4) {
				break
			}
		}

		reviewsGrid := container.NewHBox(layout.NewSpacer(), reviewedHashColumn, creationTimeColumn, verdictColumn, reasonColumn, viewReviewButtonsColumn, layout.NewSpacer())

		resultsContainer := container.NewVBox(viewingReviewsLabelWithNavRow, widget.NewSeparator(), reviewsGrid)

		return resultsContainer, nil
	}

	resultsContainer, err := getResultsContainer()
	if (err != nil) {
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	page := container.NewVBox(title, backButton, widget.NewSeparator(), reviewTypeLabel, reviewTypeSelectorCentered, widget.NewSeparator(), description, widget.NewSeparator(), resultsContainer)

	setPageContent(page, window)
}

func setViewMyModeratorScorePage(window fyne.Window, previousPage func()){

	currentPage := func(){setViewMyModeratorScorePage(window, previousPage)}

	title := getPageTitleCentered("My Moderator Score")

	backButton := getBackButtonCentered(previousPage)

	description1 := getLabelCentered("Moderator scores are used to resolve conflicts when moderators disagree.")
	description2 := getLabelCentered("You must have a minimum moderator score to be able to ban other moderators.")
	description3 := getLabelCentered("If you have a higher moderator score than another, you can ban them.")
	description4 := getLabelCentered("Increase your score by sending cryptocurrency.")

	identityExists, myIdentityScore, _, _, _, err := myIdentityScore.GetMyIdentityScore()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}
	if (identityExists == false){

		description5 := getBoldLabelCentered("Your Moderator identity does not exist.")
		description6 := getLabelCentered("You must create it to view your Moderator Identity score.")
	
		createIdentityButton := getWidgetCentered(widget.NewButtonWithIcon("Create Identity", theme.NavigateNextIcon(), func(){
				setChooseNewIdentityHashPage(window, "Moderator", currentPage, currentPage)
			}))
		page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, widget.NewSeparator(), description5, description6, createIdentityButton)

		setPageContent(page, window)
		return
	}

	myModeratorScoreString := helpers.ConvertFloat64ToStringRounded(myIdentityScore, 3)
	moderatorScoreTitle := getItalicLabelCentered("My Moderator Identity Score:")
	moderatorScoreLabel := getBoldLabelCentered(myModeratorScoreString)

	refreshButton := getWidgetCentered(widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), func(){
			//TODO: Add page to download status and show progress
			showUnderConstructionDialog(window)
		}))

	getMyModeratorRankText := func()(string, error){

		myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash("Moderator")
		if (err != nil) { return "", err }
		if (myIdentityExists == false){
			return "", errors.New("My moderator identity not found after being found already.")
		}

		appNetworkType, err := getAppNetworkType.GetAppNetworkType()
		if (err != nil) { return "", err }

		rankingKnown, moderatorRank, totalNumberOfModerators, err := moderatorRanking.GetModeratorRanking(myIdentityHash, appNetworkType)
		if (err != nil) { return "", err }

		if (rankingKnown == false){
			result := translate("Unknown")
			return result, nil
		}

		moderatorRankString := helpers.ConvertIntToString(moderatorRank)
		totalNumberOfModeratorsString := helpers.ConvertIntToString(totalNumberOfModerators)

		moderatorRankText := moderatorRankString + "/" + totalNumberOfModeratorsString

		return moderatorRankText, nil
	}

	myModeratorRankText, err := getMyModeratorRankText()
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return	
	}

	myModeratorRankTitle := widget.NewLabel("My Moderator Rank:")
	myModeratorRankLabel := getBoldLabel(myModeratorRankText)

	myModeratorRankRow := container.NewHBox(layout.NewSpacer(), myModeratorRankTitle, myModeratorRankLabel, layout.NewSpacer())

	getSendFundsButtonWithIcon := func(cryptocurrencyName string)(*fyne.Container, error){

		cryptocurrencyIcon, err := getFyneImageIcon(cryptocurrencyName)
		if (err != nil){ return nil, err }
		iconSize := getCustomFyneSize(0)
		cryptocurrencyIcon.SetMinSize(iconSize)

		sendFundsButton := widget.NewButton("Send " + cryptocurrencyName, func(){
				nextPage := func(){setViewMyAddressToIncreaseIdentityScorePage(window, cryptocurrencyName, 50, currentPage)}

				setCryptoIdentityScorePrivacyWarningPage(window, currentPage, nextPage)
			})

		sendFundsButtonWithIcon := container.NewGridWithColumns(1, cryptocurrencyIcon, sendFundsButton)

		return sendFundsButtonWithIcon, nil
	}

	sendFundsButtonWithIcon_Ethereum, err := getSendFundsButtonWithIcon("Ethereum")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	sendFundsButtonWithIcon_Cardano, err := getSendFundsButtonWithIcon("Cardano")
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	sendFundsButtonsRow := getContainerCentered(container.NewGridWithColumns(2, sendFundsButtonWithIcon_Ethereum, sendFundsButtonWithIcon_Cardano))

	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), moderatorScoreTitle, moderatorScoreLabel, refreshButton, widget.NewSeparator(), myModeratorRankRow, widget.NewSeparator(), sendFundsButtonsRow)

	setPageContent(page, window)
}


func setCryptoIdentityScorePrivacyWarningPage(window fyne.Window, previousPage func(), nextPage func()){

	title := getPageTitleCentered("Privacy Warning")

	backButton := getBackButtonCentered(previousPage)

	description1 := getBoldLabelCentered("Be aware that your privacy is at risk when using cryptocurrencies.")
	description2 := getLabelCentered("When you send money from your wallet, all users of Seekia will see your wallet and its balance.")
	description3 := getLabelCentered("If you have a large amount of cryptocurrency, you should not send your funds from that wallet.")
	description4 := getLabelCentered("For privacy, you should send funds directly from an exchange.")
	description5 := getLabelCentered("You can also use a tool such as a zero knowledge accumulator for stronger privacy.")

	description6 := getBoldLabelCentered("Do you understand the privacy risks?")

	iUnderstandButton := getWidgetCentered(widget.NewButtonWithIcon("I Understand", theme.ConfirmIcon(), nextPage))
	
	page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, description5, description6, iUnderstandButton)
	
	setPageContent(page, window)
}

func setViewMyAddressToIncreaseIdentityScorePage(window fyne.Window, cryptocurrencyName string, reviewsToFund int64, previousPage func()){

	if (cryptocurrencyName != "Ethereum" && cryptocurrencyName != "Cardano"){
		setErrorEncounteredPage(window, errors.New("setViewMyAddressToIncreaseIdentityScorePage called with invalid cryptocurrencyName: " + cryptocurrencyName), previousPage)
		return
	}
	
	currentPage := func(){setViewMyAddressToIncreaseIdentityScorePage(window, cryptocurrencyName, reviewsToFund, previousPage)}

	title := getPageTitleCentered("Send Moderator Funds")

	backButton := getBackButtonCentered(previousPage)	

	//identityExists, identityScoreAddress, err := myIdentityScore.GetMyIdentityScoreReceivingAddress(cryptocurrency)
	//if (err != nil) {
	//	setErrorEncounteredPage(window, err, previousPage)
	//	return
	//}
	//if (identityExists == false) { 
		// This should not occur, this page should not be reached unless identity exists.
	//	setErrorEncounteredPage(window, errors.New("Identity not found."), previousPage)
	//	return
	//}

	warning1 := getBoldLabelCentered("Privacy Warning: This address is linked to your moderator identity.")
	warning2 := getLabelCentered("All users of Seekia will be able to see all address transactions.")
	warning3 := getBoldLabelCentered("All funds will be destroyed forever.")

	cryptoAddressRow, err := getCryptocurrencyAddressLabelWithCopyAndQRButtons(window, cryptocurrencyName, "SeekiaIsNotCompleteYet", currentPage)
	if (err != nil){
		setErrorEncounteredPage(window, err, previousPage)
		return
	}

	content := container.NewVBox(title, backButton, widget.NewSeparator(), warning1, warning2, warning3, widget.NewSeparator(), cryptoAddressRow)
	
	setPageContent(content, window)
}


