fix `ImportCommunityPopup` issues, remove private key importing (#12554)

* feat(Storybook): added "Always on top" setting
* fix(ImportCommunityPopup): show result, remove private key support
This commit is contained in:
Igor Sirotin 2023-10-27 11:25:27 +01:00 committed by GitHub
parent dd2b58a7f5
commit a3239d9e2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 429 additions and 116 deletions

View File

@ -85,7 +85,7 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_COMMUNITY_LOAD_DATA_FAILED) do(e: Args):
let args = CommunityArgs(e)
self.delegate.onImportCommunityErrorOccured(args.communityId, args.error)
self.delegate.communityInfoRequestFailed(args.communityId, args.error)
self.events.on(SIGNAL_COMMUNITY_INFO_ALREADY_REQUESTED) do(e: Args):
self.delegate.communityInfoAlreadyRequested()

View File

@ -108,6 +108,9 @@ method communityImported*(self: AccessInterface, community: CommunityDto) {.base
method communityDataImported*(self: AccessInterface, community: CommunityDto) {.base.} =
raise newException(ValueError, "No implementation available")
method communityInfoRequestFailed*(self: AccessInterface, communityId: string, errorMsg: string) {.base.} =
raise newException(ValueError, "No implementation available")
method onImportCommunityErrorOccured*(self: AccessInterface, communityId: string, error: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -346,6 +346,10 @@ method communityImported*(self: Module, community: CommunityDto) =
method communityDataImported*(self: Module, community: CommunityDto) =
self.view.addItem(self.getCommunityItem(community))
self.view.emitCommunityInfoRequestCompleted(community.id, "")
method communityInfoRequestFailed*(self: Module, communityId: string, errorMsg: string) =
self.view.emitCommunityInfoRequestCompleted(communityId, errorMsg)
method importCommunity*(self: Module, communityId: string) =
self.view.emitImportingCommunityStateChangedSignal(communityId, ImportCommunityState.ImportingInProgress.int, errorMsg = "")

View File

@ -546,6 +546,10 @@ QtObject:
proc emitImportingCommunityStateChangedSignal*(self: View, communityId: string, state: int, errorMsg: string) =
self.importingCommunityStateChanged(communityId, state, errorMsg)
proc communityInfoRequestCompleted*(self: View, communityId: string, errorMsg: string) {.signal.}
proc emitCommunityInfoRequestCompleted*(self: View, communityId: string, errorMsg: string) =
self.communityInfoRequestCompleted(communityId, errorMsg)
proc isMemberOfCommunity*(self: View, communityId: string, pubKey: string): bool {.slot.} =
let sectionItem = self.model.getItemById(communityId)
if (section_item.id == ""):

View File

@ -140,6 +140,20 @@ ApplicationWindow {
onClicked: settingsPopup.open()
}
CheckBox {
id: windowAlwaysOnTopCheckBox
Layout.fillWidth: true
text: "Always on top"
onCheckedChanged: {
if (checked)
root.flags |= Qt.WindowStaysOnTopHint
else
root.flags &= ~Qt.WindowStaysOnTopHint
}
}
CheckBox {
id: darkModeCheckBox
@ -346,6 +360,7 @@ Tips:
property alias darkMode: darkModeCheckBox.checked
property alias hotReloading: hotReloaderControls.enabled
property alias figmaToken: settingsLayout.figmaToken
property alias windowAlwaysOnTop: windowAlwaysOnTopCheckBox.checked
}
Shortcut {

View File

@ -0,0 +1,328 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml 2.15
import Storybook 1.0
import Models 1.0
import AppLayouts.Communities.popups 1.0
import utils 1.0
import shared.popups 1.0
SplitView {
id: root
Logs { id: logs }
orientation: Qt.Vertical
QtObject {
id: d
readonly property alias dialog: loader.item
property bool utilsReady: false
readonly property string newCommunityLink: "https://status.app/c/Cw6AChsKBnlveW95bxIGeW95b3lvGAEiByM4OEIwRkYD#zQ3shwHXstcword5gUtJCWVi55ZxPsdtfTQitnWNEAR1p3Gzd"
readonly property string newCommunityPublicKey: "0x03f751777ab35759f98ad241150f4329b9cc13aa42052ef64d16e9d474b9677bea"
readonly property string newCommunityCompressedPublicKey: "zQ3shwHXstcword5gUtJCWVi55ZxPsdtfTQitnWNEAR1p3Gzd"
readonly property var newCommunityDetails: QtObject {
readonly property string id: "0x039c47e9837a1a7dcd00a6516399d0eb521ab0a92d512ca20a44ac6278bfdbb5c5"
readonly property string name: "test-1"
readonly property int memberRole: 0
readonly property bool isControlNode: false
readonly property string description: "test"
readonly property string introMessage: "123"
readonly property string outroMessage: "342"
readonly property string image: ModelsData.icons.superRare
readonly property string bannerImageData: ModelsData.banners.superRare
readonly property string icon: ""
readonly property string color: "#4360DF"
readonly property string tags: "null"
readonly property bool hasNotification: false
readonly property int notificationsCount: 0
readonly property bool active: false
readonly property bool enabled: true
readonly property bool joined: false
readonly property bool spectated: false
readonly property bool canJoin: true
readonly property bool canManageUsers: false
readonly property bool canRequestAccess: false
readonly property bool isMember: false
readonly property bool amIBanned: false
readonly property int access: 1
readonly property bool ensOnly: false
readonly property int nbMembers: 42
readonly property bool encrypted: false
}
readonly property string knownCommunityLink: "https://status.app/c/CwyAChcKBHRlc3QSBHRlc3QYAiIHIzQzNjBERgM=#zQ3shqAAKxRroS2BE4FgLjjombivfU7XgeNqVFj1eRZ4GHuAU"
readonly property string knownCommunityPublicKey: "0x03c892238f64e9b74cefbaaaf5d557fee401a20d6fb52da126de45755b2a2b8166"
readonly property string knownCommunityCompressedPublicKey: "zQ3shqAAKxRroS2BE4FgLjjombivfU7XgeNqVFj1eRZ4GHuAU"
readonly property var knownCommunityDetails: QtObject {
readonly property string id: "0x03c892238f64e9b74cefbaaaf5d557fee401a20d6fb52da126de45755b2a2b8166"
readonly property string name: "test-2"
readonly property int memberRole: 0
readonly property bool isControlNode: false
readonly property string description: "test"
readonly property string introMessage: "123"
readonly property string outroMessage: "342"
readonly property string image: ModelsData.icons.status
readonly property string bannerImageData: ModelsData.banners.status
readonly property string icon: ""
readonly property string color: "#4360DF"
readonly property string tags: "null"
readonly property bool hasNotification: false
readonly property int notificationsCount: 0
readonly property bool active: false
readonly property bool enabled: true
readonly property bool joined: false
readonly property bool spectated: false
readonly property bool canJoin: true
readonly property bool canManageUsers: false
readonly property bool canRequestAccess: false
readonly property bool isMember: false
readonly property bool amIBanned: false
readonly property int access: 1
readonly property bool ensOnly: false
readonly property int nbMembers: 15
readonly property bool encrypted: false
}
property bool currenKeyIsPublic: false
property string currentKey: ""
}
QtObject {
id: communityStoreMock
property bool newCommunityFetched: false
signal communityInfoRequestCompleted(string communityId, string errorMsg)
function getCommunityDetails(publicKey, importing, requestWhenNotFound) {
if (publicKey === d.knownCommunityPublicKey) {
return d.knownCommunityDetails
}
if (publicKey === d.newCommunityPublicKey && newCommunityFetched)
return d.newCommunityDetails
return null
}
function requestCommunityInfo(communityId) {
// Dynamically create a timer to be able to simulate overlapping requests
let timer = Qt.createQmlObject("import QtQuick 2.0; Timer {}", root)
timer.interval = 1000
timer.repeat = false
timer.triggered.connect(() => {
const communityFound = (communityId === d.knownCommunityPublicKey || communityId === d.newCommunityPublicKey)
const error = communityFound ? "" : "communtiy not found"
if (communityId === d.newCommunityPublicKey) {
newCommunityFetched = true
}
communityStoreMock.communityInfoRequestCompleted(communityId, error)
})
timer.start()
}
}
QtObject {
id: utilsMock
function getContactDetailsAsJson(arg1, arg2) {
return JSON.stringify({
displayName: "Mock user",
displayIcon: Style.png("tokens/AST"),
publicKey: 123456789,
name: "",
ensVerified: false,
alias: "",
lastUpdated: 0,
lastUpdatedLocally: 0,
localNickname: "",
thumbnailImage: "",
largeImage: "",
isContact: false,
isAdded: false,
isBlocked: false,
requestReceived: false,
isSyncing: false,
removed: false,
trustStatus: Constants.trustStatus.unknown,
verificationStatus: Constants.verificationStatus.unverified,
incomingVerificationStatus: Constants.verificationStatus.unverified
})
}
function isCompressedPubKey(key) {
return d.dialog.text === d.knownCommunityCompressedPublicKey ||
d.dialog.text === d.newCommunityCompressedPublicKey
}
function changeCommunityKeyCompression(key) {
if (key === d.knownCommunityCompressedPublicKey)
return d.knownCommunityPublicKey
if (key === d.newCommunityCompressedPublicKey)
return d.newCommunityPublicKey
if (key === d.knownCommunityPublicKey)
return d.knownCommunityCompressedPublicKey
if (key === d.newCommunityPublicKey)
return d.newCommunityCompressedPublicKey
return ""
}
function getCommunityDataFromSharedLink(link) {
return d.knownCommunityDetails
}
function getCompressedPk(publicKey) {
return d.knownCommunityCompressedPublicKey
}
signal importingCommunityStateChanged(string communityId, int state, string errorMsg)
// sharedUrlsModuleInst
function parseCommunitySharedUrl(link) {
if (link === d.knownCommunityLink)
return JSON.stringify({ communityId: d.knownCommunityPublicKey })
if (link === d.newCommunityLink)
return JSON.stringify({ communityId: d.newCommunityPublicKey })
return null
}
Component.onCompleted: {
Utils.sharedUrlsModuleInst = this
Utils.globalUtilsInst = this
d.utilsReady = true
}
Component.onDestruction: {
d.utilsReady = false
Utils.sharedUrlsModuleInst = {}
Utils.globalUtilsInst = {}
}
}
Item {
SplitView.fillWidth: true
SplitView.fillHeight: true
PopupBackground {
id: popupBg
anchors.fill: parent
Button {
anchors.centerIn: parent
text: "Reopen"
onClicked: loader.item.open()
}
}
Loader {
id: loader
active: d.utilsReady
anchors.fill: parent
sourceComponent: ImportCommunityPopup {
anchors.centerIn: parent
modal: false
closePolicy: Popup.NoAutoClose
destroyOnClose: false
store: communityStoreMock
Component.onCompleted: open()
onJoinCommunityRequested: (communityId, communityDetails) => {
logs.logEvent("onJoinCommunity", ["communityId", "communityDetails"], communityId, communityDetails)
}
}
}
}
LogsAndControlsPanel {
SplitView.minimumHeight: 100
SplitView.preferredHeight: 200
logsView.logText: logs.logText
GridLayout {
columns: 4
Button {
text: "Reset communities storage"
Layout.fillWidth: false
Layout.columnSpan: 4
Layout.alignment: Qt.AlignRight
onClicked: {
communityStoreMock.newCommunityFetched = false
const prevText = d.dialog.text
d.dialog.text = ""
d.dialog.text = prevText
}
}
Label {
text: "Known community"
}
Button {
checked: d.dialog && d.dialog.text === d.knownCommunityLink
text: "Link"
onClicked: {
d.dialog.text = d.knownCommunityLink
}
}
Button {
checked: d.dialog && d.dialog.text === d.knownCommunityPublicKey
text: "Public key"
onClicked: {
d.dialog.text = d.knownCommunityPublicKey
}
}
Button {
checked: d.dialog && d.dialog.text === d.knownCommunityCompressedPublicKey
text: "Compressed public key"
onClicked: {
d.dialog.text = d.knownCommunityCompressedPublicKey
}
}
Label {
text: "Never fetched community"
}
Button {
checked: d.dialog && d.dialog.text === d.newCommunityLink
text: "Link"
onClicked: {
d.dialog.text = d.newCommunityLink
}
}
Button {
checked: d.dialog && d.dialog.text === d.newCommunityPublicKey
text: "Public key"
onClicked: {
d.dialog.text = d.newCommunityPublicKey
}
}
Button {
checked: d.dialog && d.dialog.text === d.newCommunityCompressedPublicKey
text: "Compressed public key"
onClicked: {
d.dialog.text = d.newCommunityCompressedPublicKey
}
}
}
}
}
// category: Popups

View File

@ -62,6 +62,8 @@ QtObject {
signal communityInfoAlreadyRequested()
signal communityInfoRequestCompleted(string communityId, string errorMsg)
function createCommunity(args = {
name: "",
description: "",
@ -120,19 +122,14 @@ QtObject {
root.communitiesModuleInst.prepareTokenModelForCommunity(publicKey);
}
function getCommunityDetails(communityId, importing = false) {
function getCommunityDetails(communityId) {
const publicKey = Utils.isCompressedPubKey(communityId)
? Utils.changeCommunityKeyCompression(communityId)
: communityId
try {
const communityJson = root.communitiesList.getSectionByIdJson(publicKey)
if (!communityJson) {
root.requestCommunityInfo(publicKey, importing)
return null
}
return JSON.parse(communityJson);
if (!!communityJson)
return JSON.parse(communityJson)
} catch (e) {
console.error("Error parsing community", e)
}
@ -251,5 +248,9 @@ QtObject {
function onCommunityInfoAlreadyRequested() {
root.communityInfoAlreadyRequested()
}
function onCommunityInfoRequestCompleted(communityId, erorrMsg) {
root.communityInfoRequestCompleted(communityId, erorrMsg)
}
}
}

View File

@ -504,7 +504,7 @@ QtObject {
id: importCommunitiesPopupComponent
ImportCommunityPopup {
store: root.communitiesStore
onJoinCommunity: {
onJoinCommunityRequested: {
close()
openCommunityIntroPopup(communityId,
communityDetails.name,

View File

@ -15,111 +15,97 @@ import StatusQ.Controls 0.1
StatusDialog {
id: root
property var store
property alias text: keyInput.text
signal joinCommunityRequested(string communityId, var communityDetails)
width: 640
title: qsTr("Import Community")
signal joinCommunity(string communityId, var communityDetails)
QtObject {
id: d
property string importErrorMessage
property bool loading
property bool communityFound: (d.communityDetails !== null && !!d.communityDetails.name)
property var communityDetails: {
if (!isInputValid) {
loading = false
return null
}
loading = true
const key = isPublicKey ? Utils.getCompressedPk(publicKey) :
root.store.getCommunityPublicKeyFromPrivateKey(inputKey, true /*importing*/);
readonly property bool communityFound: !!d.communityDetails && !!d.communityDetails.name
property var communityDetails: null
const details = root.store.getCommunityDetails(key)
if (!!details) // the above can return `null` in which case we continue loading
loading = false
return details
}
property var requestedCommunityDetails: null
readonly property string inputErrorMessage: isInputValid ? "" : qsTr("Invalid key")
readonly property string errorMessage: importErrorMessage || inputErrorMessage
readonly property string inputKey: keyInput.text.trim()
readonly property bool isPrivateKey: (Utils.isPrivateKey(inputKey))
readonly property bool isPublicKey: (publicKey !== "")
readonly property string privateKey: inputKey
readonly property string publicKey: {
if (!Utils.isStatusDeepLink(inputKey)) {
const key = Utils.dropCommunityLinkPrefix(inputKey)
if (!Utils.isCommunityPublicKey(key))
return ""
if (!Utils.isCompressedPubKey(key))
return key
return Utils.changeCommunityKeyCompression(key)
} else {
return Utils.getCommunityDataFromSharedLink(inputKey).communityId;
if (Utils.isStatusDeepLink(inputKey)) {
const linkData = Utils.getCommunityDataFromSharedLink(inputKey)
return !linkData ? "" : linkData.communityId
}
if (!Utils.isCommunityPublicKey(inputKey))
return ""
if (!Utils.isCompressedPubKey(inputKey))
return inputKey
return Utils.changeCommunityKeyCompression(inputKey)
}
readonly property bool isInputValid: publicKey !== ""
property bool communityInfoRequested: false
function updateCommunityDetails(requestIfNotFound) {
if (!isInputValid) {
d.communityInfoRequested = false
d.communityDetails = null
return
}
const details = root.store.getCommunityDetails(publicKey)
if (!!details) {
d.communityInfoRequested = false
d.communityDetails = details
return
}
if (requestIfNotFound) {
root.store.requestCommunityInfo(publicKey, false)
d.communityInfoRequested = true
d.communityDetails = null
}
}
readonly property bool isInputValid: isPrivateKey || isPublicKey
}
Timer {
interval: 20000 // 20s
running: d.loading
onTriggered: {
d.loading = false
d.importErrorMessage = qsTr("Timeout reached while getting community info")
onPublicKeyChanged: {
// call later to make sure all proeprties used by `updateCommunityDetails` are udpated
Qt.callLater(() => { d.updateCommunityDetails(true) })
}
}
Connections {
target: root.store
function onImportingCommunityStateChanged(communityId, state, errorMsg) {
switch (state)
{
case Constants.communityImported:
const community = root.store.getCommunityDetailsAsJson(communityId)
d.loading = false
d.communityFound = true
d.communityDetails = community
d.importErrorMessage = ""
break
case Constants.communityImportingInProgress:
d.loading = true
break
case Constants.communityImportingError:
d.loading = false
d.communityFound = false
d.communityDetails = null
d.importErrorMessage = errorMsg
break
default:
const msg = qsTr("Error state '%1' while importing community: %2").arg(state).arg(communityId)
console.error(msg)
d.loading = false
d.communityFound = false
d.communityDetails = null
d.importErrorMessage = msg
function onCommunityInfoRequestCompleted(communityId, errorMsg) {
if (!d.communityInfoRequested)
return
d.communityInfoRequested = false
if (errorMsg !== "") {
d.importErrorMessage = qsTr("Couldn't find community")
return
}
d.updateCommunityDetails(false)
d.importErrorMessage = ""
}
}
footer: StatusDialogFooter {
rightButtons: ObjectModel {
StatusButton {
enabled: d.communityFound && ((d.isPublicKey) || (d.isPrivateKey && agreeToKeepOnline.checked))
loading: d.loading
text: d.isPrivateKey && d.communityFound ? qsTr("Make this device the control node for %1").arg(d.communityDetails.name)
: qsTr("Import")
enabled: d.isInputValid && d.communityFound
loading: d.isInputValid && !d.communityFound && d.communityInfoRequested
text: qsTr("Import")
onClicked: {
if (d.isPrivateKey) {
root.store.importCommunity(d.privateKey);
root.close();
} else if (d.isPublicKey) {
root.joinCommunity(d.publicKey, d.communityDetails);
}
root.joinCommunityRequested(d.publicKey, d.communityDetails)
}
}
}
@ -139,7 +125,7 @@ StatusDialog {
StatusBaseText {
id: infoText1
Layout.fillWidth: true
text: qsTr("Enter the public key of the community you wish to access, or enter the private key of a community you own. Remember to always keep any private key safe and never share a private key with anyone else.")
text: qsTr("Enter the public key of the community you wish to access")
wrapMode: Text.WordWrap
font.pixelSize: Style.current.additionalTextSize
color: Theme.palette.baseColor1
@ -178,43 +164,15 @@ StatusDialog {
font.pixelSize: Style.current.additionalTextSize
visible: !!d.inputKey
text: {
if (d.errorMessage !== "") {
if (d.errorMessage !== "")
return d.errorMessage
}
if (d.isPrivateKey) {
return qsTr("Private key detected")
}
if (d.isPublicKey) {
if (d.isInputValid)
return qsTr("Public key detected")
}
return ""
}
color: d.errorMessage === "" ? Theme.palette.successColor1 : Theme.palette.dangerColor1
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
visible: (d.communityFound && d.isPrivateKey)
Layout.topMargin: 12
spacing: Style.current.padding
StatusWarningBox {
Layout.fillWidth: true
icon: "caution"
text: qsTr("Another device might currently have the control node for this Community. Running multiple control nodes will cause unforeseen issues. Make sure you delete the private key in that other device in the community management tab.")
bgColor: borderColor
}
StatusDialogDivider { Layout.fillWidth: true; Layout.topMargin: Style.current.padding }
StatusBaseText {
Layout.topMargin: Style.current.halfPadding
visible: (d.communityFound && d.isPrivateKey)
text: qsTr("I acknowledge that...")
}
StatusCheckBox {
id: agreeToKeepOnline
Layout.fillWidth: true
text: qsTr("I must keep this device online and running Status for the Community to function")
}
}
}
}
}

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 27b770c41bd2896ebc30b01985eeea1fbe642fba
Subproject commit 74396b461df0c18593262a67c9fd79d706287f7a