Merge branch 'Nheko-Reborn:master' into master

pull/1471/head
Skye J 1 year ago committed by GitHub
commit dee0115daf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .ci/macos/Brewfile
  2. 15
      .ci/macos/build.sh
  3. 10
      .ci/macos/notarize.sh
  4. 2
      .ci/macos/settings.json
  5. 4
      .cirrus.yml
  6. 46
      .gitlab-ci.yml
  7. 7
      .qmlformat.ini
  8. 242
      CMakeLists.txt
  9. 4
      README.md
  10. 2
      appveyor.yml
  11. 28
      cmake/Translations.cmake
  12. 85
      im.nheko.Nheko.yaml
  13. 2
      nheko-nightly.flatpakref
  14. 5
      resources/langs/nheko_ca.ts
  15. 5
      resources/langs/nheko_cs.ts
  16. 375
      resources/langs/nheko_de.ts
  17. 5
      resources/langs/nheko_el.ts
  18. 5
      resources/langs/nheko_eo.ts
  19. 5
      resources/langs/nheko_es.ts
  20. 10
      resources/langs/nheko_et.ts
  21. 5
      resources/langs/nheko_fi.ts
  22. 144
      resources/langs/nheko_fr.ts
  23. 5
      resources/langs/nheko_hu.ts
  24. 10
      resources/langs/nheko_id.ts
  25. 5
      resources/langs/nheko_ie.ts
  26. 5
      resources/langs/nheko_it.ts
  27. 5
      resources/langs/nheko_ja.ts
  28. 5
      resources/langs/nheko_ml.ts
  29. 5
      resources/langs/nheko_nl.ts
  30. 5
      resources/langs/nheko_pl.ts
  31. 5
      resources/langs/nheko_pt_BR.ts
  32. 5
      resources/langs/nheko_pt_PT.ts
  33. 5
      resources/langs/nheko_ro.ts
  34. 5
      resources/langs/nheko_ru.ts
  35. 5
      resources/langs/nheko_si.ts
  36. 5
      resources/langs/nheko_sr_Latn.ts
  37. 5
      resources/langs/nheko_sv.ts
  38. 5
      resources/langs/nheko_tr.ts
  39. 5
      resources/langs/nheko_uk.ts
  40. 5
      resources/langs/nheko_vi.ts
  41. 5
      resources/langs/nheko_zh_CN.ts
  42. 77
      resources/qml/Avatar.qml
  43. 72
      resources/qml/ChatPage.qml
  44. 195
      resources/qml/CommunitiesList.qml
  45. 220
      resources/qml/Completer.qml
  46. 11
      resources/qml/ElidedLabel.qml
  47. 47
      resources/qml/EncryptionIndicator.qml
  48. 75
      resources/qml/ForwardCompleter.qml
  49. 20
      resources/qml/ImageButton.qml
  50. 36
      resources/qml/MatrixText.qml
  51. 128
      resources/qml/MatrixTextField.qml
  52. 258
      resources/qml/MessageInput.qml
  53. 23
      resources/qml/MessageInputWarning.qml
  54. 903
      resources/qml/MessageView.qml
  55. 45
      resources/qml/PrivacyScreen.qml
  56. 51
      resources/qml/QuickSwitcher.qml
  57. 76
      resources/qml/Reactions.qml
  58. 60
      resources/qml/ReplyPopup.qml
  59. 1186
      resources/qml/RoomList.qml
  60. 374
      resources/qml/Root.qml
  61. 96
      resources/qml/ScrollHelper.qml
  62. 174
      resources/qml/SelfVerificationCheck.qml
  63. 24
      resources/qml/StatusIndicator.qml
  64. 349
      resources/qml/TimelineRow.qml
  65. 312
      resources/qml/TimelineView.qml
  66. 79
      resources/qml/ToggleButton.qml
  67. 394
      resources/qml/TopBar.qml
  68. 14
      resources/qml/TypingIndicator.qml
  69. 71
      resources/qml/UploadBox.qml
  70. 2
      resources/qml/components/AdaptiveLayout.qml
  71. 30
      resources/qml/components/AvatarListTile.qml
  72. 27
      resources/qml/components/FlatButton.qml
  73. 2
      resources/qml/components/MainWindowDialog.qml
  74. 6
      resources/qml/components/NhekoTabButton.qml
  75. 1
      resources/qml/components/NotificationBubble.qml
  76. 8
      resources/qml/components/ReorderableListview.qml
  77. 6
      resources/qml/components/TextButton.qml
  78. 4
      resources/qml/components/UserListRow.qml
  79. 5
      resources/qml/delegates/Encrypted.qml
  80. 12
      resources/qml/delegates/EncryptionEnabled.qml
  81. 17
      resources/qml/delegates/FileMessage.qml
  82. 12
      resources/qml/delegates/ImageMessage.qml
  83. 26
      resources/qml/delegates/MessageDelegate.qml
  84. 2
      resources/qml/delegates/NoticeMessage.qml
  85. 4
      resources/qml/delegates/Pill.qml
  86. 2
      resources/qml/delegates/Placeholder.qml
  87. 47
      resources/qml/delegates/PlayableMediaMessage.qml
  88. 6
      resources/qml/delegates/Redacted.qml
  89. 8
      resources/qml/delegates/Reply.qml
  90. 17
      resources/qml/delegates/TextMessage.qml
  91. 5
      resources/qml/device-verification/DeviceVerification.qml
  92. 8
      resources/qml/device-verification/DigitVerification.qml
  93. 8
      resources/qml/device-verification/EmojiVerification.qml
  94. 2
      resources/qml/device-verification/Failed.qml
  95. 2
      resources/qml/device-verification/NewVerificationRequest.qml
  96. 2
      resources/qml/device-verification/Success.qml
  97. 4
      resources/qml/device-verification/Waiting.qml
  98. 20
      resources/qml/dialogs/AliasEditor.qml
  99. 15
      resources/qml/dialogs/AllowedRoomsSettingsDialog.qml
  100. 13
      resources/qml/dialogs/ConfirmJoinRoomDialog.qml
  101. Some files were not shown because too many files have changed in this diff Show More

@ -6,7 +6,7 @@ brew "clang-format"
brew "cmake" brew "cmake"
brew "ninja" brew "ninja"
brew "openssl" brew "openssl"
brew "qt5" brew "qt6"
brew "nlohmann_json" brew "nlohmann_json"
brew "gstreamer" brew "gstreamer"
brew "qtkeychain" brew "qtkeychain"

@ -6,27 +6,32 @@ set -ue
#TAG=$(git tag -l --points-at HEAD) #TAG=$(git tag -l --points-at HEAD)
# Add Qt binaries to path # Add Qt binaries to path
PATH="$(brew --prefix qt5):${PATH}" PATH="$(brew --prefix qt6)/bin/:${PATH}"
export PATH export PATH
CMAKE_PREFIX_PATH="$(brew --prefix qt5)" CMAKE_PREFIX_PATH="$(brew --prefix qt6)"
export CMAKE_PREFIX_PATH export CMAKE_PREFIX_PATH
cmake -GNinja -S. -Bbuild \ cmake -GNinja -S. -Bbuild \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=.deps/usr \ -DCMAKE_INSTALL_PREFIX="nheko.temp" \
-DHUNTER_ROOT="../.hunter" \ -DHUNTER_ROOT="../.hunter" \
-DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF \ -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo -DHUNTER_CONFIGURATION_TYPES=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHUNTER_CONFIGURATION_TYPES=RelWithDebInfo \
-DUSE_BUNDLED_OPENSSL=ON \ -DUSE_BUNDLED_OPENSSL=ON \
-DCI_BUILD=ON -DCI_BUILD=ON
cmake --build build cmake --build build
cmake --install build
( cd build ( cd build
git clone https://github.com/Nheko-Reborn/qt-jdenticon.git git clone https://github.com/Nheko-Reborn/qt-jdenticon.git
( cd qt-jdenticon ( cd qt-jdenticon
qmake qmake
make -j 4 make -j 4
cp libqtjdenticon.dylib ../nheko.app/Contents/MacOS cp libqtjdenticon.dylib ../../nheko.temp/nheko.app/Contents/MacOS
) )
"$(brew --prefix qt5)/bin/macdeployqt" nheko.app -always-overwrite -qmldir=../resources/qml/ # "$(brew --prefix qt6)/bin/macdeployqt" nheko.app -always-overwrite -qmldir=../resources/qml/
# # workaround for https://bugreports.qt.io/browse/QTBUG-100686
# cp "$(brew --prefix brotli)/lib/libbrotlicommon.1.dylib" nheko.app/Contents/Frameworks/libbrotlicommon.1.dylib
) )
mv nheko.temp/nheko.app nheko.app

@ -6,7 +6,7 @@ set -u
# https://forum.qt.io/topic/96652/how-to-notarize-qt-application-on-macos/18 # https://forum.qt.io/topic/96652/how-to-notarize-qt-application-on-macos/18
# Add Qt binaries to path # Add Qt binaries to path
PATH="/usr/local/opt/qt@5/bin/:${PATH}" PATH="/usr/local/opt/qt@6/bin/:${PATH}"
export PATH export PATH
security unlock-keychain -p "${RUNNER_USER_PW}" login.keychain security unlock-keychain -p "${RUNNER_USER_PW}" login.keychain
@ -20,25 +20,23 @@ if [ -n "${CI_PIPELINE_TRIGGERED:-}" ] && [ "${TRIGGERED_BY:-}" = "cirrus" ]; th
unzip binaries.zip unzip binaries.zip
# we zip 'build/nheko.app' in cirrus ci, cirrus itself puts it in a 'build' directory # we zip 'build/nheko.app' in cirrus ci, cirrus itself puts it in a 'build' directory
# so move it to the right place for the rest of the process. # so move it to the right place for the rest of the process.
( cd build || exit
unzip nheko.zip unzip nheko.zip
)
fi fi
if [ ! -d "build/nheko.app" ]; then if [ ! -d "nheko.app" ]; then
echo "nheko.app is missing, you did something wrong!" echo "nheko.app is missing, you did something wrong!"
exit 1 exit 1
fi fi
echo "[INFO] Signing app contents" echo "[INFO] Signing app contents"
find "build/nheko.app/Contents"|while read -r fname; do find "nheko.app/Contents"|while read -r fname; do
if [ -f "$fname" ]; then if [ -f "$fname" ]; then
echo "[INFO] Signing $fname" echo "[INFO] Signing $fname"
codesign --force --timestamp --options=runtime --sign "${APPLE_DEV_IDENTITY}" "$fname" codesign --force --timestamp --options=runtime --sign "${APPLE_DEV_IDENTITY}" "$fname"
fi fi
done done
codesign --force --timestamp --options=runtime --sign "${APPLE_DEV_IDENTITY}" "build/nheko.app" codesign --force --timestamp --options=runtime --sign "${APPLE_DEV_IDENTITY}" "nheko.app"
NOTARIZE_SUBMIT_LOG=$(mktemp /tmp/notarize-submit.XXXXXX) NOTARIZE_SUBMIT_LOG=$(mktemp /tmp/notarize-submit.XXXXXX)
NOTARIZE_STATUS_LOG=$(mktemp /tmp/notarize-status.XXXXXX) NOTARIZE_STATUS_LOG=$(mktemp /tmp/notarize-status.XXXXXX)

