Compare commits

..

57 Commits

Author SHA1 Message Date
Carlos De Maine
c58ac6769b try again to set track/risk on extension 2026-02-13 23:43:43 +10:00
Carlos De Maine
1f482000d2 trya nd oveeride with anchor and alias 2026-01-09 09:05:19 +10:00
Carlos De Maine
d0731c9c36 no override-pull: 2026-01-08 21:08:07 +10:00
Carlos De Maine
530fbc212b formatting 2026-01-08 20:51:26 +10:00
Carlos De Maine
ea467a7901 use the CRAFT_SNAP_CHANNEL instead 2026-01-08 20:47:00 +10:00
Carlos De Maine
78ac2681de $KDE_SNAP_CHANNEL 2026-01-08 18:07:38 +10:00
Carlos De Maine
0e16518123 docbook-xml & docbook-xsl 2026-01-08 17:51:26 +10:00
Carlos De Maine
62e204207d patch in qt6.10 support t libquotient 2026-01-08 17:42:36 +10:00
Carlos De Maine
be712a6810 modernise paths, cmake-paths etc 2026-01-08 16:45:45 +10:00
Carlos De Maine
287043ed44 libsecret and qtkeychain are in the kf6-sdk 2026-01-08 16:06:28 +10:00
Carlos De Maine
4675f5f7e7 bump keychain to 0.15.0 2026-01-08 16:01:05 +10:00
Carlos De Maine
305f008796 bump libquotient to 9.5 2026-01-08 15:43:09 +10:00
Carlos De Maine
a4bf3e4da0 duktape-dev 2026-01-08 15:14:35 +10:00
Carlos De Maine
20bd8508e0 after:
- neochat
2026-01-08 15:04:32 +10:00
Carlos De Maine
7406fd9884 add cleanup part with overriding build-snaps: 2026-01-08 15:00:14 +10:00
Carlos De Maine
ff62c74cb3 try building with kf6-extension from edge channel 2026-01-08 14:52:18 +10:00
l10n daemon script
a119563ba7 GIT_SILENT Sync po/docbooks with svn 2026-01-08 01:44:03 +00:00
Joshua Goins
5e473aae0a Avoid assert check when switching to main profile
This can happen if the user doesn't have an avatar set, as there's an
assert check inside of makeMediaUrl. We can avoid this by checking if
the string is empty before calling that.
2026-01-07 17:05:13 -05:00
Joshua Goins
aa0272a02d Remove some duplicate buttons from the room sidebar
The favorites action doesn't really deserve space here IMO, and that's a
pretty list-specific action you're more likely to do elsewhere.

For the Pinned Messages action that can be opened at the top on desktop,
but we still need it on mobile. So now it's selectively hidden based on
that.
2026-01-07 17:04:23 -05:00
Joshua Goins
fc6f345036 Small notification improvements
Changed a check to use isDirectChat (which is a clearer indication of
what we want.) I also made sure not to show the account name if you only
have one, since that's just useless noise.
2026-01-07 15:42:19 -05:00
l10n daemon script
4fa011c266 GIT_SILENT Sync po/docbooks with svn 2026-01-07 01:44:45 +00:00
l10n daemon script
44ebcc6b9a GIT_SILENT Sync po/docbooks with svn 2026-01-06 01:51:54 +00:00
Joshua Goins
32cc5d4e48 Allow switching between viewing main profiles and room profiles
Since Matrix allows users to have different profiles on a room and
"outside of room" level, it's nice to have the ability to switch between
them on-the-fly.
2026-01-05 09:52:31 -05:00
l10n daemon script
bbc5a92f62 GIT_SILENT Sync po/docbooks with svn 2026-01-05 01:51:29 +00:00
Joshua Goins
4656bf4ee7 Rename "Explore Rooms" dialog to just "Explore"
There's a bit of a discrepancy here, that directory will list both rooms
and spaces. I also removed duplicated titles where applicable.
2026-01-04 17:41:55 -05:00
Azhar Momin
5f7967363f Fix notification count refresh for low-priority and mentions-only rooms 2026-01-04 17:00:52 -05:00
Joshua Goins
a02a04d966 Fix icons on Windows
KirigamiApp currently calls KIconTheme::initTheme too late for Windows,
as a workaround we can go back to calling it ourselves.
2026-01-04 13:48:30 +00:00
l10n daemon script
dffec2f0d5 GIT_SILENT Sync po/docbooks with svn 2026-01-04 01:51:40 +00:00
Veres Károly
68b00b9fc5 Extract the space selection logic from setCurrentRoom and use it for setting lastRoomConfig too.
If a setCurrentRoom call changed the active space at the end of its execution, the new room's ID ended up still being written to the old space's lastRoomConfig.

By extracting this space selection logic into a helper function, we can now calculate this value earlier and use it as the space id when writing lastRoomConfig.
2026-01-03 07:42:56 -05:00
Heiko Becker
aa823c7629 GIT_SILENT Update Appstream for new release
(cherry picked from commit 321561fd89)
2026-01-03 12:14:48 +01:00
l10n daemon script
73b82b69d8 GIT_SILENT Sync po/docbooks with svn 2026-01-03 01:41:59 +00:00
Nate Graham
bd0588ca99 Improve hamburger menu button
- Open the menu right beneath the button
- Use pressed state for the button while the menu is open
- Close the menu when clicking the button again
- Hide the tooltip while the menu is open
2026-01-02 08:07:32 -07:00
l10n daemon script
e5b7601dac GIT_SILENT Sync po/docbooks with svn 2026-01-02 01:51:12 +00:00
l10n daemon script
f4ac3346f5 GIT_SILENT Sync po/docbooks with svn 2026-01-01 01:40:42 +00:00
Azhar Momin
58ea229b67 Add a button to cycle through unread highlights
BUG: 465095
2025-12-31 13:57:35 +00:00
l10n daemon script
fd44ff972a GIT_SILENT Sync po/docbooks with svn 2025-12-31 01:51:00 +00:00
l10n daemon script
c5eb63da15 GIT_SILENT Sync po/docbooks with svn 2025-12-30 01:49:46 +00:00
l10n daemon script
79acceb29a SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2025-12-30 01:34:45 +00:00
l10n daemon script
8022a7aae5 GIT_SILENT Sync po/docbooks with svn 2025-12-29 01:42:10 +00:00
l10n daemon script
d26862dfb6 GIT_SILENT Sync po/docbooks with svn 2025-12-28 01:39:52 +00:00
l10n daemon script
c25c095c1b GIT_SILENT Sync po/docbooks with svn 2025-12-27 01:40:07 +00:00
Tobias Fella
e09e4fb7dc Modernize PropertyChanges 2025-12-26 18:58:28 +00:00
Tobias Fella
657c8a0dcd Fixes for v12 power levels 2025-12-26 18:30:46 +00:00
l10n daemon script
82f54b4f2c GIT_SILENT Sync po/docbooks with svn 2025-12-26 01:41:25 +00:00
Joshua Goins
9e7cd0eb09 Overhaul user profile UI
Our previous iteration is hitting some limitations as the power of
profiles on Matrix grows. For example, where do we put common rooms or
extra profile fields?

I re-arranged everything to group similar actions together - instead of
throwing it all into one big list. We basically trade vertical for
horizontal space, and gives us more headroom for extra fields when we
want to add more.
2025-12-25 12:34:21 -05:00
Joshua Goins
9bdb9b6a7c Improve wording around the various link preview options
In my opinion, the significance of these options was still not super
clear. It wasn't obvious that the setting under Appearance will disable
them globally, and that the per-room option was discoverable.

Another change was using the term "Link preview" instead of "URL
preview", which is more common verbiage in chat applications other than
Element.
2025-12-25 12:33:00 -05:00
Joshua Goins
fa4a93140d Fix not being able to view the DM's profile
This button was broken for a while, so now that's fixed.
2025-12-24 18:46:06 -05:00
Joshua Goins
a41966ecd7 Adjust text for the ignore user option in invitiations
We use the "ignore" terminology elsewhere, so saying "block" here is
kinda weird. I also made it clearer that this also rejects the invite,
in case that wasn't obvious.
2025-12-24 18:43:12 -05:00
Joshua Goins
edcf81f0da Add a few ways to open profile for inviting users
I recieved a few invites from unknown users, and I had no idea who they
were/came from. I could check their profile to see if we had any rooms
in common, but there was no way to actually check their profile!

This makes it possible to open their profile by clicking their avatar
(like elsewhere) but also adds a button to make it even more obvious.
2025-12-24 18:42:26 -05:00
l10n daemon script
0a98c732e0 GIT_SILENT Sync po/docbooks with svn 2025-12-24 01:50:27 +00:00
l10n daemon script
d734e114a6 GIT_SILENT Sync po/docbooks with svn 2025-12-23 01:41:20 +00:00
Joshua Goins
946e505a24 Add missing supportsMatrixSpecVersion QML function
This was rebased out or something in 197d7ce8e8
2025-12-22 16:31:42 -05:00
Joshua Goins
092980c0ff Shorten room subtitles for direct chats
Direct chats by their very nature is between two users, and you usually
can keep track of who said what. There's no point in including the
sender's name here, and most other chat applications exclude it for this
reason (including Element X.)
2025-12-22 15:02:24 -05:00
Joshua Goins
6fcb1cc1e3 Fix assumption about unstable feature reporting
The key can be in the unstable features list, but it can be false. This
stops some features showing up and hitting API that isn't actually
implemented.
2025-12-22 07:26:27 -05:00
Olivier De Cannière
c572480d10 cmake: Explicitly link in neochatplugin and use qt_* cmake commands
The QML code of the various QML modules is compiled to C++ by
qmlcachegen and then compiled into the final binary. Unfortunately, in
the current setup, the plugin for the org.kde.neochat QML module is not
linked into the app because qmlimportscanner can't find usages of it. No
generated C++ code for that module is then run and the QML is
interpreted from its bytecode instead.

This is likely due to the project not following recommended QML
structure and because it is not using qt_* variants of certain cmake
commands. It should not be necessary to link against the plugins
manually.

As a quick fix, link against the plugin explicitly and use qt_*
variants of cmake commands to help qmlimportscanner and to pick up the
existing C++ code.
2025-12-22 11:11:35 +01:00
Joshua Goins
531df7a3b2 Send beautiful red ❤️'s when quick reacting
I wondered for a while (and could tell) when people were using NeoChat
because they would react with cold, monochrome hearts. Let's add more
color to our world!
2025-12-21 13:57:59 -05:00
Veres Károly
706f1f7836 Fix missing escape sequence in /shrug command
Before the fix, the upper arm _ characters in the command's output would be parsed as Markdown italic formatting around the (ツ).
2025-12-21 15:03:00 +01:00
97 changed files with 31393 additions and 18008 deletions

