diff --git a/storybook/pages/ActivityCenterPanelPage.qml b/storybook/pages/ActivityCenterPanelPage.qml new file mode 100644 index 00000000000..699d8779990 --- /dev/null +++ b/storybook/pages/ActivityCenterPanelPage.qml @@ -0,0 +1,263 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import StatusQ.Core.Theme + +import AppLayouts.ActivityCenter.helpers +import AppLayouts.ActivityCenter.panels + +import Storybook +import Models + +import QtModelsToolkit +import utils + +SplitView { + id: root + + Logs { id: logs } + + // Since the notifications model mock is defined as `ListModel`, once the `onAppend` happen, attachments role is converted + // from a plain array to a submodel. That's why it cannot be defined directly on the model definition file. + // Here it is a manual definition of the `attachments` role so that the plain array is set to the items needed. + ObjectProxyModel { + id: notificationsModelMock + + sourceModel: NotificationsModel{} + + delegate: QtObject { + readonly property var attachments: { + if (model.chatKey === "zQ3shuV7mZextijeBSDpgaq2EvebPGEeCrkH9AgmpCM7JTAAA") // TODO: It will be the notification type instead + return [ + "https://picsum.photos/320/240?3", + "https://picsum.photos/320/240?4", + "https://picsum.photos/320/240?5", + "https://picsum.photos/320/240?6", + "https://picsum.photos/320/240?7", + "https://picsum.photos/320/240?8", + "https://picsum.photos/320/240?1" + ] + else if (model.chatKey === "zQ3142hUdnpxi26rLmgdUwNxHgcbcYFW75JcSvVych58QVXXT") // TODO: It will be the notification type instead + return [ + "https://picsum.photos/320/240?1", + "https://picsum.photos/320/240?2", + "https://picsum.photos/320/240?9" + ] + + else if (model.chatKey === "zAssshuV7mZextijeBSDpgaq2EvebPGEeCrkH9AgmpCM7Jss12") // TODO: It will be the notification type instead + return [ + "https://picsum.photos/320/240?10", + "https://picsum.photos/320/240?9" + ] + else if (model.chatKey === "zAMNAuV7mZextijeBSDpgaq2EvebPGEeCrkH9AgmpCM7JTXcA") // TODO: It will be the notification type instead + return [ + "https://picsum.photos/320/240?11" + ] + return [] + } + } + + expectedRoles: "chatKey" // TODO: It will be the notification type instead + exposedRoles: "attachments" + } + + SplitView { + orientation: Qt.Vertical + SplitView.fillWidth: true + + Rectangle { + SplitView.fillWidth: true + SplitView.fillHeight: true + + color: Theme.palette.baseColor4 + + Rectangle { + color: Theme.palette.baseColor2 + radius: 12 + anchors.centerIn: parent + width: slider.value + height: sliderHeight.value + + ActivityCenterPanel { + property int currentActiveGroup: ActivityCenterTypes.ActivityCenterGroup.All + + anchors.fill: parent + + backgroundColor: parent.color + + hasAdmin: admin.checked + hasMentions: mentions.checked + hasReplies: replies.checked + hasContactRequests: contactRequests.checked + hasMembership: membership.checked + activeGroup: currentActiveGroup + + hasUnreadNotifications: unreadNotifications.checked + readNotificationsStatus: read.checked ? ActivityCenterTypes.ActivityCenterReadType.Read : + unread.checked ? ActivityCenterTypes.ActivityCenterReadType.Unread : + ActivityCenterTypes.ActivityCenterReadType.All + notificationsModel: (noNotifications.checked || unread.checked) ? null : notificationsModelMock + newsSettingsStatus: newsSettingsTurnOff.checked ? Constants.settingsSection.notifications.turnOffValue : Constants.settingsSection.notifications.sendAlertsValue + newsEnabledViaRSS: enabledViaRSS.checked + + onMoreOptionsRequested: logs.logEvent("ActivityCenterPanel::onMoreOptionsRequested") + onCloseRequested: logs.logEvent("ActivityCenterPanel::onCloseRequested") + onMarkAllAsReadRequested: { + logs.logEvent("ActivityCenterPanel::onMarkAllAsReadRequested") + unreadNotifications.checked = false + } + onHideShowNotificationsRequested: { + logs.logEvent("ActivityCenterPanel::onHideShowNotificationsRequested: " + hideReadNotifications) + if(hideReadNotifications) + read.checked = true + else + unread.checked = true + } + onSetActiveGroupRequested: (group) => { + logs.logEvent("ActivityCenterPanel::onSetActiveGroupRequested: " + group) + currentActiveGroup = group + } + onNotificationClicked: (index) => logs.logEvent("ActivityCenterPanel::onNotificationClicked: " + index) + onFetchMoreNotificationsRequested: logs.logEvent("ActivityCenterPanel::onFetchMoreNotificationsRequested") + onEnableNewsViaRSSRequested: { + logs.logEvent("ActivityCenterPanel::onEnableNewsViaRSSRequested") + enabledViaRSS.checked = !enabledViaRSS.checked + } + onEnableNewsRequested: { + logs.logEvent("ActivityCenterPanel::onEnableNewsRequested") + newsSettingsTurnOff.checked = !newsSettingsTurnOff.checked + } + } + } + } + + LogsAndControlsPanel { + id: logsAndControlsPanel + + SplitView.minimumHeight: 100 + SplitView.preferredHeight: 200 + + logsView.logText: logs.logText + } + } + + Pane { + SplitView.minimumWidth: 300 + SplitView.preferredWidth: 300 + + ColumnLayout { + Label { + Layout.fillWidth: true + text: "Panel dynamic width:" + font.bold: true + } + Slider { + id: slider + Layout.fillWidth: true + value: 368 + from: 250 + to: 600 + } + + Label { + Layout.fillWidth: true + text: "Panel dynamic height:" + font.bold: true + } + Slider { + id: sliderHeight + Layout.fillWidth: true + value: 650 + from: 400 + to: 800 + } + + Label { + Layout.fillWidth: true + text: "Type of notifications:" + font.bold: true + } + + CheckBox { + id: admin + Layout.fillWidth: true + text: "Has admin notifications?" + } + + CheckBox { + id: mentions + Layout.fillWidth: true + text: "Has mentions notifications?" + } + + CheckBox { + id: replies + Layout.fillWidth: true + text: "Has replies notifications?" + } + + CheckBox { + id: contactRequests + Layout.fillWidth: true + text: "Has contact requests notifications?" + } + + CheckBox { + id: membership + Layout.fillWidth: true + text: "Has membership notifications?" + } + + Label { + Layout.fillWidth: true + text: "News Feed Settings" + font.bold: true + } + + CheckBox { + id: newsSettingsTurnOff + Layout.fillWidth: true + text: "Turn Off Settings" + } + + CheckBox { + id: enabledViaRSS + Layout.fillWidth: true + text: "Enabled Via RSS?" + } + + Label { + Layout.fillWidth: true + text: "Read Status" + font.bold: true + } + + RadioButton { + id: read + text: "Read" + checked: true + } + RadioButton { + id: unread + text: "Unread" + } + RadioButton { + id: noNotifications + text: "No notifications" + } + + CheckBox { + id: unreadNotifications + Layout.fillWidth: true + text: "Has unread nontificaitons?" + checked: true + } + } + } +} + +// category: Panels +// status: good +// https://www.figma.com/design/SGyfSjxs5EbzimHDXTlj8B/Qt-Responsive---v?node-id=1868-52013&m=dev +// https://www.figma.com/design/SGyfSjxs5EbzimHDXTlj8B/Qt-Responsive---v?node-id=1902-48455&m=dev diff --git a/storybook/pages/ActivityNotificationNewDevicePage.qml b/storybook/pages/ActivityNotificationNewDevicePage.qml index 2d33f57851f..cd46018e073 100644 --- a/storybook/pages/ActivityNotificationNewDevicePage.qml +++ b/storybook/pages/ActivityNotificationNewDevicePage.qml @@ -45,15 +45,15 @@ ActivityNotificationBaseLayout { RadioButton { text: "Received" checked: true - onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.ActivityCenterNotificationType.NewInstallationReceived + onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.NotificationType.NewInstallationReceived } RadioButton { text: "Created" - onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.ActivityCenterNotificationType.NewInstallationCreated + onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.NotificationType.NewInstallationCreated } } - Component.onCompleted: baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.ActivityCenterNotificationType.NewInstallationReceived + Component.onCompleted: baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.NotificationType.NewInstallationReceived } } // category: Activity Center diff --git a/storybook/pages/ActivityNotificationTransferOwnershipPage.qml b/storybook/pages/ActivityNotificationTransferOwnershipPage.qml index 683eca74878..d7a6db79502 100644 --- a/storybook/pages/ActivityNotificationTransferOwnershipPage.qml +++ b/storybook/pages/ActivityNotificationTransferOwnershipPage.qml @@ -33,33 +33,33 @@ ActivityNotificationBaseLayout { RadioButton { text: "Pending" checked: true - onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.ActivityCenterNotificationType.OwnerTokenReceived + onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.NotificationType.OwnerTokenReceived } RadioButton { text: "Declined" - onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.ActivityCenterNotificationType.OwnershipDeclined + onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.NotificationType.OwnershipDeclined } RadioButton { text: "Succeded" - onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.ActivityCenterNotificationType.OwnershipReceived + onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.NotificationType.OwnershipReceived } RadioButton { text: "Failed" - onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.ActivityCenterNotificationType.OwnershipFailed + onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.NotificationType.OwnershipFailed } RadioButton { text: "No longer control node" - onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.ActivityCenterNotificationType.OwnershipLost + onCheckedChanged: if(checked) baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.NotificationType.OwnershipLost } } } - Component.onCompleted: baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.ActivityCenterNotificationType.OwnerTokenReceived + Component.onCompleted: baseEditor.notificationBaseMock.notificationType = ActivityCenterTypes.NotificationType.OwnerTokenReceived } // category: Activity Center // status: good diff --git a/storybook/resources.qrc b/storybook/resources.qrc index 5f6483ac33f..9f50804d292 100644 --- a/storybook/resources.qrc +++ b/storybook/resources.qrc @@ -1,5 +1,6 @@ main.qml + pages/ActivityCenterPanelPage.qml diff --git a/storybook/src/Models/NotificationsModel.qml b/storybook/src/Models/NotificationsModel.qml new file mode 100644 index 00000000000..f2e46b25bdb --- /dev/null +++ b/storybook/src/Models/NotificationsModel.qml @@ -0,0 +1,664 @@ +import QtQuick + +import StatusQ.Core.Theme + +import AppLayouts.ActivityCenter.helpers + +// *** +// This model provides examples of all notification types supported by the Status app, +// showcasing the different visual states and UI variations for each notification card. +// *** +// +// Here is an example of the complete set of properties that a `Notification model` can contain: +//{ +// // Card states related +// unread: false, +// selected: false, +// +// // Avatar related +// avatarSource: "https://i.pravatar.cc/128?img=8", +// badgeIconName: "action-mention", +// isCircularAvatar: true, +// +// // Header row related +// title: "Notification 2", +// chatKey: "zQ3saskd11lfkjs1dkf5Rj9", +// isContact: true, +// trustIndicator: 0, +// +// // Context row related +// primaryText: "Communities", +// iconName: "communities", +// secondaryText: "Channel 12", +// separatorIconName: "arrow-next", +// +// // Action text +// actionText: "Action Text", +// +// // Content block related +// preImageSource: "https://picsum.photos/320/240?6", +// preImageRadius: 8, +// content: "Some notification description that can be long and long and long", +// attachments: [ +// "https://picsum.photos/320/240?1", +// "https://picsum.photos/320/240?2", +// "https://picsum.photos/320/240?9" +// ], +// +// // Timestamp related +// timestamp: 1765799225000 +//} +// +ListModel { + id: root + + readonly property var data: [ + { + // MENTION IN 1:1 TYPE + notificationType: ActivityCenterTypes.NotificationType.Mention, + + // Card states related + unread: false, + selected: false, + + // Avatar related + avatarSource: "https://i.pravatar.cc/128?img=45", + badgeIconName: "action-mention", + isCircularAvatar: true, + + // Header row related + title: "anna.eth", + chatKey: "zQ3shuV7mZextijeBSDpgaq2EvebPGEeCrkH9AgmpCM7JTAAA", + isContact: false, + trustIndicator: 0, + + // Content block related + content: "hey, @robert.eth, " + + "Do we still plan to ship this with v2.1 or postpone to the next release cycle?", + + // Timestamp related + timestamp: 1765799225000 + }, + { + // REPLY 1:1 TYPE + notificationType: ActivityCenterTypes.NotificationType.Reply, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: "https://i.pravatar.cc/128?img=5", + badgeIconName: "action-reply", + isCircularAvatar: true, + + // Header row related + title: "anna.eth with long nickname", + chatKey: "zQ3142hUdnpxi26rLmgdUwNxHgcbcYFW75JcSvVych58QVXXT", + isContact: true, + trustIndicator: 0, + + // Content block related + content: "hey, Do we still plan to ship this with v2.1 or postpone to the next release cycle? we’re discussed it on the last meet", + + // Timestamp related + timestamp: 1729799225000 + }, + { + // CONTACT REQUEST TYPE + notificationType: ActivityCenterTypes.NotificationType.ContactRequest, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: "https://i.pravatar.cc/128?img=15", + badgeIconName: "action-add", + isCircularAvatar: true, + + // Header row related + title: "simon-dev.eth", + chatKey: "z1425uV7mZextijeBSDpgaq2EvebPGEeCrkH9AgmpCM7JTp7A", + isContact: false, + trustIndicator: 0, + + // Action text + actionText: "New contact request", + + // Content block related + content: "Hey! I came across your profile and thought it’d be nice to connect. Always happy to meet new people here 🙂", + + // Timestamp related + timestamp: 1765909333000 + }, + { + // CONTACT REMOVED TYPE + notificationType: ActivityCenterTypes.NotificationType.ContactRemoved, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: "https://i.pravatar.cc/128?img=20", + badgeIconName: "action-warn", + isCircularAvatar: true, + + // Header row related + title: "Sunshine", + chatKey: "zQ3shgUZD14523iavJnT8rMhkzZ1RzLdioS2J6dZyVp3JosEQ", + isContact: true, + trustIndicator: 1, + + // Action text + actionText: "Removed you from contacts", + + // Timestamp related + timestamp: 1765329333000 + }, + { + // NEW PRIVATE GROUP CHAT TYPE + notificationType: ActivityCenterTypes.NotificationType.NewPrivateGroupChat, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: "https://i.pravatar.cc/128?img=8", + badgeIconName: "action-add", + isCircularAvatar: true, + + // Header row related + title: "rockstar.eth", + chatKey: "z0000uV7mZextijeBSDpgaq2EvebPGEeCrkH9AgmpCM7JTAAA", + isContact: true, + trustIndicator: 2, + + // Context row related + primaryText: "Summer Vacation", + + // Action text + actionText: "You're added to private group chat", + + // Timestamp related + timestamp: 1665909333000 + }, + { + // MENTION IN GROUP CHAT TYPE + notificationType: ActivityCenterTypes.NotificationType.Mention, + + // Card states related + unread: false, + selected: false, + + // Avatar related + avatarSource: "https://i.pravatar.cc/128?img=1", + badgeIconName: "action-mention", + isCircularAvatar: true, + + // Header row related + title: "anna.eth", + chatKey: "zAMNAuV7mZextijeBSDpgaq2EvebPGEeCrkH9AgmpCM7JTXcA", + isContact: true, + trustIndicator: 1, + + // Context row related + primaryText: "Summer Vacation", + + // Content block related + content: "Hey @rockstar.eth 👋 just wanted to check something with you.", + + // Timestamp related + timestamp: 1699009333000 + }, + { + // REPLY IN GROUP CHAT TYPE + notificationType: ActivityCenterTypes.NotificationType.Reply, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: "https://i.pravatar.cc/128?img=21", + badgeIconName: "action-reply", + isCircularAvatar: true, + + // Header row related + title: "Buddy", + chatKey: "zAMZAuV7mZextijeBSDpgaq2EvebPGEeCrkH9AgmpCM7JTXcA", + isContact: false, + trustIndicator: 0, + + // Context row related + primaryText: "UI Group Chat", + + // Content block related + content: "Hi Pal! Saw your message and wanted to reply real quick.", + + // Timestamp related + timestamp: 1755329333000 + }, + { + // MENTION IN COMMUNITY TYPE + notificationType: ActivityCenterTypes.NotificationType.Mention, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.cryptPunks, + badgeIconName: "action-mention", + isCircularAvatar: true, + + // Header row related + title: "Mate-Mate", + chatKey: "zAQshuV7mZextijeBSDpgaq2EvebPGEeCrkH9AgmpCM7JTA12", + isContact: true, + trustIndicator: 0, + + // Context row related + primaryText: "CryptoPunks", + iconName: "communities", + secondaryText: "#design", + separatorIconName: "arrow-next", + + // Content block related + content: "@pepe.eth I’ve just mentioned you in a conversation you might find interesting.", + + // Timestamp related + timestamp: 1765799225000 + }, + { + // REPLY IN COMMUNITY TYPE + notificationType: ActivityCenterTypes.NotificationType.Reply, + + // Card states related + unread: false, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.cryptPunks, + badgeIconName: "action-reply", + isCircularAvatar: true, + + // Header row related + title: "Friend", + chatKey: "zAssshuV7mZextijeBSDpgaq2EvebPGEeCrkH9AgmpCM7Jss12", + isContact: true, + trustIndicator: 1, + + // Context row related + primaryText: "CryptoPunks", + iconName: "communities", + secondaryText: "#design", + separatorIconName: "arrow-next", + + // Content block related + content: "Do we still plan to ship this with v2.1, or should we postpone it to the next release cycle? + From my side, I think it would be good to clarify this soon so we can align expectations and avoid last-minute changes. Depending on the scope and remaining work, we can either lock it down for v2.1 or consciously move it to the next iteration and treat it as a follow-up improvement.", + + // Timestamp related + timestamp: 1735799225000 + }, + { + // INVITATION TO COMMUNITY TYPE + notificationType: ActivityCenterTypes.NotificationType.CommunityInvitation, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.socks, + badgeIconName: "action-add", + isCircularAvatar: true, + + // Header row related + title: "anna.eth", + chatKey: "z123shuV7mZextijeBSDpgaq2EvebPGEeCrkH9AgmpCM7Jsaaa", + isContact: false, + trustIndicator: 1, + + // Context row related + primaryText: "Socks Super Long Long Community Name ", + iconName: "communities", + secondaryText: "#design", + separatorIconName: "arrow-next", + + // Action text + actionText: "Invitation to join community", + + // Timestamp related + timestamp: 1745799225000 + }, + { + // MEMBERSHIP REQUEST TO COMMUNITY TYPE (Five states: `pending`, `accepted`, `declined, `acceptedPending`, `declinedPending`) + notificationType: ActivityCenterTypes.NotificationType.CommunityMembershipRequest, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.socks, + badgeIconName: "action-admin", + isCircularAvatar: true, + + // Context row related + primaryText: "Socks Super Long Long Community Name ", + iconName: "communities", + + // Action text + actionText: "@pepe.eth requested membership in your community", + + // Timestamp related + timestamp: 1755799225000 + }, + { + // MEMBERSHIP REQUEST ACCEPTED TO COMMUNITY TYPE (Three states: `pending`, `accepted`, `declined`) + notificationType: ActivityCenterTypes.NotificationType.CommunityRequest, + + // Card states related + unread: false, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.socks, + badgeIconName: "action-check", + isCircularAvatar: true, + + // Context row related + primaryText: "Socks Community Name ", + iconName: "communities", + + // Action text + actionText: "Request to join community accepted", + + // Timestamp related + timestamp: 1765799225000 + }, + { + // KICKED FROM COMMUNITY TYPE + notificationType: ActivityCenterTypes.NotificationType.CommunityKicked, + + // Card states related + unread: false, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.socks, + badgeIconName: "action-warn", + isCircularAvatar: true, + + // Context row related + primaryText: "Socks Super Long Long Community Name ", + iconName: "communities", + + // Action text + actionText: "You have been kicked out of community", + + // Timestamp related + timestamp: 1766899225000 + }, + { + // COMMUNITY TOKEN RECEIVED TYPE: + // Assets and Collectibles are now treated the same way here. + // This notification will consistently trigger navigation to wallet transaction activity. + notificationType: ActivityCenterTypes.NotificationType.CommunityTokenReceived, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.dribble, + badgeIconName: "action-coin", + isCircularAvatar: true, + + // Context row related + primaryText: "Dribble", + iconName: "communities", + + // Action text + actionText: "You're received a token in community", + + // Timestamp related + timestamp: 1765699225000 + }, + { + // FIRST COMMUNITY TOKEN RECEIVED TYPE + notificationType: ActivityCenterTypes.NotificationType.FirstCommunityTokenReceived, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.dribble, + badgeIconName: "action-coin", + isCircularAvatar: true, + + // Context row related + primaryText: "Dribble", + iconName: "communities", + + // Action text + actionText: "You're received a first community token", + + // Timestamp related + timestamp: 1765688225000 + }, + { + // BANNED FROM COMMUNITY TYPE + notificationType: ActivityCenterTypes.NotificationType.CommunityBanned, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.dribble, + badgeIconName: "action-warn", + isCircularAvatar: true, + + // Context row related + primaryText: "Dribble", + iconName: "communities", + + // Action text + actionText: "You were banned from community", + + // Timestamp related + timestamp: 1766988225000 + }, + { + // UNBANNED FROM COMMUNITY TYPE + notificationType: ActivityCenterTypes.NotificationType.CommunityUnbanned, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.dribble, + badgeIconName: "action-check", + isCircularAvatar: true, + + // Context row related + primaryText: "Dribble", + iconName: "communities", + + // Action text + actionText: "You have been unbanned in community", + + // Timestamp related + timestamp: 1760988225000 + }, + { + // OWNER TOKEN RECEIVED TYPE + notificationType: ActivityCenterTypes.NotificationType.OwnerTokenReceived, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.spotify, + badgeIconName: "action-admin", + isCircularAvatar: true, + + // Context row related + primaryText: "Music and Sound", + iconName: "communities", + + // Action text + actionText: "You received the owner token from @robert.eth", + + // Timestamp related + timestamp: 1760999225000 + }, + { + // OWNERSHIP RECEIVED TYPE + notificationType: ActivityCenterTypes.NotificationType.OwnershipReceived, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.spotify, + badgeIconName: "action-admin", + isCircularAvatar: true, + + // Context row related + primaryText: "Music and Sound", + iconName: "communities", + + // Action text + actionText: "You are now the owner of the community", + + // Timestamp related + timestamp: 1760859225000 + }, + { + // OWNERSHIP LOST TYPE + notificationType: ActivityCenterTypes.NotificationType.OwnershipLost, + + // Card states related + unread: false, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.spotify, + badgeIconName: "action-admin", + isCircularAvatar: true, + + // Context row related + primaryText: "Music and Sound", + iconName: "communities", + + // Action text + actionText: "You no longer control the community", + + // Timestamp related + timestamp: 1770859225000 + }, + { + // OWNERSHIP TRANSFER FAILED TYPE + notificationType: ActivityCenterTypes.NotificationType.OwnershipFailed, + + // Card states related + unread: false, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.spotify, + badgeIconName: "action-admin", + isCircularAvatar: true, + + // Context row related + primaryText: "Music and Sound", + iconName: "communities", + + // Action text + actionText: "Ownership transfer failed", + + // Timestamp related + timestamp: 1767759225000 + }, + { + // OWNERSHIP TRANSFER DECLINED TYPE + notificationType: ActivityCenterTypes.NotificationType.OwnershipDeclined, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: ModelsData.icons.spotify, + badgeIconName: "action-admin", + isCircularAvatar: true, + + // Context row related + primaryText: "Music and Sound", + iconName: "communities", + + // Action text + actionText: "Ownership transfer declined", + + // Timestamp related + timestamp: 1767785225000 + }, + { + // SYSTEM - NEW INSTALLATION TYPE + notificationType: ActivityCenterTypes.NotificationType.NewInstallationReceived, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: Assets.png("status-logo-icon"), + isCircularAvatar: false, + + + // Header row related + title: "Status", + + // Content block related + content: "New installation received from iPhoneABC", + + // Timestamp related + timestamp: 1759995225000 + }, + { + // SYSTEM - NEWS ARTICLE TYPE + notificationType: ActivityCenterTypes.NotificationType.ActivityCenterNotificationTypeNews, + + // Card states related + unread: true, + selected: false, + + // Avatar related + avatarSource: Assets.png("status-logo-icon"), + isCircularAvatar: false, + + // Header row related + title: "Status", + + // Content block related + preImageSource: "https://picsum.photos/320/240?6", + preImageRadius: 8, + content: "Update on notifications section will be rolled out to all users next week. Be prepared!", + + // Timestamp related + timestamp: 1756887225000 + } + ] + + Component.onCompleted: append(data) +} diff --git a/storybook/src/Models/qmldir b/storybook/src/Models/qmldir index 0b71c1af6d2..cf842025b26 100644 --- a/storybook/src/Models/qmldir +++ b/storybook/src/Models/qmldir @@ -14,6 +14,7 @@ LinkPreviewModel 1.0 LinkPreviewModel.qml LoginAccountsModel 1.0 LoginAccountsModel.qml MintedTokensModel 1.0 MintedTokensModel.qml ManageCollectiblesModel 1.0 ManageCollectiblesModel.qml +NotificationsModel 1.0 NotificationsModel.qml ReactionsModels 1.0 ReactionsModels.qml RecipientModel 1.0 RecipientModel.qml TokenListsModel 1.0 TokenListsModel.qml diff --git a/ui/StatusQ/src/StatusQ/Components/StatusCommunityCard.qml b/ui/StatusQ/src/StatusQ/Components/StatusCommunityCard.qml index 644c98c6a06..3694d08e902 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusCommunityCard.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusCommunityCard.qml @@ -402,7 +402,7 @@ Rectangle { id: tagsListComponent StatusRollArea { implicitWidth: d.cardWidth - arrowsGradientColor: d.cardColor + gradientColor: d.cardColor // TODO: Replace by `StatusListItemTagRow` - To be done! content: Row { diff --git a/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageImageAlbum.qml b/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageImageAlbum.qml index 39c22991a64..0b2f488f27a 100644 --- a/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageImageAlbum.qml +++ b/ui/StatusQ/src/StatusQ/Components/private/statusMessage/StatusMessageImageAlbum.qml @@ -45,7 +45,9 @@ RowLayout { QtObject { id: d - readonly property int totalAlbumItems: root.album.length + readonly property int totalAlbumItems: root.album && root.album.length !== undefined + ? root.album.length + : 0 } spacing: 9 diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusNavigationButton.qml b/ui/StatusQ/src/StatusQ/Controls/StatusNavigationButton.qml index bf1333d0dea..cee6a211a5b 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusNavigationButton.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusNavigationButton.qml @@ -9,16 +9,17 @@ Button { property color gradientColor: Theme.palette.statusAppLayout.backgroundColor property bool navigateForward: false + property bool showIcon: true - width: height * 2 + width: root.showIcon ? height * 2 : Theme.bigPadding padding: 0 hoverEnabled: true background: Rectangle { gradient: Gradient { orientation: Gradient.Horizontal - GradientStop { position: navigateForward ? 0.0 : 1.0; color: "transparent" } - GradientStop { position: 0.5; color: root.gradientColor } + GradientStop { position: 0.0; color: navigateForward ? "transparent" : root.gradientColor } + GradientStop { position: 1.0; color: navigateForward ? root.gradientColor : "transparent" } } } @@ -30,9 +31,9 @@ Button { width: parent.height height: width color: Theme.palette.primaryColor1 + visible: root.showIcon } - // otherwise there is no pointing hand cursor when button is hovered StatusMouseArea { anchors.fill: parent diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusScrollBar.qml b/ui/StatusQ/src/StatusQ/Controls/StatusScrollBar.qml index db6df7118f1..c40eb0d774a 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusScrollBar.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusScrollBar.qml @@ -43,7 +43,9 @@ T.ScrollBar { implicitWidth: 14 implicitHeight: 14 - background: null + background: Item { + opacity: 1.0 + } contentItem: Rectangle { color: Theme.palette.primaryColor2 diff --git a/ui/StatusQ/src/StatusQ/Core/StatusRollArea.qml b/ui/StatusQ/src/StatusQ/Core/StatusRollArea.qml index 9ac01d8d420..68a19a145f8 100644 --- a/ui/StatusQ/src/StatusQ/Core/StatusRollArea.qml +++ b/ui/StatusQ/src/StatusQ/Core/StatusRollArea.qml @@ -1,4 +1,5 @@ import QtQuick +import QtQuick.Controls import QtQuick.Layouts import StatusQ.Core @@ -7,18 +8,16 @@ import StatusQ.Components import StatusQ.Popups import StatusQ.Controls -Item { +Control { id: root property alias content: contentLoader.sourceComponent - property color arrowsGradientColor: Theme.palette.statusAppLayout.backgroundColor + property color gradientColor: Theme.palette.statusAppLayout.backgroundColor + property bool showIcon: true - implicitHeight: contentLoader.height - - StatusScrollView { + contentItem: StatusScrollView { id: roll - anchors.fill: parent padding: 0 contentWidth: contentLoader.width @@ -33,7 +32,8 @@ Item { anchors.left: parent.left height: parent.height visible: roll.flickable.contentX > 0 - gradientColor: root.arrowsGradientColor + gradientColor: root.gradientColor + showIcon: root.showIcon onClicked: roll.flickable.flick(roll.width, 0) } @@ -42,8 +42,9 @@ Item { anchors.right: parent.right height: parent.height visible: roll.flickable.contentX + roll.width < roll.contentWidth - gradientColor: root.arrowsGradientColor + gradientColor: root.gradientColor navigateForward: true + showIcon: root.showIcon onClicked: roll.flickable.flick(-roll.width, 0) } diff --git a/ui/StatusQ/src/assets/png/activity_center/State=Empty Notifications, Theme=Dark.png b/ui/StatusQ/src/assets/png/activity_center/State=Empty Notifications, Theme=Dark.png new file mode 100644 index 00000000000..c4e8d531134 Binary files /dev/null and b/ui/StatusQ/src/assets/png/activity_center/State=Empty Notifications, Theme=Dark.png differ diff --git a/ui/StatusQ/src/assets/png/activity_center/State=Empty Notifications, Theme=Light.png b/ui/StatusQ/src/assets/png/activity_center/State=Empty Notifications, Theme=Light.png new file mode 100644 index 00000000000..516680ba191 Binary files /dev/null and b/ui/StatusQ/src/assets/png/activity_center/State=Empty Notifications, Theme=Light.png differ diff --git a/ui/StatusQ/src/assets/png/activity_center/State=News Disabled, Theme=Dark.png b/ui/StatusQ/src/assets/png/activity_center/State=News Disabled, Theme=Dark.png new file mode 100644 index 00000000000..70efeada519 Binary files /dev/null and b/ui/StatusQ/src/assets/png/activity_center/State=News Disabled, Theme=Dark.png differ diff --git a/ui/StatusQ/src/assets/png/activity_center/State=News Disabled, Theme=Light.png b/ui/StatusQ/src/assets/png/activity_center/State=News Disabled, Theme=Light.png new file mode 100644 index 00000000000..1f27d73e14b Binary files /dev/null and b/ui/StatusQ/src/assets/png/activity_center/State=News Disabled, Theme=Light.png differ diff --git a/ui/StatusQ/src/assets/png/png.qrc b/ui/StatusQ/src/assets/png/png.qrc index 8e1197a1418..8daa37d8f6c 100644 --- a/ui/StatusQ/src/assets/png/png.qrc +++ b/ui/StatusQ/src/assets/png/png.qrc @@ -925,5 +925,9 @@ status-logo-icon.png status-preparing.png unfurling-image.png + activity_center/State=Empty Notifications, Theme=Dark.png + activity_center/State=Empty Notifications, Theme=Light.png + activity_center/State=News Disabled, Theme=Dark.png + activity_center/State=News Disabled, Theme=Light.png diff --git a/ui/app/AppLayouts/ActivityCenter/ActivityCenterLayout.qml b/ui/app/AppLayouts/ActivityCenter/ActivityCenterLayout.qml index 31db438e83e..4d7507b6a8f 100644 --- a/ui/app/AppLayouts/ActivityCenter/ActivityCenterLayout.qml +++ b/ui/app/AppLayouts/ActivityCenter/ActivityCenterLayout.qml @@ -104,13 +104,13 @@ StatusSectionLayout { StatusFlatRoundButton { id: hideReadNotificationsBtn - property bool hideReadNotifications: activityCenterStore.activityCenterReadType === ActivityCenterStore.ActivityCenterReadType.Unread + property bool hideReadNotifications: activityCenterStore.activityCenterReadType === ActivityCenterTypes.ActivityCenterReadType.Unread objectName: "hideReadNotificationsButton" icon.name: hideReadNotifications ? "hide" : "show" onClicked: activityCenterStore.setActivityCenterReadType(!hideReadNotifications ? - ActivityCenterStore.ActivityCenterReadType.Unread : - ActivityCenterStore.ActivityCenterReadType.All) + ActivityCenterTypes.ActivityCenterReadType.Unread : + ActivityCenterTypes.ActivityCenterReadType.All) StatusToolTip { visible: hideReadNotificationsBtn.hovered @@ -124,19 +124,14 @@ StatusSectionLayout { id: activityCenterTopBar Layout.fillWidth: true - unreadNotificationsCount: activityCenterStore.unreadNotificationsCount hasAdmin: activityCenterStore.adminCount > 0 hasReplies: activityCenterStore.repliesCount > 0 hasMentions: activityCenterStore.mentionsCount > 0 hasContactRequests: activityCenterStore.contactRequestsCount > 0 hasMembership: activityCenterStore.membershipCount > 0 - hideReadNotifications: activityCenterStore.activityCenterReadType === ActivityCenterStore.ActivityCenterReadType.Unread activeGroup: activityCenterStore.activeNotificationGroup - onGroupTriggered: activityCenterStore.setActiveNotificationGroup(group) - onMarkAllReadClicked: activityCenterStore.markAllActivityCenterNotificationsRead() - onShowHideReadNotifications: activityCenterStore.setActivityCenterReadType(hideReadNotifications ? - ActivityCenterStore.ActivityCenterReadType.Unread : - ActivityCenterStore.ActivityCenterReadType.All) + + onSetActiveGroupRequested: activityCenterStore.setActiveNotificationGroup(group) } StatusListView { @@ -159,45 +154,45 @@ StatusSectionLayout { sourceComponent: { switch (model.notificationType) { - case ActivityCenterTypes.ActivityCenterNotificationType.Mention: + case ActivityCenterTypes.NotificationType.Mention: return mentionNotificationComponent - case ActivityCenterTypes.ActivityCenterNotificationType.Reply: + case ActivityCenterTypes.NotificationType.Reply: return replyNotificationComponent - case ActivityCenterTypes.ActivityCenterNotificationType.ContactRequest: + case ActivityCenterTypes.NotificationType.ContactRequest: return contactRequestNotificationComponent - case ActivityCenterTypes.ActivityCenterNotificationType.CommunityInvitation: + case ActivityCenterTypes.NotificationType.CommunityInvitation: return communityInvitationNotificationComponent - case ActivityCenterTypes.ActivityCenterNotificationType.CommunityMembershipRequest: + case ActivityCenterTypes.NotificationType.CommunityMembershipRequest: return membershipRequestNotificationComponent - case ActivityCenterTypes.ActivityCenterNotificationType.CommunityRequest: + case ActivityCenterTypes.NotificationType.CommunityRequest: return communityRequestNotificationComponent - case ActivityCenterTypes.ActivityCenterNotificationType.CommunityKicked: + case ActivityCenterTypes.NotificationType.CommunityKicked: return communityKickedNotificationComponent - case ActivityCenterTypes.ActivityCenterNotificationType.ContactRemoved: + case ActivityCenterTypes.NotificationType.ContactRemoved: return contactRemovedComponent - case ActivityCenterTypes.ActivityCenterNotificationType.NewKeypairAddedToPairedDevice: + case ActivityCenterTypes.NotificationType.NewKeypairAddedToPairedDevice: return newKeypairFromPairedDeviceComponent - case ActivityCenterTypes.ActivityCenterNotificationType.CommunityTokenReceived: - case ActivityCenterTypes.ActivityCenterNotificationType.FirstCommunityTokenReceived: + case ActivityCenterTypes.NotificationType.CommunityTokenReceived: + case ActivityCenterTypes.NotificationType.FirstCommunityTokenReceived: return communityTokenReceivedComponent - case ActivityCenterTypes.ActivityCenterNotificationType.OwnerTokenReceived: - case ActivityCenterTypes.ActivityCenterNotificationType.OwnershipReceived: - case ActivityCenterTypes.ActivityCenterNotificationType.OwnershipLost: - case ActivityCenterTypes.ActivityCenterNotificationType.OwnershipFailed: - case ActivityCenterTypes.ActivityCenterNotificationType.OwnershipDeclined: + case ActivityCenterTypes.NotificationType.OwnerTokenReceived: + case ActivityCenterTypes.NotificationType.OwnershipReceived: + case ActivityCenterTypes.NotificationType.OwnershipLost: + case ActivityCenterTypes.NotificationType.OwnershipFailed: + case ActivityCenterTypes.NotificationType.OwnershipDeclined: return ownerTokenReceivedNotificationComponent - case ActivityCenterTypes.ActivityCenterNotificationType.ShareAccounts: + case ActivityCenterTypes.NotificationType.ShareAccounts: return shareAccountsNotificationComponent - case ActivityCenterTypes.ActivityCenterNotificationType.CommunityBanned: + case ActivityCenterTypes.NotificationType.CommunityBanned: return communityBannedNotificationComponent - case ActivityCenterTypes.ActivityCenterNotificationType.CommunityUnbanned: + case ActivityCenterTypes.NotificationType.CommunityUnbanned: return communityUnbannedNotificationComponent - case ActivityCenterTypes.ActivityCenterNotificationType.NewPrivateGroupChat: + case ActivityCenterTypes.NotificationType.NewPrivateGroupChat: return groupChatInvitationNotificationComponent - case ActivityCenterTypes.ActivityCenterNotificationType.NewInstallationReceived: - case ActivityCenterTypes.ActivityCenterNotificationType.NewInstallationCreated: + case ActivityCenterTypes.NotificationType.NewInstallationReceived: + case ActivityCenterTypes.NotificationType.NewInstallationCreated: return newDeviceDetectedComponent - case ActivityCenterTypes.ActivityCenterNotificationType.ActivityCenterNotificationTypeNews: + case ActivityCenterTypes.NotificationType.ActivityCenterNotificationTypeNews: return newsMessageComponent default: return null @@ -217,7 +212,7 @@ StatusSectionLayout { Layout.fillWidth: true Layout.margins: Theme.padding - active: activityCenterTopBar.activeGroup === ActivityCenterStore.ActivityCenterGroup.NewsMessage && + active: activityCenterTopBar.activeGroup === ActivityCenterTypes.ActivityCenterGroup.NewsMessage && (newsDisabledBySettings || listView.count === 0) sourceComponent: newsDisabledBySettings ? newsDisabledPanel : newsEmptyPanel } @@ -754,7 +749,7 @@ StatusSectionLayout { StatusBaseText { // If the mode is unread only, it means the user has seen all notifications // If the mode is all, it means the user doesn't have any notifications - text: activityCenterStore.activityCenterReadType === ActivityCenterStore.ActivityCenterReadType.Unread ? + text: root.activityCenterReadType === ActivityCenterTypes.ActivityCenterReadType.Unread ? qsTr("You're all caught up") : qsTr("Your notifications will appear here") horizontalAlignment: Text.AlignHCenter diff --git a/ui/app/AppLayouts/ActivityCenter/controls/NotificationCard.qml b/ui/app/AppLayouts/ActivityCenter/controls/NotificationCard.qml index 030c0256a3e..ae281c6b1ae 100644 --- a/ui/app/AppLayouts/ActivityCenter/controls/NotificationCard.qml +++ b/ui/app/AppLayouts/ActivityCenter/controls/NotificationCard.qml @@ -197,7 +197,9 @@ Control { id: bg anchors.fill: parent radius: Theme.radius - color: root.selected || root.hovered ? Theme.palette.baseColor5 : StatusColors.transparent + color: StatusColors.transparent + border.width: 2 + border.color: root.selected || (root.hovered && root.enabled )? Theme.palette.primaryColor1 : StatusColors.transparent // Unread indicator dot (top-right). Rectangle { @@ -297,6 +299,8 @@ Control { z: 1 anchors.fill: parent cursorShape: Qt.PointingHandCursor + hoverEnabled: false + enabled: root.enabled onClicked: root.clicked() } diff --git a/ui/app/AppLayouts/ActivityCenter/controls/NotificationContentBlock.qml b/ui/app/AppLayouts/ActivityCenter/controls/NotificationContentBlock.qml index 2c026436d59..bb0f6d4da41 100644 --- a/ui/app/AppLayouts/ActivityCenter/controls/NotificationContentBlock.qml +++ b/ui/app/AppLayouts/ActivityCenter/controls/NotificationContentBlock.qml @@ -91,7 +91,8 @@ Control { // Max thumbnails to show (album caps to this). // Default to the maximumb images that fit in the current layout. - property int maxThumbs: Math.min(Math.floor((root.width + root.thumbSpacing) / (root.thumbSize + root.thumbSpacing)), attachments.length) + property int maxThumbs: attachments && attachments.length ? Math.min(Math.floor((root.width + root.thumbSpacing) / (root.thumbSize + root.thumbSpacing)), attachments.length) : + Math.min(Math.floor((root.width + root.thumbSpacing) / (root.thumbSize + root.thumbSpacing)), 0) // Max thumbnails to show (album caps to this). property int thumbSize: 56 @@ -181,7 +182,9 @@ Control { StatusMessageImageAlbum { Layout.fillWidth: true Layout.preferredHeight: root.thumbSize - visible: root.attachments.length > 0 + visible: root.attachments && root.attachments.length ? + root.attachments.length > 0 : + false spacing: root.thumbSpacing imageWidth: root.thumbSize diff --git a/ui/app/AppLayouts/ActivityCenter/helpers/ActivityCenterTypes.qml b/ui/app/AppLayouts/ActivityCenter/helpers/ActivityCenterTypes.qml index b5e41d1b4f7..2244be9c68b 100644 --- a/ui/app/AppLayouts/ActivityCenter/helpers/ActivityCenterTypes.qml +++ b/ui/app/AppLayouts/ActivityCenter/helpers/ActivityCenterTypes.qml @@ -18,7 +18,7 @@ QtObject { Dismissed = 3 } - enum ActivityCenterNotificationType { + enum NotificationType { NoType = 0, NewOneToOne = 1, NewPrivateGroupChat = 2, @@ -50,4 +50,23 @@ QtObject { BackupSyncingFailure = 28, // Deprecated ActivityCenterNotificationTypeNews = 29 } + + enum ActivityCenterGroup { + All = 0, + Mentions = 1, + Replies = 2, + Membership = 3, + Admin = 4, + ContactRequests = 5, + IdentityVerification = 6, + Transactions = 7, + System = 8, + NewsMessage = 9 + } + + enum ActivityCenterReadType { + Read = 1, + Unread = 2, // All notifications have been seen + All = 3 // Means no notifications + } } diff --git a/ui/app/AppLayouts/ActivityCenter/panels/ActivityCenterOptionsPanel.qml b/ui/app/AppLayouts/ActivityCenter/panels/ActivityCenterOptionsPanel.qml new file mode 100644 index 00000000000..f9f9b74176e --- /dev/null +++ b/ui/app/AppLayouts/ActivityCenter/panels/ActivityCenterOptionsPanel.qml @@ -0,0 +1,36 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import StatusQ.Core.Theme +import StatusQ.Components +import StatusQ.Core +import StatusQ.Popups + +import utils + +// It will be reworked on task https://github.com/status-im/status-app/issues/18906 +StatusMenu { + id: root + + required property bool hasUnreadNotifications + required property bool hideReadNotifications + + signal markAllAsReadRequested() + signal hideShowNotificationsRequested() + + StatusAction { + visibleOnDisabled: true + enabled: root.hasUnreadNotifications + text: qsTr("Mark all as read") + icon.name: "double-checkmark" + onTriggered: root.markAllAsReadRequested() + } + + StatusAction { + text: !root.hideReadNotifications ? qsTr("Hide read notifications") : + qsTr("Show read notifications") + icon.name: !root.hideReadNotifications ? "hide" : "show" + onTriggered: root.hideShowNotificationsRequested() + } +} diff --git a/ui/app/AppLayouts/ActivityCenter/panels/ActivityCenterPanel.qml b/ui/app/AppLayouts/ActivityCenter/panels/ActivityCenterPanel.qml new file mode 100644 index 00000000000..13c3752e406 --- /dev/null +++ b/ui/app/AppLayouts/ActivityCenter/panels/ActivityCenterPanel.qml @@ -0,0 +1,351 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + + +import StatusQ.Core.Theme +import StatusQ.Components +import StatusQ.Controls +import StatusQ.Core +import StatusQ.Core.Backpressure + +import StatusQ +import StatusQ.Popups +import StatusQ.Popups.Dialog + +import AppLayouts.ActivityCenter.controls +import AppLayouts.ActivityCenter.helpers + +import utils + +Control { + id: root + + // Properties related to the different notification types / groups: + required property bool hasAdmin + required property bool hasMentions + required property bool hasReplies + required property bool hasContactRequests + required property bool hasMembership + required property int activeGroup + + // Properties related to notifications states: + required property int readNotificationsStatus + required property bool hasUnreadNotifications + readonly property bool hideReadNotifications: root.readNotificationsStatus === ActivityCenterTypes.ActivityCenterReadType.Unread + required property var notificationsModel + + // Properties related to news feed settings: + required property string newsSettingsStatus + required property bool newsEnabledViaRSS + + // Style: + property color backgroundColor: Theme.palette.baseColor2 + + signal moreOptionsRequested() + signal closeRequested() + signal markAllAsReadRequested() + signal hideShowNotificationsRequested() + signal setActiveGroupRequested(int group) + signal notificationClicked(int index) + signal fetchMoreNotificationsRequested() + signal enableNewsViaRSSRequested() + signal enableNewsRequested() + + QtObject { + id: d + + readonly property bool emptyNotificationsList: listView.count === 0 + readonly property bool newsDisabledBySettings: !root.newsEnabledViaRSS || root.newsSettingsStatus === Constants.settingsSection.notifications.turnOffValue + readonly property bool isNewsPlaceholderActive: root.activeGroup === ActivityCenterTypes.ActivityCenterGroup.NewsMessage && d.newsDisabledBySettings + + property bool optionsMenuVisible: false + + readonly property var fetchMoreNotifications: Backpressure.oneInTimeQueued(root, 100, function() { + if (listView.contentY >= listView.contentHeight - listView.height - 1) { + root.fetchMoreNotificationsRequested() + } + }) + } + + contentItem: ColumnLayout { + spacing: 0 + + // Panel Header + RowLayout { + id: panelHeader + + Layout.fillWidth: true + Layout.leftMargin: Theme.padding + Layout.topMargin: Theme.halfPadding + Layout.bottomMargin: Theme.halfPadding + Layout.rightMargin: 0 + + spacing: 0 + + StatusNavigationPanelHeadline { + Layout.fillWidth: true + + font.pixelSize: Theme.fontSize(19) + text: qsTr("Notifications") + elide: Text.ElideRight + } + + // Filler + Item { + Layout.fillWidth: true + } + + StatusFlatRoundButton { + id: moreBtn + objectName: "moreOptionsButton" + icon.name: "more" + onClicked: options.open() + + // It will be reworked on task https://github.com/status-im/status-app/issues/18906 + ActivityCenterOptionsPanel { + id: options + + y: panelHeader.height + x: -implicitWidth + moreBtn.width + + hasUnreadNotifications: root.hasUnreadNotifications + hideReadNotifications: root.hideReadNotifications + + onMarkAllAsReadRequested: root.markAllAsReadRequested() + onHideShowNotificationsRequested: root.hideShowNotificationsRequested() + onOpened: d.optionsMenuVisible = true + onClosed: d.optionsMenuVisible = false + } + } + + StatusFlatRoundButton { + objectName: "closeButton" + icon.name: "close" + onClicked: { + d.optionsMenuVisible = false + root.closeRequested() + } + } + } + + // Notification's List Header + ActivityCenterPopupTopBarPanel { + Layout.fillWidth: true + + hasAdmin: root.hasAdmin + hasReplies: root.hasReplies + hasMentions: root.hasMentions + hasContactRequests: root.hasContactRequests + hasMembership: root.hasMembership + activeGroup: root.activeGroup + + gradientColor: root.backgroundColor + + onSetActiveGroupRequested: (group)=> root.setActiveGroupRequested(group) + } + + // Notifications List + StatusListView { + id: listView + Layout.fillWidth: true + Layout.fillHeight: !d.emptyNotificationsList || !d.isNewsPlaceholderActive + Layout.topMargin: 2 + + visible: !d.emptyNotificationsList && !d.isNewsPlaceholderActive + enabled: !d.optionsMenuVisible + verticalScrollBar.implicitWidth: Theme.halfPadding + + spacing: 4 + implicitHeight: contentHeight + model: root.notificationsModel + clip: true + delegate: NotificationCard { + enabled: !d.optionsMenuVisible + + width: root.width - 2 * Theme.halfPadding + anchors.horizontalCenter: listView.contentItem.horizontalCenter + + // Card states related + unread: model.unread + selected: model.selected + + // Avatar related + avatarSource: model.avatarSource + badgeIconName: model.badgeIconName + isCircularAvatar: model.isCircularAvatar + + // Header row related + title: model.title + chatKey: model.chatKey + isContact: model.isContact + trustIndicator: model.trustIndicator + + // Context row related + primaryText: model.primaryText + iconName: model.iconName + secondaryText: model.secondaryText + separatorIconName: model.separatorIconName + + // Action text + actionText: model.actionText + + // Content block related + preImageSource: model.preImageSource + preImageRadius: model.preImageRadius + content: model.content + attachments: model.attachments + + // Timestamp related + timestamp: model.timestamp + + // Interactions + onClicked: root.notificationClicked(model.index) + } + + onContentYChanged: d.fetchMoreNotifications() + + // Overlay + Rectangle { + visible: d.optionsMenuVisible + anchors.fill: parent + color: root.backgroundColor + opacity: 0.8 + } + } + + // Placeholder for the status news when their settings are disabled + // OR Placeholder for the status news when they are all seen or there are no notifications + Loader { + id: placeholderLoader + + + Layout.topMargin: 2 + Layout.bottomMargin: 2 + Layout.fillWidth: true + Layout.fillHeight: true + visible: active + active: d.isNewsPlaceholderActive || d.emptyNotificationsList + + sourceComponent: d.isNewsPlaceholderActive ? newsPlaceholderPanel : emptyPlaceholderPanel + } + + // Filler + Item { + Layout.fillHeight: placeholderLoader.active || d.emptyNotificationsList + } + } + + // If !root.newsEnabledViaRSS it means the panel is for enabling RSS notification + // Otherwise, it means it is for enabling status news notifications in settings + Component { + id: newsPlaceholderPanel + + Item { + anchors.fill: parent + + ColumnLayout { + id: newsPanelLayout + + anchors.centerIn: parent + width: parent.width - 2 * Theme.bigPadding + spacing: Theme.halfPadding + + Image { + Layout.alignment: Qt.AlignHCenter + + source: (Theme.style === Theme.Light) ? Assets.png("activity_center/State=News Disabled, Theme=Light") : + Assets.png("activity_center/State=News Disabled, Theme=Dark") + fillMode: Image.PreserveAspectFit + mipmap: true + cache: false + } + + StatusBaseText { + Layout.fillWidth: true + + horizontalAlignment: Text.AlignHCenter + text: !root.newsEnabledViaRSS ? qsTr("Status News RSS are off") : + qsTr("Status News notifications are off") + wrapMode: Text.WordWrap + font.pixelSize: Theme.additionalTextSize + font.weight: Font.Medium + } + + StatusBaseText { + Layout.fillWidth: true + + horizontalAlignment: Text.AlignHCenter + text: !root.newsEnabledViaRSS ? qsTr("Turn them on to get updates about new features and announcements. You can also enable this anytime in Privacy & Security settings.") : + qsTr("Turn them on to get updates about new features and announcements. You can also enable this anytime in Notifications and Sound settings.") + + wrapMode: Text.WordWrap + font.pixelSize: Theme.additionalTextSize + font.weight: Font.Light + } + + StatusButton { + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: parent.width + + text: !root.newsEnabledViaRSS ? qsTr("Enable RSS"): + qsTr("Enable Status News notifications") + font.pixelSize: Theme.additionalTextSize + + onClicked: { + if (!root.newsEnabledViaRSS) { + root.enableNewsViaRSSRequested() + } else { + root.enableNewsRequested() + } + } + } + } + } + } + + // This is used whenever the list of notifications is empty + Component { + id: emptyPlaceholderPanel + + Item { + anchors.fill: parent + + ColumnLayout { + anchors.centerIn: parent + width: parent.width - 2 * Theme.bigPadding + spacing: Theme.halfPadding + + Image { + Layout.alignment: Qt.AlignHCenter + + source: (Theme.style === Theme.Light) ? Assets.png("activity_center/State=Empty Notifications, Theme=Light") : + Assets.png("activity_center/State=Empty Notifications, Theme=Dark") + fillMode: Image.PreserveAspectFit + mipmap: true + cache: false + } + + StatusBaseText { + Layout.fillWidth: true + + horizontalAlignment: Text.AlignHCenter + text: qsTr("No notifications right now.") + wrapMode: Text.WordWrap + font.pixelSize: Theme.additionalTextSize + font.weight: Font.Medium + } + + StatusBaseText { + Layout.fillWidth: true + + horizontalAlignment: Text.AlignHCenter + text: qsTr("Check back later for updates.") + wrapMode: Text.WordWrap + font.pixelSize: Theme.additionalTextSize + font.weight: Font.Light + } + } + } + } +} diff --git a/ui/app/AppLayouts/ActivityCenter/panels/ActivityCenterPopupTopBarPanel.qml b/ui/app/AppLayouts/ActivityCenter/panels/ActivityCenterPopupTopBarPanel.qml index 3ea27537f9a..da3842c920b 100644 --- a/ui/app/AppLayouts/ActivityCenter/panels/ActivityCenterPopupTopBarPanel.qml +++ b/ui/app/AppLayouts/ActivityCenter/panels/ActivityCenterPopupTopBarPanel.qml @@ -11,75 +11,59 @@ import StatusQ.Controls import StatusQ.Popups import AppLayouts.stores +import AppLayouts.ActivityCenter.helpers -Item { +StatusRollArea { id: root - property bool hasAdmin: false - property bool hasMentions: false - property bool hasReplies: false - property bool hasContactRequests: false - property bool hasMembership: false - - property bool hideReadNotifications: false - property int unreadNotificationsCount: 0 - - property int activeGroup: ActivityCenterStore.ActivityCenterGroup.All - - property alias errorText: errorText.text - - signal groupTriggered(int group) - signal markAllReadClicked() - signal showHideReadNotifications(bool hideReadNotifications) - - height: 64 - - RowLayout { - id: row - anchors.fill: parent - anchors.leftMargin: Theme.padding - anchors.rightMargin: Theme.padding - spacing: Theme.padding - - StatusRollArea { - Layout.fillWidth: true - - content: RowLayout { - spacing: 0 - - Repeater { - // NOTE: some entries are hidden until implimentation - model: [ { text: qsTr("All"), group: ActivityCenterStore.ActivityCenterGroup.All, visible: true, enabled: true }, - { text: qsTr("News"), group: ActivityCenterStore.ActivityCenterGroup.NewsMessage, visible: true, enabled: true }, - { text: qsTr("Admin"), group: ActivityCenterStore.ActivityCenterGroup.Admin, visible: root.hasAdmin, enabled: root.hasAdmin }, - { text: qsTr("Mentions"), group: ActivityCenterStore.ActivityCenterGroup.Mentions, visible: true, enabled: root.hasMentions }, - { text: qsTr("Replies"), group: ActivityCenterStore.ActivityCenterGroup.Replies, visible: true, enabled: root.hasReplies }, - { text: qsTr("Contact requests"), group: ActivityCenterStore.ActivityCenterGroup.ContactRequests, visible: true, enabled: root.hasContactRequests }, - { text: qsTr("Transactions"), group: ActivityCenterStore.ActivityCenterGroup.Transactions, visible: false, enabled: true }, - { text: qsTr("Membership"), group: ActivityCenterStore.ActivityCenterGroup.Membership, visible: true, enabled: root.hasMembership }, - { text: qsTr("System"), group: ActivityCenterStore.ActivityCenterGroup.System, visible: false, enabled: true } ] - - StatusFlatButton { - objectName: "activityCenterGroupButton" - enabled: modelData.enabled - visible: modelData.visible - text: modelData.text - size: StatusBaseButton.Size.Small - highlighted: modelData.group === root.activeGroup - onClicked: root.groupTriggered(modelData.group) - onEnabledChanged: if (!enabled && highlighted) root.groupTriggered(ActivityCenterStore.ActivityCenterGroup.All) - Layout.preferredWidth: visible ? implicitWidth : 0 - } - } + required property bool hasAdmin + required property bool hasMentions + required property bool hasReplies + required property bool hasContactRequests + required property bool hasMembership + required property int activeGroup + + signal setActiveGroupRequested(int group) + + bottomPadding: Theme.padding + showIcon: false + gradientColor: Theme.palette.baseColor4 + + content: RowLayout { + spacing: 4 + + anchors.left: parent.left + anchors.leftMargin: 12 // By design + + Repeater { + // NOTE: some entries are hidden until implementation + model: [ { text: qsTr("All"), group: ActivityCenterTypes.ActivityCenterGroup.All, visible: true, enabled: true }, + { text: qsTr("News"), group: ActivityCenterTypes.ActivityCenterGroup.NewsMessage, visible: true, enabled: true }, + { text: qsTr("Admin"), group: ActivityCenterTypes.ActivityCenterGroup.Admin, visible: root.hasAdmin, enabled: root.hasAdmin }, + { text: qsTr("Mentions"), group: ActivityCenterTypes.ActivityCenterGroup.Mentions, visible: true, enabled: root.hasMentions }, + { text: qsTr("Replies"), group: ActivityCenterTypes.ActivityCenterGroup.Replies, visible: true, enabled: root.hasReplies }, + { text: qsTr("Contact requests"), group: ActivityCenterTypes.ActivityCenterGroup.ContactRequests, visible: true, enabled: root.hasContactRequests }, + { text: qsTr("Transactions"), group: ActivityCenterTypes.ActivityCenterGroup.Transactions, visible: false, enabled: true }, + { text: qsTr("Membership"), group: ActivityCenterTypes.ActivityCenterGroup.Membership, visible: true, enabled: root.hasMembership }, + { text: qsTr("System"), group: ActivityCenterTypes.ActivityCenterGroup.System, visible: false, enabled: true } ] + + StatusFlatButton { + objectName: "activityCenterGroupButton" + enabled: modelData.enabled + visible: modelData.visible + text: modelData.text + size: StatusBaseButton.Size.Small + highlighted: modelData.group === root.activeGroup + onClicked: root.setActiveGroupRequested(modelData.group) + onEnabledChanged: if (!enabled && highlighted) root.setActiveGroupRequested(ActivityCenterTypes.ActivityCenterGroup.All) + Layout.preferredWidth: visible ? implicitWidth : 0 } } - } - StatusBaseText { - id: errorText - visible: !!text - anchors.top: parent.top - anchors.topMargin: Theme.smallPadding - color: Theme.palette.dangerColor1 + // Filler + Item { + height: root.height + width: 12 // By design + } } } diff --git a/ui/app/AppLayouts/ActivityCenter/panels/qmldir b/ui/app/AppLayouts/ActivityCenter/panels/qmldir index 487704db7dc..08ec53e30c1 100644 --- a/ui/app/AppLayouts/ActivityCenter/panels/qmldir +++ b/ui/app/AppLayouts/ActivityCenter/panels/qmldir @@ -1,2 +1,2 @@ +ActivityCenterPanel 1.0 ActivityCenterPanel.qml ActivityCenterPopupTopBarPanel 1.0 ActivityCenterPopupTopBarPanel.qml - diff --git a/ui/app/AppLayouts/ActivityCenter/views/ActivityNotificationNewDevice.qml b/ui/app/AppLayouts/ActivityCenter/views/ActivityNotificationNewDevice.qml index 6b0866b7083..e84e94dd6ba 100644 --- a/ui/app/AppLayouts/ActivityCenter/views/ActivityNotificationNewDevice.qml +++ b/ui/app/AppLayouts/ActivityCenter/views/ActivityNotificationNewDevice.qml @@ -25,10 +25,10 @@ ActivityNotificationBase { function setType(notification) { if (notification) { switch (notification.notificationType) { - case ActivityCenterTypes.ActivityCenterNotificationType.NewInstallationReceived: + case ActivityCenterTypes.NotificationType.NewInstallationReceived: return ActivityNotificationNewDevice.InstallationType.Received - case ActivityCenterTypes.ActivityCenterNotificationType.NewInstallationCreated: + case ActivityCenterTypes.NotificationType.NewInstallationCreated: return ActivityNotificationNewDevice.InstallationType.Created } } diff --git a/ui/app/AppLayouts/ActivityCenter/views/ActivityNotificationTransferOwnership.qml b/ui/app/AppLayouts/ActivityCenter/views/ActivityNotificationTransferOwnership.qml index 75898fff482..65699000b1c 100644 --- a/ui/app/AppLayouts/ActivityCenter/views/ActivityNotificationTransferOwnership.qml +++ b/ui/app/AppLayouts/ActivityCenter/views/ActivityNotificationTransferOwnership.qml @@ -31,19 +31,19 @@ ActivityNotificationBase { if(notification) switch(notification.notificationType){ - case ActivityCenterTypes.ActivityCenterNotificationType.OwnerTokenReceived: + case ActivityCenterTypes.NotificationType.OwnerTokenReceived: return ActivityNotificationTransferOwnership.OwnershipState.Pending - case ActivityCenterTypes.ActivityCenterNotificationType.OwnershipDeclined: + case ActivityCenterTypes.NotificationType.OwnershipDeclined: return ActivityNotificationTransferOwnership.OwnershipState.Declined - case ActivityCenterTypes.ActivityCenterNotificationType.OwnershipReceived: + case ActivityCenterTypes.NotificationType.OwnershipReceived: return ActivityNotificationTransferOwnership.OwnershipState.Succeeded - case ActivityCenterTypes.ActivityCenterNotificationType.OwnershipFailed: + case ActivityCenterTypes.NotificationType.OwnershipFailed: return ActivityNotificationTransferOwnership.OwnershipState.Failed - case ActivityCenterTypes.ActivityCenterNotificationType.OwnershipLost: + case ActivityCenterTypes.NotificationType.OwnershipLost: return ActivityNotificationTransferOwnership.OwnershipState.NoLongerControlNode } diff --git a/ui/app/AppLayouts/stores/ActivityCenterStore.qml b/ui/app/AppLayouts/stores/ActivityCenterStore.qml index efce6643c1d..9850b34604b 100644 --- a/ui/app/AppLayouts/stores/ActivityCenterStore.qml +++ b/ui/app/AppLayouts/stores/ActivityCenterStore.qml @@ -5,25 +5,6 @@ import shared QtObject { id: root - enum ActivityCenterGroup { - All = 0, - Mentions = 1, - Replies = 2, - Membership = 3, - Admin = 4, - ContactRequests = 5, - IdentityVerification = 6, - Transactions = 7, - System = 8, - NewsMessage = 9 - } - - enum ActivityCenterReadType { - Read = 1, - Unread = 2, - All = 3 - } - readonly property var activityCenterModuleInst: activityCenterModule readonly property var activityCenterNotifications: activityCenterModuleInst.activityNotificationsModel diff --git a/ui/i18n/qml_base_en.ts b/ui/i18n/qml_base_en.ts index 3137a72431e..b855b9bbc30 100644 --- a/ui/i18n/qml_base_en.ts +++ b/ui/i18n/qml_base_en.ts @@ -309,6 +309,60 @@ + + ActivityCenterOptionsPanel + + Mark all as read + + + + Hide read notifications + + + + Show read notifications + + + + + ActivityCenterPanel + + Notifications + + + + Enable Status News notifications + + + + Enable RSS + + + + Status News RSS are off + + + + Status News notifications are off + + + + Turn them on to get updates about new features and announcements. You can also enable this anytime in Privacy & Security settings. + + + + Turn them on to get updates about new features and announcements. You can also enable this anytime in Notifications and Sound settings. + + + + No notifications right now. + + + + Check back later for updates. + + + ActivityCenterPopupTopBarPanel diff --git a/ui/i18n/qml_base_lokalise_en.ts b/ui/i18n/qml_base_lokalise_en.ts index 5b38274bac3..a8c9ee66455 100644 --- a/ui/i18n/qml_base_lokalise_en.ts +++ b/ui/i18n/qml_base_lokalise_en.ts @@ -379,6 +379,72 @@ Your notifications will appear here + + ActivityCenterOptionsPanel + + Mark all as read + ActivityCenterOptionsPanel + Mark all as read + + + Hide read notifications + ActivityCenterOptionsPanel + Hide read notifications + + + Show read notifications + ActivityCenterOptionsPanel + Show read notifications + + + + ActivityCenterPanel + + Notifications + ActivityCenterPanel + Notifications + + + Enable Status News notifications + ActivityCenterPanel + Enable Status News notifications + + + Enable RSS + ActivityCenterPanel + Enable RSS + + + Status News RSS are off + ActivityCenterPanel + Status News RSS are off + + + Status News notifications are off + ActivityCenterPanel + Status News notifications are off + + + Turn them on to get updates about new features and announcements. You can also enable this anytime in Privacy & Security settings. + ActivityCenterPanel + Turn them on to get updates about new features and announcements. You can also enable this anytime in Privacy & Security settings. + + + Turn them on to get updates about new features and announcements. You can also enable this anytime in Notifications and Sound settings. + ActivityCenterPanel + Turn them on to get updates about new features and announcements. You can also enable this anytime in Notifications and Sound settings. + + + No notifications right now. + ActivityCenterPanel + No notifications right now. + + + Check back later for updates. + ActivityCenterPanel + Check back later for updates. + + ActivityCenterPopupTopBarPanel diff --git a/ui/i18n/qml_cs.ts b/ui/i18n/qml_cs.ts index 8a3dacbfe4a..359a002aef9 100644 --- a/ui/i18n/qml_cs.ts +++ b/ui/i18n/qml_cs.ts @@ -309,6 +309,60 @@ + + ActivityCenterOptionsPanel + + Mark all as read + + + + Hide read notifications + + + + Show read notifications + + + + + ActivityCenterPanel + + Notifications + + + + Enable Status News notifications + + + + Enable RSS + + + + Status News RSS are off + + + + Status News notifications are off + + + + Turn them on to get updates about new features and announcements. You can also enable this anytime in Privacy & Security settings. + + + + Turn them on to get updates about new features and announcements. You can also enable this anytime in Notifications and Sound settings. + + + + No notifications right now. + + + + Check back later for updates. + + + ActivityCenterPopupTopBarPanel diff --git a/ui/i18n/qml_es.ts b/ui/i18n/qml_es.ts index 1a30146e3cd..4a1c1547d0d 100644 --- a/ui/i18n/qml_es.ts +++ b/ui/i18n/qml_es.ts @@ -309,6 +309,60 @@ Tus notificaciones aparecerán aquí + + ActivityCenterOptionsPanel + + Mark all as read + Marcar todo como leído + + + Hide read notifications + Ocultar notificaciones leídas + + + Show read notifications + Mostrar notificaciones leídas + + + + ActivityCenterPanel + + Notifications + Notificaciones + + + Enable Status News notifications + Habilitar notificaciones de Status News + + + Enable RSS + Habilitar RSS + + + Status News RSS are off + Recibir Status News mediante RSS está deshabilitado + + + Status News notifications are off + Recibir Status News está deshabilitado + + + Turn them on to get updates about new features and announcements. You can also enable this anytime in Privacy & Security settings. + Actívalas para recibir actualizaciones sobre nuevas funciones y anuncios. También puedes habilitarlas en cualquier momento en la configuración de Privacidad y Seguridad. + + + Turn them on to get updates about new features and announcements. You can also enable this anytime in Notifications and Sound settings. + Actívalas para recibir actualizaciones sobre nuevas funciones y anuncios. También puedes activarlas en cualquier momento en la configuración de Notificaciones y Sonidos. + + + No notifications right now. + No hay notificaciones ahora mismo. + + + Check back later for updates. + Vuelva más tarde para revisar actualizaciones. + + ActivityCenterPopupTopBarPanel @@ -2480,7 +2534,7 @@ Para respaldar tu frase de recuperación, escríbela y guárdala de forma segura Choose a folder to store your backup files in. - + Elige una carpeta para almacenar tus archivos de backup. @@ -2636,15 +2690,15 @@ Do you wish to override the security check and continue? BrowserPrivacyWall Enable third-party services for browser features to work. - + Habilita servicios de terceros para que funcionen las funciones del navegador. Dapp browser - + Navegador Web/dApp Browse decentralized apps - + Navega apps descentralizadas diff --git a/ui/i18n/qml_ko.ts b/ui/i18n/qml_ko.ts index 54c4c29911f..58cdc1382f1 100644 --- a/ui/i18n/qml_ko.ts +++ b/ui/i18n/qml_ko.ts @@ -309,6 +309,60 @@ 알림이 여기에 표시됩니다 + + ActivityCenterOptionsPanel + + Mark all as read + + + + Hide read notifications + 읽은 알림 숨기기 + + + Show read notifications + 읽음 알림 표시 + + + + ActivityCenterPanel + + Notifications + 알림 + + + Enable Status News notifications + Status 뉴스 알림 활성화 + + + Enable RSS + RSS 활성화 + + + Status News RSS are off + + + + Status News notifications are off + + + + Turn them on to get updates about new features and announcements. You can also enable this anytime in Privacy & Security settings. + + + + Turn them on to get updates about new features and announcements. You can also enable this anytime in Notifications and Sound settings. + + + + No notifications right now. + + + + Check back later for updates. + + + ActivityCenterPopupTopBarPanel