import QtGraphicalEffects 1.0 import QtQuick 2.7 import QtQuick.Controls 2.4 import QtQuick.Controls.Material 2.0 import QtQuick.Layouts 1.3 import "../opaque" as Opaque import "../opaque/theme" import "../const" ColumnLayout { id: root property alias dualPane: myprof.dualPane property real logscale: 4 * Math.log10(gcd.themeScale + 1) spacing: 10 MouseArea { anchors.fill: parent onClicked: { forceActiveFocus() } } MyProfile { // CURRENT PROFILE INFO AND CONTROL BAR id: myprof } function filterContact(displayName, handle) { return searchAddText.text == "" || displayName.toLowerCase().includes(searchAddText.text.toLowerCase()) || handle.toLowerCase().startsWith(searchAddText.text.toLowerCase()) } function removeContact(model, handle) { for(var i = 0; i < model.count; i++){ if(model.get(i)["_handle"] == handle) { model.remove(i) return true } } return false } Opaque.IconTextField { id: searchAddText anchors.horizontalCenter: parent.horizontalCenter Layout.minimumWidth: parent.width - 60 Layout.maximumWidth: parent.width - 60 //: ex: "... paste an address here to add a contact ..." //placeholderText: qsTr("paste-address-to-add-contact") horizontalAlignment: TextInput.AlignHCenter icon: gcd.assetPath + "core/search-24px.webp" onTextChanged: { // TODO: detect peer or group address and insert a contactRow that asks to add the corresponding group or peer /*if (text != "") { gcd.importString(text) text = "" }*/ } } Opaque.Flickable { // THE ACTUAL CONTACT LIST id: sv Layout.minimumHeight: 100 Layout.fillHeight: true Layout.minimumWidth: parent.width Layout.maximumWidth: parent.width contentWidth: parent.width contentHeight: colContacts.height ColumnLayout { id: colContacts width: root.width spacing: 0 Connections { // ADD/REMOVE CONTACT ENTRIES target: gcd onAddContact: function(handle, displayName, image, badge, status, authorization, loading, lastMsgTs) { var model = contactsModel if (authorization == Const.auth_blocked) { model = blockedContactsModel } // If contact already in model, skip for (var i = 0; i < model.count; i++) { if (model.get(i)["_handle"] == handle) { return } } // Sort order: [unknown,authed][most recent message] var index = model.count for (var i = 0; i < model.count; i++) { var contact = model.get(i) if (contact["_authorization"] == authorization) { if (contact["_lastMsgTs"] < lastMsgTs) { index = i break } } else if (authorization == Const.auth_unknown) { index = i break } } var newContact = { "_handle": handle, "_displayName": displayName, "_image": image, "_badge": badge, "_status": status, "_authorization": authorization, "_loading": loading, "_lastMsgTs": lastMsgTs, } model.insert(index, newContact) } onRemoveContact: function(handle) { var removed = root.removeContact(contactsModel, handle) if (!removed) { root.removeContact(blockedContactsModel, handle) } } /*onIncContactUnreadCount: function(handle) { var ts = Math.round((new Date()).getTime() / 1000); for(var i = 0; i < contactsModel.count; i++){ if(contactsModel.get(i)["_handle"] == handle) { var contact = contactsModel.get(i) contact["_lastMsgTs"] = ts contactsModel.move(i, 0, 1) } } }*/ onIncContactMessageCount: function(profile, handle) { if (profile == gcd.selectedProfile) { var ts = Math.round((new Date()).getTime() / 1000); for(var i = 0; i < contactsModel.count; i++){ if(contactsModel.get(i)["_handle"] == handle) { var contact = contactsModel.get(i) contact["_lastMsgTs"] = ts contactsModel.move(i, 0, 1) } } } } onResetProfile: function() { contactsModel.clear() blockedContactsModel.clear() } } ListModel { // CONTACT OBJECTS ARE STORED HERE ... id: contactsModel } Repeater { id: contactRepeater model: contactsModel // ... AND DISPLAYED HERE delegate: ContactRow { handle: _handle displayName: _displayName image: _image badge: _badge status: _status authorization: _authorization loading: _loading rowColor: (_authorization == Const.auth_unknown) ? Theme.backgroundHilightElementColor : Theme.backgroundMainColor Layout.fillWidth: true visible: filterContact(displayName, handle) isActive: gcd.selectedConversation == _handle } } Item { id: blockItem height: blockedToggle.height Layout.fillWidth: true visible: blockedContactsModel.count > 0 MouseArea { anchors.fill: blockItem hoverEnabled: true onClicked: { blockedToggle.showing = !blockedToggle.showing blockedContacts.visible = blockedToggle.showing if (blockedToggle.showing) { gcd.storeSetting(Const.show_blocked, "true") } else { gcd.storeSetting(Const.show_blocked, "false") } } onEntered: { blockedBG.color = Theme.backgroundPaneColor } onExited: { blockedBG.color = Theme.backgroundMainColor } } Rectangle { id: blockedBG property bool isHover: false anchors.fill: blockItem color: Theme.backgroundMainColor Connections { target: Theme onThemeChanged: { blockedBG.color = Theme.backgroundMainColor } } } Row { id: blockedToggle property bool showing: true leftPadding: 32 * logscale topPadding: 16 * logscale bottomPadding: 8 * logscale spacing: 5 * logscale Opaque.ScalingLabel { id: blockLbl text: qsTr("blocked") size: Theme.chatMetaTextSize color: Theme.portraitBlockedTextColor } Opaque.ScalingLabel { id: blockBtn text: blockedToggle.showing ? "▲" : "▼" size: Theme.chatMetaTextSize color: Theme.portraitBlockedTextColor } } Connections { target: gcd onUpdateMyProfile: function(_nick, _onion, _image, _tag, _showBlocked) { blockedToggle.showing = (_showBlocked == "true") blockedContacts.visible = (_showBlocked == "true") } } } ListModel { id: blockedContactsModel } ColumnLayout { id: blockedContacts Layout.fillWidth: true spacing: 0 Repeater { id: blockedContactsRepeater model: blockedContactsModel // ... AND DISPLAYED HERE delegate: ContactRow { handle: _handle displayName: _displayName image: _image badge: _badge status: _status authorization: _authorization loading: _loading Layout.fillWidth: true visible: filterContact(displayName, handle) } } } } } }