feat: add group requests to the activity center

Fixes #2679
This commit is contained in:
Jonathan Rainville 2021-06-15 15:34:36 -04:00 committed by Iuri Matias
parent c2ffb4aee7
commit cd44b8a606
16 changed files with 492 additions and 233 deletions

View File

@ -34,11 +34,10 @@ proc init*(self: ChatController) =
self.handleSignals()
let pubKey = self.status.settings.getSetting[:string](Setting.PublicKey, "0x0")
let messagesFromContactsOnly = self.status.settings.getSetting[:bool](Setting.MessagesFromContactsOnly, false, true)
# self.view.pubKey = pubKey
self.view.setPubKey(pubKey)
self.status.chat.init(pubKey, messagesFromContactsOnly)
self.status.chat.init(pubKey)
self.status.stickers.init()
self.view.reactions.init()

View File

@ -19,6 +19,7 @@ type
Read = UserRole + 7
Dismissed = UserRole + 8
Accepted = UserRole + 9
Author = UserRole + 10
QtObject:
type
@ -71,6 +72,7 @@ QtObject:
of NotifRoles.Id: result = newQVariant(acitivityNotificationItem.id)
of NotifRoles.ChatId: result = newQVariant(acitivityNotificationItem.chatId)
of NotifRoles.Name: result = newQVariant(acitivityNotificationItem.name)
of NotifRoles.Author: result = newQVariant(acitivityNotificationItem.author)
of NotifRoles.NotificationType: result = newQVariant(acitivityNotificationItem.notificationType.int)
of NotifRoles.Message: result = newQVariant(acitivityNotificationItem.messageItem)
of NotifRoles.Timestamp: result = newQVariant(acitivityNotificationItem.timestamp)
@ -86,6 +88,7 @@ QtObject:
of "id": result = notif.id
of "chatId": result = notif.chatId
of "name": result = notif.name
of "author": result = notif.author
of "notificationType": result = $(notif.notificationType.int)
of "timestamp": result = $(notif.timestamp)
of "read": result = $(notif.read)
@ -98,6 +101,7 @@ QtObject:
NotifRoles.Id.int:"id",
NotifRoles.ChatId.int:"chatId",
NotifRoles.Name.int: "name",
NotifRoles.Author.int: "author",
NotifRoles.NotificationType.int: "notificationType",
NotifRoles.Message.int: "message",
NotifRoles.Timestamp.int: "timestamp",
@ -145,11 +149,54 @@ QtObject:
let topLeft = self.createIndex(i, 0, nil)
let bottomRight = self.createIndex(i, 0, nil)
self.dataChanged(topLeft, bottomRight, @[NotifRoles.Read.int])
break
i = i + 1
proc markActivityCenterNotificationRead(self: ActivityNotificationList, id: string): string {.slot.} =
self.markActivityCenterNotificationsRead(fmt"[""{id}""]")
proc removeNotifications(self: ActivityNotificationList, ids: seq[string]) =
var i = 0
var indexesToDelete: seq[int] = @[]
for activityCenterNotification in self.activityCenterNotifications:
for id in ids:
if (activityCenterNotification.id == id):
indexesToDelete.add(i)
break
i = i + 1
i = 0
for index in indexesToDelete:
let indexUpdated = index - i
self.beginRemoveRows(newQModelIndex(), indexUpdated, indexUpdated)
self.activityCenterNotifications.delete(indexUpdated)
self.endRemoveRows()
i = i + 1
proc acceptActivityCenterNotifications(self: ActivityNotificationList, idsJson: string): string {.slot.} =
let ids = map(parseJson(idsJson).getElems(), proc(x:JsonNode):string = x.getStr())
let error = self.status.chat.acceptActivityCenterNotifications(ids)
if (error != ""):
return error
self.removeNotifications(ids)
proc acceptActivityCenterNotification(self: ActivityNotificationList, id: string): string {.slot.} =
self.acceptActivityCenterNotifications(fmt"[""{id}""]")
proc dismissActivityCenterNotifications(self: ActivityNotificationList, idsJson: string): string {.slot.} =
let ids = map(parseJson(idsJson).getElems(), proc(x:JsonNode):string = x.getStr())
let error = self.status.chat.dismissActivityCenterNotifications(ids)
if (error != ""):
return error
self.removeNotifications(ids)
proc dismissActivityCenterNotification(self: ActivityNotificationList, id: string): string {.slot.} =
self.dismissActivityCenterNotifications(fmt"[""{id}""]")
proc toActivityCenterNotificationViewItem*(self: ActivityNotificationList, activityCenterNotification: ActivityCenterNotification): ActivityCenterNotificationViewItem =
let communityId = self.status.chat.getCommunityIdForChat(activityCenterNotification.chatId)
activityCenterNotification.message.communityId = communityId
@ -158,6 +205,7 @@ QtObject:
chatId: activityCenterNotification.chatId,
name: activityCenterNotification.name,
notificationType: activityCenterNotification.notificationType,
author: activityCenterNotification.author,
timestamp: activityCenterNotification.timestamp,
read: activityCenterNotification.read,
dismissed: activityCenterNotification.dismissed,

