From b2546fa70945e951d81d5327e854bc78fe918380 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Fri, 17 Dec 2021 14:58:51 -0500 Subject: [PATCH] refactor(ProfilePopup): hook ProfilePopup to backend properties --- src/app/global/utils.nim | 28 ++- src/app/global/utils/qrcodegen.nim | 164 ++++++++++++++++++ .../main/profile_section/contacts/view.nim | 7 + .../AppLayouts/Chat/popups/ProfilePopup.qml | 40 ++--- ui/app/AppLayouts/Chat/stores/RootStore.qml | 16 +- 5 files changed, 224 insertions(+), 31 deletions(-) create mode 100644 src/app/global/utils/qrcodegen.nim diff --git a/src/app/global/utils.nim b/src/app/global/utils.nim index 669986769c..34af51028d 100644 --- a/src/app/global/utils.nim +++ b/src/app/global/utils.nim @@ -1,6 +1,6 @@ -import NimQml, strutils, uri +import NimQml, strutils, uri, strformat, strutils, stint import stew/byteutils -import stint +import ./utils/qrcodegen # Services as instances shouldn't be used in this class, just some general/global procs import ../../app_service/service/eth/utils as procs_from_utils @@ -87,4 +87,26 @@ QtObject: writeFile(filepath, text) return true except: - return false \ No newline at end of file + return false + + proc copyToClipboard*(self: Utils, content: string) {.slot.} = + setClipBoardText(content) + + proc generateQRCodeSVG*(self: Utils, text: string, border: int = 0): string = + var qr0: array[0..qrcodegen_BUFFER_LEN_MAX, uint8] + var tempBuffer: array[0..qrcodegen_BUFFER_LEN_MAX, uint8] + let ok: bool = qrcodegen_encodeText(text, tempBuffer[0].addr, qr0[0].addr, qrcodegen_Ecc_MEDIUM, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true); + if not ok: + raise newException(Exception, "Error generating QR Code") + else: + var parts: seq[string] = @[] + let size = qrcodegen_getSize(qr0[0].addr); + for y in countup(0, size): + for x in countup(0, size): + if qrcodegen_getModule(qr0[0].addr, x.cint, y.cint): + parts.add(&"M{x + border},{y + border}h1v1h-1z") + let partsStr = parts.join(" ") + result = &"" + + proc qrCode*(self: Utils, text:string): string {.slot.} = + result = "data:image/svg+xml;utf8," & self.generateQRCodeSVG(text, 2) \ No newline at end of file diff --git a/src/app/global/utils/qrcodegen.nim b/src/app/global/utils/qrcodegen.nim new file mode 100644 index 0000000000..72e6b88e5c --- /dev/null +++ b/src/app/global/utils/qrcodegen.nim @@ -0,0 +1,164 @@ +## +## QR Code generator library (C) +## +## Copyright (c) Project Nayuki. (MIT License) +## https://www.nayuki.io/page/qr-code-generator-library +## +## Permission is hereby granted, free of charge, to any person obtaining a copy of +## this software and associated documentation files (the "Software"), to deal in +## the Software without restriction, including without limitation the rights to +## use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +## the Software, and to permit persons to whom the Software is furnished to do so, +## subject to the following conditions: +## - The above copyright notice and this permission notice shall be included in +## all copies or substantial portions of the Software. +## - The Software is provided "as is", without warranty of any kind, express or +## implied, including but not limited to the warranties of merchantability, +## fitness for a particular purpose and noninfringement. In no event shall the +## authors or copyright holders be liable for any claim, damages or other +## liability, whether in an action of contract, tort or otherwise, arising from, +## out of or in connection with the Software or the use or other dealings in the +## Software. +## + +## +## This library creates QR Code symbols, which is a type of two-dimension barcode. +## Invented by Denso Wave and described in the ISO/IEC 18004 standard. +## A QR Code structure is an immutable square grid of black and white cells. +## The library provides functions to create a QR Code from text or binary data. +## The library covers the QR Code Model 2 specification, supporting all versions (sizes) +## from 1 to 40, all 4 error correction levels, and 4 character encoding modes. +## +## Ways to create a QR Code object: +## - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary(). +## - Low level: Custom-make the list of segments and call +## qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced(). +## (Note that all ways require supplying the desired error correction level and various byte buffers.) +## +## ---- Enum and struct types---- +## +## The error correction level in a QR Code symbol. +## + +type + qrcodegen_Ecc* = enum ## Must be declared in ascending order of error protection + ## so that an internal qrcodegen function works properly + qrcodegen_Ecc_LOW = 0, ## The QR Code can tolerate about 7% erroneous codewords + qrcodegen_Ecc_MEDIUM, ## The QR Code can tolerate about 15% erroneous codewords + qrcodegen_Ecc_QUARTILE, ## The QR Code can tolerate about 25% erroneous codewords + qrcodegen_Ecc_HIGH ## The QR Code can tolerate about 30% erroneous codewords + + +## +## The mask pattern used in a QR Code symbol. +## + +type + qrcodegen_Mask* = enum ## A special value to tell the QR Code encoder to + ## automatically select an appropriate mask pattern + qrcodegen_Mask_AUTO = -1, ## The eight actual mask patterns + qrcodegen_Mask_0 = 0, qrcodegen_Mask_1, qrcodegen_Mask_2, qrcodegen_Mask_3, + qrcodegen_Mask_4, qrcodegen_Mask_5, qrcodegen_Mask_6, qrcodegen_Mask_7 + + +## +## Describes how a segment's data bits are interpreted. +## + +type + qrcodegen_Mode* = enum + qrcodegen_Mode_NUMERIC = 0x00000001, qrcodegen_Mode_ALPHANUMERIC = 0x00000002, + qrcodegen_Mode_BYTE = 0x00000004, qrcodegen_Mode_ECI = 0x00000007, + qrcodegen_Mode_KANJI = 0x00000008 + + +## +## A segment of character/binary/control data in a QR Code symbol. +## The mid-level way to create a segment is to take the payload data +## and call a factory function such as qrcodegen_makeNumeric(). +## The low-level way to create a segment is to custom-make the bit buffer +## and initialize a qrcodegen_Segment struct with appropriate values. +## Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. +## Any segment longer than this is meaningless for the purpose of generating QR Codes. +## Moreover, the maximum allowed bit length is 32767 because +## the largest QR Code (version 40) has 31329 modules. +## + +type + qrcodegen_Segment* {.bycopy.} = object + mode*: qrcodegen_Mode ## The mode indicator of this segment. + ## The length of this segment's unencoded data. Measured in characters for + ## numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + ## Always zero or positive. Not the same as the data's bit length. + numChars*: cint ## The data bits of this segment, packed in bitwise big endian. + ## Can be null if the bit length is zero. + data*: ptr uint8 ## The number of valid data bits used in the buffer. Requires + ## 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8. + ## The character count (numChars) must agree with the mode and the bit buffer length. + bitLength*: cint + + +## ---- Macro constants and functions ---- + +const + qrcodegen_VERSION_MIN* = 1 + qrcodegen_VERSION_MAX* = 40 + +## Calculates the number of bytes needed to store any QR Code up to and including the given version number, +## as a compile-time constant. For example, 'uint8 buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];' +## can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16). +## Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX. + +template qrcodegen_BUFFER_LEN_FOR_VERSION*(n: untyped): untyped = + ((((n) * 4 + 17) * ((n) * 4 + 17) + 7) div 8 + 1) + +## The worst-case number of bytes needed to store one QR Code, up to and including +## version 40. This value equals 3918, which is just under 4 kilobytes. +## Use this more convenient value to avoid calculating tighter memory bounds for buffers. + +const + qrcodegen_BUFFER_LEN_MAX* = qrcodegen_BUFFER_LEN_FOR_VERSION( + qrcodegen_VERSION_MAX) + +## ---- Functions (high level) to generate QR Codes ---- +## +## Encodes the given text string to a QR Code, returning true if encoding succeeded. +## If the data is too long to fit in any version in the given range +## at the given ECC level, then false is returned. +## - The input text must be encoded in UTF-8 and contain no NULs. +## - The variables ecl and mask must correspond to enum constant values. +## - Requires 1 <= minVersion <= maxVersion <= 40. +## - The arrays tempBuffer and qrcode must each have a length +## of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion). +## - After the function returns, tempBuffer contains no useful data. +## - If successful, the resulting QR Code may use numeric, +## alphanumeric, or byte mode to encode the text. +## - In the most optimistic case, a QR Code at version 40 with low ECC +## can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string +## up to 4296 characters, or any digit string up to 7089 characters. +## These numbers represent the hard upper limit of the QR Code standard. +## - Please consult the QR Code specification for information on +## data capacities per version, ECC level, and text encoding mode. +## + +proc qrcodegen_encodeText*(text: cstring; tempBuffer: ptr uint8; + qrcode: ptr uint8; ecl: qrcodegen_Ecc; minVersion: cint; + maxVersion: cint; mask: qrcodegen_Mask; boostEcl: bool): bool {.importc: "qrcodegen_encodeText".} + +#proc qrcodegen_makeEci*(assignVal: clong; buf: ptr uint8): qrcodegen_Segment +## ---- Functions to extract raw data from QR Codes ---- +## +## Returns the side length of the given QR Code, assuming that encoding succeeded. +## The result is in the range [21, 177]. Note that the length of the array buffer +## is related to the side length - every 'uint8 qrcode[]' must have length at least +## qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1). +## + +proc qrcodegen_getSize*(qrcode: ptr uint8): cint {.importc: "qrcodegen_getSize".} +## +## Returns the color of the module (pixel) at the given coordinates, which is false +## for white or true for black. The top left corner has the coordinates (x=0, y=0). +## If the given coordinates are out of bounds, then false (white) is returned. +## + +proc qrcodegen_getModule*(qrcode: ptr uint8; x: cint; y: cint): bool {.importc: "qrcodegen_getModule".} \ No newline at end of file diff --git a/src/app/modules/main/profile_section/contacts/view.nim b/src/app/modules/main/profile_section/contacts/view.nim index cfa384149f..a52d96eb1b 100644 --- a/src/app/modules/main/profile_section/contacts/view.nim +++ b/src/app/modules/main/profile_section/contacts/view.nim @@ -128,3 +128,10 @@ QtObject: proc removeContact*(self: View, publicKey: string) {.slot.} = self.delegate.removeContact(publicKey) + + proc isEnsVerified*(self: View, publicKey: string): bool {.slot.} = + return self.delegate.getContact(publicKey).ensVerified + + proc alias*(self: View, publicKey: string): string {.slot.} = + return self.delegate.getContact(publicKey).alias + diff --git a/ui/app/AppLayouts/Chat/popups/ProfilePopup.qml b/ui/app/AppLayouts/Chat/popups/ProfilePopup.qml index 6e6814282b..c78b0baa53 100644 --- a/ui/app/AppLayouts/Chat/popups/ProfilePopup.qml +++ b/ui/app/AppLayouts/Chat/popups/ProfilePopup.qml @@ -51,11 +51,9 @@ StatusModal { fromAuthor = fromAuthorParam || "" identicon = identiconParam || "" text = textParam || "" - // Not Refactored Yet -// isEnsVerified = chatsModel.ensView.isEnsVerified(this.fromAuthor) - isBlocked = popup.store.contactsModuleInst.model.isContactBlocked(this.fromAuthor); - // Not Refactored Yet -// alias = chatsModel.alias(this.fromAuthor) || "" + isEnsVerified = popup.store.isEnsVerified(this.fromAuthor) + isBlocked = popup.store.isContactBlocked(this.fromAuthor); + alias = popup.store.alias(this.fromAuthor) || "" isCurrentUser = userProfile.pubKey === this.fromAuthor showFooter = _showFooter; popup.open() @@ -98,15 +96,15 @@ StatusModal { } StatusDescriptionListItem { - // Not Refactored Yet -// title: ((isCurrentUser && profileModel.ens.preferredUsername) || isEnsVerified) ? qsTr("ENS username") : qsTr("Username") -// subTitle: isCurrentUser ? profileModel.ens.preferredUsername || userName : userName + title: ((isCurrentUser && userProfile.ensName) || isEnsVerified) ? + qsTr("ENS username") : + qsTr("Username") + subTitle: isCurrentUser ? userProfile.name : userName tooltip.text: qsTr("Copy to clipboard") icon.name: "copy" iconButton.onClicked: { - // Not Refactored Yet -// chatsModel.copyToClipboard(userName) -// tooltip.visible = !tooltip.visible + globalUtils.copyToClipboard(userName) + tooltip.visible = !tooltip.visible } width: parent.width } @@ -120,9 +118,8 @@ StatusModal { tooltip.text: qsTr("Copy to clipboard") icon.name: "copy" iconButton.onClicked: { - // Not Refactored Yet -// chatsModel.copyToClipboard(fromAuthor) -// tooltip.visible = !tooltip.visible + globalUtils.copyToClipboard(userName) + tooltip.visible = !tooltip.visible } width: parent.width } @@ -137,8 +134,7 @@ StatusModal { subTitle: { let user = "" if (isCurrentUser) { - // Not Refactored Yet -// user = profileModel.ens.preferredUsername + user = userProfile.name } else { if (isEnsVerified) { user = userName.startsWith("@") ? userName.substring(1) : userName @@ -154,8 +150,7 @@ StatusModal { iconButton.onClicked: { let user = "" if (isCurrentUser) { - // Not Refactored Yet -// user = profileModel.ens.preferredUsername + user = userProfile.name } else { if (isEnsVerified) { user = userName.startsWith("@") ? userName.substring(1) : userName @@ -165,8 +160,7 @@ StatusModal { user = fromAuthor } - // Not Refactored Yet -// chatsModel.copyToClipboard(Constants.userLinkPrefix + user) + globalUtils.copyToClipboard(Constants.userLinkPrefix + user) tooltip.visible = !tooltip.visible } width: parent.width @@ -205,8 +199,7 @@ StatusModal { Image { asynchronous: true fillMode: Image.PreserveAspectFit - // Not Refactored Yet -// source: profileModel.qrCode(fromAuthor) + source: globalUtils.qrCode(fromAuthor) anchors.horizontalCenter: parent.horizontalCenter height: 212 width: 212 @@ -302,8 +295,7 @@ StatusModal { text: qsTr("Add to contacts") visible: !isBlocked && !isAdded onClicked: { - // TODO make a store for this - contactsModule.addContact(fromAuthor) + popup.store.addContact(fromAuthor); popup.contactAdded(fromAuthor); popup.close(); } diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index d3bf46c44e..7f569b1d6d 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -214,11 +214,19 @@ QtObject { return contactsModuleModel.isAdded(address); } - function contactRequestReceived(activeChatId) { - return contactsModuleModel.contactRequestReceived(activeChatId) + function contactRequestReceived(pubkey) { + return contactsModuleModel.contactRequestReceived(pubkey) } - function isContactBlocked(activeChatId) { - return contactsModuleModel.isContactBlocked(activeChatId) + function isContactBlocked(pubkey) { + return contactsModuleModel.isContactBlocked(pubkey) + } + + function isEnsVerified(pubkey) { + return contactsModuleModel.isEnsVerified(pubkey) + } + + function alias(pubkey) { + return contactsModuleModel.alias(pubkey) } }