diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2cdb3e066..7495016c2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -200,6 +200,10 @@ add_library(neochat STATIC models/pollanswermodel.h ) +set_source_files_properties(qml/TextToSpeechWrapper.qml PROPERTIES + QT_QML_SINGLETON_TYPE TRUE +) + set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE ) @@ -260,6 +264,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE qml/AvatarTabButton.qml qml/SpaceDrawer.qml qml/OsmLocationPlugin.qml + qml/TextToSpeechWrapper.qml qml/FullScreenMap.qml qml/LocationsPage.qml qml/LocationMapItem.qml diff --git a/src/qml/Main.qml b/src/qml/Main.qml index be493a58f..2635e82b5 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -189,6 +189,7 @@ Kirigami.ApplicationWindow { NeoChatSettingsView.window = root; NeoChatSettingsView.connection = root.connection; WindowController.setBlur(pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout); + TextToSpeechWrapper.warmUp(); if (ShareHandler.text && root.connection) { root.handleShare() } diff --git a/src/qml/MessageDelegateContextMenu.qml b/src/qml/MessageDelegateContextMenu.qml index 54be17dac..a2b13cff6 100644 --- a/src/qml/MessageDelegateContextMenu.qml +++ b/src/qml/MessageDelegateContextMenu.qml @@ -4,8 +4,13 @@ import QtQuick import QtQuick.Controls as QQC2 +import QtTextToSpeech import QtQuick.Layouts import org.kde.kirigami as Kirigami +import org.kde.kirigamiaddons.components as KirigamiComponents +import org.kde.kirigamiaddons.formcard as FormCard +import org.kde.neochat + import org.kde.kirigamiaddons.components as KirigamiComponents import org.kde.kirigamiaddons.formcard as FormCard @@ -83,6 +88,13 @@ DelegateContextMenu { Clipboard.saveText("https://matrix.to/#/" + currentRoom.id + "/" + root.eventId); } } + QQC2.Action { + text: i18nc("@action:inmenu", "Read Text Aloud") + icon.name: "audio-speakers-symbolic" + onTriggered: { + TextToSpeechWrapper.say(i18nc("@info text-to-speech %1 is author %2 is message text", "%1 said %2", root.author.displayName, root.plainText)) + } + } Kirigami.Action { separator: true } diff --git a/src/qml/TextToSpeechWrapper.qml b/src/qml/TextToSpeechWrapper.qml new file mode 100644 index 000000000..cd4506e4d --- /dev/null +++ b/src/qml/TextToSpeechWrapper.qml @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2025 Ritchie Frodomar +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma Singleton + +import QtQuick +import QtTextToSpeech + +QtObject { + id: root + + readonly property TextToSpeech tts: TextToSpeech { + id: tts + } + + function warmUp() { + // TODO: This method is called on startup to avoid a UI freeze the first time you read a message aloud, but there's nothing for it to do. + // This would be a good place to check if TTS can actually be used. + } + + function say(text: String) { + tts.say(text) + } +} \ No newline at end of file