diff --git a/pkg/config/messages.go b/pkg/config/messages.go index 57dc8b0..89be6e7 100644 --- a/pkg/config/messages.go +++ b/pkg/config/messages.go @@ -67,6 +67,8 @@ const ( MsgChainAddAddress // MsgSSIAddVC add a vc to the ssi wallet MsgSSIAddVC + // MsgPaymentRequest receive a payment request + MsgPaymentRequest ) // AppMsg are messages that are exchanged within the app diff --git a/pkg/helpers/helpers.go b/pkg/helpers/helpers.go new file mode 100644 index 0000000..e7ede64 --- /dev/null +++ b/pkg/helpers/helpers.go @@ -0,0 +1,13 @@ +package helpers + +import ( + "time" +) + +func DelayExec(delay int, fn func()) { + t1 := time.NewTicker(time.Duration(delay) * time.Second) + go func() { + <-t1.C + fn() + }() +} diff --git a/pkg/model/presentations.go b/pkg/model/presentations.go new file mode 100644 index 0000000..2709844 --- /dev/null +++ b/pkg/model/presentations.go @@ -0,0 +1,86 @@ +package model + +import ( + "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" + log "github.com/sirupsen/logrus" +) + +// SchemaField is a field within the credential schema +type SchemaField struct { + Name string + Title string + Description string + Value string + ReadOnly bool +} + +func NewSchemaField(name, title, description string) SchemaField { + return SchemaField{ + Name: name, + Title: title, + Description: description, + ReadOnly: false, + } +} +func NewSchemaLabel(name, title, description, value string) SchemaField { + return SchemaField{ + Name: name, + Title: title, + Description: description, + Value: value, + ReadOnly: true, + } +} + +// PresentationRequest represent a verifiable credential schema +type PresentationRequest struct { + // The name of the presentation request + Name string + // Credential Should match the Verifiable Credentials that belongs to + Credential string + // Describe the request + Fields []SchemaField +} + +// LicenseSchema returns a license schema +func LicenseSchema(licenseType, authority string) PresentationRequest { + //"license_type": "MICAEMI", + //"country": "EU", + //"authority": "Another Financial Services Body (AFFB)", + //"circulation_limit": { + // "denom": "sEUR", + // "amount": "1000000000" + //} + return PresentationRequest{ + Name: "LicensePresentationRequest", + Credential: "LicenseCredential", + Fields: []SchemaField{ + NewSchemaLabel("license_type", "License Type", "license acronym", licenseType), + NewSchemaLabel("authority", "Authority", "authority issuing the license", authority), + NewSchemaField("country", "Country", "country where the license applies"), + NewSchemaField("denom", "Token denomination", "the token symbol"), + NewSchemaField("amount", "Amount", "amount approved by the license"), + }, + } +} + +func PaymentRequest(address, denom, reason string) PresentationRequest { + return PresentationRequest{ + Name: "PaymentPresentationRequest", + Credential: "ReceiptCredential", + Fields: []SchemaField{ + NewSchemaLabel("reason", "Payment reason", "reason for the payment request", address), + NewSchemaLabel("recipient_address", "Recipient Address", "address to send the payment to", address), + NewSchemaLabel("denom", "Token denomination", "the token symbol requested by the recipient", denom), + NewSchemaField("amount", "Amount", "payment request amount"), + }, + } +} + +func LicensePresentation(license *verifiable.Credential) { + vp, err := verifiable.NewPresentation(verifiable.WithCredentials(license)) + if err != nil { + log.Errorln(err) + } + log.Debugln(vp) +} diff --git a/pkg/ui/handlers.go b/pkg/ui/handlers.go index a1c96fe..215e8d4 100644 --- a/pkg/ui/handlers.go +++ b/pkg/ui/handlers.go @@ -133,6 +133,9 @@ func dispatcher(in chan config.AppMsg) { contact, _ := state.Contacts[tm.Channel] contact.Texts = append(contact.Texts, tm) // refresh view state.Contacts[tm.Channel] = contact + // process the incoming message to see if matches a schema + + // process the incoming message to see if matches a verifiable credential // save the state appCfg.RuntimeMsgs.Notification <- config.NewAppMsg(config.MsgSaveState, nil) @@ -259,6 +262,25 @@ func executeCmd() { } //hub.Notification <- "chain" + case "debug", "d": + switch s[1] { + case "show-license-credentials", "lc": + ls := model.LicenseSchema("MICAEMI", "Dredd") + RenderPresentationRequest("Please supply license information", ls, func(m map[string]string) { + // todo Create verifiable Credential + }) + } + case "payment-request", "pr": + // TODO: the recipientAddress should be chose by the person making the payment request + // in this case is the account used for the resolver demo did. see github.com/allinbits/cosmos-cash-misc + recipientAddress := "cosmos1lcc4s4qkv0yg7ntmws6v44z5r5py27hap7pfw3" + pr := model.PaymentRequest(recipientAddress, "cash", "Demo Payment") + RenderPresentationRequest("Please fill the payment request", pr, func(m map[string]string) { + helpers.DelayExec(10, func() { + appCfg.RuntimeMsgs.Notification <- config.NewAppMsg(config.MsgPaymentRequest, nil) + }) + }) + } // FINALLY RECORD THE MESSAGE IN THE CHAT diff --git a/pkg/ui/ui.go b/pkg/ui/ui.go index 24bed89..fd2430f 100644 --- a/pkg/ui/ui.go +++ b/pkg/ui/ui.go @@ -10,15 +10,21 @@ import ( "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/allinbits/cosmos-cash-agent/pkg/config" + "github.com/allinbits/cosmos-cash-agent/pkg/model" log "github.com/sirupsen/logrus" ) +var ( + mainApp fyne.App + mainWindow fyne.Window +) + func Render(cfg *config.EdgeConfigSchema) { appCfg = cfg - myApp := app.New() - myWindow := myApp.NewWindow(cfg.ControllerName) + mainApp = app.New() + mainWindow = mainApp.NewWindow(cfg.ControllerName) // main content tabs := container.NewAppTabs( @@ -30,22 +36,22 @@ func Render(cfg *config.EdgeConfigSchema) { getLogTab(), ) - myWindow.SetContent( + mainWindow.SetContent( container.NewMax( tabs, //footer, ), ) - myWindow.SetOnClosed(func() { + mainWindow.SetOnClosed(func() { log.Infoln(">>>>>>> TERMINATING <<<<<<<") }) // run the dispatcher that updates the ui go dispatcher(cfg.RuntimeMsgs.Notification) // lanuch the app - myWindow.Resize(fyne.NewSize(940, 660)) - myWindow.ShowAndRun() + mainWindow.Resize(fyne.NewSize(940, 660)) + mainWindow.ShowAndRun() } func getMessagesTab() *container.TabItem { @@ -75,6 +81,11 @@ func getMessagesTab() *container.TabItem { //msgPanel := container.NewVBox() msgScroll := container.NewScroll(msgList) + input := widget.NewEntryWithData(userCommand) + input.OnSubmitted = func(_ string) { + executeCmd() + } + // footer stuff rightPanel := container.NewBorder( nil, @@ -149,8 +160,6 @@ func getCredentialsTab() *container.TabItem { ) // right panel - - msgPanel := widget.NewEntryWithData(credentialData) rightPanel := container.NewScroll(msgPanel) @@ -212,3 +221,51 @@ func getLogTab() *container.TabItem { main := container.NewScroll(msgPanel) return container.NewTabItem("Logs", main) } + +func RenderPresentationRequest(title string, schema model.PresentationRequest, onSubmit func(map[string]string)) { + answers := make(map[string]binding.String) + + var popUp *widget.PopUp + var fields []*widget.FormItem + for _, s := range schema.Fields { + // data + b := binding.NewString() + e := widget.NewEntryWithData(b) + if s.ReadOnly { + b.Set(s.Value) + e.Disable() + } + answers[s.Name] = b + // form item + w := widget.NewFormItem(s.Title, e) + w.HintText = s.Description + // fields + fields = append(fields, w) + } + form := &widget.Form{ + Items: fields, + OnSubmit: func() { + // hide the popUp + popUp.Hide() + // convert data to string + data := make(map[string]string, len(fields)) + for k, b := range answers { + s, _ := b.Get() + data[k] = s + } + // execute the callback + log.Debugln("form data", data) + onSubmit(data) + }, + OnCancel: func() { popUp.Hide() }, + SubmitText: "Submit", + CancelText: "Cancel", + } + + // create the popUp + popUp = widget.NewModalPopUp( + widget.NewCard(title, "", form), + mainWindow.Canvas(), + ) + popUp.Show() +}