diff --git a/db/emojis.db b/db/emojis.db new file mode 100644 index 0000000..397eae8 Binary files /dev/null and b/db/emojis.db differ diff --git a/harbour-fernschreiber.pro b/harbour-fernschreiber.pro index 359923b..75db4e6 100644 --- a/harbour-fernschreiber.pro +++ b/harbour-fernschreiber.pro @@ -16,7 +16,7 @@ CONFIG += sailfishapp sailfishapp_i18n PKGCONFIG += nemonotifications-qt5 ngf-qt5 -QT += core dbus +QT += core dbus sql SOURCES += src/harbour-fernschreiber.cpp \ src/appsettings.cpp \ @@ -24,6 +24,7 @@ SOURCES += src/harbour-fernschreiber.cpp \ src/chatmodel.cpp \ src/dbusadaptor.cpp \ src/dbusinterface.cpp \ + src/emojisearchworker.cpp \ src/fernschreiberutils.cpp \ src/notificationmanager.cpp \ src/processlauncher.cpp \ @@ -102,8 +103,11 @@ ICONPATH = /usr/share/icons/hicolor fernschreiber.desktop.path = /usr/share/applications/ fernschreiber.desktop.files = harbour-fernschreiber.desktop +database.files = db +database.path = /usr/share/$${TARGET} + INSTALLS += telegram 86.png 108.png 128.png 172.png 256.png \ - fernschreiber.desktop gui images + fernschreiber.desktop gui images database HEADERS += \ src/appsettings.h \ @@ -111,6 +115,7 @@ HEADERS += \ src/chatmodel.h \ src/dbusadaptor.h \ src/dbusinterface.h \ + src/emojisearchworker.h \ src/fernschreiberutils.h \ src/notificationmanager.h \ src/processlauncher.h \ diff --git a/qml/components/InReplyToRow.qml b/qml/components/InReplyToRow.qml index 90762b8..20f27d5 100644 --- a/qml/components/InReplyToRow.qml +++ b/qml/components/InReplyToRow.qml @@ -31,6 +31,9 @@ Row { property string myUserId; property variant inReplyToMessage; + property bool editable: false; + + signal clearRequested() onInReplyToMessageChanged: { if (inReplyToMessage) { @@ -47,37 +50,51 @@ Row { border.width: 0 } - Column { - id: inReplyToMessageColumn - spacing: Theme.paddingSmall + Row { width: parent.width - Theme.paddingSmall - inReplyToMessageRectangle.width + spacing: Theme.paddingSmall - Text { - id: inReplyToUserText + Column { + id: inReplyToMessageColumn + spacing: Theme.paddingSmall + width: parent.width - ( inReplyToRow.editable ? ( Theme.paddingSmall + removeInReplyToIconButton.width ) : 0 ) - width: parent.width - font.pixelSize: Theme.fontSizeExtraSmall - font.weight: Font.ExtraBold - color: Theme.primaryColor - maximumLineCount: 1 - elide: Text.ElideRight - textFormat: Text.StyledText - horizontalAlignment: Text.AlignLeft + Text { + id: inReplyToUserText + + width: parent.width + font.pixelSize: Theme.fontSizeExtraSmall + font.weight: Font.ExtraBold + color: Theme.primaryColor + maximumLineCount: 1 + elide: Text.ElideRight + textFormat: Text.StyledText + horizontalAlignment: Text.AlignLeft + } + + Text { + id: inReplyToMessageText + font.pixelSize: Theme.fontSizeExtraSmall + color: Theme.primaryColor + width: parent.width + elide: Text.ElideRight + textFormat: Text.StyledText + onTruncatedChanged: { + // There is obviously a bug in QML in truncating text with images. + // We simply remove Emojis then... + if (truncated) { + text = text.replace(/\]+\/\>/g, ""); + } + } + } } - Text { - id: inReplyToMessageText - font.pixelSize: Theme.fontSizeExtraSmall - color: Theme.primaryColor - width: parent.width - elide: Text.ElideRight - textFormat: Text.StyledText - onTruncatedChanged: { - // There is obviously a bug in QML in truncating text with images. - // We simply remove Emojis then... - if (truncated) { - text = text.replace(/\]+\/\>/g, ""); - } + IconButton { + id: removeInReplyToIconButton + icon.source: "image://theme/icon-m-clear" + visible: inReplyToRow.editable + onClicked: { + inReplyToRow.clearRequested(); } } } diff --git a/qml/components/StickerPicker.qml b/qml/components/StickerPicker.qml index 1e6e2a8..3ff4704 100644 --- a/qml/components/StickerPicker.qml +++ b/qml/components/StickerPicker.qml @@ -74,53 +74,50 @@ Item { elide: Text.ElideRight text: qsTr("Recently used") } - Flickable { + + SilicaGridView { + id: recentStickersGridView width: parent.width - height: recentStickersRow.height + Theme.paddingSmall - anchors.horizontalCenter: parent.horizontalCenter - contentWidth: recentStickersRow.width + height: Theme.itemSizeExtraLarge + cellWidth: Theme.itemSizeExtraLarge; + cellHeight: Theme.itemSizeExtraLarge; + visible: count > 0 clip: true + flow: GridView.FlowTopToBottom - Row { - id: recentStickersRow - spacing: Theme.paddingMedium - Repeater { - model: stickerPickerOverlayItem.recentStickers + model: stickerPickerOverlayItem.recentStickers - Item { - height: singleRecentStickerRow.height - width: singleRecentStickerRow.width + delegate: Item { + width: recentStickersGridView.cellWidth + height: recentStickersGridView.cellHeight - Row { - id: singleRecentStickerRow - spacing: Theme.paddingSmall - Image { - source: modelData.thumbnail.photo.local.path - width: Theme.itemSizeExtraLarge - height: Theme.itemSizeExtraLarge - asynchronous: true - onStatusChanged: { - if (status === Image.Ready) { - stickerPickerLoadedTimer.restart(); - } - } - } - } - - MouseArea { - anchors.fill: parent - onClicked: { - tdLibWrapper.sendStickerMessage(chatInformation.id, modelData.sticker.remote.id); - stickerPickerOverlayItem.visible = false; - attachmentOptionsRow.visible = false; - stickerPickerLoader.active = false; - } + Image { + source: modelData.thumbnail.photo.local.path + anchors.fill: parent + asynchronous: true + onStatusChanged: { + if (status === Image.Ready) { + stickerPickerLoadedTimer.restart(); } } - } + + MouseArea { + anchors.fill: parent + onClicked: { + tdLibWrapper.sendStickerMessage(chatInformation.id, modelData.sticker.remote.id); + stickerPickerOverlayItem.visible = false; + attachmentOptionsRow.visible = false; + stickerPickerLoader.active = false; + } + } + } + + HorizontalScrollDecorator {} + } + Repeater { model: stickerPickerOverlayItem.installedStickerSets width: stickerPickerFlickable.width @@ -136,57 +133,58 @@ Item { elide: Text.ElideRight text: modelData.title } - Flickable { + + SilicaGridView { + id: installedStickerSetGridView width: parent.width - height: installedStickerSetRow.height + Theme.paddingSmall - anchors.horizontalCenter: parent.horizontalCenter - contentWidth: installedStickerSetRow.width + height: Theme.itemSizeExtraLarge + cellWidth: Theme.itemSizeExtraLarge; + cellHeight: Theme.itemSizeExtraLarge; + visible: count > 0 clip: true - Row { - id: installedStickerSetRow - spacing: Theme.paddingMedium + flow: GridView.FlowTopToBottom - Repeater { - model: modelData.stickers + model: modelData.stickers + delegate: Item { + width: installedStickerSetGridView.cellWidth + height: installedStickerSetGridView.cellHeight - Item { - width: Theme.itemSizeExtraLarge - height: Theme.itemSizeExtraLarge - Image { - id: singleStickerImage - source: modelData.thumbnail.photo.local.is_downloading_completed ? modelData.thumbnail.photo.local.path : "" - anchors.fill: parent - visible: modelData.thumbnail.photo.local.is_downloading_completed - asynchronous: true - onStatusChanged: { - if (status === Image.Ready) { - stickerPickerLoadedTimer.restart(); - } - } - } - Text { - font.pixelSize: Theme.fontSizeHuge - color: Theme.primaryColor - anchors.fill: parent - maximumLineCount: 1 - elide: Text.ElideRight - text: Emoji.emojify(modelData.emoji, font.pixelSize) - visible: !modelData.thumbnail.photo.local.is_downloading_completed - } - - MouseArea { - anchors.fill: parent - onClicked: { - tdLibWrapper.sendStickerMessage(chatInformation.id, modelData.sticker.remote.id); - stickerPickerOverlayItem.visible = false; - attachmentOptionsRow.visible = false; - stickerPickerLoader.active = false; - } + Image { + id: singleStickerImage + source: modelData.thumbnail.photo.local.is_downloading_completed ? modelData.thumbnail.photo.local.path : "" + anchors.fill: parent + visible: modelData.thumbnail.photo.local.is_downloading_completed + asynchronous: true + onStatusChanged: { + if (status === Image.Ready) { + stickerPickerLoadedTimer.restart(); } } } + Text { + font.pixelSize: Theme.fontSizeHuge + color: Theme.primaryColor + anchors.fill: parent + maximumLineCount: 1 + elide: Text.ElideRight + text: Emoji.emojify(modelData.emoji, font.pixelSize) + visible: !modelData.thumbnail.photo.local.is_downloading_completed + } + + MouseArea { + anchors.fill: parent + onClicked: { + tdLibWrapper.sendStickerMessage(chatInformation.id, modelData.sticker.remote.id); + stickerPickerOverlayItem.visible = false; + attachmentOptionsRow.visible = false; + stickerPickerLoader.active = false; + } + } } + + HorizontalScrollDecorator {} } + } } } diff --git a/qml/components/StickerPreview.qml b/qml/components/StickerPreview.qml index 23df934..1a3a991 100644 --- a/qml/components/StickerPreview.qml +++ b/qml/components/StickerPreview.qml @@ -17,37 +17,29 @@ along with Fernschreiber. If not, see . */ import QtQuick 2.5 -import QtGraphicalEffects 1.0 import Sailfish.Silica 1.0 Item { - - id: stickerPreviewItem - property variant stickerData; property int usedFileId; - width: stickerData.width + Theme.paddingSmall - height: stickerData.height + Theme.paddingSmall + width: stickerData.width + height: stickerData.height Component.onCompleted: { - updateSticker(); - } - - function updateSticker() { if (stickerData) { if (stickerData.is_animated) { // Use thumbnail until we can decode TGS files usedFileId = stickerData.thumbnail.photo.id; if (stickerData.thumbnail.photo.local.is_downloading_completed) { - singleImage.source = stickerData.thumbnail.photo.local.path; + stickerImage.source = stickerData.thumbnail.photo.local.path; } else { tdLibWrapper.downloadFile(usedFileId); } } else { usedFileId = stickerData.sticker.id; if (stickerData.sticker.local.is_downloading_completed) { - singleImage.source = stickerData.sticker.local.path; + stickerImage.source = stickerData.sticker.local.path; } else { tdLibWrapper.downloadFile(usedFileId); } @@ -65,45 +57,43 @@ Item { } else { stickerData.sticker = fileInformation; } - singleImage.source = fileInformation.local.path; + stickerImage.source = fileInformation.local.path; } } } } Image { - id: singleImage - width: ( ( parent.width - Theme.paddingSmall ) >= stickerData.width ) ? stickerData.width : ( parent.width - Theme.paddingSmall ) - height: ( ( parent.height - Theme.paddingSmall ) >= stickerData.height ) ? stickerData.height : ( parent.height - Theme.paddingSmall ) - anchors.centerIn: parent - - fillMode: Image.PreserveAspectCrop - autoTransform: true - asynchronous: true - visible: status === Image.Ready - opacity: status === Image.Ready ? 1 : 0 - Behavior on opacity { NumberAnimation {} } - MouseArea { - anchors.fill: parent - onClicked: { - //pageStack.push(Qt.resolvedUrl("../pages/ImagePage.qml"), { "photoData" : imagePreviewItem.photoData, "pictureFileInformation" : imagePreviewItem.pictureFileInformation }); - } - } - } - - Image { - id: imageLoadingBackgroundImage - source: "../../images/background-" + ( Theme.colorScheme ? "black" : "white" ) + "-small.png" - anchors { - centerIn: parent - } - width: ( ( parent.width - Theme.paddingSmall ) >= stickerData.width ) ? stickerData.width : ( parent.width - Theme.paddingSmall ) - height: ( ( parent.height - Theme.paddingSmall ) >= stickerData.height ) ? stickerData.height : ( parent.height - Theme.paddingSmall ) - visible: singleImage.status !== Image.Ready - asynchronous: true + id: stickerImage + anchors.fill: parent fillMode: Image.PreserveAspectFit - opacity: 0.15 + autoTransform: true + asynchronous: true + visible: opacity > 0 + opacity: status === Image.Ready ? 1 : 0 + Behavior on opacity { FadeAnimation {} } } + Loader { + anchors.fill: parent + sourceComponent: Component { + Image { + source: "../../images/background-" + ( Theme.colorScheme ? "black" : "white" ) + "-small.png" + asynchronous: true + + fillMode: Image.PreserveAspectFit + } + } + + active: opacity > 0 + opacity: !stickerImage.visible && !placeHolderDelayTimer.running ? 0.15 : 0 + Behavior on opacity { FadeAnimation {} } + } + + Timer { + id: placeHolderDelayTimer + interval: 1000 + running: true + } } diff --git a/qml/pages/AboutPage.qml b/qml/pages/AboutPage.qml index 561e40a..1dd253e 100644 --- a/qml/pages/AboutPage.qml +++ b/qml/pages/AboutPage.qml @@ -51,7 +51,7 @@ Page { fillMode: Image.PreserveAspectFit asynchronous: true - width: (aboutPage.isPortrait ? aboutPage.width : aboutPage.height) / 2 + width: Math.min(2 * Theme.itemSizeHuge, Math.min(aboutPage.width, aboutPage.height) / 2) } Label { diff --git a/qml/pages/ChatPage.qml b/qml/pages/ChatPage.qml index fa8d37a..c108784 100644 --- a/qml/pages/ChatPage.qml +++ b/qml/pages/ChatPage.qml @@ -43,6 +43,7 @@ Page { property variant chatPartnerInformation; property variant chatGroupInformation; property int chatOnlineMemberCount: 0; + property variant emojiProposals; function updateChatPartnerStatusText() { if (chatPartnerInformation.status['@type'] === "userStatusEmpty" ) { @@ -174,6 +175,46 @@ Page { } } controlSendButton(); + newMessageInReplyToRow.inReplyToMessage = null; + newMessageColumn.editMessageId = "0"; + } + + function getWordBoundaries(text, cursorPosition) { + var wordBoundaries = { beginIndex : 0, endIndex : text.length}; + var currentIndex = 0; + for (currentIndex = (cursorPosition - 1); currentIndex > 0; currentIndex--) { + if (text.charAt(currentIndex) === ' ') { + wordBoundaries.beginIndex = currentIndex + 1; + break; + } + } + for (currentIndex = cursorPosition; currentIndex < text.length; currentIndex++) { + if (text.charAt(currentIndex) === ' ') { + wordBoundaries.endIndex = currentIndex; + break; + } + } + return wordBoundaries; + } + + function handleMessageTextReplacement(text, cursorPosition) { + var wordBoundaries = getWordBoundaries(text, cursorPosition); + + var currentWord = text.substring(wordBoundaries.beginIndex, wordBoundaries.endIndex); + if (currentWord.length > 1 && currentWord.charAt(0) === ':') { + tdLibWrapper.searchEmoji(currentWord.substring(1)); + } else { + chatPage.emojiProposals = null; + } + } + + function replaceMessageText(text, cursorPosition, newText) { + var wordBoundaries = getWordBoundaries(text, cursorPosition); + var newCompleteText = text.substring(0, wordBoundaries.beginIndex) + newText + " " + text.substring(wordBoundaries.endIndex); + var newIndex = wordBoundaries.beginIndex + newText.length + 1; + newMessageTextField.text = newCompleteText; + newMessageTextField.cursorPosition = newIndex; + lostFocusTimer.start(); } Component.onCompleted: { @@ -227,6 +268,9 @@ Page { uploadingProgressBar.maximumValue = fileInformation.size; uploadingProgressBar.value = fileInformation.remote.uploaded_size; } + onEmojiSearchSuccessful: { + chatPage.emojiProposals = result; + } } Connections { @@ -269,6 +313,26 @@ Page { } } + Timer { + id: lostFocusTimer + interval: 200 + running: false + repeat: false + onTriggered: { + newMessageTextField.forceActiveFocus(); + } + } + + Timer { + id: textReplacementTimer + interval: 600 + running: false + repeat: false + onTriggered: { + handleMessageTextReplacement(newMessageTextField.text, newMessageTextField.cursorPosition); + } + } + Timer { id: chatContactTimeUpdater interval: 60000 @@ -1026,6 +1090,12 @@ Page { } } + editable: true + + onClearRequested: { + newMessageInReplyToRow.inReplyToMessage = null; + } + id: newMessageInReplyToRow myUserId: chatPage.myUserId anchors.horizontalCenter: parent.horizontalCenter @@ -1151,15 +1221,83 @@ Page { } - Text { + Column { + id: emojiColumn width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + visible: emojiProposals ? ( emojiProposals.length > 0 ? true : false ) : false + opacity: emojiProposals ? ( emojiProposals.length > 0 ? 1 : 0 ) : 0 + Behavior on opacity { NumberAnimation {} } + spacing: Theme.paddingMedium - id: editMessageText - font.pixelSize: Theme.fontSizeSmall - font.bold: true - text: qsTr("Edit Message") - color: Theme.secondaryColor + Flickable { + width: parent.width + height: emojiResultRow.height + Theme.paddingSmall + anchors.horizontalCenter: parent.horizontalCenter + contentWidth: emojiResultRow.width + clip: true + Row { + id: emojiResultRow + spacing: Theme.paddingMedium + Repeater { + model: emojiProposals + + Item { + height: singleEmojiRow.height + width: singleEmojiRow.width + + Row { + id: singleEmojiRow + spacing: Theme.paddingSmall + + Image { + id: emojiPicture + source: "../js/emoji/" + modelData.file_name + width: Theme.fontSizeLarge + height: Theme.fontSizeLarge + } + + } + + MouseArea { + anchors.fill: parent + onClicked: { + replaceMessageText(newMessageTextField.text, newMessageTextField.cursorPosition, modelData.emoji); + emojiProposals = null; + } + } + } + + } + } + } + } + + Row { + width: parent.width + spacing: Theme.paddingSmall visible: newMessageColumn.editMessageId !== "0" + + Text { + width: parent.width - Theme.paddingSmall - removeEditMessageIconButton.width + + anchors.verticalCenter: parent.verticalCenter + + id: editMessageText + font.pixelSize: Theme.fontSizeSmall + font.bold: true + text: qsTr("Edit Message") + color: Theme.secondaryColor + } + + IconButton { + id: removeEditMessageIconButton + icon.source: "image://theme/icon-m-clear" + onClicked: { + newMessageColumn.editMessageId = "0"; + newMessageTextField.text = ""; + } + } } Row { @@ -1177,15 +1315,6 @@ Page { labelVisible: false textLeftMargin: 0 textTopMargin: 0 - onFocusChanged: { - if (!focus) { - newMessageInReplyToRow.inReplyToMessage = null; - if (newMessageColumn.editMessageId !== "0") { - newMessageColumn.editMessageId = "0"; - newMessageTextField.text = ""; - } - } - } EnterKey.onClicked: { if (appSettings.sendByEnter) { sendMessage(); @@ -1199,6 +1328,7 @@ Page { onTextChanged: { controlSendButton(); + textReplacementTimer.restart(); } } diff --git a/qml/pages/InitializationPage.qml b/qml/pages/InitializationPage.qml index 02f59d9..46ec314 100644 --- a/qml/pages/InitializationPage.qml +++ b/qml/pages/InitializationPage.qml @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with Fernschreiber. If not, see . */ -import QtQuick 2.6 +import QtQuick 2.0 import Sailfish.Silica 1.0 import WerkWolf.Fernschreiber 1.0 @@ -29,11 +29,6 @@ Page { property int authorizationState: TelegramAPI.Closed property var authorizationStateData: null - BusyLabel { - text: qsTr("Loading...") - running: initializationPage.loading - } - Component.onCompleted: { initializationPage.authorizationState = tdLibWrapper.getAuthorizationState(); initializationPage.authorizationStateData = tdLibWrapper.getAuthorizationStateData(); @@ -44,6 +39,7 @@ Page { initializationPage.loading = false; welcomeColumn.visible = false; enterPinColumn.visible = true; + enterPinField.focus = true enterPasswordColumn.visible = false; waitRegistrationColumn.visible = false; break; @@ -60,6 +56,7 @@ Page { enterPinColumn.visible = false; enterPasswordColumn.visible = false; waitRegistrationColumn.visible = true; + pageHeader.title = qsTr("User Registration") break; default: // Nothing ;) @@ -73,6 +70,7 @@ Page { case TelegramAPI.WaitCode: initializationPage.loading = false; enterPinColumn.visible = true; + enterPinField.focus = true enterPasswordColumn.visible = false; waitRegistrationColumn.visible = false; break; @@ -103,21 +101,21 @@ Page { SilicaFlickable { id: welcomeFlickable - contentHeight: contentItem.childrenRect.height + contentHeight: content.height Behavior on contentHeight { NumberAnimation {} } anchors.fill: parent Column { - id: welcomeColumn + id: content width: parent.width - spacing: Theme.paddingSmall + spacing: Theme.paddingLarge PageHeader { + id: pageHeader title: qsTr("Welcome to Fernschreiber!") } Image { - id: fernschreiberImage source: "../../images/fernschreiber.png" anchors { horizontalCenter: parent.horizontalCenter @@ -125,374 +123,315 @@ Page { fillMode: Image.PreserveAspectFit asynchronous: true - width: 1/2 * parent.width + width: Math.min(2 * Theme.itemSizeHuge, Math.min(Screen.width, Screen.height) / 2) } - Label { - id: enterPhoneNumberLabel - wrapMode: Text.Wrap - x: Theme.horizontalPageMargin - width: parent.width - ( 2 * Theme.horizontalPageMargin ) - horizontalAlignment: Text.AlignHCenter - text: qsTr("Please enter your phone number to continue.") - font.pixelSize: Theme.fontSizeMedium - anchors { - horizontalCenter: parent.horizontalCenter - } + BusyLabel { + text: qsTr("Loading...") + running: initializationPage.loading + Behavior on opacity { FadeAnimation {} } + visible: opacity > 0 } - TextField { - id: phoneNumberTextField - placeholderText: "Use the international format, e.g. +4912342424242" - inputMethodHints: Qt.ImhDialableCharactersOnly - labelVisible: false + Column { + id: welcomeColumn width: parent.width - } + spacing: Theme.paddingLarge - Button { - id: continueWithPhoneNumberButton - text: qsTr("Continue") - anchors { - horizontalCenter: parent.horizontalCenter - } - enabled: phoneNumberTextField.text.match(/\+[1-9][0-9]{4,}/g) - onClicked: { - initializationPage.loading = true; - welcomeColumn.visible = false; - tdLibWrapper.setAuthenticationPhoneNumber(phoneNumberTextField.text); - } - } + Behavior on opacity { FadeAnimation {} } + opacity: visible ? 1 : 0 - } - - Column { - id: pinErrorColumn -// y: ( parent.height - ( errorInfoLabel.height + fernschreiberErrorImage.height + errorOkButton.height + ( 3 * Theme.paddingSmall ) ) ) / 2 - width: parent.width - spacing: Theme.paddingSmall - - Behavior on opacity { NumberAnimation {} } - opacity: visible ? 1 : 0 - visible: false - - Image { - id: fernschreiberErrorImage - source: "../../images/fernschreiber.png" - anchors { - horizontalCenter: parent.horizontalCenter + InfoLabel { + text: qsTr("Please enter your phone number to continue.") } - fillMode: Image.PreserveAspectFit - asynchronous: true - width: 1/2 * parent.width - } - - InfoLabel { - id: errorInfoLabel - font.pixelSize: Theme.fontSizeLarge - text: "" - } - - Button { - id: errorOkButton - text: qsTr("OK") - anchors { - horizontalCenter: parent.horizontalCenter - } - onClicked: { - pinErrorColumn.visible = false; - welcomeColumn.visible = true; - } - } - } - - Column { - id: enterPasswordColumn -// y: ( parent.height - ( fernschreiberPasswordImage.height + enterPasswordLabel.height + enterPasswordField.height + enterPasswordButton.height + ( 3 * Theme.paddingSmall ) ) ) / 2 - width: parent.width - spacing: Theme.paddingSmall - - Behavior on opacity { NumberAnimation {} } - opacity: visible ? 1.0 : 0.0 - visible: false - - Image { - id: fernschreiberPasswordImage - source: "../../images/fernschreiber.png" - anchors { - horizontalCenter: parent.horizontalCenter - } - - fillMode: Image.PreserveAspectFit - asynchronous: true - width: 1/2 * parent.width - } - - InfoLabel { - id: enterPasswordLabel - font.pixelSize: Theme.fontSizeLarge - text: qsTr("Please enter your password:") - } - - PasswordField { - id: enterPasswordField - anchors { - horizontalCenter: parent.horizontalCenter - } - font.pixelSize: Theme.fontSizeLarge - width: parent.width - 2 * Theme.horizontalPageMargin - horizontalAlignment: TextInput.AlignHCenter - } - - Button { - id: enterPasswordButton - text: qsTr("OK") - anchors { - horizontalCenter: parent.horizontalCenter - } - onClicked: { - initializationPage.loading = true; - enterPasswordColumn.visible = false; - tdLibWrapper.setAuthenticationPassword(enterPasswordField.text); - } - } - } - - Column { - id: enterPinColumn - topPadding: Theme.paddingLarge - bottomPadding: Theme.paddingLarge - width: parent.width - spacing: Theme.paddingSmall - - Behavior on opacity { NumberAnimation {} } - opacity: visible ? 1.0 : 0.0 - visible: false - - - Image { - id: fernschreiberPinImage - source: "../../images/fernschreiber.png" - anchors { - horizontalCenter: parent.horizontalCenter - } - - fillMode: Image.PreserveAspectFit - asynchronous: true - width: 1/2 * parent.width - } - - InfoLabel { - id: enterPinLabel - font.pixelSize: Theme.fontSizeLarge - text: qsTr("Please enter the code that you received:") - } - - TextField { - id: enterPinField - anchors { - horizontalCenter: parent.horizontalCenter - } - inputMethodHints: Qt.ImhDigitsOnly - font.pixelSize: Theme.fontSizeExtraLarge - width: parent.width - 4 * Theme.paddingLarge - horizontalAlignment: TextInput.AlignHCenter - } - - Button { - id: enterPinButton - text: qsTr("OK") - anchors { - horizontalCenter: parent.horizontalCenter - } - onClicked: { - initializationPage.loading = true; - enterPinColumn.visible = false; - tdLibWrapper.setAuthenticationCode(enterPinField.text); - } - } - } - - Column { - id: linkingErrorColumn - topPadding: Theme.paddingLarge - bottomPadding: Theme.paddingLarge - width: parent.width - spacing: Theme.paddingSmall - - Behavior on opacity { NumberAnimation {} } - opacity: visible ? 1.0 : 0.0 - visible: false - - Image { - id: fernschreiberLinkingErrorImage - source: "../../images/fernschreiber.png" - anchors { - horizontalCenter: parent.horizontalCenter - } - - fillMode: Image.PreserveAspectFit - asynchronous: true - width: 1/2 * parent.width - } - - InfoLabel { - id: linkingErrorInfoLabel - font.pixelSize: Theme.fontSizeLarge - text: qsTr("Unable to authenticate you with the entered code.") - } - - Button { - id: enterPinAgainButton - text: qsTr("Enter code again") - anchors { - horizontalCenter: parent.horizontalCenter - } - onClicked: { - linkingErrorColumn.visible = false; - enterPinColumn.visible = true; - } - } - - Button { - id: restartAuthenticationButton - text: qsTr("Restart authentication") - anchors { - horizontalCenter: parent.horizontalCenter - } - onClicked: { - linkingErrorColumn.visible = false; - welcomeColumn.visible = true; - } - } - } - - Column { - id: waitRegistrationColumn - topPadding: Theme.paddingLarge - bottomPadding: Theme.paddingLarge - width: parent.width - spacing: Theme.paddingLarge - - Behavior on opacity { NumberAnimation {} } - opacity: visible ? 1.0 : 0.0 - visible: false - - PageHeader { - title: qsTr("User Registration") - } - Image { - id: waitRegistrationImage - source: "../../images/fernschreiber.png" - anchors { - horizontalCenter: parent.horizontalCenter - } - - fillMode: Image.PreserveAspectFit - asynchronous: true - width: 1/2 * Screen.width - } - - InfoLabel { - id: waitRegistrationInfoLabel - property bool acknowledged - font.pixelSize: Theme.fontSizeExtraSmall - textFormat: Text.StyledText - horizontalAlignment: Text.AlignLeft - linkColor: Theme.primaryColor - property var stateText: initializationPage.authorizationStateData.authorization_state && initializationPage.authorizationStateData.authorization_state.terms_of_service ? - initializationPage.authorizationStateData.authorization_state.terms_of_service.text : - null - visible: !!stateText && !acknowledged - text: { - if(!stateText) { - return ''; + TextField { + id: phoneNumberTextField + placeholderText: "Use the international format, e.g. +4912342424242" + inputMethodHints: Qt.ImhDialableCharactersOnly + labelVisible: false + width: parent.width + readonly property bool validInput: text.match(/\+[1-9][0-9]{4,}/g) + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.enabled: validInput + EnterKey.onClicked: { + validator = filledValidator + if(acceptableInput) { + continueWithPhoneNumberButton.focus = true + continueWithPhoneNumberButton.enter() + } } - var entities = stateText.entities; - if(entities && entities.length > 0 && entities[0]["type"]["@type"] === "textEntityTypeTextUrl") { //we just use the first entity for now. - var offset = entities[0].offset; - var length = entities[0].length; - return (stateText.text.slice(0,entities[0].offset) - + "" - + stateText.text.slice(entities[0].offset, entities[0].offset + entities[0].length) - + '' - + stateText.text.slice(entities[0].offset + entities[0].length)).replace(/\n/gm, "
"); + } + + Button { + id: continueWithPhoneNumberButton + text: qsTr("Continue") + anchors { + horizontalCenter: parent.horizontalCenter + } + enabled: phoneNumberTextField.validInput + onClicked: enter() + function enter() { + initializationPage.loading = true; + welcomeColumn.visible = false; + tdLibWrapper.setAuthenticationPhoneNumber(phoneNumberTextField.text); } - return stateText.text.replace(/\n/gm, "
"); - - - } - //JSON.stringify(initializationPage.authorizationStateData, null, 2)//qsTr("Unable to authenticate you with the entered code.") - } - - Button { - id: acknowledgeTOCButton - visible: waitRegistrationInfoLabel.visible - text: qsTr("OK") - anchors { - horizontalCenter: parent.horizontalCenter - } - onClicked: { - waitRegistrationInfoLabel.acknowledged = true; - userFirstNameTextField.focus = true } } - RegExpValidator { - id: filledValidator - regExp: /.+/ - } - TextField { - id: userFirstNameTextField - visible: !waitRegistrationInfoLabel.visible - opacity: visible ? 1.0 : 0.0 - Behavior on opacity { NumberAnimation {} } - placeholderText: qsTr("Enter your First Name") - labelVisible: false + + Column { + id: pinErrorColumn width: parent.width - EnterKey.iconSource: !!text ? "image://theme/icon-m-enter-next" : "image://theme/icon-m-text-input" - EnterKey.onClicked: { - validator = filledValidator - if(acceptableInput) userLastNameTextField.focus = true; - } - } - TextField { - id: userLastNameTextField - visible: !waitRegistrationInfoLabel.visible - opacity: visible ? 1.0 : 0.0 - Behavior on opacity { NumberAnimation {} } - placeholderText: qsTr("Enter your Last Name") - labelVisible: false - width: parent.width - EnterKey.iconSource: !!text ? "image://theme/icon-m-enter-accept" : "image://theme/icon-m-text-input" - EnterKey.onClicked: { - validator = filledValidator - if(acceptableInput) registerUserButton.onClicked(null); - } - } - Button { - id: registerUserButton - visible: !waitRegistrationInfoLabel.visible - opacity: visible ? 1.0 : 0.0 - Behavior on opacity { NumberAnimation {} } - text: qsTr("Register User") - anchors { - horizontalCenter: parent.horizontalCenter - } - onClicked: { - userFirstNameTextField.validator = filledValidator - userLastNameTextField.validator = filledValidator - if(userFirstNameTextField.acceptableInput && userLastNameTextField.acceptableInput) { - tdLibWrapper.registerUser(userFirstNameTextField.text, userLastNameTextField.text); - } else if(!userFirstNameTextField.acceptableInput) { - userFirstNameTextField.focus = true; - } else { - userLastNameTextField.focus = true; - } + spacing: Theme.paddingLarge + Behavior on opacity { FadeAnimation {} } + opacity: visible ? 1 : 0 + visible: false + + InfoLabel { + id: errorInfoLabel + font.pixelSize: Theme.fontSizeLarge + text: "" } + + Button { + text: qsTr("OK") + anchors { + horizontalCenter: parent.horizontalCenter + } + onClicked: { + pinErrorColumn.visible = false; + welcomeColumn.visible = true; + } + } + } + + Column { + id: enterPasswordColumn + width: parent.width + spacing: Theme.paddingLarge + + Behavior on opacity { FadeAnimation {} } + opacity: visible ? 1.0 : 0.0 + visible: false + + InfoLabel { + font.pixelSize: Theme.fontSizeLarge + text: qsTr("Please enter your password:") + } + + PasswordField { + id: enterPasswordField + anchors { + horizontalCenter: parent.horizontalCenter + } + font.pixelSize: Theme.fontSizeLarge + width: parent.width - 2 * Theme.horizontalPageMargin + horizontalAlignment: TextInput.AlignHCenter + } + + Button { + text: qsTr("OK") + anchors { + horizontalCenter: parent.horizontalCenter + } + onClicked: { + initializationPage.loading = true; + enterPasswordColumn.visible = false; + tdLibWrapper.setAuthenticationPassword(enterPasswordField.text); + } + } + } + + Column { + id: enterPinColumn + width: parent.width + spacing: Theme.paddingLarge + + Behavior on opacity { FadeAnimation {} } + opacity: visible ? 1.0 : 0.0 + visible: false + + InfoLabel { + font.pixelSize: Theme.fontSizeLarge + text: qsTr("Please enter the code that you received:") + } + + TextField { + id: enterPinField + anchors { + horizontalCenter: parent.horizontalCenter + } + inputMethodHints: Qt.ImhDigitsOnly + font.pixelSize: Theme.fontSizeExtraLarge + width: parent.width - 4 * Theme.paddingLarge + horizontalAlignment: TextInput.AlignHCenter + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.enabled: text.length > 0 + EnterKey.onClicked: enterPinButton.enter() + } + + Button { + id: enterPinButton + text: qsTr("OK") + anchors { + horizontalCenter: parent.horizontalCenter + } + onClicked: enter() + function enter() { + initializationPage.loading = true; + enterPinColumn.visible = false; + tdLibWrapper.setAuthenticationCode(enterPinField.text); + } + } + } + + Column { + id: linkingErrorColumn + width: parent.width + spacing: Theme.paddingLarge + + Behavior on opacity { FadeAnimation {} } + opacity: visible ? 1.0 : 0.0 + visible: false + + InfoLabel { + font.pixelSize: Theme.fontSizeLarge + text: qsTr("Unable to authenticate you with the entered code.") + } + + Button { + text: qsTr("Enter code again") + anchors { + horizontalCenter: parent.horizontalCenter + } + onClicked: { + linkingErrorColumn.visible = false; + enterPinColumn.visible = true; + } + } + + Button { + text: qsTr("Restart authentication") + anchors { + horizontalCenter: parent.horizontalCenter + } + onClicked: { + linkingErrorColumn.visible = false; + welcomeColumn.visible = true; + } + } + } + + Column { + id: waitRegistrationColumn + width: parent.width + spacing: Theme.paddingLarge + + Behavior on opacity { FadeAnimation {} } + opacity: visible ? 1.0 : 0.0 + visible: false + + InfoLabel { + id: waitRegistrationInfoLabel + property bool acknowledged + font.pixelSize: Theme.fontSizeExtraSmall + textFormat: Text.StyledText + horizontalAlignment: Text.AlignLeft + linkColor: Theme.primaryColor + property var stateText: initializationPage.authorizationStateData.authorization_state && initializationPage.authorizationStateData.authorization_state.terms_of_service ? + initializationPage.authorizationStateData.authorization_state.terms_of_service.text : + null + visible: !!stateText && !acknowledged + text: { + if(!stateText) { + return ''; + } + var entities = stateText.entities; + if(entities && entities.length > 0 && entities[0]["type"]["@type"] === "textEntityTypeTextUrl") { //we just use the first entity for now. + var offset = entities[0].offset; + var length = entities[0].length; + return (stateText.text.slice(0,entities[0].offset) + + "" + + stateText.text.slice(entities[0].offset, entities[0].offset + entities[0].length) + + '' + + stateText.text.slice(entities[0].offset + entities[0].length)).replace(/\n/gm, "
"); + } + return stateText.text.replace(/\n/gm, "
"); + } + } + + Button { + visible: waitRegistrationInfoLabel.visible + text: qsTr("OK") + anchors { + horizontalCenter: parent.horizontalCenter + } + onClicked: { + waitRegistrationInfoLabel.acknowledged = true; + userFirstNameTextField.focus = true + } + } + RegExpValidator { + id: filledValidator + regExp: /.+/ + } + TextField { + id: userFirstNameTextField + visible: !waitRegistrationInfoLabel.visible + opacity: visible ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + placeholderText: qsTr("Enter your First Name") + labelVisible: false + width: parent.width + EnterKey.iconSource: !!text ? "image://theme/icon-m-enter-next" : "image://theme/icon-m-text-input" + EnterKey.onClicked: { + validator = filledValidator + if(acceptableInput) userLastNameTextField.focus = true; + } + } + TextField { + id: userLastNameTextField + visible: !waitRegistrationInfoLabel.visible + opacity: visible ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + placeholderText: qsTr("Enter your Last Name") + labelVisible: false + width: parent.width + EnterKey.iconSource: !!text ? "image://theme/icon-m-enter-accept" : "image://theme/icon-m-text-input" + EnterKey.onClicked: { + validator = filledValidator + if(acceptableInput) registerUserButton.onClicked(null); + } + } + Button { + id: registerUserButton + visible: !waitRegistrationInfoLabel.visible + opacity: visible ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + text: qsTr("Register User") + anchors { + horizontalCenter: parent.horizontalCenter + } + onClicked: { + userFirstNameTextField.validator = filledValidator + userLastNameTextField.validator = filledValidator + if(userFirstNameTextField.acceptableInput && userLastNameTextField.acceptableInput) { + tdLibWrapper.registerUser(userFirstNameTextField.text, userLastNameTextField.text); + } else if(!userFirstNameTextField.acceptableInput) { + userFirstNameTextField.focus = true; + } else { + userLastNameTextField.focus = true; + } + + } + } + } + + Item { + width: 1 + height: Theme.paddingLarge } } } - } - diff --git a/qml/pages/SettingsPage.qml b/qml/pages/SettingsPage.qml index 32cda43..f044f9c 100644 --- a/qml/pages/SettingsPage.qml +++ b/qml/pages/SettingsPage.qml @@ -18,9 +18,9 @@ */ import QtQuick 2.0 import Sailfish.Silica 1.0 +import WerkWolf.Fernschreiber 1.0 import "../js/functions.js" as Functions - Page { id: settingsPage allowedOrientations: Orientation.All @@ -52,6 +52,57 @@ Page { } } + ComboBox { + id: feedbackComboBox + label: qsTr("Notification feedback") + description: qsTr("Use non-graphical feedback (sound, vibration) for notifications") + menu: ContextMenu { + id: feedbackMenu + + MenuItem { + readonly property int value: AppSettings.NotificationFeedbackAll + text: qsTr("All events") + onClicked: { + appSettings.notificationFeedback = value + } + } + MenuItem { + readonly property int value: AppSettings.NotificationFeedbackNew + text: qsTr("Only new events") + onClicked: { + appSettings.notificationFeedback = value + } + } + MenuItem { + readonly property int value: AppSettings.NotificationFeedbackNone + text: qsTr("None") + onClicked: { + appSettings.notificationFeedback = value + } + } + } + + Component.onCompleted: updateFeedbackSelection() + + function updateFeedbackSelection() { + var menuItems = feedbackMenu.children + var n = menuItems.length + for (var i=0; i 0.2 +- Support for sending stickers +- Search for emojis from message input field, use : to start searching +- New icon/logo - thanks to iamnomeutente +- Option to customize non-graphical notification feedback - thanks to monich +- Add user registration - thanks to jgibbon +- Don't show irrelevant groups - thanks to monich +- Display information about forwarded messages +- Option to avoid displaying stickers like images - thanks to monich +- Fix: Don't reset edit/in-reply-to mode after focus change +- Fix: Display caption for documents +- Fix: Reserve some space for lazy loaded elements +- Fix: Don't display error message in case of repeated download of the same file +- New translations (Finnish, Italian, Russian), thanks to jorm1s, iamnomeutente, arustg and monich +- That was quite a lot - I hope I didn't forget anything. If I did, big sorry and please let me know! diff --git a/rpm/harbour-fernschreiber.spec b/rpm/harbour-fernschreiber.spec index fa78aed..2054851 100644 --- a/rpm/harbour-fernschreiber.spec +++ b/rpm/harbour-fernschreiber.spec @@ -24,6 +24,7 @@ BuildRequires: pkgconfig(Qt5Core) BuildRequires: pkgconfig(Qt5Qml) BuildRequires: pkgconfig(Qt5Quick) BuildRequires: pkgconfig(Qt5DBus) +BuildRequires: pkgconfig(Qt5Sql) BuildRequires: pkgconfig(nemonotifications-qt5) BuildRequires: pkgconfig(ngf-qt5) BuildRequires: desktop-file-utils diff --git a/rpm/harbour-fernschreiber.yaml b/rpm/harbour-fernschreiber.yaml index 0524663..21c163a 100644 --- a/rpm/harbour-fernschreiber.yaml +++ b/rpm/harbour-fernschreiber.yaml @@ -24,6 +24,7 @@ PkgConfigBR: - Qt5Qml - Qt5Quick - Qt5DBus + - Qt5Sql - nemonotifications-qt5 - ngf-qt5 diff --git a/src/appsettings.cpp b/src/appsettings.cpp index 3c3049e..b5c0561 100644 --- a/src/appsettings.cpp +++ b/src/appsettings.cpp @@ -23,6 +23,7 @@ namespace { const QString KEY_SEND_BY_ENTER("sendByEnter"); const QString KEY_SHOW_STICKERS_AS_IMAGES("showStickersAsImages"); + const QString KEY_NOTIFICATION_FEEDBACK("notificationFeedback"); } AppSettings::AppSettings(QObject *parent) : QObject(parent), settings("harbour-fernschreiber", "settings") @@ -56,3 +57,17 @@ void AppSettings::setShowStickersAsImages(bool showAsImages) emit showStickersAsImagesChanged(); } } + +AppSettings::NotificationFeedback AppSettings::notificationFeedback() const +{ + return (NotificationFeedback) settings.value(KEY_NOTIFICATION_FEEDBACK, (int) NotificationFeedbackAll).toInt(); +} + +void AppSettings::setNotificationFeedback(NotificationFeedback feedback) +{ + if (notificationFeedback() != feedback) { + LOG(KEY_NOTIFICATION_FEEDBACK << feedback); + settings.setValue(KEY_NOTIFICATION_FEEDBACK, (int) feedback); + emit notificationFeedbackChanged(); + } +} diff --git a/src/appsettings.h b/src/appsettings.h index 08af6eb..723c914 100644 --- a/src/appsettings.h +++ b/src/appsettings.h @@ -25,6 +25,15 @@ class AppSettings : public QObject { Q_OBJECT Q_PROPERTY(bool sendByEnter READ getSendByEnter WRITE setSendByEnter NOTIFY sendByEnterChanged) Q_PROPERTY(bool showStickersAsImages READ showStickersAsImages WRITE setShowStickersAsImages NOTIFY showStickersAsImagesChanged) + Q_PROPERTY(NotificationFeedback notificationFeedback READ notificationFeedback WRITE setNotificationFeedback NOTIFY notificationFeedbackChanged) + +public: + enum NotificationFeedback { + NotificationFeedbackNone, + NotificationFeedbackNew, + NotificationFeedbackAll + }; + Q_ENUM(NotificationFeedback) public: AppSettings(QObject *parent = Q_NULLPTR); @@ -35,9 +44,13 @@ public: bool showStickersAsImages() const; void setShowStickersAsImages(bool showAsImages); + NotificationFeedback notificationFeedback() const; + void setNotificationFeedback(NotificationFeedback feedback); + signals: void sendByEnterChanged(); void showStickersAsImagesChanged(); + void notificationFeedbackChanged(); private: QSettings settings; diff --git a/src/emojisearchworker.cpp b/src/emojisearchworker.cpp new file mode 100644 index 0000000..5e97d0b --- /dev/null +++ b/src/emojisearchworker.cpp @@ -0,0 +1,69 @@ +/* + Copyright (C) 2020 Sebastian J. Wolf + + This file is part of Fernschreiber. + + Fernschreiber is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fernschreiber is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Fernschreiber. If not, see . +*/ +#include "emojisearchworker.h" + +#define LOG(x) qDebug() << "[EmojiSearchWorker]" << x + +EmojiSearchWorker::~EmojiSearchWorker() +{ + LOG("Destroying myself..."); + database.close(); +} + +EmojiSearchWorker::EmojiSearchWorker(QObject *parent) : QThread(parent) +{ + LOG("Initializing Emoji database"); + QSqlDatabase::removeDatabase("emojis"); + database = QSqlDatabase::addDatabase("QSQLITE", "emojis"); + database.setDatabaseName("/usr/share/harbour-fernschreiber/db/emojis.db"); +} + +void EmojiSearchWorker::setParameters(const QString &queryString) +{ + this->queryString = queryString; +} + +void EmojiSearchWorker::performSearch() +{ + LOG("Performing emoji search" << this->queryString); + QVariantList resultList; + + if (database.open()) { + QSqlQuery query(database); + query.prepare("select * from emojis where description match (:queryString) limit 25"); + query.bindValue(":queryString", queryString + "*"); + query.exec(); + while (query.next()) { + if (isInterruptionRequested()) { + break; + } + QVariantMap foundEmoji; + foundEmoji.insert("file_name", query.value(0).toString()); + foundEmoji.insert("emoji", query.value(1).toString()); + foundEmoji.insert("emoji_version", query.value(2).toString()); + foundEmoji.insert("description", query.value(3).toString()); + resultList.append(foundEmoji); + } + database.close(); + } else { + LOG("Unable to perform a query on database" << database.lastError().databaseText()); + } + + emit searchCompleted(queryString, resultList); +} diff --git a/src/emojisearchworker.h b/src/emojisearchworker.h new file mode 100644 index 0000000..c728b07 --- /dev/null +++ b/src/emojisearchworker.h @@ -0,0 +1,52 @@ +/* + Copyright (C) 2020 Sebastian J. Wolf + + This file is part of Fernschreiber. + + Fernschreiber is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fernschreiber is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Fernschreiber. If not, see . +*/ +#ifndef EMOJISEARCHWORKER_H +#define EMOJISEARCHWORKER_H + +#include +#include +#include +#include +#include +#include + +class EmojiSearchWorker : public QThread +{ + Q_OBJECT + void run() Q_DECL_OVERRIDE { + performSearch(); + } +public: + ~EmojiSearchWorker() override; + explicit EmojiSearchWorker(QObject *parent = nullptr); + void setParameters(const QString &queryString); + +signals: + void searchCompleted(const QString &queryString, const QVariantList &resultList); + +public slots: + +private: + QSqlDatabase database; + QString queryString; + + void performSearch(); +}; + +#endif // EMOJISEARCHWORKER_H diff --git a/src/harbour-fernschreiber.cpp b/src/harbour-fernschreiber.cpp index b6ad42c..babb531 100644 --- a/src/harbour-fernschreiber.cpp +++ b/src/harbour-fernschreiber.cpp @@ -47,10 +47,11 @@ int main(int argc, char *argv[]) AppSettings *appSettings = new AppSettings(view.data()); context->setContextProperty("appSettings", appSettings); + qmlRegisterUncreatableType("WerkWolf.Fernschreiber", 1, 0, "AppSettings", QString()); TDLibWrapper *tdLibWrapper = new TDLibWrapper(view.data()); context->setContextProperty("tdLibWrapper", tdLibWrapper); - qmlRegisterType("WerkWolf.Fernschreiber", 1, 0, "TelegramAPI"); + qmlRegisterUncreatableType("WerkWolf.Fernschreiber", 1, 0, "TelegramAPI", QString()); DBusAdaptor *dBusAdaptor = tdLibWrapper->getDBusAdaptor(); context->setContextProperty("dBusAdaptor", dBusAdaptor); @@ -61,7 +62,7 @@ int main(int argc, char *argv[]) ChatModel chatModel(tdLibWrapper); context->setContextProperty("chatModel", &chatModel); - NotificationManager notificationManager(tdLibWrapper); + NotificationManager notificationManager(tdLibWrapper, appSettings); context->setContextProperty("notificationManager", ¬ificationManager); ProcessLauncher processLauncher; diff --git a/src/notificationmanager.cpp b/src/notificationmanager.cpp index f6ca9f3..1300c88 100644 --- a/src/notificationmanager.cpp +++ b/src/notificationmanager.cpp @@ -26,12 +26,15 @@ #include #include #include -#include -NotificationManager::NotificationManager(TDLibWrapper *tdLibWrapper, QObject *parent) : QObject(parent) +#define LOG(x) qDebug() << "[NotificationManager]" << x + +NotificationManager::NotificationManager(TDLibWrapper *tdLibWrapper, AppSettings *appSettings) : + mceInterface("com.nokia.mce", "/com/nokia/mce/request", "com.nokia.mce.request", QDBusConnection::systemBus()) { - qDebug() << "[NotificationManager] Initializing..."; + LOG("Initializing..."); this->tdLibWrapper = tdLibWrapper; + this->appSettings = appSettings; this->ngfClient = new Ngf::Client(this); connect(this->tdLibWrapper, SIGNAL(activeNotificationsUpdated(QVariantList)), this, SLOT(handleUpdateActiveNotifications(QVariantList))); @@ -44,29 +47,28 @@ NotificationManager::NotificationManager(TDLibWrapper *tdLibWrapper, QObject *pa connect(this->ngfClient, SIGNAL(eventPlaying(quint32)), this, SLOT(handleNgfEventPlaying(quint32))); if (this->ngfClient->connect()) { - qDebug() << "[NotificationManager] NGF Client successfully initialized..."; + LOG("NGF Client successfully initialized..."); } else { - qDebug() << "[NotificationManager] Failed to initialize NGF Client..."; + LOG("Failed to initialize NGF Client..."); } this->controlLedNotification(false); - } NotificationManager::~NotificationManager() { - qDebug() << "[NotificationManager] Destroying myself..."; + LOG("Destroying myself..."); } void NotificationManager::handleUpdateActiveNotifications(const QVariantList notificationGroups) { - qDebug() << "[NotificationManager] Received active notifications, number of groups:" << notificationGroups.size(); + LOG("Received active notifications, number of groups:" << notificationGroups.size()); } void NotificationManager::handleUpdateNotificationGroup(const QVariantMap notificationGroupUpdate) { QString notificationGroupId = notificationGroupUpdate.value("notification_group_id").toString(); - qDebug() << "[NotificationManager] Received notification group update, group ID:" << notificationGroupId; + LOG("Received notification group update, group ID:" << notificationGroupId); QVariantMap notificationGroup = this->notificationGroups.value(notificationGroupId).toMap(); QString chatId = notificationGroupUpdate.value("chat_id").toString(); @@ -92,7 +94,7 @@ void NotificationManager::handleUpdateNotificationGroup(const QVariantMap notifi // If we have deleted notifications, we need to update possibly existing ones if (!removedNotificationIds.isEmpty() && !activeNotifications.isEmpty()) { - qDebug() << "[NotificationManager] Some removals happend, but we have " << activeNotifications.size() << "existing notifications."; + LOG("Some removals happend, but we have" << activeNotifications.size() << "existing notifications."); QVariantMap firstActiveNotification = activeNotifications.first().toMap(); activeNotifications.remove(firstActiveNotification.value("id").toString()); QVariantMap newFirstActiveNotification = this->sendNotification(chatId, firstActiveNotification, activeNotifications); @@ -125,45 +127,43 @@ void NotificationManager::handleUpdateNotificationGroup(const QVariantMap notifi void NotificationManager::handleUpdateNotification(const QVariantMap updatedNotification) { - qDebug() << "[NotificationManager] Received notification update, group ID:" << updatedNotification.value("notification_group_id").toInt(); + LOG("Received notification update, group ID:" << updatedNotification.value("notification_group_id").toInt()); } void NotificationManager::handleChatDiscovered(const QString &chatId, const QVariantMap &chatInformation) { - this->chatListMutex.lock(); - qDebug() << "[NotificationManager] Adding chat to internal map " << chatId; + LOG("Adding chat to internal map" << chatId); this->chatMap.insert(chatId, chatInformation); - this->chatListMutex.unlock(); } void NotificationManager::handleNgfConnectionStatus(const bool &connected) { - qDebug() << "[NotificationManager] NGF Daemon connection status changed " << connected; + LOG("NGF Daemon connection status changed" << connected); } void NotificationManager::handleNgfEventFailed(const quint32 &eventId) { - qDebug() << "[NotificationManager] NGF event failed, id: " << eventId; + LOG("NGF event failed, id:" << eventId); } void NotificationManager::handleNgfEventCompleted(const quint32 &eventId) { - qDebug() << "[NotificationManager] NGF event completed, id: " << eventId; + LOG("NGF event completed, id:" << eventId); } void NotificationManager::handleNgfEventPlaying(const quint32 &eventId) { - qDebug() << "[NotificationManager] NGF event playing, id: " << eventId; + LOG("NGF event playing, id:" << eventId); } void NotificationManager::handleNgfEventPaused(const quint32 &eventId) { - qDebug() << "[NotificationManager] NGF event paused, id: " << eventId; + LOG("NGF event paused, id:" << eventId); } QVariantMap NotificationManager::sendNotification(const QString &chatId, const QVariantMap ¬ificationInformation, const QVariantMap &activeNotifications) { - qDebug() << "[NotificationManager] Sending notification" << notificationInformation.value("id").toString(); + LOG("Sending notification" << notificationInformation.value("id").toString()); QVariantMap chatInformation = this->chatMap.value(chatId).toMap(); QString chatType = chatInformation.value("type").toMap().value("@type").toString(); @@ -179,12 +179,15 @@ QVariantMap NotificationManager::sendNotification(const QString &chatId, const Q nemoNotification.setAppName("Fernschreiber"); nemoNotification.setAppIcon(appIconUrl.toLocalFile()); nemoNotification.setSummary(chatInformation.value("title").toString()); - nemoNotification.setCategory("x-nemo.messaging.im"); nemoNotification.setTimestamp(QDateTime::fromMSecsSinceEpoch(messageMap.value("date").toLongLong() * 1000)); QVariantList remoteActionArguments; remoteActionArguments.append(chatId); remoteActionArguments.append(messageMap.value("id").toString()); nemoNotification.setRemoteAction(Notification::remoteAction("default", "openMessage", "de.ygriega.fernschreiber", "/de/ygriega/fernschreiber", "de.ygriega.fernschreiber", "openMessage", remoteActionArguments)); + + bool needFeedback; + const AppSettings::NotificationFeedback feedbackStyle = appSettings->notificationFeedback(); + if (activeNotifications.isEmpty()) { QString notificationBody; if (addAuthor) { @@ -196,13 +199,19 @@ QVariantMap NotificationManager::sendNotification(const QString &chatId, const Q } notificationBody = notificationBody + this->getNotificationText(messageMap.value("content").toMap()); nemoNotification.setBody(notificationBody); + needFeedback = (feedbackStyle != AppSettings::NotificationFeedbackNone); } else { nemoNotification.setReplacesId(activeNotifications.first().toMap().value("replaces_id").toUInt()); nemoNotification.setBody(tr("%1 unread messages").arg(activeNotifications.size() + 1)); + needFeedback = (feedbackStyle == AppSettings::NotificationFeedbackAll); + } + + if (needFeedback) { + nemoNotification.setCategory("x-nemo.messaging.im"); + ngfClient->play("chat"); } nemoNotification.publish(); - this->ngfClient->play("chat"); this->controlLedNotification(true); updatedNotificationInformation.insert("replaces_id", nemoNotification.replacesId()); return updatedNotificationInformation; @@ -210,7 +219,7 @@ QVariantMap NotificationManager::sendNotification(const QString &chatId, const Q void NotificationManager::removeNotification(const QVariantMap ¬ificationInformation) { - qDebug() << "[NotificationManager] Removing notification" << notificationInformation.value("id").toString(); + LOG("Removing notification" << notificationInformation.value("id").toString()); Notification nemoNotification; nemoNotification.setReplacesId(notificationInformation.value("replaces_id").toUInt()); nemoNotification.close(); @@ -218,21 +227,17 @@ void NotificationManager::removeNotification(const QVariantMap ¬ificationInfo QString NotificationManager::getNotificationText(const QVariantMap ¬ificationContent) { - qDebug() << "[NotificationManager] Getting notification text from content" << notificationContent; + LOG("Getting notification text from content" << notificationContent); return FernschreiberUtils::getMessageShortText(notificationContent, false); } void NotificationManager::controlLedNotification(const bool &enabled) { - qDebug() << "[NotificationManager] Controlling notification LED" << enabled; - QDBusConnection dbusConnection = QDBusConnection::connectToBus(QDBusConnection::SystemBus, "system"); - QDBusInterface dbusInterface("com.nokia.mce", "/com/nokia/mce/request", "com.nokia.mce.request", dbusConnection); - - if (enabled) { - dbusInterface.call("req_led_pattern_activate", "PatternCommunicationIM"); - } else { - dbusInterface.call("req_led_pattern_deactivate", "PatternCommunicationIM"); - } + static const QString PATTERN("PatternCommunicationIM"); + static const QString ACTIVATE("req_led_pattern_activate"); + static const QString DEACTIVATE("req_led_pattern_deactivate"); + LOG("Controlling notification LED" << enabled); + mceInterface.call(enabled ? ACTIVATE : DEACTIVATE, PATTERN); } diff --git a/src/notificationmanager.h b/src/notificationmanager.h index 52c57b4..7e1c123 100644 --- a/src/notificationmanager.h +++ b/src/notificationmanager.h @@ -21,15 +21,16 @@ #define NOTIFICATIONMANAGER_H #include -#include +#include #include #include "tdlibwrapper.h" +#include "appsettings.h" class NotificationManager : public QObject { Q_OBJECT public: - explicit NotificationManager(TDLibWrapper *tdLibWrapper, QObject *parent = nullptr); + NotificationManager(TDLibWrapper *tdLibWrapper, AppSettings *appSettings); ~NotificationManager() override; signals: @@ -48,17 +49,20 @@ public slots: private: - TDLibWrapper *tdLibWrapper; - Ngf::Client *ngfClient; - QVariantMap chatMap; - QVariantMap notificationGroups; - QMutex chatListMutex; - QVariantMap sendNotification(const QString &chatId, const QVariantMap ¬ificationInformation, const QVariantMap &activeNotifications); void removeNotification(const QVariantMap ¬ificationInformation); QString getNotificationText(const QVariantMap ¬ificationContent); void controlLedNotification(const bool &enabled); +private: + + TDLibWrapper *tdLibWrapper; + AppSettings *appSettings; + Ngf::Client *ngfClient; + QVariantMap chatMap; + QVariantMap notificationGroups; + QDBusInterface mceInterface; + }; #endif // NOTIFICATIONMANAGER_H diff --git a/src/tdlibwrapper.cpp b/src/tdlibwrapper.cpp index cf85771..d694ad4 100644 --- a/src/tdlibwrapper.cpp +++ b/src/tdlibwrapper.cpp @@ -93,6 +93,8 @@ TDLibWrapper::TDLibWrapper(QObject *parent) : QObject(parent) connect(this->tdLibReceiver, SIGNAL(stickerSets(QVariantList)), this, SLOT(handleStickerSets(QVariantList))); connect(this->tdLibReceiver, SIGNAL(stickerSet(QVariantMap)), this, SLOT(handleStickerSet(QVariantMap))); + connect(&emojiSearchWorker, SIGNAL(searchCompleted(QString, QVariantList)), this, SLOT(handleEmojiSearchCompleted(QString, QVariantList))); + this->tdLibReceiver->start(); this->setLogVerbosityLevel(); @@ -467,6 +469,16 @@ void TDLibWrapper::getStickerSet(const QString &setId) this->sendRequest(requestObject); } +void TDLibWrapper::searchEmoji(const QString &queryString) +{ + LOG("Searching emoji" << queryString); + while (this->emojiSearchWorker.isRunning()) { + this->emojiSearchWorker.requestInterruption(); + } + this->emojiSearchWorker.setParameters(queryString); + this->emojiSearchWorker.start(); +} + QVariantMap TDLibWrapper::getUserInformation() { return this->userInformation; @@ -524,10 +536,14 @@ void TDLibWrapper::copyFileToDownloads(const QString &filePath) QFileInfo fileInfo(filePath); if (fileInfo.exists()) { QString downloadFilePath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/" + fileInfo.fileName(); - if (QFile::copy(filePath, downloadFilePath)) { + if (QFile::exists(downloadFilePath)) { emit copyToDownloadsSuccessful(fileInfo.fileName(), downloadFilePath); } else { - emit copyToDownloadsError(fileInfo.fileName(), downloadFilePath); + if (QFile::copy(filePath, downloadFilePath)) { + emit copyToDownloadsSuccessful(fileInfo.fileName(), downloadFilePath); + } else { + emit copyToDownloadsError(fileInfo.fileName(), downloadFilePath); + } } } else { emit copyToDownloadsError(fileInfo.fileName(), filePath); @@ -828,6 +844,12 @@ void TDLibWrapper::handleStickerSet(const QVariantMap &stickerSet) emit stickerSetReceived(stickerSet); } +void TDLibWrapper::handleEmojiSearchCompleted(const QString &queryString, const QVariantList &resultList) +{ + LOG("Emoji search completed" << queryString); + emit emojiSearchSuccessful(resultList); +} + void TDLibWrapper::setInitialParameters() { LOG("Sending initial parameters to TD Lib"); @@ -846,6 +868,7 @@ void TDLibWrapper::setInitialParameters() initialParameters.insert("device_model", hardwareSettings.value("NAME", "Unknown Mobile Device").toString()); initialParameters.insert("system_version", QSysInfo::prettyProductName()); initialParameters.insert("application_version", "0.3"); + // initialParameters.insert("use_test_dc", true); requestObject.insert("parameters", initialParameters); this->sendRequest(requestObject); } diff --git a/src/tdlibwrapper.h b/src/tdlibwrapper.h index a583505..6723470 100644 --- a/src/tdlibwrapper.h +++ b/src/tdlibwrapper.h @@ -24,6 +24,7 @@ #include "tdlibreceiver.h" #include "dbusadaptor.h" #include "dbusinterface.h" +#include "emojisearchworker.h" class TDLibWrapper : public QObject { @@ -129,6 +130,9 @@ public: Q_INVOKABLE void getInstalledStickerSets(); Q_INVOKABLE void getStickerSet(const QString &setId); + // Others (candidates for extraction ;)) + Q_INVOKABLE void searchEmoji(const QString &queryString); + public: const Group* getGroup(qlonglong groupId) const; static ChatMemberStatus chatMemberStatusFromString(const QString &status); @@ -169,6 +173,7 @@ signals: void installedStickerSetsUpdated(const QVariantList &stickerSetIds); void stickerSetsReceived(const QVariantList &stickerSets); void stickerSetReceived(const QVariantMap &stickerSet); + void emojiSearchSuccessful(const QVariantList &result); public slots: void handleVersionDetected(const QString &version); @@ -204,6 +209,7 @@ public slots: void handleInstalledStickerSetsUpdated(const QVariantList &stickerSetIds); void handleStickerSets(const QVariantList &stickerSets); void handleStickerSet(const QVariantMap &stickerSet); + void handleEmojiSearchCompleted(const QString &queryString, const QVariantList &resultList); private: void setInitialParameters(); @@ -228,6 +234,8 @@ private: QVariantMap unreadChatInformation; QHash basicGroups; QHash superGroups; + EmojiSearchWorker emojiSearchWorker; + }; #endif // TDLIBWRAPPER_H diff --git a/translations/harbour-fernschreiber-de.ts b/translations/harbour-fernschreiber-de.ts index 2af5735..90804d6 100644 --- a/translations/harbour-fernschreiber-de.ts +++ b/translations/harbour-fernschreiber-de.ts @@ -522,6 +522,26 @@ Show background for stickers and align them centrally like images Hintergrund für Sticker anzeigen und sie wie Bilder mittig platzieren. + + Notification feedback + Rückmeldung bei Hinweisen + + + All events + Alle Ereignisse + + + Only new events + Nur neue Ereignisse + + + None + Keine + + + Use non-graphical feedback (sound, vibration) for notifications + Nicht-grafische Rückmeldungen (Klänge, Vibration) bei Hinweisen nutzen + StickerPicker diff --git a/translations/harbour-fernschreiber-es.ts b/translations/harbour-fernschreiber-es.ts index 7dd6900..0398fdc 100644 --- a/translations/harbour-fernschreiber-es.ts +++ b/translations/harbour-fernschreiber-es.ts @@ -522,6 +522,26 @@ Show background for stickers and align them centrally like images Mostrar fondo para pegatinas y alinearlas centralmente como imágenes + + Notification feedback + Comentarios de notificación + + + All events + Todos los eventos + + + Only new events + Sólo nuevos eventos + + + None + Ninguno + + + Use non-graphical feedback (sound, vibration) for notifications + Usa comentarios no gráficos (sonido, vibración) para las notificaciones + StickerPicker diff --git a/translations/harbour-fernschreiber-fi.ts b/translations/harbour-fernschreiber-fi.ts index 4eba8f6..b03cc86 100644 --- a/translations/harbour-fernschreiber-fi.ts +++ b/translations/harbour-fernschreiber-fi.ts @@ -522,6 +522,26 @@ Show background for stickers and align them centrally like images + + Notification feedback + + + + All events + + + + Only new events + + + + None + + + + Use non-graphical feedback (sound, vibration) for notifications + + StickerPicker diff --git a/translations/harbour-fernschreiber-hu.ts b/translations/harbour-fernschreiber-hu.ts index c83c998..37d8ddf 100644 --- a/translations/harbour-fernschreiber-hu.ts +++ b/translations/harbour-fernschreiber-hu.ts @@ -522,6 +522,26 @@ Show background for stickers and align them centrally like images + + Notification feedback + + + + All events + + + + Only new events + + + + None + + + + Use non-graphical feedback (sound, vibration) for notifications + + StickerPicker diff --git a/translations/harbour-fernschreiber-it.ts b/translations/harbour-fernschreiber-it.ts index 6b9d7d9..f738048 100644 --- a/translations/harbour-fernschreiber-it.ts +++ b/translations/harbour-fernschreiber-it.ts @@ -522,6 +522,26 @@ Show background for stickers and align them centrally like images Mostra sfondo per gli sticker e centrali come le immagini. + + Notification feedback + + + + All events + + + + Only new events + + + + None + + + + Use non-graphical feedback (sound, vibration) for notifications + + StickerPicker diff --git a/translations/harbour-fernschreiber-pl.ts b/translations/harbour-fernschreiber-pl.ts index 4ec32e7..2bd2d43 100644 --- a/translations/harbour-fernschreiber-pl.ts +++ b/translations/harbour-fernschreiber-pl.ts @@ -522,6 +522,26 @@ Show background for stickers and align them centrally like images + + Notification feedback + + + + All events + + + + Only new events + + + + None + + + + Use non-graphical feedback (sound, vibration) for notifications + + StickerPicker diff --git a/translations/harbour-fernschreiber-ru.ts b/translations/harbour-fernschreiber-ru.ts index a5f8c8e..7b1f303 100644 --- a/translations/harbour-fernschreiber-ru.ts +++ b/translations/harbour-fernschreiber-ru.ts @@ -522,16 +522,36 @@ Show background for stickers and align them centrally like images То есть рисовать под ними фон и позиционировать по центру. + + Notification feedback + Звуки и вибрация + + + All events + На каждое событие + + + Only new events + Только на новые события + + + None + Никогда + + + Use non-graphical feedback (sound, vibration) for notifications + Сопровождать уведомления звуками и вибрацией. + StickerPicker Recently used - + Недавно использованные Loading stickers... - + Загрузка стикеров... diff --git a/translations/harbour-fernschreiber-zh_CN.ts b/translations/harbour-fernschreiber-zh_CN.ts index 59ecb00..25b5912 100644 --- a/translations/harbour-fernschreiber-zh_CN.ts +++ b/translations/harbour-fernschreiber-zh_CN.ts @@ -522,6 +522,26 @@ Show background for stickers and align them centrally like images + + Notification feedback + + + + All events + + + + Only new events + + + + None + + + + Use non-graphical feedback (sound, vibration) for notifications + + StickerPicker diff --git a/translations/harbour-fernschreiber.ts b/translations/harbour-fernschreiber.ts index acce341..9ba73fe 100644 --- a/translations/harbour-fernschreiber.ts +++ b/translations/harbour-fernschreiber.ts @@ -522,6 +522,26 @@ Show background for stickers and align them centrally like images + + Notification feedback + + + + All events + + + + Only new events + + + + None + + + + Use non-graphical feedback (sound, vibration) for notifications + + StickerPicker