feat: add profile pic support

This commit is contained in:
Jonathan Rainville 2020-11-30 12:03:52 -05:00 committed by Iuri Matias
parent 43d9d5184d
commit d01c9fef79
48 changed files with 687 additions and 128 deletions

View File

@ -15,6 +15,8 @@ import web3/[conversions, ethtypes]
import ../../status/threads
import views/[channels_list, message_list, chat_item, suggestions_list, reactions, stickers, groups, transactions]
import json_serialization
import ../../status/libstatus/utils
import ../utils/image_utils
logScope:
topics = "chats-view"
@ -149,10 +151,7 @@ QtObject:
proc sendImage*(self: ChatsView, imagePath: string): string {.slot.} =
result = ""
try:
var image: string = replace(imagePath, "file://", "")
if defined(windows):
# Windows doesn't work with paths starting with a slash
image.removePrefix('/')
var image = image_utils.formatImagePath(imagePath)
let tmpImagePath = image_resizer(image, 2000, TMPDIR)
self.status.chat.sendImage(self.activeChannel.id, tmpImagePath)
removeFile(tmpImagePath)

View File

@ -1,4 +1,4 @@
import NimQml, Tables, json, nimcrypto, strformat, json_serialization
import NimQml, Tables, json, nimcrypto, strformat, json_serialization, chronicles
import ../../status/signals/types
import ../../status/libstatus/types as status_types
import ../../status/libstatus/accounts as status_accounts
@ -9,9 +9,11 @@ import core
type
AccountRoles {.pure.} = enum
Username = UserRole + 1,
Identicon = UserRole + 2,
Username = UserRole + 1
Identicon = UserRole + 2
Address = UserRole + 3
ThumbnailImage = UserRole + 4
LargeImage = UserRole + 5
QtObject:
type LoginView* = ref object of QAbstractListModel
@ -41,7 +43,12 @@ QtObject:
proc setCurrentAccount*(self: LoginView, selectedAccountIdx: int) {.slot.} =
let currNodeAcct = self.accounts[selectedAccountIdx]
self.currentAccount.setAccount(GeneratedAccount(name: currNodeAcct.name, photoPath: currNodeAcct.photoPath, address: currNodeAcct.keyUid))
self.currentAccount.setAccount(GeneratedAccount(
name: currNodeAcct.name,
identicon: currNodeAcct.identicon,
address: currNodeAcct.keyUid,
identityImage: currNodeAcct.identityImage
))
QtProperty[QVariant] currentAccount:
read = getCurrentAccount
@ -72,13 +79,25 @@ QtObject:
let assetRole = role.AccountRoles
case assetRole:
of AccountRoles.Username: result = newQVariant(asset.name)
of AccountRoles.Identicon: result = newQVariant(asset.photoPath)
of AccountRoles.Identicon: result = newQVariant(asset.identicon)
of AccountRoles.Address: result = newQVariant(asset.keyUid)
of AccountRoles.ThumbnailImage:
if (not asset.identityImage.isNil):
result = newQVariant(asset.identityImage.thumbnail)
else:
result = newQVariant(asset.identicon)
of AccountRoles.LargeImage:
if (not asset.identityImage.isNil):
result = newQVariant(asset.identityImage.large)
else:
result = newQVariant(asset.identicon)
method roleNames(self: LoginView): Table[int, string] =
{ AccountRoles.Username.int:"username",
AccountRoles.Identicon.int:"identicon",
AccountRoles.Address.int:"address" }.toTable
AccountRoles.Address.int:"address",
AccountRoles.ThumbnailImage.int:"thumbnailImage",
AccountRoles.LargeImage.int:"largeImage" }.toTable
proc login(self: LoginView, password: string): string {.slot.} =
var currentAccountId = 0

View File

@ -57,7 +57,7 @@ QtObject:
let assetRole = role.AccountRoles
case assetRole:
of AccountRoles.Username: result = newQVariant(asset.name)
of AccountRoles.Identicon: result = newQVariant(asset.photoPath)
of AccountRoles.Identicon: result = newQVariant(asset.identicon)
of AccountRoles.Address: result = newQVariant(asset.keyUid)
method roleNames(self: OnboardingView): Table[int, string] =

View File

@ -28,11 +28,29 @@ QtObject:
read = username
notify = accountChanged
proc identicon*(self: AccountInfoView): string {.slot.} = result = ?.self.account.photoPath
proc identicon*(self: AccountInfoView): string {.slot.} = result = ?.self.account.identicon
QtProperty[string] identicon:
read = identicon
notify = accountChanged
proc thumbnailImage*(self: AccountInfoView): string {.slot.} =
if (not self.account.identityImage.isNil):
result = self.account.identityImage.thumbnail
else:
result = ?.self.account.identicon
QtProperty[string] thumbnailImage:
read = thumbnailImage
notify = identityImageChanged
proc largeImage*(self: AccountInfoView): string {.slot.} =
if (not self.account.identityImage.isNil):
result = self.account.identityImage.large
else:
result = ?.self.account.identicon
QtProperty[string] largeImage:
read = largeImage
notify = identityImageChanged
proc address*(self: AccountInfoView): string {.slot.} = result = ?.self.account.address
QtProperty[string] address:
read = address

View File

@ -43,6 +43,11 @@ proc init*(self: ProfileController, account: Account) =
profile.id = pubKey
profile.address = account.keyUid
let identityImage = self.status.profile.getIdentityImage(profile.address)
if (identityImage.thumbnail != ""):
profile.identityImage = identityImage
self.view.devices.addDevices(status_devices.getAllDevices())
self.view.devices.setDeviceSetup(status_devices.isDeviceSetup())
self.view.setNewProfile(profile)

View File

