Syncing my fork to the original repository

This commit is contained in:
Matteo 2020-10-19 17:26:29 +02:00 committed by GitHub
commit 1a0b6e0599
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1093 additions and 575 deletions

BIN
db/emojis.db Normal file

Binary file not shown.

View file

@ -16,7 +16,7 @@ CONFIG += sailfishapp sailfishapp_i18n
PKGCONFIG += nemonotifications-qt5 ngf-qt5 PKGCONFIG += nemonotifications-qt5 ngf-qt5
QT += core dbus QT += core dbus sql
SOURCES += src/harbour-fernschreiber.cpp \ SOURCES += src/harbour-fernschreiber.cpp \
src/appsettings.cpp \ src/appsettings.cpp \
@ -24,6 +24,7 @@ SOURCES += src/harbour-fernschreiber.cpp \
src/chatmodel.cpp \ src/chatmodel.cpp \
src/dbusadaptor.cpp \ src/dbusadaptor.cpp \
src/dbusinterface.cpp \ src/dbusinterface.cpp \
src/emojisearchworker.cpp \
src/fernschreiberutils.cpp \ src/fernschreiberutils.cpp \
src/notificationmanager.cpp \ src/notificationmanager.cpp \
src/processlauncher.cpp \ src/processlauncher.cpp \
@ -102,8 +103,11 @@ ICONPATH = /usr/share/icons/hicolor
fernschreiber.desktop.path = /usr/share/applications/ fernschreiber.desktop.path = /usr/share/applications/
fernschreiber.desktop.files = harbour-fernschreiber.desktop 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 \ INSTALLS += telegram 86.png 108.png 128.png 172.png 256.png \
fernschreiber.desktop gui images fernschreiber.desktop gui images database
HEADERS += \ HEADERS += \
src/appsettings.h \ src/appsettings.h \
@ -111,6 +115,7 @@ HEADERS += \
src/chatmodel.h \ src/chatmodel.h \
src/dbusadaptor.h \ src/dbusadaptor.h \
src/dbusinterface.h \ src/dbusinterface.h \
src/emojisearchworker.h \
src/fernschreiberutils.h \ src/fernschreiberutils.h \
src/notificationmanager.h \ src/notificationmanager.h \
src/processlauncher.h \ src/processlauncher.h \

View file

@ -31,6 +31,9 @@ Row {
property string myUserId; property string myUserId;
property variant inReplyToMessage; property variant inReplyToMessage;
property bool editable: false;
signal clearRequested()
onInReplyToMessageChanged: { onInReplyToMessageChanged: {
if (inReplyToMessage) { if (inReplyToMessage) {
@ -47,10 +50,14 @@ Row {
border.width: 0 border.width: 0
} }
Row {
width: parent.width - Theme.paddingSmall - inReplyToMessageRectangle.width
spacing: Theme.paddingSmall
Column { Column {
id: inReplyToMessageColumn id: inReplyToMessageColumn
spacing: Theme.paddingSmall spacing: Theme.paddingSmall
width: parent.width - Theme.paddingSmall - inReplyToMessageRectangle.width width: parent.width - ( inReplyToRow.editable ? ( Theme.paddingSmall + removeInReplyToIconButton.width ) : 0 )
Text { Text {
id: inReplyToUserText id: inReplyToUserText
@ -82,4 +89,14 @@ Row {
} }
} }
IconButton {
id: removeInReplyToIconButton
icon.source: "image://theme/icon-m-clear"
visible: inReplyToRow.editable
onClicked: {
inReplyToRow.clearRequested();
}
}
}
} }

View file

@ -74,30 +74,26 @@ Item {
elide: Text.ElideRight elide: Text.ElideRight
text: qsTr("Recently used") text: qsTr("Recently used")
} }
Flickable {
width: parent.width
height: recentStickersRow.height + Theme.paddingSmall
anchors.horizontalCenter: parent.horizontalCenter
contentWidth: recentStickersRow.width
clip: true
Row { SilicaGridView {
id: recentStickersRow id: recentStickersGridView
spacing: Theme.paddingMedium width: parent.width
Repeater { height: Theme.itemSizeExtraLarge
cellWidth: Theme.itemSizeExtraLarge;
cellHeight: Theme.itemSizeExtraLarge;
visible: count > 0
clip: true
flow: GridView.FlowTopToBottom
model: stickerPickerOverlayItem.recentStickers model: stickerPickerOverlayItem.recentStickers
Item { delegate: Item {
height: singleRecentStickerRow.height width: recentStickersGridView.cellWidth
width: singleRecentStickerRow.width height: recentStickersGridView.cellHeight
Row {
id: singleRecentStickerRow
spacing: Theme.paddingSmall
Image { Image {
source: modelData.thumbnail.photo.local.path source: modelData.thumbnail.photo.local.path
width: Theme.itemSizeExtraLarge anchors.fill: parent
height: Theme.itemSizeExtraLarge
asynchronous: true asynchronous: true
onStatusChanged: { onStatusChanged: {
if (status === Image.Ready) { if (status === Image.Ready) {
@ -105,7 +101,6 @@ Item {
} }
} }
} }
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -116,11 +111,13 @@ Item {
stickerPickerLoader.active = false; stickerPickerLoader.active = false;
} }
} }
}
} }
HorizontalScrollDecorator {}
} }
}
Repeater { Repeater {
model: stickerPickerOverlayItem.installedStickerSets model: stickerPickerOverlayItem.installedStickerSets
width: stickerPickerFlickable.width width: stickerPickerFlickable.width
@ -136,22 +133,22 @@ Item {
elide: Text.ElideRight elide: Text.ElideRight
text: modelData.title text: modelData.title
} }
Flickable {
SilicaGridView {
id: installedStickerSetGridView
width: parent.width width: parent.width
height: installedStickerSetRow.height + Theme.paddingSmall
anchors.horizontalCenter: parent.horizontalCenter
contentWidth: installedStickerSetRow.width
clip: true
Row {
id: installedStickerSetRow
spacing: Theme.paddingMedium
Repeater {
model: modelData.stickers
Item {
width: Theme.itemSizeExtraLarge
height: Theme.itemSizeExtraLarge height: Theme.itemSizeExtraLarge
cellWidth: Theme.itemSizeExtraLarge;
cellHeight: Theme.itemSizeExtraLarge;
visible: count > 0
clip: true
flow: GridView.FlowTopToBottom
model: modelData.stickers
delegate: Item {
width: installedStickerSetGridView.cellWidth
height: installedStickerSetGridView.cellHeight
Image { Image {
id: singleStickerImage id: singleStickerImage
source: modelData.thumbnail.photo.local.is_downloading_completed ? modelData.thumbnail.photo.local.path : "" source: modelData.thumbnail.photo.local.is_downloading_completed ? modelData.thumbnail.photo.local.path : ""
@ -184,9 +181,10 @@ Item {
} }
} }
} }
HorizontalScrollDecorator {}
} }
}
}
} }
} }
} }

