seekia/gui/moderatorGui.go

4856 lines
188 KiB
Go
Raw Normal View History

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