From d043cdda06716047ffcd49e73e3db98c9042beb3 Mon Sep 17 00:00:00 2001 From: Parvathy Nair Date: Thu, 10 Sep 2026 13:57:16 +0530 Subject: [PATCH] Local Account: Hide delete & view logs, allow local account renaming, and fix input method commit --- .gitignore | 4 +- models/accounts.js | 67 +++++++++++++++++-- qml/app/AppDrawer.qml | 2 +- qml/app/navigation/MenuPage.qml | 4 +- qml/components/base/OutlinedTextField.qml | 9 +++ qml/features/settings/pages/Account_Page.qml | 65 ++++++++++++++++-- .../settings/pages/Settings_Accounts.qml | 19 +++--- src/daemon.py | 6 +- 8 files changed, 147 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index bb37b641..31dcae2e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ build .agent voice_to_text/lib/ .clickable -.vscode \ No newline at end of file +.vscode +node_modules/ +website/ \ No newline at end of file diff --git a/models/accounts.js b/models/accounts.js index 617e79f1..bbe5bcb0 100644 --- a/models/accounts.js +++ b/models/accounts.js @@ -21,7 +21,7 @@ function getAccountsList() { for (var i = 0; i < accounts.rows.length; i++) { var row = accounts.rows.item(i); var obj = DBCommon.rowToObject(row); - if (obj.id === 0 || obj.name === "Local Account") { + if (obj.name === "Local Account" || (obj.id === 0 && !obj.name)) { obj.name = "Local"; } accountsList.push(obj); @@ -407,6 +407,63 @@ function updateAccount(accountId, name, link, database, username, selectedConnec return result; } +/** + * Updates only the name of an account in the local SQLite database. + * + * @param {number} accountId - The ID of the account to update. + * @param {string} name - The new name for the account. + * @returns {Object} Result object containing success status, message, and duplicateType. + */ +function updateAccountName(accountId, name) { + var result = { + success: false, + message: "", + duplicateType: null + }; + + if (!name || !name.trim()) { + result.message = "Account name cannot be empty."; + return result; + } + + var cleanName = name.trim(); + + try { + var db = Sql.LocalStorage.openDatabaseSync(DBCommon.NAME, DBCommon.VERSION, DBCommon.DISPLAY_NAME, DBCommon.SIZE); + + db.transaction(function (tx) { + // Check for duplicate account name (excluding current account) + var nameCheckResult = tx.executeSql( + 'SELECT COUNT(*) AS count FROM users WHERE LOWER(name) = LOWER(?) AND id != ?', + [cleanName, accountId] + ); + + if (nameCheckResult.rows.item(0).count > 0) { + DBCommon.log("Duplicate account name found (case-insensitive): " + cleanName); + result.duplicateType = "name"; + result.message = "An account with this name already exists."; + return; + } + + // Update the account name + tx.executeSql( + 'UPDATE users SET name = ? WHERE id = ?', + [cleanName, accountId] + ); + + DBCommon.log("Account name updated successfully for account id: " + accountId + " (" + cleanName + ")"); + result.success = true; + result.message = "Account name updated successfully."; + }); + + } catch (e) { + DBCommon.logException("updateAccountName", e); + result.message = "Error updating account name: " + e.message; + } + + return result; +} + /** * Deletes a user account and all related records from associated tables in the local SQLite database. * @@ -606,10 +663,6 @@ function getAccountName(accountId) { return ""; } - if (Number(accountId) === 0) { - return "Local"; - } - try { var db = Sql.LocalStorage.openDatabaseSync(DBCommon.NAME, DBCommon.VERSION, DBCommon.DISPLAY_NAME, DBCommon.SIZE); var name = ""; @@ -621,14 +674,14 @@ function getAccountName(accountId) { } }); - if (name === "Local Account") { + if (name === "Local Account" || (Number(accountId) === 0 && !name)) { return "Local"; } return name; } catch (e) { Logger.error("Accounts", "getAccountName failed:", e) - return ""; + return Number(accountId) === 0 ? "Local" : ""; } } diff --git a/qml/app/AppDrawer.qml b/qml/app/AppDrawer.qml index 06f72516..ceae1651 100644 --- a/qml/app/AppDrawer.qml +++ b/qml/app/AppDrawer.qml @@ -94,7 +94,7 @@ Controls.Drawer { Layout.alignment: Qt.AlignVCenter text: { if (typeof accountPicker === "undefined" || !accountPicker.selectedAccountName) return ""; - return (accountPicker.selectedAccountId === 0 || accountPicker.selectedAccountName === "Local Account") ? "Local" : accountPicker.selectedAccountName; + return (accountPicker.selectedAccountName === "Local Account" || !accountPicker.selectedAccountName) ? "Local" : accountPicker.selectedAccountName; } color: "white" font.pixelSize: units.dp(13) diff --git a/qml/app/navigation/MenuPage.qml b/qml/app/navigation/MenuPage.qml index 33ad6efd..7dc6b565 100644 --- a/qml/app/navigation/MenuPage.qml +++ b/qml/app/navigation/MenuPage.qml @@ -189,7 +189,7 @@ Page { Layout.alignment: Qt.AlignVCenter text: { if (typeof accountPicker === "undefined" || !accountPicker.selectedAccountName) return ""; - return (accountPicker.selectedAccountId === 0 || accountPicker.selectedAccountName === "Local Account") ? "Local" : accountPicker.selectedAccountName; + return (accountPicker.selectedAccountName === "Local Account" || !accountPicker.selectedAccountName) ? "Local" : accountPicker.selectedAccountName; } color: "white" font.pixelSize: units.dp(13) @@ -352,7 +352,7 @@ Page { Controls.ToolTip.visible: collapsedAccountArea.containsMouse Controls.ToolTip.text: { if (typeof accountPicker === "undefined" || !accountPicker.selectedAccountName) return i18n.dtr("ubtms", "Account"); - var accName = (accountPicker.selectedAccountId === 0 || accountPicker.selectedAccountName === "Local Account") ? i18n.dtr("ubtms", "Local") : accountPicker.selectedAccountName; + var accName = (accountPicker.selectedAccountName === "Local Account" || !accountPicker.selectedAccountName) ? i18n.dtr("ubtms", "Local") : accountPicker.selectedAccountName; return i18n.dtr("ubtms", "Account: %1").arg(accName); } Controls.ToolTip.delay: 400 diff --git a/qml/components/base/OutlinedTextField.qml b/qml/components/base/OutlinedTextField.qml index e8f8e3e8..9b5c6cfd 100644 --- a/qml/components/base/OutlinedTextField.qml +++ b/qml/components/base/OutlinedTextField.qml @@ -15,6 +15,15 @@ Item { property alias inputMethodHints: inputField.inputMethodHints property alias validator: inputField.validator property alias readOnly: inputField.readOnly + property alias inputField: inputField + property alias inputFocus: inputField.focus + + function commit() { + if (typeof Qt !== "undefined" && Qt.inputMethod) { + Qt.inputMethod.commit(); + } + inputField.focus = false; + } signal accepted() diff --git a/qml/features/settings/pages/Account_Page.qml b/qml/features/settings/pages/Account_Page.qml index 3d7eb5f5..6b5eccf2 100644 --- a/qml/features/settings/pages/Account_Page.qml +++ b/qml/features/settings/pages/Account_Page.qml @@ -69,6 +69,10 @@ Page { text: i18n.dtr("ubtms","Save") onTriggered: { + if (typeof Qt !== "undefined" && Qt.inputMethod) { + Qt.inputMethod.commit(); + } + createAccountPage.forceActiveFocus(); handleAccountSave(); } }, @@ -88,9 +92,45 @@ Page { } function handleAccountSave() { - accountNameInput.text = (accountNameInput.text || "").trim(); - if (!accountNameInput.text) { - notifPopup.open("Error", "Account name cannot be empty", "error"); + if (typeof Qt !== "undefined" && Qt.inputMethod) { + Qt.inputMethod.commit(); + } + createAccountPage.forceActiveFocus(); + + var trimmedAccountName = accountNameInput.text.trim(); + if (!trimmedAccountName) { + notifPopup.open("Error", i18n.dtr("ubtms", "Account name cannot be empty"), "error"); + return; + } + + if (accountId === 0) { + var updateResult = Accounts.updateAccountName(0, trimmedAccountName); + if (!updateResult.success) { + if (updateResult.duplicateType === "name") { + notifPopup.open("Error", i18n.dtr("ubtms", "Account name '" + trimmedAccountName + "' already exists. Please choose a different name."), "error"); + } else { + notifPopup.open("Error", updateResult.message || i18n.dtr("ubtms", "Unable to update account."), "error"); + } + return; + } + + // If local account is active, update accountPicker and rootApp + if (typeof accountPicker !== "undefined" && accountPicker && accountPicker.selectedAccountId === 0) { + accountPicker.selectedAccountName = trimmedAccountName; + if (typeof rootApp !== "undefined" && rootApp) { + rootApp.currentAccountName = trimmedAccountName; + rootApp.globalAccountChanged(0, trimmedAccountName); + } + } + + notifPopup.open("Saved", i18n.dtr("ubtms", "Your account has been updated successfully!"), "success"); + isReadOnly = true; + + if (typeof accountsSettingsPage !== "undefined" && accountsSettingsPage) { + accountsSettingsPage.fetch_accounts(); + } else if (typeof settings !== "undefined" && settings) { + settings.fetch_accounts(); + } return; } @@ -206,6 +246,12 @@ Page { passwordInput.text = account.api_key || ""; selectedconnectwithId = account.connectwith_id || 1; + if (accId === 0) { + isReadOnly = !openInEditMode; + activeBackendAccount = false; + break; + } + // Fetch databases for this URL Utils.getDatabasesFromOdooServer(account.link, function(databases) { if (databases && databases.length > 0) { @@ -400,7 +446,13 @@ Page { width: parent.width readOnly: isReadOnly labelText: i18n.dtr("ubtms", "Account Name") + inputMethodHints: Qt.ImhNoPredictiveText text: "" + onAccepted: { + if (!isReadOnly) { + handleAccountSave(); + } + } } } } @@ -411,6 +463,7 @@ Page { Rectangle { width: parent.width height: serverCol.height + units.gu(3) + visible: accountId !== 0 color: theme.name === "Ubuntu.Components.Themes.SuruDark" ? "#1a1a1a" : "white" radius: units.gu(1) @@ -497,6 +550,7 @@ Page { width: parent.width visible: isManualDbMode labelText: i18n.dtr("ubtms", "Database Name") + inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase } } } @@ -507,7 +561,7 @@ Page { Rectangle { width: parent.width height: credCol.height + units.gu(3) - visible: activeBackendAccount + visible: activeBackendAccount && accountId !== 0 color: theme.name === "Ubuntu.Components.Themes.SuruDark" ? "#1a1a1a" : "white" radius: units.gu(1) @@ -531,6 +585,7 @@ Page { width: parent.width readOnly: isReadOnly labelText: i18n.dtr("ubtms", "Username") + inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase } InlineOptionSelector { @@ -578,7 +633,7 @@ Page { // ============================================================= Rectangle { id: syncSettingsSection - visible: activeBackendAccount + visible: activeBackendAccount && accountId !== 0 width: parent.width height: syncSettingsColumn.height + units.gu(3) color: theme.name === "Ubuntu.Components.Themes.SuruDark" ? "#1a1a1a" : "white" diff --git a/qml/features/settings/pages/Settings_Accounts.qml b/qml/features/settings/pages/Settings_Accounts.qml index 29402071..511db3cd 100644 --- a/qml/features/settings/pages/Settings_Accounts.qml +++ b/qml/features/settings/pages/Settings_Accounts.qml @@ -307,11 +307,9 @@ Page { highlightColor: theme.name === "Ubuntu.Components.Themes.SuruDark" ? "#252525" : "#e8e8e8" onClicked: { - if (model.id !== 0) { - apLayout.addPageToNextColumn(accountsSettingsPage, Qt.resolvedUrl('Account_Page.qml'), { - "accountId": model.id - }); - } + apLayout.addPageToNextColumn(accountsSettingsPage, Qt.resolvedUrl('Account_Page.qml'), { + "accountId": model.id + }); } // ── Swipe Left → Edit ── @@ -319,7 +317,7 @@ Page { actions: [ Action { iconName: "edit" - enabled: model.id !== 0 + text: i18n.dtr("ubtms", "Edit") onTriggered: { apLayout.addPageToNextColumn(accountsSettingsPage, Qt.resolvedUrl('Account_Page.qml'), { "accountId": model.id, @@ -330,13 +328,15 @@ Page { ] } - // ── Swipe Right → Log, Delete ── - trailingActions: ListItemActions { + // ── Swipe Right → Log, Delete (Hidden for Local Account) ── + trailingActions: model.id === 0 ? null : accountTrailingActions + + ListItemActions { + id: accountTrailingActions actions: [ Action { iconName: "note" text: i18n.dtr("ubtms", "Log") - enabled: model.id !== 0 onTriggered: { apLayout.addPageToNextColumn(accountsSettingsPage, Qt.resolvedUrl("SyncLog.qml"), { "recordid": model.id @@ -346,7 +346,6 @@ Page { Action { iconName: "delete" text: i18n.dtr("ubtms", "Delete") - enabled: model.id !== 0 onTriggered: { accountToDelete = model.id; accountIndexToDelete = index; diff --git a/src/daemon.py b/src/daemon.py index ff7a190f..6114315b 100644 --- a/src/daemon.py +++ b/src/daemon.py @@ -1375,7 +1375,7 @@ def sync_account(self, account, sync_direction="both"): account_pass = account.get("api_key") # Skip local account or accounts without URL - if not account_url or account_name == "Local Account": + if not account_url or account_url.startswith("local://") or account_id == 0 or account_name in ("Local Account", "Local"): log.info(f"[DAEMON] Skipping local/invalid account: {account_name}") return @@ -1630,7 +1630,7 @@ def _tick(self): account_name = account.get("name", "Unknown") # Skip local/invalid accounts - if not account.get("link") or account_name == "Local Account": + if not account.get("link") or account.get("link").startswith("local://") or account_id == 0 or account_name in ("Local Account", "Local"): continue # Resolve per-account settings (with global fallback) @@ -1677,7 +1677,7 @@ def _tick(self): for account in accounts: account_id = account["id"] account_name = account.get("name", "Unknown") - if not account.get("link") or account_name == "Local Account": + if not account.get("link") or account.get("link").startswith("local://") or account_id == 0 or account_name in ("Local Account", "Local"): continue acct_settings = get_account_sync_settings(self.app_db, account_id) if not acct_settings["autosync_enabled"]: