feat(profile): implement contact management
This introduces the ability to: - list search existing contacts - block contacts - unblock contacts - list blocked contacts - remove contacts - search and add contacts Closes #608
This commit is contained in:
parent
9604faff08
commit
e18188514a
|
@ -1,4 +1,4 @@
|
||||||
import NimQml, json, eventemitter, strutils
|
import NimQml, json, eventemitter, strutils, sugar, sequtils
|
||||||
import json_serialization
|
import json_serialization
|
||||||
import ../../status/libstatus/mailservers as status_mailservers
|
import ../../status/libstatus/mailservers as status_mailservers
|
||||||
import ../../signals/types
|
import ../../signals/types
|
||||||
|
@ -62,6 +62,14 @@ proc init*(self: ProfileController, account: Account) =
|
||||||
let contacts = self.status.contacts.getContacts()
|
let contacts = self.status.contacts.getContacts()
|
||||||
self.view.setContactList(contacts)
|
self.view.setContactList(contacts)
|
||||||
|
|
||||||
|
self.status.events.on("contactBlocked") do(e: Args):
|
||||||
|
let contacts = self.status.contacts.getContacts()
|
||||||
|
self.view.setContactList(contacts)
|
||||||
|
|
||||||
|
self.status.events.on("contactUnblocked") do(e: Args):
|
||||||
|
let contacts = self.status.contacts.getContacts()
|
||||||
|
self.view.setContactList(contacts)
|
||||||
|
|
||||||
self.status.events.on("contactRemoved") do(e: Args):
|
self.status.events.on("contactRemoved") do(e: Args):
|
||||||
let contacts = self.status.contacts.getContacts()
|
let contacts = self.status.contacts.getContacts()
|
||||||
self.view.setContactList(contacts)
|
self.view.setContactList(contacts)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import NimQml, sequtils
|
import NimQml, sequtils, strutils, sugar
|
||||||
import views/[mailservers_list, contact_list, profile_info, device_list]
|
import views/[mailservers_list, contact_list, profile_info, device_list]
|
||||||
import ../../status/profile/[mailserver, profile, devices]
|
import ../../status/profile/[mailserver, profile, devices]
|
||||||
import ../../status/profile as status_profile
|
import ../../status/profile as status_profile
|
||||||
|
@ -6,6 +6,7 @@ import ../../status/contacts as status_contacts
|
||||||
import ../../status/accounts as status_accounts
|
import ../../status/accounts as status_accounts
|
||||||
import ../../status/status
|
import ../../status/status
|
||||||
import ../../status/devices as status_devices
|
import ../../status/devices as status_devices
|
||||||
|
import ../../status/ens as status_ens
|
||||||
import ../../status/chat/chat
|
import ../../status/chat/chat
|
||||||
import ../../status/libstatus/types
|
import ../../status/libstatus/types
|
||||||
import qrcode/qrcode
|
import qrcode/qrcode
|
||||||
|
@ -15,12 +16,15 @@ QtObject:
|
||||||
profile*: ProfileInfoView
|
profile*: ProfileInfoView
|
||||||
mailserversList*: MailServersList
|
mailserversList*: MailServersList
|
||||||
contactList*: ContactList
|
contactList*: ContactList
|
||||||
|
addedContacts*: ContactList
|
||||||
|
blockedContacts*: ContactList
|
||||||
deviceList*: DeviceList
|
deviceList*: DeviceList
|
||||||
mnemonic: string
|
mnemonic: string
|
||||||
network: string
|
network: string
|
||||||
status*: Status
|
status*: Status
|
||||||
isDeviceSetup: bool
|
isDeviceSetup: bool
|
||||||
changeLanguage*: proc(locale: string)
|
changeLanguage*: proc(locale: string)
|
||||||
|
contactToAdd*: Profile
|
||||||
|
|
||||||
proc setup(self: ProfileView) =
|
proc setup(self: ProfileView) =
|
||||||
self.QObject.setup
|
self.QObject.setup
|
||||||
|
@ -28,6 +32,8 @@ QtObject:
|
||||||
proc delete*(self: ProfileView) =
|
proc delete*(self: ProfileView) =
|
||||||
if not self.mailserversList.isNil: self.mailserversList.delete
|
if not self.mailserversList.isNil: self.mailserversList.delete
|
||||||
if not self.contactList.isNil: self.contactList.delete
|
if not self.contactList.isNil: self.contactList.delete
|
||||||
|
if not self.addedContacts.isNil: self.addedContacts.delete
|
||||||
|
if not self.blockedContacts.isNil: self.blockedContacts.delete
|
||||||
if not self.deviceList.isNil: self.deviceList.delete
|
if not self.deviceList.isNil: self.deviceList.delete
|
||||||
if not self.profile.isNil: self.profile.delete
|
if not self.profile.isNil: self.profile.delete
|
||||||
self.QObject.delete
|
self.QObject.delete
|
||||||
|
@ -38,12 +44,19 @@ QtObject:
|
||||||
result.profile = newProfileInfoView()
|
result.profile = newProfileInfoView()
|
||||||
result.mailserversList = newMailServersList()
|
result.mailserversList = newMailServersList()
|
||||||
result.contactList = newContactList()
|
result.contactList = newContactList()
|
||||||
|
result.addedContacts = newContactList()
|
||||||
|
result.blockedContacts = newContactList()
|
||||||
result.deviceList = newDeviceList()
|
result.deviceList = newDeviceList()
|
||||||
result.mnemonic = ""
|
result.mnemonic = ""
|
||||||
result.network = ""
|
result.network = ""
|
||||||
result.status = status
|
result.status = status
|
||||||
result.isDeviceSetup = false
|
result.isDeviceSetup = false
|
||||||
result.changeLanguage = changeLanguage
|
result.changeLanguage = changeLanguage
|
||||||
|
result.contactToAdd = Profile(
|
||||||
|
username: "",
|
||||||
|
alias: "",
|
||||||
|
ensName: ""
|
||||||
|
)
|
||||||
result.setup
|
result.setup
|
||||||
|
|
||||||
proc addMailServerToList*(self: ProfileView, mailserver: MailServer) =
|
proc addMailServerToList*(self: ProfileView, mailserver: MailServer) =
|
||||||
|
@ -58,6 +71,10 @@ QtObject:
|
||||||
proc updateContactList*(self: ProfileView, contacts: seq[Profile]) =
|
proc updateContactList*(self: ProfileView, contacts: seq[Profile]) =
|
||||||
for contact in contacts:
|
for contact in contacts:
|
||||||
self.contactList.updateContact(contact)
|
self.contactList.updateContact(contact)
|
||||||
|
if contact.systemTags.contains(":contact/added"):
|
||||||
|
self.addedContacts.updateContact(contact)
|
||||||
|
if contact.systemTags.contains(":contact/blocked"):
|
||||||
|
self.blockedContacts.updateContact(contact)
|
||||||
|
|
||||||
proc contactListChanged*(self: ProfileView) {.signal.}
|
proc contactListChanged*(self: ProfileView) {.signal.}
|
||||||
|
|
||||||
|
@ -66,6 +83,8 @@ QtObject:
|
||||||
|
|
||||||
proc setContactList*(self: ProfileView, contactList: seq[Profile]) =
|
proc setContactList*(self: ProfileView, contactList: seq[Profile]) =
|
||||||
self.contactList.setNewData(contactList)
|
self.contactList.setNewData(contactList)
|
||||||
|
self.addedContacts.setNewData(contactList.filter(c => c.systemTags.contains(":contact/added")))
|
||||||
|
self.blockedContacts.setNewData(contactList.filter(c => c.systemTags.contains(":contact/blocked")))
|
||||||
self.contactListChanged()
|
self.contactListChanged()
|
||||||
|
|
||||||
QtProperty[QVariant] contactList:
|
QtProperty[QVariant] contactList:
|
||||||
|
@ -73,6 +92,20 @@ QtObject:
|
||||||
write = setContactList
|
write = setContactList
|
||||||
notify = contactListChanged
|
notify = contactListChanged
|
||||||
|
|
||||||
|
proc getAddedContacts(self: ProfileView): QVariant {.slot.} =
|
||||||
|
return newQVariant(self.addedContacts)
|
||||||
|
|
||||||
|
QtProperty[QVariant] addedContacts:
|
||||||
|
read = getAddedContacts
|
||||||
|
notify = contactListChanged
|
||||||
|
|
||||||
|
proc getBlockedContacts(self: ProfileView): QVariant {.slot.} =
|
||||||
|
return newQVariant(self.blockedContacts)
|
||||||
|
|
||||||
|
QtProperty[QVariant] blockedContacts:
|
||||||
|
read = getBlockedContacts
|
||||||
|
notify = contactListChanged
|
||||||
|
|
||||||
proc mnemonicChanged*(self: ProfileView) {.signal.}
|
proc mnemonicChanged*(self: ProfileView) {.signal.}
|
||||||
|
|
||||||
proc getMnemonic*(self: ProfileView): QVariant {.slot.} =
|
proc getMnemonic*(self: ProfileView): QVariant {.slot.} =
|
||||||
|
@ -116,6 +149,27 @@ QtObject:
|
||||||
QtProperty[QVariant] profile:
|
QtProperty[QVariant] profile:
|
||||||
read = getProfile
|
read = getProfile
|
||||||
|
|
||||||
|
proc contactToAddChanged*(self: ProfileView) {.signal.}
|
||||||
|
|
||||||
|
proc getContactToAddUsername(self: ProfileView): QVariant {.slot.} =
|
||||||
|
var username = self.contactToAdd.alias;
|
||||||
|
|
||||||
|
if self.contactToAdd.ensVerified and self.contactToAdd.ensName != "":
|
||||||
|
username = self.contactToAdd.ensName
|
||||||
|
|
||||||
|
return newQVariant(username)
|
||||||
|
|
||||||
|
QtProperty[QVariant] contactToAddUsername:
|
||||||
|
read = getContactToAddUsername
|
||||||
|
notify = contactToAddChanged
|
||||||
|
|
||||||
|
proc getContactToAddPubKey(self: ProfileView): QVariant {.slot.} =
|
||||||
|
return newQVariant(self.contactToAdd.address)
|
||||||
|
|
||||||
|
QtProperty[QVariant] contactToAddPubKey:
|
||||||
|
read = getContactToAddPubKey
|
||||||
|
notify = contactToAddChanged
|
||||||
|
|
||||||
proc logout*(self: ProfileView) {.slot.} =
|
proc logout*(self: ProfileView) {.slot.} =
|
||||||
self.status.profile.logout()
|
self.status.profile.logout()
|
||||||
|
|
||||||
|
@ -174,3 +228,37 @@ QtObject:
|
||||||
status_devices.enable(installationId)
|
status_devices.enable(installationId)
|
||||||
else:
|
else:
|
||||||
status_devices.disable(installationId)
|
status_devices.disable(installationId)
|
||||||
|
|
||||||
|
proc lookupContact*(self: ProfileView, value: string) {.slot.} =
|
||||||
|
if value == "":
|
||||||
|
return
|
||||||
|
|
||||||
|
var id = value
|
||||||
|
|
||||||
|
if not id.startsWith("0x"):
|
||||||
|
id = status_ens.pubkey(id)
|
||||||
|
|
||||||
|
let contact = self.status.contacts.getContactByID(id)
|
||||||
|
|
||||||
|
if contact != nil:
|
||||||
|
self.contactToAdd = contact
|
||||||
|
else:
|
||||||
|
self.contactToAdd = Profile(
|
||||||
|
username: "",
|
||||||
|
alias: "",
|
||||||
|
ensName: "",
|
||||||
|
ensVerified: false
|
||||||
|
)
|
||||||
|
self.contactToAddChanged()
|
||||||
|
|
||||||
|
proc addContact*(self: ProfileView, pk: string) {.slot.} =
|
||||||
|
discard self.status.contacts.addContact(pk)
|
||||||
|
|
||||||
|
proc unblockContact*(self: ProfileView, id: string) {.slot.} =
|
||||||
|
discard self.status.contacts.unblockContact(id)
|
||||||
|
|
||||||
|
proc blockContact*(self: ProfileView, id: string) {.slot.} =
|
||||||
|
discard self.status.contacts.blockContact(id)
|
||||||
|
|
||||||
|
proc removeContact*(self: ProfileView, id: string) {.slot.} =
|
||||||
|
self.status.contacts.removeContact(id)
|
||||||
|
|
|
@ -10,6 +10,7 @@ type
|
||||||
Address = UserRole + 3
|
Address = UserRole + 3
|
||||||
Identicon = UserRole + 4
|
Identicon = UserRole + 4
|
||||||
IsContact = UserRole + 5
|
IsContact = UserRole + 5
|
||||||
|
IsBlocked = UserRole + 6
|
||||||
|
|
||||||
QtObject:
|
QtObject:
|
||||||
type ContactList* = ref object of QAbstractListModel
|
type ContactList* = ref object of QAbstractListModel
|
||||||
|
@ -46,6 +47,7 @@ QtObject:
|
||||||
of "identicon": result = contact.identicon
|
of "identicon": result = contact.identicon
|
||||||
of "pubKey": result = contact.id
|
of "pubKey": result = contact.id
|
||||||
of "isContact": result = $contact.isContact()
|
of "isContact": result = $contact.isContact()
|
||||||
|
of "isBlocked": result = $contact.isBlocked()
|
||||||
|
|
||||||
method data(self: ContactList, index: QModelIndex, role: int): QVariant =
|
method data(self: ContactList, index: QModelIndex, role: int): QVariant =
|
||||||
if not index.isValid:
|
if not index.isValid:
|
||||||
|
@ -59,6 +61,7 @@ QtObject:
|
||||||
of ContactRoles.Identicon: result = newQVariant(contact.identicon)
|
of ContactRoles.Identicon: result = newQVariant(contact.identicon)
|
||||||
of ContactRoles.PubKey: result = newQVariant(contact.id)
|
of ContactRoles.PubKey: result = newQVariant(contact.id)
|
||||||
of ContactRoles.IsContact: result = newQVariant(contact.isContact())
|
of ContactRoles.IsContact: result = newQVariant(contact.isContact())
|
||||||
|
of ContactRoles.IsBlocked: result = newQVariant(contact.isBlocked())
|
||||||
|
|
||||||
method roleNames(self: ContactList): Table[int, string] =
|
method roleNames(self: ContactList): Table[int, string] =
|
||||||
{
|
{
|
||||||
|
@ -66,7 +69,8 @@ QtObject:
|
||||||
ContactRoles.Address.int:"address",
|
ContactRoles.Address.int:"address",
|
||||||
ContactRoles.Identicon.int:"identicon",
|
ContactRoles.Identicon.int:"identicon",
|
||||||
ContactRoles.PubKey.int:"pubKey",
|
ContactRoles.PubKey.int:"pubKey",
|
||||||
ContactRoles.IsContact.int:"isContact"
|
ContactRoles.IsContact.int:"isContact",
|
||||||
|
ContactRoles.IsBlocked.int:"isBlocked"
|
||||||
}.toTable
|
}.toTable
|
||||||
|
|
||||||
proc addContactToList*(self: ContactList, contact: Profile) =
|
proc addContactToList*(self: ContactList, contact: Profile) =
|
||||||
|
|
|
@ -26,7 +26,14 @@ proc getContactByID*(self: ContactModel, id: string): Profile =
|
||||||
proc blockContact*(self: ContactModel, id: string): string =
|
proc blockContact*(self: ContactModel, id: string): string =
|
||||||
var contact = self.getContactByID(id)
|
var contact = self.getContactByID(id)
|
||||||
contact.systemTags.add(":contact/blocked")
|
contact.systemTags.add(":contact/blocked")
|
||||||
status_contacts.blockContact(contact)
|
discard status_contacts.blockContact(contact)
|
||||||
|
self.events.emit("contactBlocked", Args())
|
||||||
|
|
||||||
|
proc unblockContact*(self: ContactModel, id: string): string =
|
||||||
|
var contact = self.getContactByID(id)
|
||||||
|
contact.systemTags.delete(contact.systemTags.find(":contact/blocked"))
|
||||||
|
discard status_contacts.saveContact(contact.id, contact.ensVerified, contact.ensName, contact.ensVerifiedAt, contact.ensVerificationRetries, contact.alias, contact.identicon, contact.systemTags)
|
||||||
|
self.events.emit("contactUnblocked", Args())
|
||||||
|
|
||||||
proc getContacts*(self: ContactModel): seq[Profile] =
|
proc getContacts*(self: ContactModel): seq[Profile] =
|
||||||
result = map(status_contacts.getContacts().getElems(), proc(x: JsonNode): Profile = x.toProfileModel())
|
result = map(status_contacts.getContacts().getElems(), proc(x: JsonNode): Profile = x.toProfileModel())
|
||||||
|
|
|
@ -32,9 +32,9 @@ SplitView {
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.bottomMargin: 0
|
anchors.bottomMargin: 0
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 0
|
anchors.rightMargin: 113
|
||||||
anchors.left: leftTab.right
|
anchors.left: leftTab.right
|
||||||
anchors.leftMargin: 0
|
anchors.leftMargin: 113
|
||||||
currentIndex: leftTab.currentTab
|
currentIndex: leftTab.currentTab
|
||||||
|
|
||||||
// This list needs to match LeftTab/constants.js
|
// This list needs to match LeftTab/constants.js
|
||||||
|
|
|
@ -11,8 +11,11 @@ Rectangle {
|
||||||
property bool selectable: false
|
property bool selectable: false
|
||||||
property var profileClick: function() {}
|
property var profileClick: function() {}
|
||||||
property bool isContact: true
|
property bool isContact: true
|
||||||
|
property bool isBlocked: false
|
||||||
|
property string searchStr: ""
|
||||||
|
id: container
|
||||||
|
|
||||||
visible: isContact
|
visible: isContact && (searchStr == "" || name.includes(searchStr))
|
||||||
height: visible ? 64 : 0
|
height: visible ? 64 : 0
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
@ -26,6 +29,7 @@ Rectangle {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
source: identicon
|
source: identicon
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: usernameText
|
id: usernameText
|
||||||
text: name
|
text: name
|
||||||
|
@ -34,22 +38,106 @@ Rectangle {
|
||||||
anchors.rightMargin: Style.current.padding
|
anchors.rightMargin: Style.current.padding
|
||||||
font.pixelSize: 17
|
font.pixelSize: 17
|
||||||
anchors.top: accountImage.top
|
anchors.top: accountImage.top
|
||||||
|
anchors.topMargin: Style.current.smallPadding
|
||||||
anchors.left: accountImage.right
|
anchors.left: accountImage.right
|
||||||
anchors.leftMargin: Style.current.padding
|
anchors.leftMargin: Style.current.padding
|
||||||
}
|
}
|
||||||
StyledText {
|
|
||||||
id: addressText
|
Rectangle {
|
||||||
width: 108
|
property int iconSize: 14
|
||||||
font.family: Style.current.fontHexRegular.name
|
id: menuButton
|
||||||
text: address
|
height: 32
|
||||||
elide: Text.ElideMiddle
|
width: 32
|
||||||
anchors.bottom: accountImage.bottom
|
anchors.top: usernameText.top
|
||||||
anchors.bottomMargin: 0
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.left: usernameText.left
|
anchors.right: parent.right
|
||||||
anchors.leftMargin: 0
|
radius: 8
|
||||||
font.pixelSize: 15
|
|
||||||
color: Style.current.darkGrey
|
SVGImage {
|
||||||
|
source: "../../../../img/dots-icon.svg"
|
||||||
|
width: 18
|
||||||
|
height: 4
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
property bool menuOpened: false
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
onExited: {
|
||||||
|
menuButton.color = Style.current.white
|
||||||
|
}
|
||||||
|
onEntered: {
|
||||||
|
menuButton.color = Style.current.grey
|
||||||
|
}
|
||||||
|
onClicked: {
|
||||||
|
menuOpened = true
|
||||||
|
contactContextMenu.popup()
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupMenu {
|
||||||
|
id: contactContextMenu
|
||||||
|
hasArrow: false
|
||||||
|
onClosed: {
|
||||||
|
mouseArea.menuOpened = false
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
icon.source: "../../../../img/profileActive.svg"
|
||||||
|
icon.width: menuButton.iconSize
|
||||||
|
icon.height: menuButton.iconSize
|
||||||
|
text: qsTrId("view-profile")
|
||||||
|
onTriggered: profileClick(name, address, identicon)
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
icon.source: "../../../../img/message.svg"
|
||||||
|
icon.width: menuButton.iconSize
|
||||||
|
icon.height: menuButton.iconSize
|
||||||
|
text: qsTrId("send-message")
|
||||||
|
onTriggered: {
|
||||||
|
tabBar.currentIndex = 0
|
||||||
|
chatsModel.joinChat(address, Constants.chatTypeOneToOne)
|
||||||
|
}
|
||||||
|
enabled: !container.isBlocked
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
icon.source: "../../../../img/block-icon.svg"
|
||||||
|
icon.width: menuButton.iconSize
|
||||||
|
icon.height: menuButton.iconSize
|
||||||
|
text: qsTrId("block-user")
|
||||||
|
enabled: !container.isBlocked
|
||||||
|
onTriggered: {
|
||||||
|
profileModel.blockContact(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
icon.source: "../../../../img/remove-contact.svg"
|
||||||
|
icon.width: menuButton.iconSize
|
||||||
|
icon.height: menuButton.iconSize
|
||||||
|
icon.color: Style.current.red
|
||||||
|
text: qsTrId("remove-contact")
|
||||||
|
enabled: container.isContact
|
||||||
|
onTriggered: profileModel.removeContact(address)
|
||||||
|
}
|
||||||
|
Action {
|
||||||
|
icon.source: "../../../../img/block-icon.svg"
|
||||||
|
icon.width: menuButton.iconSize
|
||||||
|
icon.height: menuButton.iconSize
|
||||||
|
icon.color: Style.current.red
|
||||||
|
text: qsTrId("unblock-user")
|
||||||
|
enabled: container.isBlocked
|
||||||
|
onTriggered: {
|
||||||
|
profileModel.unblockContact(address)
|
||||||
|
contactContextMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RadioButton {
|
RadioButton {
|
||||||
visible: selectable
|
visible: selectable
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
@ -57,12 +145,4 @@ Rectangle {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
ButtonGroup.group: contactGroup
|
ButtonGroup.group: contactGroup
|
||||||
}
|
}
|
||||||
MouseArea {
|
|
||||||
enabled: !selectable
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
profileClick(name, address, identicon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ ListView {
|
||||||
id: contactList
|
id: contactList
|
||||||
property var contacts: ContactsData {}
|
property var contacts: ContactsData {}
|
||||||
property var selectable: true
|
property var selectable: true
|
||||||
|
property string searchStr: ""
|
||||||
property alias selectedContact: contactGroup.checkedButton
|
property alias selectedContact: contactGroup.checkedButton
|
||||||
property string searchString: ""
|
property string searchString: ""
|
||||||
property string lowerCaseSearchString: searchString.toLowerCase()
|
property string lowerCaseSearchString: searchString.toLowerCase()
|
||||||
|
@ -23,6 +24,7 @@ ListView {
|
||||||
address: model.address
|
address: model.address
|
||||||
identicon: model.identicon
|
identicon: model.identicon
|
||||||
isContact: model.isContact
|
isContact: model.isContact
|
||||||
|
isBlocked: model.isBlocked
|
||||||
selectable: contactList.selectable
|
selectable: contactList.selectable
|
||||||
profileClick: profilePopup.openPopup.bind(profilePopup)
|
profileClick: profilePopup.openPopup.bind(profilePopup)
|
||||||
visible: searchString === "" ||
|
visible: searchString === "" ||
|
||||||
|
@ -37,6 +39,7 @@ ListView {
|
||||||
ButtonGroup {
|
ButtonGroup {
|
||||||
id: contactGroup
|
id: contactGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
/*##^##
|
/*##^##
|
||||||
Designer {
|
Designer {
|
||||||
|
|
|
@ -9,33 +9,30 @@ import "./Contacts"
|
||||||
Item {
|
Item {
|
||||||
id: contactsContainer
|
id: contactsContainer
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
property alias searchStr: searchBox.text
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: 32
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: contentMargin
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: contentMargin
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
SearchBox {
|
SearchBox {
|
||||||
id: searchBox
|
id: searchBox
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 32
|
||||||
|
fontPixelSize: 15
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: addNewContact
|
id: addNewContact
|
||||||
anchors.top: searchBox.bottom
|
anchors.top: searchBox.bottom
|
||||||
anchors.topMargin: Style.current.bigPadding
|
anchors.topMargin: Style.current.bigPadding
|
||||||
width: parent.width
|
width: addButton.width + usernameText.width + Style.current.padding
|
||||||
height: addButton.height
|
height: addButton.height
|
||||||
|
|
||||||
AddButton {
|
AddButton {
|
||||||
id: addButton
|
id: addButton
|
||||||
clickable: false
|
clickable: false
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: usernameText
|
id: usernameText
|
||||||
text: qsTr("Add new contact")
|
text: qsTr("Add new contact")
|
||||||
|
@ -50,18 +47,122 @@ Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
// TODO implement adding a contact
|
addContactModal.open()
|
||||||
console.log('Add a contact')
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: blockedContactsButton
|
||||||
|
anchors.top: addNewContact.bottom
|
||||||
|
anchors.topMargin: Style.current.bigPadding
|
||||||
|
width: blockButton.width + blockButtonLabel.width + Style.current.padding
|
||||||
|
height: addButton.height
|
||||||
|
|
||||||
|
IconButton {
|
||||||
|
id: blockButton
|
||||||
|
clickable: false
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
iconName: "block-icon"
|
||||||
|
color: Style.current.lightBlue
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: blockButtonLabel
|
||||||
|
text: qsTr("Blocked contacts")
|
||||||
|
color: Style.current.blue
|
||||||
|
anchors.left: blockButton.right
|
||||||
|
anchors.leftMargin: Style.current.padding
|
||||||
|
anchors.verticalCenter: blockButton.verticalCenter
|
||||||
|
font.pixelSize: 15
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
blockedContactsModal.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModalPopup {
|
||||||
|
id: blockedContactsModal
|
||||||
|
title: qsTr("Blocked contacts")
|
||||||
|
|
||||||
|
ContactList {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
contacts: profileModel.blockedContacts
|
||||||
|
selectable: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModalPopup {
|
||||||
|
id: addContactModal
|
||||||
|
title: qsTr("Add contact")
|
||||||
|
|
||||||
|
Input {
|
||||||
|
id: addContactSearchInput
|
||||||
|
placeholderText: qsTrId("Enter ENS username or chat key")
|
||||||
|
customHeight: 44
|
||||||
|
fontPixelSize: 15
|
||||||
|
onEditingFinished: {
|
||||||
|
profileModel.lookupContact(inputValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: contactToAddInfo
|
||||||
|
anchors.top: addContactSearchInput.bottom
|
||||||
|
anchors.topMargin: Style.current.padding
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
height: contactUsername.height
|
||||||
|
width: contactUsername.width + contactPubKey.width
|
||||||
|
visible: profileModel.contactToAddPubKey !== ""
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: contactUsername
|
||||||
|
text: profileModel.contactToAddUsername + " • "
|
||||||
|
anchors.top: addContactSearchInput.bottom
|
||||||
|
anchors.topMargin: Style.current.padding
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: Style.current.darkGrey
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: contactPubKey
|
||||||
|
text: profileModel.contactToAddPubKey
|
||||||
|
anchors.left: contactUsername.right
|
||||||
|
width: 100
|
||||||
|
font.pixelSize: 12
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
color: Style.current.darkGrey
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
footer: StyledButton {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.leftMargin: Style.current.padding
|
||||||
|
//% "Send Message"
|
||||||
|
label: qsTr("Add contact")
|
||||||
|
disabled: !contactToAddInfo.visible
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
onClicked: {
|
||||||
|
profileModel.addContact(profileModel.contactToAddPubKey);
|
||||||
|
addContactModal.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactList {
|
ContactList {
|
||||||
id: contactListView
|
id: contactListView
|
||||||
anchors.top: addNewContact.bottom
|
anchors.top: blockedContactsButton.bottom
|
||||||
anchors.topMargin: Style.current.bigPadding
|
anchors.topMargin: Style.current.bigPadding
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
contacts: profileModel.contactList
|
contacts: profileModel.addedContacts
|
||||||
selectable: false
|
selectable: false
|
||||||
searchString: searchBox.text
|
searchString: searchBox.text
|
||||||
}
|
}
|
||||||
|
@ -96,7 +197,6 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/*##^##
|
/*##^##
|
||||||
Designer {
|
Designer {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.99967 13.6668C3.31778 13.6668 0.333008 10.6821 0.333008 7.00016C0.333008 3.31826 3.31778 0.333496 6.99967 0.333496C10.6816 0.333496 13.6663 3.31826 13.6663 7.00016C13.6663 10.6821 10.6816 13.6668 6.99967 13.6668ZM2.91208 10.3806C2.77375 10.519 2.54581 10.5094 2.42998 10.3518C1.74037 9.41312 1.33301 8.25421 1.33301 7.00016C1.33301 3.87055 3.87006 1.3335 6.99967 1.3335C8.25373 1.3335 9.41263 1.74086 10.3513 2.43047C10.509 2.5463 10.5185 2.77424 10.3802 2.91257L2.91208 10.3806ZM3.61919 11.0878C3.48086 11.2261 3.4904 11.454 3.64806 11.5699C4.58672 12.2595 5.74562 12.6668 6.99967 12.6668C10.1293 12.6668 12.6663 10.1298 12.6663 7.00016C12.6663 5.74611 12.259 4.5872 11.5694 3.64855C11.4535 3.49089 11.2256 3.48135 11.0873 3.61968L3.61919 11.0878Z" fill="#4360DF"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 922 B |
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="18" height="4" viewBox="0 0 18 4" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 2C4 3.10457 3.10457 4 2 4C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0C3.10457 0 4 0.895431 4 2Z" fill="#939BA1"/>
|
||||||
|
<path d="M11 2C11 3.10457 10.1046 4 9 4C7.89543 4 7 3.10457 7 2C7 0.895431 7.89543 0 9 0C10.1046 0 11 0.895431 11 2Z" fill="#939BA1"/>
|
||||||
|
<path d="M16 4C17.1046 4 18 3.10457 18 2C18 0.895431 17.1046 0 16 0C14.8954 0 14 0.895431 14 2C14 3.10457 14.8954 4 16 4Z" fill="#939BA1"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 509 B |
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.00033 6.66667C7.84127 6.66667 9.33366 5.17428 9.33366 3.33333C9.33366 1.49238 7.84127 0 6.00033 0C4.15938 0 2.66699 1.49238 2.66699 3.33333C2.66699 5.17428 4.15938 6.66667 6.00033 6.66667ZM6.00033 5.66667C7.28899 5.66667 8.33366 4.622 8.33366 3.33333C8.33366 2.04467 7.28899 1 6.00033 1C4.71166 1 3.66699 2.04467 3.66699 3.33333C3.66699 4.622 4.71166 5.66667 6.00033 5.66667Z" fill="#FF2D55"/>
|
||||||
|
<path d="M6.62336 9.18929C6.60016 9.46766 6.35289 9.67067 6.07357 9.66713C6.04916 9.66682 6.02471 9.66667 6.00022 9.66667C4.30462 9.66667 2.78296 10.4114 1.74449 11.5916C1.54282 11.8208 1.18548 11.8519 0.969607 11.636C0.795787 11.4622 0.774868 11.1856 0.934774 10.9989C2.15749 9.57132 3.97321 8.66667 6.00022 8.66667C6.05818 8.66667 6.11597 8.66741 6.17357 8.66888C6.44624 8.67584 6.64601 8.91747 6.62336 9.18929Z" fill="#FF2D55"/>
|
||||||
|
<path d="M8.66699 8.66667C8.39085 8.66667 8.16699 8.89052 8.16699 9.16667C8.16699 9.44281 8.39085 9.66667 8.66699 9.66667H12.0003C12.2765 9.66667 12.5003 9.44281 12.5003 9.16667C12.5003 8.89052 12.2765 8.66667 12.0003 8.66667H8.66699Z" fill="#FF2D55"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -308,6 +308,7 @@ DISTFILES += \
|
||||||
onboarding/img/wallet@3x.jpg \
|
onboarding/img/wallet@3x.jpg \
|
||||||
onboarding/qmldir \
|
onboarding/qmldir \
|
||||||
shared/AddButton.qml \
|
shared/AddButton.qml \
|
||||||
|
shared/IconButton.qml \
|
||||||
shared/Input.qml \
|
shared/Input.qml \
|
||||||
shared/ModalPopup.qml \
|
shared/ModalPopup.qml \
|
||||||
shared/NotificationWindow.qml \
|
shared/NotificationWindow.qml \
|
||||||
|
|
|
@ -3,80 +3,9 @@ import QtQuick.Controls 2.3
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import Qt.labs.platform 1.1
|
import Qt.labs.platform 1.1
|
||||||
import "../imports"
|
import "../imports"
|
||||||
|
import "./"
|
||||||
|
|
||||||
Rectangle {
|
IconButton {
|
||||||
signal clicked
|
id: iconButton
|
||||||
property int iconWidth: 14
|
iconName: "plusSign"
|
||||||
property int iconHeight: 14
|
|
||||||
property alias icon: imgIcon
|
|
||||||
property bool clickable: true
|
|
||||||
|
|
||||||
id: btnAddContainer
|
|
||||||
width: 36
|
|
||||||
height: 36
|
|
||||||
color: Style.current.blue
|
|
||||||
radius: width / 2
|
|
||||||
|
|
||||||
|
|
||||||
Image {
|
|
||||||
id: imgIcon
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
source: "../app/img/plusSign.svg"
|
|
||||||
width: iconWidth
|
|
||||||
height: iconHeight
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
state: "default"
|
|
||||||
rotation: 0
|
|
||||||
states: [
|
|
||||||
State {
|
|
||||||
name: "default"
|
|
||||||
PropertyChanges {
|
|
||||||
target: imgIcon
|
|
||||||
rotation: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "rotated"
|
|
||||||
PropertyChanges {
|
|
||||||
target: imgIcon
|
|
||||||
rotation: 45
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
transitions: [
|
|
||||||
Transition {
|
|
||||||
from: "default"
|
|
||||||
to: "rotated"
|
|
||||||
RotationAnimation {
|
|
||||||
duration: 150
|
|
||||||
direction: RotationAnimation.Clockwise
|
|
||||||
easing.type: Easing.InCubic
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Transition {
|
|
||||||
from: "rotated"
|
|
||||||
to: "default"
|
|
||||||
RotationAnimation {
|
|
||||||
duration: 150
|
|
||||||
direction: RotationAnimation.Counterclockwise
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
visible: btnAddContainer.clickable
|
|
||||||
anchors.fill: parent
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
imgIcon.state = "rotated"
|
|
||||||
btnAddContainer.clicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick.Controls 2.3
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
import Qt.labs.platform 1.1
|
||||||
|
import "../imports"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
signal clicked
|
||||||
|
property int iconWidth: 14
|
||||||
|
property int iconHeight: 14
|
||||||
|
property alias icon: imgIcon
|
||||||
|
property bool clickable: true
|
||||||
|
property string iconName: "plusSign"
|
||||||
|
|
||||||
|
id: btnAddContainer
|
||||||
|
width: 36
|
||||||
|
height: 36
|
||||||
|
color: Style.current.blue
|
||||||
|
radius: width / 2
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: imgIcon
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
source: "../app/img/" + parent.iconName + ".svg"
|
||||||
|
width: iconWidth
|
||||||
|
height: iconHeight
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
state: "default"
|
||||||
|
rotation: 0
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "default"
|
||||||
|
PropertyChanges {
|
||||||
|
target: imgIcon
|
||||||
|
rotation: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "rotated"
|
||||||
|
PropertyChanges {
|
||||||
|
target: imgIcon
|
||||||
|
rotation: 45
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
transitions: [
|
||||||
|
Transition {
|
||||||
|
from: "default"
|
||||||
|
to: "rotated"
|
||||||
|
RotationAnimation {
|
||||||
|
duration: 150
|
||||||
|
direction: RotationAnimation.Clockwise
|
||||||
|
easing.type: Easing.InCubic
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Transition {
|
||||||
|
from: "rotated"
|
||||||
|
to: "default"
|
||||||
|
RotationAnimation {
|
||||||
|
duration: 150
|
||||||
|
direction: RotationAnimation.Counterclockwise
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
visible: btnAddContainer.clickable
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
imgIcon.state = "rotated"
|
||||||
|
btnAddContainer.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ Item {
|
||||||
readonly property int labelMargin: 7
|
readonly property int labelMargin: 7
|
||||||
property int customHeight: 44
|
property int customHeight: 44
|
||||||
property int fontPixelSize: 15
|
property int fontPixelSize: 15
|
||||||
|
signal editingFinished(string inputValue)
|
||||||
|
|
||||||
id: inputBox
|
id: inputBox
|
||||||
height: inputRectangle.height + (hasLabel ? inputLabel.height + labelMargin : 0) + (!!validationError ? validationErrorText.height : 0)
|
height: inputRectangle.height + (hasLabel ? inputLabel.height + labelMargin : 0) + (!!validationError ? validationErrorText.height : 0)
|
||||||
|
@ -70,6 +71,7 @@ Item {
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: Style.current.transparent
|
color: Style.current.transparent
|
||||||
}
|
}
|
||||||
|
onEditingFinished: inputBox.editingFinished(inputBox.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
SVGImage {
|
SVGImage {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import "../shared"
|
||||||
Menu {
|
Menu {
|
||||||
property alias arrowX: bgPopupMenuTopArrow.x
|
property alias arrowX: bgPopupMenuTopArrow.x
|
||||||
property int paddingSize: 8
|
property int paddingSize: 8
|
||||||
|
property bool hasArrow: true
|
||||||
closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnReleaseOutside | Popup.CloseOnEscape
|
closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnReleaseOutside | Popup.CloseOnEscape
|
||||||
id: popupMenu
|
id: popupMenu
|
||||||
topPadding: bgPopupMenuTopArrow.height + paddingSize
|
topPadding: bgPopupMenuTopArrow.height + paddingSize
|
||||||
|
@ -18,6 +19,8 @@ Menu {
|
||||||
implicitHeight: 34
|
implicitHeight: 34
|
||||||
font.pixelSize: 13
|
font.pixelSize: 13
|
||||||
icon.color: popupMenuItem.action.icon.color != "#00000000" ? popupMenuItem.action.icon.color : Style.current.blue
|
icon.color: popupMenuItem.action.icon.color != "#00000000" ? popupMenuItem.action.icon.color : Style.current.blue
|
||||||
|
visible: popupMenuItem.action.enabled
|
||||||
|
height: popupMenuItem.action.enabled ? popupMenuItem.implicitHeight : 0
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
id: menuItemContent
|
id: menuItemContent
|
||||||
|
|
||||||
|
@ -66,6 +69,7 @@ Menu {
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: bgPopupMenuTopArrow
|
id: bgPopupMenuTopArrow
|
||||||
|
visible: popupMenu.hasArrow
|
||||||
color: Style.current.modalBackground
|
color: Style.current.modalBackground
|
||||||
height: 14
|
height: 14
|
||||||
width: 14
|
width: 14
|
||||||
|
|
Loading…
Reference in New Issue