feat(icon): show a red dot when we have unread notifications and windows icon with white border (#15496)

* feat(icon): show a red dot when we have unread notifications
* feat(windows-icon): update windows icon to have a white border

Fixes #14788
Fixes #14855

Adds a red dot on the tray icon if there is an unread message in an unmuted channel or in the activity center
This commit is contained in:
Jonathan Rainville 2024-07-12 09:41:27 -04:00 committed by GitHub
parent fae3d14d50
commit 8216ea37f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 128 additions and 18 deletions

View File

@ -21,6 +21,9 @@ method viewDidLoad*(self: AccessInterface) {.base.} =
method hasMoreToShow*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method unreadActivityCenterNotificationsCountFromView*(self: AccessInterface): int {.base.} =
raise newException(ValueError, "No implementation available")
method unreadActivityCenterNotificationsCount*(self: AccessInterface): int {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -24,6 +24,7 @@ type
view: View
viewVariant: QVariant
moduleLoaded: bool
unreadCount: int
proc newModule*(
delegate: delegate_interface.AccessInterface,
@ -67,6 +68,9 @@ method viewDidLoad*(self: Module) =
method hasMoreToShow*(self: Module): bool =
self.controller.hasMoreToShow()
method unreadActivityCenterNotificationsCountFromView*(self: Module): int =
self.view.unreadCount()
method unreadActivityCenterNotificationsCount*(self: Module): int =
self.controller.unreadActivityCenterNotificationsCount()
@ -76,9 +80,7 @@ method hasUnseenActivityCenterNotifications*(self: Module): bool =
method onNotificationsCountMayHaveChanged*(self: Module) =
self.view.unreadActivityCenterNotificationsCountChanged()
self.view.hasUnseenActivityCenterNotificationsChanged()
method hasUnseenActivityCenterNotificationsChanged*(self: Module) =
self.view.hasUnseenActivityCenterNotificationsChanged()
self.delegate.onActivityNotificationsUpdated()
proc createMessageItemFromDto(self: Module, message: MessageDto, communityId: string, albumMessages: seq[MessageDto]): MessageItem =
let contactDetails = self.controller.getContactDetails(message.`from`)

View File

@ -11,6 +11,7 @@ QtObject:
model: Model
modelVariant: QVariant
groupCounters: Table[ActivityCenterGroup, int]
unreadCount: int
proc delete*(self: View) =
self.QObject.delete
@ -22,6 +23,7 @@ QtObject:
result.model = newModel()
result.modelVariant = newQVariant(result.model)
result.groupCounters = initTable[ActivityCenterGroup, int]()
result.unreadCount = 0
proc load*(self: View) =
self.delegate.viewDidLoad()
@ -44,10 +46,14 @@ QtObject:
read = hasMoreToShow
notify = hasMoreToShowChanged
proc unreadCount*(self: View): int =
return self.unreadCount
proc unreadActivityCenterNotificationsCountChanged*(self: View) {.signal.}
proc unreadActivityCenterNotificationsCount*(self: View): int {.slot.} =
self.delegate.unreadActivityCenterNotificationsCount()
self.unreadCount = self.delegate.unreadActivityCenterNotificationsCount()
return self.unreadCount
QtProperty[int] unreadActivityCenterNotificationsCount:
read = unreadActivityCenterNotificationsCount

View File

@ -132,6 +132,9 @@ proc init*(self: Controller) =
self.events.on(message_service.SIGNAL_MESSAGE_MARKED_AS_UNREAD) do(e:Args):
let args = message_service.MessageMarkMessageAsUnreadArgs(e)
let chat = self.chatService.getChatById(args.chatId)
if ((self.isCommunitySection and chat.communityId != self.sectionId) or
(not self.isCommunitySection and chat.communityId != "")):
return
self.delegate.onMarkMessageAsUnread(chat)
self.events.on(chat_service.SIGNAL_CHAT_LEFT) do(e: Args):

View File

@ -641,13 +641,6 @@ QtObject:
defer: modelIndex.delete
self.dataChanged(modelIndex, modelIndex, @[ModelRole.HasUnreadMessages.int, ModelRole.NotificationsCount.int])
proc incrementNotificationsForItemByIdAndGetNotificationCount*(self: Model, id: string): int =
let index = self.getItemIdxById(id)
if index == -1:
return 0
self.updateNotificationsForItemById(id, hasUnreadMessages = true, self.items[index].notificationsCount + 1)
return self.items[index].notificationsCount
proc updateLastMessageTimestampOnItemById*(self: Model, id: string, lastMessageTimestamp: int) =
let index = self.getItemIdxById(id)
if index == -1:

View File

@ -126,6 +126,9 @@ method onChatsLoadingFailed*(self: AccessInterface) {.base.} =
method onActiveChatChange*(self: AccessInterface, sectionId: string, chatId: string) {.base.} =
raise newException(ValueError, "No implementation available")
method onActivityNotificationsUpdated*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onNotificationsUpdated*(self: AccessInterface, sectionId: string, sectionHasUnreadMessages: bool,
sectionNotificationCount: int) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -994,9 +994,18 @@ method onActiveChatChange*[T](self: Module[T], sectionId: string, chatId: string
method onChatLeft*[T](self: Module[T], chatId: string) =
self.appSearchModule.updateSearchLocationIfPointToChatWithId(chatId)
proc checkIfWeHaveNotifications[T](self: Module[T]) =
let sectionWithUnread = self.view.model().isThereASectionWithUnreadMessages()
let activtyCenterNotifications = self.activityCenterModule.unreadActivityCenterNotificationsCountFromView() > 0
self.view.setNotificationAvailable(sectionWithUnread or activtyCenterNotifications)
method onActivityNotificationsUpdated[T](self: Module[T]) =
self.checkIfWeHaveNotifications()
method onNotificationsUpdated[T](self: Module[T], sectionId: string, sectionHasUnreadMessages: bool,
sectionNotificationCount: int) =
self.view.model().updateNotifications(sectionId, sectionHasUnreadMessages, sectionNotificationCount)
self.checkIfWeHaveNotifications()
method onNetworkConnected[T](self: Module[T]) =
self.view.setConnected(true)

View File

@ -17,6 +17,7 @@ QtObject:
modelVariant: QVariant
sectionsLoaded: bool
chatsLoadingFailed: bool
notificationAvailable: bool
activeSection: SectionDetails
activeSectionVariant: QVariant
chatSearchModel: chat_search_model.Model
@ -45,6 +46,7 @@ QtObject:
result.model = section_model.newModel()
result.sectionsLoaded = false
result.chatsLoadingFailed = false
result.notificationAvailable = false
result.modelVariant = newQVariant(result.model)
result.activeSection = newActiveSection()
result.activeSectionVariant = newQVariant(result.activeSection)
@ -269,6 +271,21 @@ QtObject:
read = isConnected
notify = onlineStatusChanged
proc notificationAvailableChanged(self: View) {.signal.}
proc notificationAvailable*(self: View): bool {.slot.} =
result = self.notificationAvailable
proc setNotificationAvailable*(self: View, value: bool) =
if self.notificationAvailable == value:
return
self.notificationAvailable = value
self.notificationAvailableChanged()
QtProperty[bool] notificationAvailable:
read = notificationAvailable
notify = notificationAvailableChanged
proc displayUserProfile*(self:View, publicKey: string) {.signal.}
proc emitDisplayUserProfileSignal*(self: View, publicKey: string) =
self.displayUserProfile(publicKey)

View File

@ -369,10 +369,13 @@ QtObject:
proc disableSection*(self: SectionModel, sectionType: SectionType) =
self.enableDisableSection(sectionType, false)
proc isAMessengerItem*(item: SectionItem): bool =
return item.sectionType == SectionType.Chat or item.sectionType == SectionType.Community
# Count all mentions from all chat&community sections
proc allMentionsCount*(self: SectionModel): int =
for item in self.items:
if item.sectionType == SectionType.Chat or item.sectionType == SectionType.Community:
if item.isAMessengerItem():
result += item.notificationsCount
proc updateIsPendingOwnershipRequest*(self: SectionModel, id: string, isPending: bool) =
@ -395,6 +398,12 @@ QtObject:
self.notificationsCountChanged()
return
proc isThereASectionWithUnreadMessages*(self: SectionModel): bool =
for item in self.items:
if item.isAMessengerItem() and item.hasNotification == true:
return true
return false
proc appendCommunityToken*(self: SectionModel, id: string, item: TokenItem) =
for i in 0 ..< self.items.len:
if(self.items[i].id == id):

View File

@ -6,13 +6,19 @@ SystemTrayIcon {
id: root
property bool isProduction: true
property bool showRedDot: false
signal activateApp()
visible: true
icon.source: Qt.platform.os === Constants.windows // TODO: Add status-logo-white with stroke for windows
? Style.png("status-logo%1".arg(root.isProduction ? "" : "-dev-circle"))
: Style.svg("status-logo-white")
icon.source: {
if (Qt.platform.os === Constants.windows) {
return root.showRedDot ? Style.svg("status-logo-white-windows-with-red-dot") : Style.svg("status-logo-white-windows")
}
return root.showRedDot ? Style.svg("status-logo-white-with-red-dot") : Style.svg("status-logo-white")
}
icon.mask: Qt.platform.os !== Constants.windows
onMessageClicked: {

View File

@ -0,0 +1,24 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_520_2)">
<path d="M0.5 7.99981C0.5 5.02751 1.00183 3.20508 2.10355 2.10336C3.20527 1.00164 5.0277 0.499809 8 0.499809C10.9723 0.499809 12.7947 1.00164 13.8964 2.10336C14.9982 3.20508 15.5 5.02751 15.5 7.99981C15.5 10.9721 14.9982 12.7945 13.8964 13.8963C12.7947 14.998 10.9723 15.4998 8 15.4998C5.0277 15.4998 3.20527 14.998 2.10355 13.8963C1.00183 12.7945 0.5 10.9721 0.5 7.99981Z" fill="white" stroke="#82878A"/>
<g filter="url(#filter0_d_520_2)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.82589 8.32108C9.69564 9.95266 8.52412 11.3949 6.93746 11.4856C5.96323 11.5412 4.99014 10.9463 4.93758 9.98025C4.89683 9.20282 5.37785 8.63553 6.21726 8.41821C6.41558 8.36622 6.61875 8.33456 6.82359 8.32372C7.71517 8.27346 8.27351 8.47793 9.1651 8.42728C9.3708 8.41686 9.57493 8.38609 9.77447 8.33544C9.79047 8.33091 9.80989 8.32599 9.82589 8.32108Z" fill="#82878A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.17682 7.6789C6.30707 6.04732 7.47859 4.60509 9.06525 4.51438C10.0395 4.45883 11.0126 5.05371 11.0651 6.01973C11.1078 6.79716 10.6249 7.36446 9.78545 7.58215C9.58713 7.63414 9.38396 7.6658 9.17912 7.67664C8.28753 7.7269 7.7292 7.52244 6.83761 7.57232C6.63191 7.58275 6.42777 7.61351 6.22823 7.66417C6.21224 7.66908 6.19281 7.67399 6.17682 7.6789Z" fill="#82878A"/>
</g>
<circle cx="13" cy="13" r="2.5" fill="#EB2222" stroke="white"/>
</g>
<defs>
<filter id="filter0_d_520_2" x="-952.598" y="-697.68" width="1921.2" height="1922.04" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="255.342"/>
<feGaussianBlur stdDeviation="478.766"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0352941 0 0 0 0 0.0627451 0 0 0 0 0.109804 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_520_2"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_520_2" result="shape"/>
</filter>
<clipPath id="clip0_520_2">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,23 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_520_8)">
<path d="M0.5 7.99981C0.5 5.02751 1.00183 3.20508 2.10355 2.10336C3.20527 1.00164 5.0277 0.499809 8 0.499809C10.9723 0.499809 12.7947 1.00164 13.8964 2.10336C14.9982 3.20508 15.5 5.02751 15.5 7.99981C15.5 10.9721 14.9982 12.7945 13.8964 13.8963C12.7947 14.998 10.9723 15.4998 8 15.4998C5.0277 15.4998 3.20527 14.998 2.10355 13.8963C1.00183 12.7945 0.5 10.9721 0.5 7.99981Z" fill="white" stroke="#82878A"/>
<g filter="url(#filter0_d_520_8)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.82589 8.32108C9.69564 9.95266 8.52412 11.3949 6.93746 11.4856C5.96323 11.5412 4.99014 10.9463 4.93758 9.98025C4.89683 9.20282 5.37785 8.63553 6.21726 8.41821C6.41558 8.36622 6.61875 8.33456 6.82359 8.32372C7.71517 8.27346 8.27351 8.47793 9.1651 8.42728C9.3708 8.41686 9.57493 8.38609 9.77447 8.33544C9.79047 8.33091 9.80989 8.32599 9.82589 8.32108Z" fill="#82878A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.17682 7.6789C6.30707 6.04732 7.47859 4.60509 9.06525 4.51438C10.0395 4.45883 11.0126 5.05371 11.0651 6.01973C11.1078 6.79716 10.6249 7.36446 9.78545 7.58215C9.58713 7.63414 9.38396 7.6658 9.17912 7.67664C8.28753 7.7269 7.7292 7.52244 6.83761 7.57232C6.63191 7.58275 6.42777 7.61351 6.22823 7.66417C6.21224 7.66908 6.19281 7.67399 6.17682 7.6789Z" fill="#82878A"/>
</g>
</g>
<defs>
<filter id="filter0_d_520_8" x="-952.598" y="-697.68" width="1921.2" height="1922.04" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="255.342"/>
<feGaussianBlur stdDeviation="478.766"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0352941 0 0 0 0 0.0627451 0 0 0 0 0.109804 0 0 0 0.12 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_520_8"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_520_8" result="shape"/>
</filter>
<clipPath id="clip0_520_8">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,11 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_7558_1933)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 16C2 5.5 5.5 2 16 2C26.5 2 30 5.5 30 16C30 26.5 26.5 30 16 30C5.5 30 2 26.5 2 16ZM12.8076 15.2453C13.0356 12.3901 15.0857 9.86618 17.8624 9.70744C19.5673 9.61021 21.2702 10.6513 21.3622 12.3418C21.4368 13.7023 20.5917 14.6951 19.1227 15.076C18.7757 15.167 18.4201 15.2224 18.0616 15.2414C17.281 15.2854 16.6463 15.2178 16.0116 15.1502H16.0116L16.0116 15.1502C15.3776 15.0827 14.7436 15.0152 13.964 15.0588C13.604 15.0771 13.2468 15.1309 12.8976 15.2196C12.8836 15.2239 12.8681 15.2282 12.8526 15.2324L12.8525 15.2325C12.837 15.2368 12.8216 15.2411 12.8076 15.2453ZM19.1994 16.7545C18.9714 19.6098 16.9213 22.1337 14.1446 22.2924C12.4397 22.3897 10.7368 21.3486 10.6448 19.6581C10.5735 18.2976 11.4153 17.3048 12.8843 16.9245C13.2313 16.8335 13.5869 16.7781 13.9453 16.7592C14.725 16.7152 15.359 16.7825 15.9929 16.8499C16.6277 16.9173 17.2623 16.9847 18.043 16.9404C18.403 16.9221 18.7602 16.8683 19.1094 16.7797L19.1425 16.7706L19.1426 16.7705C19.1619 16.7653 19.1818 16.7599 19.1994 16.7545Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_7558_1933">
<rect width="28" height="28" fill="white" transform="translate(2 2)"/>
</clipPath>
</defs>
<circle r="4.776" fill="#EB2222" cx="25" cy="25"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -300,6 +300,7 @@ StatusWindow {
id: systemTray
objectName: "systemTray"
isProduction: production
showRedDot: typeof mainModule !== "undefined" ? mainModule.notificationAvailable : false
onActivateApp: {
applicationWindow.makeStatusAppActive()
}