@ -1,4 +1,4 @@
import NimQml, sequtils, strutils, sugar, os, json
import NimQml, sequtils, strutils, sugar, os, json, chronicles
import views/[mailservers_list, ens_manager, contacts, devices, mailservers, mnemonic, network, fleets, profile_info, device_list, dapp_list]
import ../chat/views/channels_list
import ../../status/profile/profile
@ -13,6 +13,7 @@ import ../../status/threads
import ../../status/libstatus/types
import ../../status/libstatus/accounts/constants as accountConstants
import qrcode/qrcode
import ../utils/image_utils
QtObject:
type ProfileView* = ref object of QObject
@ -209,3 +210,24 @@ QtObject:
QtProperty[QVariant] network:
read = getNetwork
proc uploadNewProfilePic*(self: ProfileView, imageUrl: string, aX: int, aY: int, bX: int, bY: int): string {.slot.} =
var image = image_utils.formatImagePath(imageUrl)
# FIXME the function to get the file size is messed up
# var size = image_getFileSize(image)
# TODO find a way to i18n this (maybe send just a code and then QML sets the right string)
# return "Max file size is 20MB"
try:
# TODO add crop tool for the image
let identityImage = self.status.profile.storeIdentityImage(self.profile.address, image, aX, aY, bX, bY)
self.profile.setIdentityImage(identityImage)
result = ""
except Exception as e:
error "Error storing identity image", msg=e.msg
result = "Error storing identity image: " & e.msg
proc deleteProfilePic*(self: ProfileView): string {.slot.} =
result = self.status.profile.deleteIdentityImage(self.profile.address)
if (result == ""):
self.profile.removeIdentityImage()

View File

@ -1,4 +1,4 @@
import NimQml
import NimQml, chronicles
import Tables
import ../../../status/profile/profile
from ../../../status/ens import nil
@ -14,6 +14,8 @@ type
Alias = UserRole + 7
EnsVerified = UserRole + 8
LocalNickname = UserRole + 9
ThumbnailImage = UserRole + 10
LargeImage = UserRole + 11
QtObject:
type ContactList* = ref object of QAbstractListModel
@ -42,6 +44,14 @@ QtObject:
return ens.userNameOrAlias(contact)
return defaultValue
proc getContactIndexByPubkey(self: ContactList, pubkey: string): int {.slot.} =
var i = 0
for contact in self.contacts:
if (contact.id == pubkey):
return i
i = i + 1
return -1
proc rowData(self: ContactList, index: int, column: string): string {.slot.} =
let contact = self.contacts[index]
case column:
@ -54,6 +64,8 @@ QtObject:
of "alias": result = contact.alias
of "ensVerified": result = $contact.ensVerified
of "localNickname": result = $contact.localNickname
of "thumbnailImage": result = $contact.identityImage.thumbnail
of "largeImage": result = $contact.identityImage.large
method data(self: ContactList, index: QModelIndex, role: int): QVariant =
if not index.isValid:
@ -71,6 +83,8 @@ QtObject:
of ContactRoles.Alias: result = newQVariant(contact.alias)
of ContactRoles.EnsVerified: result = newQVariant(contact.ensVerified)
of ContactRoles.LocalNickname: result = newQVariant(contact.localNickname)
of ContactRoles.ThumbnailImage: result = newQVariant(contact.identityImage.thumbnail)
of ContactRoles.LargeImage: result = newQVariant(contact.identityImage.large)
method roleNames(self: ContactList): Table[int, string] =
{
@ -82,7 +96,9 @@ QtObject:
ContactRoles.IsBlocked.int:"isBlocked",
ContactRoles.Alias.int:"alias",
ContactRoles.LocalNickname.int:"localNickname",
ContactRoles.EnsVerified.int:"ensVerified"
ContactRoles.EnsVerified.int:"ensVerified",
ContactRoles.ThumbnailImage.int:"thumbnailImage",
ContactRoles.LargeImage.int:"largeImage"
}.toTable
proc addContactToList*(self: ContactList, contact: Profile) =
@ -95,6 +111,8 @@ QtObject:
if(c.isContact()): return true
return false
proc contactChanged*(self: ContactList, pubkey: string) {.signal.}
proc updateContact*(self: ContactList, contact: Profile) =
var found = false
let topLeft = self.createIndex(0, 0, nil)
@ -104,11 +122,13 @@ QtObject:
found = true
c.ensName = contact.ensName
c.ensVerified = contact.ensVerified
c.identityImage = contact.identityImage
if not found:
self.addContactToList(contact)
else:
self.dataChanged(topLeft, bottomRight, @[ContactRoles.Name.int])
self.contactChanged(contact.id)
proc setNewData*(self: ContactList, contactList: seq[Profile]) =
self.beginResetModel()

View File

@ -1,12 +1,15 @@
import NimQml
import chronicles
import ../../../status/profile/profile
import ../../../status/libstatus/types
import std/wrapnils
QtObject:
type ProfileInfoView* = ref object of QObject
username*: string
identicon*: string
address*: string
identityImage*: IdentityImage
pubKey*: string
appearance*: int
ensVerified*: bool
@ -24,11 +27,14 @@ QtObject:
result.username = ""
result.identicon = ""
result.appearance = 0
result.identityImage = IdentityImage()
result.ensVerified = false
result.setup
proc profileChanged*(self: ProfileInfoView) {.signal.}
proc identityImageChanged*(self: ProfileInfoView) {.signal.}
proc setProfile*(self: ProfileInfoView, profile: Profile) =
self.username = profile.username
self.identicon = profile.identicon
@ -36,8 +42,17 @@ QtObject:
self.pubKey = profile.id
self.address = profile.address
self.ensVerified = profile.ensVerified
self.identityImage = profile.identityImage
self.profileChanged()
proc setIdentityImage*(self: ProfileInfoView, identityImage: IdentityImage) =
self.identityImage = identityImage
self.identityImageChanged()
proc removeIdentityImage*(self: ProfileInfoView) =
self.identityImage = IdentityImage()
self.identityImageChanged()
proc username*(self: ProfileInfoView): string {.slot.} = result = self.username
QtProperty[string] username:
read = username
@ -59,6 +74,30 @@ QtObject:
read = identicon
notify = profileChanged
proc thumbnailImage*(self: ProfileInfoView): string {.slot.} =
if (?.self.identityImage.thumbnail != ""):
result = self.identityImage.thumbnail
else:
result = self.identicon
QtProperty[string] thumbnailImage:
read = thumbnailImage
notify = identityImageChanged
proc largeImage*(self: ProfileInfoView): string {.slot.} =
if (?.self.identityImage.large != ""):
result = self.identityImage.large
else:
result = self.identicon
QtProperty[string] largeImage:
read = largeImage
notify = identityImageChanged
proc hasIdentityImage*(self: ProfileInfoView): bool {.slot.} =
result = (?.self.identityImage.thumbnail != "")
QtProperty[bool] hasIdentityImage:
read = hasIdentityImage
notify = identityImageChanged
proc pubKey*(self: ProfileInfoView): string {.slot.} = self.pubKey
QtProperty[string] pubKey:

