diff --git a/src/app/profile/views/ens_manager.nim b/src/app/profile/views/ens_manager.nim index 03faf52ac5..f776937da2 100644 --- a/src/app/profile/views/ens_manager.nim +++ b/src/app/profile/views/ens_manager.nim @@ -53,7 +53,7 @@ QtObject: let userWallet = status_settings.getSetting[string](Setting.WalletRootAddress, "0x0") let pubkey = status_ens.pubkey(ens) if ownerAddr != "": - if pubkey == "": + if pubkey == "" and ownerAddr == userWallet: output = "owned" # "Continuing will connect this username with your chat key." elif pubkey == userPubkey: output = "connected" @@ -65,13 +65,24 @@ QtObject: output = "taken" output - proc connect(self: EnsManager, username: string) {.slot.} = - let ensUsername = username & status_ens.domain + proc add*(self: EnsManager, username: string) = + self.beginInsertRows(newQModelIndex(), self.usernames.len, self.usernames.len) + self.usernames.add(username) + self.endInsertRows() + + proc connect(self: EnsManager, username: string, isStatus: bool) {.slot.} = + var ensUsername = username + if isStatus: + ensUsername = ensUsername & status_ens.domain var usernames = status_settings.getSetting[seq[string]](Setting.Usernames, @[]) - let preferredUsername = status_settings.getSetting[string](Setting.PreferredUsername, "") usernames.add ensUsername discard status_settings.saveSetting(Setting.Usernames, %*usernames) - discard status_settings.saveSetting(Setting.PreferredUsername, ensUsername) + if usernames.len == 1: + discard status_settings.saveSetting(Setting.PreferredUsername, ensUsername) + self.add ensUsername + + proc preferredUsername(self: EnsManager): string {.slot.} = + result = status_settings.getSetting[string](Setting.PreferredUsername, "") method rowCount(self: EnsManager, index: QModelIndex = nil): int = return self.usernames.len @@ -88,8 +99,3 @@ QtObject: { EnsRoles.UserName.int:"username" }.toTable - - proc add*(self: EnsManager, username: string) = - self.beginInsertRows(newQModelIndex(), self.usernames.len, self.usernames.len) - self.usernames.add(username) - self.endInsertRows() diff --git a/src/status/devices.nim b/src/status/devices.nim index 9b9dd6c394..0795c27c1e 100644 --- a/src/status/devices.nim +++ b/src/status/devices.nim @@ -19,7 +19,8 @@ proc isDeviceSetup*():bool = result = false proc syncAllDevices*() = - discard syncDevices() + let preferredUsername = getSetting[string](Setting.PreferredUsername, "") + discard syncDevices(preferredUsername) proc advertise*() = discard sendPairInstallation() diff --git a/src/status/libstatus/chat.nim b/src/status/libstatus/chat.nim index dcb6004d3a..728153d9fa 100644 --- a/src/status/libstatus/chat.nim +++ b/src/status/libstatus/chat.nim @@ -1,8 +1,9 @@ -import json, times, strutils, sequtils, chronicles +import json, times, strutils, sequtils, chronicles, json_serialization import core, utils import ../chat/[chat, message] import ../../signals/messages import ./types +import ./settings proc buildFilter*(chat: Chat):JsonNode = if chat.chatType == ChatType.PrivateGroupChat: @@ -72,34 +73,38 @@ proc generateSymKeyFromPassword*(): string = ]))["result"]).strip(chars = {'"'}) proc sendChatMessage*(chatId: string, msg: string, replyTo: string, contentType: int): string = + let preferredUsername = getSetting[string](Setting.PreferredUsername, "") callPrivateRPC("sendChatMessage".prefix, %* [ { "chatId": chatId, "text": msg, "responseTo": replyTo, - "ensName": nil, + "ensName": preferredUsername, "sticker": nil, "contentType": contentType } ]) proc sendImageMessage*(chatId: string, image: string): string = + let preferredUsername = getSetting[string](Setting.PreferredUsername, "") callPrivateRPC("sendChatMessage".prefix, %* [ { "chatId": chatId, "contentType": ContentType.Image.int, "imagePath": image, + "ensName": preferredUsername, "text": "Update to latest version to see a nice image here!" } ]) proc sendStickerMessage*(chatId: string, sticker: Sticker): string = + let preferredUsername = getSetting[string](Setting.PreferredUsername, "") callPrivateRPC("sendChatMessage".prefix, %* [ { "chatId": chatId, "text": "Update to latest version to see a nice sticker here!", "responseTo": nil, - "ensName": nil, + "ensName": preferredUsername, "sticker": { "hash": sticker.hash, "pack": sticker.packId diff --git a/src/status/libstatus/installations.nim b/src/status/libstatus/installations.nim index e82b64b369..6f8159fd9f 100644 --- a/src/status/libstatus/installations.nim +++ b/src/status/libstatus/installations.nim @@ -14,9 +14,7 @@ proc getOurInstallations*(useCached: bool = true): JsonNode = dirty = false result = installations -proc syncDevices*(): string = - # These are not being used at the moment - let preferredName = "" +proc syncDevices*(preferredName: string): string = let photoPath = "" result = callPrivateRPC("syncDevices".prefix, %* [preferredName, photoPath]) diff --git a/ui/app/AppLayouts/Profile/Sections/Ens/List.qml b/ui/app/AppLayouts/Profile/Sections/Ens/List.qml index 3b21fdb660..1c523ba55e 100644 --- a/ui/app/AppLayouts/Profile/Sections/Ens/List.qml +++ b/ui/app/AppLayouts/Profile/Sections/Ens/List.qml @@ -3,10 +3,93 @@ import QtQuick.Layouts 1.3 import QtQuick.Controls 2.14 import "../../../../../imports" import "../../../../../shared" +import "../../../Chat/ChatColumn/MessageComponents" Item { property var onClick: function(){} + // Defaults to show message + property bool isMessage: true + property bool isEmoji: false + property bool isCurrentUser: false + property int contentType: 1 + property string message: qsTr("Hey") + property string authorCurrentMsg: "0" + property string authorPrevMsg: "1" + property var clickMessage: function(){} + property string identicon: profileModel.profile.identicon + property int timestamp: 1577872140 + + Component { + id: statusENS + Item { + Text { + id: usernameTxt + text: username.substr(0, username.indexOf(".")) + color: Style.current.textColor + } + + Text { + + anchors.top: usernameTxt.bottom + anchors.topMargin: 2 + text: username.substr(username.indexOf(".")) + color: Style.current.darkGrey + } + } + } + + Component { + id: normalENS + Item { + Text { + id: usernameTxt + text: username + font.pixelSize: 16 + color: Style.current.textColor + anchors.top: parent.top + anchors.topMargin: 5 + } + } + } + + Component { + id: ensDelegate + Item { + height: 45 + + Rectangle { + id: circle + width: 35 + height: 35 + radius: 35 + color: Style.current.blue + + StyledText { + text: "@" + opacity: 0.7 + font.weight: Font.Bold + font.pixelSize: 16 + color: Style.current.white + anchors.centerIn: parent + verticalAlignment: Text.AlignVCenter + anchors.verticalCenter: parent.verticalCenter + } + + Loader { + sourceComponent: model.username.endsWith(".stateofus.eth") ? statusENS : normalENS + property string username: model.username + active: true + anchors.left: circle.right + anchors.leftMargin: Style.current.smallPadding + } + } + + + + } + } + StyledText { id: sectionTitle //% "ENS usernames" @@ -51,27 +134,136 @@ Item { } } - ScrollView { - id: sview - clip: true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - contentHeight: contentItem.childrenRect.height + + StyledText { + id: usernamesLabel + text: qsTr("Your usernames") + anchors.left: parent.left anchors.top: addUsername.bottom - anchors.topMargin: Style.current.padding - anchors.bottom: parent.bottom + anchors.topMargin: 24 + font.pixelSize: 16 + } + + Item { + anchors.top: usernamesLabel.bottom + anchors.topMargin: 10 anchors.left: parent.left anchors.right: parent.right + height: 200 + id: ensList - Item { - id: contentItem - anchors.right: parent.right; - anchors.left: parent.left; + ScrollView { + anchors.fill: parent + Layout.fillWidth: true + Layout.fillHeight: true - StyledText { - id: title - text: "TODO: Show ENS username list" - anchors.top: parent.top + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: lvEns.contentHeight > lvEns.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff + + ListView { + id: lvEns + anchors.fill: parent + model: profileModel.ens + spacing: 10 + clip: true + delegate: ensDelegate } } } + + Separator { + id: separator + anchors.topMargin: Style.current.padding + anchors.top: ensList.bottom + } + + StyledText { + id: chatSettingsLabel + visible: profileModel.ens.rowCount() > 1 + text: qsTr("Chat Settings") + anchors.left: parent.left + anchors.top: ensList.bottom + anchors.topMargin: 24 + font.pixelSize: 16 + } + + StyledText { + id: usernameLabel + visible: chatSettingsLabel.visible + text: qsTr("Primary Username") + anchors.left: parent.left + anchors.top: chatSettingsLabel.bottom + anchors.topMargin: 24 + font.pixelSize: 14 + font.weight: Font.Bold + } + + StyledText { + id: usernameLabel2 + visible: chatSettingsLabel.visible + text: profileModel.ens.preferredUsername() + anchors.left: usernameLabel.right + anchors.leftMargin: Style.current.padding + anchors.top: chatSettingsLabel.bottom + anchors.topMargin: 24 + font.pixelSize: 14 + } + + Item { + anchors.top: profileModel.ens.rowCount() == 1 ? separator.bottom : usernameLabel.bottom + anchors.topMargin: Style.current.padding * 2 + + UserImage { + id: chatImage + anchors.left: parent.left + anchors.leftMargin: Style.current.padding + anchors.top: parent.top + anchors.topMargin: 20 + } + + UsernameLabel { + id: chatName + text: profileModel.ens.preferredUsername() + anchors.leftMargin: 20 + anchors.top: parent.top + anchors.topMargin: 0 + anchors.left: chatImage.right + } + + Rectangle { + property int chatVerticalPadding: 7 + property int chatHorizontalPadding: 12 + id: chatBox + color: Style.current.secondaryBackground + height: 35 + width: 80 + radius: 16 + anchors.left: chatImage.right + anchors.leftMargin: 8 + anchors.top: chatImage.top + + ChatText { + id: chatText + anchors.top: parent.top + anchors.topMargin: chatBox.chatVerticalPadding + anchors.left: parent.left + anchors.leftMargin: chatBox.chatHorizontalPadding + horizontalAlignment: Text.AlignLeft + color: Style.current.textColor + } + + RectangleCorner {} + } + + ChatTime { + id: chatTime + anchors.top: chatBox.bottom + anchors.topMargin: 4 + anchors.bottomMargin: Style.current.padding + anchors.right: chatBox.right + anchors.rightMargin: Style.current.padding + } + } + + } \ No newline at end of file diff --git a/ui/app/AppLayouts/Profile/Sections/Ens/Search.qml b/ui/app/AppLayouts/Profile/Sections/Ens/Search.qml index 89d4aa5976..8d5e9c60b2 100644 --- a/ui/app/AppLayouts/Profile/Sections/Ens/Search.qml +++ b/ui/app/AppLayouts/Profile/Sections/Ens/Search.qml @@ -12,31 +12,33 @@ Item { property string validationMessage: "" property bool valid: false property bool isStatus: true + property bool loading: false property string ensStatus: "" property var validateENS: Backpressure.debounce(searchENS, 500, function (ensName, isStatus){ profileModel.ens.validate(ensName, isStatus) }); - function validate() { + function validate(ensUsername) { validationMessage = ""; valid = false; ensStatus = ""; - if (ensUsername.text.length < 4) { + if (ensUsername.length < 4) { validationMessage = qsTr("At least 4 characters. Latin letters, numbers, and lowercase only."); - } else if(isStatus && !ensUsername.text.match(/^[a-z0-9]+$/)){ + } else if(isStatus && !ensUsername.match(/^[a-z0-9]+$/)){ validationMessage = qsTr("Letters and numbers only."); - } else if(!isStatus && !ensUsername.text.endsWith(".eth")){ + } else if(!isStatus && !ensUsername.endsWith(".eth")){ validationMessage = qsTr("Type the entire username including the custom domain like username.domain.eth") } return validationMessage === ""; } - function onKeyReleased(){ - if (!validate()) { + function onKeyReleased(ensUsername){ + if (!validate(ensUsername)) { return; } - Qt.callLater(validateENS, ensUsername.text, isStatus) + loading = true; + Qt.callLater(validateENS, ensUsername, isStatus) } StyledText { @@ -60,8 +62,26 @@ Item { radius: 120 color: Style.current.blue + SVGImage { + id: imgIcon + visible: ensStatus === "taken" + fillMode: Image.PreserveAspectFit + source: "../../../../img/block-icon-white.svg" + width: 20 + height: 20 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + StyledText { - text: "@" + visible: ensStatus !== "taken" + text: { + if((ensStatus === "available" || ensStatus === "connected" || ensStatus === "connected-different-key")){ + return "✓" + } else { + return "@" + } + } opacity: 0.7 font.weight: Font.Bold font.pixelSize: 18 @@ -79,13 +99,15 @@ Item { anchors.right: btnContinue.left anchors.rightMargin: 24 Keys.onReleased: { - onKeyReleased(); + onKeyReleased(ensUsername.text); } Connections { target: profileModel.ens onEnsWasResolved: { + if(!validate(ensUsername.text)) return; valid = false; + loading = false; ensStatus = ensResult; switch(ensResult){ case "available": @@ -139,7 +161,7 @@ Item { if(!valid) return; if(ensStatus === "connected"){ - profileModel.ens.connect(ensUsername.text); + profileModel.ens.connect(ensUsername.text, isStatus); onClick(ensStatus, ensUsername.text); return; } @@ -188,8 +210,9 @@ Item { anchors.fill: parent onClicked : { isStatus = !isStatus; - if(validate()) - validateENS(ensUsername.text, isStatus) + let ensUser = ensUsername.text; + if(validate(ensUser)) + validateENS(ensUser, isStatus) } } } diff --git a/ui/app/img/block-icon-white.svg b/ui/app/img/block-icon-white.svg new file mode 100644 index 0000000000..b827b8ce2c --- /dev/null +++ b/ui/app/img/block-icon-white.svg @@ -0,0 +1,3 @@ + + +