feat(nav): move Settings from the primary nav bar to the profile menu

Closes part of #21861
This commit is contained in:
Jonathan Rainville
2026-08-26 09:27:40 -04:00
parent 19d9185521
commit b5de025f6b
13 changed files with 75 additions and 70 deletions
@@ -248,7 +248,6 @@ SplitView {
}
thirdpartyServicesEnabled: ctrlThirdPartyServices.checked
profileSectionHasNotification: ctrlSettingsHasNotification.checked
onItemActivated: function (sectionType, sectionId) {
logs.logEvent("onItemActivated", ["sectionType", "sectionId"], arguments)
@@ -57,7 +57,6 @@ Item {
return ["👨🏻‍🍼", "🏃🏿‍♂️", "🌇", "🤶🏿", "🏮"]
}
profileSectionHasNotification: false
thirdpartyServicesEnabled: true
acVisible: false
@@ -193,10 +192,8 @@ Item {
verify(!!walletBtn)
tryCompare(walletBtn, "visible", true)
// Check for Settings button
const settingsBtn = findChild(controlUnderTest, "Settings-navbar")
verify(!!settingsBtn)
tryCompare(settingsBtn, "visible", true)
// Settings is not a nav item anymore, it lives in the profile menu
verify(!findChild(controlUnderTest, "Settings-navbar"))
}
function test_section_button_click() {
@@ -217,16 +214,9 @@ Item {
verify(!!walletBtn)
tryCompare(walletBtn, "checked", true)
// verify the Settings button is not checked
const settingsBtn = findChild(controlUnderTest, "Settings-navbar")
verify(!!settingsBtn)
tryCompare(settingsBtn, "checked", false)
// simulate changing the active section from outside (via mock model update)
sidebarAdaptor.sectionsModel.setActiveSection("id3") // "id" of Constants.appSection.profile
// verify that Settings is active, Wallet is not
tryCompare(settingsBtn, "checked", true)
tryCompare(walletBtn, "checked", false)
}
@@ -297,17 +287,6 @@ Item {
verify(true) // Basic validation
}
function test_profile_section_notification() {
controlUnderTest.profileSectionHasNotification = true
const settingsBtn = findChild(controlUnderTest, "Settings-navbar")
verify(!!settingsBtn)
// Settings button should show notification when profileSectionHasNotification is true
tryCompare(settingsBtn, "showBadge", true)
tryCompare(settingsBtn, "badgeVisible", true)
}
function test_community_buttons_have_object_name() {
// Look for community buttons with specific objectName
const communityBtn = findChild(controlUnderTest, "CommunityNavBarButton_Status.app")
@@ -18,6 +18,7 @@ class OnlineIdentifier(QObject):
self.automatic_button = Button(names.userContextmenu_AutomaticButton)
self.view_my_profile_button = Button(names.userContextMenu_ViewMyProfileAction)
self.copy_link_to_profile_button = QObject(names.userContextMenu_CopyLinkToProfile)
self.settings_button = Button(names.userContextMenu_SettingsAction)
self.user_name_text_label = TextLabel(names.userStatusDisplayName)
@property
+4 -5
View File
@@ -45,7 +45,6 @@ class MainLeftPanel(QObject):
self.messages_button = Button(names.chatButton)
self.communities_portal_button = Button(names.communitiesPortalButton)
self.community_template_button = Button(names.statusCommunityMainNavBarListView_CommunityNavBarButton)
self.settings_button = Button(names.settingsGearButton)
self.wallet_button = Button(names.mainWalletButton)
self.activity_center_button = Button(names.activityCenterButton)
self.market_button = Button(names.marketButton)
@@ -91,10 +90,10 @@ class MainLeftPanel(QObject):
def open_messages_screen(self):
return self.messages_button
@allure.step('Click Gear button and open Settings screen')
@open_with_retries(SettingsScreen, attempts=3, delay=3.0)
def open_settings(self):
return self.settings_button
@allure.step('Open Settings screen from the profile context menu')
def open_settings(self) -> SettingsScreen:
self.open_online_identifier().settings_button.click()
return SettingsScreen().wait_until_appears()
@allure.step('Click Activity center button and open Activity center panel')
@open_with_retries(ActivityCenter)
+4 -2
View File
@@ -59,12 +59,11 @@ chatButton = {"container": primaryNavSidebar, "objectName": "Messages-navbar", "
"visible": True}
marketButton = {"container": primaryNavSidebar, "objectName": "Market-navbar", "type": "RegularSectionButton", "visible": True}
# Second half of left main panel: communities button, settings button
# Second half of left main panel: communities button
communitiesSettingsNavBarList = {"container": statusDesktop_mainWindow, "objectName": "statusMainNavBarListView",
"type": "ListView", "visible": True}
communitiesPortalButton = {"container": primaryNavSidebar, "objectName": "Communities Portal-navbar",
"type": "PrimaryNavSidebarButton", "visible": True}
settingsGearButton = {"container": primaryNavSidebar, "objectName": "Settings-navbar", "type": "BottomSectionButton", "visible": True}
activityCenterButton = {"container": statusDesktop_mainWindow, "objectName": "Activity Center-navbar", "type": "PrimaryNavSidebarButton", "visible": True}
# Online identifier
@@ -177,6 +176,9 @@ userContextMenu_ViewMyProfileAction = {"container": statusDesktop_mainWindow_ove
userContextMenu_CopyLinkToProfile = {"container": statusDesktop_mainWindow_overlay,
"objectName": "userStatusCopyLinkAction", "type": "StatusMenuItem",
"visible": True}
userContextMenu_SettingsAction = {"container": statusDesktop_mainWindow_overlay,
"objectName": "userStatusSettingsAction", "type": "StatusMenuItem",
"visible": True}
userStatusDisplayName = {"container": onlineIdentifier, "objectName": "userStatusDisplayName",
"type": "StatusBaseText", "visible": True}
+1 -1
View File
@@ -203,7 +203,7 @@ class DeviceContext:
max_taps = 5
for tap_attempt in range(1, max_taps + 1):
if not app.is_element_visible(
app_locators.LEFT_NAV_SETTINGS, timeout=1
app_locators.PROFILE_NAV_BUTTON, timeout=1
):
self.logger.info(
"Drawer closed after %d tap(s)", tap_attempt - 1
+3 -3
View File
@@ -16,9 +16,6 @@ class AppLocators(BaseLocators):
LEFT_NAV_COMMUNITIES = BaseLocators.xpath(
"//*[contains(@resource-id, 'Communities Portal-navbar')]"
)
LEFT_NAV_SETTINGS = BaseLocators.xpath(
"//*[contains(@resource-id, 'Settings-navbar')]"
)
# Home dock (visible only on Home)
HOME_DOCK_CONTAINER = BaseLocators.xpath(
@@ -45,6 +42,9 @@ class AppLocators(BaseLocators):
SHARE_PROFILE_ACTION = BaseLocators.xpath(
"//*[contains(@resource-id,'userStatusShareProfileAction')]"
)
SETTINGS_ACTION = BaseLocators.xpath(
"//*[contains(@resource-id,'userStatusSettingsAction')]"
)
PROFILE_MENU_CONTAINER = BaseLocators.xpath(
"//*[contains(@resource-id,'ProfileMenu')]"
)
+33 -14
View File
@@ -24,7 +24,6 @@ class App(BasePage):
"market": self.locators.LEFT_NAV_MARKET,
"messaging": self.locators.LEFT_NAV_MESSAGES,
"communities": self.locators.LEFT_NAV_COMMUNITIES,
"settings": self.locators.LEFT_NAV_SETTINGS,
}
for name, locator in mapping.items():
el = self.find_element_safe(locator, timeout=1)
@@ -149,7 +148,7 @@ class App(BasePage):
the app's QML picks layout based on width, so wide tablets in portrait
still get the always-visible side-nav.
"""
if self.is_element_visible(self.locators.LEFT_NAV_SETTINGS, timeout=2):
if self.is_element_visible(self.locators.PROFILE_NAV_BUTTON, timeout=2):
return True
from utils.screen_identity import dismiss_introduce_yourself
@@ -164,11 +163,11 @@ class App(BasePage):
return True
if self.is_element_visible(self.locators.LEFT_NAV_ANY, timeout=1):
return self.is_element_visible(self.locators.LEFT_NAV_SETTINGS, timeout=5)
return self.is_element_visible(self.locators.PROFILE_NAV_BUTTON, timeout=5)
# Phase 1: unwind deep navigation stack via back button
for _ in range(5):
if self.is_element_visible(self.locators.LEFT_NAV_SETTINGS, timeout=1):
if self.is_element_visible(self.locators.PROFILE_NAV_BUTTON, timeout=1):
return True
if not self.is_element_visible(
self.locators.TOOLBAR_BACK_BUTTON, timeout=1
@@ -176,7 +175,7 @@ class App(BasePage):
break
self.safe_click(self.locators.TOOLBAR_BACK_BUTTON, timeout=2)
if self.is_element_visible(self.locators.LEFT_NAV_SETTINGS, timeout=1):
if self.is_element_visible(self.locators.PROFILE_NAV_BUTTON, timeout=1):
return True
# Phase 2: drag the drawer handle to open the nav drawer
@@ -185,7 +184,7 @@ class App(BasePage):
break
self.logger.debug("Nav drawer open attempt %d did not reveal nav", attempt + 1)
return self.is_element_visible(self.locators.LEFT_NAV_SETTINGS, timeout=5)
return self.is_element_visible(self.locators.PROFILE_NAV_BUTTON, timeout=5)
# Locator for the drawer swipe-indicator handle visible in portrait mode.
NAV_DRAWER_HANDLE = (
@@ -269,16 +268,36 @@ class App(BasePage):
return False
def click_settings_button(self) -> bool:
self.logger.info("Clicking Settings button")
if self.active_section() == "settings":
self.logger.info("Opening Settings from the profile menu")
from utils.screen_identity import SCREEN_ANCHORS
if self.is_element_visible(SCREEN_ANCHORS["settings"], timeout=1):
self.logger.info("Already in Settings section — skipping nav")
return True
from utils.screen_identity import SCREEN_ANCHORS
return self._click_drawer_nav_with_verify(
nav_locator=self.locators.LEFT_NAV_SETTINGS,
landmark_locator=SCREEN_ANCHORS["settings"],
nav_name="Settings",
)
for attempt in range(1, 4):
if not self._ensure_main_nav_visible():
self.logger.warning(
"Nav not visible on Settings attempt %d", attempt
)
continue
if not self.safe_click(self.locators.PROFILE_NAV_BUTTON, timeout=5):
self.logger.warning(
"Profile nav button tap failed on Settings attempt %d", attempt
)
continue
if not self.safe_click(self.locators.SETTINGS_ACTION, timeout=5):
self.logger.warning(
"Settings menu item tap failed on attempt %d", attempt
)
continue
if self.is_element_visible(SCREEN_ANCHORS["settings"], timeout=15):
return True
self.logger.warning(
"Settings screen anchor not visible after attempt %d", attempt
)
self.dump_page_source("settings_nav_failed")
return False
def _click_drawer_nav_with_verify(
self,
+9 -11
View File
@@ -1046,6 +1046,14 @@ Item {
homePageLoader.item.focusSearch()
}
function openSettingsRoot() {
profileLoader.settingsSubSubsection = -1
profileLoader.settingsSubsection = appMain.isPortraitMode ? -1 : Constants.settingsSubsection.profile
appMain.rootStore.setActiveSectionBySectionType(Constants.appSection.profile)
if (profileLoader.item)
profileLoader.item.currentIndex = StatusSectionLayout.LeftPanel
}
function maybeDisplayIntroduceYourselfPopup() {
if (!appMainLocalSettings.introduceYourselfPopupSeen
&& appMain.ownContactDetails?.displayName === "") {
@@ -2623,17 +2631,6 @@ Item {
communityPopupMenu: communityContextMenuComponent
profileSectionHasNotification: {
if (contactsModelAdaptor?.pendingReceivedRequestContacts?.ModelCount.count > 0) // pending contact request
return true
if (appMain.mainReady
&& !appMain.privacyStore?.mnemonicBackedUp
&& !appMain.profileStore?.userDeclinedBackupBanner) // seedphrase not backed up (removed)
return true
if (d.syncingBadgeCount > 0) // sync entries
return true
return false
}
thirdpartyServicesEnabled: appMain.rootStore.thirdpartyServicesEnabled
profileLoading: !appMain.mainReady
@@ -2644,6 +2641,7 @@ Item {
onSetCurrentUserStatusRequested: status => appMain.rootStore.setCurrentUserStatus(status)
onViewProfileRequested: pubKey => Global.openProfilePopup(pubKey)
onShareOwnProfileRequested: Global.shareProfileDialogRequested(appMain.ownContactDetails.publicKey)
onSettingsRequested: d.openSettingsRoot()
onItemActivated: function(sectionType, sectionId) {
// Ensure Activity Center Panel is closed when manual navigation done
@@ -83,10 +83,6 @@ SQUtils.QObject {
roleName: "sectionType"
value: Constants.appSection.qrCodeScanner
}
ValueFilter {
roleName: "sectionType"
value: Constants.appSection.profile
}
}
}
+3 -6
View File
@@ -52,7 +52,6 @@ Control {
property Component communityPopupMenu // required property var model
required property bool profileSectionHasNotification
required property bool thirdpartyServicesEnabled
property bool profileLoading: false
@@ -74,6 +73,7 @@ Control {
signal activityCenterRequested(bool shouldShow)
signal viewProfileRequested(string pubKey)
signal shareOwnProfileRequested
signal settingsRequested
signal setCurrentUserStatusRequested(int status)
Component.onCompleted: d.snapToMode()
@@ -293,6 +293,7 @@ Control {
onSetCurrentUserStatusRequested: (status) => root.setCurrentUserStatusRequested(status)
onViewProfileRequested: (pubKey) => root.viewProfileRequested(pubKey)
onShareOwnProfileRequested: root.shareOwnProfileRequested()
onSettingsRequested: root.settingsRequested()
}
}
}
@@ -437,11 +438,7 @@ Control {
}
component BottomSectionButton: RegularSectionButton {
showBadge: {
if (model.sectionType === Constants.appSection.profile)
return root.profileSectionHasNotification
return model.hasNotification
}
showBadge: model.hasNotification
}
component SidebarListView: ListView {
@@ -27,6 +27,7 @@ StatusIconTabButton {
signal viewProfileRequested(string pubKey)
signal shareOwnProfileRequested
signal settingsRequested
signal setCurrentUserStatusRequested(int status)
name: root.name
@@ -100,6 +101,7 @@ StatusIconTabButton {
onViewProfileRequested: root.viewProfileRequested(root.pubKey)
onShareOwnProfileRequested: root.shareOwnProfileRequested()
onSettingsRequested: root.settingsRequested()
onCopyLinkRequested: ClipboardUtils.setText(root.getLinkToProfileFn(root.pubKey))
onSetCurrentUserStatusRequested: (status) => root.setCurrentUserStatusRequested(status)
}
@@ -29,6 +29,7 @@ StatusDropdown {
signal viewProfileRequested
signal copyLinkRequested
signal shareOwnProfileRequested
signal settingsRequested
signal setCurrentUserStatusRequested(int status)
implicitWidth: 400
@@ -142,6 +143,18 @@ StatusDropdown {
}
}
ActionWrapper {
action: StatusAction {
objectName: "userStatusSettingsAction"
text: qsTr("Settings")
icon.name: "settings"
onTriggered: {
root.settingsRequested()
root.close()
}
}
}
StatusMenuSeparator {
Layout.fillWidth: true
}