mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 19:08:58 +03:00
Merge pull request #100 from Nheko-Reborn/file-encryption
Add file encryption / decryption support
This commit is contained in:
commit
9d9b214e4c
29 changed files with 734 additions and 894 deletions
|
@ -31,8 +31,8 @@ if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
|||
QT_PKG="59"
|
||||
fi
|
||||
|
||||
wget https://cmake.org/files/v3.12/cmake-3.12.2-Linux-x86_64.sh
|
||||
sudo sh cmake-3.12.2-Linux-x86_64.sh --skip-license --prefix=/usr/local
|
||||
wget https://cmake.org/files/v3.15/cmake-3.15.5-Linux-x86_64.sh
|
||||
sudo sh cmake-3.15.5-Linux-x86_64.sh --skip-license --prefix=/usr/local
|
||||
|
||||
mkdir -p build-libsodium
|
||||
( cd build-libsodium
|
||||
|
|
|
@ -13,6 +13,9 @@ if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
|||
|
||||
sudo update-alternatives --set gcc "/usr/bin/${C_COMPILER}"
|
||||
sudo update-alternatives --set g++ "/usr/bin/${CXX_COMPILER}"
|
||||
|
||||
export PATH="/usr/local/bin/:${PATH}"
|
||||
cmake --version
|
||||
fi
|
||||
|
||||
if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
||||
|
@ -35,7 +38,8 @@ cmake --build .deps
|
|||
# Build nheko
|
||||
cmake -GNinja -H. -Bbuild \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_INSTALL_PREFIX=.deps/usr
|
||||
-DCMAKE_INSTALL_PREFIX=.deps/usr \
|
||||
-DBUILD_SHARED_LIBS=ON # weird workaround, as the boost 1.70 cmake files seem to be broken?
|
||||
cmake --build build
|
||||
|
||||
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
|
||||
|
|
|
@ -259,7 +259,7 @@ include(FeatureSummary)
|
|||
set(Boost_USE_STATIC_LIBS OFF)
|
||||
set(Boost_USE_STATIC_RUNTIME OFF)
|
||||
set(Boost_USE_MULTITHREADED ON)
|
||||
find_package(Boost 1.66 REQUIRED
|
||||
find_package(Boost 1.70 REQUIRED
|
||||
COMPONENTS atomic
|
||||
chrono
|
||||
date_time
|
||||
|
@ -365,6 +365,7 @@ qt5_wrap_cpp(MOC_HEADERS
|
|||
src/CommunitiesList.h
|
||||
src/LoginPage.h
|
||||
src/MainWindow.h
|
||||
src/MxcImageProvider.h
|
||||
src/InviteeItem.h
|
||||
src/QuickSwitcher.h
|
||||
src/RegisterPage.h
|
||||
|
|
|
@ -92,11 +92,11 @@ sudo port install nheko
|
|||
- Qt5 (5.8 or greater). Qt 5.7 adds support for color font rendering with
|
||||
Freetype, which is essential to properly support emoji, 5.8 adds some features
|
||||
to make interopability with Qml easier.
|
||||
- CMake 3.1 or greater.
|
||||
- CMake 3.15 or greater. (Lower version may work, but may break boost linking)
|
||||
- [mtxclient](https://github.com/Nheko-Reborn/mtxclient)
|
||||
- [LMDB](https://symas.com/lightning-memory-mapped-database/)
|
||||
- [cmark](https://github.com/commonmark/cmark)
|
||||
- Boost 1.66 or greater.
|
||||
- Boost 1.70 or greater.
|
||||
- [libolm](https://git.matrix.org/git/olm)
|
||||
- [libsodium](https://github.com/jedisct1/libsodium)
|
||||
- [spdlog](https://github.com/gabime/spdlog)
|
||||
|
|
|
@ -34,6 +34,7 @@ install:
|
|||
lmdb:%PLATFORM%-windows
|
||||
openssl:%PLATFORM%-windows
|
||||
zlib:%PLATFORM%-windows
|
||||
- vcpkg upgrade --no-dry-run
|
||||
|
||||
build_script:
|
||||
# VERSION format: branch-master/branch-1.2
|
||||
|
|
12
deps/CMakeLists.txt
vendored
12
deps/CMakeLists.txt
vendored
|
@ -33,23 +33,23 @@ option(USE_BUNDLED_JSON "Use the bundled version of nlohmann json." ${USE_BUNDLE
|
|||
option(MTX_STATIC "Compile / link bundled mtx client statically" OFF)
|
||||
|
||||
if(USE_BUNDLED_BOOST)
|
||||
# bundled boost is 1.68, which requires CMake 3.12 or greater.
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
# bundled boost is 1.70, which requires CMake 3.15 or greater.
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
endif()
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
set(BOOST_URL
|
||||
https://dl.bintray.com/boostorg/release/1.69.0/source/boost_1_69_0.tar.bz2)
|
||||
https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.bz2)
|
||||
set(BOOST_SHA256
|
||||
8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406)
|
||||
430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778)
|
||||
|
||||
set(
|
||||
MTXCLIENT_URL
|
||||
https://github.com/Nheko-Reborn/mtxclient/archive/6eee767cc25a9db9f125843e584656cde1ebb6c5.tar.gz
|
||||
https://github.com/Nheko-Reborn/mtxclient/archive/64182a84e35378113f7d3a80f3073894416480e7.zip
|
||||
)
|
||||
set(MTXCLIENT_HASH
|
||||
72fe77da4fed98b3cf069299f66092c820c900359a27ec26070175f9ad208a03)
|
||||
c9973501920046f04c72983472451736343d00e7a40f4d4a12181191093a5fab)
|
||||
set(
|
||||
TWEENY_URL
|
||||
https://github.com/mobius3/tweeny/archive/b94ce07cfb02a0eb8ac8aaf66137dabdaea857cf.tar.gz
|
||||
|
|
|
@ -4,27 +4,12 @@
|
|||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
<location filename="../../src/ChatPage.cpp" line="+330"/>
|
||||
<source>Failed to upload image. Please try again.</source>
|
||||
<translation>Hochladen des Bildes fehlgeschlagen. Bitte versuche es erneut.</translation>
|
||||
<location filename="../../src/ChatPage.cpp" line="+346"/>
|
||||
<source>Failed to upload media. Please try again.</source>
|
||||
<translation>Medienupload fehlgeschlagen. Bitte versuche es erneut.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+45"/>
|
||||
<source>Failed to upload file. Please try again.</source>
|
||||
<translation>Hochladen der Datei fehlgeschlagen. Bitte versuche es erneut.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+43"/>
|
||||
<source>Failed to upload audio. Please try again.</source>
|
||||
<translation>Hochladen der Audiodatei fehlgeschlagen. Bitte versuche es erneut.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+42"/>
|
||||
<source>Failed to upload video. Please try again.</source>
|
||||
<translation>Hochladen der Videodatei fehlgeschlagen. Bitte versuche es erneut.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+393"/>
|
||||
<location line="+389"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation>Wiederherstellung des OLM Accounts fehlgeschlagen. Bitte logge dich erneut ein.</translation>
|
||||
</message>
|
||||
|
@ -194,6 +179,19 @@
|
|||
<translation>OK</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageDelegate</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/MessageDelegate.qml" line="+43"/>
|
||||
<source>redacted</source>
|
||||
<translation>gelöscht</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+6"/>
|
||||
<source>Encryption enabled</source>
|
||||
<translation>Verschlüsselung aktiviert</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
|
@ -210,14 +208,6 @@
|
|||
<translation>Raum suchen…</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation>gelöscht</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -354,13 +344,13 @@
|
|||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
<message>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+507"/>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+502"/>
|
||||
<source>Send a file</source>
|
||||
<translation>Versende Datei</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+13"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+164"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+161"/>
|
||||
<source>Write a message...</source>
|
||||
<translation>Schreibe eine Nachricht…</translation>
|
||||
</message>
|
||||
|
@ -375,7 +365,7 @@
|
|||
<translation>Emoji</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+75"/>
|
||||
<location line="+72"/>
|
||||
<source>Select a file</source>
|
||||
<translation>Datei auswählen</translation>
|
||||
</message>
|
||||
|
@ -393,7 +383,7 @@
|
|||
<context>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+835"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation>-- verschlüsselter Event (keine Schlüssel zur Entschlüsselung gefunden) --</translation>
|
||||
|
@ -423,10 +413,30 @@
|
|||
<translation>-- verschlüsselter Event (Unbekannter Eventtyp) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<location line="+54"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation>Nachricht zurückziehen fehlgeschlagen: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+453"/>
|
||||
<source>Save image</source>
|
||||
<translation>Bild speichern</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation>Video speichern</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation>Audiodatei speichern</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation>Datei speichern</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
|
@ -474,29 +484,6 @@
|
|||
<translation>Kein Raum geöffnet</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation>Bild speichern</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation>Video speichern</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation>Audiodatei speichern</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation>Datei speichern</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
|
|
|
@ -4,27 +4,12 @@
|
|||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
<location filename="../../src/ChatPage.cpp" line="+330"/>
|
||||
<source>Failed to upload image. Please try again.</source>
|
||||
<location filename="../../src/ChatPage.cpp" line="+346"/>
|
||||
<source>Failed to upload media. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+45"/>
|
||||
<source>Failed to upload file. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+43"/>
|
||||
<source>Failed to upload audio. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+42"/>
|
||||
<source>Failed to upload video. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+393"/>
|
||||
<location line="+389"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -194,6 +179,19 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageDelegate</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/MessageDelegate.qml" line="+43"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+6"/>
|
||||
<source>Encryption enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
|
@ -210,14 +208,6 @@
|
|||
<translation>Αναζήτηση συνομιλίας...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -354,13 +344,13 @@
|
|||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
<message>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+507"/>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+502"/>
|
||||
<source>Send a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+13"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+164"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+161"/>
|
||||
<source>Write a message...</source>
|
||||
<translation>Γράψε ένα μήνυμα...</translation>
|
||||
</message>
|
||||
|
@ -375,7 +365,7 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+75"/>
|
||||
<location line="+72"/>
|
||||
<source>Select a file</source>
|
||||
<translation>Διάλεξε ένα αρχείο</translation>
|
||||
</message>
|
||||
|
@ -393,7 +383,7 @@
|
|||
<context>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+835"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -423,10 +413,30 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<location line="+54"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+453"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Αποθήκευση Εικόνας</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
|
@ -474,29 +484,6 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Αποθήκευση Εικόνας</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
|
|
|
@ -4,27 +4,12 @@
|
|||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
<location filename="../../src/ChatPage.cpp" line="+330"/>
|
||||
<source>Failed to upload image. Please try again.</source>
|
||||
<translation>Failed to upload image. Please try again.</translation>
|
||||
<location filename="../../src/ChatPage.cpp" line="+346"/>
|
||||
<source>Failed to upload media. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+45"/>
|
||||
<source>Failed to upload file. Please try again.</source>
|
||||
<translation>Failed to upload file. Please try again.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+43"/>
|
||||
<source>Failed to upload audio. Please try again.</source>
|
||||
<translation>Failed to upload audio. Please try again.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+42"/>
|
||||
<source>Failed to upload video. Please try again.</source>
|
||||
<translation>Failed to upload video. Please try again.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+393"/>
|
||||
<location line="+389"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation>Failed to restore OLM account. Please login again.</translation>
|
||||
</message>
|
||||
|
@ -194,6 +179,19 @@
|
|||
<translation>OK</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageDelegate</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/MessageDelegate.qml" line="+43"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+6"/>
|
||||
<source>Encryption enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
|
@ -210,14 +208,6 @@
|
|||
<translation>Search for a room…</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -354,13 +344,13 @@
|
|||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
<message>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+507"/>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+502"/>
|
||||
<source>Send a file</source>
|
||||
<translation>Send a file</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+13"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+164"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+161"/>
|
||||
<source>Write a message...</source>
|
||||
<translation>Write a message…</translation>
|
||||
</message>
|
||||
|
@ -375,7 +365,7 @@
|
|||
<translation>Emoji</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+75"/>
|
||||
<location line="+72"/>
|
||||
<source>Select a file</source>
|
||||
<translation>Select a file</translation>
|
||||
</message>
|
||||
|
@ -393,7 +383,7 @@
|
|||
<context>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+835"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished">-- Encrypted Event (No keys found for decryption) --</translation>
|
||||
|
@ -423,10 +413,30 @@
|
|||
<translation type="unfinished">-- Encrypted Event (Unknown event type) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<location line="+54"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished">Message redaction failed: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+453"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Save image</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
|
@ -474,29 +484,6 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Save image</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
|
|
|
@ -4,27 +4,12 @@
|
|||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
<location filename="../../src/ChatPage.cpp" line="+330"/>
|
||||
<source>Failed to upload image. Please try again.</source>
|
||||
<translation>Kuvan lähettäminen epäonnistui. Ole hyvä ja yritä uudelleen.</translation>
|
||||
<location filename="../../src/ChatPage.cpp" line="+346"/>
|
||||
<source>Failed to upload media. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+45"/>
|
||||
<source>Failed to upload file. Please try again.</source>
|
||||
<translation>Tiedoston lähettäminen epäonnistui. Ole hyvä ja yritä uudelleen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+43"/>
|
||||
<source>Failed to upload audio. Please try again.</source>
|
||||
<translation>Äänitiedoston lähettäminen epäonnistui. Ole hyvä ja yritä uudelleen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+42"/>
|
||||
<source>Failed to upload video. Please try again.</source>
|
||||
<translation>Videon lähettäminen epäonnistui. Ole hyvä ja yritä uudelleen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+393"/>
|
||||
<location line="+389"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation>OLM-tilin palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen.</translation>
|
||||
</message>
|
||||
|
@ -194,6 +179,19 @@
|
|||
<translation>OK</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageDelegate</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/MessageDelegate.qml" line="+43"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+6"/>
|
||||
<source>Encryption enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
|
@ -210,14 +208,6 @@
|
|||
<translation>Etsi huonetta…</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -354,13 +344,13 @@
|
|||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
<message>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+507"/>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+502"/>
|
||||
<source>Send a file</source>
|
||||
<translation>Lähetä tiedosto</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+13"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+164"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+161"/>
|
||||
<source>Write a message...</source>
|
||||
<translation>Kirjoita viesti…</translation>
|
||||
</message>
|
||||
|
@ -375,7 +365,7 @@
|
|||
<translation>Emoji</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+75"/>
|
||||
<location line="+72"/>
|
||||
<source>Select a file</source>
|
||||
<translation>Valitse tiedosto</translation>
|
||||
</message>
|
||||
|
@ -393,7 +383,7 @@
|
|||
<context>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+835"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished">-- Salattu viesti (salauksen purkuavaimia ei löydetty) --</translation>
|
||||
|
@ -423,10 +413,30 @@
|
|||
<translation type="unfinished">-- Salattu viesti (tuntematon viestityyppi) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<location line="+54"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished">Viestin poisto epäonnistui: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+453"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Tallenna kuva</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
|
@ -474,29 +484,6 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Tallenna kuva</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
|
|
|
@ -4,27 +4,12 @@
|
|||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
<location filename="../../src/ChatPage.cpp" line="+330"/>
|
||||
<source>Failed to upload image. Please try again.</source>
|
||||
<location filename="../../src/ChatPage.cpp" line="+346"/>
|
||||
<source>Failed to upload media. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+45"/>
|
||||
<source>Failed to upload file. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+43"/>
|
||||
<source>Failed to upload audio. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+42"/>
|
||||
<source>Failed to upload video. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+393"/>
|
||||
<location line="+389"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -194,6 +179,19 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageDelegate</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/MessageDelegate.qml" line="+43"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+6"/>
|
||||
<source>Encryption enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
|
@ -210,14 +208,6 @@
|
|||
<translation>Chercher un salon…</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -355,13 +345,13 @@
|
|||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
<message>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+507"/>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+502"/>
|
||||
<source>Send a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+13"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+164"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+161"/>
|
||||
<source>Write a message...</source>
|
||||
<translation>Écrivez un message...</translation>
|
||||
</message>
|
||||
|
@ -376,7 +366,7 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+75"/>
|
||||
<location line="+72"/>
|
||||
<source>Select a file</source>
|
||||
<translation>Sélectionnez un fichier</translation>
|
||||
</message>
|
||||
|
@ -394,7 +384,7 @@
|
|||
<context>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+835"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -424,10 +414,30 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<location line="+54"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+453"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Enregistrer l'image</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
|
@ -475,29 +485,6 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Enregistrer l'image</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
|
|
|
@ -4,27 +4,12 @@
|
|||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
<location filename="../../src/ChatPage.cpp" line="+330"/>
|
||||
<source>Failed to upload image. Please try again.</source>
|
||||
<location filename="../../src/ChatPage.cpp" line="+346"/>
|
||||
<source>Failed to upload media. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+45"/>
|
||||
<source>Failed to upload file. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+43"/>
|
||||
<source>Failed to upload audio. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+42"/>
|
||||
<source>Failed to upload video. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+393"/>
|
||||
<location line="+389"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -194,6 +179,19 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageDelegate</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/MessageDelegate.qml" line="+43"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+6"/>
|
||||
<source>Encryption enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
|
@ -210,14 +208,6 @@
|
|||
<translation>Zoek een kamer...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -354,13 +344,13 @@
|
|||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
<message>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+507"/>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+502"/>
|
||||
<source>Send a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+13"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+164"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+161"/>
|
||||
<source>Write a message...</source>
|
||||
<translation>Typ een bericht...</translation>
|
||||
</message>
|
||||
|
@ -375,7 +365,7 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+75"/>
|
||||
<location line="+72"/>
|
||||
<source>Select a file</source>
|
||||
<translation>Kies een bestand</translation>
|
||||
</message>
|
||||
|
@ -393,7 +383,7 @@
|
|||
<context>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+835"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -423,10 +413,30 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<location line="+54"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+453"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Afbeelding opslaan</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
|
@ -474,29 +484,6 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Afbeelding opslaan</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
|
|
|
@ -4,27 +4,12 @@
|
|||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
<location filename="../../src/ChatPage.cpp" line="+330"/>
|
||||
<source>Failed to upload image. Please try again.</source>
|
||||
<translation>Nie udało się wysłać obrazu. Spróbuj ponownie.</translation>
|
||||
<location filename="../../src/ChatPage.cpp" line="+346"/>
|
||||
<source>Failed to upload media. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+45"/>
|
||||
<source>Failed to upload file. Please try again.</source>
|
||||
<translation>Nie udało się wysłać pliku. Spróbuj ponownie.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+43"/>
|
||||
<source>Failed to upload audio. Please try again.</source>
|
||||
<translation>Nie udało się wysłać pliku dźwiękowego. Spróbuj ponownie.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+42"/>
|
||||
<source>Failed to upload video. Please try again.</source>
|
||||
<translation>Nie udało się wysłać filmu. Spróbuj ponownie.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+393"/>
|
||||
<location line="+389"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation>Nie udało się przywrócić konta OLM. Spróbuj zalogować się ponownie.</translation>
|
||||
</message>
|
||||
|
@ -194,6 +179,19 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageDelegate</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/MessageDelegate.qml" line="+43"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+6"/>
|
||||
<source>Encryption enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
|
@ -210,14 +208,6 @@
|
|||
<translation>Wyszukaj pokoju…</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -354,13 +344,13 @@
|
|||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
<message>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+507"/>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+502"/>
|
||||
<source>Send a file</source>
|
||||
<translation>Wyślij plik</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+13"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+164"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+161"/>
|
||||
<source>Write a message...</source>
|
||||
<translation>Napisz wiadomość…</translation>
|
||||
</message>
|
||||
|
@ -375,7 +365,7 @@
|
|||
<translation>Emoji</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+75"/>
|
||||
<location line="+72"/>
|
||||
<source>Select a file</source>
|
||||
<translation>Wybierz plik</translation>
|
||||
</message>
|
||||
|
@ -393,7 +383,7 @@
|
|||
<context>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+835"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -423,10 +413,30 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<location line="+54"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished">Redagowanie wiadomości nie powiodło się: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+453"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Zapisz obraz</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
|
@ -474,29 +484,6 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Zapisz obraz</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
|
|
|
@ -4,27 +4,12 @@
|
|||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
<location filename="../../src/ChatPage.cpp" line="+330"/>
|
||||
<source>Failed to upload image. Please try again.</source>
|
||||
<translation>Не удалось загрузить изображение. Пожалуйста, попробуйте еще раз.</translation>
|
||||
<location filename="../../src/ChatPage.cpp" line="+346"/>
|
||||
<source>Failed to upload media. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+45"/>
|
||||
<source>Failed to upload file. Please try again.</source>
|
||||
<translation>Не удалось загрузить файл. Пожалуйста, попробуйте еще раз.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+43"/>
|
||||
<source>Failed to upload audio. Please try again.</source>
|
||||
<translation>Не удалось загрузить аудио. Пожалуйста, попробуйте еще раз.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+42"/>
|
||||
<source>Failed to upload video. Please try again.</source>
|
||||
<translation>Не удалось загрузить видео. Пожалуйста, попробуйте еще раз.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+393"/>
|
||||
<location line="+389"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation>Не удалось восстановить учетную запись OLM. Пожалуйста, войдите снова.</translation>
|
||||
</message>
|
||||
|
@ -194,6 +179,19 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageDelegate</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/MessageDelegate.qml" line="+43"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+6"/>
|
||||
<source>Encryption enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
|
@ -210,14 +208,6 @@
|
|||
<translation>Поиск комнаты...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -354,13 +344,13 @@
|
|||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
<message>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+507"/>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+502"/>
|
||||
<source>Send a file</source>
|
||||
<translation>Отправить файл</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+13"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+164"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+161"/>
|
||||
<source>Write a message...</source>
|
||||
<translation>Написать сообщение...</translation>
|
||||
</message>
|
||||
|
@ -375,7 +365,7 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+75"/>
|
||||
<location line="+72"/>
|
||||
<source>Select a file</source>
|
||||
<translation>Выберите файл</translation>
|
||||
</message>
|
||||
|
@ -393,7 +383,7 @@
|
|||
<context>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+835"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -423,10 +413,30 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<location line="+54"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished">Ошибка редактирования сообщения: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+453"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Сохранить изображение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
|
@ -474,29 +484,6 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Сохранить изображение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
|
|
|
@ -4,27 +4,12 @@
|
|||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
<location filename="../../src/ChatPage.cpp" line="+330"/>
|
||||
<source>Failed to upload image. Please try again.</source>
|
||||
<translation>上传图像失败。请重试。</translation>
|
||||
<location filename="../../src/ChatPage.cpp" line="+346"/>
|
||||
<source>Failed to upload media. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+45"/>
|
||||
<source>Failed to upload file. Please try again.</source>
|
||||
<translation>上传文件失败,请重试。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+43"/>
|
||||
<source>Failed to upload audio. Please try again.</source>
|
||||
<translation>上传音频失败。请重试。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+42"/>
|
||||
<source>Failed to upload video. Please try again.</source>
|
||||
<translation>上传视频失败。请重试。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+393"/>
|
||||
<location line="+389"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation>恢复 OLM 账户失败。请重新登录。</translation>
|
||||
</message>
|
||||
|
@ -194,6 +179,19 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MessageDelegate</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/MessageDelegate.qml" line="+43"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+6"/>
|
||||
<source>Encryption enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
|
@ -210,14 +208,6 @@
|
|||
<translation>寻找一个聊天室...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -354,13 +344,13 @@
|
|||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
<message>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+507"/>
|
||||
<location filename="../../src/TextInputWidget.cpp" line="+502"/>
|
||||
<source>Send a file</source>
|
||||
<translation>发送一个文件</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+13"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+164"/>
|
||||
<location filename="../../src/TextInputWidget.h" line="+161"/>
|
||||
<source>Write a message...</source>
|
||||
<translation>写一条消息...</translation>
|
||||
</message>
|
||||
|
@ -375,7 +365,7 @@
|
|||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+75"/>
|
||||
<location line="+72"/>
|
||||
<source>Select a file</source>
|
||||
<translation>选择一个文件</translation>
|
||||
</message>
|
||||
|
@ -393,7 +383,7 @@
|
|||
<context>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+835"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -423,10 +413,30 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<location line="+54"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished">删除消息失败:%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+453"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">保存图像</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
|
@ -474,29 +484,6 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">保存图像</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
|
|
|
@ -97,7 +97,7 @@ RowLayout {
|
|||
MenuItem {
|
||||
visible: model.type == MtxEvent.ImageMessage || model.type == MtxEvent.VideoMessage || model.type == MtxEvent.AudioMessage || model.type == MtxEvent.FileMessage || model.type == MtxEvent.Sticker
|
||||
text: qsTr("Save as")
|
||||
onTriggered: timelineManager.saveMedia(model.url, model.filename, model.mimetype, model.type)
|
||||
onTriggered: timelineManager.timeline.saveMedia(model.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ Rectangle {
|
|||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: timelineManager.saveMedia(model.url, model.filename, model.mimetype, model.type)
|
||||
onClicked: timelineManager.timeline.saveMedia(model.id)
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ Item {
|
|||
MouseArea {
|
||||
enabled: model.type == MtxEvent.ImageMessage
|
||||
anchors.fill: parent
|
||||
onClicked: timelineManager.openImageOverlay(model.url, model.filename, model.mimetype, model.type)
|
||||
onClicked: timelineManager.openImageOverlay(model.url, model.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ Rectangle {
|
|||
anchors.fill: parent
|
||||
onClicked: {
|
||||
switch (button.state) {
|
||||
case "": timelineManager.cacheMedia(model.url, model.mimetype); break;
|
||||
case "": timelineManager.timeline.cacheMedia(model.id); break;
|
||||
case "stopped":
|
||||
media.play(); console.log("play");
|
||||
button.state = "playing"
|
||||
|
@ -118,7 +118,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
Connections {
|
||||
target: timelineManager
|
||||
target: timelineManager.timeline
|
||||
onMediaCached: {
|
||||
if (mxcUrl == model.url) {
|
||||
media.source = "file://" + cacheUrl
|
||||
|
|
206
src/ChatPage.cpp
206
src/ChatPage.cpp
|
@ -54,6 +54,8 @@ constexpr int CHECK_CONNECTIVITY_INTERVAL = 15'000;
|
|||
constexpr int RETRY_TIMEOUT = 5'000;
|
||||
constexpr size_t MAX_ONETIME_KEYS = 50;
|
||||
|
||||
Q_DECLARE_METATYPE(boost::optional<mtx::crypto::EncryptedFile>)
|
||||
|
||||
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, isConnected_(true)
|
||||
|
@ -62,6 +64,9 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
{
|
||||
setObjectName("chatPage");
|
||||
|
||||
qRegisterMetaType<boost::optional<mtx::crypto::EncryptedFile>>(
|
||||
"boost::optional<mtx::crypto::EncryptedFile>");
|
||||
|
||||
topLayout_ = new QHBoxLayout(this);
|
||||
topLayout_->setSpacing(0);
|
||||
topLayout_->setMargin(0);
|
||||
|
@ -299,9 +304,9 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
|
||||
connect(
|
||||
text_input_,
|
||||
&TextInputWidget::uploadImage,
|
||||
&TextInputWidget::uploadMedia,
|
||||
this,
|
||||
[this](QSharedPointer<QIODevice> dev, const QString &fn) {
|
||||
[this](QSharedPointer<QIODevice> dev, QString mimeClass, const QString &fn) {
|
||||
QMimeDatabase db;
|
||||
QMimeType mime = db.mimeTypeForData(dev.data());
|
||||
|
||||
|
@ -311,9 +316,18 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
return;
|
||||
}
|
||||
|
||||
auto bin = dev->peek(dev->size());
|
||||
auto payload = std::string(bin.data(), bin.size());
|
||||
auto dimensions = QImageReader(dev.data()).size();
|
||||
auto bin = dev->peek(dev->size());
|
||||
auto payload = std::string(bin.data(), bin.size());
|
||||
boost::optional<mtx::crypto::EncryptedFile> encryptedFile;
|
||||
if (cache::client()->isRoomEncrypted(current_room_.toStdString())) {
|
||||
mtx::crypto::BinaryBuf buf;
|
||||
std::tie(buf, encryptedFile) = mtx::crypto::encrypt_file(payload);
|
||||
payload = mtx::crypto::to_string(buf);
|
||||
}
|
||||
|
||||
QSize dimensions;
|
||||
if (mimeClass == "image")
|
||||
dimensions = QImageReader(dev.data()).size();
|
||||
|
||||
http::client()->upload(
|
||||
payload,
|
||||
|
@ -322,193 +336,61 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
[this,
|
||||
room_id = current_room_,
|
||||
filename = fn,
|
||||
mime = mime.name(),
|
||||
size = payload.size(),
|
||||
encryptedFile,
|
||||
mimeClass,
|
||||
mime = mime.name(),
|
||||
size = payload.size(),
|
||||
dimensions](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit uploadFailed(
|
||||
tr("Failed to upload image. Please try again."));
|
||||
nhlog::net()->warn("failed to upload image: {} {} ({})",
|
||||
tr("Failed to upload media. Please try again."));
|
||||
nhlog::net()->warn("failed to upload media: {} {} ({})",
|
||||
err->matrix_error.error,
|
||||
to_string(err->matrix_error.errcode),
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
emit imageUploaded(room_id,
|
||||
emit mediaUploaded(room_id,
|
||||
filename,
|
||||
encryptedFile,
|
||||
QString::fromStdString(res.content_uri),
|
||||
mimeClass,
|
||||
mime,
|
||||
size,
|
||||
dimensions);
|
||||
});
|
||||
});
|
||||
|
||||
connect(text_input_,
|
||||
&TextInputWidget::uploadFile,
|
||||
this,
|
||||
[this](QSharedPointer<QIODevice> dev, const QString &fn) {
|
||||
QMimeDatabase db;
|
||||
QMimeType mime = db.mimeTypeForData(dev.data());
|
||||
|
||||
if (!dev->open(QIODevice::ReadOnly)) {
|
||||
emit uploadFailed(
|
||||
QString("Error while reading media: %1").arg(dev->errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto bin = dev->readAll();
|
||||
auto payload = std::string(bin.data(), bin.size());
|
||||
|
||||
http::client()->upload(
|
||||
payload,
|
||||
mime.name().toStdString(),
|
||||
QFileInfo(fn).fileName().toStdString(),
|
||||
[this,
|
||||
room_id = current_room_,
|
||||
filename = fn,
|
||||
mime = mime.name(),
|
||||
size = payload.size()](const mtx::responses::ContentURI &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit uploadFailed(
|
||||
tr("Failed to upload file. Please try again."));
|
||||
nhlog::net()->warn("failed to upload file: {} ({})",
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
emit fileUploaded(room_id,
|
||||
filename,
|
||||
QString::fromStdString(res.content_uri),
|
||||
mime,
|
||||
size);
|
||||
});
|
||||
});
|
||||
|
||||
connect(text_input_,
|
||||
&TextInputWidget::uploadAudio,
|
||||
this,
|
||||
[this](QSharedPointer<QIODevice> dev, const QString &fn) {
|
||||
QMimeDatabase db;
|
||||
QMimeType mime = db.mimeTypeForData(dev.data());
|
||||
|
||||
if (!dev->open(QIODevice::ReadOnly)) {
|
||||
emit uploadFailed(
|
||||
QString("Error while reading media: %1").arg(dev->errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto bin = dev->readAll();
|
||||
auto payload = std::string(bin.data(), bin.size());
|
||||
|
||||
http::client()->upload(
|
||||
payload,
|
||||
mime.name().toStdString(),
|
||||
QFileInfo(fn).fileName().toStdString(),
|
||||
[this,
|
||||
room_id = current_room_,
|
||||
filename = fn,
|
||||
mime = mime.name(),
|
||||
size = payload.size()](const mtx::responses::ContentURI &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit uploadFailed(
|
||||
tr("Failed to upload audio. Please try again."));
|
||||
nhlog::net()->warn("failed to upload audio: {} ({})",
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
emit audioUploaded(room_id,
|
||||
filename,
|
||||
QString::fromStdString(res.content_uri),
|
||||
mime,
|
||||
size);
|
||||
});
|
||||
});
|
||||
connect(text_input_,
|
||||
&TextInputWidget::uploadVideo,
|
||||
this,
|
||||
[this](QSharedPointer<QIODevice> dev, const QString &fn) {
|
||||
QMimeDatabase db;
|
||||
QMimeType mime = db.mimeTypeForData(dev.data());
|
||||
|
||||
if (!dev->open(QIODevice::ReadOnly)) {
|
||||
emit uploadFailed(
|
||||
QString("Error while reading media: %1").arg(dev->errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto bin = dev->readAll();
|
||||
auto payload = std::string(bin.data(), bin.size());
|
||||
|
||||
http::client()->upload(
|
||||
payload,
|
||||
mime.name().toStdString(),
|
||||
QFileInfo(fn).fileName().toStdString(),
|
||||
[this,
|
||||
room_id = current_room_,
|
||||
filename = fn,
|
||||
mime = mime.name(),
|
||||
size = payload.size()](const mtx::responses::ContentURI &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit uploadFailed(
|
||||
tr("Failed to upload video. Please try again."));
|
||||
nhlog::net()->warn("failed to upload video: {} ({})",
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
emit videoUploaded(room_id,
|
||||
filename,
|
||||
QString::fromStdString(res.content_uri),
|
||||
mime,
|
||||
size);
|
||||
});
|
||||
});
|
||||
|
||||
connect(this, &ChatPage::uploadFailed, this, [this](const QString &msg) {
|
||||
text_input_->hideUploadSpinner();
|
||||
emit showNotification(msg);
|
||||
});
|
||||
connect(this,
|
||||
&ChatPage::imageUploaded,
|
||||
&ChatPage::mediaUploaded,
|
||||
this,
|
||||
[this](QString roomid,
|
||||
QString filename,
|
||||
boost::optional<mtx::crypto::EncryptedFile> encryptedFile,
|
||||
QString url,
|
||||
QString mimeClass,
|
||||
QString mime,
|
||||
qint64 dsize,
|
||||
QSize dimensions) {
|
||||
text_input_->hideUploadSpinner();
|
||||
view_manager_->queueImageMessage(
|
||||
roomid, filename, url, mime, dsize, dimensions);
|
||||
});
|
||||
connect(this,
|
||||
&ChatPage::fileUploaded,
|
||||
this,
|
||||
[this](QString roomid, QString filename, QString url, QString mime, qint64 dsize) {
|
||||
text_input_->hideUploadSpinner();
|
||||
view_manager_->queueFileMessage(roomid, filename, url, mime, dsize);
|
||||
});
|
||||
connect(this,
|
||||
&ChatPage::audioUploaded,
|
||||
this,
|
||||
[this](QString roomid, QString filename, QString url, QString mime, qint64 dsize) {
|
||||
text_input_->hideUploadSpinner();
|
||||
view_manager_->queueAudioMessage(roomid, filename, url, mime, dsize);
|
||||
});
|
||||
connect(this,
|
||||
&ChatPage::videoUploaded,
|
||||
this,
|
||||
[this](QString roomid, QString filename, QString url, QString mime, qint64 dsize) {
|
||||
text_input_->hideUploadSpinner();
|
||||
view_manager_->queueVideoMessage(roomid, filename, url, mime, dsize);
|
||||
|
||||
if (mimeClass == "image")
|
||||
view_manager_->queueImageMessage(
|
||||
roomid, filename, encryptedFile, url, mime, dsize, dimensions);
|
||||
else if (mimeClass == "audio")
|
||||
view_manager_->queueAudioMessage(
|
||||
roomid, filename, encryptedFile, url, mime, dsize);
|
||||
else if (mimeClass == "video")
|
||||
view_manager_->queueVideoMessage(
|
||||
roomid, filename, encryptedFile, url, mime, dsize);
|
||||
else
|
||||
view_manager_->queueFileMessage(
|
||||
roomid, filename, encryptedFile, url, mime, dsize);
|
||||
});
|
||||
|
||||
connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar);
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
#include <mtx/common.hpp>
|
||||
#include <mtx/responses.hpp>
|
||||
|
||||
#include <QFrame>
|
||||
|
@ -94,27 +96,14 @@ signals:
|
|||
const QPoint widgetPos);
|
||||
|
||||
void uploadFailed(const QString &msg);
|
||||
void imageUploaded(const QString &roomid,
|
||||
void mediaUploaded(const QString &roomid,
|
||||
const QString &filename,
|
||||
const boost::optional<mtx::crypto::EncryptedFile> &file,
|
||||
const QString &url,
|
||||
const QString &mimeClass,
|
||||
const QString &mime,
|
||||
qint64 dsize,
|
||||
const QSize &dimensions);
|
||||
void fileUploaded(const QString &roomid,
|
||||
const QString &filename,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
qint64 dsize);
|
||||
void audioUploaded(const QString &roomid,
|
||||
const QString &filename,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
qint64 dsize);
|
||||
void videoUploaded(const QString &roomid,
|
||||
const QString &filename,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
qint64 dsize);
|
||||
|
||||
void contentLoaded();
|
||||
void closing();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
void
|
||||
MxcImageResponse::run()
|
||||
{
|
||||
if (m_requestedSize.isValid()) {
|
||||
if (m_requestedSize.isValid() && !m_encryptionInfo) {
|
||||
QString fileName = QString("%1_%2x%3_crop")
|
||||
.arg(m_id)
|
||||
.arg(m_requestedSize.width())
|
||||
|
@ -65,7 +65,12 @@ MxcImageResponse::run()
|
|||
return;
|
||||
}
|
||||
|
||||
auto data = QByteArray(res.data(), res.size());
|
||||
auto temp = res;
|
||||
if (m_encryptionInfo)
|
||||
temp = mtx::crypto::to_string(
|
||||
mtx::crypto::decrypt_file(temp, m_encryptionInfo.value()));
|
||||
|
||||
auto data = QByteArray(temp.data(), temp.size());
|
||||
m_image.loadFromData(data);
|
||||
m_image.setText("original filename",
|
||||
QString::fromStdString(originalFilename));
|
||||
|
|
|
@ -6,14 +6,21 @@
|
|||
#include <QImage>
|
||||
#include <QThreadPool>
|
||||
|
||||
#include <mtx/common.hpp>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
class MxcImageResponse
|
||||
: public QQuickImageResponse
|
||||
, public QRunnable
|
||||
{
|
||||
public:
|
||||
MxcImageResponse(const QString &id, const QSize &requestedSize)
|
||||
MxcImageResponse(const QString &id,
|
||||
const QSize &requestedSize,
|
||||
boost::optional<mtx::crypto::EncryptedFile> encryptionInfo)
|
||||
: m_id(id)
|
||||
, m_requestedSize(requestedSize)
|
||||
, m_encryptionInfo(encryptionInfo)
|
||||
{
|
||||
setAutoDelete(false);
|
||||
}
|
||||
|
@ -29,19 +36,34 @@ public:
|
|||
QString m_id, m_error;
|
||||
QSize m_requestedSize;
|
||||
QImage m_image;
|
||||
boost::optional<mtx::crypto::EncryptedFile> m_encryptionInfo;
|
||||
};
|
||||
|
||||
class MxcImageProvider : public QQuickAsyncImageProvider
|
||||
class MxcImageProvider
|
||||
: public QObject
|
||||
, public QQuickAsyncImageProvider
|
||||
{
|
||||
public:
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
QQuickImageResponse *requestImageResponse(const QString &id,
|
||||
const QSize &requestedSize) override
|
||||
{
|
||||
MxcImageResponse *response = new MxcImageResponse(id, requestedSize);
|
||||
boost::optional<mtx::crypto::EncryptedFile> info;
|
||||
auto temp = infos.find("mxc://" + id);
|
||||
if (temp != infos.end())
|
||||
info = *temp;
|
||||
|
||||
MxcImageResponse *response = new MxcImageResponse(id, requestedSize, info);
|
||||
pool.start(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
void addEncryptionInfo(mtx::crypto::EncryptedFile info)
|
||||
{
|
||||
infos.insert(QString::fromStdString(info.url), info);
|
||||
}
|
||||
|
||||
private:
|
||||
QThreadPool pool;
|
||||
QHash<QString, mtx::crypto::EncryptedFile> infos;
|
||||
};
|
||||
|
|
|
@ -458,21 +458,16 @@ FilteredTextEdit::textChanged()
|
|||
}
|
||||
|
||||
void
|
||||
FilteredTextEdit::uploadData(const QByteArray data, const QString &media, const QString &filename)
|
||||
FilteredTextEdit::uploadData(const QByteArray data,
|
||||
const QString &mediaType,
|
||||
const QString &filename)
|
||||
{
|
||||
QSharedPointer<QBuffer> buffer{new QBuffer{this}};
|
||||
buffer->setData(data);
|
||||
|
||||
emit startedUpload();
|
||||
|
||||
if (media == "image")
|
||||
emit image(buffer, filename);
|
||||
else if (media == "audio")
|
||||
emit audio(buffer, filename);
|
||||
else if (media == "video")
|
||||
emit video(buffer, filename);
|
||||
else
|
||||
emit file(buffer, filename);
|
||||
emit media(buffer, mediaType, filename);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -580,10 +575,7 @@ TextInputWidget::TextInputWidget(QWidget *parent)
|
|||
connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage);
|
||||
connect(input_, &FilteredTextEdit::reply, this, &TextInputWidget::sendReplyMessage);
|
||||
connect(input_, &FilteredTextEdit::command, this, &TextInputWidget::command);
|
||||
connect(input_, &FilteredTextEdit::image, this, &TextInputWidget::uploadImage);
|
||||
connect(input_, &FilteredTextEdit::audio, this, &TextInputWidget::uploadAudio);
|
||||
connect(input_, &FilteredTextEdit::video, this, &TextInputWidget::uploadVideo);
|
||||
connect(input_, &FilteredTextEdit::file, this, &TextInputWidget::uploadFile);
|
||||
connect(input_, &FilteredTextEdit::media, this, &TextInputWidget::uploadMedia);
|
||||
connect(emojiBtn_,
|
||||
SIGNAL(emojiSelected(const QString &)),
|
||||
this,
|
||||
|
@ -642,14 +634,8 @@ TextInputWidget::openFileSelection()
|
|||
const auto format = mime.name().split("/")[0];
|
||||
|
||||
QSharedPointer<QFile> file{new QFile{fileName, this}};
|
||||
if (format == "image")
|
||||
emit uploadImage(file, fileName);
|
||||
else if (format == "audio")
|
||||
emit uploadAudio(file, fileName);
|
||||
else if (format == "video")
|
||||
emit uploadVideo(file, fileName);
|
||||
else
|
||||
emit uploadFile(file, fileName);
|
||||
|
||||
emit uploadMedia(file, format, fileName);
|
||||
|
||||
showUploadSpinner();
|
||||
}
|
||||
|
|
|
@ -63,10 +63,7 @@ signals:
|
|||
void message(QString);
|
||||
void reply(QString, const RelatedInfo &);
|
||||
void command(QString name, QString args);
|
||||
void image(QSharedPointer<QIODevice> data, const QString &filename);
|
||||
void audio(QSharedPointer<QIODevice> data, const QString &filename);
|
||||
void video(QSharedPointer<QIODevice> data, const QString &filename);
|
||||
void file(QSharedPointer<QIODevice> data, const QString &filename);
|
||||
void media(QSharedPointer<QIODevice> data, QString mimeClass, const QString &filename);
|
||||
|
||||
//! Trigger the suggestion popup.
|
||||
void showSuggestions(const QString &query);
|
||||
|
@ -179,10 +176,9 @@ signals:
|
|||
void sendEmoteMessage(QString msg);
|
||||
void heightChanged(int height);
|
||||
|
||||
void uploadImage(const QSharedPointer<QIODevice> data, const QString &filename);
|
||||
void uploadFile(const QSharedPointer<QIODevice> data, const QString &filename);
|
||||
void uploadAudio(const QSharedPointer<QIODevice> data, const QString &filename);
|
||||
void uploadVideo(const QSharedPointer<QIODevice> data, const QString &filename);
|
||||
void uploadMedia(const QSharedPointer<QIODevice> data,
|
||||
QString mimeClass,
|
||||
const QString &filename);
|
||||
|
||||
void sendJoinRoomRequest(const QString &room);
|
||||
|
||||
|
|
|
@ -3,11 +3,15 @@
|
|||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QMimeDatabase>
|
||||
#include <QRegularExpression>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "ChatPage.h"
|
||||
#include "Logging.h"
|
||||
#include "MainWindow.h"
|
||||
#include "MxcImageProvider.h"
|
||||
#include "Olm.h"
|
||||
#include "TimelineViewManager.h"
|
||||
#include "Utils.h"
|
||||
|
@ -88,17 +92,42 @@ eventFormattedBody(const mtx::events::RoomEvent<T> &e)
|
|||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
boost::optional<mtx::crypto::EncryptedFile>
|
||||
eventEncryptionInfo(const mtx::events::Event<T> &)
|
||||
{
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
auto
|
||||
eventEncryptionInfo(const mtx::events::RoomEvent<T> &e) -> std::enable_if_t<
|
||||
std::is_same<decltype(e.content.file), boost::optional<mtx::crypto::EncryptedFile>>::value,
|
||||
boost::optional<mtx::crypto::EncryptedFile>>
|
||||
{
|
||||
return e.content.file;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
QString
|
||||
eventUrl(const mtx::events::Event<T> &)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
QString
|
||||
eventUrl(const mtx::events::StateEvent<mtx::events::state::Avatar> &e)
|
||||
{
|
||||
return QString::fromStdString(e.content.url);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
auto
|
||||
eventUrl(const mtx::events::RoomEvent<T> &e)
|
||||
-> std::enable_if_t<std::is_same<decltype(e.content.url), std::string>::value, QString>
|
||||
{
|
||||
if (e.content.file)
|
||||
return QString::fromStdString(e.content.file->url);
|
||||
return QString::fromStdString(e.content.url);
|
||||
}
|
||||
|
||||
|
@ -644,6 +673,19 @@ TimelineModel::internalAddEvents(
|
|||
continue; // don't insert redaction into timeline
|
||||
}
|
||||
|
||||
if (auto event =
|
||||
boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&e)) {
|
||||
auto temp = decryptEvent(*event).event;
|
||||
auto encInfo = boost::apply_visitor(
|
||||
[](const auto &ev) -> boost::optional<mtx::crypto::EncryptedFile> {
|
||||
return eventEncryptionInfo(ev);
|
||||
},
|
||||
temp);
|
||||
|
||||
if (encInfo)
|
||||
emit newEncryptedImage(encInfo.value());
|
||||
}
|
||||
|
||||
this->events.insert(id, e);
|
||||
ids.push_back(id);
|
||||
}
|
||||
|
@ -1342,3 +1384,158 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
|
|||
if (!isProcessingPending)
|
||||
emit nextPendingMessage();
|
||||
}
|
||||
|
||||
void
|
||||
TimelineModel::saveMedia(QString eventId) const
|
||||
{
|
||||
mtx::events::collections::TimelineEvents event = events.value(eventId);
|
||||
|
||||
if (auto e = boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
|
||||
event = decryptEvent(*e).event;
|
||||
}
|
||||
|
||||
QString mxcUrl =
|
||||
boost::apply_visitor([](const auto &e) -> QString { return eventUrl(e); }, event);
|
||||
QString originalFilename =
|
||||
boost::apply_visitor([](const auto &e) -> QString { return eventFilename(e); }, event);
|
||||
QString mimeType =
|
||||
boost::apply_visitor([](const auto &e) -> QString { return eventMimeType(e); }, event);
|
||||
|
||||
using EncF = boost::optional<mtx::crypto::EncryptedFile>;
|
||||
EncF encryptionInfo =
|
||||
boost::apply_visitor([](const auto &e) -> EncF { return eventEncryptionInfo(e); }, event);
|
||||
|
||||
qml_mtx_events::EventType eventType = boost::apply_visitor(
|
||||
[](const auto &e) -> qml_mtx_events::EventType { return toRoomEventType(e); }, event);
|
||||
|
||||
QString dialogTitle;
|
||||
if (eventType == qml_mtx_events::EventType::ImageMessage) {
|
||||
dialogTitle = tr("Save image");
|
||||
} else if (eventType == qml_mtx_events::EventType::VideoMessage) {
|
||||
dialogTitle = tr("Save video");
|
||||
} else if (eventType == qml_mtx_events::EventType::AudioMessage) {
|
||||
dialogTitle = tr("Save audio");
|
||||
} else {
|
||||
dialogTitle = tr("Save file");
|
||||
}
|
||||
|
||||
QString filterString = QMimeDatabase().mimeTypeForName(mimeType).filterString();
|
||||
|
||||
auto filename = QFileDialog::getSaveFileName(
|
||||
manager_->getWidget(), dialogTitle, originalFilename, filterString);
|
||||
|
||||
if (filename.isEmpty())
|
||||
return;
|
||||
|
||||
const auto url = mxcUrl.toStdString();
|
||||
|
||||
http::client()->download(
|
||||
url,
|
||||
[filename, url, encryptionInfo](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to retrieve image {}: {} {}",
|
||||
url,
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auto temp = data;
|
||||
if (encryptionInfo)
|
||||
temp = mtx::crypto::to_string(
|
||||
mtx::crypto::decrypt_file(temp, encryptionInfo.value()));
|
||||
|
||||
QFile file(filename);
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
return;
|
||||
|
||||
file.write(QByteArray(temp.data(), (int)temp.size()));
|
||||
file.close();
|
||||
} catch (const std::exception &e) {
|
||||
nhlog::ui()->warn("Error while saving file to: {}", e.what());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineModel::cacheMedia(QString eventId)
|
||||
{
|
||||
mtx::events::collections::TimelineEvents event = events.value(eventId);
|
||||
|
||||
if (auto e = boost::get<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(&event)) {
|
||||
event = decryptEvent(*e).event;
|
||||
}
|
||||
|
||||
QString mxcUrl =
|
||||
boost::apply_visitor([](const auto &e) -> QString { return eventUrl(e); }, event);
|
||||
QString mimeType =
|
||||
boost::apply_visitor([](const auto &e) -> QString { return eventMimeType(e); }, event);
|
||||
|
||||
using EncF = boost::optional<mtx::crypto::EncryptedFile>;
|
||||
EncF encryptionInfo =
|
||||
boost::apply_visitor([](const auto &e) -> EncF { return eventEncryptionInfo(e); }, event);
|
||||
|
||||
// If the message is a link to a non mxcUrl, don't download it
|
||||
if (!mxcUrl.startsWith("mxc://")) {
|
||||
emit mediaCached(mxcUrl, mxcUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix();
|
||||
|
||||
const auto url = mxcUrl.toStdString();
|
||||
QFileInfo filename(QString("%1/media_cache/%2.%3")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
|
||||
.arg(QString(mxcUrl).remove("mxc://"))
|
||||
.arg(suffix));
|
||||
if (QDir::cleanPath(filename.path()) != filename.path()) {
|
||||
nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
|
||||
return;
|
||||
}
|
||||
|
||||
QDir().mkpath(filename.path());
|
||||
|
||||
if (filename.isReadable()) {
|
||||
emit mediaCached(mxcUrl, filename.filePath());
|
||||
return;
|
||||
}
|
||||
|
||||
http::client()->download(
|
||||
url,
|
||||
[this, mxcUrl, filename, url, encryptionInfo](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to retrieve image {}: {} {}",
|
||||
url,
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auto temp = data;
|
||||
if (encryptionInfo)
|
||||
temp = mtx::crypto::to_string(
|
||||
mtx::crypto::decrypt_file(temp, encryptionInfo.value()));
|
||||
|
||||
QFile file(filename.filePath());
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
return;
|
||||
|
||||
file.write(QByteArray(temp.data(), temp.size()));
|
||||
file.close();
|
||||
} catch (const std::exception &e) {
|
||||
nhlog::ui()->warn("Error while saving file to: {}", e.what());
|
||||
}
|
||||
|
||||
emit mediaCached(mxcUrl, filename.filePath());
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <QHash>
|
||||
#include <QSet>
|
||||
|
||||
#include <mtx/common.hpp>
|
||||
#include <mtx/responses.hpp>
|
||||
|
||||
#include "Cache.h"
|
||||
|
@ -159,6 +160,8 @@ public:
|
|||
Q_INVOKABLE void redactEvent(QString id);
|
||||
Q_INVOKABLE int idToIndex(QString id) const;
|
||||
Q_INVOKABLE QString indexToId(int index) const;
|
||||
Q_INVOKABLE void cacheMedia(QString eventId);
|
||||
Q_INVOKABLE void saveMedia(QString eventId) const;
|
||||
|
||||
void addEvents(const mtx::responses::Timeline &events);
|
||||
template<class T>
|
||||
|
@ -185,6 +188,8 @@ signals:
|
|||
void eventRedacted(QString id);
|
||||
void nextPendingMessage();
|
||||
void newMessageToSend(mtx::events::collections::TimelineEvents event);
|
||||
void mediaCached(QString mxcUrl, QString cacheUrl);
|
||||
void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo);
|
||||
|
||||
private:
|
||||
DecryptionResult decryptEvent(
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
#include "TimelineViewManager.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QMetaType>
|
||||
#include <QMimeDatabase>
|
||||
#include <QPalette>
|
||||
#include <QQmlContext>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "ChatPage.h"
|
||||
#include "ColorImageProvider.h"
|
||||
|
@ -105,9 +102,14 @@ TimelineViewManager::sync(const mtx::responses::Rooms &rooms)
|
|||
void
|
||||
TimelineViewManager::addRoom(const QString &room_id)
|
||||
{
|
||||
if (!models.contains(room_id))
|
||||
models.insert(room_id,
|
||||
QSharedPointer<TimelineModel>(new TimelineModel(this, room_id)));
|
||||
if (!models.contains(room_id)) {
|
||||
QSharedPointer<TimelineModel> newRoom(new TimelineModel(this, room_id));
|
||||
connect(newRoom.data(),
|
||||
&TimelineModel::newEncryptedImage,
|
||||
imgProvider,
|
||||
&MxcImageProvider::addEncryptionInfo);
|
||||
models.insert(room_id, std::move(newRoom));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -124,146 +126,24 @@ TimelineViewManager::setHistoryView(const QString &room_id)
|
|||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::openImageOverlay(QString mxcUrl,
|
||||
QString originalFilename,
|
||||
QString mimeType,
|
||||
qml_mtx_events::EventType eventType) const
|
||||
TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const
|
||||
{
|
||||
QQuickImageResponse *imgResponse =
|
||||
imgProvider->requestImageResponse(mxcUrl.remove("mxc://"), QSize());
|
||||
connect(imgResponse,
|
||||
&QQuickImageResponse::finished,
|
||||
this,
|
||||
[this, mxcUrl, originalFilename, mimeType, eventType, imgResponse]() {
|
||||
if (!imgResponse->errorString().isEmpty()) {
|
||||
nhlog::ui()->error("Error when retrieving image for overlay: {}",
|
||||
imgResponse->errorString().toStdString());
|
||||
return;
|
||||
}
|
||||
auto pixmap = QPixmap::fromImage(imgResponse->textureFactory()->image());
|
||||
connect(imgResponse, &QQuickImageResponse::finished, this, [this, eventId, imgResponse]() {
|
||||
if (!imgResponse->errorString().isEmpty()) {
|
||||
nhlog::ui()->error("Error when retrieving image for overlay: {}",
|
||||
imgResponse->errorString().toStdString());
|
||||
return;
|
||||
}
|
||||
auto pixmap = QPixmap::fromImage(imgResponse->textureFactory()->image());
|
||||
|
||||
auto imgDialog = new dialogs::ImageOverlay(pixmap);
|
||||
imgDialog->show();
|
||||
connect(imgDialog,
|
||||
&dialogs::ImageOverlay::saving,
|
||||
this,
|
||||
[this, mxcUrl, originalFilename, mimeType, eventType]() {
|
||||
saveMedia(mxcUrl, originalFilename, mimeType, eventType);
|
||||
});
|
||||
auto imgDialog = new dialogs::ImageOverlay(pixmap);
|
||||
imgDialog->show();
|
||||
connect(imgDialog, &dialogs::ImageOverlay::saving, timeline_, [this, eventId]() {
|
||||
timeline_->saveMedia(eventId);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::saveMedia(QString mxcUrl,
|
||||
QString originalFilename,
|
||||
QString mimeType,
|
||||
qml_mtx_events::EventType eventType) const
|
||||
{
|
||||
QString dialogTitle;
|
||||
if (eventType == qml_mtx_events::EventType::ImageMessage) {
|
||||
dialogTitle = tr("Save image");
|
||||
} else if (eventType == qml_mtx_events::EventType::VideoMessage) {
|
||||
dialogTitle = tr("Save video");
|
||||
} else if (eventType == qml_mtx_events::EventType::AudioMessage) {
|
||||
dialogTitle = tr("Save audio");
|
||||
} else {
|
||||
dialogTitle = tr("Save file");
|
||||
}
|
||||
|
||||
QString filterString = QMimeDatabase().mimeTypeForName(mimeType).filterString();
|
||||
|
||||
auto filename =
|
||||
QFileDialog::getSaveFileName(container, dialogTitle, originalFilename, filterString);
|
||||
|
||||
if (filename.isEmpty())
|
||||
return;
|
||||
|
||||
const auto url = mxcUrl.toStdString();
|
||||
|
||||
http::client()->download(
|
||||
url,
|
||||
[filename, url](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to retrieve image {}: {} {}",
|
||||
url,
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
QFile file(filename);
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
return;
|
||||
|
||||
file.write(QByteArray(data.data(), (int)data.size()));
|
||||
file.close();
|
||||
} catch (const std::exception &e) {
|
||||
nhlog::ui()->warn("Error while saving file to: {}", e.what());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::cacheMedia(QString mxcUrl, QString mimeType)
|
||||
{
|
||||
// If the message is a link to a non mxcUrl, don't download it
|
||||
if (!mxcUrl.startsWith("mxc://")) {
|
||||
emit mediaCached(mxcUrl, mxcUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix();
|
||||
|
||||
const auto url = mxcUrl.toStdString();
|
||||
QFileInfo filename(QString("%1/media_cache/%2.%3")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
|
||||
.arg(QString(mxcUrl).remove("mxc://"))
|
||||
.arg(suffix));
|
||||
if (QDir::cleanPath(filename.path()) != filename.path()) {
|
||||
nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
|
||||
return;
|
||||
}
|
||||
|
||||
QDir().mkpath(filename.path());
|
||||
|
||||
if (filename.isReadable()) {
|
||||
emit mediaCached(mxcUrl, filename.filePath());
|
||||
return;
|
||||
}
|
||||
|
||||
http::client()->download(
|
||||
url,
|
||||
[this, mxcUrl, filename, url](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to retrieve image {}: {} {}",
|
||||
url,
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
QFile file(filename.filePath());
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
return;
|
||||
|
||||
file.write(QByteArray(data.data(), data.size()));
|
||||
file.close();
|
||||
} catch (const std::exception &e) {
|
||||
nhlog::ui()->warn("Error while saving file to: {}", e.what());
|
||||
}
|
||||
|
||||
emit mediaCached(mxcUrl, filename.filePath());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -342,6 +222,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
|
|||
void
|
||||
TimelineViewManager::queueImageMessage(const QString &roomid,
|
||||
const QString &filename,
|
||||
const boost::optional<mtx::crypto::EncryptedFile> &file,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t dsize,
|
||||
|
@ -354,27 +235,32 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
|
|||
image.url = url.toStdString();
|
||||
image.info.h = dimensions.height();
|
||||
image.info.w = dimensions.width();
|
||||
image.file = file;
|
||||
models.value(roomid)->sendMessage(image);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::queueFileMessage(const QString &roomid,
|
||||
const QString &filename,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t dsize)
|
||||
TimelineViewManager::queueFileMessage(
|
||||
const QString &roomid,
|
||||
const QString &filename,
|
||||
const boost::optional<mtx::crypto::EncryptedFile> &encryptedFile,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t dsize)
|
||||
{
|
||||
mtx::events::msg::File file;
|
||||
file.info.mimetype = mime.toStdString();
|
||||
file.info.size = dsize;
|
||||
file.body = filename.toStdString();
|
||||
file.url = url.toStdString();
|
||||
file.file = encryptedFile;
|
||||
models.value(roomid)->sendMessage(file);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::queueAudioMessage(const QString &roomid,
|
||||
const QString &filename,
|
||||
const boost::optional<mtx::crypto::EncryptedFile> &file,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t dsize)
|
||||
|
@ -384,12 +270,14 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
|
|||
audio.info.size = dsize;
|
||||
audio.body = filename.toStdString();
|
||||
audio.url = url.toStdString();
|
||||
audio.file = file;
|
||||
models.value(roomid)->sendMessage(audio);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::queueVideoMessage(const QString &roomid,
|
||||
const QString &filename,
|
||||
const boost::optional<mtx::crypto::EncryptedFile> &file,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t dsize)
|
||||
|
@ -399,5 +287,6 @@ TimelineViewManager::queueVideoMessage(const QString &roomid,
|
|||
video.info.size = dsize;
|
||||
video.body = filename.toStdString();
|
||||
video.url = url.toStdString();
|
||||
video.file = file;
|
||||
models.value(roomid)->sendMessage(video);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <QSharedPointer>
|
||||
#include <QWidget>
|
||||
|
||||
#include <mtx/common.hpp>
|
||||
#include <mtx/responses.hpp>
|
||||
|
||||
#include "Cache.h"
|
||||
|
@ -35,38 +36,13 @@ public:
|
|||
|
||||
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
|
||||
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
|
||||
void openImageOverlay(QString mxcUrl,
|
||||
QString originalFilename,
|
||||
QString mimeType,
|
||||
qml_mtx_events::EventType eventType) const;
|
||||
void saveMedia(QString mxcUrl,
|
||||
QString originalFilename,
|
||||
QString mimeType,
|
||||
qml_mtx_events::EventType eventType) const;
|
||||
Q_INVOKABLE void cacheMedia(QString mxcUrl, QString mimeType);
|
||||
// Qml can only pass enum as int
|
||||
Q_INVOKABLE void openImageOverlay(QString mxcUrl,
|
||||
QString originalFilename,
|
||||
QString mimeType,
|
||||
int eventType) const
|
||||
{
|
||||
openImageOverlay(
|
||||
mxcUrl, originalFilename, mimeType, (qml_mtx_events::EventType)eventType);
|
||||
}
|
||||
Q_INVOKABLE void saveMedia(QString mxcUrl,
|
||||
QString originalFilename,
|
||||
QString mimeType,
|
||||
int eventType) const
|
||||
{
|
||||
saveMedia(mxcUrl, originalFilename, mimeType, (qml_mtx_events::EventType)eventType);
|
||||
}
|
||||
Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const;
|
||||
|
||||
signals:
|
||||
void clearRoomMessageCount(QString roomid);
|
||||
void updateRoomsLastMessage(QString roomid, const DescInfo &info);
|
||||
void activeTimelineChanged(TimelineModel *timeline);
|
||||
void initialSyncChanged(bool isInitialSync);
|
||||
void mediaCached(QString mxcUrl, QString cacheUrl);
|
||||
|
||||
public slots:
|
||||
void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|
||||
|
@ -80,22 +56,26 @@ public slots:
|
|||
void queueEmoteMessage(const QString &msg);
|
||||
void queueImageMessage(const QString &roomid,
|
||||
const QString &filename,
|
||||
const boost::optional<mtx::crypto::EncryptedFile> &file,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t dsize,
|
||||
const QSize &dimensions);
|
||||
void queueFileMessage(const QString &roomid,
|
||||
const QString &filename,
|
||||
const boost::optional<mtx::crypto::EncryptedFile> &file,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t dsize);
|
||||
void queueAudioMessage(const QString &roomid,
|
||||
const QString &filename,
|
||||
const boost::optional<mtx::crypto::EncryptedFile> &file,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t dsize);
|
||||
void queueVideoMessage(const QString &roomid,
|
||||
const QString &filename,
|
||||
const boost::optional<mtx::crypto::EncryptedFile> &file,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t dsize);
|
||||
|
|
Loading…
Reference in a new issue