diff --git a/harbour-fernschreiber.pro b/harbour-fernschreiber.pro
index f751a28..a99ffc0 100644
--- a/harbour-fernschreiber.pro
+++ b/harbour-fernschreiber.pro
@@ -16,7 +16,7 @@ CONFIG += sailfishapp sailfishapp_i18n
PKGCONFIG += nemonotifications-qt5 zlib
-QT += core dbus sql
+QT += core dbus sql multimedia positioning
DEFINES += QT_STATICPLUGIN
@@ -58,6 +58,7 @@ DISTFILES += qml/harbour-fernschreiber.qml \
qml/components/ReplyMarkupButtons.qml \
qml/components/StickerPicker.qml \
qml/components/PhotoTextsListItem.qml \
+ qml/components/VoiceNoteOverlay.qml \
qml/components/WebPagePreview.qml \
qml/components/chatInformationPage/ChatInformationEditArea.qml \
qml/components/chatInformationPage/ChatInformationPageContent.qml \
diff --git a/images/icon-s-pin.svg b/images/icon-s-pin.svg
new file mode 100644
index 0000000..80dfab8
--- /dev/null
+++ b/images/icon-s-pin.svg
@@ -0,0 +1,26 @@
+
+
\ No newline at end of file
diff --git a/qml/components/AudioPreview.qml b/qml/components/AudioPreview.qml
index e37c8ac..b8a7d17 100644
--- a/qml/components/AudioPreview.qml
+++ b/qml/components/AudioPreview.qml
@@ -444,7 +444,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: positionText.top
minimumValue: 0
- maximumValue: messageAudio.duration ? messageAudio.duration : 0
+ maximumValue: messageAudio.duration ? messageAudio.duration : 0.1
stepSize: 1
value: messageAudio.position
enabled: messageAudio.seekable
diff --git a/qml/components/ChatListViewItem.qml b/qml/components/ChatListViewItem.qml
index b01c487..684f02a 100644
--- a/qml/components/ChatListViewItem.qml
+++ b/qml/components/ChatListViewItem.qml
@@ -9,13 +9,14 @@ PhotoTextsListItem {
id: listItem
pictureThumbnail {
photoData: photo_small || ({})
+ highlighted: listItem.highlighted && !listItem.menuOpen
}
property int ownUserId
property bool showDraft: !!draft_message_text && draft_message_date > last_message_date
property string previewText: showDraft ? draft_message_text : last_message_text
// chat title
- primaryText.text: title ? Emoji.emojify(title + ( display.notification_settings.mute_for > 0 ? " 🔇" : "" ), Theme.fontSizeMedium) : qsTr("Unknown")
+ primaryText.text: title ? Emoji.emojify(title, Theme.fontSizeMedium) : qsTr("Unknown")
// last user
prologSecondaryText.text: showDraft ? ""+qsTr("Draft")+"" : (is_channel ? "" : ( last_message_sender_id ? ( last_message_sender_id !== ownUserId ? Emoji.emojify(Functions.getUserName(tdLibWrapper.getUserInformation(last_message_sender_id)), primaryText.font.pixelSize) : qsTr("You") ) : "" ))
// last message
@@ -25,6 +26,8 @@ PhotoTextsListItem {
unreadCount: unread_count
isSecret: ( chat_type === TelegramAPI.ChatTypeSecret )
isMarkedAsUnread: is_marked_as_unread
+ isPinned: is_pinned
+ isMuted: display.notification_settings.mute_for > 0
openMenuOnPressAndHold: true//chat_id != overviewPage.ownUserId
@@ -54,19 +57,18 @@ PhotoTextsListItem {
}
MenuItem {
- visible: unread_count === 0 && !is_marked_as_unread
+ visible: unread_count === 0
onClicked: {
- tdLibWrapper.toggleChatIsMarkedAsUnread(chat_id, true);
+ tdLibWrapper.toggleChatIsMarkedAsUnread(chat_id, !is_marked_as_unread);
}
- text: qsTr("Mark chat as unread")
+ text: is_marked_as_unread ? qsTr("Mark chat as read") : qsTr("Mark chat as unread")
}
MenuItem {
- visible: unread_count === 0 && is_marked_as_unread
onClicked: {
- tdLibWrapper.toggleChatIsMarkedAsUnread(chat_id, false);
+ tdLibWrapper.toggleChatIsPinned(chat_id, !is_pinned);
}
- text: qsTr("Mark chat as read")
+ text: is_pinned ? qsTr("Unpin chat") : qsTr("Pin chat")
}
MenuItem {
@@ -81,7 +83,7 @@ PhotoTextsListItem {
newNotificationSettings.use_default_mute_for = false;
tdLibWrapper.setChatNotificationSettings(chat_id, newNotificationSettings);
}
- text: display.notification_settings.mute_for > 0 ? qsTr("Unmute Chat") : qsTr("Mute Chat")
+ text: display.notification_settings.mute_for > 0 ? qsTr("Unmute chat") : qsTr("Mute chat")
}
MenuItem {
diff --git a/qml/components/DocumentPreview.qml b/qml/components/DocumentPreview.qml
index df5a6f1..fa1e7cf 100644
--- a/qml/components/DocumentPreview.qml
+++ b/qml/components/DocumentPreview.qml
@@ -41,9 +41,9 @@ Item {
if (documentData) {
if (documentData.document.local.is_downloading_completed) {
downloadDocumentButton.visible = false;
- openDocumentButton.visible = true;
+ openDocumentArea.visible = true;
} else {
- openDocumentButton.visible = false;
+ openDocumentArea.visible = false;
downloadDocumentButton.visible = true;
}
}
@@ -57,7 +57,7 @@ Item {
downloadingProgressBar.visible = false;
documentData.document = fileInformation;
downloadDocumentButton.visible = false;
- openDocumentButton.visible = true;
+ openDocumentArea.visible = true;
if (documentPreviewItem.openRequested) {
documentPreviewItem.openRequested = false;
tdLibWrapper.openFileOnDevice(documentData.document.local.path);
@@ -95,17 +95,40 @@ Item {
anchors.centerIn: parent
}
- Button {
- id: openDocumentButton
- preferredWidth: Theme.buttonWidthMedium
- anchors.centerIn: parent
- text: qsTr("Open Document")
+ Column {
+ id: openDocumentArea
visible: false
- highlighted: documentPreviewItem.highlighted || down
- onClicked: {
- documentPreviewItem.openRequested = true;
- tdLibWrapper.openFileOnDevice(documentData.document.local.path);
+ spacing: Theme.paddingMedium
+ width: parent.width
+
+ onVisibleChanged: {
+ visible ? (documentPreviewItem.height = openDocumentArea.height) : (documentPreviewItem.height = Theme.itemSizeLarge);
+ }
+
+ Button {
+ id: openDocumentButton
+ preferredWidth: Theme.buttonWidthMedium
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: qsTr("Open Document")
+ highlighted: documentPreviewItem.highlighted || down
+ onClicked: {
+ documentPreviewItem.openRequested = true;
+ tdLibWrapper.openFileOnDevice(documentData.document.local.path);
+ }
+ }
+
+ Button {
+ id: copyDocumentButton
+ preferredWidth: Theme.buttonWidthMedium
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: qsTr("Copy Document to Downloads")
+ highlighted: documentPreviewItem.highlighted || down
+ onClicked: {
+ tdLibWrapper.copyFileToDownloads(documentData.document.local.path);
+ }
}
}
+
+
}
diff --git a/qml/components/MessageListViewItem.qml b/qml/components/MessageListViewItem.qml
index 2a3e32e..bc306e7 100644
--- a/qml/components/MessageListViewItem.qml
+++ b/qml/components/MessageListViewItem.qml
@@ -27,6 +27,7 @@ ListItem {
contentHeight: messageBackground.height + Theme.paddingMedium
property var chatId
property var messageId
+ property int messageIndex
property var myMessage
property bool canReplyToMessage
readonly property bool isAnonymous: myMessage.sender["@type"] === "messageSenderChat"
@@ -159,13 +160,6 @@ ListItem {
Debug.log("[ChatModel] Messages in this chat were read, new last read: ", lastReadSentIndex, ", updating description for index ", index, ", status: ", (index <= lastReadSentIndex));
messageDateText.text = getMessageStatusText(myMessage, index, lastReadSentIndex, messageDateText.useElapsed);
}
- onMessageUpdated: {
- if (index === modelIndex) {
- Debug.log("[ChatModel] This message was updated, index ", index, ", updating content...");
- messageDateText.text = getMessageStatusText(myMessage, index, chatView.lastReadSentIndex, messageDateText.useElapsed);
- messageText.text = Emoji.emojify(Functions.getMessageText(myMessage, false, page.myUserId, false), messageText.font.pixelSize);
- }
- }
}
Connections {
@@ -190,6 +184,15 @@ ListItem {
}
}
+ onMyMessageChanged: {
+ Debug.log("[ChatModel] This message was updated, index", messageIndex, ", updating content...")
+ messageDateText.text = getMessageStatusText(myMessage, messageIndex, chatView.lastReadSentIndex, messageDateText.useElapsed)
+ messageText.text = Emoji.emojify(Functions.getMessageText(myMessage, false, page.myUserId, false), messageText.font.pixelSize)
+ if (webPagePreviewLoader.item) {
+ webPagePreviewLoader.item.webPageData = myMessage.content.web_page
+ }
+ }
+
Timer {
id: delegateComponentLoadingTimer
interval: 500
diff --git a/qml/components/PhotoTextsListItem.qml b/qml/components/PhotoTextsListItem.qml
index f5fbb4b..5c69d39 100644
--- a/qml/components/PhotoTextsListItem.qml
+++ b/qml/components/PhotoTextsListItem.qml
@@ -14,6 +14,8 @@ ListItem {
property bool isSecret: false
property bool isVerified: false
property bool isMarkedAsUnread: false
+ property bool isPinned: false
+ property bool isMuted: false
property alias pictureThumbnail: pictureThumbnail
contentHeight: mainRow.height + separator.height + 2 * Theme.paddingMedium
@@ -33,15 +35,14 @@ ListItem {
height: contentColumn.height
spacing: Theme.paddingMedium
- Column {
- id: pictureColumn
+ ShaderEffectSource {
+ id: pictureItem
width: contentColumn.height - Theme.paddingSmall
height: contentColumn.height - Theme.paddingSmall
anchors.verticalCenter: parent.verticalCenter
-
- Item {
- width: parent.width
- height: parent.width
+ sourceItem: Item {
+ width: pictureItem.width
+ height: pictureItem.width
ProfileThumbnail {
id: pictureThumbnail
@@ -50,11 +51,30 @@ ListItem {
height: parent.width
}
+ Rectangle {
+ id: chatPinnedBackground
+ color: Theme.highlightBackgroundColor
+ width: Theme.fontSizeLarge
+ height: Theme.fontSizeLarge
+ anchors.top: parent.top
+ radius: parent.width / 2
+ visible: chatListViewItem.isPinned
+ }
+
+ Image {
+ source: "../../images/icon-s-pin.svg"
+ height: Theme.fontSizeSmall
+ width: Theme.fontSizeSmall
+ sourceSize: Qt.size(Theme.iconSizeSmall, Theme.iconSizeSmall)
+ anchors.centerIn: chatPinnedBackground
+ visible: chatListViewItem.isPinned
+ }
+
Rectangle {
id: chatSecretBackground
- color: Theme.overlayBackgroundColor
- width: Theme.fontSizeExtraLarge
- height: Theme.fontSizeExtraLarge
+ color: Theme.highlightBackgroundColor
+ width: Theme.fontSizeLarge
+ height: Theme.fontSizeLarge
anchors.bottom: parent.bottom
radius: parent.width / 2
visible: chatListViewItem.isSecret
@@ -62,8 +82,8 @@ ListItem {
Image {
source: "image://theme/icon-s-secure"
- height: Theme.fontSizeMedium
- width: Theme.fontSizeMedium
+ height: Theme.fontSizeSmall
+ width: Theme.fontSizeSmall
anchors.centerIn: chatSecretBackground
visible: chatListViewItem.isSecret
}
@@ -93,7 +113,7 @@ ListItem {
Column {
id: contentColumn
- width: mainColumn.width - pictureColumn.width - mainRow.spacing
+ width: mainColumn.width - pictureItem.width - mainRow.spacing
spacing: Theme.paddingSmall
Row {
@@ -106,15 +126,26 @@ ListItem {
font.pixelSize: Theme.fontSizeMedium
truncationMode: TruncationMode.Fade
anchors.verticalCenter: parent.verticalCenter
- width: Math.min(contentColumn.width - (verifiedImage.visible ? (verifiedImage.width + primaryTextRow.spacing) : 0), implicitWidth)
+ width: Math.min(contentColumn.width - (verifiedImage.visible ? (verifiedImage.width + primaryTextRow.spacing) : 0) - (mutedImage.visible ? (mutedImage.width + primaryTextRow.spacing) : 0), implicitWidth)
}
Image {
id: verifiedImage
anchors.verticalCenter: parent.verticalCenter
source: chatListViewItem.isVerified ? "../../images/icon-verified.svg" : ""
- sourceSize.width: Theme.iconSizeExtraSmall
- width: Theme.iconSizeExtraSmall
+ sourceSize: Qt.size(Theme.iconSizeExtraSmall, Theme.iconSizeExtraSmall)
+ width: Theme.iconSizeSmall
+ height: Theme.iconSizeSmall
+ visible: status === Image.Ready
+ }
+
+ Image {
+ id: mutedImage
+ anchors.verticalCenter: parent.verticalCenter
+ source: chatListViewItem.isMuted ? "../js/emoji/1f507.svg" : ""
+ sourceSize: Qt.size(Theme.iconSizeExtraSmall, Theme.iconSizeExtraSmall)
+ width: Theme.iconSizeSmall
+ height: Theme.iconSizeSmall
visible: status === Image.Ready
}
}
diff --git a/qml/components/ProfileThumbnail.qml b/qml/components/ProfileThumbnail.qml
index 04f7cf9..555cc1c 100644
--- a/qml/components/ProfileThumbnail.qml
+++ b/qml/components/ProfileThumbnail.qml
@@ -22,7 +22,6 @@ import Sailfish.Silica 1.0
import WerkWolf.Fernschreiber 1.0
Item {
-
id: profileThumbnail
property alias photoData: file.fileInformation
@@ -30,6 +29,10 @@ Item {
property int radius: width / 2
property int imageStatus: -1
property bool optimizeImageSize: true
+ property bool highlighted
+
+ layer.enabled: highlighted
+ layer.effect: PressEffect { source: profileThumbnail }
function getReplacementString() {
if (replacementStringHint.length > 2) {
diff --git a/qml/components/VideoPreview.qml b/qml/components/VideoPreview.qml
index 8015d71..7854c2c 100644
--- a/qml/components/VideoPreview.qml
+++ b/qml/components/VideoPreview.qml
@@ -27,7 +27,7 @@ Item {
property ListItem messageListItem
property MessageOverlayFlickable overlayFlickable
- property var rawMessage: messageListItem ? messageListItem.myMessage : overlayFlickable.overlayMessage
+ property var rawMessage: messageListItem ? messageListItem.myMessage : ( overlayFlickable ? overlayFlickable.overlayMessage : undefined )
property var videoData: ( rawMessage.content['@type'] === "messageVideo" ) ? rawMessage.content.video : ( ( rawMessage.content['@type'] === "messageAnimation" ) ? rawMessage.content.animation : rawMessage.content.video_note )
property string videoUrl;
@@ -89,7 +89,7 @@ Item {
videoMessageComponent.videoType = videoMessageComponent.isVideoNote ? "video" : videoData['@type'];
videoFileId = videoData[videoType].id;
- if (rawMessage.content['@type'] === "messageAnimation") {
+ if (typeof rawMessage !== "undefined" && rawMessage.content['@type'] === "messageAnimation") {
playButton.visible = true;
fullscreenButton.visible = !videoMessageComponent.fullscreen;
handlePlay();
@@ -294,21 +294,6 @@ Item {
}
}
- Connections {
- target: videoMessageComponent
- onClicked: {
- if (messageVideo.playbackState === MediaPlayer.PlayingState) {
- enableScreensaver();
- messageVideo.pause();
- timeLeftItem.visible = true;
- } else {
- disableScreensaver();
- messageVideo.play();
- timeLeftTimer.start();
- }
- }
- }
-
Video {
id: messageVideo
@@ -367,7 +352,7 @@ Item {
height: parent.height
source: videoUrl
layer.enabled: videoMessageComponent.highlighted
- layer.effect: PressEffect { source: singleImage }
+ layer.effect: PressEffect { source: messageVideo }
onStopped: {
enableScreensaver();
messageVideo.visible = false;
@@ -376,6 +361,21 @@ Item {
videoComponentLoader.active = false;
fullscreenItem.visible = !videoMessageComponent.fullscreen;
}
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ if (messageVideo.playbackState === MediaPlayer.PlayingState) {
+ enableScreensaver();
+ messageVideo.pause();
+ timeLeftItem.visible = true;
+ } else {
+ disableScreensaver();
+ messageVideo.play();
+ timeLeftTimer.start();
+ }
+ }
+ }
}
BusyIndicator {
@@ -482,7 +482,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: positionText.top
minimumValue: 0
- maximumValue: messageVideo.duration ? messageVideo.duration : 0
+ maximumValue: messageVideo.duration ? messageVideo.duration : 0.1
highlighted: videoMessageComponent.highlighted || down
stepSize: 1
@@ -514,7 +514,6 @@ Item {
}
-
}
}
diff --git a/qml/components/VoiceNoteOverlay.qml b/qml/components/VoiceNoteOverlay.qml
new file mode 100644
index 0000000..cbec5a9
--- /dev/null
+++ b/qml/components/VoiceNoteOverlay.qml
@@ -0,0 +1,222 @@
+/*
+ Copyright (C) 2020-21 Sebastian J. Wolf and other contributors
+
+ 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 .
+*/
+import QtQuick 2.6
+import Sailfish.Silica 1.0
+import WerkWolf.Fernschreiber 1.0
+import "../components"
+import "../js/twemoji.js" as Emoji
+import "../js/debug.js" as Debug
+
+Item {
+ id: voiceNoteOverlayItem
+ anchors.fill: parent
+
+ property int recordingState: fernschreiberUtils.getVoiceNoteRecordingState();
+ property int recordingDuration: 0;
+ property bool recordingDone: false;
+
+ function handleRecordingState() {
+ switch (recordingState) {
+ case FernschreiberUtilities.Unavailable:
+ recordingStateLabel.text = qsTr("Unavailable");
+ break;
+ case FernschreiberUtilities.Ready:
+ recordingStateLabel.text = qsTr("Ready");
+ break;
+ case FernschreiberUtilities.Starting:
+ recordingStateLabel.text = qsTr("Starting");
+ break;
+ case FernschreiberUtilities.Recording:
+ recordingStateLabel.text = qsTr("Recording");
+ break;
+ case FernschreiberUtilities.Stopping:
+ recordingStateLabel.text = qsTr("Stopping");
+ break;
+ }
+ }
+
+ function getTwoDigitString(numberToBeConverted) {
+ var numberString = "00";
+ if (numberToBeConverted > 0 && numberToBeConverted < 10) {
+ numberString = "0" + String(numberToBeConverted);
+ }
+ if (numberToBeConverted >= 10) {
+ numberString = String(numberToBeConverted);
+ }
+ return numberString;
+ }
+
+ function handleRecordingDuration() {
+ var minutes = Math.floor(recordingDuration / 60);
+ var seconds = recordingDuration % 60;
+ recordingDurationLabel.text = getTwoDigitString(minutes) + ":" + getTwoDigitString(seconds);
+ }
+
+ Component.onCompleted: {
+ handleRecordingState();
+ handleRecordingDuration();
+ }
+
+ Connections {
+ target: fernschreiberUtils
+ onVoiceNoteDurationChanged: {
+ Debug.log("New duration received: " + duration);
+ recordingDuration = Math.round(duration / 1000);
+ handleRecordingDuration();
+ }
+ onVoiceNoteRecordingStateChanged: {
+ Debug.log("New state received: " + state);
+ recordingState = state;
+ handleRecordingState();
+ }
+ }
+
+ Rectangle {
+ id: stickerPickerOverlayBackground
+ anchors.fill: parent
+
+ color: Theme.overlayBackgroundColor
+ opacity: Theme.opacityHigh
+ }
+
+ Flickable {
+ id: voiceNoteFlickable
+ anchors.fill: parent
+ anchors.margins: Theme.paddingMedium
+
+ Behavior on opacity { NumberAnimation {} }
+
+ contentHeight: voiceNoteColumn.height
+ clip: true
+
+ Column {
+ id: voiceNoteColumn
+ spacing: Theme.paddingMedium
+ width: voiceNoteFlickable.width
+
+ InfoLabel {
+ text: qsTr("Record a Voice Note")
+ }
+
+ Label {
+ wrapMode: Text.Wrap
+ width: parent.width - ( 2 * Theme.horizontalPageMargin )
+ horizontalAlignment: Text.AlignHCenter
+ text: qsTr("Press the button to start recording")
+ font.pixelSize: Theme.fontSizeMedium
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ }
+ }
+
+ Item {
+ width: Theme.iconSizeExtraLarge
+ height: Theme.iconSizeExtraLarge
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ }
+ Rectangle {
+ color: Theme.primaryColor
+ opacity: Theme.opacityOverlay
+ width: Theme.iconSizeExtraLarge
+ height: Theme.iconSizeExtraLarge
+ anchors.centerIn: parent
+ radius: width / 2
+ }
+
+ Rectangle {
+ id: recordButton
+ color: "red"
+ width: Theme.iconSizeExtraLarge * 0.6
+ height: Theme.iconSizeExtraLarge * 0.6
+ anchors.centerIn: parent
+ radius: width / 2
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ recordButton.visible = false;
+ recordingDone = false;
+ recordingDuration = 0;
+ handleRecordingDuration();
+ fernschreiberUtils.startRecordingVoiceNote();
+ }
+ }
+ }
+
+ Rectangle {
+ id: stopButton
+ visible: !recordButton.visible
+ color: Theme.overlayBackgroundColor
+ width: Theme.iconSizeExtraLarge * 0.4
+ height: Theme.iconSizeExtraLarge * 0.4
+ anchors.centerIn: parent
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ recordButton.visible = true;
+ fernschreiberUtils.stopRecordingVoiceNote();
+ recordingDone = true;
+ }
+ }
+ }
+ }
+
+ Label {
+ id: recordingStateLabel
+ wrapMode: Text.Wrap
+ width: parent.width - ( 2 * Theme.horizontalPageMargin )
+ horizontalAlignment: Text.AlignHCenter
+ font.pixelSize: Theme.fontSizeMedium
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ }
+ }
+
+ Label {
+ id: recordingDurationLabel
+ wrapMode: Text.Wrap
+ width: parent.width - ( 2 * Theme.horizontalPageMargin )
+ horizontalAlignment: Text.AlignHCenter
+ font.pixelSize: Theme.fontSizeMedium
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ }
+ }
+
+ Button {
+ visible: recordingDone
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ }
+ text: qsTr("Use recording")
+ onClicked: {
+ attachmentOptionsFlickable.isNeeded = false;
+ attachmentPreviewRow.isVoiceNote = true;
+ attachmentPreviewRow.attachmentDescription = qsTr("Voice Note (%1)").arg(recordingDurationLabel.text);
+ attachmentPreviewRow.visible = true;
+ controlSendButton();
+ voiceNoteOverlayLoader.active = false;
+ }
+ }
+
+ }
+ }
+
+}
+
diff --git a/qml/components/WebPagePreview.qml b/qml/components/WebPagePreview.qml
index b1c9275..a814b73 100644
--- a/qml/components/WebPagePreview.qml
+++ b/qml/components/WebPagePreview.qml
@@ -33,7 +33,11 @@ Column {
spacing: Theme.paddingSmall
- Component.onCompleted: {
+ Component.onCompleted: updatePhoto()
+
+ onWebPageDataChanged: updatePhoto()
+
+ function updatePhoto() {
if (webPageData) {
if (webPageData.photo) {
// Check first which size fits best...
@@ -134,9 +138,10 @@ Column {
}
BackgroundImage {
+ id: backgroundImage
visible: hasImage && singleImage.status !== Image.Ready
layer.enabled: webPagePreviewColumn.highlighted
- layer.effect: PressEffect { source: singleImage }
+ layer.effect: PressEffect { source: backgroundImage }
}
}
diff --git a/qml/pages/AboutPage.qml b/qml/pages/AboutPage.qml
index 2d0898d..70fbcce 100644
--- a/qml/pages/AboutPage.qml
+++ b/qml/pages/AboutPage.qml
@@ -59,7 +59,7 @@ Page {
}
Label {
- text: "Fernschreiber 0.6"
+ text: "Fernschreiber 0.7"
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Theme.fontSizeExtraLarge
anchors {
@@ -178,7 +178,7 @@ Page {
}
ProfileThumbnail {
- photoData: aboutPage.userInformation.profile_photo.small
+ photoData: ((typeof aboutPage.userInformation.profile_photo !== "undefined") ? aboutPage.userInformation.profile_photo.small : {})
width: Theme.itemSizeExtraLarge
height: Theme.itemSizeExtraLarge
replacementStringHint: aboutPage.userInformation.first_name + " " + aboutPage.userInformation.last_name
diff --git a/qml/pages/ChatPage.qml b/qml/pages/ChatPage.qml
index 727b317..4db6a78 100644
--- a/qml/pages/ChatPage.qml
+++ b/qml/pages/ChatPage.qml
@@ -211,14 +211,21 @@ Page {
attachmentPreviewRow.isPicture = false;
attachmentPreviewRow.isVideo = false;
attachmentPreviewRow.isDocument = false;
+ attachmentPreviewRow.isVoiceNote = false;
+ attachmentPreviewRow.isLocation = false;
attachmentPreviewRow.fileProperties = {};
+ attachmentPreviewRow.locationData = {};
+ attachmentPreviewRow.attachmentDescription = "";
+ fernschreiberUtils.stopGeoLocationUpdates();
}
function controlSendButton() {
if (newMessageTextField.text.length !== 0
|| attachmentPreviewRow.isPicture
|| attachmentPreviewRow.isDocument
- || attachmentPreviewRow.isVideo) {
+ || attachmentPreviewRow.isVideo
+ || attachmentPreviewRow.isVoiceNote
+ || attachmentPreviewRow.isLocation) {
newMessageSendButton.enabled = true;
} else {
newMessageSendButton.enabled = false;
@@ -239,14 +246,25 @@ Page {
if (attachmentPreviewRow.isDocument) {
tdLibWrapper.sendDocumentMessage(chatInformation.id, attachmentPreviewRow.fileProperties.filePath, newMessageTextField.text, newMessageColumn.replyToMessageId);
}
+ if (attachmentPreviewRow.isVoiceNote) {
+ tdLibWrapper.sendVoiceNoteMessage(chatInformation.id, fernschreiberUtils.voiceNotePath(), newMessageTextField.text, newMessageColumn.replyToMessageId);
+ }
+ if (attachmentPreviewRow.isLocation) {
+ tdLibWrapper.sendLocationMessage(chatInformation.id, attachmentPreviewRow.locationData.latitude, attachmentPreviewRow.locationData.longitude, attachmentPreviewRow.locationData.horizontalAccuracy, newMessageColumn.replyToMessageId);
+ }
clearAttachmentPreviewRow();
} else {
tdLibWrapper.sendTextMessage(chatInformation.id, newMessageTextField.text, newMessageColumn.replyToMessageId);
}
+
+ if(appSettings.focusTextAreaAfterSend) {
+ lostFocusTimer.start();
+ }
}
controlSendButton();
newMessageInReplyToRow.inReplyToMessage = null;
newMessageColumn.editMessageId = "0";
+ fernschreiberUtils.stopGeoLocationUpdates();
}
function getWordBoundaries(text, cursorPosition) {
@@ -376,6 +394,7 @@ Page {
if (chatPage.canSendMessages) {
tdLibWrapper.setChatDraftMessage(chatInformation.id, 0, newMessageColumn.replyToMessageId, newMessageTextField.text);
}
+ fernschreiberUtils.stopGeoLocationUpdates();
tdLibWrapper.closeChat(chatInformation.id);
}
@@ -455,7 +474,7 @@ Page {
Debug.log("[ChatPage] Received pinned message");
pinnedMessageItem.pinnedMessage = message;
}
- if (messageId === chatInformation.draft_message.reply_to_message_id) {
+ if (chatInformation.draft_message && messageId === chatInformation.draft_message.reply_to_message_id) {
newMessageInReplyToRow.inReplyToMessage = message;
}
}
@@ -623,7 +642,7 @@ Page {
contentWidth: width
PullDownMenu {
- visible: chatInformation.id !== chatPage.myUserId && !stickerPickerLoader.active && !messageOverlayLoader.active
+ visible: chatInformation.id !== chatPage.myUserId && !stickerPickerLoader.active && !voiceNoteOverlayLoader.active && !messageOverlayLoader.active
MenuItem {
id: closeSecretChatMenuItem
visible: chatPage.isSecretChat && chatPage.secretChatDetails.state["@type"] !== "secretChatStateClosed"
@@ -730,7 +749,7 @@ Page {
Rectangle {
id: chatSecretBackground
- color: Theme.overlayBackgroundColor
+ color: Theme.highlightBackgroundColor
width: chatPage.isPortrait ? Theme.fontSizeLarge : Theme.fontSizeMedium
height: width
anchors.left: parent.left
@@ -876,7 +895,7 @@ Page {
id: chatView
visible: !blurred
- property bool blurred: messageOverlayLoader.item
+ property bool blurred: messageOverlayLoader.item || stickerPickerLoader.item || voiceNoteOverlayLoader.item
anchors.fill: parent
opacity: chatPage.loading ? 0 : 1
@@ -1010,6 +1029,7 @@ Page {
chatId: chatModel.chatId
myMessage: model.display
messageId: model.message_id
+ messageIndex: model.index
extraContentComponentName: chatView.contentComponentNames[model.content_type] || ""
canReplyToMessage: chatPage.canSendMessages
onReplyToMessage: {
@@ -1110,7 +1130,7 @@ Page {
Debug.log("Sticker picked: " + stickerId);
tdLibWrapper.sendStickerMessage(chatInformation.id, stickerId);
stickerPickerLoader.active = false;
- attachmentOptionsRow.isNeeded = false;
+ attachmentOptionsFlickable.isNeeded = false;
}
}
@@ -1134,6 +1154,20 @@ Page {
}
}
+ Loader {
+ id: voiceNoteOverlayLoader
+ active: false
+ asynchronous: true
+ width: parent.width
+ height: active ? parent.height : 0
+ source: "../components/VoiceNoteOverlay.qml"
+ onActiveChanged: {
+ if (!active) {
+ fernschreiberUtils.stopRecordingVoiceNote();
+ }
+ }
+ }
+
}
Column {
@@ -1141,7 +1175,6 @@ Page {
spacing: Theme.paddingSmall
topPadding: Theme.paddingSmall
anchors.horizontalCenter: parent.horizontalCenter
- clip: true
visible: height > 0
width: parent.width - ( 2 * Theme.horizontalPageMargin )
height: isNeeded ? implicitHeight : 0
@@ -1173,90 +1206,146 @@ Page {
visible: false
}
- Row {
- id: attachmentOptionsRow
+ Flickable {
+ id: attachmentOptionsFlickable
+
property bool isNeeded: false
- visible: height > 0
- height: isNeeded ? implicitHeight : 0
- anchors.right: parent.right
- width: parent.width
- layoutDirection: Qt.RightToLeft
- spacing: Theme.paddingMedium
- clip: true
+ width: chatPage.width
+ x: -Theme.horizontalPageMargin
+ height: isNeeded ? attachmentOptionsRow.height : 0
Behavior on height { SmoothedAnimation { duration: 200 } }
- IconButton {
- visible: chatPage.hasSendPrivilege("can_send_media_messages")
- icon.source: "image://theme/icon-m-image"
- onClicked: {
- var picker = pageStack.push("Sailfish.Pickers.ImagePickerPage", {
- allowedOrientations: chatPage.allowedOrientations
- })
- picker.selectedContentPropertiesChanged.connect(function(){
- attachmentOptionsRow.isNeeded = false;
- Debug.log("Selected document: ", picker.selectedContentProperties.filePath );
- attachmentPreviewRow.fileProperties = picker.selectedContentProperties;
- attachmentPreviewRow.isPicture = true;
+ visible: height > 0
+ contentHeight: attachmentOptionsRow.height
+ contentWidth: Math.max(width, attachmentOptionsRow.width)
+ property bool fadeRight: (attachmentOptionsRow.width-contentX) > width
+ property bool fadeLeft: !fadeRight && contentX > 0
+ layer.enabled: fadeRight || fadeLeft
+ layer.effect: OpacityRampEffectBase {
+ direction: attachmentOptionsFlickable.fadeRight ? OpacityRamp.LeftToRight : OpacityRamp.RightToLeft
+ source: attachmentOptionsFlickable
+ slope: 1 + 6 * (chatPage.width) / Screen.width
+ offset: 1 - 1 / slope
+ }
+
+
+ Row {
+ id: attachmentOptionsRow
+
+ height: attachImageIconButton.height
+
+ anchors.right: parent.right
+ layoutDirection: Qt.RightToLeft
+ spacing: Theme.paddingMedium
+ leftPadding: Theme.horizontalPageMargin
+ rightPadding: Theme.horizontalPageMargin
+
+ IconButton {
+ id: attachImageIconButton
+ visible: chatPage.hasSendPrivilege("can_send_media_messages")
+ icon.source: "image://theme/icon-m-image"
+ onClicked: {
+ var picker = pageStack.push("Sailfish.Pickers.ImagePickerPage", {
+ allowedOrientations: chatPage.allowedOrientations
+ })
+ picker.selectedContentPropertiesChanged.connect(function(){
+ attachmentOptionsFlickable.isNeeded = false;
+ Debug.log("Selected document: ", picker.selectedContentProperties.filePath );
+ attachmentPreviewRow.fileProperties = picker.selectedContentProperties;
+ attachmentPreviewRow.isPicture = true;
+ attachmentPreviewRow.visible = true;
+ controlSendButton();
+ })
+ }
+ }
+ IconButton {
+ visible: chatPage.hasSendPrivilege("can_send_media_messages")
+ icon.source: "image://theme/icon-m-video"
+ onClicked: {
+ var picker = pageStack.push("Sailfish.Pickers.VideoPickerPage", {
+ allowedOrientations: chatPage.allowedOrientations
+ })
+ picker.selectedContentPropertiesChanged.connect(function(){
+ attachmentOptionsFlickable.isNeeded = false;
+ Debug.log("Selected video: ", picker.selectedContentProperties.filePath );
+ attachmentPreviewRow.fileProperties = picker.selectedContentProperties;
+ attachmentPreviewRow.isVideo = true;
+ attachmentPreviewRow.visible = true;
+ controlSendButton();
+ })
+ }
+ }
+ IconButton {
+ visible: chatPage.hasSendPrivilege("can_send_media_messages")
+ icon.source: "image://theme/icon-m-mic"
+ icon.sourceSize {
+ width: Theme.iconSizeMedium
+ height: Theme.iconSizeMedium
+ }
+ highlighted: down || voiceNoteOverlayLoader.active
+ onClicked: {
+ voiceNoteOverlayLoader.active = !voiceNoteOverlayLoader.active;
+ stickerPickerLoader.active = false;
+ }
+ }
+ IconButton {
+ visible: chatPage.hasSendPrivilege("can_send_media_messages")
+ icon.source: "image://theme/icon-m-document"
+ onClicked: {
+ var picker = pageStack.push("Sailfish.Pickers.FilePickerPage", {
+ allowedOrientations: chatPage.allowedOrientations
+ })
+ picker.selectedContentPropertiesChanged.connect(function(){
+ attachmentOptionsFlickable.isNeeded = false;
+ Debug.log("Selected document: ", picker.selectedContentProperties.filePath );
+ attachmentPreviewRow.fileProperties = picker.selectedContentProperties;
+ attachmentPreviewRow.isDocument = true;
+ attachmentPreviewRow.visible = true;
+ controlSendButton();
+ })
+ }
+ }
+ IconButton {
+ visible: chatPage.hasSendPrivilege("can_send_other_messages")
+ icon.source: "../../images/icon-m-sticker.svg"
+ icon.sourceSize {
+ width: Theme.iconSizeMedium
+ height: Theme.iconSizeMedium
+ }
+ highlighted: down || stickerPickerLoader.active
+ onClicked: {
+ stickerPickerLoader.active = !stickerPickerLoader.active;
+ voiceNoteOverlayLoader.active = false;
+ }
+ }
+ IconButton {
+ visible: !(chatPage.isPrivateChat || chatPage.isSecretChat) && chatPage.hasSendPrivilege("can_send_polls")
+ icon.source: "image://theme/icon-m-question"
+ onClicked: {
+ pageStack.push(Qt.resolvedUrl("../pages/PollCreationPage.qml"), { "chatId" : chatInformation.id, groupName: chatInformation.title});
+ attachmentOptionsFlickable.isNeeded = false;
+ }
+ }
+ IconButton {
+ visible: fernschreiberUtils.supportsGeoLocation() && newMessageTextField.text === ""
+ icon.source: "image://theme/icon-m-location"
+ icon.sourceSize {
+ width: Theme.iconSizeMedium
+ height: Theme.iconSizeMedium
+ }
+ onClicked: {
+ fernschreiberUtils.startGeoLocationUpdates();
+ attachmentOptionsFlickable.isNeeded = false;
+ attachmentPreviewRow.isLocation = true;
+ attachmentPreviewRow.attachmentDescription = qsTr("Location: Obtaining position...");
attachmentPreviewRow.visible = true;
controlSendButton();
- })
- }
- }
- IconButton {
- visible: chatPage.hasSendPrivilege("can_send_media_messages")
- icon.source: "image://theme/icon-m-video"
- onClicked: {
- var picker = pageStack.push("Sailfish.Pickers.VideoPickerPage", {
- allowedOrientations: chatPage.allowedOrientations
- })
- picker.selectedContentPropertiesChanged.connect(function(){
- attachmentOptionsRow.isNeeded = false;
- Debug.log("Selected video: ", picker.selectedContentProperties.filePath );
- attachmentPreviewRow.fileProperties = picker.selectedContentProperties;
- attachmentPreviewRow.isVideo = true;
- attachmentPreviewRow.visible = true;
- controlSendButton();
- })
- }
- }
- IconButton {
- visible: chatPage.hasSendPrivilege("can_send_media_messages")
- icon.source: "image://theme/icon-m-document"
- onClicked: {
- var picker = pageStack.push("Sailfish.Pickers.FilePickerPage", {
- allowedOrientations: chatPage.allowedOrientations
- })
- picker.selectedContentPropertiesChanged.connect(function(){
- attachmentOptionsRow.isNeeded = false;
- Debug.log("Selected document: ", picker.selectedContentProperties.filePath );
- attachmentPreviewRow.fileProperties = picker.selectedContentProperties;
- attachmentPreviewRow.isDocument = true;
- attachmentPreviewRow.visible = true;
- controlSendButton();
- })
- }
- }
- IconButton {
- visible: chatPage.hasSendPrivilege("can_send_other_messages")
- icon.source: "../../images/icon-m-sticker.svg"
- icon.sourceSize {
- width: Theme.iconSizeMedium
- height: Theme.iconSizeMedium
- }
- highlighted: down || stickerPickerLoader.active
- onClicked: {
- stickerPickerLoader.active = !stickerPickerLoader.active;
- }
- }
- IconButton {
- visible: !(chatPage.isPrivateChat || chatPage.isSecretChat) && chatPage.hasSendPrivilege("can_send_polls")
- icon.source: "image://theme/icon-m-question"
- onClicked: {
- pageStack.push(Qt.resolvedUrl("../pages/PollCreationPage.qml"), { "chatId" : chatInformation.id, groupName: chatInformation.title});
- attachmentOptionsRow.isNeeded = false;
+ }
}
}
+
}
+
Row {
id: attachmentPreviewRow
visible: false
@@ -1268,7 +1357,21 @@ Page {
property bool isPicture: false;
property bool isVideo: false;
property bool isDocument: false;
+ property bool isVoiceNote: false;
+ property bool isLocation: false;
+ property var locationData: ({});
property var fileProperties:({});
+ property string attachmentDescription: "";
+
+ Connections {
+ target: fernschreiberUtils
+ onNewPositionInformation: {
+ attachmentPreviewRow.locationData = positionInformation;
+ if (attachmentPreviewRow.isLocation) {
+ attachmentPreviewRow.attachmentDescription = qsTr("Location (%1/%2)").arg(attachmentPreviewRow.locationData.latitude).arg(attachmentPreviewRow.locationData.longitude);
+ }
+ }
+ }
IconButton {
id: removeAttachmentsIconButton
@@ -1295,13 +1398,13 @@ Page {
Label {
id: attachmentPreviewText
font.pixelSize: Theme.fontSizeSmall
- text: typeof attachmentPreviewRow.fileProperties !== "undefined" ? attachmentPreviewRow.fileProperties.fileName || "" : "";
+ text: ( attachmentPreviewRow.isVoiceNote || attachmentPreviewRow.isLocation ) ? attachmentPreviewRow.attachmentDescription : ( typeof attachmentPreviewRow.fileProperties !== "undefined" ? attachmentPreviewRow.fileProperties.fileName || "" : "" );
anchors.verticalCenter: parent.verticalCenter
maximumLineCount: 1
truncationMode: TruncationMode.Fade
color: Theme.secondaryColor
- visible: attachmentPreviewRow.isDocument
+ visible: attachmentPreviewRow.isDocument || attachmentPreviewRow.isVoiceNote || attachmentPreviewRow.isLocation
}
}
@@ -1503,11 +1606,14 @@ Page {
labelVisible: false
textLeftMargin: 0
textTopMargin: 0
+ enabled: !attachmentPreviewRow.isLocation
EnterKey.onClicked: {
if (appSettings.sendByEnter) {
sendMessage();
newMessageTextField.text = "";
- newMessageTextField.focus = false;
+ if(!appSettings.focusTextAreaAfterSend) {
+ newMessageTextField.focus = false;
+ }
}
}
@@ -1522,16 +1628,17 @@ Page {
IconButton {
id: attachmentIconButton
- icon.source: "image://theme/icon-m-attach?" + (attachmentOptionsRow.isNeeded ? Theme.highlightColor : Theme.primaryColor)
+ icon.source: "image://theme/icon-m-attach?" + (attachmentOptionsFlickable.isNeeded ? Theme.highlightColor : Theme.primaryColor)
anchors.bottom: parent.bottom
anchors.bottomMargin: Theme.paddingSmall
enabled: !attachmentPreviewRow.visible
onClicked: {
- if (attachmentOptionsRow.isNeeded) {
- attachmentOptionsRow.isNeeded = false;
+ if (attachmentOptionsFlickable.isNeeded) {
+ attachmentOptionsFlickable.isNeeded = false;
stickerPickerLoader.active = false;
+ voiceNoteOverlayLoader.active = false;
} else {
- attachmentOptionsRow.isNeeded = true;
+ attachmentOptionsFlickable.isNeeded = true;
}
}
}
@@ -1546,7 +1653,9 @@ Page {
onClicked: {
sendMessage();
newMessageTextField.text = "";
- newMessageTextField.focus = false;
+ if(!appSettings.focusTextAreaAfterSend) {
+ newMessageTextField.focus = false;
+ }
}
}
}
diff --git a/qml/pages/CoverPage.qml b/qml/pages/CoverPage.qml
index 9e5df33..1a29b8d 100644
--- a/qml/pages/CoverPage.qml
+++ b/qml/pages/CoverPage.qml
@@ -67,7 +67,7 @@ CoverBackground {
coverPage.authenticated = (tdLibWrapper.getAuthorizationState() === TelegramAPI.AuthorizationReady);
coverPage.connectionState = tdLibWrapper.getConnectionState();
coverPage.unreadMessages = tdLibWrapper.getUnreadMessageInformation().unread_count || 0;
- coverPage.unreadChats = tdLibWrapper.getUnreadChatInformation().unread_count;
+ coverPage.unreadChats = tdLibWrapper.getUnreadChatInformation().unread_count || 0;
setUnreadInfoText();
}
@@ -91,6 +91,15 @@ CoverBackground {
}
}
+ Connections {
+ target: chatListModel
+ onUnreadStateChanged: {
+ coverPage.unreadMessages = unreadMessagesCount;
+ coverPage.unreadChats = unreadChatsCount;
+ setUnreadInfoText();
+ }
+ }
+
BackgroundImage {
id: backgroundImage
width: parent.height - Theme.paddingLarge
diff --git a/qml/pages/OverviewPage.qml b/qml/pages/OverviewPage.qml
index 1fb16ea..17c4f32 100644
--- a/qml/pages/OverviewPage.qml
+++ b/qml/pages/OverviewPage.qml
@@ -67,6 +67,13 @@ Page {
overviewPage.chatListCreated = true;
chatListView.scrollToTop();
updateSecondaryContentTimer.start();
+ var remainingInteractionHints = appSettings.remainingInteractionHints;
+ Debug.log("Remaining interaction hints: " + remainingInteractionHints);
+ if (remainingInteractionHints > 0) {
+ interactionHintTimer.start();
+ titleInteractionHint.opacity = 1.0;
+ appSettings.remainingInteractionHints = remainingInteractionHints - 1;
+ }
}
}
@@ -81,6 +88,7 @@ Page {
id: updateSecondaryContentTimer
interval: 600
onTriggered: {
+ chatListModel.calculateUnreadState();
tdLibWrapper.getRecentStickers();
tdLibWrapper.getInstalledStickerSets();
tdLibWrapper.getContacts();
@@ -178,9 +186,8 @@ Page {
function resetFocus() {
if (chatSearchField.text === "") {
- chatSearchField.visible = false;
- pageHeader.visible = true;
- searchChatButton.visible = overviewPage.connectionState === TelegramAPI.ConnectionReady;
+ chatSearchField.opacity = 0.0;
+ pageHeader.opacity = 1.0;
}
chatSearchField.focus = false;
overviewPage.focus = true;
@@ -202,11 +209,15 @@ Page {
onChatLastMessageUpdated: {
if (!overviewPage.chatListCreated) {
chatListCreatedTimer.restart();
+ } else {
+ chatListModel.calculateUnreadState();
}
}
onChatOrderUpdated: {
if (!overviewPage.chatListCreated) {
chatListCreatedTimer.restart();
+ } else {
+ chatListModel.calculateUnreadState();
}
}
onChatsReceived: {
@@ -268,9 +279,12 @@ Page {
}
}
- Row {
- id: headerRow
- width: parent.width - Theme.horizontalPageMargin
+ PageHeader {
+ id: pageHeader
+ title: qsTr("Fernschreiber")
+ leftMargin: Theme.itemSizeMedium
+ visible: opacity > 0
+ Behavior on opacity { FadeAnimation {} }
GlassItem {
id: pageStatus
@@ -282,63 +296,45 @@ Page {
cache: false
}
- PageHeader {
- id: pageHeader
- title: qsTr("Fernschreiber")
- width: visible ? ( parent.width - pageStatus.width - searchChatButton.width ) : 0
- opacity: visible ? 1 : 0
- Behavior on opacity { FadeAnimation {} }
- }
-
- IconButton {
- id: searchChatButton
- width: visible ? height : 0
- opacity: visible ? 1 : 0
- Behavior on opacity { NumberAnimation {} }
- anchors.verticalCenter: parent.verticalCenter
- icon {
- source: "image://theme/icon-m-search?" + Theme.highlightColor
- asynchronous: true
- }
- visible: overviewPage.connectionState === TelegramAPI.ConnectionReady
+ MouseArea {
+ anchors.fill: parent
onClicked: {
chatSearchField.focus = true;
- chatSearchField.visible = true;
- pageHeader.visible = false;
- searchChatButton.visible = false;
- }
- }
-
- SearchField {
- id: chatSearchField
- visible: false
- opacity: visible ? 1 : 0
- Behavior on opacity { FadeAnimation {} }
- width: visible ? ( parent.width - pageStatus.width ) : 0
- height: pageHeader.height
- placeholderText: qsTr("Filter your chats...")
- canHide: text === ""
-
- onTextChanged: {
- searchChatTimer.restart();
- }
-
- onHideClicked: {
- resetFocus();
- }
-
- EnterKey.iconSource: "image://theme/icon-m-enter-close"
- EnterKey.onClicked: {
- resetFocus();
+ chatSearchField.opacity = 1.0;
+ pageHeader.opacity = 0.0;
}
}
}
+ SearchField {
+ id: chatSearchField
+ visible: opacity > 0
+ opacity: 0
+ Behavior on opacity { FadeAnimation {} }
+ width: parent.width
+ height: pageHeader.height
+ placeholderText: qsTr("Filter your chats...")
+ canHide: text === ""
+
+ onTextChanged: {
+ searchChatTimer.restart();
+ }
+
+ onHideClicked: {
+ resetFocus();
+ }
+
+ EnterKey.iconSource: "image://theme/icon-m-enter-close"
+ EnterKey.onClicked: {
+ resetFocus();
+ }
+ }
+
SilicaListView {
id: chatListView
anchors {
- top: headerRow.bottom
+ top: pageHeader.bottom
bottom: parent.bottom
left: parent.left
right: parent.right
@@ -360,7 +356,8 @@ Page {
ViewPlaceholder {
enabled: chatListView.count === 0
- text: qsTr("You don't have any chats yet.")
+ text: chatSearchField.text === "" ? qsTr("You don't have any chats yet.") : qsTr("No matching chats found.")
+ hintText: qsTr("You can search public chats or create a new chat via the pull-down menu.")
}
VerticalScrollDecorator {}
@@ -403,4 +400,24 @@ Page {
}
}
}
+
+ Timer {
+ id: interactionHintTimer
+ running: false
+ interval: 4000
+ onTriggered: {
+ titleInteractionHint.opacity = 0.0;
+ }
+ }
+
+ InteractionHintLabel {
+ id: titleInteractionHint
+ text: qsTr("Tap on the title bar to filter your chats")
+ visible: opacity > 0
+ invert: true
+ anchors.fill: parent
+ Behavior on opacity { FadeAnimation {} }
+ opacity: 0
+ }
+
}
diff --git a/qml/pages/SearchChatsPage.qml b/qml/pages/SearchChatsPage.qml
index 82fe385..3e0571b 100644
--- a/qml/pages/SearchChatsPage.qml
+++ b/qml/pages/SearchChatsPage.qml
@@ -175,13 +175,13 @@ Page {
onBasicGroupFullInfoUpdated: {
if (foundChatListDelegate.isBasicGroup && groupId.toString() === foundChatListDelegate.foundChatInformation.type.basic_group_id.toString()) {
- foundChatListItem.secondaryText.text = qsTr("%1 members").arg(Number(groupFullInfo.members.length).toLocaleString(Qt.locale(), "f", 0));
+ foundChatListItem.secondaryText.text = qsTr("%1 members", "", groupFullInfo.members.length).arg(Number(groupFullInfo.members.length).toLocaleString(Qt.locale(), "f", 0));
foundChatListItem.tertiaryText.text = Emoji.emojify(groupFullInfo.description, foundChatListItem.tertiaryText.font.pixelSize, "../js/emoji/");
}
}
onBasicGroupFullInfoReceived: {
if (foundChatListDelegate.isBasicGroup && groupId.toString() === foundChatListDelegate.foundChatInformation.type.basic_group_id.toString()) {
- foundChatListItem.secondaryText.text = qsTr("%1 members").arg(Number(groupFullInfo.members.length).toLocaleString(Qt.locale(), "f", 0));
+ foundChatListItem.secondaryText.text = qsTr("%1 members", "", groupFullInfo.members.length).arg(Number(groupFullInfo.members.length).toLocaleString(Qt.locale(), "f", 0));
foundChatListItem.tertiaryText.text = Emoji.emojify(groupFullInfo.description, foundChatListItem.tertiaryText.font.pixelSize, "../js/emoji/");
}
}
@@ -189,9 +189,9 @@ Page {
onSupergroupFullInfoUpdated: {
if (foundChatListDelegate.isSupergroup && groupId.toString() === foundChatListDelegate.foundChatInformation.type.supergroup_id.toString()) {
if (foundChatListDelegate.relatedInformation.is_channel) {
- foundChatListItem.secondaryText.text = qsTr("%1 subscribers").arg(Number(groupFullInfo.member_count).toLocaleString(Qt.locale(), "f", 0));
+ foundChatListItem.secondaryText.text = qsTr("%1 subscribers", "", groupFullInfo.member_count).arg(Number(groupFullInfo.member_count).toLocaleString(Qt.locale(), "f", 0));
} else {
- foundChatListItem.secondaryText.text = qsTr("%1 members").arg(Number(groupFullInfo.member_count).toLocaleString(Qt.locale(), "f", 0));
+ foundChatListItem.secondaryText.text = qsTr("%1 members", "", groupFullInfo.member_count).arg(Number(groupFullInfo.member_count).toLocaleString(Qt.locale(), "f", 0));
}
foundChatListItem.tertiaryText.text = Emoji.emojify(groupFullInfo.description, foundChatListItem.tertiaryText.font.pixelSize, "../js/emoji/");
}
@@ -199,9 +199,9 @@ Page {
onSupergroupFullInfoReceived: {
if (foundChatListDelegate.isSupergroup && groupId.toString() === foundChatListDelegate.foundChatInformation.type.supergroup_id.toString()) {
if (foundChatListDelegate.relatedInformation.is_channel) {
- foundChatListItem.secondaryText.text = qsTr("%1 subscribers").arg(Number(groupFullInfo.member_count).toLocaleString(Qt.locale(), "f", 0));
+ foundChatListItem.secondaryText.text = qsTr("%1 subscribers", "", groupFullInfo.member_count).arg(Number(groupFullInfo.member_count).toLocaleString(Qt.locale(), "f", 0));
} else {
- foundChatListItem.secondaryText.text = qsTr("%1 members").arg(Number(groupFullInfo.member_count).toLocaleString(Qt.locale(), "f", 0));
+ foundChatListItem.secondaryText.text = qsTr("%1 members", "", groupFullInfo.member_count).arg(Number(groupFullInfo.member_count).toLocaleString(Qt.locale(), "f", 0));
}
foundChatListItem.tertiaryText.text = Emoji.emojify(groupFullInfo.description, foundChatListItem.tertiaryText.font.pixelSize, "../js/emoji/");
}
diff --git a/qml/pages/SettingsPage.qml b/qml/pages/SettingsPage.qml
index e9abe19..b91691e 100644
--- a/qml/pages/SettingsPage.qml
+++ b/qml/pages/SettingsPage.qml
@@ -52,6 +52,16 @@ Page {
}
}
+ TextSwitch {
+ checked: appSettings.focusTextAreaAfterSend
+ text: qsTr("Focus text input area after send")
+ description: qsTr("Focus the text input area after sending a message")
+ automaticCheck: false
+ onClicked: {
+ appSettings.focusTextAreaAfterSend = !checked
+ }
+ }
+
TextSwitch {
checked: appSettings.useOpenWith
text: qsTr("Open-with menu integration")
@@ -162,6 +172,16 @@ Page {
}
}
+ TextSwitch {
+ checked: appSettings.onlineOnlyMode
+ text: qsTr("Enable online-only mode")
+ description: qsTr("Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect.")
+ automaticCheck: false
+ onClicked: {
+ appSettings.onlineOnlyMode = !checked
+ }
+ }
+
Item {
width: 1
height: Theme.paddingLarge // Some space at the bottom
diff --git a/rpm/harbour-fernschreiber.changes b/rpm/harbour-fernschreiber.changes
index 4af99be..7df474a 100644
--- a/rpm/harbour-fernschreiber.changes
+++ b/rpm/harbour-fernschreiber.changes
@@ -12,6 +12,38 @@
# * date Author's Name version-release
# - Summary of changes
+* Wed Jan 6 2021 Sebastian J. Wolf 0.6.1
+- Show indicator for pinned chats
+- Use highlightBackgroundColor and align size for all chat attributes (unread messages, secret chat, pinned chat)
+- Fix: Chat list order of sequence was wrong in some circumstances
+- Fix: Make video (fullscreen) page work again
+- Fix: Add build requirements for Qt Multimedia and Qt Positioning
+- Updated translations for Finnish and Swedish - thanks to jorm1s and eson57
+
+* Tue Jan 5 2021 Sebastian J. Wolf 0.6
+- Filter chat list (tap on title bar to open search field) - thanks to the entire dev team for the great discussion and contributions to the layout :)
+- Search for public chats (see pulley menu on overview page)
+- Search for chat messages (see pulley menu on chat page)
+- Support sending voice notes
+- Support sending locations
+- Add message draft support - thanks to jgibbon
+- Basic bot messages support (reply markup) - thanks to jgibbon
+- Add 'Mark chat as read/unread' option
+- Add download option to audio messages (voice notes, music...)
+- Introduce proper text if other people added/removed a person from a chat
+- Tweaks to poll layout - thanks to monich
+- New option to keep message input focused after message was sent - thanks to jgibbon
+- Send message button now removed if Send-message-by-enter option is enabled (and no attachment is set) - thanks to santhoshmanikandan
+- Performance and code optimizations (architecture, startup, JS components) - thanks to monich and jgibbon
+- Improve readability in light ambiences
+- Upgrade to TDLib 1.7
+- Fix: Only show reply to option for messages that can be replied to - thanks to monich
+- Fix: Emoji positioning in multi-line texts - thanks to monich
+- Fix: Left-over @-mention notifications when all messages in chat are read
+- Fix: Occasional crashes on opening poll context menu - thanks to monich
+- Fix: Don't display in-reply-to section if message wasn't found
+- Several translations updated - thanks to all contributors
+
* Fri Dec 4 2020 Sebastian J. Wolf 0.5.1
- Add verification badge to verified chats - thanks to monich
- Improve preview of wide images - thanks to monich
diff --git a/rpm/harbour-fernschreiber.spec b/rpm/harbour-fernschreiber.spec
index 9d2da62..d1f9180 100644
--- a/rpm/harbour-fernschreiber.spec
+++ b/rpm/harbour-fernschreiber.spec
@@ -11,7 +11,7 @@ Name: harbour-fernschreiber
# << macros
Summary: Fernschreiber is a Telegram client for Sailfish OS
-Version: 0.6
+Version: 0.7
Release: 1
Group: Qt/Qt
License: LICENSE
@@ -25,6 +25,8 @@ BuildRequires: pkgconfig(Qt5Qml)
BuildRequires: pkgconfig(Qt5Quick)
BuildRequires: pkgconfig(Qt5DBus)
BuildRequires: pkgconfig(Qt5Sql)
+BuildRequires: pkgconfig(Qt5Multimedia)
+BuildRequires: pkgconfig(Qt5Positioning)
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 a9e8967..935a55c 100644
--- a/rpm/harbour-fernschreiber.yaml
+++ b/rpm/harbour-fernschreiber.yaml
@@ -1,6 +1,6 @@
Name: harbour-fernschreiber
Summary: Fernschreiber is a Telegram client for Sailfish OS
-Version: 0.6
+Version: 0.7
Release: 1
# The contents of the Group field should be one of the groups listed here:
# https://github.com/mer-tools/spectacle/blob/master/data/GROUPS
@@ -25,6 +25,8 @@ PkgConfigBR:
- Qt5Quick
- Qt5DBus
- Qt5Sql
+ - Qt5Multimedia
+ - Qt5Positioning
- nemonotifications-qt5
- ngf-qt5
diff --git a/src/appsettings.cpp b/src/appsettings.cpp
index 497578f..d9239a9 100644
--- a/src/appsettings.cpp
+++ b/src/appsettings.cpp
@@ -22,12 +22,15 @@
namespace {
const QString KEY_SEND_BY_ENTER("sendByEnter");
+ const QString KEY_FOCUS_TEXTAREA_AFTER_SEND("focusTextAreaAfterSend");
const QString KEY_USE_OPEN_WITH("useOpenWith");
const QString KEY_SHOW_STICKERS_AS_IMAGES("showStickersAsImages");
const QString KEY_ANIMATE_STICKERS("animateStickers");
const QString KEY_NOTIFICATION_TURNS_DISPLAY_ON("notificationTurnsDisplayOn");
const QString KEY_NOTIFICATION_FEEDBACK("notificationFeedback");
const QString KEY_STORAGE_OPTIMIZER("storageOptimizer");
+ const QString KEY_REMAINING_INTERACTION_HINTS("remainingInteractionHints");
+ const QString KEY_ONLINE_ONLY_MODE("onlineOnlyMode");
}
AppSettings::AppSettings(QObject *parent) : QObject(parent), settings("harbour-fernschreiber", "settings")
@@ -48,6 +51,20 @@ void AppSettings::setSendByEnter(bool sendByEnter)
}
}
+bool AppSettings::getFocusTextAreaAfterSend() const
+{
+ return settings.value(KEY_FOCUS_TEXTAREA_AFTER_SEND, false).toBool();
+}
+
+void AppSettings::setFocusTextAreaAfterSend(bool focusTextAreaAfterSend)
+{
+ if (getFocusTextAreaAfterSend() != focusTextAreaAfterSend) {
+ LOG(KEY_FOCUS_TEXTAREA_AFTER_SEND << focusTextAreaAfterSend);
+ settings.setValue(KEY_FOCUS_TEXTAREA_AFTER_SEND, focusTextAreaAfterSend);
+ emit focusTextAreaAfterSendChanged();
+ }
+}
+
bool AppSettings::getUseOpenWith() const
{
return settings.value(KEY_USE_OPEN_WITH, true).toBool();
@@ -131,3 +148,31 @@ void AppSettings::setStorageOptimizer(bool enable)
emit storageOptimizerChanged();
}
}
+
+int AppSettings::remainingInteractionHints() const
+{
+ return settings.value(KEY_REMAINING_INTERACTION_HINTS, 3).toInt();
+}
+
+void AppSettings::setRemainingInteractionHints(int remainingHints)
+{
+ if (remainingInteractionHints() != remainingHints) {
+ LOG(KEY_REMAINING_INTERACTION_HINTS << remainingHints);
+ settings.setValue(KEY_REMAINING_INTERACTION_HINTS, remainingHints);
+ emit remainingInteractionHintsChanged();
+ }
+}
+
+bool AppSettings::onlineOnlyMode() const
+{
+ return settings.value(KEY_ONLINE_ONLY_MODE, false).toBool();
+}
+
+void AppSettings::setOnlineOnlyMode(bool enable)
+{
+ if (onlineOnlyMode() != enable) {
+ LOG(KEY_ONLINE_ONLY_MODE << enable);
+ settings.setValue(KEY_ONLINE_ONLY_MODE, enable);
+ emit onlineOnlyModeChanged();
+ }
+}
diff --git a/src/appsettings.h b/src/appsettings.h
index bb63cdf..373d3df 100644
--- a/src/appsettings.h
+++ b/src/appsettings.h
@@ -24,12 +24,15 @@
class AppSettings : public QObject {
Q_OBJECT
Q_PROPERTY(bool sendByEnter READ getSendByEnter WRITE setSendByEnter NOTIFY sendByEnterChanged)
+ Q_PROPERTY(bool focusTextAreaAfterSend READ getFocusTextAreaAfterSend WRITE setFocusTextAreaAfterSend NOTIFY focusTextAreaAfterSendChanged)
Q_PROPERTY(bool useOpenWith READ getUseOpenWith WRITE setUseOpenWith NOTIFY useOpenWithChanged)
Q_PROPERTY(bool showStickersAsImages READ showStickersAsImages WRITE setShowStickersAsImages NOTIFY showStickersAsImagesChanged)
Q_PROPERTY(bool animateStickers READ animateStickers WRITE setAnimateStickers NOTIFY animateStickersChanged)
Q_PROPERTY(bool notificationTurnsDisplayOn READ notificationTurnsDisplayOn WRITE setNotificationTurnsDisplayOn NOTIFY notificationTurnsDisplayOnChanged)
Q_PROPERTY(NotificationFeedback notificationFeedback READ notificationFeedback WRITE setNotificationFeedback NOTIFY notificationFeedbackChanged)
Q_PROPERTY(bool storageOptimizer READ storageOptimizer WRITE setStorageOptimizer NOTIFY storageOptimizerChanged)
+ Q_PROPERTY(int remainingInteractionHints READ remainingInteractionHints WRITE setRemainingInteractionHints NOTIFY remainingInteractionHintsChanged)
+ Q_PROPERTY(bool onlineOnlyMode READ onlineOnlyMode WRITE setOnlineOnlyMode NOTIFY onlineOnlyModeChanged)
public:
enum NotificationFeedback {
@@ -45,6 +48,9 @@ public:
bool getSendByEnter() const;
void setSendByEnter(bool sendByEnter);
+ bool getFocusTextAreaAfterSend() const;
+ void setFocusTextAreaAfterSend(bool focusTextAreaAfterSend);
+
bool getUseOpenWith() const;
void setUseOpenWith(bool useOpenWith);
@@ -63,14 +69,23 @@ public:
bool storageOptimizer() const;
void setStorageOptimizer(bool enable);
+ int remainingInteractionHints() const;
+ void setRemainingInteractionHints(int remainingHints);
+
+ bool onlineOnlyMode() const;
+ void setOnlineOnlyMode(bool enable);
+
signals:
void sendByEnterChanged();
+ void focusTextAreaAfterSendChanged();
void useOpenWithChanged();
void showStickersAsImagesChanged();
void animateStickersChanged();
void notificationTurnsDisplayOnChanged();
void notificationFeedbackChanged();
void storageOptimizerChanged();
+ void remainingInteractionHintsChanged();
+ void onlineOnlyModeChanged();
private:
QSettings settings;
diff --git a/src/chatlistmodel.cpp b/src/chatlistmodel.cpp
index 7d2ed45..1a796be 100644
--- a/src/chatlistmodel.cpp
+++ b/src/chatlistmodel.cpp
@@ -19,6 +19,7 @@
#include "chatlistmodel.h"
#include "fernschreiberutils.h"
+#include
#define DEBUG_MODULE ChatListModel
#include "debuglog.h"
@@ -48,6 +49,7 @@ namespace {
const QString IS_CHANNEL("is_channel");
const QString IS_VERIFIED("is_verified");
const QString IS_MARKED_AS_UNREAD("is_marked_as_unread");
+ const QString IS_PINNED("is_pinned");
const QString PINNED_MESSAGE_ID("pinned_message_id");
const QString _TYPE("@type");
const QString SECRET_CHAT_ID("secret_chat_id");
@@ -77,6 +79,7 @@ public:
bool isChannel() const;
bool isHidden() const;
bool isMarkedAsUnread() const;
+ bool isPinned() const;
bool updateUnreadCount(int unreadCount);
bool updateLastReadInboxMessageId(qlonglong messageId);
QVector updateLastMessage(const QVariantMap &message);
@@ -272,6 +275,11 @@ bool ChatListModel::ChatData::isMarkedAsUnread() const
return chatData.value(IS_MARKED_AS_UNREAD).toBool();
}
+bool ChatListModel::ChatData::isPinned() const
+{
+ return chatData.value(IS_PINNED).toBool();
+}
+
bool ChatListModel::ChatData::updateUnreadCount(int count)
{
const int prevUnreadCount(unreadCount());
@@ -346,9 +354,10 @@ QVector ChatListModel::ChatData::updateSecretChat(const QVariantMap &secret
return changedRoles;
}
-ChatListModel::ChatListModel(TDLibWrapper *tdLibWrapper) : showHiddenChats(false)
+ChatListModel::ChatListModel(TDLibWrapper *tdLibWrapper, AppSettings *appSettings) : showHiddenChats(false)
{
this->tdLibWrapper = tdLibWrapper;
+ this->appSettings = appSettings;
connect(tdLibWrapper, SIGNAL(newChatDiscovered(QString, QVariantMap)), this, SLOT(handleChatDiscovered(QString, QVariantMap)));
connect(tdLibWrapper, SIGNAL(chatLastMessageUpdated(QString, QString, QVariantMap)), this, SLOT(handleChatLastMessageUpdated(QString, QString, QVariantMap)));
connect(tdLibWrapper, SIGNAL(chatOrderUpdated(QString, QString)), this, SLOT(handleChatOrderUpdated(QString, QString)));
@@ -364,6 +373,7 @@ ChatListModel::ChatListModel(TDLibWrapper *tdLibWrapper) : showHiddenChats(false
connect(tdLibWrapper, SIGNAL(secretChatReceived(qlonglong, QVariantMap)), this, SLOT(handleSecretChatUpdated(qlonglong, QVariantMap)));
connect(tdLibWrapper, SIGNAL(chatTitleUpdated(QString, QString)), this, SLOT(handleChatTitleUpdated(QString, QString)));
connect(tdLibWrapper, SIGNAL(chatIsMarkedAsUnreadUpdated(qlonglong, bool)), this, SLOT(handleChatIsMarkedAsUnreadUpdated(qlonglong, bool)));
+ connect(tdLibWrapper, SIGNAL(chatPinnedUpdated(qlonglong, bool)), this, SLOT(handleChatPinnedUpdated(qlonglong, bool)));
connect(tdLibWrapper, SIGNAL(chatDraftMessageUpdated(qlonglong, QVariantMap, QString)), this, SLOT(handleChatDraftMessageUpdated(qlonglong, QVariantMap, QString)));
// Don't start the timer until we have at least one chat
@@ -405,6 +415,7 @@ QHash ChatListModel::roleNames() const
roles.insert(ChatListModel::RoleIsVerified, "is_verified");
roles.insert(ChatListModel::RoleIsChannel, "is_channel");
roles.insert(ChatListModel::RoleIsMarkedAsUnread, "is_marked_as_unread");
+ roles.insert(ChatListModel::RoleIsPinned, "is_pinned");
roles.insert(ChatListModel::RoleFilter, "filter");
roles.insert(ChatListModel::RoleDraftMessageDate, "draft_message_date");
roles.insert(ChatListModel::RoleDraftMessageText, "draft_message_text");
@@ -438,6 +449,7 @@ QVariant ChatListModel::data(const QModelIndex &index, int role) const
case ChatListModel::RoleIsVerified: return data->verified;
case ChatListModel::RoleIsChannel: return data->isChannel();
case ChatListModel::RoleIsMarkedAsUnread: return data->isMarkedAsUnread();
+ case ChatListModel::RoleIsPinned: return data->isPinned();
case ChatListModel::RoleFilter: return QString(data->title() + " " + data->senderMessageText()).trimmed();
case ChatListModel::RoleDraftMessageText: return data->draftMessageText();
case ChatListModel::RoleDraftMessageDate: return data->draftMessageDate();
@@ -515,6 +527,26 @@ int ChatListModel::updateChatOrder(int chatIndex)
return newIndex;
}
+void ChatListModel::calculateUnreadState()
+{
+ if (this->appSettings->onlineOnlyMode()) {
+ LOG("Online-only mode: Calculating unread state on my own...");
+ int unreadMessages = 0;
+ int unreadChats = 0;
+ QListIterator chatIterator(this->chatList);
+ while (chatIterator.hasNext()) {
+ ChatData *currentChat = chatIterator.next();
+ int unreadCount = currentChat->unreadCount();
+ if (unreadCount > 0) {
+ unreadChats++;
+ unreadMessages += unreadCount;
+ }
+ }
+ LOG("Online-only mode: New unread state:" << unreadMessages << unreadChats);
+ emit unreadStateChanged(unreadMessages, unreadChats);
+ }
+}
+
void ChatListModel::addVisibleChat(ChatData *chat)
{
const int n = chatList.size();
@@ -715,6 +747,7 @@ void ChatListModel::handleChatReadInboxUpdated(const QString &id, const QString
}
const QModelIndex modelIndex(index(chatIndex));
emit dataChanged(modelIndex, modelIndex, changedRoles);
+ this->calculateUnreadState();
} else {
ChatData *chat = hiddenChats.value(chatId);
if (chat) {
@@ -856,6 +889,26 @@ void ChatListModel::handleChatTitleUpdated(const QString &chatId, const QString
}
}
+void ChatListModel::handleChatPinnedUpdated(qlonglong chatId, bool chatIsPinned)
+{
+ if (chatIndexMap.contains(chatId)) {
+ LOG("Updating chat is pinned for" << chatId << chatIsPinned);
+ const int chatIndex = chatIndexMap.value(chatId);
+ ChatData *chat = chatList.at(chatIndex);
+ chat->chatData.insert(IS_PINNED, chatIsPinned);
+ QVector changedRoles;
+ changedRoles.append(ChatListModel::RoleIsPinned);
+ const QModelIndex modelIndex(index(chatIndex));
+ emit dataChanged(modelIndex, modelIndex, changedRoles);
+ } else {
+ ChatData *chat = hiddenChats.value(chatId);
+ if (chat) {
+ LOG("Updating chat is pinned for hidden chat" << chatId);
+ chat->chatData.insert(IS_PINNED, chatIsPinned);
+ }
+ }
+}
+
void ChatListModel::handleChatIsMarkedAsUnreadUpdated(qlonglong chatId, bool chatIsMarkedAsUnread)
{
if (chatIndexMap.contains(chatId)) {
diff --git a/src/chatlistmodel.h b/src/chatlistmodel.h
index 964f9f7..8c17d75 100644
--- a/src/chatlistmodel.h
+++ b/src/chatlistmodel.h
@@ -22,6 +22,7 @@
#include
#include "tdlibwrapper.h"
+#include "appsettings.h"
class ChatListModel : public QAbstractListModel
{
@@ -47,12 +48,13 @@ public:
RoleIsVerified,
RoleIsChannel,
RoleIsMarkedAsUnread,
+ RoleIsPinned,
RoleFilter,
RoleDraftMessageText,
RoleDraftMessageDate
};
- ChatListModel(TDLibWrapper *tdLibWrapper);
+ ChatListModel(TDLibWrapper *tdLibWrapper, AppSettings *appSettings);
~ChatListModel() override;
virtual QHash roleNames() const override;
@@ -64,6 +66,7 @@ public:
Q_INVOKABLE QVariantMap getById(qlonglong chatId);
Q_INVOKABLE void reset();
+ Q_INVOKABLE void calculateUnreadState();
bool showAllChats() const;
void setShowAllChats(bool showAll);
@@ -81,6 +84,7 @@ private slots:
void handleGroupUpdated(qlonglong groupId);
void handleSecretChatUpdated(qlonglong secretChatId, const QVariantMap &secretChat);
void handleChatTitleUpdated(const QString &chatId, const QString &title);
+ void handleChatPinnedUpdated(qlonglong chatId, bool chatIsPinned);
void handleChatIsMarkedAsUnreadUpdated(qlonglong chatId, bool chatIsMarkedAsUnread);
void handleChatDraftMessageUpdated(qlonglong chatId, const QVariantMap &draftMessage, const QString &order);
void handleRelativeTimeRefreshTimer();
@@ -89,6 +93,7 @@ signals:
void showAllChatsChanged();
void chatChanged(const qlonglong &changedChatId);
void chatJoined(const qlonglong &chatId, const QString &chatTitle);
+ void unreadStateChanged(int unreadMessagesCount, int unreadChatsCount);
private:
class ChatData;
@@ -99,6 +104,7 @@ private:
private:
TDLibWrapper *tdLibWrapper;
+ AppSettings *appSettings;
QTimer *relativeTimeRefreshTimer;
QList chatList;
QHash chatIndexMap;
diff --git a/src/chatmodel.cpp b/src/chatmodel.cpp
index a89a74a..7dbc0a4 100644
--- a/src/chatmodel.cpp
+++ b/src/chatmodel.cpp
@@ -54,8 +54,10 @@ public:
MessageData(const QVariantMap &data, qlonglong msgid);
static bool lessThan(const MessageData *message1, const MessageData *message2);
- void setContent(const QVariantMap &content);
- void setReplyMarkup(const QVariantMap &replyMarkup);
+ QVector diff(const MessageData *message) const;
+ QVector setMessageData(const QVariantMap &data);
+ QVector setContent(const QVariantMap &content);
+ QVector setReplyMarkup(const QVariantMap &replyMarkup);
int senderUserId() const;
qlonglong senderChatId() const;
bool senderIsChat() const;
@@ -63,7 +65,7 @@ public:
public:
QVariantMap messageData;
const qlonglong messageId;
- const QString messageContentType;
+ QString messageContentType;
};
ChatModel::MessageData::MessageData(const QVariantMap &data, qlonglong msgid) :
@@ -88,13 +90,48 @@ bool ChatModel::MessageData::senderIsChat() const
return messageData.value(SENDER).toMap().value(_TYPE).toString() == "messageSenderChat";
}
-void ChatModel::MessageData::setContent(const QVariantMap &content)
+QVector ChatModel::MessageData::diff(const MessageData *message) const
{
- messageData.insert(CONTENT, content);
+ QVector roles;
+ if (message != this) {
+ roles.append(RoleDisplay);
+ if (message->messageId != messageId) {
+ roles.append(RoleMessageId);
+ }
+ if (message->messageContentType != messageContentType) {
+ roles.append(RoleMessageContentType);
+ }
+ }
+ return roles;
}
-void ChatModel::MessageData::setReplyMarkup(const QVariantMap &replyMarkup)
+
+QVector ChatModel::MessageData::setMessageData(const QVariantMap &data)
+{
+ messageData = data;
+ QVector changedRoles;
+ changedRoles.append(RoleDisplay);
+ return changedRoles;
+}
+
+QVector ChatModel::MessageData::setContent(const QVariantMap &content)
+{
+ const QString oldContentType(messageContentType);
+ messageData.insert(CONTENT, content);
+ messageContentType = content.value(_TYPE).toString();
+ QVector changedRoles;
+ if (oldContentType != messageContentType) {
+ changedRoles.append(RoleMessageContentType);
+ }
+ changedRoles.append(RoleDisplay);
+ return changedRoles;
+}
+
+QVector ChatModel::MessageData::setReplyMarkup(const QVariantMap &replyMarkup)
{
messageData.insert(REPLY_MARKUP, replyMarkup);
+ QVector changedRoles;
+ changedRoles.append(RoleDisplay);
+ return changedRoles;
}
bool ChatModel::MessageData::lessThan(const MessageData *message1, const MessageData *message2)
@@ -105,7 +142,8 @@ bool ChatModel::MessageData::lessThan(const MessageData *message1, const Message
ChatModel::ChatModel(TDLibWrapper *tdLibWrapper) :
chatId(0),
inReload(false),
- inIncrementalUpdate(false)
+ inIncrementalUpdate(false),
+ searchModeActive(false)
{
this->tdLibWrapper = tdLibWrapper;
connect(this->tdLibWrapper, SIGNAL(messagesReceived(QVariantList, int)), this, SLOT(handleMessagesReceived(QVariantList, int)));
@@ -370,11 +408,10 @@ void ChatModel::handleMessageReceived(qlonglong chatId, qlonglong messageId, con
if (chatId == this->chatId && messageIndexMap.contains(messageId)) {
LOG("Received a message that we already know, let's update it!");
const int position = messageIndexMap.value(messageId);
- MessageData *messageData = messages.at(position);
- messageData->messageData = message;
+ const QVector changedRoles(messages.at(position)->setMessageData(message));
LOG("Message was updated at index" << position);
const QModelIndex messageIndex(index(position));
- emit dataChanged(messageIndex, messageIndex);
+ emit dataChanged(messageIndex, messageIndex, changedRoles);
}
}
@@ -405,11 +442,14 @@ void ChatModel::handleMessageSendSucceeded(qlonglong messageId, qlonglong oldMes
if (this->messageIndexMap.contains(oldMessageId)) {
LOG("Message was successfully sent" << oldMessageId);
const int pos = messageIndexMap.take(oldMessageId);
- delete messages.at(pos);
- messages.replace(pos, new MessageData(message, messageId));
+ MessageData* oldMessage = messages.at(pos);
+ MessageData* newMessage = new MessageData(message, messageId);
+ messages.replace(pos, newMessage);
+ const QVector changedRoles(newMessage->diff(oldMessage));
+ delete oldMessage;
LOG("Message was replaced at index" << pos);
const QModelIndex messageIndex(index(pos));
- emit dataChanged(messageIndex, messageIndex);
+ emit dataChanged(messageIndex, messageIndex, changedRoles);
emit lastReadSentMessageUpdated(calculateLastReadSentMessageId());
}
}
@@ -448,10 +488,10 @@ void ChatModel::handleMessageContentUpdated(qlonglong chatId, qlonglong messageI
LOG("We know the message that was updated" << messageId);
const int pos = messageIndexMap.value(messageId, -1);
if (pos >= 0) {
- messages.at(pos)->setContent(newContent);
- LOG("Message was replaced at index" << pos);
+ const QVector changedRoles(messages.at(pos)->setContent(newContent));
+ LOG("Message was updated at index" << pos);
const QModelIndex messageIndex(index(pos));
- emit dataChanged(messageIndex, messageIndex);
+ emit dataChanged(messageIndex, messageIndex, changedRoles);
emit messageUpdated(pos);
}
}
@@ -464,10 +504,10 @@ void ChatModel::handleMessageEditedUpdated(qlonglong chatId, qlonglong messageId
LOG("We know the message that was updated" << messageId);
const int pos = messageIndexMap.value(messageId, -1);
if (pos >= 0) {
- messages.at(pos)->setReplyMarkup(replyMarkup);
+ const QVector changedRoles(messages.at(pos)->setReplyMarkup(replyMarkup));
LOG("Message was edited at index" << pos);
const QModelIndex messageIndex(index(pos));
- emit dataChanged(messageIndex, messageIndex);
+ emit dataChanged(messageIndex, messageIndex, changedRoles);
emit messageUpdated(pos);
}
}
diff --git a/src/fernschreiberutils.cpp b/src/fernschreiberutils.cpp
index b75734a..3237982 100644
--- a/src/fernschreiberutils.cpp
+++ b/src/fernschreiberutils.cpp
@@ -1,11 +1,74 @@
-#include "fernschreiberutils.h"
+/*
+ Copyright (C) 2020-21 Sebastian J. Wolf and other contributors
+ 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 "fernschreiberutils.h"
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define DEBUG_MODULE FernschreiberUtils
+#include "debuglog.h"
FernschreiberUtils::FernschreiberUtils(QObject *parent) : QObject(parent)
{
+ LOG("Initializing audio recorder...");
+ QString temporaryDirectoryPath = this->getTemporaryDirectoryPath();
+ QDir temporaryDirectory(temporaryDirectoryPath);
+ if (!temporaryDirectory.exists()) {
+ temporaryDirectory.mkpath(temporaryDirectoryPath);
+ }
+
+ QAudioEncoderSettings encoderSettings;
+ encoderSettings.setCodec("audio/vorbis");
+ encoderSettings.setChannelCount(1);
+ encoderSettings.setQuality(QMultimedia::LowQuality);
+ this->audioRecorder.setEncodingSettings(encoderSettings);
+ this->audioRecorder.setContainerFormat("ogg");
+
+ QMediaRecorder::Status audioRecorderStatus = this->audioRecorder.status();
+ this->handleAudioRecorderStatusChanged(audioRecorderStatus);
+
+ connect(&audioRecorder, SIGNAL(durationChanged(qlonglong)), this, SIGNAL(voiceNoteDurationChanged(qlonglong)));
+ connect(&audioRecorder, SIGNAL(statusChanged(QMediaRecorder::Status)), this, SLOT(handleAudioRecorderStatusChanged(QMediaRecorder::Status)));
+
+ this->geoPositionInfoSource = QGeoPositionInfoSource::createDefaultSource(this);
+ if (this->geoPositionInfoSource) {
+ LOG("Geolocation successfully initialized...");
+ this->geoPositionInfoSource->setUpdateInterval(5000);
+ connect(geoPositionInfoSource, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(handleGeoPositionUpdated(QGeoPositionInfo)));
+ } else {
+ LOG("Unable to initialize geolocation!");
+ }
+}
+
+FernschreiberUtils::~FernschreiberUtils()
+{
+ this->cleanUp();
}
QString FernschreiberUtils::getMessageShortText(TDLibWrapper *tdLibWrapper, const QVariantMap &messageContent, const bool isChannel, const qlonglong currentUserId, const QVariantMap &messageSender)
@@ -128,3 +191,117 @@ QString FernschreiberUtils::getUserName(const QVariantMap &userInformation)
const QString lastName = userInformation.value("last_name").toString();
return QString(firstName + " " + lastName).trimmed();
}
+
+void FernschreiberUtils::startRecordingVoiceNote()
+{
+ LOG("Start recording voice note...");
+ QDateTime thisIsNow = QDateTime::currentDateTime();
+ this->audioRecorder.setOutputLocation(QUrl::fromLocalFile(this->getTemporaryDirectoryPath() + "/voicenote-" + thisIsNow.toString("yyyy-MM-dd-HH-mm-ss") + ".ogg"));
+ this->audioRecorder.setVolume(1);
+ this->audioRecorder.record();
+}
+
+void FernschreiberUtils::stopRecordingVoiceNote()
+{
+ LOG("Stop recording voice note...");
+ this->audioRecorder.stop();
+}
+
+QString FernschreiberUtils::voiceNotePath()
+{
+ return this->audioRecorder.outputLocation().toLocalFile();
+}
+
+FernschreiberUtils::VoiceNoteRecordingState FernschreiberUtils::getVoiceNoteRecordingState()
+{
+ return this->voiceNoteRecordingState;
+}
+
+void FernschreiberUtils::startGeoLocationUpdates()
+{
+ if (this->geoPositionInfoSource) {
+ this->geoPositionInfoSource->startUpdates();
+ }
+}
+
+void FernschreiberUtils::stopGeoLocationUpdates()
+{
+ if (this->geoPositionInfoSource) {
+ this->geoPositionInfoSource->stopUpdates();
+ }
+}
+
+bool FernschreiberUtils::supportsGeoLocation()
+{
+ return this->geoPositionInfoSource;
+}
+
+void FernschreiberUtils::handleAudioRecorderStatusChanged(QMediaRecorder::Status status)
+{
+ LOG("Audio recorder status changed:" << status);
+ switch (status) {
+ case QMediaRecorder::UnavailableStatus:
+ case QMediaRecorder::UnloadedStatus:
+ case QMediaRecorder::LoadingStatus:
+ this->voiceNoteRecordingState = VoiceNoteRecordingState::Unavailable;
+ break;
+ case QMediaRecorder::LoadedStatus:
+ case QMediaRecorder::PausedStatus:
+ this->voiceNoteRecordingState = VoiceNoteRecordingState::Ready;
+ break;
+ case QMediaRecorder::StartingStatus:
+ this->voiceNoteRecordingState = VoiceNoteRecordingState::Starting;
+ break;
+ case QMediaRecorder::FinalizingStatus:
+ this->voiceNoteRecordingState = VoiceNoteRecordingState::Stopping;
+ break;
+ case QMediaRecorder::RecordingStatus:
+ this->voiceNoteRecordingState = VoiceNoteRecordingState::Recording;
+ break;
+ }
+ emit voiceNoteRecordingStateChanged(this->voiceNoteRecordingState);
+}
+
+void FernschreiberUtils::handleGeoPositionUpdated(const QGeoPositionInfo &info)
+{
+ LOG("Geo position was updated");
+ QVariantMap positionInformation;
+ if (info.hasAttribute(QGeoPositionInfo::HorizontalAccuracy)) {
+ positionInformation.insert("horizontalAccuracy", info.attribute(QGeoPositionInfo::HorizontalAccuracy));
+ } else {
+ positionInformation.insert("horizontalAccuracy", 0);
+ }
+ if (info.hasAttribute(QGeoPositionInfo::VerticalAccuracy)) {
+ positionInformation.insert("verticalAccuracy", info.attribute(QGeoPositionInfo::VerticalAccuracy));
+ } else {
+ positionInformation.insert("verticalAccuracy", 0);
+ }
+ QGeoCoordinate geoCoordinate = info.coordinate();
+ positionInformation.insert("latitude", geoCoordinate.latitude());
+ positionInformation.insert("longitude", geoCoordinate.longitude());
+
+
+ emit newPositionInformation(positionInformation);
+}
+
+void FernschreiberUtils::cleanUp()
+{
+ if (this->geoPositionInfoSource) {
+ this->geoPositionInfoSource->stopUpdates();
+ }
+ QString temporaryDirectoryPath = this->getTemporaryDirectoryPath();
+ QDirIterator temporaryDirectoryIterator(temporaryDirectoryPath, QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks, QDirIterator::Subdirectories);
+ while (temporaryDirectoryIterator.hasNext()) {
+ QString nextFilePath = temporaryDirectoryIterator.next();
+ if (QFile::remove(nextFilePath)) {
+ LOG("Temporary file removed " << nextFilePath);
+ } else {
+ LOG("Error removing temporary file " << nextFilePath);
+ }
+ }
+}
+
+QString FernschreiberUtils::getTemporaryDirectoryPath()
+{
+ return QStandardPaths::writableLocation(QStandardPaths::TempLocation) + + "/harbour-fernschreiber";
+}
diff --git a/src/fernschreiberutils.h b/src/fernschreiberutils.h
index 35fc8a0..ce3bc3b 100644
--- a/src/fernschreiberutils.h
+++ b/src/fernschreiberutils.h
@@ -1,7 +1,29 @@
+/*
+ Copyright (C) 2020-21 Sebastian J. Wolf and other contributors
+
+ 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 FERNSCHREIBERUTILS_H
#define FERNSCHREIBERUTILS_H
#include
+#include
+#include
+#include
#include "tdlibwrapper.h"
class FernschreiberUtils : public QObject
@@ -9,13 +31,46 @@ class FernschreiberUtils : public QObject
Q_OBJECT
public:
explicit FernschreiberUtils(QObject *parent = nullptr);
+ ~FernschreiberUtils();
+
+ enum VoiceNoteRecordingState {
+ Unavailable,
+ Ready,
+ Starting,
+ Recording,
+ Stopping
+ };
+ Q_ENUM(VoiceNoteRecordingState)
static QString getMessageShortText(TDLibWrapper *tdLibWrapper, const QVariantMap &messageContent, const bool isChannel, const qlonglong currentUserId, const QVariantMap &messageSender);
static QString getUserName(const QVariantMap &userInformation);
-signals:
+ Q_INVOKABLE void startRecordingVoiceNote();
+ Q_INVOKABLE void stopRecordingVoiceNote();
+ Q_INVOKABLE QString voiceNotePath();
+ Q_INVOKABLE VoiceNoteRecordingState getVoiceNoteRecordingState();
+ Q_INVOKABLE void startGeoLocationUpdates();
+ Q_INVOKABLE void stopGeoLocationUpdates();
+ Q_INVOKABLE bool supportsGeoLocation();
+
+signals:
+ void voiceNoteDurationChanged(qlonglong duration);
+ void voiceNoteRecordingStateChanged(VoiceNoteRecordingState state);
+ void newPositionInformation(const QVariantMap &positionInformation);
+
+private slots:
+ void handleAudioRecorderStatusChanged(QMediaRecorder::Status status);
+ void handleGeoPositionUpdated(const QGeoPositionInfo &info);
+
+private:
+ QAudioRecorder audioRecorder;
+ VoiceNoteRecordingState voiceNoteRecordingState;
+
+ QGeoPositionInfoSource *geoPositionInfoSource;
+
+ void cleanUp();
+ QString getTemporaryDirectoryPath();
-public slots:
};
#endif // FERNSCHREIBERUTILS_H
diff --git a/src/harbour-fernschreiber.cpp b/src/harbour-fernschreiber.cpp
index 6efdf5d..51febb6 100644
--- a/src/harbour-fernschreiber.cpp
+++ b/src/harbour-fernschreiber.cpp
@@ -82,11 +82,12 @@ int main(int argc, char *argv[])
FernschreiberUtils *fernschreiberUtils = new FernschreiberUtils(view.data());
context->setContextProperty("fernschreiberUtils", fernschreiberUtils);
+ qmlRegisterUncreatableType(uri, 1, 0, "FernschreiberUtilities", QString());
DBusAdaptor *dBusAdaptor = tdLibWrapper->getDBusAdaptor();
context->setContextProperty("dBusAdaptor", dBusAdaptor);
- ChatListModel chatListModel(tdLibWrapper);
+ ChatListModel chatListModel(tdLibWrapper, appSettings);
context->setContextProperty("chatListModel", &chatListModel);
QSortFilterProxyModel chatListProxyModel(view.data());
chatListProxyModel.setSourceModel(&chatListModel);
diff --git a/src/tdlibfile.cpp b/src/tdlibfile.cpp
index 232fa9a..dece939 100644
--- a/src/tdlibfile.cpp
+++ b/src/tdlibfile.cpp
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2020 Slava Monich at al.
+ Copyright (C) 2020-2021 Slava Monich at al.
This file is part of Fernschreiber.
@@ -107,6 +107,7 @@ void TDLibFile::init()
queuedSignals = 0;
firstQueuedSignal = SignalCount;
autoLoad = false;
+ downloadHoldOffTimer = 0;
id = 0;
expected_size = 0;
size = 0;
@@ -174,7 +175,7 @@ void TDLibFile::updateTDLibWrapper(TDLibWrapper* tdlib)
if (tdlib) {
connect(tdlib, SIGNAL(fileUpdated(int,QVariantMap)), SLOT(handleFileUpdated(int,QVariantMap)));
if (autoLoad) {
- load();
+ downloadFile();
}
}
queueSignal(SignalTdLibChanged);
@@ -187,7 +188,7 @@ void TDLibFile::setAutoLoad(bool enableAutoLoad)
autoLoad = enableAutoLoad;
queueSignal(SignalAutoLoadChanged);
if (autoLoad) {
- load();
+ downloadFile();
}
emitQueuedSignals();
}
@@ -195,7 +196,19 @@ void TDLibFile::setAutoLoad(bool enableAutoLoad)
bool TDLibFile::load()
{
- if (id && tdLibWrapper && !is_downloading_active && !is_downloading_completed && can_be_downloaded) {
+ // Manual load ignores hold-off timer
+ if (downloadHoldOffTimer) {
+ killTimer(downloadHoldOffTimer);
+ downloadHoldOffTimer = 0;
+ }
+ return downloadFile();
+}
+
+bool TDLibFile::downloadFile()
+{
+ if (id && tdLibWrapper && !downloadHoldOffTimer &&
+ !is_downloading_active && !is_downloading_completed && can_be_downloaded) {
+ downloadHoldOffTimer = startTimer(DownloadHoldOffMs);
tdLibWrapper->downloadFile(id);
return true;
}
@@ -205,12 +218,26 @@ bool TDLibFile::load()
void TDLibFile::setFileInfo(const QVariantMap &fileInfo)
{
updateFileInfo(fileInfo);
+ if (is_downloading_completed && downloadHoldOffTimer) {
+ // Don't need this timer anymore
+ killTimer(downloadHoldOffTimer);
+ downloadHoldOffTimer = 0;
+ }
if (autoLoad) {
- load();
+ downloadFile();
}
emitQueuedSignals();
}
+void TDLibFile::timerEvent(QTimerEvent *)
+{
+ killTimer(downloadHoldOffTimer);
+ downloadHoldOffTimer = 0;
+ if (autoLoad) {
+ downloadFile();
+ }
+}
+
void TDLibFile::handleFileUpdated(int fileId, const QVariantMap &fileInfo)
{
if (id == fileId) {
@@ -256,6 +283,7 @@ void TDLibFile::updateFileInfo(const QVariantMap &file)
bool b, fileChanged = false;
int i = file.value(ID).toInt();
if (id != i) {
+ LOG("File id has changed" << id << "=>" << i);
id = i;
fileChanged = true;
queueSignal(SignalIdChanged);
diff --git a/src/tdlibfile.h b/src/tdlibfile.h
index 9a16af4..0616271 100644
--- a/src/tdlibfile.h
+++ b/src/tdlibfile.h
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2020 Slava Monich at al.
+ Copyright (C) 2020-2021 Slava Monich at al.
This file is part of Fernschreiber.
@@ -45,6 +45,8 @@ class TDLibFile : public QObject
Q_PROPERTY(bool isUploadingActive READ isUploadingActive NOTIFY uploadingActiveChanged)
Q_PROPERTY(bool isUploadingCompleted READ isUploadingCompleted NOTIFY uploadingCompletedChanged)
+ enum { DownloadHoldOffMs = 1000 };
+
public:
TDLibFile();
TDLibFile(TDLibWrapper *tdlib);
@@ -102,10 +104,14 @@ signals:
private slots:
void handleFileUpdated(int fileId, const QVariantMap &fileInfo);
+protected:
+ void timerEvent(QTimerEvent *event) Q_DECL_OVERRIDE;
+
private:
void init();
void updateTDLibWrapper(TDLibWrapper* tdlib);
void updateFileInfo(const QVariantMap &fileInfo);
+ bool downloadFile();
void queueSignal(uint signal);
void emitQueuedSignals();
@@ -114,6 +120,7 @@ private:
uint queuedSignals;
uint firstQueuedSignal;
bool autoLoad;
+ int downloadHoldOffTimer;
// file
QVariantMap infoMap;
int id;
diff --git a/src/tdlibreceiver.cpp b/src/tdlibreceiver.cpp
index a367937..045e27e 100644
--- a/src/tdlibreceiver.cpp
+++ b/src/tdlibreceiver.cpp
@@ -38,6 +38,7 @@ namespace {
const QString POSITIONS("positions");
const QString PHOTO("photo");
const QString ORDER("order");
+ const QString IS_PINNED("is_pinned");
const QString BASIC_GROUP("basic_group");
const QString SUPERGROUP("supergroup");
const QString LAST_MESSAGE("last_message");
@@ -287,9 +288,20 @@ void TDLibReceiver::processUpdateChatOrder(const QVariantMap &receivedInformatio
void TDLibReceiver::processUpdateChatPosition(const QVariantMap &receivedInformation)
{
const QString chat_id(receivedInformation.value(CHAT_ID).toString());
- const QString order(receivedInformation.value(POSITION).toMap().value(ORDER).toString());
- LOG("Chat position updated for ID" << chat_id << "new order" << order);
- emit chatOrderUpdated(chat_id, order);
+ QVariantMap positionMap = receivedInformation.value(POSITION).toMap();
+
+ QString updateForChatList = positionMap.value(LIST).toMap().value(TYPE).toString();
+ const QString order(positionMap.value(ORDER).toString());
+ bool is_pinned = positionMap.value(IS_PINNED).toBool();
+
+ // We are only processing main chat list updates at the moment...
+ if (updateForChatList == "chatListMain") {
+ LOG("Chat position updated for ID" << chat_id << "new order" << order << "is pinned" << is_pinned);
+ emit chatOrderUpdated(chat_id, order);
+ emit chatPinnedUpdated(chat_id.toLongLong(), is_pinned);
+ } else {
+ LOG("Received chat position update for uninteresting list" << updateForChatList << "ID" << chat_id << "new order" << order << "is pinned" << is_pinned);
+ }
}
void TDLibReceiver::processUpdateChatReadInbox(const QVariantMap &receivedInformation)
diff --git a/src/tdlibreceiver.h b/src/tdlibreceiver.h
index 311610b..7eb8b99 100644
--- a/src/tdlibreceiver.h
+++ b/src/tdlibreceiver.h
@@ -49,6 +49,7 @@ signals:
void unreadChatCountUpdated(const QVariantMap &chatCountInformation);
void chatLastMessageUpdated(const QString &chatId, const QString &order, const QVariantMap &lastMessage);
void chatOrderUpdated(const QString &chatId, const QString &order);
+ void chatPinnedUpdated(qlonglong chatId, bool isPinned);
void chatReadInboxUpdated(const QString &chatId, const QString &lastReadInboxMessageId, int unreadCount);
void chatReadOutboxUpdated(const QString &chatId, const QString &lastReadOutboxMessageId);
void basicGroupUpdated(qlonglong groupId, const QVariantMap &groupInformation);
diff --git a/src/tdlibwrapper.cpp b/src/tdlibwrapper.cpp
index f3c41e5..9ad3a99 100644
--- a/src/tdlibwrapper.cpp
+++ b/src/tdlibwrapper.cpp
@@ -47,8 +47,10 @@ namespace {
const QString USERNAME("username");
const QString THREAD_ID("thread_id");
const QString VALUE("value");
+ const QString CHAT_LIST_TYPE("chat_list_type");
const QString _TYPE("@type");
const QString _EXTRA("@extra");
+ const QString CHAT_LIST_MAIN("chatListMain");
}
TDLibWrapper::TDLibWrapper(AppSettings *appSettings, MceInterface *mceInterface, QObject *parent) : QObject(parent), joinChatRequested(false)
@@ -145,6 +147,7 @@ void TDLibWrapper::initializeTDLibReciever() {
connect(this->tdLibReceiver, SIGNAL(chatPermissionsUpdated(QString, QVariantMap)), this, SIGNAL(chatPermissionsUpdated(QString, QVariantMap)));
connect(this->tdLibReceiver, SIGNAL(chatPhotoUpdated(qlonglong, QVariantMap)), this, SIGNAL(chatPhotoUpdated(qlonglong, QVariantMap)));
connect(this->tdLibReceiver, SIGNAL(chatTitleUpdated(QString, QString)), this, SIGNAL(chatTitleUpdated(QString, QString)));
+ connect(this->tdLibReceiver, SIGNAL(chatPinnedUpdated(qlonglong, bool)), this, SIGNAL(chatPinnedUpdated(qlonglong, bool)));
connect(this->tdLibReceiver, SIGNAL(chatPinnedMessageUpdated(qlonglong, qlonglong)), this, SIGNAL(chatPinnedMessageUpdated(qlonglong, qlonglong)));
connect(this->tdLibReceiver, SIGNAL(messageIsPinnedUpdated(qlonglong, qlonglong, bool)), this, SLOT(handleMessageIsPinnedUpdated(qlonglong, qlonglong, bool)));
connect(this->tdLibReceiver, SIGNAL(usersReceived(QString, QVariantList, int)), this, SIGNAL(usersReceived(QString, QVariantList, int)));
@@ -491,6 +494,56 @@ void TDLibWrapper::sendDocumentMessage(const QString &chatId, const QString &fil
this->sendRequest(requestObject);
}
+void TDLibWrapper::sendVoiceNoteMessage(const QString &chatId, const QString &filePath, const QString &message, const QString &replyToMessageId)
+{
+ LOG("Sending voice note message" << chatId << filePath << message << replyToMessageId);
+ QVariantMap requestObject;
+ requestObject.insert(_TYPE, "sendMessage");
+ requestObject.insert(CHAT_ID, chatId);
+ if (replyToMessageId != "0") {
+ requestObject.insert("reply_to_message_id", replyToMessageId);
+ }
+ QVariantMap inputMessageContent;
+ inputMessageContent.insert(_TYPE, "inputMessageVoiceNote");
+ QVariantMap formattedText;
+ formattedText.insert("text", message);
+ formattedText.insert(_TYPE, "formattedText");
+ inputMessageContent.insert("caption", formattedText);
+ QVariantMap documentInputFile;
+ documentInputFile.insert(_TYPE, "inputFileLocal");
+ documentInputFile.insert("path", filePath);
+ inputMessageContent.insert("voice_note", documentInputFile);
+
+ requestObject.insert("input_message_content", inputMessageContent);
+ this->sendRequest(requestObject);
+}
+
+void TDLibWrapper::sendLocationMessage(const QString &chatId, double latitude, double longitude, double horizontalAccuracy, const QString &replyToMessageId)
+{
+ LOG("Sending location message" << chatId << latitude << longitude << horizontalAccuracy << replyToMessageId);
+ QVariantMap requestObject;
+ requestObject.insert(_TYPE, "sendMessage");
+ requestObject.insert(CHAT_ID, chatId);
+ if (replyToMessageId != "0") {
+ requestObject.insert("reply_to_message_id", replyToMessageId);
+ }
+ QVariantMap inputMessageContent;
+ inputMessageContent.insert(_TYPE, "inputMessageLocation");
+ QVariantMap location;
+ location.insert("latitude", latitude);
+ location.insert("longitude", longitude);
+ location.insert("horizontal_accuracy", horizontalAccuracy);
+ location.insert(_TYPE, "location");
+ inputMessageContent.insert("location", location);
+
+ inputMessageContent.insert("live_period", 0);
+ inputMessageContent.insert("heading", 0);
+ inputMessageContent.insert("proximity_alert_radius", 0);
+
+ requestObject.insert("input_message_content", inputMessageContent);
+ this->sendRequest(requestObject);
+}
+
void TDLibWrapper::sendStickerMessage(const QString &chatId, const QString &fileId, const QString &replyToMessageId)
{
LOG("Sending sticker message" << chatId << fileId << replyToMessageId);
@@ -1002,6 +1055,20 @@ void TDLibWrapper::toggleChatIsMarkedAsUnread(qlonglong chatId, bool isMarkedAsU
this->sendRequest(requestObject);
}
+void TDLibWrapper::toggleChatIsPinned(qlonglong chatId, bool isPinned)
+{
+ LOG("Toggle chat is pinned" << chatId << isPinned);
+ QVariantMap requestObject;
+ requestObject.insert(_TYPE, "toggleChatIsPinned");
+ QVariantMap chatListMap;
+ chatListMap.insert(_TYPE, CHAT_LIST_MAIN);
+ requestObject.insert("chat_list", chatListMap);
+ requestObject.insert(CHAT_ID, chatId);
+ requestObject.insert("is_pinned", isPinned);
+ requestObject.insert("is_marked_as_unread", isPinned);
+ this->sendRequest(requestObject);
+}
+
void TDLibWrapper::setChatDraftMessage(qlonglong chatId, qlonglong threadId, qlonglong replyToMessageId, const QString &draft)
{
LOG("Set Draft Message" << chatId);
@@ -1334,7 +1401,7 @@ void TDLibWrapper::handleChatReceived(const QVariantMap &chatInformation)
void TDLibWrapper::handleUnreadMessageCountUpdated(const QVariantMap &messageCountInformation)
{
- if (messageCountInformation.value("chat_list_type").toString() == "chatListMain") {
+ if (messageCountInformation.value(CHAT_LIST_TYPE).toString() == CHAT_LIST_MAIN) {
this->unreadMessageInformation = messageCountInformation;
emit unreadMessageCountUpdated(messageCountInformation);
}
@@ -1342,7 +1409,7 @@ void TDLibWrapper::handleUnreadMessageCountUpdated(const QVariantMap &messageCou
void TDLibWrapper::handleUnreadChatCountUpdated(const QVariantMap &chatCountInformation)
{
- if (chatCountInformation.value("chat_list_type").toString() == "chatListMain") {
+ if (chatCountInformation.value(CHAT_LIST_TYPE).toString() == CHAT_LIST_MAIN) {
this->unreadChatInformation = chatCountInformation;
emit unreadChatCountUpdated(chatCountInformation);
}
@@ -1449,15 +1516,16 @@ void TDLibWrapper::setInitialParameters()
initialParameters.insert("api_id", TDLIB_API_ID);
initialParameters.insert("api_hash", TDLIB_API_HASH);
initialParameters.insert("database_directory", QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tdlib");
- initialParameters.insert("use_file_database", true);
- initialParameters.insert("use_chat_info_database", true);
- initialParameters.insert("use_message_database", true);
+ bool onlineOnlyMode = this->appSettings->onlineOnlyMode();
+ initialParameters.insert("use_file_database", !onlineOnlyMode);
+ initialParameters.insert("use_chat_info_database", !onlineOnlyMode);
+ initialParameters.insert("use_message_database", !onlineOnlyMode);
initialParameters.insert("use_secret_chats", true);
initialParameters.insert("system_language_code", QLocale::system().name());
QSettings hardwareSettings("/etc/hw-release", QSettings::NativeFormat);
initialParameters.insert("device_model", hardwareSettings.value("NAME", "Unknown Mobile Device").toString());
initialParameters.insert("system_version", QSysInfo::prettyProductName());
- initialParameters.insert("application_version", "0.6");
+ initialParameters.insert("application_version", "0.7");
initialParameters.insert("enable_storage_optimizer", appSettings->storageOptimizer());
// initialParameters.insert("use_test_dc", true);
requestObject.insert("parameters", initialParameters);
diff --git a/src/tdlibwrapper.h b/src/tdlibwrapper.h
index 72c66a8..4a02a02 100644
--- a/src/tdlibwrapper.h
+++ b/src/tdlibwrapper.h
@@ -142,6 +142,8 @@ public:
Q_INVOKABLE void sendPhotoMessage(const QString &chatId, const QString &filePath, const QString &message, const QString &replyToMessageId = "0");
Q_INVOKABLE void sendVideoMessage(const QString &chatId, const QString &filePath, const QString &message, const QString &replyToMessageId = "0");
Q_INVOKABLE void sendDocumentMessage(const QString &chatId, const QString &filePath, const QString &message, const QString &replyToMessageId = "0");
+ Q_INVOKABLE void sendVoiceNoteMessage(const QString &chatId, const QString &filePath, const QString &message, const QString &replyToMessageId = "0");
+ Q_INVOKABLE void sendLocationMessage(const QString &chatId, double latitude, double longitude, double horizontalAccuracy, const QString &replyToMessageId = "0");
Q_INVOKABLE void sendStickerMessage(const QString &chatId, const QString &fileId, const QString &replyToMessageId = "0");
Q_INVOKABLE void sendPollMessage(const QString &chatId, const QString &question, const QVariantList &options, bool anonymous, int correctOption, bool multiple, const QString &replyToMessageId = "0");
Q_INVOKABLE void forwardMessages(const QString &chatId, const QString &fromChatId, const QVariantList &messageIds, bool sendCopy, bool removeCaption);
@@ -186,6 +188,7 @@ public:
Q_INVOKABLE void searchPublicChats(const QString &query);
Q_INVOKABLE void readAllChatMentions(qlonglong chatId);
Q_INVOKABLE void toggleChatIsMarkedAsUnread(qlonglong chatId, bool isMarkedAsUnread);
+ Q_INVOKABLE void toggleChatIsPinned(qlonglong chatId, bool isPinned);
Q_INVOKABLE void setChatDraftMessage(qlonglong chatId, qlonglong threadId, qlonglong replyToMessageId, const QString &draft);
// Others (candidates for extraction ;))
@@ -211,6 +214,7 @@ signals:
void unreadChatCountUpdated(const QVariantMap &chatCountInformation);
void chatLastMessageUpdated(const QString &chatId, const QString &order, const QVariantMap &lastMessage);
void chatOrderUpdated(const QString &chatId, const QString &order);
+ void chatPinnedUpdated(qlonglong chatId, bool isPinned);
void chatReadInboxUpdated(const QString &chatId, const QString &lastReadInboxMessageId, int unreadCount);
void chatReadOutboxUpdated(const QString &chatId, const QString &lastReadOutboxMessageId);
void userUpdated(const QString &userId, const QVariantMap &userInformation);
diff --git a/translations/harbour-fernschreiber-de.ts b/translations/harbour-fernschreiber-de.ts
index aa5579c..95d0042 100644
--- a/translations/harbour-fernschreiber-de.ts
+++ b/translations/harbour-fernschreiber-de.ts
@@ -131,14 +131,6 @@
Verlasse Chat
-
-
- Stummschaltung des Chats aufheben
-
-
-
- Chat stummschalten
-
Unbekannt
@@ -191,6 +183,14 @@
Neuer geheimer Chat
+
+
+ Stummschaltung des Chats aufheben
+
+
+
+ Chat stummschalten
+
ChatInformationTabItemMembersGroups
@@ -248,14 +248,6 @@
Sie
-
-
- Stummschaltung des Chats aufheben
-
-
-
- Chat stummschalten
-
Benutzerinfos
@@ -284,6 +276,22 @@
Entwurf
+
+
+ Chat losheften
+
+
+
+ Chat anheften
+
+
+
+ Stummschaltung des Chats aufheben
+
+
+
+ Chat stummschalten
+
ChatPage
@@ -419,6 +427,14 @@
Im Chat suchen...
+
+
+ Standort: Erlange Position...
+
+
+
+ Standort (%1/%2)
+
ChatSelectionPage
@@ -484,6 +500,10 @@
Dokument öffnen
+
+
+ Dokument zu Downloads kopieren
+
EditGroupChatPermissionsColumn
@@ -1085,8 +1105,16 @@
Download fehlgeschlagen.
-
- Verbinde zum Netzwerk...
+
+ Tippen Sie auf die Titelleiste, um Ihre Chats zu filtern
+
+
+
+ Keine passenden Chats gefunden.
+
+
+
+ Sie können über das Pull-Down-Menü öffentliche Chats finden oder einen Neuen erstellen.
@@ -1308,13 +1336,19 @@
Kanal
-
+
- %1 Mitglied
+
+ %1 Mitglied
+ %1 Mitglieder
+
-
+
- %1 Abonnent
+
+ %1 Abonnent
+ %1 Abonnenten
+
@@ -1403,6 +1437,22 @@
Speicheroptimierer einschalten
+
+
+ Texteingabe nach Senden fokussieren
+
+
+
+ Fokussiert die Texteingabe nach Senden einer Nachricht
+
+
+
+ Nur-Online-Modus einschalten
+
+
+
+ Schaltet das Offline-Caching aus. Bestimmte Features können in diesem Modus eingeschränkt sein oder fehlen. Änderungen erfordern einen Neustart von Fernschreiber, um in Kraft zu treten.
+
StickerPicker
@@ -1430,6 +1480,45 @@
Download fehlgeschlagen.
+
+ VoiceNoteOverlay
+
+
+ Eine Sprachnachricht aufzeichnen
+
+
+
+ Drücken Sie den Knopf, um die Aufzeichnung zu starten
+
+
+
+ Nicht verfügbar
+
+
+
+ Bereit
+
+
+
+ Startet
+
+
+
+ Zeichnet auf
+
+
+
+ Stoppt
+
+
+
+ Aufzeichnung verwenden
+
+
+
+ Sprachnachricht (%1)
+
+
WebPagePreview
diff --git a/translations/harbour-fernschreiber-en.ts b/translations/harbour-fernschreiber-en.ts
index 66f406b..a6dbd3a 100644
--- a/translations/harbour-fernschreiber-en.ts
+++ b/translations/harbour-fernschreiber-en.ts
@@ -131,14 +131,6 @@
Leaving chat
-
-
- Unmute Chat
-
-
-
- Mute Chat
-
Unknown
@@ -191,6 +183,14 @@
New Secret Chat
+
+
+ Unmute Chat
+
+
+
+ Mute Chat
+
ChatInformationTabItemMembersGroups
@@ -248,14 +248,6 @@
You
-
-
- Unmute Chat
-
-
-
- Mute Chat
-
User Info
@@ -284,6 +276,22 @@
Draft
+
+
+ Unpin chat
+
+
+
+ Pin chat
+
+
+
+ Unmute chat
+
+
+
+ Mute chat
+
ChatPage
@@ -419,6 +427,14 @@
Search in chat...
+
+
+ Location: Obtaining position...
+
+
+
+ Location (%1/%2)
+
ChatSelectionPage
@@ -484,6 +500,10 @@
Open Document
+
+
+ Copy Document to Downloads
+
EditGroupChatPermissionsColumn
@@ -1085,12 +1105,20 @@
Download failed.
-
- Connecting to network...
+
+ Tap on the title bar to filter your chats
+
+
+
+ No matching chats found.
+
+
+
+ You can search public chats or create a new chat via the pull-down menu.
-
+ Logging out
@@ -1308,13 +1336,19 @@
Channel
-
+
- %1 member
+
+ %1 member
+ %1 members
+
-
+
- %1 subscriber
+
+ %1 subscriber
+ %1 subscribers
+
@@ -1403,6 +1437,22 @@
Enable storage optimizer
+
+
+ Focus text input area after send
+
+
+
+ Focus the text input area after sending a message
+
+
+
+ Enable online-only mode
+
+
+
+ Disables offline caching. Certain features may be limited or missing in this mode. Changes require a restart of Fernschreiber to take effect.
+
StickerPicker
@@ -1430,6 +1480,45 @@
Download failed.
+
+ VoiceNoteOverlay
+
+
+ Record a Voice Note
+
+
+
+ Press the button to start recording
+
+
+
+ Unavailable
+
+
+
+ Starting
+
+
+
+ Recording
+
+
+
+ Stopping
+
+
+
+ Use recording
+
+
+
+ Voice Note (%1)
+
+
+
+ Ready
+
+
WebPagePreview
@@ -1815,21 +1904,21 @@
- has added %1 to the chat
+ has added %1 to the chat
- has removed %1 from the chat
+ has removed %1 from the chat
myself
- have added %1 to the chat
+ have added %1 to the chat
myself
- have removed %1 from the chat
+ have removed %1 from the chat
diff --git a/translations/harbour-fernschreiber-es.ts b/translations/harbour-fernschreiber-es.ts
index c460743..9ee8018 100644
--- a/translations/harbour-fernschreiber-es.ts
+++ b/translations/harbour-fernschreiber-es.ts
@@ -129,14 +129,6 @@
Saliendo de la charla
-
-
- Notificar
-
-
-
- No notificar
-
Desconocido
@@ -188,6 +180,14 @@
Charla secreta
+
+
+ Notificar
+
+
+
+ No notificar
+
ChatInformationTabItemMembersGroups
@@ -245,14 +245,6 @@
Usted
-
-
- Notificar
-
-
-
- No notificar
-
Usuario
@@ -271,16 +263,32 @@
-
+ Marcar como no leído
-
+ Borrador
+ Marcar como leído
+
+
+
+
+
+
+
+
+
+ Notificar
+
+
+
+ No notificar
+
ChatPage
@@ -403,11 +411,19 @@
-
+ Buscar en charla
-
+ Buscar
+
+
+
+ Ubicación: Recibiendo posición ...
+
+
+
+ Ubicación (%1/%2)
@@ -474,6 +490,10 @@
Abrir Documento
+
+
+
+
EditGroupChatPermissionsColumn
@@ -785,21 +805,21 @@
-
+ ha añadido %1 a charla
-
+ ha quitado %1 de charla
myself
-
+ ha añadido %1 a la charla
myself
-
+ ha quitado %1 de charla
@@ -836,7 +856,7 @@
- Marcar número de teléfono para continuar.
+ Marcar el número de teléfono para continuar.
@@ -884,7 +904,7 @@
- Usar el formato internacional %1
+ Usa el formato internacional %1
@@ -910,7 +930,7 @@
- Copiar mensaje
+ Copiar
@@ -930,7 +950,7 @@
- Seleccionar mensaje
+ Seleccionar
@@ -938,11 +958,11 @@
- Desanclar mensaje
+ Mensaje desanclado
-
+ Desanclar mensaje
@@ -1059,19 +1079,31 @@
-
+ Filtrar las charlas...
-
+ Buscar charlas
- Bajada de %1 exitosa.
+ Bajada de %1 exitosa.
- Error al bajar
+ Error al bajar
+
+
+
+ Tocar la barra de título para filtrar las charlas
+
+
+
+ No hay coincidencias.
+
+
+
+ Puede buscar charlas públicas o crear un nueva charla a través de la polea de opciones.
@@ -1094,7 +1126,7 @@
- Desanclar mensaje
+ Mensaje desanclado
@@ -1271,43 +1303,47 @@
SearchChatsPage
-
+ No se han encontrado charlas.
-
+ Buscando charlas...
- Privado
+ Privado
-
+ Grupo
-
+ Canal
-
+
- %1 miembros
+
+ %1 miembros
+
-
+
- %1 suscriptores
+
+ %1 suscriptores
+
-
+ Buscar charla
-
+ A b c
-
+ Para iniciar la búsqueda se necesitan al menos 5 caracteres
@@ -1342,15 +1378,15 @@
- Notificaciones
+ Notificar en
- Todos los eventos
+ Eventos
- Sólo nuevos eventos
+ Nuevos eventos
@@ -1384,6 +1420,22 @@
Optimizador de almacenamiento
+
+
+ Enfocar área de entrada de texto
+
+
+
+ Enfoca el área de entrada de texto después de enviar un mensaje
+
+
+
+
+
+
+
+
+
StickerPicker
@@ -1411,6 +1463,45 @@
Error al bajar
+
+ VoiceNoteOverlay
+
+
+ Nota de voz
+
+
+
+ Presionar el botón para iniciar a grabar
+
+
+
+ No diponible
+
+
+
+ Iniciando
+
+
+
+ Grabando
+
+
+
+ Deteniendo
+
+
+
+ Usar grabación
+
+
+
+ Nota de voz (%1)
+
+
+
+ Listo
+
+
WebPagePreview
@@ -1796,21 +1887,21 @@
-
+ ha añadido %1 a la charla
-
+ ha quitado %1 de la charla
myself
-
+ ha añadido %1 a la charla
myself
-
+ ha añadido %1 de la charla
diff --git a/translations/harbour-fernschreiber-fi.ts b/translations/harbour-fernschreiber-fi.ts
index da59c9a..ca7eafc 100644
--- a/translations/harbour-fernschreiber-fi.ts
+++ b/translations/harbour-fernschreiber-fi.ts
@@ -131,14 +131,6 @@
Poistutaan keskustelusta
-
-
- Poista keskustelun vaimennus
-
-
-
- Vaimenna keskustelu
-
Tuntematon
@@ -191,6 +183,14 @@
Uusi salattu keskustelu
+
+
+ Poista keskustelun vaimennus
+
+
+
+ Vaimenna keskustelu
+
ChatInformationTabItemMembersGroups
@@ -248,14 +248,6 @@
Sinä
-
-
- Poista keskustelun vaimennus
-
-
-
- Vaimenna keskustelu
-
Käyttäjän tiedot
@@ -274,16 +266,32 @@
-
+ Merkitse keskustelu lukemattomaksi
-
+ Luonnos
+ Merkitse keskustelu luetuksi
+
+
+
+
+
+
+
+
+
+ Poista keskustelun vaimennus
+
+
+
+ Vaimenna keskustelu
+
ChatPage
@@ -413,11 +421,19 @@
-
+ Etsi keskustelusta
-
+ Etsi keskustelusta...
+
+
+
+ Sijainti: Paikannetaan...
+
+
+
+ Sijainti (%1/%2)
@@ -485,6 +501,10 @@
Avaa dokumentti
+
+
+
+
EditGroupChatPermissionsColumn
@@ -796,21 +816,21 @@
-
+ lisäsi käyttäjän %1 keskusteluun
-
+ posit käyttäjän %1 keskustelusta
myself
-
+ lisäsit käyttäjän %1 keskusteluun
myself
-
+ poistit käyttäjän %1 keskustelusta
@@ -949,11 +969,11 @@
- Viestin kiinnitys poistettu
+ Viestin kiinnitys poistettu
-
+ Poista viestin kiinnitys
@@ -1071,19 +1091,31 @@
-
+ Suodata keskustelujasi...
-
+ Etsi keskusteluista
-
+ Tiedoston %1 lataus onnistui.
- Lataus epäonnistui.
+ Lataus epäonnistui.
+
+
+
+ Kosketa otsikkopalkkia suodattaaksesi keskusteluja
+
+
+
+ Hakua vastaavia keskusteluja ei löytynyt,
+
+
+
+ Voit etsiä julkisia keskusteluja tai luoda uuden keskustelun alasvetovalikosta.
@@ -1291,43 +1323,49 @@
SearchChatsPage
-
+ Keskusteluja ei löytynyt
-
+ Etsitään keskusteluja...
- Yksityinen keskustelu
+ Yksityinen keskustelu
-
+ Ryhmä
-
+ Kanava
-
+
- %1 jäsen
+
+ %1 jäsen
+ %1 jäsentä
+
-
+
- %1 tilaaja
+
+ %1 tilaaja
+ %1 tilaajaa
+
-
+ Etsi keskusteluja
-
+ Etsi keskustelua...
-
+ Syötä hakusanasi etsiäksesi (vähintään 5 merkkiä tarvitaan)
@@ -1404,6 +1442,22 @@
Käytä tallennustilan optimointia
+
+
+ Kohdista tekstinsyöttökenttä lähetyksen jälkeen
+
+
+
+ Kohdista tekstinsyöttökenttä viestin lähetyksen jälkeen
+
+
+
+
+
+
+
+
+
StickerPicker
@@ -1431,6 +1485,45 @@
Lataus epäonnistui.
+
+ VoiceNoteOverlay
+
+
+ Nauhoita ääniviesti
+
+
+
+ Paina nappia aloittaaksesi nauhoituksen
+
+
+
+ Ei saatavilla
+
+
+
+ Aloitetaan
+
+
+
+ Nauhoitetaan
+
+
+
+ Lopetetaan
+
+
+
+ Käytä nauhoitusta
+
+
+
+ Ääniviesti (%1)
+
+
+
+ Valmis
+
+
WebPagePreview
@@ -1816,21 +1909,21 @@
-
+ lisäsi käyttäjä %1 keskusteluun
-
+ posit käyttäjän %1 keskustelusta
myself
-
+ lisäsit käyttäjän %1 keskusteluun
myself
-
+ poistit käyttäjän %1 keskustelusta
diff --git a/translations/harbour-fernschreiber-hu.ts b/translations/harbour-fernschreiber-hu.ts
index 9491de2..30a75b0 100644
--- a/translations/harbour-fernschreiber-hu.ts
+++ b/translations/harbour-fernschreiber-hu.ts
@@ -129,14 +129,6 @@
-
-
- Csevegés némítás feloldása
-
-
-
- Csevegés némítása
-
Ismeretlen
@@ -188,6 +180,14 @@
+
+
+ Csevegés némítás feloldása
+
+
+
+ Csevegés némítása
+
ChatInformationTabItemMembersGroups
@@ -245,14 +245,6 @@
Te
-
-
- Csevegés némítás feloldása
-
-
-
- Csevegés némítása
-
@@ -281,6 +273,22 @@
+
+
+
+
+
+
+
+
+
+
+ Csevegés némítás feloldása
+
+
+
+ Csevegés némítása
+
ChatPage
@@ -409,6 +417,14 @@
+
+
+
+
+
+
+
+
ChatSelectionPage
@@ -474,6 +490,10 @@
Dokumentum megyitása
+
+
+
+
EditGroupChatPermissionsColumn
@@ -1074,8 +1094,16 @@
A letöltés nem sikerült.
-
- Csatlakozás a hálózathoz...
+
+
+
+
+
+
+
+
+
+
@@ -1289,13 +1317,17 @@
-
+
- %1 tag
+
+ %1 tag
+
-
+
- %1 feliratkozott
+
+ %1 feliratkozott
+
@@ -1384,6 +1416,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
StickerPicker
@@ -1411,6 +1459,45 @@
A letöltés nem sikerült.
+
+ VoiceNoteOverlay
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
WebPagePreview
diff --git a/translations/harbour-fernschreiber-it.ts b/translations/harbour-fernschreiber-it.ts
index 50911b1..03a0be9 100644
--- a/translations/harbour-fernschreiber-it.ts
+++ b/translations/harbour-fernschreiber-it.ts
@@ -131,14 +131,6 @@
Lascia chat
-
-
- Riattiva suoni chat
-
-
-
- Silenzia chat
-
Sconosciuto
@@ -191,6 +183,14 @@
Nuova chat segreta
+
+
+ Riattiva suoni chat
+
+
+
+ Silenzia chat
+
ChatInformationTabItemMembersGroups
@@ -248,14 +248,6 @@
Tu
-
-
- Riattiva suoni chat
-
-
-
- Silenzia chat
-
Info utente
@@ -274,15 +266,31 @@
-
+ Segna chat come non letta
-
+ Bozza
-
+ Segna chat come letta
+
+
+
+ Togli chat in evidenza
+
+
+
+ Metti chat in evidenza
+
+
+
+ Riattiva suoni chat
+
+
+
+ Silenzia chat
@@ -413,11 +421,19 @@
-
+ Cerca nella chat
-
+ Cerca nella chat...
+
+
+
+ Posizione: ottengo posizione...
+
+
+
+ Posizione(%1/%2)
@@ -484,6 +500,10 @@
Apri documento
+
+
+
+
EditGroupChatPermissionsColumn
@@ -795,21 +815,21 @@
-
+ ha aggiunto %1 alla chat
-
+ ha rimosso %1 dalla chat
myself
-
+ hai aggiunto %1 alla chat
myself
-
+ hai rimosso %1 dalla chat
@@ -948,11 +968,11 @@
- Messaggio non più in evidenza
+ Messaggio non più in evidenza
-
+ Togli messaggio in evidenza
@@ -1070,19 +1090,31 @@
-
+ Filtra le chat...
-
+ Ricerca chat
- Download di %1 completato.
+ Download di %1 completato.
- Download non riuscito.
+ Download non riuscito.
+
+
+
+ Clicca sulla barra del titolo per filtrare le tue chat
+
+
+
+ Nessuna chat corrispondente.
+
+
+
+ Puoi creare una nuova chat o cercare chat pubbliche dal menu a trascinamento.
@@ -1290,43 +1322,49 @@
SearchChatsPage
-
+ Nessuna chat trovata.
-
+ Ricerca chat...
- Chat privata
+ Chat privata
-
+ Gruppo
-
+ Canale
-
+
- %1 membro
+
+ %1 membro
+ %1 membri
+
-
+
- %1 abbonato
+
+ %1 abbonato
+ %1 abbonati
+
-
+ Cerca chat
-
+ Cerca chat...
-
+ Scrivi il testo che vuoi cercare (almeno 5 caratteri)
@@ -1403,6 +1441,22 @@
Abilita ottimizzazione memoria
+
+
+ Tastiera in primo piano dopo invio
+
+
+
+ Mantieni la tastiera in primo piano dopo aver inviato un messaggio
+
+
+
+
+
+
+
+
+
StickerPicker
@@ -1430,6 +1484,45 @@
Download non riuscito.
+
+ VoiceNoteOverlay
+
+
+ Registra una nota vocale
+
+
+
+ Premi il pulsante per iniziare la registrazione
+
+
+
+ Non disponibile
+
+
+
+ Inizia
+
+
+
+ In registrazione
+
+
+
+ Stop
+
+
+
+ Usa registrazione
+
+
+
+ Nota vocale (%1)
+
+
+
+ Pronto
+
+
WebPagePreview
@@ -1815,21 +1908,21 @@
-
+ ha aggiunto %1 alla chat
-
+ ha rimosso %1 dalla chat
myself
-
+ hai aggiunto %1 alla chat
myself
-
+ hai rimosso %1 dalla chat
diff --git a/translations/harbour-fernschreiber-pl.ts b/translations/harbour-fernschreiber-pl.ts
index f3c30d7..953b8e8 100644
--- a/translations/harbour-fernschreiber-pl.ts
+++ b/translations/harbour-fernschreiber-pl.ts
@@ -105,14 +105,6 @@
ChatInformationPageContent
-
-
- Wyłącz wyciszenie czatu
-
-
-
- Wycisz czat
-
Nieznany
@@ -194,6 +186,14 @@
Nowy tajny czat
+
+
+ Wyłącz wyciszenie czatu
+
+
+
+ Wycisz czat
+
ChatInformationTabItemMembersGroups
@@ -251,14 +251,6 @@
Ty
-
-
- Wyłącz wyciszenie czatu
-
-
-
- Wycisz czat
-
Informacje o użytkowniku
@@ -277,16 +269,32 @@
-
+ Oznacz czat jako nieprzeczytany
-
+ Kopia robocza
+ Oznacz czat jako przeczytany
+
+
+
+
+
+
+
+
+
+ Wyłącz wyciszenie czatu
+
+
+
+ Wycisz czat
+
ChatPage
@@ -423,11 +431,19 @@
-
+ Wyszukaj w czacie
-
+ Wyszukaj w czacie
+
+
+
+ Lokalizacja: Uzyskanie pozycji ...
+
+
+
+ Lokalizacja (%1/%2)
@@ -494,6 +510,10 @@
Otwórz dokument
+
+
+
+
EditGroupChatPermissionsColumn
@@ -805,21 +825,21 @@
-
+ dodał %1 do czatu
-
+ usunął %1 z czatu
myself
-
+ dodsałem %1 do czatu
myself
-
+ usunąłem %1 z czatu
@@ -958,11 +978,11 @@
- Wiadomość opięta
+ Wiadomość odpięta
-
+ Odepnij wiadomość
@@ -1081,19 +1101,31 @@
-
+ Filtruj swoje czaty...
-
+ Wyszukaj czaty
-
+ Pobieranie %1 zakończone sukcesem
-
+ Nieudane pobranie
+
+
+
+ Dotknij paska tytułowego, aby filtrować swoje czaty
+
+
+
+ Brak pasujących czatów
+
+
+
+ Możesz przeszukiwać czaty publiczne lub utworzyć nowy czat za pomocą menu rozwijanego z góry.
@@ -1309,43 +1341,51 @@
SearchChatsPage
-
+ Brak pasujących czatów
-
+ Wyszukiwanie czatów...
- Prywatny czat
+ Prywatny czat
-
+ Grupa
-
+ Kanał
-
+
- %1 członek
+
+ %1 członek
+ %1 członków
+ %1 członków
+
-
+
- %1 subskrybent
+
+ %1 subskrybent
+ %1 subskrybentów
+ %1 subskrybentów
+
-
+ Wyszukaj czaty
-
+ Wyszukaj czat...
-
+ Wprowadź zapytanie aby zacząć wyszukiwanie (minimum 5 znaków)
@@ -1422,6 +1462,22 @@
Włącz optymalizację pamięci
+
+
+ Po wysłaniu zaznacz pole wprowadzania tekstu
+
+
+
+ Po wysłaniu wiadomości zaznacz pole wprowadzania tekstu
+
+
+
+
+
+
+
+
+
StickerPicker
@@ -1449,6 +1505,45 @@
Nieudane pobieranie
+
+ VoiceNoteOverlay
+
+
+ Nagraj notatkę głosową
+
+
+
+ Naciśnij przycisk, aby zacząć nagrywać
+
+
+
+ Niedostepne
+
+
+
+ Uruchamianie
+
+
+
+ Nagrywanie
+
+
+
+ Zatrzymywanie
+
+
+
+ Użyj nagrywania
+
+
+
+ Notatka głosowa (%1)
+
+
+
+ Gotowy
+
+
WebPagePreview
@@ -1834,21 +1929,21 @@
-
+ dodał %1 do czatu
-
+ usunął %1 z czatu
myself
-
+ dodałem %1 do czatu
myself
-
+ usunąłem %1 z czatu
diff --git a/translations/harbour-fernschreiber-ru.ts b/translations/harbour-fernschreiber-ru.ts
index 8d1ec48..e8385c5 100644
--- a/translations/harbour-fernschreiber-ru.ts
+++ b/translations/harbour-fernschreiber-ru.ts
@@ -123,59 +123,51 @@
- Выйти из чата
+ Выйти из чата
- Зайти в чат
+ Зайти в чат
- Выход из чата
-
-
-
- Включить уведомления
-
-
-
- Выключить уведомления
+ Выход из чата
- Неизвестный
+ Неизвестный
group title header
- Заголовок чата
+ Заголовок чата
- Введите 1-128 символов
+ Введите 1-128 символов
- Информация отсутствует
+ Информация отсутствует
group or user infotext header
- Информация
+ Информация
user phone number header
- Номер телефона
+ Номер телефона
header
- Ссылка для приглашения
+ Ссылка для приглашения
- Ссылка для приглашения скопирована в буффер обмена
+ Ссылка для приглашения скопирована в буффер обмена
@@ -194,6 +186,14 @@
Новый секретный чат
+
+
+ Включить уведомления
+
+
+
+ Выключить уведомления
+
ChatInformationTabItemMembersGroups
@@ -228,17 +228,17 @@
Button: groups in common (short)
- Группы
+ Группы
Button: Group Members
- Участники группы
+ Участники группы
Button: Chat Settings
- Настройки
+ Настройки
@@ -251,14 +251,6 @@
Вы
-
-
- Включить уведомления
-
-
-
- Выключить уведомления
-
Информация о пользователе
@@ -277,16 +269,32 @@
-
+ Отметить чат как непрочитанный
-
+ Черновик
+ Отметить чат как прочитанный
+
+
+
+
+
+
+
+
+
+ Включить уведомления
+
+
+
+ Выключить уведомления
+
ChatPage
@@ -423,11 +431,19 @@
-
+ Найти в Чате
-
+ Поиск...
+
+
+
+ Определение координат...
+
+
+
+ Местоположение (%1/%2)
@@ -438,18 +454,18 @@
- Тут пока ничего нет
+ Тут пока ничего нет
CoverPage
- непрочитанное сообщение
+ сообщение
- непрочитанных сообщений
+ сообщений
@@ -469,7 +485,7 @@
- Подключен
+ В сети
@@ -477,11 +493,11 @@
- чат
+ чате
- чаты
+ чатах
@@ -494,6 +510,10 @@
Открыть документ
+
+
+
+
EditGroupChatPermissionsColumn
@@ -805,21 +825,21 @@
-
+ %1 добавлен в чат
-
+ %1 удалён из чата
myself
-
+ %1 добавлены в чат
myself
-
+ %1 удалены из чата
@@ -958,11 +978,11 @@
- Сообщение откреплено
+ Сообщение откреплено
-
+ Открепить сообщение
@@ -991,7 +1011,7 @@
- У вас нет никаких контактов.
+ У вас нет никаких контактов.
@@ -1081,19 +1101,31 @@
-
+ Выбрать чат...
-
+ Найти Чаты
- Успешно скачано %1.
+ Успешно скачано %1.
- Ошибка скачивания.
+ Ошибка скачивания.
+
+
+
+ Коснитесь строки заголовка, чтобы отфильтровать ваши чаты
+
+
+
+ Совпадающих чатов не найдено.
+
+
+
+ Вы можете искать публичные чаты или создать новый чат с помощью выпадающего меню
@@ -1309,43 +1341,51 @@
SearchChatsPage
-
+ Чаты не найдены
-
+ Идёт поиск чатов...
- Приватный Чат
+ Приватный Чат
-
+ Группа
-
+ Канал
-
+
- %1 участников
+
+ %1 участников
+
+
+
-
+
- %1 подписчиков
+
+ %1 подписчиков
+
+
+
-
+ Найти Чаты
-
+ Поиск...
-
+ Введите не менее 5 символов, чтобы начать поиск
@@ -1422,6 +1462,22 @@
Включить оптимизацию хранилища
+
+
+ Приоритет фокусировки при разговоре в чате
+
+
+
+ Сфокусироваться на поле ввода текста после отправки сообщения
+
+
+
+
+
+
+
+
+
StickerPicker
@@ -1449,6 +1505,45 @@
Ошибка скачивания.
+
+ VoiceNoteOverlay
+
+
+ Голосовое сообщение
+
+
+
+ Коснитесь кнопки для записи
+
+
+
+ Недоступно
+
+
+
+ Пуск
+
+
+
+ Запись
+
+
+
+ Стоп
+
+
+
+ Использовать запись
+
+
+
+ Аудиозаметка (%1)
+
+
+
+ Готов
+
+
WebPagePreview
@@ -1834,21 +1929,21 @@
-
+ %1 добавлен в чат
-
+ %1 удалён из чата
myself
-
+ %1 добавлены в чат
myself
-
+ %1 удалены из чата
diff --git a/translations/harbour-fernschreiber-sv.ts b/translations/harbour-fernschreiber-sv.ts
index 16cc679..80230eb 100644
--- a/translations/harbour-fernschreiber-sv.ts
+++ b/translations/harbour-fernschreiber-sv.ts
@@ -131,14 +131,6 @@
Lämnar chatten
-
-
- Slå på chatten
-
-
-
- Stäng av chatten
-
Okänd
@@ -191,6 +183,14 @@
Ny hemlig chatt
+
+
+ Slå på chatten
+
+
+
+ Stäng av chatten
+
ChatInformationTabItemMembersGroups
@@ -248,14 +248,6 @@
Du
-
-
- Slå på chatten
-
-
-
- Stäng av chatten
-
Användarinfo
@@ -278,12 +270,28 @@
-
+ Utkast
Markera chatten som läst
+
+
+
+
+
+
+
+
+
+
+ Slå på chatten
+
+
+
+ Stäng av chatten
+
ChatPage
@@ -419,6 +427,14 @@
Sök i chatten...
+
+
+ Plats: Hämtar position...
+
+
+
+ Plats (%1/%2)
+
ChatSelectionPage
@@ -484,6 +500,10 @@
Öppna dokument
+
+
+
+
EditGroupChatPermissionsColumn
@@ -1085,8 +1105,16 @@
Nerladdning misslyckades.
-
- Ansluter till nätverket...
+
+ Tryck på titelfältet för att filtrera dina chattar
+
+
+
+ Ingen passande chatt hittades.
+
+
+
+ Du kan söka efter allmänna chattar eller skapa en ny chatt via toppmenyn.
@@ -1308,13 +1336,19 @@
Kanal
-
+
- %1 medlemmar
+
+ %1 medlem
+ %1 medlemmar
+
-
+
- %1 prenumeranter
+
+ %1 prenumerant
+ %1 prenumeranter
+
@@ -1403,6 +1437,22 @@
Aktivera lagringsoptimering
+
+
+ Fokusera textinmatningsfältet efter sändning
+
+
+
+ Fokusera textinmatningsfältet efter att ett meddelande skickats
+
+
+
+
+
+
+
+
+
StickerPicker
@@ -1430,6 +1480,45 @@
Nerladdning misslyckades.
+
+ VoiceNoteOverlay
+
+
+ Spela in ett röstmeddelande
+
+
+
+ Tryck på knappen för att starta inspelning
+
+
+
+ Ej tillgänglig
+
+
+
+ Startar
+
+
+
+ Spelar in
+
+
+
+ Stoppar
+
+
+
+ Använd inspelning
+
+
+
+ Röstmeddelande (%1)
+
+
+
+ Klar
+
+
WebPagePreview
diff --git a/translations/harbour-fernschreiber-zh_CN.ts b/translations/harbour-fernschreiber-zh_CN.ts
index 9b057bf..6dc6501 100644
--- a/translations/harbour-fernschreiber-zh_CN.ts
+++ b/translations/harbour-fernschreiber-zh_CN.ts
@@ -129,14 +129,6 @@
正在离开对话
-
-
- 取消对话静音
-
-
-
- 静音对话
-
未知
@@ -188,6 +180,14 @@
新加密对话
+
+
+ 取消对话静音
+
+
+
+ 静音对话
+
ChatInformationTabItemMembersGroups
@@ -245,14 +245,6 @@
你
-
-
- 取消对话静音
-
-
-
- 静音对话
-
用户信息
@@ -271,16 +263,32 @@
-
+ 标记此对话为未读
-
+ 草稿
+ 标记为已读
+
+
+
+
+
+
+
+
+
+ 取消对话静音
+
+
+
+ 静音对话
+
ChatPage
@@ -403,11 +411,19 @@
-
+ 对话内搜索
-
+ 正在搜索对话内容…
+
+
+
+ 位置:正在获取位置…
+
+
+
+ 位置 (%1/%2)
@@ -425,7 +441,8 @@
CoverPage
- 未读消息
+ 未读
+消息
@@ -474,6 +491,10 @@
打开文档
+
+
+
+
EditGroupChatPermissionsColumn
@@ -574,7 +595,7 @@
- 发送语言消息
+ 发送语音消息
@@ -785,21 +806,21 @@
-
+ 已加入 %1 到此对话
-
+ 已从此对话移除 %1
myself
-
+ 已加入 %1 到此对话
myself
-
+ 已从此对话移除 %1
@@ -1059,11 +1080,11 @@
-
+ 筛选你的对话…
-
+ 搜索对话
@@ -1074,8 +1095,16 @@
下载失败。
-
- 正在连接到网络…
+
+ 点击顶部状态栏即可筛选你的对话
+
+
+
+ 没有找到匹配的对话。
+
+
+
+ 你可以搜索公共对话或通过下拉菜单创建新对话。
@@ -1271,43 +1300,47 @@
SearchChatsPage
-
+ 没有找到对话。
-
+ 正在搜索对话…
- 个人对话
+ 个人对话
-
+ 群组
-
+ 频道
-
+
- %1 位成员
+
+ %1 位成员
+
-
+
- %1 位订阅者
+
+ %1 位订阅者
+
-
+ 搜索对话
-
+ 搜索对话…
-
+ 输入你要搜索的内容(至少需要输入5个字符)
@@ -1384,6 +1417,22 @@
启用储存加速器
+
+
+ 发送后聚焦文本输入区域
+
+
+
+ 发送消息后聚焦文本输入区域
+
+
+
+
+
+
+
+
+
StickerPicker
@@ -1411,6 +1460,45 @@
下载失败
+
+ VoiceNoteOverlay
+
+
+ 录制语音消息
+
+
+
+ 按下按钮即可开始录音
+
+
+
+ 不可用
+
+
+
+ 正在启动
+
+
+
+ 正在录音
+
+
+
+ 正在停止
+
+
+
+ 使用录音
+
+
+
+ 语音消息 (%1)
+
+
+
+ 就绪
+
+
WebPagePreview
@@ -1788,7 +1876,7 @@
- 已关闭!
+ 已关闭!
@@ -1796,21 +1884,21 @@
-
+ 已加入 %1 到此对话
-
+ 已从此对话移除 %1
myself
-
+ 已加入 %1 到此对话
myself
-
+ 已从此对话移除 %1
diff --git a/translations/harbour-fernschreiber.ts b/translations/harbour-fernschreiber.ts
index 99c085d..0ae37b2 100644
--- a/translations/harbour-fernschreiber.ts
+++ b/translations/harbour-fernschreiber.ts
@@ -131,14 +131,6 @@
Leaving chat
-
-
- Unmute Chat
-
-
-
- Mute Chat
-
Unknown
@@ -191,6 +183,14 @@
+
+
+ Unmute Chat
+
+
+
+ Mute Chat
+
ChatInformationTabItemMembersGroups
@@ -248,14 +248,6 @@
You
-
-
- Unmute Chat
-
-
-
- Mute Chat
-
User Info
@@ -284,6 +276,22 @@
+
+
+
+
+
+
+
+
+
+
+ Unmute chat
+
+
+
+ Mute chat
+
ChatPage
@@ -419,6 +427,14 @@
+
+
+
+
+
+
+
+
ChatSelectionPage
@@ -484,6 +500,10 @@
Open Document
+
+
+
+
EditGroupChatPermissionsColumn
@@ -1085,8 +1105,16 @@
Download failed.
-
- Connecting to network...
+
+
+
+
+
+
+
+
+
+
@@ -1308,13 +1336,19 @@
-
+
- %1 member
+
+ %1 member
+
+
-
+
- %1 subscriber
+
+ %1 subscriber
+
+
@@ -1403,6 +1437,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
StickerPicker
@@ -1430,6 +1480,45 @@
Download failed.
+
+ VoiceNoteOverlay
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
WebPagePreview