View File

@ -52,7 +52,6 @@ type
ChatModel* = ref object
publicKey*: string
messagesFromContactsOnly*: bool
events*: EventEmitter
communitiesToFetch*: seq[string]
mailserverReady*: bool
@ -72,7 +71,6 @@ include chat/utils
proc newChatModel*(events: EventEmitter): ChatModel =
result = ChatModel()
result.messagesFromContactsOnly = false
result.events = events
result.mailserverReady = false
result.communitiesToFetch = @[]
@ -86,37 +84,9 @@ proc newChatModel*(events: EventEmitter): ChatModel =
proc delete*(self: ChatModel) =
discard
proc cleanSpamChatGroups(self: ChatModel, chats: seq[Chat], contacts: seq[Profile]): seq[Chat] =
for chat in chats:
if not chat.isActive: continue
if chat.chatType == ChatType.PrivateGroupChat:
var isContact = false
var joined = false
for member in chat.members:
if member.id == self.publicKey and member.joined:
joined = true
if member.admin and member.joined:
for contact in contacts:
if contact.address == member.id:
isContact = true
if not isContact and not joined:
discard status_chat.deactivateChat(chat)
else:
result.add(chat)
else:
result.add(chat)
proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiReactions: seq[Reaction], communities: seq[Community], communityMembershipRequests: seq[CommunityMembershipRequest], pinnedMessages: seq[Message], activityCenterNotifications: seq[ActivityCenterNotification]) =
var contacts = getAddedContacts()
var chatList = chats
if (self.messagesFromContactsOnly):
# Automatically decline chat group invitations if admin is not a contact
chatList = self.cleanSpamChatGroups(chats, contacts)
for chat in chatList:
if chat.isActive:
self.channels[chat.id] = chat
for chat in chats:
self.channels[chat.id] = chat
for message in messages:
let chatId = message.chatId
@ -127,7 +97,7 @@ proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiRea
if self.lastMessageTimestamps[chatId] > ts:
self.lastMessageTimestamps[chatId] = ts
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages,chats: chatList, contacts: @[], emojiReactions: emojiReactions, communities: communities, communityMembershipRequests: communityMembershipRequests, pinnedMessages: pinnedMessages, activityCenterNotifications: activityCenterNotifications))
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages,chats: chats, contacts: @[], emojiReactions: emojiReactions, communities: communities, communityMembershipRequests: communityMembershipRequests, pinnedMessages: pinnedMessages, activityCenterNotifications: activityCenterNotifications))
proc hasChannel*(self: ChatModel, chatId: string): bool =
self.channels.hasKey(chatId)
@ -178,16 +148,12 @@ proc requestMissingCommunityInfos*(self: ChatModel) =
for communityId in self.communitiesToFetch:
status_chat.requestCommunityInfo(communityId)
proc init*(self: ChatModel, pubKey: string, messagesFromContactsOnly: bool) =
proc init*(self: ChatModel, pubKey: string) =
self.publicKey = pubKey
self.messagesFromContactsOnly = messagesFromContactsOnly
var contacts = getAddedContacts()
var chatList = status_chat.loadChats()
if (messagesFromContactsOnly):
chatList = self.cleanSpamChatGroups(chatList, contacts)
let profileUpdatesChatIds = chatList.filter(c => c.chatType == ChatType.Profile).map(c => c.id)
if chatList.filter(c => c.chatType == ChatType.Timeline).len == 0:
@ -576,6 +542,25 @@ proc markActivityCenterNotificationsRead*(self: ChatModel, ids: seq[string]): st
error "Error marking as read", msg = e.msg
result = e.msg
proc acceptActivityCenterNotifications*(self: ChatModel, ids: seq[string]): string =
try:
let response = status_chat.acceptActivityCenterNotifications(ids)
let resultTuple = self.processChatUpdate(parseJson(response))
let (chats, messages) = resultTuple
self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats))
except Exception as e:
error "Error marking as accepted", msg = e.msg
result = e.msg
proc dismissActivityCenterNotifications*(self: ChatModel, ids: seq[string]): string =
try:
discard status_chat.dismissActivityCenterNotifications(ids)
except Exception as e:
error "Error marking as dismissed", msg = e.msg
result = e.msg
proc unreadActivityCenterNotificationsCount*(self: ChatModel): int =
status_chat.unreadActivityCenterNotificationsCount()