View file

@ -17,37 +17,29 @@
along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>. along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.5 import QtQuick 2.5
import QtGraphicalEffects 1.0
import Sailfish.Silica 1.0 import Sailfish.Silica 1.0
Item { Item {
id: stickerPreviewItem
property variant stickerData; property variant stickerData;
property int usedFileId; property int usedFileId;
width: stickerData.width + Theme.paddingSmall width: stickerData.width
height: stickerData.height + Theme.paddingSmall height: stickerData.height
Component.onCompleted: { Component.onCompleted: {
updateSticker();
}
function updateSticker() {
if (stickerData) { if (stickerData) {
if (stickerData.is_animated) { if (stickerData.is_animated) {
// Use thumbnail until we can decode TGS files // Use thumbnail until we can decode TGS files
usedFileId = stickerData.thumbnail.photo.id; usedFileId = stickerData.thumbnail.photo.id;
if (stickerData.thumbnail.photo.local.is_downloading_completed) { if (stickerData.thumbnail.photo.local.is_downloading_completed) {
singleImage.source = stickerData.thumbnail.photo.local.path; stickerImage.source = stickerData.thumbnail.photo.local.path;
} else { } else {
tdLibWrapper.downloadFile(usedFileId); tdLibWrapper.downloadFile(usedFileId);
} }
} else { } else {
usedFileId = stickerData.sticker.id; usedFileId = stickerData.sticker.id;
if (stickerData.sticker.local.is_downloading_completed) { if (stickerData.sticker.local.is_downloading_completed) {
singleImage.source = stickerData.sticker.local.path; stickerImage.source = stickerData.sticker.local.path;
} else { } else {
tdLibWrapper.downloadFile(usedFileId); tdLibWrapper.downloadFile(usedFileId);
} }
@ -65,45 +57,43 @@ Item {
} else { } else {
stickerData.sticker = fileInformation; stickerData.sticker = fileInformation;
} }
singleImage.source = fileInformation.local.path; stickerImage.source = fileInformation.local.path;
} }
} }
} }
} }
Image { Image {
id: singleImage id: stickerImage
width: ( ( parent.width - Theme.paddingSmall ) >= stickerData.width ) ? stickerData.width : ( parent.width - Theme.paddingSmall ) anchors.fill: parent
height: ( ( parent.height - Theme.paddingSmall ) >= stickerData.height ) ? stickerData.height : ( parent.height - Theme.paddingSmall )
anchors.centerIn: parent
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectFit
autoTransform: true autoTransform: true
asynchronous: true asynchronous: true
visible: status === Image.Ready visible: opacity > 0
opacity: status === Image.Ready ? 1 : 0 opacity: status === Image.Ready ? 1 : 0
Behavior on opacity { NumberAnimation {} } Behavior on opacity { FadeAnimation {} }
MouseArea {
anchors.fill: parent
onClicked: {
//pageStack.push(Qt.resolvedUrl("../pages/ImagePage.qml"), { "photoData" : imagePreviewItem.photoData, "pictureFileInformation" : imagePreviewItem.pictureFileInformation });
}
}
} }
Loader {
anchors.fill: parent
sourceComponent: Component {
Image { Image {
id: imageLoadingBackgroundImage
source: "../../images/background-" + ( Theme.colorScheme ? "black" : "white" ) + "-small.png" 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 asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
opacity: 0.15 }
} }
active: opacity > 0
opacity: !stickerImage.visible && !placeHolderDelayTimer.running ? 0.15 : 0
Behavior on opacity { FadeAnimation {} }
}
Timer {
id: placeHolderDelayTimer
interval: 1000
running: true
}
} }

View file

