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 @@
Hintergrund für Sticker anzeigen und sie wie Bilder mittig platzieren.
+
+
+ Rückmeldung bei Hinweisen
+
+
+
+ Alle Ereignisse
+
+
+
+ Nur neue Ereignisse
+
+
+
+ Keine
+
+
+
+ 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 @@
Mostrar fondo para pegatinas y alinearlas centralmente como imágenes
+
+
+ Comentarios de notificación
+
+
+
+ Todos los eventos
+
+
+
+ Sólo nuevos eventos
+
+
+
+ Ninguno
+
+
+
+ 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
Mostra sfondo per gli sticker e centrali come le immagini.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
То есть рисовать под ними фон и позиционировать по центру.
+
+
+ Звуки и вибрация
+
+
+
+ На каждое событие
+
+
+
+ Только на новые события
+
+
+
+ Никогда
+
+
+
+ Сопровождать уведомления звуками и вибрацией.
+
StickerPicker
-
+ Недавно использованные
-
+ Загрузка стикеров...
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
StickerPicker