View File

@@ -4,18 +4,18 @@
include:
- project: sysadmin/ci-utilities
file:
- /gitlab-templates/reuse-lint.yml
- /gitlab-templates/json-validation.yml
- /gitlab-templates/xml-lint.yml
- /gitlab-templates/yaml-lint.yml
- /gitlab-templates/android-qt6.yml
- /gitlab-templates/linux-qt6.yml
- /gitlab-templates/linux-qt6-next.yml
- /gitlab-templates/windows-qt6.yml
- /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/flatpak.yml
#- /gitlab-templates/reuse-lint.yml
#- /gitlab-templates/json-validation.yml
#- /gitlab-templates/xml-lint.yml
#- /gitlab-templates/yaml-lint.yml
#- /gitlab-templates/android-qt6.yml
#- /gitlab-templates/linux-qt6.yml
#- /gitlab-templates/linux-qt6-next.yml
#- /gitlab-templates/windows-qt6.yml
#- /gitlab-templates/freebsd-qt6.yml
#- /gitlab-templates/flatpak.yml
- /gitlab-templates/snap-snapcraft-lxd.yml
- /gitlab-templates/craft-android-qt6-apks.yml
- /gitlab-templates/craft-appimage-qt6.yml
- /gitlab-templates/craft-windows-x86-64-qt6.yml
- /gitlab-templates/craft-windows-appx-qt6.yml
#- /gitlab-templates/craft-android-qt6-apks.yml
#- /gitlab-templates/craft-appimage-qt6.yml
#- /gitlab-templates/craft-windows-x86-64-qt6.yml
#- /gitlab-templates/craft-windows-appx-qt6.yml

View File

@@ -10,6 +10,7 @@ Dependencies:
'frameworks/ki18n': '@latest-kf6'
'frameworks/kconfig': '@latest-kf6'
'frameworks/syntax-highlighting': '@latest-kf6'
'frameworks/kiconthemes': '@latest-kf6'
'frameworks/kitemmodels': '@latest-kf6'
'frameworks/kquickcharts': '@latest-kf6'
'frameworks/knotifications': '@latest-kf6'
@@ -28,15 +29,11 @@ Dependencies:
'frameworks/kio': '@latest-kf6'
'frameworks/kwindowsystem': '@latest-kf6'
'frameworks/kstatusnotifieritem': '@latest-kf6'
- 'on': ['Linux', 'FreeBSD', 'Android']
'require':
'libraries/kunifiedpush': '@latest-kf6'
- 'on': ['Linux', 'FreeBSD']
'require':
'frameworks/kdbusaddons': '@latest-kf6'
'frameworks/purpose': '@latest-kf6'
'libraries/kunifiedpush': '@latest-kf6'
- 'on': ['Linux']
'require':

View File

@@ -65,7 +65,7 @@ if (QT_KNOWN_POLICY_QTP0004)
qt_policy(SET QTP0004 NEW)
endif ()
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme IconThemes)
set_package_properties(KF6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
@@ -148,7 +148,7 @@ set_package_properties(KF6DocTools PROPERTIES DESCRIPTION
option(WITH_UNIFIEDPUSH "Build with KUnifiedPush support" ON)
if (APPLE OR WIN32 OR HAIKU)
if (ANDROID OR APPLE OR WIN32 OR HAIKU)
set(WITH_UNIFIEDPUSH OFF)
endif()

View File

@@ -63,7 +63,7 @@ void ActionsTest::testActions_data()
QTest::addColumn<std::optional<QString>>("resultText");
QTest::addColumn<std::optional<Quotient::RoomMessageEvent::MsgType>>("type");
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\_(ツ)_/¯ Hello"_s)
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\\\_(ツ)\\_/¯ Hello"_s)
<< std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
QTest::newRow("lenny") << u"/lenny Hello"_s << std::make_optional(u"( ͡° ͜ʖ ͡°) Hello"_s) << std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
QTest::newRow("tableflip") << u"/tableflip Hello"_s << std::make_optional(u"(╯°□°)╯︵ ┻━┻ Hello"_s)

View File

@@ -487,6 +487,7 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="25.12.1" date="2026-01-08"/>
<release version="25.12.0" date="2025-12-11"/>
<release version="25.08.3" date="2025-11-06"/>
<release version="25.08.2" date="2025-10-09"/>

View File

@@ -108,6 +108,7 @@ Comment[ia]=Conversation en ditecto sur Matrix
Comment[it]= su Matrix
Comment[ka]=ჩატი Matrix-ზე
Comment[ko]=Matrix에서 대화하기
Comment[lt]=Pokalbiai per Matrix
Comment[lv]=Tērzējiet „Matrix“ tīklā
Comment[nl]=Chat op Matrix
Comment[pl]=Rozmawiaj na Matriksie

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

7082
po/ga/neochat.po Normal file

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

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

@@ -22,7 +22,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
>carl@carlschwan.eu</email
></author>
<date
>2022-11-01</date>
>20221101</date>
<releaseinfo
>22.09</releaseinfo>
<productname
@@ -111,9 +111,9 @@ SPDX-License-Identifier: CC-BY-SA-4.0
><title
>Telif Hakkı</title>
<para
>Telif hakkı &copy; 2020-2022 Tobias Fella </para>
>Telif hakkı &copy; 20202022 Tobias Fella </para>
<para
>Telif hakkı &copy; 2020-2022 Carl Schwan </para>
>Telif hakkı &copy; 20202022 Carl Schwan </para>
<para
>Lisans: GNU Genel Kamu Lisansa, 3. sürüm veya sonrası &lt;<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
>https://www.gnu.org/licenses/gpl-3.0.html</ulink

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

@@ -10,7 +10,7 @@ confinement: strict
apps:
neochat:
extensions:
- kde-neon-6
- kde-neon-6@latest/beta
command: usr/bin/neochat
common-id: org.kde.neochat
desktop: usr/share/applications/org.kde.neochat.desktop
@@ -25,7 +25,7 @@ apps:
- password-manager-service
- accounts-service
environment:
QT_PLUGIN_PATH: "$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET/plugins/snap/kf6-core24/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/plugins"
QT_PLUGIN_PATH: "$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET/plugins:/snap/kf6-core24/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/plugins"
QML_IMPORT_PATH: "$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/qml:/snap/kf6-core24/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/qml"
compression: lzo
@@ -55,53 +55,11 @@ parts:
- -usr/lib/*/pkgconfig
- -usr/lib/*/cmake
libsecret:
source: https://gitlab.gnome.org/GNOME/libsecret.git
source-tag: '0.21.4'
source-depth: 1
plugin: meson
meson-parameters:
- --prefix=/usr
- -Doptimization=3
- -Ddebug=true
- -Dmanpage=false
- -Dvapi=false
- -Dintrospection=false
- -Dcrypto=disabled
- -Dgtk_doc=false
build-packages:
- meson
- libglib2.0-dev
- libgcrypt20-dev
prime:
- -usr/include
- -usr/lib/*/pkgconfig
qtkeychain:
after: [libsecret]
source: https://github.com/frankosterfeld/qtkeychain.git
source-tag: 0.14.3
source-depth: 1
plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PKG_CONFIG_PATH: "$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET/pkgconfig:$PKG_CONFIG_PATH"
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release
- -DBUILD_TRANSLATIONS=NO
- -DBUILD_WITH_QT6=ON
prime:
- -usr/include
- -usr/lib/*/pkgconfig
- -usr/lib/*/cmake
libquotient:
after:
- olm
- qtkeychain
source: https://github.com/quotient-im/libQuotient.git
source-tag: 0.9.2
source-branch: 0.9.5
source-depth: 1
plugin: cmake
build-environment:
@@ -110,12 +68,16 @@ parts:
- cmake
build-packages:
- libssl-dev
- curl
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release
- -DBUILD_TESTING=OFF
- -DQuotient_ENABLE_E2EE=ON
- -DBUILD_WITH_QT6=ON
override-pull: |
craftctl default
curl https://github.com/quotient-im/libQuotient/commit/ea83157eed37ff97ab275a5d14c971f0a5a70595.diff | patch -p1
prime:
- -usr/include
- -usr/lib/*/pkgconfig
@@ -123,38 +85,39 @@ parts:
kquickimageeditor:
source: https://invent.kde.org/libraries/kquickimageeditor.git
source-tag: 'v0.3.0'
source-tag: 'v0.6.0'
source-depth: 1
plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
build-environment: &build-environment
- LD_LIBRARY_PATH: "/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:/snap/kde-qt6-core24-sdk/current/usr/lib:/snap/kf6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kf6-core24-sdk/current/usr/lib:$CRAFT_STAGE/usr/lib:$CRAFT_STAGE/lib/:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
- PATH: /snap/kde-qt6-core24-sdk/current/usr/bin${PATH:+:$PATH}
- PKG_CONFIG_PATH: /snap/kde-qt6-core24-sdk/current/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
cmake-parameters:
- XDG_DATA_DIRS: $CRAFT_STAGE/usr/share:/snap/kde-qt6-core24-sdk/current/usr/share:/usr/share${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}
- XDG_CONFIG_HOME: $CRAFT_STAGE/etc/xdg:/snap/kde-qt6-core24-sdk/current/etc/xdg:/etc/xdg${XDG_CONFIG_HOME:+:$XDG_CONFIG_HOME}
cmake-parameters: &cmake-parameters
- -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release
- -DQT_MAJOR_VERSION=6
- -DBUILD_WITH_QT6=ON
- -DBUILD_TESTING=OFF
prime:
- "-DCMAKE_FIND_ROOT_PATH=$CRAFT_STAGE\\;/snap/kde-qt6-core24-sdk/current\\;/snap/kf6-core24-sdk/current/usr"
- "-DCMAKE_PREFIX_PATH=$CRAFT_STAGE\\;/snap/kde-qt6-core24-sdk/current\\;/snap/kf6-core24-sdk/current/usr"
prime: &prime
- -usr/include
- -usr/lib/*/pkgconfig
- -usr/lib/*/cmake
kunifiedpush:
source: https://invent.kde.org/libraries/kunifiedpush.git
source-branch: release/24.12
plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release
- -DBUILD_TESTING=OFF
build-environment: *build-environment
cmake-parameters: *cmake-parameters
prime: *prime
neochat:
after:
- qtkeychain
- libquotient
- kquickimageeditor
- kunifiedpush
@@ -162,24 +125,20 @@ parts:
- usr/share/metainfo/org.kde.neochat.appdata.xml
source: .
plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
build-environment: *build-environment
build-packages:
- cmark
- docbook-xml
- docbook-xsl
- duktape-dev
- libcmark-dev
- libsqlite3-dev
- libvulkan-dev
- libxkbcommon-dev
- libicu-dev
- libpulse0
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release
- -DBUILD_TESTING=OFF
prime:
- -usr/share/man
cmake-parameters: *cmake-parameters
prime: *prime
deps:
after: [neochat]
@@ -198,3 +157,14 @@ parts:
${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404
prime:
- bin/gpu-2404-wrapper
cleanup:
after:
- neochat
plugin: nil
override-prime: |
set -eux
for snap in "core24" "kf6-core24"; do
cd "/snap/$snap/current" && find . -type f,l -exec rm -rf "${CRAFT_PRIME}/{}" \;
done

View File

@@ -3,7 +3,7 @@
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
add_library(neochat STATIC
qt_add_library(neochat STATIC
controller.cpp
controller.h
roommanager.cpp
@@ -35,6 +35,8 @@ add_library(neochat STATIC
models/commonroomsmodel.h
texttospeechhelper.h
texttospeechhelper.cpp
models/limitermodel.cpp
models/limitermodel.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
@@ -140,10 +142,17 @@ if(WIN32)
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
endif()
add_executable(neochat-app
qt_add_executable(neochat-app
main.cpp
)
if(ANDROID)
set_target_properties(neochat-app PROPERTIES
OUTPUT_NAME "neochat-app"
PREFIX "lib"
)
endif()
if(TARGET Qt::WebView)
target_link_libraries(neochat-app PUBLIC Qt::WebView)
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
@@ -153,6 +162,7 @@ target_include_directories(neochat-app PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(neochat-app PRIVATE
neochat
KF6::IconThemes
)
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
@@ -183,7 +193,7 @@ else()
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PRIVATE neochatplugin Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PUBLIC
LibNeoChat
Timeline

View File

@@ -33,6 +33,7 @@
#include <KWindowSystem>
#endif
#include <KIconTheme>
#include <KLocalizedQmlContext>
#include <KLocalizedString>
#include <KirigamiApp>
@@ -102,6 +103,10 @@ int main(int argc, char *argv[])
{
QNetworkProxyFactory::setUseSystemConfiguration(true);
// We currently need to do this ourselves,
// KirigamiApp currently called this after constructing the app which breaks icons on Windows.
KIconTheme::initTheme();
#ifdef HAVE_WEBVIEW
QtWebView::initialize();
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);

View File

@@ -5,6 +5,7 @@
#include "jobs/neochatgetcommonroomsjob.h"
#include <QGuiApplication>
#include <Quotient/room.h>
using namespace Quotient;
@@ -39,8 +40,22 @@ void CommonRoomsModel::setUserId(const QString &userId)
QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const
{
Q_UNUSED(index)
Q_UNUSED(roleName)
auto roomId = m_commonRooms[index.row()];
auto room = connection()->room(roomId);
if (!room) {
return {};
}
switch (roleName) {
case Qt::DisplayRole:
case RoomNameRole:
return room->displayName();
case RoomAvatarRole:
return room->avatarUrl();
case RoomIdRole:
return roomId;
}
return {};
}
@@ -50,6 +65,15 @@ int CommonRoomsModel::rowCount(const QModelIndex &parent) const
return m_commonRooms.size();
}
QHash<int, QByteArray> CommonRoomsModel::roleNames() const
{
return {
{RoomIdRole, "roomId"},
{RoomNameRole, "roomName"},
{RoomAvatarRole, "roomAvatar"},
};
}
void CommonRoomsModel::reload()
{
if (!m_connection || m_userId.isEmpty()) {

View File

@@ -24,7 +24,9 @@ class CommonRoomsModel : public QAbstractListModel
public:
enum Roles {
RoomIdRole = Qt::DisplayRole,
RoomIdRole = Qt::UserRole,
RoomNameRole,
RoomAvatarRole,
};
Q_ENUM(Roles)
@@ -39,6 +41,8 @@ public:
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
[[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override;
QHash<int, QByteArray> roleNames() const override;
Q_SIGNALS:
void connectionChanged();
void userIdChanged();

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "models/limitermodel.h"
LimiterModel::LimiterModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
connect(this, &QSortFilterProxyModel::rowsInserted, this, &LimiterModel::extraCountChanged);
connect(this, &QSortFilterProxyModel::rowsRemoved, this, &LimiterModel::extraCountChanged);
connect(this, &QSortFilterProxyModel::modelReset, this, &LimiterModel::extraCountChanged);
}
int LimiterModel::maximumCount() const
{
return m_maximumCount;
}
void LimiterModel::setMaximumCount(int maximumCount)
{
if (m_maximumCount != maximumCount) {
m_maximumCount = maximumCount;
Q_EMIT maximumCountChanged();
}
}
int LimiterModel::extraCount() const
{
if (sourceModel()) {
return std::max(sourceModel()->rowCount() - maximumCount(), 0);
}
return 0;
}
bool LimiterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent)
return source_row < maximumCount();
}
#include "moc_limitermodel.cpp"

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QQmlEngine>
#include <QSortFilterProxyModel>
/**
* @class LimiterModel
*
* @brief Takes a source QAbstractItemModel model and only displays a desired maximum amount.
*
* Also gives you the remaining (filtered out) items, useful for sticking in a label or somesuch.
*/
class LimiterModel : public QSortFilterProxyModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int maximumCount READ maximumCount WRITE setMaximumCount NOTIFY maximumCountChanged)
Q_PROPERTY(int extraCount READ extraCount NOTIFY extraCountChanged)
public:
explicit LimiterModel(QObject *parent = nullptr);
[[nodiscard]] int maximumCount() const;
void setMaximumCount(int maximumCount);
[[nodiscard]] int extraCount() const;
Q_SIGNALS:
void maximumCountChanged();
void extraCountChanged();
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
private:
int m_maximumCount = 0;
};

View File

@@ -287,6 +287,7 @@ Name[ia]=Comparti
Name[it]=Condivisione
Name[ka]=გაზიარება
Name[ko]=공유
Name[lt]=Bendrinti
Name[lv]=Kopīgot
Name[nl]=Gedeelde
Name[nn]=Del
@@ -322,6 +323,7 @@ Comment[ia]=Le exito de compartir un pecietta de contento
Comment[it]=Il risultato della condivisione di un contenuto
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
Comment[ko]=콘텐츠 공유 결과
Comment[lt]=Turinio dalies bendrinimo rezultatas
Comment[lv]=Satura kopīgošanas rezultāts
Comment[nl]=Het resultaat van het delen van een stukje inhoud
Comment[nn]=Resultatet av deling av innhald

View File

@@ -216,12 +216,12 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
}
});
notification->setTitle(room->displayName());
QString entry;
if (sender == room->displayName()) {
notification->setTitle(sender);
if (room->isDirectChat()) {
entry = text.toHtmlEscaped();
} else {
notification->setTitle(room->displayName());
entry = i18n("%1: %2", sender, text.toHtmlEscaped());
}
@@ -253,7 +253,9 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
notification->setReplyAction(std::move(replyAction));
}
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
if (Controller::instance().accounts()->rowCount() > 1) {
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
}
notification->sendEvent();
}
@@ -347,7 +349,9 @@ void NotificationsManager::doPostInviteNotification(QPointer<NeoChatRoom> room)
m_invitations.remove(room->id());
});
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
if (Controller::instance().accounts()->rowCount() > 1) {
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
}
notification->sendEvent();
}

