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.ConvertWebpBase64StringToImageObject(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){ imageBase64Webp := strings.TrimPrefix(messageCommunication, ">!>Photo=") goImage, err := imagery.ConvertWEBPBase64StringToCroppedDownsizedImageObject(imageBase64Webp) 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) }