package gui // contactsGui.go implements pages to view and manage a user's contacts import "fyne.io/fyne/v2" import "fyne.io/fyne/v2/container" import "fyne.io/fyne/v2/widget" import "fyne.io/fyne/v2/theme" import "fyne.io/fyne/v2/dialog" import "fyne.io/fyne/v2/data/binding" import "fyne.io/fyne/v2/layout" import "fyne.io/fyne/v2/canvas" import "seekia/internal/appMemory" import "seekia/internal/encoding" import "seekia/internal/helpers" import "seekia/internal/identity" import "seekia/internal/imagery" import "seekia/internal/myContacts" import "seekia/internal/myIdentity" import "seekia/internal/mySettings" import "seekia/internal/network/appNetworkType/getAppNetworkType" import "seekia/internal/profiles/viewableProfiles" import "image" import "strings" import "slices" import "errors" func setMyContactsPage(window fyne.Window, identityType string, previousPage func()){ appMemory.SetMemoryEntry("CurrentViewedPage", "MyContacts") if (identityType != "Mate" && identityType != "Moderator" && identityType != "Host"){ setErrorEncounteredPage(window, errors.New("setMyContactsPage called with invalid identityType: " + identityType), previousPage) return } currentPage := func(){ setMyContactsPage(window, identityType, previousPage) } title := getPageTitleCentered("My " + identityType + " Contacts") backButton := getBackButtonCentered(previousPage) addContactButton := getWidgetCentered(widget.NewButtonWithIcon("Add Contact", theme.ContentAddIcon(), func(){ setAddContactPage(window, currentPage, currentPage) })) createACategoryButton := widget.NewButtonWithIcon("Create A Category", theme.ContentAddIcon(), func(){ setCreateAContactCategoryPage(window, identityType, currentPage, currentPage) }) deleteACategoryButton := widget.NewButtonWithIcon("Delete A Category", theme.DeleteIcon(), func(){ setDeleteACategoryPage(window, identityType, currentPage, currentPage) }) buttonsRow := container.NewHBox(layout.NewSpacer(), addContactButton, createACategoryButton, deleteACategoryButton, layout.NewSpacer()) //TODO: Add change identity type ability? myContactsMapList, err := myContacts.GetMyContactsMapList(identityType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (len(myContactsMapList) == 0){ noContactsExistLabel := getBoldLabelCentered("No " + identityType + " contacts exist.") page := container.NewVBox(title, backButton, widget.NewSeparator(), addContactButton, widget.NewSeparator(), noContactsExistLabel) setPageContent(page, window) return } allContactCategoriesList, err := myContacts.GetAllMyContactCategories(identityType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } helpers.SortStringListToUnicodeOrder(allContactCategoriesList) allCategoryOptionsList := []string{"All Contacts"} allCategoryOptionsList = append(allCategoryOptionsList, allContactCategoriesList...) allCategoryOptionsList = append(allCategoryOptionsList, "No Category") getCurrentCategory := func()(string, error){ exists, currentCategory, err := mySettings.GetSetting("My" + identityType + "ContactsPageViewedCategory") if (err != nil) { return "", err } if (exists == false){ return "All Contacts", nil } isACategory := slices.Contains(allCategoryOptionsList, currentCategory) if (isACategory == false){ // Category must have been deleted return "All Contacts", nil } return currentCategory, nil } currentCategory, err := getCurrentCategory() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } categoryLabel := getBoldLabelCentered("Category:") categorySelector := widget.NewSelect(allCategoryOptionsList, func(newCategory string){ err := mySettings.SetSetting("My" + identityType + "ContactsPageViewedCategory", newCategory) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } currentPage() }) categorySelector.Selected = currentCategory selectCategoryRow := container.NewHBox(layout.NewSpacer(), categoryLabel, categorySelector, layout.NewSpacer()) getContactsContainer := func()(*fyne.Container, error){ appNetworkType, err := getAppNetworkType.GetAppNetworkType() if (err != nil) { return nil, err } anyContactsFound := false contactsContainer := container.NewVBox() for _, contactMap := range myContactsMapList{ checkIfContactBelongsToCurrentCategory := func()(bool, error){ if (currentCategory == "All Contacts"){ return true, nil } contactCategoriesBase64ListString, exists := contactMap["Categories"] if (exists == false){ if (currentCategory == "No Category"){ return true, nil } return false, nil } contactCategoriesBase64List := strings.Split(contactCategoriesBase64ListString, "+") for _, categoryNameBase64 := range contactCategoriesBase64List{ categoryNameString, err := encoding.DecodeBase64StringToUnicodeString(categoryNameBase64) if (err != nil) { return false, errors.New("Malformed MyContacts map list: Category name not Base64: " + categoryNameBase64) } if (categoryNameString == currentCategory){ return true, nil } } return false, nil } contactBelongsToCurrentCategory, err := checkIfContactBelongsToCurrentCategory() if (err != nil) { return nil, err } if (contactBelongsToCurrentCategory == false){ continue } anyContactsFound = true contactIdentityHashString, exists := contactMap["IdentityHash"] if (exists == false) { return nil, errors.New("Contact map malformed: Missing IdentityHash") } contactIdentityHash, _, err := identity.ReadIdentityHashString(contactIdentityHashString) if (err != nil){ return nil, errors.New("Contact map malformed: contains invalid IdentityHash: " + contactIdentityHashString) } contactName, exists := contactMap["Name"] if (exists == false) { return nil, errors.New("Contact map malformed: Missing Name") } getContactAvatarOrImage := func()(image.Image, error){ getAllowUnknownViewableStatusBool := func()bool{ if (identityType == "Mate"){ return false } return true } allowUnknownViewableStatusBool := getAllowUnknownViewableStatusBool() profileFound, _, retrieveAnyUserProfileAttributeFunction, err := viewableProfiles.GetRetrieveAnyNewestViewableUserProfileAttributeFunction(contactIdentityHash, appNetworkType, true, allowUnknownViewableStatusBool, true) if (err != nil) { return nil, err } if (profileFound == false){ emojiImageObject, err := getEmojiImageObject(2929) if (err != nil) { return nil, err } return emojiImageObject, nil } attributeExists, _, photosAttributeValue, err := retrieveAnyUserProfileAttributeFunction("Photos") if (err != nil) { return nil, err } if (attributeExists == true){ base64PhotosList := strings.Split(photosAttributeValue, "+") firstPhotoBase64 := base64PhotosList[0] userImageObject, err := imagery.ConvertWebpBase64StringToImageObject(firstPhotoBase64) if (err != nil) { return nil, errors.New("Database corrupt: Contains profile with invalid photos attribute.") } return userImageObject, nil } getContactEmojiIdentifier := func()(int, error){ attributeExists, _, avatarAttributeValue, err := retrieveAnyUserProfileAttributeFunction("Avatar") if (err != nil) { return 0, err } if (attributeExists == false){ return 2929, nil } userEmojiIdentifier, err := helpers.ConvertStringToInt(avatarAttributeValue) if (err != nil) { return 0, errors.New("Database corrupt: Contains profile with invalid Avatar attribute: " + avatarAttributeValue) } return userEmojiIdentifier, nil } contactEmojiIdentifier, err := getContactEmojiIdentifier() if (err != nil) { return nil, err } emojiImageObject, err := getEmojiImageObject(contactEmojiIdentifier) if (err != nil) { return nil, err } return emojiImageObject, nil } contactImageObject, err := getContactAvatarOrImage() if (err != nil) { return nil, err } contactFyneImage := canvas.NewImageFromImage(contactImageObject) contactFyneImage.FillMode = canvas.ImageFillContain imageSize := getCustomFyneSize(10) contactFyneImage.SetMinSize(imageSize) imageBoxed := getFyneImageBoxed(contactFyneImage) trimmedIdentityHash, _, err := helpers.TrimAndFlattenString(contactIdentityHashString, 20) if (err != nil) { return nil, err } contactIdentityHashLabel := widget.NewLabel(trimmedIdentityHash) contactNameLabel := getBoldLabel(contactName) contactNameIdentityHashColumn := getContainerBoxed(container.NewVBox(contactNameLabel, contactIdentityHashLabel)) viewProfileButton := widget.NewButtonWithIcon("Profile", theme.VisibilityIcon(), func(){ setViewPeerProfilePageFromIdentityHash(window, contactIdentityHash, currentPage) }) chatButton := widget.NewButtonWithIcon("Chat", theme.MailComposeIcon(), func(){ myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(identityType) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } if (myIdentityExists == true && contactIdentityHash == myIdentityHash){ dialogTitle := translate("Identity Hash Is Self.") dialogMessageA := getLabelCentered("This contact is your own identity.") dialogMessageB := getLabelCentered("You cannot chat with yourself.") dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } setViewAConversationPage(window, contactIdentityHash, true, currentPage) }) manageButton := widget.NewButtonWithIcon("Manage", theme.DocumentCreateIcon(), func(){ setManageContactPage(window, contactIdentityHash, currentPage) }) row := container.NewHBox(imageBoxed, contactNameIdentityHashColumn, viewProfileButton, chatButton, manageButton) rowCentered := getContainerCentered(row) contactRow := getContainerBoxed(rowCentered) contactsContainer.Add(contactRow) } if (anyContactsFound == false){ description1 := getBoldLabelCentered("No contacts belong to this category.") categoryNameLabel := getLabelCentered("Category Name:") currentCategoryLabel := getBoldLabelCentered(currentCategory) categoryNameRow := container.NewHBox(layout.NewSpacer(), categoryNameLabel, currentCategoryLabel, layout.NewSpacer()) noContactsExistContainer := container.NewVBox(description1, categoryNameRow) return noContactsExistContainer, nil } return contactsContainer, nil } contactsContainer, err := getContactsContainer() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), buttonsRow, widget.NewSeparator(), selectCategoryRow, widget.NewSeparator(), contactsContainer) setPageContent(page, window) } func setAddContactPage(window fyne.Window, previousPage func(), nextPage func()){ currentPage := func(){setAddContactPage(window, previousPage, nextPage)} title := getPageTitleCentered("Add Contact") backButton := getBackButtonCentered(previousPage) description := getLabelCentered("Enter the identity hash of your new contact below.") identityHashEntry := widget.NewEntry() identityHashEntry.SetPlaceHolder("Enter Identity Hash.") identityHashEntryBoxed := getWidgetBoxed(identityHashEntry) identityHashEntryWithDescriptionGrid := getContainerCentered(container.NewGridWithColumns(1, description, identityHashEntryBoxed)) nextButton := getWidgetCentered(widget.NewButtonWithIcon("Next", theme.NavigateNextIcon(), func(){ userIdentityHashString := identityHashEntry.Text userIdentityHash, userIdentityType, err := identity.ReadIdentityHashString(userIdentityHashString) if (err != nil){ dialogTitle := translate("Invalid Identity Hash") dialogMessageA := getLabelCentered("The identity hash you entered is invalid.") dialogContent := container.NewVBox(dialogMessageA) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(userIdentityType) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } if (myIdentityExists == true && myIdentityHash == userIdentityHash){ dialogTitle := translate("Identity Hash Is Self") dialogMessageA := getLabelCentered("The identity hash you entered is your own.") dialogMessageB := getLabelCentered("You cannot add yourself as a contact.") dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } setAddContactFromIdentityHashPage(window, userIdentityHash, currentPage, nextPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), identityHashEntryWithDescriptionGrid, nextButton) setPageContent(page, window) } func setAddContactFromIdentityHashPage(window fyne.Window, userIdentityHash [16]byte, previousPage func(), nextPage func()){ userIdentityType, err := identity.GetIdentityTypeFromIdentityHash(userIdentityHash) if (err != nil) { userIdentityHashHex := encoding.EncodeBytesToHexString(userIdentityHash[:]) setErrorEncounteredPage(window, errors.New("setAddContactFromIdentityHashPage called with invalid identity hash: " + userIdentityHashHex), previousPage) return } currentPage := func(){setAddContactFromIdentityHashPage(window, userIdentityHash, previousPage, nextPage)} title := getPageTitleCentered("Add Contact") backButton := getBackButtonCentered(previousPage) isMyContact, err := myContacts.CheckIfUserIsMyContact(userIdentityHash) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (isMyContact == true){ description1 := getBoldLabelCentered("Cannot add contact.") description2 := getLabelCentered("User is already a contact.") page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2) setPageContent(page, window) return } enterNameLabel := getBoldLabelCentered("Enter contact name:") nameEntry := widget.NewEntry() nameEntry.SetPlaceHolder("Enter contact name...") nameEntryBoxed := getWidgetBoxed(nameEntry) wideText := " " nameEntryWidenerA := widget.NewLabel(wideText) nameEntryWidenerB := widget.NewLabel(wideText) enterNameEntryWidened := getContainerCentered(container.NewGridWithColumns(3, nameEntryWidenerA, nameEntryBoxed, nameEntryWidenerB)) enterDescriptionLabel := getBoldLabelCentered("Enter contact description:") descriptionEntry := widget.NewMultiLineEntry() descriptionEntry.Wrapping = 3 descriptionEntry.SetPlaceHolder("Enter description....") descriptionEntryBoxed := getWidgetBoxed(descriptionEntry) descriptionEntryWidenerA := widget.NewLabel(wideText) descriptionEntryWidenerB := widget.NewLabel(wideText) enterDescriptionEntryWidened := getContainerCentered(container.NewGridWithColumns(3, descriptionEntryWidenerA, descriptionEntryBoxed, descriptionEntryWidenerB)) page := container.NewVBox(title, backButton, widget.NewSeparator(), enterNameLabel, enterNameEntryWidened, widget.NewSeparator(), enterDescriptionLabel, enterDescriptionEntryWidened, widget.NewSeparator()) allContactCategories, err := myContacts.GetAllMyContactCategories(userIdentityType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } contactCategoriesListBinding := binding.NewStringList() if (len(allContactCategories) != 0){ selectCategoriesLabel := getBoldLabelCentered("Select categories for the contact:") getNumberOfGridColumns := func()int{ numberOfCategories := len(allContactCategories) if (numberOfCategories <= 2){ return numberOfCategories } return 3 } numberOfGridColumns := getNumberOfGridColumns() categoriesGrid := container.NewGridWithColumns(numberOfGridColumns) for index, categoryName := range allContactCategories{ categoryNameTrimmed, _, err := helpers.TrimAndFlattenString(categoryName, 7) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } onCheckedFunction := func(isChecked bool){ currentContactCategoriesList, err := contactCategoriesListBinding.Get() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (isChecked == true){ newContactCategoriesList := helpers.AddItemToStringListAndAvoidDuplicate(currentContactCategoriesList, categoryName) contactCategoriesListBinding.Set(newContactCategoriesList) } else { newContactCategoriesList, _ := helpers.DeleteAllMatchingItemsFromList(currentContactCategoriesList, categoryName) contactCategoriesListBinding.Set(newContactCategoriesList) } } categoryCheck := widget.NewCheck(categoryNameTrimmed, onCheckedFunction) categoryCheckBoxed := getWidgetBoxed(categoryCheck) categoriesGrid.Add(categoryCheckBoxed) if (index > 15){ break } } categoriesGridBoxed := getContainerCentered(getContainerBoxed(categoriesGrid)) page.Add(selectCategoriesLabel) page.Add(categoriesGridBoxed) } addContactButton := getWidgetCentered(widget.NewButtonWithIcon(translate("Add Contact"), theme.ConfirmIcon(), func(){ newContactName := nameEntry.Text if (newContactName == ""){ dialogTitle := translate("Name Is Empty") dialogMessageA := getLabelCentered(translate("You have not entered a contact name.")) dialogMessageB := getLabelCentered(translate("Enter a name and retry.")) dialogContent := container.NewVBox(dialogMessageA, dialogMessageB) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } newContactDescription := descriptionEntry.Text contactCategoriesList, err := contactCategoriesListBinding.Get() if (err != nil) { setErrorEncounteredPage(window, err, currentPage) return } alreadyExists, err := myContacts.AddContact(userIdentityHash, newContactName, contactCategoriesList, newContactDescription) if (err != nil) { setErrorEncounteredPage(window, err, currentPage) return } if (alreadyExists == true){ setErrorEncounteredPage(window, errors.New("Contact already exists after checking already."), currentPage) return } nextPage() })) page.Add(addContactButton) setPageContent(page, window) } func setManageContactPage(window fyne.Window, contactIdentityHash [16]byte, previousPage func()){ currentPage := func(){setManageContactPage(window, contactIdentityHash, previousPage)} title := getPageTitleCentered("Manage Contact") backButton := getBackButtonCentered(previousPage) contactExists, contactName, contactAddedTime, contactCategoriesList, contactDescription, err := myContacts.GetMyContactDetails(contactIdentityHash) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (contactExists == false){ setErrorEncounteredPage(window, errors.New("setManageContactPage called with missing contact."), previousPage) return } contactIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(contactIdentityHash) if (err != nil){ contactIdentityHashHex := encoding.EncodeBytesToHexString(contactIdentityHash[:]) setErrorEncounteredPage(window, errors.New("GetMyContactDetails not verifying identity hash: " + contactIdentityHashHex), previousPage) return } identityHashLabel := widget.NewLabel("Identity Hash:") identityHashText := getBoldLabel(contactIdentityHashString) viewIdentityHashButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ setViewIdentityHashPage(window, contactIdentityHash, currentPage) }) contactIdentityHashRow := container.NewHBox(layout.NewSpacer(), identityHashLabel, identityHashText, viewIdentityHashButton, layout.NewSpacer()) nameLabel := widget.NewLabel("Name:") contactNameLabel := getBoldLabel(contactName) contactNameRow := container.NewHBox(layout.NewSpacer(), nameLabel, contactNameLabel, layout.NewSpacer()) contactCategoriesLabel := getLabelCentered("Categories:") getContactCategoriesListString := func()(string, error){ if (len(contactCategoriesList) == 0){ return "None", nil } contactCategoriesListString := strings.Join(contactCategoriesList, ", ") contactCategoriesListString, _, err := helpers.TrimAndFlattenString(contactCategoriesListString, 20) if (err != nil) { return "", err } return contactCategoriesListString, nil } contactCategoriesListString, err := getContactCategoriesListString() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } contactCategoriesListLabel := getBoldLabel(contactCategoriesListString) contactCategoriesRow := container.NewHBox(layout.NewSpacer(), contactCategoriesLabel, contactCategoriesListLabel, layout.NewSpacer()) addedAgoString, err := helpers.ConvertUnixTimeToTimeAgoTranslated(contactAddedTime, false) addedTimeAgoLabel := getItalicLabelCentered("Contact added " + addedAgoString + ".") getContactDescriptionRow := func()(*fyne.Container, error){ descriptionLabel := widget.NewLabel("Description:") if (contactDescription == ""){ noneLabel := getBoldLabel("None") descriptionRow := container.NewHBox(layout.NewSpacer(), descriptionLabel, noneLabel, layout.NewSpacer()) return descriptionRow, nil } trimmedDescription, changesOccurred, err := helpers.TrimAndFlattenString(contactDescription, 20) if (err != nil) { return nil, err } if (changesOccurred == false){ contactDescriptionLabel := getBoldLabel(contactDescription) descriptionRow := container.NewHBox(layout.NewSpacer(), descriptionLabel, contactDescriptionLabel, layout.NewSpacer()) return descriptionRow, nil } contactDescriptionTextLabel := getBoldLabel(trimmedDescription) viewFullDescriptionButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ setViewTextPage(window, "Viewing Contact Description", contactDescription, false, currentPage) }) contactDescriptionRow := container.NewHBox(layout.NewSpacer(), descriptionLabel, contactDescriptionTextLabel, viewFullDescriptionButton, layout.NewSpacer()) return contactDescriptionRow, nil } contactDescriptionRow, err := getContactDescriptionRow() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } renameButton := widget.NewButtonWithIcon("Edit Name", theme.DocumentCreateIcon(), func(){ setRenameContactPage(window, contactIdentityHash, currentPage, currentPage) }) editCategoriesButton := widget.NewButtonWithIcon("Edit Categories", theme.ListIcon(), func(){ setEditContactCategoriesPage(window, contactIdentityHash, currentPage, currentPage) }) editDescriptionButton := widget.NewButtonWithIcon("Edit Description", theme.DocumentCreateIcon(), func(){ setEditContactDescriptionPage(window, contactIdentityHash, currentPage, currentPage) }) deleteButton := widget.NewButtonWithIcon("Delete", theme.DeleteIcon(), func(){ setConfirmDeleteContactPage(window, contactIdentityHash, currentPage, previousPage) }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, renameButton, editCategoriesButton, editDescriptionButton, deleteButton)) page := container.NewVBox(title, backButton, widget.NewSeparator(), contactIdentityHashRow, contactNameRow, contactCategoriesRow, contactDescriptionRow, addedTimeAgoLabel, widget.NewSeparator(), buttonsGrid) setPageContent(page, window) } func setRenameContactPage(window fyne.Window, contactIdentityHash [16]byte, previousPage func(), nextPage func()){ title := getPageTitleCentered("Rename Contact") backButton := getBackButtonCentered(previousPage) contactExists, contactName, _, currentContactCategoriesList, contactDescription, err := myContacts.GetMyContactDetails(contactIdentityHash) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (contactExists == false){ setErrorEncounteredPage(window, errors.New("Trying to rename contact that does not exist."), previousPage) return } currentNameLabelA := getLabelCentered("Current Contact Name:") currentNameLabelB := getBoldLabelCentered(contactName) enterNameLabel := getLabelCentered("Enter new name:") enterNameEntry := widget.NewEntry() enterNameEntry.SetPlaceHolder("Enter new name.") enterNameEntryBoxed := getWidgetBoxed(enterNameEntry) changeNameButton := getWidgetCentered(widget.NewButtonWithIcon("Change Name", theme.ConfirmIcon(), func(){ newName := enterNameEntry.Text if (newName == ""){ dialogTitle := translate("Name Is Empty") dialogMessageA := getLabelCentered("You must enter a new name.") dialogContent := container.NewVBox(dialogMessageA) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } err := myContacts.EditContact(contactIdentityHash, newName, currentContactCategoriesList, contactDescription) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } nextPage() })) widener := widget.NewLabel(" ") enterNameEntryWithButton := getContainerCentered(container.NewGridWithColumns(1, enterNameEntryBoxed, changeNameButton, widener)) page := container.NewVBox(title, backButton, widget.NewSeparator(), currentNameLabelA, currentNameLabelB, widget.NewSeparator(), enterNameLabel, enterNameEntryWithButton) setPageContent(page, window) } func setEditContactCategoriesPage(window fyne.Window, contactIdentityHash [16]byte, previousPage func(), nextPage func()){ currentPage := func(){setEditContactCategoriesPage(window, contactIdentityHash, previousPage, nextPage)} contactIdentityType, err := identity.GetIdentityTypeFromIdentityHash(contactIdentityHash) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } contactExists, contactName, _, currentContactCategoriesList, contactDescription, err := myContacts.GetMyContactDetails(contactIdentityHash) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (contactExists == false){ setErrorEncounteredPage(window, errors.New("Trying to rename contact that does not exist."), previousPage) return } allMyContactCategoriesList, err := myContacts.GetAllMyContactCategories(contactIdentityType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } title := getPageTitleCentered("Edit Contact Categories") backButton := getBackButtonCentered(previousPage) createACategoryButton := getWidgetCentered(widget.NewButtonWithIcon("Create A Category", theme.ContentAddIcon(), func(){ setCreateAContactCategoryPage(window, contactIdentityType, currentPage, currentPage) })) if (len(allMyContactCategoriesList) == 0){ noCategoriesExistLabel := getBoldLabelCentered("No categories exist.") page := container.NewVBox(title, backButton, widget.NewSeparator(), noCategoriesExistLabel, createACategoryButton) setPageContent(page, window) return } description1 := getLabelCentered("Select the categories the contact should be a member of.") getNumberOfColumns := func()int{ if (len(allMyContactCategoriesList) == 1){ return 1 } return 2 } numberOfColumns := getNumberOfColumns() categoryChecksGrid := container.NewGridWithColumns(numberOfColumns) // We sort the categories so they show up in the same order every time. helpers.SortStringListToUnicodeOrder(allMyContactCategoriesList) for _, categoryName := range allMyContactCategoriesList{ handleCheckFunction := func(response bool){ getNewCategoriesList := func()[]string{ if (response == true){ newCategoriesList := append(currentContactCategoriesList, categoryName) return newCategoriesList } newCategoriesList, _ := helpers.DeleteAllMatchingItemsFromList(currentContactCategoriesList, categoryName) return newCategoriesList } newCategoriesList := getNewCategoriesList() err := myContacts.EditContact(contactIdentityHash, contactName, newCategoriesList, contactDescription) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } currentPage() } categoryCheck := widget.NewCheck(categoryName, handleCheckFunction) contactIsInCategory := slices.Contains(currentContactCategoriesList, categoryName) if (contactIsInCategory == true){ categoryCheck.Checked = true } categoryCheckBoxed := getWidgetBoxed(categoryCheck) categoryChecksGrid.Add(categoryCheckBoxed) } categoryChecksGridCentered := getContainerCentered(categoryChecksGrid) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, widget.NewSeparator(), createACategoryButton, widget.NewSeparator(), categoryChecksGridCentered) setPageContent(page, window) } func setEditContactDescriptionPage(window fyne.Window, contactIdentityHash [16]byte, previousPage func(), nextPage func()){ currentPage := func(){setEditContactDescriptionPage(window, contactIdentityHash, previousPage, nextPage)} title := getPageTitleCentered("Edit Contact Description") backButton := getBackButtonCentered(previousPage) contactExists, contactName, _, contactCategoriesList, currentContactDescription, err := myContacts.GetMyContactDetails(contactIdentityHash) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (contactExists == false){ setErrorEncounteredPage(window, errors.New("Trying to rename contact that does not exist."), previousPage) return } editDescriptionDescription := getLabelCentered("Edit your contact description below.") enterDescriptionEntry := widget.NewMultiLineEntry() enterDescriptionEntry.Wrapping = 3 if (currentContactDescription == ""){ enterDescriptionEntry.SetPlaceHolder("Enter description.") } else { enterDescriptionEntry.SetText(currentContactDescription) } enterDescriptionEntryBoxed := getWidgetBoxed(enterDescriptionEntry) entryHeightenerA := container.NewVBox(widget.NewLabel(""), widget.NewLabel(""), widget.NewLabel(""), widget.NewLabel(""), widget.NewLabel("")) entryHeightenerB := container.NewVBox(widget.NewLabel(""), widget.NewLabel(""), widget.NewLabel(""), widget.NewLabel(""), widget.NewLabel("")) changeDescriptionButton := getWidgetCentered(widget.NewButtonWithIcon("Change Description", theme.ConfirmIcon(), func(){ newContactDescription := enterDescriptionEntry.Text err := myContacts.EditContact(contactIdentityHash, contactName, contactCategoriesList, newContactDescription) if (err != nil) { setErrorEncounteredPage(window, err, currentPage) return } nextPage() })) enterDescriptionEntryWithButton := container.NewBorder(nil, changeDescriptionButton, entryHeightenerA, entryHeightenerB, enterDescriptionEntryBoxed) page := container.NewVBox(title, backButton, widget.NewSeparator(), editDescriptionDescription, enterDescriptionEntryWithButton) setPageContent(page, window) } func setConfirmDeleteContactPage(window fyne.Window, contactIdentityHash [16]byte, previousPage func(), nextPage func()){ currentPage := func(){setConfirmDeleteContactPage(window, contactIdentityHash, previousPage, nextPage)} contactExists, contactName, contactAddedTime, _, contactDescription, err := myContacts.GetMyContactDetails(contactIdentityHash) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } if (contactExists == false){ setErrorEncounteredPage(window, errors.New("setConfirmDeleteContactPage called with non-contact identity hash"), previousPage) return } title := getPageTitleCentered("Confirm Delete Contact") backButton := getBackButtonCentered(previousPage) description1 := getBoldLabelCentered("Delete contact?") description2 := getLabelCentered("The user will be removed from your contacts.") contactIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(contactIdentityHash) if (err != nil){ contactIdentityHashHex := encoding.EncodeBytesToHexString(contactIdentityHash[:]) setErrorEncounteredPage(window, errors.New("setConfirmDeleteContactPage not verifying identity hash: " + contactIdentityHashHex), previousPage) } identityHashTitle := widget.NewLabel("Identity Hash:") identityHashLabel := getBoldLabel(contactIdentityHashString) identityHashRow := container.NewHBox(layout.NewSpacer(), identityHashTitle, identityHashLabel, layout.NewSpacer()) contactNameLabel := widget.NewLabel("Contact Name:") contactNameText := getBoldLabel(contactName) contactNameRow := container.NewHBox(layout.NewSpacer(), contactNameLabel, contactNameText, layout.NewSpacer()) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, widget.NewSeparator(), identityHashRow, contactNameRow) if (contactDescription != ""){ contactDescriptionLabel := widget.NewLabel("Contact Description:") contactDescriptionTrimmed, changesOccurred, err := helpers.TrimAndFlattenString(contactDescription, 20) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } contactDescriptionTextLabel := getBoldLabel(contactDescriptionTrimmed) if (changesOccurred == false){ contactDescriptionRow := container.NewHBox(layout.NewSpacer(), contactDescriptionLabel, contactDescriptionTextLabel, layout.NewSpacer()) page.Add(contactDescriptionRow) } else { viewFullDescriptionButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){ setViewTextPage(window, "Viewing Contact Description", contactDescription, false, currentPage) }) contactDescriptionRow := container.NewHBox(layout.NewSpacer(), contactDescriptionLabel, contactDescriptionTextLabel, viewFullDescriptionButton, layout.NewSpacer()) page.Add(contactDescriptionRow) } } addedAgoString, err := helpers.ConvertUnixTimeToTimeAgoTranslated(contactAddedTime, false) addedTimeAgoLabel := getItalicLabelCentered("Contact added " + addedAgoString + ".") page.Add(addedTimeAgoLabel) deleteContactButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm Delete", theme.DeleteIcon(), func(){ err := myContacts.DeleteContact(contactIdentityHash) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } nextPage() })) page.Add(deleteContactButton) setPageContent(page, window) } func setCreateAContactCategoryPage(window fyne.Window, identityType string, previousPage func(), nextPage func()){ currentPage := func(){setCreateAContactCategoryPage(window, identityType, previousPage, nextPage)} title := getPageTitleCentered("Create " + identityType + " Contact Category") backButton := getBackButtonCentered(previousPage) description1 := getLabelCentered("Create a " + identityType + " contact category.") enterNameLabel := getBoldLabelCentered("Enter category name:") descriptionsContainer := container.NewVBox(description1, enterNameLabel) enterNameEntry := widget.NewEntry() enterNameEntry.SetPlaceHolder("Enter category name.") enterNameEntryBoxed := getWidgetBoxed(enterNameEntry) enterNameEntryWithDescriptions := getContainerCentered(container.NewGridWithColumns(1, descriptionsContainer, enterNameEntryBoxed)) contactCategoriesList, err := myContacts.GetAllMyContactCategories(identityType) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } createCategoryButton := getWidgetCentered(widget.NewButtonWithIcon("Create Category", theme.ConfirmIcon(), func(){ categoryName := enterNameEntry.Text if (categoryName == ""){ dialogTitle := translate("Name Is Empty") dialogMessageA := getLabelCentered("You must enter a category name.") dialogContent := container.NewVBox(dialogMessageA) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } categoryAlreadyExists := slices.Contains(contactCategoriesList, categoryName) if (categoryAlreadyExists == true){ dialogTitle := translate("Category Already Exists") dialogMessageA := getLabelCentered("The category you are trying to create already exists.") dialogContent := container.NewVBox(dialogMessageA) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } err := myContacts.AddContactCategory(identityType, categoryName) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } nextPage() })) page := container.NewVBox(title, backButton, widget.NewSeparator(), enterNameEntryWithDescriptions, createCategoryButton) if (len(contactCategoriesList) != 0){ page.Add(widget.NewSeparator()) existingCategoriesLabel := getItalicLabelCentered("Existing Categories:") page.Add(existingCategoriesLabel) for _, categoryName := range contactCategoriesList{ categoryNameLabel := getBoldLabelCentered(categoryName) page.Add(categoryNameLabel) } } setPageContent(page, window) } func setDeleteACategoryPage(window fyne.Window, identityType string, previousPage func(), nextPage func()){ currentPage := func(){setDeleteACategoryPage(window, identityType, previousPage, nextPage)} title := getPageTitleCentered("Delete " + identityType + " Contact Category") backButton := getBackButtonCentered(previousPage) myContactCategoriesList, err := myContacts.GetAllMyContactCategories(identityType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (len(myContactCategoriesList) == 0){ description1 := getBoldLabelCentered("No " + identityType + " contact categories exist.") page := container.NewVBox(title, backButton, widget.NewSeparator(), description1) setPageContent(page, window) return } description := getLabelCentered("Select the contact category to delete.") // We sort the categories so they show up in the same order every time. helpers.SortStringListToUnicodeOrder(myContactCategoriesList) categorySelector := widget.NewSelect(myContactCategoriesList, nil) categorySelector.PlaceHolder = "Select Category..." categorySelectorCentered := getWidgetCentered(categorySelector) deleteButton := getWidgetCentered(widget.NewButtonWithIcon("Delete Category", theme.DeleteIcon(), func(){ categoryToDeleteIndex := categorySelector.SelectedIndex() if (categoryToDeleteIndex == -1){ dialogTitle := translate("No Category Selected") dialogMessageA := getLabelCentered("You must select a category to delete.") dialogContent := container.NewVBox(dialogMessageA) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } categoryToDelete := categorySelector.Selected err := myContacts.DeleteContactCategory(identityType, categoryToDelete) if (err != nil){ setErrorEncounteredPage(window, err, currentPage) return } nextPage() })) page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), categorySelectorCentered, deleteButton) setPageContent(page, window) }