Skip to content
Closed
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ build
.agent
voice_to_text/lib/
.clickable
.vscode
.vscode
node_modules/
website/
67 changes: 60 additions & 7 deletions models/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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 = "";
Expand All @@ -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" : "";
}
}

Expand Down
2 changes: 1 addition & 1 deletion qml/app/AppDrawer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,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)
Expand Down
4 changes: 2 additions & 2 deletions qml/app/navigation/MenuPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,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)
Expand Down Expand Up @@ -319,7 +319,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
Expand Down
9 changes: 9 additions & 0 deletions qml/components/base/OutlinedTextField.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
65 changes: 60 additions & 5 deletions qml/features/settings/pages/Account_Page.qml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ Page {
text: i18n.dtr("ubtms","Save")

onTriggered: {
if (typeof Qt !== "undefined" && Qt.inputMethod) {
Qt.inputMethod.commit();
}
createAccountPage.forceActiveFocus();
handleAccountSave();
}
},
Expand All @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -400,7 +446,13 @@ Page {
width: parent.width
readOnly: isReadOnly
labelText: i18n.dtr("ubtms", "Account Name")
inputMethodHints: Qt.ImhNoPredictiveText
text: ""
onAccepted: {
if (!isReadOnly) {
handleAccountSave();
}
}
}
}
}
Expand All @@ -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)

Expand Down Expand Up @@ -497,6 +550,7 @@ Page {
width: parent.width
visible: isManualDbMode
labelText: i18n.dtr("ubtms", "Database Name")
inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
}
}
}
Expand All @@ -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)

Expand All @@ -531,6 +585,7 @@ Page {
width: parent.width
readOnly: isReadOnly
labelText: i18n.dtr("ubtms", "Username")
inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
}

InlineOptionSelector {
Expand Down Expand Up @@ -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"
Expand Down
19 changes: 9 additions & 10 deletions qml/features/settings/pages/Settings_Accounts.qml
Original file line number Diff line number Diff line change
Expand Up @@ -307,19 +307,17 @@ 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 ──
leadingActions: ListItemActions {
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,
Expand All @@ -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
Expand All @@ -346,7 +346,6 @@ Page {
Action {
iconName: "delete"
text: i18n.dtr("ubtms", "Delete")
enabled: model.id !== 0
onTriggered: {
accountToDelete = model.id;
accountIndexToDelete = index;
Expand Down
6 changes: 3 additions & 3 deletions src/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"]:
Expand Down
Loading