View File

@ -0,0 +1,8 @@
import strutils, os
proc formatImagePath*(imagePath: string): string =
var image: string = replace(imagePath, "file://", "")
if defined(windows):
# Windows doesn't work with paths starting with a slash
image.removePrefix('/')
return image

View File

@ -19,7 +19,7 @@ proc generateAddresses*(self: AccountModel): seq[GeneratedAccount] =
var accounts = status_accounts.generateAddresses()
for account in accounts.mitems:
account.name = status_accounts.generateAlias(account.derived.whisper.publicKey)
account.photoPath = status_accounts.generateIdenticon(account.derived.whisper.publicKey)
account.identicon = status_accounts.generateIdenticon(account.derived.whisper.publicKey)
self.generatedAddresses.add(account)
result = self.generatedAddresses
@ -41,7 +41,7 @@ proc importMnemonic*(self: AccountModel, mnemonic: string): GeneratedAccount =
let importedAccount = status_accounts.multiAccountImportMnemonic(mnemonic)
importedAccount.derived = status_accounts.deriveAccounts(importedAccount.id)
importedAccount.name = status_accounts.generateAlias(importedAccount.derived.whisper.publicKey)
importedAccount.photoPath = status_accounts.generateIdenticon(importedAccount.derived.whisper.publicKey)
importedAccount.identicon = status_accounts.generateIdenticon(importedAccount.derived.whisper.publicKey)
result = importedAccount
proc reset*(self: AccountModel) =

View File

