package gui // viewContentGui.go implements a page used by moderators to browse content import "fyne.io/fyne/v2" import "fyne.io/fyne/v2/container" import "fyne.io/fyne/v2/widget" import "fyne.io/fyne/v2/layout" import "fyne.io/fyne/v2/theme" import "fyne.io/fyne/v2/data/binding" import "seekia/internal/appMemory" import "seekia/internal/encoding" import "seekia/internal/helpers" import "seekia/internal/moderation/contentControversy" import "seekia/internal/moderation/reviewStorage" import "seekia/internal/moderation/viewedContent" import "seekia/internal/mySettings" import "seekia/internal/network/appNetworkType/getAppNetworkType" import "time" import "errors" // The viewedContent page provides a way for moderators to view, filter and sort all stored content // One use of this is to sort content by controversy, to find moderators to ban func setBrowseContentPage(window fyne.Window, previousPage func()){ appMemory.SetMemoryEntry("CurrentViewedPage", "BrowseContent") checkIfPageHasChangedFunction := func()bool{ exists, currentViewedPage := appMemory.GetMemoryEntry("CurrentViewedPage") if (exists == false || currentViewedPage != "BrowseContent"){ return true } return false } //TODO: Check if moderator/host mode is enabled // If not, we cannot calculate many of the attributes // We should let the user know that somehow currentPage := func(){setBrowseContentPage(window, previousPage)} title := getPageTitleCentered("Browse Content") backButton := getBackButtonCentered(previousPage) statsIcon, err := getFyneImageIcon("Stats") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } statsButton := widget.NewButton("Stats", func(){ //TODO: A page to view statistics about all stored content showUnderConstructionDialog(window) }) statsButtonWithIcon := container.NewGridWithRows(2, statsIcon, statsButton) filtersIcon, err := getFyneImageIcon("Desires") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } filtersButton := widget.NewButton("Filters", func(){ //TODO: A page to configure filters for the viewed content // Examples: // -Only show content created between a certain time frame // -Only show content authored by certain identity types // -Only show a certain contentType (Profile/Message) showUnderConstructionDialog(window) }) filtersButtonWithIcon := container.NewGridWithRows(2, filtersIcon, filtersButton) controversyIcon, err := getFyneImageIcon("Controversy") if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } controversyButton := widget.NewButton("Controversy", func(){ //TODO: A page to tune the controversy calculation parameters showUnderConstructionDialog(window) }) controversyButtonWithIcon := container.NewGridWithRows(2, controversyIcon, controversyButton) pageButtonsRow := getContainerCentered(container.NewGridWithRows(1, controversyButtonWithIcon, filtersButtonWithIcon, statsButtonWithIcon)) currentSortByAttribute, err := viewedContent.GetViewedContentSortByAttribute() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } sortingByLabel := getBoldLabel(translate("Sorting By:")) getSortByAttributeTitle := func()string{ if (currentSortByAttribute == "BanAdvocates"){ result := translate("Ban Advocates") return result } if (currentSortByAttribute == "NumberOfReviewers"){ result := translate("Number Of Reviewers") return result } result := translate(currentSortByAttribute) return result } sortByAttributeTitle := getSortByAttributeTitle() sortByAttributeButton := widget.NewButton(sortByAttributeTitle, func(){ setSelectViewedContentSortByAttributePage(window, currentPage) }) getSortDirectionButtonWithIcon := func()(fyne.Widget, error){ currentSortDirection, err := viewedContent.GetViewedContentSortDirection() if (err != nil) { return nil, err } if (currentSortDirection == "Ascending"){ button := widget.NewButtonWithIcon(translate("Ascending"), theme.MoveUpIcon(), func(){ appMemory.SetMemoryEntry("StopBuildViewedContentYesNo", "Yes") _ = mySettings.SetSetting("ViewedContentSortDirection", "Descending") _ = mySettings.SetSetting("ViewedContentSortedStatus", "No") _ = mySettings.SetSetting("ViewedContentViewIndex", "0") currentPage() }) return button, nil } button := widget.NewButtonWithIcon(translate("Descending"), theme.MoveDownIcon(), func(){ appMemory.SetMemoryEntry("StopBuildViewedContentYesNo", "Yes") _ = mySettings.SetSetting("ViewedContentSortDirection", "Ascending") _ = mySettings.SetSetting("ViewedContentSortedStatus", "No") _ = mySettings.SetSetting("ViewedContentViewIndex", "0") currentPage() }) return button, nil } sortByDirectionButton, err := getSortDirectionButtonWithIcon() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } sortByRow := container.NewHBox(layout.NewSpacer(), sortingByLabel, sortByAttributeButton, sortByDirectionButton, layout.NewSpacer()) appNetworkType, err := getAppNetworkType.GetAppNetworkType() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } viewedContentReady, err := viewedContent.GetViewedContentIsReadyStatus(appNetworkType) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (viewedContentReady == false){ progressPercentageBinding := binding.NewFloat() sortingDetailsBinding := binding.NewString() startUpdateViewedContentAndLoadingBarFunction := func(){ err := viewedContent.StartUpdatingViewedContent(appNetworkType) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } sortingDetailsBindingSet := false var encounteredError error for{ pageHasChanged := checkIfPageHasChangedFunction() if (pageHasChanged == true){ appMemory.SetMemoryEntry("StopBuildViewedContentYesNo", "Yes") return } buildEncounteredError, errorEncounteredString, buildIsStopped, contentIsReady, currentPercentageProgress, err := viewedContent.GetViewedContentBuildStatus(appNetworkType) if (err != nil){ encounteredError = err break } if (buildEncounteredError == true){ encounteredError = errors.New(errorEncounteredString) break } if (buildIsStopped == true){ return } if (contentIsReady == true){ progressPercentageBinding.Set(1) // We wait so that the loading bar will appear complete. time.Sleep(100 * time.Millisecond) currentPage() return } progressPercentageBinding.Set(currentPercentageProgress) if (currentPercentageProgress >= .50 && sortingDetailsBindingSet == false){ numberOfContents, err := viewedContent.GetNumberOfGeneratedViewedContents() if (err != nil) { encounteredError = err break } if (numberOfContents != 0){ numberOfContentsString := helpers.ConvertIntToString(numberOfContents) sortingDetailsBinding.Set("Sorting " + numberOfContentsString + " Contents...") } sortingDetailsBindingSet = true } time.Sleep(100 * time.Millisecond) } // This is only reached if an error is encountered during build errorToShow := errors.New("Error encountered during build of viewed content: " + encounteredError.Error()) setErrorEncounteredPage(window, errorToShow, previousPage) } loadingLabel := getBoldLabelCentered("Loading Content...") loadingBar := getWidgetCentered(widget.NewProgressBarWithData(progressPercentageBinding)) loadingDetailsLabel := widget.NewLabelWithData(sortingDetailsBinding) loadingDetailsLabel.TextStyle = getFyneTextStyle_Italic() loadingDetailsLabelCentered := getWidgetCentered(loadingDetailsLabel) page := container.NewVBox(title, backButton, widget.NewSeparator(), pageButtonsRow, widget.NewSeparator(), sortByRow, widget.NewSeparator(), loadingLabel, loadingBar, loadingDetailsLabelCentered) setPageContent(page, window) go startUpdateViewedContentAndLoadingBarFunction() return } getResultsContainer := func()(*fyne.Container, error){ getRefreshResultsButtonText := func()(string, error){ needsRefresh, err := viewedContent.CheckIfViewedContentNeedsRefresh() if (err != nil) { return "", err } if (needsRefresh == false){ return "Refresh Results", nil } return "Refresh Results - Updates Available!", nil } refreshButtonText, err := getRefreshResultsButtonText() if (err != nil){ return nil, err } refreshResultsButton := getWidgetCentered(widget.NewButtonWithIcon(refreshButtonText, theme.ViewRefreshIcon(), func(){ _ = mySettings.SetSetting("ViewedContentGeneratedStatus", "No") _ = mySettings.SetSetting("ViewedContentViewIndex", "0") currentPage() })) viewedContentIsReady, currentViewedContentList, err := viewedContent.GetViewedContentList(appNetworkType) if (err != nil) { return nil, err } if (viewedContentIsReady == false){ return nil, errors.New("Viewed content is not ready after earlier check determined it was ready.") } numberOfViewedContents := len(currentViewedContentList) if (numberOfViewedContents == 0){ noContentFoundLabel := getBoldLabelCentered("No content found.") numberOfFilters, err := viewedContent.GetNumberOfActiveViewedContentFilters() if (err != nil) { return nil, err } if (numberOfFilters == 0){ noContentFoundWithRefreshButton := container.NewVBox(noContentFoundLabel, refreshResultsButton) return noContentFoundWithRefreshButton, nil } numberOfFiltersString := helpers.ConvertIntToString(numberOfFilters) getActiveFiltersText := func()string{ if (numberOfFilters == 1){ return "active filter" } return "active filters" } activeFiltersText := getActiveFiltersText() activeFiltersLabel := getLabelCentered(numberOfFiltersString + " " + activeFiltersText) noContentFoundWithFiltersInfo := container.NewVBox(noContentFoundLabel, activeFiltersLabel, refreshResultsButton) return noContentFoundWithFiltersInfo, nil } getCurrentViewIndex := func()(int, error){ exists, viewIndexString, err := mySettings.GetSetting("ViewedContentViewIndex") if (err != nil) { return 0, err } if (exists == false){ return 0, nil } viewIndex, err := helpers.ConvertStringToInt(viewIndexString) if (err != nil){ return 0, errors.New("MySettings invalid: Invalid ViewedContentViewIndex: " + viewIndexString) } if (viewIndex < 0) { return 0, nil } maximumViewIndex := numberOfViewedContents-1 if (viewIndex > maximumViewIndex){ return maximumViewIndex, nil } return viewIndex, nil } viewIndex, err := getCurrentViewIndex() if (err != nil) { return nil, err } getNavigateToBeginningButton := func()fyne.Widget{ if (numberOfViewedContents <= 5 || viewIndex == 0){ emptyButton := widget.NewButton(" ", nil) return emptyButton } goToBeginningButton := widget.NewButtonWithIcon("", theme.MediaSkipPreviousIcon(), func(){ _ = mySettings.SetSetting("ViewedContentViewIndex", "0") currentPage() }) return goToBeginningButton } getNavigateToEndButton := func()fyne.Widget{ emptyButton := widget.NewButton(" ", nil) if (numberOfViewedContents <= 5){ return emptyButton } finalPageMinimumIndex := numberOfViewedContents - 5 if (viewIndex >= finalPageMinimumIndex){ return emptyButton } goToEndButton := widget.NewButtonWithIcon("", theme.MediaSkipNextIcon(), func(){ finalPageIndexString := helpers.ConvertIntToString(finalPageMinimumIndex) _ = mySettings.SetSetting("ViewedContentViewIndex", finalPageIndexString) currentPage() }) return goToEndButton } getNavigateLeftButton := func()fyne.Widget{ if (numberOfViewedContents <= 5 || viewIndex == 0){ emptyButton := widget.NewButton(" ", nil) return emptyButton } leftButton := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func(){ newIndex := helpers.ConvertIntToString(viewIndex-5) _ = mySettings.SetSetting("ViewedContentViewIndex", newIndex) currentPage() }) return leftButton } getNavigateRightButton := func()fyne.Widget{ emptyButton := widget.NewButton(" ", nil) if (numberOfViewedContents <= 5){ return emptyButton } finalPageMinimumIndex := numberOfViewedContents - 5 if (viewIndex >= finalPageMinimumIndex){ return emptyButton } rightButton := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func(){ newIndex := helpers.ConvertIntToString(viewIndex+5) _ = mySettings.SetSetting("ViewedContentViewIndex", newIndex) currentPage() }) return rightButton } getViewedContentInfoRow := func()*fyne.Container{ numberOfViewedContentsString := helpers.ConvertIntToString(numberOfViewedContents) getResultOrResultsText := func()string{ if (numberOfViewedContents == 1){ return "Result" } return "Results" } resultOrResultsText := getResultOrResultsText() numberOfViewedContentsLabel := getBoldLabel("Viewing " + numberOfViewedContentsString + " " + resultOrResultsText) if (numberOfViewedContents <= 5){ viewedContentInfoRow := getWidgetCentered(numberOfViewedContentsLabel) return viewedContentInfoRow } navigateToBeginningButton := getNavigateToBeginningButton() navigateToEndButton := getNavigateToEndButton() navigateLeftButton := getNavigateLeftButton() navigateRightButton := getNavigateRightButton() viewedContentInfoRow := container.NewHBox(layout.NewSpacer(), navigateToBeginningButton, navigateLeftButton, numberOfViewedContentsLabel, navigateRightButton, navigateToEndButton, layout.NewSpacer()) return viewedContentInfoRow } viewedContentInfoRow := getViewedContentInfoRow() viewIndexOnwardsViewedContentList := currentViewedContentList[viewIndex:] contentResultsContainer := container.NewVBox() if (viewIndex == 0){ contentResultsContainer.Add(refreshResultsButton) contentResultsContainer.Add(widget.NewSeparator()) } emptyLabel1 := widget.NewLabel("") contentHashTitle := getItalicLabelCentered("Content Hash") contentTypeTitle := getItalicLabelCentered("Content Type") featuredAttributeTitle := getItalicLabelCentered(sortByAttributeTitle) emptyLabel2 := widget.NewLabel("") contentIndexColumn := container.NewVBox(emptyLabel1, widget.NewSeparator()) contentHashColumn := container.NewVBox(contentHashTitle, widget.NewSeparator()) contentTypeColumn := container.NewVBox(contentTypeTitle, widget.NewSeparator()) featuredAttributeColumn := container.NewVBox(featuredAttributeTitle, widget.NewSeparator()) viewContentButtonsColumn := container.NewVBox(emptyLabel2, widget.NewSeparator()) for index, contentHashString := range viewIndexOnwardsViewedContentList{ resultIndex := viewIndex + index + 1 resultIndexString := helpers.ConvertIntToString(resultIndex) resultIndexLabel := getBoldLabelCentered(resultIndexString + ".") contentHash, err := encoding.DecodeHexStringToBytes(contentHashString) if (err != nil){ return nil, errors.New("Viewed content list contains invalid contentHash: " + contentHashString) } contentType, err := helpers.GetContentTypeFromContentHash(contentHash) if (err != nil) { return nil, err } if (contentType != "Profile" && contentType != "Message"){ return nil, errors.New("Viewed content list contains invalid contentHash: " + contentHashString) } getFeaturedAttributeValue := func()(string, error){ unknownTranslated := translate("Unknown") if (currentSortByAttribute == "Controversy"){ controversyIsKnown, controversyRating, err := contentControversy.GetContentControversyRating(contentHash) if (err != nil) { return "", err } if (controversyIsKnown == false){ return unknownTranslated, nil } controversyRatingString := helpers.ConvertInt64ToString(controversyRating) return controversyRatingString, nil } if (currentSortByAttribute == "NumberOfReviewers"){ if (contentType == "Profile"){ if (len(contentHash) != 28){ return "", errors.New("GetContentTypeFromContentHash returning Profile for different length content hash.") } profileHash := [28]byte(contentHash) profileMetadataIsKnown, downloadingRequiredData, numberOfReviewers, err := reviewStorage.GetNumberOfProfileReviewers(profileHash) if (err != nil) { return "", err } if (profileMetadataIsKnown == false || downloadingRequiredData == false){ return unknownTranslated, nil } numberOfReviewersString := helpers.ConvertIntToString(numberOfReviewers) return numberOfReviewersString, nil } // contentType == "Message" if (len(contentHash) != 26){ return "", errors.New("GetContentTypeFromContentHash returning Message for different length content hash.") } messageHash := [26]byte(contentHash) messageMetadataIsKnown, downloadingRequiredData, numberOfReviewers, err := reviewStorage.GetNumberOfMessageReviewers(messageHash) if (err != nil) { return "", err } if (messageMetadataIsKnown == false || downloadingRequiredData == false){ return unknownTranslated, nil } numberOfReviewersString := helpers.ConvertIntToString(numberOfReviewers) return numberOfReviewersString, nil } return "", errors.New("Unknown featured attribute: " + currentSortByAttribute) } featuredAttributeValue, err := getFeaturedAttributeValue() if (err != nil) { return nil, err } featuredAttributeLabel := getBoldLabelCentered(featuredAttributeValue) contentTypeLabel := getBoldLabelCentered(translate(contentType)) contentHashTrimmed, _, err := helpers.TrimAndFlattenString(contentHashString, 7) if (err != nil) { return nil, err } contentHashLabel := getBoldLabelCentered(contentHashTrimmed) viewContentButton := widget.NewButtonWithIcon("View Details", theme.VisibilityIcon(), func(){ if (contentType == "Message"){ if (len(contentHash) != 26){ setErrorEncounteredPage(window, errors.New("GetContentTypeFromContentHash returning Message for different length content hash."), currentPage) return } messageHash := [26]byte(contentHash) setViewMessageModerationDetailsPage(window, messageHash, currentPage) } else if (contentType == "Profile"){ if (len(contentHash) != 28){ setErrorEncounteredPage(window, errors.New("GetContentTypeFromContentHash returning Profile for different length content hash."), currentPage) return } profileHash := [28]byte(contentHash) setViewProfileModerationDetailsPage(window, profileHash, currentPage) } }) contentIndexColumn.Add(resultIndexLabel) contentHashColumn.Add(contentHashLabel) contentTypeColumn.Add(contentTypeLabel) featuredAttributeColumn.Add(featuredAttributeLabel) viewContentButtonsColumn.Add(viewContentButton) contentIndexColumn.Add(widget.NewSeparator()) contentHashColumn.Add(widget.NewSeparator()) contentTypeColumn.Add(widget.NewSeparator()) featuredAttributeColumn.Add(widget.NewSeparator()) viewContentButtonsColumn.Add(widget.NewSeparator()) if (index >= 4){ break } } gridInfoRowWithSeparator := container.NewVBox(viewedContentInfoRow, widget.NewSeparator()) resultsGrid := container.NewHBox(layout.NewSpacer(), contentIndexColumn, contentHashColumn, contentTypeColumn, featuredAttributeColumn, viewContentButtonsColumn, layout.NewSpacer()) contentResultsContainer.Add(resultsGrid) viewedContentContainerScrollable := container.NewVScroll(contentResultsContainer) resultsContainer := container.NewBorder(gridInfoRowWithSeparator, nil, nil, nil, viewedContentContainerScrollable) return resultsContainer, nil } resultsContainer, err := getResultsContainer() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } header := container.NewVBox(title, backButton, widget.NewSeparator(), pageButtonsRow, widget.NewSeparator(), sortByRow, widget.NewSeparator()) page := container.NewBorder(header, nil, nil, nil, resultsContainer) setPageContent(page, window) } func setSelectViewedContentSortByAttributePage(window fyne.Window, previousPage func()){ appMemory.SetMemoryEntry("CurrentViewedPage", "ViewedContentSortBySelect") title := getPageTitleCentered("Select Sort By Attribute") backButton := getBackButtonCentered(previousPage) description := getLabelCentered("Select the attribute to sort the viewed content by.") getSelectButton := func(attributeTitle string, attributeName string, sortDirection string) fyne.Widget{ button := widget.NewButton(attributeTitle, func(){ _ = mySettings.SetSetting("ViewedContentSortedStatus", "No") _ = mySettings.SetSetting("ViewedContentSortByAttribute", attributeName) _ = mySettings.SetSetting("ViewedContentSortDirection", sortDirection) _ = mySettings.SetSetting("ViewedContentViewIndex", "0") previousPage() }) return button } //TODO: Add more attributes controversyButton := getSelectButton("Controversy", "Controversy", "Ascending") numberOfReviewersButton := getSelectButton("Number Of Reviewers", "NumberOfReviewers", "Descending") buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, controversyButton, numberOfReviewersButton)) content := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), buttonsGrid) setPageContent(content, window) }