Compare commits

..

No commits in common. "master" and "move-state" have entirely different histories.

280 changed files with 18862 additions and 37494 deletions

View File

@ -7,24 +7,11 @@
set -eu set -eu
FILES=$(find src -type f \( -iname "*.cpp" -o -iname "*.h" \)) FILES=$(find src -type f -type f \( -iname "*.cpp" -o -iname "*.h" \))
for f in $FILES for f in $FILES
do do
clang-format -i "$f" clang-format -i "$f"
done; done;
QMLFORMAT_PATH=$(command -v qmlformat || true)
if [ -n "$QMLFORMAT_PATH" ]; then
QML_FILES=$(find resources -type f -iname "*.qml")
for f in $QML_FILES
do
$QMLFORMAT_PATH -i "$f"
done;
else
echo "qmlformat not found; skipping qml formatting"
fi
git diff --exit-code git diff --exit-code

View File

@ -1,14 +0,0 @@
#!/usr/bin/env sh
# Runs the license update
# Return codes:
# - 1 there are files to be formatted
# - 0 everything looks fine
set -eu
FILES=$(find src resources/qml -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.qml" \))
reuse addheader --copyright="Nheko Contributors" --license="GPL-3.0-or-later" $FILES
git diff --exit-code

View File

@ -11,13 +11,13 @@ build-gcc7:
variables: variables:
CXX: g++-7 CXX: g++-7
CC: gcc-7 CC: gcc-7
QT_PKG: 512 QT_PKG: 510
TRAVIS_OS_NAME: linux TRAVIS_OS_NAME: linux
before_script: before_script:
- apt-get update - apt-get update
- apt-get install -y software-properties-common - apt-get install -y software-properties-common
- add-apt-repository ppa:ubuntu-toolchain-r/test -y - add-apt-repository ppa:ubuntu-toolchain-r/test -y
- add-apt-repository ppa:beineri/opt-qt-5.12.9-xenial -y - add-apt-repository ppa:beineri/opt-qt-5.10.1-xenial -y
- apt-get update && apt-get -y install --no-install-recommends g++-7 build-essential ninja-build qt${QT_PKG}{base,declarative,tools,multimedia,script,quickcontrols2,svg} liblmdb-dev libgl1-mesa-dev libssl-dev git ccache - apt-get update && apt-get -y install --no-install-recommends g++-7 build-essential ninja-build qt${QT_PKG}{base,declarative,tools,multimedia,script,quickcontrols2,svg} liblmdb-dev libgl1-mesa-dev libssl-dev git ccache
# need recommended deps for wget # need recommended deps for wget
- apt-get -y install wget - apt-get -y install wget
@ -37,7 +37,7 @@ build-gcc7:
- cmake -GNinja -H. -Bbuild - cmake -GNinja -H. -Bbuild
-DCMAKE_INSTALL_PREFIX=.deps/usr -DCMAKE_INSTALL_PREFIX=.deps/usr
-DHUNTER_ROOT=".hunter" -DHUNTER_ROOT=".hunter"
-DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF -DUSE_BUNDLED_OPENSSL=ON -DUSE_BUNDLED_LMDB=OFF -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF
-DCMAKE_BUILD_TYPE=Release -DHUNTER_CONFIGURATION_TYPES=Release -DCMAKE_BUILD_TYPE=Release -DHUNTER_CONFIGURATION_TYPES=Release
-DCI_BUILD=ON -DCI_BUILD=ON
- cmake --build build - cmake --build build
@ -88,7 +88,7 @@ build-flatpak-amd64:
#image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master' #image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master'
tags: [docker] tags: [docker]
before_script: before_script:
- apt-get update && apt-get -y install flatpak-builder git python curl python3-aiohttp python3-tenacity gir1.2-ostree-1.0 - apt-get update && apt-get -y install flatpak-builder git python curl
- flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
- flatpak --noninteractive install --user flathub org.kde.Platform//5.15 - flatpak --noninteractive install --user flathub org.kde.Platform//5.15
- flatpak --noninteractive install --user flathub org.kde.Sdk//5.15 - flatpak --noninteractive install --user flathub org.kde.Sdk//5.15
@ -96,10 +96,9 @@ build-flatpak-amd64:
- export VERSION=$(git describe) - export VERSION=$(git describe)
- mkdir -p build-flatpak - mkdir -p build-flatpak
- cd build-flatpak - cd build-flatpak
- flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date`" app ../io.github.NhekoReborn.Nheko.yaml - flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date`" app ../io.github.NhekoReborn.Nheko.json
- flatpak build-bundle repo nheko-amd64.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME//\//_} - flatpak build-bundle repo nheko-amd64.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME//\//_}
after_script: after_script:
- (cd ./scripts && ./upload-to-flatpak-repo.sh ../build-flatpak/repo) || true
- bash ./.ci/upload-nightly-gitlab.sh build-flatpak/nheko-amd64.flatpak - bash ./.ci/upload-nightly-gitlab.sh build-flatpak/nheko-amd64.flatpak
cache: cache:
key: "$CI_JOB_NAME" key: "$CI_JOB_NAME"
@ -116,7 +115,7 @@ build-flatpak-arm64:
#image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master' #image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master'
tags: [docker-arm64] tags: [docker-arm64]
before_script: before_script:
- apt-get update && apt-get -y install flatpak-builder git python curl python3-aiohttp python3-tenacity gir1.2-ostree-1.0 - apt-get update && apt-get -y install flatpak-builder git python curl
- flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
- flatpak --noninteractive install --user flathub org.kde.Platform//5.15 - flatpak --noninteractive install --user flathub org.kde.Platform//5.15
- flatpak --noninteractive install --user flathub org.kde.Sdk//5.15 - flatpak --noninteractive install --user flathub org.kde.Sdk//5.15
@ -124,10 +123,9 @@ build-flatpak-arm64:
- export VERSION=$(git describe) - export VERSION=$(git describe)
- mkdir -p build-flatpak - mkdir -p build-flatpak
- cd build-flatpak - cd build-flatpak
- flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date` for arm64" app ../io.github.NhekoReborn.Nheko.yaml - flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date` for arm64" app ../io.github.NhekoReborn.Nheko.json
- flatpak build-bundle repo nheko-arm64.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME//\//_} - flatpak build-bundle repo nheko-arm64.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME//\//_}
after_script: after_script:
- (cd ./scripts && ./upload-to-flatpak-repo.sh ../build-flatpak/repo) || true
- bash ./.ci/upload-nightly-gitlab.sh build-flatpak/nheko-arm64.flatpak - bash ./.ci/upload-nightly-gitlab.sh build-flatpak/nheko-arm64.flatpak
cache: cache:
key: "$CI_JOB_NAME" key: "$CI_JOB_NAME"
@ -143,12 +141,9 @@ linting:
image: alpine:latest image: alpine:latest
tags: [docker] tags: [docker]
before_script: before_script:
- apk update && apk add clang make git python3 py3-pip - apk update && apk add clang make git
- export PATH="$PATH:/root/.local/bin"
- pip3 install --user reuse
script: script:
- make lint - make lint
- make license
appimage-amd64: appimage-amd64:
stage: build stage: build

View File

@ -79,7 +79,6 @@ AppDir:
- libxv1 - libxv1
- libxxf86vm1 - libxxf86vm1
- libzstd1 - libzstd1
- qml-module-qt-labs-platform
- qml-module-qtgraphicaleffects - qml-module-qtgraphicaleffects
- qml-module-qtmultimedia - qml-module-qtmultimedia
- qml-module-qtquick-controls2 - qml-module-qtquick-controls2

View File

@ -1,151 +1,5 @@
# Changelog # Changelog
## [0.8.2] -- 2021-04-23
### Highlights
- Edits
- If you made a typo, just press the `Up` key and edit what you wrote.
- Messages other users edited will get updated automatically and have a small
pen symbol next to them.
- Privacy Screen
- Blur your messages, when Nheko looses focus, which prevents others from
peeking at your messages.
- You can configure the timeout of when this happens.
- Improved notifications (contributed by lorendb)
- No more breakage, because the message included a > on KDE based DEs.
- Render html and images where possible in the notification.
- Render if a message is a reply or someone sent an emote message more nicely
where possible.
- Encrypted notifications now show, that the content is encrypted instead of
being empty.
- Screenshare support in calls on X11 (contributed by trilene)
- Share your screen in a call!
- Select if your mouse cursor should be shown or not and if your webcam should
be included.
- SEND MESSAGES AS RAINBOWS! (contributed by LordMZTE)
- YES MESSAGES, EMOTES AND NOTICES!
### Features
- Set your displayname and avatar from Nheko either globally or per room.
(contributed by jedi18)
- Show room topic in the room settings.
- Double tap a message to reply to it.
- Leave a room using `/part` or `/leave`. (contributed by lorendb)
- Show mxid when hovering a username or avatar.
- Allow opening matrix: uris on Windows.
- Disable room pings caused by replies sent via Nheko (unless you are using
Element Web/Desktop).
### Improvements
- Userprofile can be closed via the Escape key. No more hotel california!
(contributed by lorendb)
- Most dialogs are now centered on the Nheko window. (contributed by lorendb)
- Update Hungarian translations. (contributed by maxigaz)
- Update Estonian translations. (contributed by Priit)
- Update Russian translations. (contributed by Alexey Murz and Artem)
- Update Swedish translations. (contributed by Emilie)
- Update French translations. (contributed by MayeulC, Nicolas Guichard and Carl Schwan)
- Allow drag and drop of files on the whole timeline. (contributed by lorendb)
- Enable notifications on Haiku. (contributed by kallisti5)
- Update scheme handler to the latest matrix: scheme proposal.
- Close completers when typing a space after the colon. (contributed by jedi18)
- Port room settings to Qml. (contributed by jedi18)
- Improved read marker handling. Read marker should now get stuck less often.
- Various changes around hover and tap handling in the timeline, which hopefully
now works more predicatably.
- Buttons in the timeline are now rendered in a box on hover on desktop
platforms.
- Complete room links in the timeline after typing a # character. (contributed
by jedi18)
- An improved quick switcher with better rendering and search. (contributed by jedi18)
- Some fixes around inline emoji and images.
- Jump into new rooms, after you created them. (contrubuted by jedi18)
- Improved search in the emoji picker.
- Allow disabling certificate checks via the config file.
- Use native menus where possible.
- Fix video playback on Windows. (contrubuted by jedi18)
- Send image messages by pressing Enter. (contributed by salahmak)
- Escape closes the upload widget. (contributed by salahmak)
- Improve session rotation and sharing in E2EE rooms.
### Bugfixes
- Emojis joined from separate emojis with a 0xfe0f in the middle should now
render correctly.
- Fix a bug when logging out of a non default profile clearing the wrong
profile. (contrubuted by lorendb)
- Various fixed around profile handling. (contributed by lorendb)
- Focus message input after a reaction. (contributed by jedi18)
- Disable native rendering to prevent kerning bugs on non integer scale factors.
- Fix duplex call devices not showing up. (contributed by trilene)
- Fix a few crashes when leaving a room. (contributed by jedi18)
- Fix hidden tags not updating properly. (contributed by jedi18)
- Fix some issues with login, when a server had SSO as well as password login
enabled (for example matrix.org).
- Properly set the dialog flag for dialogs on most platforms. (Wayland does not
support that.)
- Properly add license to source files.
- Fix fingerprint increasing the minimum window size.
- Don't send markdown links in the plain text body of events when autocompleting
user or room names.
- Fix webcam not working in flatpaks.
- Fix markdown override in replies.
- Fix unsupported events causing errors when saving them. (contributed by
anjanik)
- Fix exif rotation not being respected anymore in E2EE rooms.
- Remove unused qml plugins in the windows package.
- Fix broken olm channels automatically when noticed.
- Fix pasting not overwriting the selection.
- Fix Nheko sometimes overwriting received keys with keys it requested, even if
they have a higher minimum index.
### Packaging changes
- Added xcb dependency on X11 based platforms for screensharing (optional)
- Bumped lmdbxx version from 0.9.14.0 to 1.0.0, which is a BREAKING change. You
can get the new version here: https://github.com/hoytech/lmdbxx/releases
(repo changed)
- Removed tweeny as a dependency.
## [0.8.1] -- 2021-01-27
### Features
- `/plain` and `/md` commands to override the current markdown setting. (contributed by lorendb)
- Allow persistent hiding of rooms with a specific tag (or from a community) via a context menu.
- Allow open media messages in an external program immediately. (contributed by rnhmjoj)
### Improvements
- Use async dbus connection for notifications. (contributed by lorendb)
- Update Hungarian translations. (contributed by maxigaz)
- Update Finnish translations. (contributed by Priit)
- Update Malayalam translations. (contributed by vachan-maker)
- Update Dutch translations. (contributed by Glael)
- Store splitter size across restarts.
- Add a border around the completer. (contributed by lorendb)
- Request keys for messages with unknown message indices (once per restart, when they are shown).
- Move the database location to XDG_DATA_DIR. (contributed by rnhmjoj)
- Reload the timeline after key backup import.
- Autoclose completer on `space`, when there are no matches.
- Make completer only react, when the mouse cursor is moved.
### Bugfixes
- Fix unhandled exception, when a device has no keys.
- Fix some cmake warnings regarding GNUInstallDirs.
- Fix tags being broken. If you have no tags showing up, you may want to logout and login again.
- Fix versionOk being called on the wrong thread. (contributed by Jedi18)
- Fix font tags showing up in media message filenames.
- Fix user profile in dark themes showing the wrong colors. (contributed by lorendb)
- Fix emoji category switching on old Qt versions. (contributed by lorendb)
- Fix old messages being replayed after a limited timeline.
- Fix empty secrets being returned from the wallet breaking verification.
- Make matrix link chat invites create a direct chat.
- Fix focus handling on room change or reply button clicks.
- Fix username completion deleting the character before it.
## [0.8.0] -- 2021-01-21 ## [0.8.0] -- 2021-01-21
### Highlights ### Highlights

View File