@ -67,9 +67,34 @@ proc initNode*() =
discard $nim_status.initKeystore(KEYSTOREDIR)
proc parseIdentityImage*(images: JsonNode): IdentityImage =
result = IdentityImage()
if (images.kind != JNull):
for image in images:
if (image["type"].getStr == "thumbnail"):
# TODO check if this can be url or if it's always uri
result.thumbnail = image["uri"].getStr
elif (image["type"].getStr == "large"):
result.large = image["uri"].getStr
proc openAccounts*(): seq[NodeAccount] =
let strNodeAccounts = $nim_status.openAccounts(DATADIR)
result = Json.decode(strNodeAccounts, seq[NodeAccount])
let strNodeAccounts = nim_status.openAccounts(DATADIR).parseJson
# FIXME fix serialization
result = @[]
if (strNodeAccounts.kind != JNull):
for account in strNodeAccounts:
let nodeAccount = NodeAccount(
name: account["name"].getStr,
timestamp: account["timestamp"].getInt,
keyUid: account["key-uid"].getStr,
identicon: account["identicon"].getStr,
keycardPairing: account["keycard-pairing"].getStr
)
if (account{"images"}.kind != JNull):
nodeAccount.identityImage = parseIdentityImage(account["images"])
result.add(nodeAccount)
proc saveAccountAndLogin*(
account: GeneratedAccount,
@ -91,7 +116,7 @@ proc saveAccountAndLogin*(
"public-key": account.derived.whisper.publicKey,
"address": account.derived.whisper.address,
"name": account.name,
"photo-path": account.photoPath,
"identicon": account.identicon,
"path": constants.PATH_WHISPER,
"chat": true
}
@ -127,7 +152,7 @@ proc getAccountData*(account: GeneratedAccount): JsonNode =
result = %* {
"name": account.name,
"address": account.address,
"photo-path": account.photoPath,
"identicon": account.identicon,
"key-uid": account.keyUid,
"keycard-pairing": nil
}
@ -148,7 +173,7 @@ proc getAccountSettings*(account: GeneratedAccount, defaultNetworks: JsonNode, i
"latest-derived-path": 0,
"networks/networks": defaultNetworks,
"currency": "usd",
"photo-path": account.photoPath,
"identicon": account.identicon,
"waku-enabled": true,
"wallet/visible-tokens": {
"mainnet": ["SNT"]
@ -336,3 +361,22 @@ proc deriveAccounts*(accountId: string): MultiAccounts =
proc logout*(): StatusGoError =
result = Json.decode($nim_status.logout(), StatusGoError)
proc storeIdentityImage*(keyUID: string, imagePath: string, aX, aY, bX, bY: int): IdentityImage =
let response = callPrivateRPC("multiaccounts_storeIdentityImage", %* [keyUID, imagePath, aX, aY, bX, bY]).parseJson
result = parseIdentityImage(response{"result"})
proc getIdentityImage*(keyUID: string): IdentityImage =
try:
let response = callPrivateRPC("multiaccounts_getIdentityImages", %* [keyUID]).parseJson
result = parseIdentityImage(response{"result"})
except Exception as e:
error "Error getting identity image", msg=e.msg
proc deleteIdentityImage*(keyUID: string): string =
try:
let response = callPrivateRPC("multiaccounts_deleteIdentityImage", %* [keyUID]).parseJson
result = ""
except Exception as e:
error "Error getting identity image", msg=e.msg
result = e.msg

View File

@ -15,6 +15,7 @@ proc getOurInstallations*(useCached: bool = true): JsonNode =
result = installations
proc syncDevices*(preferredName: string): string =
# TODO change this to identicon when status-go is updated
let photoPath = ""
result = callPrivateRPC("syncDevices".prefix, %* [preferredName, photoPath])

View File

@ -42,12 +42,17 @@ type MultiAccounts* = object
defaultWallet* {.serializedFieldName(PATH_DEFAULT_WALLET).}: DerivedAccount
eip1581* {.serializedFieldName(PATH_EIP_1581).}: DerivedAccount
type
IdentityImage* = ref object
thumbnail*: string
large*: string
type
Account* = ref object of RootObj
name*: string
keyUid* {.serializedFieldName("key-uid").}: string
photoPath* {.serializedFieldName("photo-path").}: string
identityImage*: IdentityImage
identicon*: string
type
NodeAccount* = ref object of Account
@ -66,7 +71,8 @@ type
# serializedFieldName pragma would need to be different
name*: string
keyUid*: string
photoPath*: string
identicon*: string
identityImage*: IdentityImage
type RpcError* = ref object
code*: int
@ -88,10 +94,10 @@ type
error*: RpcError
proc toAccount*(account: GeneratedAccount): Account =
result = Account(name: account.name, photoPath: account.photoPath, keyUid: account.address)
result = Account(name: account.name, identityImage: account.identityImage, identicon: account.identicon, keyUid: account.address)
proc toAccount*(account: NodeAccount): Account =
result = Account(name: account.name, photoPath: account.photoPath, keyUid: account.keyUid)
result = Account(name: account.name, identityImage: account.identityImage, identicon: account.identicon, keyUid: account.keyUid)
type AccountArgs* = ref object of Args
account*: Account
@ -247,4 +253,4 @@ proc getNodes*(self: FleetConfig, fleet: Fleet, nodeType: FleetNodes = FleetNode
result = toSeq(self.fleet[$fleet][$nodeType].values)
proc getMailservers*(self: FleetConfig, fleet: Fleet): Table[string, string] =
result = self.fleet[$fleet][$FleetNodes.Mailservers]
result = self.fleet[$fleet][$FleetNodes.Mailservers]

View File

@ -17,3 +17,12 @@ proc logout*(self: ProfileModel) =
proc getLinkPreviewWhitelist*(self: ProfileModel): JsonNode =
result = status_settings.getLinkPreviewWhitelist()
proc storeIdentityImage*(self: ProfileModel, keyUID: string, imagePath: string, aX, aY, bX, bY: int): IdentityImage =
result = status_accounts.storeIdentityImage(keyUID, imagePath, aX, aY, bX, bY)
proc getIdentityImage*(self: ProfileModel, keyUID: string): IdentityImage =
result = status_accounts.getIdentityImage(keyUID)
proc deleteIdentityImage*(self: ProfileModel, keyUID: string): string =
result = status_accounts.deleteIdentityImage(keyUID)

View File

@ -4,6 +4,7 @@ import ../libstatus/types
type Profile* = ref object
id*, alias*, username*, identicon*, address*, ensName*, localNickname*: string
ensVerified*: bool
identityImage*: IdentityImage
ensVerifiedAt*, ensVerificationRetries*, appearance*: int
systemTags*: seq[string]
@ -17,7 +18,7 @@ proc toProfileModel*(account: Account): Profile =
result = Profile(
id: "",
username: account.name,
identicon: account.photoPath,
identicon: account.identicon,
alias: account.name,
ensName: "",
ensVerified: false,
@ -36,6 +37,7 @@ proc toProfileModel*(profile: JsonNode): Profile =
id: profile["id"].str,
username: profile["alias"].str,
identicon: profile["identicon"].str,
identityImage: IdentityImage(),
address: profile["id"].str,
alias: profile["alias"].str,
ensName: "",
@ -51,3 +53,8 @@ proc toProfileModel*(profile: JsonNode): Profile =
if profile.hasKey("localNickname"):
result.localNickname = profile["localNickname"].str
if profile.hasKey("images"):
if profile["images"].hasKey("thumbnail"):
result.identityImage.thumbnail = profile["images"]["thumbnail"]["uri"].str
if profile["images"].hasKey("large"):
result.identityImage.large = profile["images"]["large"]["uri"].str

View File

@ -41,7 +41,7 @@ Item {
onLinkActivated: function (linkClicked) {
switch (linkClicked) {
case "shareKey":
openProfilePopup(profileModel.profile.username, profileModel.profile.pubKey, profileModel.profile.identicon);
openProfilePopup(profileModel.profile.username, profileModel.profile.pubKey, profileModel.profile.thumbnailImage);
break;
case "invite": inviteFriendsPopup.open(); break;
default: //no idea what was clicked

View File

@ -1,4 +1,4 @@
import QtQuick 2.3
import QtQuick 2.13
import "../../../../shared"
import "../../../../imports"
import "./MessageComponents"
@ -25,6 +25,7 @@ Item {
property bool timeout: false
property string linkUrls: ""
property string imageUrls: ""
property bool placeholderMessage: false
property string authorCurrentMsg: "authorCurrentMsg"
property string authorPrevMsg: "authorPrevMsg"
@ -48,6 +49,25 @@ Item {
property var imageClick: function () {}
property var scrollToBottom: function () {}
property string userPubKey: {
if (contentType === Constants.chatIdentifier) {
return chatId
}
return fromAuthor
}
property bool useLargeImage: contentType === Constants.chatIdentifier
property string profileImageSource: !placeholderMessage && chatView.getProfileImage(userPubKey, isCurrentUser, useLargeImage) || ""
Connections {
enabled: !placeholderMessage
target: profileModel.contacts.list
onContactChanged: {
if (pubkey === fromAuthor) {
profileImageSource = chatView.getProfileImage(userPubKey, isCurrentUser, useLargeImage)
}
}
}
id: root
width: parent.width
@ -82,13 +102,13 @@ Item {
messageContextMenu.isProfile = !!isProfileClick
messageContextMenu.isSticker = isSticker
messageContextMenu.emojiOnly = emojiOnly
messageContextMenu.show(userName, fromAuthor, identicon, "", nickname)
messageContextMenu.show(userName, fromAuthor, root.profileImageSource || identicon, "", nickname)
// Position the center of the menu where the mouse is
messageContextMenu.x = messageContextMenu.x - messageContextMenu.width / 2
}
Loader {
active :true
active: true
width: parent.width
sourceComponent: {
switch(contentType) {
@ -165,6 +185,7 @@ Item {
id: channelIdentifierComponent
ChannelIdentifier {
authorCurrentMsg: root.authorCurrentMsg
profileImage: profileImageSource
}
}
@ -173,7 +194,7 @@ Item {
id: privateGroupHeaderComponent
StyledText {
wrapMode: Text.Wrap
text: {
text: {
return `<html>`+
`<head>`+
`<style type="text/css">`+

View File

@ -6,8 +6,10 @@ Item {
property string authorCurrentMsg: "authorCurrentMsg"
property int verticalMargin: 50
property string profileImage
id: channelIdentifier
visible: authorCurrentMsg == ""
visible: authorCurrentMsg === ""
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: this.visible ? verticalMargin : 0
@ -28,15 +30,13 @@ Item {
return chatsModel.activeChannel.color
}
Image {
RoundedImage {
visible: chatsModel.activeChannel.chatType === Constants.chatTypeOneToOne
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
width: 120
height: 120
fillMode: Image.PreserveAspectFit
source: chatsModel.activeChannel.identicon
mipmap: true
source: channelIdentifier.profileImage || chatsModel.activeChannel.identicon
smooth: false
antialiasing: true
}
@ -71,7 +71,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
}
Rectangle {
Item {
id: channelDescription
visible: descText.visible
width: visible ? 330 : 0
@ -79,7 +79,6 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: channelName.bottom
anchors.topMargin: 16
color: Style.current.transparent
StyledText {
id: descText

View File

@ -50,7 +50,8 @@ Item {
if (link.startsWith('//')) {
let pk = link.replace("//", "");
openProfilePopup(chatsModel.userNameOrAlias(pk), pk, utilsModel.generateIdenticon(pk))
const userProfileImage = chatView.getProfileImage(pk)
openProfilePopup(chatsModel.userNameOrAlias(pk), pk, userProfileImage || utilsModel.generateIdenticon(pk))
return;
}

View File

@ -7,21 +7,24 @@ Loader {
height: active ? item.height : 0
sourceComponent: Component {
Rectangle {
Item {
id: chatImage
width: identiconImage.width
height: identiconImage.height
border.width: 1
border.color: Style.current.border
radius: 50
Image {
RoundedImage {
id: identiconImage
width: 36
height: 36
fillMode: Image.PreserveAspectFit
source: !isCurrentUser ? identicon : profileModel.profile.identicon
mipmap: true
border.width: 1
border.color: Style.current.border
source: {
if (profileImageSource) {
return profileImageSource
}
return !isCurrentUser ? identicon : profileModel.profile.identicon
}
smooth: false
antialiasing: true

View File

@ -40,7 +40,8 @@ Rectangle {
groupInfoPopup.open()
break;
case Constants.chatTypeOneToOne:
openProfilePopup(chatsModel.activeChannel.name, chatsModel.activeChannel.id, chatsModel.activeChannel.identicon)
const profileImage = chatView.getProfileImage(chatsModel.activeChannel.id)
openProfilePopup(chatsModel.activeChannel.name, chatsModel.activeChannel.id, profileImage || chatsModel.activeChannel.identicon)
break;
}
}

View File

@ -37,6 +37,19 @@ SplitView {
chatGroupsListViewCount: contactsColumn.chatGroupsListViewCount
}
function getProfileImage(pubkey, isCurrentUser, useLargeImage) {
if (isCurrentUser || (isCurrentUser === undefined && pubkey === profileModel.profile.pubKey)) {
return profileModel.profile.thumbnailImage
}
const index = profileModel.contacts.list.getContactIndexByPubkey(pubkey)
if (index === -1) {
return
}
return profileModel.contacts.list.rowData(index, useLargeImage ? "largeImage" : "thumbnailImage")
}
function openProfilePopup(userNameParam, fromAuthorParam, identiconParam, textParam, nicknameParam, parentPopup){
var popup = profilePopupComponent.createObject(chatView);
if(parentPopup){

View File

@ -20,6 +20,18 @@ Rectangle {
property bool muted: false
property bool hovered: false
property string profileImage: chatType === Constants.chatTypeOneToOne ? chatView.getProfileImage(chatId) || "" : ""
Connections {
enabled: chatType === Constants.chatTypeOneToOne
target: profileModel.contacts.list
onContactChanged: {
if (pubkey === wrapper.chatId) {
wrapper.profileImage = chatView.getProfileImage(wrapper.chatId)
}
}
}
id: wrapper
color: {
if (ListView.isCurrentItem || wrapper.hovered) {
@ -42,7 +54,7 @@ Rectangle {
width: !isCompact ? 40 : 20
chatName: wrapper.name
chatType: wrapper.chatType
identicon: wrapper.identicon
identicon: wrapper.profileImage || wrapper.identicon
anchors.left: parent.left
anchors.leftMargin: !isCompact ? Style.current.padding : Style.current.smallPadding
anchors.verticalCenter: parent.verticalCenter

View File

@ -156,7 +156,8 @@ ScrollView {
chatsModel.setActiveChannelByIndex(channelContextMenu.channelIndex)
chatGroupsListView.currentIndex = channelContextMenu.channelIndex
if (channelContextMenu.chatType === Constants.chatTypeOneToOne) {
return openProfilePopup(channelContextMenu.chatName, channelContextMenu.chatId, channelContextMenu.chatIdenticon)
const userProfileImage = chatView.getProfileImage(channelContextMenu.chatId)
return openProfilePopup(channelContextMenu.chatName, channelContextMenu.chatId, userProfileImage || channelContextMenu.chatIdenticon)
}
if (channelContextMenu.chatType === Constants.chatTypePrivateGroupChat) {
return groupInfoPopup.open()

View File

@ -37,6 +37,7 @@ ModalPopup {
pubKey: profileModel.contacts.list.rowData(i, "pubKey"),
address: profileModel.contacts.list.rowData(i, "address"),
identicon: profileModel.contacts.list.rowData(i, "identicon"),
thumbnailImage: profileModel.contacts.list.rowData(i, "thumbnailImage"),
isUser: false,
isContact: profileModel.contacts.list.rowData(i, "isContact") !== "false"
});
@ -47,6 +48,7 @@ ModalPopup {
pubKey: profileModel.profile.pubKey,
address: "",
identicon: profileModel.profile.identicon,
thumbnailImage: profileModel.profile.thumbnailImage,
isUser: true
});
noContactsRect.visible = !profileModel.contacts.list.hasAddedContacts();
@ -186,7 +188,7 @@ ModalPopup {
name: !model.name.endsWith(".eth") && !!model.localNickname ?
model.localNickname : Utils.removeStatusEns(model.name)
address: model.address
identicon: model.identicon
identicon: model.thumbnailImage || model.identicon
onItemChecked: function(pubKey, itemChecked){
var idx = pubKeys.indexOf(pubKey)
if(itemChecked){

View File

@ -30,6 +30,7 @@ ModalPopup {
pubKey: profileModel.contacts.list.rowData(i, "pubKey"),
address: profileModel.contacts.list.rowData(i, "address"),
identicon: profileModel.contacts.list.rowData(i, "identicon"),
thumbnailImage: profileModel.contacts.list.rowData(i, "thumbnailImage"),
isUser: false
});
}
@ -211,7 +212,7 @@ ModalPopup {
name: model.name.endsWith(".eth") || !model.localNickname ?
Utils.removeStatusEns(model.name) : model.localNickname
address: model.address
identicon: model.identicon
identicon: model.thumbnailImage || model.identicon
onItemChecked: function(pubKey, itemChecked){
var idx = pubKeys.indexOf(pubKey)
if(itemChecked){
@ -278,7 +279,7 @@ ModalPopup {
StatusImageIdenticon {
id: identicon
anchors.left: parent.left
source: model.identicon
source: chatView.getProfileImage(model.pubKey)|| model.identicon
}
StyledText {
@ -293,7 +294,10 @@ ModalPopup {
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: openProfilePopup(model.userName, model.pubKey, model.identicon, '', contactRow.nickname)
onClicked: {
const userProfileImage = chatView.getProfileImage(model.pubKey)
openProfilePopup(model.userName, model.pubKey, userProfileImage || model.identicon, '', contactRow.nickname)
}
}
}

View File

@ -21,7 +21,7 @@ PopupMenu {
property var fromAuthor: ""
property var text: ""
function show(userNameParam, fromAuthorParam, identiconParam, textParam, nicknameParam){
function show(userNameParam, fromAuthorParam, identiconParam, textParam, nicknameParam) {
userName = userNameParam || ""
nickname = nicknameParam || ""
fromAuthor = fromAuthorParam || ""

View File

@ -138,7 +138,7 @@ ModalPopup {
isUser: false
name: model.name
address: model.address
identicon: model.identicon
identicon: model.thumbnailImage || model.identicon
showListSelector: true
onItemChecked: function(pubKey, itemChecked){
chatsModel.joinChat(pubKey, Constants.chatTypeOneToOne);

View File

@ -49,21 +49,14 @@ ModalPopup {
header: Item {
height: children[0].height
width: parent.width
Rectangle {
RoundedImage {
id: profilePic
width: 40
height: 40
radius: 30
border.color: "#10000000"
border.color: Style.current.border
border.width: 1
color: Style.current.transparent
anchors.top: parent.top
SVGImage {
width: parent.width
height: parent.height
fillMode: Image.PreserveAspectFit
source: identicon
}
source: identicon
}
StyledTextEdit {

View File

@ -45,12 +45,7 @@ SplitView {
// This list needs to match LeftTab/constants.js
// Would be better if we could make them match automatically
MyProfileContainer {
username: profileModel.profile.username
ensName: profileModel.ens.preferredUsername
identicon: profileModel.profile.identicon
pubkey: profileModel.profile.pubKey
}
MyProfileContainer {}
onCurrentIndexChanged: {
if(visibleChildren[0] === ensContainer){

View File

@ -81,7 +81,7 @@ ScrollView {
identicon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAb0lEQVR4Ae3UQQqAIBRF0Wj9ba9Bq6l5JBQqfn/ngDMH3YS3AAB/tO3H+XRG3b9bR/+gVoREI2RapVXpfd5+X5oXERKNkHS+rk3tOpWkeREh0QiZVu91ql2zNC8iJBoh0yqtSqt1slpCghICANDPBc0ESPh0bHkHAAAAAElFTkSuQmCC"
message: qsTr("Blockchains will drop search costs, causing a kind of decomposition that allows you to have markets of entities that are horizontally segregated and vertically segregated.")
contentType: Constants.messageType
placeholderMessage: true
}
}

View File

@ -0,0 +1,150 @@
import QtQuick 2.13
import QtQuick.Dialogs 1.3
import "../../../../imports"
import "../../../../shared"
import "../../../../shared/status"
ModalPopup {
property string selectedImage // selectedImage is for us to be able to analyze it before setting it as current
property string uploadError
id: popup
title: qsTr("Profile picture")
onClosed: {
destroy()
}
onSelectedImageChanged: {
if (!selectedImage) {
return
}
cropImageModal.open()
}
Item {
anchors.fill: parent
RoundedImage {
id: profilePic
source: profileModel.profile.largeImage
width: 160
height: 160
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
border.width: 1
border.color: Style.current.border
onClicked: imageDialog.open()
}
StyledText {
visible: !!uploadError
text: uploadError
anchors.left: parent.left
anchors.right: parent.right
anchors.top: profilePic.bottom
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 13
wrapMode: Text.WordWrap
anchors.topMargin: 13
font.weight: Font.Thin
color: Style.current.danger
}
ModalPopup {
id: cropImageModal
width: image.width + 50
height: image.height + 170
title: qsTr("Crop your image (optionnal)")
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
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)
uploadError = profileModel.uploadNewProfilePic(selectedImage, aX, aY, bX, bY)
cropImageModal.close()
}
}
}
}
footer: Item {
anchors.fill:parent
StatusButton {
visible: profileModel.profile.hasIdentityImage
type: "secondary"
flat: true
color: Style.current.danger
text: qsTr("Remove")
anchors.right: uploadBtn.left
anchors.rightMargin: Style.current.padding
anchors.bottom: parent.bottom
onClicked: {
uploadError = profileModel.deleteProfilePic()
}
}
StatusButton {
id: uploadBtn
text: qsTr("Upload")
anchors.right: parent.right
anchors.bottom: parent.bottom
onClicked: {
imageDialog.open()
}
FileDialog {
id: imageDialog
//% "Please choose an image"
title: qsTrId("please-choose-an-image")
folder: shortcuts.pictures
nameFilters: [
//% "Image files (*.jpg *.jpeg *.png)"
qsTrId("image-files----jpg---jpeg---png-")
]
onAccepted: {
selectedImage = imageDialog.fileUrls[0]
}
}
}
}
}
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:640}
}
##^##*/

View File

@ -25,7 +25,7 @@ ListView {
name: Utils.removeStatusEns(model.name)
address: model.address
localNickname: model.localNickname
identicon: model.identicon
identicon: model.thumbnailImage || model.identicon
isContact: model.isContact
isBlocked: model.isBlocked
selectable: contactList.selectable

View File

@ -6,15 +6,19 @@ import "../../../../shared"
import "../../../../shared/status"
Item {
property string username: "Jotaro Kujo"
property string identicon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAhklEQVR4nOzWwQ1AQBgFYUQvelKHMtShJ9VwFyvrsExe5jvKXiYv+WPoQhhCYwiNITSG0MSEjLUPt3097r7P09L/8f4qZhFDaAyhqboIT76+TiUxixhCYwhN9b/WW6Xr1ErMIobQGEJjCI0hNIbQGEJjCI0haiRmEUNoDKExhMYQmjMAAP//B2kXcP2uDV8AAAAASUVORK5CYII="
property string pubkey: "0x04d8c07dd137bd1b73a6f51df148b4f77ddaa11209d36e43d8344c0a7d6db1cad6085f27cfb75dd3ae21d86ceffebe4cf8a35b9ce8d26baa19dc264efe6d8f221b"
property string ensName: "joestar.eth"
property string ensName: profileModel.profile.preferredUsername || ""
property string username: profileModel.profile.username
property string pubkey: profileModel.profile.pubKey
id: profileHeaderContent
height: parent.height
Layout.fillWidth: true
Component {
id: changeProfileModalComponent
ChangeProfilePicModal {}
}
Item {
id: profileImgNameContainer
anchors.top: parent.top
@ -26,43 +30,58 @@ Item {
height: this.childrenRect.height
Rectangle {
id: profileImg
width: identiconImage.width
height: identiconImage.height
border.width: 1
border.color: Style.current.border
radius: 50
color: Style.current.background
Item {
id: profileImgContainer
width: profileImg.width
height: profileImg.height
Image {
id: identiconImage
width: 60
height: 60
fillMode: Image.PreserveAspectFit
source: identicon
mipmap: true
RoundedImage {
id: profileImg
width: 64
height: 64
border.width: 1
border.color: Style.current.border
source: profileModel.profile.thumbnailImage || ""
smooth: false
antialiasing: true
}
RoundedIcon {
source: "../../../img/pencil.svg"
anchors.bottom: parent.bottom
anchors.bottomMargin: -3
anchors.right: parent.right
anchors.rightMargin: -3
width: 24
height: 24
border.width: 1
border.color: Style.current.background
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
const popup = changeProfileModalComponent.createObject(profileHeaderContent);
popup.open()
}
}
}
StyledText {
id: profileName
text: ensName !== "" ? ensName : username
anchors.left: profileImg.right
anchors.left: profileImgContainer.right
anchors.leftMargin: 8
anchors.top: profileImg.top
anchors.topMargin: 4
font.family: "Inter"
font.weight: Font.Bold
font.pixelSize: 20
anchors.top: profileImgContainer.top
font.weight: Font.Medium
font.pixelSize: 15
}
Address {
id: pubkeyText
text: ensName !== "" ? username : pubkey
anchors.bottom: profileImg.bottom
anchors.bottom: profileImgContainer.bottom
anchors.left: profileName.left
anchors.bottomMargin: 4
width: 200
@ -85,7 +104,9 @@ Item {
Separator {
id: lineSeparator
anchors.top: profileImg.bottom
anchors.top: profileImgContainer.bottom
anchors.topMargin: 36
}
}

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

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.10144 13.5959L2.79885 10.8062C2.83108 10.6773 2.87171 10.5512 2.92034 10.4285C2.9822 10.2724 3.18194 10.2423 3.30066 10.361L5.63945 12.6998C5.75817 12.8185 5.72809 13.0183 5.572 13.0801C5.44932 13.1288 5.32315 13.1694 5.19424 13.2016L2.40461 13.899C2.22152 13.9448 2.05567 13.779 2.10144 13.5959Z" fill="white"/>
<path d="M6.32361 11.2627C6.71413 11.6532 7.3473 11.6532 7.73782 11.2627L14.2933 4.7072C14.6838 4.31668 14.6838 3.68351 14.2933 3.29299L12.7075 1.7072C12.317 1.31668 11.6838 1.31668 11.2933 1.7072L4.73782 8.26266C4.3473 8.65318 4.3473 9.28635 4.73782 9.67687L6.32361 11.2627Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 720 B

View File

@ -182,6 +182,7 @@ DISTFILES += \
app/AppLayouts/Profile/Sections/AppearanceContainer.qml \
app/AppLayouts/Profile/Sections/BackupSeedModal.qml \
app/AppLayouts/Profile/Sections/BrowserContainer.qml \
app/AppLayouts/Profile/Sections/ChangeProfilePicModal.qml \
app/AppLayouts/Profile/Sections/MyProfileContainer.qml \
app/AppLayouts/Profile/Sections/SoundsContainer.qml \
app/AppLayouts/UIComponents/UIComponents.qml \
@ -364,8 +365,10 @@ DISTFILES += \
shared/AccountSelector.qml \
shared/AddButton.qml \
shared/Address.qml \
shared/CropCornerRectangle.qml \
shared/FormGroup.qml \
shared/IconButton.qml \
shared/ImageCropper.qml \
shared/Input.qml \
shared/LabelValueRow.qml \
shared/ModalPopup.qml \

View File

@ -35,7 +35,7 @@ Item {
StatusImageIdenticon {
id: userImage
anchors.horizontalCenter: parent.horizontalCenter
source: loginModel.currentAccount.identicon
source: loginModel.currentAccount.thumbnailImage
}
StyledText {

View File

@ -17,7 +17,7 @@ ListView {
delegate: AddressView {
username: model.username
address: model.address
identicon: model.identicon
identicon: model.thumbnailImage
isSelected: function (index, address) {
return addressesView.isSelected(index, address)
}

View File

@ -0,0 +1,23 @@
import QtQuick 2.13
import "../imports"
Rectangle {
id: root
width: 25
height: 25
border.width: 2
border.color: Style.current.orange
color: Utils.setColorAlpha(Style.current.orange, 0.5)
Drag.active: dragArea.drag.active
MouseArea {
id: dragArea
property int oldX
property int oldY
anchors.fill: parent
drag.target: parent
cursorShape: Qt.PointingHandCursor
}
}

107
ui/shared/ImageCropper.qml Normal file
View File

@ -0,0 +1,107 @@
import QtQuick 2.13
import "."
import "../imports"
Item {
property var image
property alias selectorRectangle: selectorRectangle
width: image.width
height: image.height
Rectangle {
id: selectorRectangle
visible: false
x: 0
y: 0
border.width: 2
border.color: Style.current.orange
color: Style.current.transparent
function initialSetup() {
topLeftCorner.x = 0
topLeftCorner.y = 0
topRightCorner.x = image.width - topRightCorner.width
topRightCorner.y = 0
bottomLeftCorner.x = 0
bottomLeftCorner.y = image.height - topRightCorner.height
bottomRightCorner.x = image.width - topRightCorner.width
bottomRightCorner.y = image.height - topRightCorner.height
selectorRectangle.width = image.width
selectorRectangle.height = image.height
}
function adjustRectangleSize() {
selectorRectangle.width = bottomRightCorner.x + bottomRightCorner.width - topLeftCorner.x
selectorRectangle.height = bottomRightCorner.y + bottomRightCorner.height - topLeftCorner.y
selectorRectangle.x = topLeftCorner.x
selectorRectangle.y = topLeftCorner.y
}
Connections {
target: image
onStatusChanged: {
if (image.status === Image.Ready) {
selectorRectangle.initialSetup()
selectorRectangle.visible = true
}
}
}
}
// Size calculations are only done on top-left and bottom-right, because the other two corners follow them
CropCornerRectangle {
id: topLeftCorner
onXChanged: {
if (x < 0) x = 0
if (x > topRightCorner.x - width) x = topRightCorner.x - width
bottomLeftCorner.x = x
selectorRectangle.adjustRectangleSize()
}
onYChanged: {
if (y < 0) y = 0
if (y > bottomRightCorner.y - height) y = bottomRightCorner.y - height
topRightCorner.y = y
selectorRectangle.adjustRectangleSize()
}
}
CropCornerRectangle {
id: topRightCorner
onXChanged: {
bottomRightCorner.x = x
}
onYChanged: {
topLeftCorner.y = y
}
}
CropCornerRectangle {
id: bottomLeftCorner
onXChanged: {
topLeftCorner.x = x
}
onYChanged: {
bottomRightCorner.y = y
}
}
CropCornerRectangle {
id: bottomRightCorner
onXChanged: {
if (x < topLeftCorner.x + topLeftCorner.width) x = topLeftCorner.x + topLeftCorner.width
if (x > image.width - width) x = image.width - width
topRightCorner.x = x
selectorRectangle.adjustRectangleSize()
}
onYChanged: {
if (y < topRightCorner.y + topRightCorner.height) y = topRightCorner.y + topRightCorner.height
if (y > image.height - height) y = image.height - height
bottomLeftCorner.y = y
selectorRectangle.adjustRectangleSize()
}
}
}

View File

@ -4,6 +4,7 @@ import "../imports"
Rectangle {
id: root
property bool noHover: false
property alias source: image.source
property alias fillMode: image.fillMode
signal loaded
@ -138,7 +139,7 @@ Rectangle {
}
}
MouseArea {
cursorShape: Qt.PointingHandCursor
cursorShape: noHover ? Qt.ArrowCursor : Qt.PointingHandCursor
anchors.fill: parent
onClicked: {
root.clicked()

View File

@ -4,6 +4,7 @@ import QtGraphicalEffects 1.0
Rectangle {
id: root
signal clicked
property bool noHover: false
property alias source: imgStickerPackThumb.source
property alias fillMode: imgStickerPackThumb.fillMode
@ -25,6 +26,7 @@ Rectangle {
ImageLoader {
id: imgStickerPackThumb
noHover: root.noHover
opacity: 1
smooth: false
radius: root.radius
@ -32,4 +34,4 @@ Rectangle {
source: "https://ipfs.infura.io/ipfs/" + thumbnail
onClicked: root.clicked()
}
}
}

View File

@ -14,14 +14,26 @@ Item {
property int identiconSize: 40
property bool isCompact: false
property string profileImage: chatType === Constants.chatTypeOneToOne ? chatView.getProfileImage(chatId) || "" : ""
height: 48
width: nameAndInfo.width + chatIdenticon.width + Style.current.smallPadding
Connections {
enabled: chatType === Constants.chatTypeOneToOne
target: profileModel.contacts.list
onContactChanged: {
if (pubkey === root.chatId) {
root.profileImage = chatView.getProfileImage(root.chatId)
}
}
}
StatusIdenticon {
id: chatIdenticon
chatType: root.chatType
chatName: root.chatName
identicon: root.identicon
identicon: root.profileImage || root.identicon
width: root.isCompact ? 20 : root.identiconSize
height: root.isCompact ? 20 : root.identiconSize
anchors.verticalCenter: parent.verticalCenter

View File

@ -24,7 +24,12 @@ Button {
chatId: control.chatId
chatName: control.chatName
chatType: control.chatType
identicon: control.identicon
identicon: {
if (control.chatType === Constants.chatTypeOneToOne) {
return chatView.getProfileImage(control.chatId) || control.identicon
}
return control.identicon
}
identiconSize: control.identiconSize
isCompact: control.isCompact
}

View File

@ -2,25 +2,14 @@ import QtQuick 2.13
import "../../imports"
import "../../shared"
Rectangle {
RoundedImage {
id: root
property url source:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAg0lEQVR4nOzXwQmAMBAFURV7sQybsgybsgyr0QYUlE1g+Mw7ioQMe9lMQwhDaAyhMYTGEJqYkPnrj/t5XE/ft2UdW1yken7MRAyhMYTGEBpDaAyhKe9JbzvSX9WdLWYihtAYQuMLkcYQGkPUScxEDKExhMYQGkNoDKExhMYQmjsAAP//ZfIUZgXTZXQAAAAASUVORK5CYII="
noHover: true
source:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAg0lEQVR4nOzXwQmAMBAFURV7sQybsgybsgyr0QYUlE1g+Mw7ioQMe9lMQwhDaAyhMYTGEJqYkPnrj/t5XE/ft2UdW1yken7MRAyhMYTGEBpDaAyhKe9JbzvSX9WdLWYihtAYQuMLkcYQGkPUScxEDKExhMYQGkNoDKExhMYQmjsAAP//ZfIUZgXTZXQAAAAASUVORK5CYII="
width: 40
height: 40
color: Style.current.background
radius: width / 2
border.width: 1
border.color: Style.current.borderSecondary
Image {
width: parent.width
height: parent.height
fillMode: Image.PreserveAspectFit
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
source: root.source
mipmap: true
smooth: false
antialiasing: true
}
smooth: false
antialiasing: true
}

2
vendor/DOtherSide vendored

@ -1 +1 @@
Subproject commit a474e0e1fedd213fa9590811faca198761660857
Subproject commit 5c34c8e3563b2d10a2f3d44ac0ee11da13f9a916

2
vendor/nimqml vendored

@ -1 +1 @@
Subproject commit b1ba8cba3c75de574046071de824031cde6e580b
Subproject commit db7042d4902dc19e5a286416ffa293e43b6249c5

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit d8307a60cfc49998a48dcb7026b3a1ca17d55985
Subproject commit 55a08e9e4d259d77637420ec318cfbafeb8f9f6e