Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion qml/components/navigation/ListHeader.qml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import ".."
Rectangle {
id: topFilterBar
width: parent ? parent.width : Screen.width
height: showSearchBox ? units.gu(11) : units.gu(6) // Restored height to give proper space
height: (showSearchBox ? units.gu(5) : 0) + (filterModel.length > 0 ? units.gu(6) : 0)
color: "transparent"

// Helper property to check if dark mode is active
Expand Down Expand Up @@ -207,6 +207,7 @@ Rectangle {

// Filter buttons row below search
Item {
visible: topFilterBar.filterModel.length > 0
width: parent.width
height: units.gu(6) // Adjusted back

Expand Down
1 change: 1 addition & 0 deletions qml/components/richtext/HtmlEditorToolbar.qml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Rectangle {
width: units.gu(5)
height: units.gu(4)
color: (editor && editor.listening) ? LomiriColors.red : (darkMode ? "#555555" : "#F0F0F0")
visible: editor ? editor.isVoiceInputEnabled : false

Icon {
anchors.centerIn: parent
Expand Down
198 changes: 188 additions & 10 deletions qml/components/richtext/ReadMorePage.qml
Comment thread
AnmollGarg marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import QtQuick 2.7
import Lomiri.Components 1.3
import QtQuick.LocalStorage 2.7 as Sql
import "../../../models/global.js" as Global
import "../system"

Page {
id: readmepage
Expand All @@ -23,6 +25,110 @@ Page {
property string _lastKnownHolder: ""
property bool _parentSaveCommitted: false

property bool listening: false
property bool processing: false
property bool ignoreNextResult: false
property string textBeforeRecording: ""
property bool isVoiceInputEnabled: true
property string _partialRecognizedText: ""
property string _currentVoiceStatus: ""

function checkVoiceInputEnabled() {
try {
var db = Sql.LocalStorage.openDatabaseSync("myDatabase", "1.0", "My Database", 1000000);
var result = true;
db.transaction(function (tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS app_settings (key TEXT PRIMARY KEY, value TEXT)');
var rs = tx.executeSql('SELECT value FROM app_settings WHERE key = "voice_input_enabled"');
if (rs.rows.length > 0) {
result = rs.rows.item(0).value === "true";
}
});
isVoiceInputEnabled = result;
} catch (e) {
console.warn("Error reading voice_input_enabled:", e);
}
}

Connections {
target: mainView.backend_bridge
onMessageReceived: {
if (!readmepage.listening && !readmepage.processing) return;

if (data.event === "voice_recognition_partial") {
var partialText = data.payload
if (partialText) {
readmepage._currentVoiceStatus = i18n.dtr("ubtms", "Listening...");

var prefix = "";
if (readmepage.textBeforeRecording.length > 0) {
var lastChar = readmepage.textBeforeRecording.charAt(readmepage.textBeforeRecording.length - 1);
if (lastChar !== '\n' && lastChar !== '\r') {
prefix = "\n";
}
}
simpleEditor.text = readmepage.textBeforeRecording + prefix + partialText;
cursorTimer.start();
}
} else if (data.event === "voice_recognition_status") {
var statusText = data.payload;
if (statusText) {
readmepage._currentVoiceStatus = statusText;
}
} else if (data.event === "voice_recognition_result") {
if (readmepage.ignoreNextResult) {
readmepage.ignoreNextResult = false;
readmepage.listening = false;
readmepage.processing = false;
readmepage._currentVoiceStatus = "";
readmepage.textBeforeRecording = simpleEditor.text;
cursorTimer.start();
return;
}

readmepage.listening = false
readmepage.processing = false
var recognizedText = data.payload
readmepage._currentVoiceStatus = "";

if (recognizedText) {
var prefix = "";
if (readmepage.textBeforeRecording.length > 0) {
var lastChar = readmepage.textBeforeRecording.charAt(readmepage.textBeforeRecording.length - 1);
if (lastChar !== '\n' && lastChar !== '\r') {
prefix = "\n";
}
}
simpleEditor.text = readmepage.textBeforeRecording + prefix + recognizedText;
readmepage.textBeforeRecording = simpleEditor.text;
cursorTimer.start();
}
} else if (data.event === "voice_recognition_error") {
readmepage.listening = false
readmepage.processing = false
readmepage._currentVoiceStatus = "";
readmepage.textBeforeRecording = simpleEditor.text;
cursorTimer.start()
}
}
}

Timer {
id: cursorTimer
interval: 100
repeat: false
onTriggered: {
if (simpleEditor) {
simpleEditor.cursorPosition = simpleEditor.length;
if (simpleEditor.flickableItem) {
simpleEditor.flickableItem.contentY = Math.max(0, simpleEditor.flickableItem.contentHeight - simpleEditor.flickableItem.height);
} else if (simpleEditor.flickable) {
simpleEditor.flickable.contentY = Math.max(0, simpleEditor.flickable.contentHeight - simpleEditor.flickable.height);
}
}
}
}

header: PageHeader {
id: header
title: i18n.dtr("ubtms","Description")
Expand All @@ -35,20 +141,45 @@ Page {
dividerColor: LomiriColors.slate
}

trailingActionBar.numberOfSlots: 3
trailingActionBar.actions: [
Action {
visible: !isReadOnly && useRichText
iconName: editor.toolbarExpanded ? "view-collapse" : "view-expand"
text: editor.toolbarExpanded ? i18n.dtr("ubtms", "Hide Toolbar") : i18n.dtr("ubtms", "Show Toolbar")
visible: !isReadOnly
iconName: "tick"
onTriggered: {
editor.toolbarExpanded = !editor.toolbarExpanded
saveAndClose()
}
},
Action {
visible: !isReadOnly
iconName: "tick"
visible: !isReadOnly && readmepage.isVoiceInputEnabled && !useRichText
iconName: "microphone"
text: readmepage.listening ? i18n.dtr("ubtms", "Stop Recording") : i18n.dtr("ubtms", "Start Recording")
onTriggered: {
saveAndClose()
if (readmepage.listening) {
readmepage.listening = false
readmepage.processing = true
readmepage._currentVoiceStatus = i18n.dtr("ubtms", "Processing...");

backend_bridge.call("backend.stop_voice_recognition", [])
return;
}
if (readmepage.processing) return;

readmepage.textBeforeRecording = simpleEditor.text
readmepage._partialRecognizedText = "";
readmepage._currentVoiceStatus = i18n.dtr("ubtms", "Starting...");

readmepage.listening = true
readmepage.processing = false
backend_bridge.call("backend.run_voice_recognition", [])
}
},
Action {
visible: !isReadOnly && useRichText && (!readmepage.listening && !readmepage.processing)
iconName: editor.toolbarExpanded ? "view-collapse" : "view-expand"
text: editor.toolbarExpanded ? i18n.dtr("ubtms", "Hide Toolbar") : i18n.dtr("ubtms", "Show Toolbar")
onTriggered: {
editor.toolbarExpanded = !editor.toolbarExpanded
}
}
]
Expand Down Expand Up @@ -109,13 +240,28 @@ Page {
}

function saveAndClose() {
// Auto-stop voice recognition if it's still running
if (readmepage.listening || readmepage.processing) {
readmepage.ignoreNextResult = true;
readmepage.listening = false;
readmepage.processing = false;
readmepage._currentVoiceStatus = "";
backend_bridge.call("backend.stop_voice_recognition", []);
}

if (useRichText) {
// Finalize voice span in RichTextEditor if needed
if (editor.editor && editor.editor.listening || editor.editor && editor.editor.processing) {
editor.editor.stopAndFinalizeVoice();
}
editor.getText(function (content) {
if (commitContent(content)) {
closePage();
}
});
} else {
// For plain text, textBeforeRecording is already updated
readmepage.textBeforeRecording = simpleEditor.text;
if (commitContent(simpleEditor.text)) {
closePage();
}
Expand All @@ -133,8 +279,8 @@ Page {
id: editor
visible: useRichText
text: Global.description_temporary_holder
readOnly: isReadOnly
showToolbar: !isReadOnly
readOnly: isReadOnly || readmepage.listening || readmepage.processing
showToolbar: !isReadOnly && !readmepage.listening && !readmepage.processing
anchors.fill: parent

onContentChanged: {
Expand Down Expand Up @@ -165,7 +311,7 @@ Page {
id: simpleEditor
visible: !useRichText
text: Global.description_temporary_holder
readOnly: isReadOnly
readOnly: isReadOnly || readmepage.listening || readmepage.processing
textFormat: Text.PlainText
font.pixelSize: units.gu(2)
wrapMode: TextArea.Wrap
Expand Down Expand Up @@ -222,6 +368,27 @@ Page {
}
}

VoiceTimerWidget {
id: voiceTimerWidget
parent: readmepage

isListening: readmepage.listening
isProcessing: readmepage.processing
partialText: "" // Don't show partial text in the widget anymore
voiceStatus: readmepage._currentVoiceStatus

onStopClicked: {
readmepage.ignoreNextResult = true;
readmepage.listening = false
readmepage.processing = false
readmepage.textBeforeRecording = simpleEditor.text;

readmepage._currentVoiceStatus = "";

backend_bridge.call("backend.stop_voice_recognition", [])
}
}

// Handle page visibility changes to ensure content is saved
onVisibleChanged: {
if (!visible && !isReadOnly && !_parentSaveCommitted) {
Expand Down Expand Up @@ -249,9 +416,15 @@ Page {
}
}
}

if (!visible && (listening || processing)) {
console.log("[ReadMorePage] visibility changed: Stopping voice recognition...")
mainView.backend_bridge.call("backend.stop_voice_recognition", [])
}
}

Component.onCompleted: {
checkVoiceInputEnabled();
// Initialize tracking to avoid false external-change detection
_lastKnownHolder = Global.description_temporary_holder || "";

Expand Down Expand Up @@ -281,5 +454,10 @@ Page {
parentDraftHandler.saveDraft();
}
}

if (listening || processing) {
console.log("[ReadMorePage] destruction: Stopping voice recognition...")
mainView.backend_bridge.call("backend.stop_voice_recognition", [])
}
}
}
Loading