feat: add community requests, permissions, ENS and more

This commit is contained in:
Jonathan Rainville 2021-02-10 15:37:17 -05:00 committed by Iuri Matias
parent fb8380a861
commit f9817d4f52
33 changed files with 1166 additions and 310 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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
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

View File

@ -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]) =

View File

@ -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

View File

@ -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)
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()

View File

@ -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

View File

@ -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
)

View File

@ -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])
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

View File

@ -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]}},

View File

@ -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
)

View File

@ -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,

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -25,6 +25,7 @@ Item {
name: model.name
description: model.description
searchStr: root.searchStr
image: model.thumbnailImage
}
}

View File

@ -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) {

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}
}

View File

@ -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)
}
}
}

View File

@ -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 {

View File

@ -0,0 +1,4 @@
<svg width="28" height="40" viewBox="0 0 28 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="14" cy="20" r="14" fill="#FF2D55"/>
<path d="M21.9468 17.4414C21.9468 14.856 20.2915 12.7393 18.1675 12.7393H15.7578C14.769 12.2339 13.5972 11.9409 12.3081 11.9409H11.2827C10.3306 11.9409 9.51758 11.9995 8.99023 12.1313C7.91357 12.395 7.23242 13.1641 7.23242 14.1235C7.23242 14.292 7.26172 14.4531 7.30566 14.6069C6.80029 15.0024 6.51465 15.5737 6.51465 16.2036C6.51465 16.4966 6.57324 16.7822 6.67578 17.0239C6.33887 17.3828 6.14111 17.9028 6.14111 18.4375C6.14111 18.7891 6.229 19.1479 6.37549 19.4263C6.17041 19.7339 6.05322 20.1514 6.05322 20.6128C6.05322 21.7847 6.94678 22.6855 8.104 22.6855H10.8286C10.9824 22.6855 11.085 22.7588 11.0776 22.8979C11.041 23.6523 9.85449 25.4688 9.85449 26.9556C9.85449 28.0469 10.6235 28.8452 11.6855 28.8452C12.4546 28.8452 12.9819 28.4424 13.4873 27.4829C14.4028 25.7251 15.5015 24.187 17.1348 22.1729H18.4458C20.438 22.1729 21.9468 20.0708 21.9468 17.4414ZM17.3838 17.5C17.3838 19.0527 17.0396 20.0415 16.0435 21.3745C14.9375 22.854 13.3994 24.6411 12.2935 26.853C12.0591 27.3145 11.8979 27.4243 11.6709 27.4243C11.3926 27.4243 11.2095 27.2266 11.2095 26.875C11.2095 25.791 12.4473 23.9746 12.4473 22.7808C12.4473 21.9019 11.7368 21.3306 10.77 21.3306H8.14062C7.71582 21.3306 7.40088 21.0156 7.40088 20.5908C7.40088 20.2905 7.50342 20.0928 7.75244 19.8438C7.94287 19.646 7.97217 19.3677 7.80371 19.1772C7.59131 18.877 7.50342 18.6865 7.50342 18.4375C7.50342 18.1372 7.64258 17.8809 7.93555 17.6611C8.18457 17.4854 8.27246 17.1851 8.12598 16.8921C7.97217 16.5991 7.8916 16.4453 7.8916 16.2183C7.8916 15.8667 8.11865 15.5957 8.57275 15.354C8.81445 15.2222 8.88037 14.9658 8.77783 14.7314C8.62402 14.3579 8.60205 14.292 8.60938 14.1309C8.60938 13.8159 8.83643 13.5669 9.32715 13.4424C9.75928 13.3325 10.4478 13.2886 11.356 13.2959L12.3008 13.3032C15.333 13.3325 17.3838 15.0317 17.3838 17.5ZM20.6211 17.4414C20.6211 19.3018 19.6616 20.7886 18.563 20.8325C18.3726 20.8398 18.1821 20.8398 17.9917 20.8398C18.5117 19.8218 18.7388 18.7671 18.7388 17.5C18.7388 16.1523 18.27 14.9658 17.4204 14.0356C17.6914 14.0356 17.9697 14.043 18.248 14.043C19.5298 14.0942 20.6211 15.5957 20.6211 17.4414Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

4
ui/app/img/thumbsUp.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="28" height="40" viewBox="0 0 28 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="14" cy="20" r="14" fill="#4EBC60"/>
<path d="M6.05322 22.4878C6.05322 25.0732 7.7085 27.1899 9.83252 27.1899H12.2422C13.231 27.6953 14.4028 27.9883 15.6919 27.9883H16.7173C17.6694 27.9883 18.4824 27.9297 19.0098 27.7979C20.0864 27.5269 20.7676 26.7651 20.7676 25.8057C20.7676 25.6299 20.7456 25.4761 20.6943 25.3149C21.1997 24.9268 21.4854 24.3481 21.4854 23.7183C21.4854 23.4253 21.4268 23.1396 21.3242 22.8979C21.6611 22.5391 21.8589 22.0264 21.8589 21.4917C21.8589 21.1328 21.771 20.7812 21.6245 20.5029C21.8296 20.188 21.9468 19.7705 21.9468 19.3164C21.9468 18.1445 21.0532 17.2437 19.896 17.2437H17.1714C17.0176 17.2437 16.915 17.1704 16.9224 17.0312C16.959 16.2695 18.1455 14.4604 18.1455 12.9736C18.1455 11.8823 17.3765 11.084 16.3145 11.084C15.5454 11.084 15.0181 11.4868 14.5127 12.4463C13.6045 14.2041 12.4985 15.7422 10.8652 17.749H9.5542C7.56201 17.749 6.05322 19.8584 6.05322 22.4878ZM10.6162 22.4292C10.6162 20.8765 10.9604 19.8804 11.9565 18.5547C13.0625 17.0679 14.6006 15.2881 15.7065 13.0762C15.9409 12.6147 16.1021 12.4976 16.3364 12.4976C16.6147 12.4976 16.7979 12.7026 16.7979 13.0542C16.7979 14.1382 15.5527 15.9546 15.5527 17.1411C15.5527 18.0273 16.2632 18.5913 17.23 18.5913H19.8594C20.2842 18.5913 20.5991 18.9136 20.5991 19.3384C20.5991 19.6387 20.5039 19.8364 20.2476 20.0854C20.0571 20.2759 20.0278 20.5542 20.1963 20.752C20.4087 21.0449 20.4966 21.2427 20.4966 21.4917C20.4966 21.792 20.3574 22.0483 20.0645 22.2607C19.8154 22.4438 19.7275 22.7368 19.874 23.0298C20.0278 23.3301 20.1084 23.4766 20.1084 23.7109C20.1084 24.0625 19.8813 24.3262 19.4272 24.5679C19.1855 24.6997 19.1196 24.9634 19.2222 25.1904C19.376 25.564 19.3979 25.6372 19.3906 25.7983C19.3906 26.1133 19.1636 26.3623 18.6729 26.4868C18.2407 26.5894 17.5522 26.6406 16.644 26.6333L15.6992 26.626C12.667 26.5967 10.6162 24.8901 10.6162 22.4292ZM7.38623 22.4878C7.38623 20.6274 8.33838 19.1406 9.43701 19.0894C9.62744 19.0894 9.81787 19.0894 10.0083 19.0894C9.48828 20.1001 9.26123 21.1621 9.26123 22.4292C9.26123 23.7769 9.72998 24.9634 10.5869 25.8862C10.3159 25.8862 10.0303 25.8862 9.75195 25.8862C8.47021 25.835 7.38623 24.3262 7.38623 22.4878Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -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;

View File

@ -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 \

View File

@ -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])
}
}
}

View File

@ -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()
}
}
}

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 363ab0a2ab8dce98193c899c2c202cc885a45143
Subproject commit 09942bf200bd90bf3f36c5db2a911644fb6822e7