View File

@ -134,6 +134,7 @@ type ActivityCenterNotification* = ref object of RootObj
id*: string # ID is the id of the chat, for public chats it is the name e.g. status, for one-to-one is the hex encoded public key and for group chats is a random uuid appended with the hex encoded pk of the creator of the chat
chatId*: string
name*: string
author*: string
notificationType*: ActivityCenterNotificationType
message*: Message
timestamp*: int64

View File

@ -604,7 +604,13 @@ proc markAllActivityCenterNotificationsRead*() =
discard callPrivateRPC("markAllActivityCenterNotificationsRead".prefix, %*[])
proc markActivityCenterNotificationsRead*(ids: seq[string]) =
let res = callPrivateRPC("markActivityCenterNotificationsRead".prefix, %*[ids])
discard callPrivateRPC("markActivityCenterNotificationsRead".prefix, %*[ids])
proc acceptActivityCenterNotifications*(ids: seq[string]): string =
result = callPrivateRPC("acceptActivityCenterNotifications".prefix, %*[ids])
proc dismissActivityCenterNotifications*(ids: seq[string]): string =
result = callPrivateRPC("dismissActivityCenterNotifications".prefix, %*[ids])
proc unreadActivityCenterNotificationsCount*(): int =
let rpcResult = callPrivateRPC("unreadActivityCenterNotificationsCount".prefix, %*[]).parseJson

View File

@ -385,6 +385,7 @@ proc toActivityCenterNotification*(jsonNotification: JsonNode, pk: string): Acti
id: jsonNotification{"id"}.getStr,
chatId: jsonNotification{"chatId"}.getStr,
name: jsonNotification{"name"}.getStr,
author: jsonNotification{"author"}.getStr,
notificationType: activityCenterNotificationType,
timestamp: jsonNotification{"timestamp"}.getInt,
read: jsonNotification{"read"}.getBool,

View File

