diff --git a/src/app/core/fleets/fleet_configuration.nim b/src/app/core/fleets/fleet_configuration.nim index 98db33e974..a9a9700e44 100644 --- a/src/app/core/fleets/fleet_configuration.nim +++ b/src/app/core/fleets/fleet_configuration.nim @@ -64,7 +64,7 @@ proc getMailservers*(self: FleetConfiguration, fleet: Fleet, isWakuV2: bool): Ta # TODO: If using wakuV2, this assumes that Waku nodes in fleet.status.json are also store nodes. # Maybe it make senses to add a "waku-store" section in case we want to have separate node types? # Discuss with @iurimatias, @cammellos and Vac team - let fleetKey = if isWakuV2: $FleetNodes.Waku else: $FleetNodes.Mailservers + let fleetKey = if isWakuV2: $FleetNodes.TCP_P2P_Waku else: $FleetNodes.Mailservers if not self.fleet[$fleet].hasKey(fleetKey) : result = initTable[string,string]() return diff --git a/src/app/modules/main/profile_section/advanced/controller.nim b/src/app/modules/main/profile_section/advanced/controller.nim index 4004a0afcc..fc2510d7db 100644 --- a/src/app/modules/main/profile_section/advanced/controller.nim +++ b/src/app/modules/main/profile_section/advanced/controller.nim @@ -58,6 +58,18 @@ proc setBloomLevel*(self: Controller, bloomLevel: string) = self.delegate.onBloomLevelSet() +method toggleWakuV2Store*(self: Controller) = + let enabled = self.nodeConfigurationService.isWakuV2StoreEnabled() + if (not self.nodeConfigurationService.setWakuV2StoreEnabled(not enabled)): + # in the future we may do a call from here to show a popup about this error + error "an error occurred, we couldn't enable community history archive support" + return + self.delegate.onWakuV2StoreToggled() + +method isWakuV2StoreEnabled*(self: Controller): bool = + return self.nodeConfigurationService.isWakuV2StoreEnabled() + + proc getWakuV2LightClientEnabled*(self: Controller): bool = return self.nodeConfigurationService.getV2LightMode() diff --git a/src/app/modules/main/profile_section/advanced/io_interface.nim b/src/app/modules/main/profile_section/advanced/io_interface.nim index 46038e560b..f2b4ed9138 100644 --- a/src/app/modules/main/profile_section/advanced/io_interface.nim +++ b/src/app/modules/main/profile_section/advanced/io_interface.nim @@ -21,6 +21,9 @@ method onFleetSet*(self: AccessInterface) {.base.} = method onBloomLevelSet*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") +method onWakuV2StoreToggled*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + method onWakuV2LightClientSet*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") @@ -92,3 +95,9 @@ method toggleNodeManagementSection*(self: AccessInterface) {.base.} = method enableDeveloperFeatures*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") + +method isWakuV2StoreEnabled*(self: AccessInterface): bool {.base.} = + raise newException(ValueError, "No implementation available") + +method toggleWakuV2Store*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/profile_section/advanced/module.nim b/src/app/modules/main/profile_section/advanced/module.nim index dbcf8f0dea..9059e7f4bc 100644 --- a/src/app/modules/main/profile_section/advanced/module.nim +++ b/src/app/modules/main/profile_section/advanced/module.nim @@ -130,3 +130,12 @@ method toggleCommunitiesPortalSection*(self: Module) = method toggleNodeManagementSection*(self: Module) = self.controller.toggleNodeManagementSection() + +method onWakuV2StoreToggled*(self: Module) = + self.view.emitWakuV2StoreEnabledSignal() + +method toggleWakuV2Store*(self: Module) = + self.controller.toggleWakuV2Store() + +method isWakuV2StoreEnabled*(self: Module): bool = + self.controller.isWakuV2StoreEnabled() diff --git a/src/app/modules/main/profile_section/advanced/view.nim b/src/app/modules/main/profile_section/advanced/view.nim index 4a37fd1b6e..f93c3645eb 100644 --- a/src/app/modules/main/profile_section/advanced/view.nim +++ b/src/app/modules/main/profile_section/advanced/view.nim @@ -119,3 +119,16 @@ QtObject: proc toggleCommunitiesPortalSection*(self: View) {.slot.} = self.delegate.toggleCommunitiesPortalSection() + + proc isWakuV2StoreEnabledChanged*(self: View) {.signal.} + proc getIsWakuV2StoreEnabled*(self: View): bool {.slot.} = + return self.delegate.isWakuV2StoreEnabled() + QtProperty[bool] isWakuV2StoreEnabled: + read = getIsWakuV2StoreEnabled + notify = isWakuV2StoreEnabledChanged + + proc emitWakuV2StoreEnabledSignal*(self: View) = + self.isWakuV2StoreEnabledChanged() + + proc toggleWakuV2Store*(self: View) {.slot.} = + self.delegate.toggleWakuV2Store() diff --git a/src/app/modules/main/profile_section/io_interface.nim b/src/app/modules/main/profile_section/io_interface.nim index 657669601c..443bb8e975 100644 --- a/src/app/modules/main/profile_section/io_interface.nim +++ b/src/app/modules/main/profile_section/io_interface.nim @@ -65,9 +65,15 @@ method getDevicesModule*(self: AccessInterface): QVariant {.base.} = method syncModuleDidLoad*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") +method wakuModuleDidLoad*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + method getSyncModule*(self: AccessInterface): QVariant {.base.} = raise newException(ValueError, "No implementation available") +method getWakuModule*(self: AccessInterface): QVariant {.base.} = + raise newException(ValueError, "No implementation available") + method notificationsModuleDidLoad*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/profile_section/module.nim b/src/app/modules/main/profile_section/module.nim index 2cce5ddf36..d7ae6b5c82 100644 --- a/src/app/modules/main/profile_section/module.nim +++ b/src/app/modules/main/profile_section/module.nim @@ -32,6 +32,7 @@ import ./about/module as about_module import ./advanced/module as advanced_module import ./devices/module as devices_module import ./sync/module as sync_module +import ./waku/module as waku_module import ./notifications/module as notifications_module import ./ens_usernames/module as ens_usernames_module import ./communities/module as communities_module @@ -55,6 +56,7 @@ type advancedModule: advanced_module.AccessInterface devicesModule: devices_module.AccessInterface syncModule: sync_module.AccessInterface + wakuModule: waku_module.AccessInterface notificationsModule: notifications_module.AccessInterface ensUsernamesModule: ens_usernames_module.AccessInterface communitiesModule: communities_module.AccessInterface @@ -98,6 +100,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface, result.advancedModule = advanced_module.newModule(result, events, settingsService, stickersService, nodeConfigurationService) result.devicesModule = devices_module.newModule(result, events, settingsService, devicesService) result.syncModule = sync_module.newModule(result, events, settingsService, nodeConfigurationService, mailserversService) + result.wakuModule = waku_module.newModule(result, events, settingsService, nodeConfigurationService) result.notificationsModule = notifications_module.newModule(result, events, settingsService, chatService, contactsService) result.ensUsernamesModule = ens_usernames_module.newModule( result, events, settingsService, ensService, walletAccountService, networkService, tokenService @@ -117,6 +120,7 @@ method delete*(self: Module) = self.advancedModule.delete self.devicesModule.delete self.syncModule.delete + self.wakuModule.delete self.communitiesModule.delete self.keycardModule.delete @@ -134,6 +138,7 @@ method load*(self: Module) = self.advancedModule.load() self.devicesModule.load() self.syncModule.load() + self.wakuModule.load() self.notificationsModule.load() self.ensUsernamesModule.load() self.communitiesModule.load() @@ -167,6 +172,9 @@ proc checkIfModuleDidLoad(self: Module) = if(not self.syncModule.isLoaded()): return + if(not self.wakuModule.isLoaded()): + return + if(not self.notificationsModule.isLoaded()): return @@ -227,9 +235,15 @@ method getDevicesModule*(self: Module): QVariant = method syncModuleDidLoad*(self: Module) = self.checkIfModuleDidLoad() +method wakuModuleDidLoad*(self: Module) = + self.checkIfModuleDidLoad() + method getSyncModule*(self: Module): QVariant = self.syncModule.getModuleAsVariant() +method getWakuModule*(self: Module): QVariant = + self.wakuModule.getModuleAsVariant() + method notificationsModuleDidLoad*(self: Module) = self.checkIfModuleDidLoad() diff --git a/src/app/modules/main/profile_section/view.nim b/src/app/modules/main/profile_section/view.nim index bfcdfb6519..ef68394de8 100644 --- a/src/app/modules/main/profile_section/view.nim +++ b/src/app/modules/main/profile_section/view.nim @@ -51,6 +51,11 @@ QtObject: QtProperty[QVariant] syncModule: read = getSyncModule + proc getWakuModule(self: View): QVariant {.slot.} = + return self.delegate.getWakuModule() + QtProperty[QVariant] wakuModule: + read = getWakuModule + proc getNotificationsModule(self: View): QVariant {.slot.} = return self.delegate.getNotificationsModule() QtProperty[QVariant] notificationsModule: diff --git a/src/app/modules/main/profile_section/waku/controller.nim b/src/app/modules/main/profile_section/waku/controller.nim new file mode 100644 index 0000000000..63c40fdecb --- /dev/null +++ b/src/app/modules/main/profile_section/waku/controller.nim @@ -0,0 +1,39 @@ +import Tables, chronicles +import io_interface + +import ../../../../core/eventemitter +import ../../../../core/fleets/fleet_configuration +import ../../../../../app_service/service/settings/service as settings_service +import ../../../../../app_service/service/node_configuration/service as node_configuration_service + +logScope: + topics = "profile-section-waku-module-controller" + +type + Controller* = ref object of RootObj + delegate: io_interface.AccessInterface + events: EventEmitter + settingsService: settings_service.Service + nodeConfigurationService: node_configuration_service.Service + +proc newController*(delegate: io_interface.AccessInterface, + events: EventEmitter, + settingsService: settings_service.Service, + nodeConfigurationService: node_configuration_service.Service): Controller = + result = Controller() + result.delegate = delegate + result.events = events + result.settingsService = settingsService + result.nodeConfigurationService = nodeConfigurationService + +proc delete*(self: Controller) = + discard + +proc init*(self: Controller) = + discard + +proc getAllWakuNodes*(self: Controller): seq[string] = + return self.nodeConfigurationService.getAllWakuNodes() + +proc saveNewWakuNode*(self: Controller, nodeAddress: string) = + self.nodeConfigurationService.saveNewWakuNode(nodeAddress) \ No newline at end of file diff --git a/src/app/modules/main/profile_section/waku/io_interface.nim b/src/app/modules/main/profile_section/waku/io_interface.nim new file mode 100644 index 0000000000..6d40d98a50 --- /dev/null +++ b/src/app/modules/main/profile_section/waku/io_interface.nim @@ -0,0 +1,25 @@ +import NimQml + +type + AccessInterface* {.pure inheritable.} = ref object of RootObj + +method delete*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method load*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method isLoaded*(self: AccessInterface): bool {.base.} = + raise newException(ValueError, "No implementation available") + +method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} = + raise newException(ValueError, "No implementation available") + +method viewDidLoad*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method isAutomaticSelection*(self: AccessInterface): bool {.base.} = + raise newException(ValueError, "No implementation available") + +method saveNewWakuNode*(self: AccessInterface, nodeAddress: string) {.base.} = + raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/profile_section/waku/item.nim b/src/app/modules/main/profile_section/waku/item.nim new file mode 100644 index 0000000000..0284e8b4ec --- /dev/null +++ b/src/app/modules/main/profile_section/waku/item.nim @@ -0,0 +1,11 @@ + +type + Item* = ref object + nodeAddress: string + +proc initItem*(nodeAddress: string): Item = + result = Item() + result.nodeAddress = nodeAddress + +proc nodeAddress*(self: Item): string = + self.nodeAddress diff --git a/src/app/modules/main/profile_section/waku/model.nim b/src/app/modules/main/profile_section/waku/model.nim new file mode 100644 index 0000000000..6613cf9129 --- /dev/null +++ b/src/app/modules/main/profile_section/waku/model.nim @@ -0,0 +1,62 @@ +import NimQml, Tables +import item + +type + ModelRole {.pure.} = enum + NodeAddress = UserRole + 1 + +QtObject: + type Model* = ref object of QAbstractListModel + items*: seq[Item] + + proc setup(self: Model) = + self.QAbstractListModel.setup + + proc delete(self: Model) = + self.items = @[] + self.QAbstractListModel.delete + + proc newModel*(): Model = + new(result, delete) + result.setup + + method rowCount(self: Model, index: QModelIndex = nil): int = + return self.items.len + + method roleNames(self: Model): Table[int, string] = + { + ModelRole.NodeAddress.int:"nodeAddress" + }.toTable + + method data(self: Model, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.items.len: + return + let item = self.items[index.row] + let enumRole = role.ModelRole + + case enumRole: + of ModelRole.NodeAddress: + result = newQVariant(item.nodeAddress) + + proc addItem*(self: Model, item: Item) = + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + + self.beginInsertRows(parentModelIndex, self.items.len, self.items.len) + self.items.add(item) + self.endInsertRows() + + proc addItems*(self: Model, items: seq[Item]) = + if(items.len == 0): + return + + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + + let first = self.items.len + let last = first + items.len - 1 + self.beginInsertRows(parentModelIndex, first, last) + self.items.add(items) + self.endInsertRows() diff --git a/src/app/modules/main/profile_section/waku/module.nim b/src/app/modules/main/profile_section/waku/module.nim new file mode 100644 index 0000000000..f4f3d13ddb --- /dev/null +++ b/src/app/modules/main/profile_section/waku/module.nim @@ -0,0 +1,66 @@ +import NimQml, chronicles +import io_interface +import ../io_interface as delegate_interface +import view, controller, model, item + +import ../../../../core/eventemitter +import ../../../../../app_service/service/settings/service as settings_service +import ../../../../../app_service/service/node_configuration/service as node_configuration_service + +export io_interface + +logScope: + topics = "profile-section-waku-module" + +type + Module* = ref object of io_interface.AccessInterface + delegate: delegate_interface.AccessInterface + controller: Controller + view: View + viewVariant: QVariant + moduleLoaded: bool + +proc newModule*(delegate: delegate_interface.AccessInterface, + events: EventEmitter, + settingsService: settings_service.Service, + nodeConfigurationService: node_configuration_service.Service): Module = + result = Module() + result.delegate = delegate + result.view = view.newView(result) + result.viewVariant = newQVariant(result.view) + result.controller = controller.newController(result, events, settingsService, nodeConfigurationService) + result.moduleLoaded = false + +method delete*(self: Module) = + self.view.delete + self.viewVariant.delete + self.controller.delete + +method load*(self: Module) = + self.controller.init() + self.view.load() + +method isLoaded*(self: Module): bool = + return self.moduleLoaded + +proc initModel(self: Module) = + var items: seq[Item] + let allWakuNodes = self.controller.getAllWakuNodes() + for w in allWakuNodes: + let item = initItem(w) + items.add(item) + + self.view.model().addItems(items) + +method viewDidLoad*(self: Module) = + self.initModel() + self.moduleLoaded = true + self.delegate.wakuModuleDidLoad() + +method getModuleAsVariant*(self: Module): QVariant = + return self.viewVariant + +method saveNewWakuNode*(self: Module, nodeAddress: string) = + self.controller.saveNewWakuNode(nodeAddress) + let item = initItem(nodeAddress) + self.view.model().addItem(item) diff --git a/src/app/modules/main/profile_section/waku/view.nim b/src/app/modules/main/profile_section/waku/view.nim new file mode 100644 index 0000000000..7af0db9c41 --- /dev/null +++ b/src/app/modules/main/profile_section/waku/view.nim @@ -0,0 +1,39 @@ +import NimQml +import io_interface, model + +QtObject: + type + View* = ref object of QObject + delegate: io_interface.AccessInterface + activeMailserver: string + model: Model + modelVariant: QVariant + + proc delete*(self: View) = + self.model.delete + self.modelVariant.delete + self.QObject.delete + + proc newView*(delegate: io_interface.AccessInterface): View = + new(result, delete) + result.QObject.setup + result.delegate = delegate + result.activeMailserver = "" + result.model = newModel() + result.modelVariant = newQVariant(result.model) + + proc load*(self: View) = + self.delegate.viewDidLoad() + + proc model*(self: View): Model = + return self.model + + proc modelChanged*(self: View) {.signal.} + proc getModel(self: View): QVariant {.slot.} = + return self.modelVariant + QtProperty[QVariant] model: + read = getModel + notify = modelChanged + + proc saveNewWakuNode(self: View, address: string) {.slot.} = + self.delegate.saveNewWakuNode(address) diff --git a/src/app_service/service/node_configuration/dto/node_config.nim b/src/app_service/service/node_configuration/dto/node_config.nim index 26d8f7fc0e..9f53b1bd90 100644 --- a/src/app_service/service/node_configuration/dto/node_config.nim +++ b/src/app_service/service/node_configuration/dto/node_config.nim @@ -100,6 +100,9 @@ type EnableDiscV5*: bool UDPPort*: int AutoUpdate*: bool + EnableStore*: bool + StoreCapacity*: int + StoreSeconds*: int ShhextConfig* = object PFSEnabled*: bool @@ -322,6 +325,9 @@ proc toWaku2Config*(jsonObj: JsonNode): Waku2Config = discard jsonObj.getProp("EnableDiscV5", result.EnableDiscV5) discard jsonObj.getProp("UDPPort", result.UDPPort) discard jsonObj.getProp("AutoUpdate", result.AutoUpdate) + discard jsonObj.getProp("EnableStore", result.EnableStore) + discard jsonObj.getProp("StoreCapacity", result.StoreCapacity) + discard jsonObj.getProp("StoreSeconds", result.StoreSeconds) proc toWakuConfig*(jsonObj: JsonNode): WakuConfig = discard jsonObj.getProp("Enabled", result.Enabled) diff --git a/src/app_service/service/node_configuration/service.nim b/src/app_service/service/node_configuration/service.nim index ba1c059328..4409dcfde6 100644 --- a/src/app_service/service/node_configuration/service.nim +++ b/src/app_service/service/node_configuration/service.nim @@ -33,6 +33,7 @@ type configuration: NodeConfigDto fleetConfiguration: FleetConfiguration settingsService: settings_service.Service + wakuNodes: seq[string] events: EventEmitter # Forward declarations @@ -48,6 +49,7 @@ proc newService*(fleetConfiguration: FleetConfiguration, settingsService: settin result.fleetConfiguration = fleetConfiguration result.settingsService = settingsService result.events = events + result.wakuNodes = @[] proc adaptNodeSettingsForTheAppNeed(self: Service) = self.configuration.DataDir = "./ethereum" @@ -68,6 +70,10 @@ proc init*(self: Service) = let response = status_node_config.getNodeConfig() self.configuration = response.result.toNodeConfigDto() + let wakuNodes = self.configuration.ClusterConfig.WakuNodes + for nodeAddress in wakuNodes: + self.wakuNodes.add(nodeAddress) + self.adaptNodeSettingsForTheAppNeed() except Exception as e: let errDesription = e.msg @@ -202,6 +208,15 @@ proc getFleet*(self: Service): Fleet = proc getFleetAsString*(self: Service): string = result = $self.getFleet() +proc getAllWakuNodes*(self: Service): seq[string] = + return self.wakuNodes + +proc saveNewWakuNode*(self: Service, nodeAddress: string) = + var newConfiguration = self.configuration + newConfiguration.ClusterConfig.WakuNodes.add(nodeAddress) + self.configuration = newConfiguration + discard self.saveConfiguration(newConfiguration) + proc setFleet*(self: Service, fleet: string): bool = if(not self.settingsService.saveFleet(fleet)): error "error saving fleet ", procName="setFleet" @@ -270,3 +285,13 @@ proc isV2LightMode*(self: Service): bool = proc isFullNode*(self: Service): bool = return self.configuration.WakuConfig.FullNode + +method isWakuV2StoreEnabled*(self: Service): bool = + return self.configuration.WakuV2Config.EnableStore + +proc setWakuV2StoreEnabled*(self: Service, enabled: bool, storeCapacity: int = 0, storeSeconds: int = 0): bool = + var newConfiguration = self.configuration + newConfiguration.WakuV2Config.EnableStore = enabled + newConfiguration.WakuV2Config.StoreCapacity = storeCapacity + newConfiguration.WakuV2Config.StoreSeconds = storeSeconds + return self.saveConfiguration(newConfiguration) diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index d27b3ec7ad..aa07bfbae4 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -145,6 +145,8 @@ QtObject { property var walletSectionTransactionsInst: walletSectionTransactions + property bool isWakuV2StoreEnabled: advancedModule ? advancedModule.isWakuV2StoreEnabled : false + property string communityTags: communitiesModule.tags property var stickersModuleInst: stickersModule diff --git a/ui/app/AppLayouts/Profile/popups/AddWakuNodeModal.qml b/ui/app/AppLayouts/Profile/popups/AddWakuNodeModal.qml index 120fec16a5..1425260dfb 100644 --- a/ui/app/AppLayouts/Profile/popups/AddWakuNodeModal.qml +++ b/ui/app/AppLayouts/Profile/popups/AddWakuNodeModal.qml @@ -26,8 +26,7 @@ StatusModal { } onOpened: { - nameInput.text = ""; - enodeInput.text = ""; + addrInput.text = ""; } contentItem: StatusScrollView { @@ -37,29 +36,19 @@ StatusModal { Column { id: nodesColumn width: parent.width - StatusInput { - id: nameInput - label: qsTr("Name") - placeholderText: qsTr("Specify a name") - validators: [StatusMinLengthValidator { - minLength: 1 - errorMessage: qsTr("You need to enter a name") - }] - validationMode: StatusInput.ValidationMode.Always - } StatusInput { - id: enodeInput - label: popup.advancedStore.isWakuV2 ? qsTr("Storenode multiaddress") : qsTr("History node address") - placeholderText: popup.advancedStore.isWakuV2 ? "/ip4/0.0.0.0/tcp/123/..." : "enode://{enode-id}:{password}@{ip-address}:{port-number}" + id: addrInput + label: qsTr("Node multiaddress or DNS Discovery address") + placeholderText: "/ipv4/0.0.0.0/tcp/123/..." validators: [ StatusMinLengthValidator { minLength: 1 - errorMessage: popup.advancedStore.isWakuV2 ? qsTr("You need to enter the storenode multiaddress") : qsTr("You need to enter the enode address") + errorMessage: qsTr("You need to enter a value") }, StatusRegularExpressionValidator { - errorMessage: popup.advancedStore.isWakuV2 ? qsTr('Multiaddress must start with a "/"') : qsTr("The format must be: enode://{enode-id}:{password}@{ip-address}:{port}") - regularExpression: popup.advancedStore.isWakuV2 ? /\/.+/ : /enode:\/\/[a-z0-9]+:[a-z0-9]+@(\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}:[0-9]+/ + errorMessage: qsTr("Value should start with '/' or 'enr:'") + regularExpression: /(\/|enr:).+/ }] validationMode: StatusInput.ValidationMode.Always } @@ -69,10 +58,9 @@ StatusModal { rightButtons: [ StatusButton { text: qsTr("Save") - enabled: nameInput.valid && enodeInput.valid - // enabled: nameInput.text !== "" && enodeInput.text !== "" + enabled: addrInput.valid onClicked: { - root.messagingStore.saveNewMailserver(nameInput.text, enodeInput.text) + root.messagingStore.saveNewWakuNode(addrInput.text) popup.close() } } diff --git a/ui/app/AppLayouts/Profile/popups/AddWakuStoreModal.qml b/ui/app/AppLayouts/Profile/popups/AddWakuStoreModal.qml new file mode 100644 index 0000000000..120fec16a5 --- /dev/null +++ b/ui/app/AppLayouts/Profile/popups/AddWakuStoreModal.qml @@ -0,0 +1,80 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.3 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Controls.Validators 0.1 +import StatusQ.Popups 0.1 + + +import utils 1.0 + +StatusModal { + id: popup + + anchors.centerIn: parent + height: 560 + header.title: qsTr("Waku nodes") + + property var messagingStore + property var advancedStore + + onClosed: { + destroy() + } + + onOpened: { + nameInput.text = ""; + enodeInput.text = ""; + } + + contentItem: StatusScrollView { + height: parent.height + width: parent.width + + Column { + id: nodesColumn + width: parent.width + StatusInput { + id: nameInput + label: qsTr("Name") + placeholderText: qsTr("Specify a name") + validators: [StatusMinLengthValidator { + minLength: 1 + errorMessage: qsTr("You need to enter a name") + }] + validationMode: StatusInput.ValidationMode.Always + } + + StatusInput { + id: enodeInput + label: popup.advancedStore.isWakuV2 ? qsTr("Storenode multiaddress") : qsTr("History node address") + placeholderText: popup.advancedStore.isWakuV2 ? "/ip4/0.0.0.0/tcp/123/..." : "enode://{enode-id}:{password}@{ip-address}:{port-number}" + validators: [ + StatusMinLengthValidator { + minLength: 1 + errorMessage: popup.advancedStore.isWakuV2 ? qsTr("You need to enter the storenode multiaddress") : qsTr("You need to enter the enode address") + }, + StatusRegularExpressionValidator { + errorMessage: popup.advancedStore.isWakuV2 ? qsTr('Multiaddress must start with a "/"') : qsTr("The format must be: enode://{enode-id}:{password}@{ip-address}:{port}") + regularExpression: popup.advancedStore.isWakuV2 ? /\/.+/ : /enode:\/\/[a-z0-9]+:[a-z0-9]+@(\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}:[0-9]+/ + }] + validationMode: StatusInput.ValidationMode.Always + } + } + } + + rightButtons: [ + StatusButton { + text: qsTr("Save") + enabled: nameInput.valid && enodeInput.valid + // enabled: nameInput.text !== "" && enodeInput.text !== "" + onClicked: { + root.messagingStore.saveNewMailserver(nameInput.text, enodeInput.text) + popup.close() + } + } + ] +} diff --git a/ui/app/AppLayouts/Profile/popups/WakuNodesModal.qml b/ui/app/AppLayouts/Profile/popups/WakuNodesModal.qml index 5ee7397d0c..95ef9a8e0b 100644 --- a/ui/app/AppLayouts/Profile/popups/WakuNodesModal.qml +++ b/ui/app/AppLayouts/Profile/popups/WakuNodesModal.qml @@ -26,9 +26,7 @@ StatusModal { property var messagingStore property var advancedStore - property string nameValidationError: "" - property string enodeValidationError: "" - + onClosed: { destroy() } @@ -41,74 +39,16 @@ StatusModal { id: nodesColumn width: parent.width - StatusListItem { - width: parent.width - title: qsTr("Use Waku nodes") - components: [ - StatusSwitch { - checked: root.messagingStore.useMailservers - onCheckedChanged: root.messagingStore.toggleUseMailservers(checked) - } - ] - onClicked: { - root.messagingStore.toggleUseMailservers(!root.messagingStore.useMailservers) - } - } - - Separator { - width: parent.width - } - - StatusListItem { - width: parent.width - title: qsTr("Select node automatically") - components: [ - StatusSwitch { - id: automaticSelectionSwitch - checked: root.messagingStore.automaticMailserverSelection - onCheckedChanged: root.messagingStore.enableAutomaticMailserverSelection(checked) - } - ] - onClicked: { - automaticSelectionSwitch.checked = !automaticSelectionSwitch.checked - } - } - - StatusSectionHeadline { - text: qsTr("Waku Nodes") - visible: !automaticSelectionSwitch.checked - width: parent.width - height: visible ? implicitHeight : 0 - } - - ButtonGroup { - id: nodesButtonGroup - } - Repeater { - id: mailServersListView - model: root.messagingStore.mailservers + id: wakunodesListView + model: root.messagingStore.wakunodes delegate: Component { StatusListItem { title: qsTr("Node %1").arg(index + 1) - subTitle: model.name - visible: !automaticSelectionSwitch.checked - height: visible ? implicitHeight : 0 + subTitle: model.nodeAddress components: [ - StatusRadioButton { - id: nodeRadioBtn - ButtonGroup.group: nodesButtonGroup - checked: model.nodeAddress === root.messagingStore.activeMailserver - onCheckedChanged: { - if (checked) { - root.messagingStore.setActiveMailserver(model.name) - } - } - } + // TODO: add a button to delete nodes and restore default fleet nodes if necessary ] - onClicked: { - nodeRadioBtn.checked = true - } } } } diff --git a/ui/app/AppLayouts/Profile/popups/WakuStoreModal.qml b/ui/app/AppLayouts/Profile/popups/WakuStoreModal.qml new file mode 100644 index 0000000000..c228a4c2fc --- /dev/null +++ b/ui/app/AppLayouts/Profile/popups/WakuStoreModal.qml @@ -0,0 +1,136 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.3 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Controls.Validators 0.1 +import StatusQ.Popups 0.1 + +import "." + +import utils 1.0 +import shared 1.0 +import shared.panels 1.0 +import shared.popups 1.0 +import shared.status 1.0 +import shared.controls 1.0 + +StatusModal { + id: root + + anchors.centerIn: parent + height: 560 + header.title: qsTr("History Nodes") + + property var messagingStore + property var advancedStore + property string nameValidationError: "" + property string enodeValidationError: "" + + onClosed: { + destroy() + } + + contentItem: StatusScrollView { + height: parent.height + width: parent.width + + Column { + id: nodesColumn + width: parent.width + + StatusListItem { + width: parent.width + title: qsTr("Use Waku nodes") + components: [ + StatusSwitch { + checked: root.messagingStore.useMailservers + onCheckedChanged: root.messagingStore.toggleUseMailservers(checked) + } + ] + onClicked: { + root.messagingStore.toggleUseMailservers(!root.messagingStore.useMailservers) + } + } + + Separator { + width: parent.width + } + + StatusListItem { + width: parent.width + title: qsTr("Select node automatically") + components: [ + StatusSwitch { + id: automaticSelectionSwitch + checked: root.messagingStore.automaticMailserverSelection + onCheckedChanged: root.messagingStore.enableAutomaticMailserverSelection(checked) + } + ] + onClicked: { + automaticSelectionSwitch.checked = !automaticSelectionSwitch.checked + } + } + + StatusSectionHeadline { + text: qsTr("Waku Nodes") + visible: !automaticSelectionSwitch.checked + width: parent.width + height: visible ? implicitHeight : 0 + } + + ButtonGroup { + id: nodesButtonGroup + } + + Repeater { + id: mailServersListView + model: root.messagingStore.mailservers + delegate: Component { + StatusListItem { + title: qsTr("Node %1").arg(index + 1) + subTitle: model.name + visible: !automaticSelectionSwitch.checked + height: visible ? implicitHeight : 0 + components: [ + StatusRadioButton { + id: nodeRadioBtn + ButtonGroup.group: nodesButtonGroup + checked: model.nodeAddress === root.messagingStore.activeMailserver + onCheckedChanged: { + if (checked) { + root.messagingStore.setActiveMailserver(model.name) + } + } + } + ] + onClicked: { + nodeRadioBtn.checked = true + } + } + } + } + + StatusBaseText { + text: qsTr("Add a new node") + color: Theme.palette.primaryColor1 + width: parent.width + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Global.openPopup(wakuNodeModalComponent) + } + } + } + } + + Component { + id: wakuNodeModalComponent + AddWakuNodeModal { + messagingStore: root.messagingStore + advancedStore: root.advancedStore + } + } +} diff --git a/ui/app/AppLayouts/Profile/stores/AdvancedStore.qml b/ui/app/AppLayouts/Profile/stores/AdvancedStore.qml index ee0547e8ee..7580360c7d 100644 --- a/ui/app/AppLayouts/Profile/stores/AdvancedStore.qml +++ b/ui/app/AppLayouts/Profile/stores/AdvancedStore.qml @@ -13,6 +13,8 @@ QtObject { property bool isTelemetryEnabled: advancedModule? advancedModule.isTelemetryEnabled : false property bool isAutoMessageEnabled: advancedModule? advancedModule.isAutoMessageEnabled : false property bool isDebugEnabled: advancedModule? advancedModule.isDebugEnabled : false + property bool isCommunityHistoryArchiveSupportEnabled: advancedModule? advancedModule.isCommunityHistoryArchiveSupportEnabled : false + property bool isWakuV2StoreEnabled: advancedModule ? advancedModule.isWakuV2StoreEnabled : false property var customNetworksModel: advancedModule? advancedModule.customNetworksModel : [] @@ -32,6 +34,7 @@ QtObject { readonly property string communitiesPortal: "communitiesPortal" readonly property string communityPermissions: "communityPermissions" readonly property string discordImportTool: "discordImportTool" + readonly property string wakuV2StoreEnabled: "wakuV2StoreEnabled" } function logDir() { @@ -113,6 +116,10 @@ QtObject { else if (feature === experimentalFeatures.communitiesPortal) { advancedModule.toggleCommunitiesPortalSection() } + else if (feature === experimentalFeatures.wakuV2StoreEnabled) { + // toggle history archive support + advancedModule.toggleWakuV2Store() + } else if (feature === experimentalFeatures.activityCenter) { localAccountSensitiveSettings.isActivityCenterEnabled = !localAccountSensitiveSettings.isActivityCenterEnabled } diff --git a/ui/app/AppLayouts/Profile/stores/MessagingStore.qml b/ui/app/AppLayouts/Profile/stores/MessagingStore.qml index 61f91b35de..a8b190f9d4 100644 --- a/ui/app/AppLayouts/Profile/stores/MessagingStore.qml +++ b/ui/app/AppLayouts/Profile/stores/MessagingStore.qml @@ -6,8 +6,10 @@ QtObject { property var privacyModule property var syncModule + property var wakuModule property var mailservers: syncModule.model + property var wakunodes: wakuModule.model property bool useMailservers: syncModule.useMailservers @@ -31,6 +33,10 @@ QtObject { root.syncModule.saveNewMailserver(name, nodeAddress) } + function saveNewWakuNode(nodeAddress) { + root.wakuModule.saveNewWakuNode(nodeAddress) + } + function enableAutomaticMailserverSelection(checked) { if (automaticMailserverSelection === checked) { return diff --git a/ui/app/AppLayouts/Profile/stores/ProfileSectionStore.qml b/ui/app/AppLayouts/Profile/stores/ProfileSectionStore.qml index fb2e00e55b..400c6d0680 100644 --- a/ui/app/AppLayouts/Profile/stores/ProfileSectionStore.qml +++ b/ui/app/AppLayouts/Profile/stores/ProfileSectionStore.qml @@ -25,6 +25,7 @@ QtObject { property MessagingStore messagingStore: MessagingStore { privacyModule: profileSectionModuleInst.privacyModule syncModule: profileSectionModuleInst.syncModule + wakuModule: profileSectionModuleInst.wakuModule } property DevicesStore devicesStore: DevicesStore { diff --git a/ui/app/AppLayouts/Profile/views/AdvancedView.qml b/ui/app/AppLayouts/Profile/views/AdvancedView.qml index 91fc53ac43..6b650b42ea 100644 --- a/ui/app/AppLayouts/Profile/views/AdvancedView.qml +++ b/ui/app/AppLayouts/Profile/views/AdvancedView.qml @@ -185,6 +185,19 @@ SettingsContentBase { } } + // TODO: replace with StatusQ component + StatusSettingsLineButton { + anchors.leftMargin: 0 + anchors.rightMargin: 0 + text: qsTr("WakuV2 Store") + isSwitch: true + visible: root.advancedStore.isWakuV2 + switchChecked: root.advancedStore.isWakuV2StoreEnabled + onClicked: { + Global.openPopup(enableWakuV2StoreComponent) + } + } + StatusSectionHeadline { anchors.left: parent.left anchors.right: parent.right @@ -278,7 +291,7 @@ SettingsContentBase { anchors.right: parent.right anchors.leftMargin: Style.current.padding anchors.rightMargin: Style.current.padding - visible: root.advancedStore.isWakuV2 + visible: root.advancedStore.isWakuV2 && root.advancedStore.fleet != Constants.status_prod text: qsTr("WakuV2 mode") topPadding: Style.current.bigPadding bottomPadding: Style.current.padding @@ -487,6 +500,27 @@ SettingsContentBase { } } + Component { + id: enableWakuV2StoreComponent + ConfirmationDialog { + property bool mode: false + + id: confirmDialog + showCancelButton: true + confirmationText: qsTr("Are you sure you want to %1 WakuV2 Store? You need to restart the app for this change to take effect.") + .arg(root.advancedStore.isWakuV2StoreEnabled ? + qsTr("disable") : + qsTr("enable")) + onConfirmButtonClicked: { + root.advancedStore.toggleExperimentalFeature(root.advancedStore.experimentalFeatures.wakuV2StoreEnabled) + close() + } + onCancelButtonClicked: { + close() + } + } + } + Component { id: enableDebugComponent ConfirmationDialog { diff --git a/ui/app/AppLayouts/Profile/views/MessagingView.qml b/ui/app/AppLayouts/Profile/views/MessagingView.qml index 2995e00e3f..62b975a66b 100644 --- a/ui/app/AppLayouts/Profile/views/MessagingView.qml +++ b/ui/app/AppLayouts/Profile/views/MessagingView.qml @@ -337,7 +337,7 @@ SettingsContentBase { StatusListItem { Layout.fillWidth: true - title: qsTr("Waku nodes") + title: qsTr("History nodes") label: root.messagingStore.getMailserverNameForNodeAddress(root.messagingStore.activeMailserver) components: [ StatusIcon { @@ -346,11 +346,33 @@ SettingsContentBase { color: Theme.palette.baseColor1 } ] - onClicked: Global.openPopup(wakuNodeModalComponent) + onClicked: Global.openPopup(wakuStoreModalComponent) } Component { - id: wakuNodeModalComponent + id: wakuStoreModalComponent + WakuStoreModal { + messagingStore: root.messagingStore + advancedStore: root.advancedStore + } + } + + StatusListItem { + Layout.fillWidth: true + title: qsTr("Waku Nodes") + visible: root.advancedStore.isWakuV2 + components: [ + StatusIcon { + icon: "chevron-down" + rotation: 270 + color: Theme.palette.baseColor1 + } + ] + onClicked: Global.openPopup(wakuNodesModalComponent) + } + + Component { + id: wakuNodesModalComponent WakuNodesModal { messagingStore: root.messagingStore advancedStore: root.advancedStore