1089 lines
38 KiB
Go
1089 lines
38 KiB
Go
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)
|
|
}
|
|
|
|
|
|
|
|
|