View File

@@ -45,14 +45,12 @@ Labs.MenuBar {
}
Labs.MenuItem {
icon.name: "compass-symbolic"
text: i18nc("@action:inmenu", "Explore Rooms")
text: i18nc("@action:inmenu Explore public rooms and spaces", "Explore")
enabled: root.connection
onTriggered: {
let dialog = root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Explore Rooms")
});
}, {});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});

View File

@@ -27,16 +27,17 @@ ColumnLayout {
Layout.fillHeight: true
}
KirigamiComponents.Avatar {
KirigamiComponents.AvatarButton {
id: avatar
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
name: root.invitingMember.displayName
source: NeoChatConfig.hideImages ? undefined : root.invitingMember.avatarUrl
color: root.invitingMember.color
onClicked: RoomManager.resolveResource(root.currentRoom.invitingUserId)
}
Loader {
@@ -138,8 +139,24 @@ ColumnLayout {
Layout.fillWidth: true
FormCard.FormButtonDelegate {
id: viewProfileDelegate
icon.name: "user-properties-symbolic"
text: i18nc("@action:button View this user's profile", "View %1's Profile", root.invitingMember.displayName)
onClicked: RoomManager.resolveResource(root.currentRoom.invitingUserId)
}
FormCard.FormDelegateSeparator {
above: viewProfileDelegate
below: ignoreUserDelegate
}
FormCard.FormButtonDelegate {
id: ignoreUserDelegate
icon.name: "list-remove-symbolic"
text: i18nc("@action:button Block the user", "Block %1", root.invitingMember.displayName)
text: i18nc("@action:button Ignore the user", "Ignore %1 and Reject Invite", root.invitingMember.displayName)
onClicked: {
root.currentRoom.forget()

View File

@@ -23,72 +23,63 @@ Kirigami.Page {
name: "cancelled"
when: root.session.state === KeyVerificationSession.CANCELED
PropertyChanges {
target: stateLoader
sourceComponent: verificationCanceled
stateLoader.sourceComponent: verificationCanceled
}
},
State {
name: "waitingForVerification"
when: root.session.state === KeyVerificationSession.WAITINGFORVERIFICATION
PropertyChanges {
target: stateLoader
sourceComponent: emojiSas
stateLoader.sourceComponent: emojiSas
}
},
State {
name: "waitingForReady"
when: root.session.state === KeyVerificationSession.WAITINGFORREADY
PropertyChanges {
target: stateLoader
sourceComponent: message
stateLoader.sourceComponent: message
}
},
State {
name: "incoming"
when: root.session.state === KeyVerificationSession.INCOMING
PropertyChanges {
target: stateLoader
sourceComponent: message
stateLoader.sourceComponent: message
}
},
State {
name: "waitingForKey"
when: root.session.state === KeyVerificationSession.WAITINGFORKEY
PropertyChanges {
target: stateLoader
sourceComponent: message
stateLoader.sourceComponent: message
}
},
State {
name: "waitingForAccept"
when: root.session.state === KeyVerificationSession.WAITINGFORACCEPT
PropertyChanges {
target: stateLoader
sourceComponent: message
stateLoader.sourceComponent: message
}
},
State {
name: "waitingForMac"
when: root.session.state === KeyVerificationSession.WAITINGFORMAC
PropertyChanges {
target: stateLoader
sourceComponent: message
stateLoader.sourceComponent: message
}
},
State {
name: "ready"
when: root.session.state === KeyVerificationSession.READY
PropertyChanges {
target: stateLoader
sourceComponent: chooseVerificationComponent
stateLoader.sourceComponent: chooseVerificationComponent
}
},
State {
name: "done"
when: root.session.state === KeyVerificationSession.DONE
PropertyChanges {
target: stateLoader
sourceComponent: message
stateLoader.sourceComponent: message
}
}
]

View File

@@ -30,15 +30,13 @@ Kirigami.SearchDialog {
emptyText: i18nc("Placeholder message", "No room found")
Kirigami.Action {
id: exploreRoomAction
text: i18nc("@action:button", "Explore rooms")
text: i18nc("@action:button Explore public rooms and spaces", "Explore")
icon.name: "compass"
onTriggered: {
root.close()
let dialog = root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Explore Rooms")
});
}, {});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});

View File

@@ -20,37 +20,62 @@ Kirigami.Dialog {
// Make sure that code is prepared to deal with this property being null
property NeoChatRoom room
property var user
// Used for the "View Main Profile" feature so you can toggle back to the room profile.
property NeoChatRoom oldRoom
property NeoChatConnection connection
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
property CommonRoomsModel model: CommonRoomsModel {
connection: root.connection
userId: root.user.id
}
property LimiterModel limiterModel: LimiterModel {
maximumCount: 5
sourceModel: root.model
}
readonly property bool isSelf: root.user.id === root.connection.localUserId
readonly property bool hasMutualRooms: root.model.count > 0
readonly property bool isRoomProfile: root.room
readonly property string shareUrl: "https://matrix.to/#/" + root.user.id
leftPadding: Kirigami.Units.largeSpacing * 2
rightPadding: Kirigami.Units.largeSpacing * 2
topPadding: Kirigami.Units.largeSpacing * 2
bottomPadding: Kirigami.Units.largeSpacing * 2
standardButtons: Kirigami.Dialog.NoButton
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
title: i18nc("@title:menu Account details dialog", "Account Details")
header: null
contentItem: ColumnLayout {
spacing: 0
RowLayout {
id: detailRow
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
Layout.fillWidth: true
KirigamiComponents.Avatar {
id: avatar
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
name: root.room ? root.room.member(root.user.id).displayName : root.user.displayName
source: root.room ? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl
source: {
if (root.room) {
return root.room.member(root.user.id).avatarUrl;
} else if(root.user.avatarUrl.toString() !== '') {
return root.connection.makeMediaUrl(root.user.avatarUrl);
}
return "";
}
color: root.room ? root.room.member(root.user.id).color : QmlUtils.getUserColor(root.user.hueF)
}
@@ -75,242 +100,305 @@ Kirigami.Dialog {
id: idLabel
textFormat: TextEdit.PlainText
text: idLabelTextMetrics.elidedText
color: Kirigami.Theme.disabledTextColor
TextMetrics {
id: idLabelTextMetrics
text: root.user.id
elide: Qt.ElideRight
elideWidth: root.availableWidth - avatar.width - qrButton.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin
elideWidth: root.availableWidth - avatar.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin
}
}
QQC2.Label {
property CommonRoomsModel model: CommonRoomsModel {
connection: root.connection
userId: root.user.id
}
text: i18ncp("@info", "One mutual room", "%1 mutual rooms", model.count)
color: Kirigami.Theme.disabledTextColor
visible: model.count > 0
Kirigami.ActionToolBar {
Layout.topMargin: Kirigami.Units.smallSpacing
actions: [
Kirigami.Action {
text: i18nc("@action:intoolbar Message this user directly", "Message")
icon.name: "document-send-symbolic"
onTriggered: {
root.close();
root.connection.requestDirectChat(root.user.id);
}
},
Kirigami.Action {
icon.name: "im-invisible-user-symbolic"
text: root.connection.isIgnored(root.user.id) ? i18nc("@action:intoolbar Unignore or 'unblock' this user", "Unignore") : i18nc("@action:intoolbar Ignore or 'block' this user", "Ignore")
onTriggered: {
root.close();
root.connection.isIgnored(root.user.id) ? root.connection.removeFromIgnoredUsers(root.user.id) : root.connection.addToIgnoredUsers(root.user.id);
}
},
Kirigami.Action {
text: i18nc("@action:intoolbar Copy shareable link for this user", "Copy Link")
icon.name: "username-copy-symbolic"
onTriggered: Clipboard.saveText(root.shareUrl)
},
Kirigami.Action {
text: i18nc("@action:intoolbar Search for this user's messages.", "Search Messages…")
icon.name: "search-symbolic"
onTriggered: {
((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
room: root.room,
senderId: root.user.id
}, {
title: i18nc("@action:title", "Search")
});
root.close();
}
},
Kirigami.Action {
text: i18nc("@action:intoolbar", "Show QR Code")
icon.name: "view-barcode-qr-symbolic"
onTriggered: {
let qrCode = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
text: root.shareUrl,
title: root.room ? root.room.member(root.user.id).displayName : root.user.displayName,
subtitle: root.user.id,
avatarColor: root.room?.member(root.user.id).color,
avatarSource: root.room? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl
}) as QrCodeMaximizeComponent;
root.close();
qrCode.open();
}
},
Kirigami.Action {
text: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report…")
icon.name: "dialog-warning-symbolic"
visible: root.connection.supportsMatrixSpecVersion("v1.13")
onTriggered: {
let dialog = ((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Report User"),
placeholder: i18nc("@info:placeholder", "Reason for reporting this user"),
icon: "dialog-warning-symbolic",
actionText: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report")
}, {
title: i18nc("@title", "Report User"),
width: Kirigami.Units.gridUnit * 25
}) as ReasonDialog;
dialog.accepted.connect(reason => {
root.connection.reportUser(root.user.id, reason);
});
}
},
Kirigami.Action {
visible: root.room
text: i18nc("@action:button", "View Main Profile")
icon.name: "user-properties-symbolic"
onTriggered: {
root.oldRoom = root.room;
root.room = null;
}
},
Kirigami.Action {
visible: !root.room && root.oldRoom
text: i18nc("@action:button", "View Room Profile")
icon.name: "user-properties-symbolic"
onTriggered: {
root.room = root.oldRoom;
root.oldRoom = null;
}
}
]
}
}
QQC2.AbstractButton {
id: qrButton
Layout.minimumHeight: avatar.height * 0.75
Layout.maximumHeight: avatar.height * 1.5
Layout.maximumWidth: avatar.height * 1.5
}
contentItem: Barcode {
id: barcode
barcodeType: Barcode.QRCode
content: "https://matrix.to/#/" + root.user.id
}
Kirigami.Heading {
text: i18nc("@title Moderation actions for this user", "Moderation")
level: 2
visible: root.isRoomProfile && moderationToolbar.actions.filter(function (it) { return it.visible; }).length > 0
onClicked: {
let qrCode = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
text: barcode.content,
title: root.room ? root.room.member(root.user.id).displayName : root.user.displayName,
subtitle: root.user.id,
avatarColor: root.room?.member(root.user.id).color,
avatarSource: root.room? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl
}) as QrCodeMaximizeComponent;
root.close();
qrCode.open();
Layout.topMargin: Kirigami.Units.largeSpacing
}
Kirigami.ActionToolBar {
id: moderationToolbar
flat: false
visible: root.isRoomProfile
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
actions: [
Kirigami.Action {
visible: !root.isSelf && root.room.canSendState("kick") && root.room.containsUser(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId)
text: i18nc("@action:button Kick the user from the room", "Kick…")
icon.name: "im-kick-user"
onTriggered: {
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Kick User"),
placeholder: i18nc("@info:placeholder", "Reason for kicking this user"),
actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"),
icon: "im-kick-user"
}, {
title: i18nc("@title:dialog", "Kick User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.kickMember(root.user.id, reason);
});
root.close();
}
},
Kirigami.Action {
visible: !root.isSelf && root.room.canSendState("ban") && !root.room.isUserBanned(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId)
text: i18nc("@action:button Ban this user from the room", "Ban…")
icon.name: "im-ban-user"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Ban User"),
placeholder: i18nc("@info:placeholder", "Reason for banning this user"),
actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"),
icon: "im-ban-user"
}, {
title: i18nc("@title:dialog", "Ban User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.ban(root.user.id, reason);
});
root.close();
}
},
Kirigami.Action {
visible: !root.isSelf && root.room.canSendState("ban") && root.room.isUserBanned(root.user.id)
text: i18nc("@action:button Unban the user from this room", "Unban")
icon.name: "im-irc"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
root.room.unban(root.user.id);
root.close();
}
},
Kirigami.Action {
visible: (root.user.id === root.connection.localUserId || root.room.canSendState("redact"))
text: i18nc("@action:button Remove messages from the user in this room", "Remove Messages…")
icon.name: "delete"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
let dialog = ((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Remove Messages"),
placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"),
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
icon: "delete"
}, {
title: i18nc("@title", "Remove Messages"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.deleteMessagesByUser(root.user.id, reason);
});
root.close();
}
}
]
}
Kirigami.Heading {
text: i18nc("@title Role such as 'Admin' or 'Moderator' for this user", "Role")
level: 2
visible: root.isRoomProfile
Layout.topMargin: Kirigami.Units.largeSpacing
}
RowLayout {
spacing: Kirigami.Units.smallSpacing
visible: root.isRoomProfile
Layout.topMargin: Kirigami.Units.smallSpacing
QQC2.Label {
text: root.room ? QmlUtils.nameForPowerLevelValue(root.room.memberEffectivePowerLevel(root.user.id)) : ""
}
QQC2.Button {
visible: root.room.canSendState("m.room.power_levels") && !(root.room.roomCreatorHasUltimatePowerLevel() && root.room.isCreator(root.user.id))
text: i18nc("@action:button Set the power level (such as 'Admin') for this user", "Set Power Level")
icon.name: "document-edit-symbolic"
display: QQC2.AbstractButton.IconOnly
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: barcode.content
QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
Kirigami.Chip {
visible: root.room
text: root.room ? QmlUtils.nameForPowerLevelValue(root.room.memberEffectivePowerLevel(root.user.id)) : ""
closable: false
checkable: false
onClicked: {
(powerLevelDialog.createObject(this, {
room: root.room,
userId: root.user.id,
powerLevel: root.room.memberEffectivePowerLevel(root.user.id)
}) as PowerLevelDialog).open();
root.close();
}
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
}
Kirigami.Separator {
Layout.fillWidth: true
}
FormCard.FormButtonDelegate {
visible: root.user.id !== root.connection.localUserId && !!root.user
text: !!root.user && root.connection.isIgnored(root.user.id) ? i18n("Unignore this user") : i18n("Ignore this user")
icon.name: "im-invisible-user"
onClicked: {
root.close();
root.connection.isIgnored(root.user.id) ? root.connection.removeFromIgnoredUsers(root.user.id) : root.connection.addToIgnoredUsers(root.user.id);
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("kick") && root.room.containsUser(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId)
text: i18nc("@action:button", "Kick this user")
icon.name: "im-kick-user"
onClicked: {
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Kick User"),
placeholder: i18nc("@info:placeholder", "Reason for kicking this user"),
actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"),
icon: "im-kick-user"
}, {
title: i18nc("@title:dialog", "Kick User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.kickMember(root.user.id, reason);
});
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("invite") && !root.room.containsUser(root.user.id)
enabled: root.room && !root.room.isUserBanned(root.user.id)
text: i18nc("@action:button", "Invite this user")
icon.name: "list-add-user"
onClicked: {
root.room.inviteToRoom(root.user.id);
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("ban") && !root.room.isUserBanned(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId)
text: i18nc("@action:button", "Ban this user")
icon.name: "im-ban-user"
icon.color: Kirigami.Theme.negativeTextColor
onClicked: {
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Ban User"),
placeholder: i18nc("@info:placeholder", "Reason for banning this user"),
actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"),
icon: "im-ban-user"
}, {
title: i18nc("@title:dialog", "Ban User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.ban(root.user.id, reason);
});
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("ban") && root.room.isUserBanned(root.user.id)
text: i18nc("@action:button", "Unban this user")
icon.name: "im-irc"
icon.color: Kirigami.Theme.negativeTextColor
onClicked: {
root.room.unban(root.user.id);
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.room.canSendState("m.room.power_levels")
text: i18nc("@action:button", "Set user power level")
icon.name: "visibility"
onClicked: {
(powerLevelDialog.createObject(this, {
room: root.room,
userId: root.user.id,
powerLevel: root.room.memberEffectivePowerLevel(root.user.id)
}) as PowerLevelDialog).open();
root.close();
}
Component {
id: powerLevelDialog
PowerLevelDialog {
Component {
id: powerLevelDialog
PowerLevelDialog {
id: powerLevelDialog
}
}
}
}
FormCard.FormButtonDelegate {
visible: root.room && (root.user.id === root.connection.localUserId || root.room.canSendState("redact"))
Kirigami.Heading {
text: i18nc("@title The set of common rooms between your current user and the one shown", "Mutual Rooms")
level: 4
visible: !root.isSelf && root.hasMutualRooms
text: i18nc("@action:button", "Remove recent messages by this user")
icon.name: "delete"
icon.color: Kirigami.Theme.negativeTextColor
onClicked: {
let dialog = ((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Remove Messages"),
placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"),
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
icon: "delete"
}, {
title: i18nc("@title", "Remove Messages"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.deleteMessagesByUser(root.user.id, reason);
});
root.close();
Layout.topMargin: Kirigami.Units.largeSpacing
}
RowLayout {
spacing: Kirigami.Units.smallSpacing
visible: !root.isSelf && root.hasMutualRooms
Layout.topMargin: Kirigami.Units.smallSpacing
Repeater {
model: root.limiterModel
delegate: KirigamiComponents.AvatarButton {
required property string roomName
required property string roomAvatar
required property string roomId
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
name: roomName
source: roomAvatar
onClicked: {
root.close();
RoomManager.resolveResource(roomId);
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: name
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
}
FormCard.FormButtonDelegate {
visible: root.user.id !== root.connection.localUserId
text: root.connection.directChatExists(root.user) ? i18nc("%1 is the name of the user.", "Chat with %1", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName)) : i18n("Invite to private chat")
icon.name: "document-send"
onClicked: {
root.connection.requestDirectChat(root.user.id);
root.close();
}
}
FormCard.FormButtonDelegate {
text: i18nc("@action:button %1 is the name of the user.", "Search room for %1's messages", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName))
icon.name: "search-symbolic"
onClicked: {
((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
room: root.room,
senderId: root.user.id
}, {
title: i18nc("@action:title", "Search")
});
root.close();
}
}
FormCard.FormButtonDelegate {
text: i18n("Copy link")
icon.name: "username-copy"
onClicked: Clipboard.saveText("https://matrix.to/#/" + root.user.id)
}
FormCard.FormButtonDelegate {
text: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report…")
icon.name: "dialog-warning-symbolic"
visible: root.connection.supportsMatrixSpecVersion("v1.13")
onClicked: {
let dialog = ((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Report User"),
placeholder: i18nc("@info:placeholder", "Reason for reporting this user"),
icon: "dialog-warning-symbolic",
actionText: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report")
}, {
title: i18nc("@title", "Report User"),
width: Kirigami.Units.gridUnit * 25
}) as ReasonDialog;
dialog.accepted.connect(reason => {
root.connection.reportUser(root.user.id, reason);
});
QQC2.Label {
text: i18ncp("@info:label And '%1' more rooms you have in common with this user, but are not shown", "and 1 more…", "and %1 more…", root.limiterModel.extraCount)
visible: root.limiterModel.extraCount > 0
color: Kirigami.Theme.disabledTextColor
}
}
}

View File

@@ -554,6 +554,45 @@ void RoomManager::setCurrentSpace(const QString &spaceId, bool setRoom)
setCurrentRoom({});
}
QString RoomManager::findSpaceIdForCurrentRoom() const
{
if (!m_currentRoom) {
return m_currentSpaceId;
}
if (m_currentRoom->isDirectChat()) {
const auto roomsInSpace = SpaceHierarchyCache::instance().getRoomListForSpace(m_currentSpaceId, false);
if (roomsInSpace.contains(m_currentRoom->id())) {
return m_currentSpaceId;
}
return "DM"_L1;
}
const auto &parentSpaces = SpaceHierarchyCache::instance().parentSpaces(m_currentRoom->id());
if (parentSpaces.contains(m_currentSpaceId)) {
return m_currentSpaceId;
}
static auto config = NeoChatConfig::self();
if (config->allRoomsInHome()) {
return {};
}
if (const auto &parent = m_connection->room(m_currentRoom->canonicalParent())) {
for (const auto &parentParent : SpaceHierarchyCache::instance().parentSpaces(parent->id())) {
if (SpaceHierarchyCache::instance().parentSpaces(parentParent).isEmpty()) {
return parentParent;
}
}
return parent->id();
}
for (const auto &space : parentSpaces) {
if (SpaceHierarchyCache::instance().parentSpaces(space).isEmpty()) {
return space;
}
}
if (m_currentRoom->isSpace()) {
return m_currentSpaceId;
}
return {};
}
void RoomManager::setCurrentRoom(const QString &roomId)
{
if (m_currentRoom != nullptr) {
@@ -571,57 +610,23 @@ void RoomManager::setCurrentRoom(const QString &roomId)
}
Q_EMIT currentRoomChanged();
if (m_connection) {
if (roomId.isEmpty()) {
m_lastRoomConfig.deleteEntry(m_currentSpaceId);
} else {
// We can't have empty keys in KConfig, so name it "Home"
if (m_currentSpaceId.isEmpty()) {
m_lastRoomConfig.writeEntry(u"Home"_s, roomId);
} else {
m_lastRoomConfig.writeEntry(m_currentSpaceId, roomId);
}
}
}
if (roomId.isEmpty()) {
m_lastRoomConfig.deleteEntry(m_currentSpaceId);
return;
}
if (m_currentRoom->isSpace()) {
return;
const auto spaceIdForRoom = findSpaceIdForCurrentRoom();
// We can't have empty keys in KConfig, so name it "Home"
if (spaceIdForRoom.isEmpty()) {
m_lastRoomConfig.writeEntry(u"Home"_s, roomId);
} else {
m_lastRoomConfig.writeEntry(spaceIdForRoom, roomId);
}
if (m_currentRoom->isDirectChat()) {
const auto roomsInSpace = SpaceHierarchyCache::instance().getRoomListForSpace(m_currentSpaceId, false);
if (!roomsInSpace.contains(m_currentRoom->id()) && m_currentSpaceId != "DM"_L1) {
setCurrentSpace("DM"_L1, false);
}
return;
if (m_currentSpaceId != spaceIdForRoom) {
setCurrentSpace(spaceIdForRoom, false);
}
const auto &parentSpaces = SpaceHierarchyCache::instance().parentSpaces(roomId);
if (parentSpaces.contains(m_currentSpaceId)) {
return;
}
static auto config = NeoChatConfig::self();
if (config->allRoomsInHome()) {
setCurrentSpace({}, false);
return;
}
if (const auto &parent = m_connection->room(m_currentRoom->canonicalParent())) {
for (const auto &parentParent : SpaceHierarchyCache::instance().parentSpaces(parent->id())) {
if (SpaceHierarchyCache::instance().parentSpaces(parentParent).isEmpty()) {
setCurrentSpace(parentParent, false);
return;
}
}
setCurrentSpace(parent->id(), false);
return;
}
for (const auto &space : parentSpaces) {
if (SpaceHierarchyCache::instance().parentSpaces(space).isEmpty()) {
setCurrentSpace(space, false);
return;
}
}
setCurrentSpace({}, false);
}
void RoomManager::clearCurrentRoom()

View File

@@ -373,6 +373,15 @@ private:
void setCurrentRoom(const QString &roomId);
/**
* @brief Find the most appropriate space for the currently selected room
*
* Should be used to figure out what space to switch to after a room change.
*
* @return The Space ID that the currently set room should be displayed as part of. (or "DM" for DM and "" for Home)
*/
QString findSpaceIdForCurrentRoom() const;
// Space ID, "DM", or empty string
void setCurrentSpace(const QString &spaceId, bool setRoom = true);

View File

@@ -148,7 +148,7 @@ ColumnLayout {
id: quickReactions
Layout.fillWidth: true
model: ["👍", "👎", "😄", "🎉", "😕", "❤", "🚀", "👀"]
model: ["👍", "👎", "😄", "🎉", "😕", "❤", "🚀", "👀"]
delegate: EmojiDelegate {
required property string modelData

View File

@@ -12,6 +12,10 @@ QString PowerLevel::nameForLevel(Level level)
return i18n("Moderator");
case PowerLevel::Admin:
return i18n("Admin");
case PowerLevel::Owner:
return i18nc("The person that owns a room", "Owner");
case PowerLevel::Creator:
return i18nc("The person that created a room", "Creator");
case PowerLevel::Mute:
return i18n("Mute");
case PowerLevel::Custom:
@@ -30,6 +34,8 @@ int PowerLevel::valueForLevel(Level level)
return 50;
case PowerLevel::Admin:
return 100;
case PowerLevel::Owner:
return 150;
case PowerLevel::Mute:
return -1;
default:
@@ -46,8 +52,12 @@ PowerLevel::Level PowerLevel::levelForValue(int value)
return PowerLevel::Moderator;
case 100:
return PowerLevel::Admin;
case 150:
return PowerLevel::Owner;
case -1:
return PowerLevel::Mute;
case std::numeric_limits<int>::max():
return PowerLevel::Creator;
default:
return PowerLevel::Custom;
}

View File

@@ -31,10 +31,12 @@ public:
enum Level {
Member, /**< A basic member. */
Moderator, /**< A moderator with enhanced powers. */
Admin, /**< The highest power level in the room. */
Admin, /**< Power level 100. */
Owner, /**< Power level 150. */
Mute, /**< The level to remove posting privileges. */
NUMLevels,
Custom, /**< A non-standard value. Intentionally after NUMLevels so it doesn't appear in the model. */
Creator, /**< The user creating the (co-)creating the room. */
};
Q_ENUM(Level);

View File

@@ -697,6 +697,9 @@ QString EventHandler::subtitleText(const NeoChatRoom *room, const Quotient::Room
qCWarning(EventHandling) << "subtitleText called with event set to nullptr.";
return {};
}
if (room->isDirectChat()) {
return plainBody(room, event, true);
}
return singleLineAuthorDisplayname(room, event) + (event->isStateEvent() ? u" "_s : u": "_s) + plainBody(room, event, true);
}

View File

@@ -59,7 +59,7 @@ QList<ActionsModel::Action> actions{
Action{
u"shrug"_s,
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
return u"¯\\\\_(ツ)_/¯ %1"_s.arg(message);
return u"¯\\\\\\_(ツ)\\_/¯ %1"_s.arg(message);
},
Quotient::RoomMessageEvent::MsgType::Text,
kli18n("<message>"),

View File

@@ -118,8 +118,10 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
connect(room, &Room::displaynameChanged, this, [this, room] {
refresh(room, {DisplayNameRole});
});
connect(room, &Room::unreadStatsChanged, this, [this, room] {
refresh(room, {ContextNotificationCountRole, HasHighlightNotificationsRole, NotificationCountRole});
connect(room, &Room::changed, this, [this, room](Room::Changes changes) {
if (changes & (Room::Change::UnreadStats | Room::Change::Highlights)) {
refresh(room, {ContextNotificationCountRole, HasHighlightNotificationsRole, NotificationCountRole});
}
});
connect(room, &Room::notificationCountChanged, this, [this, room] {
refresh(room);

View File

@@ -77,31 +77,37 @@ void NeoChatConnection::connectSignals()
Q_EMIT directChatInvitesChanged();
for (const auto &chatId : additions) {
if (const auto chat = room(chatId)) {
connect(chat, &Room::unreadStatsChanged, this, [this]() {
refreshBadgeNotificationCount();
Q_EMIT directChatNotificationsChanged();
Q_EMIT directChatsHaveHighlightNotificationsChanged();
connect(chat, &Room::changed, this, [this](Room::Changes changes) {
if (changes & (Room::Change::UnreadStats | Room::Change::Highlights)) {
refreshBadgeNotificationCount();
Q_EMIT directChatNotificationsChanged();
Q_EMIT directChatsHaveHighlightNotificationsChanged();
}
});
}
}
for (const auto &chatId : removals) {
if (const auto chat = room(chatId)) {
disconnect(chat, &Room::unreadStatsChanged, this, nullptr);
disconnect(chat, &Room::changed, this, nullptr);
}
}
});
connect(this, &NeoChatConnection::joinedRoom, this, [this](Room *room) {
if (room->isDirectChat()) {
connect(room, &Room::unreadStatsChanged, this, [this]() {
Q_EMIT directChatNotificationsChanged();
Q_EMIT directChatsHaveHighlightNotificationsChanged();
connect(room, &Room::changed, this, [this](Room::Changes changes) {
if (changes & (Room::Change::UnreadStats | Room::Change::Highlights)) {
Q_EMIT directChatNotificationsChanged();
Q_EMIT directChatsHaveHighlightNotificationsChanged();
}
});
}
Q_EMIT roomInvitesChanged();
connect(room, &Room::unreadStatsChanged, this, [this]() {
refreshBadgeNotificationCount();
Q_EMIT homeNotificationsChanged();
Q_EMIT homeHaveHighlightNotificationsChanged();
connect(room, &Room::changed, this, [this](Room::Changes changes) {
if (changes & (Room::Change::UnreadStats | Room::Change::Highlights)) {
refreshBadgeNotificationCount();
Q_EMIT homeNotificationsChanged();
Q_EMIT homeHaveHighlightNotificationsChanged();
}
});
});
connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) {
@@ -136,9 +142,9 @@ void NeoChatConnection::connectSignals()
this,
[this] {
callApi<GetVersionsJob>(BackgroundRequest).onResult([this](const auto &job) {
m_canCheckMutualRooms = job->unstableFeatures().contains("uk.half-shot.msc2666.query_mutual_rooms"_L1);
m_canCheckMutualRooms = job->unstableFeatures().value("uk.half-shot.msc2666.query_mutual_rooms"_L1, false);
Q_EMIT canCheckMutualRoomsChanged();
m_canEraseData = job->unstableFeatures().contains("org.matrix.msc4025"_L1) || job->versions().count("v1.10"_L1);
m_canEraseData = job->unstableFeatures().value("org.matrix.msc4025"_L1, false) || job->versions().count("v1.10"_L1);
Q_EMIT canEraseDataChanged();
});
},
@@ -608,4 +614,9 @@ void NeoChatConnection::reportUser(const QString &userId, const QString &reason)
callApi<NeochatReportUserJob>(userId, reason);
}
bool NeoChatConnection::supportsMatrixSpecVersion(const QString &version)
{
return supportedMatrixSpecVersions().contains(version);
}
#include "moc_neochatconnection.cpp"

View File

@@ -229,6 +229,11 @@ public:
*/
Q_INVOKABLE void reportUser(const QString &userId, const QString &reason);
/**
* @return True if this connection supports the given spec version (e.g. "v1.11").
*/
Q_INVOKABLE bool supportsMatrixSpecVersion(const QString &version);
Q_SIGNALS:
void globalUrlPreviewEnabledChanged();
void labelChanged();

View File

@@ -169,6 +169,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
const auto neochatconnection = static_cast<NeoChatConnection *>(connection);
Q_ASSERT(neochatconnection);
connect(neochatconnection, &NeoChatConnection::globalUrlPreviewEnabledChanged, this, &NeoChatRoom::urlPreviewEnabledChanged);
connect(this, &Room::fullyReadMarkerMoved, this, &NeoChatRoom::invalidateLastUnreadHighlightId);
}
bool NeoChatRoom::visible() const
@@ -1843,4 +1844,52 @@ void NeoChatRoom::report(const QString &reason)
connection()->callApi<NeochatReportRoomJob>(id(), reason);
}
QString NeoChatRoom::findNextUnreadHighlightId()
{
const QString startEventId = !m_lastUnreadHighlightId.isEmpty() ? m_lastUnreadHighlightId : lastFullyReadEventId();
const auto startIt = findInTimeline(startEventId);
if (startIt == historyEdge()) {
return {};
}
for (auto it = startIt.base(); it != messageEvents().cend(); ++it) {
const RoomEvent *ev = it->event();
if (highlights.contains(ev)) {
m_lastUnreadHighlightId = ev->id();
Q_EMIT highlightCycleStartedChanged();
return m_lastUnreadHighlightId;
}
}
if (!m_lastUnreadHighlightId.isEmpty()) {
m_lastUnreadHighlightId.clear();
Q_EMIT highlightCycleStartedChanged();
return findNextUnreadHighlightId();
}
return {};
}
bool NeoChatRoom::highlightCycleStarted() const
{
return !m_lastUnreadHighlightId.isEmpty();
}
void NeoChatRoom::invalidateLastUnreadHighlightId(const QString &fromEventId, const QString &toEventId)
{
Q_UNUSED(fromEventId);
if (m_lastUnreadHighlightId.isEmpty()) {
return;
}
const auto lastIt = findInTimeline(m_lastUnreadHighlightId);
const auto newReadIt = findInTimeline(toEventId);
// opposite comparision because both are reverse iterators :p
if (newReadIt <= lastIt) {
m_lastUnreadHighlightId.clear();
Q_EMIT highlightCycleStartedChanged();
}
}
#include "moc_neochatroom.cpp"

View File

@@ -208,6 +208,11 @@ class NeoChatRoom : public Quotient::Room
*/
Q_PROPERTY(QString pinnedMessage READ pinnedMessage NOTIFY pinnedMessageChanged)
/**
* @brief Whether the highlight finding cycle has started.
*/
Q_PROPERTY(bool highlightCycleStarted READ highlightCycleStarted NOTIFY highlightCycleStartedChanged)
public:
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {});
@@ -615,7 +620,7 @@ public:
/**
* @brief Whether this user is considered a creator of this room. Only applies to post-v12 rooms.
*/
bool isCreator(const QString &userId) const;
Q_INVOKABLE bool isCreator(const QString &userId) const;
/**
* @return The most recent pinned message in the room.
@@ -627,6 +632,19 @@ public:
*/
Q_INVOKABLE void report(const QString &reason);
/**
* @brief Returns the ID of the next unread highlight in the room.
*
* Each call advances the internal highlight cursor. Once the last unread highlight
* is reached, the cycle is reset.
*/
Q_INVOKABLE QString findNextUnreadHighlightId();
/**
* @brief Whether the highlight finding cycle has started.
*/
bool highlightCycleStarted() const;
private:
bool m_visible = false;
@@ -662,11 +680,15 @@ private:
QString m_pinnedMessage;
void loadPinnedMessage();
QString m_lastUnreadHighlightId;
private Q_SLOTS:
void updatePushNotificationState(QString type);
void cacheLastEvent();
void invalidateLastUnreadHighlightId(const QString &fromEventId, const QString &toEventId);
Q_SIGNALS:
void cachedInputChanged();
void busyChanged();
@@ -692,6 +714,7 @@ Q_SIGNALS:
void extraEventNotFound(const QString &eventId);
void inviteTimestampChanged();
void pinnedMessageChanged();
void highlightCycleStartedChanged();
/**
* @brief Request a message be shown to the user of the given type.

View File

@@ -50,7 +50,7 @@ SearchPage {
*/
signal roomSelected(string roomId, string displayName, url avatarUrl, string alias, string topic, int memberCount, bool isJoined)
title: i18nc("@action:title", "Explore Rooms")
title: i18nc("@action:title Explore public rooms and spaces", "Explore")
customPlaceholderText: publicRoomListModel.redirectedText
customPlaceholderIcon: "data-warning"

View File

@@ -43,8 +43,8 @@ void SpaceHierarchyCache::cacheSpaceHierarchy()
Qt::SingleShotConnection);
}
connect(neoChatRoom, &NeoChatRoom::unreadStatsChanged, this, [this, neoChatRoom]() {
if (neoChatRoom != nullptr) {
connect(neoChatRoom, &NeoChatRoom::changed, this, [this, neoChatRoom](NeoChatRoom::Changes changes) {
if (neoChatRoom != nullptr && (changes & (NeoChatRoom::Change::UnreadStats | NeoChatRoom::Change::Highlights))) {
const auto parents = parentSpaces(neoChatRoom->id());
if (parents.count() > 0) {
Q_EMIT spaceNotifcationCountChanged(parents);

View File

@@ -53,22 +53,19 @@ ColumnLayout {
when: !root.fileTransferInfo.completed && !root.fileTransferInfo.active
PropertyChanges {
target: playButton
icon.name: "media-playback-start"
onClicked: Message.room.downloadFile(root.eventId)
playButton.icon.name: "media-playback-start"
playButton.onClicked: Message.room.downloadFile(root.eventId)
}
},
State {
name: "downloading"
when: root.fileTransferInfo.active && !root.fileTransferInfo.completed
PropertyChanges {
target: downloadBar
visible: true
downloadBar.visible: true
}
PropertyChanges {
target: playButton
icon.name: "media-playback-stop"
onClicked: {
playButton.icon.name: "media-playback-stop"
playButton.onClicked: {
Message.room.cancelFileTransfer(root.eventId);
}
}
@@ -77,9 +74,8 @@ ColumnLayout {
name: "paused"
when: root.fileTransferInfo.completed && (audio.playbackState === MediaPlayer.StoppedState || audio.playbackState === MediaPlayer.PausedState)
PropertyChanges {
target: playButton
icon.name: "media-playback-start"
onClicked: {
playButton.icon.name: "media-playback-start"
playButton.onClicked: {
audio.source = root.fileTransferInfo.localPath;
MediaManager.startPlayback();
audio.play();
@@ -91,11 +87,8 @@ ColumnLayout {
when: root.fileTransferInfo.completed && audio.playbackState === MediaPlayer.PlayingState
PropertyChanges {
target: playButton
icon.name: "media-playback-pause"
onClicked: audio.pause()
playButton.icon.name: "media-playback-pause"
playButton.onClicked: audio.pause()
}
}
]

View File

@@ -73,16 +73,13 @@ ColumnLayout {
when: root.fileTransferInfo.completed && autoOpenFile
PropertyChanges {
target: openButton
icon.name: "document-open"
onClicked: openSavedFile()
}
PropertyChanges {
target: downloadButton
icon.name: "download"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
onClicked: saveFileAs()
openButton.icon.name: "document-open"
openButton.onClicked: openSavedFile()
downloadButton {
icon.name: "download"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
onClicked: saveFileAs()
}
}
},
State {
@@ -90,15 +87,12 @@ ColumnLayout {
when: root.fileTransferInfo.completed && !autoOpenFile
PropertyChanges {
target: openButton
visible: false
}
PropertyChanges {
target: downloadButton
icon.name: "document-open"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
onClicked: openSavedFile()
openButton.visible: false
downloadButton {
icon.name: "document-open"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
onClicked: openSavedFile()
}
}
},
State {
@@ -106,19 +100,13 @@ ColumnLayout {
when: root.fileTransferInfo.active
PropertyChanges {
target: openButton
visible: false
}
PropertyChanges {
target: sizeLabel
text: i18nc("file download progress", "%1 / %2", Format.formatByteSize(root.fileTransferInfo.progress), Format.formatByteSize(root.fileTransferInfo.total))
}
PropertyChanges {
target: downloadButton
icon.name: "media-playback-stop"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
onClicked: Message.room.cancelFileTransfer(root.eventId)
sizeLabel.text: i18nc("file download progress", "%1 / %2", Format.formatByteSize(root.fileTransferInfo.progress), Format.formatByteSize(root.fileTransferInfo.total))
openButton.visible: false
downloadButton {
icon.name: "media-playback-stop"
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
onClicked: Message.room.cancelFileTransfer(root.eventId)
}
}
}
]

View File

@@ -75,37 +75,25 @@ Video {
name: "notDownloaded"
when: !root.fileTransferInfo.completed && !root.fileTransferInfo.active
PropertyChanges {
target: videoLabel
visible: true
}
PropertyChanges {
target: mediaThumbnail
visible: true
videoLabel.visible: true
mediaThumbnail.visible: true
}
},
State {
name: "downloading"
when: root.fileTransferInfo.active && !root.fileTransferInfo.completed && (Controller.isImageShown(root.eventId) || !NeoChatConfig.hideImages)
PropertyChanges {
target: downloadBar
visible: true
}
PropertyChanges {
target: mediaThumbnail
visible: true
downloadBar.visible: true
mediaThumbnail.visible: true
}
},
State {
name: "paused"
when: root.fileTransferInfo.completed && root.playbackState === MediaPlayer.PausedState && (Controller.isImageShown(root.eventId) || !NeoChatConfig.hideImages)
PropertyChanges {
target: videoControls
stateVisible: true
}
PropertyChanges {
target: playButton
icon.name: "media-playback-start"
onClicked: {
videoControls.stateVisible: true
playButton.icon.name: "media-playback-start"
playButton.onClicked: {
MediaManager.startPlayback();
root.play();
}
@@ -115,34 +103,20 @@ Video {
name: "playing"
when: root.fileTransferInfo.completed && root.playbackState === MediaPlayer.PlayingState && (Controller.isImageShown(root.eventId) || !NeoChatConfig.hideImages)
PropertyChanges {
target: videoControls
stateVisible: true
}
PropertyChanges {
target: playButton
icon.name: "media-playback-pause"
onClicked: root.pause()
videoControls.stateVisible: true
playButton.icon.name: "media-playback-pause"
playButton.onClicked: root.pause()
}
},
State {
name: "stopped"
when: root.fileTransferInfo.completed && root.playbackState === MediaPlayer.StoppedState && (Controller.isImageShown(root.eventId) || !NeoChatConfig.hideImages)
PropertyChanges {
target: videoControls
stateVisible: true
}
PropertyChanges {
target: mediaThumbnail
visible: true
}
PropertyChanges {
target: videoLabel
visible: true
}
PropertyChanges {
target: playButton
icon.name: "media-playback-start"
onClicked: {
videoControls.stateVisible: true
mediaThumbnail.visible: true
videoLabel.visible: true
playButton.icon.name: "media-playback-start"
playButton.onClicked: {
MediaManager.startPlayback();
root.play();
}
@@ -151,16 +125,9 @@ Video {
State {
name: "hidden"
PropertyChanges {
target: mediaThumbnail
visible: false
}
PropertyChanges {
target: videoControls
visible: false
}
PropertyChanges {
target: hidden
visible: true
mediaThumbnail.visible: false
videoControls.visible: false
hidden.visible: true
}
}
]

View File

@@ -6,8 +6,9 @@ import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.neochat
import org.kde.neochat.libneochat
ColumnLayout {
@@ -29,34 +30,30 @@ ColumnLayout {
Layout.preferredHeight: Kirigami.Units.largeSpacing * 2
}
QQC2.AbstractButton {
KirigamiComponents.AvatarButton {
name: root.room ? root.room.displayName : ""
source: root.room ? root.room.avatarMediaUrl : ""
Layout.preferredWidth: Math.round(Kirigami.Units.gridUnit * 3.5)
Layout.preferredHeight: Math.round(Kirigami.Units.gridUnit * 3.5)
Layout.alignment: Qt.AlignHCenter
onClicked: {
root.resolveResource(root.room.directChatRemoteMember.uri, "")
}
onClicked: RoomManager.resolveResource(root.room.directChatRemoteMember.id)
contentItem: KirigamiComponents.Avatar {
name: root.room ? root.room.displayName : ""
source: root.room ? root.room.avatarMediaUrl : ""
Rectangle {
visible: root.room.usesEncryption
color: Kirigami.Theme.backgroundColor
Rectangle {
visible: root.room.usesEncryption
color: Kirigami.Theme.backgroundColor
width: Kirigami.Units.gridUnit
height: Kirigami.Units.gridUnit
anchors.bottom: parent.bottom
anchors.right: parent.right
width: Kirigami.Units.gridUnit
height: Kirigami.Units.gridUnit
anchors.bottom: parent.bottom
anchors.right: parent.right
radius: Math.round(width / 2)
radius: Math.round(width / 2)
Kirigami.Icon {
source: "channel-secure-symbolic"
anchors.fill: parent
}
Kirigami.Icon {
source: "channel-secure-symbolic"
anchors.fill: parent
}
}
}

View File

@@ -103,19 +103,6 @@ QQC2.ScrollView {
Layout.fillWidth: true
}
Delegates.RoundedItemDelegate {
id: favouriteButton
visible: !root.room.isSpace
icon.name: root.room && root.room.isFavourite ? "rating" : "rating-unrated"
text: root.room && root.room.isFavourite ? i18nc("@action:button", "Remove from Favorites") : i18nc("@action:button", "Add to Favorites")
onClicked: root.room.isFavourite ? root.room.removeTag("m.favourite") : root.room.addTag("m.favourite", 1.0)
activeFocusOnTab: true
Layout.fillWidth: true
}
Delegates.RoundedItemDelegate {
id: widgetsButton
visible: !root.room.isSpace
@@ -150,7 +137,8 @@ QQC2.ScrollView {
Delegates.RoundedItemDelegate {
id: pinnedMessagesButton
visible: !root.room.isSpace
// On mobile the pinned message at the top is hidden, so we need this button in that case.
visible: !root.room.isSpace && Kirigami.Settings.isMobile
icon.name: "pin-symbolic"
text: i18nc("@action:button", "Pinned Messages")
activeFocusOnTab: true

View File

@@ -54,19 +54,31 @@ RowLayout {
QQC2.ToolButton {
id: menuButton
Accessible.role: Accessible.ButtonMenu
Accessible.onPressAction: menuButton.action.trigger()
display: QQC2.AbstractButton.IconOnly
checkable: true
text: i18nc("@action:button", "Show Menu")
icon.name: "application-menu-symbolic"
onClicked: {
const item = menu.createObject(menuButton) as QQC2.Menu;
item.closed.connect(menuButton.toggle);
item.open();
property QQC2.Menu menuItem: undefined
function openMenu(): void {
if (!menuItem || !menuItem.visible) {
menuItem = menu.createObject(menuButton) as QQC2.Menu;
menuItem.closed.connect(menuButton.toggle);
menuItem.open();
} else {
menuItem.dismiss()
}
}
QQC2.ToolTip.visible: hovered
Accessible.role: Accessible.ButtonMenu
display: QQC2.AbstractButton.IconOnly
down: pressed || menuItem.visible
text: i18nc("@action:button", "Show Menu")
icon.name: "application-menu-symbolic"
onPressed: openMenu()
Keys.onReturnPressed: openMenu()
Keys.onEnterPressed: openMenu()
Accessible.onPressAction: openMenu()
QQC2.ToolTip.visible: hovered && !menuItem.visible
QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
@@ -74,6 +86,8 @@ RowLayout {
Component {
id: menu
QQC2.Menu {
y: menuButton.height
QQC2.MenuItem {
text: i18n("Find your friends")
icon.name: "list-add-user"

View File

@@ -46,15 +46,13 @@ Kirigami.NavigationTabBar {
}
},
Kirigami.Action {
text: i18n("Explore rooms")
text: i18nc("@action:inmenu Explore public rooms and spaces", "Explore")
icon.name: "compass"
onTriggered: {
explorePopup.close();
let dialog = (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Explore Rooms")
});
}, {});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});

View File

@@ -222,14 +222,12 @@ Kirigami.Page {
Kirigami.Action {
id: exploreRoomAction
icon.name: RoomManager.sortFilterRoomTreeModel.filterText.length > 0 ? "search" : "compass"
text: RoomManager.sortFilterRoomTreeModel.filterText.length > 0 ? i18nc("@action", "Search in room directory") : i18nc("@action", "Explore rooms")
text: RoomManager.sortFilterRoomTreeModel.filterText.length > 0 ? i18nc("@action:button Search public directory for this room", "Search for Room") : i18nc("@action:button Explore public rooms and spaces", "Explore")
onTriggered: {
let dialog = (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).layers.push(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection,
keyword: RoomManager.sortFilterRoomTreeModel.filterText
}, {
title: i18nc("@title", "Explore Rooms")
});
}, {});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});

View File

@@ -286,7 +286,7 @@ QQC2.Control {
Layout.preferredHeight: width - Kirigami.Units.smallSpacing
Layout.maximumHeight: width - Kirigami.Units.smallSpacing
text: i18nc("@action:button", "Explore rooms")
text: i18nc("@action:button Explore public rooms and spaces", "Explore")
contentItem: Kirigami.Icon {
source: "compass"
}
@@ -297,9 +297,7 @@ QQC2.Control {
let dialog = (Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection,
keyword: RoomManager.sortFilterRoomTreeModel.filterText
}, {
title: i18nc("@title", "Explore Rooms")
});
}, {});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});

View File

@@ -170,10 +170,12 @@ void RoomTreeModel::connectRoomSignals(NeoChatRoom *room)
connect(room, &Room::displaynameChanged, this, [this, room] {
refreshRoomRoles(room, {DisplayNameRole});
});
connect(room, &Room::unreadStatsChanged, this, [this, room] {
refreshRoomRoles(room, {ContextNotificationCountRole, HasHighlightNotificationsRole, NotificationCountRole});
if (room->isServerNoticeRoom()) {
Q_EMIT invalidateSort();
connect(room, &Room::changed, this, [this, room](Room::Changes changes) {
if (changes & (Room::Change::UnreadStats | Room::Change::Highlights)) {
refreshRoomRoles(room, {ContextNotificationCountRole, HasHighlightNotificationsRole, NotificationCountRole});
if (room->isServerNoticeRoom()) {
Q_EMIT invalidateSort();
}
}
});
connect(room, &Room::avatarChanged, this, [this, room] {

View File

@@ -341,7 +341,8 @@ FormCard.FormCardPage {
FormCard.FormCheckDelegate {
id: showLinkPreviewDelegate
text: i18nc("@label:checkbox", "Show link previews in messages")
text: i18nc("@label:checkbox", "Show link previews")
description: i18nc("@info:label", "You can customize this per-room under room settings. If unchecked, disables link previews in every room.")
checked: NeoChatConfig.showLinkPreview
onToggled: {
NeoChatConfig.showLinkPreview = checked;

View File

@@ -191,44 +191,43 @@ FormCard.FormCardPage {
}
FormCard.FormHeader {
title: i18nc("@title", "URL Previews")
}
FormCard.FormCard {
FormCard.FormCheckDelegate {
text: i18nc("@label:checkbox", "Enable URL previews by default for room members")
checked: root.room.defaultUrlPreviewState
visible: root.room.canSendState("org.matrix.room.preview_urls")
onToggled: {
root.room.defaultUrlPreviewState = checked;
}
}
FormCard.FormCheckDelegate {
enabled: NeoChatConfig.showLinkPreview
text: i18nc("@label:checkbox", "Enable URL previews")
// Most users won't see the above setting so tell them the default.
description: root.room.defaultUrlPreviewState ? i18nc("@info", "URL previews are enabled by default in this room") : i18nc("@info", "URL previews are disabled by default in this room")
checked: root.room.urlPreviewEnabled
onToggled: {
root.room.urlPreviewEnabled = checked;
}
}
title: i18nc("@title", "Link Previews")
}
Kirigami.InlineMessage {
Layout.fillWidth: true
Layout.maximumWidth: Kirigami.Units.gridUnit * 30
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
Layout.alignment: Qt.AlignHCenter
text: i18nc("As in the user has switched off showing previews of hyperlinks in timeline messages", "URL previews are currently disabled for your account")
text: i18nc("As in the user has switched off showing previews of hyperlinks in timeline messages", "Link previews are disabled globally in Appearance settings")
type: Kirigami.MessageType.Information
visible: !NeoChatConfig.showLinkPreview
actions: Kirigami.Action {
text: i18nc("@action:button", "Enable")
icon.name: "checkmark-symbolic"
text: i18nc("@action:button Enable link previews globally", "Enable")
onTriggered: {
NeoChatConfig.showLinkPreview = true;
NeoChatConfig.save();
}
}
}
FormCard.FormCard {
visible: NeoChatConfig.showLinkPreview || root.room.canSendState("org.matrix.room.preview_urls")
FormCard.FormCheckDelegate {
visible: NeoChatConfig.showLinkPreview
text: i18nc("@label:checkbox", "Enable link previews for this room")
// Most users won't see the above setting so tell them the default.
description: root.room.defaultUrlPreviewState ? i18nc("@info", "Link previews were enabled by default by the room admins.") : i18nc("@info", "Link previews were disabled by default by the room admins.")
checked: root.room.urlPreviewEnabled
onToggled: root.room.urlPreviewEnabled = checked
}
FormCard.FormCheckDelegate {
text: i18nc("@label:checkbox", "Enable link previews by default")
checked: root.room.defaultUrlPreviewState
visible: root.room.canSendState("org.matrix.room.preview_urls")
onToggled: root.room.defaultUrlPreviewState = checked
}
}
FormCard.FormHeader {
title: i18nc("@title", "Official Parent Spaces")
}

View File

@@ -138,9 +138,7 @@ Kirigami.Dialog {
connection: root.room.connection,
showOnlySpaces: true,
showOnlySpacesButton: false
}, {
title: i18nc("@title", "Explore Rooms")
});
}, {});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
chosenRoomDelegate.roomId = roomId;
chosenRoomDelegate.displayName = displayName;

View File

@@ -41,7 +41,7 @@ Kirigami.Dialog {
let dialog = ((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat.libneochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Explore Rooms")
title: i18nc("@title Explore public rooms and spaces", "Explore")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
chosenRoomDelegate.roomId = roomId;
@@ -123,9 +123,7 @@ Kirigami.Dialog {
onClicked: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Explore Rooms")
});
}, {});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
chosenRoomDelegate.roomId = roomId;
chosenRoomDelegate.displayName = displayName;

View File

@@ -193,9 +193,7 @@ QQC2.ScrollView {
room: _private.room
}
KirigamiComponents.FloatingButton {
id: goReadMarkerFab
ColumnLayout {
anchors {
right: parent.right
top: parent.top
@@ -203,28 +201,59 @@ QQC2.ScrollView {
rightMargin: Kirigami.Units.largeSpacing
}
implicitWidth: Kirigami.Settings.hasTransientTouchInput ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2
implicitHeight: Kirigami.Settings.hasTransientTouchInput ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2
spacing: Kirigami.Units.largeSpacing
padding: Kirigami.Units.largeSpacing
KirigamiComponents.FloatingButton {
id: goReadMarkerFab
z: 2
visible: !_private.room?.partiallyReadStats.empty()
implicitWidth: Kirigami.Settings.hasTransientTouchInput ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2
implicitHeight: Kirigami.Settings.hasTransientTouchInput ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2
text: _private.room.readMarkerLoaded ? i18nc("@action:button", "Jump to first unread message") : i18nc("@action:button", "Jump to oldest loaded message")
icon.name: "go-up"
onClicked: {
goReadMarkerFab.textChanged()
root.goToEvent(_private.room.lastFullyReadEventId);
}
Kirigami.Action {
shortcut: "Shift+PgUp"
onTriggered: goReadMarkerFab.clicked()
padding: Kirigami.Units.largeSpacing
z: 2
visible: !_private.room?.partiallyReadStats.empty()
text: _private.room.readMarkerLoaded ? i18nc("@action:button", "Jump to first unread message") : i18nc("@action:button", "Jump to oldest loaded message")
icon.name: "go-up"
onClicked: {
goReadMarkerFab.textChanged()
root.goToEvent(_private.room.lastFullyReadEventId);
}
Kirigami.Action {
shortcut: "Shift+PgUp"
onTriggered: goReadMarkerFab.clicked()
}
QQC2.ToolTip.text: goReadMarkerFab.text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: goReadMarkerFab.hovered
}
KirigamiComponents.FloatingButton {
id: goUnreadHighlightFab
QQC2.ToolTip.text: goReadMarkerFab.text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: goReadMarkerFab.hovered
implicitWidth: Kirigami.Settings.hasTransientTouchInput ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2
implicitHeight: Kirigami.Settings.hasTransientTouchInput ? Kirigami.Units.gridUnit * 3 : Kirigami.Units.gridUnit * 2
padding: Kirigami.Units.largeSpacing
z: 2
visible: _private.room?.highlightCount > 0
text: _private.room?.highlightCycleStarted ? i18nc("@action:button", "Jump to next unread highlight") : i18nc("@action:button", "Jump to first unread message")
icon.name: "mail-unread-symbolic"
onClicked: {
const eventId = _private.room.findNextUnreadHighlightId();
if (eventId !== "") {
goUnreadHighlightFab.textChanged();
root.goToEvent(eventId);
}
}
QQC2.ToolTip.text: goUnreadHighlightFab.text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: goUnreadHighlightFab.hovered
}
}
KirigamiComponents.FloatingButton {
id: goMarkAsReadFab