@ -3,7 +3,7 @@
"compression-level": 9, "compression-level": 9,
"contents": [ "contents": [
{ {
"path": "./build/Nheko.app", "path": "./nheko.app",
"type": "file", "type": "file",
"x": 140, "x": 140,
"y": 120 "y": 120

@ -13,9 +13,9 @@ task:
- export PATH="$(brew --prefix qt5)/bin/:${PATH}" - export PATH="$(brew --prefix qt5)/bin/:${PATH}"
- ./.ci/macos/build.sh - ./.ci/macos/build.sh
zip_script: zip_script:
- ditto -c -k --sequesterRsrc --keepParent build/nheko.app build/nheko.zip - ditto -c -k --sequesterRsrc --keepParent nheko.app nheko.zip
gitlab_script: gitlab_script:
- > - >
[ "${CIRRUS_BRANCH}" == "master" ] && curl -X POST --fail -F token="${GITLAB_TRIGGER_TOKEN}" -F ref="${CIRRUS_BRANCH}" -F "variables[TRIGGER_BUILD_ID]=${CIRRUS_BUILD_ID}" -F "variables[TRIGGERED_BY]=cirrus" "https://nheko.im/api/v4/projects/2/trigger/pipeline" || true [ "${CIRRUS_BRANCH}" == "master" ] && curl -X POST --fail -F token="${GITLAB_TRIGGER_TOKEN}" -F ref="${CIRRUS_BRANCH}" -F "variables[TRIGGER_BUILD_ID]=${CIRRUS_BUILD_ID}" -F "variables[TRIGGERED_BY]=cirrus" "https://nheko.im/api/v4/projects/2/trigger/pipeline" || true
binaries_artifacts: binaries_artifacts:
path: build/nheko.zip path: nheko.zip

@ -18,14 +18,14 @@ build-clazy:
TRAVIS_OS_NAME: linux TRAVIS_OS_NAME: linux
before_script: before_script:
- echo -e "\e[0Ksection_start:`date +%s`:install_deps[collapsed=true]\r\e[0K\e[1m\e[95mInstalling apk dependencies" - echo -e "\e[0Ksection_start:`date +%s`:install_deps[collapsed=true]\r\e[0K\e[1m\e[95mInstalling apk dependencies"
- apk add asciidoctor cmake cmark-dev gst-plugins-bad-dev gst-plugins-base-dev gstreamer-dev lmdb-dev lmdbxx nlohmann-json olm-dev openssl-dev qt5-qtbase-dev qt5-qtdeclarative-dev qt5-qtmultimedia-dev qt5-qtquickcontrols2-dev qt5-qtsvg-dev qt5-qttools-dev qtkeychain-dev samurai spdlog-dev xcb-util-wm-dev zlib-dev ccache curl-dev libevent-dev meson clazy clang16 gcc musl-dev git re2-dev - apk add asciidoctor cmake cmark-dev gst-plugins-bad-dev gst-plugins-base-dev gstreamer-dev lmdb-dev lmdbxx nlohmann-json olm-dev openssl-dev qt6-qtbase-dev qt6-qtdeclarative-dev qt6-qtmultimedia-dev qt6-qtsvg-dev qt6-qttools-dev samurai spdlog-dev xcb-util-wm-dev zlib-dev ccache curl-dev libevent-dev meson clazy clang16 gcc musl-dev git re2-dev libsecret-dev
- echo -e "\e[0Ksection_end:`date +%s`:install_deps\r\e[0K" - echo -e "\e[0Ksection_end:`date +%s`:install_deps\r\e[0K"
script: script:
- export PATH="/usr/lib/ccache:${PATH}" - export PATH="/usr/lib/ccache:${PATH}"
- export CMAKE_BUILD_PARALLEL_LEVEL=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l) - export CMAKE_BUILD_PARALLEL_LEVEL=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l)
- cmake -GNinja -H. -Bbuild - cmake -GNinja -H. -Bbuild
-DCMAKE_INSTALL_PREFIX=.deps/usr -DCMAKE_INSTALL_PREFIX=.deps/usr
-DHUNTER_ENABLED=OFF -DBUILD_SHARED_LIBS=OFF -DUSE_BUNDLED_OPENSSL=ON -DUSE_BUNDLED_MTXCLIENT=ON -DUSE_BUNDLED_COEURL=ON -DUSE_BUNDLED_OLM=ON -DHUNTER_ENABLED=OFF -DBUILD_SHARED_LIBS=OFF -DUSE_BUNDLED_OPENSSL=ON -DUSE_BUNDLED_MTXCLIENT=ON -DUSE_BUNDLED_COEURL=ON -DUSE_BUNDLED_OLM=ON -DUSE_BUNDLED_QTKEYCHAIN=ON
-DVOIP=OFF -DVOIP=OFF
-DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_TYPE=Release
-DCI_BUILD=ON -DFETCHCONTENT_QUIET=OFF -DCMAKE_CXX_COMPILER=clazy -DCI_BUILD=ON -DFETCHCONTENT_QUIET=OFF -DCMAKE_CXX_COMPILER=clazy
@ -37,7 +37,8 @@ build-clazy:
paths: paths:
- .ccache - .ccache
build-gcc11: # disabled until I find a qt6.5 ppa
.build-gcc11:
stage: build stage: build
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/ubuntu:22.04 image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/ubuntu:22.04
tags: [docker] tags: [docker]
@ -50,7 +51,7 @@ build-gcc11:
libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev
qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-dev qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-dev
qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform
qt5keychain-dev ccache clazy libcurl4-openssl-dev libevent-dev libspdlog-dev git nlohmann-json3-dev libcmark-dev asciidoc time # libolm-dev qt5keychain-dev ccache libcurl4-openssl-dev libevent-dev libspdlog-dev git nlohmann-json3-dev libcmark-dev asciidoc time # libolm-dev
# need recommended deps for wget # need recommended deps for wget
- apt-get -y install wget - apt-get -y install wget
- /usr/sbin/update-ccache-symlinks - /usr/sbin/update-ccache-symlinks
@ -107,16 +108,15 @@ build-tw:
"zlib-devel" "zlib-devel"
"libQt5PlatformHeaders-devel" "libQt5PlatformHeaders-devel"
"cmake(re2)" "cmake(re2)"
"cmake(Qt5Concurrent)" "cmake(Qt6Core)"
"cmake(Qt5Core)" "cmake(Qt6DBus)"
"cmake(Qt5DBus)" "cmake(Qt6Keychain)"
"cmake(Qt5Keychain)" "cmake(Qt6LinguistTools)"
"cmake(Qt5LinguistTools)" "cmake(Qt6Multimedia)"
"cmake(Qt5Multimedia)" "cmake(Qt6QuickControls2)"
"cmake(Qt5Network)" "cmake(Qt6Svg)"
"cmake(Qt5QuickControls2)" "cmake(Qt6Widgets)"
"cmake(Qt5Svg)" "cmake(Qt6Gui)"
"cmake(Qt5Widgets)"
"pkgconfig(libcurl)" "pkgconfig(libcurl)"
"pkgconfig(libevent)" "pkgconfig(libevent)"
"pkgconfig(gstreamer-webrtc-1.0)" "pkgconfig(gstreamer-webrtc-1.0)"
@ -155,7 +155,7 @@ build-macos:
- if : '$CI_PIPELINE_TRIGGERED == null' - if : '$CI_PIPELINE_TRIGGERED == null'
artifacts: artifacts:
paths: paths:
- build/nheko.app # not putting this in 'artifacts' subdir because we don't want to put it on releases - nheko.app # not putting this in 'artifacts' subdir because we don't want to put it on releases
name: nheko-${CI_COMMIT_SHORT_SHA}-macos-app name: nheko-${CI_COMMIT_SHORT_SHA}-macos-app
expose_as: 'macos-app' expose_as: 'macos-app'
public: false public: false
@ -200,7 +200,10 @@ build-flatpak:
- docker-${ARCH} - docker-${ARCH}
parallel: parallel:
matrix: matrix:
- ARCH: [amd64, arm64] - ARCH: amd64
JOBS: 0
- ARCH: arm64
JOBS: 3
before_script: before_script:
- echo -e "\e[0Ksection_start:`date +%s`:install_deps[collapsed=true]\r\e[0K\e[1m\e[95mInstalling apt dependencies" - echo -e "\e[0Ksection_start:`date +%s`:install_deps[collapsed=true]\r\e[0K\e[1m\e[95mInstalling apt dependencies"
- apt-get update && apt-get -y install flatpak-builder git python3 curl python3-aiohttp python3-tenacity gir1.2-ostree-1.0 - apt-get update && apt-get -y install flatpak-builder git python3 curl python3-aiohttp python3-tenacity gir1.2-ostree-1.0
@ -213,9 +216,9 @@ build-flatpak:
- mkdir -p build-flatpak - mkdir -p build-flatpak
- cd build-flatpak - cd build-flatpak
- echo -e "\e[0Ksection_start:`date +%s`:build_flatpak[collapsed=true]\r\e[0K\e[1m\e[95mBuilding flatpak" - echo -e "\e[0Ksection_start:`date +%s`:build_flatpak[collapsed=true]\r\e[0K\e[1m\e[95mBuilding flatpak"
- flatpak-builder --install-deps-from=flathub --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date` for ${ARCH}" app ../io.github.NhekoReborn.Nheko.yaml - flatpak-builder --install-deps-from=flathub --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date` for ${ARCH}" app ../im.nheko.Nheko.yaml --jobs=$JOBS
- echo -e "\e[0Ksection_end:`date +%s`:build_flatpak\r\e[0K" - echo -e "\e[0Ksection_end:`date +%s`:build_flatpak\r\e[0K"
- flatpak build-bundle repo nheko-${ARCH}.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME//\//_} - flatpak build-bundle repo nheko-${ARCH}.flatpak im.nheko.Nheko ${CI_COMMIT_REF_NAME//\//_}
after_script: after_script:
- echo -e "\e[0Ksection_start:`date +%s`:upload_flatpak[collapsed=true]\r\e[0K\e[1m\e[95mUploading flatpak" - echo -e "\e[0Ksection_start:`date +%s`:upload_flatpak[collapsed=true]\r\e[0K\e[1m\e[95mUploading flatpak"
- bash ./.ci/upload-nightly-gitlab.sh build-flatpak/nheko-${ARCH}.flatpak - bash ./.ci/upload-nightly-gitlab.sh build-flatpak/nheko-${ARCH}.flatpak
@ -233,7 +236,8 @@ build-flatpak:
paths: ['build-flatpak/nheko-${ARCH}.flatpak'] paths: ['build-flatpak/nheko-${ARCH}.flatpak']
name: flatpak-${CI_COMMIT_REF_NAME}-${VERSION}-${ARCH} name: flatpak-${CI_COMMIT_REF_NAME}-${VERSION}-${ARCH}
appimage-amd64: # disabled until I find a qt6.5 ppa for Ubuntu
.appimage-amd64:
stage: build stage: build
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/ubuntu:22.04 image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/ubuntu:22.04
tags: [docker] tags: [docker]
@ -249,7 +253,7 @@ appimage-amd64:
libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev
qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-dev qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-dev
qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform
qt5keychain-dev ccache clazy libcurl4-openssl-dev libevent-dev libspdlog-dev nlohmann-json3-dev libcmark-dev asciidoc libre2-dev libgtest-dev libgl1-mesa-dev qml-module-qtquick-particles2 qt5keychain-dev ccache libcurl4-openssl-dev libevent-dev libspdlog-dev nlohmann-json3-dev libcmark-dev asciidoc libre2-dev libgtest-dev libgl1-mesa-dev qml-module-qtquick-particles2
# Installing the packages needed to build AppImage # Installing the packages needed to build AppImage
- apt-get -yq install breeze-icon-theme desktop-file-utils elfutils fakeroot file gnupg2 gtk-update-icon-cache libgdk-pixbuf2.0-dev libgdk-pixbuf2.0-0 libglib2.0-bin librsvg2-dev libyaml-dev strace zsync squashfs-tools - apt-get -yq install breeze-icon-theme desktop-file-utils elfutils fakeroot file gnupg2 gtk-update-icon-cache libgdk-pixbuf2.0-dev libgdk-pixbuf2.0-0 libglib2.0-bin librsvg2-dev libyaml-dev strace zsync squashfs-tools
@ -325,7 +329,7 @@ github-release:
rules: rules:
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/' - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
dependencies: dependencies:
- appimage-amd64 #- appimage-amd64 <- disabled because of missing packages
- build-flatpak - build-flatpak
- codesign-macos - codesign-macos
before_script: before_script:

@ -0,0 +1,7 @@
[General]
FunctionsSpacing=
IndentWidth=4
NewlineType=native
NormalizeOrder=true
ObjectsSpacing=
UseTabs=false

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13..3.21)
option(APPVEYOR_BUILD "Build on appveyor" OFF) option(APPVEYOR_BUILD "Build on appveyor" OFF)
option(CI_BUILD "Set when building in CI. Enables -Werror where possible" OFF) option(CI_BUILD "Set when building in CI. Enables -Werror where possible" OFF)
@ -57,7 +57,7 @@ option(USE_BUNDLED_OPENSSL "Use the bundled version of OpenSSL." OFF)
option(USE_BUNDLED_MTXCLIENT "Use the bundled version of the Matrix Client library." ${HUNTER_ENABLED}) option(USE_BUNDLED_MTXCLIENT "Use the bundled version of the Matrix Client library." ${HUNTER_ENABLED})
option(USE_BUNDLED_LMDB "Use the bundled version of lmdb." ${HUNTER_ENABLED}) option(USE_BUNDLED_LMDB "Use the bundled version of lmdb." ${HUNTER_ENABLED})
option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdb++." ${HUNTER_ENABLED}) option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdb++." ${HUNTER_ENABLED})
option(USE_BUNDLED_QTKEYCHAIN "Use the bundled version of Qt5Keychain." ${HUNTER_ENABLED}) option(USE_BUNDLED_QTKEYCHAIN "Use the bundled version of Qt6Keychain." ${HUNTER_ENABLED})
option(USE_BUNDLED_COEURL "Use a bundled version of the Curl wrapper" option(USE_BUNDLED_COEURL "Use a bundled version of the Curl wrapper"
${HUNTER_ENABLED}) ${HUNTER_ENABLED})
option(USE_BUNDLED_LIBEVENT "Use the bundled version of libevent." ${HUNTER_ENABLED}) option(USE_BUNDLED_LIBEVENT "Use the bundled version of libevent." ${HUNTER_ENABLED})
@ -72,7 +72,11 @@ if (APPLE OR WIN32)
set(VOIP_DEFAULT OFF) set(VOIP_DEFAULT OFF)
endif() endif()
option(VOIP "Whether to enable voip support. Disable this, if you don't have gstreamer." ${VOIP_DEFAULT}) option(VOIP "Whether to enable voip support. Disable this, if you don't have gstreamer." ${VOIP_DEFAULT})
cmake_dependent_option(SCREENSHARE_X11 "Whether to enable screenshare support on X11." ON "VOIP" OFF) set(X11_DEFAULT)
if (WIN32 OR APPLE OR HAIKU)
set(X11_DEFAULT OFF)
endif()
option(X11 "Whether to enable X11 specific features (screenshare, window roles)." ${X11_DEFAULT})
cmake_dependent_option(SCREENSHARE_XDP "Whether to enable screenshare support using xdg-desktop-portal." ON "VOIP" OFF) cmake_dependent_option(SCREENSHARE_XDP "Whether to enable screenshare support using xdg-desktop-portal." ON "VOIP" OFF)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
@ -239,16 +243,17 @@ endif()
# #
# Discover Qt dependencies. # Discover Qt dependencies.
# #
find_package(Qt5 5.15 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia Qml QuickControls2 QuickWidgets REQUIRED) find_package(Qt6 6.5 COMPONENTS Core Widgets Gui LinguistTools Svg Multimedia Qml QuickControls2 REQUIRED)
find_package(Qt5QuickCompiler) #find_package(Qt6QuickCompiler)
find_package(Qt5DBus) find_package(Qt6DBus)
if (USE_BUNDLED_QTKEYCHAIN) if (USE_BUNDLED_QTKEYCHAIN)
include(FetchContent) include(FetchContent)
set(BUILD_WITH_QT6 ON)
FetchContent_Declare( FetchContent_Declare(
qt5keychain qt6keychain
GIT_REPOSITORY https://github.com/frankosterfeld/qtkeychain.git GIT_REPOSITORY https://github.com/frankosterfeld/qtkeychain.git
GIT_TAG v0.13.1 GIT_TAG v0.14.0
) )
if (BUILD_SHARED_LIBS) if (BUILD_SHARED_LIBS)
set(QTKEYCHAIN_STATIC OFF CACHE INTERNAL "") set(QTKEYCHAIN_STATIC OFF CACHE INTERNAL "")
@ -256,21 +261,17 @@ if (USE_BUNDLED_QTKEYCHAIN)
set(QTKEYCHAIN_STATIC ON CACHE INTERNAL "") set(QTKEYCHAIN_STATIC ON CACHE INTERNAL "")
endif() endif()
set(BUILD_TEST_APPLICATION OFF CACHE INTERNAL "") set(BUILD_TEST_APPLICATION OFF CACHE INTERNAL "")
FetchContent_MakeAvailable(qt5keychain) FetchContent_MakeAvailable(qt6keychain)
else() else()
find_package(Qt5Keychain REQUIRED) find_package(Qt6Keychain REQUIRED)
endif() endif()
if (APPLE) if (Qt6Widgets_FOUND)
find_package(Qt5MacExtras REQUIRED) if (Qt6Widgets_VERSION VERSION_LESS 6.5.0)
endif(APPLE) message(STATUS "Qt version ${Qt6Widgets_VERSION}")
message(WARNING "Minimum supported Qt6 version is 6.5!")
if (Qt5Widgets_FOUND)
if (Qt5Widgets_VERSION VERSION_LESS 5.15.0)
message(STATUS "Qt version ${Qt5Widgets_VERSION}")
message(WARNING "Minimum supported Qt5 version is 5.15!")
endif() endif()
endif(Qt5Widgets_FOUND) endif(Qt6Widgets_FOUND)
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
if(NOT MSVC) if(NOT MSVC)
@ -386,6 +387,8 @@ set(SRC_FILES
# UI components # UI components
src/ui/HiddenEvents.cpp src/ui/HiddenEvents.cpp
src/ui/HiddenEvents.h src/ui/HiddenEvents.h
src/ui/EventExpiry.cpp
src/ui/EventExpiry.h
src/ui/MxcAnimatedImage.cpp src/ui/MxcAnimatedImage.cpp
src/ui/MxcAnimatedImage.h src/ui/MxcAnimatedImage.h
src/ui/MxcMediaProxy.cpp src/ui/MxcMediaProxy.cpp
@ -394,8 +397,6 @@ set(SRC_FILES
src/ui/NhekoCursorShape.h src/ui/NhekoCursorShape.h
src/ui/NhekoDropArea.cpp src/ui/NhekoDropArea.cpp
src/ui/NhekoDropArea.h src/ui/NhekoDropArea.h
src/ui/NhekoEventObserver.cpp
src/ui/NhekoEventObserver.h
src/ui/NhekoGlobalObject.cpp src/ui/NhekoGlobalObject.cpp
src/ui/NhekoGlobalObject.h src/ui/NhekoGlobalObject.h
src/ui/RoomSettings.cpp src/ui/RoomSettings.cpp
@ -498,8 +499,6 @@ set(SRC_FILES
src/SingleImagePackModel.h src/SingleImagePackModel.h
src/TrayIcon.cpp src/TrayIcon.cpp
src/TrayIcon.h src/TrayIcon.h
src/UserDirectoryModel.cpp
src/UserDirectoryModel.h
src/UserSettingsPage.cpp src/UserSettingsPage.cpp
src/UserSettingsPage.h src/UserSettingsPage.h
src/UsersModel.cpp src/UsersModel.cpp
@ -602,7 +601,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare( FetchContent_Declare(
MatrixClient MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
GIT_TAG e136bc27b28d3bb5683735eb5a65d6ef2534ca3a GIT_TAG 0a4cc9421a97bea81a8921f3f5e040f0a34278fc
) )
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
@ -614,9 +613,10 @@ endif()
if (VOIP) if (VOIP)
include(FindPkgConfig) include(FindPkgConfig)
pkg_check_modules(GSTREAMER REQUIRED IMPORTED_TARGET gstreamer-sdp-1.0>=1.18 gstreamer-webrtc-1.0>=1.18) pkg_check_modules(GSTREAMER REQUIRED IMPORTED_TARGET gstreamer-sdp-1.0>=1.18 gstreamer-webrtc-1.0>=1.18)
if (SCREENSHARE_X11 AND NOT WIN32 AND NOT APPLE)
pkg_check_modules(XCB REQUIRED IMPORTED_TARGET xcb xcb-ewmh)
endif() endif()
if (X11 AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
pkg_check_modules(XCB REQUIRED IMPORTED_TARGET xcb xcb-ewmh)
endif() endif()
# single instance functionality # single instance functionality
@ -631,10 +631,13 @@ if (NOT APPLE AND NOT WIN32)
endif() endif()
# #
# Bundle translations. # Bundle resources
# #
include(Translations) if(Qt6QuickCompiler_FOUND AND COMPILE_QML)
set(TRANSLATION_DEPS ${LANG_QRC} ${QRC} ${QM_SRC}) qtquick_compiler_add_resources(QRC resources/res.qrc)
else()
qt_add_resources(QRC resources/res.qrc)
endif()
if (APPLE) if (APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa -framework UserNotifications") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa -framework UserNotifications")
@ -666,7 +669,7 @@ endif ()
set(NHEKO_DEPS set(NHEKO_DEPS
${SRC_FILES} ${SRC_FILES}
${TRANSLATION_DEPS} ${QRC}
${META_FILES_TO_INCLUDE}) ${META_FILES_TO_INCLUDE})
if(ASAN) if(ASAN)
@ -674,10 +677,10 @@ if(ASAN)
endif() endif()
if(WIN32) if(WIN32)
add_executable (nheko WIN32 ${OS_BUNDLE} ${NHEKO_DEPS}) qt_add_executable (nheko WIN32 ${OS_BUNDLE} ${NHEKO_DEPS})
target_compile_definitions(nheko PRIVATE _WIN32_WINNT=0x0601 NOMINMAX WIN32_LEAN_AND_MEAN STRICT) target_compile_definitions(nheko PRIVATE _WIN32_WINNT=0x0601 NOMINMAX WIN32_LEAN_AND_MEAN STRICT)
else() else()
add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS}) qt_add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
if (HAVE_BACKTRACE_SYMBOLS_FD AND NOT CMAKE_BUILD_TYPE STREQUAL "Release") if (HAVE_BACKTRACE_SYMBOLS_FD AND NOT CMAKE_BUILD_TYPE STREQUAL "Release")
set_target_properties(nheko PROPERTIES ENABLE_EXPORTS ON) set_target_properties(nheko PROPERTIES ENABLE_EXPORTS ON)
@ -689,22 +692,150 @@ set_target_properties(nheko
CMAKE_SKIP_INSTALL_RPATH TRUE CMAKE_SKIP_INSTALL_RPATH TRUE
AUTOMOC ON) AUTOMOC ON)
if(APPLE) #
target_link_libraries (nheko PRIVATE Qt5::MacExtras) # Bundle translations
elseif(WIN32) #
file(GLOB LANG_TS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/resources/langs/*.ts")
qt_add_translations(nheko RESOURCE_PREFIX "/translations" TS_FILES ${LANG_TS_SRC})
#
# Add qml files
#
set(QML_SOURCES
resources/qml/Root.qml
resources/qml/ChatPage.qml
resources/qml/CommunitiesList.qml
resources/qml/RoomList.qml
resources/qml/TimelineView.qml
resources/qml/Avatar.qml
resources/qml/Completer.qml
resources/qml/EncryptionIndicator.qml
resources/qml/ImageButton.qml
resources/qml/ElidedLabel.qml
resources/qml/MatrixText.qml
resources/qml/MatrixTextField.qml
resources/qml/ToggleButton.qml
resources/qml/UploadBox.qml
resources/qml/MessageInput.qml
resources/qml/MessageView.qml
resources/qml/PrivacyScreen.qml
resources/qml/Reactions.qml
resources/qml/ReplyPopup.qml
resources/qml/StatusIndicator.qml
resources/qml/TimelineRow.qml
resources/qml/TopBar.qml
resources/qml/QuickSwitcher.qml
resources/qml/ForwardCompleter.qml
resources/qml/SelfVerificationCheck.qml
resources/qml/TypingIndicator.qml
resources/qml/MessageInputWarning.qml
resources/qml/components/AdaptiveLayout.qml
resources/qml/components/AdaptiveLayoutElement.qml
resources/qml/components/AvatarListTile.qml
resources/qml/components/FlatButton.qml
resources/qml/components/MainWindowDialog.qml
resources/qml/components/NhekoTabButton.qml
resources/qml/components/NotificationBubble.qml
resources/qml/components/ReorderableListview.qml
resources/qml/components/SpaceMenuLevel.qml
resources/qml/components/TextButton.qml
resources/qml/components/UserListRow.qml
resources/qml/delegates/Encrypted.qml
resources/qml/delegates/FileMessage.qml
resources/qml/delegates/ImageMessage.qml
resources/qml/delegates/MessageDelegate.qml
resources/qml/delegates/NoticeMessage.qml
resources/qml/delegates/Pill.qml
resources/qml/delegates/Placeholder.qml
resources/qml/delegates/PlayableMediaMessage.qml
resources/qml/delegates/Redacted.qml
resources/qml/delegates/Reply.qml
resources/qml/delegates/TextMessage.qml
resources/qml/device-verification/DeviceVerification.qml
resources/qml/device-verification/DigitVerification.qml
resources/qml/device-verification/EmojiVerification.qml
resources/qml/device-verification/Failed.qml
resources/qml/device-verification/NewVerificationRequest.qml
resources/qml/device-verification/Success.qml
resources/qml/device-verification/Waiting.qml
resources/qml/dialogs/AliasEditor.qml
resources/qml/dialogs/ConfirmJoinRoomDialog.qml
resources/qml/dialogs/CreateDirect.qml
resources/qml/dialogs/CreateRoom.qml
resources/qml/dialogs/HiddenEventsDialog.qml
resources/qml/dialogs/EventExpirationDialog.qml
resources/qml/dialogs/ImageOverlay.qml
resources/qml/dialogs/ImagePackEditorDialog.qml
resources/qml/dialogs/ImagePackSettingsDialog.qml
resources/qml/dialogs/InputDialog.qml
resources/qml/dialogs/InviteDialog.qml
resources/qml/dialogs/JoinRoomDialog.qml
resources/qml/dialogs/LeaveRoomDialog.qml
resources/qml/dialogs/LogoutDialog.qml
resources/qml/dialogs/PhoneNumberInputDialog.qml
resources/qml/dialogs/PowerLevelEditor.qml
resources/qml/dialogs/PowerLevelSpacesApplyDialog.qml
resources/qml/dialogs/RawMessageDialog.qml
resources/qml/dialogs/ReadReceipts.qml
resources/qml/dialogs/RoomDirectory.qml
resources/qml/dialogs/RoomMembers.qml
resources/qml/dialogs/AllowedRoomsSettingsDialog.qml
resources/qml/dialogs/RoomSettings.qml
resources/qml/dialogs/UserProfile.qml
resources/qml/emoji/StickerPicker.qml
resources/qml/pages/LoginPage.qml
resources/qml/pages/RegisterPage.qml
resources/qml/pages/UserSettingsPage.qml
resources/qml/pages/WelcomePage.qml
resources/qml/ui/NhekoSlider.qml
resources/qml/ui/Ripple.qml
resources/qml/ui/Snackbar.qml
resources/qml/ui/Spinner.qml
resources/qml/ui/animations/BlinkAnimation.qml
resources/qml/ui/media/MediaControls.qml
resources/qml/voip/ActiveCallBar.qml
resources/qml/voip/CallDevices.qml
resources/qml/voip/CallInvite.qml
resources/qml/voip/CallInviteBar.qml
resources/qml/voip/DeviceError.qml
resources/qml/voip/PlaceCall.qml
resources/qml/voip/ScreenShare.qml
resources/qml/voip/VideoCall.qml
resources/qml/delegates/EncryptionEnabled.qml
resources/qml/ui/TimelineEffects.qml
)
qt_add_qml_module(nheko
URI im.nheko
NO_RESOURCE_TARGET_PATH
RESOURCE_PREFIX "/"
VERSION 1.1
DEPENDENCIES QtQml QtQuick # https://bugreports.qt.io/browse/QTBUG-102554
QML_FILES
${QML_SOURCES}
SOURCES
src/UserDirectoryModel.cpp
src/UserDirectoryModel.h
)
#qt_target_qml_sources(nheko
# #PREFIX "/"
#)
if(WIN32)
target_compile_definitions(nheko PRIVATE WIN32_LEAN_AND_MEAN) target_compile_definitions(nheko PRIVATE WIN32_LEAN_AND_MEAN)
target_link_libraries (nheko PRIVATE ${NTDLIB} Qt5::WinMain)
if(MSVC) if(MSVC)
target_compile_options(nheko PUBLIC "/Zc:__cplusplus") target_compile_options(nheko PUBLIC "/Zc:__cplusplus")
endif() endif()
else() else()
target_link_libraries (nheko PRIVATE Qt5::DBus) target_link_libraries (nheko PRIVATE Qt6::DBus)
if (FLATPAK) if (FLATPAK)
target_compile_definitions(nheko PRIVATE NHEKO_FLATPAK) target_compile_definitions(nheko PRIVATE NHEKO_FLATPAK)
endif() endif()
endif() endif()
target_include_directories(nheko PRIVATE src includes) target_include_directories(nheko PRIVATE src includes src/timeline/ src/ui/ src/encryption/ src/voip/)
if (USE_BUNDLED_CPPHTTPLIB) if (USE_BUNDLED_CPPHTTPLIB)
target_include_directories(nheko PRIVATE third_party/cpp-httplib-0.5.12) target_include_directories(nheko PRIVATE third_party/cpp-httplib-0.5.12)
@ -729,7 +860,7 @@ endif()
# Fixup bundled keychain include dirs # Fixup bundled keychain include dirs
if (USE_BUNDLED_QTKEYCHAIN) if (USE_BUNDLED_QTKEYCHAIN)
target_include_directories(nheko PRIVATE ${qt5keychain_SOURCE_DIR} ${qt5keychain_BINARY_DIR}) target_include_directories(nheko PRIVATE ${qt6keychain_SOURCE_DIR} ${qt6keychain_BINARY_DIR})
endif() endif()
if (NOT JSON_ImplicitConversions) if (NOT JSON_ImplicitConversions)
@ -744,14 +875,13 @@ target_link_libraries(nheko PRIVATE
MatrixClient::MatrixClient MatrixClient::MatrixClient
cmark::cmark cmark::cmark
spdlog::spdlog spdlog::spdlog
Qt5::Widgets Qt::Widgets
Qt5::Svg Qt::Svg
Qt5::Concurrent Qt::Gui
Qt5::Multimedia Qt::Multimedia
Qt5::Qml Qt::Qml
Qt5::QuickControls2 Qt::QuickControls2
Qt5::QuickWidgets qt6keychain
qt5keychain
nlohmann_json::nlohmann_json nlohmann_json::nlohmann_json
lmdbxx::lmdbxx lmdbxx::lmdbxx
liblmdb::lmdb liblmdb::lmdb
@ -768,11 +898,11 @@ endif()
if (TARGET PkgConfig::GSTREAMER) if (TARGET PkgConfig::GSTREAMER)
target_link_libraries(nheko PRIVATE PkgConfig::GSTREAMER) target_link_libraries(nheko PRIVATE PkgConfig::GSTREAMER)
target_compile_definitions(nheko PRIVATE GSTREAMER_AVAILABLE) target_compile_definitions(nheko PRIVATE GSTREAMER_AVAILABLE)
endif()
if (TARGET PkgConfig::XCB) if (TARGET PkgConfig::XCB)
target_link_libraries(nheko PRIVATE PkgConfig::XCB) target_link_libraries(nheko PRIVATE PkgConfig::XCB)
target_compile_definitions(nheko PRIVATE XCB_AVAILABLE) target_compile_definitions(nheko PRIVATE XCB_AVAILABLE)
endif() endif()
endif()
if(MSVC) if(MSVC)
target_link_libraries(nheko PRIVATE ntdll) target_link_libraries(nheko PRIVATE ntdll)
@ -797,9 +927,23 @@ if(MAN)
add_subdirectory(man) add_subdirectory(man)
endif() endif()
# potential workaround for macdeployqt issues
if(APPLE)
install(TARGETS nheko
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
qt_generate_deploy_qml_app_script(
TARGET nheko
OUTPUT_SCRIPT deploy_script
NO_UNSUPPORTED_PLATFORM_ERROR
)
install(SCRIPT ${deploy_script})
endif()
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
if(FLATPAK) if(FLATPAK)
set(APPID "io.github.NhekoReborn.Nheko") set(APPID "im.nheko.Nheko")
set_target_properties(nheko PROPERTIES OUTPUT_NAME "${APPID}") set_target_properties(nheko PROPERTIES OUTPUT_NAME "${APPID}")
else() else()
set(APPID "nheko") set(APPID "nheko")

@ -4,7 +4,7 @@ nheko
[![Build status](https://ci.appveyor.com/api/projects/status/07qrqbfylsg4hw2h/branch/master?svg=true)](https://ci.appveyor.com/project/redsky17/nheko/branch/master) [![Build status](https://ci.appveyor.com/api/projects/status/07qrqbfylsg4hw2h/branch/master?svg=true)](https://ci.appveyor.com/project/redsky17/nheko/branch/master)
[![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/latest) [![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/latest)
[![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://matrix-static.neko.dev/room/!TshDrgpBNBDmfDeEGN:neko.dev/) [![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://matrix-static.neko.dev/room/!TshDrgpBNBDmfDeEGN:neko.dev/)
<a href='https://flatpak.neko.dev/repo/nightly/appstream/io.github.NhekoReborn.Nheko.flatpakref' download='nheko-nightly.flatpakref'><img alt='Download Nightly Flatpak' src='https://img.shields.io/badge/download-flatpak--nightly-green'/></a> <a href='https://flatpak.neko.dev/repo/nightly/appstream/im.nheko.Nheko.flatpakref' download='nheko-nightly.flatpakref'><img alt='Download Nightly Flatpak' src='https://img.shields.io/badge/download-flatpak--nightly-green'/></a>
[![#nheko-reborn:matrix.org](https://img.shields.io/matrix/nheko-reborn:matrix.org.svg?label=%23nheko-reborn:matrix.org)](https://matrix.to/#/#nheko-reborn:matrix.org) [![#nheko-reborn:matrix.org](https://img.shields.io/matrix/nheko-reborn:matrix.org.svg?label=%23nheko-reborn:matrix.org)](https://matrix.to/#/#nheko-reborn:matrix.org)
[![Arch package](https://repology.org/badge/version-for-repo/arch/nheko.svg)](https://archlinux.org/packages/community/x86_64/nheko/) [![Arch package](https://repology.org/badge/version-for-repo/arch/nheko.svg)](https://archlinux.org/packages/community/x86_64/nheko/)
<a href='https://flathub.org/apps/details/io.github.NhekoReborn.Nheko'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a> <a href='https://flathub.org/apps/details/io.github.NhekoReborn.Nheko'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a>
@ -249,7 +249,7 @@ KDE has similar plugins, that can extend the supported image types even more.
- Voice call support: dtls, opus, rtpmanager, srtp, webrtc - Voice call support: dtls, opus, rtpmanager, srtp, webrtc
- Video call support (optional): compositor, opengl, qmlgl, rtp, vpx - Video call support (optional): compositor, opengl, qmlgl, rtp, vpx
- [libnice](https://gitlab.freedesktop.org/libnice/libnice) - [libnice](https://gitlab.freedesktop.org/libnice/libnice)
- XCB, XCB-EWMH: For screensharing support on X11. VOIP needs to be enabled. Can be disabled with `-DSCREENSHARE_X11=OFF`. - XCB, XCB-EWMH: For screensharing support on X11 and setting window roles. Can be disabled with `-DSCREENSHARE_X11=OFF`.
- [qtkeychain](https://github.com/frankosterfeld/qtkeychain) (You need at least version 0.12 for proper Gnome Keychain support. The bundled version requires libsecret, unless you pass `-DLIBSECRET_SUPPORT=OFF`.) - [qtkeychain](https://github.com/frankosterfeld/qtkeychain) (You need at least version 0.12 for proper Gnome Keychain support. The bundled version requires libsecret, unless you pass `-DLIBSECRET_SUPPORT=OFF`.)
- A compiler that supports C++ 20: - A compiler that supports C++ 20:
- Clang 16 (Only clazy 16 is tested in CI) - Clang 16 (Only clazy 16 is tested in CI)

@ -22,7 +22,7 @@ build:
verbosity: minimal verbosity: minimal
install: install:
- set QT_DIR=C:\Qt\5.15\msvc2019_64 - set QT_DIR=C:\Qt\6.5\msvc2019_64
- set PATH=C:\Strawberry\perl\bin;C:\Python39-x64;%QT_DIR%\bin;%PATH% - set PATH=C:\Strawberry\perl\bin;C:\Python39-x64;%QT_DIR%\bin;%PATH%
build_script: build_script:

@ -1,28 +0,0 @@
#
# Generate the translation resource file
#
file(GLOB LANG_TS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/resources/langs/*.ts")
qt5_add_translation(QM_SRC ${LANG_TS_SRC})
qt5_create_translation(${QM_SRC})
add_custom_target(LANG_QRC ALL DEPENDS ${QM_SRC})
# Generate a qrc file for the translations
set(_qrc ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
if(NOT EXISTS ${_qrc})
file(WRITE ${_qrc} "<RCC>\n <qresource prefix=\"/translations\">\n")
foreach(_lang ${QM_SRC})
get_filename_component(_filename ${_lang} NAME)
file(APPEND ${_qrc} " <file>${_filename}</file>\n")
endforeach(_lang)
file(APPEND ${_qrc} " </qresource>\n</RCC>\n")
endif()
qt5_add_resources(LANG_QRC ${_qrc})
if(Qt5QuickCompiler_FOUND AND COMPILE_QML)
qtquick_compiler_add_resources(QRC resources/res.qrc)
else()
qt5_add_resources(QRC resources/res.qrc)
endif()

@ -1,7 +1,7 @@
id: io.github.NhekoReborn.Nheko id: im.nheko.Nheko
command: io.github.NhekoReborn.Nheko command: im.nheko.Nheko
runtime: org.kde.Platform runtime: org.kde.Platform
runtime-version: '5.15-22.08' runtime-version: '6.5'
sdk: org.kde.Sdk sdk: org.kde.Sdk
finish-args: finish-args:
- --device=dri - --device=dri
@ -44,9 +44,9 @@ cleanup:
modules: modules:
- name: lmdb - name: lmdb
sources: sources:
- sha256: f3927859882eb608868c8c31586bb7eb84562a40a6bf5cc3e13b6b564641ea28 - sha256: 8c5a93ac3cc97427c54571ad5a6140b7469389d01e6d2f43df39f96d3a4ccef7
type: archive type: archive
url: https://github.com/LMDB/lmdb/archive/LMDB_0.9.22.tar.gz url: https://git.openldap.org/openldap/openldap/-/archive/LMDB_0.9.30/openldap-LMDB_0.9.30.tar.gz
make-install-args: make-install-args:
- prefix=/app - prefix=/app
no-autogen: true no-autogen: true
@ -109,45 +109,46 @@ modules:
tag: 0.20.4 tag: 0.20.4
type: git type: git
url: https://gitlab.gnome.org/GNOME/libsecret.git url: https://gitlab.gnome.org/GNOME/libsecret.git
- config-opts: #- config-opts:
- -DCMAKE_BUILD_TYPE=Release # - -DCMAKE_BUILD_TYPE=Release
- -DAVIF_CODEC_AOM=ON # - -DAVIF_CODEC_AOM=ON
#- -DBUILD_SHARED_LIBS=OFF # #- -DBUILD_SHARED_LIBS=OFF
buildsystem: cmake-ninja # buildsystem: cmake-ninja
name: libavif # name: libavif
sources: # sources:
- sha256: 66e82854ceb84a3e542bc140a343bc90e56c68f3ecb4fff63e636c136ed9a05e # - sha256: 66e82854ceb84a3e542bc140a343bc90e56c68f3ecb4fff63e636c136ed9a05e
type: archive # type: archive
url: https://github.com/AOMediaCodec/libavif/archive/refs/tags/v0.10.1.tar.gz # url: https://github.com/AOMediaCodec/libavif/archive/refs/tags/v0.10.1.tar.gz
- config-opts: #- config-opts:
- -DCMAKE_BUILD_TYPE=Release # - -DCMAKE_BUILD_TYPE=Release
- -DWITH_EXAMPLES=OFF # - -DWITH_EXAMPLES=OFF
#- -DBUILD_SHARED_LIBS=OFF # #- -DBUILD_SHARED_LIBS=OFF
buildsystem: cmake-ninja # buildsystem: cmake-ninja
name: libheif # name: libheif
sources: # sources:
- sha256: e1ac2abb354fdc8ccdca71363ebad7503ad731c84022cf460837f0839e171718 # - sha256: e1ac2abb354fdc8ccdca71363ebad7503ad731c84022cf460837f0839e171718
type: archive # type: archive
url: https://github.com/strukturag/libheif/releases/download/v1.12.0/libheif-1.12.0.tar.gz # url: https://github.com/strukturag/libheif/releases/download/v1.12.0/libheif-1.12.0.tar.gz
- config-opts: #- config-opts:
- -DCMAKE_BUILD_TYPE=Release # - -DCMAKE_BUILD_TYPE=Release
- -DKIMAGEFORMATS_HEIF=ON # - -DKIMAGEFORMATS_HEIF=ON
buildsystem: cmake-ninja # buildsystem: cmake-ninja
name: KImageFormats # name: KImageFormats
sources: # sources:
- commit: ae6b724824fc2fdf71d50dc7ae0052ad1551b25a # - commit: ae6b724824fc2fdf71d50dc7ae0052ad1551b25a
tag: v5.93.0 # tag: v5.93.0
type: git # type: git
url: https://invent.kde.org/frameworks/kimageformats.git # url: https://invent.kde.org/frameworks/kimageformats.git
- config-opts: - config-opts:
- -DCMAKE_BUILD_TYPE=Release - -DCMAKE_BUILD_TYPE=Release
- -DBUILD_TEST_APPLICATION=OFF - -DBUILD_TEST_APPLICATION=OFF
- -DQTKEYCHAIN_STATIC=ON - -DQTKEYCHAIN_STATIC=ON
- -DBUILD_WITH_QT6=ON
buildsystem: cmake-ninja buildsystem: cmake-ninja
name: QtKeychain name: QtKeychain
sources: sources:
- commit: f59ac26be709fd2d8d7a062fab1cf1e67a93806c - commit: 69f993c47efed7e557d79a30a367014d9a27d809
tag: v0.13.1 tag: 0.14.1
type: git type: git
url: https://github.com/frankosterfeld/qtkeychain.git url: https://github.com/frankosterfeld/qtkeychain.git
- config-opts: - config-opts:
@ -170,15 +171,15 @@ modules:
- buildsystem: meson - buildsystem: meson
name: gstreamer name: gstreamer
sources: sources:
- commit: f7806a854aad960eae3288db4a67a574f92428fe - commit: ecd471f5ea4645102b206a43d863f0f0fe7d04ec
tag: 1.20.5 tag: 1.22.3
type: git type: git
url: https://gitlab.freedesktop.org/gstreamer/gstreamer.git url: https://gitlab.freedesktop.org/gstreamer/gstreamer.git
config-opts: config-opts:
- --auto-features=disabled - --auto-features=disabled
- -Dgood=enabled - -Dgood=enabled
- -Dgst-plugins-good:qt5=enabled - -Dgst-plugins-good:qt6=enabled
- -Dqt5=enabled #- -Dqt6=enabled <- not available on 1.22
- -Dbase=enabled - -Dbase=enabled
- -Dgst-plugins-base:gl=enabled - -Dgst-plugins-base:gl=enabled
- -Dgst-plugins-base:gl_platform=glx,egl - -Dgst-plugins-base:gl_platform=glx,egl
@ -213,7 +214,7 @@ modules:
buildsystem: cmake-ninja buildsystem: cmake-ninja
name: mtxclient name: mtxclient
sources: sources:
- commit: e136bc27b28d3bb5683735eb5a65d6ef2534ca3a - commit: 0a4cc9421a97bea81a8921f3f5e040f0a34278fc
#tag: v0.9.2 #tag: v0.9.2
type: git type: git
url: https://github.com/Nheko-Reborn/mtxclient.git url: https://github.com/Nheko-Reborn/mtxclient.git

@ -1,6 +1,6 @@
[Flatpak Ref] [Flatpak Ref]
Title=Nheko Nightly Title=Nheko Nightly
Name=io.github.NhekoReborn.Nheko Name=im.nheko.Nheko
Branch=master Branch=master
Url=https://flatpak.neko.dev/repo/nightly Url=https://flatpak.neko.dev/repo/nightly
SuggestRemoteName=nheko-nightlies SuggestRemoteName=nheko-nightlies

@ -1390,11 +1390,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1392,11 +1392,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

File diff suppressed because it is too large Load Diff

@ -1390,11 +1390,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation type="unfinished">Διάλεξε ένα αρχείο</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1394,11 +1394,6 @@ Vi povas aldoni noton, pri kial oni akceptu vian frapadon:</translation>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Elektu dosieron</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1392,11 +1392,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Seleccionar un archivo</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1393,11 +1393,6 @@ Kui soovid, siis võid lisada ka selgituse, miks peaks sinu koputusele reageerim
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Vali fail</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>
@ -1408,6 +1403,11 @@ Kui soovid, siis võid lisada ka selgituse, miks peaks sinu koputusele reageerim
<source>Upload of &apos;%1&apos; failed</source> <source>Upload of &apos;%1&apos; failed</source>
<translation>%1 üleslaadimine ei õnnestunud</translation> <translation>%1 üleslaadimine ei õnnestunud</translation>
</message> </message>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select file(s)</source>
<translation>Vali fail(id)</translation>
</message>
</context> </context>
<context> <context>
<name>InviteDialog</name> <name>InviteDialog</name>

@ -1393,11 +1393,6 @@ Voit antaa valinnaisen syyn muiden hyväksyäkseen koputuksesi:</translation>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Valitse tiedosto</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -96,7 +96,7 @@
<message> <message>
<location line="+12"/> <location line="+12"/>
<source>Add</source> <source>Add</source>
<translation type="unfinished">Ajouter</translation> <translation>Ajouter</translation>
</message> </message>
</context> </context>
<context> <context>
@ -229,7 +229,7 @@
<message> <message>
<location line="-554"/> <location line="-554"/>
<source>Confirm logout</source> <source>Confirm logout</source>
<translation type="unfinished"></translation> <translation>Confirmer la déconnexion</translation>
</message> </message>
<message> <message>
<location line="+102"/> <location line="+102"/>
@ -239,7 +239,7 @@
<message> <message>
<location line="+35"/> <location line="+35"/>
<source>Failed to open database, logging out!</source> <source>Failed to open database, logging out!</source>
<translation>Impossible d&apos;ouvrir la base de données, déconnexion&#x202f;!</translation> <translation>Impossible d&apos;ouvrir la base de données, déconnexion!</translation>
</message> </message>
<message> <message>
<location line="+262"/> <location line="+262"/>
@ -249,7 +249,7 @@
<message> <message>
<location line="+4"/> <location line="+4"/>
<source>Do you really want to knock on %1? You may optionally provide a reason for others to accept your knock:</source> <source>Do you really want to knock on %1? You may optionally provide a reason for others to accept your knock:</source>
<translation>Voulez-vous vraiment toquer à %1 ? Vous pouvez fournir une raison aux autres de l&apos;accepter :</translation> <translation>Voulez-vous vraiment frapper à %1 ? Vous pouvez donner une raison aux membres actuels de vous accepter :</translation>
</message> </message>
<message> <message>
<location line="+15"/> <location line="+15"/>
@ -275,7 +275,7 @@
<message> <message>
<location line="-470"/> <location line="-470"/>
<source>Do you really want to invite %1 (%2)?</source> <source>Do you really want to invite %1 (%2)?</source>
<translation>Voulez-vous vraiment inviter %1 (%2)&#x202f;?</translation> <translation>Voulez-vous vraiment inviter %1 (%2)?</translation>
</message> </message>
<message> <message>
<location line="+12"/> <location line="+12"/>
@ -305,7 +305,7 @@
<message> <message>
<location line="+1"/> <location line="+1"/>
<source>Do you really want to unban %1 (%2)?</source> <source>Do you really want to unban %1 (%2)?</source>
<translation>Voulez-vous vraiment annuler le bannissement de %1 (%2)&#x202f;?</translation> <translation>Voulez-vous vraiment annuler le bannissement de %1 (%2)?</translation>
</message> </message>
<message> <message>
<location line="+10"/> <location line="+10"/>
@ -325,19 +325,21 @@
<message> <message>
<location line="-922"/> <location line="-922"/>
<source>Cache migration failed!</source> <source>Cache migration failed!</source>
<translation>Échec de la migration du cache&#x202f;!</translation> <translation>Échec de la migration du cache!</translation>
</message> </message>
<message> <message>
<location line="-87"/> <location line="-87"/>
<source>Because of the following reason Nheko wants to drop you to the login page: <source>Because of the following reason Nheko wants to drop you to the login page:
%1 %1
If you think this is a mistake, you can close Nheko instead to possibly recover your encryption keys. After you have been dropped to the login page, you can sign in again using your usual methods.</source> If you think this is a mistake, you can close Nheko instead to possibly recover your encryption keys. After you have been dropped to the login page, you can sign in again using your usual methods.</source>
<translation type="unfinished"></translation> <translation>Nheko veut vous renvoyer à la page de connexion pour cette raison&#xa0;:
%1
Si vous pensez qu&apos;il s&apos;agit d&apos;une erreur, vous pouvez plutôt fermer Nheko pour essayer de récupérer vos clés de chiffrement. De retour à la page de connexion, vous pourrez vous reconnecter par vos méthodes habituelles.</translation>
</message> </message>
<message> <message>
<location line="+88"/> <location line="+88"/>
<source>Migrating the cache to the current version failed. This can have different reasons. Please open an issue at https://github.com/Nheko-Reborn/nheko and try to use an older version in the meantime. Alternatively you can try deleting the cache manually.</source> <source>Migrating the cache to the current version failed. This can have different reasons. Please open an issue at https://github.com/Nheko-Reborn/nheko and try to use an older version in the meantime. Alternatively you can try deleting the cache manually.</source>
<translation type="unfinished"></translation> <translation>La migration du cache vers la version actuelle a échoué. Plusieurs causes sont possibles. Merci d&apos;ouvrir un rapport d&apos;anomalie sur https://github.com/Nheko-Reborn/nheko et essayez d&apos;utiliser une version antérieure entretemps. Vous pouvez également tenter d&apos;effacer le cache manuellement.</translation>
</message> </message>
<message> <message>
<location line="+12"/> <location line="+12"/>
@ -371,7 +373,8 @@ If you think this is a mistake, you can close Nheko instead to possibly recover
<location line="+34"/> <location line="+34"/>
<source>You failed to join %1. You can try to knock so that others can invite you in. Do you want to do so? <source>You failed to join %1. You can try to knock so that others can invite you in. Do you want to do so?
You may optionally provide a reason for others to accept your knock:</source> You may optionally provide a reason for others to accept your knock:</source>
<translation type="unfinished"></translation> <translation>Vous n&apos;avez pas pu rejoindre %1. Vous pouvez essayer de frapper au salon afin que les autres membres vous invitent. Voulez-vous le faire?
Vous pouvez éventuellement fournir une raison afin que les membres acceptent votre requête&#xa0;:</translation>
</message> </message>
<message> <message>
<location line="+52"/> <location line="+52"/>
@ -381,7 +384,7 @@ You may optionally provide a reason for others to accept your knock:</source>
<message> <message>
<location line="+9"/> <location line="+9"/>
<source>Failed to remove invite: %1</source> <source>Failed to remove invite: %1</source>
<translation>Impossible de supprimer l&apos;invitation&#x202f;: %1</translation> <translation>Impossible de supprimer l&apos;invitation: %1</translation>
</message> </message>
<message> <message>
<location line="+27"/> <location line="+27"/>
@ -406,7 +409,7 @@ You may optionally provide a reason for others to accept your knock:</source>
<message> <message>
<location line="+15"/> <location line="+15"/>
<source>Failed to kick %1 from %2: %3</source> <source>Failed to kick %1 from %2: %3</source>
<translation>Échec de l&apos;expulsion de %1 de %2&#x202f;&#x202f;: %3</translation> <translation>Échec de l&apos;expulsion de %1 de %2: %3</translation>
</message> </message>
<message> <message>
<location line="+13"/> <location line="+13"/>
@ -429,27 +432,27 @@ You may optionally provide a reason for others to accept your knock:</source>
<message> <message>
<location filename="../../src/CommandCompleter.cpp" line="+106"/> <location filename="../../src/CommandCompleter.cpp" line="+106"/>
<source>/me &lt;message&gt;</source> <source>/me &lt;message&gt;</source>
<translation type="unfinished"></translation> <translation>/me &lt;message&gt;</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>/react &lt;text&gt;</source> <source>/react &lt;text&gt;</source>
<translation type="unfinished"></translation> <translation>/react &lt;texte&gt;</translation>
</message> </message>
<message> <message>
<location line="+6"/> <location line="+6"/>
<source>/part [reason]</source> <source>/part [reason]</source>
<translation type="unfinished"></translation> <translation>/part [raison]</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>/leave [reason]</source> <source>/leave [reason]</source>
<translation type="unfinished"></translation> <translation>/leave [raison]</translation>
</message> </message>
<message> <message>
<location line="+12"/> <location line="+12"/>
<source>/roomnick &lt;displayname&gt;</source> <source>/roomnick &lt;displayname&gt;</source>
<translation type="unfinished"></translation> <translation>/roomnick &lt;nomaffiché&gt;</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
@ -565,57 +568,57 @@ You may optionally provide a reason for others to accept your knock:</source>
<location line="+2"/> <location line="+2"/>
<location line="+2"/> <location line="+2"/>
<source>Leave a room. Reason is optional.</source> <source>Leave a room. Reason is optional.</source>
<translation type="unfinished"></translation> <translation>Quitte un salon. La raison est optionnelle.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Invite a user into the current room. Reason is optional.</source> <source>Invite a user into the current room. Reason is optional.</source>
<translation type="unfinished"></translation> <translation>Invite un utilisateur dans le salon actuel. La raison est optionnelle.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Kick a user from the current room. Reason is optional.</source> <source>Kick a user from the current room. Reason is optional.</source>
<translation type="unfinished"></translation> <translation>Expulse un utilisateur du salon actuel. La raison est optionnelle.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Ban a user from the current room. Reason is optional.</source> <source>Ban a user from the current room. Reason is optional.</source>
<translation type="unfinished"></translation> <translation>Bannit un utilisateur du salon actuel. La raison est optionnelle.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Unban a user in the current room. Reason is optional.</source> <source>Unban a user in the current room. Reason is optional.</source>
<translation type="unfinished"></translation> <translation>-bannit un utilisateur du salon actuel. La raison est optionnelle.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Redact an event or all locally cached messages of a user.</source> <source>Redact an event or all locally cached messages of a user.</source>
<translation type="unfinished"></translation> <translation>Efface un évènement ou tous les messages connus (présents dans la base de données locale) d&apos;un utilisateur.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Change your displayname in this room.</source> <source>Change your displayname in this room.</source>
<translation type="unfinished"></translation> <translation>Change votre nom affiché dans ce salon.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>¯\_()_/¯ with an optional message.</source> <source>¯\_()_/¯ with an optional message.</source>
<translation type="unfinished"></translation> <translation>¯\_()_/¯ avec un message optionnel.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>(°°) </source> <source>(°°) </source>
<translation type="unfinished"></translation> <translation>(°°) </translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>( º _ º)</source> <source>( º _ º)</source>
<translation type="unfinished"></translation> <translation>( º _ º)</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source> ( \o°o)\</source> <source> ( \o°o)\</source>
<translation type="unfinished"></translation> <translation> ( \o°o)\</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
@ -650,32 +653,32 @@ You may optionally provide a reason for others to accept your knock:</source>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Send a message in rainbow colors.</source> <source>Send a message in rainbow colors.</source>
<translation type="unfinished"></translation> <translation>Envoie un message aux couleurs de l&apos;arc-en-ciel.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Send /me in rainbow colors.</source> <source>Send /me in rainbow colors.</source>
<translation type="unfinished"></translation> <translation>Envoie /me aux couleurs de l&apos;arc-en-ciel.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Send a bot message.</source> <source>Send a bot message.</source>
<translation type="unfinished"></translation> <translation>Envoie un message de robot.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Send a bot message in rainbow colors.</source> <source>Send a bot message in rainbow colors.</source>
<translation type="unfinished"></translation> <translation>Envoie un message de robot aux couleurs de l&apos;arc-en-ciel.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Send a message with confetti.</source> <source>Send a message with confetti.</source>
<translation type="unfinished"></translation> <translation>Envoie un message avec des confettis.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Send a message in rainbow colors with confetti.</source> <source>Send a message in rainbow colors with confetti.</source>
<translation type="unfinished"></translation> <translation>Envoie des confettis avec un message aux couleurs de l&apos;arc-en-ciel.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
@ -698,12 +701,12 @@ You may optionally provide a reason for others to accept your knock:</source>
<message> <message>
<location filename="../qml/CommunitiesList.qml" line="+65"/> <location filename="../qml/CommunitiesList.qml" line="+65"/>
<source>Do not show notification counts for this community or tag.</source> <source>Do not show notification counts for this community or tag.</source>
<translation type="unfinished"></translation> <translation>Ne pas afficher le compteur de notifications pour cette communauté ou cette étiquette.</translation>
</message> </message>
<message> <message>
<location line="+7"/> <location line="+7"/>
<source>Hide rooms with this tag or from this community by default.</source> <source>Hide rooms with this tag or from this community by default.</source>
<translation type="unfinished"></translation> <translation>Cache par défaut les salons avec cette étiquette ou provenant de cette communauté.</translation>
</message> </message>
<message> <message>
<location line="+86"/> <location line="+86"/>
@ -771,7 +774,7 @@ You may optionally provide a reason for others to accept your knock:</source>
<message> <message>
<location line="+660"/> <location line="+660"/>
<source>Failed to update community: %1</source> <source>Failed to update community: %1</source>
<translation type="unfinished"></translation> <translation>Erreur lors de la mise à jour de cette communauté&#xa0;: %1</translation>
</message> </message>
<message> <message>
<location line="+18"/> <location line="+18"/>
@ -804,7 +807,7 @@ You may optionally provide a reason for others to accept your knock:</source>
<message numerus="yes"> <message numerus="yes">
<location line="+67"/> <location line="+67"/>
<source>%n member(s)</source> <source>%n member(s)</source>
<translation type="unfinished"> <translation>
<numerusform>%n membre</numerusform> <numerusform>%n membre</numerusform>
<numerusform>%n membres</numerusform> <numerusform>%n membres</numerusform>
</translation> </translation>
@ -964,17 +967,17 @@ You may optionally provide a reason for others to accept your knock:</source>
<message> <message>
<location line="+8"/> <location line="+8"/>
<source>Please verify the following digits. You should see the same numbers on both sides. If they differ, please press &apos;They do not match!&apos; to abort verification!</source> <source>Please verify the following digits. You should see the same numbers on both sides. If they differ, please press &apos;They do not match!&apos; to abort verification!</source>
<translation>Veuillez vérifier les chiffres suivants. Vous devriez voir les mêmes chiffres des deux côtés. Si ceux-ci diffèrent, veuillez choisir «&#x202f;Ils sont différents&#x202f;!&#x202f;» pour annuler la vérification&#x202f;!</translation> <translation>Veuillez vérifier les chiffres suivants. Vous devriez voir les mêmes chiffres des deux côtés. Si ceux-ci diffèrent, veuillez choisir «Ils sont différents!» pour annuler la vérification!</translation>
</message> </message>
<message> <message>
<location line="+33"/> <location line="+33"/>
<source>They do not match!</source> <source>They do not match!</source>
<translation>Ils sont différents&#x202f;!</translation> <translation>Ils sont différents!</translation>
</message> </message>
<message> <message>
<location line="+13"/> <location line="+13"/>
<source>They match!</source> <source>They match!</source>
<translation>Ils sont identiques&#x202f;!</translation> <translation>Ils sont identiques!</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1035,7 +1038,7 @@ You may optionally provide a reason for others to accept your knock:</source>
<message> <message>
<location line="+8"/> <location line="+8"/>
<source>Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press &apos;They do not match!&apos; to abort verification!</source> <source>Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press &apos;They do not match!&apos; to abort verification!</source>
<translation>Veuillez vérifier les émoji suivants. Vous devriez voir les mêmes émoji des deux côtés. S&apos;ils diffèrent, veuillez choisir « Ils sont différents&#x202f;!&#x202f;» pour annuler la vérification&#x202f;!</translation> <translation>Veuillez vérifier les émoji suivants. Vous devriez voir les mêmes émoji des deux côtés. S&apos;ils diffèrent, veuillez choisir « Ils sont différents!» pour annuler la vérification!</translation>
</message> </message>
<message> <message>
<location line="+379"/> <location line="+379"/>
@ -1045,12 +1048,12 @@ You may optionally provide a reason for others to accept your knock:</source>
<message> <message>
<location line="+10"/> <location line="+10"/>
<source>They do not match!</source> <source>They do not match!</source>
<translation>Ils sont différents&#x202f;!</translation> <translation>Ils sont différents!</translation>
</message> </message>
<message> <message>
<location line="+13"/> <location line="+13"/>
<source>They match!</source> <source>They match!</source>
<translation>Ils sont identiques&#x202f;!</translation> <translation>Ils sont identiques!</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1114,7 +1117,7 @@ You may optionally provide a reason for others to accept your knock:</source>
<message> <message>
<location filename="../qml/EncryptionIndicator.qml" line="+57"/> <location filename="../qml/EncryptionIndicator.qml" line="+57"/>
<source>This message is not encrypted!</source> <source>This message is not encrypted!</source>
<translation>Ce message n&apos;est pas chiffré&#x202f;!</translation> <translation>Ce message n&apos;est pas chiffré!</translation>
</message> </message>
<message> <message>
<location line="+4"/> <location line="+4"/>
@ -1147,7 +1150,7 @@ You may optionally provide a reason for others to accept your knock:</source>
<message> <message>
<location line="+4"/> <location line="+4"/>
<source>Key mismatch detected!</source> <source>Key mismatch detected!</source>
<translation>Clés non correspondantes détectées&#x202f;!</translation> <translation>Clés non correspondantes détectées!</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
@ -1162,7 +1165,7 @@ You may optionally provide a reason for others to accept your knock:</source>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Verification messages received out of order!</source> <source>Verification messages received out of order!</source>
<translation>Messages de vérification reçus dans le désordre&#x202f;!</translation> <translation>Messages de vérification reçus dans le désordre!</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
@ -1390,11 +1393,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Sélectionnez un fichier</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>
@ -1543,7 +1541,7 @@ Example: https://server.my:8787</source>
<location line="+64"/> <location line="+64"/>
<location line="+127"/> <location line="+127"/>
<source>You have entered an invalid Matrix ID e.g @joe:matrix.org</source> <source>You have entered an invalid Matrix ID e.g @joe:matrix.org</source>
<translation>Vous avez entré un identifiant Matrix invalide exemple correct&#x202f;: @moi:monserveur.example.com)</translation> <translation>Vous avez entré un identifiant Matrix invalide exemple correct: @moi:monserveur.example.com)</translation>
</message> </message>
<message> <message>
<location line="-157"/> <location line="-157"/>
@ -1626,12 +1624,12 @@ Example: https://server.my:8787</source>
<message> <message>
<location line="+1"/> <location line="+1"/>
<source>A call is in progress. Log out?</source> <source>A call is in progress. Log out?</source>
<translation>Un appel est en cours. Se déconnecter&#x202f;?</translation> <translation>Un appel est en cours. Se déconnecter?</translation>
</message> </message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>Are you sure you want to log out?</source> <source>Are you sure you want to log out?</source>
<translation>Êtes-vous certain de vouloir vous déconnecter&#x202f;?</translation> <translation>Êtes-vous certain de vouloir vous déconnecter?</translation>
</message> </message>
</context> </context>
<context> <context>
@ -2041,7 +2039,7 @@ Example: https://server.my:8787</source>
<message> <message>
<location filename="../qml/voip/PlaceCall.qml" line="+42"/> <location filename="../qml/voip/PlaceCall.qml" line="+42"/>
<source>Place a call to %1?</source> <source>Place a call to %1?</source>
<translation>Appeler %1&#x202f;?</translation> <translation>Appeler %1?</translation>
</message> </message>
<message> <message>
<location line="+16"/> <location line="+16"/>
@ -3174,17 +3172,17 @@ Veuillez noter qu&apos;il ne pourra plus être désactivé par la suite.</transl
<message> <message>
<location filename="../qml/voip/ScreenShare.qml" line="+30"/> <location filename="../qml/voip/ScreenShare.qml" line="+30"/>
<source>Share desktop with %1?</source> <source>Share desktop with %1?</source>
<translation>Partager le bureau avec %1&#x202f;&#x202f;?</translation> <translation>Partager le bureau avec %1?</translation>
</message> </message>
<message> <message>
<location line="+11"/> <location line="+11"/>
<source>Window:</source> <source>Window:</source>
<translation>Fenêtre&#x202f;:</translation> <translation>Fenêtre:</translation>
</message> </message>
<message> <message>
<location line="+20"/> <location line="+20"/>
<source>Frame rate:</source> <source>Frame rate:</source>
<translation>Fréquence d&apos;images&#x202f;:</translation> <translation>Fréquence d&apos;images:</translation>
</message> </message>
<message> <message>
<location line="+19"/> <location line="+19"/>
@ -3233,7 +3231,7 @@ Veuillez noter qu&apos;il ne pourra plus être désactivé par la suite.</transl
<message> <message>
<location line="+1"/> <location line="+1"/>
<source>Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues</source> <source>Nheko could not connect to the secure storage to save encryption secrets to. This can have multiple reasons. Check if your D-Bus service is running and you have configured a service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If you are having trouble, feel free to open an issue here: https://github.com/Nheko-Reborn/nheko/issues</source>
<translation>Nheko n&apos;a pas pu se connecter au stockage sécurisé afin d&apos;y sauvegarder les clés de chiffrement. Cela peut avoir différentes causes. Vérifiez si votre service D-Bus est lancé, et si vous avez configuré un service tel que KWallet&#x202f;; Gnome Keyring&#x202f;; KeePassXC ou l&apos;équivalent pour votre système. Si vous n&apos;arrivez pas à résoudre le problème, n&apos;hésitez pas à nous en faire part ici&#x202f;: https&#x202f;://github.com/Nheko-Reborn/nheko/issues</translation> <translation>Nheko n&apos;a pas pu se connecter au stockage sécurisé afin d&apos;y sauvegarder les clés de chiffrement. Cela peut avoir différentes causes. Vérifiez si votre service D-Bus est lancé, et si vous avez configuré un service tel que KWallet; Gnome Keyring; KeePassXC ou l&apos;équivalent pour votre système. Si vous n&apos;arrivez pas à résoudre le problème, n&apos;hésitez pas à nous en faire part ici: https://github.com/Nheko-Reborn/nheko/issues</translation>
</message> </message>
</context> </context>
<context> <context>
@ -3241,7 +3239,7 @@ Veuillez noter qu&apos;il ne pourra plus être désactivé par la suite.</transl
<message> <message>
<location filename="../qml/SelfVerificationCheck.qml" line="+41"/> <location filename="../qml/SelfVerificationCheck.qml" line="+41"/>
<source>This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don&apos;t share it with anyone and don&apos;t lose it! Do not pass go! Do not collect $200!</source> <source>This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don&apos;t share it with anyone and don&apos;t lose it! Do not pass go! Do not collect $200!</source>
<translation>Ceci est votre clé de récupération. Vous en aurez besoin afin de restaurer l&apos;accès à vos messages chiffrés et à vos clés de vérification. Gardez cette clé en sûreté. Ne la partagez pas avec qui que ce soit et ne la perdez pas&#x202f;! Ne passez pas par la case départ et ne recevez pas 20 000 francs&#x202f;!</translation> <translation>Ceci est votre clé de récupération. Vous en aurez besoin afin de restaurer l&apos;accès à vos messages chiffrés et à vos clés de vérification. Gardez cette clé en sûreté. Ne la partagez pas avec qui que ce soit et ne la perdez pas! Ne passez pas par la case départ et ne recevez pas 20 000 francs!</translation>
</message> </message>
<message> <message>
<location line="+33"/> <location line="+33"/>
@ -3262,8 +3260,8 @@ Veuillez noter qu&apos;il ne pourra plus être désactivé par la suite.</transl
<location line="+10"/> <location line="+10"/>
<source>Hello and welcome to Matrix! <source>Hello and welcome to Matrix!
It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!</source> It seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!</source>
<translation>Bonjour et bienvenue sur le réseau Matrix&#x202f;! <translation>Bonjour et bienvenue sur le réseau Matrix!
Il semblerait que ce soit votre première fois ici. Avant de pouvoir chiffrer vos messages de manière sécurisée, nous devons configurer quelques détails. Vous pouvez soit accepter immédiatement, soit ajuster quelques options basiques. Nous essayons également d&apos;expliquer le fonctionnement de certains mécanismes. Vous pouvez sauter ces étapes, mais celles-ci pourraient se montrer utiles par la suite&#x202f;!</translation> Il semblerait que ce soit votre première fois ici. Avant de pouvoir chiffrer vos messages de manière sécurisée, nous devons configurer quelques détails. Vous pouvez soit accepter immédiatement, soit ajuster quelques options basiques. Nous essayons également d&apos;expliquer le fonctionnement de certains mécanismes. Vous pouvez sauter ces étapes, mais celles-ci pourraient se montrer utiles par la suite!</translation>
</message> </message>
<message> <message>
<location line="+124"/> <location line="+124"/>
@ -3293,17 +3291,17 @@ Si vous choisissez de vérifier, vous aurez besoin de l&apos;autre appareil. Si
<message> <message>
<location filename="../../src/encryption/SelfVerificationStatus.cpp" line="+47"/> <location filename="../../src/encryption/SelfVerificationStatus.cpp" line="+47"/>
<source>Failed to create keys for cross-signing!</source> <source>Failed to create keys for cross-signing!</source>
<translation>Échec de la création des clés pour l&apos;auto-vérification (cross-signing)&#x202f;!</translation> <translation>Échec de la création des clés pour l&apos;auto-vérification (cross-signing)!</translation>
</message> </message>
<message> <message>
<location line="+16"/> <location line="+16"/>
<source>Failed to create keys for online key backup!</source> <source>Failed to create keys for online key backup!</source>
<translation>Échec de la création de clés pour la sauvegarde en ligne&#x202f;!</translation> <translation>Échec de la création de clés pour la sauvegarde en ligne!</translation>
</message> </message>
<message> <message>
<location line="+29"/> <location line="+29"/>
<source>Failed to create keys for secure server side secret storage!</source> <source>Failed to create keys for secure server side secret storage!</source>
<translation>Échec de la création des clés pour le stockage sécurisé côté serveur&#x202f;!</translation> <translation>Échec de la création des clés pour le stockage sécurisé côté serveur!</translation>
</message> </message>
<message> <message>
<location line="+44"/> <location line="+44"/>
@ -3313,7 +3311,7 @@ Si vous choisissez de vérifier, vous aurez besoin de l&apos;autre appareil. Si
<message> <message>
<location line="+6"/> <location line="+6"/>
<source>Encryption setup failed: %1</source> <source>Encryption setup failed: %1</source>
<translation>Échec de la configuration du chiffrement&#x202f;: %1</translation> <translation>Échec de la configuration du chiffrement: %1</translation>
</message> </message>
<message> <message>
<location line="+156"/> <location line="+156"/>
@ -3426,7 +3424,7 @@ Si vous choisissez de vérifier, vous aurez besoin de l&apos;autre appareil. Si
<message> <message>
<location line="+10"/> <location line="+10"/>
<source>Verification successful! Both sides verified their devices!</source> <source>Verification successful! Both sides verified their devices!</source>
<translation>Vérification réussie&#x202f;! Les deux côtés ont vérifié leur appareil&#x202f;!</translation> <translation>Vérification réussie! Les deux côtés ont vérifié leur appareil!</translation>
</message> </message>
<message> <message>
<location line="+14"/> <location line="+14"/>
@ -3446,7 +3444,7 @@ Si vous choisissez de vérifier, vous aurez besoin de l&apos;autre appareil. Si
<location line="+115"/> <location line="+115"/>
<location line="+5"/> <location line="+5"/>
<source>Failed to encrypt event, sending aborted!</source> <source>Failed to encrypt event, sending aborted!</source>
<translation>Échec du chiffrement de l&apos;évènement, envoi abandonné&#x202f;!</translation> <translation>Échec du chiffrement de l&apos;évènement, envoi abandonné!</translation>
</message> </message>
<message> <message>
<location line="+196"/> <location line="+196"/>
@ -3826,7 +3824,7 @@ Raison : %4</translation>
<location line="+36"/> <location line="+36"/>
<source>%1 left after having already left!</source> <source>%1 left after having already left!</source>
<comment>This is a leave event after the user already left and shouldn&apos;t happen apart from state resets</comment> <comment>This is a leave event after the user already left and shouldn&apos;t happen apart from state resets</comment>
<translation>%1 a quitté le salon après l&apos;avoir déjà quitté&#x202f;!</translation> <translation>%1 a quitté le salon après l&apos;avoir déjà quitté!</translation>
</message> </message>
<message> <message>
<location line="+7"/> <location line="+7"/>
@ -4863,17 +4861,17 @@ This setting will take effect upon restart.</source>
<message> <message>
<location line="+12"/> <location line="+12"/>
<source>Waiting for other side to accept the verification request.</source> <source>Waiting for other side to accept the verification request.</source>
<translation>Attente que le correspondant accepte la demande de vérification.</translation> <translation>Attente d&apos;acceptation de la demande de vérification par le correspondant.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Waiting for other side to continue the verification process.</source> <source>Waiting for other side to continue the verification process.</source>
<translation>Attente que le correspondant poursuive le processus de vérification.</translation> <translation>Attente de la poursuite du processus de vérification par le correspondant.</translation>
</message> </message>
<message> <message>
<location line="+2"/> <location line="+2"/>
<source>Waiting for other side to complete the verification process.</source> <source>Waiting for other side to complete the verification process.</source>
<translation>Attente que le correspondant termine le processus de vérification.</translation> <translation>Attente de la fin du processus de vérification par le correspondant.</translation>
</message> </message>
<message> <message>
<location line="+19"/> <location line="+19"/>

@ -1388,11 +1388,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Fájl kiválasztása</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1391,11 +1391,6 @@ Kamu dapat memberikan alasan untuk orang lain untuk menerima ketukanmu:</transla
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Pilih sebuah file</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>
@ -1406,6 +1401,11 @@ Kamu dapat memberikan alasan untuk orang lain untuk menerima ketukanmu:</transla
<source>Upload of &apos;%1&apos; failed</source> <source>Upload of &apos;%1&apos; failed</source>
<translation>Pengunggahan &apos;%1&apos; gagal</translation> <translation>Pengunggahan &apos;%1&apos; gagal</translation>
</message> </message>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select file(s)</source>
<translation>Pilih berkas</translation>
</message>
</context> </context>
<context> <context>
<name>InviteDialog</name> <name>InviteDialog</name>

@ -1390,11 +1390,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1390,11 +1390,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation type="unfinished">Seleziona un file</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1388,11 +1388,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1390,11 +1390,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation> ി</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1393,11 +1393,6 @@ Je kan optioneel hier een reden invoeren dat je aanklopt:</translation>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Selecteer een bestand</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1392,11 +1392,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Wybierz plik</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1390,11 +1390,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1390,11 +1390,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Selecionar um ficheiro</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1392,11 +1392,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1392,11 +1392,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Выберите файл</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1390,11 +1390,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1392,11 +1392,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1390,11 +1390,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Välj en fil</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1393,11 +1393,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1395,11 +1395,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation>Виберіть файл</translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1388,11 +1388,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -1391,11 +1391,6 @@ You may optionally provide a reason for others to accept your knock:</source>
</context> </context>
<context> <context>
<name>InputBar</name> <name>InputBar</name>
<message>
<location filename="../../src/timeline/InputBar.cpp" line="+372"/>
<source>Select a file</source>
<translation></translation>
</message>
<message> <message>
<location line="+0"/> <location line="+0"/>
<source>All Files (*)</source> <source>All Files (*)</source>

@ -11,78 +11,67 @@ import im.nheko 1.0
AbstractButton { AbstractButton {
id: avatar id: avatar
property string url property alias color: bg.color
property string userid property bool crop: true
property string roomid
property string displayName property string displayName
property string roomid
property alias textColor: label.color property alias textColor: label.color
property bool crop: true property string url
property alias color: bg.color property string userid
width: 48
height: 48 height: 48
width: 48
background: Rectangle { background: Rectangle {
id: bg id: bg
color: palette.alternateBase
radius: Settings.avatarCircles ? height / 2 : height / 8 radius: Settings.avatarCircles ? height / 2 : height / 8
color: Nheko.colors.alternateBase
} }
Label { Label {
id: label id: label
enabled: false
anchors.fill: parent anchors.fill: parent
text: TimelineManager.escapeEmoji(displayName ? String.fromCodePoint(displayName.codePointAt(0)) : "") color: palette.text
textFormat: Text.RichText enabled: false
font.pixelSize: avatar.height / 2 font.pixelSize: avatar.height / 2
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: TimelineManager.escapeEmoji(avatar.displayName ? String.fromCodePoint(avatar.displayName.codePointAt(0)) : "")
textFormat: Text.RichText
verticalAlignment: Text.AlignVCenter
visible: img.status != Image.Ready && !Settings.useIdenticon visible: img.status != Image.Ready && !Settings.useIdenticon
color: Nheko.colors.text
} }
Image { Image {
id: identicon id: identicon
anchors.fill: parent anchors.fill: parent
source: Settings.useIdenticon ? ("image://jdenticon/" + (avatar.userid !== "" ? avatar.userid : avatar.roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : ""
visible: Settings.useIdenticon && img.status != Image.Ready visible: Settings.useIdenticon && img.status != Image.Ready
source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : ""
} }
Image { Image {
id: img id: img
anchors.fill: parent anchors.fill: parent
asynchronous: true asynchronous: true
fillMode: avatar.crop ? Image.PreserveAspectCrop : Image.PreserveAspectFit fillMode: avatar.crop ? Image.PreserveAspectCrop : Image.PreserveAspectFit
mipmap: true source: if (avatar.url.startsWith('image://colorimage')) {
smooth: true return avatar.url + "&radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale");
sourceSize.width: avatar.width * Screen.devicePixelRatio } else if (avatar.url.startsWith('image://')) {
sourceSize.height: avatar.height * Screen.devicePixelRatio
source: if (avatar.url.startsWith('image://')) {
return avatar.url + "?radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale"); return avatar.url + "?radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale");
} else if (avatar.url.startsWith(':/')) { } else if (avatar.url.startsWith(':/')) {
return "image://colorimage/" + avatar.url + "?" + textColor; return "image://colorimage/" + avatar.url + "?" + label.color;
} else { } else {
return ""; return "";
} }
sourceSize.height: avatar.height * Screen.devicePixelRatio
sourceSize.width: avatar.width * Screen.devicePixelRatio
} }
Rectangle { Rectangle {
id: onlineIndicator id: onlineIndicator
anchors.bottom: avatar.bottom
anchors.right: avatar.right
visible: !!userid
height: avatar.height / 6
width: height
radius: Settings.avatarCircles ? height / 2 : height / 8
color: updatePresence()
function updatePresence() { function updatePresence() {
switch (Presence.userPresence(userid)) { switch (Presence.userPresence(avatar.userid)) {
case "online": case "online":
return Nheko.theme.online; return Nheko.theme.online;
case "unavailable": case "unavailable":
@ -94,22 +83,28 @@ AbstractButton {
} }
} }
Connections { anchors.bottom: avatar.bottom
target: Presence anchors.right: avatar.right
color: updatePresence()
height: avatar.height / 6
radius: Settings.avatarCircles ? height / 2 : height / 8
visible: !!avatar.userid
width: height
Connections {
function onPresenceChanged(id) { function onPresenceChanged(id) {
if (id == userid) onlineIndicator.color = onlineIndicator.updatePresence(); if (id == avatar.userid)
onlineIndicator.color = onlineIndicator.updatePresence();
} }
target: Presence
} }
} }
NhekoCursorShape {
CursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
Ripple { Ripple {
color: Qt.rgba(Nheko.colors.alternateBase.r, Nheko.colors.alternateBase.g, Nheko.colors.alternateBase.b, 0.5) color: Qt.rgba(palette.alternateBase.r, palette.alternateBase.g, palette.alternateBase.b, 0.5)
} }
} }

@ -14,19 +14,19 @@ import QtQml 2.15
Rectangle { Rectangle {
id: chatPage id: chatPage
color: Nheko.colors.window color: palette.window
ColumnLayout { ColumnLayout {
spacing: 0
anchors.fill: parent anchors.fill: parent
spacing: 0
Rectangle { Rectangle {
id: offlineIndicator id: offlineIndicator
Layout.fillWidth: true
Layout.preferredHeight: offlineLabel.height + Nheko.paddingMedium
color: Nheko.theme.error color: Nheko.theme.error
visible: !TimelineManager.isConnected visible: !TimelineManager.isConnected
Layout.preferredHeight: offlineLabel.height + Nheko.paddingMedium
Layout.fillWidth: true
z: 1 z: 1
Label { Label {
@ -36,18 +36,9 @@ Rectangle {
text: qsTr("No network connection") text: qsTr("No network connection")
} }
} }
AdaptiveLayout { AdaptiveLayout {
id: adaptiveView id: adaptiveView
Layout.fillWidth: true
Layout.fillHeight: true
singlePageMode: communityListC.preferredWidth + roomListC.preferredWidth + timlineViewC.minimumWidth > width
pageIndex: 1
Component.onCompleted: initializePageIndex()
onSinglePageModeChanged: initializePageIndex()
function initializePageIndex() { function initializePageIndex() {
if (!singlePageMode) if (!singlePageMode)
adaptiveView.pageIndex = 0; adaptiveView.pageIndex = 0;
@ -57,67 +48,67 @@ Rectangle {
adaptiveView.pageIndex = 1; adaptiveView.pageIndex = 1;
} }
Layout.fillHeight: true
Layout.fillWidth: true
pageIndex: 1
singlePageMode: communityListC.preferredWidth + roomListC.preferredWidth + timlineViewC.minimumWidth > width
Component.onCompleted: initializePageIndex()
onSinglePageModeChanged: initializePageIndex()
Connections { Connections {
target: Rooms
function onCurrentRoomChanged() { function onCurrentRoomChanged() {
adaptiveView.initializePageIndex(); adaptiveView.initializePageIndex();
} }
}
target: Rooms
}
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: communityListC id: communityListC
visible: Settings.groupView
minimumWidth: communitiesList.avatarSize * 4 + Nheko.paddingMedium * 2
collapsedWidth: communitiesList.avatarSize + 2 * Nheko.paddingMedium collapsedWidth: communitiesList.avatarSize + 2 * Nheko.paddingMedium
preferredWidth: Settings.communityListWidth >= minimumWidth ? Settings.communityListWidth : collapsedWidth
maximumWidth: communitiesList.avatarSize * 10 + 2 * Nheko.paddingMedium maximumWidth: communitiesList.avatarSize * 10 + 2 * Nheko.paddingMedium
minimumWidth: communitiesList.avatarSize * 4 + Nheko.paddingMedium * 2
preferredWidth: Settings.communityListWidth >= minimumWidth ? Settings.communityListWidth : collapsedWidth
visible: Settings.groupView
CommunitiesList { CommunitiesList {
id: communitiesList id: communitiesList
collapsed: parent.collapsed collapsed: parent.collapsed
} }
Binding { Binding {
target: Settings delayed: true
property: 'communityListWidth' property: 'communityListWidth'
restoreMode: Binding.RestoreBindingOrValue
target: Settings
value: communityListC.preferredWidth value: communityListC.preferredWidth
when: !adaptiveView.singlePageMode when: !adaptiveView.singlePageMode
delayed: true
restoreMode: Binding.RestoreBindingOrValue
} }
} }
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: roomListC id: roomListC
minimumWidth: roomlist.avatarSize * 4 + Nheko.paddingSmall * 2
preferredWidth: (Settings.roomListWidth == - 1)
? (roomlist.avatarSize * 5 + Nheko.paddingSmall * 2)
: (Settings.roomListWidth >= minimumWidth ? Settings.roomListWidth : collapsedWidth)
maximumWidth: roomlist.avatarSize * 10 + Nheko.paddingSmall * 2
collapsedWidth: roomlist.avatarSize + 2 * Nheko.paddingMedium collapsedWidth: roomlist.avatarSize + 2 * Nheko.paddingMedium
maximumWidth: roomlist.avatarSize * 10 + Nheko.paddingSmall * 2
minimumWidth: roomlist.avatarSize * 4 + Nheko.paddingSmall * 2
preferredWidth: (Settings.roomListWidth == -1) ? (roomlist.avatarSize * 5 + Nheko.paddingSmall * 2) : (Settings.roomListWidth >= minimumWidth ? Settings.roomListWidth : collapsedWidth)
RoomList { RoomList {
id: roomlist id: roomlist
height: adaptiveView.height
collapsed: parent.collapsed collapsed: parent.collapsed
height: adaptiveView.height
} }
Binding { Binding {
target: Settings delayed: true
property: 'roomListWidth' property: 'roomListWidth'
restoreMode: Binding.RestoreBindingOrValue
target: Settings
value: roomListC.preferredWidth value: roomListC.preferredWidth
when: !adaptiveView.singlePageMode when: !adaptiveView.singlePageMode
delayed: true
restoreMode: Binding.RestoreBindingOrValue
} }
} }
AdaptiveLayoutElement { AdaptiveLayoutElement {
id: timlineViewC id: timlineViewC
@ -127,25 +118,20 @@ Rectangle {
id: timeline id: timeline
privacyScreen: privacyScreen privacyScreen: privacyScreen
showBackButton: adaptiveView.singlePageMode
room: Rooms.currentRoom room: Rooms.currentRoom
roomPreview: Rooms.currentRoomPreview.roomid ? Rooms.currentRoomPreview : null roomPreview: Rooms.currentRoomPreview.roomid ? Rooms.currentRoomPreview : null
showBackButton: adaptiveView.singlePageMode
} }
} }
} }
} }
PrivacyScreen { PrivacyScreen {
id: privacyScreen id: privacyScreen
anchors.fill: parent anchors.fill: parent
visible: Settings.privacyScreen
screenTimeout: Settings.privacyScreenTimeout screenTimeout: Settings.privacyScreenTimeout
timelineRoot: adaptiveView timelineRoot: adaptiveView
visible: Settings.privacyScreen
windowTarget: MainWindow windowTarget: MainWindow
} }
} }

@ -13,19 +13,24 @@ import im.nheko 1.0
Page { Page {
id: communitySidebar id: communitySidebar
//leftPadding: Nheko.paddingSmall //leftPadding: Nheko.paddingSmall
//rightPadding: Nheko.paddingSmall //rightPadding: Nheko.paddingSmall
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6) property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 1.6)
property bool collapsed: false property bool collapsed: false
background: Rectangle {
color: Nheko.theme.sidebarBackground
}
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu // HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
Connections { Connections {
function onHideMenu() { function onHideMenu() {
communityContextMenu.close() communityContextMenu.close();
} }
target: MainWindow target: MainWindow
} }
ListView { ListView {
id: communitiesList id: communitiesList
@ -36,201 +41,183 @@ Page {
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
id: scrollbar id: scrollbar
parent: !collapsed && Settings.scrollbarsInRoomlist ? communitiesList : null
}
ScrollHelper {
flickable: parent
anchors.fill: parent
enabled: !Settings.mobileMode
}
Platform.Menu {
id: communityContextMenu
property string tagId
property bool hidden
property bool muted
function show(id_, hidden_, muted_) {
tagId = id_;
hidden = hidden_;
muted = muted_;
open();
}
Platform.MenuItem {
text: qsTr("Do not show notification counts for this community or tag.")
checkable: true
checked: communityContextMenu.muted
onTriggered: Communities.toggleTagMute(communityContextMenu.tagId)
}
Platform.MenuItem {
text: qsTr("Hide rooms with this tag or from this community by default.")
checkable: true
checked: communityContextMenu.hidden
onTriggered: Communities.toggleTagId(communityContextMenu.tagId)
}
parent: !collapsed && Settings.scrollbarsInRoomlist ? communitiesList : null
} }
delegate: ItemDelegate { delegate: ItemDelegate {
id: communityItem id: communityItem
property color backgroundColor: Nheko.colors.window property color backgroundColor: palette.window
property color importantText: Nheko.colors.text property color bubbleBackground: palette.highlight
property color unimportantText: Nheko.colors.buttonText property color bubbleText: palette.highlightedText
property color bubbleBackground: Nheko.colors.highlight property color importantText: palette.text
property color bubbleText: Nheko.colors.highlightedText
required property var model required property var model
property color unimportantText: palette.buttonText
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: model.tooltip
ToolTip.visible: hovered && collapsed
height: avatarSize + 2 * Nheko.paddingMedium height: avatarSize + 2 * Nheko.paddingMedium
width: ListView.view.width - ((scrollbar.interactive && scrollbar.visible && scrollbar.parent) ? scrollbar.width : 0)
state: "normal" state: "normal"
ToolTip.visible: hovered && collapsed width: ListView.view.width - ((scrollbar.interactive && scrollbar.visible && scrollbar.parent) ? scrollbar.width : 0)
ToolTip.text: model.tooltip
ToolTip.delay: Nheko.tooltipDelay background: Rectangle {
onClicked: Communities.setCurrentTagId(model.id) color: communityItem.backgroundColor
onPressAndHold: communityContextMenu.show(model.id, model.hidden, model.muted) }
states: [ states: [
State { State {
name: "highlight" name: "highlight"
when: (communityItem.hovered || model.hidden) && !(Communities.currentTagId === model.id) when: (communityItem.hovered || model.hidden) && !(Communities.currentTagId === model.id)
PropertyChanges { PropertyChanges {
backgroundColor: palette.dark
bubbleBackground: palette.highlight
bubbleText: palette.highlightedText
importantText: palette.brightText
target: communityItem target: communityItem
backgroundColor: Nheko.colors.dark unimportantText: palette.brightText
importantText: Nheko.colors.brightText
unimportantText: Nheko.colors.brightText
bubbleBackground: Nheko.colors.highlight
bubbleText: Nheko.colors.highlightedText
} }
}, },
State { State {
name: "selected" name: "selected"
when: Communities.currentTagId == model.id when: Communities.currentTagId == model.id
PropertyChanges { PropertyChanges {
backgroundColor: palette.highlight
bubbleBackground: palette.highlightedText
bubbleText: palette.highlight
importantText: palette.highlightedText
target: communityItem target: communityItem
backgroundColor: Nheko.colors.highlight unimportantText: palette.highlightedText
importantText: Nheko.colors.highlightedText
unimportantText: Nheko.colors.highlightedText
bubbleBackground: Nheko.colors.highlightedText
bubbleText: Nheko.colors.highlight
} }
} }
] ]
onClicked: Communities.setCurrentTagId(model.id)
onPressAndHold: communityContextMenu.show(model.id, model.hidden, model.muted)
Item { Item {
anchors.fill: parent anchors.fill: parent
TapHandler { TapHandler {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onSingleTapped: communityContextMenu.show(model.id, model.hidden, model.muted)
gesturePolicy: TapHandler.ReleaseWithinBounds
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
} gesturePolicy: TapHandler.ReleaseWithinBounds
onSingleTapped: communityContextMenu.show(model.id, model.hidden, model.muted)
}
} }
RowLayout { RowLayout {
id: r id: r
spacing: Nheko.paddingMedium
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingMedium
anchors.leftMargin: Nheko.paddingMedium + (communitySidebar.collapsed ? 0 : (fontMetrics.lineSpacing * model.depth)) anchors.leftMargin: Nheko.paddingMedium + (communitySidebar.collapsed ? 0 : (fontMetrics.lineSpacing * model.depth))
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
ImageButton { ImageButton {
visible: !communitySidebar.collapsed && model.collapsible Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: fontMetrics.lineSpacing Layout.preferredHeight: fontMetrics.lineSpacing
Layout.preferredWidth: fontMetrics.lineSpacing Layout.preferredWidth: fontMetrics.lineSpacing
Layout.alignment: Qt.AlignVCenter
height: fontMetrics.lineSpacing
width: fontMetrics.lineSpacing
image: model.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg"
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: model.collapsed ? qsTr("Expand") : qsTr("Collapse") ToolTip.text: model.collapsed ? qsTr("Expand") : qsTr("Collapse")
ToolTip.visible: hovered
height: fontMetrics.lineSpacing
hoverEnabled: true hoverEnabled: true
image: model.collapsed ? ":/icons/icons/ui/collapsed.svg" : ":/icons/icons/ui/expanded.svg"
visible: !communitySidebar.collapsed && model.collapsible
width: fontMetrics.lineSpacing
onClicked: model.collapsed = !model.collapsed onClicked: model.collapsed = !model.collapsed
} }
Item { Item {
Layout.preferredWidth: fontMetrics.lineSpacing Layout.preferredWidth: fontMetrics.lineSpacing
visible: !communitySidebar.collapsed && !model.collapsible && Communities.containsSubspaces visible: !communitySidebar.collapsed && !model.collapsible && Communities.containsSubspaces
} }
Avatar { Avatar {
id: avatar id: avatar
enabled: false
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: communityItem.backgroundColor
displayName: model.displayName
enabled: false
height: avatarSize height: avatarSize
width: avatarSize roomid: model.id
textColor: model.avatarUrl.startsWith(":/") ? communityItem.unimportantText : communityItem.importantText
url: { url: {
if (model.avatarUrl.startsWith("mxc://")) if (model.avatarUrl.startsWith("mxc://"))
return model.avatarUrl.replace("mxc://", "image://MxcImage/"); return model.avatarUrl.replace("mxc://", "image://MxcImage/");
else if (model.avatarUrl.length > 0)
return model.avatarUrl;
else else
return "image://colorimage/" + model.avatarUrl + "?" + communityItem.unimportantText; return "";
} }
roomid: model.id width: avatarSize
displayName: model.displayName
color: communityItem.backgroundColor
NotificationBubble { NotificationBubble {
notificationCount: model.unreadMessages anchors.bottom: avatar.bottom
hasLoudNotification: model.hasLoudNotification anchors.margins: -Nheko.paddingSmall
anchors.right: avatar.right
bubbleBackgroundColor: communityItem.bubbleBackground bubbleBackgroundColor: communityItem.bubbleBackground
bubbleTextColor: communityItem.bubbleText bubbleTextColor: communityItem.bubbleText
font.pixelSize: fontMetrics.font.pixelSize * 0.6 font.pixelSize: fontMetrics.font.pixelSize * 0.6
hasLoudNotification: model.hasLoudNotification
mayBeVisible: communitySidebar.collapsed && !model.muted && Settings.spaceNotifications mayBeVisible: communitySidebar.collapsed && !model.muted && Settings.spaceNotifications
anchors.right: avatar.right notificationCount: model.unreadMessages
anchors.bottom: avatar.bottom
anchors.margins: -Nheko.paddingSmall
} }
} }
ElidedLabel { ElidedLabel {
visible: !communitySidebar.collapsed
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: communityItem.importantText
Layout.fillWidth: true Layout.fillWidth: true
color: communityItem.importantText
elideWidth: width elideWidth: width
fullText: model.displayName fullText: model.displayName
textFormat: Text.PlainText textFormat: Text.PlainText
visible: !communitySidebar.collapsed
} }
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
NotificationBubble { NotificationBubble {
notificationCount: model.unreadMessages Layout.alignment: Qt.AlignRight
hasLoudNotification: model.hasLoudNotification Layout.leftMargin: Nheko.paddingSmall
bubbleBackgroundColor: communityItem.bubbleBackground bubbleBackgroundColor: communityItem.bubbleBackground
bubbleTextColor: communityItem.bubbleText bubbleTextColor: communityItem.bubbleText
hasLoudNotification: model.hasLoudNotification
mayBeVisible: !communitySidebar.collapsed && !model.muted && Settings.spaceNotifications mayBeVisible: !communitySidebar.collapsed && !model.muted && Settings.spaceNotifications
Layout.alignment: Qt.AlignRight notificationCount: model.unreadMessages
Layout.leftMargin: Nheko.paddingSmall
} }
} }
background: Rectangle {
color: communityItem.backgroundColor
} }
} Platform.Menu {
id: communityContextMenu
property bool hidden
property bool muted
property string tagId
function show(id_, hidden_, muted_) {
tagId = id_;
hidden = hidden_;
muted = muted_;
open();
} }
background: Rectangle { Platform.MenuItem {
color: Nheko.theme.sidebarBackground checkable: true
checked: communityContextMenu.muted
text: qsTr("Do not show notification counts for this community or tag.")
onTriggered: Communities.toggleTagMute(communityContextMenu.tagId)
} }
Platform.MenuItem {
checkable: true
checked: communityContextMenu.hidden
text: qsTr("Hide rooms with this tag or from this community by default.")
onTriggered: Communities.toggleTagId(communityContextMenu.tagId)
}
}
}
} }

@ -11,124 +11,107 @@ import im.nheko 1.0
Control { Control {
id: popup id: popup
property alias currentIndex: listView.currentIndex
property string roomId
property string completerName
property var completer
property bool bottomToTop: true
property bool fullWidth: false
property bool centerRowContent: true
property int avatarHeight: 24 property int avatarHeight: 24
property int avatarWidth: 24 property int avatarWidth: 24
property bool bottomToTop: true
property bool centerRowContent: true
property var completer
property string completerName
property alias count: listView.count
property alias currentIndex: listView.currentIndex
property bool fullWidth: false
property string roomId
property int rowMargin: 0 property int rowMargin: 0
property int rowSpacing: Nheko.paddingSmall property int rowSpacing: Nheko.paddingSmall
property alias count: listView.count
signal completionClicked(string completion) signal completionClicked(string completion)
signal completionSelected(string id) signal completionSelected(string id)
function up() { function changeCompleter() {
if (bottomToTop) if (completerName) {
down_(); completer = TimelineManager.completerFor(completerName, completerName == "room" ? "" : (popup.roomId != "" ? popup.roomId : room.roomId));
completer.setSearchString("");
} else {
completer = undefined;
}
currentIndex = -1;
}
function currentCompletion() {
if (currentIndex > -1 && currentIndex < listView.count)
return completer.completionAt(currentIndex);
else else
up_(); return null;
} }
function down() { function down() {
if (bottomToTop) if (bottomToTop)
up_(); up_();
else else
down_(); down_();
} }
function up_() {
currentIndex = currentIndex - 1;
if (currentIndex == -2)
currentIndex = listView.count - 1;
}
function down_() { function down_() {
currentIndex = currentIndex + 1; currentIndex = currentIndex + 1;
if (currentIndex >= listView.count) if (currentIndex >= listView.count)
currentIndex = -1; currentIndex = -1;
}
function currentCompletion() {
if (currentIndex > -1 && currentIndex < listView.count)
return completer.completionAt(currentIndex);
else
return null;
} }
function finishCompletion() { function finishCompletion() {
if (popup.completerName == "room") if (popup.completerName == "room")
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid); popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid);
else if (popup.completerName == "user") else if (popup.completerName == "user")
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.userid); popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.userid);
} }
function up() {
function changeCompleter() { if (bottomToTop)
if (completerName) { down_();
completer = TimelineManager.completerFor(completerName, completerName == "room" ? "" : (popup.roomId != "" ? popup.roomId : room.roomId)); else
completer.setSearchString(""); up_();
} else {
completer = undefined;
} }
currentIndex = -1 function up_() {
currentIndex = currentIndex - 1;
if (currentIndex == -2)
currentIndex = listView.count - 1;
} }
onCompleterNameChanged: changeCompleter()
onRoomIdChanged: changeCompleter()
bottomPadding: 1 bottomPadding: 1
leftPadding: 1 leftPadding: 1
topPadding: 1
// Workaround palettes not inheriting for popups
palette: timelineRoot.palette
rightPadding: 1 rightPadding: 1
topPadding: 1
background: Rectangle {
border.color: palette.mid
color: palette.base
}
contentItem: ListView { contentItem: ListView {
id: listView id: listView
clip: true
displayMarginBeginning: height / 2
displayMarginEnd: height / 2
highlightFollowsCurrentItem: true
// If we have fewer than 7 items, just use the list view's content height. // If we have fewer than 7 items, just use the list view's content height.
// Otherwise, we want to show 7 items. Each item consists of row spacing between rows, row margins // Otherwise, we want to show 7 items. Each item consists of row spacing between rows, row margins
// on each side of a row, 1px of padding above the first item and below the last item, and nominally // on each side of a row, 1px of padding above the first item and below the last item, and nominally
// some kind of content height. avatarHeight is used for just about every delegate, so we're using // some kind of content height. avatarHeight is used for just about every delegate, so we're using
// that until we find something better. Put is all together and you have the formula below! // that until we find something better. Put is all together and you have the formula below!
implicitHeight: Math.min(contentHeight, 6 * rowSpacing + 7 * (popup.avatarHeight + 2 * rowMargin)) implicitHeight: Math.min(contentHeight, 6 * rowSpacing + 7 * (popup.avatarHeight + 2 * rowMargin))
clip: true
ScrollHelper {
flickable: parent
anchors.fill: parent
enabled: !Settings.mobileMode
}
Timer {
id: deadTimer
interval: 50
}
onContentYChanged: deadTimer.restart()
// Broken, see https://bugreports.qt.io/browse/QTBUG-102811 // Broken, see https://bugreports.qt.io/browse/QTBUG-102811
//reuseItems: true //reuseItems: true
implicitWidth: listView.contentItem.childrenRect.width implicitWidth: Math.max(listView.contentItem.childrenRect.width, 20)
model: completer model: completer
verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
spacing: rowSpacing
pixelAligned: true pixelAligned: true
highlightFollowsCurrentItem: true spacing: rowSpacing
verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
displayMarginBeginning: height / 2
displayMarginEnd: height / 2
delegate: Rectangle { delegate: Rectangle {
property variant modelData: model property variant modelData: model
ListView.delayRemove: true ListView.delayRemove: true
color: model.index == popup.currentIndex ? palette.highlight : palette.base
color: model.index == popup.currentIndex ? Nheko.colors.highlight : Nheko.colors.base height: (chooser.child?.implicitHeight ?? 0) + 2 * popup.rowMargin
height: chooser.child.implicitHeight + 2 * popup.rowMargin
implicitWidth: fullWidth ? ListView.view.width : chooser.child.implicitWidth + 4 implicitWidth: fullWidth ? ListView.view.width : chooser.child.implicitWidth + 4
MouseArea { MouseArea {
@ -136,7 +119,7 @@ Control {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onPositionChanged: if (!listView.moving && !deadTimer.running) popup.currentIndex = model.index
onClicked: { onClicked: {
popup.completionClicked(completer.completionAt(model.index)); popup.completionClicked(completer.completionAt(model.index));
if (popup.completerName == "room") if (popup.completerName == "room")
@ -144,18 +127,19 @@ Control {
else if (popup.completerName == "user") else if (popup.completerName == "user")
popup.completionSelected(model.userid); popup.completionSelected(model.userid);
} }
onPositionChanged: if (!listView.moving && !deadTimer.running)
popup.currentIndex = model.index
} }
Ripple { Ripple {
color: Qt.rgba(Nheko.colors.base.r, Nheko.colors.base.g, Nheko.colors.base.b, 0.5) color: Qt.rgba(palette.base.r, palette.base.g, palette.base.b, 0.5)
} }
DelegateChooser { DelegateChooser {
id: chooser id: chooser
roleValue: popup.completerName
anchors.fill: parent anchors.fill: parent
anchors.margins: popup.rowMargin anchors.margins: popup.rowMargin
enabled: false enabled: false
roleValue: popup.completerName
DelegateChoice { DelegateChoice {
roleValue: "user" roleValue: "user"
@ -167,28 +151,23 @@ Control {
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
height: popup.avatarHeight
width: popup.avatarWidth
displayName: model.displayName displayName: model.displayName
userid: model.userid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
enabled: false enabled: false
height: popup.avatarHeight
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
userid: model.userid
width: popup.avatarWidth
} }
Label { Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
text: model.displayName text: model.displayName
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
} }
Label { Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
text: "(" + model.userid + ")" text: "(" + model.userid + ")"
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.buttonText
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: "emoji" roleValue: "emoji"
@ -199,39 +178,33 @@ Control {
spacing: rowSpacing spacing: rowSpacing
Label { Label {
visible: !!model.unicode color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
text: model.unicode
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
font: Settings.emojiFont font: Settings.emojiFont
text: model.unicode
visible: !!model.unicode
} }
Avatar { Avatar {
visible: !model.unicode crop: false
height: popup.avatarHeight
width: popup.avatarWidth
displayName: model.shortcode displayName: model.shortcode
enabled: false
height: popup.avatarHeight
//userid: model.shortcode //userid: model.shortcode
url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/") url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/")
enabled: false visible: !model.unicode
crop: false width: popup.avatarWidth
} }
Label { Label {
Layout.leftMargin: Nheko.paddingSmall Layout.leftMargin: Nheko.paddingSmall
Layout.rightMargin: Nheko.paddingSmall Layout.rightMargin: Nheko.paddingSmall
color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
text: model.shortcode text: model.shortcode
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
} }
Label { Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
text: "(" + model.packname + ")" text: "(" + model.packname + ")"
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.buttonText
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: "command" roleValue: "command"
@ -242,20 +215,16 @@ Control {
spacing: rowSpacing spacing: rowSpacing
Label { Label {
text: model.name color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
font.bold: true font.bold: true
text: model.name
} }
Label { Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
text: model.description text: model.description
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.buttonText
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: "room" roleValue: "room"
@ -266,26 +235,22 @@ Control {
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
height: popup.avatarHeight
width: popup.avatarWidth
displayName: model.roomName displayName: model.roomName
enabled: false
height: popup.avatarHeight
roomid: model.roomid roomid: model.roomid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
enabled: false width: popup.avatarWidth
} }
Label { Label {
text: model.roomName color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
font.pixelSize: popup.avatarHeight * 0.5
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
font.italic: model.isTombstoned font.italic: model.isTombstoned
font.pixelSize: popup.avatarHeight * 0.5
text: model.roomName
textFormat: Text.RichText textFormat: Text.RichText
} }
} }
} }
DelegateChoice { DelegateChoice {
roleValue: "roomAliases" roleValue: "roomAliases"
@ -296,41 +261,38 @@ Control {
spacing: rowSpacing spacing: rowSpacing
Avatar { Avatar {
height: popup.avatarHeight
width: popup.avatarWidth
displayName: model.roomName displayName: model.roomName
enabled: false
height: popup.avatarHeight
roomid: model.roomid roomid: model.roomid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
enabled: false width: popup.avatarWidth
} }
Label { Label {
text: model.roomName color: model.index == popup.currentIndex ? palette.highlightedText : palette.text
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
font.italic: model.isTombstoned font.italic: model.isTombstoned
text: model.roomName
textFormat: Text.RichText textFormat: Text.RichText
} }
Label { Label {
color: model.index == popup.currentIndex ? palette.highlightedText : palette.buttonText
text: "(" + model.roomAlias + ")" text: "(" + model.roomAlias + ")"
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.buttonText
textFormat: Text.RichText textFormat: Text.RichText
} }
} }
} }
} }
} }
} onContentYChanged: deadTimer.restart()
Timer {
id: deadTimer
background: Rectangle { interval: 50
color: Nheko.colors.base }
border.color: Nheko.colors.mid
} }
onCompleterNameChanged: changeCompleter()
onRoomIdChanged: changeCompleter()
} }

@ -9,21 +9,20 @@ import im.nheko 1.0
Label { Label {
id: root id: root
property alias fullText: metrics.text
property alias elideWidth: metrics.elideWidth property alias elideWidth: metrics.elideWidth
property alias fullText: metrics.text
property int fullTextWidth: Math.ceil(metrics.advanceWidth) property int fullTextWidth: Math.ceil(metrics.advanceWidth)
color: Nheko.colors.text color: palette.text
text: (textFormat == Text.PlainText) ? metrics.elidedText : TimelineManager.escapeEmoji(metrics.elidedText)
maximumLineCount: 1
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1
text: (textFormat == Text.PlainText) ? metrics.elidedText : TimelineManager.escapeEmoji(metrics.elidedText)
textFormat: Text.PlainText textFormat: Text.PlainText
TextMetrics { TextMetrics {
id: metrics id: metrics
font.pointSize: root.font.pointSize
elide: Text.ElideRight elide: Text.ElideRight
font.pointSize: root.font.pointSize
} }
} }

@ -11,16 +11,10 @@ Image {
id: stateImg id: stateImg
property bool encrypted: false property bool encrypted: false
property int trust: Crypto.Unverified
property string unencryptedIcon: ":/icons/icons/ui/shield-filled-cross.svg"
property color unencryptedColor: Nheko.theme.error
property color unencryptedHoverColor: unencryptedColor
property bool hovered: ma.hovered property bool hovered: ma.hovered
property string sourceUrl: { property string sourceUrl: {
if (!encrypted) if (!encrypted)
return "image://colorimage/" + unencryptedIcon + "?"; return "image://colorimage/" + unencryptedIcon + "?";
switch (trust) { switch (trust) {
case Crypto.Verified: case Crypto.Verified:
return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?"; return "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?";
@ -32,18 +26,32 @@ Image {
return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?"; return "image://colorimage/:/icons/icons/ui/shield-filled-cross.svg?";
} }
} }
property int trust: Crypto.Unverified
property color unencryptedColor: Nheko.theme.error
property color unencryptedHoverColor: unencryptedColor
property string unencryptedIcon: ":/icons/icons/ui/shield-filled-cross.svg"
width: 16 ToolTip.text: {
if (!encrypted)
return qsTr("This message is not encrypted!");
switch (trust) {
case Crypto.Verified:
return qsTr("Encrypted by a verified device");
case Crypto.TOFU:
return qsTr("Encrypted by an unverified device, but you have trusted that user so far.");
default:
return qsTr("Encrypted by an unverified device or the key is from an untrusted source like the key backup.");
}
}
ToolTip.visible: stateImg.hovered
height: 16 height: 16
sourceSize.height: height
sourceSize.width: width
source: { source: {
if (encrypted) { if (encrypted) {
switch (trust) { switch (trust) {
case Crypto.Verified: case Crypto.Verified:
return sourceUrl + Nheko.theme.green; return sourceUrl + Nheko.theme.green;
case Crypto.TOFU: case Crypto.TOFU:
return sourceUrl + Nheko.colors.buttonText; return sourceUrl + palette.buttonText;
default: default:
return sourceUrl + Nheko.theme.error; return sourceUrl + Nheko.theme.error;
} }
@ -51,23 +59,12 @@ Image {
return sourceUrl + (stateImg.hovered ? unencryptedHoverColor : unencryptedColor); return sourceUrl + (stateImg.hovered ? unencryptedHoverColor : unencryptedColor);
} }
} }
ToolTip.visible: stateImg.hovered sourceSize.height: height
ToolTip.text: { sourceSize.width: width
if (!encrypted) width: 16
return qsTr("This message is not encrypted!");
switch (trust) {
case Crypto.Verified:
return qsTr("Encrypted by a verified device");
case Crypto.TOFU:
return qsTr("Encrypted by an unverified device, but you have trusted that user so far.");
default:
return qsTr("Encrypted by an unverified device or the key is from an untrusted source like the key backup.");
}
}
HoverHandler { HoverHandler {
id: ma id: ma
}
} }
}

@ -16,14 +16,24 @@ Popup {
mid = mid_in; mid = mid_in;
} }
x: Math.round(parent.width / 2 - width / 2) leftPadding: 10
y: Math.round(parent.height / 4)
modal: true modal: true
palette: Nheko.colors
// Workaround palettes not inheriting for popups
palette: timelineRoot.palette
parent: Overlay.overlay parent: Overlay.overlay
width: timelineRoot.width * 0.8
leftPadding: 10
rightPadding: 10 rightPadding: 10
width: timelineRoot.width * 0.8
x: Math.round(parent.width / 2 - width / 2)
y: Math.round(parent.height / 4)
Overlay.modal: Rectangle {
color: Qt.rgba(palette.window.r, palette.window.g, palette.window.b, 0.7)
}
background: Rectangle {
color: palette.window
}
onOpened: { onOpened: {
roomTextInput.forceActiveFocus(); roomTextInput.forceActiveFocus();
} }
@ -36,46 +46,40 @@ Popup {
Label { Label {
id: titleLabel id: titleLabel
text: qsTr("Forward Message")
font.bold: true
bottomPadding: 10 bottomPadding: 10
color: Nheko.colors.text color: palette.text
font.bold: true
text: qsTr("Forward Message")
} }
Reply { Reply {
id: replyPreview id: replyPreview
property var modelData: room ? room.getDump(mid, "") : { property var modelData: room ? room.getDump(mid, "") : {}
}
width: parent.width
userColor: TimelineManager.userColor(modelData.userId, Nheko.colors.window)
blurhash: modelData.blurhash ?? "" blurhash: modelData.blurhash ?? ""
body: modelData.body ?? "" body: modelData.body ?? ""
formattedBody: modelData.formattedBody ?? "" encryptionError: modelData.encryptionError ?? ""
eventId: modelData.eventId ?? "" eventId: modelData.eventId ?? ""
filename: modelData.filename ?? "" filename: modelData.filename ?? ""
filesize: modelData.filesize ?? "" filesize: modelData.filesize ?? ""
formattedBody: modelData.formattedBody ?? ""
isOnlyEmoji: modelData.isOnlyEmoji ?? false
originalWidth: modelData.originalWidth ?? 0
proportionalHeight: modelData.proportionalHeight ?? 1 proportionalHeight: modelData.proportionalHeight ?? 1
type: modelData.type ?? MtxEvent.UnknownMessage type: modelData.type ?? MtxEvent.UnknownMessage
typeString: modelData.typeString ?? "" typeString: modelData.typeString ?? ""
url: modelData.url ?? "" url: modelData.url ?? ""
originalWidth: modelData.originalWidth ?? 0 userColor: TimelineManager.userColor(modelData.userId, palette.window)
isOnlyEmoji: modelData.isOnlyEmoji ?? false
userId: modelData.userId ?? "" userId: modelData.userId ?? ""
userName: modelData.userName ?? "" userName: modelData.userName ?? ""
encryptionError: modelData.encryptionError ?? "" width: parent.width
} }
MatrixTextField { MatrixTextField {
id: roomTextInput id: roomTextInput
color: palette.text
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2 width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
color: Nheko.colors.text
onTextEdited: {
completerPopup.completer.searchString = text;
}
Keys.onPressed: { Keys.onPressed: {
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) { if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
event.accepted = true; event.accepted = true;
@ -91,43 +95,32 @@ Popup {
event.accepted = true; event.accepted = true;
} }
} }
onTextEdited: {
completerPopup.completer.searchString = text;
}
} }
Completer { Completer {
id: completerPopup id: completerPopup
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
completerName: "room"
fullWidth: true
centerRowContent: false
avatarHeight: 24 avatarHeight: 24
avatarWidth: 24 avatarWidth: 24
bottomToTop: false bottomToTop: false
centerRowContent: false
completerName: "room"
fullWidth: true
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
} }
} }
Connections { Connections {
function onCompletionSelected(id) { function onCompletionSelected(id) {
room.forwardMessage(messageContextMenu.eventId, id); room.forwardMessage(messageContextMenu.eventId, id);
forwardMessagePopup.close(); forwardMessagePopup.close();
} }
function onCountChanged() { function onCountChanged() {
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count)) if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
completerPopup.currentIndex = 0; completerPopup.currentIndex = 0;
} }
target: completerPopup target: completerPopup
} }
background: Rectangle {
color: Nheko.colors.window
}
Overlay.modal: Rectangle {
color: Qt.rgba(Nheko.colors.window.r, Nheko.colors.window.g, Nheko.colors.window.b, 0.7)
}
} }

@ -2,6 +2,7 @@
// //
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
pragma ComponentBehavior: Bound
import "./ui" import "./ui"
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -10,38 +11,35 @@ import im.nheko 1.0 // for cursor shape
AbstractButton { AbstractButton {
id: button id: button
property color buttonTextColor: palette.buttonText
property bool changeColorOnHover: true
property alias cursor: mouseArea.cursorShape property alias cursor: mouseArea.cursorShape
property color highlightColor: palette.highlight
property string image: undefined property string image: undefined
property color highlightColor: Nheko.colors.highlight
property color buttonTextColor: Nheko.colors.buttonText
property bool changeColorOnHover: true
property bool ripple: true property bool ripple: true
focusPolicy: Qt.NoFocus focusPolicy: Qt.NoFocus
width: 16
height: 16 height: 16
width: 16
Image { Image {
id: buttonImg id: buttonImg
// Workaround, can't get icon.source working for now... // Workaround, can't get icon.source working for now...
anchors.fill: parent anchors.fill: parent
source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : "" fillMode: Image.PreserveAspectFit
source: button.image != "" ? ("image://colorimage/" + button.image + "?" + ((button.hovered && button.changeColorOnHover) ? button.highlightColor : button.buttonTextColor)) : ""
sourceSize.height: button.height sourceSize.height: button.height
sourceSize.width: button.width sourceSize.width: button.width
fillMode: Image.PreserveAspectFit
} }
NhekoCursorShape {
CursorShape {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
Ripple { Ripple {
color: Qt.rgba(button.buttonTextColor.r, button.buttonTextColor.g, button.buttonTextColor.b, 0.5)
enabled: button.ripple enabled: button.ripple
color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5)
} }
} }

@ -11,28 +11,42 @@ TextEdit {
property alias cursorShape: cs.cursorShape property alias cursorShape: cs.cursorShape
textFormat: TextEdit.RichText //leftInset: 0
readOnly: true //bottomInset: 0
focus: false //rightInset: 0
wrapMode: Text.Wrap //topInset: 0
selectByMouse: !Settings.mobileMode //leftPadding: 0
//bottomPadding: 0
//rightPadding: 0
//topPadding: 0
//background: null
ToolTip.text: hoveredLink
ToolTip.visible: hoveredLink || false
// this always has to be enabled, otherwise you can't click links anymore! // this always has to be enabled, otherwise you can't click links anymore!
//enabled: selectByMouse //enabled: selectByMouse
color: Nheko.colors.text color: palette.text
onLinkActivated: Nheko.openLink(link) focus: false
ToolTip.visible: hoveredLink || false readOnly: true
ToolTip.text: hoveredLink selectByMouse: !Settings.mobileMode
textFormat: TextEdit.RichText
wrapMode: Text.Wrap
// Setting a tooltip delay makes the hover text empty .-. // Setting a tooltip delay makes the hover text empty .-.
//ToolTip.delay: Nheko.tooltipDelay //ToolTip.delay: Nheko.tooltipDelay
Component.onCompleted: { Component.onCompleted: {
TimelineManager.fixImageRendering(r.textDocument, r); TimelineManager.fixImageRendering(r.textDocument, r);
} }
onLinkActivated: Nheko.openLink(link)
CursorShape { //// propagate events up
//onPressAndHold: (event) => event.accepted = false
//onPressed: (event) => event.accepted = (event.button == Qt.LeftButton)
NhekoCursorShape {
id: cs id: cs
anchors.fill: parent anchors.fill: parent
cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
} }

@ -7,68 +7,63 @@ import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import im.nheko 1.0 import im.nheko 1.0
ColumnLayout { ColumnLayout {
id: c id: c
property color backgroundColor: Nheko.colors.base
property color backgroundColor: palette.base
property alias color: labelC.color property alias color: labelC.color
property alias textPadding: input.padding property alias echoMode: input.echoMode
property alias text: input.text property alias font: input.font
property var hasClear: false
property alias label: labelC.text property alias label: labelC.text
property alias placeholderText: input.placeholderText property alias placeholderText: input.placeholderText
property alias font: input.font
property alias echoMode: input.echoMode
property alias selectByMouse: input.selectByMouse property alias selectByMouse: input.selectByMouse
property var hasClear: false property alias text: input.text
property alias textPadding: input.padding
Timer {
id: timer
interval: 350
onTriggered: editingFinished()
}
onTextChanged: timer.restart()
signal textEdited
signal accepted signal accepted
signal editingFinished signal editingFinished
signal textEdited
function forceActiveFocus() {
input.forceActiveFocus();
}
function clear() { function clear() {
input.clear(); input.clear();
} }
function forceActiveFocus() {
input.forceActiveFocus();
}
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.visible: hover.hovered ToolTip.visible: hover.hovered
spacing: 0 spacing: 0
onTextChanged: timer.restart()
Timer {
id: timer
interval: 350
onTriggered: editingFinished()
}
Item { Item {
Layout.bottomMargin: Nheko.paddingSmall
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: labelC.contentHeight
Layout.margins: input.padding Layout.margins: input.padding
Layout.bottomMargin: Nheko.paddingSmall Layout.preferredHeight: labelC.contentHeight
visible: labelC.text visible: labelC.text
z: 1 z: 1
Label { Label {
id: labelC id: labelC
y: contentHeight + input.padding + Nheko.paddingSmall color: palette.text
enabled: false enabled: false
font.letterSpacing: input.font.pixelSize * 0.02
palette: Nheko.colors
color: Nheko.colors.text
font.pixelSize: input.font.pixelSize font.pixelSize: input.font.pixelSize
font.weight: Font.DemiBold font.weight: Font.DemiBold
font.letterSpacing: input.font.pixelSize * 0.02
width: parent.width
state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : "" state: labelC.text && (input.activeFocus == true || input.text) ? "focused" : ""
width: parent.width
y: contentHeight + input.padding + Nheko.paddingSmall
states: State { states: State {
name: "focused" name: "focused"
@ -77,51 +72,40 @@ ColumnLayout {
target: labelC target: labelC
y: 0 y: 0
} }
PropertyChanges { PropertyChanges {
target: input
opacity: 1 opacity: 1
target: input
} }
} }
transitions: Transition { transitions: Transition {
from: "" from: ""
to: "focused"
reversible: true reversible: true
to: "focused"
NumberAnimation { NumberAnimation {
target: labelC alwaysRunToEnd: true
properties: "y"
duration: 210 duration: 210
easing.type: Easing.InCubic easing.type: Easing.InCubic
alwaysRunToEnd: true properties: "y"
target: labelC
} }
NumberAnimation { NumberAnimation {
target: input alwaysRunToEnd: true
properties: "opacity"
duration: 210 duration: 210
easing.type: Easing.InCubic easing.type: Easing.InCubic
alwaysRunToEnd: true properties: "opacity"
target: input
} }
} }
} }
} }
TextField { TextField {
id: input id: input
Layout.fillWidth: true
palette: Nheko.colors Layout.fillWidth: true
color: labelC.color color: labelC.color
opacity: labelC.text ? 0 : 1
focus: true focus: true
opacity: labelC.text ? 0 : 1
onTextEdited: c.textEdited()
onAccepted: c.accepted()
onEditingFinished: c.editingFinished()
background: Rectangle { background: Rectangle {
id: backgroundRect id: backgroundRect
@ -129,44 +113,46 @@ ColumnLayout {
color: labelC.text ? "transparent" : backgroundColor color: labelC.text ? "transparent" : backgroundColor
} }
onAccepted: c.accepted()
onEditingFinished: c.editingFinished()
onTextEdited: c.textEdited()
ImageButton { ImageButton {
id: clearText id: clearText
focusPolicy: Qt.NoFocus
hoverEnabled: true
image: ":/icons/icons/ui/round-remove-button.svg"
visible: c.hasClear && searchField.text !== '' visible: c.hasClear && searchField.text !== ''
image: ":/icons/icons/ui/round-remove-button.svg"
focusPolicy: Qt.NoFocus
onClicked: { onClicked: {
searchField.clear() searchField.clear();
topBar.searchString = ""; topBar.searchString = "";
} }
hoverEnabled: true
anchors { anchors {
top: parent.top
bottom: parent.bottom bottom: parent.bottom
right: parent.right right: parent.right
rightMargin: Nheko.paddingSmall rightMargin: Nheko.paddingSmall
top: parent.top
} }
} }
} }
Rectangle { Rectangle {
id: blueBar id: blueBar
Layout.fillWidth: true Layout.fillWidth: true
color: palette.highlight
color: Nheko.colors.highlight
height: 1 height: 1
Rectangle { Rectangle {
id: blackBar id: blackBar
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
color: palette.text
height: parent.height * 2 height: parent.height * 2
width: 0 width: 0
color: Nheko.colors.text
states: State { states: State {
name: "focused" name: "focused"
@ -176,31 +162,25 @@ ColumnLayout {
target: blackBar target: blackBar
width: blueBar.width width: blueBar.width
} }
} }
transitions: Transition { transitions: Transition {
from: "" from: ""
to: "focused"
reversible: true reversible: true
to: "focused"
NumberAnimation { NumberAnimation {
target: blackBar alwaysRunToEnd: true
properties: "width"
duration: 310 duration: 310
easing.type: Easing.InCubic easing.type: Easing.InCubic
alwaysRunToEnd: true properties: "width"
target: blackBar
} }
} }
} }
} }
HoverHandler { HoverHandler {
id: hover id: hover
enabled: c.ToolTip.text enabled: c.ToolTip.text
} }
} }

@ -14,60 +14,54 @@ import im.nheko 1.0
Rectangle { Rectangle {
id: inputBar id: inputBar
property bool showAllButtons: width > 450 || (messageInput.length == 0 && !messageInput.inputMethodComposing)
readonly property string text: messageInput.text readonly property string text: messageInput.text
color: Nheko.colors.window
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: row.implicitHeight
Layout.minimumHeight: 40 Layout.minimumHeight: 40
property bool showAllButtons: width > 450 || (messageInput.length == 0 && !messageInput.inputMethodComposing) Layout.preferredHeight: row.implicitHeight
color: palette.window
Component { Component {
id: placeCallDialog id: placeCallDialog
PlaceCall { PlaceCall {
} }
} }
Component { Component {
id: screenShareDialog id: screenShareDialog
ScreenShare { ScreenShare {
} }
} }
RowLayout { RowLayout {
id: row id: row
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
ImageButton { ImageButton {
visible: CallManager.callsSupported && showAllButtons
opacity: (CallManager.haveCallInvite || CallManager.isOnCallOnOtherDevice) ? 0.3 : 1
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
hoverEnabled: true Layout.margins: 8
width: 22 ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : (CallManager.isOnCallOnOtherDevice ? qsTr("Already on a call") : qsTr("Place a call"))
ToolTip.visible: hovered
height: 22 height: 22
hoverEnabled: true
image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg" image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.svg" : ":/icons/icons/ui/place-call.svg"
ToolTip.visible: hovered opacity: (CallManager.haveCallInvite || CallManager.isOnCallOnOtherDevice) ? 0.3 : 1
ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : (CallManager.isOnCallOnOtherDevice ? qsTr("Already on a call") : qsTr("Place a call")) visible: CallManager.callsSupported && showAllButtons
Layout.margins: 8 width: 22
onClicked: { onClicked: {
if (room) { if (room) {
if (CallManager.haveCallInvite) { if (CallManager.haveCallInvite) {
return; return;
} else if (CallManager.isOnCall) { } else if (CallManager.isOnCall) {
CallManager.hangUp(); CallManager.hangUp();
} } else if (CallManager.isOnCallOnOtherDevice) {
else if(CallManager.isOnCallOnOtherDevice) {
return; return;
} } else {
else {
var dialog = placeCallDialog.createObject(timelineRoot); var dialog = placeCallDialog.createObject(timelineRoot);
dialog.open(); dialog.open();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
@ -75,22 +69,22 @@ Rectangle {
} }
} }
} }
ImageButton { ImageButton {
visible: showAllButtons
Layout.alignment: Qt.AlignBottom Layout.alignment: Qt.AlignBottom
hoverEnabled: true Layout.margins: 8
width: 22 ToolTip.text: qsTr("Send a file")
ToolTip.visible: hovered
height: 22 height: 22
hoverEnabled: true
image: ":/icons/icons/ui/attach.svg" image: ":/icons/icons/ui/attach.svg"
Layout.margins: 8 visible: showAllButtons
width: 22
onClicked: room.input.openFileSelection() onClicked: room.input.openFileSelection()
ToolTip.visible: hovered
ToolTip.text: qsTr("Send a file")
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Nheko.colors.window color: palette.window
visible: room && room.input.uploading visible: room && room.input.uploading
Spinner { Spinner {
@ -98,112 +92,67 @@ Rectangle {
height: parent.height / 2 height: parent.height / 2
running: parent.visible running: parent.visible
} }
} }
} }
ScrollView { ScrollView {
id: textInput id: textInput
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
Layout.maximumHeight: Window.height / 4 Layout.maximumHeight: Window.height / 4
Layout.minimumHeight: fontMetrics.lineSpacing Layout.minimumHeight: fontMetrics.lineSpacing
Layout.preferredHeight: contentHeight Layout.preferredHeight: contentHeight
Layout.fillWidth: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
contentWidth: availableWidth contentWidth: availableWidth
TextArea { TextArea {
id: messageInput id: messageInput
property int completerTriggeredAt: 0 property int completerTriggeredAt: 0
property string lastChar
function insertCompletion(completion) { function insertCompletion(completion) {
messageInput.remove(completerTriggeredAt, cursorPosition); messageInput.remove(completerTriggeredAt, cursorPosition);
messageInput.insert(cursorPosition, completion); messageInput.insert(cursorPosition, completion);
} }
function openCompleter(pos, type) { function openCompleter(pos, type) {
if (popup.opened) return; if (popup.opened)
return;
completerTriggeredAt = pos; completerTriggeredAt = pos;
completer.completerName = type; completer.completerName = type;
popup.open(); popup.open();
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText); completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText);
} }
function positionCursorAtEnd() { function positionCursorAtEnd() {
cursorPosition = messageInput.length; cursorPosition = messageInput.length;
} }
function positionCursorAtStart() { function positionCursorAtStart() {
cursorPosition = 0; cursorPosition = 0;
} }
selectByMouse: true background: null
bottomPadding: 8
color: palette.text
focus: true
leftPadding: inputBar.showAllButtons ? 0 : 8
padding: 0
placeholderText: qsTr("Write a message...") placeholderText: qsTr("Write a message...")
placeholderTextColor: Nheko.colors.buttonText placeholderTextColor: palette.buttonText
color: Nheko.colors.text selectByMouse: true
width: textInput.width topPadding: 8
verticalAlignment: TextEdit.AlignVCenter verticalAlignment: TextEdit.AlignVCenter
width: textInput.width
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
padding: 0
topPadding: 8
bottomPadding: 8
leftPadding: inputBar.showAllButtons? 0 : 8
focus: true
property string lastChar
onTextChanged: {
if (room)
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
forceActiveFocus();
if (cursorPosition > 0)
lastChar = text.charAt(cursorPosition-1)
else
lastChar = ''
if (lastChar == '@') {
messageInput.openCompleter(selectionStart-1, "user");
} else if (lastChar == ':') {
messageInput.openCompleter(selectionStart-1, "emoji");
} else if (lastChar == '#') {
messageInput.openCompleter(selectionStart-1, "roomAliases");
} else if (lastChar == "/" && cursorPosition == 1) {
messageInput.openCompleter(selectionStart-1, "command");
}
}
onCursorPositionChanged: {
if (!room)
return ;
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
if (popup.opened && cursorPosition <= completerTriggeredAt)
popup.close();
if (popup.opened) Keys.onPressed: event => {
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)+messageInput.preeditText);
}
onPreeditTextChanged: {
if (popup.opened)
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)+messageInput.preeditText);
}
onSelectionStartChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
onSelectionEndChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
// Ensure that we get escape key press events first.
Keys.onShortcutOverride: event.accepted = (popup.opened && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter || event.key === Qt.Key_Space))
Keys.onPressed: {
if (event.matches(StandardKey.Paste)) { if (event.matches(StandardKey.Paste)) {
event.accepted = room.input.tryPasteAttachment(false); event.accepted = room.input.tryPasteAttachment(false);
} else if (event.key == Qt.Key_Space) { } else if (event.key == Qt.Key_Space) {
// close popup if user enters space after colon // close popup if user enters space after colon
if (cursorPosition == completerTriggeredAt + 1) if (cursorPosition == completerTriggeredAt + 1)
popup.close(); popup.close();
if (popup.opened && completer.count <= 0) if (popup.opened && completer.count <= 0)
popup.close(); popup.close();
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) { } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_U) {
messageInput.clear(); messageInput.clear();
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) { } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) {
@ -218,8 +167,8 @@ Rectangle {
completer.completerName = ""; completer.completerName = "";
popup.close(); popup.close();
} else if (event.matches(StandardKey.InsertLineSeparator)) { } else if (event.matches(StandardKey.InsertLineSeparator)) {
if (popup.opened) popup.close(); if (popup.opened)
popup.close();
if (Settings.invertEnterKey && (!Qt.inputMethod.visible || Qt.platform.os === "windows")) { if (Settings.invertEnterKey && (!Qt.inputMethod.visible || Qt.platform.os === "windows")) {
room.input.send(); room.input.send();
event.accepted = true; event.accepted = true;
@ -312,21 +261,53 @@ Rectangle {
} }
} }
} }
background: null // Ensure that we get escape key press events first.
Keys.onShortcutOverride: event => event.accepted = (popup.opened && (event.key === Qt.Key_Escape || event.key === Qt.Key_Tab || event.key === Qt.Key_Enter || event.key === Qt.Key_Space))
onCursorPositionChanged: {
if (!room)
return;
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
if (popup.opened && cursorPosition <= completerTriggeredAt)
popup.close();
if (popup.opened)
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText);
}
onPreeditTextChanged: {
if (popup.opened)
completer.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition) + messageInput.preeditText);
}
onSelectionEndChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
onSelectionStartChanged: room.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
onTextChanged: {
if (room)
room.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
forceActiveFocus();
if (cursorPosition > 0)
lastChar = text.charAt(cursorPosition - 1);
else
lastChar = '';
if (lastChar == '@') {
messageInput.openCompleter(selectionStart - 1, "user");
} else if (lastChar == ':') {
messageInput.openCompleter(selectionStart - 1, "emoji");
} else if (lastChar == '#') {
messageInput.openCompleter(selectionStart - 1, "roomAliases");
} else if (lastChar == "/" && cursorPosition == 1) {
messageInput.openCompleter(selectionStart - 1, "command");
}
}
Connections { Connections {
function onRoomChanged() { function onRoomChanged() {
messageInput.clear(); messageInput.clear();
if (room) if (room)
messageInput.append(room.input.text); messageInput.append(room.input.text);
completer.completerName = ""; completer.completerName = "";
messageInput.forceActiveFocus(); messageInput.forceActiveFocus();
} }
target: timelineView target: timelineView
} }
Connections { Connections {
function onCompletionClicked(completion) { function onCompletionClicked(completion) {
messageInput.insertCompletion(completion); messageInput.insertCompletion(completion);
@ -334,43 +315,39 @@ Rectangle {
target: completer target: completer
} }
Popup { Popup {
id: popup id: popup
x: messageInput.positionToRectangle(messageInput.completerTriggeredAt).x
y: messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height
background: null background: null
padding: 0 padding: 0
x: messageInput.positionToRectangle(messageInput.completerTriggeredAt).x
Completer { y: messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height
anchors.fill: parent
id: completer
rowMargin: 2
rowSpacing: 0
}
enter: Transition { enter: Transition {
NumberAnimation { NumberAnimation {
property: "opacity" duration: 100
from: 0 from: 0
property: "opacity"
to: 1 to: 1
duration: 100
} }
} }
exit: Transition { exit: Transition {
NumberAnimation { NumberAnimation {
property: "opacity" duration: 100
from: 1 from: 1
property: "opacity"
to: 0 to: 0
duration: 100
}
} }
} }
Completer {
id: completer
anchors.fill: parent
rowMargin: 2
rowSpacing: 0
}
}
Connections { Connections {
function onTextChanged(newText) { function onTextChanged(newText) {
messageInput.text = newText; messageInput.text = newText;
@ -380,16 +357,13 @@ Rectangle {
ignoreUnknownSignals: true ignoreUnknownSignals: true
target: room ? room.input : null target: room ? room.input : null
} }
Connections { Connections {
function onReplyChanged() { function onEditChanged() {
messageInput.forceActiveFocus(); messageInput.forceActiveFocus();
} }
function onReplyChanged() {
function onEditChanged() {
messageInput.forceActiveFocus(); messageInput.forceActiveFocus();
} }
function onThreadChanged() { function onThreadChanged() {
messageInput.forceActiveFocus(); messageInput.forceActiveFocus();
} }
@ -397,7 +371,6 @@ Rectangle {
ignoreUnknownSignals: true ignoreUnknownSignals: true
target: room target: room
} }
Connections { Connections {
function onFocusInput() { function onFocusInput() {
messageInput.forceActiveFocus(); messageInput.forceActiveFocus();
@ -405,31 +378,29 @@ Rectangle {
target: TimelineManager target: TimelineManager
} }
MouseArea { MouseArea {
acceptedButtons: Qt.MiddleButton
// workaround for wrong cursor shape on some platforms // workaround for wrong cursor shape on some platforms
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.MiddleButton
cursorShape: Qt.IBeamCursor cursorShape: Qt.IBeamCursor
onPressed: (mouse) => mouse.accepted = room.input.tryPasteAttachment(true)
}
onPressed: mouse => mouse.accepted = room.input.tryPasteAttachment(true)
}
} }
} }
ImageButton { ImageButton {
id: stickerButton id: stickerButton
visible: showAllButtons
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
hoverEnabled: true ToolTip.text: qsTr("Stickers")
width: 22 ToolTip.visible: hovered
height: 22 height: 22
hoverEnabled: true
image: ":/icons/icons/ui/sticky-note-solid.svg" image: ":/icons/icons/ui/sticky-note-solid.svg"
ToolTip.visible: hovered visible: showAllButtons
ToolTip.text: qsTr("Stickers") width: 22
onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function (row) { onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function (row) {
room.input.sticker(row); room.input.sticker(row);
TimelineManager.focusMessageInput(); TimelineManager.focusMessageInput();
@ -438,23 +409,21 @@ Rectangle {
StickerPicker { StickerPicker {
id: stickerPopup id: stickerPopup
colors: Nheko.colors
emoji: false emoji: false
} }
} }
ImageButton { ImageButton {
id: emojiButton id: emojiButton
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
hoverEnabled: true ToolTip.text: qsTr("Emoji")
width: 22 ToolTip.visible: hovered
height: 22 height: 22
hoverEnabled: true
image: ":/icons/icons/ui/smile.svg" image: ":/icons/icons/ui/smile.svg"
ToolTip.visible: hovered width: 22
ToolTip.text: qsTr("Emoji")
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, room.roomId, function (plaintext, markdown) { onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, room.roomId, function (plaintext, markdown) {
messageInput.insert(messageInput.cursorPosition, markdown); messageInput.insert(messageInput.cursorPosition, markdown);
TimelineManager.focusMessageInput(); TimelineManager.focusMessageInput();
@ -463,33 +432,28 @@ Rectangle {
StickerPicker { StickerPicker {
id: emojiPopup id: emojiPopup
colors: Nheko.colors
emoji: true emoji: true
} }
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8 Layout.margins: 8
hoverEnabled: true
width: 22
height: 22
image: ":/icons/icons/ui/send.svg"
Layout.rightMargin: 8 Layout.rightMargin: 8
ToolTip.visible: hovered
ToolTip.text: qsTr("Send") ToolTip.text: qsTr("Send")
ToolTip.visible: hovered
height: 22
hoverEnabled: true
image: ":/icons/icons/ui/send.svg"
width: 22
onClicked: { onClicked: {
room.input.send(); room.input.send();
} }
} }
} }
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
visible: room ? (!room.permissions.canSend(MtxEvent.TextMessage)) : false
text: qsTr("You don't have permission to send messages in this room") text: qsTr("You don't have permission to send messages in this room")
color: Nheko.colors.text visible: room ? (!room.permissions.canSend(MtxEvent.TextMessage)) : false
} }
} }

@ -10,38 +10,35 @@ import im.nheko 1.0
Rectangle { Rectangle {
id: warningRoot id: warningRoot
required property string text
property color bubbleColor: Nheko.theme.error property color bubbleColor: Nheko.theme.error
required property string text
implicitHeight: visible ? warningDisplay.implicitHeight + 4 * Nheko.paddingSmall : 0
height: implicitHeight
Layout.fillWidth: true Layout.fillWidth: true
color: Nheko.colors.window // required to hide the timeline behind this warning color: palette.window // required to hide the timeline behind this warning
height: implicitHeight
implicitHeight: visible ? warningDisplay.implicitHeight + 4 * Nheko.paddingSmall : 0
Rectangle { Rectangle {
id: warningRect id: warningRect
visible: warningRoot.visible anchors.fill: parent
anchors.margins: visible ? Nheko.paddingSmall : 0
border.color: bubbleColor
border.width: 1
// TODO: Qt.alpha() would make more sense but it wasn't working... // TODO: Qt.alpha() would make more sense but it wasn't working...
color: Qt.rgba(bubbleColor.r, bubbleColor.g, bubbleColor.b, 0.3) color: Qt.rgba(bubbleColor.r, bubbleColor.g, bubbleColor.b, 0.3)
border.width: 1
border.color: bubbleColor
radius: 3 radius: 3
anchors.fill: parent visible: warningRoot.visible
anchors.margins: visible ? Nheko.paddingSmall : 0
z: 3 z: 3
Label { Label {
id: warningDisplay id: warningDisplay
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Nheko.paddingSmall anchors.margins: Nheko.paddingSmall
color: Nheko.colors.text anchors.verticalCenter: parent.verticalCenter
text: warningRoot.text text: warningRoot.text
textFormat: Text.PlainText textFormat: Text.PlainText
} }
} }
} }

File diff suppressed because it is too large Load Diff

@ -2,18 +2,17 @@
// //
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtGraphicalEffects 1.0 import QtQuick
import QtQuick 2.12 import QtQuick.Window
import QtQuick.Window 2.2 import im.nheko
import im.nheko 1.0 import QtQuick.Effects
Item { Item {
id: privacyScreen id: privacyScreen
readonly property bool active: Settings.privacyScreen && screenSaver.state === "Visible" readonly property bool active: Settings.privacyScreen && screenSaver.state === "Visible"
property var timelineRoot
property int screenTimeout property int screenTimeout
property var timelineRoot
required property var windowTarget required property var windowTarget
Connections { Connections {
@ -24,29 +23,28 @@ Item {
} else { } else {
if (timelineRoot.visible) if (timelineRoot.visible)
screenSaverTimer.start(); screenSaverTimer.start();
} }
} }
target: windowTarget target: windowTarget
} }
Timer { Timer {
id: screenSaverTimer id: screenSaverTimer
interval: screenTimeout * 1000 interval: screenTimeout * 1000
running: !windowTarget.active running: !windowTarget.active
onTriggered: { onTriggered: {
screenSaver.state = "Visible"; screenSaver.state = "Visible";
} }
} }
Item { Item {
id: screenSaver id: screenSaver
state: "Invisible"
anchors.fill: parent anchors.fill: parent
state: "Invisible"
visible: false visible: false
states: [ states: [
State { State {
name: "Visible" name: "Visible"
@ -55,20 +53,18 @@ Item {
target: screenSaver target: screenSaver
visible: true visible: true
} }
PropertyChanges { PropertyChanges {
target: screenSaver
opacity: 1 opacity: 1
target: screenSaver
} }
}, },
State { State {
name: "Invisible" name: "Invisible"
PropertyChanges { PropertyChanges {
target: screenSaver
opacity: 0 opacity: 0
target: screenSaver
} }
PropertyChanges { PropertyChanges {
target: screenSaver target: screenSaver
visible: false visible: false
@ -78,36 +74,33 @@ Item {
transitions: [ transitions: [
Transition { Transition {
from: "Invisible" from: "Invisible"
to: "Visible"
reversible: true reversible: true
to: "Visible"
SequentialAnimation { SequentialAnimation {
NumberAnimation { NumberAnimation {
target: screenSaver
property: "visible"
duration: 0 duration: 0
property: "visible"
target: screenSaver
} }
NumberAnimation { NumberAnimation {
target: screenSaver
property: "opacity"
duration: 300 duration: 300
easing.type: Easing.Linear easing.type: Easing.Linear
property: "opacity"
target: screenSaver
} }
} }
} }
] ]
FastBlur { MultiEffect {
id: blur id: blur
anchors.fill: parent anchors.fill: parent
blur: 1.0
blurEnabled: true
blurMax: 32
source: timelineRoot source: timelineRoot
radius: 50
} }
} }
} }

@ -11,20 +11,27 @@ Popup {
id: quickSwitcher id: quickSwitcher
property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4) property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
property int textMargin: Nheko.paddingSmall
background: null background: null
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
modal: true
// Workaround palettes not inheriting for popups
palette: timelineRoot.palette
parent: Overlay.overlay
width: Math.min(Math.max(Math.round(parent.width / 2), 450), parent.width) // limiting width to parent.width/2 can be a bit narrow width: Math.min(Math.max(Math.round(parent.width / 2), 450), parent.width) // limiting width to parent.width/2 can be a bit narrow
x: Math.round(parent.width / 2 - contentWidth / 2) x: Math.round(parent.width / 2 - contentWidth / 2)
y: Math.round(parent.height / 4) y: Math.round(parent.height / 4)
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside Overlay.modal: Rectangle {
parent: Overlay.overlay color: "#aa1E1E1E"
palette: Nheko.colors }
onClosed: TimelineManager.focusMessageInput()
onOpened: { onOpened: {
roomTextInput.forceActiveFocus(); roomTextInput.forceActiveFocus();
} }
onClosed: TimelineManager.focusMessageInput()
property int textMargin: Nheko.paddingSmall
Column { Column {
anchors.fill: parent anchors.fill: parent
@ -33,13 +40,11 @@ Popup {
MatrixTextField { MatrixTextField {
id: roomTextInput id: roomTextInput
width: parent.width color: palette.text
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6) font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
color: Nheko.colors.text width: parent.width
onTextEdited: {
completerPopup.completer.searchString = text; Keys.onPressed: event => {
}
Keys.onPressed: {
if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) { if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
event.accepted = true; event.accepted = true;
completerPopup.up(); completerPopup.up();
@ -54,41 +59,35 @@ Popup {
event.accepted = true; event.accepted = true;
} }
} }
onTextEdited: {
completerPopup.completer.searchString = text;
}
} }
Completer { Completer {
id: completerPopup id: completerPopup
visible: roomTextInput.text.length > 0
width: parent.width
completerName: "room"
bottomToTop: false
fullWidth: true
avatarHeight: quickSwitcher.textHeight avatarHeight: quickSwitcher.textHeight
avatarWidth: quickSwitcher.textHeight avatarWidth: quickSwitcher.textHeight
bottomToTop: false
centerRowContent: false centerRowContent: false
completerName: "room"
fullWidth: true
rowMargin: Math.round(quickSwitcher.textMargin / 2) rowMargin: Math.round(quickSwitcher.textMargin / 2)
rowSpacing: quickSwitcher.textMargin rowSpacing: quickSwitcher.textMargin
visible: roomTextInput.text.length > 0
width: parent.width
} }
} }
Connections { Connections {
function onCompletionSelected(id) { function onCompletionSelected(id) {
Rooms.setCurrentRoom(id); Rooms.setCurrentRoom(id);
quickSwitcher.close(); quickSwitcher.close();
} }
function onCountChanged() { function onCountChanged() {
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count)) if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
completerPopup.currentIndex = 0; completerPopup.currentIndex = 0;
} }
target: completerPopup target: completerPopup
} }
Overlay.modal: Rectangle {
color: "#aa1E1E1E"
}
} }

@ -11,10 +11,11 @@ import im.nheko 1.0
Flow { Flow {
id: reactionFlow id: reactionFlow
// lower-contrast colors to avoid distracting from text & to enhance hover effect
property color gentleHighlight: Qt.hsla(Nheko.colors.highlight.hslHue, Nheko.colors.highlight.hslSaturation, Nheko.colors.highlight.hslLightness, 0.8)
property color gentleText: Qt.hsla(Nheko.colors.text.hslHue, Nheko.colors.text.hslSaturation, Nheko.colors.text.hslLightness, 0.6)
property string eventId property string eventId
// lower-contrast colors to avoid distracting from text & to enhance hover effect
property color gentleHighlight: Qt.hsla(palette.highlight.hslHue, palette.highlight.hslSaturation, palette.highlight.hslLightness, 0.8)
property color gentleText: Qt.hsla(palette.text.hslHue, palette.text.hslSaturation, palette.text.hslLightness, 0.6)
property alias reactions: repeater.model property alias reactions: repeater.model
spacing: 4 spacing: 4
@ -25,40 +26,39 @@ Flow {
delegate: AbstractButton { delegate: AbstractButton {
id: reaction id: reaction
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
onClicked: { ToolTip.visible: hovered
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent); hoverEnabled: true
room.input.reaction(reactionFlow.eventId, modelData.key);
}
Component.onCompleted: {
ToolTip.text = Qt.binding(function() {
if (textMetrics.elidedText === textMetrics.text) {
return modelData.users;
}
return modelData.displayKey + "\n" + modelData.users;
})
}
leftPadding: textMetrics.height / 2 leftPadding: textMetrics.height / 2
rightPadding: textMetrics.height / 2 rightPadding: textMetrics.height / 2
background: Rectangle {
anchors.centerIn: parent
border.color: reaction.hovered ? palette.text : gentleText
border.width: 1
color: reaction.hovered ? palette.highlight : (modelData.selfReactedEvent !== '' ? gentleHighlight : palette.window)
implicitHeight: reaction.implicitHeight
implicitWidth: reaction.implicitWidth
radius: reaction.height / 2
}
contentItem: Row { contentItem: Row {
spacing: textMetrics.height / 4 spacing: textMetrics.height / 4
TextMetrics { TextMetrics {
id: textMetrics id: textMetrics
font.family: Settings.emojiFont
elide: Text.ElideRight elide: Text.ElideRight
elideWidth: 150 elideWidth: 150
font.family: Settings.emojiFont
text: modelData.displayKey text: modelData.displayKey
} }
Text { Text {
id: reactionText id: reactionText
anchors.baseline: reactionCounter.baseline anchors.baseline: reactionCounter.baseline
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? palette.highlightedText : palette.text
font.family: Settings.emojiFont
maximumLineCount: 1
text: { text: {
// When an emoji font is selected that doesn't have , it is dropped from elidedText. So we add it back. // When an emoji font is selected that doesn't have , it is dropped from elidedText. So we add it back.
if (textMetrics.elidedText !== modelData.displayKey) { if (textMetrics.elidedText !== modelData.displayKey) {
@ -68,51 +68,45 @@ Flow {
} }
return textMetrics.elidedText; return textMetrics.elidedText;
} }
font.family: Settings.emojiFont
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? Nheko.colors.highlightedText: Nheko.colors.text
maximumLineCount: 1
visible: !modelData.key.startsWith("mxc://") visible: !modelData.key.startsWith("mxc://")
} }
Image { Image {
anchors.verticalCenter: divider.verticalCenter anchors.verticalCenter: divider.verticalCenter
fillMode: Image.PreserveAspectFit
height: textMetrics.height height: textMetrics.height
width: textMetrics.height
source: modelData.key.startsWith("mxc://") ? (modelData.key.replace("mxc://", "image://MxcImage/") + "?scale") : "" source: modelData.key.startsWith("mxc://") ? (modelData.key.replace("mxc://", "image://MxcImage/") + "?scale") : ""
visible: modelData.key.startsWith("mxc://") visible: modelData.key.startsWith("mxc://")
fillMode: Image.PreserveAspectFit width: textMetrics.height
} }
Rectangle { Rectangle {
id: divider id: divider
color: reaction.hovered ? palette.text : gentleText
height: Math.floor(reactionCounter.implicitHeight * 1.4) height: Math.floor(reactionCounter.implicitHeight * 1.4)
width: 1 width: 1
color: reaction.hovered ? Nheko.colors.text: gentleText
} }
Text { Text {
id: reactionCounter id: reactionCounter
anchors.verticalCenter: divider.verticalCenter anchors.verticalCenter: divider.verticalCenter
text: modelData.count color: (reaction.hovered || modelData.selfReactedEvent !== '') ? palette.highlightedText : palette.windowText
font: reaction.font font: reaction.font
color: (reaction.hovered || modelData.selfReactedEvent !== '') ? Nheko.colors.highlightedText: Nheko.colors.windowText text: modelData.count
} }
} }
background: Rectangle { Component.onCompleted: {
anchors.centerIn: parent ToolTip.text = Qt.binding(function () {
implicitWidth: reaction.implicitWidth if (textMetrics.elidedText === textMetrics.text) {
implicitHeight: reaction.implicitHeight return modelData.users;
border.color: reaction.hovered ? Nheko.colors.text: gentleText }
color: reaction.hovered ? Nheko.colors.highlight : (modelData.selfReactedEvent !== '' ? gentleHighlight : Nheko.colors.window) return modelData.displayKey + "\n" + modelData.users;
border.width: 1 });
radius: reaction.height / 2 }
onClicked: {
console.debug("Picked " + modelData.key + "in response to " + reactionFlow.eventId + ". selfReactedEvent: " + modelData.selfReactedEvent);
room.input.reaction(reactionFlow.eventId, modelData.key);
} }
} }
} }
} }

@ -12,91 +12,89 @@ Rectangle {
id: replyPopup id: replyPopup
Layout.fillWidth: true Layout.fillWidth: true
visible: room && (room.reply || room.edit || room.thread) color: palette.window
// Height of child, plus margins, plus border // Height of child, plus margins, plus border
implicitHeight: (room && room.reply ? replyPreview.height : Math.max(closeEditButton.height, closeThreadButton.height)) + Nheko.paddingSmall implicitHeight: (room && room.reply ? replyPreview.height : Math.max(closeEditButton.height, closeThreadButton.height)) + Nheko.paddingSmall
color: Nheko.colors.window visible: room && (room.reply || room.edit || room.thread)
z: 3 z: 3
Reply { Reply {
id: replyPreview id: replyPreview
property var modelData: room ? room.getDump(room.reply, room.id) : { property var modelData: room ? room.getDump(room.reply, room.id) : {}
}
visible: room && room.reply
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: replyPopup.width < 450 ? Nheko.paddingSmall : (CallManager.callsSupported ? 2 * (22 + 16) : 1 * (22 + 16)) anchors.leftMargin: replyPopup.width < 450 ? Nheko.paddingSmall : (CallManager.callsSupported ? 2 * (22 + 16) : 1 * (22 + 16))
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: replyPopup.width < 450 ? 2 * (22 + 16) : 3 * (22 + 16) anchors.rightMargin: replyPopup.width < 450 ? 2 * (22 + 16) : 3 * (22 + 16)
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: Nheko.paddingSmall anchors.topMargin: Nheko.paddingSmall
userColor: TimelineManager.userColor(modelData.userId, Nheko.colors.window)
blurhash: modelData.blurhash ?? "" blurhash: modelData.blurhash ?? ""
body: modelData.body ?? "" body: modelData.body ?? ""
formattedBody: modelData.formattedBody ?? "" encryptionError: modelData.encryptionError ?? 0
eventId: modelData.eventId ?? "" eventId: modelData.eventId ?? ""
filename: modelData.filename ?? "" filename: modelData.filename ?? ""
filesize: modelData.filesize ?? "" filesize: modelData.filesize ?? ""
formattedBody: modelData.formattedBody ?? ""
isOnlyEmoji: modelData.isOnlyEmoji ?? false
originalWidth: modelData.originalWidth ?? 0
proportionalHeight: modelData.proportionalHeight ?? 1 proportionalHeight: modelData.proportionalHeight ?? 1
type: modelData.type ?? MtxEvent.UnknownMessage type: modelData.type ?? MtxEvent.UnknownMessage
typeString: modelData.typeString ?? "" typeString: modelData.typeString ?? ""
url: modelData.url ?? "" url: modelData.url ?? ""
originalWidth: modelData.originalWidth ?? 0 userColor: TimelineManager.userColor(modelData.userId, palette.window)
isOnlyEmoji: modelData.isOnlyEmoji ?? false
userId: modelData.userId ?? "" userId: modelData.userId ?? ""
userName: modelData.userName ?? "" userName: modelData.userName ?? ""
encryptionError: modelData.encryptionError ?? "" visible: room && room.reply
width: parent.width width: parent.width
} }
ImageButton { ImageButton {
id: closeReplyButton id: closeReplyButton
visible: room && room.reply ToolTip.text: qsTr("Close")
ToolTip.visible: closeReplyButton.hovered
anchors.margins: Nheko.paddingSmall
anchors.right: replyPreview.right anchors.right: replyPreview.right
anchors.top: replyPreview.top anchors.top: replyPreview.top
anchors.margins: Nheko.paddingSmall
hoverEnabled: true
width: 16
height: 16 height: 16
hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg" image: ":/icons/icons/ui/dismiss.svg"
ToolTip.visible: closeReplyButton.hovered visible: room && room.reply
ToolTip.text: qsTr("Close") width: 16
onClicked: room.reply = undefined onClicked: room.reply = undefined
} }
ImageButton { ImageButton {
id: closeEditButton id: closeEditButton
visible: room && room.edit ToolTip.text: qsTr("Cancel Edit")
anchors.right: closeThreadButton.left ToolTip.visible: closeEditButton.hovered
anchors.margins: 8 anchors.margins: 8
anchors.right: closeThreadButton.left
anchors.top: parent.top anchors.top: parent.top
height: 22
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/dismiss_edit.svg" image: ":/icons/icons/ui/dismiss_edit.svg"
visible: room && room.edit
width: 22 width: 22
height: 22
ToolTip.visible: closeEditButton.hovered
ToolTip.text: qsTr("Cancel Edit")
onClicked: room.edit = undefined onClicked: room.edit = undefined
} }
ImageButton { ImageButton {
id: closeThreadButton id: closeThreadButton
visible: room && room.thread ToolTip.text: qsTr("Cancel Thread")
anchors.right: parent.right ToolTip.visible: closeThreadButton.hovered
anchors.margins: 8 anchors.margins: 8
anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
buttonTextColor: room ? TimelineManager.userColor(room.thread, palette.base) : palette.buttonText
height: 22
hoverEnabled: true hoverEnabled: true
buttonTextColor: room ? TimelineManager.userColor(room.thread, Nheko.colors.base) : Nheko.colors.buttonText
image: ":/icons/icons/ui/dismiss_thread.svg" image: ":/icons/icons/ui/dismiss_thread.svg"
visible: room && room.thread
width: 22 width: 22
height: 22
ToolTip.visible: closeThreadButton.hovered
ToolTip.text: qsTr("Cancel Thread")
onClicked: room.thread = undefined onClicked: room.thread = undefined
} }
} }

File diff suppressed because it is too large Load Diff

@ -10,30 +10,23 @@ import "./pages"
import "./voip" import "./voip"
import "./ui" import "./ui"
import Qt.labs.platform 1.1 as Platform import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15 import QtQuick
import QtQuick.Controls 2.15 import QtQuick.Controls
import QtQuick.Layouts 1.3 import QtQuick.Layouts
import QtQuick.Window 2.15 import QtQuick.Window
import im.nheko 1.0 import im.nheko
import im.nheko.EmojiModel 1.0
Pane { Pane {
id: timelineRoot id: timelineRoot
palette: Nheko.colors function destroyOnClose(obj) {
background: null if (obj.closing != undefined)
padding: 0 obj.closing.connect(() => obj.destroy(1000));
else if (obj.aboutToHide != undefined)
FontMetrics { obj.aboutToHide.connect(() => obj.destroy(1000));
id: fontMetrics
}
RoomDirectoryModel {
id: publicRooms
} }
function destroyOnClosed(obj) {
UserDirectoryModel { obj.aboutToHide.connect(() => obj.destroy(1000));
id: userDirectory
} }
//Timer { //Timer {
@ -42,9 +35,8 @@ Pane {
// running: true // running: true
// repeat: true // repeat: true
//} //}
function showAliasEditor(settings) { function showAliasEditor(settings) {
var component = Qt.createComponent("qrc:/qml/dialogs/AliasEditor.qml") var component = Qt.createComponent("qrc:/resources/qml/dialogs/AliasEditor.qml");
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot, { var dialog = component.createObject(timelineRoot, {
"roomSettings": settings "roomSettings": settings
@ -54,11 +46,9 @@ Pane {
} else { } else {
console.error("Failed to create component: " + component.errorString()); console.error("Failed to create component: " + component.errorString());
} }
} }
function showAllowedRoomsEditor(settings) {
function showPLEditor(settings) { var component = Qt.createComponent("qrc:/resources/qml/dialogs/AllowedRoomsSettingsDialog.qml");
var component = Qt.createComponent("qrc:/qml/dialogs/PowerLevelEditor.qml")
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot, { var dialog = component.createObject(timelineRoot, {
"roomSettings": settings "roomSettings": settings
@ -69,13 +59,11 @@ Pane {
console.error("Failed to create component: " + component.errorString()); console.error("Failed to create component: " + component.errorString());
} }
} }
function showPLEditor(settings) {
function showSpacePLApplyPrompt(settings, editingModel) { var component = Qt.createComponent("qrc:/resources/qml/dialogs/PowerLevelEditor.qml");
var component = Qt.createComponent("qrc:/qml/dialogs/PowerLevelSpacesApplyDialog.qml")
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot, { var dialog = component.createObject(timelineRoot, {
"roomSettings": settings, "roomSettings": settings
"editingModel": editingModel
}); });
dialog.show(); dialog.show();
destroyOnClose(dialog); destroyOnClose(dialog);
@ -83,12 +71,12 @@ Pane {
console.error("Failed to create component: " + component.errorString()); console.error("Failed to create component: " + component.errorString());
} }
} }
function showSpacePLApplyPrompt(settings, editingModel) {
function showAllowedRoomsEditor(settings) { var component = Qt.createComponent("qrc:/resources/qml/dialogs/PowerLevelSpacesApplyDialog.qml");
var component = Qt.createComponent("qrc:/qml/dialogs/AllowedRoomsSettingsDialog.qml")
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot, { var dialog = component.createObject(timelineRoot, {
"roomSettings": settings "roomSettings": settings,
"editingModel": editingModel
}); });
dialog.show(); dialog.show();
destroyOnClose(dialog); destroyOnClose(dialog);
@ -97,23 +85,37 @@ Pane {
} }
} }
background: null
padding: 0
FontMetrics {
id: fontMetrics
}
UserDirectoryModel {
id: userDirectory
}
RoomDirectoryModel {
id: publicRooms
}
Component { Component {
id: readReceiptsDialog id: readReceiptsDialog
ReadReceipts { ReadReceipts {
} }
} }
Shortcut { Shortcut {
sequence: StandardKey.Quit sequence: StandardKey.Quit
onActivated: Qt.quit() onActivated: Qt.quit()
} }
Shortcut { Shortcut {
sequence: "Ctrl+K" sequence: "Ctrl+K"
onActivated: { onActivated: {
var component = Qt.createComponent("qrc:/qml/QuickSwitcher.qml") var component = Qt.createComponent("qrc:/resources/qml/QuickSwitcher.qml");
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var quickSwitch = component.createObject(timelineRoot); var quickSwitch = component.createObject(timelineRoot);
quickSwitch.open(); quickSwitch.open();
@ -123,50 +125,49 @@ Pane {
} }
} }
} }
Shortcut { Shortcut {
// Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit // Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit
sequences: ["Alt+A", "Ctrl+Shift+A"] sequences: ["Alt+A", "Ctrl+Shift+A"]
onActivated: Rooms.nextRoomWithActivity() onActivated: Rooms.nextRoomWithActivity()
} }
Shortcut { Shortcut {
sequence: "Ctrl+Down" sequence: "Ctrl+Down"
onActivated: Rooms.nextRoom() onActivated: Rooms.nextRoom()
} }
Shortcut { Shortcut {
sequence: "Ctrl+Up" sequence: "Ctrl+Up"
onActivated: Rooms.previousRoom() onActivated: Rooms.previousRoom()
} }
Connections { Connections {
function onOpenLogoutDialog() { function onOpenJoinRoomDialog() {
var component = Qt.createComponent("qrc:/qml/dialogs/LogoutDialog.qml") var component = Qt.createComponent("qrc:/resources/qml/dialogs/JoinRoomDialog.qml");
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot); var dialog = component.createObject(timelineRoot);
dialog.open(); dialog.show();
destroyOnClose(dialog); destroyOnClose(dialog);
} else { } else {
console.error("Failed to create component: " + component.errorString()); console.error("Failed to create component: " + component.errorString());
} }
} }
function onOpenLogoutDialog() {
function onOpenJoinRoomDialog() { var component = Qt.createComponent("qrc:/resources/qml/dialogs/LogoutDialog.qml");
var component = Qt.createComponent("qrc:/qml/dialogs/JoinRoomDialog.qml")
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot); var dialog = component.createObject(timelineRoot);
dialog.show(); dialog.open();
destroyOnClose(dialog); destroyOnClose(dialog);
} else { } else {
console.error("Failed to create component: " + component.errorString()); console.error("Failed to create component: " + component.errorString());
} }
} }
function onShowRoomJoinPrompt(summary) { function onShowRoomJoinPrompt(summary) {
var component = Qt.createComponent("qrc:/qml/dialogs/ConfirmJoinRoomDialog.qml") var component = Qt.createComponent("qrc:/resources/qml/dialogs/ConfirmJoinRoomDialog.qml");
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot, {"summary": summary}); var dialog = component.createObject(timelineRoot, {
"summary": summary
});
dialog.show(); dialog.show();
destroyOnClose(dialog); destroyOnClose(dialog);
} else { } else {
@ -176,12 +177,13 @@ Pane {
target: Nheko target: Nheko
} }
Connections { Connections {
function onNewDeviceVerificationRequest(flow) { function onNewDeviceVerificationRequest(flow) {
var component = Qt.createComponent("qrc:/qml/device-verification/DeviceVerification.qml") var component = Qt.createComponent("qrc:/resources/qml/device-verification/DeviceVerification.qml");
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot, {"flow": flow}); var dialog = component.createObject(timelineRoot, {
"flow": flow
});
dialog.show(); dialog.show();
destroyOnClose(dialog); destroyOnClose(dialog);
} else { } else {
@ -191,45 +193,46 @@ Pane {
target: VerificationManager target: VerificationManager
} }
Connections {
function destroyOnClose(obj) { function onOpenInviteUsersDialog(invitees) {
if (obj.closing != undefined) obj.closing.connect(() => obj.destroy(1000)); var component = Qt.createComponent("qrc:/resources/qml/dialogs/InviteDialog.qml");
else if (obj.aboutToHide != undefined) obj.aboutToHide.connect(() => obj.destroy(1000)); if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot, {
"invitees": invitees
});
dialog.show();
destroyOnClose(dialog);
} else {
console.error("Failed to create component: " + component.errorString());
} }
function destroyOnClosed(obj) {
obj.aboutToHide.connect(() => obj.destroy(1000));
} }
function onOpenLeaveRoomDialog(roomid, reason) {
Connections { var component = Qt.createComponent("qrc:/resources/qml/dialogs/LeaveRoomDialog.qml");
function onOpenProfile(profile) {
var component = Qt.createComponent("qrc:/qml/dialogs/UserProfile.qml")
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var userProfile = component.createObject(timelineRoot, {"profile": profile}); var dialog = component.createObject(timelineRoot, {
userProfile.show(); "roomId": roomid,
destroyOnClose(userProfile); "reason": reason
});
dialog.open();
destroyOnClose(dialog);
} else { } else {
console.error("Failed to create component: " + component.errorString()); console.error("Failed to create component: " + component.errorString());
} }
} }
function onOpenProfile(profile) {
function onShowImagePackSettings(room, packlist) { var component = Qt.createComponent("qrc:/resources/qml/dialogs/UserProfile.qml");
var component = Qt.createComponent("qrc:/qml/dialogs/ImagePackSettingsDialog.qml")
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var packSet = component.createObject(timelineRoot, { var userProfile = component.createObject(timelineRoot, {
"room": room, "profile": profile
"packlist": packlist
}); });
packSet.show(); userProfile.show();
destroyOnClose(packSet); destroyOnClose(userProfile);
} else { } else {
console.error("Failed to create component: " + component.errorString()); console.error("Failed to create component: " + component.errorString());
} }
} }
function onOpenRoomMembersDialog(members, room) { function onOpenRoomMembersDialog(members, room) {
var component = Qt.createComponent("qrc:/qml/dialogs/RoomMembers.qml") var component = Qt.createComponent("qrc:/resources/qml/dialogs/RoomMembers.qml");
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var membersDialog = component.createObject(timelineRoot, { var membersDialog = component.createObject(timelineRoot, {
"members": members, "members": members,
@ -240,11 +243,9 @@ Pane {
} else { } else {
console.error("Failed to create component: " + component.errorString()); console.error("Failed to create component: " + component.errorString());
} }
} }
function onOpenRoomSettingsDialog(settings) { function onOpenRoomSettingsDialog(settings) {
var component = Qt.createComponent("qrc:/qml/dialogs/RoomSettings.qml") var component = Qt.createComponent("qrc:/resources/qml/dialogs/RoomSettings.qml");
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var roomSettings = component.createObject(timelineRoot, { var roomSettings = component.createObject(timelineRoot, {
"roomSettings": settings "roomSettings": settings
@ -254,38 +255,9 @@ Pane {
} else { } else {
console.error("Failed to create component: " + component.errorString()); console.error("Failed to create component: " + component.errorString());
} }
} }
function onOpenInviteUsersDialog(invitees) {
var component = Qt.createComponent("qrc:/qml/dialogs/InviteDialog.qml")
if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot, {
"invitees": invitees
});
dialog.show();
destroyOnClose(dialog);
} else {
console.error("Failed to create component: " + component.errorString());
}
}
function onOpenLeaveRoomDialog(roomid, reason) {
var component = Qt.createComponent("qrc:/qml/dialogs/LeaveRoomDialog.qml")
if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot, {
"roomId": roomid,
"reason": reason
});
dialog.open();
destroyOnClose(dialog);
} else {
console.error("Failed to create component: " + component.errorString());
}
}
function onShowImageOverlay(room, eventId, url, originalWidth, proportionalHeight) { function onShowImageOverlay(room, eventId, url, originalWidth, proportionalHeight) {
var component = Qt.createComponent("qrc:/qml/dialogs/ImageOverlay.qml") var component = Qt.createComponent("qrc:/resources/qml/dialogs/ImageOverlay.qml");
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot, { var dialog = component.createObject(timelineRoot, {
"room": room, "room": room,
@ -293,22 +265,33 @@ Pane {
"url": url, "url": url,
"originalWidth": originalWidth ?? 0, "originalWidth": originalWidth ?? 0,
"proportionalHeight": proportionalHeight ?? 0 "proportionalHeight": proportionalHeight ?? 0
} });
);
dialog.showFullScreen(); dialog.showFullScreen();
destroyOnClose(dialog); destroyOnClose(dialog);
} else { } else {
console.error("Failed to create component: " + component.errorString()); console.error("Failed to create component: " + component.errorString());
} }
} }
function onShowImagePackSettings(room, packlist) {
var component = Qt.createComponent("qrc:/resources/qml/dialogs/ImagePackSettingsDialog.qml");
if (component.status == Component.Ready) {
var packSet = component.createObject(timelineRoot, {
"room": room,
"packlist": packlist
});
packSet.show();
destroyOnClose(packSet);
} else {
console.error("Failed to create component: " + component.errorString());
}
}
target: TimelineManager target: TimelineManager
} }
Connections { Connections {
function onNewInviteState() { function onNewInviteState() {
if (CallManager.haveCallInvite && Settings.mobileMode) { if (CallManager.haveCallInvite && Settings.mobileMode) {
var component = Qt.createComponent("qrc:/qml/voip/CallInvite.qml") var component = Qt.createComponent("qrc:/resources/qml/voip/CallInvite.qml");
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot); var dialog = component.createObject(timelineRoot);
dialog.open(); dialog.open();
@ -321,210 +304,205 @@ Pane {
target: CallManager target: CallManager
} }
SelfVerificationCheck { SelfVerificationCheck {
} }
InputDialog { InputDialog {
id: uiaPassPrompt id: uiaPassPrompt
echoMode: TextInput.Password echoMode: TextInput.Password
title: UIA.title
prompt: qsTr("Please enter your login password to continue:") prompt: qsTr("Please enter your login password to continue:")
onAccepted: (t) => { title: UIA.title
onAccepted: t => {
return UIA.continuePassword(t); return UIA.continuePassword(t);
} }
} }
InputDialog { InputDialog {
id: uiaEmailPrompt id: uiaEmailPrompt
title: UIA.title
prompt: qsTr("Please enter a valid email address to continue:") prompt: qsTr("Please enter a valid email address to continue:")
onAccepted: (t) => { title: UIA.title
onAccepted: t => {
return UIA.continueEmail(t); return UIA.continueEmail(t);
} }
} }
PhoneNumberInputDialog { PhoneNumberInputDialog {
id: uiaPhoneNumberPrompt id: uiaPhoneNumberPrompt
title: UIA.title
prompt: qsTr("Please enter a valid phone number to continue:") prompt: qsTr("Please enter a valid phone number to continue:")
title: UIA.title
onAccepted: (p, t) => { onAccepted: (p, t) => {
return UIA.continuePhoneNumber(p, t); return UIA.continuePhoneNumber(p, t);
} }
} }
InputDialog { InputDialog {
id: uiaTokenPrompt id: uiaTokenPrompt
title: UIA.title
prompt: qsTr("Please enter the token which has been sent to you:") prompt: qsTr("Please enter the token which has been sent to you:")
onAccepted: (t) => { title: UIA.title
onAccepted: t => {
return UIA.submit3pidToken(t); return UIA.submit3pidToken(t);
} }
} }
Platform.MessageDialog { Platform.MessageDialog {
id: uiaErrorDialog id: uiaErrorDialog
buttons: Platform.MessageDialog.Ok buttons: Platform.MessageDialog.Ok
} }
Platform.MessageDialog { Platform.MessageDialog {
id: uiaConfirmationLinkDialog id: uiaConfirmationLinkDialog
buttons: Platform.MessageDialog.Ok buttons: Platform.MessageDialog.Ok
text: qsTr("Wait for the confirmation link to arrive, then continue.") text: qsTr("Wait for the confirmation link to arrive, then continue.")
onAccepted: UIA.continue3pidReceived() onAccepted: UIA.continue3pidReceived()
} }
Connections { Connections {
function onPassword() { function onConfirm3pidToken() {
console.log("UIA: password needed"); uiaConfirmationLinkDialog.open();
uiaPassPrompt.show();
} }
function onEmail() { function onEmail() {
uiaEmailPrompt.show(); uiaEmailPrompt.show();
} }
function onError(msg) {
uiaErrorDialog.text = msg;
uiaErrorDialog.open();
}
function onPassword() {
console.log("UIA: password needed");
uiaPassPrompt.show();
}
function onPhoneNumber() { function onPhoneNumber() {
uiaPhoneNumberPrompt.show(); uiaPhoneNumberPrompt.show();
} }
function onPrompt3pidToken() { function onPrompt3pidToken() {
uiaTokenPrompt.show(); uiaTokenPrompt.show();
} }
function onConfirm3pidToken() {
uiaConfirmationLinkDialog.open();
}
function onError(msg) {
uiaErrorDialog.text = msg;
uiaErrorDialog.open();
}
target: UIA target: UIA
} }
StackView { StackView {
id: mainWindow id: mainWindow
property Transition popEnterOrg
property Transition popExitOrg
// for some reason direct bindings to a hidden StackView don't work, so manually store and restore here.
property Transition pushEnterOrg
property Transition pushExitOrg
property Transition replaceEnterOrg
property Transition replaceExitOrg
function updateTrans() {
pushEnter = Settings.reducedMotion ? reducedMotionTransitionEnter : pushEnterOrg;
pushExit = Settings.reducedMotion ? reducedMotionTransitionExit : pushExitOrg;
popEnter = Settings.reducedMotion ? reducedMotionTransitionEnter : popEnterOrg;
popExit = Settings.reducedMotion ? reducedMotionTransitionExit : popExitOrg;
replaceEnter = Settings.reducedMotion ? reducedMotionTransitionEnter : replaceEnterOrg;
replaceExit = Settings.reducedMotion ? reducedMotionTransitionExit : replaceExitOrg;
}
anchors.fill: parent anchors.fill: parent
initialItem: welcomePage initialItem: welcomePage
Component.onCompleted: {
pushEnterOrg = pushEnter;
popEnterOrg = popEnter;
replaceEnterOrg = replaceEnter;
pushExitOrg = pushExit;
popExitOrg = popExit;
replaceExitOrg = replaceExit;
updateTrans();
}
Transition { Transition {
id: reducedMotionTransitionExit id: reducedMotionTransitionExit
PropertyAnimation { PropertyAnimation {
property: "opacity" duration: 200
from: 1 from: 1
property: "opacity"
to: 0 to: 0
duration: 200
} }
} }
Transition { Transition {
id: reducedMotionTransitionEnter id: reducedMotionTransitionEnter
SequentialAnimation { SequentialAnimation {
PropertyAction { property: "opacity"; value: 0 } PropertyAction {
PauseAnimation { duration: 200 }
PropertyAnimation {
property: "opacity" property: "opacity"
value: 0
}
PauseAnimation {
duration: 200
}
PropertyAnimation {
duration: 200
from: 0 from: 0
property: "opacity"
to: 1 to: 1
duration: 200
} }
} }
} }
// for some reason direct bindings to a hidden StackView don't work, so manually store and restore here.
property Transition pushEnterOrg
property Transition pushExitOrg
property Transition popEnterOrg
property Transition popExitOrg
property Transition replaceEnterOrg
property Transition replaceExitOrg
Component.onCompleted: {
pushEnterOrg = pushEnter;
popEnterOrg = popEnter;
replaceEnterOrg = replaceEnter;
pushExitOrg = pushExit;
popExitOrg = popExit;
replaceExitOrg = replaceExit;
updateTrans()
}
function updateTrans() {
pushEnter = Settings.reducedMotion ? reducedMotionTransitionEnter : pushEnterOrg;
pushExit = Settings.reducedMotion ? reducedMotionTransitionExit : pushExitOrg;
popEnter = Settings.reducedMotion ? reducedMotionTransitionEnter : popEnterOrg;
popExit = Settings.reducedMotion ? reducedMotionTransitionExit : popExitOrg;
replaceEnter = Settings.reducedMotion ? reducedMotionTransitionEnter : replaceEnterOrg;
replaceExit = Settings.reducedMotion ? reducedMotionTransitionExit : replaceExitOrg;
}
Connections { Connections {
target: Settings
function onReducedMotionChanged() { function onReducedMotionChanged() {
mainWindow.updateTrans(); mainWindow.updateTrans();
} }
target: Settings
} }
} }
Component { Component {
id: welcomePage id: welcomePage
WelcomePage { WelcomePage {
} }
} }
Component { Component {
id: chatPage id: chatPage
ChatPage { ChatPage {
} }
} }
Component { Component {
id: loginPage id: loginPage
LoginPage { LoginPage {
} }
} }
Component { Component {
id: registerPage id: registerPage
RegisterPage { RegisterPage {
} }
} }
Component { Component {
id: userSettingsPage id: userSettingsPage
UserSettingsPage { UserSettingsPage {
} }
} }
Snackbar {
id: snackbar
}
Snackbar { id: snackbar }
Connections { Connections {
function onShowNotification(msg) {
snackbar.showNotification(msg);
console.log("New snack: " + msg);
}
function onSwitchToChatPage() { function onSwitchToChatPage() {
mainWindow.replace(null, chatPage); mainWindow.replace(null, chatPage);
} }
function onSwitchToLoginPage(error) { function onSwitchToLoginPage(error) {
mainWindow.replace(welcomePage, {}, loginPage, {"error": error}, StackView.PopTransition); mainWindow.replace(welcomePage, {}, loginPage, {
} "error": error
function onShowNotification(msg) { }, StackView.PopTransition);
snackbar.showNotification(msg);
console.log("New snack: " + msg);
} }
target: MainWindow target: MainWindow
} }
} }

@ -1,96 +0,0 @@
// Copyright (C) 2016 Michael Bohlender, <michael.bohlender@kdemail.net>
// Copyright (C) 2017 Christian Mollekopf, <mollekopf@kolabsystems.com>
// SPDX-FileCopyrightText: Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
/*
* Shamelessly stolen from:
* https://cgit.kde.org/kube.git/tree/framework/qml/ScrollHelper.qml
*
* The MouseArea + interactive: false + maximumFlickVelocity are required
* to fix scrolling for desktop systems where we don't want flicking behaviour.
*
* See also:
* ScrollView.qml in qtquickcontrols
* qquickwheelarea.cpp in qtquickcontrols
*/
import QtQuick 2.9
import QtQuick.Controls 2.3
MouseArea {
// console.warn("Delta: ", wheel.pixelDelta.y);
// console.warn("Old position: ", flickable.contentY);
// console.warn("New position: ", newPos);
// breaks ListView's with headers...
//if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem)
// minYExtent += flickableItem.headerItem.height;
id: root
property Flickable flickable
property alias enabled: root.enabled
function calculateNewPosition(flickableItem, wheel) {
//Nothing to scroll
if (flickableItem.contentHeight < flickableItem.height)
return flickableItem.contentY;
//Ignore 0 events (happens at least with Christians trackpad)
if (wheel.pixelDelta.y == 0 && wheel.angleDelta.y == 0)
return flickableItem.contentY;
//pixelDelta seems to be the same as angleDelta/8
var pixelDelta = 0;
//The pixelDelta is a smaller number if both are provided, so pixelDelta can be 0 while angleDelta is still something. So we check the angleDelta
if (wheel.angleDelta.y) {
var wheelScrollLines = 3; //Default value of QApplication wheelScrollLines property
var pixelPerLine = 20; //Default value in Qt, originally comes from QTextEdit
var ticks = (wheel.angleDelta.y / 8) / 15; //Divide by 8 gives us pixels typically come in 15pixel steps.
pixelDelta = ticks * pixelPerLine * wheelScrollLines;
} else {
pixelDelta = wheel.pixelDelta.y;
}
pixelDelta = Math.round(pixelDelta);
if (!pixelDelta)
return flickableItem.contentY;
var minYExtent = flickableItem.originY + flickableItem.topMargin;
var maxYExtent = (flickableItem.contentHeight + flickableItem.bottomMargin + flickableItem.originY) - flickableItem.height;
//Avoid overscrolling
return Math.max(minYExtent, Math.min(maxYExtent, flickableItem.contentY - pixelDelta));
}
propagateComposedEvents: true
//Place the mouse area under the flickable
z: -1
onFlickableChanged: {
if (enabled) {
flickable.maximumFlickVelocity = 100000;
flickable.boundsBehavior = Flickable.StopAtBounds;
root.parent = flickable;
}
}
acceptedButtons: Qt.NoButton
onWheel: {
var newPos = calculateNewPosition(flickable, wheel);
// Show the scrollbars
flickable.flick(0, 0);
flickable.contentY = newPos;
cancelFlickStateTimer.restart();
}
Timer {
id: cancelFlickStateTimer
//How long the scrollbar will remain visible
interval: 500
// Hide the scrollbars
onTriggered: {
flickable.cancelFlick();
flickable.movementEnded();
}
}
}

@ -10,22 +10,32 @@ import QtQuick.Layouts 1.3
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
visible: false
enabled: false enabled: false
visible: false
Dialog { Dialog {
id: showRecoverKeyDialog id: showRecoverKeyDialog
property string recoveryKey: "" property string recoveryKey: ""
parent: Overlay.overlay
anchors.centerIn: parent anchors.centerIn: parent
closePolicy: Popup.NoAutoClose
height: content.height + implicitFooterHeight + implicitHeaderHeight height: content.height + implicitFooterHeight + implicitHeaderHeight
width: content.width
padding: 0
modal: true modal: true
padding: 0
// Workaround palettes not inheriting for popups
palette: timelineRoot.palette
parent: Overlay.overlay
standardButtons: Dialog.Ok standardButtons: Dialog.Ok
closePolicy: Popup.NoAutoClose width: content.width
background: Rectangle {
border.color: Nheko.theme.separator
border.width: 1
color: palette.window
radius: Nheko.paddingSmall
}
ColumnLayout { ColumnLayout {
id: content id: content
@ -33,45 +43,33 @@ Item {
spacing: 0 spacing: 0
Label { Label {
Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4 Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4
Layout.fillWidth: true color: palette.text
text: qsTr("This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200!") text: qsTr("This is your recovery key. You will need it to restore access to your encrypted messages and verification keys. Keep this safe. Don't share it with anyone and don't lose it! Do not pass go! Do not collect $200!")
color: Nheko.colors.text
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
TextEdit { TextEdit {
Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: (Overlay.overlay ? Overlay.overlay.width : 400) - Nheko.paddingMedium * 4
color: palette.text
font.bold: true
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
verticalAlignment: TextEdit.AlignVCenter
readOnly: true readOnly: true
selectByMouse: true selectByMouse: true
text: showRecoverKeyDialog.recoveryKey text: showRecoverKeyDialog.recoveryKey
color: Nheko.colors.text verticalAlignment: TextEdit.AlignVCenter
font.bold: true
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
} }
}
background: Rectangle {
color: Nheko.colors.window
border.color: Nheko.theme.separator
border.width: 1
radius: Nheko.paddingSmall
} }
} }
P.MessageDialog { P.MessageDialog {
id: successDialog id: successDialog
buttons: P.MessageDialog.Ok buttons: P.MessageDialog.Ok
text: qsTr("Encryption setup successfully") text: qsTr("Encryption setup successfully")
} }
P.MessageDialog { P.MessageDialog {
id: failureDialog id: failureDialog
@ -80,85 +78,89 @@ Item {
buttons: P.MessageDialog.Ok buttons: P.MessageDialog.Ok
text: qsTr("Failed to setup encryption: %1").arg(errorMessage) text: qsTr("Failed to setup encryption: %1").arg(errorMessage)
} }
MainWindowDialog { MainWindowDialog {
id: bootstrapCrosssigning id: bootstrapCrosssigning
// Workaround palettes not inheriting for popups
palette: timelineRoot.palette
background: Rectangle {
border.color: Nheko.theme.separator
border.width: 1
color: palette.window
radius: Nheko.paddingSmall
}
onAccepted: SelfVerificationStatus.setupCrosssigning(storeSecretsOnline.checked, usePassword.checked ? passwordField.text : "", useOnlineKeyBackup.checked) onAccepted: SelfVerificationStatus.setupCrosssigning(storeSecretsOnline.checked, usePassword.checked ? passwordField.text : "", useOnlineKeyBackup.checked)
GridLayout { GridLayout {
id: grid id: grid
width: bootstrapCrosssigning.useableWidth columnSpacing: 0
columns: 2 columns: 2
rowSpacing: 0 rowSpacing: 0
columnSpacing: 0 width: bootstrapCrosssigning.useableWidth
z: 1 z: 1
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.margins: Nheko.paddingMedium
color: palette.text
font.pointSize: fontMetrics.font.pointSize * 2 font.pointSize: fontMetrics.font.pointSize * 2
text: qsTr("Setup Encryption") text: qsTr("Setup Encryption")
color: Nheko.colors.text
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2
color: palette.text
text: qsTr("Hello and welcome to Matrix!\nIt seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!") text: qsTr("Hello and welcome to Matrix!\nIt seems like you are new. Before you can securely encrypt your messages, we need to setup a few small things. You can either press accept immediately or adjust a few basic options. We also try to explain a few of the basics. You can skip those parts, but they might prove to be helpful!")
color: Nheko.colors.text
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.columnSpan: 1 Layout.columnSpan: 1
Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
color: palette.text
text: "Store secrets online.\nYou have a few secrets to make all the encryption magic work. While you can keep them stored only locally, we recommend storing them encrypted on the server. Otherwise it will be painful to recover them. Only disable this if you are paranoid and like losing your data!" text: "Store secrets online.\nYou have a few secrets to make all the encryption magic work. While you can keep them stored only locally, we recommend storing them encrypted on the server. Otherwise it will be painful to recover them. Only disable this if you are paranoid and like losing your data!"
color: Nheko.colors.text
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
Item { Item {
Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: storeSecretsOnline.height
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: storeSecretsOnline.height
ToggleButton { ToggleButton {
id: storeSecretsOnline id: storeSecretsOnline
checked: true checked: true
onClicked: console.log("Store secrets toggled: " + checked) onClicked: console.log("Store secrets toggled: " + checked)
} }
} }
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.columnSpan: 1 Layout.columnSpan: 1
Layout.rowSpan: 2 Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
visible: storeSecretsOnline.checked Layout.rowSpan: 2
color: palette.text
text: "Set an online backup password.\nWe recommend you DON'T set a password and instead only rely on the recovery key. You will get a recovery key in any case when storing the cross-signing secrets online, but passwords are usually not very random, so they are easier to attack than a completely random recovery key. If you choose to use a password, DON'T make it the same as your login password, otherwise your server can read all your encrypted messages. (You don't want that.)" text: "Set an online backup password.\nWe recommend you DON'T set a password and instead only rely on the recovery key. You will get a recovery key in any case when storing the cross-signing secrets online, but passwords are usually not very random, so they are easier to attack than a completely random recovery key. If you choose to use a password, DON'T make it the same as your login password, otherwise your server can read all your encrypted messages. (You don't want that.)"
color: Nheko.colors.text visible: storeSecretsOnline.checked
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
Item { Item {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium Layout.margins: Nheko.paddingMedium
Layout.topMargin: Nheko.paddingLarge
Layout.preferredHeight: storeSecretsOnline.height Layout.preferredHeight: storeSecretsOnline.height
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.rowSpan: usePassword.checked ? 1 : 2 Layout.rowSpan: usePassword.checked ? 1 : 2
Layout.fillWidth: true Layout.topMargin: Nheko.paddingLarge
visible: storeSecretsOnline.checked visible: storeSecretsOnline.checked
ToggleButton { ToggleButton {
@ -166,113 +168,108 @@ Item {
checked: false checked: false
} }
} }
MatrixTextField { MatrixTextField {
id: passwordField id: passwordField
Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.columnSpan: 1 Layout.columnSpan: 1
Layout.fillWidth: true Layout.fillWidth: true
visible: storeSecretsOnline.checked && usePassword.checked Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
echoMode: TextInput.Password echoMode: TextInput.Password
visible: storeSecretsOnline.checked && usePassword.checked
} }
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.columnSpan: 1 Layout.columnSpan: 1
Layout.margins: Nheko.paddingMedium
Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2 Layout.maximumWidth: Math.floor(grid.width / 2) - Nheko.paddingMedium * 2
color: palette.text
text: "Use online key backup.\nStore the keys for your messages securely encrypted online. In general you do want this, because it protects your messages from becoming unreadable, if you log out by accident. It does however carry a small security risk, if you ever share your recovery key by accident. Currently this also has some other weaknesses, that might allow the server to insert new keys into your backup. The server will however never be able to read your messages." text: "Use online key backup.\nStore the keys for your messages securely encrypted online. In general you do want this, because it protects your messages from becoming unreadable, if you log out by accident. It does however carry a small security risk, if you ever share your recovery key by accident. Currently this also has some other weaknesses, that might allow the server to insert new keys into your backup. The server will however never be able to read your messages."
color: Nheko.colors.text
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
Item { Item {
Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: storeSecretsOnline.height
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium
Layout.preferredHeight: storeSecretsOnline.height
ToggleButton { ToggleButton {
id: useOnlineKeyBackup id: useOnlineKeyBackup
checked: true checked: true
onClicked: console.log("Online key backup toggled: " + checked)
}
onClicked: console.log("Online key backup toggled: " + checked)
} }
} }
background: Rectangle {
color: Nheko.colors.window
border.color: Nheko.theme.separator
border.width: 1
radius: Nheko.paddingSmall
} }
} }
MainWindowDialog { MainWindowDialog {
id: verifyMasterKey id: verifyMasterKey
// Workaround palettes not inheriting for popups
palette: timelineRoot.palette
standardButtons: Dialog.Cancel standardButtons: Dialog.Cancel
GridLayout { GridLayout {
id: masterGrid id: masterGrid
width: verifyMasterKey.useableWidth
columns: 1 columns: 1
width: verifyMasterKey.useableWidth
z: 1 z: 1
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingMedium
color: palette.text
//Layout.columnSpan: 2 //Layout.columnSpan: 2
font.pointSize: fontMetrics.font.pointSize * 2 font.pointSize: fontMetrics.font.pointSize * 2
text: qsTr("Activate Encryption") text: qsTr("Activate Encryption")
color: Nheko.colors.text
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
Label { Label {
Layout.margins: Nheko.paddingMedium
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
Layout.margins: Nheko.paddingMedium
//Layout.columnSpan: 2 //Layout.columnSpan: 2
Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2 Layout.maximumWidth: grid.width - Nheko.paddingMedium * 2
color: palette.text
text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.") text: qsTr("It seems like you have encryption already configured for this account. To be able to access your encrypted messages and make this device appear as trusted, you can either verify an existing device or (if you have one) enter your recovery passphrase. Please select one of the options below.\nIf you choose verify, you need to have the other device available. If you choose \"enter passphrase\", you will need your recovery key or passphrase. If you click cancel, you can choose to verify yourself at a later point.")
color: Nheko.colors.text
wrapMode: Text.Wrap wrapMode: Text.Wrap
} }
FlatButton { FlatButton {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("verify") text: qsTr("verify")
onClicked: { onClicked: {
SelfVerificationStatus.verifyMasterKey(); SelfVerificationStatus.verifyMasterKey();
verifyMasterKey.close(); verifyMasterKey.close();
} }
} }
FlatButton { FlatButton {
visible: SelfVerificationStatus.hasSSSS
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("enter passphrase") text: qsTr("enter passphrase")
visible: SelfVerificationStatus.hasSSSS
onClicked: { onClicked: {
SelfVerificationStatus.verifyMasterKeyWithPassphrase(); SelfVerificationStatus.verifyMasterKeyWithPassphrase();
verifyMasterKey.close(); verifyMasterKey.close();
} }
} }
} }
} }
Connections { Connections {
function onSetupCompleted() {
successDialog.open();
}
function onSetupFailed(m) {
failureDialog.errorMessage = m;
failureDialog.open();
}
function onShowRecoveryKey(key) {
showRecoverKeyDialog.recoveryKey = key;
showRecoverKeyDialog.open();
}
function onStatusChanged() { function onStatusChanged() {
console.log("STATUS CHANGED: " + SelfVerificationStatus.status); console.log("STATUS CHANGED: " + SelfVerificationStatus.status);
if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) { if (SelfVerificationStatus.status == SelfVerificationStatus.NoMasterKey) {
@ -285,21 +282,6 @@ Item {
} }
} }
function onShowRecoveryKey(key) {
showRecoverKeyDialog.recoveryKey = key;
showRecoverKeyDialog.open();
}
function onSetupCompleted() {
successDialog.open();
}
function onSetupFailed(m) {
failureDialog.errorMessage = m;
failureDialog.open();
}
target: SelfVerificationStatus target: SelfVerificationStatus
} }
} }

@ -9,15 +9,9 @@ import im.nheko 1.0
ImageButton { ImageButton {
id: indicator id: indicator
required property int status
required property string eventId required property string eventId
required property int status
width: 16
height: 16
hoverEnabled: true
changeColorOnHover: (status == MtxEvent.Read)
cursor: (status == MtxEvent.Read) ? Qt.PointingHandCursor : Qt.ArrowCursor
ToolTip.visible: hovered && status != MtxEvent.Empty
ToolTip.text: { ToolTip.text: {
switch (status) { switch (status) {
case MtxEvent.Failed: case MtxEvent.Failed:
@ -32,11 +26,11 @@ ImageButton {
return ""; return "";
} }
} }
onClicked: { ToolTip.visible: hovered && status != MtxEvent.Empty
if (status == MtxEvent.Read) changeColorOnHover: (status == MtxEvent.Read)
room.showReadReceipts(eventId); cursor: (status == MtxEvent.Read) ? Qt.PointingHandCursor : Qt.ArrowCursor
height: 16
} hoverEnabled: true
image: { image: {
switch (status) { switch (status) {
case MtxEvent.Failed: case MtxEvent.Failed:
@ -51,4 +45,10 @@ ImageButton {
return ""; return "";
} }
} }
width: 16
onClicked: {
if (status == MtxEvent.Read)
room.showReadReceipts(eventId);
}
} }

@ -13,72 +13,44 @@ import im.nheko 1.0
AbstractButton { AbstractButton {
id: r id: r
required property double proportionalHeight
required property int type
required property string typeString
required property int originalWidth
required property string blurhash required property string blurhash
required property string body required property string body
required property string formattedBody required property string callType
required property int duration
required property int encryptionError
required property string eventId required property string eventId
required property string filename required property string filename
required property string filesize required property string filesize
required property string url required property string formattedBody
required property string thumbnailUrl required property int index
required property bool isOnlyEmoji
required property bool isSender
required property bool isEncrypted
required property bool isEditable required property bool isEditable
required property bool isEdited required property bool isEdited
required property bool isEncrypted
required property bool isOnlyEmoji
required property bool isSender
required property bool isStateEvent required property bool isStateEvent
required property int notificationlevel
required property int originalWidth
required property double proportionalHeight
required property var reactions
required property int relatedEventCacheBuster
required property string replyTo required property string replyTo
required property string roomName
required property string roomTopic
required property int status
required property string threadId required property string threadId
required property string thumbnailUrl
required property var timestamp
required property int trustlevel
required property int type
required property string typeString
required property string url
required property string userId required property string userId
required property string userName required property string userName
required property string roomTopic
required property string roomName
required property string callType
required property var reactions
required property int trustlevel
required property int notificationlevel
required property int encryptionError
required property int duration
required property var timestamp
required property int status
required property int index
required property int relatedEventCacheBuster
hoverEnabled: true
width: parent.width
height: row.height + (reactionRow.height > 0 ? reactionRow.height - 2 : 0) + unreadRow.height height: row.height + (reactionRow.height > 0 ? reactionRow.height - 2 : 0) + unreadRow.height
hoverEnabled: true
Rectangle {
color: (Settings.messageHoverHighlight && hovered) ? Nheko.colors.alternateBase : "transparent"
anchors.fill: parent
// this looks better without margins
TapHandler {
acceptedButtons: Qt.RightButton
onSingleTapped: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
gesturePolicy: TapHandler.ReleaseWithinBounds
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
}
}
onPressAndHold: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
onDoubleClicked: room.reply = eventId
DragHandler {
id: draghandler
yAxis.enabled: false
xAxis.maximum: 100
xAxis.minimum: -100
onActiveChanged: {
if(!active && (x < -70 || x > 70))
room.reply = eventId
}
}
states: State { states: State {
name: "dragging" name: "dragging"
when: draghandler.active when: draghandler.active
@ -86,265 +58,292 @@ AbstractButton {
transitions: Transition { transitions: Transition {
from: "dragging" from: "dragging"
to: "" to: ""
PropertyAnimation { PropertyAnimation {
target: r duration: 100
properties: "x"
easing.type: Easing.InOutQuad easing.type: Easing.InOutQuad
properties: "x"
target: r
to: 0 to: 0
duration: 100
} }
} }
onClicked: { onClicked: {
let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX - row.x - msg.x, pressY - row.y - msg.y - contentItem.y); let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX - row.x - msg.x, pressY - row.y - msg.y - contentItem.y);
if (link) { if (link) {
Nheko.openLink(link) Nheko.openLink(link);
}
}
onDoubleClicked: room.reply = eventId
onPressAndHold: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
Rectangle {
anchors.fill: parent
color: (Settings.messageHoverHighlight && hovered) ? palette.alternateBase : "transparent"
// this looks better without margins
TapHandler {
acceptedButtons: Qt.RightButton
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
gesturePolicy: TapHandler.ReleaseWithinBounds
onSingleTapped: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
} }
} }
DragHandler {
id: draghandler
xAxis.maximum: 100
xAxis.minimum: -100
yAxis.enabled: false
onActiveChanged: {
if (!active && (x < -70 || x > 70))
room.reply = eventId;
}
}
AbstractButton { AbstractButton {
anchors.leftMargin: Settings.smallAvatars? 0 : (Nheko.avatarSize + 8) // align bubble with section header ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Part of a thread")
ToolTip.visible: hovered
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8) // align bubble with section header
height: parent.height
visible: threadId visible: threadId
width: 4 width: 4
height: parent.height
onClicked: room.thread = threadId
Rectangle { Rectangle {
id: threadLine id: threadLine
color: TimelineManager.userColor(threadId, Nheko.colors.base)
anchors.fill: parent anchors.fill: parent
color: TimelineManager.userColor(threadId, palette.base)
} }
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Part of a thread")
onClicked: room.thread = threadId
} }
Rectangle { Rectangle {
id: row id: row
property color bgColor: palette.base
property bool bubbleOnRight: isSender && Settings.bubbles property bool bubbleOnRight: isSender && Settings.bubbles
anchors.leftMargin: (isStateEvent || Settings.smallAvatars? 0 : (Nheko.avatarSize + 8)) + (threadId ? 6 : 0) // align bubble with section header
anchors.left: (isStateEvent || bubbleOnRight) ? undefined : parent.left
anchors.right: (isStateEvent || !bubbleOnRight) ? undefined : parent.right
anchors.horizontalCenter: isStateEvent? parent.horizontalCenter : undefined
property int maxWidth: (parent.width - (Settings.smallAvatars || isStateEvent ? 0 : Nheko.avatarSize + 8)) * (Settings.bubbles && !isStateEvent ? 0.9 : 1) property int maxWidth: (parent.width - (Settings.smallAvatars || isStateEvent ? 0 : Nheko.avatarSize + 8)) * (Settings.bubbles && !isStateEvent ? 0.9 : 1)
width: Settings.bubbles? Math.min(maxWidth,Math.max(reply.implicitWidth+8,contentItem.implicitWidth+metadata.width+20)) : maxWidth property color userColor: TimelineManager.userColor(userId, palette.base)
height: msg.height+msg.anchors.margins*2
property color userColor: TimelineManager.userColor(userId, Nheko.colors.base) anchors.horizontalCenter: isStateEvent ? parent.horizontalCenter : undefined
property color bgColor: Nheko.colors.base anchors.left: (isStateEvent || bubbleOnRight) ? undefined : parent.left
anchors.leftMargin: (isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) + (threadId ? 6 : 0) // align bubble with section header
anchors.right: (isStateEvent || !bubbleOnRight) ? undefined : parent.right
border.color: Nheko.theme.red
border.width: r.notificationlevel == MtxEvent.Highlight ? 1 : 0
color: (Settings.bubbles && !isStateEvent) ? Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.2)) : "#00000000" color: (Settings.bubbles && !isStateEvent) ? Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.2)) : "#00000000"
height: msg.height + msg.anchors.margins * 2
radius: 4 radius: 4
border.width: r.notificationlevel == MtxEvent.Highlight ? 1 : 0 width: Settings.bubbles ? Math.min(maxWidth, Math.max(reply.implicitWidth + 8, contentItem.implicitWidth + metadata.width + 20)) : maxWidth
border.color: Nheko.theme.red
GridLayout { GridLayout {
id: msg
columnSpacing: 2
columns: Settings.bubbles ? 1 : 2
rowSpacing: 0
rows: Settings.bubbles ? 3 : 2
anchors { anchors {
left: parent.left left: parent.left
top: parent.top
right: parent.right
margins: (Settings.bubbles && ! isStateEvent)? 4 : 2
leftMargin: 4 leftMargin: 4
margins: (Settings.bubbles && !isStateEvent) ? 4 : 2
right: parent.right
rightMargin: 4 rightMargin: 4
top: parent.top
} }
id: msg
rowSpacing: 0
columnSpacing: 2
columns: Settings.bubbles? 1 : 2
rows: Settings.bubbles? 3 : 2
// fancy reply, if this is a reply // fancy reply, if this is a reply
Reply { Reply {
Layout.row: 0
Layout.column: 0
Layout.fillWidth: true
Layout.maximumWidth: Settings.bubbles? Number.MAX_VALUE : implicitWidth
Layout.bottomMargin: visible? 2 : 0
Layout.preferredHeight: height
id: reply id: reply
function fromModel(role) { function fromModel(role) {
return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null; return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null;
} }
visible: replyTo
userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, Nheko.colors.base) Layout.bottomMargin: visible ? 2 : 0
Layout.column: 0
Layout.fillWidth: true
Layout.maximumWidth: Settings.bubbles ? Number.MAX_VALUE : implicitWidth
Layout.preferredHeight: height
Layout.row: 0
blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? "" blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? ""
body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? "" body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? ""
formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? "" callType: r.relatedEventCacheBuster, fromModel(Room.Voip) ?? ""
duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? 0
encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? 0
eventId: fromModel(Room.EventId) ?? "" eventId: fromModel(Room.EventId) ?? ""
filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? "" filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? ""
filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? "" filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? ""
formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? ""
isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false
isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false
originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0
proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1 proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1
relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? ""
type: r.relatedEventCacheBuster, fromModel(Room.Type) ?? MtxEvent.UnknownMessage type: r.relatedEventCacheBuster, fromModel(Room.Type) ?? MtxEvent.UnknownMessage
typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? "" typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? ""
url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? "" url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? ""
originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0 userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, palette.base)
isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false
isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false
userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? "" userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? ""
userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? "" userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? ""
thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? "" visible: replyTo
duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? ""
roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? ""
encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? ""
relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
} }
// actual message content // actual message content
MessageDelegate { MessageDelegate {
Layout.row: 1 id: contentItem
Layout.column: 0 Layout.column: 0
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: height Layout.preferredHeight: height
id: contentItem Layout.row: 1
blurhash: r.blurhash blurhash: r.blurhash
body: r.body body: r.body
formattedBody: r.formattedBody callType: r.callType
duration: r.duration
encryptionError: r.encryptionError
eventId: r.eventId eventId: r.eventId
filename: r.filename filename: r.filename
filesize: r.filesize filesize: r.filesize
formattedBody: r.formattedBody
isOnlyEmoji: r.isOnlyEmoji
isReply: false
isStateEvent: r.isStateEvent
metadataWidth: metadata.width
originalWidth: r.originalWidth
proportionalHeight: r.proportionalHeight proportionalHeight: r.proportionalHeight
relatedEventCacheBuster: r.relatedEventCacheBuster
roomName: r.roomName
roomTopic: r.roomTopic
thumbnailUrl: r.thumbnailUrl
type: r.type type: r.type
typeString: r.typeString ?? "" typeString: r.typeString ?? ""
url: r.url url: r.url
thumbnailUrl: r.thumbnailUrl
duration: r.duration
originalWidth: r.originalWidth
isOnlyEmoji: r.isOnlyEmoji
isStateEvent: r.isStateEvent
userId: r.userId userId: r.userId
userName: r.userName userName: r.userName
roomTopic: r.roomTopic
roomName: r.roomName
callType: r.callType
encryptionError: r.encryptionError
relatedEventCacheBuster: r.relatedEventCacheBuster
isReply: false
metadataWidth: metadata.width
} }
Row { Row {
id: metadata id: metadata
property int iconSize: Math.floor(fontMetrics.ascent * scaling)
property double scaling: Settings.bubbles ? 0.75 : 1
Layout.alignment: Qt.AlignTop | Qt.AlignRight
Layout.bottomMargin: -2
Layout.column: Settings.bubbles ? 0 : 1 Layout.column: Settings.bubbles ? 0 : 1
Layout.preferredWidth: implicitWidth
Layout.row: Settings.bubbles ? 2 : 0 Layout.row: Settings.bubbles ? 2 : 0
Layout.rowSpan: Settings.bubbles ? 1 : 2 Layout.rowSpan: Settings.bubbles ? 1 : 2
Layout.bottomMargin: -2
Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles) ? -height - Layout.bottomMargin : 0 Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles) ? -height - Layout.bottomMargin : 0
Layout.alignment: Qt.AlignTop | Qt.AlignRight
Layout.preferredWidth: implicitWidth
visible: !isStateEvent
spacing: 2 spacing: 2
visible: !isStateEvent
property double scaling: Settings.bubbles? 0.75 : 1
property int iconSize: Math.floor(fontMetrics.ascent*scaling)
StatusIndicator { StatusIndicator {
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
anchors.verticalCenter: ts.verticalCenter
eventId: r.eventId
height: parent.iconSize height: parent.iconSize
width: parent.iconSize
status: r.status status: r.status
eventId: r.eventId width: parent.iconSize
anchors.verticalCenter: ts.verticalCenter
} }
Image { Image {
visible: isEdited || eventId == room.edit
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
height: parent.iconSize
width: parent.iconSize
sourceSize.width: parent.iconSize * Screen.devicePixelRatio
sourceSize.height: parent.iconSize * Screen.devicePixelRatio
source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == room.edit) ? Nheko.colors.highlight : Nheko.colors.buttonText)
ToolTip.visible: editHovered.hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Edited") ToolTip.text: qsTr("Edited")
ToolTip.visible: editHovered.hovered
anchors.verticalCenter: ts.verticalCenter anchors.verticalCenter: ts.verticalCenter
height: parent.iconSize
source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == room.edit) ? palette.highlight : palette.buttonText)
sourceSize.height: parent.iconSize * Screen.devicePixelRatio
sourceSize.width: parent.iconSize * Screen.devicePixelRatio
visible: isEdited || eventId == room.edit
width: parent.iconSize
HoverHandler { HoverHandler {
id: editHovered id: editHovered
}
} }
}
ImageButton { ImageButton {
visible: threadId
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
height: parent.iconSize
width: parent.iconSize
image: ":/icons/icons/ui/thread.svg"
buttonTextColor: TimelineManager.userColor(threadId, Nheko.colors.base)
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Part of a thread") ToolTip.text: qsTr("Part of a thread")
ToolTip.visible: hovered
anchors.verticalCenter: ts.verticalCenter anchors.verticalCenter: ts.verticalCenter
buttonTextColor: TimelineManager.userColor(threadId, palette.base)
height: parent.iconSize
image: ":/icons/icons/ui/thread.svg"
visible: threadId
width: parent.iconSize
onClicked: room.thread = threadId onClicked: room.thread = threadId
} }
EncryptionIndicator { EncryptionIndicator {
visible: room.isEncrypted
encrypted: isEncrypted
trust: trustlevel
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
anchors.verticalCenter: ts.verticalCenter
encrypted: isEncrypted
height: parent.iconSize height: parent.iconSize
width: parent.iconSize
sourceSize.width: parent.iconSize * Screen.devicePixelRatio
sourceSize.height: parent.iconSize * Screen.devicePixelRatio sourceSize.height: parent.iconSize * Screen.devicePixelRatio
anchors.verticalCenter: ts.verticalCenter sourceSize.width: parent.iconSize * Screen.devicePixelRatio
trust: trustlevel
visible: room.isEncrypted
width: parent.iconSize
} }
Label { Label {
id: ts id: ts
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
text: timestamp.toLocaleTimeString(Locale.ShortFormat)
color: Nheko.inactiveColors.text
ToolTip.visible: ma.hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate) ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
ToolTip.visible: ma.hovered
color: palette.inactive.text
font.pointSize: fontMetrics.font.pointSize * parent.scaling font.pointSize: fontMetrics.font.pointSize * parent.scaling
text: timestamp.toLocaleTimeString(Locale.ShortFormat)
HoverHandler { HoverHandler {
id: ma id: ma
}
} }
} }
} }
} }
}
Reactions { Reactions {
id: reactionRow
eventId: r.eventId
layoutDirection: row.bubbleOnRight ? Qt.RightToLeft : Qt.LeftToRight
reactions: r.reactions
width: row.maxWidth
anchors { anchors {
top: row.bottom
topMargin: -4
left: row.bubbleOnRight ? undefined : row.left left: row.bubbleOnRight ? undefined : row.left
right: row.bubbleOnRight ? row.right : undefined right: row.bubbleOnRight ? row.right : undefined
top: row.bottom
topMargin: -4
} }
width: row.maxWidth
layoutDirection: row.bubbleOnRight? Qt.RightToLeft : Qt.LeftToRight
id: reactionRow
reactions: r.reactions
eventId: r.eventId
} }
Rectangle { Rectangle {
id: unreadRow id: unreadRow
color: palette.highlight
height: visible ? 3 : 0
visible: (r.index > 0 && (room.fullyReadEventId == r.eventId))
anchors { anchors {
top: reactionRow.bottom
topMargin: 5
left: parent.left left: parent.left
right: parent.right right: parent.right
top: reactionRow.bottom
topMargin: 5
} }
color: Nheko.colors.highlight
visible: (r.index > 0 && (room.fullyReadEventId == r.eventId))
height: visible ? 3 : 0
} }
} }