@ -33,12 +33,14 @@ option(USE_BUNDLED_CMARK "Use the bundled version of cmark."
option(USE_BUNDLED_JSON "Use the bundled version of nlohmann json." option(USE_BUNDLED_JSON "Use the bundled version of nlohmann json."
${HUNTER_ENABLED}) ${HUNTER_ENABLED})
option(USE_BUNDLED_OPENSSL "Use the bundled version of OpenSSL." option(USE_BUNDLED_OPENSSL "Use the bundled version of OpenSSL."
OFF) ${HUNTER_ENABLED})
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." option(USE_BUNDLED_LMDB "Use the bundled version of lmdb."
${HUNTER_ENABLED}) ${HUNTER_ENABLED})
option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdb++." option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdb++."
${HUNTER_ENABLED}) ${HUNTER_ENABLED})
option(USE_BUNDLED_TWEENY "Use the bundled version of tweeny."
${HUNTER_ENABLED})
option(USE_BUNDLED_QTKEYCHAIN "Use the bundled version of Qt5Keychain." option(USE_BUNDLED_QTKEYCHAIN "Use the bundled version of Qt5Keychain."
${HUNTER_ENABLED}) ${HUNTER_ENABLED})
@ -70,16 +72,16 @@ if(${CMAKE_VERSION} VERSION_LESS "3.14.0")
endmacro() endmacro()
endif() endif()
# Include Qt basic functions
include(QtCommon)
project(nheko LANGUAGES CXX C) project(nheko LANGUAGES CXX C)
include(GNUInstallDirs) include(GNUInstallDirs)
# Include Qt basic functions
include(QtCommon)
set(CPACK_PACKAGE_VERSION_MAJOR "0") set(CPACK_PACKAGE_VERSION_MAJOR "0")
set(CPACK_PACKAGE_VERSION_MINOR "8") set(CPACK_PACKAGE_VERSION_MINOR "8")
set(CPACK_PACKAGE_VERSION_PATCH "2") set(CPACK_PACKAGE_VERSION_PATCH "0")
set(PROJECT_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR}) set(PROJECT_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR})
set(PROJECT_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR}) set(PROJECT_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR})
set(PROJECT_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH}) set(PROJECT_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH})
@ -127,11 +129,8 @@ endif()
if(USE_BUNDLED_LMDB) if(USE_BUNDLED_LMDB)
hunter_add_package(lmdb) hunter_add_package(lmdb)
find_package(liblmdb CONFIG REQUIRED) find_package(liblmdb CONFIG REQUIRED)
target_include_directories(liblmdb::lmdb INTERFACE
"${HUNTER_INSTALL_PREFIX}/include/lmdb")
else() else()
find_package(LMDB REQUIRED) find_package(LMDB)
endif() endif()
# #
@ -211,7 +210,7 @@ set(SPDLOG_DEBUG_ON false)
# Windows doesn't handle CMAKE_BUILD_TYPE. # Windows doesn't handle CMAKE_BUILD_TYPE.
if(NOT WIN32) if(NOT WIN32)
if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
set(SPDLOG_DEBUG_ON true) set(SPDLOG_DEBUG_ON true)
else() else()
set(SPDLOG_DEBUG_ON false) set(SPDLOG_DEBUG_ON false)
@ -258,6 +257,7 @@ set(SRC_FILES
src/dialogs/PreviewUploadOverlay.cpp src/dialogs/PreviewUploadOverlay.cpp
src/dialogs/ReCaptcha.cpp src/dialogs/ReCaptcha.cpp
src/dialogs/ReadReceipts.cpp src/dialogs/ReadReceipts.cpp
src/dialogs/RoomSettings.cpp
# Emoji # Emoji
src/emoji/EmojiModel.cpp src/emoji/EmojiModel.cpp
@ -271,7 +271,6 @@ set(SRC_FILES
src/timeline/TimelineViewManager.cpp src/timeline/TimelineViewManager.cpp
src/timeline/TimelineModel.cpp src/timeline/TimelineModel.cpp
src/timeline/DelegateChooser.cpp src/timeline/DelegateChooser.cpp
src/timeline/Permissions.cpp
# UI components # UI components
src/ui/Avatar.cpp src/ui/Avatar.cpp
@ -282,7 +281,6 @@ set(SRC_FILES
src/ui/InfoMessage.cpp src/ui/InfoMessage.cpp
src/ui/Label.cpp src/ui/Label.cpp
src/ui/LoadingIndicator.cpp src/ui/LoadingIndicator.cpp
src/ui/NhekoCursorShape.cpp
src/ui/NhekoDropArea.cpp src/ui/NhekoDropArea.cpp
src/ui/OverlayModal.cpp src/ui/OverlayModal.cpp
src/ui/OverlayWidget.cpp src/ui/OverlayWidget.cpp
@ -296,18 +294,12 @@ set(SRC_FILES
src/ui/ThemeManager.cpp src/ui/ThemeManager.cpp
src/ui/ToggleButton.cpp src/ui/ToggleButton.cpp
src/ui/UserProfile.cpp src/ui/UserProfile.cpp
src/ui/RoomSettings.cpp
# Generic notification stuff
src/notifications/Manager.cpp
src/AvatarProvider.cpp src/AvatarProvider.cpp
src/BlurhashProvider.cpp src/BlurhashProvider.cpp
src/Cache.cpp src/Cache.cpp
src/CallDevices.cpp
src/CallManager.cpp src/CallManager.cpp
src/ChatPage.cpp src/ChatPage.cpp
src/Clipboard.cpp
src/ColorImageProvider.cpp src/ColorImageProvider.cpp
src/CommunitiesList.cpp src/CommunitiesList.cpp
src/CommunitiesListItem.cpp src/CommunitiesListItem.cpp
@ -321,6 +313,7 @@ set(SRC_FILES
src/MatrixClient.cpp src/MatrixClient.cpp
src/MxcImageProvider.cpp src/MxcImageProvider.cpp
src/Olm.cpp src/Olm.cpp
src/QuickSwitcher.cpp
src/RegisterPage.cpp src/RegisterPage.cpp
src/RoomInfoListItem.cpp src/RoomInfoListItem.cpp
src/RoomList.cpp src/RoomList.cpp
@ -331,7 +324,6 @@ set(SRC_FILES
src/UserInfoWidget.cpp src/UserInfoWidget.cpp
src/UserSettingsPage.cpp src/UserSettingsPage.cpp
src/UsersModel.cpp src/UsersModel.cpp
src/RoomsModel.cpp
src/Utils.cpp src/Utils.cpp
src/WebRTCSession.cpp src/WebRTCSession.cpp
src/WelcomePage.cpp src/WelcomePage.cpp
@ -363,13 +355,13 @@ 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 5d2f055ea9403770039ddf66b1900f890cd5cde7 GIT_TAG v0.4.0
) )
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 "")
FetchContent_MakeAvailable(MatrixClient) FetchContent_MakeAvailable(MatrixClient)
else() else()
find_package(MatrixClient 0.5.1 REQUIRED) find_package(MatrixClient 0.4.0 REQUIRED)
endif() endif()
if(USE_BUNDLED_OLM) if(USE_BUNDLED_OLM)
include(FetchContent) include(FetchContent)
@ -381,7 +373,7 @@ if(USE_BUNDLED_OLM)
set(OLM_TESTS OFF CACHE INTERNAL "") set(OLM_TESTS OFF CACHE INTERNAL "")
FetchContent_MakeAvailable(Olm) FetchContent_MakeAvailable(Olm)
else() else()
find_package(Olm 3 REQUIRED) find_package(Olm 3)
set_package_properties(Olm PROPERTIES set_package_properties(Olm PROPERTIES
DESCRIPTION "An implementation of the Double Ratchet cryptographic ratchet" DESCRIPTION "An implementation of the Double Ratchet cryptographic ratchet"
URL "https://git.matrix.org/git/olm/about/" URL "https://git.matrix.org/git/olm/about/"
@ -422,18 +414,8 @@ set_package_properties(nlohmann_json PROPERTIES
) )
if(USE_BUNDLED_LMDBXX) if(USE_BUNDLED_LMDBXX)
include(FetchContent) hunter_add_package(lmdbxx)
FetchContent_Declare( find_package(lmdbxx CONFIG REQUIRED)
lmdbxx
URL "https://raw.githubusercontent.com/hoytech/lmdbxx/1.0.0/lmdb++.h"
DOWNLOAD_NO_EXTRACT TRUE
)
if(NOT lmdbxx_POPULATED)
FetchContent_Populate(lmdbxx)
endif()
add_library(lmdbxx INTERFACE)
target_include_directories(lmdbxx INTERFACE ${lmdbxx_SOURCE_DIR})
add_library(lmdbxx::lmdbxx ALIAS lmdbxx)
else() else()
if(NOT LMDBXX_INCLUDE_DIR) if(NOT LMDBXX_INCLUDE_DIR)
find_path(LMDBXX_INCLUDE_DIR find_path(LMDBXX_INCLUDE_DIR
@ -449,18 +431,24 @@ else()
add_library(lmdbxx::lmdbxx ALIAS lmdbxx) add_library(lmdbxx::lmdbxx ALIAS lmdbxx)
endif() endif()
if(USE_BUNDLED_TWEENY)
include(FetchContent)
FetchContent_Declare(
Tweeny
GIT_REPOSITORY https://github.com/mobius3/tweeny.git
GIT_TAG 6a5033372fe53c4c731c66c8a2d56261746cd85c #v3 <- v3 has unfixed warnings
)
FetchContent_MakeAvailable(Tweeny)
else()
find_package(Tweeny REQUIRED)
endif()
include(FindPkgConfig) include(FindPkgConfig)
pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-sdp-1.0>=1.18 gstreamer-webrtc-1.0>=1.18) pkg_check_modules(GSTREAMER IMPORTED_TARGET gstreamer-sdp-1.0>=1.16 gstreamer-webrtc-1.0>=1.16)
if (TARGET PkgConfig::GSTREAMER) if (TARGET PkgConfig::GSTREAMER)
add_feature_info(voip ON "GStreamer found. Call support is enabled automatically.") add_feature_info(voip ON "GStreamer found. Call support is enabled automatically.")
pkg_check_modules(XCB IMPORTED_TARGET xcb xcb-ewmh)
if (TARGET PkgConfig::XCB)
add_feature_info("Window selection when screen sharing (X11)" ON "XCB-EWMH found. Window selection is enabled when screen sharing (X11).")
else()
add_feature_info("Window selection when screen sharing (X11)" OFF "XCB-EWMH could not be found on your system. Screen sharing (X11) is limited to the entire screen only. To enable window selection, make sure xcb and xcb-ewmh can be found via pkgconfig.")
endif()
else() else()
add_feature_info(voip OFF "GStreamer could not be found on your system. As a consequence call support has been disabled. If you don't want that, make sure gstreamer-sdp-1.0>=1.18 gstreamer-webrtc-1.0>=1.18 can be found via pkgconfig.") add_feature_info(voip OFF "GStreamer could not be found on your system. As a consequence call support has been disabled. If you don't want that, make sure gstreamer-sdp-1.0>=1.16 gstreamer-webrtc-1.0>=1.16 can be found via pkgconfig.")
endif() endif()
# single instance functionality # single instance functionality
@ -483,6 +471,7 @@ qt5_wrap_cpp(MOC_HEADERS
src/dialogs/RawMessage.h src/dialogs/RawMessage.h
src/dialogs/ReCaptcha.h src/dialogs/ReCaptcha.h
src/dialogs/ReadReceipts.h src/dialogs/ReadReceipts.h
src/dialogs/RoomSettings.h
# Emoji # Emoji
src/emoji/EmojiModel.h src/emoji/EmojiModel.h
@ -495,7 +484,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/timeline/TimelineViewManager.h src/timeline/TimelineViewManager.h
src/timeline/TimelineModel.h src/timeline/TimelineModel.h
src/timeline/DelegateChooser.h src/timeline/DelegateChooser.h
src/timeline/Permissions.h
# UI components # UI components
src/ui/Avatar.h src/ui/Avatar.h
@ -506,7 +494,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/ui/Label.h src/ui/Label.h
src/ui/FloatingButton.h src/ui/FloatingButton.h
src/ui/Menu.h src/ui/Menu.h
src/ui/NhekoCursorShape.h
src/ui/NhekoDropArea.h src/ui/NhekoDropArea.h
src/ui/OverlayWidget.h src/ui/OverlayWidget.h
src/ui/SnackBar.h src/ui/SnackBar.h
@ -519,18 +506,14 @@ qt5_wrap_cpp(MOC_HEADERS
src/ui/Theme.h src/ui/Theme.h
src/ui/ThemeManager.h src/ui/ThemeManager.h
src/ui/UserProfile.h src/ui/UserProfile.h
src/ui/RoomSettings.h
src/notifications/Manager.h src/notifications/Manager.h
src/AvatarProvider.h src/AvatarProvider.h
src/BlurhashProvider.h src/BlurhashProvider.h
src/Cache_p.h src/Cache_p.h
src/CacheCryptoStructs.h
src/CallDevices.h
src/CallManager.h src/CallManager.h
src/ChatPage.h src/ChatPage.h
src/Clipboard.h
src/CommunitiesList.h src/CommunitiesList.h
src/CommunitiesListItem.h src/CommunitiesListItem.h
src/CompletionProxyModel.h src/CompletionProxyModel.h
@ -539,6 +522,7 @@ qt5_wrap_cpp(MOC_HEADERS
src/LoginPage.h src/LoginPage.h
src/MainWindow.h src/MainWindow.h
src/MxcImageProvider.h src/MxcImageProvider.h
src/QuickSwitcher.h
src/RegisterPage.h src/RegisterPage.h
src/RoomInfoListItem.h src/RoomInfoListItem.h
src/RoomList.h src/RoomList.h
@ -549,7 +533,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/UserInfoWidget.h src/UserInfoWidget.h
src/UserSettingsPage.h src/UserSettingsPage.h
src/UsersModel.h src/UsersModel.h
src/RoomsModel.h
src/WebRTCSession.h src/WebRTCSession.h
src/WelcomePage.h src/WelcomePage.h
src/popups/PopupItem.h src/popups/PopupItem.h
@ -565,7 +548,7 @@ set(TRANSLATION_DEPS ${LANG_QRC} ${QRC} ${QM_SRC})
if (APPLE) if (APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa")
set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/notifications/ManagerMac.cpp src/emoji/MacHelper.mm) set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/emoji/MacHelper.mm)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0") if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
set_source_files_properties( src/notifications/ManagerMac.mm src/emoji/MacHelper.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON) set_source_files_properties( src/notifications/ManagerMac.mm src/emoji/MacHelper.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
endif() endif()
@ -608,9 +591,6 @@ if(APPLE)
elseif(WIN32) elseif(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) target_link_libraries (nheko PRIVATE ${NTDLIB} Qt5::WinMain)
if(MSVC)
target_compile_options(nheko PUBLIC "/Zc:__cplusplus")
endif()
else() else()
target_link_libraries (nheko PRIVATE Qt5::DBus) target_link_libraries (nheko PRIVATE Qt5::DBus)
endif() endif()
@ -639,6 +619,7 @@ target_link_libraries(nheko PRIVATE
nlohmann_json::nlohmann_json nlohmann_json::nlohmann_json
lmdbxx::lmdbxx lmdbxx::lmdbxx
liblmdb::lmdb liblmdb::lmdb
tweeny
SingleApplication::SingleApplication) SingleApplication::SingleApplication)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0") if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
@ -652,10 +633,6 @@ 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)
if (TARGET PkgConfig::XCB)
target_link_libraries(nheko PRIVATE PkgConfig::XCB)
target_compile_definitions(nheko PRIVATE XCB_AVAILABLE)
endif()
endif() endif()
if(MSVC) if(MSVC)
@ -668,7 +645,7 @@ if(QML_DEBUGGING)
endif() endif()
if(NOT MSVC AND NOT HAIKU) if(NOT MSVC)
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR CI_BUILD) if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR CI_BUILD)
target_compile_options(nheko PRIVATE "-Werror") target_compile_options(nheko PRIVATE "-Werror")
endif() endif()

View File

@ -41,9 +41,6 @@ macos-app-install:
lint: lint:
./.ci/format.sh ./.ci/format.sh
license:
./.ci/licenses.sh
image: image:
docker build -t nheko-app-image . docker build -t nheko-app-image .

View File

@ -2,9 +2,8 @@ nheko
---- ----
[![Build Status](https://nheko.im/nheko-reborn/nheko/badges/master/pipeline.svg)](https://nheko.im/nheko-reborn/nheko/-/pipelines/latest) [![Build Status](https://nheko.im/nheko-reborn/nheko/badges/master/pipeline.svg)](https://nheko.im/nheko-reborn/nheko/-/pipelines/latest)
[![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/v0.8.2-RC) [![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/v0.8.0)
[![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>
[![#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)
[![AUR: nheko](https://img.shields.io/badge/AUR-nheko-blue.svg)](https://aur.archlinux.org/packages/nheko) [![AUR: nheko](https://img.shields.io/badge/AUR-nheko-blue.svg)](https://aur.archlinux.org/packages/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>
@ -50,7 +49,7 @@ Specifically there is support for:
### Releases ### Releases
Releases for Linux (AppImage), macOS (disk image) & Windows (x64 installer) Releases for Linux (AppImage), macOS (disk image) & Windows (x64 installer)
can be found in the [GitHub releases](https://github.com/Nheko-Reborn/nheko/releases). can be found in the [Github releases](https://github.com/Nheko-Reborn/nheko/releases).
### Repositories ### Repositories
@ -75,7 +74,7 @@ sudo dnf install nheko
#### Gentoo Linux #### Gentoo Linux
```bash ```bash
sudo eselect repository enable guru sudo eselect repository enable matrix
sudo emerge -a nheko sudo emerge -a nheko
``` ```
@ -115,29 +114,14 @@ with [homebrew](https://brew.sh/):
brew install --cask nheko brew install --cask nheko
``` ```
#### Windows
with [Chocolatey](https://chocolatey.org/):
```posh
choco install nheko-reborn
```
### FAQ
##
**Q:** Why don't videos run for me on Windows?
**A:** You're probably missing the required video codecs, download [K-Lite Codec Pack](https://codecguide.com/download_kl.htm).
##
### Build Requirements ### Build Requirements
- Qt5 (5.12 or greater). Required for overlapping hover handlers in Qml. - Qt5 (5.10 or greater). Qt 5.7 adds support for color font rendering with
Freetype, which is essential to properly support emoji, 5.8 adds some features
to make interopability with Qml easier, 5.10 makes sliders actually visible with different palettes.
- CMake 3.15 or greater. (Lower version may work, but may break boost linking) - CMake 3.15 or greater. (Lower version may work, but may break boost linking)
- [mtxclient](https://github.com/Nheko-Reborn/mtxclient) - [mtxclient](https://github.com/Nheko-Reborn/mtxclient)
- [LMDB](https://symas.com/lightning-memory-mapped-database/) - [LMDB](https://symas.com/lightning-memory-mapped-database/)
- [lmdb++](https://github.com/hoytech/lmdbxx)
- [cmark](https://github.com/commonmark/cmark) 0.29 or greater. - [cmark](https://github.com/commonmark/cmark) 0.29 or greater.
- Boost 1.70 or greater. - Boost 1.70 or greater.
- [libolm](https://gitlab.matrix.org/matrix-org/olm) - [libolm](https://gitlab.matrix.org/matrix-org/olm)
@ -158,7 +142,7 @@ choco install nheko-reborn
Nheko can use bundled version for most of those libraries automatically, if the versions in your distro are too old. Nheko can use bundled version for most of those libraries automatically, if the versions in your distro are too old.
To use them, you can enable the hunter integration by passing `-DHUNTER_ENABLED=ON`. To use them, you can enable the hunter integration by passing `-DHUNTER_ENABLED=ON`.
It is probably wise to link those dependencies statically by passing `-DBUILD_SHARED_LIBS=OFF` It is probably wise to link those dependencies statically by passing `-DBUILD_SHARED_LIBS=OFF`
You can select which bundled dependencies you want to use by passing various `-DUSE_BUNDLED_*` flags. By default all dependencies are bundled *if* you enable hunter. (The exception to that is OpenSSL, which is always disabled by default.) You can select which bundled dependencies you want to use by passing various `-DUSE_BUNDLED_*` flags. By default all dependencies are bundled *if* you enable hunter.
If you experience build issues and you are trying to link `mtxclient` library without hunter, make sure the library version(commit) as mentioned in the `CMakeList.txt` is used. Sometimes we have to make breaking changes in `mtxclient` and for that period the master branch of both repos may not be compatible. If you experience build issues and you are trying to link `mtxclient` library without hunter, make sure the library version(commit) as mentioned in the `CMakeList.txt` is used. Sometimes we have to make breaking changes in `mtxclient` and for that period the master branch of both repos may not be compatible.
The bundle flags are currently: The bundle flags are currently:
@ -175,8 +159,6 @@ The bundle flags are currently:
- USE_BUNDLED_LMDBXX - USE_BUNDLED_LMDBXX
- USE_BUNDLED_TWEENY - USE_BUNDLED_TWEENY
A note on bundled OpenSSL: You need to explicitly enable it and it will not be using your system certificate directory by default, if you enable it. You need to override that at runtime with the SSL_CERT_FILE variable. On Windows it will still be using your system certificates though, since it loads them from the system store instead of the OpenSSL directory.
#### Linux #### Linux
If you don't want to install any external dependencies, you can generate an AppImage locally using docker. It is not that well maintained though... If you don't want to install any external dependencies, you can generate an AppImage locally using docker. It is not that well maintained though...
@ -211,7 +193,7 @@ sudo emerge -a ">=dev-qt/qtgui-5.10.0" media-libs/fontconfig dev-libs/qtkeychain
```bash ```bash
# Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports): # Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports):
sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,-labs-platform,graphicaleffects,quick-controls2} qt5keychain-dev sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,graphicaleffects,quick-controls2} qt5keychain-dev
``` ```
This will install all dependencies, except for tweeny (use bundled tweeny) This will install all dependencies, except for tweeny (use bundled tweeny)
and mtxclient (needs to be build separately). and mtxclient (needs to be build separately).
@ -223,8 +205,8 @@ and mtxclient (needs to be build separately).
```bash ```bash
sudo apt install cmake gcc make automake liblmdb-dev \ sudo apt install cmake gcc make automake liblmdb-dev \
qt5-default libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev \ qt5-default libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev \
qml-module-qtgstreamer qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-dev \ qml-module-qtgstreamer qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools \
qml-module-qtgraphicaleffects qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform\ qml-module-qtgraphicaleffects qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts \
qt5keychain-dev qt5keychain-dev
``` ```

View File

@ -15,7 +15,7 @@ environment:
cache: cache:
- c:\hunter\ -> appveyor.yml - c:\hunter\
- build\_deps -> appveyor.yml - build\_deps -> appveyor.yml
build: build:
@ -23,16 +23,15 @@ build:
install: install:
- set QT_DIR=C:\Qt\5.15\msvc2019_64 - set QT_DIR=C:\Qt\5.15\msvc2019_64
- set PATH=Path=C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\wbin;C:\Program Files\Git\cmd;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files\PowerShell\7\;C:\Program Files\7-Zip;C:\Program Files\Microsoft\Web Platform Installer\;C:\Tools\NuGet;C:\Tools\PsTools;C:\Program Files\Git\usr\bin;C:\Program Files\Git LFS;C:\Program Files\Mercurial\;C:\Program Files (x86)\Subversion\bin;C:\Program Files\Docker\Docker\resources\bin;C:\ProgramData\DockerDesktop\version-bin;C:\Program Files\dotnet\;C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\150;C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin;C:\Tools\xUnit;C:\Tools\xUnit20;C:\Tools\NUnit\bin;C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\Extensions\TestPlatform;C:\Ruby193\bin;C:\Tools\WebDriver;C:\Python27;C:\Python27\Scripts;C:\Program Files\erl10.7\bin;C:\Program Files (x86)\Windows Kits\8.1\Windows Performance Toolkit\;C:\Program Files (x86)\Microsoft DirectX SDK;C:\Program Files\Microsoft Service Fabric\bin\Fabric\Fabric.Code;C:\Program Files\Microsoft SDKs\Service Fabric\Tools\ServiceFabricLocalClusterManager;C:\Tools\Doxygen;C:\Program Files (x86)\CMake\bin;C:\ProgramData\chocolatey\bin;C:\Tools\vcpkg;C:\Tools\Coverity\bin;C:\Program Files (x86)\NSIS;C:\Tools\Octopus;C:\Program Files\Meson\;C:\Tools\GitVersion;C:\Tools\NUnit3\bin;C:\Users\appveyor\AppData\Local\Microsoft\WindowsApps;C:\Users\appveyor\.dotnet\tools;C:\Program Files\AppVeyor\BuildAgent\ - set PATH=C:\Strawberry\perl\bin;%PATH%;%QT_DIR%\bin
- set PATH=C:\Strawberry\perl\bin;C:\Python39-x64;%QT_DIR%\bin;%PATH%
- call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
build_script: build_script:
# VERSION format: branch-master/branch-1.2 # VERSION format: branch-master/branch-1.2
# INSTVERSION format: x.y.z # INSTVERSION format: x.y.z
# WINVERSION format: 9999.0.0.123/1.2.0.234 # WINVERSION format: 9999.0.0.123/1.2.0.234
- if "%APPVEYOR_REPO_TAG%"=="false" set INSTVERSION=0.8.2 - if "%APPVEYOR_REPO_TAG%"=="false" set INSTVERSION=0.8.0
- if "%APPVEYOR_REPO_TAG%"=="false" set VERSION=0.8.2 - if "%APPVEYOR_REPO_TAG%"=="false" set VERSION=0.8.0
- if "%APPVEYOR_REPO_TAG%"=="false" if "%APPVEYOR_REPO_BRANCH%"=="master" set INSTVERSION=9999.0 - if "%APPVEYOR_REPO_TAG%"=="false" if "%APPVEYOR_REPO_BRANCH%"=="master" set INSTVERSION=9999.0
- if "%APPVEYOR_REPO_TAG%"=="false" set WINVERSION=%INSTVERSION%.0.%APPVEYOR_BUILD_NUMBER% - if "%APPVEYOR_REPO_TAG%"=="false" set WINVERSION=%INSTVERSION%.0.%APPVEYOR_BUILD_NUMBER%
# VERSION format: v1.2.3/v1.3.4 # VERSION format: v1.2.3/v1.3.4
@ -47,9 +46,10 @@ build_script:
- echo %DATE% - echo %DATE%
# Build nheko # Build nheko
#- cmake -G "Visual Studio 15 2017 Win64" -H. -Bbuild
- cmake -G "Visual Studio 16 2019" -A x64 -H. -Bbuild - cmake -G "Visual Studio 16 2019" -A x64 -H. -Bbuild
-DHUNTER_ROOT="C:\hunter" -DHUNTER_ROOT="C:\hunter"
-DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF -DUSE_BUNDLED_OPENSSL=ON -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF
-DCMAKE_BUILD_TYPE=Release -DHUNTER_CONFIGURATION_TYPES=Release -DCMAKE_BUILD_TYPE=Release -DHUNTER_CONFIGURATION_TYPES=Release
- cmake --build build --config Release - cmake --build build --config Release
@ -61,7 +61,7 @@ after_build:
- mkdir NhekoRelease - mkdir NhekoRelease
- copy build\Release\nheko.exe NhekoRelease\nheko.exe - copy build\Release\nheko.exe NhekoRelease\nheko.exe
- copy build\_deps\cmark-build\src\Release\cmark.dll NhekoRelease\cmark.dll - copy build\_deps\cmark-build\src\Release\cmark.dll NhekoRelease\cmark.dll
- windeployqt --qmldir resources\qml\ NhekoRelease\nheko.exe - windeployqt --qmldir %QT_DIR%\qml\ --release NhekoRelease\nheko.exe
- 7z a nheko_win_64.zip .\NhekoRelease\* - 7z a nheko_win_64.zip .\NhekoRelease\*
- ls -lh build\Release\ - ls -lh build\Release\
@ -77,19 +77,25 @@ after_build:
- mkdir installer\packages\io.github.nhekoreborn.nheko - mkdir installer\packages\io.github.nhekoreborn.nheko
- mkdir installer\packages\io.github.nhekoreborn.nheko\data - mkdir installer\packages\io.github.nhekoreborn.nheko\data
- mkdir installer\packages\io.github.nhekoreborn.nheko\meta - mkdir installer\packages\io.github.nhekoreborn.nheko\meta
- mkdir installer\packages\io.github.nhekoreborn.nheko.cleanup\meta
# Copy installer data # Copy installer data
- copy %BUILD%\resources\nheko.ico installer\config - copy %BUILD%\resources\nheko.ico installer\config
- copy %BUILD%\resources\nheko.png installer\config - copy %BUILD%\resources\nheko.png installer\config
- copy %BUILD%\COPYING installer\packages\io.github.nhekoreborn.nheko\meta\license.txt - copy %BUILD%\COPYING installer\packages\io.github.nhekoreborn.nheko\meta\license.txt
- copy %BUILD%\COPYING installer\packages\io.github.nhekoreborn.nheko.cleanup\meta\license.txt
- copy %BUILD%\deploy\installer\config.xml installer\config - copy %BUILD%\deploy\installer\config.xml installer\config
- copy %BUILD%\deploy\installer\controlscript.qs installer\config - copy %BUILD%\deploy\installer\controlscript.qs installer\config
- copy %BUILD%\deploy\installer\uninstall.qs installer\packages\io.github.nhekoreborn.nheko\data - copy %BUILD%\deploy\installer\uninstall.qs installer\packages\io.github.nhekoreborn.nheko\data
- copy %BUILD%\deploy\installer\gui\package.xml installer\packages\io.github.nhekoreborn.nheko\meta - copy %BUILD%\deploy\installer\gui\package.xml installer\packages\io.github.nhekoreborn.nheko\meta
- copy %BUILD%\deploy\installer\gui\installscript.qs installer\packages\io.github.nhekoreborn.nheko\meta - copy %BUILD%\deploy\installer\gui\installscript.qs installer\packages\io.github.nhekoreborn.nheko\meta
- copy %BUILD%\deploy\installer\cleanup\package.xml installer\packages\io.github.nhekoreborn.nheko.cleanup\meta
- copy %BUILD%\deploy\installer\cleanup\installscript.qs installer\packages\io.github.nhekoreborn.nheko.cleanup\meta
# Amend version and date # Amend version and date
- sed -i "s/__VERSION__/0.8.2/" installer\config\config.xml - sed -i "s/__VERSION__/0.8.0/" installer\config\config.xml
- sed -i "s/__VERSION__/0.8.2/" installer\packages\io.github.nhekoreborn.nheko\meta\package.xml - sed -i "s/__VERSION__/0.8.0/" installer\packages\io.github.nhekoreborn.nheko\meta\package.xml
- sed -i "s/__VERSION__/0.8.0/" installer\packages\io.github.nhekoreborn.nheko.cleanup\meta\package.xml
- sed -i "s/__DATE__/%DATE%/" installer\packages\io.github.nhekoreborn.nheko\meta\package.xml - sed -i "s/__DATE__/%DATE%/" installer\packages\io.github.nhekoreborn.nheko\meta\package.xml
- sed -i "s/__DATE__/%DATE%/" installer\packages\io.github.nhekoreborn.nheko.cleanup\meta\package.xml
# Copy nheko data # Copy nheko data
- xcopy NhekoData\*.* installer\packages\io.github.nhekoreborn.nheko\data\*.* /s /e /c /y - xcopy NhekoData\*.* installer\packages\io.github.nhekoreborn.nheko\data\*.* /s /e /c /y
- move NhekoRelease\nheko.exe installer\packages\io.github.nhekoreborn.nheko\data - move NhekoRelease\nheko.exe installer\packages\io.github.nhekoreborn.nheko\data
@ -101,13 +107,13 @@ after_build:
- set PATH=%BUILD%\tools\bin;%PATH% - set PATH=%BUILD%\tools\bin;%PATH%
- binarycreator.exe -f -c installer\config\config.xml -p installer\packages nheko-installer.exe - binarycreator.exe -f -c installer\config\config.xml -p installer\packages nheko-installer.exe
- copy nheko-installer.exe nheko-%APPVEYOR_REPO_TAG_NAME%-installer.exe - mv nheko-installer.exe nheko-%APPVEYOR_REPO_TAG_NAME%-installer.exe
- copy nheko-installer.exe nheko-%APPVEYOR_PULL_REQUEST_HEAD_COMMIT%-installer.exe
- ps: .\.ci\upload-nightly.ps1
on_success: on_success:
- if "%APPVEYOR_REPO_TAG%" == "true" (curl -T nheko-%APPVEYOR_REPO_TAG_NAME%-installer.exe -uredsky17:%BINTRAY_APIKEY% https://api.bintray.com/content/nheko-reborn/nheko/%APPVEYOR_REPO_TAG_NAME%/nheko/%APPVEYOR_REPO_TAG_NAME%/) - if "%APPVEYOR_REPO_TAG%" == "true" (curl -T nheko-%APPVEYOR_REPO_TAG_NAME%-installer.exe -uredsky17:%BINTRAY_APIKEY% https://api.bintray.com/content/nheko-reborn/nheko/%APPVEYOR_REPO_TAG_NAME%/nheko/%APPVEYOR_REPO_TAG_NAME%/)
before_deploy:
- ps: .\.ci\upload-nightly.ps1
deploy: deploy:
- description: "Development builds" - description: "Development builds"
provider: GitHub provider: GitHub
@ -122,4 +128,3 @@ deploy:
artifacts: artifacts:
- path: nheko_win_64.zip - path: nheko_win_64.zip
- path: nheko-$(APPVEYOR_REPO_TAG_NAME)-installer.exe - path: nheko-$(APPVEYOR_REPO_TAG_NAME)-installer.exe
- path: nheko-$(APPVEYOR_PULL_REQUEST_HEAD_COMMIT)-installer.exe

View File

@ -0,0 +1,28 @@
function Component()
{
}
Component.prototype.createOperations = function()
{
component.createOperations();
try
{
if( installer.value("os") === "win" )
{
/**
* Cleanup AppData and registry
*/
component.addElevatedOperation("Execute","UNDOEXECUTE","cmd /C reg delete HKEY_CURRENT_USER\Software\nheko\nheko /f");
var localappdata = installer.environmentVariable("LOCALAPPDATA");
if( localappdata != "" )
{
component.addElevatedOperation("Execute","UNDOEXECUTE","cmd /C rmdir "+localappdata+"\nheko /f");
}
}
}
catch( e )
{
print( e );
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Package>
<DisplayName>Cleanup AppData and Registry</DisplayName>
<Description>Cleans up AppData and Registry when selected (logs you out) - Broken</Description>
<Version>__VERSION__</Version>
<ReleaseDate>__DATE__</ReleaseDate>
<SortingPriority>80</SortingPriority>
<Script>installscript.qs</Script>
<Checkable>false</Checkable>
</Package>

View File

@ -23,18 +23,6 @@ Component.prototype.createOperations = function()
component.addOperation( "CreateShortcut", "@TargetDir@\\nheko.exe", "@DesktopDir@\\nheko.lnk", component.addOperation( "CreateShortcut", "@TargetDir@\\nheko.exe", "@DesktopDir@\\nheko.lnk",
"workingDirectory=@TargetDir@", "iconPath=@TargetDir@\\nheko.exe", "workingDirectory=@TargetDir@", "iconPath=@TargetDir@\\nheko.exe",
"iconId=0", "description=Desktop client for the Matrix protocol"); "iconId=0", "description=Desktop client for the Matrix protocol");
var reg = installer.environmentVariable("SystemRoot") + "\\System32\\reg.exe";
var key= "HKEY_CLASSES_ROOT\\matrix";
component.addOperation("Execute", reg, "ADD", key, "/f", "/t", "REG_SZ", "/d", "URL:Matrix Protocol");
component.addOperation("Execute", reg, "ADD", key, "/f", "/v", "URL Protocol", "/t", "REG_SZ");
var iconkey = "HKEY_CLASSES_ROOT\\matrix\\DefaultIcon";
component.addOperation("Execute", reg, "ADD", iconkey, "/f", "/t", "REG_SZ", "/d", "@TargetDir@\\nheko.exe,1");
component.addOperation("Execute", reg, "ADD", "HKEY_CLASSES_ROOT\\matrix\\shell", "/f");
component.addOperation("Execute", reg, "ADD", "HKEY_CLASSES_ROOT\\matrix\\shell\\open", "/f");
var commandkey = "HKEY_CLASSES_ROOT\\matrix\\shell\\open\\command"
component.addOperation("Execute", reg, "ADD", commandkey, "/f");
component.addOperation("Execute", reg, "ADD", commandkey, "/f", "/t", "REG_SZ", "/d", "\"@TargetDir@\\nheko.exe\" \"%1\"");
} }
} }
catch( e ) catch( e )

View File

@ -0,0 +1,269 @@
{
"id": "io.github.NhekoReborn.Nheko",
"command": "nheko",
"runtime": "org.kde.Platform",
"runtime-version": "5.15",
"sdk": "org.kde.Sdk",
"rename-icon": "nheko",
"rename-desktop-file": "nheko.desktop",
"rename-appdata-file": "nheko.appdata.xml",
"finish-args": [
"--device=dri",
"--share=ipc",
"--share=network",
"--socket=pulseaudio",
"--socket=wayland",
"--socket=x11",
"--talk-name=org.freedesktop.Notifications",
"--talk-name=org.kde.StatusNotifierWatcher"
],
"cleanup": [
"/include",
"/bin/mdb*",
"*.a"
],
"build-options" : {
"arch": {
"aarch64": {
"cxxflags": "-DBOOST_ASIO_DISABLE_EPOLL"
}
}
},
"modules": [
{
"name": "lmdb",
"sources": [
{
"sha256": "f3927859882eb608868c8c31586bb7eb84562a40a6bf5cc3e13b6b564641ea28",
"type": "archive",
"url": "https://github.com/LMDB/lmdb/archive/LMDB_0.9.22.tar.gz"
}
],
"make-install-args": [
"prefix=/app"
],
"no-autogen": true,
"subdir": "libraries/liblmdb"
},
{
"name": "cmark",
"buildsystem": "cmake-ninja",
"builddir": true,
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release",
"-DCMARK_TESTS=OFF"
],
"sources": [
{
"sha256": "2558ace3cbeff85610de3bda32858f722b359acdadf0c4691851865bb84924a6",
"type": "archive",
"url": "https://github.com/commonmark/cmark/archive/0.29.0.tar.gz"
}
]
},
{
"name": "spdlog",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release",
"-DSPDLOG_BUILD_EXAMPLES=0",
"-DSPDLOG_BUILD_BENCH=0",
"-DSPDLOG_BUILD_TESTING=0"
],
"sources": [
{
"sha256": "5197b3147cfcfaa67dd564db7b878e4a4b3d9f3443801722b3915cdeced656cb",
"type": "archive",
"url": "https://github.com/gabime/spdlog/archive/v1.8.1.tar.gz"
}
]
},
{
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release"
],
"buildsystem": "cmake-ninja",
"name": "olm",
"sources": [
{
"commit": "6753595300767dd70150831dbbe6f92d64e75038",
"disable-shallow-clone": true,
"tag": "3.1.4",
"type": "git",
"url": "https://gitlab.matrix.org/matrix-org/olm.git"
}
]
},
{
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release",
"-DBUILD_TEST_APPLICATION=OFF",
"-DQTKEYCHAIN_STATIC=ON"
],
"buildsystem": "cmake-ninja",
"name": "QtKeychain",
"sources": [
{
"commit": "815fe610353ff8ad7e2f1121c368a74df8db5eb7",
"tag": "v0.12.0",
"type": "git",
"url": "https://github.com/frankosterfeld/qtkeychain.git"
}
]
},
{
"config-opts":[
"-DJSON_BuildTests=OFF"
],
"buildsystem":"cmake",
"name": "nlohmann",
"sources":[
{
"sha256": "d51a3a8d3efbb1139d7608e28782ea9efea7e7933157e8ff8184901efd8ee760",
"type": "archive",
"url": "https://github.com/nlohmann/json/archive/v3.7.0.tar.gz"
}
]
},
{
"build-commands": [
"./bootstrap.sh --with-libraries=thread,system,iostreams --prefix=/app",
"./b2 -d0 variant=release link=static threading=multi --layout=system",
"./b2 -d0 install"
],
"buildsystem": "simple",
"name": "boost",
"sources": [
{
"sha256": "59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722",
"type": "archive",
"url": "https://sourceforge.net/projects/boost/files/boost/1.72.0/boost_1_72_0.tar.bz2"
}
]
},
{
"buildsystem": "meson",
"name": "gstreamer",
"sources": [
{
"commit": "a42fe476d3ee5576921f67a331464065ec33b9a4",
"tag": "1.18.3",
"type": "git",
"url": "https://gitlab.freedesktop.org/gstreamer/gstreamer.git"
}
]
},
{
"config-opts": [
"-Dcompositor=enabled",
"-Dgl=enabled"
],
"buildsystem": "meson",
"name": "gstreamer-plugins-base",
"sources": [
{
"commit": "2cc319ee13f6b72df3d432b7c75aca81feb260e5",
"tag": "1.18.3",
"type": "git",
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-base.git"
}
]
},
{
"config-opts": [
"-Dpulse=enabled",
"-Dqt5=enabled",
"-Drtp=enabled",
"-Drtpmanager=enabled",
"-Dvpx=enabled"
],
"buildsystem": "meson",
"name": "gstreamer-plugins-good",
"sources": [
{
"commit": "e816c6cd73c9e0676828c9e227a049ebad3d019f",
"tag": "1.18.3",
"type": "git",
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-good.git"
}
]
},
{
"config-opts": [
"-Ddtls=enabled",
"-Dgl=enabled",
"-Dopenh264=enabled",
"-Dopus=enabled",
"-Dsrtp=enabled",
"-Dwebrtc=enabled",
"-Dflite=disabled"
],
"buildsystem": "meson",
"name": "gstreamer-plugins-bad",
"sources": [
{
"commit": "382e373d9be363f1e21b12990a4d12f1ecb6df41",
"tag": "1.18.3",
"type": "git",
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad.git"
}
]
},
{
"config-opts": [
"-DBUILD_LIB_TESTS=OFF",
"-DBUILD_LIB_EXAMPLES=OFF",
"-DCMAKE_BUILD_TYPE=Release",
"-DBUILD_SHARED_LIBS=OFF"
],
"buildsystem": "cmake-ninja",
"name": "mtxclient",
"sources": [
{
"commit": "2d6e3f79917ce2065a54ca32e6a9f9d42c0b6347",
"tag": "v0.4.0",
"type": "git",
"url": "https://github.com/Nheko-Reborn/mtxclient.git"
}
]
},
{
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release",
"-DTWEENY_BUILD_DOCUMENTATION=OFF",
"-DTWEENY_BUILD_EXAMPLES=OFF"
],
"buildsystem": "cmake-ninja",
"name": "tweeny",
"sources": [
{
"sha256": "482857256a7235646004682912badb6521d361ed6987c8ebdae7986bf64ce694",
"type": "archive",
"url": "https://github.com/mobius3/tweeny/archive/43f4130f7e4a67c19d870b60864bc2862c19b81f.tar.gz"
}
]
},
{
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release",
"-DLMDBXX_INCLUDE_DIR=.deps/lmdbxx",
"-DCOMPILE_QML=ON"
],
"buildsystem": "cmake-ninja",
"name": "nheko",
"sources": [
{
"path": ".",
"type": "dir",
"skip": ["build-flatpak"]
},
{
"dest": ".deps/lmdbxx",
"sha256": "93721132bbf5045d38ad62de2997655e9984c48ea5c9886746d42128f4b26fbd",
"type": "archive",
"url": "https://github.com/bendiken/lmdbxx/archive/0b43ca87d8cfabba392dfe884eb1edb83874de02.tar.gz"
}
]
}
]
}

View File

@ -1,180 +0,0 @@
id: io.github.NhekoReborn.Nheko
command: nheko
runtime: org.kde.Platform
runtime-version: '5.15'
sdk: org.kde.Sdk
rename-icon: nheko
rename-desktop-file: nheko.desktop
rename-appdata-file: nheko.appdata.xml
finish-args:
- --device=dri
# needed for webcams, see #517
- --device=all
- --share=ipc
- --share=network
- --socket=pulseaudio
- --socket=wayland
- --socket=x11
- --talk-name=org.freedesktop.Notifications
- --talk-name=org.freedesktop.secrets
- --talk-name=org.freedesktop.StatusNotifierItem
- --talk-name=org.kde.*
cleanup:
- /include
- /bin/mdb*
- '*.a'
build-options:
arch:
aarch64:
cxxflags: -DBOOST_ASIO_DISABLE_EPOLL
modules:
- name: lmdb
sources:
- sha256: f3927859882eb608868c8c31586bb7eb84562a40a6bf5cc3e13b6b564641ea28
type: archive
url: https://github.com/LMDB/lmdb/archive/LMDB_0.9.22.tar.gz
make-install-args:
- prefix=/app
no-autogen: true
subdir: libraries/liblmdb
- name: cmark
buildsystem: cmake-ninja
builddir: true
config-opts:
- -DCMAKE_BUILD_TYPE=Release
- -DCMARK_TESTS=OFF
sources:
- sha256: 2558ace3cbeff85610de3bda32858f722b359acdadf0c4691851865bb84924a6
type: archive
url: https://github.com/commonmark/cmark/archive/0.29.0.tar.gz
- name: spdlog
buildsystem: cmake-ninja
config-opts:
- -DCMAKE_BUILD_TYPE=Release
- -DSPDLOG_BUILD_EXAMPLES=0
- -DSPDLOG_BUILD_BENCH=0
- -DSPDLOG_BUILD_TESTING=0
sources:
- sha256: 5197b3147cfcfaa67dd564db7b878e4a4b3d9f3443801722b3915cdeced656cb
type: archive
url: https://github.com/gabime/spdlog/archive/v1.8.1.tar.gz
- config-opts:
- -DCMAKE_BUILD_TYPE=Release
buildsystem: cmake-ninja
name: olm
sources:
- commit: 6753595300767dd70150831dbbe6f92d64e75038
disable-shallow-clone: true
tag: 3.1.4
type: git
url: https://gitlab.matrix.org/matrix-org/olm.git
- config-opts:
- -DCMAKE_BUILD_TYPE=Release
- -DBUILD_TEST_APPLICATION=OFF
- -DQTKEYCHAIN_STATIC=ON
buildsystem: cmake-ninja
name: QtKeychain
sources:
- commit: 815fe610353ff8ad7e2f1121c368a74df8db5eb7
tag: v0.12.0
type: git
url: https://github.com/frankosterfeld/qtkeychain.git
- config-opts:
- -DJSON_BuildTests=OFF
buildsystem: cmake
name: nlohmann
sources:
- sha256: d51a3a8d3efbb1139d7608e28782ea9efea7e7933157e8ff8184901efd8ee760
type: archive
url: https://github.com/nlohmann/json/archive/v3.7.0.tar.gz
- build-commands:
- ./bootstrap.sh --with-libraries=thread,system,iostreams --prefix=/app
- ./b2 -d0 variant=release link=static threading=multi --layout=system
- ./b2 -d0 install
buildsystem: simple
name: boost
sources:
- sha256: 59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722
type: archive
url: https://sourceforge.net/projects/boost/files/boost/1.72.0/boost_1_72_0.tar.bz2
- buildsystem: meson
name: gstreamer
sources:
- commit: a42fe476d3ee5576921f67a331464065ec33b9a4
tag: 1.18.3
type: git
url: https://gitlab.freedesktop.org/gstreamer/gstreamer.git
- config-opts:
- -Dcompositor=enabled
- -Dgl=enabled
buildsystem: meson
name: gstreamer-plugins-base
sources:
- commit: 2cc319ee13f6b72df3d432b7c75aca81feb260e5
tag: 1.18.3
type: git
url: https://gitlab.freedesktop.org/gstreamer/gst-plugins-base.git
- config-opts:
- -Dpulse=enabled
- -Dqt5=enabled
- -Drtp=enabled
- -Drtpmanager=enabled
- -Dvpx=enabled
buildsystem: meson
name: gstreamer-plugins-good
sources:
- commit: e816c6cd73c9e0676828c9e227a049ebad3d019f
tag: 1.18.3
type: git
url: https://gitlab.freedesktop.org/gstreamer/gst-plugins-good.git
- config-opts:
- -Ddtls=enabled
- -Dgl=enabled
- -Dopenh264=enabled
- -Dopus=enabled
- -Dsrtp=enabled
- -Dwebrtc=enabled
- -Dflite=disabled
buildsystem: meson
name: gstreamer-plugins-bad
sources:
- commit: 382e373d9be363f1e21b12990a4d12f1ecb6df41
tag: 1.18.3
type: git
url: https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad.git
- config-opts:
- -DBUILD_LIB_TESTS=OFF
- -DBUILD_LIB_EXAMPLES=OFF
- -DCMAKE_BUILD_TYPE=Release
- -DBUILD_SHARED_LIBS=OFF
buildsystem: cmake-ninja
name: mtxclient
sources:
- commit: 5d2f055ea9403770039ddf66b1900f890cd5cde7
type: git
url: https://github.com/Nheko-Reborn/mtxclient.git
- config-opts:
- -DCMAKE_BUILD_TYPE=Release
- -DTWEENY_BUILD_DOCUMENTATION=OFF
- -DTWEENY_BUILD_EXAMPLES=OFF
buildsystem: cmake-ninja
name: tweeny
sources:
- sha256: 482857256a7235646004682912badb6521d361ed6987c8ebdae7986bf64ce694
type: archive
url: https://github.com/mobius3/tweeny/archive/43f4130f7e4a67c19d870b60864bc2862c19b81f.tar.gz
- config-opts:
- -DCMAKE_BUILD_TYPE=Release
- -DLMDBXX_INCLUDE_DIR=.deps/lmdbxx
- -DCOMPILE_QML=ON
buildsystem: cmake-ninja
name: nheko
sources:
- path: .
type: dir
skip:
- build-flatpak
- dest: .deps/lmdbxx
sha256: 5e12eb3aefe9050068af7df2c663edabc977ef34c9e7ba7b9d2c43e0ad47d8df
type: archive
url: https://github.com/hoytech/lmdbxx/archive/1.0.0.tar.gz

View File

@ -1,10 +0,0 @@
[Flatpak Ref]
Title=Nheko Nightly
Name=io.github.NhekoReborn.Nheko
Branch=master
Url=https://flatpak.neko.dev/repo/nightly
Homepage=https://nheko-reborn.github.io/
Icon=https://nheko.im/nheko-reborn/nheko/-/raw/master/resources/nheko.svg
RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo
IsRuntime=false
GPGKey=mDMEXENMphYJKwYBBAHaRw8BAQdAqn+Eo42lPoGpJ5HaOf4nFGfxR0QtOggJTCfsdbOyL4e0Kk5pY29sYXMgV2VybmVyIDxuaWNvbGFzLndlcm5lckBob3RtYWlsLmRlPoiWBBMWCAA+FiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAlxDTVUCGwMFCQtJjooFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQkgauGyMeBbs2rQD/dAEoOGT21BL85A8LmPK743EboBAjoRbWcI1hHnvS28AA/3b3HYGwgvTC6hQLyz75zjpeO5ZaUtbezRyDUR4xabMAtCROaWNvbGFzIFdlcm5lciA8bmljb2xhc0BuZWtvZGV2Lm5ldD6IlgQTFggAPhYhBNWLRiQlpqNxJcb+25IGrhsjHgW7BQJcQ01GAhsDBQkLSY6KBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEJIGrhsjHgW7GxwBANT4gL03Uu9N5KmTBihC7B71+0r7M/azPbUh86NthCeIAQCF2JXa0axBKhgQF5fWC5ncL+m8ZpH7C5rzDqVgO82WALQnTmljb2xhcyBXZXJuZXIgPG5pY29sYXMud2VybmVyQGdteC5uZXQ+iJYEExYIAD4WIQTVi0YkJaajcSXG/tuSBq4bIx4FuwUCXENMpgIbAwUJC0mOigULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCSBq4bIx4FuxU5APoCRDYlJW0oTsJs3lcTTB5Nsqb3X4iCEDCjIgsA3wtsIwEAlGBzD8ElCYi2+8m8esSRNlmpRcGoqgXbceLxPUXFpQu4OARcQ0ymEgorBgEEAZdVAQUBAQdAD8dBmT3iqrqdlxSw90L0SIH11fVxiX9MdWfBkTi6PzUDAQgHiH4EGBYIACYWIQTVi0YkJaajcSXG/tuSBq4bIx4FuwUCXENMpgIbDAUJC0mOigAKCRCSBq4bIx4Fu/LNAQDhH64IBic6h7H3uvtSAFT4xNn7Epobt2baIaDp7uKsQQEAyI+oc5dLknABwIOMrQQuZCmGejx9e4/8HEqLCdszhgG4MwRgNICHFgkrBgEEAdpHDwEBB0DR9eFFzfR62FIi7g+txcQenLvKFzhlyTz0wo3icOy6RYj1BBgWCAAmFiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAmA0gIcCGwIFCQlmAYAAgQkQkgauGyMeBbt2IAQZFggAHRYhBGz14re9h4cNPaFEKMjXXmEHc/LZBQJgNICHAAoJEMjXXmEHc/LZhVMBAPdYRspdeFh6E9BDxGubT705e/pZFdCHjCToDyxgdW5KAP9sU0hFI5VDHD1h98RzxSt7hc3jxyPSzbG1MBUJ9gbfCVhcAPsFfeZc3v5UBgmn4uICFEGjlzAWCQ7WctE6QTSkY5aL/wD9ETJH5lB+i/8km/sOBKQozXR0yHHw46gB6ZWMeN1wfgq4MwRgNPutFgkrBgEEAdpHDwEBB0APwMn0FJmnAds8IO8iCl/RHr7fz8xnpGd7E4zVgCNZpIj1BBgWCAAmFiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAmA0+60CGwIFCQANLwAAgQkQkgauGyMeBbt2IAQZFggAHRYhBAH7QBkzNfVIZJM93RNnXzGtBKQcBQJgNPutAAoJEBNnXzGtBKQcHnUA/0E2H5sxmfZ+EWFTso3X4NWu3uN2xF+MdNaY8C72f9H6AP91XaNmlB9gV61rg6wcB5E/j0998yWS9gltY1XY1ImqDPvlAP4sHFs5zuDazgKYxZ/kFhENCgEStdpnvJjt/DxmQPVT3AD/QK5vGoMTIeYjihv0QCnnRDfboTTZHlaEqJW8i02PQww=

View File

@ -1,8 +0,0 @@
[Flatpak Repo]
Title=Nheko Nightly
Url=https://flatpak.neko.dev/repo/nightly
Homepage=https://nheko.im/
Comment=Nheko nightly release repository
Description=Nheko nightly release repository
Icon=https://nheko.im/nheko-reborn/nheko/-/raw/master/resources/nheko.svg
GPGKey=mDMEXENMphYJKwYBBAHaRw8BAQdAqn+Eo42lPoGpJ5HaOf4nFGfxR0QtOggJTCfsdbOyL4e0Kk5pY29sYXMgV2VybmVyIDxuaWNvbGFzLndlcm5lckBob3RtYWlsLmRlPoiWBBMWCAA+FiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAlxDTVUCGwMFCQtJjooFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQkgauGyMeBbs2rQD/dAEoOGT21BL85A8LmPK743EboBAjoRbWcI1hHnvS28AA/3b3HYGwgvTC6hQLyz75zjpeO5ZaUtbezRyDUR4xabMAtCROaWNvbGFzIFdlcm5lciA8bmljb2xhc0BuZWtvZGV2Lm5ldD6IlgQTFggAPhYhBNWLRiQlpqNxJcb+25IGrhsjHgW7BQJcQ01GAhsDBQkLSY6KBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEJIGrhsjHgW7GxwBANT4gL03Uu9N5KmTBihC7B71+0r7M/azPbUh86NthCeIAQCF2JXa0axBKhgQF5fWC5ncL+m8ZpH7C5rzDqVgO82WALQnTmljb2xhcyBXZXJuZXIgPG5pY29sYXMud2VybmVyQGdteC5uZXQ+iJYEExYIAD4WIQTVi0YkJaajcSXG/tuSBq4bIx4FuwUCXENMpgIbAwUJC0mOigULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCSBq4bIx4FuxU5APoCRDYlJW0oTsJs3lcTTB5Nsqb3X4iCEDCjIgsA3wtsIwEAlGBzD8ElCYi2+8m8esSRNlmpRcGoqgXbceLxPUXFpQu4OARcQ0ymEgorBgEEAZdVAQUBAQdAD8dBmT3iqrqdlxSw90L0SIH11fVxiX9MdWfBkTi6PzUDAQgHiH4EGBYIACYWIQTVi0YkJaajcSXG/tuSBq4bIx4FuwUCXENMpgIbDAUJC0mOigAKCRCSBq4bIx4Fu/LNAQDhH64IBic6h7H3uvtSAFT4xNn7Epobt2baIaDp7uKsQQEAyI+oc5dLknABwIOMrQQuZCmGejx9e4/8HEqLCdszhgG4MwRgNICHFgkrBgEEAdpHDwEBB0DR9eFFzfR62FIi7g+txcQenLvKFzhlyTz0wo3icOy6RYj1BBgWCAAmFiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAmA0gIcCGwIFCQlmAYAAgQkQkgauGyMeBbt2IAQZFggAHRYhBGz14re9h4cNPaFEKMjXXmEHc/LZBQJgNICHAAoJEMjXXmEHc/LZhVMBAPdYRspdeFh6E9BDxGubT705e/pZFdCHjCToDyxgdW5KAP9sU0hFI5VDHD1h98RzxSt7hc3jxyPSzbG1MBUJ9gbfCVhcAPsFfeZc3v5UBgmn4uICFEGjlzAWCQ7WctE6QTSkY5aL/wD9ETJH5lB+i/8km/sOBKQozXR0yHHw46gB6ZWMeN1wfgq4MwRgNPutFgkrBgEEAdpHDwEBB0APwMn0FJmnAds8IO8iCl/RHr7fz8xnpGd7E4zVgCNZpIj1BBgWCAAmFiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAmA0+60CGwIFCQANLwAAgQkQkgauGyMeBbt2IAQZFggAHRYhBAH7QBkzNfVIZJM93RNnXzGtBKQcBQJgNPutAAoJEBNnXzGtBKQcHnUA/0E2H5sxmfZ+EWFTso3X4NWu3uN2xF+MdNaY8C72f9H6AP91XaNmlB9gV61rg6wcB5E/j0998yWS9gltY1XY1ImqDPvlAP4sHFs5zuDazgKYxZ/kFhENCgEStdpnvJjt/DxmQPVT3AD/QK5vGoMTIeYjihv0QCnnRDfboTTZHlaEqJW8i02PQww=

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 773 B

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -13,23 +13,13 @@
</description> </description>
<translation/> <translation/>
<languages> <languages>
<lang>cs</lang>
<lang>de</lang> <lang>de</lang>
<lang>el</lang> <lang>el</lang>
<lang>en</lang> <lang>en</lang>
<lang>eo</lang>
<lang>et</lang>
<lang>fi</lang>
<lang>fr</lang> <lang>fr</lang>
<lang>hu</lang>
<lang>it</lang>
<lang>ja</lang>
<lang>ml</lang>
<lang>nl</lang> <lang>nl</lang>
<lang>pl</lang> <lang>pl</lang>
<lang>ro</lang>
<lang>ru</lang> <lang>ru</lang>
<lang>sv</lang>
<lang>zh_CN</lang> <lang>zh_CN</lang>
</languages> </languages>
<content_rating type="oars-1.0"> <content_rating type="oars-1.0">
@ -53,8 +43,6 @@
<url type="homepage">https://github.com/Nheko-Reborn/nheko</url> <url type="homepage">https://github.com/Nheko-Reborn/nheko</url>
<update_contact>https://github.com/Nheko-Reborn</update_contact> <update_contact>https://github.com/Nheko-Reborn</update_contact>
<releases> <releases>
<release date="2021-04-23" version="0.8.2"/>
<release date="2021-01-27" version="0.8.1"/>
<release date="2021-01-21" version="0.8.0"/> <release date="2021-01-21" version="0.8.0"/>
<release date="2020-06-12" version="0.7.2"/> <release date="2020-06-12" version="0.7.2"/>
<release date="2020-04-24" version="0.7.1"/> <release date="2020-04-24" version="0.7.1"/>
@ -69,11 +57,12 @@
<release date="2018-08-12" version="0.5.3"/> <release date="2018-08-12" version="0.5.3"/>
<release date="2018-07-28" version="0.5.2"/> <release date="2018-07-28" version="0.5.2"/>
</releases> </releases>
<developer_name>Nheko Reborn</developer_name>
<url type="bugtracker">https://github.com/Nheko-Reborn/nheko/issues</url>
<url type="help">https://github.com/Nheko-Reborn/nheko/</url>
<url type="translate">https://weblate.nheko.im/projects/nheko/</url>
<!--FIXME: where to donate to the application--> <developer_name>Nheko Reborn</developer_name>
<url type="donation"/>
<url type="bugtracker">https://github.com/Nheko-Reborn/nheko/issues</url>
<url type="help">https://github.com/Nheko-Reborn/nheko/</url>
<url type="translate">https://weblate.nheko.im/projects/nheko/</url>
</component> </component>

View File

@ -2,7 +2,7 @@
Name=nheko Name=nheko
Version=1.0 Version=1.0
Comment=Desktop client for Matrix Comment=Desktop client for Matrix
Exec=nheko %u Exec=nheko
Icon=nheko Icon=nheko
Type=Application Type=Application
Categories=Network;InstantMessaging;Qt; Categories=Network;InstantMessaging;Qt;

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./ui" import "./ui"
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick 2.6 import QtQuick 2.6
@ -61,7 +57,6 @@ Rectangle {
} }
layer.effect: OpacityMask { layer.effect: OpacityMask {
cached: true
maskSource: Rectangle { maskSource: Rectangle {
anchors.fill: parent anchors.fill: parent
@ -95,9 +90,4 @@ Rectangle {
} }
} }
CursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
} }

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./ui" import "./ui"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -15,16 +11,8 @@ Popup {
property string completerName property string completerName
property var completer property var completer
property bool bottomToTop: true property bool bottomToTop: true
property bool fullWidth: false
property bool centerRowContent: true
property int avatarHeight: 24
property int avatarWidth: 24
property int rowMargin: 0
property int rowSpacing: 5
property alias count: listView.count
signal completionClicked(string completion) signal completionClicked(string completion)
signal completionSelected(string id)
function up() { function up() {
if (bottomToTop) if (bottomToTop)
@ -61,18 +49,9 @@ Popup {
return null; return null;
} }
function finishCompletion() {
if (popup.completerName == "room")
popup.completionSelected(listView.itemAtIndex(currentIndex).modelData.roomid);
}
onCompleterNameChanged: { onCompleterNameChanged: {
if (completerName) { if (completerName) {
if (completerName == "user") completer = TimelineManager.timeline.input.completerFor(completerName);
completer = TimelineManager.completerFor(completerName, TimelineManager.timeline.roomId());
else
completer = TimelineManager.completerFor(completerName);
completer.setSearchString(""); completer.setSearchString("");
} else { } else {
completer = undefined; completer = undefined;
@ -91,31 +70,22 @@ Popup {
id: listView id: listView
anchors.fill: parent anchors.fill: parent
implicitWidth: fullWidth ? parent.width : contentItem.childrenRect.width implicitWidth: contentItem.childrenRect.width
model: completer model: completer
verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom verticalLayoutDirection: popup.bottomToTop ? ListView.BottomToTop : ListView.TopToBottom
spacing: rowSpacing
pixelAligned: true
delegate: Rectangle { delegate: Rectangle {
property variant modelData: model
color: model.index == popup.currentIndex ? colors.highlight : colors.base color: model.index == popup.currentIndex ? colors.highlight : colors.base
height: chooser.childrenRect.height + 2 * popup.rowMargin height: chooser.childrenRect.height + 4
implicitWidth: fullWidth ? popup.width : chooser.childrenRect.width + 4 implicitWidth: chooser.childrenRect.width + 4
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onPositionChanged: popup.currentIndex = model.index onEntered: popup.currentIndex = model.index
onClicked: { onClicked: popup.completionClicked(completer.completionAt(model.index))
popup.completionClicked(completer.completionAt(model.index));
if (popup.completerName == "room")
popup.completionSelected(model.roomid);
}
Ripple { Ripple {
rippleTarget: mouseArea rippleTarget: mouseArea
@ -128,8 +98,7 @@ Popup {
id: chooser id: chooser
roleValue: popup.completerName roleValue: popup.completerName
anchors.fill: parent anchors.centerIn: parent
anchors.margins: popup.rowMargin
DelegateChoice { DelegateChoice {
roleValue: "user" roleValue: "user"
@ -138,11 +107,10 @@ Popup {
id: del id: del
anchors.centerIn: parent anchors.centerIn: parent
spacing: rowSpacing
Avatar { Avatar {
height: popup.avatarHeight height: 24
width: popup.avatarWidth width: 24
displayName: model.displayName displayName: model.displayName
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
onClicked: popup.completionClicked(completer.completionAt(model.index)) onClicked: popup.completionClicked(completer.completionAt(model.index))
@ -169,7 +137,6 @@ Popup {
id: del id: del
anchors.centerIn: parent anchors.centerIn: parent
spacing: rowSpacing
Label { Label {
text: model.unicode text: model.unicode
@ -186,67 +153,6 @@ Popup {
} }
DelegateChoice {
roleValue: "room"
RowLayout {
id: del
anchors.centerIn: centerRowContent ? parent : undefined
spacing: rowSpacing
Avatar {
height: popup.avatarHeight
width: popup.avatarWidth
displayName: model.roomName
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
onClicked: {
popup.completionClicked(completer.completionAt(model.index));
popup.completionSelected(model.roomid);
}
}
Label {
text: model.roomName
font.pixelSize: popup.avatarHeight * 0.5
color: model.index == popup.currentIndex ? colors.highlightedText : colors.text
}
}
}
DelegateChoice {
roleValue: "roomAliases"
RowLayout {
id: del
anchors.centerIn: parent
spacing: rowSpacing
Avatar {
height: popup.avatarHeight
width: popup.avatarWidth
displayName: model.roomName
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
onClicked: popup.completionClicked(completer.completionAt(model.index))
}
Label {
text: model.roomName
color: model.index == popup.currentIndex ? colors.highlightedText : colors.text
}
Label {
text: "(" + model.roomAlias + ")"
color: model.index == popup.currentIndex ? colors.highlightedText : colors.buttonText
}
}
}
} }
} }

View File

@ -1,50 +1,44 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors import QtQuick 2.5
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import im.nheko 1.0 import im.nheko 1.0
Image { Rectangle {
id: stateImg id: indicator
property bool encrypted: false property bool encrypted: false
property int trust: Crypto.Unverified
function getEncryptionImage() {
if (encrypted)
return "image://colorimage/:/icons/icons/ui/lock.png?" + colors.buttonText;
else
return "image://colorimage/:/icons/icons/ui/unlock.png?#dd3d3d";
}
function getEncryptionTooltip() {
if (encrypted)
return qsTr("Encrypted");
else
return qsTr("This message is not encrypted!");
}
color: "transparent"
width: 16 width: 16
height: 16 height: 16
source: { ToolTip.visible: ma.containsMouse && indicator.visible
if (encrypted) { ToolTip.text: getEncryptionTooltip()
switch (trust) {
case Crypto.Verified:
return "image://colorimage/:/icons/icons/ui/lock.png?green";
case Crypto.TOFU:
return "image://colorimage/:/icons/icons/ui/lock.png?" + colors.buttonText;
default:
return "image://colorimage/:/icons/icons/ui/lock.png?#dd3d3d";
}
} else {
return "image://colorimage/:/icons/icons/ui/unlock.png?#dd3d3d";
}
}
ToolTip.visible: ma.hovered
ToolTip.text: {
if (!encrypted)
return qsTr("This message is not encrypted!");
switch (trust) { MouseArea {
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");
}
}
HoverHandler {
id: ma id: ma
anchors.fill: parent
hoverEnabled: true
}
Image {
id: stateImg
anchors.fill: parent
source: getEncryptionImage()
} }
} }

View File

@ -1,117 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./delegates/"
import QtQuick 2.9
import QtQuick.Controls 2.3
import im.nheko 1.0
Popup {
id: forwardMessagePopup
property var mid
function setMessageEventId(mid_in) {
mid = mid_in;
}
x: Math.round(parent.width / 2 - width / 2)
y: Math.round(parent.height / 2 - height / 2)
modal: true
palette: colors
parent: Overlay.overlay
width: implicitWidth >= (timelineRoot.width * 0.8) ? implicitWidth : (timelineRoot.width * 0.8)
height: implicitHeight + completerPopup.height + padding * 2
leftPadding: 10
rightPadding: 10
onOpened: {
completerPopup.open();
roomTextInput.forceActiveFocus();
}
onClosed: {
completerPopup.close();
}
Column {
id: forwardColumn
spacing: 5
Label {
id: titleLabel
text: qsTr("Forward Message")
font.bold: true
bottomPadding: 10
color: colors.text
}
Reply {
id: replyPreview
modelData: TimelineManager.timeline ? TimelineManager.timeline.getDump(mid, "") : {
}
userColor: TimelineManager.userColor(modelData.userId, colors.window)
}
MatrixTextField {
id: roomTextInput
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
color: colors.text
onTextEdited: {
completerPopup.completer.searchString = text;
}
Keys.onPressed: {
if (event.key == Qt.Key_Up && completerPopup.opened) {
event.accepted = true;
completerPopup.up();
} else if (event.key == Qt.Key_Down && completerPopup.opened) {
event.accepted = true;
completerPopup.down();
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
completerPopup.finishCompletion();
event.accepted = true;
}
}
}
}
Completer {
id: completerPopup
y: titleLabel.height + replyPreview.height + roomTextInput.height + roomTextInput.bottomPadding + forwardColumn.spacing * 3
width: forwardMessagePopup.width - forwardMessagePopup.leftPadding * 2
completerName: "room"
fullWidth: true
centerRowContent: false
avatarHeight: 24
avatarWidth: 24
bottomToTop: false
closePolicy: Popup.NoAutoClose
}
Connections {
onCompletionSelected: {
TimelineManager.timeline.forwardMessage(messageContextMenu.eventId, id);
forwardMessagePopup.close();
}
onCountChanged: {
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
completerPopup.currentIndex = 0;
}
target: completerPopup
}
background: Rectangle {
color: colors.window
}
Overlay.modal: Rectangle {
color: Qt.rgba(colors.window.r, colors.window.g, colors.window.b, 0.7)
}
}

View File

@ -1,11 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./ui" import "./ui"
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import im.nheko 1.0 // for cursor shape
AbstractButton { AbstractButton {
id: button id: button
@ -28,10 +23,11 @@ AbstractButton {
source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : "" source: image != "" ? ("image://colorimage/" + image + "?" + ((button.hovered && changeColorOnHover) ? highlightColor : buttonTextColor)) : ""
} }
CursorShape { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import im.nheko 1.0 import im.nheko 1.0
@ -12,15 +8,29 @@ TextEdit {
focus: false focus: false
wrapMode: Text.Wrap wrapMode: Text.Wrap
selectByMouse: !Settings.mobileMode selectByMouse: !Settings.mobileMode
enabled: selectByMouse
color: colors.text color: colors.text
onLinkActivated: TimelineManager.openLink(link) onLinkActivated: {
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) {
chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1]);
} else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) {
TimelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1]);
} else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) {
var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link);
TimelineManager.setHistoryView(match[1]);
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain);
} else {
TimelineManager.openLink(link);
}
}
ToolTip.visible: hoveredLink ToolTip.visible: hoveredLink
ToolTip.text: hoveredLink ToolTip.text: hoveredLink
CursorShape { MouseArea {
id: ma
anchors.fill: parent anchors.fill: parent
cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
} }

View File

@ -1,66 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
TextField {
id: input
palette: colors
Rectangle {
id: blueBar
anchors.top: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
color: colors.highlight
height: 1
width: parent.width
Rectangle {
id: blackBar
anchors.verticalCenter: blueBar.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
height: parent.height + 1
width: 0
color: colors.text
states: State {
name: "focused"
when: input.activeFocus == true
PropertyChanges {
target: blackBar
width: blueBar.width
}
}
transitions: Transition {
from: ""
to: "focused"
reversible: true
NumberAnimation {
target: blackBar
properties: "width"
duration: 500
easing.type: Easing.InOutQuad
alwaysRunToEnd: true
}
}
}
}
background: Rectangle {
color: colors.base
}
}

View File

@ -1,20 +1,14 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./voip" import "./voip"
import QtQuick 2.12 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import im.nheko 1.0 import im.nheko 1.0
Rectangle { Rectangle {
id: inputBar
color: colors.window color: colors.window
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: row.implicitHeight Layout.preferredHeight: textInput.height + 16
Layout.minimumHeight: 40 Layout.minimumHeight: 40
Component { Component {
@ -26,10 +20,11 @@ Rectangle {
} }
RowLayout { RowLayout {
id: row id: inputBar
visible: (TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false) || messageContextMenu.isSender
anchors.fill: parent anchors.fill: parent
anchors.margins: 8
spacing: 16
ImageButton { ImageButton {
visible: CallManager.callsSupported visible: CallManager.callsSupported
@ -41,7 +36,7 @@ Rectangle {
image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.png" : ":/icons/icons/ui/place-call.png" image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.png" : ":/icons/icons/ui/place-call.png"
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call") ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call")
Layout.margins: 8 Layout.leftMargin: 8
onClicked: { onClicked: {
if (TimelineManager.timeline) { if (TimelineManager.timeline) {
if (CallManager.haveCallInvite) { if (CallManager.haveCallInvite) {
@ -49,6 +44,7 @@ Rectangle {
} else if (CallManager.isOnCall) { } else if (CallManager.isOnCall) {
CallManager.hangUp(); CallManager.hangUp();
} else { } else {
CallManager.refreshDevices();
var dialog = placeCallDialog.createObject(timelineRoot); var dialog = placeCallDialog.createObject(timelineRoot);
dialog.open(); dialog.open();
} }
@ -62,7 +58,7 @@ Rectangle {
width: 22 width: 22
height: 22 height: 22
image: ":/icons/icons/ui/paper-clip-outline.png" image: ":/icons/icons/ui/paper-clip-outline.png"
Layout.margins: 8 Layout.leftMargin: CallManager.callsSupported ? 0 : 8
onClicked: TimelineManager.timeline.input.openFileSelection() onClicked: TimelineManager.timeline.input.openFileSelection()
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Send a file") ToolTip.text: qsTr("Send a file")
@ -81,53 +77,71 @@ Rectangle {
} }
ScrollView { Flickable {
id: textInput id: textInput
Layout.alignment: Qt.AlignBottom // | Qt.AlignHCenter function ensureVisible(r) {
if (contentX >= r.x)
contentX = r.x;
else if (contentX + width <= r.x + r.width)
contentX = r.x + r.width - width;
if (contentY >= r.y)
contentY = r.y;
else if (contentY + height <= r.y + r.height)
contentY = r.y + r.height - height;
}
Layout.alignment: Qt.AlignBottom
Layout.maximumHeight: Window.height / 4 Layout.maximumHeight: Window.height / 4
Layout.minimumHeight: Settings.fontSize Layout.minimumHeight: Settings.fontSize
implicitWidth: inputBar.width - 4 * (22 + 16) - 24 Layout.fillWidth: true
clip: true
boundsBehavior: Flickable.StopAtBounds
flickableDirection: Flickable.VerticalFlick
implicitWidth: textArea.width
implicitHeight: textArea.height
contentWidth: textArea.width
contentHeight: textArea.height
TextArea { TextArea {
id: messageInput id: textArea
property int completerTriggeredAt: -1 property int completerTriggeredAt: -1
function insertCompletion(completion) { function insertCompletion(completion) {
messageInput.remove(completerTriggeredAt, cursorPosition); textArea.remove(completerTriggeredAt, cursorPosition);
messageInput.insert(cursorPosition, completion); textArea.insert(cursorPosition, completion);
} }
function openCompleter(pos, type) { function openCompleter(pos, type) {
completerTriggeredAt = pos; completerTriggeredAt = pos;
popup.completerName = type; popup.completerName = type;
popup.open(); popup.open();
popup.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)); popup.completer.setSearchString(textArea.getText(completerTriggeredAt, cursorPosition));
}
function positionCursorAtEnd() {
cursorPosition = messageInput.length;
}
function positionCursorAtStart() {
cursorPosition = 0;
} }
text: "asfkajsdf"
selectByMouse: true selectByMouse: true
placeholderText: qsTr("Write a message...") placeholderText: qsTr("Write a message...")
placeholderTextColor: colors.buttonText //placeholderTextColor: colors.buttonText
// only set the anchors on Qt 5.12 or higher
// see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
Component.onCompleted: {
if (placeholderTextColor !== undefined)
placeholderTextColor = colors.buttonText;
}
color: colors.text color: colors.text
width: textInput.width width: textInput.width
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
padding: 8 padding: 0
focus: true focus: true
onTextChanged: { onTextChanged: {
if (TimelineManager.timeline) if (TimelineManager.timeline)
TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text); TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text);
forceActiveFocus();
} }
onCursorRectangleChanged: textInput.ensureVisible(cursorRectangle)
onCursorPositionChanged: { onCursorPositionChanged: {
if (!TimelineManager.timeline) if (!TimelineManager.timeline)
return ; return ;
@ -138,7 +152,7 @@ Rectangle {
popup.close(); popup.close();
} }
if (popup.opened) if (popup.opened)
popup.completer.setSearchString(messageInput.getText(completerTriggeredAt, cursorPosition)); popup.completer.setSearchString(textArea.getText(completerTriggeredAt, cursorPosition));
} }
onSelectionStartChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text) onSelectionStartChanged: TimelineManager.timeline.input.updateState(selectionStart, selectionEnd, cursorPosition, text)
@ -149,28 +163,17 @@ Rectangle {
if (event.matches(StandardKey.Paste)) { if (event.matches(StandardKey.Paste)) {
TimelineManager.timeline.input.paste(false); TimelineManager.timeline.input.paste(false);
event.accepted = true; event.accepted = true;
} else if (event.key == Qt.Key_Space) {
// close popup if user enters space after colon
if (cursorPosition == completerTriggeredAt + 1)
popup.close();
if (popup.opened && popup.count <= 0)
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(); textArea.clear();
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) { } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_P) {
messageInput.text = TimelineManager.timeline.input.previousText(); textArea.text = TimelineManager.timeline.input.previousText();
} else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_N) { } else if (event.modifiers == Qt.ControlModifier && event.key == Qt.Key_N) {
messageInput.text = TimelineManager.timeline.input.nextText(); textArea.text = TimelineManager.timeline.input.nextText();
} else if (event.key == Qt.Key_At) { } else if (event.key == Qt.Key_At) {
messageInput.openCompleter(cursorPosition, "user"); textArea.openCompleter(cursorPosition, "user");
popup.open(); popup.open();
} else if (event.key == Qt.Key_Colon) { } else if (event.key == Qt.Key_Colon) {
messageInput.openCompleter(cursorPosition, "emoji"); textArea.openCompleter(cursorPosition, "emoji");
popup.open();
} else if (event.key == Qt.Key_NumberSign) {
messageInput.openCompleter(cursorPosition, "roomAliases");
popup.open(); popup.open();
} else if (event.key == Qt.Key_Escape && popup.opened) { } else if (event.key == Qt.Key_Escape && popup.opened) {
completerTriggeredAt = -1; completerTriggeredAt = -1;
@ -183,12 +186,13 @@ Rectangle {
popup.completerName = ""; popup.completerName = "";
popup.close(); popup.close();
if (currentCompletion) { if (currentCompletion) {
messageInput.insertCompletion(currentCompletion); textArea.insertCompletion(currentCompletion);
event.accepted = true; event.accepted = true;
return ; return ;
} }
} }
TimelineManager.timeline.input.send(); TimelineManager.timeline.input.send();
textArea.clear();
event.accepted = true; event.accepted = true;
} else if (event.key == Qt.Key_Tab) { } else if (event.key == Qt.Key_Tab) {
event.accepted = true; event.accepted = true;
@ -197,22 +201,19 @@ Rectangle {
} else { } else {
var pos = cursorPosition - 1; var pos = cursorPosition - 1;
while (pos > -1) { while (pos > -1) {
var t = messageInput.getText(pos, pos + 1); var t = textArea.getText(pos, pos + 1);
console.log('"' + t + '"'); console.log('"' + t + '"');
if (t == '@') { if (t == '@' || t == ' ' || t == '\t') {
messageInput.openCompleter(pos, "user"); textArea.openCompleter(pos, "user");
return ;
} else if (t == ' ' || t == '\t') {
messageInput.openCompleter(pos + 1, "user");
return ; return ;
} else if (t == ':') { } else if (t == ':') {
messageInput.openCompleter(pos, "emoji"); textArea.openCompleter(pos, "emoji");
return ; return ;
} }
pos = pos - 1; pos = pos - 1;
} }
// At start of input // At start of input
messageInput.openCompleter(0, "user"); textArea.openCompleter(0, "user");
} }
} else if (event.key == Qt.Key_Up && popup.opened) { } else if (event.key == Qt.Key_Up && popup.opened) {
event.accepted = true; event.accepted = true;
@ -220,93 +221,38 @@ Rectangle {
} else if (event.key == Qt.Key_Down && popup.opened) { } else if (event.key == Qt.Key_Down && popup.opened) {
event.accepted = true; event.accepted = true;
popup.down(); popup.down();
} else if (event.key == Qt.Key_Up && event.modifiers == Qt.NoModifier) {
if (cursorPosition == 0) {
event.accepted = true;
var idx = TimelineManager.timeline.edit ? TimelineManager.timeline.idToIndex(TimelineManager.timeline.edit) + 1 : 0;
while (true) {
var id = TimelineManager.timeline.indexToId(idx);
if (!id || TimelineManager.timeline.getDump(id, "").isEditable) {
TimelineManager.timeline.edit = id;
cursorPosition = 0;
Qt.callLater(positionCursorAtEnd);
break;
}
idx++;
}
} else if (positionAt(0, cursorRectangle.y) === 0) {
event.accepted = true;
positionCursorAtStart();
}
} else if (event.key == Qt.Key_Down && event.modifiers == Qt.NoModifier) {
if (cursorPosition == messageInput.length && TimelineManager.timeline.edit) {
event.accepted = true;
var idx = TimelineManager.timeline.idToIndex(TimelineManager.timeline.edit) - 1;
while (true) {
var id = TimelineManager.timeline.indexToId(idx);
if (!id || TimelineManager.timeline.getDump(id, "").isEditable) {
TimelineManager.timeline.edit = id;
Qt.callLater(positionCursorAtStart);
break;
}
idx--;
}
} else if (positionAt(width, cursorRectangle.y + 2) === messageInput.length) {
event.accepted = true;
positionCursorAtEnd();
}
} }
} }
background: null background: null
Connections { Connections {
onActiveTimelineChanged: { onTimelineChanged: {
messageInput.clear(); textArea.clear();
messageInput.append(TimelineManager.timeline.input.text()); textArea.append(TimelineManager.timeline.input.text());
messageInput.completerTriggeredAt = -1; textArea.completerTriggeredAt = -1;
popup.completerName = ""; popup.completerName = "";
messageInput.forceActiveFocus();
} }
target: TimelineManager target: TimelineManager
} }
Connections { Connections {
onCompletionClicked: messageInput.insertCompletion(completion) onCompletionClicked: textArea.insertCompletion(completion)
target: popup target: popup
} }
Completer { Completer {
id: popup id: popup
x: messageInput.completerTriggeredAt >= 0 ? messageInput.positionToRectangle(messageInput.completerTriggeredAt).x : 0 x: textArea.completerTriggeredAt >= 0 ? textArea.positionToRectangle(textArea.completerTriggeredAt).x : 0
y: messageInput.completerTriggeredAt >= 0 ? messageInput.positionToRectangle(messageInput.completerTriggeredAt).y - height : 0 y: textArea.completerTriggeredAt >= 0 ? textArea.positionToRectangle(textArea.completerTriggeredAt).y - height : 0
} }
Connections { Connections {
ignoreUnknownSignals: true ignoreUnknownSignals: true
onInsertText: { onInsertText: textArea.insert(textArea.cursorPosition, text)
messageInput.remove(messageInput.selectionStart, messageInput.selectionEnd);
messageInput.insert(messageInput.cursorPosition, text);
}
onTextChanged: {
messageInput.text = newText;
messageInput.cursorPosition = newText.length;
}
target: TimelineManager.timeline ? TimelineManager.timeline.input : null target: TimelineManager.timeline ? TimelineManager.timeline.input : null
} }
Connections {
ignoreUnknownSignals: true
onReplyChanged: messageInput.forceActiveFocus()
onEditChanged: messageInput.forceActiveFocus()
target: TimelineManager.timeline
}
Connections {
target: TimelineManager
onFocusInput: messageInput.forceActiveFocus()
}
MouseArea { MouseArea {
// workaround for wrong cursor shape on some platforms // workaround for wrong cursor shape on some platforms
anchors.fill: parent anchors.fill: parent
@ -315,6 +261,14 @@ Rectangle {
onClicked: TimelineManager.timeline.input.paste(true) onClicked: TimelineManager.timeline.input.paste(true)
} }
NhekoDropArea {
anchors.fill: parent
roomid: TimelineManager.timeline ? TimelineManager.timeline.roomId() : ""
}
}
ScrollBar.vertical: ScrollBar {
} }
} }
@ -323,7 +277,6 @@ Rectangle {
id: emojiButton id: emojiButton
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8
hoverEnabled: true hoverEnabled: true
width: 22 width: 22
height: 22 height: 22
@ -331,14 +284,12 @@ Rectangle {
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Emoji") ToolTip.text: qsTr("Emoji")
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) { onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) {
messageInput.insert(messageInput.cursorPosition, emoji); textArea.insert(textArea.cursorPosition, emoji);
TimelineManager.focusMessageInput();
}) })
} }
ImageButton { ImageButton {
Layout.alignment: Qt.AlignRight | Qt.AlignBottom Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins: 8
hoverEnabled: true hoverEnabled: true
width: 22 width: 22
height: 22 height: 22
@ -348,16 +299,10 @@ Rectangle {
ToolTip.text: qsTr("Send") ToolTip.text: qsTr("Send")
onClicked: { onClicked: {
TimelineManager.timeline.input.send(); TimelineManager.timeline.input.send();
textArea.clear();
} }
} }
} }
Text {
anchors.centerIn: parent
visible: TimelineManager.timeline ? (!TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage)) : false
text: qsTr("You don't have permission to send messages in this room")
color: colors.text
}
} }

View File

@ -1,416 +1,192 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./delegates" import "./delegates"
import "./emoji"
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick 2.12 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import im.nheko 1.0 import im.nheko 1.0
ScrollView { ListView {
clip: false id: chat
palette: colors
padding: 8
ScrollBar.horizontal.visible: false
ListView { property int delegateMaxWidth: (Settings.timelineMaxWidth > 100 && (parent.width - Settings.timelineMaxWidth) > scrollbar.width * 2) ? Settings.timelineMaxWidth : (parent.width - scrollbar.width * 2 - 8)
id: chat
property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < parent.availableWidth) ? Settings.timelineMaxWidth : parent.availableWidth) - parent.padding * 2 Layout.fillWidth: true
Layout.fillHeight: true
cacheBuffer: 400
model: TimelineManager.timeline
boundsBehavior: Flickable.StopAtBounds
pixelAligned: true
spacing: 4
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: {
// Mark timeline as read
if (atYEnd)
model.currentIndex = 0;
model: TimelineManager.timeline }
boundsBehavior: Flickable.StopAtBounds
pixelAligned: true
spacing: 4
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: {
// Mark timeline as read
if (atYEnd)
model.currentIndex = 0;
ScrollHelper {
flickable: parent
anchors.fill: parent
enabled: !Settings.mobileMode
}
Shortcut {
sequence: StandardKey.MoveToPreviousPage
onActivated: {
chat.contentY = chat.contentY - chat.height / 2;
chat.returnToBounds();
} }
}
Rectangle { Shortcut {
//closePolicy: Popup.NoAutoClose sequence: StandardKey.MoveToNextPage
onActivated: {
chat.contentY = chat.contentY + chat.height / 2;
chat.returnToBounds();
}
}
id: messageActions Shortcut {
sequence: StandardKey.Cancel
onActivated: chat.model.reply = undefined
}
property Item attached: null Shortcut {
property alias model: row.model sequence: "Alt+Up"
// use comma to update on scroll onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
property var attachedPos: chat.contentY, attached ? chat.mapFromItem(attached, attached ? attached.width - width : 0, -height) : null }
readonly property int padding: 4
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || messageActionHover.hovered) Shortcut {
x: attached ? attachedPos.x : 0 sequence: "Alt+Down"
y: attached ? attachedPos.y : 0 onActivated: {
z: 10 var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
height: row.implicitHeight + padding * 2 chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : undefined;
width: row.implicitWidth + padding * 2 }
color: colors.window }
border.color: colors.buttonText
border.width: 1
radius: padding
HoverHandler { Component {
id: messageActionHover id: sectionHeader
Column {
topPadding: 4
bottomPadding: 4
spacing: 8
visible: modelData && (modelData.previousMessageUserId !== modelData.userId || modelData.previousMessageDay !== modelData.day)
width: parentWidth
height: ((modelData && modelData.previousMessageDay !== modelData.day) ? dateBubble.height + 8 + userName.height : userName.height) + 8
Label {
id: dateBubble
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
visible: modelData && modelData.previousMessageDay !== modelData.day
text: modelData ? chat.model.formatDateSeparator(modelData.timestamp) : ""
color: colors.text
height: Math.round(fontMetrics.height * 1.4)
width: contentWidth * 1.2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
background: Rectangle {
radius: parent.height / 2
color: colors.window
}
grabPermissions: PointerHandler.CanTakeOverFromAnything
} }
Row { Row {
id: row height: userName.height
property var model
anchors.centerIn: parent
spacing: messageActions.padding
ImageButton {
id: editButton
visible: !!row.model && row.model.isEditable
buttonTextColor: colors.buttonText
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/edit.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Edit")
onClicked: {
if (row.model.isEditable)
chat.model.editAction(row.model.id);
}
}
EmojiButton {
id: reactButton
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
width: 16
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("React")
emojiPicker: emojiPopup
event_id: row.model ? row.model.id : ""
}
ImageButton {
id: replyButton
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.TextMessage) : false
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/mail-reply.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Reply")
onClicked: chat.model.replyAction(row.model.id)
}
ImageButton {
id: optionsButton
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Options")
onClicked: messageContextMenu.show(row.model.id, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
}
}
}
ScrollHelper {
flickable: parent
anchors.fill: parent
enabled: !Settings.mobileMode
}
Shortcut {
sequence: StandardKey.MoveToPreviousPage
onActivated: {
chat.contentY = chat.contentY - chat.height / 2;
chat.returnToBounds();
}
}
Shortcut {
sequence: StandardKey.MoveToNextPage
onActivated: {
chat.contentY = chat.contentY + chat.height / 2;
chat.returnToBounds();
}
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: {
if (chat.model.reply)
chat.model.reply = undefined;
else
chat.model.edit = undefined;
}
}
Shortcut {
sequence: "Alt+Up"
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
}
Shortcut {
sequence: "Alt+Down"
onActivated: {
var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : null;
}
}
Shortcut {
sequence: "Alt+F"
onActivated: {
if (chat.model.reply) {
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
forwardMess.setMessageEventId(chat.model.reply);
forwardMess.open();
chat.model.reply = null;
}
}
}
Shortcut {
sequence: "Ctrl+E"
onActivated: {
chat.model.edit = chat.model.reply;
}
}
Connections {
target: TimelineManager
onFocusChanged: readTimer.running = TimelineManager.isWindowFocused
}
Timer {
id: readTimer
// force current read index to update
onTriggered: chat.model.setCurrentIndex(chat.model.currentIndex)
interval: 1000
}
Component {
id: sectionHeader
Column {
topPadding: 4
bottomPadding: 4
spacing: 8 spacing: 8
visible: modelData && (modelData.previousMessageUserId !== modelData.userId || modelData.previousMessageDay !== modelData.day)
width: parentWidth Avatar {
height: ((modelData && modelData.previousMessageDay !== modelData.day) ? dateBubble.height + 8 + userName.height : userName.height) + 8 width: avatarSize
height: avatarSize
url: modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : ""
displayName: modelData ? modelData.userName : ""
userid: modelData ? modelData.userId : ""
onClicked: chat.model.openUserProfile(modelData.userId)
}
Label { Label {
id: dateBubble id: userName
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined text: modelData ? TimelineManager.escapeEmoji(modelData.userName) : ""
visible: modelData && modelData.previousMessageDay !== modelData.day color: TimelineManager.userColor(modelData ? modelData.userId : "", colors.window)
text: modelData ? chat.model.formatDateSeparator(modelData.timestamp) : "" textFormat: Text.RichText
color: colors.text
height: Math.round(fontMetrics.height * 1.4)
width: contentWidth * 1.2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
background: Rectangle { MouseArea {
radius: parent.height / 2 anchors.fill: parent
color: colors.window Layout.alignment: Qt.AlignHCenter
}
}
Row {
height: userName.height
spacing: 8
Avatar {
id: messageUserAvatar
width: avatarSize
height: avatarSize
url: modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : ""
displayName: modelData ? modelData.userName : ""
userid: modelData ? modelData.userId : ""
onClicked: chat.model.openUserProfile(modelData.userId) onClicked: chat.model.openUserProfile(modelData.userId)
ToolTip.visible: avatarHover.hovered cursorShape: Qt.PointingHandCursor
ToolTip.text: userid propagateComposedEvents: true
HoverHandler {
id: avatarHover
}
}
Connections {
target: chat.model
onRoomAvatarUrlChanged: {
messageUserAvatar.url = modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : "";
}
onScrollToIndex: chat.positionViewAtIndex(index, ListView.Visible)
}
Label {
id: userName
text: modelData ? TimelineManager.escapeEmoji(modelData.userName) : ""
color: TimelineManager.userColor(modelData ? modelData.userId : "", colors.window)
textFormat: Text.RichText
ToolTip.visible: displayNameHover.hovered
ToolTip.text: modelData ? modelData.userId : ""
TapHandler {
onSingleTapped: chat.model.openUserProfile(modelData.userId)
}
CursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
HoverHandler {
id: displayNameHover
}
}
Label {
color: colors.buttonText
text: modelData ? TimelineManager.userStatus(modelData.userId) : ""
textFormat: Text.PlainText
elide: Text.ElideRight
width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - avatarSize
font.italic: true
} }
} }
} Label {
color: colors.buttonText
} text: modelData ? TimelineManager.userStatus(modelData.userId) : ""
textFormat: Text.PlainText
delegate: Item { elide: Text.ElideRight
id: wrapper width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - avatarSize
font.italic: true
property bool scrolledToThis: model.id === chat.model.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth
height: section ? section.height + timelinerow.height : timelinerow.height
Rectangle {
id: scrollHighlight
opacity: 0
visible: true
anchors.fill: timelinerow
color: colors.highlight
states: State {
name: "revealed"
when: wrapper.scrolledToThis
}
transitions: Transition {
from: ""
to: "revealed"
SequentialAnimation {
PropertyAnimation {
target: scrollHighlight
properties: "opacity"
easing.type: Easing.InOutQuad
from: 0
to: 1
duration: 500
}
PropertyAnimation {
target: scrollHighlight
properties: "opacity"
easing.type: Easing.InOutQuad
from: 1
to: 0
duration: 500
}
ScriptAction {
script: chat.model.eventShown()
}
}
} }
} }
Loader {
id: section
property var modelData: model
property int parentWidth: parent.width
active: model.previousMessageUserId !== undefined && model.previousMessageUserId !== model.userId || model.previousMessageDay !== model.day
//asynchronous: true
sourceComponent: sectionHeader
visible: status == Loader.Ready
}
TimelineRow {
id: timelinerow
property alias hovered: hoverHandler.hovered
y: section.visible && section.active ? section.y + section.height : 0
HoverHandler {
id: hoverHandler
enabled: !Settings.mobileMode
onHoveredChanged: {
if (hovered) {
if (!messageActionHover.hovered) {
messageActions.attached = timelinerow;
messageActions.model = model;
}
}
}
}
}
Connections {
target: chat
onMovementEnded: {
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
chat.model.currentIndex = index;
}
}
}
footer: BusyIndicator {
anchors.horizontalCenter: parent.horizontalCenter
running: chat.model && chat.model.paginationInProgress
height: 50
width: 50
z: 3
} }
} }
ScrollBar.vertical: ScrollBar {
id: scrollbar
}
delegate: Item {
id: wrapper
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth
height: section ? section.height + timelinerow.height : timelinerow.height
Loader {
id: section
property var modelData: model
property int parentWidth: parent.width
active: model.previousMessageUserId !== undefined && model.previousMessageUserId !== model.userId || model.previousMessageDay !== model.day
//asynchronous: true
sourceComponent: sectionHeader
visible: status == Loader.Ready
}
TimelineRow {
id: timelinerow
y: section.active && section.visible ? section.y + section.height : 0
}
Connections {
target: chat
onMovementEnded: {
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
chat.model.currentIndex = index;
}
}
}
footer: BusyIndicator {
anchors.horizontalCenter: parent.horizontalCenter
running: chat.model && chat.model.paginationInProgress
height: 50
width: 50
z: 3
}
} }

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2

View File

@ -1,130 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtGraphicalEffects 1.0
import QtQuick 2.12
import im.nheko 1.0
Item {
id: privacyScreen
property var timelineRoot
property int screenTimeout
Connections {
target: TimelineManager
onFocusChanged: {
if (TimelineManager.isWindowFocused) {
screenSaverTimer.stop();
screenSaver.state = "Invisible";
} else {
if (timelineRoot.visible)
screenSaverTimer.start();
}
}
}
Timer {
id: screenSaverTimer
interval: screenTimeout * 1000
running: true
onTriggered: {
screenSaver.state = "Visible";
}
}
Item {
id: screenSaver
state: "Invisible"
anchors.fill: parent
visible: false
states: [
State {
name: "Visible"
PropertyChanges {
target: screenSaver
visible: true
}
PropertyChanges {
target: screenSaver
opacity: 1
}
},
State {
name: "Invisible"
PropertyChanges {
target: screenSaver
opacity: 0
}
PropertyChanges {
target: screenSaver
visible: false
}
}
]
transitions: [
Transition {
from: "Visible"
to: "Invisible"
SequentialAnimation {
NumberAnimation {
target: screenSaver
property: "opacity"
duration: 250
easing.type: Easing.InQuad
}
NumberAnimation {
target: screenSaver
property: "visible"
duration: 0
}
}
},
Transition {
from: "Invisible"
to: "Visible"
SequentialAnimation {
NumberAnimation {
target: screenSaver
property: "visible"
duration: 0
}
NumberAnimation {
target: screenSaver
property: "opacity"
duration: 500
easing.type: Easing.InQuad
}
}
}
]
FastBlur {
id: blur
anchors.fill: parent
source: timelineRoot
radius: 50
}
}
}

View File

@ -1,91 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9
import QtQuick.Controls 2.3
import im.nheko 1.0
Popup {
id: quickSwitcher
property int textHeight: Math.round(Qt.application.font.pixelSize * 2.4)
property int textMargin: Math.round(textHeight / 8)
background: null
width: Math.round(parent.width / 2)
x: Math.round(parent.width / 2 - width / 2)
y: Math.round(parent.height / 4 - height / 2)
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
parent: Overlay.overlay
palette: colors
onOpened: {
completerPopup.open();
roomTextInput.forceActiveFocus();
}
onClosed: {
completerPopup.close();
}
MatrixTextField {
id: roomTextInput
anchors.fill: parent
font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
padding: textMargin
color: colors.text
onTextEdited: {
completerPopup.completer.searchString = text;
}
Keys.onPressed: {
if (event.key == Qt.Key_Up && completerPopup.opened) {
event.accepted = true;
completerPopup.up();
} else if (event.key == Qt.Key_Down && completerPopup.opened) {
event.accepted = true;
completerPopup.down();
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
completerPopup.finishCompletion();
event.accepted = true;
}
}
}
Completer {
id: completerPopup
x: roomTextInput.x
y: roomTextInput.y + quickSwitcher.textHeight + quickSwitcher.textMargin
visible: roomTextInput.length > 0
width: parent.width
completerName: "room"
bottomToTop: false
fullWidth: true
avatarHeight: quickSwitcher.textHeight
avatarWidth: quickSwitcher.textHeight
centerRowContent: false
rowMargin: Math.round(quickSwitcher.textMargin / 2)
rowSpacing: quickSwitcher.textMargin
closePolicy: Popup.NoAutoClose
}
Connections {
onCompletionSelected: {
TimelineManager.setHistoryView(id);
TimelineManager.highlightRoom(id);
quickSwitcher.close();
}
onCountChanged: {
if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count))
completerPopup.currentIndex = 0;
}
target: completerPopup
}
Overlay.modal: Rectangle {
color: "#aa1E1E1E"
}
}

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.6 import QtQuick 2.6
import QtQuick.Controls 2.2 import QtQuick.Controls 2.2
import im.nheko 1.0 import im.nheko 1.0

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./delegates/" import "./delegates/"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -14,16 +10,15 @@ Rectangle {
property var room: TimelineManager.timeline property var room: TimelineManager.timeline
Layout.fillWidth: true Layout.fillWidth: true
visible: room && (room.reply || room.edit) visible: room && room.reply
// Height of child, plus margins, plus border // Height of child, plus margins, plus border
implicitHeight: (room && room.reply ? replyPreview.height : closeEditButton.height) + 10 implicitHeight: replyPreview.height + 10
color: colors.window color: colors.window
z: 3 z: 3
Reply { Reply {
id: replyPreview id: replyPreview
visible: room && room.reply
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 2 * 22 + 3 * 16 anchors.leftMargin: 2 * 22 + 3 * 16
anchors.right: closeReplyButton.left anchors.right: closeReplyButton.left
@ -37,9 +32,8 @@ Rectangle {
ImageButton { ImageButton {
id: closeReplyButton id: closeReplyButton
visible: room && room.reply
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 16 anchors.rightMargin: 15
anchors.top: replyPreview.top anchors.top: replyPreview.top
hoverEnabled: true hoverEnabled: true
width: 16 width: 16
@ -50,17 +44,4 @@ Rectangle {
onClicked: room.reply = undefined onClicked: room.reply = undefined
} }
Button {
id: closeEditButton
visible: room && room.edit
anchors.left: parent.left
anchors.rightMargin: 16
anchors.topMargin: 10
anchors.top: parent.top
//height: 16
text: qsTr("Cancel edit")
onClicked: room.edit = undefined
}
} }

View File

@ -1,287 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import Qt.labs.platform 1.1 as Platform
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtQuick.Window 2.3
import im.nheko 1.0
ApplicationWindow {
id: roomSettingsDialog
property var roomSettings
x: MainWindow.x + (MainWindow.width / 2) - (width / 2)
y: MainWindow.y + (MainWindow.height / 2) - (height / 2)
minimumWidth: 420
minimumHeight: 650
palette: colors
color: colors.window
modality: Qt.WindowModal
flags: Qt.Dialog
title: qsTr("Room Settings")
Shortcut {
sequence: StandardKey.Cancel
onActivated: roomSettingsDialog.close()
}
ColumnLayout {
id: contentLayout1
anchors.fill: parent
anchors.margins: 10
spacing: 10
Avatar {
url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
displayName: roomSettings.roomName
height: 130
width: 130
Layout.alignment: Qt.AlignHCenter
onClicked: {
if (roomSettings.canChangeAvatar)
roomSettings.updateAvatar();
}
}
BusyIndicator {
Layout.alignment: Qt.AlignHCenter
running: roomSettings.isLoading
visible: roomSettings.isLoading
}
Text {
id: errorText
color: "red"
visible: opacity > 0
opacity: 0
Layout.alignment: Qt.AlignHCenter
}
SequentialAnimation {
id: hideErrorAnimation
running: false
PauseAnimation {
duration: 4000
}
NumberAnimation {
target: errorText
property: 'opacity'
to: 0
duration: 1000
}
}
Connections {
target: roomSettings
onDisplayError: {
errorText.text = errorMessage;
errorText.opacity = 1;
hideErrorAnimation.restart();
}
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
MatrixText {
text: roomSettings.roomName
font.pixelSize: 24
Layout.alignment: Qt.AlignHCenter
}
MatrixText {
text: qsTr("%1 member(s)").arg(roomSettings.memberCount)
Layout.alignment: Qt.AlignHCenter
}
}
ImageButton {
Layout.alignment: Qt.AlignHCenter
image: ":/icons/icons/ui/edit.png"
visible: roomSettings.canChangeNameAndTopic
onClicked: roomSettings.openEditModal()
}
ScrollView {
Layout.maximumHeight: 75
Layout.alignment: Qt.AlignHCenter
width: parent.width
TextArea {
text: TimelineManager.escapeEmoji(roomSettings.roomTopic)
wrapMode: TextEdit.WordWrap
textFormat: TextEdit.RichText
readOnly: true
background: null
selectByMouse: true
color: colors.text
horizontalAlignment: TextEdit.AlignHCenter
onLinkActivated: TimelineManager.openLink(link)
CursorShape {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
}
GridLayout {
columns: 2
rowSpacing: 10
MatrixText {
text: qsTr("SETTINGS")
font.bold: true
}
Item {
Layout.fillWidth: true
}
MatrixText {
text: qsTr("Notifications")
Layout.fillWidth: true
}
ComboBox {
model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")]
currentIndex: roomSettings.notifications
onActivated: {
roomSettings.changeNotifications(index);
}
Layout.fillWidth: true
}
MatrixText {
text: "Room access"
Layout.fillWidth: true
}
ComboBox {
enabled: roomSettings.canChangeJoinRules
model: [qsTr("Anyone and guests"), qsTr("Anyone"), qsTr("Invited users")]
currentIndex: roomSettings.accessJoinRules
onActivated: {
roomSettings.changeAccessRules(index);
}
Layout.fillWidth: true
}
MatrixText {
text: qsTr("Encryption")
}
ToggleButton {
id: encryptionToggle
checked: roomSettings.isEncryptionEnabled
onClicked: {
if (roomSettings.isEncryptionEnabled) {
checked = true;
return ;
}
confirmEncryptionDialog.open();
}
Layout.alignment: Qt.AlignRight
}
Platform.MessageDialog {
id: confirmEncryptionDialog
title: qsTr("End-to-End Encryption")
text: qsTr("Encryption is currently experimental and things might break unexpectedly. <br>
Please take note that it can't be disabled afterwards.")
modality: Qt.WindowModal
onAccepted: {
if (roomSettings.isEncryptionEnabled)
return ;
roomSettings.enableEncryption();
}
onRejected: {
encryptionToggle.checked = false;
}
buttons: Dialog.Ok | Dialog.Cancel
}
MatrixText {
visible: roomSettings.isEncryptionEnabled
text: qsTr("Respond to key requests")
}
ToggleButton {
visible: roomSettings.isEncryptionEnabled
ToolTip.text: qsTr("Whether or not the client should respond automatically with the session keys
upon request. Use with caution, this is a temporary measure to test the
E2E implementation until device verification is completed.")
checked: roomSettings.respondsToKeyRequests
onClicked: {
roomSettings.changeKeyRequestsPreference(checked);
}
Layout.alignment: Qt.AlignRight
}
Item {
// for adding extra space between sections
Layout.fillWidth: true
}
Item {
// for adding extra space between sections
Layout.fillWidth: true
}
MatrixText {
text: qsTr("INFO")
font.bold: true
}
Item {
Layout.fillWidth: true
}
MatrixText {
text: qsTr("Internal ID")
}
MatrixText {
text: roomSettings.roomId
font.pixelSize: 14
Layout.alignment: Qt.AlignRight
}
MatrixText {
text: qsTr("Room Version")
}
MatrixText {
text: roomSettings.roomVersion
font.pixelSize: 14
Layout.alignment: Qt.AlignRight
}
}
Button {
Layout.alignment: Qt.AlignRight
text: qsTr("OK")
onClicked: close()
}
}
}

View File

@ -1,9 +1,21 @@
// Copyright (C) 2016 Michael Bohlender, <michael.bohlender@kdemail.net> /*
// Copyright (C) 2017 Christian Mollekopf, <mollekopf@kolabsystems.com> * Copyright (C) 2016 Michael Bohlender, <michael.bohlender@kdemail.net>
// SPDX-FileCopyrightText: 2021 Nheko Contributors * Copyright (C) 2017 Christian Mollekopf, <mollekopf@kolabsystems.com>
// *
// SPDX-License-Identifier: GPL-3.0-or-later * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/* /*
* Shamelessly stolen from: * Shamelessly stolen from:
* https://cgit.kde.org/kube.git/tree/framework/qml/ScrollHelper.qml * https://cgit.kde.org/kube.git/tree/framework/qml/ScrollHelper.qml
@ -78,7 +90,7 @@ MouseArea {
// Show the scrollbars // Show the scrollbars
flickable.flick(0, 0); flickable.flick(0, 0);
flickable.contentY = newPos; flickable.contentY = newPos;
cancelFlickStateTimer.restart(); cancelFlickStateTimer.start();
} }
Timer { Timer {

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import im.nheko 1.0 import im.nheko 1.0

View File

@ -1,10 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./delegates" import "./delegates"
import "./emoji" import "./emoji"
import QtQuick 2.12 import QtQuick 2.6
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.2 import QtQuick.Window 2.2
@ -16,32 +12,32 @@ Item {
height: row.height height: row.height
Rectangle { Rectangle {
color: (Settings.messageHoverHighlight && hoverHandler.hovered) ? colors.alternateBase : "transparent" color: (Settings.messageHoverHighlight && hoverHandler.containsMouse) ? colors.alternateBase : "transparent"
anchors.fill: row anchors.fill: row
} }
HoverHandler { MouseArea {
id: hoverHandler id: hoverHandler
acceptedDevices: PointerDevice.GenericPointer anchors.fill: parent
} propagateComposedEvents: true
preventStealing: false
TapHandler { hoverEnabled: true
acceptedButtons: Qt.RightButton acceptedButtons: Qt.AllButtons
onSingleTapped: messageContextMenu.show(model.id, model.type, model.isSender, model.isEncrypted, model.isEditable, contentItem.child.hoveredLink, contentItem.child.copyText) onClicked: {
gesturePolicy: TapHandler.ReleaseWithinBounds if (mouse.button === Qt.RightButton)
} messageContextMenu.show(model.id, model.type, model.isEncrypted, row);
else
TapHandler { event.accepted = false;
onLongPressed: messageContextMenu.show(model.id, model.type, model.isSender, model.isEncrypted, model.isEditable, contentItem.child.hoveredLink, contentItem.child.copyText) }
onDoubleTapped: chat.model.reply = model.id onPressAndHold: {
gesturePolicy: TapHandler.ReleaseWithinBounds messageContextMenu.show(model.id, model.type, model.isEncrypted, row, mapToItem(timelineRoot, mouse.x, mouse.y));
}
} }
RowLayout { RowLayout {
id: row id: row
anchors.rightMargin: 1
anchors.leftMargin: avatarSize + 16 anchors.leftMargin: avatarSize + 16
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@ -50,8 +46,6 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
spacing: 4 spacing: 4
Layout.topMargin: 1
Layout.bottomMargin: 1
// fancy reply, if this is a reply // fancy reply, if this is a reply
Reply { Reply {
@ -86,41 +80,67 @@ Item {
EncryptionIndicator { EncryptionIndicator {
visible: model.isRoomEncrypted visible: model.isRoomEncrypted
encrypted: model.isEncrypted encrypted: model.isEncrypted
trust: model.trustlevel
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 width: 16
} }
Image { EmojiButton {
visible: model.isEdited || model.id == chat.model.edit id: reactButton
visible: Settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16
height: 16
width: 16 width: 16
sourceSize.width: 16 hoverEnabled: true
sourceSize.height: 16 ToolTip.visible: hovered
source: "image://colorimage/:/icons/icons/ui/edit.png?" + ((model.id == chat.model.edit) ? colors.highlight : colors.buttonText) ToolTip.text: qsTr("React")
ToolTip.visible: editHovered.hovered emojiPicker: emojiPopup
ToolTip.text: qsTr("Edited") event_id: model.id
}
HoverHandler { ImageButton {
id: editHovered id: replyButton
}
visible: Settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/mail-reply.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Reply")
onClicked: chat.model.replyAction(model.id)
}
ImageButton {
id: optionsButton
visible: Settings.buttonsInTimeline
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Options")
onClicked: messageContextMenu.show(model.id, model.type, model.isEncrypted, optionsButton)
} }
Label { Label {
Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.alignment: Qt.AlignRight | Qt.AlignTop
text: model.timestamp.toLocaleTimeString(Locale.ShortFormat) text: model.timestamp.toLocaleTimeString("HH:mm")
width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth) width: Math.max(implicitWidth, text.length * fontMetrics.maximumCharacterWidth)
color: inactiveColors.text color: inactiveColors.text
ToolTip.visible: ma.hovered ToolTip.visible: ma.containsMouse
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate) ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
HoverHandler { MouseArea {
id: ma id: ma
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
} }
} }

View File

@ -1,12 +1,7 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./delegates" import "./delegates"
import "./device-verification" import "./device-verification"
import "./emoji" import "./emoji"
import "./voip" import "./voip"
import Qt.labs.platform 1.1 as Platform
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -21,7 +16,7 @@ Page {
property var colors: currentActivePalette property var colors: currentActivePalette
property var systemInactive property var systemInactive
property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
readonly property int avatarSize: 40 property int avatarSize: 40
property real highlightHue: colors.highlight.hslHue property real highlightHue: colors.highlight.hslHue
property real highlightSat: colors.highlight.hslSaturation property real highlightSat: colors.highlight.hslSaturation
property real highlightLight: colors.highlight.hslLightness property real highlightLight: colors.highlight.hslLightness
@ -35,8 +30,18 @@ Page {
EmojiPicker { EmojiPicker {
id: emojiPopup id: emojiPopup
width: 7 * 52 + 20
height: 6 * 52
colors: palette colors: palette
model: TimelineManager.completerFor("allemoji", "")
model: EmojiProxyModel {
category: EmojiCategory.People
sourceModel: EmojiModel {
}
}
} }
Component { Component {
@ -47,14 +52,6 @@ Page {
} }
Component {
id: roomSettingsComponent
RoomSettings {
}
}
Component { Component {
id: mobileCallInviteDialog id: mobileCallInviteDialog
@ -63,158 +60,71 @@ Page {
} }
Component { Menu {
id: quickSwitcherComponent
QuickSwitcher {
}
}
Component {
id: forwardCompleterComponent
ForwardCompleter {
}
}
Shortcut {
sequence: "Ctrl+K"
onActivated: {
var quickSwitch = quickSwitcherComponent.createObject(timelineRoot);
TimelineManager.focusTimeline();
quickSwitch.open();
}
}
Platform.Menu {
id: messageContextMenu id: messageContextMenu
property string eventId property string eventId
property string link
property string text
property int eventType property int eventType
property bool isEncrypted property bool isEncrypted
property bool isEditable
property bool isSender
function show(eventId_, eventType_, isSender_, isEncrypted_, isEditable_, link_, text_, showAt_) { function show(eventId_, eventType_, isEncrypted_, showAt_, position) {
eventId = eventId_; eventId = eventId_;
eventType = eventType_; eventType = eventType_;
isEncrypted = isEncrypted_; isEncrypted = isEncrypted_;
isEditable = isEditable_; if (position)
isSender = isSender_; popup(position, showAt_);
if (text_)
text = text_;
else else
text = ""; popup(showAt_);
if (link_)
link = link_;
else
link = "";
if (showAt_)
open(showAt_);
else
open();
} }
Platform.MenuItem { modal: true
visible: messageContextMenu.text
enabled: visible
text: qsTr("&Copy")
onTriggered: Clipboard.text = messageContextMenu.text
}
Platform.MenuItem { MenuItem {
visible: messageContextMenu.link text: qsTr("React")
enabled: visible onClicked: emojiPopup.show(messageContextMenu.parent, function(emoji) {
text: qsTr("Copy &link location")
onTriggered: Clipboard.text = messageContextMenu.link
}
Platform.MenuItem {
id: reactionOption
visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.Reaction) : false
text: qsTr("Re&act")
onTriggered: emojiPopup.show(null, function(emoji) {
TimelineManager.queueReactionMessage(messageContextMenu.eventId, emoji); TimelineManager.queueReactionMessage(messageContextMenu.eventId, emoji);
}) })
} }
Platform.MenuItem { MenuItem {
visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false text: qsTr("Reply")
text: qsTr("Repl&y") onClicked: TimelineManager.timeline.replyAction(messageContextMenu.eventId)
onTriggered: TimelineManager.timeline.replyAction(messageContextMenu.eventId)
} }
Platform.MenuItem { MenuItem {
visible: messageContextMenu.isEditable && (TimelineManager.timeline ? TimelineManager.timeline.permissions.canSend(MtxEvent.TextMessage) : false) text: qsTr("Read receipts")
enabled: visible
text: qsTr("&Edit")
onTriggered: TimelineManager.timeline.editAction(messageContextMenu.eventId)
}
Platform.MenuItem {
text: qsTr("Read receip&ts")
onTriggered: TimelineManager.timeline.readReceiptsAction(messageContextMenu.eventId) onTriggered: TimelineManager.timeline.readReceiptsAction(messageContextMenu.eventId)
} }
Platform.MenuItem { MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker || messageContextMenu.eventType == MtxEvent.TextMessage || messageContextMenu.eventType == MtxEvent.LocationMessage || messageContextMenu.eventType == MtxEvent.EmoteMessage || messageContextMenu.eventType == MtxEvent.NoticeMessage text: qsTr("Mark as read")
text: qsTr("&Forward")
onTriggered: {
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
forwardMess.setMessageEventId(messageContextMenu.eventId);
forwardMess.open();
}
} }
Platform.MenuItem { MenuItem {
text: qsTr("&Mark as read")
}
Platform.MenuItem {
text: qsTr("View raw message") text: qsTr("View raw message")
onTriggered: TimelineManager.timeline.viewRawMessage(messageContextMenu.eventId) onTriggered: TimelineManager.timeline.viewRawMessage(messageContextMenu.eventId)
} }
Platform.MenuItem { MenuItem {
// TODO(Nico): Fix this still being iterated over, when using keyboard to select options // TODO(Nico): Fix this still being iterated over, when using keyboard to select options
visible: messageContextMenu.isEncrypted visible: messageContextMenu.isEncrypted
enabled: visible height: visible ? implicitHeight : 0
text: qsTr("View decrypted raw message") text: qsTr("View decrypted raw message")
onTriggered: TimelineManager.timeline.viewDecryptedRawMessage(messageContextMenu.eventId) onTriggered: TimelineManager.timeline.viewDecryptedRawMessage(messageContextMenu.eventId)
} }
Platform.MenuItem { MenuItem {
visible: (TimelineManager.timeline ? TimelineManager.timeline.permissions.canRedact() : false) || messageContextMenu.isSender text: qsTr("Remove message")
text: qsTr("Remo&ve message")
onTriggered: TimelineManager.timeline.redactEvent(messageContextMenu.eventId) onTriggered: TimelineManager.timeline.redactEvent(messageContextMenu.eventId)
} }
Platform.MenuItem { MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
enabled: visible height: visible ? implicitHeight : 0
text: qsTr("&Save as") text: qsTr("Save as")
onTriggered: TimelineManager.timeline.saveMedia(messageContextMenu.eventId) onTriggered: TimelineManager.timeline.saveMedia(messageContextMenu.eventId)
} }
Platform.MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
enabled: visible
text: qsTr("&Open in external program")
onTriggered: TimelineManager.timeline.openMedia(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.eventId
enabled: visible
text: qsTr("Copy link to eve&nt")
onTriggered: TimelineManager.timeline.copyLinkToEvent(messageContextMenu.eventId)
}
} }
Rectangle { Rectangle {
@ -237,6 +147,10 @@ Page {
}); });
dialog.show(); dialog.show();
} }
}
Connections {
target: TimelineManager.timeline
onOpenProfile: { onOpenProfile: {
var userProfile = userProfileComponent.createObject(timelineRoot, { var userProfile = userProfileComponent.createObject(timelineRoot, {
"profile": profile "profile": profile
@ -245,16 +159,6 @@ Page {
} }
} }
Connections {
target: TimelineManager.timeline
onOpenRoomSettingsDialog: {
var roomSettings = roomSettingsComponent.createObject(timelineRoot, {
"roomSettings": settings
});
roomSettings.show();
}
}
Connections { Connections {
target: CallManager target: CallManager
onNewInviteState: { onNewInviteState: {
@ -283,8 +187,6 @@ Page {
} }
ColumnLayout { ColumnLayout {
id: timelineLayout
visible: TimelineManager.timeline != null visible: TimelineManager.timeline != null
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
@ -329,7 +231,7 @@ Page {
} }
Loader { Loader {
source: CallManager.isOnCall && CallManager.callType != CallType.VOICE ? "voip/VideoCall.qml" : "" source: CallManager.isOnCall && CallManager.isVideo ? "voip/VideoCall.qml" : ""
onLoaded: TimelineManager.setVideoCallItem() onLoaded: TimelineManager.setVideoCallItem()
} }
@ -369,18 +271,6 @@ Page {
} }
NhekoDropArea {
anchors.fill: parent
roomid: TimelineManager.timeline ? TimelineManager.timeline.roomId() : ""
}
}
PrivacyScreen {
anchors.fill: parent
visible: Settings.privacyScreen
screenTimeout: Settings.privacyScreenTimeout
timelineRoot: timelineLayout
} }
systemInactive: SystemPalette { systemInactive: SystemPalette {

View File

@ -1,44 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.5
import QtQuick 2.12
import QtQuick.Controls 2.12
import im.nheko 1.0
Switch {
id: toggleButton
implicitWidth: indicatorItem.width
indicator: Item {
id: indicatorItem
implicitWidth: 48
implicitHeight: 24
y: parent.height / 2 - height / 2
Rectangle {
height: 3 * parent.height / 4
radius: height / 2
width: parent.width - height
x: radius
y: parent.height / 2 - height / 2
color: toggleButton.checked ? "skyblue" : "grey"
border.color: "#cccccc"
}
Rectangle {
x: toggleButton.checked ? parent.width - width : 0
y: parent.height / 2 - height / 2
width: parent.height
height: width
radius: width / 2
color: toggleButton.down ? "whitesmoke" : "whitesmoke"
border.color: "#ebebeb"
}
}
}

View File

@ -1,10 +1,5 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors import QtQuick 2.9
// import QtQuick.Controls 2.3
// SPDX-License-Identifier: GPL-3.0-or-later
import Qt.labs.platform 1.1 as Platform
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
@ -18,12 +13,9 @@ Rectangle {
z: 3 z: 3
color: colors.window color: colors.window
TapHandler { MouseArea {
onSingleTapped: { anchors.fill: parent
TimelineManager.timeline.openRoomSettings(); onClicked: TimelineManager.openRoomSettings()
eventPoint.accepted = true;
}
gesturePolicy: TapHandler.ReleaseWithinBounds
} }
GridLayout { GridLayout {
@ -61,7 +53,7 @@ Rectangle {
height: avatarSize height: avatarSize
url: room ? room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : "" url: room ? room.roomAvatarUrl.replace("mxc://", "image://MxcImage/") : ""
displayName: room ? room.roomName : qsTr("No room selected") displayName: room ? room.roomName : qsTr("No room selected")
onClicked: TimelineManager.timeline.openRoomSettings() onClicked: TimelineManager.openRoomSettings()
} }
Label { Label {
@ -73,7 +65,12 @@ Rectangle {
text: room ? room.roomName : qsTr("No room selected") text: room ? room.roomName : qsTr("No room selected")
maximumLineCount: 1 maximumLineCount: 1
elide: Text.ElideRight elide: Text.ElideRight
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
onClicked: TimelineManager.openRoomSettings()
}
} }
MatrixText { MatrixText {
@ -95,30 +92,29 @@ Rectangle {
image: ":/icons/icons/ui/vertical-ellipsis.png" image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Room options") ToolTip.text: qsTr("Room options")
onClicked: roomOptionsMenu.open(roomOptionsButton) onClicked: roomOptionsMenu.popup(roomOptionsButton)
Platform.Menu { Menu {
id: roomOptionsMenu id: roomOptionsMenu
Platform.MenuItem { MenuItem {
visible: TimelineManager.timeline ? TimelineManager.timeline.permissions.canInvite() : false
text: qsTr("Invite users") text: qsTr("Invite users")
onTriggered: TimelineManager.openInviteUsersDialog() onTriggered: TimelineManager.openInviteUsersDialog()
} }
Platform.MenuItem { MenuItem {
text: qsTr("Members") text: qsTr("Members")
onTriggered: TimelineManager.openMemberListDialog() onTriggered: TimelineManager.openMemberListDialog()
} }
Platform.MenuItem { MenuItem {
text: qsTr("Leave room") text: qsTr("Leave room")
onTriggered: TimelineManager.openLeaveRoomDialog() onTriggered: TimelineManager.openLeaveRoomDialog()
} }
Platform.MenuItem { MenuItem {
text: qsTr("Settings") text: qsTr("Settings")
onTriggered: TimelineManager.timeline.openRoomSettings() onTriggered: TimelineManager.openRoomSettings()
} }
} }

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "./device-verification" import "./device-verification"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -14,21 +10,11 @@ ApplicationWindow {
property var profile property var profile
x: MainWindow.x + (MainWindow.width / 2) - (width / 2)
y: MainWindow.y + (MainWindow.height / 2) - (height / 2)
height: 650 height: 650
width: 420 width: 420
minimumHeight: 420 minimumHeight: 420
palette: colors palette: colors
color: colors.window color: colors.window
title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
modality: Qt.WindowModal
flags: Qt.Dialog
Shortcut {
sequence: StandardKey.Cancel
onActivated: userProfileDialog.close()
}
ColumnLayout { ColumnLayout {
id: contentL id: contentL
@ -44,86 +30,16 @@ ApplicationWindow {
displayName: profile.displayName displayName: profile.displayName
userid: profile.userid userid: profile.userid
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
onClicked: profile.isSelf ? profile.changeAvatar() : TimelineManager.openImageOverlay(profile.avatarUrl, "") onClicked: TimelineManager.openImageOverlay(TimelineManager.timeline.avatarUrl(userid), TimelineManager.timeline.data.id)
} }
BusyIndicator { Label {
Layout.alignment: Qt.AlignHCenter
running: profile.isLoading
visible: profile.isLoading
}
Text {
id: errorText
color: "red"
visible: opacity > 0
opacity: 0
Layout.alignment: Qt.AlignHCenter
}
SequentialAnimation {
id: hideErrorAnimation
running: false
PauseAnimation {
duration: 4000
}
NumberAnimation {
target: errorText
property: 'opacity'
to: 0
duration: 1000
}
}
Connections {
target: profile
onDisplayError: {
errorText.text = errorMessage;
errorText.opacity = 1;
hideErrorAnimation.restart();
}
}
TextInput {
id: displayUsername
property bool isUsernameEditingAllowed
readOnly: !isUsernameEditingAllowed
text: profile.displayName text: profile.displayName
fontSizeMode: Text.HorizontalFit
font.pixelSize: 20 font.pixelSize: 20
color: TimelineManager.userColor(profile.userid, colors.window) color: TimelineManager.userColor(profile.userid, colors.window)
font.bold: true font.bold: true
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
selectByMouse: true
onAccepted: {
profile.changeUsername(displayUsername.text);
displayUsername.isUsernameEditingAllowed = false;
}
ImageButton {
visible: profile.isSelf
anchors.leftMargin: 5
anchors.left: displayUsername.right
anchors.verticalCenter: displayUsername.verticalCenter
image: displayUsername.isUsernameEditingAllowed ? ":/icons/icons/ui/checkmark.png" : ":/icons/icons/ui/edit.png"
onClicked: {
if (displayUsername.isUsernameEditingAllowed) {
profile.changeUsername(displayUsername.text);
displayUsername.isUsernameEditingAllowed = false;
} else {
displayUsername.isUsernameEditingAllowed = true;
displayUsername.focus = true;
displayUsername.selectAll();
}
}
}
} }
MatrixText { MatrixText {
@ -137,21 +53,32 @@ ApplicationWindow {
text: qsTr("Verify") text: qsTr("Verify")
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
enabled: profile.userVerified != Crypto.Verified enabled: !profile.isUserVerified
visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled visible: !profile.isUserVerified && !profile.isSelf && profile.userVerificationEnabled
onClicked: profile.verify() onClicked: profile.verify()
} }
Image { Image {
Layout.preferredHeight: 16 Layout.preferredHeight: 16
Layout.preferredWidth: 16 Layout.preferredWidth: 16
source: "image://colorimage/:/icons/icons/ui/lock.png?" + ((profile.userVerified == Crypto.Verified) ? "green" : colors.buttonText) source: "image://colorimage/:/icons/icons/ui/lock.png?green"
visible: profile.userVerified != Crypto.Unverified visible: profile.isUserVerified
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
} }
RowLayout { RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 8
ImageButton {
image: ":/icons/icons/ui/do-not-disturb-rounded-sign.png"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Ban the user")
onClicked: profile.banUser()
}
// ImageButton{ // ImageButton{
// image:":/icons/icons/ui/volume-off-indicator.png" // image:":/icons/icons/ui/volume-off-indicator.png"
// Layout.margins: { // Layout.margins: {
// left: 5 // left: 5
@ -163,10 +90,6 @@ ApplicationWindow {
// profile.ignoreUser() // profile.ignoreUser()
// } // }
// } // }
Layout.alignment: Qt.AlignHCenter
spacing: 8
ImageButton { ImageButton {
image: ":/icons/icons/ui/black-bubble-speech.png" image: ":/icons/icons/ui/black-bubble-speech.png"
hoverEnabled: true hoverEnabled: true
@ -181,16 +104,6 @@ ApplicationWindow {
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Kick the user") ToolTip.text: qsTr("Kick the user")
onClicked: profile.kickUser() onClicked: profile.kickUser()
visible: profile.room ? profile.room.permissions.canKick() : false
}
ImageButton {
image: ":/icons/icons/ui/do-not-disturb-rounded-sign.png"
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr("Ban the user")
onClicked: profile.banUser()
visible: profile.room ? profile.room.permissions.canBan() : false
} }
} }
@ -242,7 +155,7 @@ ApplicationWindow {
id: verifyButton id: verifyButton
visible: (!profile.userVerificationEnabled && !profile.isSelf) || (profile.isSelf && (model.verificationStatus != VerificationStatus.VERIFIED || !profile.userVerificationEnabled)) visible: (!profile.userVerificationEnabled && !profile.isSelf) || (profile.isSelf && (model.verificationStatus != VerificationStatus.VERIFIED || !profile.userVerificationEnabled))
text: (model.verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify") text: (model.verificationStatus != VerificationStatus.VERIFIED) ? "Verify" : "Unverify"
onClicked: { onClicked: {
if (model.verificationStatus == VerificationStatus.VERIFIED) if (model.verificationStatus == VerificationStatus.VERIFIED)
profile.unverify(model.deviceId); profile.unverify(model.deviceId);

View File

@ -1,8 +1,4 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors import QtQuick 2.6
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
@ -33,13 +29,9 @@ Item {
fillMode: Image.Pad fillMode: Image.Pad
} }
TapHandler { MouseArea {
onSingleTapped: TimelineManager.timeline.saveMedia(model.data.id)
gesturePolicy: TapHandler.ReleaseWithinBounds
}
CursorShape {
anchors.fill: parent anchors.fill: parent
onClicked: TimelineManager.timeline.saveMedia(model.data.id)
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }

View File

@ -1,14 +1,10 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors import QtQuick 2.6
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width) property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? parent.width : model.data.width)
property double tempHeight: tempWidth * model.data.proportionalHeight property double tempHeight: tempWidth * model.data.proportionalHeight
property double divisor: model.isReply ? 5 : 3 property double divisor: model.isReply ? 4 : 2
property bool tooHigh: tempHeight > timelineRoot.height / divisor property bool tooHigh: tempHeight > timelineRoot.height / divisor
height: Math.round(tooHigh ? timelineRoot.height / divisor : tempHeight) height: Math.round(tooHigh ? timelineRoot.height / divisor : tempHeight)
@ -36,24 +32,20 @@ Item {
smooth: true smooth: true
mipmap: true mipmap: true
TapHandler { MouseArea {
enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready
onSingleTapped: {
TimelineManager.openImageOverlay(model.data.url, model.data.id);
eventPoint.accepted = true;
}
gesturePolicy: TapHandler.ReleaseWithinBounds
}
HoverHandler {
id: mouseArea id: mouseArea
enabled: model.data.type == MtxEvent.ImageMessage && img.status == Image.Ready
hoverEnabled: true
anchors.fill: parent
onClicked: TimelineManager.openImageOverlay(model.data.url, model.data.id)
} }
Item { Item {
id: overlay id: overlay
anchors.fill: parent anchors.fill: parent
visible: mouseArea.hovered visible: mouseArea.containsMouse
Rectangle { Rectangle {
id: container id: container

View File

@ -1,14 +1,9 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.6 import QtQuick 2.6
import im.nheko 1.0 import im.nheko 1.0
Item { Item {
property alias modelData: model.data property alias modelData: model.data
property alias isReply: model.isReply property alias isReply: model.isReply
property alias child: chooser.child
property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width property real implicitWidth: (chooser.child && chooser.child.implicitWidth) ? chooser.child.implicitWidth : width
height: chooser.childrenRect.height height: chooser.childrenRect.height

View File

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
TextMessage { TextMessage {
font.italic: true font.italic: true
color: colors.buttonText color: colors.buttonText
height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined
clip: isReply
} }

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import ".." import ".."
MatrixText { MatrixText {

View File

@ -1,10 +1,5 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import QtMultimedia 5.6 import QtMultimedia 5.6
import QtQuick 2.12 import QtQuick 2.6
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import im.nheko 1.0 import im.nheko 1.0
@ -108,40 +103,20 @@ Rectangle {
width: parent.width width: parent.width
spacing: 15 spacing: 15
ImageButton { Rectangle {
id: button id: button
Layout.alignment: Qt.AlignVCenter color: colors.window
//color: colors.window radius: 22
//radius: 22 height: 44
height: 32 width: 44
width: 32
z: 3
image: ":/icons/icons/ui/arrow-pointing-down.png"
onClicked: {
switch (button.state) {
case "":
TimelineManager.timeline.cacheMedia(model.data.id);
break;
case "stopped":
media.play();
console.log("play");
button.state = "playing";
break;
case "playing":
media.pause();
console.log("pause");
button.state = "stopped";
break;
}
}
states: [ states: [
State { State {
name: "stopped" name: "stopped"
PropertyChanges { PropertyChanges {
target: button target: img
image: ":/icons/icons/ui/play-sign.png" source: "image://colorimage/:/icons/icons/ui/play-sign.png?" + colors.text
} }
}, },
@ -149,15 +124,41 @@ Rectangle {
name: "playing" name: "playing"
PropertyChanges { PropertyChanges {
target: button target: img
image: ":/icons/icons/ui/pause-symbol.png" source: "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + colors.text
} }
} }
] ]
CursorShape { Image {
id: img
anchors.centerIn: parent
z: 3
source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + colors.text
fillMode: Image.Pad
}
MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: {
switch (button.state) {
case "":
TimelineManager.timeline.cacheMedia(model.data.id);
break;
case "stopped":
media.play();
console.log("play");
button.state = "playing";
break;
case "playing":
media.pause();
console.log("pause");
button.state = "stopped";
break;
}
}
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
@ -177,7 +178,7 @@ Rectangle {
target: TimelineManager.timeline target: TimelineManager.timeline
onMediaCached: { onMediaCached: {
if (mxcUrl == model.data.url) { if (mxcUrl == model.data.url) {
media.source = cacheUrl; media.source = "file://" + cacheUrl;
button.state = "stopped"; button.state = "stopped";
console.log("media loaded: " + mxcUrl + " at " + cacheUrl); console.log("media loaded: " + mxcUrl + " at " + cacheUrl);
} }

View File

@ -1,8 +1,4 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors import QtQuick 2.6
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
import QtQuick.Window 2.2 import QtQuick.Window 2.2
@ -17,13 +13,10 @@ Item {
width: parent.width width: parent.width
height: replyContainer.height height: replyContainer.height
TapHandler { MouseArea {
onSingleTapped: chat.model.showEvent(modelData.id)
gesturePolicy: TapHandler.ReleaseWithinBounds
}
CursorShape {
anchors.fill: parent anchors.fill: parent
preventStealing: false
onClicked: chat.positionViewAtIndex(chat.model.idToIndex(modelData.id), ListView.Contain)
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
} }
@ -50,9 +43,10 @@ Item {
color: replyComponent.userColor color: replyComponent.userColor
textFormat: Text.RichText textFormat: Text.RichText
TapHandler { MouseArea {
onSingleTapped: chat.model.openUserProfile(reply.modelData.userId) anchors.fill: parent
gesturePolicy: TapHandler.ReleaseWithinBounds onClicked: chat.model.openUserProfile(reply.modelData.userId)
cursorShape: Qt.PointingHandCursor
} }
} }

View File

@ -1,18 +1,12 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import ".." import ".."
import im.nheko 1.0 import im.nheko 1.0
MatrixText { MatrixText {
property string formatted: model.data.formattedBody property string formatted: model.data.formattedBody
property string copyText: selectedText ? getText(selectionStart, selectionEnd) : model.data.body
text: "<style type=\"text/css\">a { color:" + colors.link + ";}\ncode { background-color: " + colors.alternateBase + ";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap; background-color: " + colors.alternateBase + "'>") text: "<span style='white-space: pre-wrap'><style type=\"text/css\">a { color:" + colors.link + ";}\ncode { background-color: " + colors.alternateBase + ";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap; background-color: " + colors.alternateBase + "'>") + "</span>"
width: parent ? parent.width : undefined width: parent ? parent.width : undefined
height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined height: isReply ? Math.round(Math.min(timelineRoot.height / 8, implicitHeight)) : undefined
clip: isReply clip: isReply
selectByMouse: !Settings.mobileMode && !isReply
font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize font.pointSize: (Settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? Settings.fontSize * 3 : Settings.fontSize
} }

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.10 import QtQuick 2.10
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Window 2.10 import QtQuick.Window 2.10
@ -15,12 +11,9 @@ ApplicationWindow {
onClosing: TimelineManager.removeVerificationFlow(flow) onClosing: TimelineManager.removeVerificationFlow(flow)
title: stack.currentItem.title title: stack.currentItem.title
flags: Qt.Dialog flags: Qt.Dialog
modality: Qt.WindowModal
palette: colors palette: colors
height: stack.implicitHeight height: stack.implicitHeight
width: stack.implicitWidth width: stack.implicitWidth
x: MainWindow.x + (MainWindow.width / 2) - (width / 2)
y: MainWindow.y + (MainWindow.height / 2) - (height / 2)
StackView { StackView {
id: stack id: stack

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.10 import QtQuick.Layouts 1.10

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtQuick 2.10 import QtQuick 2.10
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
@ -18,6 +14,5 @@ ImageButton {
image: ":/icons/icons/ui/smile.png" image: ":/icons/icons/ui/smile.png"
onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, function(emoji) { onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, function(emoji) {
TimelineManager.queueReactionMessage(event_id, emoji); TimelineManager.queueReactionMessage(event_id, emoji);
TimelineManager.focusMessageInput();
}) })
} }

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick 2.9 import QtQuick 2.9
@ -10,7 +6,7 @@ import QtQuick.Layouts 1.3
import im.nheko 1.0 import im.nheko 1.0
import im.nheko.EmojiModel 1.0 import im.nheko.EmojiModel 1.0
Menu { Popup {
id: emojiPopup id: emojiPopup
property var callback property var callback
@ -24,8 +20,13 @@ Menu {
function show(showAt, callback) { function show(showAt, callback) {
console.debug("Showing emojiPicker"); console.debug("Showing emojiPicker");
if (showAt) {
parent = showAt;
x = Math.round((showAt.width - width) / 2);
y = showAt.height;
}
emojiPopup.callback = callback; emojiPopup.callback = callback;
popup(showAt ? showAt : null); open();
} }
margins: 0 margins: 0
@ -35,74 +36,24 @@ Menu {
modal: true modal: true
focus: true focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
height: columnView.implicitHeight + 4
width: columnView.implicitWidth
ColumnLayout { ColumnLayout {
id: columnView id: columnView
anchors.fill: parent
spacing: 0 spacing: 0
anchors.leftMargin: 3 Layout.bottomMargin: 0
anchors.rightMargin: 3 Layout.leftMargin: 3
anchors.bottom: parent.bottom Layout.rightMargin: 3
anchors.left: parent.left Layout.topMargin: 2
anchors.right: parent.right
anchors.topMargin: 2
// Search field
TextField {
id: emojiSearch
//width: gridView.width - 6
Layout.topMargin: 3
Layout.preferredWidth: 7 * 52 + 20 - 6
placeholderText: qsTr("Search")
selectByMouse: true
rightPadding: clearSearch.width
onTextChanged: searchTimer.restart()
onVisibleChanged: {
if (visible)
forceActiveFocus();
}
Timer {
id: searchTimer
interval: 350 // tweak as needed?
onTriggered: {
emojiPopup.model.searchString = emojiSearch.text;
emojiPopup.model.category = Emoji.Category.Search;
}
}
ToolButton {
id: clearSearch
visible: emojiSearch.text !== ''
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? colors.highlight : colors.buttonText)
focusPolicy: Qt.NoFocus
onClicked: emojiSearch.clear()
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
}
// clear the default hover effects.
background: Item {
}
}
}
// emoji grid // emoji grid
GridView { GridView {
id: gridView id: gridView
Layout.preferredHeight: cellHeight * 5 Layout.preferredHeight: emojiPopup.height
Layout.preferredWidth: 7 * 52 + 20 Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 4 Layout.leftMargin: 4
cellWidth: 52 cellWidth: 52
cellHeight: 52 cellHeight: 52
@ -152,6 +103,54 @@ Menu {
} }
// Search field
header: TextField {
id: emojiSearch
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: emojiScroll.width + 4
placeholderText: qsTr("Search")
selectByMouse: true
rightPadding: clearSearch.width
onTextChanged: searchTimer.restart()
onVisibleChanged: {
if (visible)
forceActiveFocus();
}
Timer {
id: searchTimer
interval: 350 // tweak as needed?
onTriggered: {
emojiPopup.model.filter = emojiSearch.text;
emojiPopup.model.category = EmojiCategory.Search;
}
}
ToolButton {
id: clearSearch
visible: emojiSearch.text !== ''
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? colors.highlight : colors.buttonText)
focusPolicy: Qt.NoFocus
onClicked: emojiSearch.clear()
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
}
// clear the default hover effects.
background: Item {
}
}
}
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
id: emojiScroll id: emojiScroll
} }
@ -160,7 +159,6 @@ Menu {
// Separator // Separator
Rectangle { Rectangle {
visible: emojiSearch.text === ''
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 1 Layout.preferredHeight: 1
color: emojiPopup.colors.alternateBase color: emojiPopup.colors.alternateBase
@ -168,7 +166,6 @@ Menu {
// Category picker row // Category picker row
RowLayout { RowLayout {
visible: emojiSearch.text === ''
Layout.bottomMargin: 0 Layout.bottomMargin: 0
Layout.preferredHeight: 42 Layout.preferredHeight: 42
implicitHeight: 42 implicitHeight: 42
@ -181,42 +178,42 @@ Menu {
// TODO: Would like to get 'simple' icons for the categories // TODO: Would like to get 'simple' icons for the categories
ListElement { ListElement {
image: ":/icons/icons/emoji-categories/people.png" image: ":/icons/icons/emoji-categories/people.png"
category: Emoji.Category.People category: EmojiCategory.People
} }
ListElement { ListElement {
image: ":/icons/icons/emoji-categories/nature.png" image: ":/icons/icons/emoji-categories/nature.png"
category: Emoji.Category.Nature category: EmojiCategory.Nature
} }
ListElement { ListElement {
image: ":/icons/icons/emoji-categories/foods.png" image: ":/icons/icons/emoji-categories/foods.png"
category: Emoji.Category.Food category: EmojiCategory.Food
} }
ListElement { ListElement {
image: ":/icons/icons/emoji-categories/activity.png" image: ":/icons/icons/emoji-categories/activity.png"
category: Emoji.Category.Activity category: EmojiCategory.Activity
} }
ListElement { ListElement {
image: ":/icons/icons/emoji-categories/travel.png" image: ":/icons/icons/emoji-categories/travel.png"
category: Emoji.Category.Travel category: EmojiCategory.Travel
} }
ListElement { ListElement {
image: ":/icons/icons/emoji-categories/objects.png" image: ":/icons/icons/emoji-categories/objects.png"
category: Emoji.Category.Objects category: EmojiCategory.Objects
} }
ListElement { ListElement {
image: ":/icons/icons/emoji-categories/symbols.png" image: ":/icons/icons/emoji-categories/symbols.png"
category: Emoji.Category.Symbols category: EmojiCategory.Symbols
} }
ListElement { ListElement {
image: ":/icons/icons/emoji-categories/flags.png" image: ":/icons/icons/emoji-categories/flags.png"
category: Emoji.Category.Flags category: EmojiCategory.Flags
} }
} }
@ -227,28 +224,27 @@ Menu {
hoverEnabled: true hoverEnabled: true
ToolTip.text: { ToolTip.text: {
switch (model.category) { switch (model.category) {
case Emoji.Category.People: case EmojiCategory.People:
return qsTr('People'); return qsTr('People');
case Emoji.Category.Nature: case EmojiCategory.Nature:
return qsTr('Nature'); return qsTr('Nature');
case Emoji.Category.Food: case EmojiCategory.Food:
return qsTr('Food'); return qsTr('Food');
case Emoji.Category.Activity: case EmojiCategory.Activity:
return qsTr('Activity'); return qsTr('Activity');
case Emoji.Category.Travel: case EmojiCategory.Travel:
return qsTr('Travel'); return qsTr('Travel');
case Emoji.Category.Objects: case EmojiCategory.Objects:
return qsTr('Objects'); return qsTr('Objects');
case Emoji.Category.Symbols: case EmojiCategory.Symbols:
return qsTr('Symbols'); return qsTr('Symbols');
case Emoji.Category.Flags: case EmojiCategory.Flags:
return qsTr('Flags'); return qsTr('Flags');
} }
} }
ToolTip.visible: hovered ToolTip.visible: hovered
onClicked: { onClicked: {
//emojiPopup.model.category = model.category; emojiPopup.model.category = model.category;
gridView.positionViewAtIndex(emojiPopup.model.sourceModel.categoryToIndex(model.category), GridView.Beginning);
} }
MouseArea { MouseArea {
@ -279,6 +275,56 @@ Menu {
} }
// Separator
Rectangle {
Layout.fillHeight: true
Layout.preferredWidth: 1
implicitWidth: 1
height: parent.height
color: emojiPopup.colors.alternateBase
}
// Search Button is special
AbstractButton {
id: searchBtn
hoverEnabled: true
Layout.alignment: Qt.AlignRight
Layout.bottomMargin: 0
ToolTip.text: qsTr("Search")
ToolTip.visible: hovered
onClicked: {
// clear any filters
emojiPopup.model.category = EmojiCategory.Search;
gridView.positionViewAtBeginning();
emojiSearch.forceActiveFocus();
}
Layout.preferredWidth: 36
Layout.preferredHeight: 36
implicitWidth: 36
implicitHeight: 36
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
contentItem: Image {
anchors.right: parent.right
horizontalAlignment: Image.AlignHCenter
verticalAlignment: Image.AlignVCenter
sourceSize.width: 32
sourceSize.height: 32
fillMode: Image.Pad
smooth: true
source: "image://colorimage/:/icons/icons/ui/search.png?" + (parent.hovered ? colors.highlight : colors.buttonText)
}
}
} }
} }

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick 2.10 import QtQuick 2.10
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -13,9 +9,9 @@ Item {
property real radius: 0 property real radius: 0
property color color: "#22000000" property color color: "#22000000"
property real maxRadius: Math.max(width, height) property real maxRadius: Math.max(width, height)
readonly property real radiusAnimationRate: 0.05 property real radiusAnimationRate: 0.05
readonly property real radiusTailAnimationRate: 0.5 property real radiusTailAnimationRate: 0.5
readonly property real opacityAnimationDuration: 300 property real opacityAnimationDuration: 300
readonly property real diameter: radius * 2 readonly property real diameter: radius * 2
property real centerX property real centerX
property real centerY property real centerY

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -16,7 +12,7 @@ Rectangle {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
if (CallManager.callType != CallType.VOICE) if (CallManager.isVideo)
stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1; stackLayout.currentIndex = stackLayout.currentIndex ? 0 : 1;
} }
@ -46,46 +42,10 @@ Rectangle {
} }
Image { Image {
id: callTypeIcon
Layout.leftMargin: 4 Layout.leftMargin: 4
Layout.preferredWidth: 24 Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
} source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
Item {
states: [
State {
name: "VOICE"
when: CallManager.callType == CallType.VOICE
PropertyChanges {
target: callTypeIcon
source: "qrc:/icons/icons/ui/place-call.png"
}
},
State {
name: "VIDEO"
when: CallManager.callType == CallType.VIDEO
PropertyChanges {
target: callTypeIcon
source: "qrc:/icons/icons/ui/video-call.png"
}
},
State {
name: "SCREEN"
when: CallManager.callType == CallType.SCREEN
PropertyChanges {
target: callTypeIcon
source: "qrc:/icons/icons/ui/screen-share.png"
}
}
]
} }
Label { Label {
@ -143,7 +103,7 @@ Rectangle {
PropertyChanges { PropertyChanges {
target: stackLayout target: stackLayout
currentIndex: CallManager.callType != CallType.VOICE ? 1 : 0 currentIndex: CallManager.isVideo ? 1 : 0
} }
}, },
@ -187,28 +147,20 @@ Rectangle {
} }
} }
Label {
Layout.leftMargin: 16
visible: CallManager.callType == CallType.SCREEN && CallManager.callState == WebRTCState.CONNECTED
text: qsTr("You are screen sharing")
font.pointSize: fontMetrics.font.pointSize * 1.1
color: "#000000"
}
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
ImageButton { ImageButton {
visible: CallManager.haveLocalPiP visible: CallManager.haveLocalVideo
width: 24 width: 24
height: 24 height: 24
buttonTextColor: "#000000" buttonTextColor: "#000000"
image: ":/icons/icons/ui/toggle-camera-view.png" image: ":/icons/icons/ui/toggle-camera-view.png"
hoverEnabled: true hoverEnabled: true
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Hide/Show Picture-in-Picture") ToolTip.text: qsTr("Toggle camera view")
onClicked: CallManager.toggleLocalPiP() onClicked: CallManager.toggleCameraView()
} }
ImageButton { ImageButton {

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
@ -44,7 +40,7 @@ Popup {
} }
RowLayout { RowLayout {
visible: CallManager.callType == CallType.VIDEO && CallManager.cameras.length > 0 visible: CallManager.isVideo && CallManager.cameras.length > 0
Image { Image {
Layout.preferredWidth: 22 Layout.preferredWidth: 22

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -57,7 +53,7 @@ Popup {
Layout.bottomMargin: msgView.height / 25 Layout.bottomMargin: msgView.height / 25
Image { Image {
property string image: CallManager.callType == CallType.VIDEO ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png" property string image: CallManager.isVideo ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png"
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: msgView.height / 10 Layout.preferredWidth: msgView.height / 10
@ -67,7 +63,7 @@ Popup {
Label { Label {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
text: CallManager.callType == CallType.VIDEO ? qsTr("Video Call") : qsTr("Voice Call") text: CallManager.isVideo ? qsTr("Video Call") : qsTr("Voice Call")
font.pointSize: fontMetrics.font.pointSize * 2 font.pointSize: fontMetrics.font.pointSize * 2
color: colors.windowText color: colors.windowText
} }
@ -101,7 +97,7 @@ Popup {
} }
RowLayout { RowLayout {
visible: CallManager.callType == CallType.VIDEO && CallManager.cameras.length > 0 visible: CallManager.isVideo && CallManager.cameras.length > 0
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
Image { Image {
@ -163,7 +159,7 @@ Popup {
RoundButton { RoundButton {
id: acceptButton id: acceptButton
property string image: CallManager.callType == CallType.VIDEO ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png" property string image: CallManager.isVideo ? ":/icons/icons/ui/video-call.png" : ":/icons/icons/ui/place-call.png"
implicitWidth: buttonLayout.buttonSize implicitWidth: buttonLayout.buttonSize
implicitHeight: buttonLayout.buttonSize implicitHeight: buttonLayout.buttonSize

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -56,12 +52,12 @@ Rectangle {
Layout.leftMargin: 4 Layout.leftMargin: 4
Layout.preferredWidth: 24 Layout.preferredWidth: 24
Layout.preferredHeight: 24 Layout.preferredHeight: 24
source: CallManager.callType == CallType.VIDEO ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
} }
Label { Label {
font.pointSize: fontMetrics.font.pointSize * 1.1 font.pointSize: fontMetrics.font.pointSize * 1.1
text: CallManager.callType == CallType.VIDEO ? qsTr("Video Call") : qsTr("Voice Call") text: CallManager.isVideo ? qsTr("Video Call") : qsTr("Voice Call")
color: "#000000" color: "#000000"
} }
@ -79,6 +75,7 @@ Rectangle {
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Devices") ToolTip.text: qsTr("Devices")
onClicked: { onClicked: {
CallManager.refreshDevices();
var dialog = devicesDialog.createObject(timelineRoot); var dialog = devicesDialog.createObject(timelineRoot);
dialog.open(); dialog.open();
} }
@ -86,7 +83,7 @@ Rectangle {
Button { Button {
Layout.rightMargin: 4 Layout.rightMargin: 4
icon.source: CallManager.callType == CallType.VIDEO ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png" icon.source: CallManager.isVideo ? "qrc:/icons/icons/ui/video-call.png" : "qrc:/icons/icons/ui/place-call.png"
text: qsTr("Accept") text: qsTr("Accept")
palette: colors palette: colors
onClicked: { onClicked: {
@ -105,7 +102,7 @@ Rectangle {
dialog.open(); dialog.open();
return ; return ;
} }
if (CallManager.callType == CallType.VIDEO && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) { if (CallManager.isVideo && CallManager.cameras.length > 0 && !CallManager.cameras.includes(Settings.camera)) {
var dialog = deviceError.createObject(timelineRoot, { var dialog = deviceError.createObject(timelineRoot, {
"errorString": qsTr("Unknown camera: %1").arg(Settings.camera), "errorString": qsTr("Unknown camera: %1").arg(Settings.camera),
"image": ":/icons/icons/ui/video-call.png" "image": ":/icons/icons/ui/video-call.png"

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
@ -27,14 +23,6 @@ Popup {
} }
Component {
id: screenShareDialog
ScreenShare {
}
}
ColumnLayout { ColumnLayout {
id: columnLayout id: columnLayout
@ -88,7 +76,7 @@ Popup {
onClicked: { onClicked: {
if (buttonLayout.validateMic()) { if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText; Settings.microphone = micCombo.currentText;
CallManager.sendInvite(TimelineManager.timeline.roomId(), CallType.VOICE); CallManager.sendInvite(TimelineManager.timeline.roomId(), false);
close(); close();
} }
} }
@ -102,23 +90,12 @@ Popup {
if (buttonLayout.validateMic()) { if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText; Settings.microphone = micCombo.currentText;
Settings.camera = cameraCombo.currentText; Settings.camera = cameraCombo.currentText;
CallManager.sendInvite(TimelineManager.timeline.roomId(), CallType.VIDEO); CallManager.sendInvite(TimelineManager.timeline.roomId(), true);
close(); close();
} }
} }
} }
Button {
visible: CallManager.screenShareSupported
text: qsTr("Screen")
icon.source: "qrc:/icons/icons/ui/screen-share.png"
onClicked: {
var dialog = screenShareDialog.createObject(timelineRoot);
dialog.open();
close();
}
}
Button { Button {
text: qsTr("Cancel") text: qsTr("Cancel")
onClicked: { onClicked: {

View File

@ -1,168 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import im.nheko 1.0
Popup {
modal: true
// only set the anchors on Qt 5.12 or higher
// see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop
Component.onCompleted: {
if (anchors)
anchors.centerIn = parent;
frameRateCombo.currentIndex = frameRateCombo.find(Settings.screenShareFrameRate);
}
palette: colors
ColumnLayout {
Label {
Layout.topMargin: 16
Layout.bottomMargin: 16
Layout.leftMargin: 8
Layout.rightMargin: 8
Layout.alignment: Qt.AlignLeft
text: qsTr("Share desktop with %1?").arg(TimelineManager.timeline.roomName)
color: colors.windowText
}
RowLayout {
Layout.leftMargin: 8
Layout.rightMargin: 8
Layout.bottomMargin: 8
Label {
Layout.alignment: Qt.AlignLeft
text: qsTr("Window:")
color: colors.windowText
}
ComboBox {
id: windowCombo
Layout.fillWidth: true
model: CallManager.windowList()
}
}
RowLayout {
Layout.leftMargin: 8
Layout.rightMargin: 8
Layout.bottomMargin: 8
Label {
Layout.alignment: Qt.AlignLeft
text: qsTr("Frame rate:")
color: colors.windowText
}
ComboBox {
id: frameRateCombo
Layout.fillWidth: true
model: ["25", "20", "15", "10", "5", "2", "1"]
}
}
GridLayout {
columns: 2
rowSpacing: 10
Layout.margins: 8
MatrixText {
text: qsTr("Include your camera picture-in-picture")
}
ToggleButton {
id: pipCheckBox
enabled: CallManager.cameras.length > 0
checked: Settings.screenSharePiP
Layout.alignment: Qt.AlignRight
}
MatrixText {
text: qsTr("Request remote camera")
ToolTip.text: qsTr("View your callee's camera like a regular video call")
ToolTip.visible: hovered
}
ToggleButton {
id: remoteVideoCheckBox
Layout.alignment: Qt.AlignRight
checked: Settings.screenShareRemoteVideo
ToolTip.text: qsTr("View your callee's camera like a regular video call")
ToolTip.visible: hovered
}
MatrixText {
text: qsTr("Hide mouse cursor")
}
ToggleButton {
id: hideCursorCheckBox
Layout.alignment: Qt.AlignRight
checked: Settings.screenShareHideCursor
}
}
RowLayout {
Layout.margins: 8
Item {
Layout.fillWidth: true
}
Button {
text: qsTr("Share")
icon.source: "qrc:/icons/icons/ui/screen-share.png"
onClicked: {
if (buttonLayout.validateMic()) {
Settings.microphone = micCombo.currentText;
if (pipCheckBox.checked)
Settings.camera = cameraCombo.currentText;
Settings.screenShareFrameRate = frameRateCombo.currentText;
Settings.screenSharePiP = pipCheckBox.checked;
Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked;
Settings.screenShareHideCursor = hideCursorCheckBox.checked;
CallManager.sendInvite(TimelineManager.timeline.roomId(), CallType.SCREEN, windowCombo.currentIndex);
close();
}
}
}
Button {
text: qsTr("Preview")
onClicked: {
CallManager.previewWindow(windowCombo.currentIndex);
}
}
Button {
text: qsTr("Cancel")
onClicked: {
close();
}
}
}
}
background: Rectangle {
color: colors.window
border.color: colors.windowText
}
}

View File

@ -1,7 +1,3 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.9 import QtQuick 2.9
import org.freedesktop.gstreamer.GLVideoItem 1.0 import org.freedesktop.gstreamer.GLVideoItem 1.0

View File

@ -74,7 +74,6 @@
<file>icons/ui/end-call.png</file> <file>icons/ui/end-call.png</file>
<file>icons/ui/microphone-mute.png</file> <file>icons/ui/microphone-mute.png</file>
<file>icons/ui/microphone-unmute.png</file> <file>icons/ui/microphone-unmute.png</file>
<file>icons/ui/screen-share.png</file>
<file>icons/ui/toggle-camera-view.png</file> <file>icons/ui/toggle-camera-view.png</file>
<file>icons/ui/video-call.png</file> <file>icons/ui/video-call.png</file>
@ -129,22 +128,16 @@
<file>qml/EncryptionIndicator.qml</file> <file>qml/EncryptionIndicator.qml</file>
<file>qml/ImageButton.qml</file> <file>qml/ImageButton.qml</file>
<file>qml/MatrixText.qml</file> <file>qml/MatrixText.qml</file>
<file>qml/MatrixTextField.qml</file>
<file>qml/ToggleButton.qml</file>
<file>qml/MessageInput.qml</file> <file>qml/MessageInput.qml</file>
<file>qml/MessageView.qml</file> <file>qml/MessageView.qml</file>
<file>qml/NhekoBusyIndicator.qml</file> <file>qml/NhekoBusyIndicator.qml</file>
<file>qml/PrivacyScreen.qml</file>
<file>qml/Reactions.qml</file> <file>qml/Reactions.qml</file>
<file>qml/ReplyPopup.qml</file> <file>qml/ReplyPopup.qml</file>
<file>qml/ScrollHelper.qml</file> <file>qml/ScrollHelper.qml</file>
<file>qml/StatusIndicator.qml</file> <file>qml/StatusIndicator.qml</file>
<file>qml/TimelineRow.qml</file> <file>qml/TimelineRow.qml</file>
<file>qml/TopBar.qml</file> <file>qml/TopBar.qml</file>
<file>qml/QuickSwitcher.qml</file>
<file>qml/ForwardCompleter.qml</file>
<file>qml/TypingIndicator.qml</file> <file>qml/TypingIndicator.qml</file>
<file>qml/RoomSettings.qml</file>
<file>qml/emoji/EmojiButton.qml</file> <file>qml/emoji/EmojiButton.qml</file>
<file>qml/emoji/EmojiPicker.qml</file> <file>qml/emoji/EmojiPicker.qml</file>
<file>qml/UserProfile.qml</file> <file>qml/UserProfile.qml</file>
@ -171,7 +164,6 @@
<file>qml/voip/CallInviteBar.qml</file> <file>qml/voip/CallInviteBar.qml</file>
<file>qml/voip/DeviceError.qml</file> <file>qml/voip/DeviceError.qml</file>
<file>qml/voip/PlaceCall.qml</file> <file>qml/voip/PlaceCall.qml</file>
<file>qml/voip/ScreenShare.qml</file>
<file>qml/voip/VideoCall.qml</file> <file>qml/voip/VideoCall.qml</file>
</qresource> </qresource>
<qresource prefix="/media"> <qresource prefix="/media">

View File

@ -11,13 +11,26 @@ class Emoji(object):
self.code = repr(code.encode('utf-8'))[1:].strip("'") self.code = repr(code.encode('utf-8'))[1:].strip("'")
self.shortname = shortname self.shortname = shortname
def generate_code(emojis, category):
tmpl = Template('''
const std::vector<Emoji> emoji::Provider::{{ category }} = {
// {{ category.capitalize() }}
{%- for e in emoji %}
Emoji{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}", emoji::EmojiCategory::{{ category.capitalize() }}},
{%- endfor %}
};
''')
d = dict(category=category, emoji=emojis)
print(tmpl.render(d))
def generate_qml_list(**kwargs): def generate_qml_list(**kwargs):
tmpl = Template(''' tmpl = Template('''
const QVector<Emoji> emoji::Provider::emoji = { const QVector<Emoji> emoji::Provider::emoji = {
{%- for c in kwargs.items() %} {%- for c in kwargs.items() %}
// {{ c[0].capitalize() }} // {{ c[0].capitalize() }}
{%- for e in c[1] %} {%- for e in c[1] %}
Emoji{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}", emoji::Emoji::Category::{{ c[0].capitalize() }}}, Emoji{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}", emoji::EmojiCategory::{{ c[0].capitalize() }}},
{%- endfor %} {%- endfor %}
{%- endfor %} {%- endfor %}
}; };
@ -80,4 +93,12 @@ if __name__ == '__main__':
# Use xclip to pipe the output to clipboard. # Use xclip to pipe the output to clipboard.
# e.g ./codegen.py emoji.json | xclip -sel clip # e.g ./codegen.py emoji.json | xclip -sel clip
generate_qml_list(people=people, nature=nature, food=food, activity=activity, travel=travel, objects=objects, symbols=symbols, flags=flags) generate_code(people, 'people')
generate_code(nature, 'nature')
generate_code(food, 'food')
generate_code(activity, 'activity')
generate_code(travel, 'travel')
generate_code(objects, 'objects')
generate_code(symbols, 'symbols')
generate_code(flags, 'flags')
generate_qml_list(people=people, nature=nature, food=food, activity=activity, travel=travel, objects=objects, symbols=symbols, flags=flags)

View File

@ -1,772 +0,0 @@
#!/usr/bin/python3
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import asyncio
import base64
import binascii
import errno
import fnmatch
import gzip
import json
import logging
import os
import sys
import time
import traceback
from argparse import ArgumentParser
from functools import reduce
from urllib.parse import urljoin, urlparse, urlsplit, urlunparse, urlunsplit
import aiohttp
from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed
import gi
gi.require_version('OSTree', '1.0')
from gi.repository import Gio, GLib, OSTree
UPLOAD_CHUNK_LIMIT = 4 * 1024 * 1024
DEFAULT_LIMIT = 2 ** 16
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
class UsageException(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
class ApiError(Exception):
def __init__(self, response, body):
self.url = str(response.url)
self.status = response.status
try:
self.body = json.loads(response);
except:
self.body = {"status": self.status, "error-type": "no-error", "message": "No json error details from server"}
def repr(self):
return {
"type": "api",
"url": self.url,
"status_code": self.status,
"details": self.body
}
def __str__(self):
return "Api call to %s failed with status %d, details: %s" % (self.url, self.status, self.body)
# This is similar to the regular payload, but opens the file lazily
class AsyncNamedFilePart(aiohttp.payload.Payload):
def __init__(self,
value,
disposition='attachment',
*args,
**kwargs):
self._file = None
if 'filename' not in kwargs:
kwargs['filename'] = os.path.basename(value)
super().__init__(value, *args, **kwargs)
if self._filename is not None and disposition is not None:
self.set_content_disposition(disposition, filename=self._filename, quote_fields=False)
self._size = os.stat(value).st_size
async def write(self, writer):
if self._file is None or self._file.closed:
self._file = open(self._value, 'rb')
try:
chunk = self._file.read(DEFAULT_LIMIT)
while chunk:
await writer.write(chunk)
chunk = self._file.read(DEFAULT_LIMIT)
finally:
self._file.close()
@property
def size(self):
return self._size
def ostree_object_path(repo, obj):
repodir = repo.get_path().get_path()
return os.path.join(repodir, 'objects', obj[0:2], obj[2:])
def ostree_get_dir_files(repo, objects, dirtree):
if dirtree.endswith(".dirtree"):
dirtree = dirtree[:-8]
dirtreev = repo.load_variant(OSTree.ObjectType.DIR_TREE, dirtree)[1]
iter = OSTree.RepoCommitTraverseIter()
iter.init_dirtree(repo, dirtreev, 0)
while True:
type = iter.next()
if type == OSTree.RepoCommitIterResult.END:
break
if type == OSTree.RepoCommitIterResult.ERROR:
break
if type == OSTree.RepoCommitIterResult.FILE:
d = iter.get_file()
objects.add(d.out_checksum + ".filez")
if type == OSTree.RepoCommitIterResult.DIR:
pass
def local_needed_files(repo, metadata_objects):
objects = set()
for c in metadata_objects:
if c.endswith(".dirtree"):
ostree_get_dir_files(repo, objects, c)
return objects
def local_needed_metadata_dirtree(repo, objects, dirtree_content, dirtree_meta):
objects.add(dirtree_meta + ".dirmeta")
dirtree_content_name = dirtree_content + ".dirtree"
if dirtree_content_name in objects:
return
objects.add(dirtree_content_name)
dirtreev = repo.load_variant(OSTree.ObjectType.DIR_TREE, dirtree_content)[1]
iter = OSTree.RepoCommitTraverseIter()
iter.init_dirtree(repo, dirtreev, 0)
while True:
type = iter.next()
if type == OSTree.RepoCommitIterResult.END:
break
if type == OSTree.RepoCommitIterResult.ERROR:
break
if type == OSTree.RepoCommitIterResult.FILE:
pass
if type == OSTree.RepoCommitIterResult.DIR:
d = iter.get_dir()
local_needed_metadata_dirtree(repo, objects, d.out_content_checksum, d.out_meta_checksum)
def local_needed_metadata(repo, commits):
objects = set()
for rev in commits:
objects.add(rev + ".commit")
commitv = repo.load_variant(OSTree.ObjectType.COMMIT, rev)[1]
iter = OSTree.RepoCommitTraverseIter()
iter.init_commit(repo, commitv, 0)
while True:
type = iter.next()
if type == OSTree.RepoCommitIterResult.END:
break
if type == OSTree.RepoCommitIterResult.ERROR:
break
if type == OSTree.RepoCommitIterResult.FILE:
pass
if type == OSTree.RepoCommitIterResult.DIR:
d = iter.get_dir()
local_needed_metadata_dirtree(repo, objects, d.out_content_checksum, d.out_meta_checksum)
return objects
def chunks(l, n):
"""Yield successive n-sized chunks from l."""
for i in range(0, len(l), n):
yield l[i:i + n]
async def missing_objects(session, build_url, token, wanted):
missing=[]
for chunk in chunks(wanted, 2000):
wanted_json=json.dumps({'wanted': chunk}).encode('utf-8')
data=gzip.compress(wanted_json)
headers = {
'Authorization': 'Bearer ' + token,
'Content-Encoding': 'gzip',
'Content-Type': 'application/json'
}
resp = await session.get(build_url + "/missing_objects", data=data, headers=headers)
async with resp:
if resp.status != 200:
raise ApiError(resp, await resp.text())
data = await resp.json()
missing.extend(data["missing"])
return missing
async def upload_files(session, build_url, token, files):
if len(files) == 0:
return
print("Uploading %d files (%d bytes)" % (len(files), reduce(lambda x, y: x + y, map(lambda f: f.size, files))))
with aiohttp.MultipartWriter() as writer:
for f in files:
writer.append(f)
writer.headers['Authorization'] = 'Bearer ' + token
resp = await session.request("post", build_url + '/upload', data=writer, headers=writer.headers)
async with resp:
if resp.status != 200:
raise ApiError(resp, await resp.text())
async def upload_deltas(session, repo_path, build_url, token, deltas, refs, ignore_delta):
if not len(deltas):
return
req = []
for ref, commit in refs.items():
# Skip screenshots here
parts = ref.split("/")
if len(parts) == 4 and (parts[0] == "app" or parts[0] =="runtime") and not should_skip_delta(parts[1], ignore_delta):
for delta in deltas:
# Only upload from-scratch deltas, as these are the only reused ones
if delta == commit:
print(" %s: %s" % (ref, delta))
delta_name = delta_name_encode (delta)
delta_dir = repo_path + "/deltas/" + delta_name[:2] + "/" + delta_name[2:]
parts = os.listdir(delta_dir)
for part in parts:
req.append(AsyncNamedFilePart(delta_dir + "/" + part, filename = delta_name + "." + part + ".delta"))
if len(req):
await upload_files(session, build_url, token, req)
async def upload_objects(session, repo_path, build_url, token, objects):
req = []
total_size = 0
for file_obj in objects:
named = get_object_multipart(repo_path, file_obj)
file_size = named.size
if total_size + file_size > UPLOAD_CHUNK_LIMIT: # The new object would bring us over the chunk limit
if len(req) > 0: # We already have some objects, upload those first
next_req = [named]
total_size = file_size
else:
next_req = []
req.append(named)
total_size = 0
await upload_files(session, build_url, token, req)
req = next_req
else:
total_size = total_size + file_size
req.append(named)
# Upload any remainder
await upload_files(session, build_url, token, req)
async def create_ref(session, build_url, token, ref, commit):
print("Creating ref %s with commit %s" % (ref, commit))
resp = await session.post(build_url + "/build_ref", headers={'Authorization': 'Bearer ' + token}, json= { "ref": ref, "commit": commit} )
async with resp:
if resp.status != 200:
raise ApiError(resp, await resp.text())
data = await resp.json()
return data
async def add_extra_ids(session, build_url, token, extra_ids):
print("Adding extra ids %s" % (extra_ids))
resp = await session.post(build_url + "/add_extra_ids", headers={'Authorization': 'Bearer ' + token}, json= { "ids": extra_ids} )
async with resp:
if resp.status != 200:
raise ApiError(resp, await resp.text())
data = await resp.json()
return data
async def get_build(session, build_url, token):
resp = await session.get(build_url, headers={'Authorization': 'Bearer ' + token})
if resp.status != 200:
raise ApiError(resp, await resp.text())
data = await resp.json()
return data
# For stupid reasons this is a string with json, lets expand it
def reparse_job_results(job):
job["results"] = json.loads(job.get("results", "{}"))
return job
async def get_job(session, job_url, token):
resp = await session.get(job_url, headers={'Authorization': 'Bearer ' + token}, json={})
async with resp:
if resp.status != 200:
raise ApiError(resp, await resp.text())
data = await resp.json()
return data
async def wait_for_job(session, job_url, token):
reported_delay = False
old_job_status = 0
printed_len = 0
iterations_since_change=0
error_iterations = 0
while True:
try:
resp = await session.get(job_url, headers={'Authorization': 'Bearer ' + token}, json={'log-offset': printed_len})
async with resp:
if resp.status == 200:
error_iterations = 0
job = await resp.json()
job_status = job['status']
if job_status == 0 and not reported_delay:
reported_delay = True
start_after_struct = job.get("start_after", None)
if start_after_struct:
start_after = start_after_struct.get("secs_since_epoch", None)
now = time.time()
if start_after and start_after > now:
print("Waiting %d seconds before starting job" % (int(start_after - now)))
if job_status > 0 and old_job_status == 0:
print("/ Job was started");
old_job_status = job_status
log = job['log']
if len(log) > 0:
iterations_since_change=0
for line in log.splitlines(True):
print("| %s" % line, end="")
printed_len = printed_len + len(log)
else:
iterations_since_change=iterations_since_change+1
if job_status > 1:
if job_status == 2:
print("\ Job completed successfully")
else:
print("\ Job failed")
return job
else:
iterations_since_change=4 # Start at 4 so we ramp up the delay faster
error_iterations=error_iterations + 1
if error_iterations <= 5:
print("Unexpected response %s getting job log, ignoring" % resp.status)
else:
raise ApiError(resp, await resp.text())
except OSError as e:
if e.args[0] == errno.ECONNRESET:
# Client disconnected, retry
# Not sure exactly why, but i got a lot of ConnectionResetErrors here
# in tests. I guess the server stops reusing a http2 session after a bit
# Should be fine to retry with the backof
pass
else:
raise
# Some polling backoff to avoid loading the server
if iterations_since_change <= 1:
sleep_time=1
elif iterations_since_change < 5:
sleep_time=3
elif iterations_since_change < 15:
sleep_time=5
elif iterations_since_change < 30:
sleep_time=10
else:
sleep_time=60
time.sleep(sleep_time)
async def commit_build(session, build_url, eol, eol_rebase, token_type, wait, token):
print("Committing build %s" % (build_url))
json = {
"endoflife": eol,
"endoflife_rebase": eol_rebase
}
if token_type != None:
json['token_type'] = token_type
resp = await session.post(build_url + "/commit", headers={'Authorization': 'Bearer ' + token}, json=json)
async with resp:
if resp.status != 200:
raise ApiError(resp, await resp.text())
job = await resp.json()
job_url = resp.headers['location'];
if wait:
print("Waiting for commit job")
job = await wait_for_job(session, job_url, token);
reparse_job_results(job)
job["location"] = job_url
return job
async def publish_build(session, build_url, wait, token):
print("Publishing build %s" % (build_url))
resp = await session.post(build_url + "/publish", headers={'Authorization': 'Bearer ' + token}, json= { } )
async with resp:
if resp.status == 400:
body = await resp.text()
try:
msg = json.loads(body)
if msg.get("current-state", "") == "published":
print("the build has been already published")
return {}
except:
pass
if resp.status != 200:
raise ApiError(resp, await resp.text())
job = await resp.json()
job_url = resp.headers['location'];
if wait:
print("Waiting for publish job")
job = await wait_for_job(session, job_url, token);
reparse_job_results(job)
job["location"] = job_url
return job
async def purge_build(session, build_url, token):
print("Purging build %s" % (build_url))
resp = await session.post(build_url + "/purge", headers={'Authorization': 'Bearer ' + token}, json= {} )
async with resp:
if resp.status != 200:
raise ApiError(resp, await resp.text())
return await resp.json()
async def create_token(session, manager_url, token, name, subject, scope, duration):
token_url = urljoin(manager_url, "api/v1/token_subset")
resp = await session.post(token_url, headers={'Authorization': 'Bearer ' + token}, json = {
"name": name,
"sub": subject,
"scope": scope,
"duration": duration,
})
async with resp:
if resp.status != 200:
raise ApiError(resp, await resp.text())
return await resp.json()
def get_object_multipart(repo_path, object):
return AsyncNamedFilePart(repo_path + "/objects/" + object[:2] + "/" + object[2:], filename=object)
async def create_command(session, args):
build_url = urljoin(args.manager_url, "api/v1/build")
resp = await session.post(build_url, headers={'Authorization': 'Bearer ' + args.token}, json={
"repo": args.repo
})
async with resp:
if resp.status != 200:
raise ApiError(resp, await resp.text())
data = await resp.json()
data["location"] = resp.headers['location']
if not args.print_output:
print(resp.headers['location'])
return data
def delta_name_part_encode(commit):
return base64.b64encode(binascii.unhexlify(commit), b"+_")[:-1].decode("utf-8")
def delta_name_encode (delta):
return "-".join(map(delta_name_part_encode, delta.split("-")))
def should_skip_delta(id, globs):
if globs:
for glob in globs:
if fnmatch.fnmatch(id, glob):
return True
return False
def build_url_to_api(build_url):
parts = urlparse(build_url)
path = os.path.dirname(os.path.dirname(parts.path))
return urlunparse((parts.scheme, parts.netloc, path, None, None, None))
@retry(
stop=stop_after_attempt(6),
wait=wait_fixed(10),
retry=retry_if_exception_type(ApiError),
reraise=True,
)
async def push_command(session, args):
local_repo = OSTree.Repo.new(Gio.File.new_for_path(args.repo_path))
try:
local_repo.open(None)
except GLib.Error as err:
raise UsageException("Can't open repo %s: %s" % (args.repo_path, err.message)) from err
refs = {}
if len(args.branches) == 0:
_, all_refs = local_repo.list_refs(None, None)
for ref in all_refs:
if ref.startswith("app/") or ref.startswith("runtime/") or ref.startswith("screenshots/"):
refs[ref] = all_refs[ref]
else:
for branch in args.branches:
_, rev = local_repo.resolve_rev(branch, False)
refs[branch] = rev
if (args.minimal_token):
id = os.path.basename(urlparse(args.build_url).path)
token = create_token(args.build_url, args.token, "minimal-upload", "build/%s" % (id), ["upload"], 60*60)["token"]
else:
token = args.token
print("Uploading refs to %s: %s"% (args.build_url, list(refs)))
metadata_objects = local_needed_metadata(local_repo, refs.values())
print("Refs contain %d metadata objects" % (len(metadata_objects)))
missing_metadata_objects = await missing_objects(session, args.build_url, token, list(metadata_objects))
print("Remote missing %d of those" % (len(missing_metadata_objects)))
file_objects = local_needed_files(local_repo, missing_metadata_objects)
print("Has %d file objects for those" % (len(file_objects)))
missing_file_objects = await missing_objects(session, args.build_url, token, list(file_objects))
print("Remote missing %d of those" % (len(missing_file_objects)))
# First upload all missing file objects
print("Uploading file objects")
await upload_objects(session, args.repo_path, args.build_url, token, missing_file_objects)
# Then all the metadata
print("Uploading metadata objects")
await upload_objects(session, args.repo_path, args.build_url, token, missing_metadata_objects)
_, deltas = local_repo.list_static_delta_names()
print("Uploading deltas")
await upload_deltas(session, args.repo_path, args.build_url, token, deltas, refs, args.ignore_delta)
# Then the refs
for ref, commit in refs.items():
await create_ref(session, args.build_url, token, ref, commit)
# Then any extra ids
if args.extra_id:
await add_extra_ids(session, args.build_url, token, args.extra_id)
commit_job = None
publish_job = None
update_job = None
# Note, this always uses the full token, as the minimal one only has upload permissions
if args.commit or args.publish:
commit_job = await commit_build(session, args.build_url, args.end_of_life, args.end_of_life_rebase, args.token_type, args.publish or args.wait, args.token)
if args.publish:
publish_job = await publish_build(session, args.build_url, args.wait or args.wait_update, args.token)
update_job_id = publish_job.get("results", {}).get("update-repo-job", None)
if update_job_id:
print("Queued repo update job %d" %(update_job_id))
update_job_url = build_url_to_api(args.build_url) + "/job/" + str(update_job_id)
if args.wait_update:
print("Waiting for repo update job")
update_job = await wait_for_job (session, update_job_url, token);
else:
update_job = await get_job(session, update_job_url, token)
reparse_job_results(update_job)
update_job["location"] = update_job_url
data = await get_build(session, args.build_url, args.token)
if commit_job:
data["commit_job"] = commit_job
if publish_job:
data["publish_job"] = publish_job
if update_job:
data["update_job"] = update_job
return data
async def commit_command(session, args):
job = await commit_build(session, args.build_url, args.end_of_life, args.end_of_life_rebase, args.token_type, args.wait, args.token)
return job
async def publish_command(session, args):
job = await publish_build(session, args.build_url, args.wait or args.wait_update, args.token)
update_job_id = job.get("results", {}).get("update-repo-job", None)
if update_job_id:
print("Queued repo update job %d" %(update_job_id))
update_job_url = build_url_to_api(args.build_url) + "/job/" + str(update_job_id)
if args.wait_update:
print("Waiting for repo update job")
update_job = await wait_for_job(session, update_job_url, args.token);
else:
update_job = await get_job(session, update_job_url, args.token)
reparse_job_results(update_job)
update_job["location"] = update_job_url
return job
async def purge_command(session, args):
job = await purge_build(session, args.build_url, args.token)
return job
async def create_token_command(session, args):
data = await create_token(session, args.manager_url, args.token, args.name, args.subject, args.scope, args.duration)
if not args.print_output:
print(data['token'])
return data
async def follow_job_command(session, args):
job = await wait_for_job(session, args.job_url, args.token)
return job
async def run_with_session(args):
timeout = aiohttp.ClientTimeout(total=90*60)
async with aiohttp.ClientSession(timeout=timeout) as session:
result = await args.func(session, args)
return result
if __name__ == '__main__':
progname = os.path.basename(sys.argv[0])
parser = ArgumentParser(prog=progname)
parser.add_argument('-v', '--verbose', action='store_true',
help='enable verbose output')
parser.add_argument('--debug', action='store_true',
help='enable debugging output')
parser.add_argument('--output', help='Write output json to file')
parser.add_argument('--print-output', action='store_true', help='Print output json')
parser.add_argument('--token', help='use this token')
parser.add_argument('--token-file', help='use token from file')
subparsers = parser.add_subparsers(title='subcommands',
dest='subparser_name',
description='valid subcommands',
help='additional help')
create_parser = subparsers.add_parser('create', help='Create new build')
create_parser.add_argument('manager_url', help='remote repo manager url')
create_parser.add_argument('repo', help='repo name')
create_parser.set_defaults(func=create_command)
push_parser = subparsers.add_parser('push', help='Push to repo manager')
push_parser.add_argument('build_url', help='remote build url')
push_parser.add_argument('repo_path', help='local repository')
push_parser.add_argument('branches', nargs='*', help='branches to push')
push_parser.add_argument('--commit', action='store_true',
help='commit build after pushing')
push_parser.add_argument('--publish', action='store_true',
help='publish build after committing')
push_parser.add_argument('--extra-id', action='append', help='add extra collection-id')
push_parser.add_argument('--ignore-delta', action='append', help='don\'t upload deltas matching this glob')
push_parser.add_argument('--wait', action='store_true',
help='wait for commit/publish to finish')
push_parser.add_argument('--wait-update', action='store_true',
help='wait for update-repo to finish')
push_parser.add_argument('--minimal-token', action='store_true',
help='Create minimal token for the upload')
push_parser.add_argument('--end-of-life', help='Set end of life')
push_parser.add_argument('--end-of-life-rebase', help='Set new ID which will supercede the current one')
push_parser.add_argument('--token-type', help='Set token type', type=int)
push_parser.set_defaults(func=push_command)
commit_parser = subparsers.add_parser('commit', help='Commit build')
commit_parser.add_argument('--wait', action='store_true',
help='wait for commit to finish')
commit_parser.add_argument('--end-of-life', help='Set end of life')
commit_parser.add_argument('--end-of-life-rebase', help='Set new ID which will supercede the current one')
commit_parser.add_argument('--token-type', help='Set token type', type=int)
commit_parser.add_argument('build_url', help='remote build url')
commit_parser.set_defaults(func=commit_command)
publish_parser = subparsers.add_parser('publish', help='Publish build')
publish_parser.add_argument('--wait', action='store_true',
help='wait for publish to finish')
publish_parser.add_argument('--wait-update', action='store_true',
help='wait for update-repo to finish')
publish_parser.add_argument('build_url', help='remote build url')
publish_parser.set_defaults(func=publish_command)
purge_parser = subparsers.add_parser('purge', help='Purge build')
purge_parser.add_argument('build_url', help='remote build url')
purge_parser.set_defaults(func=purge_command)
create_token_parser = subparsers.add_parser('create-token', help='Create subset token')
create_token_parser.add_argument('manager_url', help='remote repo manager url')
create_token_parser.add_argument('name', help='Name')
create_token_parser.add_argument('subject', help='Subject')
create_token_parser.add_argument('scope', nargs='*', help='Scope')
create_token_parser.add_argument('--duration', help='Duration until expires, in seconds',
default=60*60*24, # Default duration is one day
type=int)
create_token_parser.set_defaults(func=create_token_command)
follow_job_parser = subparsers.add_parser('follow-job', help='Follow existing job log')
follow_job_parser.add_argument('job_url', help='url of job')
follow_job_parser.set_defaults(func=follow_job_command)
args = parser.parse_args()
loglevel = logging.WARNING
if args.verbose:
loglevel = logging.INFO
if args.debug:
loglevel = logging.DEBUG
logging.basicConfig(format='%(module)s: %(levelname)s: %(message)s',
level=loglevel, stream=sys.stderr)
if not args.subparser_name:
print("No subcommand specified, see --help for usage")
exit(1)
if not args.token:
if args.token_file:
file = open(args.token_file, 'rb')
args.token = file.read().splitlines()[0].decode("utf-8").strip()
elif "REPO_TOKEN" in os.environ:
args.token = os.environ["REPO_TOKEN"]
else:
print("No token available, pass with --token, --token-file or $REPO_TOKEN")
exit(1)
res = 1
output = None
try:
loop = asyncio.get_event_loop()
result = loop.run_until_complete(run_with_session(args))
output = {
"command": args.subparser_name,
"result": result,
}
res = 0
except SystemExit:
# Something called sys.exit(), lets just exit
res = 1
raise # Pass on regular exit callse
except ApiError as e:
eprint(str(e))
output = {
"command": args.subparser_name,
"error": e.repr(),
}
except UsageException as e:
eprint(str(e))
output = {
"error": {
"type": "usage",
"details": {
"message": str(e),
}
}
}
except:
ei = sys.exc_info()
eprint("Unexpected %s exception in %s: %s" % (ei[0].__name__, args.subparser_name, ei[1]))
eprint(traceback.format_exc())
output = {
"command": args.subparser_name,
"error": {
"type": "exception",
"details": {
"error-type": ei[0].__name__,
"message": str(ei[1]),
}
}
}
res = 1
if output:
if args.print_output:
print(json.dumps(output, indent=4))
if args.output:
f = open(args.output,"w+")
f.write(json.dumps(output, indent=4))
f.write("\n")
f.close()
exit(res)

View File

@ -1,24 +0,0 @@
#!/bin/bash
if [ -z "$1" ]; then
echo "Missing repo to upload!"
exit 1
fi
if [ -n "${CI_COMMIT_TAG}" ]; then
BUILD_URL=$(./flat-manager-client create https://flatpak.neko.dev stable)
elif [ "master" = "${CI_COMMIT_REF_NAME}" ]; then
BUILD_URL=$(./flat-manager-client create https://flatpak.neko.dev nightly)
fi
if [ -z "${BUILD_URL}" ]; then
echo "No upload to repo."
exit 0
fi
BUILD_URL=${BUILD_URL/http:/https:}
./flat-manager-client push $BUILD_URL $1
./flat-manager-client commit --wait $BUILD_URL
./flat-manager-client publish --wait $BUILD_URL

View File

@ -1,11 +1,22 @@
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr> /*
// SPDX-FileCopyrightText: 2021 Nheko Contributors * nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
// *
// SPDX-License-Identifier: GPL-3.0-or-later * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QBuffer> #include <QBuffer>
#include <QPixmapCache> #include <QPixmapCache>
#include <QPointer>
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
@ -13,14 +24,13 @@
#include "Cache.h" #include "Cache.h"
#include "Logging.h" #include "Logging.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "MxcImageProvider.h"
#include "Utils.h" #include "Utils.h"
static QPixmapCache avatar_cache; static QPixmapCache avatar_cache;
namespace AvatarProvider { namespace AvatarProvider {
void void
resolve(QString avatarUrl, int size, QObject *receiver, AvatarCallback callback) resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback callback)
{ {
const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size); const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size);
@ -35,32 +45,44 @@ resolve(QString avatarUrl, int size, QObject *receiver, AvatarCallback callback)
return; return;
} }
MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")), auto data = cache::image(cacheKey);
QSize(size, size), if (!data.isNull()) {
[callback, cacheKey, recv = QPointer<QObject>(receiver)]( pixmap = QPixmap::fromImage(utils::readImage(&data));
QString, QSize, QImage img, QString) { avatar_cache.insert(cacheKey, pixmap);
if (!recv) callback(pixmap);
return; return;
}
auto proxy = std::make_shared<AvatarProxy>(); auto proxy = std::make_shared<AvatarProxy>();
QObject::connect(proxy.get(), QObject::connect(proxy.get(),
&AvatarProxy::avatarDownloaded, &AvatarProxy::avatarDownloaded,
recv, receiver,
[callback, cacheKey](QPixmap pm) { [callback, cacheKey](QByteArray data) {
if (!pm.isNull()) QPixmap pm = QPixmap::fromImage(utils::readImage(&data));
avatar_cache.insert( avatar_cache.insert(cacheKey, pm);
cacheKey, pm); callback(pm);
callback(pm); });
});
if (img.isNull()) { mtx::http::ThumbOpts opts;
emit proxy->avatarDownloaded(QPixmap{}); opts.width = size;
return; opts.height = size;
} opts.mxc_url = avatarUrl.toStdString();
auto pm = QPixmap::fromImage(std::move(img)); http::client()->get_thumbnail(
emit proxy->avatarDownloaded(pm); opts,
}); [opts, cacheKey, proxy = std::move(proxy)](const std::string &res,
mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("failed to download avatar: {} - ({} {})",
opts.mxc_url,
mtx::errors::to_string(err->matrix_error.errcode),
err->matrix_error.error);
} else {
cache::saveImage(cacheKey.toStdString(), res);
}
emit proxy->avatarDownloaded(QByteArray(res.data(), (int)res.size()));
});
} }
void void
@ -70,8 +92,8 @@ resolve(const QString &room_id,
QObject *receiver, QObject *receiver,
AvatarCallback callback) AvatarCallback callback)
{ {
auto avatarUrl = cache::avatarUrl(room_id, user_id); const auto avatarUrl = cache::avatarUrl(room_id, user_id);
resolve(std::move(avatarUrl), size, receiver, callback); resolve(avatarUrl, size, receiver, callback);
} }
} }

View File

@ -1,26 +1,38 @@
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr> /*
// SPDX-FileCopyrightText: 2021 Nheko Contributors * nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
// *
// SPDX-License-Identifier: GPL-3.0-or-later * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once #pragma once
#include <QPixmap> #include <QPixmap>
#include <functional> #include <functional>
using AvatarCallback = std::function<void(QPixmap)>;
class AvatarProxy : public QObject class AvatarProxy : public QObject
{ {
Q_OBJECT Q_OBJECT
signals: signals:
void avatarDownloaded(QPixmap pm); void avatarDownloaded(const QByteArray &data);
}; };
using AvatarCallback = std::function<void(QPixmap)>;
namespace AvatarProvider { namespace AvatarProvider {
void void
resolve(QString avatarUrl, int size, QObject *receiver, AvatarCallback cb); resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback cb);
void void
resolve(const QString &room_id, resolve(const QString &room_id,
const QString &user_id, const QString &user_id,

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