diff --git a/src/app/chat/event_handling.nim b/src/app/chat/event_handling.nim
index dcfce697b9..eef60474e2 100644
--- a/src/app/chat/event_handling.nim
+++ b/src/app/chat/event_handling.nim
@@ -28,6 +28,8 @@ proc handleChatEvents(self: ChatController) =
if (evArgs.communities.len > 0):
for community in evArgs.communities:
self.view.addCommunityToList(community)
+ if (evArgs.communityMembershipRequests.len > 0):
+ self.view.addMembershipRequests(evArgs.communityMembershipRequests)
self.status.events.on("channelUpdate") do(e: Args):
var evArgs = ChatUpdateArgs(e)
diff --git a/src/app/chat/signal_handling.nim b/src/app/chat/signal_handling.nim
index d2ff4d58f1..80281fb48c 100644
--- a/src/app/chat/signal_handling.nim
+++ b/src/app/chat/signal_handling.nim
@@ -1,7 +1,7 @@
proc handleSignals(self: ChatController) =
self.status.events.on(SignalType.Message.event) do(e:Args):
var data = MessageSignal(e)
- self.status.chat.update(data.chats, data.messages, data.emojiReactions, data.communities)
+ self.status.chat.update(data.chats, data.messages, data.emojiReactions, data.communities, data.membershipRequests)
self.status.events.on(SignalType.DiscoverySummary.event) do(e:Args):
## Handle mailserver peers being added and removed
diff --git a/src/app/chat/view.nim b/src/app/chat/view.nim
index fafedf850f..613618d828 100644
--- a/src/app/chat/view.nim
+++ b/src/app/chat/view.nim
@@ -17,7 +17,7 @@ import ../../status/chat/[chat, message]
import ../../status/profile/profile
import web3/[conversions, ethtypes]
import ../../status/threads
-import views/[channels_list, message_list, chat_item, suggestions_list, reactions, stickers, groups, transactions, community_list, community_item]
+import views/[channels_list, message_list, chat_item, suggestions_list, reactions, stickers, groups, transactions, community_list, community_item, community_membership_request_list]
import json_serialization
import ../utils/image_utils
@@ -48,6 +48,7 @@ QtObject:
observedCommunity*: CommunityItemView
communityList*: CommunityList
joinedCommunityList*: CommunityList
+ myCommunityRequests*: seq[CommunityMembershipRequest]
replyTo: string
channelOpenTime*: Table[string, int64]
connected: bool
@@ -699,6 +700,32 @@ QtObject:
QtProperty[QVariant] transactions:
read = getTransactions
+
+ proc pendingRequestsToJoinForCommunity*(self: ChatsView, communityId: string): seq[CommunityMembershipRequest] =
+ result = self.status.chat.pendingRequestsToJoinForCommunity(communityId)
+
+ proc membershipRequestPushed*(self: ChatsView, communityName: string, pubKey: string) {.signal.}
+
+ proc addMembershipRequests*(self: ChatsView, membershipRequests: seq[CommunityMembershipRequest]) =
+ var communityId: string
+ var community: Community
+ for request in membershipRequests:
+ communityId = request.communityId
+ community = self.joinedCommunityList.getCommunityById(communityId)
+ if (community.id == ""):
+ continue
+ let alreadyPresentRequestIdx = community.membershipRequests.findIndexById(request.id)
+ if (alreadyPresentRequestIdx == -1):
+ community.membershipRequests.add(request)
+ self.membershipRequestPushed(community.name, request.publicKey)
+ else:
+ community.membershipRequests[alreadyPresentRequestIdx] = request
+ self.joinedCommunityList.replaceCommunity(community)
+
+ # Add to active community list
+ if (communityId == self.activeCommunity.communityItem.id):
+ self.activeCommunity.communityMembershipRequestList.addCommunityMembershipRequestItemToList(request)
+
proc communitiesChanged*(self: ChatsView) {.signal.}
proc getCommunitiesIfNotFetched*(self: ChatsView): CommunityList =
@@ -723,12 +750,45 @@ QtObject:
self.joinedCommunityList.setNewData(communities)
self.joinedCommunityList.fetched = true
+ # Also fetch requests
+ self.myCommunityRequests = self.status.chat.myPendingRequestsToJoin()
+
return newQVariant(self.joinedCommunityList)
QtProperty[QVariant] joinedCommunities:
read = getJoinedComunities
notify = joinedCommunitiesChanged
+ proc activeCommunityChanged*(self: ChatsView) {.signal.}
+
+ proc setActiveCommunity*(self: ChatsView, communityId: string) {.slot.} =
+ if(communityId == ""): return
+ self.addMembershipRequests(self.pendingRequestsToJoinForCommunity(communityId))
+ self.activeCommunity.setCommunityItem(self.joinedCommunityList.getCommunityById(communityId))
+ self.activeCommunity.setActive(true)
+ self.activeCommunityChanged()
+
+ proc getActiveCommunity*(self: ChatsView): QVariant {.slot.} =
+ newQVariant(self.activeCommunity)
+
+ QtProperty[QVariant] activeCommunity:
+ read = getActiveCommunity
+ write = setActiveCommunity
+ notify = activeCommunityChanged
+
+ proc joinCommunity*(self: ChatsView, communityId: string, setActive: bool = true): string {.slot.} =
+ result = ""
+ try:
+ self.status.chat.joinCommunity(communityId)
+ self.joinedCommunityList.addCommunityItemToList(self.communityList.getCommunityById(communityId))
+ if (setActive):
+ self.setActiveCommunity(communityId)
+ except Exception as e:
+ error "Error joining the community", msg = e.msg
+ result = fmt"Error joining the community: {e.msg}"
+
+ proc membershipRequestChanged*(self: ChatsView, communityName: string, accepted: bool) {.signal.}
+
proc addCommunityToList*(self: ChatsView, community: Community) =
let communityCheck = self.communityList.getCommunityById(community.id)
if (communityCheck.id == ""):
@@ -742,16 +802,27 @@ QtObject:
self.joinedCommunityList.addCommunityItemToList(community)
else:
self.joinedCommunityList.replaceCommunity(community)
+ elif (community.isMember == true):
+ discard self.joinCommunity(community.id, false)
+ var i = 0
+ for communityRequest in self.myCommunityRequests:
+ if (communityRequest.communityId == community.id):
+ self.membershipRequestChanged(community.name, true)
+ self.myCommunityRequests.delete(i, i)
+ break
+ i = i + 1
- proc createCommunity*(self: ChatsView, name: string, description: string, color: string, imagePath: string): string {.slot.} =
+ proc isCommunityRequestPending*(self: ChatsView, communityId: string): bool {.slot.} =
+ for communityRequest in self.myCommunityRequests:
+ if (communityRequest.communityId == communityId):
+ return true
+ return false
+
+ proc createCommunity*(self: ChatsView, name: string, description: string, access: int, ensOnly: bool, imagePath: string, aX: int, aY: int, bX: int, bY: int): string {.slot.} =
result = ""
try:
- # TODO Change this to get it from the user choices
- let access = ord(CommunityAccessLevel.public)
var image = image_utils.formatImagePath(imagePath)
- let tmpImagePath = image_resizer(image, 2000, TMPDIR)
- let community = self.status.chat.createCommunity(name, description, color, tmpImagePath, access)
- removeFile(tmpImagePath)
+ let community = self.status.chat.createCommunity(name, description, access, ensOnly, image, aX, aY, bX, bY)
if (community.id == ""):
return "Community was not created. Please try again later"
@@ -777,22 +848,6 @@ QtObject:
error "Error creating the channel", msg = e.msg
result = fmt"Error creating the channel: {e.msg}"
- proc activeCommunityChanged*(self: ChatsView) {.signal.}
-
- proc setActiveCommunity*(self: ChatsView, communityId: string) {.slot.} =
- if(communityId == ""): return
- self.activeCommunity.setCommunityItem(self.joinedCommunityList.getCommunityById(communityId))
- self.activeCommunity.setActive(true)
- self.activeCommunityChanged()
-
- proc getActiveCommunity*(self: ChatsView): QVariant {.slot.} =
- newQVariant(self.activeCommunity)
-
- QtProperty[QVariant] activeCommunity:
- read = getActiveCommunity
- write = setActiveCommunity
- notify = activeCommunityChanged
-
proc observedCommunityChanged*(self: ChatsView) {.signal.}
proc setObservedCommunity*(self: ChatsView, communityId: string) {.slot.} =
@@ -812,16 +867,6 @@ QtObject:
write = setObservedCommunity
notify = observedCommunityChanged
- proc joinCommunity*(self: ChatsView, communityId: string): string {.slot.} =
- result = ""
- try:
- self.status.chat.joinCommunity(communityId)
- self.joinedCommunityList.addCommunityItemToList(self.communityList.getCommunityById(communityId))
- self.setActiveCommunity(communityId)
- except Exception as e:
- error "Error joining the community", msg = e.msg
- result = fmt"Error joining the community: {e.msg}"
-
proc leaveCommunity*(self: ChatsView, communityId: string): string {.slot.} =
result = ""
try:
@@ -867,6 +912,32 @@ QtObject:
error "Error removing user from the community", msg = e.msg
+ proc requestToJoinCommunity*(self: ChatsView, communityId: string, ensName: string) {.slot.} =
+ try:
+ let requests = self.status.chat.requestToJoinCommunity(communityId, ensName)
+ for request in requests:
+ self.myCommunityRequests.add(request)
+ except Exception as e:
+ error "Error requesting to join the community", msg = e.msg
+
+ proc acceptRequestToJoinCommunity*(self: ChatsView, requestId: string): string {.slot.} =
+ try:
+ self.status.chat.acceptRequestToJoinCommunity(requestId)
+ self.activeCommunity.communityMembershipRequestList.removeCommunityMembershipRequestItemFromList(requestId)
+ except Exception as e:
+ error "Error accepting request to join the community", msg = e.msg
+ return "Error accepting request to join the community"
+ return ""
+
+ proc declineRequestToJoinCommunity*(self: ChatsView, requestId: string): string {.slot.} =
+ try:
+ self.status.chat.declineRequestToJoinCommunity(requestId)
+ self.activeCommunity.communityMembershipRequestList.removeCommunityMembershipRequestItemFromList(requestId)
+ except Exception as e:
+ error "Error declining request to join the community", msg = e.msg
+ return "Error declining request to join the community"
+ return ""
+
method rowCount*(self: ChatsView, index: QModelIndex = nil): int =
result = self.messageList.len
diff --git a/src/app/chat/views/community_item.nim b/src/app/chat/views/community_item.nim
index 335795d62c..9cc625d8c2 100644
--- a/src/app/chat/views/community_item.nim
+++ b/src/app/chat/views/community_item.nim
@@ -3,10 +3,12 @@ import ../../../status/[chat/chat, status]
import channels_list
import ../../../eventemitter
import community_members_list
+import community_membership_request_list
QtObject:
type CommunityItemView* = ref object of QObject
communityItem*: Community
+ communityMembershipRequestList*: CommunityMembershipRequestList
chats*: ChannelsList
members*: CommunityMembersView
status*: Status
@@ -25,6 +27,7 @@ QtObject:
result.status = status
result.active = false
result.chats = newChannelsList(status)
+ result.communityMembershipRequestList = newCommunityMembershipRequestList()
result.members = newCommunityMembersView(status)
result.setup
@@ -32,6 +35,7 @@ QtObject:
self.communityItem = communityItem
self.chats.setChats(communityItem.chats)
self.members.setMembers(communityItem.members)
+ self.communityMembershipRequestList.setNewData(communityItem.membershipRequests)
proc activeChanged*(self: CommunityItemView) {.signal.}
@@ -88,6 +92,31 @@ QtObject:
QtProperty[bool] verified:
read = verified
+ proc ensOnly*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.ensOnly
+
+ QtProperty[bool] ensOnly:
+ read = ensOnly
+
+ proc canRequestAccess*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.canRequestAccess
+
+ QtProperty[bool] canRequestAccess:
+ read = canRequestAccess
+
+ proc canManageUsers*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.canManageUsers
+
+ QtProperty[bool] canManageUsers:
+ read = canManageUsers
+
+ proc canJoin*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.canJoin
+
+ QtProperty[bool] canJoin:
+ read = canJoin
+
+ proc isMember*(self: CommunityItemView): bool {.slot.} = result = ?.self.communityItem.isMember
+
+ QtProperty[bool] isMember:
+ read = isMember
+
proc nbMembers*(self: CommunityItemView): int {.slot.} = result = ?.self.communityItem.members.len
QtProperty[int] nbMembers:
@@ -104,4 +133,26 @@ QtObject:
result = newQVariant(self.members)
QtProperty[QVariant] members:
- read = getMembers
\ No newline at end of file
+ read = getMembers
+
+ proc getCommunityMembershipRequest*(self: CommunityItemView): QVariant {.slot.} =
+ result = newQVariant(self.communityMembershipRequestList)
+
+ QtProperty[QVariant] communityMembershipRequests:
+ read = getCommunityMembershipRequest
+
+ proc thumbnailImage*(self: CommunityItemView): string {.slot.} =
+ if (self.communityItem.communityImage.isNil):
+ return ""
+ result = self.communityItem.communityImage.thumbnail
+
+ QtProperty[string] thumbnailImage:
+ read = thumbnailImage
+
+ proc largeImage*(self: CommunityItemView): string {.slot.} =
+ if (self.communityItem.communityImage.isNil):
+ return ""
+ result = self.communityItem.communityImage.large
+
+ QtProperty[string] largeImage:
+ read = largeImage
\ No newline at end of file
diff --git a/src/app/chat/views/community_list.nim b/src/app/chat/views/community_list.nim
index fa46b20b7c..9ef3a35385 100644
--- a/src/app/chat/views/community_list.nim
+++ b/src/app/chat/views/community_list.nim
@@ -10,12 +10,18 @@ type
Id = UserRole + 1,
Name = UserRole + 2
Description = UserRole + 3
- # Color = UserRole + 4
- Access = UserRole + 5
- Admin = UserRole + 6
- Joined = UserRole + 7
- Verified = UserRole + 8
- NumMembers = UserRole + 9
+ Access = UserRole + 4
+ Admin = UserRole + 5
+ Joined = UserRole + 6
+ Verified = UserRole + 7
+ NumMembers = UserRole + 8
+ ThumbnailImage = UserRole + 9
+ LargeImage = UserRole + 10
+ EnsOnly = UserRole + 11
+ CanRequestAccess = UserRole + 12
+ CanManageUsers = UserRole + 13
+ CanJoin = UserRole + 14
+ IsMember = UserRole + 15
QtObject:
type
@@ -50,25 +56,44 @@ QtObject:
of CommunityRoles.Name: result = newQVariant(communityItem.name)
of CommunityRoles.Description: result = newQVariant(communityItem.description)
of CommunityRoles.Id: result = newQVariant(communityItem.id)
- # of CommunityRoles.Color: result = newQVariant(communityItem.color)
of CommunityRoles.Access: result = newQVariant(communityItem.access.int)
of CommunityRoles.Admin: result = newQVariant(communityItem.admin.bool)
of CommunityRoles.Joined: result = newQVariant(communityItem.joined.bool)
of CommunityRoles.Verified: result = newQVariant(communityItem.verified.bool)
+ of CommunityRoles.EnsOnly: result = newQVariant(communityItem.ensOnly.bool)
+ of CommunityRoles.CanRequestAccess: result = newQVariant(communityItem.canRequestAccess.bool)
+ of CommunityRoles.CanManageUsers: result = newQVariant(communityItem.canManageUsers.bool)
+ of CommunityRoles.CanJoin: result = newQVariant(communityItem.canJoin.bool)
+ of CommunityRoles.IsMember: result = newQVariant(communityItem.isMember.bool)
of CommunityRoles.NumMembers: result = newQVariant(communityItem.members.len)
-
+ of CommunityRoles.ThumbnailImage:
+ if (not communityItem.communityImage.isNil):
+ result = newQVariant(communityItem.communityImage.thumbnail)
+ else:
+ result = newQVariant("")
+ of CommunityRoles.LargeImage:
+ if (not communityItem.communityImage.isNil):
+ result = newQVariant(communityItem.communityImage.large)
+ else:
+ result = newQVariant("")
method roleNames(self: CommunityList): Table[int, string] =
{
CommunityRoles.Name.int:"name",
CommunityRoles.Description.int:"description",
CommunityRoles.Id.int: "id",
- # CommunityRoles.Color.int: "color",
CommunityRoles.Access.int: "access",
CommunityRoles.Admin.int: "admin",
CommunityRoles.Verified.int: "verified",
CommunityRoles.Joined.int: "joined",
- CommunityRoles.NumMembers.int: "nbMembers"
+ CommunityRoles.EnsOnly.int: "ensOnly",
+ CommunityRoles.CanRequestAccess.int: "canRequestAccess",
+ CommunityRoles.CanManageUsers.int: "canManageUsers",
+ CommunityRoles.CanJoin.int: "canJoin",
+ CommunityRoles.IsMember.int: "isMember",
+ CommunityRoles.NumMembers.int: "nbMembers",
+ CommunityRoles.ThumbnailImage.int:"thumbnailImage",
+ CommunityRoles.LargeImage.int:"largeImage"
}.toTable
proc setNewData*(self: CommunityList, communityList: seq[Community]) =
diff --git a/src/app/chat/views/community_membership_request_list.nim b/src/app/chat/views/community_membership_request_list.nim
new file mode 100644
index 0000000000..3a88cf1419
--- /dev/null
+++ b/src/app/chat/views/community_membership_request_list.nim
@@ -0,0 +1,91 @@
+import NimQml, Tables, chronicles
+import ../../../status/chat/[chat, message]
+import ../../../status/status
+import ../../../status/ens
+import ../../../status/accounts
+import strutils
+
+type
+ CommunityMembershipRequestRoles {.pure.} = enum
+ Id = UserRole + 1,
+ PublicKey = UserRole + 2
+ ChatId = UserRole + 3
+ CommunityId = UserRole + 4
+ State = UserRole + 5
+ Our = UserRole + 6
+
+QtObject:
+ type
+ CommunityMembershipRequestList* = ref object of QAbstractListModel
+ communityMembershipRequests*: seq[CommunityMembershipRequest]
+
+ proc setup(self: CommunityMembershipRequestList) = self.QAbstractListModel.setup
+
+ proc delete(self: CommunityMembershipRequestList) =
+ self.communityMembershipRequests = @[]
+ self.QAbstractListModel.delete
+
+ proc newCommunityMembershipRequestList*(): CommunityMembershipRequestList =
+ new(result, delete)
+ result.communityMembershipRequests = @[]
+ result.setup()
+
+ method rowCount*(self: CommunityMembershipRequestList, index: QModelIndex = nil): int = self.communityMembershipRequests.len
+
+ method data(self: CommunityMembershipRequestList, index: QModelIndex, role: int): QVariant =
+ if not index.isValid:
+ return
+ if index.row < 0 or index.row >= self.communityMembershipRequests.len:
+ return
+
+ let communityMembershipRequestItem = self.communityMembershipRequests[index.row]
+ let communityMembershipRequestItemRole = role.CommunityMembershipRequestRoles
+ case communityMembershipRequestItemRole:
+ of CommunityMembershipRequestRoles.Id: result = newQVariant(communityMembershipRequestItem.id.string)
+ of CommunityMembershipRequestRoles.PublicKey: result = newQVariant(communityMembershipRequestItem.publicKey.string)
+ of CommunityMembershipRequestRoles.ChatId: result = newQVariant(communityMembershipRequestItem.chatId.string)
+ of CommunityMembershipRequestRoles.CommunityId: result = newQVariant(communityMembershipRequestItem.communityId.string)
+ of CommunityMembershipRequestRoles.State: result = newQVariant(communityMembershipRequestItem.state.int)
+ of CommunityMembershipRequestRoles.Our: result = newQVariant(communityMembershipRequestItem.our.string)
+
+ method roleNames(self: CommunityMembershipRequestList): Table[int, string] =
+ {
+ CommunityMembershipRequestRoles.Id.int: "id",
+ CommunityMembershipRequestRoles.PublicKey.int: "publicKey",
+ CommunityMembershipRequestRoles.ChatId.int: "chatId",
+ CommunityMembershipRequestRoles.CommunityId.int: "communityId",
+ CommunityMembershipRequestRoles.State.int: "state",
+ CommunityMembershipRequestRoles.Our.int: "our"
+ }.toTable
+
+ proc nbRequestsChanged*(self: CommunityMembershipRequestList) {.signal.}
+
+ proc nbRequests*(self: CommunityMembershipRequestList): int {.slot.} = result = self.communityMembershipRequests.len
+
+ QtProperty[int] nbRequests:
+ read = nbRequests
+ notify = nbRequestsChanged
+
+ proc setNewData*(self: CommunityMembershipRequestList, communityMembershipRequestList: seq[CommunityMembershipRequest]) =
+ self.beginResetModel()
+ self.communityMembershipRequests = communityMembershipRequestList
+ self.endResetModel()
+ self.nbRequestsChanged()
+
+ proc addCommunityMembershipRequestItemToList*(self: CommunityMembershipRequestList, communityMemberphipRequest: CommunityMembershipRequest) =
+ self.beginInsertRows(newQModelIndex(), self.communityMembershipRequests.len, self.communityMembershipRequests.len)
+ self.communityMembershipRequests.add(communityMemberphipRequest)
+ self.endInsertRows()
+ self.nbRequestsChanged()
+
+ proc removeCommunityMembershipRequestItemFromList*(self: CommunityMembershipRequestList, id: string) =
+ let idx = self.communityMembershipRequests.findIndexById(id)
+ self.beginRemoveRows(newQModelIndex(), idx, idx)
+ self.communityMembershipRequests.delete(idx)
+ self.endRemoveRows()
+ self.nbRequestsChanged()
+
+ proc getCommunityMembershipRequestById*(self: CommunityMembershipRequestList, communityMembershipRequestId: string): CommunityMembershipRequest =
+ for communityMembershipRequest in self.communityMembershipRequests:
+ if communityMembershipRequest.id == communityMembershipRequestId:
+ return communityMembershipRequest
diff --git a/src/status/chat.nim b/src/status/chat.nim
index 636fbbd01a..ee4bdcd0ad 100644
--- a/src/status/chat.nim
+++ b/src/status/chat.nim
@@ -25,6 +25,7 @@ type
contacts*: seq[Profile]
emojiReactions*: seq[Reaction]
communities*: seq[Community]
+ communityMembershipRequests*: seq[CommunityMembershipRequest]
ChatIdArg* = ref object of Args
chatId*: string
@@ -70,7 +71,7 @@ proc newChatModel*(events: EventEmitter): ChatModel =
proc delete*(self: ChatModel) =
discard
-proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiReactions: seq[Reaction], communities: seq[Community]) =
+proc update*(self: ChatModel, chats: seq[Chat], messages: seq[Message], emojiReactions: seq[Reaction], communities: seq[Community], communityMembershipRequests: seq[CommunityMembershipRequest]) =
for chat in chats:
if chat.isActive:
self.channels[chat.id] = chat
@@ -84,7 +85,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: chats, contacts: @[], emojiReactions: emojiReactions, communities: communities))
+ self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats, contacts: @[], emojiReactions: emojiReactions, communities: communities, communityMembershipRequests: communityMembershipRequests))
proc hasChannel*(self: ChatModel, chatId: string): bool =
self.channels.hasKey(chatId)
@@ -412,8 +413,8 @@ proc getAllComunities*(self: ChatModel): seq[Community] =
proc getJoinedComunities*(self: ChatModel): seq[Community] =
result = status_chat.getJoinedComunities()
-proc createCommunity*(self: ChatModel, name: string, description: string, color: string, image: string, access: int): Community =
- result = status_chat.createCommunity(name, description, color, image, access)
+proc createCommunity*(self: ChatModel, name: string, description: string, access: int, ensOnly: bool, imageUrl: string, aX: int, aY: int, bX: int, bY: int): Community =
+ result = status_chat.createCommunity(name, description, access, ensOnly, imageUrl, aX, aY, bX, bY)
proc createCommunityChannel*(self: ChatModel, communityId: string, name: string, description: string): Chat =
result = status_chat.createCommunityChannel(communityId, name, description)
@@ -436,4 +437,19 @@ proc exportCommunity*(self: ChatModel, communityId: string): string =
result = status_chat.exportCommunity(communityId)
proc importCommunity*(self: ChatModel, communityKey: string) =
- status_chat.importCommunity(communityKey)
\ No newline at end of file
+ status_chat.importCommunity(communityKey)
+
+proc requestToJoinCommunity*(self: ChatModel, communityKey: string, ensName: string): seq[CommunityMembershipRequest] =
+ status_chat.requestToJoinCommunity(communityKey, ensName)
+
+proc acceptRequestToJoinCommunity*(self: ChatModel, requestId: string) =
+ status_chat.acceptRequestToJoinCommunity(requestId)
+
+proc declineRequestToJoinCommunity*(self: ChatModel, requestId: string) =
+ status_chat.declineRequestToJoinCommunity(requestId)
+
+proc pendingRequestsToJoinForCommunity*(self: ChatModel, communityKey: string): seq[CommunityMembershipRequest] =
+ result = status_chat.pendingRequestsToJoinForCommunity(communityKey)
+
+proc myPendingRequestsToJoin*(self: ChatModel): seq[CommunityMembershipRequest] =
+ result = status_chat.myPendingRequestsToJoin()
\ No newline at end of file
diff --git a/src/status/chat/chat.nim b/src/status/chat/chat.nim
index b0d8aa157d..074018f5fc 100644
--- a/src/status/chat/chat.nim
+++ b/src/status/chat/chat.nim
@@ -1,5 +1,6 @@
import strformat, json, sequtils
from message import Message
+import ../libstatus/types
type ChatType* {.pure.}= enum
Unknown = 0,
@@ -74,13 +75,22 @@ type Chat* = ref object
membershipUpdateEvents*: seq[ChatMembershipEvent]
hasMentions*: bool
muted*: bool
+ canPost*: bool
ensName*: string
type CommunityAccessLevel* = enum
- unknown = 0
- public = 1
- invitationOnly = 2
- onRequest = 3
+ unknown = 0
+ public = 1
+ invitationOnly = 2
+ onRequest = 3
+
+type CommunityMembershipRequest* = object
+ id*: string
+ publicKey*: string
+ chatId*: string
+ communityId*: string
+ state*: int
+ our*: string
type Community* = object
id*: string
@@ -88,11 +98,17 @@ type Community* = object
description*: string
chats*: seq[Chat]
members*: seq[string]
- # color*: string
access*: int
admin*: bool
joined*: bool
verified*: bool
+ ensOnly*: bool
+ canRequestAccess*: bool
+ canManageUsers*: bool
+ canJoin*: bool
+ isMember*: bool
+ communityImage*: IdentityImage
+ membershipRequests*: seq[CommunityMembershipRequest]
proc `$`*(self: Chat): string =
result = fmt"Chat(id:{self.id}, name:{self.name}, active:{self.isActive}, type:{self.chatType})"
@@ -134,6 +150,15 @@ proc findIndexById*(self: seq[Community], id: string): int =
result = idx
break
+proc findIndexById*(self: seq[CommunityMembershipRequest], id: string): int =
+ result = -1
+ var idx = -1
+ for item in self:
+ inc idx
+ if(item.id == id):
+ result = idx
+ break
+
proc isMember*(self: Chat, pubKey: string): bool =
for member in self.members:
if member.id == pubKey and member.joined: return true
diff --git a/src/status/contacts.nim b/src/status/contacts.nim
index 8b7d00bf58..2c7a5d3cec 100644
--- a/src/status/contacts.nim
+++ b/src/status/contacts.nim
@@ -41,6 +41,7 @@ proc unblockContact*(self: ContactModel, id: string): string =
var contact = self.getContactByID(id)
contact.systemTags.delete(contact.systemTags.find(":contact/blocked"))
discard status_contacts.saveContact(contact.id, contact.ensVerified, contact.ensName, contact.ensVerifiedAt, contact.ensVerificationRetries,contact.alias, contact.identicon, contact.identityImage.thumbnail, contact.systemTags, contact.localNickname)
+ discard status_contacts.saveContact(contact.id, contact.ensVerified, contact.ensName, contact.alias, contact.identicon, contact.systemTags, contact.localNickname)
self.events.emit("contactUnblocked", Args())
proc getAllContacts*(): seq[Profile] =
@@ -64,9 +65,7 @@ proc addContact*(self: ContactModel, id: string, localNickname: string): string
alias: alias,
ensName: "",
ensVerified: false,
- ensVerifiedAt: 0,
appearance: 0,
- ensVerificationRetries: 0,
systemTags: @[]
)
@@ -98,9 +97,7 @@ proc addContact*(self: ContactModel, id: string, localNickname: string): string
alias: contact.alias,
ensName: contact.ensName,
ensVerified: contact.ensVerified,
- ensVerifiedAt: contact.ensVerifiedAt,
appearance: 0,
- ensVerificationRetries: contact.ensVerificationRetries,
systemTags: contact.systemTags,
localNickname: nickname
)
diff --git a/src/status/libstatus/chat.nim b/src/status/libstatus/chat.nim
index 717787fa0f..f7bdc94b4b 100644
--- a/src/status/libstatus/chat.nim
+++ b/src/status/libstatus/chat.nim
@@ -257,24 +257,18 @@ proc getJoinedComunities*(): seq[Community] =
communities.add(community)
return communities
-proc createCommunity*(name: string, description: string, color: string, image: string, access: int): Community =
+proc createCommunity*(name: string, description: string, access: int, ensOnly: bool, imageUrl: string, aX: int, aY: int, bX: int, bY: int): Community =
let rpcResult = callPrivateRPC("createCommunity".prefix, %*[{
- "permissions": {
- "access": access
- },
- "identity": {
- "display_name": name,
- "description": description#,
- # "color": color#,
- # TODO add images once it is supported by Status-Go
- # "images": [
- # {
- # "payload": image,
- # # TODO get that from an enum
- # "image_type": 1 # 1 is a raw payload
- # }
- # ]
- }
+ # TODO this will need to be renamed membership (small m)
+ "Membership": access,
+ "name": name,
+ "description": description,
+ "ensOnly": ensOnly,
+ "image": imageUrl,
+ "imageAx": aX,
+ "imageAy": aY,
+ "imageBx": bX,
+ "imageBy": bY
}]).parseJSON()
if rpcResult{"result"}.kind != JNull:
@@ -321,4 +315,50 @@ proc importCommunity*(communityKey: string) =
discard callPrivateRPC("importCommunity".prefix, %*[communityKey])
proc removeUserFromCommunity*(communityId: string, pubKey: string) =
- discard callPrivateRPC("removeUserFromCommunity".prefix, %*[communityId, pubKey])
\ No newline at end of file
+ discard callPrivateRPC("removeUserFromCommunity".prefix, %*[communityId, pubKey])
+
+proc requestToJoinCommunity*(communityId: string, ensName: string): seq[CommunityMembershipRequest] =
+ let rpcResult = callPrivateRPC("requestToJoinCommunity".prefix, %*[{
+ "communityId": communityId,
+ "ensName": ensName
+ }]).parseJSON()
+
+ var communityRequests: seq[CommunityMembershipRequest] = @[]
+
+ if rpcResult{"result"}{"requestsToJoinCommunity"}.kind != JNull:
+ for jsonCommunityReqest in rpcResult["result"]["requestsToJoinCommunity"]:
+ communityRequests.add(jsonCommunityReqest.toCommunityMembershipRequest())
+
+ return communityRequests
+
+proc acceptRequestToJoinCommunity*(requestId: string) =
+ discard callPrivateRPC("acceptRequestToJoinCommunity".prefix, %*[{
+ "id": requestId
+ }])
+
+proc declineRequestToJoinCommunity*(requestId: string) =
+ discard callPrivateRPC("declineRequestToJoinCommunity".prefix, %*[{
+ "id": requestId
+ }])
+
+proc pendingRequestsToJoinForCommunity*(communityId: string): seq[CommunityMembershipRequest] =
+ let rpcResult = callPrivateRPC("pendingRequestsToJoinForCommunity".prefix, %*[communityId]).parseJSON()
+
+ var communityRequests: seq[CommunityMembershipRequest] = @[]
+
+ if rpcResult{"result"}.kind != JNull:
+ for jsonCommunityReqest in rpcResult["result"]:
+ communityRequests.add(jsonCommunityReqest.toCommunityMembershipRequest())
+
+ return communityRequests
+
+proc myPendingRequestsToJoin*(): seq[CommunityMembershipRequest] =
+ let rpcResult = callPrivateRPC("myPendingRequestsToJoin".prefix).parseJSON()
+
+ var communityRequests: seq[CommunityMembershipRequest] = @[]
+
+ if rpcResult{"result"}.kind != JNull:
+ for jsonCommunityReqest in rpcResult["result"]:
+ communityRequests.add(jsonCommunityReqest.toCommunityMembershipRequest())
+
+ return communityRequests
\ No newline at end of file
diff --git a/src/status/libstatus/contacts.nim b/src/status/libstatus/contacts.nim
index f6ed9666ea..5086d3d657 100644
--- a/src/status/libstatus/contacts.nim
+++ b/src/status/libstatus/contacts.nim
@@ -8,8 +8,6 @@ proc blockContact*(contact: Profile): string =
{
"id": contact.id,
"ensVerified": contact.ensVerified,
- "ensVerifiedAt": contact.ensVerifiedAt,
- "ensVerificationRetries": contact.ensVerificationRetries,
"alias": contact.alias,
"identicon": contact.identicon,
"systemTags": contact.systemTags
@@ -27,12 +25,11 @@ proc getContacts*(): JsonNode =
return response["result"]
proc saveContact*(id: string, ensVerified: bool, ensName: string, ensVerifiedAt: int, ensVerificationRetries: int, alias: string, identicon: string, thumbnail: string, systemTags: seq[string], localNickname: string): string =
+proc saveContact*(id: string, ensVerified: bool, ensName: string, alias: string, identicon: string, systemTags: seq[string], localNickname: string): string =
let payload = %* [{
"id": id,
"name": ensName,
"ensVerified": ensVerified,
- "ensVerifiedAt": ensVerifiedAt,
- "ensVerificationRetries": ensVerificationRetries,
"alias": alias,
"identicon": identicon,
"images": {"thumbnail": {"Payload": thumbnail.partition(",")[2]}},
diff --git a/src/status/profile/profile.nim b/src/status/profile/profile.nim
index 0b75d49b38..bac361df1c 100644
--- a/src/status/profile/profile.nim
+++ b/src/status/profile/profile.nim
@@ -5,7 +5,7 @@ type Profile* = ref object
id*, alias*, username*, identicon*, address*, ensName*, localNickname*: string
ensVerified*: bool
identityImage*: IdentityImage
- ensVerifiedAt*, ensVerificationRetries*, appearance*: int
+ appearance*: int
systemTags*: seq[string]
proc isContact*(self: Profile): bool =
@@ -22,9 +22,7 @@ proc toProfileModel*(account: Account): Profile =
alias: account.name,
ensName: "",
ensVerified: false,
- ensVerifiedAt: 0,
appearance: 0,
- ensVerificationRetries: 0,
systemTags: @[]
)
@@ -43,8 +41,6 @@ proc toProfileModel*(profile: JsonNode): Profile =
ensName: "",
ensVerified: profile["ensVerified"].getBool,
appearance: 0,
- ensVerifiedAt: profile["ensVerifiedAt"].getInt,
- ensVerificationRetries: profile["ensVerificationRetries"].getInt,
systemTags: systemTags
)
diff --git a/src/status/signals/messages.nim b/src/status/signals/messages.nim
index c261b0a4c4..8f1c0f6dad 100644
--- a/src/status/signals/messages.nim
+++ b/src/status/signals/messages.nim
@@ -21,6 +21,8 @@ proc toReaction*(jsonReaction: JsonNode): Reaction
proc toCommunity*(jsonCommunity: JsonNode): Community
+proc toCommunityMembershipRequest*(jsonCommunityMembershipRequest: JsonNode): CommunityMembershipRequest
+
proc fromEvent*(event: JsonNode): Signal =
var signal:MessageSignal = MessageSignal()
signal.messages = @[]
@@ -61,6 +63,11 @@ proc fromEvent*(event: JsonNode): Signal =
for jsonCommunity in event["event"]["communities"]:
signal.communities.add(jsonCommunity.toCommunity)
+ if event["event"]{"requestsToJoinCommunity"} != nil:
+ debug "requests", event = event["event"]["requestsToJoinCommunity"]
+ for jsonCommunity in event["event"]["requestsToJoinCommunity"]:
+ signal.membershipRequests.add(jsonCommunity.toCommunityMembershipRequest)
+
result = signal
proc toChatMember*(jsonMember: JsonNode): ChatMember =
@@ -164,32 +171,53 @@ proc toChat*(jsonChat: JsonNode): Chat =
proc toCommunity*(jsonCommunity: JsonNode): Community =
result = Community(
id: jsonCommunity{"id"}.getStr,
- name: jsonCommunity{"description"}{"identity"}{"display_name"}.getStr,
- description: jsonCommunity{"description"}{"identity"}{"description"}.getStr,
- # color: jsonCommunity{"description"}{"identity"}{"color"}.getStr,
- access: jsonCommunity{"description"}{"permissions"}{"access"}.getInt,
+ name: jsonCommunity{"name"}.getStr,
+ description: jsonCommunity{"description"}.getStr,
+ access: jsonCommunity{"permissions"}{"access"}.getInt,
admin: jsonCommunity{"admin"}.getBool,
joined: jsonCommunity{"joined"}.getBool,
verified: jsonCommunity{"verified"}.getBool,
+ ensOnly: jsonCommunity{"permissions"}{"ens_only"}.getBool,
+ canRequestAccess: jsonCommunity{"canRequestAccess"}.getBool,
+ canManageUsers: jsonCommunity{"canManageUsers"}.getBool,
+ canJoin: jsonCommunity{"canJoin"}.getBool,
+ isMember: jsonCommunity{"isMember"}.getBool,
chats: newSeq[Chat](),
- members: newSeq[string]()
+ members: newSeq[string](),
+ communityImage: IdentityImage()
)
- if jsonCommunity["description"].hasKey("chats") and jsonCommunity["description"]["chats"].kind != JNull:
- for chatId, chat in jsonCommunity{"description"}{"chats"}:
+ if jsonCommunity.hasKey("images") and jsonCommunity["images"].kind != JNull:
+ if jsonCommunity["images"].hasKey("thumbnail"):
+ result.communityImage.thumbnail = jsonCommunity["images"]["thumbnail"]["uri"].str
+ if jsonCommunity["images"].hasKey("large"):
+ result.communityImage.large = jsonCommunity["images"]["large"]["uri"].str
+
+ if jsonCommunity.hasKey("chats") and jsonCommunity["chats"].kind != JNull:
+ for chatId, chat in jsonCommunity{"chats"}:
result.chats.add(Chat(
id: result.id & chatId,
- name: chat{"identity"}{"display_name"}.getStr,
- description: chat{"identity"}{"description"}.getStr,
+ name: chat{"name"}.getStr,
+ canPost: chat{"canPost"}.getBool,
# TODO get this from access
- chatType: ChatType.Public#chat{"permissions"}{"access"}.getInt,
+ chatType: ChatType.Public#chat{"permissions"}{"access"}.getInt,
))
- if jsonCommunity["description"].hasKey("members") and jsonCommunity["description"]["members"].kind != JNull:
+ if jsonCommunity.hasKey("members") and jsonCommunity["members"].kind != JNull:
# memberInfo is empty for now
- for memberPubKey, memeberInfo in jsonCommunity{"description"}{"members"}:
+ for memberPubKey, memeberInfo in jsonCommunity{"members"}:
result.members.add(memberPubKey)
+proc toCommunityMembershipRequest*(jsonCommunityMembershipRequest: JsonNode): CommunityMembershipRequest =
+ result = CommunityMembershipRequest(
+ id: jsonCommunityMembershipRequest{"id"}.getStr,
+ publicKey: jsonCommunityMembershipRequest{"publicKey"}.getStr,
+ chatId: jsonCommunityMembershipRequest{"chatId"}.getStr,
+ state: jsonCommunityMembershipRequest{"state"}.getInt,
+ communityId: jsonCommunityMembershipRequest{"communityId"}.getStr,
+ our: jsonCommunityMembershipRequest{"our"}.getStr,
+ )
+
proc toTextItem*(jsonText: JsonNode): TextItem =
result = TextItem(
literal: jsonText{"literal"}.getStr,
diff --git a/src/status/signals/types.nim b/src/status/signals/types.nim
index 3e1e5c67f7..d5a7c16595 100644
--- a/src/status/signals/types.nim
+++ b/src/status/signals/types.nim
@@ -34,6 +34,7 @@ type MessageSignal* = ref object of Signal
installations*: seq[Installation]
emojiReactions*: seq[Reaction]
communities*: seq[Community]
+ membershipRequests*: seq[CommunityMembershipRequest]
type MailserverRequestCompletedSignal* = ref object of Signal
requestID*: string
diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml
index acb4d116d7..b52b1a5e0e 100644
--- a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml
+++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml
@@ -201,6 +201,21 @@ ScrollView {
}
}
}
+
+ onMembershipRequestChanged: function (communityName, accepted) {
+ systemTray.showMessage("Status",
+ accepted ? qsTr("You have been accepted into the ‘%1’ community").arg(communityName) :
+ qsTr("Your request to join the ‘%1’ community was declined").arg(communityName),
+ SystemTrayIcon.NoIcon,
+ Constants.notificationPopupTTL)
+ }
+
+ onMembershipRequestPushed: function (communityName, pubKey) {
+ systemTray.showMessage(qsTr("New membership request"),
+ qsTr("%1 asks to join ‘%2’").arg(Utils.getDisplayName(pubKey)).arg(communityName),
+ SystemTrayIcon.NoIcon,
+ Constants.notificationPopupTTL)
+ }
}
Connections {
@@ -237,52 +252,12 @@ ScrollView {
icon: StandardIcon.Critical
}
- DelegateModel {
+ DelegateModelGeneralized {
id: messageListDelegate
property var lessThan: [
function(left, right) { return left.clock > right.clock }
]
- property int sortOrder: 0
- onSortOrderChanged: items.setGroups(0, items.count, "unsorted")
-
- function insertPosition(lessThan, item) {
- var lower = 0
- var upper = items.count
- while (lower < upper) {
- var middle = Math.floor(lower + (upper - lower) / 2)
- var result = lessThan(item.model, items.get(middle).model);
- if (result) {
- upper = middle
- } else {
- lower = middle + 1
- }
- }
- return lower
- }
-
- function sort(lessThan) {
- while (unsortedItems.count > 0) {
- var item = unsortedItems.get(0)
- var index = insertPosition(lessThan, item)
- item.groups = "items"
- items.move(item.itemsIndex, index)
- }
- }
-
- items.includeByDefault: false
- groups: DelegateModelGroup {
- id: unsortedItems
- name: "unsorted"
- includeByDefault: true
- onChanged: {
- if (messageListDelegate.sortOrder == messageListDelegate.lessThan.length)
- setGroups(0, count, "items")
- else {
- messageListDelegate.sort(messageListDelegate.lessThan[messageListDelegate.sortOrder])
- }
- }
- }
model: messageList
delegate: Message {
diff --git a/ui/app/AppLayouts/Chat/CommunityColumn.qml b/ui/app/AppLayouts/Chat/CommunityColumn.qml
index 101e25f3cc..9459c80845 100644
--- a/ui/app/AppLayouts/Chat/CommunityColumn.qml
+++ b/ui/app/AppLayouts/Chat/CommunityColumn.qml
@@ -1,5 +1,6 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
+import QtGraphicalEffects 1.13
import QtQuick.Layouts 1.13
import "../../../imports"
@@ -86,10 +87,74 @@ Item {
}
}
+ Rectangle {
+ property int nbRequests: chatsModel.activeCommunity.communityMembershipRequests.nbRequests
+
+ id: membershipRequestsBtn
+ visible: nbRequests > 0
+ width: parent.width
+ height: visible ? 52 : 0
+ color: Style.current.secondaryBackground
+ anchors.top: communityHeader.bottom
+ anchors.topMargin: visible ? Style.current.halfPadding : 0
+
+ StyledText {
+ text: qsTr("Membership requests")
+ font.pixelSize: 15
+ anchors.left: parent.left
+ anchors.leftMargin: Style.current.padding
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ Rectangle {
+ id: badge
+ anchors.right: caret.left
+ anchors.rightMargin: Style.current.padding
+ anchors.verticalCenter: parent.verticalCenter
+ color: Style.current.blue
+ width: 22
+ height: 22
+ radius: width / 2
+ Text {
+ font.pixelSize: 12
+ color: Style.current.white
+ anchors.centerIn: parent
+ text: membershipRequestsBtn.nbRequests.toString()
+ }
+ }
+
+ SVGImage {
+ id: caret
+ source: "../../img/caret.svg"
+ fillMode: Image.PreserveAspectFit
+ rotation: -90
+ anchors.right: parent.right
+ anchors.rightMargin: Style.current.padding
+ anchors.verticalCenter: parent.verticalCenter
+ width: 13
+
+
+ ColorOverlay {
+ anchors.fill: parent
+ source: parent
+ color: Style.current.darkGrey
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: membershipRequestPopup.open()
+ }
+ }
+
+ MembershipRequestsPopup {
+ id: membershipRequestPopup
+ }
ScrollView {
id: chatGroupsContainer
- anchors.top: communityHeader.bottom
+ anchors.top: membershipRequestsBtn.bottom
anchors.topMargin: Style.current.padding
anchors.bottom: parent.bottom
anchors.left: parent.left
diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/CommunitiesPopup.qml b/ui/app/AppLayouts/Chat/CommunityComponents/CommunitiesPopup.qml
index 5c42708415..5e2c1f0fc1 100644
--- a/ui/app/AppLayouts/Chat/CommunityComponents/CommunitiesPopup.qml
+++ b/ui/app/AppLayouts/Chat/CommunityComponents/CommunitiesPopup.qml
@@ -39,7 +39,7 @@ ModalPopup {
ListView {
anchors.fill: parent
- model: chatsModel.communities
+ model: communitiesDelegateModel
spacing: 4
clip: true
id: communitiesList
@@ -50,7 +50,7 @@ ModalPopup {
width: parent.width
height: childrenRect.height + Style.current.halfPadding
StyledText {
- text: section
+ text: section.toUpperCase()
}
Separator {
anchors.left: popup.left
@@ -58,8 +58,19 @@ ModalPopup {
}
}
+ }
+
+ DelegateModelGeneralized {
+ id: communitiesDelegateModel
+ lessThan: [
+ function(left, right) {
+ return left.name.toLowerCase() < right.name.toLowerCase()
+ }
+ ]
+
+ model: chatsModel.communities
delegate: Item {
- // TODO add the serach for the name and category once they exist
+ // TODO add the search for the name and category once they exist
visible: {
if (!searchBox.text) {
return true
@@ -74,8 +85,7 @@ ModalPopup {
id: communityImage
width: 40
height: 40
- // TODO get the real image once it's available
- source: "../../../img/ens-header-dark@2x.png"
+ source: thumbnailImage
}
StyledText {
@@ -100,11 +110,9 @@ ModalPopup {
StyledText {
id: communityMembers
- text: nbMembers === 1 ?
- //% "1 member"
- qsTrId("1-member") :
- //% "%1 members"
- qsTrId("-1-members").arg(nbMembers)
+ text: nbMembers === 1 ?
+ qsTr("1 member") :
+ qsTr("%1 members").arg(nbMembers)
anchors.left: communityDesc.left
anchors.right: parent.right
anchors.top: communityDesc.bottom
@@ -117,7 +125,7 @@ ModalPopup {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
- if (joined) {
+ if (joined && isMember) {
chatsModel.setActiveCommunity(id)
} else {
chatsModel.setObservedCommunity(id)
diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/CommunityButton.qml b/ui/app/AppLayouts/Chat/CommunityComponents/CommunityButton.qml
index 254248a0d9..03716ae2d8 100644
--- a/ui/app/AppLayouts/Chat/CommunityComponents/CommunityButton.qml
+++ b/ui/app/AppLayouts/Chat/CommunityComponents/CommunityButton.qml
@@ -10,7 +10,7 @@ Rectangle {
property string name: "channelName"
property string description: "channel description"
property string unviewedMessagesCount: "0"
- property string image: "../../../img/ens-header-dark@2x.png"
+ property string image
property bool hasMentions: false
property string searchStr: ""
property bool isCompact: appSettings.useCompactMode
diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/CommunityDetailPopup.qml b/ui/app/AppLayouts/Chat/CommunityComponents/CommunityDetailPopup.qml
index 7300f185ff..030d2c2a80 100644
--- a/ui/app/AppLayouts/Chat/CommunityComponents/CommunityDetailPopup.qml
+++ b/ui/app/AppLayouts/Chat/CommunityComponents/CommunityDetailPopup.qml
@@ -11,9 +11,11 @@ ModalPopup {
property string name: community.name
property string description: community.description
property int access: community.access
- // TODO get the real image once it's available
- property string source: "../../../img/ens-header-dark@2x.png"
+ property string source: community.thumbnailImage
property int nbMembers: community.nbMembers
+ property bool ensOnly: community.ensOnly
+ property bool canJoin: community.canJoin
+ property bool canRequestAccess: community.canRequestAccess
id: popup
@@ -41,7 +43,7 @@ ModalPopup {
}
StyledText {
- // TODO get this from access property
+ id: accessText
text: {
switch(access) {
//% "Public community"
@@ -61,6 +63,17 @@ ModalPopup {
font.weight: Font.Thin
color: Style.current.secondaryText
}
+
+ StyledText {
+ visible: popup.ensOnly
+ text: qsTr(" - ENS Only")
+ anchors.left: accessText.right
+ anchors.verticalCenter: accessText.verticalCenter
+ anchors.topMargin: 2
+ font.pixelSize: 15
+ font.weight: Font.Thin
+ color: Style.current.secondaryText
+ }
}
StyledText {
@@ -168,11 +181,55 @@ ModalPopup {
}
StatusButton {
- //% "Join ‘%1’"
- text: qsTrId("join---1-").arg(popup.name)
+ property bool isPendingRequest: {
+ if (access !== Constants.communityChatOnRequestAccess) {
+ return false
+ }
+ return chatsModel.isCommunityRequestPending(communityId)
+ }
+ text: {
+ if (ensOnly && !profileModel.profile.ensVerified) {
+ return qsTr("Membership requires an ENS username")
+ }
+ if (canJoin) {
+ return qsTr("Join ‘%1’").arg(popup.name);
+ }
+ if (isPendingRequest) {
+ return qsTr("Pending")
+ }
+ switch(access) {
+ case Constants.communityChatPublicAccess: return qsTr("Join ‘%1’").arg(popup.name);
+ case Constants.communityChatInvitationOnlyAccess: return qsTr("You need to be invited");
+ case Constants.communityChatOnRequestAccess: return qsTr("Request to join ‘%1’").arg(popup.name);
+ default: return qsTr("Unknown community");
+ }
+ }
+ enabled: {
+ if (ensOnly && !profileModel.profile.ensVerified) {
+ return false
+ }
+ if (canJoin) {
+ return true
+ }
+ if (access === Constants.communityChatInvitationOnlyAccess || isPendingRequest) {
+ return false
+ }
+ return true
+ }
+
anchors.right: parent.right
onClicked: {
- const error = chatsModel.joinCommunity(popup.communityId)
+ let error
+ if (access === Constants.communityChatOnRequestAccess) {
+ error = chatsModel.requestToJoinCommunity(popup.communityId,
+ profileModel.profile.ensVerified ? profileModel.profile.username : "")
+ if (!error) {
+ enabled = false
+ text = qsTr("Pending")
+ }
+ } else {
+ error = chatsModel.joinCommunity(popup.communityId)
+ }
if (error) {
joiningError.text = error
diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/CommunityList.qml b/ui/app/AppLayouts/Chat/CommunityComponents/CommunityList.qml
index 5978f3063b..d1ffae6ecc 100644
--- a/ui/app/AppLayouts/Chat/CommunityComponents/CommunityList.qml
+++ b/ui/app/AppLayouts/Chat/CommunityComponents/CommunityList.qml
@@ -25,6 +25,7 @@ Item {
name: model.name
description: model.description
searchStr: root.searchStr
+ image: model.thumbnailImage
}
}
diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/CreateCommunityPopup.qml b/ui/app/AppLayouts/Chat/CommunityComponents/CreateCommunityPopup.qml
index 854e259516..78e8f78328 100644
--- a/ui/app/AppLayouts/Chat/CommunityComponents/CreateCommunityPopup.qml
+++ b/ui/app/AppLayouts/Chat/CommunityComponents/CreateCommunityPopup.qml
@@ -12,6 +12,13 @@ ModalPopup {
property string colorValidationError: ""
property string selectedImageValidationError: ""
property string selectedImage: ""
+ property var imageDimensions: ({
+ aX: 0,
+ aY: 0,
+ bY: 1,
+ bY: 1
+ })
+
property QtObject community: chatsModel.activeCommunity
property bool isEdit: false
@@ -51,13 +58,11 @@ ModalPopup {
selectedImageValidationError = qsTrId("you-need-to-select-an-image")
}
- if (colorPicker.text === "") {
- //% "You need to enter a color"
- colorValidationError = qsTrId("you-need-to-enter-a-color")
- } else if (!Utils.isHexColor(colorPicker.text)) {
- //% "This field needs to be an hexadecimal color (eg: #4360DF)"
- colorValidationError = qsTrId("this-field-needs-to-be-an-hexadecimal-color--eg---4360df-")
- }
+// if (colorPicker.text === "") {
+// colorValidationError = qsTr("You need to enter a color")
+// } else if (!Utils.isHexColor(colorPicker.text)) {
+// colorValidationError = qsTr("This field needs to be an hexadecimal color (eg: #4360DF)")
+// }
return !nameValidationError && !descriptionTextArea.validationError && !colorValidationError
}
@@ -73,8 +78,10 @@ ModalPopup {
id: scrollView
anchors.fill: parent
- rightPadding: Style.current.padding
- anchors.rightMargin: - Style.current.halfPadding
+ rightPadding: Style.current.bigPadding
+ anchors.rightMargin: - Style.current.bigPadding
+ leftPadding: Style.current.bigPadding
+ anchors.leftMargin: - Style.current.bigPadding
contentHeight: content.height
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
@@ -154,7 +161,8 @@ ModalPopup {
qsTrId("image-files----jpg---jpeg---png-")
]
onAccepted: {
- selectedImage = imageDialog.fileUrls[0]
+ popup.selectedImage = imageDialog.fileUrls[0]
+ imageCropperModal.open()
}
}
@@ -227,82 +235,108 @@ ModalPopup {
cursorShape: Qt.PointingHandCursor
onClicked: imageDialog.open()
}
- }
- Input {
- id: colorPicker
- //% "Community color"
- label: qsTrId("community-color")
- //% "Pick a color"
- placeholderText: qsTrId("pick-a-color")
- anchors.top: addImageButton.bottom
- anchors.topMargin: Style.current.smallPadding
- validationError: popup.colorValidationError
-
- StatusIconButton {
- icon.name: "caret"
- iconRotation: -90
- iconColor: Style.current.textColor
- icon.width: 13
- icon.height: 7
- anchors.right: parent.right
- anchors.rightMargin: Style.current.smallPadding
- anchors.top: parent.top
- anchors.topMargin: colorPicker.textField.height / 2 - height / 2 + Style.current.bigPadding
- onClicked: colorDialog.open()
- }
-
- ColorDialog {
- id: colorDialog
- //% "Please choose a color"
- title: qsTrId("please-choose-a-color")
- onAccepted: {
- colorPicker.text = colorDialog.color
+ ImageCropperModal {
+ id: imageCropperModal
+ selectedImage: popup.selectedImage
+ onCropFinished: {
+ imageDimensions.aX = aX
+ imageDimensions.aY = aY
+ imageDimensions.bX = bX
+ imageDimensions.bY = bY
}
}
}
+ // TODO re-add color picker when status-go supports it
+// Input {
+// id: colorPicker
+// label: qsTr("Community color")
+// placeholderText: qsTr("Pick a color")
+// anchors.top: addImageButton.bottom
+// anchors.topMargin: Style.current.smallPadding
+// validationError: popup.colorValidationError
+
+// StatusIconButton {
+// icon.name: "caret"
+// iconRotation: -90
+// iconColor: Style.current.textColor
+// icon.width: 13
+// icon.height: 7
+// anchors.right: parent.right
+// anchors.rightMargin: Style.current.smallPadding
+// anchors.top: parent.top
+// anchors.topMargin: colorPicker.textField.height / 2 - height / 2 + Style.current.bigPadding
+// onClicked: colorDialog.open()
+// }
+
+// ColorDialog {
+// id: colorDialog
+// title: qsTr("Please choose a color")
+// onAccepted: {
+// colorPicker.text = colorDialog.color
+// }
+// }
+// }
+
Separator {
id: separator1
- anchors.top: colorPicker.bottom
+ anchors.top: addImageButton.bottom
anchors.topMargin: isEdit ? 0 : Style.current.bigPadding
visible: !isEdit
}
- Item {
- visible: !isEdit
- id: privateSwitcher
- height: visible ? privateSwitch.height : 0
- width: parent.width
+ StatusSettingsLineButton {
+ id: membershipRequirementSetting
anchors.top: separator1.bottom
- anchors.topMargin: isEdit ? 0 : Style.current.smallPadding * 2
-
- StyledText {
- //% "Private community"
- text: qsTrId("private-community")
- anchors.verticalCenter: parent.verticalCenter
+ anchors.topMargin: Style.current.halfPadding
+ text: qsTr("Membership requirement")
+ currentValue: {
+ switch (membershipRequirementSettingPopup.checkedMembership) {
+ case Constants.communityChatInvitationOnlyAccess: return qsTr("Require invite from another member")
+ case Constants.communityChatOnRequestAccess: return qsTr("Require approval")
+ default: return qsTr("No requirement")
+ }
}
-
- StatusSwitch {
- id: privateSwitch
- anchors.right: parent.right
+ onClicked: {
+ membershipRequirementSettingPopup.open()
}
}
StyledText {
visible: !isEdit
- height: visible ? 50 : 0
+ height: visible ? implicitHeight : 0
id: privateExplanation
- anchors.top: privateSwitcher.bottom
+ anchors.top: membershipRequirementSetting.bottom
wrapMode: Text.WordWrap
- anchors.topMargin: isEdit ? 0 : Style.current.smallPadding * 2
+ anchors.topMargin: isEdit ? 0 : Style.current.halfPadding
width: parent.width
- text: privateSwitch.checked ?
- //% "Only members with an invite link will be able to join your community. Private communities are not listed inside Status"
- qsTrId("only-members-with-an-invite-link-will-be-able-to-join-your-community--private-communities-are-not-listed-inside-status") :
- //% "Your community will be public for anyone to join. Public communities are listed inside Status for easy discovery"
- qsTrId("your-community-will-be-public-for-anyone-to-join--public-communities-are-listed-inside-status-for-easy-discovery")
+ text: qsTr("You can require new members to meet certain criteria before they can join. This can be changed at any time")
}
+
+ StatusSettingsLineButton {
+ id: ensOnlySwitch
+ anchors.top: privateExplanation.bottom
+ anchors.topMargin: Style.current.padding
+ text: qsTr("Require ENS username")
+ isSwitch: true
+ onClicked: switchChecked = checked
+ }
+
+ StyledText {
+ visible: !isEdit
+ height: visible ? implicitHeight : 0
+ id: ensExplanation
+ anchors.top: ensOnlySwitch.bottom
+ wrapMode: Text.WordWrap
+ anchors.topMargin: isEdit ? 0 : Style.current.halfPadding
+ width: parent.width
+ text: qsTr("Your community requires an ENS username to be able to join")
+ }
+ }
+
+ MembershipRequirementPopup {
+ id: membershipRequirementSettingPopup
}
}
@@ -325,8 +359,13 @@ ModalPopup {
} else {
error = chatsModel.createCommunity(Utils.filterXSS(nameInput.text),
Utils.filterXSS(descriptionTextArea.text),
- colorPicker.text,
- popup.selectedImage)
+ membershipRequirementSettingPopup.checkedMembership,
+ ensOnlySwitch.switchChecked,
+ popup.selectedImage,
+ imageDimensions.aX,
+ imageDimensions.aY,
+ imageDimensions.bX,
+ imageDimensions.bY)
}
if (error) {
diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/MembershipRadioButton.qml b/ui/app/AppLayouts/Chat/CommunityComponents/MembershipRadioButton.qml
new file mode 100644
index 0000000000..35f486f27e
--- /dev/null
+++ b/ui/app/AppLayouts/Chat/CommunityComponents/MembershipRadioButton.qml
@@ -0,0 +1,49 @@
+import QtQuick 2.13
+import QtQuick.Controls 2.13
+import QtGraphicalEffects 1.13
+import "../../../../shared"
+import "../../../../shared/status"
+import "../../../../imports"
+import "."
+
+Item {
+ property string text
+ property string description
+ property var buttonGroup
+ property bool checked: false
+ property bool hideSeparator: false
+ signal radioCheckedChanged(bool checked)
+
+ id: root
+ width: parent.width
+ height: childrenRect.height
+
+ StatusRadioButtonRow {
+ id: radioBtn
+ text: root.text
+ buttonGroup: root.buttonGroup
+ checked: root.checked
+ onRadioCheckedChanged: {
+ root.radioCheckedChanged(checked)
+ }
+ }
+
+ StyledText {
+ id: radioDesc
+ text: root.description
+ anchors.top: radioBtn.bottom
+ anchors.topMargin: Style.current.halfPadding
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.rightMargin: 100
+ font.pixelSize: 13
+ color: Style.current.secondaryText
+ wrapMode: Text.WordWrap
+ }
+
+ Separator {
+ visible: !root.hideSeparator
+ anchors.top: radioDesc.bottom
+ anchors.topMargin: visible ? Style.current.halfPadding : 0
+ }
+}
diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/MembershipRequestsPopup.qml b/ui/app/AppLayouts/Chat/CommunityComponents/MembershipRequestsPopup.qml
new file mode 100644
index 0000000000..49103985bb
--- /dev/null
+++ b/ui/app/AppLayouts/Chat/CommunityComponents/MembershipRequestsPopup.qml
@@ -0,0 +1,165 @@
+import QtQuick 2.12
+import "../../../../imports"
+import "../../../../shared"
+import "../../../../shared/status"
+
+ModalPopup {
+ property alias membershipRequestList: membershipRequestList
+
+ id: popup
+
+ onOpened: {
+ errorText.text = ""
+ }
+
+ header: Item {
+ height: 60
+ width: parent.width
+
+ StyledText {
+ id: titleText
+ text: qsTr("Membership requests")
+ anchors.top: parent.top
+ anchors.topMargin: 2
+ anchors.left: parent.left
+ font.bold: true
+ font.pixelSize: 17
+ wrapMode: Text.WordWrap
+ }
+
+ StyledText {
+ id: nbRequestsText
+ text: membershipRequestList.count
+ width: 160
+ anchors.left: titleText.left
+ anchors.top: titleText.bottom
+ anchors.topMargin: 2
+ font.pixelSize: 15
+ color: Style.current.darkGrey
+ }
+
+ Separator {
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.rightMargin: -Style.current.padding
+ anchors.leftMargin: -Style.current.padding
+ }
+ }
+
+ Item {
+ anchors.fill: parent
+
+ StyledText {
+ id: errorText
+ visible: !!text
+ height: visible ? implicitHeight : 0
+ color: Style.current.danger
+ anchors.top: parent.top
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.Wrap
+ anchors.topMargin: visible ? Style.current.smallPadding : 0
+ width: parent.width
+ }
+
+ ListView {
+ id: membershipRequestList
+ model: chatsModel.activeCommunity.communityMembershipRequests
+ anchors.top: errorText.bottom
+ anchors.topMargin: Style.current.smallPadding
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.rightMargin: -Style.current.xlPadding
+ anchors.leftMargin: -Style.current.xlPadding
+ height: parent.height
+
+ delegate: Item {
+ property int contactIndex: profileModel.contacts.list.getContactIndexByPubkey(publicKey)
+ property string identicon: utilsModel.generateIdenticon(publicKey)
+ property string profileImage: contactIndex === -1 ? identicon :
+ profileModel.contacts.list.rowData(contactIndex, 'thumbnailImage') || identicon
+ property string displayName: Utils.getDisplayName(publicKey, contactIndex)
+
+ id: requestLine
+ height: 52
+ width: parent.width
+
+ StatusImageIdenticon {
+ id: accountImage
+ width: 36
+ height: 36
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+ source: requestLine.profileImage
+ anchors.leftMargin: Style.current.padding
+ }
+
+ StyledText {
+ text: requestLine.displayName
+ anchors.left: accountImage.right
+ anchors.leftMargin: Style.current.padding
+ anchors.right: thumbsUp.left
+ anchors.rightMargin: Style.current.padding
+ anchors.verticalCenter: parent.verticalCenter
+ font.pixelSize: 15
+ color: Style.current.darkGrey
+ }
+
+ SVGImage {
+ id: thumbsUp
+ source: "../../../img/thumbsUp.svg"
+ fillMode: Image.PreserveAspectFit
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: thumbsDown.left
+ anchors.rightMargin: Style.current.padding
+ width: 28
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ errorText.text = ""
+ const error = chatsModel.acceptRequestToJoinCommunity(id)
+ if (error) {
+ errorText.text = error
+ }
+ }
+ }
+ }
+
+ SVGImage {
+ id: thumbsDown
+ source: "../../../img/thumbsDown.svg"
+ fillMode: Image.PreserveAspectFit
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.right: parent.right
+ anchors.rightMargin: Style.current.padding
+ width: 28
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ errorText.text = ""
+ const error = chatsModel.declineRequestToJoinCommunity(id)
+ if (error) {
+ errorText.text = error
+ }
+ }
+ }
+ }
+ }
+
+ }
+ }
+
+ footer: StatusRoundButton {
+ id: btnBack
+ anchors.left: parent.left
+ icon.name: "arrow-right"
+ icon.width: 20
+ icon.height: 16
+ rotation: 180
+ onClicked: popup.close()
+ }
+}
diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/MembershipRequirementPopup.qml b/ui/app/AppLayouts/Chat/CommunityComponents/MembershipRequirementPopup.qml
new file mode 100644
index 0000000000..0811239e0f
--- /dev/null
+++ b/ui/app/AppLayouts/Chat/CommunityComponents/MembershipRequirementPopup.qml
@@ -0,0 +1,101 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.3
+import QtGraphicalEffects 1.13
+import QtQuick.Dialogs 1.3
+import "../../../../imports"
+import "../../../../shared"
+import "../../../../shared/status"
+
+ModalPopup {
+ property int checkedMembership: Constants.communityChatPublicAccess
+
+ id: popup
+ height: 600
+
+ title: qsTr("Membership requirement")
+
+ ScrollView {
+ property ScrollBar vScrollBar: ScrollBar.vertical
+
+ id: scrollView
+ anchors.fill: parent
+ rightPadding: Style.current.bigPadding
+ anchors.rightMargin: - Style.current.bigPadding
+ leftPadding: Style.current.bigPadding
+ anchors.leftMargin: - Style.current.bigPadding
+ contentHeight: content.height
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+ clip: true
+
+ ButtonGroup {
+ id: membershipRequirementGroup
+ }
+
+ Column {
+ id: content
+ width: parent.width
+ spacing: Style.current.padding
+
+ MembershipRadioButton {
+ text: qsTr("Require approval")
+ description: qsTr("Your community is free to join, but new members are required to be approved by the community creator first")
+ buttonGroup: membershipRequirementGroup
+ checked: popup.checkedMembership === Constants.communityChatOnRequestAccess
+ onRadioCheckedChanged: {
+ if (checked) {
+ popup.checkedMembership = Constants.communityChatOnRequestAccess
+ }
+ }
+ }
+
+ MembershipRadioButton {
+ text: qsTr("Require invite from another member")
+ description: qsTr("Your community can only be joined by an invitation from existing community members")
+ buttonGroup: membershipRequirementGroup
+ checked: popup.checkedMembership === Constants.communityChatInvitationOnlyAccess
+ onRadioCheckedChanged: {
+ if (checked) {
+ popup.checkedMembership = Constants.communityChatInvitationOnlyAccess
+ }
+ }
+ }
+
+ // This should be a check box
+// MembershipRadioButton {
+// text: qsTr("Require ENS username")
+// description: qsTr("Your community requires an ENS username to be able to join")
+// buttonGroup: membershipRequirementGroup
+// }
+
+ MembershipRadioButton {
+ text: qsTr("No requirement")
+ description: qsTr("Your community is free for anyone to join")
+ buttonGroup: membershipRequirementGroup
+ hideSeparator: true
+ checked: popup.checkedMembership === Constants.communityChatPublicAccess
+ onRadioCheckedChanged: {
+ if (checked) {
+ popup.checkedMembership = Constants.communityChatPublicAccess
+ }
+ }
+ }
+ }
+ }
+
+ footer: StatusIconButton {
+ id: backButton
+ icon.name: "leave_chat"
+ width: 44
+ height: 44
+ iconColor: Style.current.primary
+ highlighted: true
+ icon.color: Style.current.primary
+ icon.width: 28
+ icon.height: 28
+ radius: width / 2
+ onClicked: {
+ popup.close()
+ }
+ }
+}
+
diff --git a/ui/app/AppLayouts/Profile/Sections/ChangeProfilePicModal.qml b/ui/app/AppLayouts/Profile/Sections/ChangeProfilePicModal.qml
index cf0524c898..7cb4c7ad5f 100644
--- a/ui/app/AppLayouts/Profile/Sections/ChangeProfilePicModal.qml
+++ b/ui/app/AppLayouts/Profile/Sections/ChangeProfilePicModal.qml
@@ -54,51 +54,12 @@ ModalPopup {
color: Style.current.danger
}
- ModalPopup {
+ ImageCropperModal {
id: cropImageModal
- width: image.width + 50
- height: image.height + 170
- //% "Crop your image (optional)"
- title: qsTrId("crop-your-image--optional-")
- Image {
- id: image
- width: 400
- source: popup.selectedImage
- anchors.verticalCenter: parent.verticalCenter
- anchors.horizontalCenter: parent.horizontalCenter
- fillMode: Image.PreserveAspectFit
- }
-
- ImageCropper {
- id: imageCropper
- x: image.x
- y: image.y
- image: image
- }
-
- footer: StatusButton {
- id: doUploadBtn
- //% "Finish"
- text: qsTrId("finish")
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- onClicked: {
- const aXPercent = imageCropper.selectorRectangle.x / image.width
- const aYPercent = imageCropper.selectorRectangle.y / image.height
- const bXPercent = (imageCropper.selectorRectangle.x + imageCropper.selectorRectangle.width) / image.width
- const bYPercent = (imageCropper.selectorRectangle.y + imageCropper.selectorRectangle.height) / image.height
-
-
- const aX = Math.round(aXPercent * image.sourceSize.width)
- const aY = Math.round(aYPercent * image.sourceSize.height)
-
- const bX = Math.round(bXPercent * image.sourceSize.width)
- const bY = Math.round(bYPercent * image.sourceSize.height)
-
- uploadError = profileModel.uploadNewProfilePic(selectedImage, aX, aY, bX, bY)
- cropImageModal.close()
- }
+ selectedImage: popup.selectedImage
+ onCropFinished: {
+ uploadError = profileModel.uploadNewProfilePic(selectedImage, aX, aY, bX, bY)
}
}
}
diff --git a/ui/app/AppLayouts/Timeline/TimelineLayout.qml b/ui/app/AppLayouts/Timeline/TimelineLayout.qml
index dbe36478b7..6ec1210e2b 100644
--- a/ui/app/AppLayouts/Timeline/TimelineLayout.qml
+++ b/ui/app/AppLayouts/Timeline/TimelineLayout.qml
@@ -111,52 +111,12 @@ ScrollView {
section.criteria: ViewSection.FullString
}
- DelegateModel {
+ DelegateModelGeneralized {
id: messageListDelegate
- property var moreThan: [
+ lessThan: [
function(left, right) { return left.clock > right.clock }
]
- property int sortOrder: 0
- onSortOrderChanged: items.setGroups(0, items.count, "unsorted")
-
- function insertPosition(moreThan, item) {
- var lower = 0
- var upper = items.count
- while (lower < upper) {
- var middle = Math.floor(lower + (upper - lower) / 2)
- var result = moreThan(item.model, items.get(middle).model);
- if (result) {
- upper = middle
- } else {
- lower = middle + 1
- }
- }
- return lower
- }
-
- function sort(moreThan) {
- while (unsortedItems.count > 0) {
- var item = unsortedItems.get(0)
- var index = insertPosition(moreThan, item)
- item.groups = "items"
- items.move(item.itemsIndex, index)
- }
- }
-
- items.includeByDefault: false
- groups: DelegateModelGroup {
- id: unsortedItems
- name: "unsorted"
- includeByDefault: true
- onChanged: {
- if (messageListDelegate.sortOrder == messageListDelegate.moreThan.length)
- setGroups(0, count, "items")
- else {
- messageListDelegate.sort(messageListDelegate.moreThan[messageListDelegate.sortOrder])
- }
- }
- }
model: chatsModel.messageList
delegate: Message {
diff --git a/ui/app/img/thumbsDown.svg b/ui/app/img/thumbsDown.svg
new file mode 100644
index 0000000000..bf396dd9c3
--- /dev/null
+++ b/ui/app/img/thumbsDown.svg
@@ -0,0 +1,4 @@
+
diff --git a/ui/app/img/thumbsUp.svg b/ui/app/img/thumbsUp.svg
new file mode 100644
index 0000000000..6c4ac8f8d7
--- /dev/null
+++ b/ui/app/img/thumbsUp.svg
@@ -0,0 +1,4 @@
+
diff --git a/ui/imports/Utils.qml b/ui/imports/Utils.qml
index 054f83928c..26028c5cbf 100644
--- a/ui/imports/Utils.qml
+++ b/ui/imports/Utils.qml
@@ -30,6 +30,24 @@ QtObject {
(!startsWith0x(value) && value.length === 64))
}
+ function getDisplayName(publicKey, contactIndex) {
+ if (contactIndex === undefined) {
+ contactIndex = profileModel.contacts.list.getContactIndexByPubkey(publicKey)
+ }
+
+ if (contactIndex === -1) {
+ return utilsModel.generateAlias(publicKey)
+ }
+ const ensVerified = profileModel.contacts.list.rowData(contactIndex, 'ensVerified')
+ if (!ensVerified) {
+ const nickname = profileModel.contacts.list.rowData(contactIndex, 'localNickname')
+ if (nickname) {
+ return nickname
+ }
+ }
+ return profileModel.contacts.list.rowData(contactIndex, 'name')
+ }
+
function isMnemonic(value) {
if(!value.match(/^([a-z\s]+)$/)){
return false;
diff --git a/ui/nim-status-client.pro b/ui/nim-status-client.pro
index a3af5ec2a6..3cde22aceb 100644
--- a/ui/nim-status-client.pro
+++ b/ui/nim-status-client.pro
@@ -136,6 +136,9 @@ DISTFILES += \
app/AppLayouts/Chat/CommunityComponents/CreateCommunityPopup.qml \
app/AppLayouts/Chat/CommunityComponents/ImportCommunityPopup.qml \
app/AppLayouts/Chat/CommunityComponents/InviteFriendsToCommunityPopup.qml \
+ app/AppLayouts/Chat/CommunityComponents/MembershipRadioButton.qml \
+ app/AppLayouts/Chat/CommunityComponents/MembershipRequestsPopup.qml \
+ app/AppLayouts/Chat/CommunityComponents/MembershipRequirementPopup.qml \
app/AppLayouts/Chat/ContactsColumn/AddChat.qml \
app/AppLayouts/Chat/ContactsColumn/ClosedEmptyView.qml \
app/AppLayouts/Chat/components/ChooseBrowserPopup.qml \
@@ -354,9 +357,11 @@ DISTFILES += \
shared/AddButton.qml \
shared/Address.qml \
shared/CropCornerRectangle.qml \
+ shared/DelegateModelGeneralized.qml \
shared/FormGroup.qml \
shared/IconButton.qml \
shared/ImageCropper.qml \
+ shared/ImageCropperModal.qml \
shared/Input.qml \
shared/LabelValueRow.qml \
shared/ModalPopup.qml \
diff --git a/ui/shared/DelegateModelGeneralized.qml b/ui/shared/DelegateModelGeneralized.qml
new file mode 100644
index 0000000000..a953917587
--- /dev/null
+++ b/ui/shared/DelegateModelGeneralized.qml
@@ -0,0 +1,50 @@
+import QtQuick 2.13
+import QtQml.Models 2.3
+import "../imports"
+
+DelegateModel {
+ id: delegateModel
+ property var lessThan
+
+ property int sortOrder: 0
+ onSortOrderChanged: items.setGroups(0, items.count, "unsorted")
+
+ function insertPosition(lessThan, item) {
+ var lower = 0
+ var upper = items.count
+ while (lower < upper) {
+ var middle = Math.floor(lower + (upper - lower) / 2)
+ var result = lessThan(item.model, items.get(middle).model);
+ if (result) {
+ upper = middle
+ } else {
+ lower = middle + 1
+ }
+ }
+ return lower
+ }
+
+ function sort(lessThan) {
+ while (unsortedItems.count > 0) {
+ var item = unsortedItems.get(0)
+ var index = insertPosition(lessThan, item)
+
+ item.groups = "items"
+ items.move(item.itemsIndex, index)
+ }
+ }
+
+ items.includeByDefault: false
+ groups: DelegateModelGroup {
+ id: unsortedItems
+ name: "unsorted"
+
+ includeByDefault: true
+ onChanged: {
+ if (delegateModel.sortOrder === delegateModel.lessThan.length)
+ setGroups(0, count, "items")
+ else
+ delegateModel.sort(delegateModel.lessThan[delegateModel.sortOrder])
+ }
+ }
+}
diff --git a/ui/shared/ImageCropperModal.qml b/ui/shared/ImageCropperModal.qml
new file mode 100644
index 0000000000..1624d7833a
--- /dev/null
+++ b/ui/shared/ImageCropperModal.qml
@@ -0,0 +1,54 @@
+import QtQuick 2.13
+import QtQuick.Controls 2.13
+import QtQuick.Layouts 1.13
+import "../imports"
+import "./status"
+
+ModalPopup {
+ property string selectedImage
+ signal cropFinished(aX: int, aY: int, bX: int, bY: int)
+
+ id: cropImageModal
+ width: image.width + 50
+ height: image.height + 170
+ title: qsTr("Crop your image (optional)")
+
+ Image {
+ id: image
+ width: 400
+ source: cropImageModal.selectedImage
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ fillMode: Image.PreserveAspectFit
+ }
+
+ ImageCropper {
+ id: imageCropper
+ x: image.x
+ y: image.y
+ image: image
+ }
+
+ footer: StatusButton {
+ id: doneBtn
+ text: qsTr("Finish")
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ onClicked: {
+ const aXPercent = imageCropper.selectorRectangle.x / image.width
+ const aYPercent = imageCropper.selectorRectangle.y / image.height
+ const bXPercent = (imageCropper.selectorRectangle.x + imageCropper.selectorRectangle.width) / image.width
+ const bYPercent = (imageCropper.selectorRectangle.y + imageCropper.selectorRectangle.height) / image.height
+
+
+ const aX = Math.round(aXPercent * image.sourceSize.width)
+ const aY = Math.round(aYPercent * image.sourceSize.height)
+
+ const bX = Math.round(bXPercent * image.sourceSize.width)
+ const bY = Math.round(bYPercent * image.sourceSize.height)
+
+ cropImageModal.cropFinished(aX, aY, bX, bY)
+ cropImageModal.close()
+ }
+ }
+}
diff --git a/vendor/status-go b/vendor/status-go
index 363ab0a2ab..09942bf200 160000
--- a/vendor/status-go
+++ b/vendor/status-go
@@ -1 +1 @@
-Subproject commit 363ab0a2ab8dce98193c899c2c202cc885a45143
+Subproject commit 09942bf200bd90bf3f36c5db2a911644fb6822e7