@ -8,101 +8,97 @@ import "./device-verification"
import "./emoji" import "./emoji"
import "./ui" import "./ui"
import "./voip" import "./voip"
import Qt.labs.platform 1.1 as Platform import Qt.labs.platform as Platform
import QtQuick 2.15 import QtQuick
import QtQuick.Controls 2.5 import QtQuick.Controls
import QtQuick.Layouts 1.3 import QtQuick.Layouts
import QtQuick.Particles 2.15 import QtQuick.Particles
import QtQuick.Window 2.13 import QtQuick.Window
import im.nheko 1.0 import im.nheko
import im.nheko.EmojiModel 1.0
Item { Item {
id: timelineView id: timelineView
required property PrivacyScreen privacyScreen
property var room: null property var room: null
property var roomPreview: null property var roomPreview: null
property bool showBackButton: false
property bool shouldEffectsRun: false property bool shouldEffectsRun: false
required property PrivacyScreen privacyScreen property bool showBackButton: false
clip: true
onRoomChanged: if (room != null) room.triggerSpecialEffects()
StickerPicker {
id: emojiPopup
colors: Nheko.colors clip: true
emoji: true
}
// focus message input on key press, but not on Ctrl-C and such. // focus message input on key press, but not on Ctrl-C and such.
Keys.onPressed: { Keys.onPressed: event => {
if (event.text && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return && !topBar.searchHasFocus) { if (event.text && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return && !topBar.searchHasFocus) {
TimelineManager.focusMessageInput(); TimelineManager.focusMessageInput();
room.input.setText(room.input.text + event.text); room.input.setText(room.input.text + event.text);
} }
} }
onRoomChanged: if (room != null)
room.triggerSpecialEffects()
StickerPicker {
id: emojiPopup
emoji: true
}
Shortcut { Shortcut {
sequence: StandardKey.Close sequence: StandardKey.Close
onActivated: Rooms.resetCurrentRoom() onActivated: Rooms.resetCurrentRoom()
} }
Label { Label {
visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid)
anchors.centerIn: parent anchors.centerIn: parent
text: qsTr("No room open")
font.pointSize: 24 font.pointSize: 24
color: Nheko.colors.text text: qsTr("No room open")
visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid)
} }
Spinner { Spinner {
visible: TimelineManager.isInitialSync
anchors.centerIn: parent anchors.centerIn: parent
foreground: Nheko.colors.mid foreground: palette.mid
running: TimelineManager.isInitialSync
// height is somewhat arbitrary here... don't set width because width scales w/ height // height is somewhat arbitrary here... don't set width because width scales w/ height
height: parent.height / 16 height: parent.height / 16
z: 3
opacity: hh.hovered ? 0.3 : 1 opacity: hh.hovered ? 0.3 : 1
running: TimelineManager.isInitialSync
visible: TimelineManager.isInitialSync
z: 3
Behavior on opacity { Behavior on opacity {
NumberAnimation { duration: 100; } NumberAnimation {
duration: 100
}
} }
HoverHandler { HoverHandler {
id: hh id: hh
} }
} }
ColumnLayout { ColumnLayout {
id: timelineLayout id: timelineLayout
visible: room != null && !room.isSpace
enabled: visible
anchors.fill: parent anchors.fill: parent
enabled: visible
spacing: 0 spacing: 0
visible: room != null && !room.isSpace
TopBar { TopBar {
id: topBar id: topBar
showBackButton: timelineView.showBackButton showBackButton: timelineView.showBackButton
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
color: Nheko.theme.separator
height: 1 height: 1
z: 3 z: 3
color: Nheko.theme.separator
} }
Rectangle { Rectangle {
id: msgView id: msgView
Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
color: Nheko.colors.base Layout.fillWidth: true
color: palette.base
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
@ -120,143 +116,121 @@ Item {
target: timelineView target: timelineView
} }
MessageView { MessageView {
Layout.fillWidth: true
implicitHeight: msgView.height - typingIndicator.height implicitHeight: msgView.height - typingIndicator.height
searchString: topBar.searchString searchString: topBar.searchString
Layout.fillWidth: true
} }
Loader { Loader {
source: CallManager.isOnCall && CallManager.callType != CallType.VOICE ? "voip/VideoCall.qml" : "" source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? "voip/VideoCall.qml" : ""
onLoaded: TimelineManager.setVideoCallItem() onLoaded: TimelineManager.setVideoCallItem()
} }
} }
TypingIndicator { TypingIndicator {
id: typingIndicator id: typingIndicator
}
} }
} }
}
CallInviteBar { CallInviteBar {
id: callInviteBar id: callInviteBar
Layout.fillWidth: true Layout.fillWidth: true
z: 3 z: 3
} }
ActiveCallBar { ActiveCallBar {
Layout.fillWidth: true Layout.fillWidth: true
z: 3 z: 3
} }
Rectangle { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
z: 3
height: 1
color: Nheko.theme.separator color: Nheko.theme.separator
height: 1
z: 3
} }
UploadBox { UploadBox {
} }
MessageInputWarning { MessageInputWarning {
text: qsTr("You are about to notify the whole room") text: qsTr("You are about to notify the whole room")
visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom) visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom)
} }
MessageInputWarning { MessageInputWarning {
text: qsTr("The command /%1 is not recognized and will be sent as part of your message").arg(room ? room.input.currentCommand : "") text: qsTr("The command /%1 is not recognized and will be sent as part of your message").arg(room ? room.input.currentCommand : "")
visible: room ? room.input.containsInvalidCommand && !room.input.containsIncompleteCommand : false visible: room ? room.input.containsInvalidCommand && !room.input.containsIncompleteCommand : false
} }
MessageInputWarning { MessageInputWarning {
bubbleColor: Nheko.theme.orange
text: qsTr("/%1 looks like an incomplete command. To send it anyway, add a space to the end of your message.").arg(room ? room.input.currentCommand : "") text: qsTr("/%1 looks like an incomplete command. To send it anyway, add a space to the end of your message.").arg(room ? room.input.currentCommand : "")
visible: room ? room.input.containsIncompleteCommand : false visible: room ? room.input.containsIncompleteCommand : false
bubbleColor: Nheko.theme.orange
} }
ReplyPopup { ReplyPopup {
} }
MessageInput { MessageInput {
} }
} }
ColumnLayout { ColumnLayout {
id: preview id: preview
property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "")
property string reason: roomPreview ? roomPreview.reason : ""
property string roomId: room ? room.roomId : (roomPreview ? roomPreview.roomid : "") property string roomId: room ? room.roomId : (roomPreview ? roomPreview.roomid : "")
property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "") property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "")
property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "") property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "")
property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "")
property string reason: roomPreview ? roomPreview.reason : ""
visible: room != null && room.isSpace || roomPreview != null
enabled: visible
anchors.fill: parent anchors.fill: parent
anchors.margins: Nheko.paddingLarge anchors.margins: Nheko.paddingLarge
enabled: visible
spacing: Nheko.paddingLarge spacing: Nheko.paddingLarge
visible: room != null && room.isSpace || roomPreview != null
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
} }
Avatar { Avatar {
url: parent.avatarUrl.replace("mxc://", "image://MxcImage/") Layout.alignment: Qt.AlignHCenter
roomid: parent.roomId
displayName: parent.roomName displayName: parent.roomName
enabled: false
height: 130 height: 130
roomid: parent.roomId
url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
width: 130 width: 130
Layout.alignment: Qt.AlignHCenter
enabled: false
} }
RowLayout { RowLayout {
spacing: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingMedium
MatrixText { MatrixText {
text: !roomPreview.isFetched ? qsTr("No preview available") : preview.roomName
font.pixelSize: 24 font.pixelSize: 24
text: !(roomPreview?.isFetched ?? false) ? qsTr("No preview available") : preview.roomName
} }
ImageButton { ImageButton {
ToolTip.text: qsTr("Settings")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/settings.svg" image: ":/icons/icons/ui/settings.svg"
visible: !!room visible: !!room
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Settings")
onClicked: TimelineManager.openRoomSettings(room.roomId) onClicked: TimelineManager.openRoomSettings(room.roomId)
} }
} }
RowLayout { RowLayout {
visible: !!room
spacing: Nheko.paddingMedium
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingMedium
visible: !!room
MatrixText { MatrixText {
text: qsTr("%n member(s)", "", room ? room.roomMemberCount : 0) text: qsTr("%n member(s)", "", room ? room.roomMemberCount : 0)
} }
ImageButton { ImageButton {
image: ":/icons/icons/ui/people.svg"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("View members of %1").arg(room ? room.roomName : "") ToolTip.text: qsTr("View members of %1").arg(room ? room.roomName : "")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/people.svg"
onClicked: TimelineManager.openRoomMembers(room) onClicked: TimelineManager.openRoomMembers(room)
} }
} }
ScrollView { ScrollView {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
@ -264,55 +238,76 @@ Item {
Layout.rightMargin: Nheko.paddingLarge Layout.rightMargin: Nheko.paddingLarge
TextArea { TextArea {
text: roomPreview.isFetched ? TimelineManager.escapeEmoji(preview.roomTopic) : qsTr("This room is possibly inaccessible. If this room is private, you should remove it from this community.")
wrapMode: TextEdit.WordWrap
textFormat: TextEdit.RichText
readOnly: true
background: null background: null
selectByMouse: true
color: Nheko.colors.text
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
readOnly: true
selectByMouse: true
text: (roomPreview?.isFetched ?? false) ? TimelineManager.escapeEmoji(preview.roomTopic) : qsTr("This room is possibly inaccessible. If this room is private, you should remove it from this community.")
textFormat: TextEdit.RichText
wrapMode: TextEdit.WordWrap
onLinkActivated: Nheko.openLink(link) onLinkActivated: Nheko.openLink(link)
CursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
} }
} }
FlatButton { FlatButton {
visible: roomPreview && !roomPreview.isInvite
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("join the conversation") text: qsTr("join the conversation")
visible: roomPreview && !roomPreview.isInvite
onClicked: Rooms.joinPreview(roomPreview.roomid) onClicked: Rooms.joinPreview(roomPreview.roomid)
} }
FlatButton { FlatButton {
visible: roomPreview && roomPreview.isInvite
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("accept invite") text: qsTr("accept invite")
visible: roomPreview && roomPreview.isInvite
onClicked: Rooms.acceptInvite(roomPreview.roomid) onClicked: Rooms.acceptInvite(roomPreview.roomid)
} }
FlatButton { FlatButton {
visible: roomPreview && roomPreview.isInvite
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("decline invite") text: qsTr("decline invite")
visible: roomPreview && roomPreview.isInvite
onClicked: Rooms.declineInvite(roomPreview.roomid) onClicked: Rooms.declineInvite(roomPreview.roomid)
} }
FlatButton { FlatButton {
visible: !!room
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("leave") text: qsTr("leave")
visible: !!room
onClicked: TimelineManager.openLeaveRoomDialog(room.roomId) onClicked: TimelineManager.openLeaveRoomDialog(room.roomId)
} }
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Nheko.paddingMedium
visible: roomPreview && roomPreview.isInvite && reasonField.showReason
MatrixText {
text: qsTr("Invited by %1 (%2)").arg(TimelineManager.escapeEmoji(inviterAvatar.displayName)).arg(TimelineManager.escapeEmoji(TimelineManager.htmlEscape(inviterAvatar.userid)))
}
Avatar {
id: inviterAvatar
Layout.alignment: Qt.AlignHCenter
displayName: roomPreview?.inviterDisplayName ?? ""
enabled: true
height: 48
roomid: preview.roomId
url: (roomPreview?.inviterAvatarUrl ?? "").replace("mxc://", "image://MxcImage/")
userid: roomPreview?.inviterUserId ?? ""
width: 48
onClicked: TimelineManager.openGlobalUserProfile(roomPreview.inviterUserId)
}
}
ScrollView { ScrollView {
id: reasonField id: reasonField
property bool showReason: false property bool showReason: false
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -322,18 +317,15 @@ Item {
visible: preview.reason !== "" && showReason visible: preview.reason !== "" && showReason
TextArea { TextArea {
text: TimelineManager.escapeEmoji(preview.reason)
wrapMode: TextEdit.WordWrap
textFormat: TextEdit.RichText
readOnly: true
background: null background: null
selectByMouse: true
color: Nheko.colors.text
horizontalAlignment: TextEdit.AlignHCenter horizontalAlignment: TextEdit.AlignHCenter
readOnly: true
selectByMouse: true
text: TimelineManager.escapeEmoji(preview.reason)
textFormat: TextEdit.RichText
wrapMode: TextEdit.WordWrap
} }
} }
Button { Button {
id: showReasonButton id: showReasonButton
@ -341,61 +333,69 @@ Item {
//Layout.fillWidth: true //Layout.fillWidth: true
Layout.leftMargin: Nheko.paddingLarge Layout.leftMargin: Nheko.paddingLarge
Layout.rightMargin: Nheko.paddingLarge Layout.rightMargin: Nheko.paddingLarge
visible: preview.reason !== ""
text: reasonField.showReason ? qsTr("Hide invite reason") : qsTr("Show invite reason") text: reasonField.showReason ? qsTr("Hide invite reason") : qsTr("Show invite reason")
visible: roomPreview && roomPreview.isInvite
onClicked: { onClicked: {
reasonField.showReason = !reasonField.showReason; reasonField.showReason = !reasonField.showReason;
} }
} }
Item { Item {
visible: room != null
Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 2) Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 2)
visible: room != null
} }
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
} }
} }
ImageButton { ImageButton {
id: backToRoomsButton id: backToRoomsButton
anchors.top: parent.top ToolTip.text: qsTr("Back to room list")
ToolTip.visible: hovered
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
width: Nheko.avatarSize anchors.top: parent.top
height: Nheko.avatarSize
visible: (room == null || room.isSpace) && showBackButton
enabled: visible enabled: visible
height: Nheko.avatarSize
image: ":/icons/icons/ui/angle-arrow-left.svg" image: ":/icons/icons/ui/angle-arrow-left.svg"
ToolTip.visible: hovered visible: (room == null || room.isSpace) && showBackButton
ToolTip.text: qsTr("Back to room list") width: Nheko.avatarSize
onClicked: Rooms.resetCurrentRoom() onClicked: Rooms.resetCurrentRoom()
} }
TimelineEffects { TimelineEffects {
id: timelineEffects id: timelineEffects
anchors.fill: parent anchors.fill: parent
shouldEffectsRun: timelineView.shouldEffectsRun
} }
NhekoDropArea { NhekoDropArea {
anchors.fill: parent anchors.fill: parent
roomid: room ? room.roomId : "" roomid: room ? room.roomId : ""
} }
Timer { Timer {
id: effectsTimer id: effectsTimer
onTriggered: shouldEffectsRun = false;
interval: timelineEffects.maxLifespan interval: timelineEffects.maxLifespan
repeat: false repeat: false
running: false running: false
}
onTriggered: shouldEffectsRun = false
}
Connections { Connections {
function onConfetti() {
if (!Settings.fancyEffects)
return;
shouldEffectsRun = true;
timelineEffects.pulseConfetti();
room.markSpecialEffectsDone();
}
function onConfettiDone() {
if (!Settings.fancyEffects)
return;
effectsTimer.restart();
}
function onOpenReadReceiptsDialog(rr) { function onOpenReadReceiptsDialog(rr) {
var dialog = readReceiptsDialog.createObject(timelineRoot, { var dialog = readReceiptsDialog.createObject(timelineRoot, {
"readReceipts": rr, "readReceipts": rr,
@ -404,9 +404,20 @@ Item {
dialog.show(); dialog.show();
timelineRoot.destroyOnClose(dialog); timelineRoot.destroyOnClose(dialog);
} }
function onRainfall() {
if (!Settings.fancyEffects)
return;
shouldEffectsRun = true;
timelineEffects.pulseRainfall();
room.markSpecialEffectsDone();
}
function onRainfallDone() {
if (!Settings.fancyEffects)
return;
effectsTimer.restart();
}
function onShowRawMessageDialog(rawMessage) { function onShowRawMessageDialog(rawMessage) {
var component = Qt.createComponent("qrc:/qml/dialogs/RawMessageDialog.qml") var component = Qt.createComponent("qrc:/resources/qml/dialogs/RawMessageDialog.qml");
if (component.status == Component.Ready) { if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot, { var dialog = component.createObject(timelineRoot, {
"rawMessage": rawMessage "rawMessage": rawMessage
@ -418,43 +429,6 @@ Item {
} }
} }
function onConfetti()
{
if (!Settings.fancyEffects)
return
shouldEffectsRun = true;
timelineEffects.pulseConfetti()
room.markSpecialEffectsDone()
}
function onConfettiDone()
{
if (!Settings.fancyEffects)
return
effectsTimer.restart();
}
function onRainfall()
{
if (!Settings.fancyEffects)
return
shouldEffectsRun = true;
timelineEffects.pulseRainfall()
room.markSpecialEffectsDone()
}
function onRainfallDone()
{
if (!Settings.fancyEffects)
return
effectsTimer.restart();
}
target: room target: room
} }
} }

@ -11,17 +11,44 @@ Switch {
id: toggleButton id: toggleButton
implicitWidth: indicatorItem.width implicitWidth: indicatorItem.width
state: checked ? "on" : "off" state: checked ? "on" : "off"
indicator: Item {
id: indicatorItem
implicitHeight: 24
implicitWidth: 48
y: parent.height / 2 - height / 2
Rectangle {
id: track
color: Qt.rgba(border.color.r, border.color.g, border.color.b, 0.6)
height: parent.height * 0.6
radius: height / 2
width: parent.width - height
x: radius
y: parent.height / 2 - height / 2
}
Rectangle {
id: handle
border.color: "#767676"
color: palette.button
height: width
radius: width / 2
width: parent.height * 0.9
y: parent.height / 2 - height / 2
}
}
states: [ states: [
State { State {
name: "off" name: "off"
PropertyChanges { PropertyChanges {
target: track
border.color: "#767676" border.color: "#767676"
target: track
} }
PropertyChanges { PropertyChanges {
target: handle target: handle
x: 0 x: 0
@ -31,10 +58,9 @@ Switch {
name: "on" name: "on"
PropertyChanges { PropertyChanges {
border.color: palette.highlight
target: track target: track
border.color: Nheko.colors.highlight
} }
PropertyChanges { PropertyChanges {
target: handle target: handle
x: indicatorItem.width - handle.width x: indicatorItem.width - handle.width
@ -43,55 +69,22 @@ Switch {
] ]
transitions: [ transitions: [
Transition { Transition {
to: "off"
reversible: true reversible: true
to: "off"
ParallelAnimation { ParallelAnimation {
NumberAnimation { NumberAnimation {
target: handle
property: "x"
duration: 200 duration: 200
easing.type: Easing.InOutQuad easing.type: Easing.InOutQuad
property: "x"
target: handle
} }
ColorAnimation { ColorAnimation {
target: track
properties: "color,border.color"
duration: 200 duration: 200
properties: "color,border.color"
target: track
} }
} }
} }
] ]
indicator: Item {
id: indicatorItem
implicitWidth: 48
implicitHeight: 24
y: parent.height / 2 - height / 2
Rectangle {
id: track
height: parent.height * 0.6
radius: height / 2
width: parent.width - height
x: radius
y: parent.height / 2 - height / 2
color: Qt.rgba(border.color.r, border.color.g, border.color.b, 0.6)
}
Rectangle {
id: handle
y: parent.height / 2 - height / 2
width: parent.height * 0.9
height: width
radius: width / 2
color: Nheko.colors.button
border.color: "#767676"
}
}
} }

@ -8,212 +8,142 @@ import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.15 import QtQuick.Window 2.15
import im.nheko 1.0 import im.nheko 1.0
import "./delegates" import "./delegates"
Pane { Pane {
id: topBar id: topBar
property bool showBackButton: false
property string roomName: room ? room.roomName : qsTr("No room selected")
property string roomId: room ? room.roomId : ""
property string avatarUrl: room ? room.roomAvatarUrl : "" property string avatarUrl: room ? room.roomAvatarUrl : ""
property string roomTopic: room ? room.roomTopic : ""
property bool isEncrypted: room ? room.isEncrypted : false
property int trustlevel: room ? room.trustlevel : Crypto.Unverified
property bool isDirect: room ? room.isDirect : false
property string directChatOtherUserId: room ? room.directChatOtherUserId : "" property string directChatOtherUserId: room ? room.directChatOtherUserId : ""
property bool isDirect: room ? room.isDirect : false
property bool isEncrypted: room ? room.isEncrypted : false
property string roomId: room ? room.roomId : ""
property string roomName: room ? room.roomName : qsTr("No room selected")
property string roomTopic: room ? room.roomTopic : ""
property bool searchHasFocus: searchField.focus && searchField.enabled property bool searchHasFocus: searchField.focus && searchField.enabled
property string searchString: "" property string searchString: ""
property bool showBackButton: false
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu property int trustlevel: room ? room.trustlevel : Crypto.Unverified
Connections {
function onHideMenu() {
roomOptionsMenu.close()
}
target: MainWindow
}
onRoomIdChanged: {
searchString = "";
searchButton.searchActive = false;
searchField.text = ""
}
Shortcut {
sequence: StandardKey.Find
onActivated: searchButton.searchActive = !searchButton.searchActive
}
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: topLayout.height + Nheko.paddingMedium * 2 implicitHeight: topLayout.height + Nheko.paddingMedium * 2
padding: 0
z: 3 z: 3
padding: 0
background: Rectangle { background: Rectangle {
color: Nheko.colors.window color: palette.window
}
TapHandler {
onSingleTapped: {
if (eventPoint.position.y > topBar.height - (pinnedMessages.visible ? pinnedMessages.height : 0) - (widgets.visible ? widgets.height : 0)) {
eventPoint.accepted = true
return;
}
if (showBackButton && eventPoint.position.x < Nheko.paddingMedium + backToRoomsButton.width) {
eventPoint.accepted = true
return;
} }
if (eventPoint.position.x > topBar.width - Nheko.paddingMedium - roomOptionsButton.width) {
eventPoint.accepted = true
return;
}
if (communityLabel.visible && eventPoint.position.y < communityAvatar.height + Nheko.paddingMedium + Nheko.paddingSmall/2) {
if (!Communities.trySwitchToSpace(room.parentSpace.roomid))
room.parentSpace.promptJoin();
eventPoint.accepted = true
return;
}
if (room) {
let p = topBar.mapToItem(roomTopicC, eventPoint.position.x, eventPoint.position.y);
let link = roomTopicC.linkAt(p.x, p.y);
if (link) {
Nheko.openLink(link);
} else {
TimelineManager.openRoomSettings(room.roomId);
}
}
eventPoint.accepted = true;
}
gesturePolicy: TapHandler.ReleaseWithinBounds
}
HoverHandler {
grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything
}
contentItem: Item { contentItem: Item {
GridLayout { GridLayout {
id: topLayout id: topLayout
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
columnSpacing: Nheko.paddingSmall columnSpacing: Nheko.paddingSmall
rowSpacing: Nheko.paddingSmall rowSpacing: Nheko.paddingSmall
Avatar { Avatar {
id: communityAvatar id: communityAvatar
visible: roomid && room.parentSpace.isLoaded && ("space:"+room.parentSpace.roomid != Communities.currentTagId)
property string avatarUrl: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomAvatarUrl) || "" property string avatarUrl: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomAvatarUrl) || ""
property string communityId: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomid) || "" property string communityId: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomid) || ""
property string communityName: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomName) || "" property string communityName: (Settings.groupView && room && room.parentSpace && room.parentSpace.roomName) || ""
Layout.alignment: Qt.AlignRight
Layout.column: 1 Layout.column: 1
Layout.row: 0 Layout.row: 0
Layout.alignment: Qt.AlignRight
width: fontMetrics.lineSpacing
height: fontMetrics.lineSpacing
url: avatarUrl.replace("mxc://", "image://MxcImage/")
roomid: communityId
displayName: communityName displayName: communityName
enabled: false enabled: false
height: fontMetrics.lineSpacing
roomid: communityId
url: avatarUrl.replace("mxc://", "image://MxcImage/")
visible: roomid && room.parentSpace.isLoaded && ("space:" + room.parentSpace.roomid != Communities.currentTagId)
width: fontMetrics.lineSpacing
} }
Label { Label {
id: communityLabel id: communityLabel
visible: communityAvatar.visible
Layout.column: 2 Layout.column: 2
Layout.row: 0
Layout.fillWidth: true Layout.fillWidth: true
color: Nheko.colors.text Layout.row: 0
text: qsTr("In %1").arg(communityAvatar.displayName) color: palette.text
maximumLineCount: 1
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1
text: qsTr("In %1").arg(communityAvatar.displayName)
textFormat: Text.RichText textFormat: Text.RichText
visible: communityAvatar.visible
} }
ImageButton { ImageButton {
id: backToRoomsButton id: backToRoomsButton
Layout.column: 0
Layout.row: 1
Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.column: 0
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
visible: showBackButton Layout.row: 1
image: ":/icons/icons/ui/angle-arrow-left.svg" Layout.rowSpan: 2
ToolTip.visible: hovered
ToolTip.text: qsTr("Back to room list") ToolTip.text: qsTr("Back to room list")
ToolTip.visible: hovered
image: ":/icons/icons/ui/angle-arrow-left.svg"
visible: showBackButton
onClicked: Rooms.resetCurrentRoom() onClicked: Rooms.resetCurrentRoom()
} }
Avatar { Avatar {
Layout.alignment: Qt.AlignVCenter
Layout.column: 1 Layout.column: 1
Layout.row: 1 Layout.row: 1
Layout.rowSpan: 2 Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter displayName: roomName
width: Nheko.avatarSize enabled: false
height: Nheko.avatarSize height: Nheko.avatarSize
url: avatarUrl.replace("mxc://", "image://MxcImage/")
roomid: roomId roomid: roomId
url: avatarUrl.replace("mxc://", "image://MxcImage/")
userid: isDirect ? directChatOtherUserId : "" userid: isDirect ? directChatOtherUserId : ""
displayName: roomName width: Nheko.avatarSize
enabled: false
} }
Label { Label {
Layout.fillWidth: true
Layout.column: 2 Layout.column: 2
Layout.fillWidth: true
Layout.row: 1 Layout.row: 1
color: Nheko.colors.text color: palette.text
font.pointSize: fontMetrics.font.pointSize * 1.1 elide: Text.ElideRight
font.bold: true font.bold: true
text: roomName font.pointSize: fontMetrics.font.pointSize * 1.1
maximumLineCount: 1 maximumLineCount: 1
elide: Text.ElideRight text: roomName
textFormat: Text.RichText textFormat: Text.RichText
} }
MatrixText { MatrixText {
id: roomTopicC id: roomTopicC
Layout.fillWidth: true
Layout.column: 2 Layout.column: 2
Layout.row: 2 Layout.fillWidth: true
Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines Layout.maximumHeight: fontMetrics.lineSpacing * 2 // show 2 lines
selectByMouse: false Layout.row: 2
enabled: false
clip: true clip: true
enabled: false
selectByMouse: false
text: roomTopic text: roomTopic
} }
ImageButton { ImageButton {
id: pinButton id: pinButton
property bool pinsShown: !Settings.hiddenPins.includes(roomId) property bool pinsShown: !Settings.hiddenPins.includes(roomId)
visible: !!room && room.pinnedMessages.length > 0
Layout.column: 3
Layout.row: 1
Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.column: 3
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg" Layout.row: 1
ToolTip.visible: hovered Layout.rowSpan: 2
ToolTip.text: qsTr("Show or hide pinned messages") ToolTip.text: qsTr("Show or hide pinned messages")
ToolTip.visible: hovered
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
visible: !!room && room.pinnedMessages.length > 0
onClicked: { onClicked: {
var ps = Settings.hiddenPins; var ps = Settings.hiddenPins;
if (pinsShown) { if (pinsShown) {
@ -226,30 +156,20 @@ Pane {
} }
Settings.hiddenPins = ps; Settings.hiddenPins = ps;
} }
} }
AbstractButton { AbstractButton {
Layout.column: 4 Layout.column: 4
Layout.row: 1
Layout.rowSpan: 2
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
Layout.row: 1
Layout.rowSpan: 2
background: null
contentItem: EncryptionIndicator { contentItem: EncryptionIndicator {
encrypted: isEncrypted
trust: trustlevel
enabled: false
unencryptedIcon: ":/icons/icons/ui/people.svg"
unencryptedColor: Nheko.colors.buttonText
unencryptedHoverColor: Nheko.colors.highlight
hovered: parent.hovered
ToolTip.delay: Nheko.tooltipDelay ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: { ToolTip.text: {
if (!isEncrypted) if (!isEncrypted)
return qsTr("Show room members."); return qsTr("Show room members.");
switch (trustlevel) { switch (trustlevel) {
case Crypto.Verified: case Crypto.Verified:
return qsTr("This room contains only verified devices."); return qsTr("This room contains only verified devices.");
@ -259,221 +179,257 @@ Pane {
return qsTr("This room contains unverified devices!"); return qsTr("This room contains unverified devices!");
} }
} }
enabled: false
encrypted: isEncrypted
hovered: parent.hovered
trust: trustlevel
unencryptedColor: palette.buttonText
unencryptedHoverColor: palette.highlight
unencryptedIcon: ":/icons/icons/ui/people.svg"
} }
background: null
onClicked: TimelineManager.openRoomMembers(room) onClicked: TimelineManager.openRoomMembers(room)
} }
ImageButton { ImageButton {
id: searchButton id: searchButton
property bool searchActive: false property bool searchActive: false
visible: !!room
Layout.column: 5
Layout.row: 1
Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.column: 5
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
image: ":/icons/icons/ui/search.svg" Layout.row: 1
ToolTip.visible: hovered Layout.rowSpan: 2
ToolTip.text: qsTr("Search this room") ToolTip.text: qsTr("Search this room")
onClicked: searchActive = !searchActive ToolTip.visible: hovered
image: ":/icons/icons/ui/search.svg"
visible: !!room
onClicked: searchActive = !searchActive
onSearchActiveChanged: { onSearchActiveChanged: {
if (searchActive) { if (searchActive) {
searchField.forceActiveFocus(); searchField.forceActiveFocus();
} } else {
else {
searchField.clear(); searchField.clear();
topBar.searchString = ""; topBar.searchString = "";
} }
} }
} }
ImageButton { ImageButton {
id: roomOptionsButton id: roomOptionsButton
visible: !!room
Layout.column: 6
Layout.row: 1
Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.column: 6
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
image: ":/icons/icons/ui/options.svg" Layout.row: 1
ToolTip.visible: hovered Layout.rowSpan: 2
ToolTip.text: qsTr("Room options") ToolTip.text: qsTr("Room options")
ToolTip.visible: hovered
image: ":/icons/icons/ui/options.svg"
visible: !!room
onClicked: roomOptionsMenu.open(roomOptionsButton) onClicked: roomOptionsMenu.open(roomOptionsButton)
Platform.Menu { Platform.Menu {
id: roomOptionsMenu id: roomOptionsMenu
Platform.MenuItem { Platform.MenuItem {
visible: room ? room.permissions.canInvite() : false
text: qsTr("Invite users") text: qsTr("Invite users")
visible: room ? room.permissions.canInvite() : false
onTriggered: TimelineManager.openInviteUsers(roomId) onTriggered: TimelineManager.openInviteUsers(roomId)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Members") text: qsTr("Members")
onTriggered: TimelineManager.openRoomMembers(room) onTriggered: TimelineManager.openRoomMembers(room)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Leave room") text: qsTr("Leave room")
onTriggered: TimelineManager.openLeaveRoomDialog(roomId) onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Settings") text: qsTr("Settings")
onTriggered: TimelineManager.openRoomSettings(roomId) onTriggered: TimelineManager.openRoomSettings(roomId)
} }
} }
} }
ScrollView { ScrollView {
id: pinnedMessages id: pinnedMessages
Layout.row: 3
Layout.column: 2 Layout.column: 2
Layout.columnSpan: 4 Layout.columnSpan: 4
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4) Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
Layout.row: 3
visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId)
clip: true
palette: Nheko.colors
ScrollBar.horizontal.visible: false ScrollBar.horizontal.visible: false
clip: true
visible: !!room && room.pinnedMessages.length > 0 && !Settings.hiddenPins.includes(roomId)
ListView { ListView {
spacing: Nheko.paddingSmall
model: room ? room.pinnedMessages : undefined model: room ? room.pinnedMessages : undefined
spacing: Nheko.paddingSmall
delegate: RowLayout { delegate: RowLayout {
required property string modelData required property string modelData
width: ListView.view.width
height: implicitHeight height: implicitHeight
width: ListView.view.width
Reply { Reply {
id: reply id: reply
property var e: room ? room.getDump(modelData, "pins") : {} property var e: room ? room.getDump(modelData, "pins") : {}
Connections {
function onPinnedMessagesChanged() { reply.e = room.getDump(modelData, "pins") }
target: room
}
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: height Layout.preferredHeight: height
userColor: TimelineManager.userColor(e.userId, Nheko.colors.window)
blurhash: e.blurhash ?? "" blurhash: e.blurhash ?? ""
body: e.body ?? "" body: e.body ?? ""
formattedBody: e.formattedBody ?? "" encryptionError: e.encryptionError ?? 0
eventId: e.eventId ?? "" eventId: e.eventId ?? ""
filename: e.filename ?? "" filename: e.filename ?? ""
filesize: e.filesize ?? "" filesize: e.filesize ?? ""
formattedBody: e.formattedBody ?? ""
isOnlyEmoji: e.isOnlyEmoji ?? false
keepFullText: true
originalWidth: e.originalWidth ?? 0
proportionalHeight: e.proportionalHeight ?? 1 proportionalHeight: e.proportionalHeight ?? 1
type: e.type ?? MtxEvent.UnknownMessage type: e.type ?? MtxEvent.UnknownMessage
typeString: e.typeString ?? "" typeString: e.typeString ?? ""
url: e.url ?? "" url: e.url ?? ""
originalWidth: e.originalWidth ?? 0 userColor: TimelineManager.userColor(e.userId, palette.window)
isOnlyEmoji: e.isOnlyEmoji ?? false
userId: e.userId ?? "" userId: e.userId ?? ""
userName: e.userName ?? "" userName: e.userName ?? ""
encryptionError: e.encryptionError ?? ""
keepFullText: true Connections {
function onPinnedMessagesChanged() {
reply.e = room.getDump(modelData, "pins");
} }
target: room
}
}
ImageButton { ImageButton {
id: deletePinButton id: deletePinButton
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16
Layout.alignment: Qt.AlignTop | Qt.AlignLeft ToolTip.text: qsTr("Unpin")
visible: room.permissions.canChange(MtxEvent.PinnedEvents) ToolTip.visible: hovered
hoverEnabled: true hoverEnabled: true
image: ":/icons/icons/ui/dismiss.svg" image: ":/icons/icons/ui/dismiss.svg"
ToolTip.visible: hovered visible: room.permissions.canChange(MtxEvent.PinnedEvents)
ToolTip.text: qsTr("Unpin")
onClicked: room.unpin(modelData) onClicked: room.unpin(modelData)
} }
} }
ScrollHelper {
flickable: parent
anchors.fill: parent
enabled: !Settings.mobileMode
}
} }
} }
ScrollView { ScrollView {
id: widgets id: widgets
Layout.row: 4
Layout.column: 2 Layout.column: 2
Layout.columnSpan: 4 Layout.columnSpan: 4
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5) Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5)
Layout.row: 4
visible: !!room && room.widgetLinks.length > 0 && !Settings.hiddenWidgets.includes(roomId)
clip: true
palette: Nheko.colors
ScrollBar.horizontal.visible: false ScrollBar.horizontal.visible: false
clip: true
visible: !!room && room.widgetLinks.length > 0 && !Settings.hiddenWidgets.includes(roomId)
ListView { ListView {
spacing: Nheko.paddingSmall
model: room ? room.widgetLinks : undefined model: room ? room.widgetLinks : undefined
spacing: Nheko.paddingSmall
delegate: MatrixText { delegate: MatrixText {
required property var modelData required property var modelData
color: Nheko.colors.text color: palette.text
text: modelData text: modelData
} }
ScrollHelper {
flickable: parent
anchors.fill: parent
enabled: !Settings.mobileMode
} }
} }
}
MatrixTextField { MatrixTextField {
id: searchField id: searchField
visible: searchButton.searchActive
enabled: visible
hasClear: true
Layout.row: 5
Layout.column: 2 Layout.column: 2
Layout.columnSpan: 4 Layout.columnSpan: 4
Layout.fillWidth: true Layout.fillWidth: true
Layout.row: 5
enabled: visible
hasClear: true
placeholderText: qsTr("Enter search query") placeholderText: qsTr("Enter search query")
visible: searchButton.searchActive
onAccepted: topBar.searchString = text onAccepted: topBar.searchString = text
} }
} }
NhekoCursorShape {
CursorShape {
anchors.fill: parent
anchors.bottomMargin: (pinnedMessages.visible ? pinnedMessages.height : 0) + (widgets.visible ? widgets.height : 0) anchors.bottomMargin: (pinnedMessages.visible ? pinnedMessages.height : 0) + (widgets.visible ? widgets.height : 0)
anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
} }
onRoomIdChanged: {
searchString = "";
searchButton.searchActive = false;
searchField.text = "";
}
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
Connections {
function onHideMenu() {
roomOptionsMenu.close();
}
target: MainWindow
}
Shortcut {
sequence: StandardKey.Find
onActivated: searchButton.searchActive = !searchButton.searchActive
}
TapHandler {
gesturePolicy: TapHandler.ReleaseWithinBounds
onSingleTapped: {
if (eventPoint.position.y > topBar.height - (pinnedMessages.visible ? pinnedMessages.height : 0) - (widgets.visible ? widgets.height : 0)) {
eventPoint.accepted = true;
return;
}
if (showBackButton && eventPoint.position.x < Nheko.paddingMedium + backToRoomsButton.width) {
eventPoint.accepted = true;
return;
}
if (eventPoint.position.x > topBar.width - Nheko.paddingMedium - roomOptionsButton.width) {
eventPoint.accepted = true;
return;
}
if (communityLabel.visible && eventPoint.position.y < communityAvatar.height + Nheko.paddingMedium + Nheko.paddingSmall / 2) {
if (!Communities.trySwitchToSpace(room.parentSpace.roomid))
room.parentSpace.promptJoin();
eventPoint.accepted = true;
return;
}
if (room) {
let p = topBar.mapToItem(roomTopicC, eventPoint.position.x, eventPoint.position.y);
let link = roomTopicC.linkAt(p.x, p.y);
if (link) {
Nheko.openLink(link);
} else {
TimelineManager.openRoomSettings(room.roomId);
}
}
eventPoint.accepted = true;
}
}
HoverHandler {
grabPermissions: PointerHandler.TakeOverForbidden | PointerHandler.CanTakeOverFromAnything
}
} }

@ -8,30 +8,28 @@ import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: Math.max(fontMetrics.height * 1.2, typingDisplay.height)
Rectangle { Rectangle {
id: typingRect id: typingRect
visible: (room && room.typingUsers.length > 0)
color: Nheko.colors.base
anchors.fill: parent anchors.fill: parent
color: palette.base
visible: (room && room.typingUsers.length > 0)
z: 3 z: 3
Label { Label {
id: typingDisplay id: typingDisplay
anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 10 anchors.rightMargin: 10
anchors.bottom: parent.bottom color: palette.text
color: Nheko.colors.text text: room ? room.formatTypingUsers(room.typingUsers, palette.base) : ""
text: room ? room.formatTypingUsers(room.typingUsers, Nheko.colors.base) : ""
textFormat: Text.RichText textFormat: Text.RichText
} }
} }
} }

@ -4,7 +4,6 @@
import "./components" import "./components"
import "./ui" import "./ui"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.5 import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
@ -12,79 +11,85 @@ import im.nheko 1.0
Page { Page {
id: uploadPopup id: uploadPopup
visible: room && room.input.uploads.length > 0
Layout.preferredHeight: 200
clip: true
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 200
clip: true
padding: Nheko.paddingMedium padding: Nheko.paddingMedium
visible: room && room.input.uploads.length > 0
background: Rectangle {
color: palette.base
}
contentItem: ListView { contentItem: ListView {
id: uploadsList id: uploadsList
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
model: room ? room.input.uploads : undefined
orientation: ListView.Horizontal
spacing: Nheko.paddingMedium
width: Math.min(contentWidth, parent.availableWidth)
ScrollBar.horizontal: ScrollBar { ScrollBar.horizontal: ScrollBar {
id: scr id: scr
}
orientation: ListView.Horizontal
width: Math.min(contentWidth, parent.availableWidth)
model: room ? room.input.uploads : undefined
spacing: Nheko.paddingMedium
}
delegate: Pane { delegate: Pane {
padding: Nheko.paddingSmall id: pane
height: uploadPopup.availableHeight - buttons.height - (scr.visible ? scr.height : 0) height: uploadPopup.availableHeight - buttons.height - (scr.visible ? scr.height : 0)
padding: Nheko.paddingSmall
width: uploadPopup.availableHeight - buttons.height width: uploadPopup.availableHeight - buttons.height
background: Rectangle { background: Rectangle {
color: Nheko.colors.window color: palette.window
radius: Nheko.paddingMedium radius: Nheko.paddingMedium
} }
contentItem: ColumnLayout { contentItem: ColumnLayout {
Image { Image {
property string typeStr: switch (modelData.mediaType) {
case MediaUpload.Video:
return "video-file";
case MediaUpload.Audio:
return "music";
case MediaUpload.Image:
return "image";
default:
return "zip";
}
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
sourceSize.height: parent.availableHeight - namefield.height
sourceSize.width: parent.availableWidth
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
smooth: true
mipmap: true mipmap: true
smooth: true
property string typeStr: switch(modelData.mediaType) { source: (modelData.thumbnail != "") ? modelData.thumbnail : ("image://colorimage/:/icons/icons/ui/" + typeStr + ".svg?" + palette.buttonText)
case MediaUpload.Video: return "video-file"; sourceSize.height: pane.availableHeight - namefield.height
case MediaUpload.Audio: return "music"; sourceSize.width: pane.availableWidth
case MediaUpload.Image: return "image";
default: return "zip";
}
source: (modelData.thumbnail != "") ? modelData.thumbnail : ("image://colorimage/:/icons/icons/ui/"+typeStr+".svg?" + Nheko.colors.buttonText)
} }
MatrixTextField { MatrixTextField {
id: namefield id: namefield
Layout.fillWidth: true Layout.fillWidth: true
text: modelData.filename text: modelData.filename
onTextEdited: modelData.filename = text onTextEdited: modelData.filename = text
} }
} }
} }
} }
footer: DialogButtonBox { footer: DialogButtonBox {
id: buttons id: buttons
standardButtons: DialogButtonBox.Cancel standardButtons: DialogButtonBox.Cancel
Button {
text: qsTr("Upload %n file(s)", "", (room ? room.input.uploads.length : 0))
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
onAccepted: room.input.acceptUploads() onAccepted: room.input.acceptUploads()
onRejected: room.input.declineUploads() onRejected: room.input.declineUploads()
}
background: Rectangle { Button {
color: Nheko.colors.base DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
text: qsTr("Upload %n file(s)", "", (room ? room.input.uploads.length : 0))
}
} }
} }

@ -87,7 +87,7 @@ Container {
x: parent.preferredWidth x: parent.preferredWidth
z: 3 z: 3
CursorShape { NhekoCursorShape {
height: parent.height height: parent.height
width: container.splitterGrabMargin * 2 width: container.splitterGrabMargin * 2
x: -container.splitterGrabMargin x: -container.splitterGrabMargin

@ -11,11 +11,11 @@ import im.nheko 1.0
Rectangle { Rectangle {
id: tile id: tile
property color background: Nheko.colors.window property color background: palette.window
property color importantText: Nheko.colors.text property color importantText: palette.text
property color unimportantText: Nheko.colors.buttonText property color unimportantText: palette.buttonText
property color bubbleBackground: Nheko.colors.highlight property color bubbleBackground: palette.highlight
property color bubbleText: Nheko.colors.highlightedText property color bubbleText: palette.highlightedText
property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3)
required property string avatarUrl required property string avatarUrl
required property string title required property string title
@ -37,11 +37,11 @@ Rectangle {
PropertyChanges { PropertyChanges {
target: tile target: tile
background: Nheko.colors.dark background: palette.dark
importantText: Nheko.colors.brightText importantText: palette.brightText
unimportantText: Nheko.colors.brightText unimportantText: palette.brightText
bubbleBackground: Nheko.colors.highlight bubbleBackground: palette.highlight
bubbleText: Nheko.colors.highlightedText bubbleText: palette.highlightedText
} }
}, },
@ -51,11 +51,11 @@ Rectangle {
PropertyChanges { PropertyChanges {
target: tile target: tile
background: Nheko.colors.highlight background: palette.highlight
importantText: Nheko.colors.highlightedText importantText: palette.highlightedText
unimportantText: Nheko.colors.highlightedText unimportantText: palette.highlightedText
bubbleBackground: Nheko.colors.highlightedText bubbleBackground: palette.highlightedText
bubbleText: Nheko.colors.highlight bubbleText: palette.highlight
} }
} }

@ -2,11 +2,11 @@
// //
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtGraphicalEffects 1.12 import QtQuick
import QtQuick 2.9 import QtQuick.Controls
import QtQuick.Controls 2.5 import QtQuick.Layouts
import QtQuick.Layouts 1.2 import QtQuick.Effects
import im.nheko 1.0 import im.nheko
// FIXME(Nico): Don't use hardcoded colors. // FIXME(Nico): Don't use hardcoded colors.
Button { Button {
@ -18,14 +18,13 @@ Button {
property string iconImage: "" property string iconImage: ""
DropShadow { MultiEffect {
anchors.fill: control.background anchors.fill: control.background
horizontalOffset: 3 shadowHorizontalOffset: 3
verticalOffset: 3 shadowVerticalOffset: 3
radius: 8 shadowBlur: 8
samples: 17 shadowEnabled: true
cached: true shadowColor: "#80000000"
color: "#80000000"
source: control.background source: control.background
} }
@ -48,7 +47,7 @@ Button {
font.capitalization: Font.AllUppercase font.capitalization: Font.AllUppercase
font.pointSize: Math.ceil(fontMetrics.font.pointSize * 1.5) font.pointSize: Math.ceil(fontMetrics.font.pointSize * 1.5)
//font.capitalization: Font.AllUppercase //font.capitalization: Font.AllUppercase
color: Nheko.colors.light color: palette.light
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight elide: Text.ElideRight
@ -59,7 +58,7 @@ Button {
//height: control.contentItem.implicitHeight * 2 //height: control.contentItem.implicitHeight * 2
//width: control.contentItem.implicitWidth * 2 //width: control.contentItem.implicitWidth * 2
radius: height / 8 radius: height / 8
color: Qt.lighter(Nheko.colors.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1)) color: Qt.lighter(palette.dark, control.down ? 1.4 : (control.hovered ? 1.2 : 1))
} }
} }

@ -32,7 +32,7 @@ Dialog {
] ]
background: Rectangle { background: Rectangle {
color: Nheko.colors.window color: palette.window
border.color: Nheko.theme.separator border.color: Nheko.theme.separator
border.width: 1 border.width: 1
radius: Nheko.paddingSmall radius: Nheko.paddingSmall

@ -13,15 +13,15 @@ TabButton {
text: control.text text: control.text
font: control.font font: control.font
opacity: enabled ? 1.0 : 0.3 opacity: enabled ? 1.0 : 0.3
color: control.down ? Nheko.colors.highlightedText : Nheko.colors.text color: control.down ? palette.highlightedText : palette.text
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight elide: Text.ElideRight
} }
background: Rectangle { background: Rectangle {
border.color: control.down ? Nheko.colors.highlight : Nheko.theme.separator border.color: control.down ? palette.highlight : Nheko.theme.separator
color: control.checked ? Nheko.colors.highlight : Nheko.colors.base color: control.checked ? palette.highlight : palette.base
border.width: 1 border.width: 1
radius: 2 radius: 2
} }

@ -15,6 +15,7 @@ Rectangle {
required property color bubbleTextColor required property color bubbleTextColor
property bool mayBeVisible: true property bool mayBeVisible: true
property alias font: notificationBubbleText.font property alias font: notificationBubbleText.font
baselineOffset: notificationBubbleText.baseline - bubbleRoot.top
visible: mayBeVisible && notificationCount > 0 visible: mayBeVisible && notificationCount > 0
implicitHeight: notificationBubbleText.height + Nheko.paddingMedium implicitHeight: notificationBubbleText.height + Nheko.paddingMedium

@ -47,9 +47,9 @@ Item {
width: dragArea.width; height: actualDelegate.implicitHeight + 4 width: dragArea.width; height: actualDelegate.implicitHeight + 4
border.width: dragArea.enabled ? 1 : 0 border.width: dragArea.enabled ? 1 : 0
border.color: Nheko.colors.highlight border.color: palette.highlight
color: dragArea.held ? Nheko.colors.highlight : Nheko.colors.base color: dragArea.held ? palette.highlight : palette.base
Behavior on color { ColorAnimation { duration: 100 } } Behavior on color { ColorAnimation { duration: 100 } }
radius: 2 radius: 2
@ -105,10 +105,6 @@ Item {
clip: true clip: true
anchors { fill: parent; margins: 2 } anchors { fill: parent; margins: 2 }
ScrollHelper {
flickable: parent
anchors.fill: parent
}
model: visualModel model: visualModel

@ -11,8 +11,8 @@ AbstractButton {
id: button id: button
property alias cursor: mouseArea.cursorShape property alias cursor: mouseArea.cursorShape
property color highlightColor: Nheko.colors.highlight property color highlightColor: palette.highlight
property color buttonTextColor: Nheko.colors.buttonText property color buttonTextColor: palette.buttonText
focusPolicy: Qt.NoFocus focusPolicy: Qt.NoFocus
width: buttonText.implicitWidth width: buttonText.implicitWidth
@ -32,7 +32,7 @@ AbstractButton {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
CursorShape { NhekoCursorShape {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent

@ -36,7 +36,7 @@ ItemDelegate {
Label { Label {
Layout.fillWidth: true Layout.fillWidth: true
text: displayName text: displayName
color: TimelineManager.userColor(userid, Nheko.colors.window) color: TimelineManager.userColor(userid, palette.window)
font.pointSize: fontMetrics.font.pointSize font.pointSize: fontMetrics.font.pointSize
} }
@ -44,7 +44,7 @@ ItemDelegate {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
text: userid text: userid
color: Nheko.colors.buttonText color: palette.buttonText
font.pointSize: fontMetrics.font.pointSize * 0.9 font.pointSize: fontMetrics.font.pointSize * 0.9
} }
} }

@ -18,7 +18,7 @@ Rectangle {
width: parent.width? parent.width : 0 width: parent.width? parent.width : 0
implicitWidth: encryptedText.implicitWidth+24+Nheko.paddingMedium*3 // Column doesn't provide a useful implicitWidth, should be replaced by ColumnLayout implicitWidth: encryptedText.implicitWidth+24+Nheko.paddingMedium*3 // Column doesn't provide a useful implicitWidth, should be replaced by ColumnLayout
height: contents.implicitHeight + Nheko.paddingMedium * 2 height: contents.implicitHeight + Nheko.paddingMedium * 2
color: Nheko.colors.alternateBase color: palette.alternateBase
RowLayout { RowLayout {
id: contents id: contents
@ -58,12 +58,11 @@ Rectangle {
return qsTr("Unknown decryption error"); return qsTr("Unknown decryption error");
} }
} }
color: Nheko.colors.text color: palette.text
width: parent.width width: parent.width
} }
Button { Button {
palette: Nheko.colors
visible: encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex visible: encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex
text: qsTr("Request key") text: qsTr("Request key")
onClicked: room.requestKeyForEvent(eventId) onClicked: room.requestKeyForEvent(eventId)

@ -4,7 +4,6 @@
import ".." import ".."
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import im.nheko 1.0 import im.nheko 1.0
@ -15,9 +14,8 @@ Rectangle {
radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium radius: fontMetrics.lineSpacing / 2 + Nheko.paddingMedium
width: parent.width ? Math.min(parent.width, 700) : 0 width: parent.width ? Math.min(parent.width, 700) : 0
anchors.horizontalCenter: parent.horizontalCenter
height: contents.implicitHeight + Nheko.paddingMedium * 2 height: contents.implicitHeight + Nheko.paddingMedium * 2
color: Nheko.colors.alternateBase color: palette.alternateBase
border.color: Nheko.theme.green border.color: Nheko.theme.green
border.width: 2 border.width: 2
@ -31,8 +29,8 @@ Rectangle {
Image { Image {
source: "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green source: "image://colorimage/:/icons/icons/ui/shield-filled-checkmark.svg?" + Nheko.theme.green
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
width: 24 Layout.preferredWidth: 24
height: width Layout.preferredHeight: 24
} }
Column { Column {
@ -43,13 +41,13 @@ Rectangle {
text: qsTr("%1 enabled end-to-end encryption").arg(r.username) text: qsTr("%1 enabled end-to-end encryption").arg(r.username)
font.bold: true font.bold: true
font.pointSize: 14 font.pointSize: 14
color: Nheko.colors.text color: palette.text
width: parent.width width: parent.width
} }
MatrixText { MatrixText {
text: qsTr("Encryption keeps your messages safe by only allowing the people you sent the message to to read it. For extra security, if you want to make sure you are talking to the right people, you can verify them in real life.") text: qsTr("Encryption keeps your messages safe by only allowing the people you sent the message to to read it. For extra security, if you want to make sure you are talking to the right people, you can verify them in real life.")
color: Nheko.colors.text color: palette.text
width: parent.width width: parent.width
} }

@ -11,14 +11,13 @@ Item {
required property string filename required property string filename
required property string filesize required property string filesize
height: row.height + (Settings.bubbles? 16: 24) height: rowa.height + (Settings.bubbles? 16: 24)
width: parent.width implicitWidth: rowa.implicitWidth + metadataWidth
implicitWidth: row.implicitWidth+metadataWidth
property int metadataWidth property int metadataWidth
property bool fitsMetadata: true property bool fitsMetadata: true
RowLayout { RowLayout {
id: row id: rowa
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - (Settings.bubbles? 16 : 24) width: parent.width - (Settings.bubbles? 16 : 24)
@ -27,7 +26,7 @@ Item {
Rectangle { Rectangle {
id: button id: button
color: Nheko.colors.light color: palette.light
radius: 22 radius: 22
height: 44 height: 44
width: 44 width: 44
@ -50,7 +49,7 @@ Item {
gesturePolicy: TapHandler.ReleaseWithinBounds gesturePolicy: TapHandler.ReleaseWithinBounds
} }
CursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
@ -67,7 +66,7 @@ Item {
text: filename text: filename
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight elide: Text.ElideRight
color: Nheko.colors.text color: palette.text
} }
Text { Text {
@ -77,7 +76,7 @@ Item {
text: filesize text: filesize
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight elide: Text.ElideRight
color: Nheko.colors.text color: palette.text
} }
} }
@ -85,7 +84,7 @@ Item {
} }
Rectangle { Rectangle {
color: Nheko.colors.alternateBase color: palette.alternateBase
z: -1 z: -1
radius: 10 radius: 10
anchors.fill: parent anchors.fill: parent

@ -22,7 +22,7 @@ AbstractButton {
property int tempWidth: originalWidth < 1? 400: originalWidth property int tempWidth: originalWidth < 1? 400: originalWidth
implicitWidth: Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1)) implicitWidth: Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1))
width: Math.min(parent.width,implicitWidth) width: Math.min(parent?.width ?? 2000,implicitWidth)
height: width*proportionalHeight height: width*proportionalHeight
hoverEnabled: true hoverEnabled: true
@ -106,14 +106,14 @@ AbstractButton {
] ]
property int metadataWidth property int metadataWidth
property bool fitsMetadata: (parent.width - width) > metadataWidth+4 property bool fitsMetadata: parent != null ? (parent.width - width) > metadataWidth+4 : false
Image { Image {
id: img id: img
visible: !mxcimage.loaded visible: !mxcimage.loaded
anchors.fill: parent anchors.fill: parent
source: url.replace("mxc://", "image://MxcImage/") + "?scale" source: url != "" ? (url.replace("mxc://", "image://MxcImage/") + "?scale") : ""
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
smooth: true smooth: true
@ -137,7 +137,7 @@ AbstractButton {
id: blurhash_ id: blurhash_
anchors.fill: parent anchors.fill: parent
source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + Nheko.colors.buttonText) source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + palette.buttonText)
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
sourceSize.width: parent.width * Screen.devicePixelRatio sourceSize.width: parent.width * Screen.devicePixelRatio
@ -158,7 +158,7 @@ AbstractButton {
width: parent.width width: parent.width
implicitHeight: imgcaption.implicitHeight implicitHeight: imgcaption.implicitHeight
anchors.bottom: overlay.bottom anchors.bottom: overlay.bottom
color: Nheko.colors.window color: palette.window
opacity: 0.75 opacity: 0.75
} }
@ -171,7 +171,7 @@ AbstractButton {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
// See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530 // See this MSC: https://github.com/matrix-org/matrix-doc/pull/2530
text: filename ? filename : body text: filename ? filename : body
color: Nheko.colors.text color: palette.text
} }
} }

@ -13,7 +13,7 @@ Item {
required property bool isReply required property bool isReply
property bool keepFullText: !isReply property bool keepFullText: !isReply
property alias child: chooser.child property alias child: chooser.child
implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : 0 //implicitWidth: chooser.child?.implicitWidth ?? 0
required property double proportionalHeight required property double proportionalHeight
required property int type required property int type
required property string typeString required property string typeString
@ -39,6 +39,8 @@ Item {
property bool fitsMetadata: (chooser.child && chooser.child.fitsMetadata) ? chooser.child.fitsMetadata : false property bool fitsMetadata: (chooser.child && chooser.child.fitsMetadata) ? chooser.child.fitsMetadata : false
property int metadataWidth property int metadataWidth
implicitWidth: chooser.child?.implicitWidth
height: chooser.child ? chooser.child.height : Nheko.paddingLarge height: chooser.child ? chooser.child.height : Nheko.paddingLarge
DelegateChooser { DelegateChooser {
@ -48,7 +50,7 @@ Item {
roleValue: type roleValue: type
//anchors.fill: parent //anchors.fill: parent
width: parent.width? parent.width: 0 // this should get rid of "cannot read property 'width' of null" width: parent?.width ?? 0 // this should get rid of "cannot read property 'width' of null"
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.UnknownEvent roleValue: MtxEvent.UnknownEvent
@ -78,7 +80,6 @@ Item {
} }
Button { Button {
palette: Nheko.colors
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("Go to replacement room") text: qsTr("Go to replacement room")
onClicked: room.joinReplacementRoom(eventId) onClicked: room.joinReplacementRoom(eventId)
@ -149,7 +150,7 @@ Item {
NoticeMessage { NoticeMessage {
formatted: TimelineManager.escapeEmoji(d.userName) + " " + d.formattedBody formatted: TimelineManager.escapeEmoji(d.userName) + " " + d.formattedBody
color: TimelineManager.userColor(d.userId, Nheko.colors.base) color: TimelineManager.userColor(d.userId, palette.base)
body: d.body body: d.body
isOnlyEmoji: d.isOnlyEmoji isOnlyEmoji: d.isOnlyEmoji
isReply: d.isReply isReply: d.isReply
@ -281,6 +282,20 @@ Item {
} }
DelegateChoice {
roleValue: MtxEvent.ServerAcl
NoticeMessage {
body: formatted
isOnlyEmoji: false
isReply: d.isReply
keepFullText: d.keepFullText
isStateEvent: d.isStateEvent
formatted: qsTr("%1 changed which servers are allowed in this room.").arg(d.userName)
}
}
DelegateChoice { DelegateChoice {
roleValue: MtxEvent.Name roleValue: MtxEvent.Name
@ -603,7 +618,7 @@ Item {
roleValue: MtxEvent.Member roleValue: MtxEvent.Member
ColumnLayout { ColumnLayout {
width: parent.width width: parent?.width ?? 100
NoticeMessage { NoticeMessage {
body: formatted body: formatted
@ -617,7 +632,6 @@ Item {
Button { Button {
visible: d.relatedEventCacheBuster, room.showAcceptKnockButton(d.eventId) visible: d.relatedEventCacheBuster, room.showAcceptKnockButton(d.eventId)
palette: Nheko.colors
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: qsTr("Allow them in") text: qsTr("Allow them in")
onClicked: room.acceptKnock(eventId) onClicked: room.acceptKnock(eventId)

@ -9,7 +9,7 @@ import im.nheko 1.0
TextMessage { TextMessage {
property bool isStateEvent property bool isStateEvent
font.italic: true font.italic: true
color: Nheko.colors.buttonText color: palette.buttonText
font.pointSize: isStateEvent? 0.8*Settings.fontSize : Settings.fontSize font.pointSize: isStateEvent? 0.8*Settings.fontSize : Settings.fontSize
horizontalAlignment: isStateEvent? Text.AlignHCenter : undefined horizontalAlignment: isStateEvent? Text.AlignHCenter : undefined
} }

@ -8,14 +8,14 @@ import im.nheko 1.0
Label { Label {
property bool isStateEvent property bool isStateEvent
color: Nheko.colors.text color: palette.text
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
height: Math.round(fontMetrics.height * 1.4) height: Math.round(fontMetrics.height * 1.4)
width: contentWidth * 1.2 width: contentWidth * 1.2
background: Rectangle { background: Rectangle {
radius: parent.height / 2 radius: parent.height / 2
color: Nheko.colors.alternateBase color: palette.alternateBase
} }
} }

@ -10,5 +10,5 @@ MatrixText {
text: qsTr("unimplemented event: ") + typeString text: qsTr("unimplemented event: ") + typeString
// width: parent.width // width: parent.width
color: Nheko.inactiveColors.text color: palette.inactive.text
} }

@ -4,11 +4,11 @@
import "../" import "../"
import "../ui/media" import "../ui/media"
import QtMultimedia 5.15 import QtMultimedia
import QtQuick 2.15 import QtQuick
import QtQuick.Controls 2.15 import QtQuick.Controls
import QtQuick.Layouts 1.15 import QtQuick.Layouts
import im.nheko 1.0 import im.nheko
Item { Item {
id: content id: content
@ -25,9 +25,9 @@ Item {
property double divisor: isReply ? 4 : 2 property double divisor: isReply ? 4 : 2
property int tempWidth: originalWidth < 1? 400: originalWidth property int tempWidth: originalWidth < 1? 400: originalWidth
implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1)) : 500 implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1)) : 500
width: Math.min(parent.width, implicitWidth) width: Math.min(parent?.width ?? implicitWidth, implicitWidth)
height: (type == MtxEvent.VideoMessage ? width*proportionalHeight : 80) + fileInfoLabel.height height: (type == MtxEvent.VideoMessage ? width*proportionalHeight : 80) + fileInfoLabel.height
implicitHeight: height //implicitHeight: height
property int metadataWidth property int metadataWidth
property bool fitsMetadata: (parent.width - fileInfoLabel.width) > metadataWidth+4 property bool fitsMetadata: (parent.width - fileInfoLabel.width) > metadataWidth+4
@ -36,18 +36,18 @@ Item {
id: mxcmedia id: mxcmedia
// TODO: Show error in overlay or so? // TODO: Show error in overlay or so?
onError: console.log(error)
roomm: room roomm: room
// desiredVolume is a float from 0.0 -> 1.0, MediaPlayer volume is an int from 0 to 100 audioOutput: AudioOutput {
// this value automatically gets clamped for us between these two values.
volume: mediaControls.desiredVolume * 100
muted: mediaControls.muted muted: mediaControls.muted
volume: mediaControls.desiredVolume
}
videoOutput: videoOutput
} }
Rectangle { Rectangle {
id: videoContainer id: videoContainer
color: type == MtxEvent.VideoMessage ? Nheko.colors.window : "transparent" color: type == MtxEvent.VideoMessage ? palette.window : "transparent"
width: parent.width width: parent.width
height: parent.height - fileInfoLabel.height height: parent.height - fileInfoLabel.height
@ -57,7 +57,7 @@ Item {
Image { Image {
anchors.fill: parent anchors.fill: parent
source: thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : "image://colorimage/:/icons/icons/ui/video-file.svg?" + Nheko.colors.windowText source: thumbnailUrl ? thumbnailUrl.replace("mxc://", "image://MxcImage/") + "?scale" : "image://colorimage/:/icons/icons/ui/video-file.svg?" + palette.windowText
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
@ -68,43 +68,40 @@ Item {
clip: true clip: true
anchors.fill: parent anchors.fill: parent
fillMode: VideoOutput.PreserveAspectFit fillMode: VideoOutput.PreserveAspectFit
source: mxcmedia
flushMode: VideoOutput.FirstFrame
orientation: mxcmedia.orientation orientation: mxcmedia.orientation
} }
} }
}
MediaControls { MediaControls {
id: mediaControls id: mediaControls
anchors.left: content.left anchors.left: videoContainer.left
anchors.right: content.right anchors.right: videoContainer.right
anchors.bottom: fileInfoLabel.top anchors.bottom: videoContainer.bottom
playingVideo: type == MtxEvent.VideoMessage playingVideo: type == MtxEvent.VideoMessage
positionValue: mxcmedia.position positionValue: mxcmedia.position
duration: mediaLoaded ? mxcmedia.duration : content.duration duration: mediaLoaded ? mxcmedia.duration : content.duration
mediaLoaded: mxcmedia.loaded mediaLoaded: mxcmedia.loaded
mediaState: mxcmedia.state mediaState: mxcmedia.playbackState
onPositionChanged: mxcmedia.position = position onPositionChanged: mxcmedia.position = position
onPlayPauseActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play() onPlayPauseActivated: mxcmedia.playbackState == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
onLoadActivated: mxcmedia.eventId = eventId onLoadActivated: mxcmedia.eventId = eventId
} }
}
// information about file name and file size // information about file name and file size
Label { Label {
id: fileInfoLabel id: fileInfoLabel
anchors.bottom: content.bottom anchors.top: videoContainer.bottom
text: body + " [" + filesize + "]" text: body + " [" + filesize + "]"
textFormat: Text.RichText textFormat: Text.RichText
elide: Text.ElideRight elide: Text.ElideRight
color: Nheko.colors.text color: palette.text
background: Rectangle { background: Rectangle {
color: Nheko.colors.base color: palette.base
} }
} }

@ -13,7 +13,7 @@ Rectangle{
implicitWidth: redactedLayout.implicitWidth + 2 * Nheko.paddingMedium implicitWidth: redactedLayout.implicitWidth + 2 * Nheko.paddingMedium
width: Math.min(parent.width,implicitWidth+1) width: Math.min(parent.width,implicitWidth+1)
radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
color: Nheko.colors.alternateBase color: palette.alternateBase
property int metadataWidth property int metadataWidth
property bool fitsMetadata: parent.width - redactedLayout.width > metadataWidth + 4 property bool fitsMetadata: parent.width - redactedLayout.width > metadataWidth + 4
@ -28,7 +28,7 @@ Rectangle{
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.preferredWidth: fontMetrics.font.pixelSize Layout.preferredWidth: fontMetrics.font.pixelSize
Layout.preferredHeight: fontMetrics.font.pixelSize Layout.preferredHeight: fontMetrics.font.pixelSize
source: "image://colorimage/:/icons/icons/ui/delete.svg?" + Nheko.colors.text source: "image://colorimage/:/icons/icons/ui/delete.svg?" + palette.text
} }
Label { Label {
id: redactedLabel id: redactedLabel
@ -39,7 +39,7 @@ Rectangle{
property var redactedPair: room.formatRedactedEvent(eventId) property var redactedPair: room.formatRedactedEvent(eventId)
text: redactedPair["first"] text: redactedPair["first"]
wrapMode: Label.WordWrap wrapMode: Label.WordWrap
color: Nheko.colors.text color: palette.text
ToolTip.text: redactedPair["second"] ToolTip.text: redactedPair["second"]
ToolTip.visible: hh.hovered ToolTip.visible: hh.hovered

@ -43,7 +43,7 @@ AbstractButton {
implicitHeight: replyContainer.height implicitHeight: replyContainer.height
implicitWidth: visible? colorLine.width+Math.max(replyContainer.implicitWidth,userName_.fullTextWidth) : 0 // visible? seems to be causing issues implicitWidth: visible? colorLine.width+Math.max(replyContainer.implicitWidth,userName_.fullTextWidth) : 0 // visible? seems to be causing issues
CursorShape { NhekoCursorShape {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
@ -54,7 +54,7 @@ AbstractButton {
anchors.top: replyContainer.top anchors.top: replyContainer.top
anchors.bottom: replyContainer.bottom anchors.bottom: replyContainer.bottom
width: 4 width: 4
color: TimelineManager.userColor(userId, Nheko.colors.base) color: TimelineManager.userColor(userId, palette.base)
} }
onClicked: { onClicked: {
@ -135,8 +135,8 @@ AbstractButton {
z: -1 z: -1
anchors.fill: replyContainer anchors.fill: replyContainer
property color userColor: TimelineManager.userColor(userId, Nheko.colors.base) property color userColor: TimelineManager.userColor(userId, palette.base)
property color bgColor: Nheko.colors.base property color bgColor: palette.base
color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1)) color: Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.1))
} }

@ -14,20 +14,19 @@ MatrixText {
required property string formatted required property string formatted
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body
property int metadataWidth property int metadataWidth
property bool fitsMetadata: positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4) property bool fitsMetadata: false //positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4)
// table border-collapse doesn't seem to work // table border-collapse doesn't seem to work
text: " text: "
<style type=\"text/css\"> <style type=\"text/css\">
a { color:" + Nheko.colors.link + ";} code { background-color: " + palette.alternateBase + "; white-space: pre-wrap; }
code { background-color: " + Nheko.colors.alternateBase + "; white-space: pre-wrap; } pre { background-color: " + palette.alternateBase + "; white-space: pre-wrap; }
pre { background-color: " + Nheko.colors.alternateBase + "; white-space: pre-wrap; }
table { table {
border-width: 1px; border-width: 1px;
border-collapse: collapse; border-collapse: collapse;
border-style: solid; border-style: solid;
border-color: " + Nheko.colors.text + "; border-color: " + palette.text + ";
background-color: " + Nheko.colors.alternateBase + "; background-color: " + palette.alternateBase + ";
} }
table th, table th,
table td { table td {
@ -36,18 +35,18 @@ MatrixText {
blockquote { margin-left: 1em; } blockquote { margin-left: 1em; }
" + (!Settings.mobileMode ? "span[data-mx-spoiler] { " + (!Settings.mobileMode ? "span[data-mx-spoiler] {
color: transparent; color: transparent;
background-color: " + Nheko.colors.text + "; background-color: " + palette.text + ";
}" : "") + // TODO(Nico): Figure out how to support mobile }" : "") + // TODO(Nico): Figure out how to support mobile
"</style> "</style>
" + formatted.replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>") " + formatted.replace(/<del>/g, "<s>").replace(/<\/del>/g, "</s>").replace(/<strike>/g, "<s>").replace(/<\/strike>/g, "</s>")
width: parent.width width: parent?.width ?? 0
height: !keepFullText ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight height: !keepFullText ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : implicitHeight
clip: !keepFullText clip: !keepFullText
selectByMouse: !Settings.mobileMode && !isReply selectByMouse: !Settings.mobileMode && !isReply
enabled: !Settings.mobileMode enabled: !Settings.mobileMode
font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize font.pointSize: (Settings.enlargeEmojiOnlyMessages && isOnlyEmoji > 0 && isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
CursorShape { NhekoCursorShape {
enabled: isReply enabled: isReply
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor

@ -15,8 +15,7 @@ ApplicationWindow {
onClosing: VerificationManager.removeVerificationFlow(flow) onClosing: VerificationManager.removeVerificationFlow(flow)
title: stack.currentItem ? (stack.currentItem.title_ || "") : "" title: stack.currentItem ? (stack.currentItem.title_ || "") : ""
modality: Qt.NonModal modality: Qt.NonModal
palette: Nheko.colors color: palette.window
color: Nheko.colors.window
//height: stack.currentItem.implicitHeight //height: stack.currentItem.implicitHeight
minimumHeight: stack.currentItem.implicitHeight + 2 * Nheko.paddingLarge minimumHeight: stack.currentItem.implicitHeight + 2 * Nheko.paddingLarge
height: stack.currentItem.implicitHeight + 2 * Nheko.paddingMedium height: stack.currentItem.implicitHeight + 2 * Nheko.paddingMedium
@ -25,7 +24,7 @@ ApplicationWindow {
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
background: Rectangle { background: Rectangle {
color: Nheko.colors.window color: palette.window
} }

@ -17,7 +17,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap wrapMode: Text.Wrap
text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!") text: qsTr("Please verify the following digits. You should see the same numbers on both sides. If they differ, please press 'They do not match!' to abort verification!")
color: Nheko.colors.text color: palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
@ -28,19 +28,19 @@ ColumnLayout {
Label { Label {
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[0] text: flow.sasList[0]
color: Nheko.colors.text color: palette.text
} }
Label { Label {
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[1] text: flow.sasList[1]
color: Nheko.colors.text color: palette.text
} }
Label { Label {
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
text: flow.sasList[2] text: flow.sasList[2]
color: Nheko.colors.text color: palette.text
} }
} }

@ -17,7 +17,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap wrapMode: Text.Wrap
text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!") text: qsTr("Please verify the following emoji. You should see the same emoji on both sides. If they differ, please press 'They do not match!' to abort verification!")
color: Nheko.colors.text color: palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
@ -373,13 +373,13 @@ ColumnLayout {
text: col.emoji.emoji text: col.emoji.emoji
font.pixelSize: Qt.application.font.pixelSize * 2 font.pixelSize: Qt.application.font.pixelSize * 2
font.family: Settings.emojiFont font.family: Settings.emojiFont
color: Nheko.colors.text color: palette.text
} }
Label { Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
text: col.emoji.description text: col.emoji.description
color: Nheko.colors.text color: palette.text
} }
} }
@ -396,7 +396,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap wrapMode: Text.Wrap
text: qsTr("The displayed emoji might look different in different clients if a different font is used. Similarly they might be translated into different languages. Nonetheless they should depict one of 64 different objects or animals. For example a lion and a cat are different, but a cat is the same even if one client just shows a cat face, while another client shows a full cat body.") text: qsTr("The displayed emoji might look different in different clients if a different font is used. Similarly they might be translated into different languages. Nonetheless they should depict one of 64 different objects or animals. For example a lion and a cat are different, but a cat is the same even if one client just shows a cat face, while another client shows a full cat body.")
color: Nheko.colors.text color: palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }

@ -35,7 +35,7 @@ ColumnLayout {
return qsTr("Unknown verification error."); return qsTr("Unknown verification error.");
} }
} }
color: Nheko.colors.text color: palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }

@ -36,7 +36,7 @@ ColumnLayout {
return qsTr("Your device (%1) has requested to be verified.").arg(flow.deviceId); return qsTr("Your device (%1) has requested to be verified.").arg(flow.deviceId);
} }
} }
color: Nheko.colors.text color: palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }

@ -19,7 +19,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.Wrap wrapMode: Text.Wrap
text: qsTr("Verification successful! Both sides verified their devices!") text: qsTr("Verification successful! Both sides verified their devices!")
color: Nheko.colors.text color: palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }

@ -30,14 +30,14 @@ ColumnLayout {
return ""; return "";
} }
} }
color: Nheko.colors.text color: palette.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
Item { Layout.fillHeight: true; } Item { Layout.fillHeight: true; }
Spinner { Spinner {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
foreground: Nheko.colors.mid foreground: palette.mid
} }
Item { Layout.fillHeight: true; } Item { Layout.fillHeight: true; }

@ -41,7 +41,7 @@ ApplicationWindow {
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: false Layout.fillHeight: false
color: Nheko.colors.text color: palette.text
Layout.bottomMargin: Nheko.paddingMedium Layout.bottomMargin: Nheko.paddingMedium
} }
@ -53,10 +53,6 @@ ApplicationWindow {
clip: true clip: true
ScrollHelper {
flickable: parent
anchors.fill: parent
}
model: editingModel model: editingModel
spacing: 4 spacing: 4
@ -69,7 +65,7 @@ ApplicationWindow {
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.name text: model.name
color: model.isPublished ? Nheko.colors.text : Nheko.theme.error color: model.isPublished ? palette.text : Nheko.theme.error
textFormat: Text.PlainText textFormat: Text.PlainText
} }
@ -78,8 +74,8 @@ ApplicationWindow {
Layout.margins: 2 Layout.margins: 2
image: ":/icons/icons/ui/star.svg" image: ":/icons/icons/ui/star.svg"
hoverEnabled: true hoverEnabled: true
buttonTextColor: model.isCanonical ? Nheko.colors.highlight : Nheko.colors.text buttonTextColor: model.isCanonical ? palette.highlight : palette.text
highlightColor: editingModel.canAdvertize ? Nheko.colors.highlight : buttonTextColor highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: model.isCanonical ? qsTr("Primary alias") : qsTr("Make primary alias") ToolTip.text: model.isCanonical ? qsTr("Primary alias") : qsTr("Make primary alias")
@ -92,8 +88,8 @@ ApplicationWindow {
Layout.margins: 2 Layout.margins: 2
image: ":/icons/icons/ui/building-shop.svg" image: ":/icons/icons/ui/building-shop.svg"
hoverEnabled: true hoverEnabled: true
buttonTextColor: model.isAdvertized ? Nheko.colors.highlight : Nheko.colors.text buttonTextColor: model.isAdvertized ? palette.highlight : palette.text
highlightColor: editingModel.canAdvertize ? Nheko.colors.highlight : buttonTextColor highlightColor: editingModel.canAdvertize ? palette.highlight : buttonTextColor
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Advertise as an alias in this room") ToolTip.text: qsTr("Advertise as an alias in this room")
@ -106,7 +102,7 @@ ApplicationWindow {
Layout.margins: 2 Layout.margins: 2
image: ":/icons/icons/ui/room-directory.svg" image: ":/icons/icons/ui/room-directory.svg"
hoverEnabled: true hoverEnabled: true
buttonTextColor: model.isPublished ? Nheko.colors.highlight : Nheko.colors.text buttonTextColor: model.isPublished ? palette.highlight : palette.text
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Publish in room directory") ToolTip.text: qsTr("Publish in room directory")
@ -139,7 +135,7 @@ ApplicationWindow {
Layout.fillWidth: true Layout.fillWidth: true
selectByMouse: true selectByMouse: true
font.pixelSize: fontMetrics.font.pixelSize font.pixelSize: fontMetrics.font.pixelSize
color: Nheko.colors.text color: palette.text
placeholderText: qsTr("#new-alias:server.tld") placeholderText: qsTr("#new-alias:server.tld")
Component.onCompleted: forceActiveFocus() Component.onCompleted: forceActiveFocus()

@ -20,8 +20,7 @@ ApplicationWindow {
minimumHeight: 450 minimumHeight: 450
width: 450 width: 450
height: 680 height: 680
palette: Nheko.colors color: palette.window
color: Nheko.colors.window
modality: Qt.NonModal modality: Qt.NonModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
title: qsTr("Allowed rooms settings") title: qsTr("Allowed rooms settings")
@ -42,7 +41,7 @@ ApplicationWindow {
font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1) font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: false Layout.fillHeight: false
color: Nheko.colors.text color: palette.text
Layout.bottomMargin: Nheko.paddingMedium Layout.bottomMargin: Nheko.paddingMedium
} }
@ -54,10 +53,6 @@ ApplicationWindow {
clip: true clip: true
ScrollHelper {
flickable: parent
anchors.fill: parent
}
model: roomSettings.allowedRoomsModel model: roomSettings.allowedRoomsModel
spacing: 4 spacing: 4
@ -72,14 +67,14 @@ ApplicationWindow {
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.name text: model.name
color: Nheko.colors.text color: palette.text
textFormat: Text.PlainText textFormat: Text.PlainText
} }
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: model.isParent ? qsTr("Parent community") : qsTr("Other room") text: model.isParent ? qsTr("Parent community") : qsTr("Other room")
color: Nheko.colors.buttonText color: palette.buttonText
textFormat: Text.PlainText textFormat: Text.PlainText
} }
} }
@ -122,7 +117,7 @@ ApplicationWindow {
placeholderText: qsTr("Enter additional rooms not in the list yet...") placeholderText: qsTr("Enter additional rooms not in the list yet...")
color: Nheko.colors.text color: palette.text
onTextEdited: { onTextEdited: {
roomCompleter.completer.searchString = text; roomCompleter.completer.searchString = text;
} }

@ -19,8 +19,7 @@ ApplicationWindow {
title: summary.isSpace ? qsTr("Confirm community join") : qsTr("Confirm room join") title: summary.isSpace ? qsTr("Confirm community join") : qsTr("Confirm room join")
modality: Qt.WindowModal modality: Qt.WindowModal
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
palette: Nheko.colors color: palette.window
color: Nheko.colors.window
width: 350 width: 350
height: content.implicitHeight + Nheko.paddingLarge + footer.implicitHeight height: content.implicitHeight + Nheko.paddingLarge + footer.implicitHeight
@ -48,7 +47,7 @@ ApplicationWindow {
Spinner { Spinner {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
visible: !summary.isLoaded visible: !summary.isLoaded
foreground: Nheko.colors.mid foreground: palette.mid
running: !summary.isLoaded running: !summary.isLoaded
} }
@ -57,7 +56,7 @@ ApplicationWindow {
textFormat: TextEdit.RichText textFormat: TextEdit.RichText
text: summary.roomName text: summary.roomName
font.pixelSize: fontMetrics.font.pixelSize * 2 font.pixelSize: fontMetrics.font.pixelSize * 2
color: Nheko.colors.text color: palette.text
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
@ -70,7 +69,7 @@ ApplicationWindow {
textFormat: TextEdit.RichText textFormat: TextEdit.RichText
text: summary.roomid text: summary.roomid
font.pixelSize: fontMetrics.font.pixelSize * 0.8 font.pixelSize: fontMetrics.font.pixelSize * 0.8
color: Nheko.colors.text color: palette.text
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
@ -96,7 +95,7 @@ ApplicationWindow {
readOnly: true readOnly: true
textFormat: TextEdit.RichText textFormat: TextEdit.RichText
text: summary.roomTopic text: summary.roomTopic
color: Nheko.colors.text color: palette.text
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true Layout.fillWidth: true
@ -109,7 +108,7 @@ ApplicationWindow {
id: promptLabel id: promptLabel
text: summary.isKnockOnly ? qsTr("This room can't be joined directly. You can, however, knock on the room and room members can accept or decline this join request. You can additionally provide a reason for them to let you in below:") : qsTr("Do you want to join this room? You can optionally add a reason below:") text: summary.isKnockOnly ? qsTr("This room can't be joined directly. You can, however, knock on the room and room members can accept or decline this join request. You can additionally provide a reason for them to let you in below:") : qsTr("Do you want to join this room? You can optionally add a reason below:")
color: Nheko.colors.text color: palette.text
Layout.fillWidth: true Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap wrapMode: Text.Wrap

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save