From 60dfb2e41aafd15b8623b9b9fe44d6849f46776c Mon Sep 17 00:00:00 2001 From: elros34 Date: Mon, 2 Mar 2020 11:10:11 +0100 Subject: [PATCH 1/5] Configurable time label and voice button --- depecher/qml/pages/GroupInfoPage.qml | 2 +- .../components/settings/AppearancePage.qml | 36 +++++++++++++++++++ depecher/qml/pages/items/VoiceButton.qml | 2 +- depecher/qml/pages/items/WritingItem.qml | 33 ++++++++++++++--- depecher/translations/depecher-de.ts | 8 +++++ depecher/translations/depecher-es.ts | 8 +++++ depecher/translations/depecher-fi.ts | 8 +++++ depecher/translations/depecher-hu.ts | 8 +++++ depecher/translations/depecher-it.ts | 8 +++++ depecher/translations/depecher-sv.ts | 8 +++++ depecher/translations/depecher-zh_CN.ts | 8 +++++ depecher/translations/depecher.ts | 8 +++++ 12 files changed, 130 insertions(+), 7 deletions(-) diff --git a/depecher/qml/pages/GroupInfoPage.qml b/depecher/qml/pages/GroupInfoPage.qml index 322576c1..c4eb42cc 100644 --- a/depecher/qml/pages/GroupInfoPage.qml +++ b/depecher/qml/pages/GroupInfoPage.qml @@ -156,7 +156,7 @@ SharedContent { x: Theme.horizontalPageMargin CircleImage { id:userPhoto - source: avatar + source: avatar ? avatar : "" fallbackItemVisible: avatar == undefined fallbackText:name.charAt(0) anchors.verticalCenter: parent.verticalCenter diff --git a/depecher/qml/pages/components/settings/AppearancePage.qml b/depecher/qml/pages/components/settings/AppearancePage.qml index bc2497b7..e565acb8 100644 --- a/depecher/qml/pages/components/settings/AppearancePage.qml +++ b/depecher/qml/pages/components/settings/AppearancePage.qml @@ -34,6 +34,17 @@ Page { key:settingsUiPath + "/nightModeTill" defaultValue: "1900-01-01T08:00:00" } + ConfigurationValue { + id:showVoiceMessageButton + key:settingsUiPath + "/showVoiceMessageButton" + defaultValue: true + } + ConfigurationValue { + id:showCurrentTimeLabel + key:settingsUiPath + "/showCurrentTimeLabel" + defaultValue: true + } + SilicaFlickable { anchors.fill: parent @@ -340,6 +351,31 @@ Page { fullSizeInChannels.sync() } } + + TextSwitch { + width: parent.width -2*x + x:Theme.horizontalPageMargin + checked: showVoiceMessageButton.value + automaticCheck: false + text: qsTr("Show voice message button") + onClicked: { + showVoiceMessageButton.value = !checked + showVoiceMessageButton.sync() + } + } + + TextSwitch { + width: parent.width -2*x + x:Theme.horizontalPageMargin + checked: showCurrentTimeLabel.value + automaticCheck: false + text: qsTr("Show current time below message input") + onClicked: { + showCurrentTimeLabel.value = !checked + showCurrentTimeLabel.sync() + } + } + ExpandingSectionGroup { width: parent.width ExpandingSection { diff --git a/depecher/qml/pages/items/VoiceButton.qml b/depecher/qml/pages/items/VoiceButton.qml index 6d4b8939..255132dc 100644 --- a/depecher/qml/pages/items/VoiceButton.qml +++ b/depecher/qml/pages/items/VoiceButton.qml @@ -120,7 +120,7 @@ Row { width:rootItem.width == buttonWidth ? 0 : rootItem.width - mic.width - Theme.horizontalPageMargin Row { width: parent.width - anchors.bottom: rootItem.bottom + anchors.bottom: parent.bottom visible: voiceLabel.width > 0 Item { width: Theme.horizontalPageMargin diff --git a/depecher/qml/pages/items/WritingItem.qml b/depecher/qml/pages/items/WritingItem.qml index 5347e9bd..c5649489 100644 --- a/depecher/qml/pages/items/WritingItem.qml +++ b/depecher/qml/pages/items/WritingItem.qml @@ -29,12 +29,32 @@ Drawer { signal setFocusToEdit() signal replyAreaCleared() + property string settingsUiPath: "/apps/depecher/ui" property string settingsBehaviorPath: "/apps/depecher/behavior" ConfigurationValue { id:sendByEnter key:settingsBehaviorPath +"/sendByEnter" defaultValue: false } + + ConfigurationValue { + id: showVoiceMessageButton + key: settingsUiPath + "/showVoiceMessageButton" + defaultValue: true + } + + ConfigurationValue { + id:showCurrentTimeLabel + key:settingsUiPath + "/showCurrentTimeLabel" + defaultValue: true + onValueChanged: { + if (value) { + var date = new Date() + labelTime.text = Format.formatDate(date, Formatter.TimeValue) + } + } + } + function clearReplyArea() { if(attachDrawer.state == "editText" || attachDrawer.state == "editCaption") { messageArea.text = "" @@ -207,8 +227,10 @@ Drawer { labelVisible: false Component.onCompleted: { - var date = new Date() - labelTime.text = Format.formatDate(date, Formatter.TimeValue) + if (showCurrentTimeLabel.value) { + var date = new Date() + labelTime.text = Format.formatDate(date, Formatter.TimeValue) + } } state:"text" states:[ @@ -229,7 +251,7 @@ Drawer { } PropertyChanges { target:mic - visible: reply_id != "-1" + visible: showVoiceMessageButton.value && reply_id != "-1" y:stickerButton.y } PropertyChanges { @@ -409,11 +431,12 @@ Drawer { anchors.leftMargin: Theme.horizontalPageMargin anchors.bottom: parent.bottom anchors.bottomMargin: Theme.paddingMedium - visible: messageArea.visible + visible: showCurrentTimeLabel.value && messageArea.visible + height: visible ? undefined : 0 Timer { interval: 60*1000 repeat: true - running: true + running: showCurrentTimeLabel.value onTriggered: { var date = new Date() labelTime.text = Format.formatDate(date, Formatter.TimeValue) diff --git a/depecher/translations/depecher-de.ts b/depecher/translations/depecher-de.ts index f187875d..fd306558 100644 --- a/depecher/translations/depecher-de.ts +++ b/depecher/translations/depecher-de.ts @@ -179,6 +179,14 @@ Show full screen images in channels Bilder in Kanälen in Vollbild darstellen + + Show voice message button + + + + Show current time below message input + + AreYouSureDialog diff --git a/depecher/translations/depecher-es.ts b/depecher/translations/depecher-es.ts index ff209949..a9b831f0 100644 --- a/depecher/translations/depecher-es.ts +++ b/depecher/translations/depecher-es.ts @@ -179,6 +179,14 @@ Show full screen images in channels Mostrar mensajes a pantalla completa en los canales + + Show voice message button + + + + Show current time below message input + + AreYouSureDialog diff --git a/depecher/translations/depecher-fi.ts b/depecher/translations/depecher-fi.ts index a6d3ba2f..cae20db2 100644 --- a/depecher/translations/depecher-fi.ts +++ b/depecher/translations/depecher-fi.ts @@ -179,6 +179,14 @@ Show full screen images in channels + + Show voice message button + + + + Show current time below message input + + AreYouSureDialog diff --git a/depecher/translations/depecher-hu.ts b/depecher/translations/depecher-hu.ts index d743264a..1e617ab7 100644 --- a/depecher/translations/depecher-hu.ts +++ b/depecher/translations/depecher-hu.ts @@ -180,6 +180,14 @@ Show full screen images in channels + + Show voice message button + + + + Show current time below message input + + AreYouSureDialog diff --git a/depecher/translations/depecher-it.ts b/depecher/translations/depecher-it.ts index 22d096e1..661d6523 100644 --- a/depecher/translations/depecher-it.ts +++ b/depecher/translations/depecher-it.ts @@ -179,6 +179,14 @@ Show full screen images in channels + + Show voice message button + + + + Show current time below message input + + AreYouSureDialog diff --git a/depecher/translations/depecher-sv.ts b/depecher/translations/depecher-sv.ts index 6d5c5947..324c4db2 100644 --- a/depecher/translations/depecher-sv.ts +++ b/depecher/translations/depecher-sv.ts @@ -179,6 +179,14 @@ Show full screen images in channels Visa helskärmsbilder i kanaler + + Show voice message button + + + + Show current time below message input + + AreYouSureDialog diff --git a/depecher/translations/depecher-zh_CN.ts b/depecher/translations/depecher-zh_CN.ts index c00b22d0..674ff33d 100644 --- a/depecher/translations/depecher-zh_CN.ts +++ b/depecher/translations/depecher-zh_CN.ts @@ -179,6 +179,14 @@ Show full screen images in channels 在群组显示全屏图片 + + Show voice message button + + + + Show current time below message input + + AreYouSureDialog diff --git a/depecher/translations/depecher.ts b/depecher/translations/depecher.ts index 5a94b5e0..21306dc2 100644 --- a/depecher/translations/depecher.ts +++ b/depecher/translations/depecher.ts @@ -179,6 +179,14 @@ Show full screen images in channels + + Show voice message button + + + + Show current time below message input + + AreYouSureDialog From 1d0b24ae7283dd897b82591c2aca1fc1ebc0956f Mon Sep 17 00:00:00 2001 From: elros34 Date: Mon, 2 Mar 2020 11:13:27 +0100 Subject: [PATCH 2/5] Add nemotransferengine-qt5 dependency --- depecher/rpm/depecher.spec | 1 + depecher/rpm/depecher.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/depecher/rpm/depecher.spec b/depecher/rpm/depecher.spec index 88f0c64f..a7cfc7af 100644 --- a/depecher/rpm/depecher.spec +++ b/depecher/rpm/depecher.spec @@ -34,6 +34,7 @@ BuildRequires: pkgconfig(tdlibjson) BuildRequires: pkgconfig(nemonotifications-qt5) BuildRequires: pkgconfig(vorbisfile) BuildRequires: pkgconfig(libdbusaccess) +BuildRequires: pkgconfig(nemotransferengine-qt5) BuildRequires: sailfish-svg2png >= 0.1.5 BuildRequires: desktop-file-utils diff --git a/depecher/rpm/depecher.yaml b/depecher/rpm/depecher.yaml index f2e4efca..2472a4f8 100644 --- a/depecher/rpm/depecher.yaml +++ b/depecher/rpm/depecher.yaml @@ -30,6 +30,7 @@ PkgConfigBR: - nemonotifications-qt5 - vorbisfile - libdbusaccess + - nemotransferengine-qt5 # Build dependencies without a pkgconfig setup can be listed here PkgBR: From c61943ae40d61947f156c6bdcefdf54826895b1b Mon Sep 17 00:00:00 2001 From: elros34 Date: Mon, 9 Mar 2020 21:59:14 +0100 Subject: [PATCH 3/5] Implement basic chat members completer Add human readable way to create tdlib json queries --- depecher/qml/pages/DialogsPage.qml | 2 +- depecher/qml/pages/GroupInfoPage.qml | 81 +++++++++-- depecher/qml/pages/MessagingPage.qml | 66 ++++++--- depecher/qml/pages/UserPage.qml | 112 +++++++++++++-- depecher/qml/pages/items/CircleImage.qml | 1 + depecher/qml/pages/items/WritingItem.qml | 134 +++++++++++++++++- depecher/src/DBusAdaptor.cpp | 2 + depecher/translations/depecher-de.ts | 8 +- depecher/translations/depecher-es.ts | 8 +- depecher/translations/depecher-fi.ts | 8 +- depecher/translations/depecher-hu.ts | 8 +- depecher/translations/depecher-it.ts | 8 +- depecher/translations/depecher-sv.ts | 8 +- depecher/translations/depecher-zh_CN.ts | 8 +- depecher/translations/depecher.ts | 8 +- tdlibjson_wrapper/tdlibQt/ParseObject.cpp | 17 ++- tdlibjson_wrapper/tdlibQt/ParseObject.hpp | 2 + .../tdlibQt/TdlibJsonWrapper.cpp | 46 +++++- .../tdlibQt/TdlibJsonWrapper.hpp | 6 +- .../infoProviders/ChannelInfoProvider.cpp | 23 ++- .../infoProviders/ChannelInfoProvider.hpp | 8 ++ .../tdlibQt/models/ChatMembersModel.cpp | 20 ++- .../tdlibQt/models/ChatMembersModel.hpp | 5 +- .../tdlibQt/models/FilterChatMembersModel.cpp | 55 +++++++ .../tdlibQt/models/FilterChatMembersModel.hpp | 31 ++++ .../tdlibQt/models/singletons/UsersModel.cpp | 15 ++ .../tdlibQt/models/singletons/UsersModel.hpp | 2 + tdlibjson_wrapper/tdlibjson_wrapper.pro | 2 + 28 files changed, 605 insertions(+), 89 deletions(-) create mode 100644 tdlibjson_wrapper/tdlibQt/models/FilterChatMembersModel.cpp create mode 100644 tdlibjson_wrapper/tdlibQt/models/FilterChatMembersModel.hpp diff --git a/depecher/qml/pages/DialogsPage.qml b/depecher/qml/pages/DialogsPage.qml index ddd9da4a..89897d1e 100644 --- a/depecher/qml/pages/DialogsPage.qml +++ b/depecher/qml/pages/DialogsPage.qml @@ -9,7 +9,7 @@ Page { id: page allowedOrientations: Orientation.All - property string titleHeader:Qt.application.version + property string titleHeader: "Depecher" //for search in pageStack property bool __chat_page: true property string _opened_chat_id: "" diff --git a/depecher/qml/pages/GroupInfoPage.qml b/depecher/qml/pages/GroupInfoPage.qml index c4eb42cc..1715d90e 100644 --- a/depecher/qml/pages/GroupInfoPage.qml +++ b/depecher/qml/pages/GroupInfoPage.qml @@ -2,18 +2,23 @@ import QtQuick 2.6 import Sailfish.Silica 1.0 import TelegramDAO 1.0 import tdlibQtEnums 1.0 +import TelegramModels 1.0 import "items" import "items/filter_delegates" Page { - + id: page property alias chat_id: groupInfo.chatId + signal filterChatMembersModelChanged(var membersModel) + BasicGroupInfo { id:groupInfo onMembersModelChanged: { - membersList.model = groupInfo.membersModel + filterMembersModel.sourceModel = groupInfo.membersModel + page.filterChatMembersModelChanged(filterMembersModel) } } SilicaFlickable { + id: flickable anchors.fill: parent contentHeight: contentWrapper.height + header.height PageHeader { @@ -21,16 +26,29 @@ Page { title:qsTr("Group info") } PullDownMenu { + visible: groupInfo.inviteLink MenuItem { text: groupInfo.inviteLink } } + + Connections { + target: page + onStatusChanged: { + if (page.status == PageStatus.Inactive) { + searchField.text = "" + searchField.focus = false + } + } + } + Column { id:contentWrapper width: parent.width anchors.top: header.bottom Row { - width: parent.width + width: parent.width - 2 * x + x:Theme.horizontalPageMargin CircleImage { id: avatar width: Theme.itemSizeLarge @@ -103,16 +121,16 @@ Page { width: 1 height:Theme.paddingLarge } -SharedContent { - chatId:groupInfo.chatId + SharedContent { + chatId:groupInfo.chatId - photoCount:groupInfo.photoCount - videoCount:groupInfo.videoCount - fileCount:groupInfo.fileCount - audioCount:groupInfo.audioCount - linkCount:groupInfo.linkCount - voiceCount:groupInfo.voiceCount -} + photoCount:groupInfo.photoCount + videoCount:groupInfo.videoCount + fileCount:groupInfo.fileCount + audioCount:groupInfo.audioCount + linkCount:groupInfo.linkCount + voiceCount:groupInfo.voiceCount + } Item { width: 1 height:Theme.paddingLarge @@ -142,16 +160,44 @@ SharedContent { } } + SearchField { + id: searchField + width: membersList.width + placeholderText: "Search" + inputMethodHints: Qt.ImhNoAutoUppercase + focusOutBehavior: FocusBehavior.ClearItemFocus + autoScrollEnabled: false + + Component.onCompleted: membersList.searchField = searchField + + onFocusChanged: { + flickable.scrollToBottom() + } + + onTextChanged: { + filterMembersModel.search = text + flickable.scrollToBottom() + } + } + SilicaListView { id:membersList width: parent.width - height: 6 * Theme.itemSizeSmall + height: page.height - searchField.height + interactive: flickable.atYEnd || !membersList.atYBeginning clip:true + property SearchField searchField + + model: FilterChatMembersModel { + id: filterMembersModel + showDeleted: page.visible + } delegate: BackgroundItem { width: parent.width height: Theme.itemSizeSmall Row { width: parent.width - 2 * x + height: parent.height anchors.verticalCenter: parent.verticalCenter x: Theme.horizontalPageMargin CircleImage { @@ -160,6 +206,7 @@ SharedContent { fallbackItemVisible: avatar == undefined fallbackText:name.charAt(0) anchors.verticalCenter: parent.verticalCenter + width: parent.height - 3*Theme.paddingSmall } Item { width: Theme.paddingLarge @@ -169,15 +216,19 @@ SharedContent { width: parent.width - userPhoto.width anchors.verticalCenter: parent.verticalCenter Label { - text: name + property string mText: model.deleted ? "Deleted account" : (name + (username ? " @" + username : "")) + text: membersList.searchField.text.length ? + Theme.highlightText(mText, filterMembersModel.search, Theme.highlightColor) : mText font.pixelSize: Theme.fontSizeMedium - width:parent.width + width: parent.width } Label { font.pixelSize: Theme.fontSizeTiny color:Theme.secondaryColor text: online_status width:parent.width + visible: text.length + height: !visible ? 0 : contentHeight } } } diff --git a/depecher/qml/pages/MessagingPage.qml b/depecher/qml/pages/MessagingPage.qml index 96b2f79b..546e785f 100644 --- a/depecher/qml/pages/MessagingPage.qml +++ b/depecher/qml/pages/MessagingPage.qml @@ -14,16 +14,28 @@ Page { property alias chatId: messagingModel.peerId property var forwardMessages: ({}) property var arrayIndex: [] + property FilterChatMembersModel filterChatMembersModel: null + property bool infoPageLoaded: false + onStatusChanged: { - if(status == PageStatus.Active) { - if(messagingModel.chatType["type"] == TdlibState.BasicGroup) - pageStack.pushAttached(Qt.resolvedUrl("GroupInfoPage.qml"),{chat_id:parseFloat(chatId)}) - else if(messagingModel.chatType["type"] == TdlibState.Secret || messagingModel.chatType["type"] == TdlibState.Private) + if (!infoPageLoaded && status == PageStatus.Active) { + infoPageLoaded = true + if(messagingModel.chatType["type"] == TdlibState.BasicGroup) { + var infoPage = pageStack.pushAttached(Qt.resolvedUrl("GroupInfoPage.qml"),{chat_id:parseFloat(chatId)}) + infoPage.filterChatMembersModelChanged.connect(function (membersModel) { + page.filterChatMembersModel = membersModel + }) + } else if (messagingModel.chatType["type"] == TdlibState.Secret || messagingModel.chatType["type"] == TdlibState.Private) { pageStack.pushAttached(Qt.resolvedUrl("UserPage.qml"),{user_id:parseInt(chatId)}) - else if (messagingModel.chatType["type"] == TdlibState.Supergroup)// && messagingModel.chatType["is_channel"]) - pageStack.pushAttached(Qt.resolvedUrl("UserPage.qml"),{chat_id:parseFloat(chatId),hideOpenMenu:true}) + } else if (messagingModel.chatType["type"] == TdlibState.Supergroup) {// && messagingModel.chatType["is_channel"]) + var userPage = pageStack.pushAttached(Qt.resolvedUrl("UserPage.qml"),{chat_id:parseFloat(chatId),hideOpenMenu:true}) + userPage.filterChatMembersModelChanged.connect(function (membersModel) { + page.filterChatMembersModel = membersModel + }) + } + } else if (status == PageStatus.Inactive) { + if (filterChatMembersModel) filterChatMembersModel.search = "" } - } Notification { @@ -106,6 +118,7 @@ Page { id: writer rootPage: page anchors.fill: parent + Timer { //Because TextBase of TextArea uses Timer for losing focus. //Let's reuse that =) @@ -115,27 +128,44 @@ Page { } EnterKey.iconSource: sendByEnter.value ? "image://theme/icon-m-enter-next" : "image://theme/icon-m-enter" EnterKey.onClicked: { - if(sendByEnter.value) { + if (textArea.state === "searchMember" && chatMembersList.sourceIndex >= 0) { + var username = filterChatMembersModel.sourceModel.getProperty(chatMembersList.sourceIndex, "username") + if (username.length) + textArea.text = "@" + username + " " + else + textArea.text = filterChatMembersModel.sourceModel.getProperty(chatMembersList.sourceIndex, "name") + " " + textArea.cursorPosition = textArea.text.length + chatMembersList.sourceIndex = -1 + } else if(sendByEnter.value) { //removing on enter clicked symbol - /n var messageText = textArea.text.slice(0,textArea.cursorPosition-1) + textArea.text.slice(textArea.cursorPosition,textArea.text.length) sendText(messageText,writer.reply_id) } } Keys.onUpPressed: { - var listItems=messageList.contentItem.children - for (var i=0; i0) diff --git a/depecher/qml/pages/UserPage.qml b/depecher/qml/pages/UserPage.qml index 6b5cd43e..97e1c895 100644 --- a/depecher/qml/pages/UserPage.qml +++ b/depecher/qml/pages/UserPage.qml @@ -2,11 +2,13 @@ import QtQuick 2.6 import Sailfish.Silica 1.0 import TelegramDAO 1.0 import tdlibQtEnums 1.0 +import TelegramModels 1.0 import Nemo.Notifications 1.0 import "items" import "items/delegates" import "items/filter_delegates" Page { + id: page property int user_id: -1 property int supergroup_id: -1 property double chat_id: -1 @@ -14,6 +16,8 @@ Page { property bool hideOpenMenu: false readonly property string httpTgPrefix: "https://t.me/" property string tgDN: "" + signal filterChatMembersModelChanged(var membersModel) + Component.onCompleted: { if(user_id > -1 && username == "") { @@ -432,7 +436,22 @@ Page { ChannelInfo { id:channelInfo + onMembersModelChanged: { + filterMembersModel.sourceModel = channelInfo.membersModel + page.filterChatMembersModelChanged(filterMembersModel) + } + } + + Connections { + target: page + onStatusChanged: { + if (page.status == PageStatus.Inactive) { + searchField.text = "" + searchField.focus = false + } + } } + Connections { target: channelInfo onErrorChanged: { @@ -463,12 +482,11 @@ Page { text: qsTr("Open channel") visible: !flickable.hideOpenMenu onClicked:{ - { - var page = pageStack.find(function (page) { - return page.__chat_page !== undefined; - }); - pageStack.replaceAbove(page,"MessagingPage.qml",{chatId:channelInfo.chatId}) - } + var page = pageStack.find(function (page) { + return page.__chat_page !== undefined; + }); + pageStack.replaceAbove(page,"MessagingPage.qml",{chatId:channelInfo.chatId}) + } } RemorsePopup { @@ -612,9 +630,6 @@ Page { audioCount:channelInfo.audioCount linkCount:channelInfo.linkCount voiceCount:channelInfo.voiceCount - } Item { - width: 1 - height:Theme.paddingLarge } // Column { @@ -642,6 +657,85 @@ Page { // } + SearchField { + id: searchField + width: membersList.width + placeholderText: "Search" + inputMethodHints: Qt.ImhNoAutoUppercase + focusOutBehavior: FocusBehavior.ClearItemFocus + autoScrollEnabled: false + + Component.onCompleted: membersList.searchField = searchField + + onFocusChanged: { + flickable.scrollToBottom() + } + + onTextChanged: { + filterMembersModel.search = text + flickable.scrollToBottom() + } + } + + SilicaListView { + id:membersList + width: parent.width + height: page.height - searchField.height + clip:true + interactive: flickable.atYEnd || !membersList.atYBeginning + property SearchField searchField + + model: FilterChatMembersModel { + id: filterMembersModel + showDeleted: page.visible + } + delegate: BackgroundItem { + width: membersList.width + height: Theme.itemSizeSmall + Row { + width: parent.width - 2 * x + height: parent.height + anchors.verticalCenter: parent.verticalCenter + x: Theme.horizontalPageMargin + CircleImage { + id:userPhoto + source: avatar ? avatar : "" + fallbackItemVisible: avatar == undefined + fallbackText:name.charAt(0) + anchors.verticalCenter: parent.verticalCenter + width: parent.height - 3*Theme.paddingSmall + } + Item { + width: Theme.paddingLarge + height: Theme.paddingLarge + } + Column { + width: parent.width - userPhoto.width + anchors.verticalCenter: parent.verticalCenter + + Label { + property string mText: model.deleted ? "Deleted account" : (name + (username ? " @" + username : "")) + text: membersList.searchField.text.length ? + Theme.highlightText(mText, filterMembersModel.search, Theme.highlightColor) : mText + font.pixelSize: Theme.fontSizeMedium + width: parent.width + } + Label { + font.pixelSize: Theme.fontSizeTiny + color:Theme.secondaryColor + text: online_status + width:parent.width + visible: text.length + height: !visible ? 0 : contentHeight + } + } + } + } + + VerticalScrollDecorator { + flickable: membersList + } + } } } diff --git a/depecher/qml/pages/items/CircleImage.qml b/depecher/qml/pages/items/CircleImage.qml index cbf6ad1b..0b30d009 100644 --- a/depecher/qml/pages/items/CircleImage.qml +++ b/depecher/qml/pages/items/CircleImage.qml @@ -9,6 +9,7 @@ Image { property alias fallbackItemVisible: fallbackitem.visible width:parent.height-5 height: width + asynchronous: true layer.enabled:true layer.effect: OpacityMask { diff --git a/depecher/qml/pages/items/WritingItem.qml b/depecher/qml/pages/items/WritingItem.qml index c5649489..c1d54d86 100644 --- a/depecher/qml/pages/items/WritingItem.qml +++ b/depecher/qml/pages/items/WritingItem.qml @@ -4,6 +4,7 @@ import Sailfish.Silica 1.0 import Nemo.Configuration 1.0 import QtGraphicalEffects 1.0 import depecherUtils 1.0 +import TelegramModels 1.0 import QtMultimedia 5.6 Drawer { @@ -20,6 +21,7 @@ Drawer { property alias replyMessageText: authorsTextLabel.text property alias returnButtonItem: returnButton property alias returnButtonEnabled: returnButton.enabled + property alias chatMembersList: chatMembersList property string reply_id: "0" property string edit_message_id: "0" @@ -97,13 +99,103 @@ Drawer { open: false dock: Dock.Bottom anchors.fill: parent + backgroundSize: isPortrait ? height/2 : height * 2/3 Item { id: sendArea anchors.bottom: parent.bottom z:1 width: parent.width - height: replyArea.height + messageArea.height + returnButton.height + labelTime.height + labelTime.anchors.bottomMargin + Theme.paddingSmall + height: chatMembersList.height + replyArea.height + messageArea.height + returnButton.height + labelTime.height + labelTime.anchors.bottomMargin + Theme.paddingSmall + + Rectangle { + width: parent.width + height: chatMembersList.height + color: Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity) + opacity: chatMembersList.opacity + } + SilicaListView { + id: chatMembersList + width: parent.width + height: opacity ? Math.min(3, count)*Theme.itemSizeExtraSmall : 0 + opacity: 0 + clip: true + currentIndex: -1 + focus: false + highlightRangeMode: ListView.ApplyRange + + // currentIndex is invalid after EnterKey is clicked + property int sourceIndex: -1 + + onCurrentIndexChanged: { + if (currentIndex != -1) + sourceIndex = filterChatMembersModel.sourceIndex(currentIndex) + } + + onCountChanged: { + if (count != 0) + sourceIndex = filterChatMembersModel.sourceIndex(currentIndex) + } + + Behavior on height { NumberAnimation { duration: 150} } + + delegate: BackgroundItem { + width: chatMembersList.width + height: Theme.itemSizeExtraSmall + highlighted: index == chatMembersList.currentIndex + Row { + width: parent.width - 2 * x + height: parent.height + anchors.verticalCenter: parent.verticalCenter + x: Theme.horizontalPageMargin + Item { + height: parent.height + width: height + CircleImage { + id:userPhoto + source: avatar ? avatar : "" + fallbackItemVisible: avatar == undefined + fallbackText:name.charAt(0) + anchors.centerIn: parent + } + } + + Item { + width: Theme.paddingLarge + height: Theme.paddingLarge + } + Label { + property string mText: name + (username ? " @" + username : "") + text: textArea.text.length ? + Theme.highlightText(mText, filterChatMembersModel.search, Theme.highlightColor) : mText + font.pixelSize: Theme.fontSizeSmall + color: Theme.primaryColor + width: parent.width - userPhoto.width + anchors.verticalCenter: parent.verticalCenter + } + } + onClicked: { + delegateTimer.start() + if (model.username.length) + textArea.text = "@" + model.username + " " + else + textArea.text = model.name + " " + } + } + VerticalScrollDecorator { + flickable: chatMembersList + } + + Timer { + id: delegateTimer + interval: 0 + onTriggered: { + textArea.cursorPosition = textArea.text.length + restoreFocusTimer.start() + } + } + } + BackgroundItem { id:returnButton @@ -212,13 +304,14 @@ Drawer { TextArea { id: messageArea onTextChanged: { - if(text === "") - { + if (filterChatMembersModel && text.indexOf("@") == 0 && text.indexOf(" ") < 0) { + state = "searchMember" + } else if (text === "") { state ="text" messageArea.forceActiveFocus() - } - if(text != "") + } else if(text != "") { state ="typing" + } } height: Math.min(Theme.itemSizeHuge ,implicitHeight) @@ -232,6 +325,7 @@ Drawer { labelTime.text = Format.formatDate(date, Formatter.TimeValue) } } + state:"text" states:[ State { @@ -372,6 +466,26 @@ Drawer { target:sendButton visible:false } + }, + State { + name: "searchMember" + extend: "typing" + PropertyChanges { + target: filterChatMembersModel + search: text.substr(1) + } + PropertyChanges { + target: chatMembersList + model: filterChatMembersModel + } + PropertyChanges { + target: chatMembersList + opacity: 1 + } + PropertyChanges { + target: chatMembersList + currentIndex: 0 + } } ] transitions: [ @@ -393,6 +507,16 @@ Drawer { properties :"x" duration :200 } + }, + Transition { + from: "*" + to: "searchMember" + reversible: true + NumberAnimation { + target: chatMembersList + properties: "opacity" + duration: 150 + } } ] } diff --git a/depecher/src/DBusAdaptor.cpp b/depecher/src/DBusAdaptor.cpp index 245ce64b..7af20f00 100644 --- a/depecher/src/DBusAdaptor.cpp +++ b/depecher/src/DBusAdaptor.cpp @@ -11,6 +11,7 @@ #include "tdlibQt/models/ContactsModel.hpp" #include "tdlibQt/models/FilterContactsModel.hpp" #include "tdlibQt/models/ChatMembersModel.hpp" +#include "tdlibQt/models/FilterChatMembersModel.hpp" #include "tdlibQt/models/SearchChatMessagesModel.hpp" #include "tdlibQt/TelegramProfileProvider.hpp" @@ -112,6 +113,7 @@ void DBusAdaptor::showApp(const QStringList &cmd) qmlRegisterType("TelegramModels", 1, 0, "StickerModel"); qmlRegisterType("TelegramModels", 1, 0, "ContactsModel"); qmlRegisterType("TelegramModels", 1, 0, "FilterContactsModel"); + qmlRegisterType("TelegramModels", 1, 0, "FilterChatMembersModel"); qmlRegisterType("TelegramModels", 1, 0, "SearchChatMessagesModel"); qmlRegisterType("TelegramDAO", 1, 0, "UserInfo"); diff --git a/depecher/translations/depecher-de.ts b/depecher/translations/depecher-de.ts index fd306558..7ffee037 100644 --- a/depecher/translations/depecher-de.ts +++ b/depecher/translations/depecher-de.ts @@ -862,10 +862,6 @@ Beachte! Danach wird die 2FA Autorisierung deaktiviert werden. Channel info - - %1 members - - Leave channel @@ -898,6 +894,10 @@ Beachte! Danach wird die 2FA Autorisierung deaktiviert werden. Bio + + %1 members + + VideoView diff --git a/depecher/translations/depecher-es.ts b/depecher/translations/depecher-es.ts index a9b831f0..048ca8ae 100644 --- a/depecher/translations/depecher-es.ts +++ b/depecher/translations/depecher-es.ts @@ -862,10 +862,6 @@ Channel info Info de canal - - %1 members - %1 miembros - Leave channel Dejar canal @@ -898,6 +894,10 @@ Bio + + %1 members + %1 miembros + VideoView diff --git a/depecher/translations/depecher-fi.ts b/depecher/translations/depecher-fi.ts index cae20db2..fb4aa09c 100644 --- a/depecher/translations/depecher-fi.ts +++ b/depecher/translations/depecher-fi.ts @@ -865,10 +865,6 @@ Channel info - - %1 members - - Leave channel @@ -901,6 +897,10 @@ Bio + + %1 members + + VideoView diff --git a/depecher/translations/depecher-hu.ts b/depecher/translations/depecher-hu.ts index 1e617ab7..3350c70e 100644 --- a/depecher/translations/depecher-hu.ts +++ b/depecher/translations/depecher-hu.ts @@ -862,10 +862,6 @@ Channel info - - %1 members - - Leave channel @@ -898,6 +894,10 @@ Bio + + %1 members + + VideoView diff --git a/depecher/translations/depecher-it.ts b/depecher/translations/depecher-it.ts index 661d6523..6c74f92d 100644 --- a/depecher/translations/depecher-it.ts +++ b/depecher/translations/depecher-it.ts @@ -862,10 +862,6 @@ Channel info - - %1 members - - Leave channel @@ -898,6 +894,10 @@ Bio + + %1 members + + VideoView diff --git a/depecher/translations/depecher-sv.ts b/depecher/translations/depecher-sv.ts index 324c4db2..6e39d6a0 100644 --- a/depecher/translations/depecher-sv.ts +++ b/depecher/translations/depecher-sv.ts @@ -882,10 +882,6 @@ Observera att efter detta, kommer 2-faktorsauktoriseringen att inaktiverasOpen channel - - %1 members - - Copy @@ -898,6 +894,10 @@ Observera att efter detta, kommer 2-faktorsauktoriseringen att inaktiverasBio + + %1 members + + VideoView diff --git a/depecher/translations/depecher-zh_CN.ts b/depecher/translations/depecher-zh_CN.ts index 674ff33d..a800bcc3 100644 --- a/depecher/translations/depecher-zh_CN.ts +++ b/depecher/translations/depecher-zh_CN.ts @@ -862,10 +862,6 @@ Channel info 频道信息 - - %1 members - %1 位成员 - Leave channel 退出频道 @@ -898,6 +894,10 @@ Bio + + %1 members + %1 位成员 + VideoView diff --git a/depecher/translations/depecher.ts b/depecher/translations/depecher.ts index 21306dc2..f0ff0fdc 100644 --- a/depecher/translations/depecher.ts +++ b/depecher/translations/depecher.ts @@ -861,10 +861,6 @@ Channel info - - %1 members - - Leave channel @@ -897,6 +893,10 @@ Bio + + %1 members + + VideoView diff --git a/tdlibjson_wrapper/tdlibQt/ParseObject.cpp b/tdlibjson_wrapper/tdlibQt/ParseObject.cpp index 378413d3..e9e846b5 100644 --- a/tdlibjson_wrapper/tdlibQt/ParseObject.cpp +++ b/tdlibjson_wrapper/tdlibQt/ParseObject.cpp @@ -21,7 +21,7 @@ void ParseObject::parseResponse(const QByteArray &json) } QString typeField = doc.object()["@type"].toString(); #ifdef QT_DEBUG - qDebug() << json; + qDebug().noquote() << json << "\n"; #endif #ifdef WITH_LOG QFile logFile("/home/nemo/depecherDatabase/depecher.log"); @@ -310,6 +310,9 @@ void ParseObject::parseResponse(const QByteArray &json) if (typeField == "basicGroupFullInfo") { emit basicGroupFullInfoReceived(doc.object()); } + if (typeField == "chatMembers") { + emit supergroupMembersReceived(doc.object()); + } // case "updateBasicGroup": // case "updateBasicGroupFullInfo": @@ -1539,6 +1542,18 @@ QSharedPointer ParseObject::parseChatMember(const QJsonObject &chatM return result; } +QSharedPointer ParseObject::parseChatMembers(const QJsonObject &chatMembersObject) +{ + QSharedPointer result(new chatMembers); + if (chatMembersObject["@type"].toString() != "chatMembers") + return result; + result->total_count_ = chatMembersObject["total_count"].toInt(); + foreach (auto itm, chatMembersObject["members"].toArray()) { + result->members_.push_back(parseChatMember(itm.toObject())); + } + return result; +} + QSharedPointer ParseObject::parseChatPhoto(const QJsonObject &chatPhotoObject) { diff --git a/tdlibjson_wrapper/tdlibQt/ParseObject.hpp b/tdlibjson_wrapper/tdlibQt/ParseObject.hpp index 210e41f2..1ab84f4e 100644 --- a/tdlibjson_wrapper/tdlibQt/ParseObject.hpp +++ b/tdlibjson_wrapper/tdlibQt/ParseObject.hpp @@ -107,6 +107,7 @@ class ParseObject : public QObject static QSharedPointer parseBasicGroup(const QJsonObject &basicGroupObject); static QSharedPointer parseBasicGroupFullInfo(const QJsonObject &basicGroupFullInfoObject); static QSharedPointer parseChatMember(const QJsonObject &chatMemberObject); + static QSharedPointer parseChatMembers(const QJsonObject &chatMembersObject); signals: @@ -169,6 +170,7 @@ class ParseObject : public QObject void updateBasicGroupReceived(const QJsonObject &updateBasicGroupObject); void basicGroupFullInfoReceived(const QJsonObject &basicGroupFullInfoObject); void updateBasicGroupFullInfoReceived(const QJsonObject &updateBasicGroupFullInfoObject); + void supergroupMembersReceived(const QJsonObject &supergroupMembersObject); public slots: void parseResponse(const QByteArray &json); }; diff --git a/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.cpp b/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.cpp index e82569f9..7ee08f39 100644 --- a/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.cpp +++ b/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.cpp @@ -19,11 +19,27 @@ TdlibJsonWrapper::TdlibJsonWrapper(QObject *parent) : QObject(parent) void TdlibJsonWrapper::sendToTelegram(void *Client, const char *str) { #ifdef QT_DEBUG - qDebug() << QString::fromLatin1(str); + qDebug().noquote() << QString::fromLatin1(str); #endif td_json_client_send(Client, str); } +void TdlibJsonWrapper::sendJsonObjToTelegram(QJsonObject &queryObj, const QString &extra) +{ + if (!extra.isEmpty()) + queryObj.insert("@extra", extra); + + QJsonDocument doc(queryObj); + QByteArray query; +#ifdef QT_DEBUG + query = doc.toJson(QJsonDocument::Indented); +#else + query = doc.toJson(QJsonDocument::Compact); +#endif + + sendToTelegram(client, query.constData()); +} + QByteArray TdlibJsonWrapper::sendSyncroniousMessage(const QString &json) { std::string reply = td_json_client_execute(client, json.toStdString().c_str()); @@ -207,6 +223,8 @@ void TdlibJsonWrapper::startListen() this, &TdlibJsonWrapper::updateBasicGroupFullInfoReceived); connect(parseObject, &ParseObject::updateBasicGroupReceived, this, &TdlibJsonWrapper::updateBasicGroupReceived); + connect(parseObject, &ParseObject::supergroupMembersReceived, + this, &TdlibJsonWrapper::supergroupMembersReceived); listenThread->start(); parseThread->start(); @@ -850,6 +868,32 @@ void TdlibJsonWrapper::deleteChatHistory(qint64 chat_id, bool remove_from_chat_l sendToTelegram(client, query.toStdString().c_str()); } +void TdlibJsonWrapper::getSupergroupMembers(const int supergroup_id, const QString &search, + const int offset, const int limit, const QString &extra) +{ + QJsonObject filter; + if (search.isEmpty()) { + filter = QJsonObject { + {"@type", "supergroupMembersFilterRecent"} + }; + } else { + filter = QJsonObject { + {"@type", "supergroupMembersFilterSearch"}, + {"query", search} + }; + } + + QJsonObject query { + {"@type", "getSupergroupMembers"}, + {"supergroup_id", supergroup_id}, + {"filter", filter}, + {"offset", offset}, + {"limit", limit} + }; + + sendJsonObjToTelegram(query, extra); +} + void TdlibJsonWrapper::getChatMessageCount(qint64 chat_id, Enums::SearchFilter filter, bool return_local, const QString &extra) { QString query = "{\"@type\":\"getChatMessageCount\"," diff --git a/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.hpp b/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.hpp index ff78a085..6c46fddc 100644 --- a/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.hpp +++ b/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.hpp @@ -45,6 +45,7 @@ class TdlibJsonWrapper : public QObject int m_totalUnreadCount; void sendToTelegram(void *Client, const char *str); + void sendJsonObjToTelegram(QJsonObject &queryObj, const QString &extra = ""); const QList m_searchFilters = { "searchMessagesFilterEmpty", "searchMessagesFilterAnimation", @@ -80,8 +81,6 @@ class TdlibJsonWrapper : public QObject int totalUnreadCount() const; void checkPassword(const QString &password); - - signals: void updateNewChat(const QJsonObject &updateNewChatObject); void updateUserReceived(const QJsonObject &updateNewUserObject); @@ -144,6 +143,7 @@ class TdlibJsonWrapper : public QObject void updateBasicGroupReceived(const QJsonObject &updateBasicGroupObject); void basicGroupFullInfoReceived(const QJsonObject &basicGroupFullInfoObject); void updateBasicGroupFullInfoReceived(const QJsonObject &updateBasicGroupFullInfoObject); + void supergroupMembersReceived(const QJsonObject &supergroupMembersObject); private slots: void setTdlibParameters(); @@ -229,6 +229,8 @@ public slots: void createPrivateChat(const int user_id = 0, bool force = false, const QString &extra = ""); void deleteChatHistory(qint64 chat_id = 0, bool remove_from_chat_list = false, bool revoke = false, const QString &extra = ""); + void getSupergroupMembers(const int supergroup_id, + const QString &search, const int offset, const int limit, const QString &extra); }; } //namespace tdlibQt #endif // TDLIBJSONWRAPPER_HPP diff --git a/tdlibjson_wrapper/tdlibQt/infoProviders/ChannelInfoProvider.cpp b/tdlibjson_wrapper/tdlibQt/infoProviders/ChannelInfoProvider.cpp index 89d4ff64..6b4b1460 100644 --- a/tdlibjson_wrapper/tdlibQt/infoProviders/ChannelInfoProvider.cpp +++ b/tdlibjson_wrapper/tdlibQt/infoProviders/ChannelInfoProvider.cpp @@ -2,10 +2,12 @@ #include "tdlibQt/models/singletons/UsersModel.hpp" #include "tdlibQt/TdlibJsonWrapper.hpp" #include "tdlibQt/ParseObject.hpp" +#include "tdlibQt/models/ChatMembersModel.hpp" namespace tdlibQt { static const QString c_extra = QLatin1String("ChannelInfoProvider %1"); -ChannelInfoProvider::ChannelInfoProvider(QObject *parent) : InfoProvider(parent) +ChannelInfoProvider::ChannelInfoProvider(QObject *parent) : InfoProvider(parent), + m_membersModel(NULL) { connect(m_tdlibJson, &TdlibJsonWrapper::supergroupFullInfoReceived, this, &ChannelInfoProvider::infoChanged); @@ -13,6 +15,8 @@ ChannelInfoProvider::ChannelInfoProvider(QObject *parent) : InfoProvider(parent) this, &ChannelInfoProvider::supergroupChanged); connect(this, &ChannelInfoProvider::chatIdChanged, this, &ChannelInfoProvider::onChatIdChanged); + connect(m_tdlibJson, &TdlibJsonWrapper::supergroupMembersReceived, + this, &ChannelInfoProvider::supergroupMembersReceived); } QString ChannelInfoProvider::name() const @@ -253,6 +257,16 @@ void ChannelInfoProvider::supergroupChanged(const QJsonObject &obj) } } +void ChannelInfoProvider::supergroupMembersReceived(const QJsonObject &obj) +{ + if (obj["@extra"].toString() == c_extra.arg(QString::number(m_supergroupId))) { + m_chatMembers = ParseObject::parseChatMembers(obj); + m_membersModel = new ChatMembersModel(this, true); + m_membersModel->setMembers(m_chatMembers->members_); + emit membersModelChanged(); + } +} + void ChannelInfoProvider::onChatIdChanged(const double chatId) { Q_UNUSED(chatId) @@ -294,6 +308,11 @@ void ChannelInfoProvider::emitInfo() } +ChatMembersModel *ChannelInfoProvider::membersModel() const +{ + return m_membersModel; +} + void ChannelInfoProvider::setSupergroupId(int supergroupId) { @@ -303,6 +322,8 @@ void ChannelInfoProvider::setSupergroupId(int supergroupId) m_supergroupId = supergroupId; m_supergroup = UsersModel::instance()->getSupergroup(m_supergroupId); m_tdlibJson->getSupergroupFullInfo(m_supergroupId, c_extra.arg(QString::number(m_supergroupId))); + m_tdlibJson->getSupergroupMembers(m_supergroupId, + "", 0, 200, c_extra.arg(QString::number(m_supergroupId))); emitAll(); emit supergroupIdChanged(m_supergroupId); } diff --git a/tdlibjson_wrapper/tdlibQt/infoProviders/ChannelInfoProvider.hpp b/tdlibjson_wrapper/tdlibQt/infoProviders/ChannelInfoProvider.hpp index 0a9886cf..3d11ea7b 100644 --- a/tdlibjson_wrapper/tdlibQt/infoProviders/ChannelInfoProvider.hpp +++ b/tdlibjson_wrapper/tdlibQt/infoProviders/ChannelInfoProvider.hpp @@ -6,6 +6,7 @@ #include "tdlibQt/items/TdApi.hpp" namespace tdlibQt { +class ChatMembersModel; class ChannelInfoProvider : public InfoProvider { Q_OBJECT @@ -17,6 +18,7 @@ class ChannelInfoProvider : public InfoProvider Q_PROPERTY(QString description READ description NOTIFY descriptionChanged) Q_PROPERTY(int members READ members NOTIFY membersChanged) Q_PROPERTY(MemberStatus status READ status NOTIFY statusChanged) + Q_PROPERTY(ChatMembersModel *membersModel READ membersModel NOTIFY membersModelChanged) Q_PROPERTY(bool canBeEdited READ canBeEdited NOTIFY canBeEditedChanged) Q_PROPERTY(bool canChangeInfo READ canChangeInfo NOTIFY canChangeInfoChanged) Q_PROPERTY(bool canPostMessages READ canPostMessages NOTIFY canPostMessagesChanged) @@ -74,6 +76,8 @@ class ChannelInfoProvider : public InfoProvider int untilDate() const; MemberStatus status() const; + ChatMembersModel *membersModel() const; + signals: void nameChanged(); void linkChanged(); @@ -97,10 +101,12 @@ class ChannelInfoProvider : public InfoProvider void untilDateChanged(); void canRestrictMembersChanged(); void statusChanged(MemberStatus status); + void membersModelChanged(); private slots: void infoChanged(const QJsonObject &obj); void supergroupChanged(const QJsonObject &obj); + void supergroupMembersReceived(const QJsonObject &obj); void onChatIdChanged(const double chatId); public slots: @@ -112,6 +118,8 @@ public slots: void emitInfo(); QSharedPointer m_supergroup; QSharedPointer m_supergroupFullInfo; + QSharedPointer m_chatMembers; + ChatMembersModel *m_membersModel; int m_supergroupId; }; diff --git a/tdlibjson_wrapper/tdlibQt/models/ChatMembersModel.cpp b/tdlibjson_wrapper/tdlibQt/models/ChatMembersModel.cpp index 2b5b0206..7884a85b 100644 --- a/tdlibjson_wrapper/tdlibQt/models/ChatMembersModel.cpp +++ b/tdlibjson_wrapper/tdlibQt/models/ChatMembersModel.cpp @@ -54,7 +54,7 @@ QVariant ChatMembersModel::data(const QModelIndex &index, int role) const return QVariant(); int rowIndex = index.row(); - if (rowIndex < 0 || rowIndex > rowCount(QModelIndex())) + if (rowIndex < 0 || rowIndex >= rowCount(QModelIndex())) return QVariant(); switch (role) { case AVATAR: { @@ -99,9 +99,15 @@ QVariant ChatMembersModel::data(const QModelIndex &index, int role) const } case NAME: return UsersModel::instance()->getUserFullName(m_members[rowIndex]->user_id_); + case USERNAME: + return UsersModel::instance()->getUsername(m_members[rowIndex]->user_id_); case ONLINE_STATUS: return UsersModel::getUserStatusAsString(UsersModel::instance()->getUserStatus(m_members[rowIndex]->user_id_)); + case DELETED: + auto userTypePtr = UsersModel::instance()->getUserType(m_members[rowIndex]->user_id_); + return userTypePtr->get_id() == userTypeDeleted::ID; } + return QVariant(); } void ChatMembersModel::processFile(const QJsonObject &fileObject) @@ -115,7 +121,7 @@ void ChatMembersModel::processFile(const QJsonObject &fileObject) UsersModel::instance()->setSmallAvatar(m_members[rowIndex]->user_id_, file); QVector avatarRole; avatarRole.append(AVATAR); - emit dataChanged(index(rowIndex), QModelIndex(), avatarRole); + emit dataChanged(index(rowIndex), index(rowIndex), avatarRole); } } } @@ -135,7 +141,7 @@ void ChatMembersModel::userStatusReceived(const QJsonObject &userStatusObject) if (m_members[i]->user_id_ == user_id) { QVector roles; roles.append(ONLINE_STATUS); - emit dataChanged(index(i), QModelIndex(), roles); + emit dataChanged(index(i), index(i), roles); break; } } @@ -156,8 +162,10 @@ QHash ChatMembersModel::roleNames() const roleNames[USER_ID] = "user_id"; roleNames[AVATAR] = "avatar"; roleNames[NAME] = "name"; + roleNames[USERNAME] = "username"; roleNames[STATUS] = "status"; roleNames[ONLINE_STATUS] = "online_status"; + roleNames[DELETED] = "deleted"; return roleNames; @@ -167,4 +175,10 @@ bool ChatMembersModel::supergroupMode() const { return m_supergroupMode; } + +QVariant ChatMembersModel::getProperty(int idx, const QByteArray &prop) +{ + return data(index(idx), roleNames().key(prop)); +} + } // namespace tdlibQt diff --git a/tdlibjson_wrapper/tdlibQt/models/ChatMembersModel.hpp b/tdlibjson_wrapper/tdlibQt/models/ChatMembersModel.hpp index 73c90744..4db1ca05 100644 --- a/tdlibjson_wrapper/tdlibQt/models/ChatMembersModel.hpp +++ b/tdlibjson_wrapper/tdlibQt/models/ChatMembersModel.hpp @@ -21,8 +21,10 @@ class ChatMembersModel : public QAbstractListModel USER_ID, AVATAR, NAME, + USERNAME, STATUS, - ONLINE_STATUS + ONLINE_STATUS, + DELETED }; enum MemberStatus { Administrator, @@ -34,6 +36,7 @@ class ChatMembersModel : public QAbstractListModel }; void setMembers(const std::vector > &members); Q_ENUM(MemberStatus) + Q_INVOKABLE QVariant getProperty(int idx, const QByteArray &prop); signals: void supergroupModeChanged(bool supergroupMode); diff --git a/tdlibjson_wrapper/tdlibQt/models/FilterChatMembersModel.cpp b/tdlibjson_wrapper/tdlibQt/models/FilterChatMembersModel.cpp new file mode 100644 index 00000000..3c2d7b43 --- /dev/null +++ b/tdlibjson_wrapper/tdlibQt/models/FilterChatMembersModel.cpp @@ -0,0 +1,55 @@ +#include "FilterChatMembersModel.hpp" +namespace tdlibQt { +FilterChatMembersModel::FilterChatMembersModel(QObject *parent): + QSortFilterProxyModel(parent) +{ + +} + +QString FilterChatMembersModel::search() const +{ + return m_search; +} + +void FilterChatMembersModel::setSearch(const QString &search) +{ + if (m_search == search || !sourceModel()) + return; + m_search = search; + invalidateFilter(); + emit searchChanged(); +} + +bool FilterChatMembersModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + QModelIndex index = sourceModel()->index(source_row, 0, source_parent); + + static const int nameRole = sourceModel()->roleNames().key("name"); + static const int usernameRole = sourceModel()->roleNames().key("username"); + static const int deletedRole = sourceModel()->roleNames().key("deleted"); + + if (m_search.isEmpty()) { + if (m_showDeleted) + return true; + else + return !sourceModel()->data(index, deletedRole).toBool(); + } + + return (!sourceModel()->data(index, deletedRole).toBool() && + (sourceModel()->data(index, nameRole).toString().contains(m_search, Qt::CaseInsensitive) || + sourceModel()->data(index, usernameRole).toString().contains(m_search, Qt::CaseInsensitive))); +} + +int FilterChatMembersModel::sourceIndex(const int idx) +{ + return mapToSource(index(idx, 0)).row(); +} + +void FilterChatMembersModel::setShowDeleted(bool showDeleted) +{ + if (m_showDeleted == showDeleted) + return; + m_showDeleted = showDeleted; + invalidateFilter(); +} +} // tdlibQt diff --git a/tdlibjson_wrapper/tdlibQt/models/FilterChatMembersModel.hpp b/tdlibjson_wrapper/tdlibQt/models/FilterChatMembersModel.hpp new file mode 100644 index 00000000..4d6dc6f1 --- /dev/null +++ b/tdlibjson_wrapper/tdlibQt/models/FilterChatMembersModel.hpp @@ -0,0 +1,31 @@ +#ifndef FILTERCHATMEMBERSMODEL_HPP +#define FILTERCHATMEMBERSMODEL_HPP + +#include +namespace tdlibQt { +class FilterChatMembersModel : public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(QString search READ search WRITE setSearch NOTIFY searchChanged) + Q_PROPERTY(bool showDeleted WRITE setShowDeleted) +public: + FilterChatMembersModel(QObject *parent = 0); + + QString search() const; + void setSearch(const QString &search); + void setShowDeleted(bool showDeleted); + + Q_INVOKABLE int sourceIndex(const int idx); + +private: + QString m_search; + bool m_showDeleted; + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + +signals: + void searchChanged(); +}; +} // tdlibQt +#endif // FILTERCHATMEMBERSMODEL_HPP diff --git a/tdlibjson_wrapper/tdlibQt/models/singletons/UsersModel.cpp b/tdlibjson_wrapper/tdlibQt/models/singletons/UsersModel.cpp index 440a23f5..e517da8b 100644 --- a/tdlibjson_wrapper/tdlibQt/models/singletons/UsersModel.cpp +++ b/tdlibjson_wrapper/tdlibQt/models/singletons/UsersModel.cpp @@ -125,6 +125,21 @@ QString UsersModel::getUserFullName(const int userId) m_users[userId]->last_name_); } +QString UsersModel::getUsername(const int userId) +{ + if (!m_users.contains(userId)) + return tr("Unknown user"); + + return QString::fromStdString(m_users[userId]->username_); +} + +QSharedPointer UsersModel::getUserType(const int userId) +{ + if (!m_users.contains(userId)) + return QSharedPointer(nullptr); + return m_users[userId]->type_; +} + int UsersModel::getChatMuteFor(const qint64 chatId) { if (!m_chats.contains(chatId)) diff --git a/tdlibjson_wrapper/tdlibQt/models/singletons/UsersModel.hpp b/tdlibjson_wrapper/tdlibQt/models/singletons/UsersModel.hpp index 5aa92dd6..5e1f91f0 100644 --- a/tdlibjson_wrapper/tdlibQt/models/singletons/UsersModel.hpp +++ b/tdlibjson_wrapper/tdlibQt/models/singletons/UsersModel.hpp @@ -28,7 +28,9 @@ class UsersModel : public QObject QVariantMap getChatType(const qint64 chatId); QString getUserFirstName(const int userId); QString getUserFullName(const int userId); + QString getUsername(const int userId); void setSmallAvatar(qint64 id, QSharedPointer small); + QSharedPointer getUserType(const int userId); QSharedPointer getUserPhoto(const int userId); QSharedPointer getGroupStatus(int group_id); QSharedPointer getUser(const int userId); diff --git a/tdlibjson_wrapper/tdlibjson_wrapper.pro b/tdlibjson_wrapper/tdlibjson_wrapper.pro index c899b220..e30ff5dc 100644 --- a/tdlibjson_wrapper/tdlibjson_wrapper.pro +++ b/tdlibjson_wrapper/tdlibjson_wrapper.pro @@ -36,6 +36,7 @@ HEADERS += \ tdlibQt/models/ChatMembersModel.hpp \ tdlibQt/models/ChatsModel.hpp \ tdlibQt/TelegramProfileProvider.hpp \ + tdlibQt/models/FilterChatMembersModel.hpp \ tdlibQt/models/MessagingModel.hpp \ tdlibQt/FileWriter.hpp \ tdlibQt/ListenScheduler.hpp \ @@ -64,6 +65,7 @@ SOURCES += \ tdlibQt/models/ChatMembersModel.cpp \ tdlibQt/models/ChatsModel.cpp \ tdlibQt/TelegramProfileProvider.cpp \ + tdlibQt/models/FilterChatMembersModel.cpp \ tdlibQt/models/MessagingModel.cpp \ tdlibQt/FileWriter.cpp \ tdlibQt/ListenScheduler.cpp \ From 11eb3fd45c1aa0675bced08ee1ff02aad4b148a1 Mon Sep 17 00:00:00 2001 From: elros34 Date: Mon, 9 Mar 2020 22:06:10 +0100 Subject: [PATCH 4/5] Fix segfault on ui close --- depecher/dbus/ChatShareAdaptor.cpp | 6 ++++-- depecher/rpm/depecher.spec | 1 - depecher/src/DBusAdaptor.cpp | 1 + depecher/src/DBusAdaptor.hpp | 3 +++ depecher/src/main.cpp | 12 ++++++++++-- tdlibjson_wrapper/tdlibQt/ListenObject.cpp | 10 +++++++++- tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.cpp | 7 +++++++ tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.hpp | 1 + 8 files changed, 35 insertions(+), 6 deletions(-) diff --git a/depecher/dbus/ChatShareAdaptor.cpp b/depecher/dbus/ChatShareAdaptor.cpp index 2197563f..90e86ae9 100644 --- a/depecher/dbus/ChatShareAdaptor.cpp +++ b/depecher/dbus/ChatShareAdaptor.cpp @@ -10,7 +10,8 @@ static const QString c_dbusObjectPath = QStringLiteral("/org/blacksailer/depeche static const QString c_dbusInterface = QStringLiteral("org.blacksailer.depecher.share"); static const QString c_extraName = QStringLiteral("dbus"); -ChatShareAdaptor::ChatShareAdaptor(QObject *parent) : QObject(parent) +ChatShareAdaptor::ChatShareAdaptor(QObject *parent) : QObject(parent), + m_dbusConnection(NULL) { m_dbusServer = new QDBusServer("unix:abstract=depecher-dbus", this); connect(m_dbusServer, &QDBusServer::newConnection, @@ -83,5 +84,6 @@ QDBusVariant ChatShareAdaptor::getChatList(const qint64 lastChatId, const qint64 ChatShareAdaptor::~ChatShareAdaptor() { - delete m_dbusConnection; + if (m_dbusConnection) + delete m_dbusConnection; } diff --git a/depecher/rpm/depecher.spec b/depecher/rpm/depecher.spec index a7cfc7af..317fa463 100644 --- a/depecher/rpm/depecher.spec +++ b/depecher/rpm/depecher.spec @@ -72,7 +72,6 @@ if /sbin/pidof depecher > /dev/null; then killall depecher || true fi -systemctl restart mce.service systemctl-user restart ngfd.service #Moving db dir issue - #14 if [ -d "/home/nemo/depecherDatabase" ]; then diff --git a/depecher/src/DBusAdaptor.cpp b/depecher/src/DBusAdaptor.cpp index 7af20f00..5ef7b707 100644 --- a/depecher/src/DBusAdaptor.cpp +++ b/depecher/src/DBusAdaptor.cpp @@ -150,6 +150,7 @@ void DBusAdaptor::openConversation(const qlonglong &chatId) void DBusAdaptor::onViewDestroyed() { view = nullptr; + emit viewDestroyed(); } void DBusAdaptor::onViewClosing(QQuickCloseEvent *v) diff --git a/depecher/src/DBusAdaptor.hpp b/depecher/src/DBusAdaptor.hpp index 546cf002..83fc0cea 100644 --- a/depecher/src/DBusAdaptor.hpp +++ b/depecher/src/DBusAdaptor.hpp @@ -29,6 +29,9 @@ public slots: private slots: void onViewDestroyed(); void onViewClosing(QQuickCloseEvent *v); + +signals: + void viewDestroyed(); }; #endif // DBUSADAPTOR_HPP diff --git a/depecher/src/main.cpp b/depecher/src/main.cpp index 5f53f85a..616db585 100644 --- a/depecher/src/main.cpp +++ b/depecher/src/main.cpp @@ -55,11 +55,19 @@ int main(int argc, char *argv[]) // tdlib->setEncryptionKey(); FileGeneratedHandler generationHandler(app); + app->setQuitOnLastWindowClosed(false); if (quitOnCloseUi.value(false).toBool()) { - app->setQuitOnLastWindowClosed(true); + QObject::connect(dbusWatcher.data(), &DBusAdaptor::viewDestroyed, tdlib, [=] () { + tdlib->close(); + // In case tdlib close fail + QTimer *closeTimer = new QTimer(tdlib); + closeTimer->start(5000); + QObject::connect(closeTimer, &QTimer::timeout, + app , &QGuiApplication::quit); + }); + DBusAdaptor::raiseApp(); } else { - app->setQuitOnLastWindowClosed(false); QObject::connect(app, &QGuiApplication::aboutToQuit, NotificationManager, &tdlibQt::NotificationManager::removeAll); } diff --git a/tdlibjson_wrapper/tdlibQt/ListenObject.cpp b/tdlibjson_wrapper/tdlibQt/ListenObject.cpp index 61b1f4ce..73905bd4 100644 --- a/tdlibjson_wrapper/tdlibQt/ListenObject.cpp +++ b/tdlibjson_wrapper/tdlibQt/ListenObject.cpp @@ -4,6 +4,9 @@ #include #include namespace tdlibQt { + +static const QByteArray authorizationStateClosedStr("{\"@type\":\"updateAuthorizationState\",\"authorization_state\":{\"@type\":\"authorizationStateClosed\"}}"); + ListenObject::ListenObject(void *client_, QWaitCondition *condition, QObject *parent) : QObject(parent), client(client_), @@ -46,7 +49,12 @@ void ListenObject::listen() if (!str.empty()) { QByteArray result = QByteArray::fromStdString(str); - emit resultReady(result); + if (result == authorizationStateClosedStr) { + qApp->quit(); + return; + } else { + emit resultReady(result); + } } } catch (std::logic_error err) { #ifdef QT_DEBUG diff --git a/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.cpp b/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.cpp index 7ee08f39..09c47679 100644 --- a/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.cpp +++ b/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.cpp @@ -1158,5 +1158,12 @@ void TdlibJsonWrapper::setConnectionState(Enums::ConnectionState &connState) emit connectionStateChanged(connectionState); } +void TdlibJsonWrapper::close() +{ + QJsonObject query { + {"@type", "close"} + }; + sendJsonObjToTelegram(query); +} }// namespace tdlibQt diff --git a/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.hpp b/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.hpp index 6c46fddc..fcddb1a6 100644 --- a/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.hpp +++ b/tdlibjson_wrapper/tdlibQt/TdlibJsonWrapper.hpp @@ -231,6 +231,7 @@ public slots: void deleteChatHistory(qint64 chat_id = 0, bool remove_from_chat_list = false, bool revoke = false, const QString &extra = ""); void getSupergroupMembers(const int supergroup_id, const QString &search, const int offset, const int limit, const QString &extra); + void close(); }; } //namespace tdlibQt #endif // TDLIBJSONWRAPPER_HPP From 3db3cf082ee2947401a82dd96f8ed62c87f06d82 Mon Sep 17 00:00:00 2001 From: elros34 Date: Wed, 18 Mar 2020 22:32:37 +0100 Subject: [PATCH 5/5] Improve chat members completer --- depecher/depecher.pro | 2 + depecher/qml/pages/MessagingPage.qml | 87 +++++++++++++--------- depecher/qml/pages/items/WritingItem.qml | 91 +++++++++++++++--------- depecher/src/DBusAdaptor.cpp | 2 + depecher/src/KeysEater.cpp | 52 ++++++++++++++ depecher/src/KeysEater.h | 33 +++++++++ 6 files changed, 201 insertions(+), 66 deletions(-) create mode 100644 depecher/src/KeysEater.cpp create mode 100644 depecher/src/KeysEater.h diff --git a/depecher/depecher.pro b/depecher/depecher.pro index 07d53c06..a06991cc 100644 --- a/depecher/depecher.pro +++ b/depecher/depecher.pro @@ -79,6 +79,7 @@ CONFIG (debug, debug|release) { MOC_DIR = build/mocs SOURCES += \ + src/KeysEater.cpp \ src/main.cpp \ src/FileWorker.cpp \ ModelTest.cpp \ @@ -159,6 +160,7 @@ HEADERS += \ ModelTest.hpp \ src/DBusAdaptor.hpp \ dbus/DepecherAdaptor.hpp \ + src/KeysEater.h \ src/singletons/PageAppStarter.hpp \ src/singletons/DNSTXTLookup.hpp \ src/fileGeneratedHandlers/FileGeneratedHandler.hpp \ diff --git a/depecher/qml/pages/MessagingPage.qml b/depecher/qml/pages/MessagingPage.qml index 546e785f..c769c50c 100644 --- a/depecher/qml/pages/MessagingPage.qml +++ b/depecher/qml/pages/MessagingPage.qml @@ -3,6 +3,7 @@ import Sailfish.Silica 1.0 import TelegramModels 1.0 import QtFeedback 5.0 import tdlibQtEnums 1.0 +import depecherUtils 1.0 import Nemo.Notifications 1.0 import Nemo.Configuration 1.0 import "../js/utils.js" as Utils @@ -34,7 +35,12 @@ Page { }) } } else if (status == PageStatus.Inactive) { - if (filterChatMembersModel) filterChatMembersModel.search = "" + if (filterChatMembersModel) { + writer.textArea.state = "text" + writer.textArea.searchMemberStr = "" + writer.textArea.searchMemberStrPos = -1 + filterChatMembersModel.search = "" + } } } @@ -126,46 +132,63 @@ Page { interval: 50 onTriggered: writer.textArea.forceActiveFocus() } + + KeysEater { + target: keys.length ? writer.textArea._editor : null + keys: { + if (writer.textArea.state === "searchMember" && writer.chatMembersList.count) + return [Qt.Key_Return, Qt.Key_Enter, Qt.Key_Up, Qt.Key_Down] + else + return sendByEnter.value ? [Qt.Key_Return, Qt.Key_Enter] : [] + } + + onKeyPressed: { + switch (key) { + case Qt.Key_Up: + writer.chatMembersList.currentIndex = Math.max(writer.chatMembersList.currentIndex - 1, 0) + break + case Qt.Key_Down: + writer.chatMembersList.currentIndex = Math.min(writer.chatMembersList.currentIndex + 1, writer.chatMembersList.count - 1) + break + } + } + } + EnterKey.iconSource: sendByEnter.value ? "image://theme/icon-m-enter-next" : "image://theme/icon-m-enter" EnterKey.onClicked: { - if (textArea.state === "searchMember" && chatMembersList.sourceIndex >= 0) { - var username = filterChatMembersModel.sourceModel.getProperty(chatMembersList.sourceIndex, "username") - if (username.length) - textArea.text = "@" + username + " " - else - textArea.text = filterChatMembersModel.sourceModel.getProperty(chatMembersList.sourceIndex, "name") + " " - textArea.cursorPosition = textArea.text.length - chatMembersList.sourceIndex = -1 - } else if(sendByEnter.value) { - //removing on enter clicked symbol - /n - var messageText = textArea.text.slice(0,textArea.cursorPosition-1) + textArea.text.slice(textArea.cursorPosition,textArea.text.length) - sendText(messageText,writer.reply_id) + if (textArea.state === "searchMember" && chatMembersList.currentIndex >= 0) { + var targetCursorPos + var sourceIndex = filterChatMembersModel.sourceIndex(chatMembersList.currentIndex) + var username = filterChatMembersModel.sourceModel.getProperty(sourceIndex, "username") + if (username.length) { + targetCursorPos = textArea.searchMemberStrPos + username.length + 2 + textArea.text = text.substring(0, textArea.searchMemberStrPos) + "@" + username + " " + text.substring(textArea.cursorPosition) + } else { + var name = filterChatMembersModel.sourceModel.getProperty(sourceIndex, "name") + targetCursorPos = textArea.searchMemberStrPos + name.length + 1 + textArea.text = text.substring(0, textArea.searchMemberStrPos) + name + " " + text.substring(textArea.cursorPosition) + } + textArea.cursorPosition = targetCursorPos + chatMembersList.currentIndex = -1 + } else if (sendByEnter.value) { + sendText(textArea.text, writer.reply_id) } } Keys.onUpPressed: { - if (textArea.state === "searchMember") { - chatMembersList.currentIndex = Math.max(chatMembersList.currentIndex - 1, 0) - } else { - var listItems=messageList.contentItem.children - for (var i=0; i0) diff --git a/depecher/qml/pages/items/WritingItem.qml b/depecher/qml/pages/items/WritingItem.qml index c1d54d86..3d2fc15e 100644 --- a/depecher/qml/pages/items/WritingItem.qml +++ b/depecher/qml/pages/items/WritingItem.qml @@ -106,35 +106,29 @@ Drawer { anchors.bottom: parent.bottom z:1 width: parent.width - height: chatMembersList.height + replyArea.height + messageArea.height + returnButton.height + labelTime.height + labelTime.anchors.bottomMargin + Theme.paddingSmall + height: basicHeight + chatMembersList.height + property int basicHeight: replyArea.height + messageArea.height + returnButton.height + labelTime.height + labelTime.anchors.bottomMargin + Theme.paddingSmall Rectangle { width: parent.width height: chatMembersList.height color: Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity) - opacity: chatMembersList.opacity } SilicaListView { id: chatMembersList width: parent.width - height: opacity ? Math.min(3, count)*Theme.itemSizeExtraSmall : 0 - opacity: 0 + height: visible ? Math.min(page.height - sendArea.basicHeight, + 3*Theme.itemSizeExtraSmall, + count*Theme.itemSizeExtraSmall) : 0 + visible: false clip: true currentIndex: -1 focus: false highlightRangeMode: ListView.ApplyRange - // currentIndex is invalid after EnterKey is clicked - property int sourceIndex: -1 - - onCurrentIndexChanged: { - if (currentIndex != -1) - sourceIndex = filterChatMembersModel.sourceIndex(currentIndex) - } - onCountChanged: { - if (count != 0) - sourceIndex = filterChatMembersModel.sourceIndex(currentIndex) + if (count > 0 && currentIndex < 0) + currentIndex = 0 } Behavior on height { NumberAnimation { duration: 150} } @@ -157,6 +151,7 @@ Drawer { fallbackItemVisible: avatar == undefined fallbackText:name.charAt(0) anchors.centerIn: parent + width: parent.height - 2*Theme.paddingSmall } } @@ -176,10 +171,13 @@ Drawer { } onClicked: { delegateTimer.start() - if (model.username.length) - textArea.text = "@" + model.username + " " - else - textArea.text = model.name + " " + if (username.length) { + delegateTimer.cursorPosition = textArea.searchMemberStrPos + username.length + 2 + textArea.text = text.substring(0, textArea.searchMemberStrPos) + "@" + username + " " + text.substring(textArea.cursorPosition) + } else { + delegateTimer.cursorPosition = textArea.searchMemberStrPos + name.length + 1 + textArea.text = text.substring(0, textArea.searchMemberStrPos) + name + " " + text.substring(textArea.cursorPosition) + } } } VerticalScrollDecorator { @@ -189,8 +187,9 @@ Drawer { Timer { id: delegateTimer interval: 0 + property int cursorPosition: 0 onTriggered: { - textArea.cursorPosition = textArea.text.length + textArea.cursorPosition = cursorPosition restoreFocusTimer.start() } } @@ -303,10 +302,31 @@ Drawer { } TextArea { id: messageArea + property string searchMemberStr + property int searchMemberStrPos: -1 + + function showMembersList() { + cursorTimer.stop() + var atPos = text.lastIndexOf("@", cursorPosition) + if (atPos === 0 || (atPos > 0 && !/[\w]/.test(text.charAt(atPos-1)))) { + var spacePos = text.indexOf(" ", atPos) + if (spacePos >= cursorPosition || spacePos < 0) { + searchMemberStrPos = atPos + searchMemberStr = text.substring(atPos+1, cursorPosition) + state = "searchMember" + return true + } + } + + if (searchMemberStr.length) + searchMemberStr = "" + return false + } + onTextChanged: { - if (filterChatMembersModel && text.indexOf("@") == 0 && text.indexOf(" ") < 0) { - state = "searchMember" - } else if (text === "") { + if (filterChatMembersModel && showMembersList()) + return + if (text === "") { state ="text" messageArea.forceActiveFocus() } else if(text != "") { @@ -314,6 +334,19 @@ Drawer { } } + onCursorPositionChanged: { + if (filterChatMembersModel) + cursorTimer.restart() + } + Timer { + id: cursorTimer + interval: 10 + onTriggered: { + if (!textArea.showMembersList() && textArea.state == "searchMember") + textArea.state = "typing" + } + } + height: Math.min(Theme.itemSizeHuge ,implicitHeight) font.pixelSize: Theme.fontSizeMedium //Hiding last line of text. Bug of Silica? @@ -472,7 +505,7 @@ Drawer { extend: "typing" PropertyChanges { target: filterChatMembersModel - search: text.substr(1) + search: textArea.searchMemberStr } PropertyChanges { target: chatMembersList @@ -480,7 +513,7 @@ Drawer { } PropertyChanges { target: chatMembersList - opacity: 1 + visible: true } PropertyChanges { target: chatMembersList @@ -507,16 +540,6 @@ Drawer { properties :"x" duration :200 } - }, - Transition { - from: "*" - to: "searchMember" - reversible: true - NumberAnimation { - target: chatMembersList - properties: "opacity" - duration: 150 - } } ] } diff --git a/depecher/src/DBusAdaptor.cpp b/depecher/src/DBusAdaptor.cpp index 5ef7b707..c277db18 100644 --- a/depecher/src/DBusAdaptor.cpp +++ b/depecher/src/DBusAdaptor.cpp @@ -2,6 +2,7 @@ #include "singletons/PageAppStarter.hpp" #include "FileWorker.hpp" #include "components/AudioRecorder.hpp" +#include "KeysEater.h" #include "tdlibQt/TdlibJsonWrapper.hpp" #include "tdlibQt/models/StickerModel.hpp" @@ -106,6 +107,7 @@ void DBusAdaptor::showApp(const QStringList &cmd) qmlRegisterType("depecherUtils", 1, 0, "FileWorker"); qmlRegisterType("depecherUtils", 1, 0, "AudioRecorder"); + qmlRegisterType("depecherUtils", 1, 0, "KeysEater"); qmlRegisterType("TelegramItems", 1, 0, "AboutMeDAO"); qmlRegisterType("TelegramItems", 1, 0, "ProxyDAO"); qmlRegisterType("TelegramModels", 1, 0, "MessagingModel"); diff --git a/depecher/src/KeysEater.cpp b/depecher/src/KeysEater.cpp new file mode 100644 index 00000000..f26e3559 --- /dev/null +++ b/depecher/src/KeysEater.cpp @@ -0,0 +1,52 @@ +#include "KeysEater.h" +#include + +KeysEater::KeysEater(QObject *parent) : + QObject(parent), + m_target(NULL) +{ + +} + +QList KeysEater::keys() const +{ + return m_keys; +} + +void KeysEater::setKeys(const QList &keys) +{ + if (m_keys == keys) + return; + m_keys = keys; + emit keysChanged(); +} + + +QQuickItem *KeysEater::target() const +{ + return m_target; +} + +void KeysEater::setTarget(QQuickItem *target) +{ + if (m_target == target) + return; + if (m_target) + m_target->removeEventFilter(this); + m_target = target; + if (target) + m_target->installEventFilter(this); +} + +bool KeysEater::eventFilter(QObject *, QEvent *event) +{ + if (event->type() == QKeyEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + int i = m_keys.indexOf(keyEvent->key()); + if (i >= 0) {; + emit keyPressed(m_keys.at(i)); + return true; + } + } + return false; +} diff --git a/depecher/src/KeysEater.h b/depecher/src/KeysEater.h new file mode 100644 index 00000000..c2cf6bac --- /dev/null +++ b/depecher/src/KeysEater.h @@ -0,0 +1,33 @@ +#ifndef KEYSEATER_H +#define KEYSEATER_H + +#include + +class QQuickItem; +class KeysEater : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickItem* target READ target WRITE setTarget) + Q_PROPERTY(QList keys READ keys WRITE setKeys NOTIFY keysChanged) +public: + explicit KeysEater(QObject *parent = nullptr); + + QQuickItem *target() const; + void setTarget(QQuickItem *target); + + QList keys() const; + void setKeys(const QList &keys); + +private: + QQuickItem *m_target; + QList m_keys; + +private: + bool eventFilter(QObject *, QEvent *event); + +signals: + void keysChanged(); + void keyPressed(const int key); +}; + +#endif // KEYSEATER_H