@ -51,7 +51,7 @@ Page {
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
asynchronous: true asynchronous: true
width: (aboutPage.isPortrait ? aboutPage.width : aboutPage.height) / 2 width: Math.min(2 * Theme.itemSizeHuge, Math.min(aboutPage.width, aboutPage.height) / 2)
} }
Label { Label {

View file

@ -43,6 +43,7 @@ Page {
property variant chatPartnerInformation; property variant chatPartnerInformation;
property variant chatGroupInformation; property variant chatGroupInformation;
property int chatOnlineMemberCount: 0; property int chatOnlineMemberCount: 0;
property variant emojiProposals;
function updateChatPartnerStatusText() { function updateChatPartnerStatusText() {
if (chatPartnerInformation.status['@type'] === "userStatusEmpty" ) { if (chatPartnerInformation.status['@type'] === "userStatusEmpty" ) {
@ -174,6 +175,46 @@ Page {
} }
} }
controlSendButton(); 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: { Component.onCompleted: {
@ -227,6 +268,9 @@ Page {
uploadingProgressBar.maximumValue = fileInformation.size; uploadingProgressBar.maximumValue = fileInformation.size;
uploadingProgressBar.value = fileInformation.remote.uploaded_size; uploadingProgressBar.value = fileInformation.remote.uploaded_size;
} }
onEmojiSearchSuccessful: {
chatPage.emojiProposals = result;
}
} }
Connections { 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 { Timer {
id: chatContactTimeUpdater id: chatContactTimeUpdater
interval: 60000 interval: 60000
@ -1026,6 +1090,12 @@ Page {
} }
} }
editable: true
onClearRequested: {
newMessageInReplyToRow.inReplyToMessage = null;
}
id: newMessageInReplyToRow id: newMessageInReplyToRow
myUserId: chatPage.myUserId myUserId: chatPage.myUserId
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@ -1151,15 +1221,83 @@ Page {
} }
Text { Column {
id: emojiColumn
width: parent.width 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
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 id: editMessageText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.bold: true font.bold: true
text: qsTr("Edit Message") text: qsTr("Edit Message")
color: Theme.secondaryColor color: Theme.secondaryColor
visible: newMessageColumn.editMessageId !== "0" }
IconButton {
id: removeEditMessageIconButton
icon.source: "image://theme/icon-m-clear"
onClicked: {
newMessageColumn.editMessageId = "0";
newMessageTextField.text = "";
}
}
} }
Row { Row {
@ -1177,15 +1315,6 @@ Page {
labelVisible: false labelVisible: false
textLeftMargin: 0 textLeftMargin: 0
textTopMargin: 0 textTopMargin: 0
onFocusChanged: {
if (!focus) {
newMessageInReplyToRow.inReplyToMessage = null;
if (newMessageColumn.editMessageId !== "0") {
newMessageColumn.editMessageId = "0";
newMessageTextField.text = "";
}
}
}
EnterKey.onClicked: { EnterKey.onClicked: {
if (appSettings.sendByEnter) { if (appSettings.sendByEnter) {
sendMessage(); sendMessage();
@ -1199,6 +1328,7 @@ Page {
onTextChanged: { onTextChanged: {
controlSendButton(); controlSendButton();
textReplacementTimer.restart();
} }
} }

View file

@ -16,7 +16,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>. along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.6 import QtQuick 2.0
import Sailfish.Silica 1.0 import Sailfish.Silica 1.0
import WerkWolf.Fernschreiber 1.0 import WerkWolf.Fernschreiber 1.0
@ -29,11 +29,6 @@ Page {
property int authorizationState: TelegramAPI.Closed property int authorizationState: TelegramAPI.Closed
property var authorizationStateData: null property var authorizationStateData: null
BusyLabel {
text: qsTr("Loading...")
running: initializationPage.loading
}
Component.onCompleted: { Component.onCompleted: {
initializationPage.authorizationState = tdLibWrapper.getAuthorizationState(); initializationPage.authorizationState = tdLibWrapper.getAuthorizationState();
initializationPage.authorizationStateData = tdLibWrapper.getAuthorizationStateData(); initializationPage.authorizationStateData = tdLibWrapper.getAuthorizationStateData();
@ -44,6 +39,7 @@ Page {
initializationPage.loading = false; initializationPage.loading = false;
welcomeColumn.visible = false; welcomeColumn.visible = false;
enterPinColumn.visible = true; enterPinColumn.visible = true;
enterPinField.focus = true
enterPasswordColumn.visible = false; enterPasswordColumn.visible = false;
waitRegistrationColumn.visible = false; waitRegistrationColumn.visible = false;
break; break;
@ -60,6 +56,7 @@ Page {
enterPinColumn.visible = false; enterPinColumn.visible = false;
enterPasswordColumn.visible = false; enterPasswordColumn.visible = false;
waitRegistrationColumn.visible = true; waitRegistrationColumn.visible = true;
pageHeader.title = qsTr("User Registration")
break; break;
default: default:
// Nothing ;) // Nothing ;)
@ -73,6 +70,7 @@ Page {
case TelegramAPI.WaitCode: case TelegramAPI.WaitCode:
initializationPage.loading = false; initializationPage.loading = false;
enterPinColumn.visible = true; enterPinColumn.visible = true;
enterPinField.focus = true
enterPasswordColumn.visible = false; enterPasswordColumn.visible = false;
waitRegistrationColumn.visible = false; waitRegistrationColumn.visible = false;
break; break;
@ -103,21 +101,21 @@ Page {
SilicaFlickable { SilicaFlickable {
id: welcomeFlickable id: welcomeFlickable
contentHeight: contentItem.childrenRect.height contentHeight: content.height
Behavior on contentHeight { NumberAnimation {} } Behavior on contentHeight { NumberAnimation {} }
anchors.fill: parent anchors.fill: parent
Column { Column {
id: welcomeColumn id: content
width: parent.width width: parent.width
spacing: Theme.paddingSmall spacing: Theme.paddingLarge
PageHeader { PageHeader {
id: pageHeader
title: qsTr("Welcome to Fernschreiber!") title: qsTr("Welcome to Fernschreiber!")
} }
Image { Image {
id: fernschreiberImage
source: "../../images/fernschreiber.png" source: "../../images/fernschreiber.png"
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
@ -125,20 +123,26 @@ Page {
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
asynchronous: true asynchronous: true
width: 1/2 * parent.width width: Math.min(2 * Theme.itemSizeHuge, Math.min(Screen.width, Screen.height) / 2)
} }
Label { BusyLabel {
id: enterPhoneNumberLabel text: qsTr("Loading...")
wrapMode: Text.Wrap running: initializationPage.loading
x: Theme.horizontalPageMargin Behavior on opacity { FadeAnimation {} }
width: parent.width - ( 2 * Theme.horizontalPageMargin ) visible: opacity > 0
horizontalAlignment: Text.AlignHCenter
text: qsTr("Please enter your phone number to continue.")
font.pixelSize: Theme.fontSizeMedium
anchors {
horizontalCenter: parent.horizontalCenter
} }
Column {
id: welcomeColumn
width: parent.width
spacing: Theme.paddingLarge
Behavior on opacity { FadeAnimation {} }
opacity: visible ? 1 : 0
InfoLabel {
text: qsTr("Please enter your phone number to continue.")
} }
TextField { TextField {
@ -147,6 +151,16 @@ Page {
inputMethodHints: Qt.ImhDialableCharactersOnly inputMethodHints: Qt.ImhDialableCharactersOnly
labelVisible: false labelVisible: false
width: parent.width 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()
}
}
} }
Button { Button {
@ -155,38 +169,25 @@ Page {
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
} }
enabled: phoneNumberTextField.text.match(/\+[1-9][0-9]{4,}/g) enabled: phoneNumberTextField.validInput
onClicked: { onClicked: enter()
function enter() {
initializationPage.loading = true; initializationPage.loading = true;
welcomeColumn.visible = false; welcomeColumn.visible = false;
tdLibWrapper.setAuthenticationPhoneNumber(phoneNumberTextField.text); tdLibWrapper.setAuthenticationPhoneNumber(phoneNumberTextField.text);
} }
} }
} }
Column { Column {
id: pinErrorColumn id: pinErrorColumn
// y: ( parent.height - ( errorInfoLabel.height + fernschreiberErrorImage.height + errorOkButton.height + ( 3 * Theme.paddingSmall ) ) ) / 2
width: parent.width width: parent.width
spacing: Theme.paddingSmall spacing: Theme.paddingLarge
Behavior on opacity { NumberAnimation {} } Behavior on opacity { FadeAnimation {} }
opacity: visible ? 1 : 0 opacity: visible ? 1 : 0
visible: false visible: false
Image {
id: fernschreiberErrorImage
source: "../../images/fernschreiber.png"
anchors {
horizontalCenter: parent.horizontalCenter
}
fillMode: Image.PreserveAspectFit
asynchronous: true
width: 1/2 * parent.width
}
InfoLabel { InfoLabel {
id: errorInfoLabel id: errorInfoLabel
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
@ -194,7 +195,6 @@ Page {
} }
Button { Button {
id: errorOkButton
text: qsTr("OK") text: qsTr("OK")
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
@ -208,28 +208,14 @@ Page {
Column { Column {
id: enterPasswordColumn id: enterPasswordColumn
// y: ( parent.height - ( fernschreiberPasswordImage.height + enterPasswordLabel.height + enterPasswordField.height + enterPasswordButton.height + ( 3 * Theme.paddingSmall ) ) ) / 2
width: parent.width width: parent.width
spacing: Theme.paddingSmall spacing: Theme.paddingLarge
Behavior on opacity { NumberAnimation {} } Behavior on opacity { FadeAnimation {} }
opacity: visible ? 1.0 : 0.0 opacity: visible ? 1.0 : 0.0
visible: false visible: false
Image {
id: fernschreiberPasswordImage
source: "../../images/fernschreiber.png"
anchors {
horizontalCenter: parent.horizontalCenter
}
fillMode: Image.PreserveAspectFit
asynchronous: true
width: 1/2 * parent.width
}
InfoLabel { InfoLabel {
id: enterPasswordLabel
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
text: qsTr("Please enter your password:") text: qsTr("Please enter your password:")
} }
@ -245,7 +231,6 @@ Page {
} }
Button { Button {
id: enterPasswordButton
text: qsTr("OK") text: qsTr("OK")
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
@ -260,30 +245,14 @@ Page {
Column { Column {
id: enterPinColumn id: enterPinColumn
topPadding: Theme.paddingLarge
bottomPadding: Theme.paddingLarge
width: parent.width width: parent.width
spacing: Theme.paddingSmall spacing: Theme.paddingLarge
Behavior on opacity { NumberAnimation {} } Behavior on opacity { FadeAnimation {} }
opacity: visible ? 1.0 : 0.0 opacity: visible ? 1.0 : 0.0
visible: false visible: false
Image {
id: fernschreiberPinImage
source: "../../images/fernschreiber.png"
anchors {
horizontalCenter: parent.horizontalCenter
}
fillMode: Image.PreserveAspectFit
asynchronous: true
width: 1/2 * parent.width
}
InfoLabel { InfoLabel {
id: enterPinLabel
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
text: qsTr("Please enter the code that you received:") text: qsTr("Please enter the code that you received:")
} }
@ -297,6 +266,9 @@ Page {
font.pixelSize: Theme.fontSizeExtraLarge font.pixelSize: Theme.fontSizeExtraLarge
width: parent.width - 4 * Theme.paddingLarge width: parent.width - 4 * Theme.paddingLarge
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignHCenter
EnterKey.iconSource: "image://theme/icon-m-enter-next"
EnterKey.enabled: text.length > 0
EnterKey.onClicked: enterPinButton.enter()
} }
Button { Button {
@ -305,7 +277,8 @@ Page {
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
} }
onClicked: { onClicked: enter()
function enter() {
initializationPage.loading = true; initializationPage.loading = true;
enterPinColumn.visible = false; enterPinColumn.visible = false;
tdLibWrapper.setAuthenticationCode(enterPinField.text); tdLibWrapper.setAuthenticationCode(enterPinField.text);
@ -315,35 +288,19 @@ Page {
Column { Column {
id: linkingErrorColumn id: linkingErrorColumn
topPadding: Theme.paddingLarge
bottomPadding: Theme.paddingLarge
width: parent.width width: parent.width
spacing: Theme.paddingSmall spacing: Theme.paddingLarge
Behavior on opacity { NumberAnimation {} } Behavior on opacity { FadeAnimation {} }
opacity: visible ? 1.0 : 0.0 opacity: visible ? 1.0 : 0.0
visible: false visible: false
Image {
id: fernschreiberLinkingErrorImage
source: "../../images/fernschreiber.png"
anchors {
horizontalCenter: parent.horizontalCenter
}
fillMode: Image.PreserveAspectFit
asynchronous: true
width: 1/2 * parent.width
}
InfoLabel { InfoLabel {
id: linkingErrorInfoLabel
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
text: qsTr("Unable to authenticate you with the entered code.") text: qsTr("Unable to authenticate you with the entered code.")
} }
Button { Button {
id: enterPinAgainButton
text: qsTr("Enter code again") text: qsTr("Enter code again")
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
@ -355,7 +312,6 @@ Page {
} }
Button { Button {
id: restartAuthenticationButton
text: qsTr("Restart authentication") text: qsTr("Restart authentication")
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
@ -369,30 +325,13 @@ Page {
Column { Column {
id: waitRegistrationColumn id: waitRegistrationColumn
topPadding: Theme.paddingLarge
bottomPadding: Theme.paddingLarge
width: parent.width width: parent.width
spacing: Theme.paddingLarge spacing: Theme.paddingLarge
Behavior on opacity { NumberAnimation {} } Behavior on opacity { FadeAnimation {} }
opacity: visible ? 1.0 : 0.0 opacity: visible ? 1.0 : 0.0
visible: false 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 { InfoLabel {
id: waitRegistrationInfoLabel id: waitRegistrationInfoLabel
property bool acknowledged property bool acknowledged
@ -419,14 +358,10 @@ Page {
+ stateText.text.slice(entities[0].offset + entities[0].length)).replace(/\n/gm, "<br>"); + stateText.text.slice(entities[0].offset + entities[0].length)).replace(/\n/gm, "<br>");
} }
return stateText.text.replace(/\n/gm, "<br>"); return stateText.text.replace(/\n/gm, "<br>");
} }
//JSON.stringify(initializationPage.authorizationStateData, null, 2)//qsTr("Unable to authenticate you with the entered code.")
} }
Button { Button {
id: acknowledgeTOCButton
visible: waitRegistrationInfoLabel.visible visible: waitRegistrationInfoLabel.visible
text: qsTr("OK") text: qsTr("OK")
anchors { anchors {
@ -445,7 +380,7 @@ Page {
id: userFirstNameTextField id: userFirstNameTextField
visible: !waitRegistrationInfoLabel.visible visible: !waitRegistrationInfoLabel.visible
opacity: visible ? 1.0 : 0.0 opacity: visible ? 1.0 : 0.0
Behavior on opacity { NumberAnimation {} } Behavior on opacity { FadeAnimation {} }
placeholderText: qsTr("Enter your First Name") placeholderText: qsTr("Enter your First Name")
labelVisible: false labelVisible: false
width: parent.width width: parent.width
@ -459,7 +394,7 @@ Page {
id: userLastNameTextField id: userLastNameTextField
visible: !waitRegistrationInfoLabel.visible visible: !waitRegistrationInfoLabel.visible
opacity: visible ? 1.0 : 0.0 opacity: visible ? 1.0 : 0.0
Behavior on opacity { NumberAnimation {} } Behavior on opacity { FadeAnimation {} }
placeholderText: qsTr("Enter your Last Name") placeholderText: qsTr("Enter your Last Name")
labelVisible: false labelVisible: false
width: parent.width width: parent.width
@ -473,7 +408,7 @@ Page {
id: registerUserButton id: registerUserButton
visible: !waitRegistrationInfoLabel.visible visible: !waitRegistrationInfoLabel.visible
opacity: visible ? 1.0 : 0.0 opacity: visible ? 1.0 : 0.0
Behavior on opacity { NumberAnimation {} } Behavior on opacity { FadeAnimation {} }
text: qsTr("Register User") text: qsTr("Register User")
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
@ -492,7 +427,11 @@ Page {
} }
} }
} }
Item {
width: 1
height: Theme.paddingLarge
}
}
} }
} }

View file

@ -18,9 +18,9 @@
*/ */
import QtQuick 2.0 import QtQuick 2.0
import Sailfish.Silica 1.0 import Sailfish.Silica 1.0
import WerkWolf.Fernschreiber 1.0
import "../js/functions.js" as Functions import "../js/functions.js" as Functions
Page { Page {
id: settingsPage id: settingsPage
allowedOrientations: Orientation.All 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<n; i++) {
if (menuItems[i].value === appSettings.notificationFeedback) {
currentIndex = i
return
}
}
}
Connections {
target: appSettings
onNotificationFeedbackChanged: {
feedbackComboBox.updateFeedbackSelection()
}
}
}
SectionHeader { SectionHeader {
text: qsTr("Appearance") text: qsTr("Appearance")
} }

View file

@ -25,3 +25,19 @@
- Differentiate text between other people and current user ("You have..." vs. "Somebody has...") - Differentiate text between other people and current user ("You have..." vs. "Somebody has...")
- Support for the Jolla Tablet - Support for the Jolla Tablet
- New translations (Chinese, Hungarian, Polish, Spanish) - thanks to dashinfantry, edp17, atlochowski, GNUuser - New translations (Chinese, Hungarian, Polish, Spanish) - thanks to dashinfantry, edp17, atlochowski, GNUuser
* Sun Oct 18 2020 Sebastian J. Wolf <sebastian@ygriega.de> 0.2
- Support for sending stickers
- Search for emojis from message input field, use :<keyword> 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!

View file

@ -24,6 +24,7 @@ BuildRequires: pkgconfig(Qt5Core)
BuildRequires: pkgconfig(Qt5Qml) BuildRequires: pkgconfig(Qt5Qml)
BuildRequires: pkgconfig(Qt5Quick) BuildRequires: pkgconfig(Qt5Quick)
BuildRequires: pkgconfig(Qt5DBus) BuildRequires: pkgconfig(Qt5DBus)
BuildRequires: pkgconfig(Qt5Sql)
BuildRequires: pkgconfig(nemonotifications-qt5) BuildRequires: pkgconfig(nemonotifications-qt5)
BuildRequires: pkgconfig(ngf-qt5) BuildRequires: pkgconfig(ngf-qt5)
BuildRequires: desktop-file-utils BuildRequires: desktop-file-utils

View file

@ -24,6 +24,7 @@ PkgConfigBR:
- Qt5Qml - Qt5Qml
- Qt5Quick - Qt5Quick
- Qt5DBus - Qt5DBus
- Qt5Sql
- nemonotifications-qt5 - nemonotifications-qt5
- ngf-qt5 - ngf-qt5

View file

@ -23,6 +23,7 @@
namespace { namespace {
const QString KEY_SEND_BY_ENTER("sendByEnter"); const QString KEY_SEND_BY_ENTER("sendByEnter");
const QString KEY_SHOW_STICKERS_AS_IMAGES("showStickersAsImages"); const QString KEY_SHOW_STICKERS_AS_IMAGES("showStickersAsImages");
const QString KEY_NOTIFICATION_FEEDBACK("notificationFeedback");
} }
AppSettings::AppSettings(QObject *parent) : QObject(parent), settings("harbour-fernschreiber", "settings") AppSettings::AppSettings(QObject *parent) : QObject(parent), settings("harbour-fernschreiber", "settings")
@ -56,3 +57,17 @@ void AppSettings::setShowStickersAsImages(bool showAsImages)
emit showStickersAsImagesChanged(); 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();
}
}

View file

@ -25,6 +25,15 @@ class AppSettings : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool sendByEnter READ getSendByEnter WRITE setSendByEnter NOTIFY sendByEnterChanged) Q_PROPERTY(bool sendByEnter READ getSendByEnter WRITE setSendByEnter NOTIFY sendByEnterChanged)
Q_PROPERTY(bool showStickersAsImages READ showStickersAsImages WRITE setShowStickersAsImages NOTIFY showStickersAsImagesChanged) 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: public:
AppSettings(QObject *parent = Q_NULLPTR); AppSettings(QObject *parent = Q_NULLPTR);
@ -35,9 +44,13 @@ public:
bool showStickersAsImages() const; bool showStickersAsImages() const;
void setShowStickersAsImages(bool showAsImages); void setShowStickersAsImages(bool showAsImages);
NotificationFeedback notificationFeedback() const;
void setNotificationFeedback(NotificationFeedback feedback);
signals: signals:
void sendByEnterChanged(); void sendByEnterChanged();
void showStickersAsImagesChanged(); void showStickersAsImagesChanged();
void notificationFeedbackChanged();
private: private:
QSettings settings; QSettings settings;

69
src/emojisearchworker.cpp Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

52
src/emojisearchworker.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef EMOJISEARCHWORKER_H
#define EMOJISEARCHWORKER_H
#include <QThread>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QVariantList>
#include <QDebug>
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

View file

@ -47,10 +47,11 @@ int main(int argc, char *argv[])
AppSettings *appSettings = new AppSettings(view.data()); AppSettings *appSettings = new AppSettings(view.data());
context->setContextProperty("appSettings", appSettings); context->setContextProperty("appSettings", appSettings);
qmlRegisterUncreatableType<AppSettings>("WerkWolf.Fernschreiber", 1, 0, "AppSettings", QString());
TDLibWrapper *tdLibWrapper = new TDLibWrapper(view.data()); TDLibWrapper *tdLibWrapper = new TDLibWrapper(view.data());
context->setContextProperty("tdLibWrapper", tdLibWrapper); context->setContextProperty("tdLibWrapper", tdLibWrapper);
qmlRegisterType<TDLibWrapper>("WerkWolf.Fernschreiber", 1, 0, "TelegramAPI"); qmlRegisterUncreatableType<TDLibWrapper>("WerkWolf.Fernschreiber", 1, 0, "TelegramAPI", QString());
DBusAdaptor *dBusAdaptor = tdLibWrapper->getDBusAdaptor(); DBusAdaptor *dBusAdaptor = tdLibWrapper->getDBusAdaptor();
context->setContextProperty("dBusAdaptor", dBusAdaptor); context->setContextProperty("dBusAdaptor", dBusAdaptor);
@ -61,7 +62,7 @@ int main(int argc, char *argv[])
ChatModel chatModel(tdLibWrapper); ChatModel chatModel(tdLibWrapper);
context->setContextProperty("chatModel", &chatModel); context->setContextProperty("chatModel", &chatModel);
NotificationManager notificationManager(tdLibWrapper); NotificationManager notificationManager(tdLibWrapper, appSettings);
context->setContextProperty("notificationManager", &notificationManager); context->setContextProperty("notificationManager", &notificationManager);
ProcessLauncher processLauncher; ProcessLauncher processLauncher;

View file

@ -26,12 +26,15 @@
#include <QUrl> #include <QUrl>
#include <QDateTime> #include <QDateTime>
#include <QDBusConnection> #include <QDBusConnection>
#include <QDBusInterface>
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->tdLibWrapper = tdLibWrapper;
this->appSettings = appSettings;
this->ngfClient = new Ngf::Client(this); this->ngfClient = new Ngf::Client(this);
connect(this->tdLibWrapper, SIGNAL(activeNotificationsUpdated(QVariantList)), this, SLOT(handleUpdateActiveNotifications(QVariantList))); 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))); connect(this->ngfClient, SIGNAL(eventPlaying(quint32)), this, SLOT(handleNgfEventPlaying(quint32)));
if (this->ngfClient->connect()) { if (this->ngfClient->connect()) {
qDebug() << "[NotificationManager] NGF Client successfully initialized..."; LOG("NGF Client successfully initialized...");
} else { } else {
qDebug() << "[NotificationManager] Failed to initialize NGF Client..."; LOG("Failed to initialize NGF Client...");
} }
this->controlLedNotification(false); this->controlLedNotification(false);
} }
NotificationManager::~NotificationManager() NotificationManager::~NotificationManager()
{ {
qDebug() << "[NotificationManager] Destroying myself..."; LOG("Destroying myself...");
} }
void NotificationManager::handleUpdateActiveNotifications(const QVariantList notificationGroups) 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) void NotificationManager::handleUpdateNotificationGroup(const QVariantMap notificationGroupUpdate)
{ {
QString notificationGroupId = notificationGroupUpdate.value("notification_group_id").toString(); 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(); QVariantMap notificationGroup = this->notificationGroups.value(notificationGroupId).toMap();
QString chatId = notificationGroupUpdate.value("chat_id").toString(); 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 we have deleted notifications, we need to update possibly existing ones
if (!removedNotificationIds.isEmpty() && !activeNotifications.isEmpty()) { 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(); QVariantMap firstActiveNotification = activeNotifications.first().toMap();
activeNotifications.remove(firstActiveNotification.value("id").toString()); activeNotifications.remove(firstActiveNotification.value("id").toString());
QVariantMap newFirstActiveNotification = this->sendNotification(chatId, firstActiveNotification, activeNotifications); QVariantMap newFirstActiveNotification = this->sendNotification(chatId, firstActiveNotification, activeNotifications);
@ -125,45 +127,43 @@ void NotificationManager::handleUpdateNotificationGroup(const QVariantMap notifi
void NotificationManager::handleUpdateNotification(const QVariantMap updatedNotification) 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) void NotificationManager::handleChatDiscovered(const QString &chatId, const QVariantMap &chatInformation)
{ {
this->chatListMutex.lock(); LOG("Adding chat to internal map" << chatId);
qDebug() << "[NotificationManager] Adding chat to internal map " << chatId;
this->chatMap.insert(chatId, chatInformation); this->chatMap.insert(chatId, chatInformation);
this->chatListMutex.unlock();
} }
void NotificationManager::handleNgfConnectionStatus(const bool &connected) 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) 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) 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) 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) 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 &notificationInformation, const QVariantMap &activeNotifications) QVariantMap NotificationManager::sendNotification(const QString &chatId, const QVariantMap &notificationInformation, 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(); QVariantMap chatInformation = this->chatMap.value(chatId).toMap();
QString chatType = chatInformation.value("type").toMap().value("@type").toString(); 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.setAppName("Fernschreiber");
nemoNotification.setAppIcon(appIconUrl.toLocalFile()); nemoNotification.setAppIcon(appIconUrl.toLocalFile());
nemoNotification.setSummary(chatInformation.value("title").toString()); nemoNotification.setSummary(chatInformation.value("title").toString());
nemoNotification.setCategory("x-nemo.messaging.im");
nemoNotification.setTimestamp(QDateTime::fromMSecsSinceEpoch(messageMap.value("date").toLongLong() * 1000)); nemoNotification.setTimestamp(QDateTime::fromMSecsSinceEpoch(messageMap.value("date").toLongLong() * 1000));
QVariantList remoteActionArguments; QVariantList remoteActionArguments;
remoteActionArguments.append(chatId); remoteActionArguments.append(chatId);
remoteActionArguments.append(messageMap.value("id").toString()); remoteActionArguments.append(messageMap.value("id").toString());
nemoNotification.setRemoteAction(Notification::remoteAction("default", "openMessage", "de.ygriega.fernschreiber", "/de/ygriega/fernschreiber", "de.ygriega.fernschreiber", "openMessage", remoteActionArguments)); 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()) { if (activeNotifications.isEmpty()) {
QString notificationBody; QString notificationBody;
if (addAuthor) { if (addAuthor) {
@ -196,13 +199,19 @@ QVariantMap NotificationManager::sendNotification(const QString &chatId, const Q
} }
notificationBody = notificationBody + this->getNotificationText(messageMap.value("content").toMap()); notificationBody = notificationBody + this->getNotificationText(messageMap.value("content").toMap());
nemoNotification.setBody(notificationBody); nemoNotification.setBody(notificationBody);
needFeedback = (feedbackStyle != AppSettings::NotificationFeedbackNone);
} else { } else {
nemoNotification.setReplacesId(activeNotifications.first().toMap().value("replaces_id").toUInt()); nemoNotification.setReplacesId(activeNotifications.first().toMap().value("replaces_id").toUInt());
nemoNotification.setBody(tr("%1 unread messages").arg(activeNotifications.size() + 1)); 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(); nemoNotification.publish();
this->ngfClient->play("chat");
this->controlLedNotification(true); this->controlLedNotification(true);
updatedNotificationInformation.insert("replaces_id", nemoNotification.replacesId()); updatedNotificationInformation.insert("replaces_id", nemoNotification.replacesId());
return updatedNotificationInformation; return updatedNotificationInformation;
@ -210,7 +219,7 @@ QVariantMap NotificationManager::sendNotification(const QString &chatId, const Q
void NotificationManager::removeNotification(const QVariantMap &notificationInformation) void NotificationManager::removeNotification(const QVariantMap &notificationInformation)
{ {
qDebug() << "[NotificationManager] Removing notification" << notificationInformation.value("id").toString(); LOG("Removing notification" << notificationInformation.value("id").toString());
Notification nemoNotification; Notification nemoNotification;
nemoNotification.setReplacesId(notificationInformation.value("replaces_id").toUInt()); nemoNotification.setReplacesId(notificationInformation.value("replaces_id").toUInt());
nemoNotification.close(); nemoNotification.close();
@ -218,21 +227,17 @@ void NotificationManager::removeNotification(const QVariantMap &notificationInfo
QString NotificationManager::getNotificationText(const QVariantMap &notificationContent) QString NotificationManager::getNotificationText(const QVariantMap &notificationContent)
{ {
qDebug() << "[NotificationManager] Getting notification text from content" << notificationContent; LOG("Getting notification text from content" << notificationContent);
return FernschreiberUtils::getMessageShortText(notificationContent, false); return FernschreiberUtils::getMessageShortText(notificationContent, false);
} }
void NotificationManager::controlLedNotification(const bool &enabled) void NotificationManager::controlLedNotification(const bool &enabled)
{ {
qDebug() << "[NotificationManager] Controlling notification LED" << enabled; static const QString PATTERN("PatternCommunicationIM");
QDBusConnection dbusConnection = QDBusConnection::connectToBus(QDBusConnection::SystemBus, "system"); static const QString ACTIVATE("req_led_pattern_activate");
QDBusInterface dbusInterface("com.nokia.mce", "/com/nokia/mce/request", "com.nokia.mce.request", dbusConnection); static const QString DEACTIVATE("req_led_pattern_deactivate");
if (enabled) {
dbusInterface.call("req_led_pattern_activate", "PatternCommunicationIM");
} else {
dbusInterface.call("req_led_pattern_deactivate", "PatternCommunicationIM");
}
LOG("Controlling notification LED" << enabled);
mceInterface.call(enabled ? ACTIVATE : DEACTIVATE, PATTERN);
} }

View file

@ -21,15 +21,16 @@
#define NOTIFICATIONMANAGER_H #define NOTIFICATIONMANAGER_H
#include <QObject> #include <QObject>
#include <QMutex> #include <QDBusInterface>
#include <ngf-qt5/NgfClient> #include <ngf-qt5/NgfClient>
#include "tdlibwrapper.h" #include "tdlibwrapper.h"
#include "appsettings.h"
class NotificationManager : public QObject class NotificationManager : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit NotificationManager(TDLibWrapper *tdLibWrapper, QObject *parent = nullptr); NotificationManager(TDLibWrapper *tdLibWrapper, AppSettings *appSettings);
~NotificationManager() override; ~NotificationManager() override;
signals: signals:
@ -48,17 +49,20 @@ public slots:
private: private:
TDLibWrapper *tdLibWrapper;
Ngf::Client *ngfClient;
QVariantMap chatMap;
QVariantMap notificationGroups;
QMutex chatListMutex;
QVariantMap sendNotification(const QString &chatId, const QVariantMap &notificationInformation, const QVariantMap &activeNotifications); QVariantMap sendNotification(const QString &chatId, const QVariantMap &notificationInformation, const QVariantMap &activeNotifications);
void removeNotification(const QVariantMap &notificationInformation); void removeNotification(const QVariantMap &notificationInformation);
QString getNotificationText(const QVariantMap &notificationContent); QString getNotificationText(const QVariantMap &notificationContent);
void controlLedNotification(const bool &enabled); void controlLedNotification(const bool &enabled);
private:
TDLibWrapper *tdLibWrapper;
AppSettings *appSettings;
Ngf::Client *ngfClient;
QVariantMap chatMap;
QVariantMap notificationGroups;
QDBusInterface mceInterface;
}; };
#endif // NOTIFICATIONMANAGER_H #endif // NOTIFICATIONMANAGER_H

View file

@ -93,6 +93,8 @@ TDLibWrapper::TDLibWrapper(QObject *parent) : QObject(parent)
connect(this->tdLibReceiver, SIGNAL(stickerSets(QVariantList)), this, SLOT(handleStickerSets(QVariantList))); connect(this->tdLibReceiver, SIGNAL(stickerSets(QVariantList)), this, SLOT(handleStickerSets(QVariantList)));
connect(this->tdLibReceiver, SIGNAL(stickerSet(QVariantMap)), this, SLOT(handleStickerSet(QVariantMap))); 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->tdLibReceiver->start();
this->setLogVerbosityLevel(); this->setLogVerbosityLevel();
@ -467,6 +469,16 @@ void TDLibWrapper::getStickerSet(const QString &setId)
this->sendRequest(requestObject); 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() QVariantMap TDLibWrapper::getUserInformation()
{ {
return this->userInformation; return this->userInformation;
@ -524,11 +536,15 @@ void TDLibWrapper::copyFileToDownloads(const QString &filePath)
QFileInfo fileInfo(filePath); QFileInfo fileInfo(filePath);
if (fileInfo.exists()) { if (fileInfo.exists()) {
QString downloadFilePath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/" + fileInfo.fileName(); QString downloadFilePath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/" + fileInfo.fileName();
if (QFile::exists(downloadFilePath)) {
emit copyToDownloadsSuccessful(fileInfo.fileName(), downloadFilePath);
} else {
if (QFile::copy(filePath, downloadFilePath)) { if (QFile::copy(filePath, downloadFilePath)) {
emit copyToDownloadsSuccessful(fileInfo.fileName(), downloadFilePath); emit copyToDownloadsSuccessful(fileInfo.fileName(), downloadFilePath);
} else { } else {
emit copyToDownloadsError(fileInfo.fileName(), downloadFilePath); emit copyToDownloadsError(fileInfo.fileName(), downloadFilePath);
} }
}
} else { } else {
emit copyToDownloadsError(fileInfo.fileName(), filePath); emit copyToDownloadsError(fileInfo.fileName(), filePath);
} }
@ -828,6 +844,12 @@ void TDLibWrapper::handleStickerSet(const QVariantMap &stickerSet)
emit stickerSetReceived(stickerSet); emit stickerSetReceived(stickerSet);
} }
void TDLibWrapper::handleEmojiSearchCompleted(const QString &queryString, const QVariantList &resultList)
{
LOG("Emoji search completed" << queryString);
emit emojiSearchSuccessful(resultList);
}
void TDLibWrapper::setInitialParameters() void TDLibWrapper::setInitialParameters()
{ {
LOG("Sending initial parameters to TD Lib"); 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("device_model", hardwareSettings.value("NAME", "Unknown Mobile Device").toString());
initialParameters.insert("system_version", QSysInfo::prettyProductName()); initialParameters.insert("system_version", QSysInfo::prettyProductName());
initialParameters.insert("application_version", "0.3"); initialParameters.insert("application_version", "0.3");
// initialParameters.insert("use_test_dc", true);
requestObject.insert("parameters", initialParameters); requestObject.insert("parameters", initialParameters);
this->sendRequest(requestObject); this->sendRequest(requestObject);
} }

View file

@ -24,6 +24,7 @@
#include "tdlibreceiver.h" #include "tdlibreceiver.h"
#include "dbusadaptor.h" #include "dbusadaptor.h"
#include "dbusinterface.h" #include "dbusinterface.h"
#include "emojisearchworker.h"
class TDLibWrapper : public QObject class TDLibWrapper : public QObject
{ {
@ -129,6 +130,9 @@ public:
Q_INVOKABLE void getInstalledStickerSets(); Q_INVOKABLE void getInstalledStickerSets();
Q_INVOKABLE void getStickerSet(const QString &setId); Q_INVOKABLE void getStickerSet(const QString &setId);
// Others (candidates for extraction ;))
Q_INVOKABLE void searchEmoji(const QString &queryString);
public: public:
const Group* getGroup(qlonglong groupId) const; const Group* getGroup(qlonglong groupId) const;
static ChatMemberStatus chatMemberStatusFromString(const QString &status); static ChatMemberStatus chatMemberStatusFromString(const QString &status);
@ -169,6 +173,7 @@ signals:
void installedStickerSetsUpdated(const QVariantList &stickerSetIds); void installedStickerSetsUpdated(const QVariantList &stickerSetIds);
void stickerSetsReceived(const QVariantList &stickerSets); void stickerSetsReceived(const QVariantList &stickerSets);
void stickerSetReceived(const QVariantMap &stickerSet); void stickerSetReceived(const QVariantMap &stickerSet);
void emojiSearchSuccessful(const QVariantList &result);
public slots: public slots:
void handleVersionDetected(const QString &version); void handleVersionDetected(const QString &version);
@ -204,6 +209,7 @@ public slots:
void handleInstalledStickerSetsUpdated(const QVariantList &stickerSetIds); void handleInstalledStickerSetsUpdated(const QVariantList &stickerSetIds);
void handleStickerSets(const QVariantList &stickerSets); void handleStickerSets(const QVariantList &stickerSets);
void handleStickerSet(const QVariantMap &stickerSet); void handleStickerSet(const QVariantMap &stickerSet);
void handleEmojiSearchCompleted(const QString &queryString, const QVariantList &resultList);
private: private:
void setInitialParameters(); void setInitialParameters();
@ -228,6 +234,8 @@ private:
QVariantMap unreadChatInformation; QVariantMap unreadChatInformation;
QHash<qlonglong,Group*> basicGroups; QHash<qlonglong,Group*> basicGroups;
QHash<qlonglong,Group*> superGroups; QHash<qlonglong,Group*> superGroups;
EmojiSearchWorker emojiSearchWorker;
}; };
#endif // TDLIBWRAPPER_H #endif // TDLIBWRAPPER_H

View file

@ -522,6 +522,26 @@
<source>Show background for stickers and align them centrally like images</source> <source>Show background for stickers and align them centrally like images</source>
<translation>Hintergrund für Sticker anzeigen und sie wie Bilder mittig platzieren.</translation> <translation>Hintergrund für Sticker anzeigen und sie wie Bilder mittig platzieren.</translation>
</message> </message>
<message>
<source>Notification feedback</source>
<translation>Rückmeldung bei Hinweisen</translation>
</message>
<message>
<source>All events</source>
<translation>Alle Ereignisse</translation>
</message>
<message>
<source>Only new events</source>
<translation>Nur neue Ereignisse</translation>
</message>
<message>
<source>None</source>
<translation>Keine</translation>
</message>
<message>
<source>Use non-graphical feedback (sound, vibration) for notifications</source>
<translation>Nicht-grafische Rückmeldungen (Klänge, Vibration) bei Hinweisen nutzen</translation>
</message>
</context> </context>
<context> <context>
<name>StickerPicker</name> <name>StickerPicker</name>

View file

@ -522,6 +522,26 @@
<source>Show background for stickers and align them centrally like images</source> <source>Show background for stickers and align them centrally like images</source>
<translation>Mostrar fondo para pegatinas y alinearlas centralmente como imágenes</translation> <translation>Mostrar fondo para pegatinas y alinearlas centralmente como imágenes</translation>
</message> </message>
<message>
<source>Notification feedback</source>
<translation>Comentarios de notificación</translation>
</message>
<message>
<source>All events</source>
<translation>Todos los eventos</translation>
</message>
<message>
<source>Only new events</source>
<translation>Sólo nuevos eventos</translation>
</message>
<message>
<source>None</source>
<translation>Ninguno </translation>
</message>
<message>
<source>Use non-graphical feedback (sound, vibration) for notifications</source>
<translation>Usa comentarios no gráficos (sonido, vibración) para las notificaciones</translation>
</message>
</context> </context>
<context> <context>
<name>StickerPicker</name> <name>StickerPicker</name>

View file

@ -522,6 +522,26 @@
<source>Show background for stickers and align them centrally like images</source> <source>Show background for stickers and align them centrally like images</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Notification feedback</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>All events</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Only new events</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>None</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use non-graphical feedback (sound, vibration) for notifications</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>StickerPicker</name> <name>StickerPicker</name>

View file

@ -522,6 +522,26 @@
<source>Show background for stickers and align them centrally like images</source> <source>Show background for stickers and align them centrally like images</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Notification feedback</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>All events</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Only new events</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>None</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use non-graphical feedback (sound, vibration) for notifications</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>StickerPicker</name> <name>StickerPicker</name>

View file

@ -522,6 +522,26 @@
<source>Show background for stickers and align them centrally like images</source> <source>Show background for stickers and align them centrally like images</source>
<translation>Mostra sfondo per gli sticker e centrali come le immagini.</translation> <translation>Mostra sfondo per gli sticker e centrali come le immagini.</translation>
</message> </message>
<message>
<source>Notification feedback</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>All events</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Only new events</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>None</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use non-graphical feedback (sound, vibration) for notifications</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>StickerPicker</name> <name>StickerPicker</name>

View file

@ -522,6 +522,26 @@
<source>Show background for stickers and align them centrally like images</source> <source>Show background for stickers and align them centrally like images</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Notification feedback</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>All events</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Only new events</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>None</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use non-graphical feedback (sound, vibration) for notifications</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>StickerPicker</name> <name>StickerPicker</name>

View file

@ -522,16 +522,36 @@
<source>Show background for stickers and align them centrally like images</source> <source>Show background for stickers and align them centrally like images</source>
<translation>То есть рисовать под ними фон и позиционировать по центру.</translation> <translation>То есть рисовать под ними фон и позиционировать по центру.</translation>
</message> </message>
<message>
<source>Notification feedback</source>
<translation>Звуки и вибрация</translation>
</message>
<message>
<source>All events</source>
<translation>На каждое событие</translation>
</message>
<message>
<source>Only new events</source>
<translation>Только на новые события</translation>
</message>
<message>
<source>None</source>
<translation>Никогда</translation>
</message>
<message>
<source>Use non-graphical feedback (sound, vibration) for notifications</source>
<translation>Сопровождать уведомления звуками и вибрацией.</translation>
</message>
</context> </context>
<context> <context>
<name>StickerPicker</name> <name>StickerPicker</name>
<message> <message>
<source>Recently used</source> <source>Recently used</source>
<translation type="unfinished"></translation> <translation>Недавно использованные</translation>
</message> </message>
<message> <message>
<source>Loading stickers...</source> <source>Loading stickers...</source>
<translation type="unfinished"></translation> <translation>Загрузка стикеров...</translation>
</message> </message>
</context> </context>
<context> <context>

View file

@ -522,6 +522,26 @@
<source>Show background for stickers and align them centrally like images</source> <source>Show background for stickers and align them centrally like images</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Notification feedback</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>All events</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Only new events</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>None</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use non-graphical feedback (sound, vibration) for notifications</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>StickerPicker</name> <name>StickerPicker</name>

View file

@ -522,6 +522,26 @@
<source>Show background for stickers and align them centrally like images</source> <source>Show background for stickers and align them centrally like images</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Notification feedback</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>All events</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Only new events</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>None</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Use non-graphical feedback (sound, vibration) for notifications</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>StickerPicker</name> <name>StickerPicker</name>