@ -104,6 +104,7 @@ Popup {
DelegateModelGeneralized {
id: notifDelegateList
lessThan: [
function(left, right) { return left.timestamp > right.timestamp }
]
@ -135,15 +136,30 @@ Popup {
}
Loader {
property int previousNotificationIndex: {
if (notificationDelegate.idx === 0) {
return 0
}
// This is used in order to have access to the previous message and determine the timestamp
// we can't rely on the index because the sequence of messages is not ordered on the nim side
if (notificationDelegate.idx < notifDelegateList.items.count - 1) {
return notifDelegateList.items.get(notificationDelegate.idx - 1).model.index
}
return -1;
}
property string previousNotificationTimestamp: notificationDelegate.idx === 0 ? "" : chatsModel.activityNotificationList.getNotificationData(previousNotificationIndex, "timestamp")
id: notifLoader
anchors.top: parent.top
active: !!sourceComponent
width: parent.width
height: active && item.visible ? item.height : 0
sourceComponent: {
switch (model.notificationType) {
case Constants.activityCenterNotificationTypeMention:return messageNotificationComponent
case Constants.activityCenterNotificationTypeReply: return messageNotificationComponent
case Constants.activityCenterNotificationTypeGroupRequest: return groupRequestNotificationComponent
default: return null
}
}
@ -152,128 +168,13 @@ Popup {
Component {
id: messageNotificationComponent
Item {
visible: {
if (hideReadNotifications && model.read) {
return false
}
ActivityCenterMessageComponent {}
}
return activityCenter.currentFilter === ActivityCenter.Filter.All ||
(model.notificationType === Constants.activityCenterNotificationTypeMention && activityCenter.currentFilter === ActivityCenter.Filter.Mentions) ||
(model.notificationType === Constants.activityCenterNotificationTypeReply && activityCenter.currentFilter === ActivityCenter.Filter.Replies)
}
width: parent.width
height: messageNotificationContent.height
Component {
id: groupRequestNotificationComponent
StatusIconButton {
id: markReadBtn
icon.name: "double-check"
iconColor: Style.current.primary
icon.width: 24
icon.height: 24
width: 32
height: 32
onClicked: chatsModel.activityNotificationList.markActivityCenterNotificationRead(model.id)
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: messageNotificationContent.verticalCenter
z: 52
StatusToolTip {
visible: markReadBtn.hovered
text: qsTr("Mark as Read")
orientation: "left"
x: - width - Style.current.padding
y: markReadBtn.height / 2 - height / 2 + 4
}
}
Item {
id: messageNotificationContent
width: parent.width
height: childrenRect.height
Message {
id: notificationMessage
anchors.right: undefined
fromAuthor: model.message.fromAuthor
chatId: model.message.chatId
userName: model.message.userName
alias: model.message.alias
localName: model.message.localName
message: model.message.message
plainText: model.message.plainText
identicon: model.message.identicon
isCurrentUser: model.message.isCurrentUser
timestamp: model.message.timestamp
sticker: model.message.sticker
contentType: model.message.contentType
outgoingStatus: model.message.outgoingStatus
responseTo: model.message.responseTo
imageClick: imagePopup.openPopup.bind(imagePopup)
messageId: model.message.messageId
linkUrls: model.message.linkUrls
communityId: model.message.communityId
hasMention: model.message.hasMention
stickerPackId: model.message.stickerPackId
pinnedBy: model.message.pinnedBy
pinnedMessage: model.message.isPinned
activityCenterMessage: true
read: model.read
clickMessage: function (isProfileClick) {
if (isProfileClick) {
const pk = model.message.fromAuthor
const userProfileImage = appMain.getProfileImage(pk)
return openProfilePopup(chatsModel.userNameOrAlias(pk), pk, userProfileImage || utilsModel.generateIdenticon(pk))
}
activityCenter.close()
if (model.message.communityId) {
chatsModel.communities.setActiveCommunity(model.message.communityId)
}
chatsModel.channelView.setActiveChannel(model.message.chatId)
positionAtMessage(model.message.messageId)
}
prevMessageIndex: {
if (notificationDelegate.idx === 0) {
return 0
}
// This is used in order to have access to the previous message and determine the timestamp
// we can't rely on the index because the sequence of messages is not ordered on the nim side
if (notificationDelegate.idx < notifDelegateList.items.count - 1) {
return notifDelegateList.items.get(notificationDelegate.idx - 1).model.index
}
return -1;
}
prevMsgTimestamp: notificationDelegate.idx === 0 ? "" : chatsModel.activityNotificationList.getNotificationData(prevMessageIndex, "timestamp")
}
Rectangle {
anchors.top: notificationMessage.bottom
anchors.bottom: badge.bottom
anchors.bottomMargin: -Style.current.smallPadding
width: parent.width
color: model.read ? Style.current.transparent : Utils.setColorAlpha(Style.current.blue, 0.1)
}
ActivityChannelBadge {
id: badge
name: model.name
chatId: model.chatId
notificationType: model.notificationType
responseTo: model.message.responseTo
communityId: model.message.communityId
anchors.top: notificationMessage.bottom
anchors.left: parent.left
anchors.leftMargin: 61 // TODO find a way to align with the text of the message
}
}
}
ActivityCenterGroupRequest {}
}
}
}

View File

@ -0,0 +1,171 @@
import QtQuick 2.13
import QtGraphicalEffects 1.13
import "../../../../../imports"
import "../../../../../shared"
import "../../../../../shared/status"
import "../MessageComponents"
import "../../components"
import ".."
Item {
width: parent.width
height: childrenRect.height + dateGroupLbl.anchors.topMargin
DateGroup {
id: dateGroupLbl
previousMessageIndex: previousNotificationIndex
previousMessageTimestamp: previousNotificationTimestamp
messageTimestamp: model.timestamp
isActivityCenterMessage: true
height: visible ? implicitHeight : 0
}
Rectangle {
id: groupRequestContent
property string timestamp: model.timestamp
visible: {
if (hideReadNotifications && model.read) {
return false
}
return activityCenter.currentFilter === ActivityCenter.Filter.All
}
width: parent.width
height: visible ? 60 : 0
anchors.top: dateGroupLbl.bottom
anchors.topMargin: dateGroupLbl.visible ? 4 : 0
color: model.read ? Style.current.transparent : Utils.setColorAlpha(Style.current.blue, 0.1)
StatusIdenticon {
id: channelIdenticon
height: 40
width: 40
chatId: model.chatId
chatName: model.name
chatType: Constants.chatTypePrivateGroupChat
identicon: ""
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
}
Item {
id: nameItem
width: childrenRect.width
height: chatName.name
anchors.top: parent.top
anchors.topMargin: Style.current.halfPadding
// TODO fix anchoring to center when there is no author
// anchors.top: inviteText.visible ? parent.top: undefined
// anchors.topMargin: inviteText.visible ? Style.current.halfPadding : 0
// anchors.verticalCenter: inviteText.visible ? undefined : parent.verticalCenter
anchors.left: channelIdenticon.right
anchors.leftMargin: Style.current.halfPadding
SVGImage {
id: groupImage
width: 16
height: 16
anchors.verticalCenter: chatName.verticalCenter
anchors.left: parent.left
source: "../../../../img/channel-icon-group.svg"
ColorOverlay {
anchors.fill: parent
source: parent
color: chatName.color
}
}
StyledText {
id: chatName
text: model.name
anchors.left: groupImage.right
anchors.leftMargin: 4
font.pixelSize: 15
font.weight: Font.Medium
}
ChatTime {
anchors.verticalCenter: chatName.verticalCenter
anchors.left: chatName.right
anchors.leftMargin: 4
font.pixelSize: 10
visible: true
color: Style.current.secondaryText
}
}
function openProfile() {
const pk = model.author
const userProfileImage = appMain.getProfileImage(pk)
openProfilePopup(chatsModel.userNameOrAlias(pk), pk, userProfileImage || utilsModel.generateIdenticon(pk))
}
StyledTextEdit {
id: inviteText
visible: !!model.author
text: {
if (!visible) {
return ""
}
let name = chatsModel.userNameOrAlias(model.author)
if (name.length > 20) {
name = name.substring(0, 9) + "..." + name.substring(name.length - 10)
}
return qsTr("%1 invited you to join the group")
.arg(`<style type="text/css">`+
`a {`+
`color: ${Style.current.primary};`+
`text-decoration: none;` +
`}`+
`</style>`+
`<a href="#">${name}</a>`)
}
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.current.halfPadding
anchors.left: nameItem.left
anchors.right: buttons.left
anchors.rightMargin: Style.current.halfPadding
clip: true
font.pixelSize: 15
font.weight: Font.Medium
readOnly: true
selectByMouse: true
textFormat: Text.RichText
onLinkActivated: groupRequestContent.openProfile()
onLinkHovered: {
cursorShape: Qt.PointingHandCursor
}
}
AcceptRejectOptionsButtons {
id: buttons
anchors.right: parent.right
anchors.rightMargin: Style.current.halfPadding
anchors.verticalCenter: parent.verticalCenter
onAcceptClicked: chatsModel.activityNotificationList.acceptActivityCenterNotification(model.id)
onDeclineClicked: chatsModel.activityNotificationList.dismissActivityCenterNotification(model.id)
onProfileClicked: groupRequestContent.openProfile()
onBlockClicked: {
const pk = model.author
blockContactConfirmationDialog.contactName = chatsModel.userNameOrAlias(pk)
blockContactConfirmationDialog.contactAddress = pk
blockContactConfirmationDialog.open()
}
BlockContactConfirmationDialog {
id: blockContactConfirmationDialog
onBlockButtonClicked: {
profileModel.contacts.blockContact(blockContactConfirmationDialog.contactAddress)
chatsModel.activityNotificationList.dismissActivityCenterNotification(model.id)
blockContactConfirmationDialog.close()
}
}
}
}
}

View File

@ -0,0 +1,118 @@
import QtQuick 2.13
import "../../../../../imports"
import "../../../../../shared"
import "../../../../../shared/status"
import ".."
Item {
visible: {
if (hideReadNotifications && model.read) {
return false
}
return activityCenter.currentFilter === ActivityCenter.Filter.All ||
(model.notificationType === Constants.activityCenterNotificationTypeMention && activityCenter.currentFilter === ActivityCenter.Filter.Mentions) ||
(model.notificationType === Constants.activityCenterNotificationTypeReply && activityCenter.currentFilter === ActivityCenter.Filter.Replies)
}
width: parent.width
height: visible ? messageNotificationContent.height : 0
StatusIconButton {
id: markReadBtn
icon.name: "double-check"
iconColor: Style.current.primary
icon.width: 24
icon.height: 24
width: 32
height: 32
onClicked: chatsModel.activityNotificationList.markActivityCenterNotificationRead(model.id)
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: messageNotificationContent.verticalCenter
z: 52
StatusToolTip {
visible: markReadBtn.hovered
text: qsTr("Mark as Read")
orientation: "left"
x: - width - Style.current.padding
y: markReadBtn.height / 2 - height / 2 + 4
}
}
Item {
id: messageNotificationContent
width: parent.width
height: childrenRect.height
Message {
id: notificationMessage
anchors.right: undefined
fromAuthor: model.message.fromAuthor
chatId: model.message.chatId
userName: model.message.userName
alias: model.message.alias
localName: model.message.localName
message: model.message.message
plainText: model.message.plainText
identicon: model.message.identicon
isCurrentUser: model.message.isCurrentUser
timestamp: model.message.timestamp
sticker: model.message.sticker
contentType: model.message.contentType
outgoingStatus: model.message.outgoingStatus
responseTo: model.message.responseTo
imageClick: imagePopup.openPopup.bind(imagePopup)
messageId: model.message.messageId
linkUrls: model.message.linkUrls
communityId: model.message.communityId
hasMention: model.message.hasMention
stickerPackId: model.message.stickerPackId
pinnedBy: model.message.pinnedBy
pinnedMessage: model.message.isPinned
activityCenterMessage: true
read: model.read
clickMessage: function (isProfileClick) {
if (isProfileClick) {
const pk = model.message.fromAuthor
const userProfileImage = appMain.getProfileImage(pk)
return openProfilePopup(chatsModel.userNameOrAlias(pk), pk, userProfileImage || utilsModel.generateIdenticon(pk))
}
activityCenter.close()
if (model.message.communityId) {
chatsModel.communities.setActiveCommunity(model.message.communityId)
}
chatsModel.channelView.setActiveChannel(model.message.chatId)
positionAtMessage(model.message.messageId)
}
prevMessageIndex: previousNotificationIndex
prevMsgTimestamp: previousNotificationTimestamp
}
Rectangle {
anchors.top: notificationMessage.bottom
anchors.bottom: badge.bottom
anchors.bottomMargin: -Style.current.smallPadding
width: parent.width
color: model.read ? Style.current.transparent : Utils.setColorAlpha(Style.current.blue, 0.1)
}
ActivityChannelBadge {
id: badge
name: model.name
chatId: model.chatId
notificationType: model.notificationType
responseTo: model.message.responseTo
communityId: model.message.communityId
anchors.top: notificationMessage.bottom
anchors.left: parent.left
anchors.leftMargin: 61 // TODO find a way to align with the text of the message
}
}
}

View File

@ -58,6 +58,7 @@ Item {
previousMessageIndex: prevMessageIndex
previousMessageTimestamp: prevMsgTimestamp
messageTimestamp: timestamp
isActivityCenterMessage: activityCenterMessage
}
Rectangle {

View File

@ -3,6 +3,7 @@ import "../../../../../shared"
import "../../../../../imports"
StyledText {
property bool isActivityCenterMessage: false
property int previousMessageIndex: -1
property string previousMessageTimestamp
property string messageTimestamp
@ -11,11 +12,11 @@ StyledText {
font.pixelSize: 13
color: Style.current.secondaryText
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: activityCenterMessage ? undefined : parent.horizontalCenter
anchors.horizontalCenter: isActivityCenterMessage ? undefined : parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: visible ? (activityCenterMessage ? Style.current.halfPadding : 20) : 0
anchors.topMargin: visible ? (isActivityCenterMessage ? Style.current.halfPadding : 20) : 0
anchors.left: parent.left
anchors.leftMargin: activityCenterMessage ? Style.current.padding : 0
anchors.leftMargin: isActivityCenterMessage ? Style.current.padding : 0
text: {
if (previousMessageIndex === -1) return ""; // identifier

View File

@ -21,6 +21,7 @@ Item {
previousMessageIndex: prevMessageIndex
previousMessageTimestamp: prevMsgTimestamp
messageTimestamp: timestamp
isActivityCenterMessage: activityCenterMessage
}
UserImage {

View File

@ -0,0 +1,81 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import "../../../../imports"
import "../../../../shared"
import "../../../../shared/status"
Row {
signal acceptClicked()
signal declineClicked()
signal blockClicked()
signal profileClicked()
id: root
height: acceptBtn.height
spacing: Style.current.halfPadding
StatusIconButton {
id: acceptBtn
icon.name: "check-circle"
onClicked: root.acceptClicked()
width: 32
height: 32
padding: 6
iconColor: Style.current.success
hoveredIconColor: Style.current.success
highlightedBackgroundColor: Utils.setColorAlpha(Style.current.success, 0.1)
anchors.verticalCenter: parent.verticalCenter
}
StatusIconButton {
id: declineBtn
icon.name: "close"
onClicked: root.declineClicked()
width: 32
height: 32
padding: 6
iconColor: Style.current.danger
hoveredIconColor: Style.current.danger
highlightedBackgroundColor: Utils.setColorAlpha(Style.current.danger, 0.1)
anchors.verticalCenter: parent.verticalCenter
}
StatusContextMenuButton {
property int iconSize: 14
id: menuButton
anchors.verticalCenter: parent.verticalCenter
MouseArea {
id: mouseArea
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: {
contactContextMenu.popup()
}
PopupMenu {
id: contactContextMenu
hasArrow: false
Action {
icon.source: "../../../img/profileActive.svg"
icon.width: menuButton.iconSize
icon.height: menuButton.iconSize
//% "View Profile"
text: qsTrId("view-profile")
onTriggered: root.profileClicked()
enabled: true
}
Separator {}
Action {
icon.source: "../../../img/block-icon.svg"
icon.width: menuButton.iconSize
icon.height: menuButton.iconSize
icon.color: Style.current.danger
text: qsTr("Decline and block")
onTriggered: root.blockClicked()
}
}
}
}
}

View File

@ -39,7 +39,7 @@ Rectangle {
anchors.topMargin: Style.current.smallPadding
anchors.left: accountImage.right
anchors.leftMargin: Style.current.padding
anchors.right: declineBtn.left
anchors.right: buttons.left
anchors.rightMargin: Style.current.padding
}
@ -58,76 +58,17 @@ Rectangle {
onHoveredChanged: container.isHovered = hovered
}
StatusIconButton {
id: declineBtn
icon.name: "close"
onClicked: profileModel.contacts.rejectContactRequest(container.address)
width: 32
height: 32
padding: 6
iconColor: Style.current.danger
hoveredIconColor: Style.current.danger
highlightedBackgroundColor: Utils.setColorAlpha(Style.current.danger, 0.1)
anchors.right: acceptBtn.left
anchors.rightMargin: Style.current.halfPadding
AcceptRejectOptionsButtons {
id: buttons
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
}
StatusIconButton {
id: acceptBtn
icon.name: "check-circle"
onClicked: {
onAcceptClicked: {
chatsModel.channelView.joinPrivateChat(container.address, "")
profileModel.contacts.addContact(container.address)
}
width: 32
height: 32
padding: 6
iconColor: Style.current.success
hoveredIconColor: Style.current.success
highlightedBackgroundColor: Utils.setColorAlpha(Style.current.success, 0.1)
anchors.right: menuButton.left
anchors.rightMargin: Style.current.halfPadding
anchors.verticalCenter: parent.verticalCenter
}
StatusContextMenuButton {
property int iconSize: 14
id: menuButton
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
MouseArea {
id: mouseArea
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: {
contactContextMenu.popup()
}
PopupMenu {
id: contactContextMenu
hasArrow: false
Action {
icon.source: "../../../img/profileActive.svg"
icon.width: menuButton.iconSize
icon.height: menuButton.iconSize
//% "View Profile"
text: qsTrId("view-profile")
onTriggered: profileClick(true, name, address, identicon, "", localNickname)
enabled: true
}
Separator {}
Action {
icon.source: "../../../img/block-icon.svg"
icon.width: menuButton.iconSize
icon.height: menuButton.iconSize
icon.color: Style.current.danger
text: qsTr("Decline and block")
onTriggered: container.blockContactActionTriggered(name, address)
}
}
}
onDeclineClicked: profileModel.contacts.rejectContactRequest(container.address)
onProfileClicked: profileClick(true, name, address, identicon, "", localNickname)
onBlockClicked: container.blockContactActionTriggered(name, address)
}
}

View File

@ -14,6 +14,7 @@ QtObject {
readonly property int communityChatOnRequestAccess: 3
readonly property int activityCenterNotificationTypeGroupRequest: 2
readonly property int activityCenterNotificationTypeMention: 3
readonly property int activityCenterNotificationTypeReply: 4

View File

@ -97,6 +97,8 @@ DISTFILES += \
app/AppLayouts/Browser/FavoritesList.qml \
app/AppLayouts/Browser/components/BookmarkButton.qml \
app/AppLayouts/Chat/ChatColumn/ActivityCenter.qml \
app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityCenterGroupRequest.qml \
app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityCenterMessageComponent.qml \
app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityCenterTopBar.qml \
app/AppLayouts/Chat/ChatColumn/ChatComponents/ActivityChannelBadge.qml \
app/AppLayouts/Chat/ChatColumn/ChatComponents/AddToContactBanner.qml \
@ -155,6 +157,7 @@ DISTFILES += \
app/AppLayouts/Chat/CommunityComponents/TransferOwnershipPopup.qml \
app/AppLayouts/Chat/ContactsColumn/AddChat.qml \
app/AppLayouts/Chat/ContactsColumn/ClosedEmptyView.qml \
app/AppLayouts/Chat/components/AcceptRejectOptionsButtons.qml \
app/AppLayouts/Chat/components/ChooseBrowserPopup.qml \
app/AppLayouts/Chat/ContactsColumn/CommunityButton.qml \
app/AppLayouts/Chat/ContactsColumn/CommunityList.qml \