parent
6fa74b7ba9
commit
44a8b6df0a
|
@ -0,0 +1,29 @@
|
|||
import controller_interface
|
||||
import io_interface
|
||||
|
||||
import ../../../../../app_service/service/transaction/service as transaction_service
|
||||
import ../../../../../app_service/service/transaction/cryptoRampDto
|
||||
|
||||
export controller_interface
|
||||
|
||||
type
|
||||
Controller* = ref object of controller_interface.AccessInterface
|
||||
delegate: io_interface.AccessInterface
|
||||
transactionService: transaction_service.Service
|
||||
|
||||
proc newController*(
|
||||
delegate: io_interface.AccessInterface,
|
||||
transactionService: transaction_service.Service
|
||||
): Controller =
|
||||
result = Controller()
|
||||
result.delegate = delegate
|
||||
result.transactionService = transactionService
|
||||
|
||||
method delete*(self: Controller) =
|
||||
discard
|
||||
|
||||
method init*(self: Controller) =
|
||||
discard
|
||||
|
||||
method fetchCryptoServices*(self: Controller): seq[CryptoRampDto] =
|
||||
return self.transactionService.fetchCryptoServices()
|
|
@ -0,0 +1,19 @@
|
|||
import ../../../../../app_service/service/transaction/cryptoRampDto
|
||||
|
||||
type
|
||||
AccessInterface* {.pure inheritable.} = ref object of RootObj
|
||||
## Abstract class for any input/interaction with this module.
|
||||
|
||||
method delete*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method init*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method fetchCryptoServices*(self: AccessInterface): seq[CryptoRampDto] {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
type
|
||||
## Abstract class (concept) which must be implemented by object/s used in this
|
||||
## module.
|
||||
DelegateInterface* = concept c
|
|
@ -0,0 +1,18 @@
|
|||
type
|
||||
AccessInterface* {.pure inheritable.} = ref object of RootObj
|
||||
## Abstract class for any input/interaction with this module.
|
||||
|
||||
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")
|
||||
|
||||
# View Delegate Interface
|
||||
# Delegate for the view must be declared here due to use of QtObject and multi
|
||||
# inheritance, which is not well supported in Nim.
|
||||
method viewDidLoad*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
|
@ -0,0 +1,46 @@
|
|||
import strformat, chronicles
|
||||
|
||||
type Item* = object
|
||||
name: string
|
||||
description: string
|
||||
fees: string
|
||||
logoUrl: string
|
||||
siteUrl: string
|
||||
hostname: string
|
||||
|
||||
proc initItem*(name, description, fees, logoUrl, siteUrl,
|
||||
hostname: string): Item =
|
||||
result.name = name
|
||||
result.description = description
|
||||
result.fees = fees
|
||||
result.logoUrl = logoUrl
|
||||
result.siteUrl = siteUrl
|
||||
result.hostname = hostname
|
||||
|
||||
proc `$`*(self: Item): string =
|
||||
result = "Item("
|
||||
result &= fmt"name:{self.name}, "
|
||||
result &= fmt"description:{self.description}, "
|
||||
result &= fmt"fees:{self.fees}, "
|
||||
result &= fmt"logoUrl:{self.logoUrl}, "
|
||||
result &= fmt"siteUrl:{self.siteUrl}"
|
||||
result &= fmt"hostname:{self.hostname}"
|
||||
result &= ")"
|
||||
|
||||
method getName*(self: Item): string {.base.} =
|
||||
return self.name
|
||||
|
||||
method getDescription*(self: Item): string {.base.} =
|
||||
return self.description
|
||||
|
||||
method getFees*(self: Item): string {.base.} =
|
||||
return self.fees
|
||||
|
||||
method getLogoUrl*(self: Item): string {.base.} =
|
||||
return self.logoUrl
|
||||
|
||||
method getSiteUrl*(self: Item): string {.base.} =
|
||||
return self.siteUrl
|
||||
|
||||
method getHostname*(self: Item): string {.base.} =
|
||||
return self.hostname
|
|
@ -0,0 +1,69 @@
|
|||
import NimQml, Tables
|
||||
|
||||
import item
|
||||
|
||||
type
|
||||
ModelRole {.pure.} = enum
|
||||
Name = UserRole + 1
|
||||
Description
|
||||
Fees
|
||||
LogoUrl
|
||||
SiteUrl
|
||||
Hostname
|
||||
|
||||
QtObject:
|
||||
type
|
||||
Model* = ref object of QAbstractListModel
|
||||
list: seq[Item]
|
||||
|
||||
proc delete(self: Model) =
|
||||
self.QAbstractListModel.delete
|
||||
|
||||
proc setup(self: Model) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc newModel*(): Model =
|
||||
new(result, delete)
|
||||
result.setup()
|
||||
|
||||
method rowCount(self: Model, index: QModelIndex = nil): int =
|
||||
return self.list.len
|
||||
|
||||
method roleNames(self: Model): Table[int, string] =
|
||||
{
|
||||
ModelRole.Name.int:"name",
|
||||
ModelRole.Description.int:"description",
|
||||
ModelRole.Fees.int:"fees",
|
||||
ModelRole.LogoUrl.int:"logoUrl",
|
||||
ModelRole.SiteUrl.int:"siteUrl",
|
||||
ModelRole.Hostname.int:"hostname"
|
||||
}.toTable
|
||||
|
||||
method data(self: Model, index: QModelIndex, role: int): QVariant =
|
||||
if (not index.isValid):
|
||||
return
|
||||
|
||||
if (index.row < 0 or index.row >= self.list.len):
|
||||
return
|
||||
|
||||
let item = self.list[index.row]
|
||||
let enumRole = role.ModelRole
|
||||
|
||||
case enumRole:
|
||||
of ModelRole.Name:
|
||||
result = newQVariant(item.getName)
|
||||
of ModelRole.Description:
|
||||
result = newQVariant(item.getDescription)
|
||||
of ModelRole.Fees:
|
||||
result = newQVariant(item.getFees)
|
||||
of ModelRole.LogoUrl:
|
||||
result = newQVariant(item.getLogoUrl)
|
||||
of ModelRole.SiteUrl:
|
||||
result = newQVariant(item.getSiteUrl)
|
||||
of ModelRole.Hostname:
|
||||
result = newQVariant(item.getHostname)
|
||||
|
||||
proc setItems*(self: Model, items: seq[Item]) =
|
||||
self.beginResetModel()
|
||||
self.list = items
|
||||
self.endResetModel()
|
|
@ -0,0 +1,61 @@
|
|||
import NimQml, sequtils, sugar
|
||||
|
||||
import ./io_interface, ./view, ./item, ./controller
|
||||
import ../io_interface as delegate_interface
|
||||
import ../../../../global/global_singleton
|
||||
import ../../../../core/eventemitter
|
||||
import ../../../../../app_service/service/transaction/service as transaction_service
|
||||
import ../../../../../app_service/service/transaction/cryptoRampDto
|
||||
|
||||
export io_interface
|
||||
|
||||
type
|
||||
Module* = ref object of io_interface.AccessInterface
|
||||
delegate: delegate_interface.AccessInterface
|
||||
events: EventEmitter
|
||||
view: View
|
||||
controller: controller.AccessInterface
|
||||
moduleLoaded: bool
|
||||
|
||||
proc newModule*(
|
||||
delegate: delegate_interface.AccessInterface,
|
||||
events: EventEmitter,
|
||||
transactionService: transaction_service.Service,
|
||||
): Module =
|
||||
result = Module()
|
||||
result.delegate = delegate
|
||||
result.events = events
|
||||
result.view = newView(result)
|
||||
result.controller = controller.newController(result, transactionService)
|
||||
result.moduleLoaded = false
|
||||
|
||||
method delete*(self: Module) =
|
||||
self.view.delete
|
||||
self.controller.delete
|
||||
|
||||
method fetchCryptoServices*(self: Module) =
|
||||
let cryptoServices = self.controller.fetchCryptoServices()
|
||||
|
||||
let items = cryptoServices.map(proc (w: CryptoRampDto): item.Item =
|
||||
result = initItem(
|
||||
w.name,
|
||||
w.description,
|
||||
w.fees,
|
||||
w.logoUrl,
|
||||
w.siteUrl,
|
||||
w.hostname
|
||||
))
|
||||
self.view.setItems(items)
|
||||
|
||||
method load*(self: Module) =
|
||||
singletonInstance.engine.setRootContextProperty("walletSectionBuySellCrypto", newQVariant(self.view))
|
||||
self.controller.init()
|
||||
self.view.load()
|
||||
|
||||
method isLoaded*(self: Module): bool =
|
||||
return self.moduleLoaded
|
||||
|
||||
method viewDidLoad*(self: Module) =
|
||||
self.fetchCryptoServices()
|
||||
self.moduleLoaded = true
|
||||
self.delegate.buySellCryptoModuleDidLoad()
|
|
@ -0,0 +1,40 @@
|
|||
import NimQml
|
||||
|
||||
import ./model
|
||||
import ./item
|
||||
import ./io_interface
|
||||
|
||||
QtObject:
|
||||
type
|
||||
View* = ref object of QObject
|
||||
delegate: io_interface.AccessInterface
|
||||
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.model = newModel()
|
||||
result.modelVariant = newQVariant(result.model)
|
||||
|
||||
proc load*(self: View) =
|
||||
self.delegate.viewDidLoad()
|
||||
|
||||
proc modelChanged*(self: View) {.signal.}
|
||||
|
||||
proc getModel(self: View): QVariant {.slot.} =
|
||||
return self.modelVariant
|
||||
|
||||
QtProperty[QVariant] model:
|
||||
read = getModel
|
||||
notify = modelChanged
|
||||
|
||||
proc setItems*(self: View, items: seq[Item]) =
|
||||
self.model.setItems(items)
|
||||
self.modelChanged()
|
|
@ -51,6 +51,9 @@ method savedAddressesModuleDidLoad*(self: AccessInterface) {.base.} =
|
|||
method isEIP1559Enabled*(self: AccessInterface): bool {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method buySellCryptoModuleDidLoad*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
type
|
||||
## Abstract class (concept) which must be implemented by object/s used in this
|
||||
## module.
|
||||
|
|
|
@ -10,6 +10,7 @@ import ./collectibles/module as collectibles_module
|
|||
import ./current_account/module as current_account_module
|
||||
import ./transactions/module as transactions_module
|
||||
import ./saved_addresses/module as saved_addresses_module
|
||||
import ./buy_sell_crypto/module as buy_sell_crypto_module
|
||||
|
||||
import ../../../global/global_singleton
|
||||
import ../../../core/eventemitter
|
||||
|
@ -39,6 +40,7 @@ type
|
|||
currentAccountModule: current_account_module.AccessInterface
|
||||
transactionsModule: transactions_module.AccessInterface
|
||||
savedAddressesModule: saved_addresses_module.AccessInterface
|
||||
buySellCryptoModule: buy_sell_crypto_module.AccessInterface
|
||||
|
||||
proc newModule*[T](
|
||||
delegate: T,
|
||||
|
@ -65,6 +67,7 @@ proc newModule*[T](
|
|||
result.currentAccountModule = current_account_module.newModule(result, events, walletAccountService)
|
||||
result.transactionsModule = transactions_module.newModule(result, events, transactionService, walletAccountService)
|
||||
result.savedAddressesModule = saved_addresses_module.newModule(result, events, savedAddressService)
|
||||
result.buySellCryptoModule = buy_sell_crypto_module.newModule(result, events, transactionService)
|
||||
|
||||
method delete*[T](self: Module[T]) =
|
||||
self.accountTokensModule.delete
|
||||
|
@ -74,6 +77,7 @@ method delete*[T](self: Module[T]) =
|
|||
self.currentAccountModule.delete
|
||||
self.transactionsModule.delete
|
||||
self.savedAddressesModule.delete
|
||||
self.buySellCryptoModule.delete
|
||||
self.controller.delete
|
||||
self.view.delete
|
||||
|
||||
|
@ -115,6 +119,7 @@ method load*[T](self: Module[T]) =
|
|||
self.currentAccountModule.load()
|
||||
self.transactionsModule.load()
|
||||
self.savedAddressesModule.load()
|
||||
self.buySellCryptoModule.load()
|
||||
|
||||
method isLoaded*[T](self: Module[T]): bool =
|
||||
return self.moduleLoaded
|
||||
|
@ -141,6 +146,9 @@ proc checkIfModuleDidLoad[T](self: Module[T]) =
|
|||
if(not self.savedAddressesModule.isLoaded()):
|
||||
return
|
||||
|
||||
if(not self.buySellCryptoModule.isLoaded()):
|
||||
return
|
||||
|
||||
self.switchAccount(0)
|
||||
let currency = self.controller.getCurrency()
|
||||
let signingPhrase = self.controller.getSigningPhrase()
|
||||
|
@ -173,4 +181,7 @@ method transactionsModuleDidLoad*[T](self: Module[T]) =
|
|||
self.checkIfModuleDidLoad()
|
||||
|
||||
method savedAddressesModuleDidLoad*[T](self: Module[T]) =
|
||||
self.checkIfModuleDidLoad()
|
||||
self.checkIfModuleDidLoad()
|
||||
|
||||
method buySellCryptoModuleDidLoad*[T](self: Module[T]) =
|
||||
self.checkIfModuleDidLoad()
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import json, strformat
|
||||
|
||||
include ../../common/json_utils
|
||||
|
||||
type
|
||||
CryptoRampDto* = ref object of RootObj
|
||||
name*: string
|
||||
description*: string
|
||||
fees*: string
|
||||
logoUrl*: string
|
||||
siteUrl*: string
|
||||
hostname*: string
|
||||
|
||||
proc newDto*(
|
||||
name: string,
|
||||
description: string,
|
||||
fees: string,
|
||||
logoUrl: string,
|
||||
siteUrl: string,
|
||||
hostname: string
|
||||
): CryptoRampDto =
|
||||
return CryptoRampDto(
|
||||
name: name,
|
||||
description: description,
|
||||
fees: fees,
|
||||
logoUrl: logoUrl,
|
||||
siteUrl: siteUrl,
|
||||
hostname: hostname
|
||||
)
|
||||
|
||||
proc `$`*(self: CryptoRampDto): string =
|
||||
result = "CryptoRampDto("
|
||||
result &= fmt"name:{self.name}, "
|
||||
result &= fmt"description:{self.description}, "
|
||||
result &= fmt"fees:{self.fees}, "
|
||||
result &= fmt"logoUrl:{self.logoUrl}, "
|
||||
result &= fmt"siteUrl:{self.siteUrl}, "
|
||||
result &= fmt"hostname:{self.hostname}"
|
||||
result &= ")"
|
||||
|
||||
proc toCryptoRampDto*(jsonObj: JsonNode): CryptoRampDto =
|
||||
result = CryptoRampDto()
|
||||
discard jsonObj.getProp("name", result.name)
|
||||
discard jsonObj.getProp("description", result.description)
|
||||
discard jsonObj.getProp("fees", result.fees)
|
||||
discard jsonObj.getProp("logoUrl", result.logoUrl)
|
||||
discard jsonObj.getProp("siteUrl", result.siteUrl)
|
||||
discard jsonObj.getProp("hostname", result.hostname)
|
|
@ -17,6 +17,7 @@ import ../settings/service as settings_service
|
|||
import ../eth/dto/transaction as transaction_data_dto
|
||||
import ../eth/dto/[contract, method_dto]
|
||||
import ./dto as transaction_dto
|
||||
import ./cryptoRampDto
|
||||
import ../eth/utils as eth_utils
|
||||
import ../../common/conversion
|
||||
|
||||
|
@ -407,3 +408,15 @@ QtObject:
|
|||
maxFeePerGasM: parseFloat(common_conversion.wei2gwei($(maxFeePerGasM))),
|
||||
maxFeePerGasH: parseFloat(common_conversion.wei2gwei($(baseFee + maxPriorityFeePerGas)))
|
||||
)
|
||||
|
||||
proc fetchCryptoServices*(self: Service): seq[CryptoRampDto] =
|
||||
try:
|
||||
let response = transactions.fetchCryptoServices()
|
||||
|
||||
if not response.error.isNil:
|
||||
raise newException(ValueError, "Error fetching crypto services" & response.error.message)
|
||||
|
||||
return response.result.getElems().map(x => x.toCryptoRampDto())
|
||||
except Exception as e:
|
||||
error "Error fetching crypto services", message = e.msg
|
||||
return @[]
|
||||
|
|
|
@ -38,4 +38,7 @@ proc deletePendingTransaction*(transactionHash: string): RpcResponse[JsonNode] {
|
|||
proc getPendingOutboundTransactionsByAddress*(address: string): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
let payload = %* [address]
|
||||
result = callPrivateRPC("wallet_getPendingOutboundTransactionsByAddress", payload)
|
||||
|
||||
|
||||
proc fetchCryptoServices*(): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
result = core.callPrivateRPC("wallet_getCryptoOnRamps", %* [])
|
||||
|
||||
|
|
|
@ -123,16 +123,24 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
property int btnOuterMargin: Style.current.bigPadding
|
||||
|
||||
Component {
|
||||
id: buySellModal
|
||||
CryptoServicesModal {
|
||||
onClosed: destroy()
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: walletMenu
|
||||
width: sendBtn.width + receiveBtn.width + settingsBtn.width
|
||||
+ walletMenu.btnOuterMargin * 2
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 16
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 16
|
||||
|
||||
spacing: Style.current.bigPadding
|
||||
|
||||
HeaderButton {
|
||||
id: sendBtn
|
||||
imageSource: Style.svg("send")
|
||||
|
@ -152,8 +160,17 @@ Item {
|
|||
onClicked: function () {
|
||||
Global.openPopup(receiveModalComponent);
|
||||
}
|
||||
anchors.left: sendBtn.right
|
||||
anchors.leftMargin: walletMenu.btnOuterMargin
|
||||
}
|
||||
|
||||
HeaderButton {
|
||||
id: buySellBtn
|
||||
imageSource: Style.svg("crypto-icon")
|
||||
flipImage: true
|
||||
//% "Buy / Sell"
|
||||
text: qsTrId("Buy / Sell")
|
||||
onClicked: function () {
|
||||
Global.openPopup(buySellModal);
|
||||
}
|
||||
}
|
||||
|
||||
HeaderButton {
|
||||
|
@ -169,8 +186,6 @@ Item {
|
|||
newSettingsMenu.popup(x, settingsBtn.height)
|
||||
}
|
||||
}
|
||||
anchors.left: receiveBtn.right
|
||||
anchors.leftMargin: walletMenu.btnOuterMargin
|
||||
|
||||
// TODO: replace with StatusPopupMenu
|
||||
PopupMenu {
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
|
||||
import utils 1.0
|
||||
import shared.panels 1.0
|
||||
|
||||
import StatusQ.Popups 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Core 0.1
|
||||
|
||||
import "../controls"
|
||||
import "../stores"
|
||||
|
||||
StatusModal {
|
||||
id: cryptoServicesPopupRoot
|
||||
|
||||
height: 400
|
||||
header.title: qsTr("Buy / Sell crypto")
|
||||
anchors.centerIn: parent
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
anchors.fill: parent
|
||||
sourceComponent: servicesComponent
|
||||
|
||||
Component {
|
||||
id: servicesComponent
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Style.current.padding
|
||||
anchors.bottomMargin: Style.current.padding
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.rightMargin: Style.current.padding
|
||||
StyledText {
|
||||
id: note
|
||||
anchors.top: parent.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Style.current.secondaryText
|
||||
text: qsTr("Choose a service you'd like to use to buy crypto")
|
||||
}
|
||||
|
||||
ListView {
|
||||
anchors.top: note.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: 394
|
||||
model: RootStore.cryptoRampServicesModel
|
||||
focus: true
|
||||
spacing: Style.current.padding
|
||||
clip: true
|
||||
|
||||
delegate: StatusListItem {
|
||||
width: parent.width
|
||||
title: name
|
||||
subTitle: description
|
||||
image.source: logoUrl
|
||||
label: fees
|
||||
statusListItemSubTitle.maximumLineCount: 1
|
||||
components: [
|
||||
StatusIcon {
|
||||
icon: "chevron-down"
|
||||
rotation: 270
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
]
|
||||
onClicked: {
|
||||
Global.openLink(siteUrl);
|
||||
cryptoServicesPopupRoot.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,6 +41,8 @@ QtObject {
|
|||
property var testNetworks: networksModule.test
|
||||
property var enabledNetworks: networksModule.enabled
|
||||
|
||||
property var cryptoRampServicesModel: walletSectionBuySellCrypto.model
|
||||
|
||||
// This should be exposed to the UI via "walletModule", WalletModule should use
|
||||
// Accounts Service which keeps the info about that (isFirstTimeAccountLogin).
|
||||
// Then in the View of WalletModule we may have either QtProperty or
|
||||
|
|
Loading…
Reference in New Issue