diff --git a/custom.go b/custom.go index 97df2a6..87ab10d 100644 --- a/custom.go +++ b/custom.go @@ -1,217 +1,219 @@ -// Copyright 2023-2024 DERO Foundation. All rights reserved. -// Use of this source code in any form is governed by RESEARCH license. -// license can be found in the LICENSE file. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package main - -import ( - "image/color" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/widget" -) - -type returnEntry struct { - widget.Entry - OnReturn func() -} - -// NewReturnEntry creates a new single line entry widget that executes a function when the -// return key is pressed -func NewReturnEntry() *returnEntry { - entry := &returnEntry{} - entry.ExtendBaseWidget(entry) - return entry -} - -func (e *returnEntry) TypedKey(key *fyne.KeyEvent) { - switch key.Name { - case fyne.KeyReturn: - e.OnReturn() - case fyne.KeyEnter: - e.OnReturn() - default: - e.Entry.TypedKey(key) - } -} - -var _ fyne.Draggable = (*iframe)(nil) - -type iframe struct { - widget.BaseWidget -} - -func (o *iframe) CreateRenderer() fyne.WidgetRenderer { - rect := canvas.NewRectangle(color.Transparent) - rect.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.99)) - o.ExtendBaseWidget(o) - return &iframeRenderer{ - rect: rect, - } -} - -func (o *iframe) MinSize() fyne.Size { - o.ExtendBaseWidget(o) - return o.BaseWidget.MinSize() -} - -func (o *iframe) Tapped(e *fyne.PointEvent) { - -} - -func (o *iframe) TappedSecondary(e *fyne.PointEvent) { - -} - -func (o *iframe) Dragged(e *fyne.DragEvent) { - if engram.Disk != nil { - if nav.PosX == 0 && nav.PosY == 0 { - nav.PosX = e.Position.X - nav.PosY = e.Position.Y - } - nav.CurX = e.Position.X - nav.CurY = e.Position.Y - } -} - -func (o *iframe) DragEnd() { - /* - if engram.Disk != nil { - if nav.CurX > nav.PosX+30 { - if session.Domain == "app.wallet" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutIdentity()) - } else if session.Domain == "app.cyberdeck" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - } - } else if nav.CurX < nav.PosX-30 { - if session.Domain == "app.wallet" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutCyberdeck()) - } else if session.Domain == "app.Identity" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - } - } else if nav.CurY > nav.PosY+30 { - if session.Domain == "app.wallet" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutTransfers()) - } else if session.Domain == "app.messages" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - } else if session.Domain == "app.messages.contact" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMessages()) - } - } else if nav.CurY < nav.PosY-30 { - if session.Domain == "app.wallet" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMessages()) - } else if session.Domain == "app.transfers" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - } - } - - nav.PosX = 0 - nav.PosY = 0 - } - */ - if engram.Disk != nil { - if nav.CurY > nav.PosY+30 { - if session.Domain == "app.messages" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMessages()) - } else if session.Domain == "app.messages.contact" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutPM()) - } else if session.Domain == "app.Identity" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutIdentity()) - } - } - } -} - -var _ fyne.WidgetRenderer = (*iframeRenderer)(nil) - -type iframeRenderer struct { - rect *canvas.Rectangle -} - -func (o *iframeRenderer) BackgroundColor() color.Color { - return color.Transparent -} - -func (o *iframeRenderer) Destroy() { -} - -func (o *iframeRenderer) Layout(size fyne.Size) { - o.rect.Resize(size) -} - -func (o *iframeRenderer) MinSize() fyne.Size { - return o.rect.MinSize() -} - -func (o *iframeRenderer) Objects() []fyne.CanvasObject { - return []fyne.CanvasObject{o.rect} -} - -func (o *iframeRenderer) Refresh() { - o.rect.Refresh() -} - -type mobileEntry struct { - widget.Entry - OnFocusLost func() - OnFocusGained func() -} - -// NewMobileEntry creates a new single line entry widget with more options for mobile devices -func NewMobileEntry() *mobileEntry { - entry := &mobileEntry{} - entry.ExtendBaseWidget(entry) - return entry -} - -func (o *mobileEntry) FocusGained() { - o.Entry.FocusGained() - o.OnFocusGained() -} - -type contextMenuButton struct { - widget.Button - menu *fyne.Menu -} - -func (o *contextMenuButton) Tapped(e *fyne.PointEvent) { - widget.ShowPopUpMenuAtPosition(o.menu, fyne.CurrentApp().Driver().CanvasForObject(o), e.AbsolutePosition) -} - -// NewContextMenuButton creates a new button widget with a dropdown menu -func NewContextMenuButton(label string, image fyne.Resource, menu *fyne.Menu) *contextMenuButton { - o := &contextMenuButton{menu: menu} - o.Text = label - o.SetIcon(image) - o.ExtendBaseWidget(o) - return o -} - -// NewVScroll places content in a VScroll container for mobile orientations and scrolling -func NewVScroll(content *fyne.Container) *container.Scroll { - return container.NewVScroll(container.NewCenter(content, widget.NewLabel(""))) -} +// Copyright 2023-2024 DERO Foundation. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import ( + "image/color" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" +) + +type returnEntry struct { + widget.Entry + OnReturn func() +} + +// NewReturnEntry creates a new single line entry widget that executes a function when the +// return key is pressed +func NewReturnEntry() *returnEntry { + entry := &returnEntry{} + entry.ExtendBaseWidget(entry) + return entry +} + +func (e *returnEntry) TypedKey(key *fyne.KeyEvent) { + switch key.Name { + case fyne.KeyReturn: + e.OnReturn() + case fyne.KeyEnter: + e.OnReturn() + default: + e.Entry.TypedKey(key) + } +} + +var _ fyne.Draggable = (*iframe)(nil) + +type iframe struct { + widget.BaseWidget +} + +func (o *iframe) CreateRenderer() fyne.WidgetRenderer { + rect := canvas.NewRectangle(color.Transparent) + rect.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.99)) + o.ExtendBaseWidget(o) + return &iframeRenderer{ + rect: rect, + } +} + +func (o *iframe) MinSize() fyne.Size { + o.ExtendBaseWidget(o) + return o.BaseWidget.MinSize() +} + +func (o *iframe) Tapped(e *fyne.PointEvent) { + +} + +func (o *iframe) TappedSecondary(e *fyne.PointEvent) { + +} + +func (o *iframe) Dragged(e *fyne.DragEvent) { + if engram.Disk != nil { + if nav.PosX == 0 && nav.PosY == 0 { + nav.PosX = e.Position.X + nav.PosY = e.Position.Y + } + nav.CurX = e.Position.X + nav.CurY = e.Position.Y + } +} + +func (o *iframe) DragEnd() { + /* + if engram.Disk != nil { + if nav.CurX > nav.PosX+30 { + if session.Domain == "app.wallet" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutIdentity()) + } else if session.Domain == "app.cyberdeck" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + } + } else if nav.CurX < nav.PosX-30 { + if session.Domain == "app.wallet" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutCyberdeck()) + } else if session.Domain == "app.Identity" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + } + } else if nav.CurY > nav.PosY+30 { + if session.Domain == "app.wallet" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutTransfers()) + } else if session.Domain == "app.messages" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + } else if session.Domain == "app.messages.contact" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMessages()) + } + } else if nav.CurY < nav.PosY-30 { + if session.Domain == "app.wallet" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMessages()) + } else if session.Domain == "app.transfers" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + } + } + + nav.PosX = 0 + nav.PosY = 0 + } + */ + if engram.Disk != nil { + if nav.CurY > nav.PosY+30 { + if session.Domain == "app.messages" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMessages()) + } else if session.Domain == "app.messages.contact" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutPM()) + } else if session.Domain == "app.Identity" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutIdentity()) + } + } + } +} + +var _ fyne.WidgetRenderer = (*iframeRenderer)(nil) + +type iframeRenderer struct { + rect *canvas.Rectangle +} + +func (o *iframeRenderer) BackgroundColor() color.Color { + return color.Transparent +} + +func (o *iframeRenderer) Destroy() { +} + +func (o *iframeRenderer) Layout(size fyne.Size) { + o.rect.Resize(size) +} + +func (o *iframeRenderer) MinSize() fyne.Size { + return o.rect.MinSize() +} + +func (o *iframeRenderer) Objects() []fyne.CanvasObject { + return []fyne.CanvasObject{o.rect} +} + +func (o *iframeRenderer) Refresh() { + o.rect.Refresh() +} + +type mobileEntry struct { + widget.Entry + OnFocusLost func() + OnFocusGained func() +} + +// NewMobileEntry creates a new single line entry widget with more options for mobile devices +func NewMobileEntry() *mobileEntry { + entry := &mobileEntry{} + entry.ExtendBaseWidget(entry) + return entry +} + +func (o *mobileEntry) FocusGained() { + o.Entry.FocusGained() + if o.OnFocusGained != nil { + o.OnFocusGained() + } +} + +type contextMenuButton struct { + widget.Button + menu *fyne.Menu +} + +func (o *contextMenuButton) Tapped(e *fyne.PointEvent) { + widget.ShowPopUpMenuAtPosition(o.menu, fyne.CurrentApp().Driver().CanvasForObject(o), e.AbsolutePosition) +} + +// NewContextMenuButton creates a new button widget with a dropdown menu +func NewContextMenuButton(label string, image fyne.Resource, menu *fyne.Menu) *contextMenuButton { + o := &contextMenuButton{menu: menu} + o.Text = label + o.SetIcon(image) + o.ExtendBaseWidget(o) + return o +} + +// NewVScroll places content in a VScroll container for mobile orientations and scrolling +func NewVScroll(content *fyne.Container) *container.Scroll { + return container.NewVScroll(container.NewCenter(content, widget.NewLabel(""))) +} diff --git a/functions.go b/functions.go index 47ef7d3..23204c8 100644 --- a/functions.go +++ b/functions.go @@ -1,4547 +1,4589 @@ -// Copyright 2023-2024 DERO Foundation. All rights reserved. -// Use of this source code in any form is governed by RESEARCH license. -// license can be found in the LICENSE file. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package main - -import ( - "context" - "crypto/rand" - "crypto/sha1" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "image/color" - "math/big" - "net" - "os" - "path/filepath" - "runtime" - "sort" - "strconv" - "strings" - "sync" - "time" - "unicode" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/data/binding" - "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/widget" - x "fyne.io/x/fyne/widget" - "github.com/civilware/Gnomon/indexer" - "github.com/civilware/Gnomon/rwc" - "github.com/civilware/Gnomon/structures" - "github.com/civilware/epoch" - "github.com/civilware/tela" - "github.com/civilware/tela/logger" - "github.com/civilware/tela/shards" - "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/channel" - "github.com/creachadair/jrpc2/handler" - "github.com/gorilla/websocket" - "mvdan.cc/xurls/v2" - - "github.com/civilware/Gnomon/storage" - "github.com/deroproject/derohe/config" - "github.com/deroproject/derohe/cryptography/crypto" - "github.com/deroproject/derohe/dvm" - "github.com/deroproject/derohe/globals" - - "github.com/deroproject/derohe/rpc" - "github.com/deroproject/derohe/transaction" - - "github.com/deroproject/derohe/walletapi" - "github.com/deroproject/derohe/walletapi/mnemonics" - "github.com/deroproject/derohe/walletapi/rpcserver" - "github.com/deroproject/derohe/walletapi/xswd" -) - -type App struct { - App fyne.App - Window fyne.Window - Focus bool -} - -type UI struct { - Padding float32 - MaxWidth float32 - Width float32 - MaxHeight float32 - Height float32 -} - -type Colors struct { - Network color.Color - Account color.Color - Blue color.Color - Red color.Color - DarkGreen color.Color - Green color.Color - Gray color.Color - Yellow color.Color - DarkMatter color.Color - Cold color.Color - Flint color.Color -} - -type Navigation struct { - PosX float32 - PosY float32 - CurX float32 - CurY float32 -} - -type Session struct { - Window fyne.Window - DesktopMode bool - Domain string - LastDomain fyne.CanvasObject - Network string - Offline bool - Language int - ID string - Link string - Type string - Daemon string - WalletOpen bool - Username string - Datapad string - DatapadChanged bool - LastBalance uint64 - Balance uint64 - BalanceUSD string - BalanceText *canvas.Text - BalanceUSDText *canvas.Text - ModeText *canvas.Text - IDText *canvas.Text - LinkText *canvas.Text - StatusText *canvas.Text - Path string - Name string - Password string - PasswordConfirm string - DaemonHeight uint64 - WalletHeight uint64 - RPCServer *rpcserver.RPCServer - Verified bool - Dashboard string - Error string - NewUser string - Gif *x.AnimatedGif - RegHashes int64 - LimitMessages bool - TrackRecentBlocks int64 -} - -type Cyberdeck struct { - RPC struct { - user string - pass string - port string - userText *widget.Entry - passText *widget.Entry - portText *widget.Entry - toggle *widget.Button - status *canvas.Text - server *rpcserver.RPCServer - } - WS struct { - sync.RWMutex - port string - portText *widget.Entry - list *widget.List - toggle *widget.Button - status *canvas.Text - server *xswd.XSWD - apps []xswd.ApplicationData - advanced bool - global struct { - connect bool - enabled bool - status *canvas.Text - permissions map[string]xswd.Permission - } - } - EPOCH struct { - enabled bool - allowWithAddress bool - err error - total epoch.GetSessionEPOCH_Result - } -} - -type INDEXwithRatings struct { - ratings tela.Rating_Result - tela.INDEX -} - -type Engram struct { - Disk *walletapi.Wallet_Disk -} - -type Theme struct { - main eTheme - alt eTheme2 -} - -type Gnomon struct { - Active int - Index *indexer.Indexer - BBolt *storage.BboltStore - Graviton *storage.GravitonStore - Path string -} - -type ProofData struct { - Receivers []string - Amounts []uint64 - Payloads []string -} - -type Status struct { - Canvas *canvas.Text - Message string - Network *canvas.Text - Connection *canvas.Circle - Sync *canvas.Circle - Cyberdeck *canvas.Circle - Gnomon *canvas.Circle - EPOCH *canvas.Circle -} - -type Transfers struct { - Address *rpc.Address - PaymentID uint64 - Amount uint64 - Comment string - GasStorage uint64 - Fees uint64 - Pending []rpc.Transfer - TX *transaction.Transaction - TXID crypto.Hash - Proof string - Ringsize uint64 - SendAll bool - Size float32 - Status string - OfflineTX bool - Filename string -} - -type MessageBox struct { - List *widget.List - Data binding.ExternalStringList -} - -type Messages struct { - Contact string - Address string - Data []string - Height uint64 - Message string -} - -type InstallContract struct { - TXID string -} - -type Client struct { - WS *websocket.Conn - RPC *jrpc2.Client -} - -// Get the Engram settings from the local Graviton tree -func initSettings() { - getNetwork() - getMode() - getDaemon() - getGnomon() - if a.Driver().Device().IsMobile() { - err := tela.SetShardPath(filepath.Join(AppPath(), filepath.Dir(shards.GetPath()))) - if err != nil { - logger.Errorf("[Engram] Setting TELA shard: %s\n", err) - return - } - - os.RemoveAll(tela.GetPath()) - } -} - -// Go routine to update the latest information from the connected daemon (Online Mode only) -func StartPulse() { - if !walletapi.Connected && engram.Disk != nil { - logger.Printf("[Network] Attempting network connection to: %s\n", walletapi.Daemon_Endpoint) - err := walletapi.Connect(session.Daemon) - if err != nil { - logger.Errorf("[Network] Failed to connect to: %s\n", walletapi.Daemon_Endpoint) - walletapi.Connected = false - closeWallet() - session.Window.SetContent(layoutAlert(1)) - removeOverlays() - return - } else { - sentNotifications := false - walletapi.Connected = true - engram.Disk.SetOnlineMode() - session.BalanceText = canvas.NewText("", colors.Blue) - session.StatusText = canvas.NewText("", colors.Blue) - status.Connection.FillColor = colors.Gray - status.Sync.FillColor = colors.Gray - - go func() { - count := 0 - for engram.Disk != nil { - if walletapi.Get_Daemon_Height() < 1 || !walletapi.Connected { - logger.Printf("[Network] Attempting network connection to: %s\n", walletapi.Daemon_Endpoint) - err := walletapi.Connect(session.Daemon) - if err != nil { - // If we fail DEFAULT_DAEMON_RECONNECT_TIMEOUT+ times, display node communication layout err - if count >= DEFAULT_DAEMON_RECONNECT_TIMEOUT { - walletapi.Connected = false - closeWallet() - session.Window.SetContent(layoutAlert(1)) - removeOverlays() - break - } - count++ - logger.Errorf("[Network] Failed to connect to: %s (%d / %d)\n", walletapi.Daemon_Endpoint, count, DEFAULT_DAEMON_RECONNECT_TIMEOUT) - walletapi.Connected = false - status.Connection.FillColor = colors.Red - status.Sync.FillColor = colors.Red - status.Gnomon.FillColor = colors.Red - status.EPOCH.FillColor = colors.Red - session.Offline = true - - time.Sleep(time.Second) - continue - } else { - count = 0 - time.Sleep(time.Second) - session.Offline = false - } - } - - if !engram.Disk.IsRegistered() { - if !walletapi.Connected { - logger.Errorf("[Network] Could not connect to daemon...%d\n", engram.Disk.Get_Daemon_TopoHeight()) - status.Connection.FillColor = colors.Red - status.Connection.Refresh() - status.Sync.FillColor = colors.Red - } - - time.Sleep(time.Second) - } else { - if session.WalletHeight != engram.Disk.Get_Height() { - sentNotifications = false - } - - session.Balance, _ = engram.Disk.Get_Balance() - session.BalanceText.Text = globals.FormatMoney(session.Balance) - session.WalletHeight = engram.Disk.Get_Height() - session.DaemonHeight = engram.Disk.Get_Daemon_Height() - session.StatusText.Text = fmt.Sprintf("%d", session.WalletHeight) - - session.LastBalance = session.Balance - - if walletapi.IsDaemonOnline() { - status.Connection.FillColor = colors.Green - if session.DaemonHeight > 0 && session.DaemonHeight-session.WalletHeight < 2 { - status.Connection.FillColor = colors.Green - status.Sync.FillColor = colors.Green - } else if session.DaemonHeight == 0 { - status.Sync.FillColor = colors.Red - } else { - status.Sync.FillColor = color.Transparent - } - - if gnomon.Index != nil { - if gnomon.Index.Status == "indexed" { - status.Gnomon.FillColor = colors.Green - } else { - if uint64(gnomon.Index.LastIndexedHeight) < session.WalletHeight-15 { - status.Gnomon.FillColor = colors.Red - } else { - status.Gnomon.FillColor = color.Transparent - } - } - } else { - status.Gnomon.FillColor = colors.Gray - - if gnomon.Index == nil && engram.Disk != nil { - enableGnomon, _ := getGnomon() - if enableGnomon == "1" { - startGnomon() - } - } - } - - if epoch.IsActive() { - if epoch.IsProcessing() { - status.EPOCH.FillColor = color.Transparent - } else { - status.EPOCH.FillColor = colors.Green - } - } else { - if cyberdeck.EPOCH.err != nil { - status.EPOCH.FillColor = colors.Red - } else { - status.EPOCH.FillColor = colors.Gray - } - } - } else { - status.Connection.FillColor = colors.Gray - status.Sync.FillColor = colors.Gray - status.Cyberdeck.FillColor = colors.Gray - status.Gnomon.FillColor = colors.Gray - status.EPOCH.FillColor = colors.Gray - logger.Printf("[Network] Offline › Last Height: " + strconv.FormatUint(session.WalletHeight, 10) + " / " + strconv.FormatUint(session.DaemonHeight, 10) + "\n") - } - - // Check for updates and send appropriate notifications - var zeroscid crypto.Hash - - // Query incoming messages - entries := engram.Disk.Show_Transfers(zeroscid, false, true, false, session.WalletHeight-1, session.WalletHeight-1, "", "", uint64(1337), 0) - - for e := range entries { - if entries[e].Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) && !sentNotifications { - sender := entries[e].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) - - notification := fyne.NewNotification(sender, "New message was received (Height: "+fmt.Sprintf("%d", entries[e].Height)+")") - fyne.CurrentApp().SendNotification(notification) - - sentNotifications = true - } - } - - fyne.Do(func() { - session.BalanceText.Refresh() - session.StatusText.Refresh() - status.Connection.Refresh() - status.Sync.Refresh() - status.Cyberdeck.Refresh() - status.Gnomon.Refresh() - status.EPOCH.Refresh() - }) - - time.Sleep(time.Second) - } - } - - if walletapi.Connected { - walletapi.Connected = false - } - }() - } - } -} - -// Get Network setting from the local Graviton tree (Ex: Mainnet, Testnet, Simulator) -func getNetwork() (network string) { - result, err := GetValue("settings", []byte("network")) - if err != nil { - network = NETWORK_MAINNET - session.Network = network - globals.Arguments["--testnet"] = false - globals.Arguments["--simulator"] = false - setNetwork(network) - return - } else { - if string(result) == NETWORK_TESTNET { - network = NETWORK_TESTNET - session.Network = network - globals.Arguments["--testnet"] = true - globals.Arguments["--simulator"] = false - return - } else if string(result) == NETWORK_SIMULATOR { - network = NETWORK_SIMULATOR - session.Network = network - globals.Arguments["--testnet"] = true - globals.Arguments["--simulator"] = true - return - } else { - network = NETWORK_MAINNET - session.Network = network - globals.Arguments["--testnet"] = false - globals.Arguments["--simulator"] = false - return - } - } -} - -// Set Network setting to the local Graviton tree (Ex: Mainnet, Testnet, Simulator) -func setNetwork(network string) (err error) { - s := "" - if network == NETWORK_MAINNET { - s = network - globals.Arguments["--testnet"] = false - globals.Arguments["--simulator"] = false - } else if network == NETWORK_SIMULATOR { - s = network - globals.Arguments["--testnet"] = true - globals.Arguments["--simulator"] = true - } else { - s = NETWORK_TESTNET - globals.Arguments["--testnet"] = true - globals.Arguments["--simulator"] = false - } - - session.Network = s - - StoreValue("settings", []byte("network"), []byte(s)) - - return -} - -// Get daemon endpoint setting from the local Graviton tree -func getDaemon() (r string) { - result, err := GetValue("settings", []byte("endpoint")) - if err != nil { - r = DEFAULT_REMOTE_DAEMON - setDaemon(r) - session.Daemon = r - globals.Arguments["--daemon-address"] = r - return - } - - r = string(result) - session.Daemon = r - globals.Arguments["--daemon-address"] = r - return -} - -// Set the daemon endpoint setting to the local Graviton tree -func setDaemon(s string) (err error) { - StoreValue("settings", []byte("endpoint"), []byte(s)) - globals.Arguments["--daemon-address"] = s - session.Daemon = s - return -} - -// Get Cyberdeck endpoint setting from the local Graviton tree -func getCyberdeck(key string) (r string) { - switch key { - case "RPC": - key = "port.RPC" - case "WS": - key = "port.WS" - case "EPOCH": - key = "port.EPOCH" - default: - return - } - - stored, err := GetEncryptedValue("Cyberdeck", []byte(key)) - if err != nil { - logger.Debugf("[Engram] getCyberdeck %s: %s\n", key, err) - return - } - - return string(stored) -} - -// Set Cyberdeck endpoint setting to the local Graviton tree -func setCyberdeck(port, key string) { - switch key { - case "RPC": - key = "port.RPC" - case "WS": - key = "port.WS" - case "EPOCH": - key = "port.EPOCH" - default: - logger.Debugf("[Engram] setCyberdeck: invalid key\n") - return - } - - err := StoreEncryptedValue("Cyberdeck", []byte(key), []byte(port)) - if err != nil { - logger.Debugf("[Engram] setCyberdeck %s: %s\n", key, err) - } -} - -// Get mode (online, offline) setting from local Graviton tree -func getMode() { - - /* - if globals.Arguments["--offline"].(bool) == true { - session.Mode = "Offline" - return - } - - s := "mode" - t := "settings" - key := []byte(s) - result, err := GetValue(t, key) - if err != nil { - session.Mode = "Online" - err := setMode("Online") - globals.Arguments["--offline"] = false - if err != nil { - fmt.Printf("[Engram] Error: %s\n", err) - return - } - } else { - if result == nil { - session.Mode = "Online" - err := setMode("Online") - globals.Arguments["--offline"] = false - if err != nil { - fmt.Printf("[Engram] Error: %s\n", err) - return - } - } else { - if string(result) == "Offline" { - globals.Arguments["--offline"] = true - session.Mode = "Offline" - } else { - globals.Arguments["--offline"] = false - session.Mode = "Online" - } - } - } - */ -} - -// Set the default Offline Mode settings to the local Graviton tree -/* -func setMode(s string) (err error) { - err = StoreValue("settings", []byte("mode"), []byte(s)) - if s == "Offline" { - globals.Arguments["--offline"] = true - } else { - globals.Arguments["--offline"] = false - } - return -} -*/ - -// Get the default Gnomon settings from local Graviton tree -func getGnomon() (r string, err error) { - v, err := GetValue("settings", []byte("gnomon")) - if err != nil { - gnomon.Active = 1 - if gnomon.Index != nil { - gnomon.Index.Endpoint = getDaemon() - } - StoreValue("settings", []byte("gnomon"), []byte("1")) - } - - if string(v) == "1" { - gnomon.Active = 1 - if gnomon.Index != nil { - gnomon.Index.Endpoint = getDaemon() - } - } else { - gnomon.Active = 0 - } - - r = string(v) - return -} - -// Set the default Gnomon settings to the local Graviton tree -func setGnomon(s string) (err error) { - if s == "1" { - err = StoreValue("settings", []byte("gnomon"), []byte("1")) - gnomon.Active = 1 - if gnomon.Index != nil { - gnomon.Index.Endpoint = getDaemon() - } - } else { - err = StoreValue("settings", []byte("gnomon"), []byte("0")) - gnomon.Active = 0 - } - return -} - -/* -func getAuthMode() (result string, err error) { - r, err := GetValue("settings", []byte("auth_mode")) - if err != nil { - StoreValue("settings", []byte("auth_mode"), []byte("true")) - cyberdeck.mode = 1 - result = "true" - } else { - result = string(r) - if string(result) == "true" { - cyberdeck.mode = 1 - result = "true" - } else { - cyberdeck.mode = 0 - result = "false" - } - } - return -} -*/ - -// Get the auth_mode settings from local Graviton tree -func setAuthMode(s string) { - if s == "true" { - StoreValue("settings", []byte("auth_mode"), []byte("true")) - } else { - StoreValue("settings", []byte("auth_mode"), []byte("false")) - } -} - -// Check if a URL exists in the string -func getTextURL(s string) (result []string) { - return xurls.Relaxed().FindAllString(s, -1) -} - -// Set the window size from provided height and width -func resizeWindow(width float32, height float32) { - s := fyne.NewSize(width, height) - session.Window.Resize(s) -} - -// Close the active wallet -func closeWallet() { - showLoadingOverlay() - - if engram.Disk != nil { - logger.Printf("[Engram] Shutting down wallet services...\n") - stopEPOCH() - engram.Disk.SetOfflineMode() - engram.Disk.Save_Wallet() - - globals.Exit_In_Progress = true - engram.Disk.Close_Encrypted_Wallet() - session.WalletOpen = false - session.Domain = "app.main" - session.BalanceUSD = "" - session.LastBalance = 0 - engram.Disk = nil - tx = Transfers{} - - if gnomon.Index != nil { - logger.Printf("[Gnomon] Shutting down indexers...\n") - stopGnomon() - } - - if cyberdeck.RPC.server != nil { - cyberdeck.RPC.server.RPCServer_Stop() - cyberdeck.RPC.server = nil - logger.Printf("[Engram] Cyberdeck RPC closed.\n") - } - - if cyberdeck.WS.server != nil { - cyberdeck.WS.server.Stop() - cyberdeck.WS.server = nil - cyberdeck.WS.apps = nil - cyberdeck.WS.list = nil - logger.Printf("[Engram] Cyberdeck XSWD closed.\n") - } - cyberdeck.WS.advanced = false - cyberdeck.WS.global.enabled = false - cyberdeck.WS.global.connect = false - - tela.ShutdownTELA() - - if rpc_client.WS != nil { - rpc_client.WS.Close() - rpc_client.WS = nil - logger.Printf("[Engram] Websocket client closed.\n") - } - - if rpc_client.RPC != nil { - rpc_client.RPC.Close() - rpc_client.RPC = nil - logger.Printf("[Engram] RPC client closed.\n") - } - - session.Path = "" - session.Name = "" - - session.LastDomain = layoutMain() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMain()) - removeOverlays() - //session.Window.CenterOnScreen() - logger.Printf("[Engram] Wallet saved and closed successfully.\n") - return - } -} - -// Create a new account and wallet file -func create() (address string, seed string, err error) { - check := findAccount() - - if session.Path == "" { - session.Error = "Please enter an account name." - } else if session.Language == -1 { - session.Error = "Please select a language." - } else if session.Password == "" { - session.Error = "Please enter a password." - } else if session.PasswordConfirm == "" { - session.Error = "Please confirm your password." - } else if session.PasswordConfirm != session.Password { - session.Error = "Passwords do not match." - } else if check { - session.Error = "Account name already exists." - } else { - engram.Disk, err = walletapi.Create_Encrypted_Wallet_Random(session.Path, session.Password) - - if err != nil { - session.Language = -1 - session.Name = "" - session.Path = "" - session.Password = "" - session.PasswordConfirm = "" - session.Error = "Account could not be created." - } else { - switch session.Network { - case NETWORK_TESTNET: - engram.Disk.SetNetwork(false) - globals.Arguments["--testnet"] = true - globals.Arguments["--simulator"] = false - case NETWORK_SIMULATOR: - engram.Disk.SetNetwork(false) - globals.Arguments["--testnet"] = true - globals.Arguments["--simulator"] = true - default: - engram.Disk.SetNetwork(true) - globals.Arguments["--testnet"] = false - globals.Arguments["--simulator"] = false - } - - languages := mnemonics.Language_List() - - if session.Language < 0 || session.Language > len(languages)-1 { - session.Language = 0 // English - } - - engram.Disk.SetSeedLanguage(languages[session.Language]) - address = engram.Disk.GetAddress().String() - seed = engram.Disk.GetSeed() - engram.Disk.Close_Encrypted_Wallet() - engram.Disk = nil - session.Error = "Account successfully created." - session.Language = -1 - session.Name = "" - session.Path = "" - session.Password = "" - session.PasswordConfirm = "" - session.Domain = "app.main" - } - } - return -} - -// The main login routine -func login() { - showLoadingOverlay() - - if engram.Disk == nil { - temp, err := walletapi.Open_Encrypted_Wallet(session.Path, session.Password) - if err != nil { - session.Domain = "app.main" - session.Error = err.Error() - if len(session.Error) > 40 { - session.Error = fmt.Sprintf("%s...", session.Error[0:40]) - } - session.Window.Canvas().Content().Refresh() - removeOverlays() - return - } - - engram.Disk = temp - session.Password = "" - } - - switch session.Network { - case NETWORK_TESTNET: - engram.Disk.SetNetwork(false) - globals.Arguments["--testnet"] = true - globals.Arguments["--simulator"] = false - case NETWORK_SIMULATOR: - engram.Disk.SetNetwork(false) - globals.Arguments["--testnet"] = true - globals.Arguments["--simulator"] = true - default: - engram.Disk.SetNetwork(true) - globals.Arguments["--testnet"] = false - globals.Arguments["--simulator"] = false - } - - session.WalletOpen = true - session.BalanceUSD = "" - session.LastBalance = 0 - - if !session.Offline { - walletapi.SetDaemonAddress(session.Daemon) - engram.Disk.SetDaemonAddress(session.Daemon) - - if session.TrackRecentBlocks > 0 { - logger.Printf("[Engram] Scan tracking enabled, only scanning the last %d blocks...\n", session.TrackRecentBlocks) - engram.Disk.SetTrackRecentBlocks(session.TrackRecentBlocks) - } - - if s, err := strconv.Atoi(getCyberdeck("EPOCH")); err == nil { - if err := epoch.SetPort(s); err != nil { - logger.Errorf("[Engram] Setting EPOCH port: %s\n", err) - } - } - - cyberdeck.EPOCH.total.Hashes = 0 - cyberdeck.EPOCH.total.MiniBlocks = 0 - if epochData, err := GetEncryptedValue("Cyberdeck", []byte("EPOCH")); err == nil { - if err := json.Unmarshal(epochData, &cyberdeck.EPOCH.total); err != nil { - logger.Errorf("[Engram] Setting EPOCH total: %s\n", err) - } - } - - go StartPulse() - } else { - engram.Disk.SetOfflineMode() - status.Connection.FillColor = colors.Gray - status.Connection.Refresh() - status.Sync.FillColor = colors.Gray - status.Sync.Refresh() - } - - setRingSize(engram.Disk, 16) - session.Verified = false - - if !session.Offline { - // Online mode - status.Connection.FillColor = colors.Green - status.Connection.Refresh() - session.Balance = 0 - - count := 0 - for count < 5 { - if !walletapi.Connected { - count += 1 - time.Sleep(time.Second) - } else { - break - } - } - - if !walletapi.Connected { - closeWallet() - session.Window.SetContent(layoutAlert(1)) - removeOverlays() - return - } - - if engram.Disk.Get_Height() < session.DaemonHeight { - time.Sleep(time.Second * 1) - } - - for i := 0; i < 10; i++ { - height := engram.Disk.Get_Registration_TopoHeight() - - if height < 1 { - time.Sleep(time.Second * 1) - } else { - break - } - - if i == 9 { - registerAccount() - removeOverlays() - session.Verified = true - logger.Printf("[Registration] Account registration PoW started...\n") - logger.Printf("[Registration] Registering your account. This can take up to 120 minutes (one time). Please wait...\n") - return - } - } - - go startGnomon() - } - - if a.Driver().Device().IsMobile() { - session.Domain = "app.wallet" - resizeWindow(ui.MaxWidth, ui.MaxHeight) - } - - session.Window.SetContent(layoutDashboard()) - removeOverlays() - - session.Balance, _ = engram.Disk.Get_Balance() - session.BalanceText.Text = globals.FormatMoney(session.Balance) - session.BalanceText.Refresh() - - session.WalletHeight = engram.Disk.Wallet_Memory.Get_Height() - session.DaemonHeight = engram.Disk.Get_Daemon_Height() - session.StatusText.Text = fmt.Sprintf("%d", session.WalletHeight) - session.StatusText.Refresh() - - if session.WalletHeight == session.DaemonHeight && !session.Offline { - status.Sync.FillColor = colors.Green - status.Sync.Refresh() - } - - address := engram.Disk.GetAddress().String() - shard := fmt.Sprintf("%x", sha1.Sum([]byte(address))) - session.ID = shard - session.LimitMessages = true -} - -// Remove all overlays -func removeOverlays() { - overlays := session.Window.Canvas().Overlays() - list := overlays.List() - - for o := range list { - overlays.Remove(list[o]) - } - - if res.loading != nil { - res.loading.Hide() - res.loading.Stop() - res.loading = nil - } -} - -// Add an overlay with the loading animation -func showLoadingOverlay() { - frame := &iframe{} - - if res.loading == nil { - res.loading, _ = x.NewAnimatedGifFromResource(resourceLoadingGif) - res.loading.SetMinSize(fyne.NewSize(ui.Width*0.45, ui.Width*0.45)) - } - - rect := canvas.NewRectangle(colors.DarkMatter) - rect.SetMinSize(frame.Size()) - - background := container.NewStack( - rect, - container.NewCenter( - res.loading, - ), - ) - - res.loading.Start() - - layout := container.NewStack( - frame, - background, - ) - - overlays := session.Window.Canvas().Overlays() - overlays.Add(layout) -} - -// Load embedded resources -func loadResources() { - res.bg = canvas.NewImageFromResource(resourceBgPng) - res.bg.FillMode = canvas.ImageFillContain - - res.bg2 = canvas.NewImageFromResource(resourceBg2Png) - res.bg2.FillMode = canvas.ImageFillContain - - res.icon = canvas.NewImageFromResource(resourceIconPng) - res.icon.FillMode = canvas.ImageFillContain - - res.header = canvas.NewImageFromResource(resourceBackground1Png) - res.header.FillMode = canvas.ImageFillContain - - res.load = canvas.NewImageFromResource(resourceLoadPng) - res.load.FillMode = canvas.ImageFillStretch - - res.dero = canvas.NewImageFromResource(resourceDeroPng) - res.dero.FillMode = canvas.ImageFillContain - - res.gram = canvas.NewImageFromResource(resourceGramPng) - res.gram.FillMode = canvas.ImageFillContain - - res.block = canvas.NewImageFromResource(resourceBlankPng) - res.block.FillMode = canvas.ImageFillContain - - res.red_alert = canvas.NewImageFromResource(resourceRedAlertPng) - res.red_alert.FillMode = canvas.ImageFillContain - - res.green_alert = canvas.NewImageFromResource(resourceGreenAlertPng) - res.green_alert.FillMode = canvas.ImageFillContain - - res.mainBg = canvas.NewImageFromResource(resourceEngramMainPng) - res.mainBg.FillMode = canvas.ImageFillContain - -} - -// Validate if the provided word is a seed word -func checkSeedWord(w string) (check bool) { - split := strings.Split(w, " ") - - if len(split) > 1 { - return - } - _, _, _, check = mnemonics.Find_indices([]string{w}) - - return -} - -// Add a DERO transfer to the batch -func addTransfer() error { - var arguments = rpc.Arguments{} - var err error - - logger.Printf("[Send] Starting tx...\n") - if tx.Address.IsIntegratedAddress() { - if tx.Address.Arguments.Validate_Arguments() != nil { - logger.Errorf("[Service] Integrated Address arguments could not be validated\n") - err = errors.New("integrated address arguments could not be validated") - return err - } - - logger.Printf("[Send] Not Integrated..\n") - if !tx.Address.Arguments.Has(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { - logger.Errorf("[Service] Integrated Address does not contain destination port\n") - err = errors.New("integrated address does not contain destination port") - return err - } - - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: tx.Address.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)}) - logger.Printf("[Send] Added arguments..\n") - - if tx.Address.Arguments.Has(rpc.RPC_EXPIRY, rpc.DataTime) { - - if tx.Address.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime).(time.Time).Before(time.Now().UTC()) { - logger.Errorf("[Service] This address has expired: %s\n", tx.Address.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) - err = errors.New("this address has expired") - return err - } else { - logger.Warnf("[Service] This address will expire: %s\n", tx.Address.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) - } - } - - logger.Printf("[Service] Destination port is integrated in address: %d\n", tx.Address.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)) - - if tx.Address.Arguments.Has(rpc.RPC_COMMENT, rpc.DataString) { - logger.Printf("[Service] Integrated Message: %s\n", tx.Address.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)) - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: tx.Address.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)}) - } - } - - logger.Printf("[Send] Checking arguments..\n") - - for _, arg := range tx.Address.Arguments { - if !(arg.Name == rpc.RPC_COMMENT || arg.Name == rpc.RPC_EXPIRY || arg.Name == rpc.RPC_DESTINATION_PORT || arg.Name == rpc.RPC_SOURCE_PORT || arg.Name == rpc.RPC_VALUE_TRANSFER || arg.Name == rpc.RPC_NEEDS_REPLYBACK_ADDRESS) { - switch arg.DataType { - case rpc.DataString: - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) - case rpc.DataInt64: - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) - case rpc.DataUint64: - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) - case rpc.DataFloat64: - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) - case rpc.DataTime: - logger.Warnf("[Service] Time currently not supported.\n") - } - } - } - - logger.Printf("[Send] Checking Amount..\n") - - if tx.Address.Arguments.Has(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { - logger.Printf("[Service] Transaction amount: %s\n", globals.FormatMoney(tx.Address.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64))) - tx.Amount = tx.Address.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64) - } else { - balance, _ := engram.Disk.Get_Balance() - logger.Printf("[Send] Balance: %d\n", balance) - logger.Printf("[Send] Amount: %d\n", tx.Amount) - - if tx.Amount > balance { - logger.Errorf("[Send] Error: Insufficient funds\n") - err = errors.New("insufficient funds") - return err - } else if tx.Amount == balance { - tx.SendAll = true - } else { - tx.SendAll = false - } - } - - logger.Printf("[Send] Checking services..\n") - - if tx.Address.Arguments.Has(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataUint64) { - logger.Printf("[Service] Reply Address required, sending: %s\n", engram.Disk.GetAddress().String()) - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_REPLYBACK_ADDRESS, DataType: rpc.DataAddress, Value: engram.Disk.GetAddress()}) - } - - logger.Printf("[Send] Checking payment ID/destination port..\n") - - if len(arguments) == 0 { - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: tx.PaymentID}) - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: tx.Comment}) - } - - logger.Printf("[Send] Checking Pack..\n") - - if _, err := arguments.CheckPack(transaction.PAYLOAD0_LIMIT); err != nil { - logger.Errorf("[Send] Arguments packing err: %s\n", err) - return err - } - - if tx.Ringsize == 0 { - tx.Ringsize = 2 - } else if tx.Ringsize > 128 { - tx.Ringsize = 128 - } else if !crypto.IsPowerOf2(int(tx.Ringsize)) { - tx.Ringsize = 2 - logger.Errorf("[Send] Error: Invalid ringsize - New ringsize = %d\n", tx.Ringsize) - err = errors.New("invalid ringsize") - return err - } - - tx.Status = "Unsent" - - logger.Printf("[Send] Ringsize: %d\n", tx.Ringsize) - - tx.Pending = append(tx.Pending, rpc.Transfer{Amount: tx.Amount, Destination: tx.Address.String(), Payload_RPC: arguments}) - logger.Printf("[Send] Added transfer to the pending list.\n") - - return nil -} - -// Send all batched transfers (TODO: export offline transactions to file in Offline mode) -func sendTransfers() (txid crypto.Hash, err error) { - if session.Offline { - return - } - - fees := ((tx.Ringsize + 1) * config.FEE_PER_KB) / 4 - if fees < 85 { - fees = 85 - } - - logger.Printf("[Send] Calculated Fees: %d\n", fees*uint64(len(tx.Pending))) - - tx.TX, err = engram.Disk.TransferPayload0(tx.Pending, tx.Ringsize, false, rpc.Arguments{}, fees, false) - if err != nil { - logger.Errorf("[Send] Error while building transaction: %s\n", err) - return - } - - if err = engram.Disk.SendTransaction(tx.TX); err != nil { - logger.Errorf("[Send] Error while dispatching transaction: %s\n", err) - return - } - - tx.Fees = tx.TX.Fees() - tx.TXID = tx.TX.GetHash() - - logger.Printf("[Send] Dispatched transaction: %s\n", tx.TXID) - - txid = tx.TX.GetHash() - - tx = Transfers{} - - return -} - -// Go Routine for account registration -func registerAccount() { - session.Domain = "app.register" - if engram.Disk == nil { - resizeWindow(ui.MaxWidth, ui.MaxHeight) - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMain()) - session.Domain = "app.main" - return - } - - link := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - link.OnTapped = func() { - session.Gif.Stop() - session.Gif = nil - closeWallet() - } - - title := canvas.NewText("R E G I S T R A T I O N", colors.Green) - title.TextStyle = fyne.TextStyle{Bold: true} - title.TextSize = 16 - - heading := canvas.NewText("Please wait...", colors.Gray) - heading.TextSize = 22 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - sub := canvas.NewText("This one-time process can take a while.", colors.Gray) - sub.TextSize = 14 - sub.Alignment = fyne.TextAlignCenter - sub.TextStyle = fyne.TextStyle{Bold: true} - - resizeWindow(ui.MaxWidth, ui.MaxHeight) - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutWaiting(title, heading, sub, link)) - - // Registration PoW - go func() { - var reg_tx *transaction.Transaction - successful_regs := make(chan *transaction.Transaction) - counter := 0 - session.RegHashes = 0 - - for i := 0; i < runtime.GOMAXPROCS(0)-1; i++ { - go func() { - for counter == 0 { - if engram.Disk == nil { - break - } else if engram.Disk.IsRegistered() { - break - } - - lreg_tx := engram.Disk.GetRegistrationTX() - hash := lreg_tx.GetHash() - session.RegHashes++ - - if hash[0] == 0 && hash[1] == 0 && hash[2] == 0 { - successful_regs <- lreg_tx - counter++ - break - } - } - }() - } - - if engram.Disk == nil { - session.Gif.Stop() - session.Gif = nil - - fyne.Do(func() { - resizeWindow(ui.MaxWidth, ui.MaxHeight) - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMain()) - }) - - session.Domain = "app.main" - return - } - - reg_tx = <-successful_regs - - logger.Printf("[Registration] Registration TXID: %s\n", reg_tx.GetHash()) - err := engram.Disk.SendTransaction(reg_tx) - if err != nil { - session.Gif.Stop() - session.Gif = nil - logger.Errorf("[Registration] Error: %s\n", err) - - fyne.Do(func() { - resizeWindow(ui.MaxWidth, ui.MaxHeight) - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMain()) - }) - - session.Domain = "app.main" - } else { - session.Gif.Stop() - session.Gif = nil - logger.Printf("[Registration] Registration transaction dispatched successfully.\n") - session.Domain = "app.wallet" - - fyne.Do(func() { - resizeWindow(ui.MaxWidth, ui.MaxHeight) - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - }) - } - }() -} - -// Set the ring size for transactions -func setRingSize(wallet *walletapi.Wallet_Disk, s int) bool { - if wallet == nil { - logger.Errorf("[Engram] No wallet found.\n") - return false - } - - // Minimum ring size is 2, only accept powers of 2. - if s < 2 { - wallet.SetRingSize(2) - logger.Printf("[Engram] Set minimum ring size: 2\n") - } else { - wallet.SetRingSize(s) - logger.Printf("[Engram] Set default ring size: %d\n", s) - } - - return true -} - -// Check if a username exists, return the registered address if so -func checkUsername(s string, h int64) (address string, err error) { - if session.Offline { - return - } - - if h < 0 { - address, err = engram.Disk.NameToAddress(s) - } else { - var params rpc.NameToAddress_Params - var response *jrpc2.Response - var result rpc.NameToAddress_Result - - rpc_client.WS, _, err = websocket.DefaultDialer.Dial("ws://"+session.Daemon+"/ws", nil) - if err != nil { - return - } - - input_output := rwc.New(rpc_client.WS) - rpc_client.RPC = jrpc2.NewClient(channel.RawJSON(input_output, input_output), nil) - - if rpc_client.RPC != nil { - params.Name = s - params.TopoHeight = h - - address = "" - response, err = rpc_client.RPC.Call(context.Background(), "DERO.NameToAddress", params) - - rpc_client.WS.Close() - rpc_client.RPC.Close() - - if err != nil { - return - } - - err = response.UnmarshalResult(&result) - if err != nil { - return - } - - if result.Status != "OK" { - err = errors.New("username does not exist") - return - } - - address = result.Address - } - } - - return -} - -// Get the transaction fees to be paid -func getGasEstimate(gp rpc.GasEstimate_Params) (gas uint64, err error) { - var result rpc.GasEstimate_Result - - rpc_client.WS, _, err = websocket.DefaultDialer.Dial("ws://"+session.Daemon+"/ws", nil) - if err != nil { - return - } - - input_output := rwc.New(rpc_client.WS) - rpc_client.RPC = jrpc2.NewClient(channel.RawJSON(input_output, input_output), nil) - - if err = rpc_client.RPC.CallResult(context.Background(), "DERO.GetGasEstimate", gp, &result); err != nil { - return - } - - if result.Status != "OK" { - return - } - - gas = result.GasStorage - - return -} - -// Register a new DERO username -func registerUsername(s string) (storage uint64, err error) { - // Check first if the name is taken - valid, _ := checkUsername(s, -1) - if valid != "" { - logger.Errorf("[Username] Error: skipping registration - username exists.\n") - err = errors.New("username already exists") - return - } - - scid := crypto.HashHexToHash("0000000000000000000000000000000000000000000000000000000000000001") - - var args = rpc.Arguments{} - args = append(args, rpc.Argument{Name: "entrypoint", DataType: "S", Value: "Register"}) - args = append(args, rpc.Argument{Name: "SC_ID", DataType: "H", Value: scid}) - args = append(args, rpc.Argument{Name: "SC_ACTION", DataType: "U", Value: uint64(rpc.SC_CALL)}) - args = append(args, rpc.Argument{Name: "name", DataType: "S", Value: s}) - - var p rpc.Transfer_Params - var dest string - - switch session.Network { - case NETWORK_MAINNET: - dest = "dero1qykyta6ntpd27nl0yq4xtzaf4ls6p5e9pqu0k2x4x3pqq5xavjsdxqgny8270" - case NETWORK_SIMULATOR: - dest = "deto1qyvyeyzrcm2fzf6kyq7egkes2ufgny5xn77y6typhfx9s7w3mvyd5qqynr5hx" - default: - dest = "deto1qy0ehnqjpr0wxqnknyc66du2fsxyktppkr8m8e6jvplp954klfjz2qqdzcd8p" - } - p.Transfers = append(p.Transfers, rpc.Transfer{ - Destination: dest, - Amount: 0, - Burn: 0, - }) - - gp := rpc.GasEstimate_Params{SC_RPC: args, Ringsize: 2, Signer: engram.Disk.GetAddress().String(), Transfers: p.Transfers} - - storage, err = getGasEstimate(gp) - if err != nil { - logger.Errorf("[Username] Error estimating fees: %s\n", err) - return - } - - tx, err := engram.Disk.TransferPayload0(p.Transfers, 2, false, args, storage, false) - if err != nil { - logger.Errorf("[Username] Error while building transaction: %s\n", err) - return - } - - err = engram.Disk.SendTransaction(tx) - if err != nil { - logger.Errorf("[Username] Error while dispatching transaction: %s\n", err) - return - } - - logger.Printf("[Username] Username Registration TXID: %s\n", tx.GetHash().String()) - - return -} - -// Check to make sure the message transaction meets criteria -func checkMessagePack(m string, s string, r string) (err error) { - if m == "" { - return - } - - mapAddress := "" - a, err := globals.ParseValidateAddress(r) - if err != nil { - //mapAddress, err = engram.Disk.NameToAddress(r) - mapAddress, err = checkUsername(r, -1) - if err != nil { - return - } - a, err = globals.ParseValidateAddress(mapAddress) - if err != nil { - return - } - } - - if s == "" { - s = engram.Disk.GetAddress().String() - } - - var amount uint64 - - if a.Arguments.Has(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { // but only it is present - //logger.Info("Transaction", "Value", globals.FormatMoney(a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64))) - amount = a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64) - } else { - amount, err = globals.ParseAmount("0.00001") - if err != nil { - //logger.Error(err, "Err parsing amount\n") - return - } - } - - var arguments = rpc.Arguments{ - {Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: uint64(1337)}, - {Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: amount}, - {Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: m}, - {Name: rpc.RPC_NEEDS_REPLYBACK_ADDRESS, DataType: rpc.DataString, Value: s}, - } - - if a.IsIntegratedAddress() { // read everything from the address - if a.Arguments.Validate_Arguments() != nil { - //fmt.Printf(err, "Integrated Address arguments could not be validated.\n") - return - } - - if !a.Arguments.Has(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { // but only it is present - //fmt.Printf(fmt.Errorf("Integrated Address does not contain destination port.\n"), "") - return - } - - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)}) - - if a.Arguments.Has(rpc.RPC_EXPIRY, rpc.DataTime) { // but only it is present - if a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime).(time.Time).Before(time.Now().UTC()) { - //fmt.Printf(nil, "This address has expired.", "expiry time", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) - return - } - } - - logger.Printf("Destination port is integrated in address. %d\n", a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)) - - if a.Arguments.Has(rpc.RPC_COMMENT, rpc.DataString) { // but only it is present - logger.Printf("Integrated Message: %s\n", a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)) - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)}) - } - } - - for _, arg := range arguments { - if !(arg.Name == rpc.RPC_COMMENT || arg.Name == rpc.RPC_EXPIRY || arg.Name == rpc.RPC_DESTINATION_PORT || arg.Name == rpc.RPC_SOURCE_PORT || arg.Name == rpc.RPC_VALUE_TRANSFER || arg.Name == rpc.RPC_NEEDS_REPLYBACK_ADDRESS) { - switch arg.DataType { - case rpc.DataString: - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) - case rpc.DataInt64: - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) - case rpc.DataUint64: - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) - case rpc.DataFloat64: - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) - case rpc.DataTime: - logger.Warnf("[Service] Time currently not supported.\n") - } - } - } - - if a.Arguments.Has(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_NEEDS_REPLYBACK_ADDRESS, DataType: rpc.DataString, Value: s}) - } - - // if no arguments, use space by embedding a small comment - if len(arguments) == 0 { // allow user to enter Comment - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: uint64(1337)}) - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: m}) - } - - if _, err = arguments.CheckPack(transaction.PAYLOAD0_LIMIT); err != nil { - logger.Errorf("[Message] Arguments packing err: %s\n", err) - return - } - - return -} - -// Send a private message to another account -func sendMessage(m string, s string, r string) (txid crypto.Hash, err error) { - if m == "" { - return - } - - mapAddress := "" - a, err := globals.ParseValidateAddress(r) - if err != nil { - //mapAddress, err = engram.Disk.NameToAddress(r) - mapAddress, err = checkUsername(r, -1) - if err != nil { - return - } - a, err = globals.ParseValidateAddress(mapAddress) - if err != nil { - return - } - } - - if s == "" { - s = engram.Disk.GetAddress().String() - } - - amount, err := globals.ParseAmount("0.00001") - if err != nil { - //logger.Error(err, "Err parsing amount\n") - return - } - - var arguments = rpc.Arguments{ - {Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: uint64(1337)}, - {Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: amount}, - {Name: rpc.RPC_EXPIRY, DataType: rpc.DataTime, Value: time.Now().Add(time.Hour).UTC()}, - {Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: m}, - {Name: rpc.RPC_NEEDS_REPLYBACK_ADDRESS, DataType: rpc.DataString, Value: s}, - } - - if a.IsIntegratedAddress() { - if a.Arguments.Validate_Arguments() != nil { - return - } - - if !a.Arguments.Has(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { - logger.Errorf("[Send Message] Integrated Address does not contain destination port.\n") - return - } - - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)}) - - if a.Arguments.Has(rpc.RPC_EXPIRY, rpc.DataTime) { - if a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime).(time.Time).Before(time.Now().UTC()) { - logger.Errorf("[Send Message] This address has expired on %x\n", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) - return - } else { - logger.Warnf("[Send Message] This address will expire on %x\n", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) - } - } - - logger.Printf("[Send Message] Destination port is integrated in address. %x\n", a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)) - - if a.Arguments.Has(rpc.RPC_COMMENT, rpc.DataString) { - logger.Printf("[Send Message] Integrated Message: %s\n", a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)) - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)}) - } - } - - for _, arg := range arguments { - if !(arg.Name == rpc.RPC_COMMENT || arg.Name == rpc.RPC_EXPIRY || arg.Name == rpc.RPC_DESTINATION_PORT || arg.Name == rpc.RPC_SOURCE_PORT || arg.Name == rpc.RPC_VALUE_TRANSFER || arg.Name == rpc.RPC_NEEDS_REPLYBACK_ADDRESS) { - switch arg.DataType { - case rpc.DataString: - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) - case rpc.DataInt64: - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) - case rpc.DataUint64: - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) - case rpc.DataFloat64: - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) - case rpc.DataTime: - logger.Warnf("[Service] Time currently not supported.\n") - } - } - } - - if a.Arguments.Has(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { - amount = a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64) - } else { - amount, err = globals.ParseAmount("0.00001") - if err != nil { - logger.Errorf("[Send Message] Failed parsing transfer amount: %s\n", err) - return - } - } - - if a.Arguments.Has(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_NEEDS_REPLYBACK_ADDRESS, DataType: rpc.DataString, Value: s}) - } - - if len(arguments) == 0 { - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: uint64(1337)}) - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: m}) - } - - if _, err = arguments.CheckPack(transaction.PAYLOAD0_LIMIT); err != nil { - logger.Errorf("[Message] Arguments packing err: %s\n", err) - return - } - - fees := ((uint64(engram.Disk.GetRingSize()) + 1) * config.FEE_PER_KB) / 4 - - logger.Printf("[Message] Calculated Fees: %d\n", fees) - - transfer := rpc.Transfer{Amount: amount, Destination: a.String(), Payload_RPC: arguments} - - tx, err := engram.Disk.TransferPayload0([]rpc.Transfer{transfer}, 0, false, rpc.Arguments{}, fees, false) - if err != nil { - logger.Errorf("[Message] Error while building transaction: %s\n", err) - return - } - - if err = engram.Disk.SendTransaction(tx); err != nil { - logger.Errorf("[Message] Error while dispatching transaction: %s\n", err) - return - } - - txid = tx.GetHash() - - logger.Printf("[Message] Dispatched transaction: %s\n", txid) - - return -} - -// Get a list of message transactions from an address -func getMessagesFromUser(s string, h uint64) (result []rpc.Entry) { - var zeroscid crypto.Hash - if s == "" { - return - } - - messages := engram.Disk.Get_Payments_DestinationPort(zeroscid, uint64(1337), h) - - for m := range messages { - var username bool - var username2 bool - - txid := messages[m].TXID - _, tx := engram.Disk.Get_Payments_TXID(zeroscid, txid) - - //check, err := engram.Disk.NameToAddress(s) - check, err := checkUsername(s, -1) - if err != nil { - username = false - } else { - username = true - } - - if tx.Incoming { - if tx.Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { - height := int64(tx.Height) - check2, err := checkUsername(tx.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string), height) - if err != nil { - username2 = false - addr, err := globals.ParseValidateAddress(tx.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string)) - if err != nil { - check2 = "" - } else { - check2 = addr.String() - } - } else { - username2 = true - } - - // Check for spoofing - //if ring_member_exists(txid, check2) { - - if username && username2 { - if check == check2 { - result = append(result, messages[m]) - } - } else if !username && !username2 { - if s == tx.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) { - result = append(result, messages[m]) - } - } else if check == tx.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) { - result = append(result, messages[m]) - } else if s == check2 { - result = append(result, messages[m]) - } - //} - } - } else { - //addr, err := engram.Disk.NameToAddress(s) - addr, err := checkUsername(s, -1) - if err != nil { - if tx.Destination == s { - result = append(result, messages[m]) - } - } else { - if tx.Destination == addr { - result = append(result, messages[m]) - } - } - } - } - - return -} - -// Get a list of all message transactions and sort them by address -func getMessages(h uint64) (result []string) { - var zeroscid crypto.Hash - messages := engram.Disk.Get_Payments_DestinationPort(zeroscid, uint64(1337), h) - - for m := range messages { - if messages[m].Incoming { - if messages[m].Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { - if messages[m].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) == "" { - - } else { - height := int64(messages[m].Height) - sender, _ := checkUsername(messages[m].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string), height) - if sender == "" { - addr, err := globals.ParseValidateAddress(messages[m].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string)) - if err != nil { - - } else { - sender = addr.String() - for r := range result { - if r > -1 && r < len(result) { - if strings.Contains(result[r], sender+"~~~") { - copy(result[r:], result[r+1:]) - result[len(result)-1] = "" - result = result[:len(result)-1] - } - } - } - result = append(result, sender+"~~~") - } - } else { - // Check for spoofing - //if ring_member_exists(messages[m].TXID, sender) { - for r := range result { - if r > -1 && r < len(result) { - //if strings.Contains(result[r], sender+"```"+messages[m].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string)) { - if strings.Contains(result[r], sender+"~~~") { - copy(result[r:], result[r+1:]) - result[len(result)-1] = "" - result = result[:len(result)-1] - } - } - } - result = append(result, sender+"~~~"+messages[m].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string)) - //} else { - // TODO: Add spoofing address to the ban list? - //} - } - } - } - } else { - if messages[m].Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { - uname := "" - for r := range result { - if r > -1 && r < len(result) { - if strings.Contains(result[r], messages[m].Destination+"~~~") { - split := strings.Split(result[r], "~~~") - uname = split[1] - copy(result[r:], result[r+1:]) - result[len(result)-1] = "" - result = result[:len(result)-1] - } - } - } - result = append(result, messages[m].Destination+"~~~"+uname) - } - } - } - - sort.Sort(sort.Reverse(sort.StringSlice(result))) - return -} - -// Returns a list of registered usernames from Gnomon -func queryUsernames(address string) (result []string, err error) { - if gnomon.Index != nil && engram.Disk != nil { - result, _ = gnomon.Graviton.GetSCIDKeysByValue("0000000000000000000000000000000000000000000000000000000000000001", address, engram.Disk.Get_Daemon_TopoHeight(), false) - if len(result) <= 0 { - result, _, err = gnomon.Index.GetSCIDKeysByValue(nil, "0000000000000000000000000000000000000000000000000000000000000001", address, engram.Disk.Get_Daemon_TopoHeight()) - if err != nil { - logger.Errorf("[Gnomon] Querying usernames failed: %s\n", err) - return - } - } - - sort.Strings(result) - } - - return -} - -// Get the local list of registered usernames saved from previous Gnomon scans -func getUsernames() (result []string, err error) { - usernames, err := GetEncryptedValue("Usernames", []byte("usernames")) - if err != nil { - return - } - - result = strings.Split(string(usernames), ",") - return -} - -// Set the Primary Username saved to a wallet's datashard -func setPrimaryUsername(s string) (err error) { - err = StoreEncryptedValue("settings", []byte("username"), []byte(s)) - return -} - -// Get the Primary Username saved to a wallet's datashard -func getPrimaryUsername() (err error) { - u, err := GetEncryptedValue("settings", []byte("username")) - if err != nil { - session.Username = "" - return - } - session.Username = string(u) - return -} - -// Start the Gnomon indexer -func startGnomon() { - if walletapi.Connected { - if gnomon.Index == nil && gnomon.Active == 1 { - path := filepath.Join(AppPath(), "datashards", "gnomon") - switch session.Network { - case NETWORK_TESTNET: - path = filepath.Join(AppPath(), "datashards", "gnomon_testnet") - case NETWORK_SIMULATOR: - path = filepath.Join(AppPath(), "datashards", "gnomon_simulator") - } - gnomon.BBolt, _ = storage.NewBBoltDB(path, "gnomon") - gnomon.Graviton, _ = storage.NewGravDB(path, "25ms") - term := []string(nil) - term = append(term, "Function Initialize") - height, err := gnomon.Graviton.GetLastIndexHeight() - if err != nil { - height = 0 - } - - // Fastsync Config - config := &structures.FastSyncConfig{ - Enabled: true, - SkipFSRecheck: true, - ForceFastSync: true, - ForceFastSyncDiff: 20, - NoCode: true, - } - - // exclude the Gnomon SC, etc. to keep faster sync times - var exclusions []string - - gnomon.Index = indexer.NewIndexer(gnomon.Graviton, gnomon.BBolt, "gravdb", term, height, session.Daemon, "daemon", false, false, config, exclusions) - indexer.InitLog(globals.Arguments, os.Stdout) - - // We can allow parallel processing of x blocks at a time - go gnomon.Index.StartDaemonMode(1) - - logger.Printf("[Gnomon] Scan Status: [%d / %d]\n", height, gnomon.Index.LastIndexedHeight) - } - } -} - -// Stop all indexers and close Gnomon -func stopGnomon() { - if gnomon.Index != nil { - gnomon.Index.Close() - gnomon.Index = nil - logger.Printf("[Gnomon] Closed all indexers.\n") - } -} - -// Method of Gnomon GetAllOwnersAndSCIDs() where DB type is defined by Indexer.DBType -func (g *Gnomon) GetAllOwnersAndSCIDs() (scids map[string]string) { - switch g.Index.DBType { - case "gravdb": - return g.Index.GravDBBackend.GetAllOwnersAndSCIDs() - case "boltdb": - return g.Index.BBSBackend.GetAllOwnersAndSCIDs() - default: - return - } -} - -// Method of Gnomon GetAllSCIDVariableDetails() where DB type is defined by Indexer.DBType -func (g *Gnomon) GetAllSCIDVariableDetails(scid string) (vars []*structures.SCIDVariable) { - switch g.Index.DBType { - case "gravdb": - return g.Index.GravDBBackend.GetAllSCIDVariableDetails(scid) - case "boltdb": - return g.Index.BBSBackend.GetAllSCIDVariableDetails(scid) - default: - return - } -} - -// Method of Gnomon GetSCIDValuesByKey() where DB type is defined by Indexer.DBType -func (g *Gnomon) GetSCIDValuesByKey(scid string, key interface{}) (valuesstring []string, valuesuint64 []uint64) { - switch g.Index.DBType { - case "gravdb": - return g.Index.GravDBBackend.GetSCIDValuesByKey(scid, key, g.Index.ChainHeight, true) - case "boltdb": - return g.Index.BBSBackend.GetSCIDValuesByKey(scid, key, g.Index.ChainHeight, true) - default: - return - } -} - -// Add a var store only scid to Gnomon DB -func (g *Gnomon) AddSCIDToIndex(scid string) (err error) { - add := make(map[string]*structures.FastSyncImport) - add[scid] = &structures.FastSyncImport{} - - return gnomon.Index.AddSCIDToIndex(add, false, true) -} - -// Get the current code of a smart contract -func getContractCode(scid string) (code string, err error) { - var params = rpc.GetSC_Params{SCID: scid, Variables: false, Code: true} - var result rpc.GetSC_Result - - rpc_client.WS, _, err = websocket.DefaultDialer.Dial("ws://"+session.Daemon+"/ws", nil) - if err != nil { - return - } - - input_output := rwc.New(rpc_client.WS) - rpc_client.RPC = jrpc2.NewClient(channel.RawJSON(input_output, input_output), nil) - - err = rpc_client.RPC.CallResult(context.Background(), "DERO.GetSC", params, &result) - if err != nil { - logger.Errorf("[Engram] Error getting SC code: %s\n", err) - return - } - - code = result.Code - - return -} - -// DVM starter InitializePrivate() example for smart contract builder -func dvmInitFuncExample() string { - return `Function InitializePrivate() Uint64 -10 IF EXISTS("owner") == 0 THEN GOTO 30 -20 RETURN 1 -30 STORE("owner", SIGNER()) -31 STORE("var_header_name", "") -32 STORE("var_header_description", "") -33 STORE("var_header_icon", "") -40 RETURN 0 -End Function` -} - -// DVM starter function for smart contract builder -func dvmFuncExample(increment int) string { - return `Function new` + fmt.Sprintf("%d", increment) + `() Uint64 -10 -20 -30 RETURN 0 -End Function` -} - -// Verification overlay for user actions with or without password -func verificationOverlay(password bool, headerText, subText, dismiss string, callback func(bool)) { - overlay := session.Window.Canvas().Overlays() - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - - if password { - headerText = "ACCOUNT VERIFICATION REQUIRED" - dismiss = "Submit" - } - - header := canvas.NewText(headerText, colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - btnConfirm := widget.NewButton(dismiss, nil) - btnConfirm.Disable() - - entryPassword := NewReturnEntry() - entryPassword.Password = true - entryPassword.PlaceHolder = "Password" - entryPassword.OnChanged = func(s string) { - if s == "" { - btnConfirm.Text = dismiss - btnConfirm.Disable() - btnConfirm.Refresh() - } else { - btnConfirm.Text = dismiss - btnConfirm.Enable() - btnConfirm.Refresh() - } - } - - subHeader := canvas.NewText(subText, colors.Account) - if password { - subText = "Confirm Password" - subHeader.TextSize = 22 - } else { - subHeader.TextSize = 18 - entryPassword.Hide() - btnConfirm.Enable() - btnConfirm.Refresh() - } - - subHeader.Text = subText - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - subHeader.Refresh() - - linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - callback(false) - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } - - btnConfirm.OnTapped = func() { - btnConfirm.Disable() - if password { - if engram.Disk.Check_Password(entryPassword.Text) { - callback(true) - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } else { - btnConfirm.Text = "Invalid Password..." - btnConfirm.Refresh() - } - } else { - callback(true) - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } - } - - entryPassword.OnReturn = btnConfirm.OnTapped - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - subHeader, - widget.NewLabel(""), - container.NewCenter( - container.NewStack( - span, - entryPassword, - ), - ), - rectSpacer, - rectSpacer, - btnConfirm, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - - if password { - session.Window.Canvas().Focus(entryPassword) - } -} - -// Color for the TELA likes ratio and individual rating numbers -func telaRatingColor(r uint64) color.Color { - if r > 65 { - return colors.Green - } else if r > 32 { - return colors.Yellow - } else { - return colors.Red - } -} - -// Color for the TELA average rating number hexagon -func telaHexagonColor(r float64) fyne.Resource { - if r > 6.5 { - return resourceTelaHexagonGreen - } else if r > 3.2 { - return resourceTelaHexagonYellow - } else { - return resourceTelaHexagonRed - } -} - -// Display ratings overview and the details of each rating for the TELA SCID -func viewTELARatingsOverlay(name, scid string) (err error) { - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - - header := canvas.NewText("TELA RATINGS", colors.Gray) - header.TextSize = 16 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - if len(name) > 30 { - name = fmt.Sprintf("%s...", name[0:30]) - } - - nameHdr := canvas.NewText(name, colors.Account) - nameHdr.Alignment = fyne.TextAlignCenter - nameHdr.TextStyle = fyne.TextStyle{Bold: true} - - labelSCID := canvas.NewText(" SMART CONTRACT ID", colors.Gray) - labelSCID.TextSize = 14 - labelSCID.Alignment = fyne.TextAlignLeading - labelSCID.TextStyle = fyne.TextStyle{Bold: true} - - textSCID := widget.NewRichTextWithText(scid) - textSCID.Wrapping = fyne.TextWrapWord - - textLikes := widget.NewRichTextFromMarkdown("Likes:") - textDislikes := widget.NewRichTextFromMarkdown("Dislikes:") - textAverage := widget.NewRichTextFromMarkdown("Average:") - - ratingsBox := container.NewVBox(labelSCID, textSCID) - - ratings, err := tela.GetRating(scid, session.Daemon, 0) - if err != nil { - removeOverlays() - logger.Errorf("[Engram] GetRating: %s\n", err) - err = fmt.Errorf("error could not get ratings") - return - } - - removeOverlays() - overlay := session.Window.Canvas().Overlays() - - ratingsBox.Add(container.NewHBox(textLikes, canvas.NewText(fmt.Sprintf("%d", ratings.Likes), colors.Green))) - ratingsBox.Add(container.NewHBox(textDislikes, canvas.NewText(fmt.Sprintf("%d", ratings.Dislikes), colors.Red))) - ratingsBox.Add(container.NewHBox(textAverage, canvas.NewText(fmt.Sprintf("%0.1f/10", ratings.Average), colors.Account))) - - linkRate := widget.NewHyperlinkWithStyle("Rate SCID", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - linkRate.OnTapped = func() { - rateTELAOverlay(name, scid) - } - linkRate.Hide() - - // Check if wallet has rated SCID - if gnomon.Index != nil { - ratingStore, _ := gnomon.GetSCIDValuesByKey(scid, engram.Disk.GetAddress().String()) - if ratingStore == nil { - linkRate.Show() - } - } - - linkBack := widget.NewHyperlinkWithStyle("Back to Application", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } - - labelSeparator := widget.NewRichTextFromMarkdown("") - labelSeparator.Wrapping = fyne.TextWrapOff - labelSeparator.ParseMarkdown("---") - labelSeparator2 := widget.NewRichTextFromMarkdown("") - labelSeparator2.Wrapping = fyne.TextWrapOff - labelSeparator2.ParseMarkdown("---") - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - userRatingsBox := container.NewVBox() - - for _, r := range ratings.Ratings { - ratingString, err := tela.Ratings.ParseString(r.Rating) - if err != nil { - ratingString = fmt.Sprintf("%d", r.Rating) - } - - labelSeparator := widget.NewRichTextFromMarkdown("") - labelSeparator.Wrapping = fyne.TextWrapOff - labelSeparator.ParseMarkdown("---") - - labelAddress := widget.NewRichTextFromMarkdown(r.Address) - labelAddress.Wrapping = fyne.TextWrapWord - - userRatingsBox.Add( - container.NewVBox( - labelAddress, - container.NewHBox(widget.NewRichTextFromMarkdown("Height:"), canvas.NewText(fmt.Sprintf("%d", r.Height), colors.Account)), - container.NewHBox(widget.NewRichTextFromMarkdown("Rating:"), canvas.NewText(fmt.Sprintf("%d", r.Rating), telaRatingColor(r.Rating))), - widget.NewRichTextFromMarkdown(ratingString), - labelSeparator, - ), - ) - } - - userRatingsBoxScroll := container.NewVScroll( - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - ratingsBox, - rectWidth90, - container.NewHBox( - linkRate, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator2, - rectSpacer, - rectSpacer, - userRatingsBox, - ), - layout.NewSpacer(), - ), - ) - userRatingsBoxScroll.SetMinSize(fyne.NewSize(ui.Width*0.80, ui.Height*0.50)) - - overlayCont := container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - rectSpacer, - container.NewCenter( - nameHdr, - ), - rectSpacer, - rectSpacer, - rectSpacer, - labelSeparator, - rectSpacer, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - ), - userRatingsBoxScroll, - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ) - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewBorder( - nil, - bottom, - nil, - nil, - overlayCont, - ), - ), - ) - - return -} - -// TELA smart contract rating overlay with password confirmation -func rateTELAOverlay(name, scid string) { - overlay := session.Window.Canvas().Overlays() - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - - selectSpacer := canvas.NewRectangle(color.Transparent) - selectSpacer.SetMinSize(fyne.NewSize(80, 5)) - - header := canvas.NewText("RATE TELA INDEX", colors.Gray) - header.TextSize = 16 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - if len(name) > 30 { - name = fmt.Sprintf("%s...", name[0:30]) - } - - nameHdr := canvas.NewText(name, colors.Account) - nameHdr.Alignment = fyne.TextAlignCenter - nameHdr.TextStyle = fyne.TextStyle{Bold: true} - - btnConfirm := widget.NewButton("Rate", nil) - btnConfirm.Disable() - - var telaCategories, negativeDetails, positiveDetails []string - for i := uint64(0); i < 10; i++ { - telaCategories = append(telaCategories, tela.Ratings.Category(i)) - } - categorySelect := widget.NewSelect(telaCategories, nil) - - scidLabel := canvas.NewText(" SMART CONTRACT ID", colors.Gray) - scidLabel.TextSize = 14 - scidLabel.Alignment = fyne.TextAlignCenter - scidLabel.TextStyle = fyne.TextStyle{Bold: true} - - scidText := widget.NewRichTextFromMarkdown(scid) - scidText.Wrapping = fyne.TextWrapWord - - errorText := canvas.NewText(" ", colors.Green) - errorText.TextSize = 12 - errorText.Alignment = fyne.TextAlignCenter - - for i := uint64(0); i < 10; i++ { - negativeDetails = append(negativeDetails, tela.Ratings.Detail(i, false)) - positiveDetails = append(positiveDetails, tela.Ratings.Detail(i, true)) - } - negativeSelect := widget.NewSelect(negativeDetails, nil) - positiveSelect := widget.NewSelect(positiveDetails, nil) - - categoryHeader := canvas.NewText("Category", colors.Account) - categoryHeader.Alignment = fyne.TextAlignLeading - categoryHeader.TextStyle = fyne.TextStyle{Bold: true} - categoryHeader.Refresh() - - detailHeader := canvas.NewText("Detail", colors.Account) - detailHeader.Alignment = fyne.TextAlignLeading - detailHeader.TextStyle = fyne.TextStyle{Bold: true} - detailHeader.Refresh() - - ratingHeader := canvas.NewText("Rating", colors.Account) - ratingHeader.Alignment = fyne.TextAlignLeading - ratingHeader.TextStyle = fyne.TextStyle{Bold: true} - ratingHeader.Refresh() - - ratingText := widget.NewRichTextFromMarkdown("") - ratingText.Wrapping = fyne.TextWrapWord - - linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } - - btnConfirm.OnTapped = func() { - errorText.Text = "" - errorText.Refresh() - btnConfirm.Disable() - if gnomon.Index != nil { - var ratingStore []string - switch gnomon.Index.DBType { - case "gravdb": - ratingStore, _ = gnomon.Index.GravDBBackend.GetSCIDValuesByKey(scid, engram.Disk.GetAddress().String(), gnomon.Index.LastIndexedHeight, false) - case "boltdb": - ratingStore, _ = gnomon.Index.BBSBackend.GetSCIDValuesByKey(scid, engram.Disk.GetAddress().String(), gnomon.Index.LastIndexedHeight, false) - } - if ratingStore != nil { - errorText.Text = "already rated this contract" - errorText.Color = colors.Red - errorText.Refresh() - return - } - } - - category := categorySelect.SelectedIndex() - if category < 0 { - errorText.Text = "select a category" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - var detail int - if category > 4 { - detail = positiveSelect.SelectedIndex() - } else { - detail = negativeSelect.SelectedIndex() - } - - if detail < 0 { - errorText.Text = "select a detail" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - rating := (category * 10) + detail - - verificationOverlay( - true, - "", - "", - "", - func(b bool) { - if !b { - btnConfirm.Enable() - return - } - - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - - txid, err := tela.Rate(engram.Disk, scid, uint64(rating)) - if err != nil { - logger.Errorf("[Engram] Rate TX: %s\n", err) - return - } - - logger.Printf("[Engram] Rate TXID: %s\n", txid) - }, - ) - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlayCont := container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - rectSpacer, - container.NewCenter( - nameHdr, - ), - rectSpacer, - rectSpacer, - rectSpacer, - scidLabel, - scidText, - widget.NewLabel(""), - container.NewCenter( - container.NewStack( - span, - container.NewBorder( - nil, - nil, - container.NewStack( - selectSpacer, - categoryHeader, - ), - nil, - categorySelect, - ), - ), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - container.NewCenter( - container.NewStack( - span, - container.NewBorder( - nil, - nil, - container.NewStack( - selectSpacer, - detailHeader, - ), - nil, - positiveSelect, - ), - ), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - container.NewCenter( - container.NewStack( - span, - container.NewBorder( - nil, - nil, - container.NewStack( - selectSpacer, - ratingHeader, - ), - nil, - ratingText, - ), - ), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - errorText, - rectSpacer, - rectSpacer, - btnConfirm, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ) - - categorySelect.OnChanged = func(s string) { - if positiveSelect.SelectedIndex() > -1 && negativeSelect.SelectedIndex() > -1 { - btnConfirm.Enable() - } - - if categorySelect.SelectedIndex() > 4 { - overlayCont.Objects[17] = container.NewCenter( - container.NewStack( - span, - container.NewBorder( - nil, - nil, - container.NewStack( - selectSpacer, - detailHeader, - ), - nil, - - positiveSelect, - ), - ), - ) - positiveSelect.SetSelectedIndex(0) - ratingText.ParseMarkdown(fmt.Sprintf("%d", (categorySelect.SelectedIndex()*10)+positiveSelect.SelectedIndex())) - } else { - overlayCont.Objects[17] = container.NewCenter( - container.NewStack( - span, - container.NewBorder( - nil, - nil, - container.NewStack( - selectSpacer, - detailHeader, - ), - nil, - negativeSelect, - ), - ), - ) - negativeSelect.SetSelectedIndex(0) - ratingText.ParseMarkdown(fmt.Sprintf("%d", (categorySelect.SelectedIndex()*10)+negativeSelect.SelectedIndex())) - } - } - - positiveSelect.OnChanged = func(s string) { - if categorySelect.SelectedIndex() > -1 { - btnConfirm.Enable() - ratingText.ParseMarkdown(fmt.Sprintf("%d", (categorySelect.SelectedIndex()*10)+positiveSelect.SelectedIndex())) - } - } - - negativeSelect.OnChanged = func(s string) { - if categorySelect.SelectedIndex() > -1 { - btnConfirm.Enable() - ratingText.ParseMarkdown(fmt.Sprintf("%d", (categorySelect.SelectedIndex()*10)+negativeSelect.SelectedIndex())) - } - } - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - overlayCont, - ), - ), - ) -} - -// Install a new smart contract -func installSC(code string, args []rpc.Argument) (txid string, err error) { - var dest string - switch session.Network { - case NETWORK_MAINNET: - dest = "dero1qykyta6ntpd27nl0yq4xtzaf4ls6p5e9pqu0k2x4x3pqq5xavjsdxqgny8270" - case NETWORK_SIMULATOR: - dest = "deto1qyvyeyzrcm2fzf6kyq7egkes2ufgny5xn77y6typhfx9s7w3mvyd5qqynr5hx" - case NETWORK_TESTNET: - dest = "deto1qy0ehnqjpr0wxqnknyc66du2fsxyktppkr8m8e6jvplp954klfjz2qqdzcd8p" - } - - transfer := rpc.Transfer{ - Destination: dest, - Amount: 0, - Burn: 0, - } - - _, err = transfer.Payload_RPC.CheckPack(transaction.PAYLOAD0_LIMIT) - if err != nil { - logger.Errorf("[Engram] Install arguments packing err: %s\n", err) - err = fmt.Errorf("contract install pack error") - return - } - - // decode SC from base64 if possible - if sc, err := base64.StdEncoding.DecodeString(code); err == nil { - code = string(sc) - } - - args = append(args, rpc.Argument{Name: rpc.SCACTION, DataType: rpc.DataUint64, Value: uint64(rpc.SC_INSTALL)}) - args = append(args, rpc.Argument{Name: rpc.SCCODE, DataType: rpc.DataString, Value: code}) - - fees := uint64(0) - gasParams := rpc.GasEstimate_Params{ - Transfers: []rpc.Transfer{transfer}, - SC_Code: code, - SC_Value: 0, - SC_RPC: args, - Ringsize: 2, - Signer: engram.Disk.GetAddress().String(), - } - - if gas, err := getGasEstimate(gasParams); err == nil { - fees = gas - logger.Printf("[Engram] SC install fees: %d\n", fees) - } else { - // uses default fees - logger.Errorf("[Engram] Error estimating fees: %s\n", err) - } - - tx, err := engram.Disk.TransferPayload0([]rpc.Transfer{transfer}, 2, false, args, fees, false) - if err != nil { - logger.Errorf("[Engram] Error while building install transaction: %s\n", err) - err = fmt.Errorf("contract install build error") - return - } - - if err = engram.Disk.SendTransaction(tx); err != nil { - logger.Errorf("[Engram] Error while dispatching install transaction: %s\n", err) - err = fmt.Errorf("contract install dispatch error") - return - } - - txid = tx.GetHash().String() - - logger.Printf("[Engram] SC Installed: %s\n", txid) - - return -} - -// Set the Cyberdeck password -func newRPCPassword() (s string) { - r := make([]byte, 20) - _, err := rand.Read(r) - if err != nil { - panic(err) - } - - s = base64.URLEncoding.EncodeToString(r) - cyberdeck.RPC.pass = s - return -} - -// Set the Cyberdeck username -func newRPCUsername() (s string) { - r, _ := rand.Int(rand.Reader, big.NewInt(1600)) - w := mnemonics.Key_To_Words(r, "english") - l := strings.Split(string(w), " ") - s = l[len(l)-2] - cyberdeck.RPC.user = s - return -} - -// Start an RPC server to allow decentralized application communication -func toggleRPCServer(port string) { - var err error - if engram.Disk == nil { - return - } - - if cyberdeck.RPC.server != nil { - cyberdeck.RPC.server.RPCServer_Stop() - cyberdeck.RPC.server = nil - cyberdeck.RPC.status.Text = "Blocked" - cyberdeck.RPC.status.Color = colors.Gray - cyberdeck.RPC.status.Refresh() - cyberdeck.RPC.toggle.Text = "Turn On" - cyberdeck.RPC.toggle.Refresh() - status.Cyberdeck.FillColor = colors.Gray - status.Cyberdeck.StrokeColor = colors.Gray - status.Cyberdeck.Refresh() - cyberdeck.RPC.userText.Text = cyberdeck.RPC.user - cyberdeck.RPC.passText.Text = cyberdeck.RPC.pass - cyberdeck.RPC.userText.Enable() - cyberdeck.RPC.passText.Enable() - logger.Printf("[Engram] RPC server closed\n") - } else { - logger.Printf("[Engram] Starting RPC server %s\n", port) - - globals.Arguments["--rpc-bind"] = port - - if cyberdeck.RPC.user == "" { - cyberdeck.RPC.user = newRPCUsername() - } - - if cyberdeck.RPC.pass == "" { - cyberdeck.RPC.pass = newRPCPassword() - } - - globals.Arguments["--rpc-login"] = cyberdeck.RPC.user + ":" + cyberdeck.RPC.pass - - cyberdeck.RPC.server, err = rpcserver.RPCServer_Start(engram.Disk, "Cyberdeck") - if err != nil { - cyberdeck.RPC.server = nil - cyberdeck.RPC.status.Text = "Blocked" - cyberdeck.RPC.status.Color = colors.Gray - cyberdeck.RPC.status.Refresh() - cyberdeck.RPC.toggle.Text = "Turn On" - cyberdeck.RPC.toggle.Refresh() - status.Cyberdeck.FillColor = colors.Gray - status.Cyberdeck.StrokeColor = colors.Gray - status.Cyberdeck.Refresh() - cyberdeck.RPC.userText.Text = cyberdeck.RPC.user - cyberdeck.RPC.passText.Text = cyberdeck.RPC.pass - cyberdeck.RPC.userText.Enable() - cyberdeck.RPC.passText.Enable() - } else { - cyberdeck.RPC.status.Text = "Allowed" - cyberdeck.RPC.status.Color = colors.Green - cyberdeck.RPC.status.Refresh() - cyberdeck.RPC.toggle.Text = "Turn Off" - cyberdeck.RPC.toggle.Refresh() - status.Cyberdeck.FillColor = colors.Green - status.Cyberdeck.StrokeColor = colors.Green - status.Cyberdeck.Refresh() - cyberdeck.RPC.userText.Text = cyberdeck.RPC.user - cyberdeck.RPC.passText.Text = cyberdeck.RPC.pass - cyberdeck.RPC.userText.Disable() - cyberdeck.RPC.passText.Disable() - } - } -} - -// Get the latest smart contract header data (must follow the standard here: https://github.com/civilware/artificer-nfa-standard/blob/main/Headers/README.md) -func getContractHeader(scid crypto.Hash) (name string, desc string, icon string, owner string, code string) { - var headerData []*structures.SCIDVariable - var found bool - - switch gnomon.Index.DBType { - case "gravdb": - headerData = gnomon.Index.GravDBBackend.GetAllSCIDVariableDetails(scid.String()) - case "boltdb": - headerData = gnomon.Index.BBSBackend.GetAllSCIDVariableDetails(scid.String()) - } - if headerData == nil { - addIndex := make(map[string]*structures.FastSyncImport) - addIndex[scid.String()] = &structures.FastSyncImport{} - gnomon.Index.AddSCIDToIndex(addIndex, false, true) - switch gnomon.Index.DBType { - case "gravdb": - headerData = gnomon.Index.GravDBBackend.GetAllSCIDVariableDetails(scid.String()) - case "boltdb": - headerData = gnomon.Index.BBSBackend.GetAllSCIDVariableDetails(scid.String()) - } - } - - for _, h := range headerData { - switch key := h.Key.(type) { - case string: - if key == "var_header_name" { - found = true - name = h.Value.(string) - } else if name == "" && key == "nameHdr" { - found = true - name = h.Value.(string) - } - - if key == "var_header_description" { - found = true - desc = h.Value.(string) - } else if desc == "" && key == "descrHdr" { - found = true - desc = h.Value.(string) - } - - if key == "var_header_icon" { - found = true - icon = h.Value.(string) - } else if icon == "" && key == "iconURLHdr" { - found = true - icon = h.Value.(string) - } - - if key == "owner" { - owner = h.Value.(string) - } - - if key == "C" { - code = h.Value.(string) - } - } - } - - // Secondary check for headers in Gnomon SC - if !found { - switch gnomon.Index.DBType { - case "gravdb": - headerData = gnomon.Index.GravDBBackend.GetAllSCIDVariableDetails(structures.MAINNET_GNOMON_SCID) - case "boltdb": - headerData = gnomon.Index.BBSBackend.GetAllSCIDVariableDetails(structures.MAINNET_GNOMON_SCID) - } - if headerData == nil { - addIndex := make(map[string]*structures.FastSyncImport) - addIndex[structures.MAINNET_GNOMON_SCID] = &structures.FastSyncImport{} - gnomon.Index.AddSCIDToIndex(addIndex, false, true) - switch gnomon.Index.DBType { - case "gravdb": - headerData = gnomon.Index.GravDBBackend.GetAllSCIDVariableDetails(structures.MAINNET_GNOMON_SCID) - case "boltdb": - headerData = gnomon.Index.BBSBackend.GetAllSCIDVariableDetails(structures.MAINNET_GNOMON_SCID) - } - } - - for _, h := range headerData { - if strings.Contains(h.Key.(string), scid.String()) { - switch key := h.Key.(type) { - case string: - if key == scid.String() { - query := h.Value.(string) - header := strings.Split(query, ";") - - if len(header) > 2 { - name = header[0] - desc = header[1] - icon = header[2] - } - } - - if key == scid.String()+"owner" { - owner = h.Value.(string) - } - } - } - } - } - - return -} - -// Send an asset from one account to another -func transferAsset(scid crypto.Hash, ringsize uint64, address string, amount string) (txid crypto.Hash, err error) { - var amount_to_transfer uint64 - - if amount == "" { - amount = ".00001" - } - - amount_to_transfer, err = globals.ParseAmount(amount) - if err != nil { - logger.Errorf("[Transfer] Failed parsing transfer amount: %s\n", err) - return - } - - tx, err := engram.Disk.TransferPayload0([]rpc.Transfer{{SCID: scid, Amount: amount_to_transfer, Destination: address}}, ringsize, false, rpc.Arguments{}, 0, false) - if err != nil { - logger.Errorf("[Transfer] Failed to build transaction: %s\n", err) - return - } - - if err = engram.Disk.SendTransaction(tx); err != nil { - logger.Errorf("[Transfer] Failed to send asset: %s - %s\n", scid, err) - return - } - - txid = tx.GetHash() - - logger.Printf("[Transfer] Successfully sent asset: %s - TXID: %s\n", scid, tx.GetHash().String()) - return -} - -// Transfer a username to another account -func transferUsername(username string, address string) (storage uint64, err error) { - var args = rpc.Arguments{} - var dest string - - scid := crypto.HashHexToHash("0000000000000000000000000000000000000000000000000000000000000001") - - args = append(args, rpc.Argument{Name: "entrypoint", DataType: "S", Value: "TransferOwnership"}) - args = append(args, rpc.Argument{Name: "SC_ID", DataType: "H", Value: scid}) - args = append(args, rpc.Argument{Name: "SC_ACTION", DataType: "U", Value: uint64(rpc.SC_CALL)}) - args = append(args, rpc.Argument{Name: "name", DataType: "S", Value: username}) - args = append(args, rpc.Argument{Name: "newowner", DataType: "S", Value: address}) - - switch session.Network { - case NETWORK_MAINNET: - dest = "dero1qykyta6ntpd27nl0yq4xtzaf4ls6p5e9pqu0k2x4x3pqq5xavjsdxqgny8270" - case NETWORK_SIMULATOR: - dest = "deto1qyvyeyzrcm2fzf6kyq7egkes2ufgny5xn77y6typhfx9s7w3mvyd5qqynr5hx" - default: - dest = "deto1qy0ehnqjpr0wxqnknyc66du2fsxyktppkr8m8e6jvplp954klfjz2qqdzcd8p" - } - - transfer := rpc.Transfer{ - Destination: dest, - Amount: 0, - Burn: 0, - } - - gasParams := rpc.GasEstimate_Params{ - SC_RPC: args, - SC_Value: 0, - Ringsize: 2, - Signer: engram.Disk.GetAddress().String(), - Transfers: []rpc.Transfer{transfer}, - } - - storage, err = getGasEstimate(gasParams) - if err != nil { - logger.Errorf("[%s] Error estimating fees: %s\n", "TransferOwnership", err) - return - } - - tx, err := engram.Disk.TransferPayload0([]rpc.Transfer{transfer}, 2, false, args, storage, false) - if err != nil { - logger.Errorf("[%s] Error while building transaction: %s\n", "TransferOwnership", err) - return - } - - txid := tx.GetHash().String() - - err = engram.Disk.SendTransaction(tx) - if err != nil { - logger.Errorf("[%s] Error while dispatching transaction: %s\n", "TransferOwnership", err) - return - } - - walletapi.WaitNewHeightBlock() - logger.Printf("[%s] Username transfer successful - TXID: %s\n", "TransferOwnership", txid) - _ = tx - - return -} - -// Execute arbitrary exportable smart contract functions -func executeContractFunction(scid crypto.Hash, ringsize uint64, dero_amount uint64, asset_amount uint64, funcName string, params []dvm.Variable) (storage uint64, err error) { - var args = rpc.Arguments{} - var zero uint64 - var dest string - - args = append(args, rpc.Argument{Name: "entrypoint", DataType: "S", Value: funcName}) - args = append(args, rpc.Argument{Name: "SC_ID", DataType: "H", Value: scid}) - args = append(args, rpc.Argument{Name: "SC_ACTION", DataType: "U", Value: uint64(rpc.SC_CALL)}) - - for p := range params { - if params[p].Type == 0x4 { - args = append(args, rpc.Argument{Name: params[p].Name, DataType: "U", Value: params[p].ValueUint64}) - } else { - args = append(args, rpc.Argument{Name: params[p].Name, DataType: "S", Value: params[p].ValueString}) - } - } - - switch session.Network { - case NETWORK_MAINNET: - dest = "dero1qykyta6ntpd27nl0yq4xtzaf4ls6p5e9pqu0k2x4x3pqq5xavjsdxqgny8270" - case NETWORK_SIMULATOR: - dest = "deto1qyvyeyzrcm2fzf6kyq7egkes2ufgny5xn77y6typhfx9s7w3mvyd5qqynr5hx" - default: - dest = "deto1qy0ehnqjpr0wxqnknyc66du2fsxyktppkr8m8e6jvplp954klfjz2qqdzcd8p" - } - - var transfers []rpc.Transfer - - if dero_amount != zero { - burn := dero_amount - - transfer := rpc.Transfer{ - Destination: dest, - Amount: 0, - Burn: burn, - } - - transfers = append(transfers, transfer) - } - if asset_amount != zero { - burn := asset_amount - - transfer := rpc.Transfer{ - SCID: scid, - Destination: dest, - Amount: 0, - Burn: burn, - } - - transfers = append(transfers, transfer) - } - - if len(transfers) < 1 { - transfer := rpc.Transfer{ - Destination: dest, - Amount: 0, - Burn: 0, - } - - transfers = append(transfers, transfer) - } - - gasParams := rpc.GasEstimate_Params{ - SC_RPC: args, - SC_Value: 0, - Ringsize: ringsize, - Signer: engram.Disk.GetAddress().String(), - Transfers: transfers, - } - - storage, err = getGasEstimate(gasParams) - if err != nil { - logger.Errorf("[%s] Error estimating fees: %s\n", funcName, err) - return - } - - tx, err := engram.Disk.TransferPayload0(transfers, ringsize, false, args, storage, false) - if err != nil { - logger.Errorf("[%s] Error while building transaction: %s\n", funcName, err) - return - } - - err = engram.Disk.SendTransaction(tx) - if err != nil { - logger.Errorf("[%s] Error while dispatching transaction: %s\n", funcName, err) - return - } - - walletapi.WaitNewHeightBlock() - logger.Printf("[%s] Function execution successful - TXID: %s\n", funcName, tx.GetHash().String()) - _ = tx - - return -} - -// Delete the Gnomon directory -func cleanGnomonData() error { - path := filepath.Join(AppPath(), "datashards", "gnomon") - switch session.Network { - case NETWORK_TESTNET: - path = filepath.Join(AppPath(), "datashards", "gnomon_testnet") - case NETWORK_SIMULATOR: - path = filepath.Join(AppPath(), "datashards", "gnomon_simulator") - } - - dir, err := os.ReadDir(path) - if err != nil { - logger.Errorf("[Gnomon] Error purging local Gnomon data: %s\n", err) - return err - } - - for _, d := range dir { - os.RemoveAll(filepath.Join([]string{path, d.Name()}...)) - logger.Printf("[Gnomon] Local Gnomon data has been purged successfully\n") - } - - return nil -} - -// Delete the datashard directory for the active wallet -func cleanWalletData() (err error) { - path, err := GetShard() - if err != nil { - return - } - - dir, err := os.ReadDir(path) - if err != nil { - logger.Errorf("[Engram] Error purging local datashard data: %s\n", err) - return err - } - - for _, d := range dir { - os.RemoveAll(filepath.Join([]string{path, d.Name()}...)) - logger.Printf("[Engram] Local datashard data has been purged successfully\n") - } - - return nil -} - -// Get transaction data for any TXID from the daemon -func getTxData(txid string) (result rpc.GetTransaction_Result, err error) { - if engram.Disk == nil || session.Offline { - return - } - - var params rpc.GetTransaction_Params - - params.Tx_Hashes = append(params.Tx_Hashes, txid) - - rpc_client.WS, _, err = websocket.DefaultDialer.Dial("ws://"+session.Daemon+"/ws", nil) - if err != nil { - return - } - - input_output := rwc.New(rpc_client.WS) - rpc_client.RPC = jrpc2.NewClient(channel.RawJSON(input_output, input_output), nil) - - if err = rpc_client.RPC.CallResult(context.Background(), "DERO.GetTransaction", params, &result); err != nil { - logger.Errorf("[Engram] getTxData TXID: %s (Failed: %s)\n", txid, err) - return - } - - rpc_client.WS.Close() - rpc_client.RPC.Close() - - if result.Status != "OK" { - logger.Errorf("[Engram] getTxData TXID: %s (Failed: %s)\n", txid, result.Status) - return - } - - if len(result.Txs_as_hex[0]) < 50 { - return - } - - return -} - -// Methods Engram will use as XSWD noStore -func engramNoStoreMethods() []string { - return []string{ - "Subscribe", - "SignData", - "CheckSignature", - "GetDaemon", - "GetPrimaryUsername", - "query_key", - "QueryKey", - "HandleTELALinks"} -} - -// Check if method is in Engram's noStore list -func engramCanStoreMethod(method string) bool { - noStoreMethods := engramNoStoreMethods() - for _, m := range noStoreMethods { - if m == method { - return false - } - } - - return true -} - -// Set XSWD permissions to the local Graviton tree -func setPermissions() { - data, err := json.Marshal(&cyberdeck.WS.global.permissions) - if err != nil { - logger.Errorf("[Engram] setPermissions: %s\n", err) - } else { - err = StoreEncryptedValue("XSWD", []byte("Globals"), data) - if err != nil { - logger.Debugf("[Engram] setPermissions: %s\n", err) - } - } -} - -// Set all noStore methods to XSWD Ask permission -func SetDefaultPermissions() (defaults map[string]xswd.Permission) { - defaults = make(map[string]xswd.Permission) - for method := range rpcserver.WalletHandler { - defaults[method] = xswd.Ask - } - - // XSWD methods - defaults["Subscribe"] = xswd.Ask - defaults["HasMethod"] = xswd.Ask - defaults["Unsubscribe"] = xswd.Ask - defaults["GetDaemon"] = xswd.Ask - defaults["SignData"] = xswd.Ask - defaults["CheckSignature"] = xswd.Ask - - // Engram methods - defaults["GetPrimaryUsername"] = xswd.Ask - defaults["HandleTELALinks"] = xswd.Ask - - // EPOCH methods - defaults["AttemptEPOCHWithAddr"] = xswd.Ask - for method := range epoch.GetHandler() { - defaults[method] = xswd.Ask - } - - return -} - -// Get XSWD permissions from local Graviton tree and sorted wallet methods -func getPermissions() (handler map[string]xswd.Permission, methods []string) { - cyberdeck.WS.Lock() - defer cyberdeck.WS.Unlock() - - cyberdeck.WS.global.permissions = SetDefaultPermissions() - - stored, err := GetEncryptedValue("XSWD", []byte("Globals")) - if err != nil { - logger.Debugf("[Engram] getPermissions: %s\n", err) - } else { - if err := json.Unmarshal(stored, &cyberdeck.WS.global.permissions); err != nil { - logger.Errorf("[Engram] getPermissions: %s\n", err) - } - } - - for k := range cyberdeck.WS.global.permissions { - methods = append(methods, k) - } - - sort.Strings(methods) - - return cyberdeck.WS.global.permissions, methods -} - -// Start a permissioned web socket server to allow decentralized application communication -func toggleXSWD(endpoint string) { - if engram.Disk == nil { - return - } - - if cyberdeck.WS.server != nil { - cyberdeck.WS.server.Stop() - cyberdeck.WS.server = nil - cyberdeck.WS.status.Text = "Blocked" - cyberdeck.WS.status.Color = colors.Gray - cyberdeck.WS.status.Refresh() - cyberdeck.WS.toggle.Text = "Turn On" - cyberdeck.WS.toggle.Refresh() - status.Cyberdeck.FillColor = colors.Gray - status.Cyberdeck.StrokeColor = colors.Gray - status.Cyberdeck.Refresh() - cyberdeck.WS.advanced = false - cyberdeck.WS.global.enabled = false - cyberdeck.WS.global.connect = false - cyberdeck.WS.apps = []xswd.ApplicationData{} - if cyberdeck.WS.list != nil { - cyberdeck.WS.list.Refresh() - } - logger.Printf("[Engram] XSWD server closed\n") - } else { - _, portNum, err := net.SplitHostPort(endpoint) - if err != nil { - logger.Errorf("[Engram] Invalid XSWD server endpoint: %s\n", err) - return - } - - portInt, err := strconv.Atoi(portNum) - if err != nil { - logger.Errorf("[Engram] Invalid XSWD server port: %s\n", err) - return - } - - logger.Printf("[Engram] Starting XSWD server %s\n", endpoint) - - noStoreMethods := engramNoStoreMethods() - - cyberdeck.WS.server = xswd.NewXSWDServerWithPort(portInt, engram.Disk, false, noStoreMethods, func(ad *xswd.ApplicationData) bool { - return XSWDPrompt(ad) - }, func(ad *xswd.ApplicationData, r *jrpc2.Request) xswd.Permission { - return AskPermissionForRequest(ad, r) - }) - - cyberdeck.WS.toggle.Disable() - cyberdeck.WS.toggle.Text = "Initializing" - cyberdeck.WS.toggle.Refresh() - time.Sleep(time.Second) - if !cyberdeck.WS.server.IsRunning() { - cyberdeck.WS.server = nil - logger.Errorf("[Engram] Error starting XSWD server\n") - cyberdeck.WS.toggle.Text = "Error starting web sockets" - cyberdeck.WS.toggle.Refresh() - go func() { - time.Sleep(time.Second * 2) - fyne.Do(func() { - fyne.Do(func() { - cyberdeck.WS.toggle.Text = "Turn On" - cyberdeck.WS.toggle.Refresh() - cyberdeck.WS.toggle.Enable() - }) - }) - }() - - return - } - cyberdeck.WS.toggle.Enable() - - if cyberdeck.WS.server == nil { - cyberdeck.WS.status.Text = "Blocked" - cyberdeck.WS.status.Color = colors.Gray - cyberdeck.WS.status.Refresh() - cyberdeck.WS.toggle.Text = "Turn On" - cyberdeck.WS.toggle.Refresh() - status.Cyberdeck.FillColor = colors.Gray - status.Cyberdeck.StrokeColor = colors.Gray - status.Cyberdeck.Refresh() - } else { - for method, h := range EngramHandler { - cyberdeck.WS.server.SetCustomMethod(method, h) - } - - cyberdeck.WS.server.SetCustomMethod("HandleTELALinks", handler.New(HandleTELALinks)) - - cyberdeck.WS.server.SetCustomMethod("AttemptEPOCHWithAddr", handler.New(AttemptEPOCHWithAddr)) - - for method, h := range epoch.GetHandler() { - cyberdeck.WS.server.SetCustomMethod(method, h) - } - - cyberdeck.WS.status.Text = "Allowed" - cyberdeck.WS.status.Color = colors.Green - cyberdeck.WS.status.Refresh() - cyberdeck.WS.toggle.Text = "Turn Off" - cyberdeck.WS.toggle.Refresh() - status.Cyberdeck.FillColor = colors.Green - status.Cyberdeck.StrokeColor = colors.Green - status.Cyberdeck.Refresh() - } - } -} - -// Prompt when an application submits request to connect to wallet with XSWD -func XSWDPrompt(ad *xswd.ApplicationData) (confirmed bool) { - if cyberdeck.WS.advanced { - // If global permissions enabled set them here - if cyberdeck.WS.global.enabled { - logger.Printf("[Engram] Applied global XSWD permissions to %s\n", ad.Name) - cyberdeck.WS.RLock() - for k, v := range cyberdeck.WS.global.permissions { - ad.Permissions[k] = v - } - cyberdeck.WS.RUnlock() - } - - // If wallet is set to connect to all requests, connect to app - if cyberdeck.WS.global.connect { - logger.Printf("[Engram] Applied automatic XSWD connection to %s\n", ad.Name) - fyne.CurrentApp().SendNotification(&fyne.Notification{Title: ad.Name, Content: "A new connection request has been approved"}) - go refreshXSWDList() - return true - } - } else { - // Restrictive mode overwrites any requested permissions to default Ask, and sets certain methods to AlwaysDeny - ad.Permissions = map[string]xswd.Permission{} - ad.Permissions["QueryKey"] = xswd.AlwaysDeny - ad.Permissions["query_key"] = xswd.AlwaysDeny - } - - overlay := session.Window.Canvas().Overlays() - - headerText := "NEW CONNECTION REQUEST" - - header := canvas.NewText(headerText, colors.Gray) - header.TextSize = 16 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - labelApp := canvas.NewText("APP NAME", colors.Gray) - labelApp.TextSize = 14 - labelApp.Alignment = fyne.TextAlignLeading - labelApp.TextStyle = fyne.TextStyle{Bold: true} - - textApp := widget.NewRichTextFromMarkdown("### " + ad.Name) - textApp.Wrapping = fyne.TextWrapWord - - labelID := canvas.NewText("APP ID", colors.Gray) - labelID.TextSize = 14 - labelID.Alignment = fyne.TextAlignLeading - labelID.TextStyle = fyne.TextStyle{Bold: true} - - textID := widget.NewRichTextFromMarkdown(ad.Id) - textID.Wrapping = fyne.TextWrapWord - - labelURL := canvas.NewText("URL", colors.Gray) - labelURL.TextSize = 14 - labelURL.Alignment = fyne.TextAlignLeading - labelURL.TextStyle = fyne.TextStyle{Bold: true} - - textURL := widget.NewRichTextFromMarkdown(ad.Url) - textURL.Wrapping = fyne.TextWrapWord - - labelPermissions := canvas.NewText("PERMISSIONS", colors.Gray) - labelPermissions.TextSize = 14 - labelPermissions.Alignment = fyne.TextAlignLeading - labelPermissions.TextStyle = fyne.TextStyle{Bold: true} - - // Get permissioned methods from xswd.ApplicationData and create permission objects - var methods []string - for k := range ad.Permissions { - methods = append(methods, k) - } - - sort.Strings(methods) - - permForm := container.NewVBox() - - textSpacer := canvas.NewRectangle(color.Transparent) - textSpacer.SetMinSize(fyne.NewSize(10, 3)) - - for _, k := range methods { - perm := ad.Permissions[k] - permColor := colors.Account - switch perm { - case xswd.AlwaysAllow: - permColor = colors.Green - case xswd.AlwaysDeny: - permColor = colors.Red - } - - textMethod := widget.NewRichTextFromMarkdown("### " + k) - textMethod.Wrapping = fyne.TextWrapWord - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.5, 2)) - - add := container.NewVBox( - textMethod, - container.NewHBox( - textSpacer, - canvas.NewText(perm.String(), permColor), - ), - textSpacer, - container.NewHBox( - sep, - ), - ) - - permForm.Add(add) - } - - if len(permForm.Objects) == 0 { - permForm.Add( - container.NewVBox( - widget.NewRichTextFromMarkdown("No permissions"), - ), - ) - } else { - permForm.Add(textSpacer) - } - - labelEvents := canvas.NewText("EVENTS", colors.Gray) - labelEvents.TextSize = 14 - labelEvents.Alignment = fyne.TextAlignLeading - labelEvents.TextStyle = fyne.TextStyle{Bold: true} - - eventsForm := container.NewVBox() - - // Get registered events from xswd.ApplicationData and create event objects - for name, b := range ad.RegisteredEvents { - eventColor := colors.Red - if b { - eventColor = colors.Green - } - - textEvent := widget.NewRichTextFromMarkdown(fmt.Sprintf("### %s", name)) - textEvent.Wrapping = fyne.TextWrapWord - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.5, 2)) - - add := container.NewVBox( - textEvent, - container.NewHBox( - textSpacer, - canvas.NewText(strconv.FormatBool(b), eventColor), - ), - textSpacer, - container.NewHBox( - sep, - ), - ) - - eventsForm.Add(add) - } - - if len(eventsForm.Objects) == 0 { - eventsForm.Add( - container.NewVBox( - widget.NewRichTextFromMarkdown("No events"), - ), - ) - } - - rectBox := canvas.NewRectangle(color.Transparent) - rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.90, ui.MaxHeight*0.48)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(0, 10)) - - options := widget.NewSelect([]string{xswd.Allow.String(), xswd.Deny.String()}, nil) - - content := container.NewStack( - container.NewBorder( - nil, - container.NewVBox( - rectSpacer, - rectSpacer, - options, - rectSpacer, - rectSpacer, - ), - nil, - nil, - container.NewStack( - rectBox, - container.NewVScroll( - container.NewVBox( - rectSpacer, - rectSpacer, - labelApp, - textApp, - rectSpacer, - labelID, - textID, - rectSpacer, - labelURL, - textURL, - rectSpacer, - labelPermissions, - permForm, - rectSpacer, - labelEvents, - eventsForm, - rectSpacer, - ), - ), - ), - ), - ) - - // Create and show connection prompt - done := make(chan struct{}) - btnDismiss := widget.NewButton("Deny", nil) - btnDismiss.OnTapped = func() { - if options.Selected == xswd.Allow.String() { - confirmed = true - } - done <- struct{}{} - } - - options.OnChanged = func(s string) { - if s == xswd.Deny.String() { - btnDismiss.Importance = widget.MediumImportance - } else { - btnDismiss.Importance = widget.HighImportance - } - btnDismiss.SetText(s) - btnDismiss.Refresh() - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - container.NewCenter( - container.NewStack( - span, - ), - ), - rectSpacer, - rectSpacer, - content, - btnDismiss, - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ), - ), - ) - - if a.Driver().Device().IsMobile() { - fyne.Do(func() { - fyne.CurrentApp().SendNotification(&fyne.Notification{Title: ad.Name, Content: "A new connection request has been received"}) - }) - } else { - fyne.Do(func() { - session.Window.RequestFocus() - }) - } - - // Wait for user input or socket close - select { - case <-done: - - case <-ad.OnClose: - - } - - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - - go refreshXSWDList() - - return -} - -// Handle incoming TELA link requests and return params to be displayed in approval prompt -func handleTELALinkRequest(linkParams TELALink_Params) (params string, err error) { - var args []string - var target string - target, args, err = tela.ParseTELALink(linkParams.TelaLink) - if err != nil { - return - } - - switch target { - case "tela": - switch args[0] { - case "open": // open TELA content similar to a hyperlink - if len(args) < 2 || len(args[1]) != 64 { - err = fmt.Errorf("/open/ request has invalid scid argument") - return - } - - // Engram will check content rating and show it in prompt - var rating tela.Rating_Result - rating, err = tela.GetRating(args[1], session.Daemon, 0) - if err != nil { - return - } - - var index tela.INDEX - index, err = tela.GetINDEXInfo(args[1], session.Daemon) - if err != nil { - return - } - - var linkDisplay TELALink_Display - linkDisplay.Name = index.NameHdr - linkDisplay.Descr = index.DescrHdr - linkDisplay.DURL = index.DURL - linkDisplay.TelaLink = linkParams.TelaLink - rating.Ratings = nil // don't need to show each individual rating in prompt - linkDisplay.Rating = &rating - - params = fmt.Sprintf("%+v", linkDisplay) - if indentParams, err := json.MarshalIndent(linkDisplay, "", " "); err == nil { - params = string(indentParams) - } - default: - err = fmt.Errorf("invalid argument: %s", args[0]) - return - } - case "engram": - if len(args) < 3 { - err = fmt.Errorf("invalid engram link format") - return - } - - switch args[0] { - case "asset": - switch args[1] { - case "manager": // open asset manager module with scid data - if len(args[2]) != 64 { - err = fmt.Errorf("/manager/ request has invalid scid argument") - return - } - default: - err = fmt.Errorf("invalid argument: %s", args[1]) - return - } - default: - err = fmt.Errorf("invalid argument: %s", args[0]) - return - } - - params = fmt.Sprintf("%+v", linkParams) - if indentParams, err := json.MarshalIndent(linkParams, "", " "); err == nil { - params = string(indentParams) // indent params if able - } - default: - err = fmt.Errorf("invalid target: %s", target) - return - } - - return -} - -// Ask permission to complete a specific request from a connected application, -// can choose to Allow, Always Allow, Deny, Always Deny the request -func AskPermissionForRequest(ad *xswd.ApplicationData, request *jrpc2.Request) (choice xswd.Permission) { - method := request.Method() - // Gnomon methods behave as AlwaysAllow - if strings.HasPrefix(method, "Gnomon.") { - return xswd.Allow - } - - // All other methods require approval - choice = xswd.Deny - - // EPOCH is not online or permissioned so Deny request - if strings.HasSuffix(method, "EPOCH") && !epoch.IsActive() { - return - } - - overlay := session.Window.Canvas().Overlays() - - headerText := "NEW PERMISSION REQUEST" - - header := canvas.NewText(headerText, colors.Gray) - header.TextSize = 16 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - labelApp := canvas.NewText("FROM", colors.Gray) - labelApp.TextSize = 14 - labelApp.Alignment = fyne.TextAlignLeading - labelApp.TextStyle = fyne.TextStyle{Bold: true} - - textApp := widget.NewRichTextFromMarkdown("### " + ad.Name) - textApp.Wrapping = fyne.TextWrapWord - - labelRequest := canvas.NewText("REQUESTING", colors.Gray) - labelRequest.TextSize = 14 - labelRequest.Alignment = fyne.TextAlignLeading - labelRequest.TextStyle = fyne.TextStyle{Bold: true} - - textRequest := widget.NewRichTextFromMarkdown("### " + method) - textRequest.Wrapping = fyne.TextWrapWord - - labelParams := canvas.NewText("PARAMETERS", colors.Gray) - labelParams.TextSize = 14 - labelParams.Alignment = fyne.TextAlignLeading - labelParams.TextStyle = fyne.TextStyle{Bold: true} - - params := "None" - if method == "HandleTELALinks" { - var linkParams TELALink_Params - err := request.UnmarshalParams(&linkParams) - if err != nil { - logger.Errorf("[Engram] Denied TELA link request %s from %s: %s\n", request.ParamString(), ad.Name, err) - return - } - - params, err = handleTELALinkRequest(linkParams) - if err != nil { - logger.Errorf("[Engram] Denied TELA link request %q from %s: %s\n", linkParams.TelaLink, ad.Name, err) - return - } - } else if request.ParamString() != "" { - params = strings.ReplaceAll(strings.Join(strings.Fields(request.ParamString()), " "), "\n", " ") - - // Unmarshall and indent params if able - var buffer interface{} - if request.UnmarshalParams(&buffer) == nil { - if indentParams, err := json.MarshalIndent(buffer, "", " "); err == nil { - params = string(indentParams) - } - } - } - - textParams := widget.NewLabel(params) - textParams.Wrapping = fyne.TextWrapWord - - rectBox := canvas.NewRectangle(color.Transparent) - rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.90, ui.MaxHeight*0.48)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(0, 10)) - - permissions := []string{ - xswd.Allow.String(), - xswd.Deny.String(), - } - - // Add AlwaysAllow option if method is !noStore - if cyberdeck.WS.server.CanStorePermission(method) { - permissions = append(permissions, xswd.AlwaysAllow.String()) - } - - permissions = append(permissions, xswd.AlwaysDeny.String()) - - options := widget.NewSelect(permissions, nil) - - content := container.NewStack( - container.NewBorder( - nil, - container.NewVBox( - rectSpacer, - rectSpacer, - options, - rectSpacer, - rectSpacer, - ), - nil, - nil, - container.NewStack( - rectBox, - container.NewVScroll( - container.NewVBox( - labelApp, - textApp, - rectSpacer, - labelRequest, - textRequest, - rectSpacer, - labelParams, - textParams, - ), - ), - ), - ), - ) - - // Create and show request prompt - done := make(chan struct{}) - btnDismiss := widget.NewButton("Deny", nil) - btnDismiss.OnTapped = func() { - switch options.Selected { - case xswd.Allow.String(): - choice = xswd.Allow - case xswd.Deny.String(): - choice = xswd.Deny - case xswd.AlwaysAllow.String(): - choice = xswd.AlwaysAllow - case xswd.AlwaysDeny.String(): - choice = xswd.AlwaysDeny - } - done <- struct{}{} - } - - options.OnChanged = func(s string) { - switch s { - case xswd.Allow.String(), xswd.AlwaysAllow.String(): - btnDismiss.Importance = widget.HighImportance - case xswd.Deny.String(), xswd.AlwaysDeny.String(): - btnDismiss.Importance = widget.MediumImportance - } - btnDismiss.SetText(s) - btnDismiss.Refresh() - } - - linkRemove := widget.NewHyperlinkWithStyle("Remove Application", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkRemove.OnTapped = func() { - verificationOverlay( - false, - ad.Name, - "Remove this application?", - "Remove", - func(b bool) { - if b { - cyberdeck.WS.server.RemoveApplication(ad) - } - }, - ) - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - container.NewCenter( - container.NewStack( - span, - ), - ), - rectSpacer, - rectSpacer, - content, - btnDismiss, - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkRemove, - layout.NewSpacer(), - ), - rectSpacer, - ), - ), - ), - ) - - if a.Driver().Device().IsMobile() { - fyne.CurrentApp().SendNotification(&fyne.Notification{Title: ad.Name, Content: "A new permission request has been received"}) - } else { - session.Window.RequestFocus() - } - - // Wait for user input or socket close - select { - case <-done: - - case <-ad.OnClose: - - } - - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - - go refreshXSWDList() - - return choice -} - -// Refresh list of connected XSWD apps -func refreshXSWDList() { - time.Sleep(time.Second) - if cyberdeck.WS.server != nil { - cyberdeck.WS.apps = cyberdeck.WS.server.GetApplications() - sort.Slice(cyberdeck.WS.apps, func(i, j int) bool { return cyberdeck.WS.apps[i].Name < cyberdeck.WS.apps[j].Name }) - if cyberdeck.WS.list != nil { - fyne.Do(func() { - cyberdeck.WS.list.UnselectAll() - cyberdeck.WS.list.FocusLost() - cyberdeck.WS.list.Refresh() - }) - } - } -} - -// Ask permission to complete a specific Engram action, using xswd permissions to match existing requests that have params to display -func AskPermissionForRequestE(headerText string, params interface{}) (choice xswd.Permission, err error) { - choice = xswd.Deny - - var paramString string - - switch p := params.(type) { - case TELALink_Params: - paramString, err = handleTELALinkRequest(p) - if err != nil { - err = fmt.Errorf("denied TELA link request %s: %s", p.TelaLink, err) - return - } - case string: - switch p { - case "TELA R OFF": - paramString = "You will be viewing all TELA content as per your TELA settings.\n\n" - paramString += "Min Likes will omit results if they are below the set likes ratio.\n\n" - paramString += "Search exclusions will omit results that include the set exclusion text in their dURL." - default: - err = fmt.Errorf("unknown Engram request param string: %s", p) - return - } - default: - err = fmt.Errorf("unknown Engram request params: %T", p) - return - } - - overlay := session.Window.Canvas().Overlays() - - header := canvas.NewText(headerText, colors.Gray) - header.TextSize = 16 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - labelApp := canvas.NewText("FROM", colors.Gray) - labelApp.TextSize = 14 - labelApp.Alignment = fyne.TextAlignLeading - labelApp.TextStyle = fyne.TextStyle{Bold: true} - - textApp := widget.NewRichTextFromMarkdown("### Engram") - textApp.Wrapping = fyne.TextWrapWord - - labelParams := canvas.NewText("PARAMETERS", colors.Gray) - labelParams.TextSize = 14 - labelParams.Alignment = fyne.TextAlignLeading - labelParams.TextStyle = fyne.TextStyle{Bold: true} - - textParams := widget.NewLabel(paramString) - textParams.Wrapping = fyne.TextWrapWord - - rectBox := canvas.NewRectangle(color.Transparent) - rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.90, ui.MaxHeight*0.48)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(0, 10)) - - permissions := []string{ - xswd.Allow.String(), - xswd.Deny.String(), - } - - options := widget.NewSelect(permissions, nil) - - content := container.NewStack( - container.NewBorder( - nil, - container.NewVBox( - rectSpacer, - rectSpacer, - options, - rectSpacer, - rectSpacer, - ), - nil, - nil, - container.NewStack( - rectBox, - container.NewVScroll( - container.NewVBox( - labelApp, - textApp, - rectSpacer, - labelParams, - textParams, - rectSpacer, - ), - ), - ), - ), - ) - - // Create and show request prompt - done := make(chan struct{}) - btnDismiss := widget.NewButton("Deny", nil) - btnDismiss.OnTapped = func() { - switch options.Selected { - case xswd.Allow.String(): - choice = xswd.Allow - default: - choice = xswd.Deny - } - done <- struct{}{} - } - - options.OnChanged = func(s string) { - switch s { - case xswd.Allow.String(): - btnDismiss.Importance = widget.HighImportance - default: - btnDismiss.Importance = widget.MediumImportance - } - btnDismiss.SetText(s) - btnDismiss.Refresh() - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - container.NewCenter( - container.NewStack( - span, - ), - ), - rectSpacer, - rectSpacer, - rectSpacer, - content, - btnDismiss, - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ), - ), - ) - - // Wait for user input - <-done - - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - - return -} - -func isASCII(s string) bool { - for _, c := range s { - if c > unicode.MaxASCII { - return false - } - } - return true -} - -// Wrapper for serving TELA content toggling tela.updates if disabled, updated content should be checked for and presented to the user before calling serveTELAUpdates -func serveTELAUpdates(scid string) (link string, err error) { - var toggledUpdates bool - if !tela.UpdatesAllowed() { - tela.AllowUpdates(true) - toggledUpdates = true - } - - link, err = tela.ServeTELA(scid, session.Daemon) - if toggledUpdates { - tela.AllowUpdates(false) - } - - return -} - -// Convert TELA error to shortened string for display -func telaErrorToString(err error) string { - str := "serving TELA" - if strings.Contains(err.Error(), "user defined no updates and content has been updated to") { - str = "content has been updated" - } else if strings.Contains(err.Error(), "already exists") { - str = "content already exists" - } - - return fmt.Sprintf("%s %s", "error", str) -} - -// Get the ratio of likes for a TELA SCID, if ratio < minLines an error will be returned -func getLikesRatio(scid, dURL, searchExclusions string, minLikes float64) (ratio float64, ratings tela.Rating_Result, err error) { - if gnomon.Index == nil { - err = fmt.Errorf("gnomon is not online") - return - } - - err = telaFilterSearchExclusions(dURL, searchExclusions) - if err != nil { - return - } - - _, up := gnomon.GetSCIDValuesByKey(scid, "likes") - if up == nil { - err = fmt.Errorf("could not get %s likes", scid) - return - } - - _, down := gnomon.GetSCIDValuesByKey(scid, "dislikes") - if down == nil { - err = fmt.Errorf("could not get %s dislikes", scid) - return - } - - ratings.Likes = up[0] - ratings.Dislikes = down[0] - - total := float64(up[0] + down[0]) - if total == 0 { - ratio = 50 - } else { - ratio = (float64(up[0]) / total) * 100 - } - - if ratio < minLikes { - err = fmt.Errorf("%s is below min rating setting", scid) - } - - return -} - -// Check if a search exclusion is found in a TELA dURL -func telaFilterSearchExclusions(dURL, searchExclusions string) (err error) { - for _, split := range strings.Split(searchExclusions, ",") { - exclude := strings.TrimSpace(split) - if exclude != "" && strings.Contains(dURL, exclude) { - err = fmt.Errorf("found search exclusion %q in dURL %s", exclude, dURL) - return - } - } - - return -} - -// Sort and return search display strings for list widget -func telaSearchDisplayAll(telaSearch []INDEXwithRatings, sortBy string) (display []string) { - switch sortBy { - case "Z-A": - sort.Slice(telaSearch, func(i, j int) bool { - return telaSearch[i].NameHdr > telaSearch[j].NameHdr - }) - case "A-Z": - sort.Slice(telaSearch, func(i, j int) bool { - return telaSearch[i].NameHdr < telaSearch[j].NameHdr - }) - default: // Ratings - sort.Slice(telaSearch, func(i, j int) bool { - if telaSearch[i].ratings.Likes != telaSearch[j].ratings.Likes { - return telaSearch[i].ratings.Likes > telaSearch[j].ratings.Likes - } - - return telaSearch[i].ratings.Dislikes < telaSearch[j].ratings.Dislikes - }) - } - - for _, ind := range telaSearch { - display = append(display, ind.NameHdr+";;;"+ind.SCID) - } - - return -} - -// Validate the URL as URI or SC image and return it as a canvas.Image -func handleImageURL(nameHdr, imageURL string, size fyne.Size) (image *canvas.Image, err error) { - scImage, err := tela.ValidateImageURL(imageURL, session.Daemon) - if err != nil { - return - } - - var resource fyne.Resource - image = canvas.NewImageFromResource(nil) - - if scImage != "" { - resource = fyne.NewStaticResource(nameHdr, []byte(scImage)) - } else { - resource, err = fyne.LoadResourceFromURLString(imageURL) - if err != nil { - return - } - } - - image.Resource = resource - image.SetMinSize(size) - image.FillMode = canvas.ImageFillContain - image.Refresh() - - return -} - -// Convert session.Domain to string for display -func sessionDomainToString(domain string) string { - str := strings.TrimPrefix(domain, "app.") - switch str { - // case "main": - // case "create": - // case "restore": - // case "settings": - case "wallet": - return "Dashboard" - // case "register": - case "explorer": - return "Asset Explorer" - case "manager": - return "Asset Manager" - case "send", "transfers", "messages", "cyberdeck", "Identity", "datapad": - return fmt.Sprintf("%s%s", strings.ToUpper(str[0:1]), str[1:]) - case "tela", "tela.manager": - return "TELA" - case "service": - return "Services" - case "sign", "verify": - return "File Manager" - case "messages.contact": - return "Message Contact" - case "cyberdeck.manager": - return "Cyberdeck Manager" - case "cyberdeck.permissions": - return "Cyberdeck Settings" - case "sc.builder": - return "Contract Builder" - case "sc.editor": - return "Contract Editor" - default: - return "" - } -} - -// Add EPOCH session values to the account total stores -func storeEPOCHTotal(timeout time.Duration) { - epochSession, err := epoch.GetSession(timeout) - if err == nil { - cyberdeck.EPOCH.total.Hashes += epochSession.Hashes - cyberdeck.EPOCH.total.MiniBlocks += epochSession.MiniBlocks - - var eMar []byte - if eMar, err = json.Marshal(cyberdeck.EPOCH.total); err == nil { - err = StoreEncryptedValue("Cyberdeck", []byte("EPOCH"), eMar) - } - } - - if err != nil { - logger.Errorf("[EPOCH] Storing total: %s\n", err) - } -} - -// Store account EPOCH session and stop EPOCH -func stopEPOCH() { - if cyberdeck.EPOCH.enabled { - storeEPOCHTotal(time.Second * 4) - } - - epoch.StopGetWork() - cyberdeck.EPOCH.enabled = false - //cyberdeck.EPOCH.allowWithAddress = false -} - -// Check if value exists within a string array/slice -func scidExist(s []string, str string) bool { - for _, v := range s { - if v == str { - return true - } - } - - return false -} +// Copyright 2023-2024 DERO Foundation. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import ( + "context" + "crypto/rand" + "crypto/sha1" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "image/color" + "math/big" + "net" + "os" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "time" + "unicode" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" + x "fyne.io/x/fyne/widget" + "github.com/civilware/Gnomon/indexer" + "github.com/civilware/Gnomon/rwc" + "github.com/civilware/Gnomon/structures" + "github.com/civilware/epoch" + "github.com/civilware/tela" + "github.com/civilware/tela/logger" + "github.com/civilware/tela/shards" + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/channel" + "github.com/creachadair/jrpc2/handler" + "github.com/gorilla/websocket" + "mvdan.cc/xurls/v2" + + "github.com/civilware/Gnomon/storage" + "github.com/deroproject/derohe/config" + "github.com/deroproject/derohe/cryptography/crypto" + "github.com/deroproject/derohe/dvm" + "github.com/deroproject/derohe/globals" + + "github.com/deroproject/derohe/rpc" + "github.com/deroproject/derohe/transaction" + + "github.com/deroproject/derohe/walletapi" + "github.com/deroproject/derohe/walletapi/mnemonics" + "github.com/deroproject/derohe/walletapi/rpcserver" + "github.com/deroproject/derohe/walletapi/xswd" +) + +type App struct { + App fyne.App + Window fyne.Window + Focus bool +} + +type UI struct { + Padding float32 + MaxWidth float32 + Width float32 + MaxHeight float32 + Height float32 +} + +type Colors struct { + Network color.Color + Account color.Color + Blue color.Color + Red color.Color + DarkGreen color.Color + Green color.Color + Gray color.Color + Yellow color.Color + DarkMatter color.Color + Cold color.Color + Flint color.Color +} + +type Navigation struct { + PosX float32 + PosY float32 + CurX float32 + CurY float32 +} + +type Session struct { + Window fyne.Window + DesktopMode bool + Domain string + LastDomain fyne.CanvasObject + Network string + Offline bool + Language int + ID string + Link string + Type string + Daemon string + WalletOpen bool + Username string + Datapad string + DatapadChanged bool + LastBalance uint64 + Balance uint64 + BalanceUSD string + BalanceText *canvas.Text + BalanceUSDText *canvas.Text + ModeText *canvas.Text + IDText *canvas.Text + LinkText *canvas.Text + StatusText *canvas.Text + Path string + Name string + Password string + PasswordConfirm string + DaemonHeight uint64 + WalletHeight uint64 + RPCServer *rpcserver.RPCServer + Verified bool + Dashboard string + Error string + NewUser string + Gif *x.AnimatedGif + RegHashes int64 + LimitMessages bool + TrackRecentBlocks int64 +} + +type Cyberdeck struct { + RPC struct { + user string + pass string + port string + userText *widget.Entry + passText *widget.Entry + portText *widget.Entry + toggle *widget.Button + status *canvas.Text + server *rpcserver.RPCServer + } + WS struct { + sync.RWMutex + port string + portText *widget.Entry + list *widget.List + toggle *widget.Button + status *canvas.Text + server *xswd.XSWD + apps []xswd.ApplicationData + advanced bool + global struct { + connect bool + enabled bool + status *canvas.Text + permissions map[string]xswd.Permission + } + } + EPOCH struct { + enabled bool + allowWithAddress bool + err error + total epoch.GetSessionEPOCH_Result + } +} + +type INDEXwithRatings struct { + ratings tela.Rating_Result + tela.INDEX +} + +type Engram struct { + Disk *walletapi.Wallet_Disk +} + +type Theme struct { + main eTheme + alt eTheme2 +} + +type Gnomon struct { + Active int + Index *indexer.Indexer + BBolt *storage.BboltStore + Graviton *storage.GravitonStore + Path string +} + +type ProofData struct { + Receivers []string + Amounts []uint64 + Payloads []string +} + +type Status struct { + Canvas *canvas.Text + Message string + Network *canvas.Text + Connection *canvas.Circle + Sync *canvas.Circle + Cyberdeck *canvas.Circle + Gnomon *canvas.Circle + EPOCH *canvas.Circle +} + +type Transfers struct { + Address *rpc.Address + PaymentID uint64 + Amount uint64 + Comment string + GasStorage uint64 + Fees uint64 + Pending []rpc.Transfer + TX *transaction.Transaction + TXID crypto.Hash + Proof string + Ringsize uint64 + SendAll bool + Size float32 + Status string + OfflineTX bool + Filename string +} + +type MessageBox struct { + List *widget.List + Data binding.ExternalStringList +} + +type Messages struct { + Contact string + Address string + Data []string + Height uint64 + Message string +} + +type InstallContract struct { + TXID string +} + +type Client struct { + WS *websocket.Conn + RPC *jrpc2.Client +} + +// Get the Engram settings from the local Graviton tree +func initSettings() { + getNetwork() + getMode() + getDaemon() + getGnomon() + getRPCCredentials() + if a.Driver().Device().IsMobile() { + err := tela.SetShardPath(filepath.Join(AppPath(), filepath.Dir(shards.GetPath()))) + if err != nil { + logger.Errorf("[Engram] Setting TELA shard: %s\n", err) + return + } + + os.RemoveAll(tela.GetPath()) + } +} + +// Go routine to update the latest information from the connected daemon (Online Mode only) +func StartPulse() { + if !walletapi.Connected && engram.Disk != nil { + logger.Printf("[Network] Attempting network connection to: %s\n", walletapi.Daemon_Endpoint) + err := walletapi.Connect(session.Daemon) + if err != nil { + logger.Errorf("[Network] Failed to connect to: %s\n", walletapi.Daemon_Endpoint) + walletapi.Connected = false + closeWallet() + session.Window.SetContent(layoutAlert(1)) + removeOverlays() + return + } else { + sentNotifications := false + walletapi.Connected = true + engram.Disk.SetOnlineMode() + session.BalanceText = canvas.NewText("", colors.Blue) + session.StatusText = canvas.NewText("", colors.Blue) + status.Connection.FillColor = colors.Gray + status.Sync.FillColor = colors.Gray + + go func() { + count := 0 + for engram.Disk != nil { + if walletapi.Get_Daemon_Height() < 1 || !walletapi.Connected { + logger.Printf("[Network] Attempting network connection to: %s\n", walletapi.Daemon_Endpoint) + err := walletapi.Connect(session.Daemon) + if err != nil { + // If we fail DEFAULT_DAEMON_RECONNECT_TIMEOUT+ times, display node communication layout err + if count >= DEFAULT_DAEMON_RECONNECT_TIMEOUT { + walletapi.Connected = false + closeWallet() + session.Window.SetContent(layoutAlert(1)) + removeOverlays() + break + } + count++ + logger.Errorf("[Network] Failed to connect to: %s (%d / %d)\n", walletapi.Daemon_Endpoint, count, DEFAULT_DAEMON_RECONNECT_TIMEOUT) + walletapi.Connected = false + status.Connection.FillColor = colors.Red + status.Sync.FillColor = colors.Red + status.Gnomon.FillColor = colors.Red + status.EPOCH.FillColor = colors.Red + session.Offline = true + + time.Sleep(time.Second) + continue + } else { + count = 0 + time.Sleep(time.Second) + session.Offline = false + } + } + + if !engram.Disk.IsRegistered() { + if !walletapi.Connected { + logger.Errorf("[Network] Could not connect to daemon...%d\n", engram.Disk.Get_Daemon_TopoHeight()) + status.Connection.FillColor = colors.Red + status.Connection.Refresh() + status.Sync.FillColor = colors.Red + } + + time.Sleep(time.Second) + } else { + if session.WalletHeight != engram.Disk.Get_Height() { + sentNotifications = false + } + + session.Balance, _ = engram.Disk.Get_Balance() + session.BalanceText.Text = globals.FormatMoney(session.Balance) + session.WalletHeight = engram.Disk.Get_Height() + session.DaemonHeight = engram.Disk.Get_Daemon_Height() + session.StatusText.Text = fmt.Sprintf("%d", session.WalletHeight) + + session.LastBalance = session.Balance + + if walletapi.IsDaemonOnline() { + status.Connection.FillColor = colors.Green + if session.DaemonHeight > 0 && session.DaemonHeight-session.WalletHeight < 2 { + status.Connection.FillColor = colors.Green + status.Sync.FillColor = colors.Green + } else if session.DaemonHeight == 0 { + status.Sync.FillColor = colors.Red + } else { + status.Sync.FillColor = color.Transparent + } + + if gnomon.Index != nil { + if gnomon.Index.Status == "indexed" { + status.Gnomon.FillColor = colors.Green + } else { + if uint64(gnomon.Index.LastIndexedHeight) < session.WalletHeight-15 { + status.Gnomon.FillColor = colors.Red + } else { + status.Gnomon.FillColor = color.Transparent + } + } + } else { + status.Gnomon.FillColor = colors.Gray + + if gnomon.Index == nil && engram.Disk != nil { + enableGnomon, _ := getGnomon() + if enableGnomon == "1" { + startGnomon() + } + } + } + + if epoch.IsActive() { + if epoch.IsProcessing() { + status.EPOCH.FillColor = color.Transparent + } else { + status.EPOCH.FillColor = colors.Green + } + } else { + if cyberdeck.EPOCH.err != nil { + status.EPOCH.FillColor = colors.Red + } else { + status.EPOCH.FillColor = colors.Gray + } + } + } else { + status.Connection.FillColor = colors.Gray + status.Sync.FillColor = colors.Gray + status.Cyberdeck.FillColor = colors.Gray + status.Gnomon.FillColor = colors.Gray + status.EPOCH.FillColor = colors.Gray + logger.Printf("[Network] Offline › Last Height: " + strconv.FormatUint(session.WalletHeight, 10) + " / " + strconv.FormatUint(session.DaemonHeight, 10) + "\n") + } + + // Check for updates and send appropriate notifications + var zeroscid crypto.Hash + + // Query incoming messages + entries := engram.Disk.Show_Transfers(zeroscid, false, true, false, session.WalletHeight-1, session.WalletHeight-1, "", "", uint64(1337), 0) + + for e := range entries { + if entries[e].Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) && !sentNotifications { + sender := entries[e].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) + + notification := fyne.NewNotification(sender, "New message was received (Height: "+fmt.Sprintf("%d", entries[e].Height)+")") + fyne.CurrentApp().SendNotification(notification) + + sentNotifications = true + } + } + + fyne.Do(func() { + session.BalanceText.Refresh() + session.StatusText.Refresh() + status.Connection.Refresh() + status.Sync.Refresh() + status.Cyberdeck.Refresh() + status.Gnomon.Refresh() + status.EPOCH.Refresh() + }) + + time.Sleep(time.Second) + } + } + + if walletapi.Connected { + walletapi.Connected = false + } + }() + } + } +} + +// Get Network setting from the local Graviton tree (Ex: Mainnet, Testnet, Simulator) +func getNetwork() (network string) { + result, err := GetValue("settings", []byte("network")) + if err != nil { + network = NETWORK_MAINNET + session.Network = network + globals.Arguments["--testnet"] = false + globals.Arguments["--simulator"] = false + setNetwork(network) + return + } else { + if string(result) == NETWORK_TESTNET { + network = NETWORK_TESTNET + session.Network = network + globals.Arguments["--testnet"] = true + globals.Arguments["--simulator"] = false + return + } else if string(result) == NETWORK_SIMULATOR { + network = NETWORK_SIMULATOR + session.Network = network + globals.Arguments["--testnet"] = true + globals.Arguments["--simulator"] = true + return + } else { + network = NETWORK_MAINNET + session.Network = network + globals.Arguments["--testnet"] = false + globals.Arguments["--simulator"] = false + return + } + } +} + +// Set Network setting to the local Graviton tree (Ex: Mainnet, Testnet, Simulator) +func setNetwork(network string) (err error) { + s := "" + if network == NETWORK_MAINNET { + s = network + globals.Arguments["--testnet"] = false + globals.Arguments["--simulator"] = false + } else if network == NETWORK_SIMULATOR { + s = network + globals.Arguments["--testnet"] = true + globals.Arguments["--simulator"] = true + } else { + s = NETWORK_TESTNET + globals.Arguments["--testnet"] = true + globals.Arguments["--simulator"] = false + } + + session.Network = s + + StoreValue("settings", []byte("network"), []byte(s)) + + return +} + +// Get daemon endpoint setting from the local Graviton tree +func getDaemon() (r string) { + result, err := GetValue("settings", []byte("endpoint")) + if err != nil { + if checkLocalNode() { + r = "127.0.0.1:10102" + } else { + r = DEFAULT_REMOTE_DAEMON + } + setDaemon(r) + session.Daemon = r + globals.Arguments["--daemon-address"] = r + return + } + + r = string(result) + session.Daemon = r + globals.Arguments["--daemon-address"] = r + return +} + +func checkLocalNode() bool { + conn, err := net.DialTimeout("tcp", "127.0.0.1:10102", 500*time.Millisecond) + if err != nil { + return false + } + defer conn.Close() + return true +} + +func testNodeConnection(address string) bool { + conn, err := net.DialTimeout("tcp", address, 2*time.Second) + if err != nil { + return false + } + defer conn.Close() + return true +} + +func testNodeConnectionTimeout(address string, timeout time.Duration) bool { + conn, err := net.DialTimeout("tcp", address, timeout) + if err != nil { + return false + } + defer conn.Close() + return true +} + +// Set the daemon endpoint setting to local Graviton tree +func setDaemon(s string) (err error) { + StoreValue("settings", []byte("endpoint"), []byte(s)) + globals.Arguments["--daemon-address"] = s + session.Daemon = s + return +} + +// Get Cyberdeck endpoint setting from the local Graviton tree +func getCyberdeck(key string) (r string) { + switch key { + case "RPC": + key = "port.RPC" + case "WS": + key = "port.WS" + case "EPOCH": + key = "port.EPOCH" + default: + return + } + + stored, err := GetEncryptedValue("Cyberdeck", []byte(key)) + if err != nil { + logger.Debugf("[Engram] getCyberdeck %s: %s\n", key, err) + return + } + + return string(stored) +} + +// Set Cyberdeck endpoint setting to the local Graviton tree +func setCyberdeck(port, key string) { + switch key { + case "RPC": + key = "port.RPC" + case "WS": + key = "port.WS" + case "EPOCH": + key = "port.EPOCH" + default: + logger.Debugf("[Engram] setCyberdeck: invalid key\n") + return + } + + err := StoreEncryptedValue("Cyberdeck", []byte(key), []byte(port)) + if err != nil { + logger.Debugf("[Engram] setCyberdeck %s: %s\n", key, err) + } +} + +// Get mode (online, offline) setting from local Graviton tree +func getMode() { + + /* + if globals.Arguments["--offline"].(bool) == true { + session.Mode = "Offline" + return + } + + s := "mode" + t := "settings" + key := []byte(s) + result, err := GetValue(t, key) + if err != nil { + session.Mode = "Online" + err := setMode("Online") + globals.Arguments["--offline"] = false + if err != nil { + fmt.Printf("[Engram] Error: %s\n", err) + return + } + } else { + if result == nil { + session.Mode = "Online" + err := setMode("Online") + globals.Arguments["--offline"] = false + if err != nil { + fmt.Printf("[Engram] Error: %s\n", err) + return + } + } else { + if string(result) == "Offline" { + globals.Arguments["--offline"] = true + session.Mode = "Offline" + } else { + globals.Arguments["--offline"] = false + session.Mode = "Online" + } + } + } + */ +} + +// Set the default Offline Mode settings to the local Graviton tree +/* +func setMode(s string) (err error) { + err = StoreValue("settings", []byte("mode"), []byte(s)) + if s == "Offline" { + globals.Arguments["--offline"] = true + } else { + globals.Arguments["--offline"] = false + } + return +} +*/ + +// Get the default Gnomon settings from local Graviton tree +func getGnomon() (r string, err error) { + v, err := GetValue("settings", []byte("gnomon")) + if err != nil { + gnomon.Active = 1 + if gnomon.Index != nil { + gnomon.Index.Endpoint = getDaemon() + } + StoreValue("settings", []byte("gnomon"), []byte("1")) + } + + if string(v) == "1" { + gnomon.Active = 1 + if gnomon.Index != nil { + gnomon.Index.Endpoint = getDaemon() + } + } else { + gnomon.Active = 0 + } + + r = string(v) + return +} + +// Set the default Gnomon settings to the local Graviton tree +func setGnomon(s string) (err error) { + if s == "1" { + err = StoreValue("settings", []byte("gnomon"), []byte("1")) + gnomon.Active = 1 + if gnomon.Index != nil { + gnomon.Index.Endpoint = getDaemon() + } + } else { + err = StoreValue("settings", []byte("gnomon"), []byte("0")) + gnomon.Active = 0 + } + return +} + +// Get the RPC credentials from local Graviton tree +func getRPCCredentials() { + if user, err := GetValue("settings", []byte("rpc_user")); err == nil && len(user) > 0 { + cyberdeck.RPC.user = string(user) + } + if pass, err := GetValue("settings", []byte("rpc_pass")); err == nil && len(pass) > 0 { + cyberdeck.RPC.pass = string(pass) + } +} + +/* +func getAuthMode() (result string, err error) { + r, err := GetValue("settings", []byte("auth_mode")) + if err != nil { + StoreValue("settings", []byte("auth_mode"), []byte("true")) + cyberdeck.mode = 1 + result = "true" + } else { + result = string(r) + if string(result) == "true" { + cyberdeck.mode = 1 + result = "true" + } else { + cyberdeck.mode = 0 + result = "false" + } + } + return +} +*/ + +// Get the auth_mode settings from local Graviton tree +func setAuthMode(s string) { + if s == "true" { + StoreValue("settings", []byte("auth_mode"), []byte("true")) + } else { + StoreValue("settings", []byte("auth_mode"), []byte("false")) + } +} + +// Check if a URL exists in the string +func getTextURL(s string) (result []string) { + return xurls.Relaxed().FindAllString(s, -1) +} + +// Set the window size from provided height and width +func resizeWindow(width float32, height float32) { + s := fyne.NewSize(width, height) + session.Window.Resize(s) +} + +// Close the active wallet +func closeWallet() { + showLoadingOverlay() + + if engram.Disk != nil { + logger.Printf("[Engram] Shutting down wallet services...\n") + stopEPOCH() + engram.Disk.SetOfflineMode() + engram.Disk.Save_Wallet() + + globals.Exit_In_Progress = true + engram.Disk.Close_Encrypted_Wallet() + session.WalletOpen = false + session.Domain = "app.main" + session.BalanceUSD = "" + session.LastBalance = 0 + engram.Disk = nil + tx = Transfers{} + + if gnomon.Index != nil { + logger.Printf("[Gnomon] Shutting down indexers...\n") + stopGnomon() + } + + if cyberdeck.RPC.server != nil { + cyberdeck.RPC.server.RPCServer_Stop() + cyberdeck.RPC.server = nil + logger.Printf("[Engram] Cyberdeck RPC closed.\n") + } + + if cyberdeck.WS.server != nil { + cyberdeck.WS.server.Stop() + cyberdeck.WS.server = nil + cyberdeck.WS.apps = nil + cyberdeck.WS.list = nil + logger.Printf("[Engram] Cyberdeck XSWD closed.\n") + } + cyberdeck.WS.advanced = false + cyberdeck.WS.global.enabled = false + cyberdeck.WS.global.connect = false + + tela.ShutdownTELA() + + if rpc_client.WS != nil { + rpc_client.WS.Close() + rpc_client.WS = nil + logger.Printf("[Engram] Websocket client closed.\n") + } + + if rpc_client.RPC != nil { + rpc_client.RPC.Close() + rpc_client.RPC = nil + logger.Printf("[Engram] RPC client closed.\n") + } + + session.Path = "" + session.Name = "" + + session.LastDomain = layoutMain() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMain()) + removeOverlays() + //session.Window.CenterOnScreen() + logger.Printf("[Engram] Wallet saved and closed successfully.\n") + return + } +} + +// Create a new account and wallet file +func create() (address string, seed string, err error) { + check := findAccount() + + if session.Path == "" { + session.Error = "Please enter an account name." + } else if session.Language == -1 { + session.Error = "Please select a language." + } else if session.Password == "" { + session.Error = "Please enter a password." + } else if session.PasswordConfirm == "" { + session.Error = "Please confirm your password." + } else if session.PasswordConfirm != session.Password { + session.Error = "Passwords do not match." + } else if check { + session.Error = "Account name already exists." + } else { + engram.Disk, err = walletapi.Create_Encrypted_Wallet_Random(session.Path, session.Password) + + if err != nil { + session.Language = -1 + session.Name = "" + session.Path = "" + session.Password = "" + session.PasswordConfirm = "" + session.Error = "Account could not be created." + } else { + switch session.Network { + case NETWORK_TESTNET: + engram.Disk.SetNetwork(false) + globals.Arguments["--testnet"] = true + globals.Arguments["--simulator"] = false + case NETWORK_SIMULATOR: + engram.Disk.SetNetwork(false) + globals.Arguments["--testnet"] = true + globals.Arguments["--simulator"] = true + default: + engram.Disk.SetNetwork(true) + globals.Arguments["--testnet"] = false + globals.Arguments["--simulator"] = false + } + + languages := mnemonics.Language_List() + + if session.Language < 0 || session.Language > len(languages)-1 { + session.Language = 0 // English + } + + engram.Disk.SetSeedLanguage(languages[session.Language]) + address = engram.Disk.GetAddress().String() + seed = engram.Disk.GetSeed() + engram.Disk.Close_Encrypted_Wallet() + engram.Disk = nil + session.Error = "Account successfully created." + session.Language = -1 + session.Name = "" + session.Path = "" + session.Password = "" + session.PasswordConfirm = "" + session.Domain = "app.main" + } + } + return +} + +// The main login routine +func login() { + showLoadingOverlay() + + if engram.Disk == nil { + temp, err := walletapi.Open_Encrypted_Wallet(session.Path, session.Password) + if err != nil { + session.Domain = "app.main" + session.Error = err.Error() + if len(session.Error) > 40 { + session.Error = fmt.Sprintf("%s...", session.Error[0:40]) + } + session.Window.Canvas().Content().Refresh() + removeOverlays() + return + } + + engram.Disk = temp + session.Password = "" + } + + switch session.Network { + case NETWORK_TESTNET: + engram.Disk.SetNetwork(false) + globals.Arguments["--testnet"] = true + globals.Arguments["--simulator"] = false + case NETWORK_SIMULATOR: + engram.Disk.SetNetwork(false) + globals.Arguments["--testnet"] = true + globals.Arguments["--simulator"] = true + default: + engram.Disk.SetNetwork(true) + globals.Arguments["--testnet"] = false + globals.Arguments["--simulator"] = false + } + + session.WalletOpen = true + session.BalanceUSD = "" + session.LastBalance = 0 + + if !session.Offline { + walletapi.SetDaemonAddress(session.Daemon) + engram.Disk.SetDaemonAddress(session.Daemon) + + if session.TrackRecentBlocks > 0 { + logger.Printf("[Engram] Scan tracking enabled, only scanning the last %d blocks...\n", session.TrackRecentBlocks) + engram.Disk.SetTrackRecentBlocks(session.TrackRecentBlocks) + } + + if s, err := strconv.Atoi(getCyberdeck("EPOCH")); err == nil { + if err := epoch.SetPort(s); err != nil { + logger.Errorf("[Engram] Setting EPOCH port: %s\n", err) + } + } + + cyberdeck.EPOCH.total.Hashes = 0 + cyberdeck.EPOCH.total.MiniBlocks = 0 + if epochData, err := GetEncryptedValue("Cyberdeck", []byte("EPOCH")); err == nil { + if err := json.Unmarshal(epochData, &cyberdeck.EPOCH.total); err != nil { + logger.Errorf("[Engram] Setting EPOCH total: %s\n", err) + } + } + + go StartPulse() + } else { + engram.Disk.SetOfflineMode() + status.Connection.FillColor = colors.Gray + status.Connection.Refresh() + status.Sync.FillColor = colors.Gray + status.Sync.Refresh() + } + + setRingSize(engram.Disk, 16) + session.Verified = false + + if !session.Offline { + // Online mode + status.Connection.FillColor = colors.Green + status.Connection.Refresh() + session.Balance = 0 + + count := 0 + for count < 5 { + if !walletapi.Connected { + count += 1 + time.Sleep(time.Second) + } else { + break + } + } + + if !walletapi.Connected { + closeWallet() + session.Window.SetContent(layoutAlert(1)) + removeOverlays() + return + } + + if engram.Disk.Get_Height() < session.DaemonHeight { + time.Sleep(time.Second * 1) + } + + for i := 0; i < 10; i++ { + height := engram.Disk.Get_Registration_TopoHeight() + + if height < 1 { + time.Sleep(time.Second * 1) + } else { + break + } + + if i == 9 { + registerAccount() + removeOverlays() + session.Verified = true + logger.Printf("[Registration] Account registration PoW started...\n") + logger.Printf("[Registration] Registering your account. This can take up to 120 minutes (one time). Please wait...\n") + return + } + } + + go startGnomon() + } + + if a.Driver().Device().IsMobile() { + session.Domain = "app.wallet" + resizeWindow(ui.MaxWidth, ui.MaxHeight) + } + + session.Window.SetContent(layoutDashboard()) + removeOverlays() + + session.Balance, _ = engram.Disk.Get_Balance() + session.BalanceText.Text = globals.FormatMoney(session.Balance) + session.BalanceText.Refresh() + + session.WalletHeight = engram.Disk.Wallet_Memory.Get_Height() + session.DaemonHeight = engram.Disk.Get_Daemon_Height() + session.StatusText.Text = fmt.Sprintf("%d", session.WalletHeight) + session.StatusText.Refresh() + + if session.WalletHeight == session.DaemonHeight && !session.Offline { + status.Sync.FillColor = colors.Green + status.Sync.Refresh() + } + + address := engram.Disk.GetAddress().String() + shard := fmt.Sprintf("%x", sha1.Sum([]byte(address))) + session.ID = shard + session.LimitMessages = true +} + +// Remove all overlays +func removeOverlays() { + overlays := session.Window.Canvas().Overlays() + list := overlays.List() + + for o := range list { + overlays.Remove(list[o]) + } + + if res.loading != nil { + res.loading.Hide() + res.loading.Stop() + res.loading = nil + } +} + +// Add an overlay with the loading animation +func showLoadingOverlay() { + frame := &iframe{} + + if res.loading == nil { + res.loading, _ = x.NewAnimatedGifFromResource(resourceLoadingGif) + res.loading.SetMinSize(fyne.NewSize(ui.Width*0.45, ui.Width*0.45)) + } + + rect := canvas.NewRectangle(colors.DarkMatter) + rect.SetMinSize(frame.Size()) + + background := container.NewStack( + rect, + container.NewCenter( + res.loading, + ), + ) + + res.loading.Start() + + layout := container.NewStack( + frame, + background, + ) + + overlays := session.Window.Canvas().Overlays() + overlays.Add(layout) +} + +// Load embedded resources +func loadResources() { + res.bg = canvas.NewImageFromResource(resourceBgPng) + res.bg.FillMode = canvas.ImageFillContain + + res.bg2 = canvas.NewImageFromResource(resourceBg2Png) + res.bg2.FillMode = canvas.ImageFillContain + + res.icon = canvas.NewImageFromResource(resourceIconPng) + res.icon.FillMode = canvas.ImageFillContain + + res.header = canvas.NewImageFromResource(resourceBackground1Png) + res.header.FillMode = canvas.ImageFillContain + + res.load = canvas.NewImageFromResource(resourceLoadPng) + res.load.FillMode = canvas.ImageFillStretch + + res.dero = canvas.NewImageFromResource(resourceDeroPng) + res.dero.FillMode = canvas.ImageFillContain + + res.gram = canvas.NewImageFromResource(resourceGramPng) + res.gram.FillMode = canvas.ImageFillContain + + res.block = canvas.NewImageFromResource(resourceBlankPng) + res.block.FillMode = canvas.ImageFillContain + + res.red_alert = canvas.NewImageFromResource(resourceRedAlertPng) + res.red_alert.FillMode = canvas.ImageFillContain + + res.green_alert = canvas.NewImageFromResource(resourceGreenAlertPng) + res.green_alert.FillMode = canvas.ImageFillContain + + res.mainBg = canvas.NewImageFromResource(resourceEngramMainPng) + res.mainBg.FillMode = canvas.ImageFillContain + +} + +// Validate if the provided word is a seed word +func checkSeedWord(w string) (check bool) { + split := strings.Split(w, " ") + + if len(split) > 1 { + return + } + _, _, _, check = mnemonics.Find_indices([]string{w}) + + return +} + +// Add a DERO transfer to the batch +func addTransfer() error { + var arguments = rpc.Arguments{} + var err error + + logger.Printf("[Send] Starting tx...\n") + if tx.Address.IsIntegratedAddress() { + if tx.Address.Arguments.Validate_Arguments() != nil { + logger.Errorf("[Service] Integrated Address arguments could not be validated\n") + err = errors.New("integrated address arguments could not be validated") + return err + } + + logger.Printf("[Send] Not Integrated..\n") + if !tx.Address.Arguments.Has(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { + logger.Errorf("[Service] Integrated Address does not contain destination port\n") + err = errors.New("integrated address does not contain destination port") + return err + } + + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: tx.Address.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)}) + logger.Printf("[Send] Added arguments..\n") + + if tx.Address.Arguments.Has(rpc.RPC_EXPIRY, rpc.DataTime) { + + if tx.Address.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime).(time.Time).Before(time.Now().UTC()) { + logger.Errorf("[Service] This address has expired: %s\n", tx.Address.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) + err = errors.New("this address has expired") + return err + } else { + logger.Warnf("[Service] This address will expire: %s\n", tx.Address.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) + } + } + + logger.Printf("[Service] Destination port is integrated in address: %d\n", tx.Address.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)) + + if tx.Address.Arguments.Has(rpc.RPC_COMMENT, rpc.DataString) { + logger.Printf("[Service] Integrated Message: %s\n", tx.Address.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)) + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: tx.Address.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)}) + } + } + + logger.Printf("[Send] Checking arguments..\n") + + for _, arg := range tx.Address.Arguments { + if !(arg.Name == rpc.RPC_COMMENT || arg.Name == rpc.RPC_EXPIRY || arg.Name == rpc.RPC_DESTINATION_PORT || arg.Name == rpc.RPC_SOURCE_PORT || arg.Name == rpc.RPC_VALUE_TRANSFER || arg.Name == rpc.RPC_NEEDS_REPLYBACK_ADDRESS) { + switch arg.DataType { + case rpc.DataString: + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) + case rpc.DataInt64: + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) + case rpc.DataUint64: + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) + case rpc.DataFloat64: + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) + case rpc.DataTime: + logger.Warnf("[Service] Time currently not supported.\n") + } + } + } + + logger.Printf("[Send] Checking Amount..\n") + + if tx.Address.Arguments.Has(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { + logger.Printf("[Service] Transaction amount: %s\n", globals.FormatMoney(tx.Address.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64))) + tx.Amount = tx.Address.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64) + } else { + balance, _ := engram.Disk.Get_Balance() + logger.Printf("[Send] Balance: %d\n", balance) + logger.Printf("[Send] Amount: %d\n", tx.Amount) + + if tx.Amount > balance { + logger.Errorf("[Send] Error: Insufficient funds\n") + err = errors.New("insufficient funds") + return err + } else if tx.Amount == balance { + tx.SendAll = true + } else { + tx.SendAll = false + } + } + + logger.Printf("[Send] Checking services..\n") + + if tx.Address.Arguments.Has(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataUint64) { + logger.Printf("[Service] Reply Address required, sending: %s\n", engram.Disk.GetAddress().String()) + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_REPLYBACK_ADDRESS, DataType: rpc.DataAddress, Value: engram.Disk.GetAddress()}) + } + + logger.Printf("[Send] Checking payment ID/destination port..\n") + + if len(arguments) == 0 { + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: tx.PaymentID}) + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: tx.Comment}) + } + + logger.Printf("[Send] Checking Pack..\n") + + if _, err := arguments.CheckPack(transaction.PAYLOAD0_LIMIT); err != nil { + logger.Errorf("[Send] Arguments packing err: %s\n", err) + return err + } + + if tx.Ringsize == 0 { + tx.Ringsize = 2 + } else if tx.Ringsize > 128 { + tx.Ringsize = 128 + } else if !crypto.IsPowerOf2(int(tx.Ringsize)) { + tx.Ringsize = 2 + logger.Errorf("[Send] Error: Invalid ringsize - New ringsize = %d\n", tx.Ringsize) + err = errors.New("invalid ringsize") + return err + } + + tx.Status = "Unsent" + + logger.Printf("[Send] Ringsize: %d\n", tx.Ringsize) + + tx.Pending = append(tx.Pending, rpc.Transfer{Amount: tx.Amount, Destination: tx.Address.String(), Payload_RPC: arguments}) + logger.Printf("[Send] Added transfer to the pending list.\n") + + return nil +} + +// Send all batched transfers (TODO: export offline transactions to file in Offline mode) +func sendTransfers() (txid crypto.Hash, err error) { + if session.Offline { + return + } + + fees := ((tx.Ringsize + 1) * config.FEE_PER_KB) / 4 + if fees < 85 { + fees = 85 + } + + logger.Printf("[Send] Calculated Fees: %d\n", fees*uint64(len(tx.Pending))) + + tx.TX, err = engram.Disk.TransferPayload0(tx.Pending, tx.Ringsize, false, rpc.Arguments{}, fees, false) + if err != nil { + logger.Errorf("[Send] Error while building transaction: %s\n", err) + return + } + + if err = engram.Disk.SendTransaction(tx.TX); err != nil { + logger.Errorf("[Send] Error while dispatching transaction: %s\n", err) + return + } + + tx.Fees = tx.TX.Fees() + tx.TXID = tx.TX.GetHash() + + logger.Printf("[Send] Dispatched transaction: %s\n", tx.TXID) + + txid = tx.TX.GetHash() + + tx = Transfers{} + + return +} + +// Go Routine for account registration +func registerAccount() { + session.Domain = "app.register" + if engram.Disk == nil { + resizeWindow(ui.MaxWidth, ui.MaxHeight) + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMain()) + session.Domain = "app.main" + return + } + + link := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + link.OnTapped = func() { + session.Gif.Stop() + session.Gif = nil + closeWallet() + } + + title := canvas.NewText("R E G I S T R A T I O N", colors.Green) + title.TextStyle = fyne.TextStyle{Bold: true} + title.TextSize = 16 + + heading := canvas.NewText("Please wait...", colors.Gray) + heading.TextSize = 22 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + sub := canvas.NewText("This one-time process can take a while.", colors.Gray) + sub.TextSize = 14 + sub.Alignment = fyne.TextAlignCenter + sub.TextStyle = fyne.TextStyle{Bold: true} + + resizeWindow(ui.MaxWidth, ui.MaxHeight) + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutWaiting(title, heading, sub, link)) + + // Registration PoW + go func() { + var reg_tx *transaction.Transaction + successful_regs := make(chan *transaction.Transaction) + counter := 0 + session.RegHashes = 0 + + for i := 0; i < runtime.GOMAXPROCS(0)-1; i++ { + go func() { + for counter == 0 { + if engram.Disk == nil { + break + } else if engram.Disk.IsRegistered() { + break + } + + lreg_tx := engram.Disk.GetRegistrationTX() + hash := lreg_tx.GetHash() + session.RegHashes++ + + if hash[0] == 0 && hash[1] == 0 && hash[2] == 0 { + successful_regs <- lreg_tx + counter++ + break + } + } + }() + } + + if engram.Disk == nil { + session.Gif.Stop() + session.Gif = nil + + fyne.Do(func() { + resizeWindow(ui.MaxWidth, ui.MaxHeight) + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMain()) + }) + + session.Domain = "app.main" + return + } + + reg_tx = <-successful_regs + + logger.Printf("[Registration] Registration TXID: %s\n", reg_tx.GetHash()) + err := engram.Disk.SendTransaction(reg_tx) + if err != nil { + session.Gif.Stop() + session.Gif = nil + logger.Errorf("[Registration] Error: %s\n", err) + + fyne.Do(func() { + resizeWindow(ui.MaxWidth, ui.MaxHeight) + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMain()) + }) + + session.Domain = "app.main" + } else { + session.Gif.Stop() + session.Gif = nil + logger.Printf("[Registration] Registration transaction dispatched successfully.\n") + session.Domain = "app.wallet" + + fyne.Do(func() { + resizeWindow(ui.MaxWidth, ui.MaxHeight) + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + }) + } + }() +} + +// Set the ring size for transactions +func setRingSize(wallet *walletapi.Wallet_Disk, s int) bool { + if wallet == nil { + logger.Errorf("[Engram] No wallet found.\n") + return false + } + + // Minimum ring size is 2, only accept powers of 2. + if s < 2 { + wallet.SetRingSize(2) + logger.Printf("[Engram] Set minimum ring size: 2\n") + } else { + wallet.SetRingSize(s) + logger.Printf("[Engram] Set default ring size: %d\n", s) + } + + return true +} + +// Check if a username exists, return the registered address if so +func checkUsername(s string, h int64) (address string, err error) { + if session.Offline { + return + } + + if h < 0 { + address, err = engram.Disk.NameToAddress(s) + } else { + var params rpc.NameToAddress_Params + var response *jrpc2.Response + var result rpc.NameToAddress_Result + + rpc_client.WS, _, err = websocket.DefaultDialer.Dial("ws://"+session.Daemon+"/ws", nil) + if err != nil { + return + } + + input_output := rwc.New(rpc_client.WS) + rpc_client.RPC = jrpc2.NewClient(channel.RawJSON(input_output, input_output), nil) + + if rpc_client.RPC != nil { + params.Name = s + params.TopoHeight = h + + address = "" + response, err = rpc_client.RPC.Call(context.Background(), "DERO.NameToAddress", params) + + rpc_client.WS.Close() + rpc_client.RPC.Close() + + if err != nil { + return + } + + err = response.UnmarshalResult(&result) + if err != nil { + return + } + + if result.Status != "OK" { + err = errors.New("username does not exist") + return + } + + address = result.Address + } + } + + return +} + +// Get the transaction fees to be paid +func getGasEstimate(gp rpc.GasEstimate_Params) (gas uint64, err error) { + var result rpc.GasEstimate_Result + + rpc_client.WS, _, err = websocket.DefaultDialer.Dial("ws://"+session.Daemon+"/ws", nil) + if err != nil { + return + } + + input_output := rwc.New(rpc_client.WS) + rpc_client.RPC = jrpc2.NewClient(channel.RawJSON(input_output, input_output), nil) + + if err = rpc_client.RPC.CallResult(context.Background(), "DERO.GetGasEstimate", gp, &result); err != nil { + return + } + + if result.Status != "OK" { + return + } + + gas = result.GasStorage + + return +} + +// Register a new DERO username +func registerUsername(s string) (storage uint64, err error) { + // Check first if the name is taken + valid, _ := checkUsername(s, -1) + if valid != "" { + logger.Errorf("[Username] Error: skipping registration - username exists.\n") + err = errors.New("username already exists") + return + } + + scid := crypto.HashHexToHash("0000000000000000000000000000000000000000000000000000000000000001") + + var args = rpc.Arguments{} + args = append(args, rpc.Argument{Name: "entrypoint", DataType: "S", Value: "Register"}) + args = append(args, rpc.Argument{Name: "SC_ID", DataType: "H", Value: scid}) + args = append(args, rpc.Argument{Name: "SC_ACTION", DataType: "U", Value: uint64(rpc.SC_CALL)}) + args = append(args, rpc.Argument{Name: "name", DataType: "S", Value: s}) + + var p rpc.Transfer_Params + var dest string + + switch session.Network { + case NETWORK_MAINNET: + dest = "dero1qykyta6ntpd27nl0yq4xtzaf4ls6p5e9pqu0k2x4x3pqq5xavjsdxqgny8270" + case NETWORK_SIMULATOR: + dest = "deto1qyvyeyzrcm2fzf6kyq7egkes2ufgny5xn77y6typhfx9s7w3mvyd5qqynr5hx" + default: + dest = "deto1qy0ehnqjpr0wxqnknyc66du2fsxyktppkr8m8e6jvplp954klfjz2qqdzcd8p" + } + p.Transfers = append(p.Transfers, rpc.Transfer{ + Destination: dest, + Amount: 0, + Burn: 0, + }) + + gp := rpc.GasEstimate_Params{SC_RPC: args, Ringsize: 2, Signer: engram.Disk.GetAddress().String(), Transfers: p.Transfers} + + storage, err = getGasEstimate(gp) + if err != nil { + logger.Errorf("[Username] Error estimating fees: %s\n", err) + return + } + + tx, err := engram.Disk.TransferPayload0(p.Transfers, 2, false, args, storage, false) + if err != nil { + logger.Errorf("[Username] Error while building transaction: %s\n", err) + return + } + + err = engram.Disk.SendTransaction(tx) + if err != nil { + logger.Errorf("[Username] Error while dispatching transaction: %s\n", err) + return + } + + logger.Printf("[Username] Username Registration TXID: %s\n", tx.GetHash().String()) + + return +} + +// Check to make sure the message transaction meets criteria +func checkMessagePack(m string, s string, r string) (err error) { + if m == "" { + return + } + + mapAddress := "" + a, err := globals.ParseValidateAddress(r) + if err != nil { + //mapAddress, err = engram.Disk.NameToAddress(r) + mapAddress, err = checkUsername(r, -1) + if err != nil { + return + } + a, err = globals.ParseValidateAddress(mapAddress) + if err != nil { + return + } + } + + if s == "" { + s = engram.Disk.GetAddress().String() + } + + var amount uint64 + + if a.Arguments.Has(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { // but only it is present + //logger.Info("Transaction", "Value", globals.FormatMoney(a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64))) + amount = a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64) + } else { + amount, err = globals.ParseAmount("0.00001") + if err != nil { + //logger.Error(err, "Err parsing amount\n") + return + } + } + + var arguments = rpc.Arguments{ + {Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: uint64(1337)}, + {Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: amount}, + {Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: m}, + {Name: rpc.RPC_NEEDS_REPLYBACK_ADDRESS, DataType: rpc.DataString, Value: s}, + } + + if a.IsIntegratedAddress() { // read everything from the address + if a.Arguments.Validate_Arguments() != nil { + //fmt.Printf(err, "Integrated Address arguments could not be validated.\n") + return + } + + if !a.Arguments.Has(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { // but only it is present + //fmt.Printf(fmt.Errorf("Integrated Address does not contain destination port.\n"), "") + return + } + + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)}) + + if a.Arguments.Has(rpc.RPC_EXPIRY, rpc.DataTime) { // but only it is present + if a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime).(time.Time).Before(time.Now().UTC()) { + //fmt.Printf(nil, "This address has expired.", "expiry time", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) + return + } + } + + logger.Printf("Destination port is integrated in address. %d\n", a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)) + + if a.Arguments.Has(rpc.RPC_COMMENT, rpc.DataString) { // but only it is present + logger.Printf("Integrated Message: %s\n", a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)) + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)}) + } + } + + for _, arg := range arguments { + if !(arg.Name == rpc.RPC_COMMENT || arg.Name == rpc.RPC_EXPIRY || arg.Name == rpc.RPC_DESTINATION_PORT || arg.Name == rpc.RPC_SOURCE_PORT || arg.Name == rpc.RPC_VALUE_TRANSFER || arg.Name == rpc.RPC_NEEDS_REPLYBACK_ADDRESS) { + switch arg.DataType { + case rpc.DataString: + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) + case rpc.DataInt64: + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) + case rpc.DataUint64: + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) + case rpc.DataFloat64: + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) + case rpc.DataTime: + logger.Warnf("[Service] Time currently not supported.\n") + } + } + } + + if a.Arguments.Has(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_NEEDS_REPLYBACK_ADDRESS, DataType: rpc.DataString, Value: s}) + } + + // if no arguments, use space by embedding a small comment + if len(arguments) == 0 { // allow user to enter Comment + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: uint64(1337)}) + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: m}) + } + + if _, err = arguments.CheckPack(transaction.PAYLOAD0_LIMIT); err != nil { + logger.Errorf("[Message] Arguments packing err: %s\n", err) + return + } + + return +} + +// Send a private message to another account +func sendMessage(m string, s string, r string) (txid crypto.Hash, err error) { + if m == "" { + return + } + + mapAddress := "" + a, err := globals.ParseValidateAddress(r) + if err != nil { + //mapAddress, err = engram.Disk.NameToAddress(r) + mapAddress, err = checkUsername(r, -1) + if err != nil { + return + } + a, err = globals.ParseValidateAddress(mapAddress) + if err != nil { + return + } + } + + if s == "" { + s = engram.Disk.GetAddress().String() + } + + amount, err := globals.ParseAmount("0.00001") + if err != nil { + //logger.Error(err, "Err parsing amount\n") + return + } + + var arguments = rpc.Arguments{ + {Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: uint64(1337)}, + {Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: amount}, + {Name: rpc.RPC_EXPIRY, DataType: rpc.DataTime, Value: time.Now().Add(time.Hour).UTC()}, + {Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: m}, + {Name: rpc.RPC_NEEDS_REPLYBACK_ADDRESS, DataType: rpc.DataString, Value: s}, + } + + if a.IsIntegratedAddress() { + if a.Arguments.Validate_Arguments() != nil { + return + } + + if !a.Arguments.Has(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { + logger.Errorf("[Send Message] Integrated Address does not contain destination port.\n") + return + } + + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)}) + + if a.Arguments.Has(rpc.RPC_EXPIRY, rpc.DataTime) { + if a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime).(time.Time).Before(time.Now().UTC()) { + logger.Errorf("[Send Message] This address has expired on %x\n", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) + return + } else { + logger.Warnf("[Send Message] This address will expire on %x\n", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) + } + } + + logger.Printf("[Send Message] Destination port is integrated in address. %x\n", a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)) + + if a.Arguments.Has(rpc.RPC_COMMENT, rpc.DataString) { + logger.Printf("[Send Message] Integrated Message: %s\n", a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)) + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)}) + } + } + + for _, arg := range arguments { + if !(arg.Name == rpc.RPC_COMMENT || arg.Name == rpc.RPC_EXPIRY || arg.Name == rpc.RPC_DESTINATION_PORT || arg.Name == rpc.RPC_SOURCE_PORT || arg.Name == rpc.RPC_VALUE_TRANSFER || arg.Name == rpc.RPC_NEEDS_REPLYBACK_ADDRESS) { + switch arg.DataType { + case rpc.DataString: + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) + case rpc.DataInt64: + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) + case rpc.DataUint64: + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) + case rpc.DataFloat64: + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: arg.Value.(string)}) + case rpc.DataTime: + logger.Warnf("[Service] Time currently not supported.\n") + } + } + } + + if a.Arguments.Has(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { + amount = a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64) + } else { + amount, err = globals.ParseAmount("0.00001") + if err != nil { + logger.Errorf("[Send Message] Failed parsing transfer amount: %s\n", err) + return + } + } + + if a.Arguments.Has(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_NEEDS_REPLYBACK_ADDRESS, DataType: rpc.DataString, Value: s}) + } + + if len(arguments) == 0 { + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: uint64(1337)}) + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: m}) + } + + if _, err = arguments.CheckPack(transaction.PAYLOAD0_LIMIT); err != nil { + logger.Errorf("[Message] Arguments packing err: %s\n", err) + return + } + + fees := ((uint64(engram.Disk.GetRingSize()) + 1) * config.FEE_PER_KB) / 4 + + logger.Printf("[Message] Calculated Fees: %d\n", fees) + + transfer := rpc.Transfer{Amount: amount, Destination: a.String(), Payload_RPC: arguments} + + tx, err := engram.Disk.TransferPayload0([]rpc.Transfer{transfer}, 0, false, rpc.Arguments{}, fees, false) + if err != nil { + logger.Errorf("[Message] Error while building transaction: %s\n", err) + return + } + + if err = engram.Disk.SendTransaction(tx); err != nil { + logger.Errorf("[Message] Error while dispatching transaction: %s\n", err) + return + } + + txid = tx.GetHash() + + logger.Printf("[Message] Dispatched transaction: %s\n", txid) + + return +} + +// Get a list of message transactions from an address +func getMessagesFromUser(s string, h uint64) (result []rpc.Entry) { + var zeroscid crypto.Hash + if s == "" { + return + } + + messages := engram.Disk.Get_Payments_DestinationPort(zeroscid, uint64(1337), h) + + for m := range messages { + var username bool + var username2 bool + + txid := messages[m].TXID + _, tx := engram.Disk.Get_Payments_TXID(zeroscid, txid) + + //check, err := engram.Disk.NameToAddress(s) + check, err := checkUsername(s, -1) + if err != nil { + username = false + } else { + username = true + } + + if tx.Incoming { + if tx.Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { + height := int64(tx.Height) + check2, err := checkUsername(tx.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string), height) + if err != nil { + username2 = false + addr, err := globals.ParseValidateAddress(tx.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string)) + if err != nil { + check2 = "" + } else { + check2 = addr.String() + } + } else { + username2 = true + } + + // Check for spoofing + //if ring_member_exists(txid, check2) { + + if username && username2 { + if check == check2 { + result = append(result, messages[m]) + } + } else if !username && !username2 { + if s == tx.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) { + result = append(result, messages[m]) + } + } else if check == tx.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) { + result = append(result, messages[m]) + } else if s == check2 { + result = append(result, messages[m]) + } + //} + } + } else { + //addr, err := engram.Disk.NameToAddress(s) + addr, err := checkUsername(s, -1) + if err != nil { + if tx.Destination == s { + result = append(result, messages[m]) + } + } else { + if tx.Destination == addr { + result = append(result, messages[m]) + } + } + } + } + + return +} + +// Get a list of all message transactions and sort them by address +func getMessages(h uint64) (result []string) { + var zeroscid crypto.Hash + messages := engram.Disk.Get_Payments_DestinationPort(zeroscid, uint64(1337), h) + + for m := range messages { + if messages[m].Incoming { + if messages[m].Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { + if messages[m].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) == "" { + + } else { + height := int64(messages[m].Height) + sender, _ := checkUsername(messages[m].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string), height) + if sender == "" { + addr, err := globals.ParseValidateAddress(messages[m].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string)) + if err != nil { + + } else { + sender = addr.String() + for r := range result { + if r > -1 && r < len(result) { + if strings.Contains(result[r], sender+"~~~") { + copy(result[r:], result[r+1:]) + result[len(result)-1] = "" + result = result[:len(result)-1] + } + } + } + result = append(result, sender+"~~~") + } + } else { + // Check for spoofing + //if ring_member_exists(messages[m].TXID, sender) { + for r := range result { + if r > -1 && r < len(result) { + //if strings.Contains(result[r], sender+"```"+messages[m].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string)) { + if strings.Contains(result[r], sender+"~~~") { + copy(result[r:], result[r+1:]) + result[len(result)-1] = "" + result = result[:len(result)-1] + } + } + } + result = append(result, sender+"~~~"+messages[m].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string)) + //} else { + // TODO: Add spoofing address to the ban list? + //} + } + } + } + } else { + if messages[m].Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { + uname := "" + for r := range result { + if r > -1 && r < len(result) { + if strings.Contains(result[r], messages[m].Destination+"~~~") { + split := strings.Split(result[r], "~~~") + uname = split[1] + copy(result[r:], result[r+1:]) + result[len(result)-1] = "" + result = result[:len(result)-1] + } + } + } + result = append(result, messages[m].Destination+"~~~"+uname) + } + } + } + + sort.Sort(sort.Reverse(sort.StringSlice(result))) + return +} + +// Returns a list of registered usernames from Gnomon +func queryUsernames(address string) (result []string, err error) { + if gnomon.Index != nil && engram.Disk != nil { + result, _ = gnomon.Graviton.GetSCIDKeysByValue("0000000000000000000000000000000000000000000000000000000000000001", address, engram.Disk.Get_Daemon_TopoHeight(), false) + if len(result) <= 0 { + result, _, err = gnomon.Index.GetSCIDKeysByValue(nil, "0000000000000000000000000000000000000000000000000000000000000001", address, engram.Disk.Get_Daemon_TopoHeight()) + if err != nil { + logger.Errorf("[Gnomon] Querying usernames failed: %s\n", err) + return + } + } + + sort.Strings(result) + } + + return +} + +// Get the local list of registered usernames saved from previous Gnomon scans +func getUsernames() (result []string, err error) { + usernames, err := GetEncryptedValue("Usernames", []byte("usernames")) + if err != nil { + return + } + + result = strings.Split(string(usernames), ",") + return +} + +// Set the Primary Username saved to a wallet's datashard +func setPrimaryUsername(s string) (err error) { + err = StoreEncryptedValue("settings", []byte("username"), []byte(s)) + return +} + +// Get the Primary Username saved to a wallet's datashard +func getPrimaryUsername() (err error) { + u, err := GetEncryptedValue("settings", []byte("username")) + if err != nil { + session.Username = "" + return + } + session.Username = string(u) + return +} + +// Start the Gnomon indexer +func startGnomon() { + if walletapi.Connected { + if gnomon.Index == nil && gnomon.Active == 1 { + path := filepath.Join(AppPath(), "datashards", "gnomon") + switch session.Network { + case NETWORK_TESTNET: + path = filepath.Join(AppPath(), "datashards", "gnomon_testnet") + case NETWORK_SIMULATOR: + path = filepath.Join(AppPath(), "datashards", "gnomon_simulator") + } + gnomon.BBolt, _ = storage.NewBBoltDB(path, "gnomon") + gnomon.Graviton, _ = storage.NewGravDB(path, "25ms") + term := []string(nil) + term = append(term, "Function Initialize") + height, err := gnomon.Graviton.GetLastIndexHeight() + if err != nil { + height = 0 + } + + // Fastsync Config + config := &structures.FastSyncConfig{ + Enabled: true, + SkipFSRecheck: true, + ForceFastSync: true, + ForceFastSyncDiff: 20, + NoCode: true, + } + + // exclude the Gnomon SC, etc. to keep faster sync times + var exclusions []string + + gnomon.Index = indexer.NewIndexer(gnomon.Graviton, gnomon.BBolt, "gravdb", term, height, session.Daemon, "daemon", false, false, config, exclusions) + indexer.InitLog(globals.Arguments, os.Stdout) + + // We can allow parallel processing of x blocks at a time + go gnomon.Index.StartDaemonMode(1) + + logger.Printf("[Gnomon] Scan Status: [%d / %d]\n", height, gnomon.Index.LastIndexedHeight) + } + } +} + +// Stop all indexers and close Gnomon +func stopGnomon() { + if gnomon.Index != nil { + gnomon.Index.Close() + gnomon.Index = nil + logger.Printf("[Gnomon] Closed all indexers.\n") + } +} + +// Method of Gnomon GetAllOwnersAndSCIDs() where DB type is defined by Indexer.DBType +func (g *Gnomon) GetAllOwnersAndSCIDs() (scids map[string]string) { + switch g.Index.DBType { + case "gravdb": + return g.Index.GravDBBackend.GetAllOwnersAndSCIDs() + case "boltdb": + return g.Index.BBSBackend.GetAllOwnersAndSCIDs() + default: + return + } +} + +// Method of Gnomon GetAllSCIDVariableDetails() where DB type is defined by Indexer.DBType +func (g *Gnomon) GetAllSCIDVariableDetails(scid string) (vars []*structures.SCIDVariable) { + switch g.Index.DBType { + case "gravdb": + return g.Index.GravDBBackend.GetAllSCIDVariableDetails(scid) + case "boltdb": + return g.Index.BBSBackend.GetAllSCIDVariableDetails(scid) + default: + return + } +} + +// Method of Gnomon GetSCIDValuesByKey() where DB type is defined by Indexer.DBType +func (g *Gnomon) GetSCIDValuesByKey(scid string, key interface{}) (valuesstring []string, valuesuint64 []uint64) { + switch g.Index.DBType { + case "gravdb": + return g.Index.GravDBBackend.GetSCIDValuesByKey(scid, key, g.Index.ChainHeight, true) + case "boltdb": + return g.Index.BBSBackend.GetSCIDValuesByKey(scid, key, g.Index.ChainHeight, true) + default: + return + } +} + +// Add a var store only scid to Gnomon DB +func (g *Gnomon) AddSCIDToIndex(scid string) (err error) { + add := make(map[string]*structures.FastSyncImport) + add[scid] = &structures.FastSyncImport{} + + return gnomon.Index.AddSCIDToIndex(add, false, true) +} + +// Get the current code of a smart contract +func getContractCode(scid string) (code string, err error) { + var params = rpc.GetSC_Params{SCID: scid, Variables: false, Code: true} + var result rpc.GetSC_Result + + rpc_client.WS, _, err = websocket.DefaultDialer.Dial("ws://"+session.Daemon+"/ws", nil) + if err != nil { + return + } + + input_output := rwc.New(rpc_client.WS) + rpc_client.RPC = jrpc2.NewClient(channel.RawJSON(input_output, input_output), nil) + + err = rpc_client.RPC.CallResult(context.Background(), "DERO.GetSC", params, &result) + if err != nil { + logger.Errorf("[Engram] Error getting SC code: %s\n", err) + return + } + + code = result.Code + + return +} + +// DVM starter InitializePrivate() example for smart contract builder +func dvmInitFuncExample() string { + return `Function InitializePrivate() Uint64 +10 IF EXISTS("owner") == 0 THEN GOTO 30 +20 RETURN 1 +30 STORE("owner", SIGNER()) +31 STORE("var_header_name", "") +32 STORE("var_header_description", "") +33 STORE("var_header_icon", "") +40 RETURN 0 +End Function` +} + +// DVM starter function for smart contract builder +func dvmFuncExample(increment int) string { + return `Function new` + fmt.Sprintf("%d", increment) + `() Uint64 +10 +20 +30 RETURN 0 +End Function` +} + +// Verification overlay for user actions with or without password +func verificationOverlay(password bool, headerText, subText, dismiss string, callback func(bool)) { + overlay := session.Window.Canvas().Overlays() + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + + if password { + headerText = "ACCOUNT VERIFICATION REQUIRED" + dismiss = "Submit" + } + + header := canvas.NewText(headerText, colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + btnConfirm := widget.NewButton(dismiss, nil) + btnConfirm.Disable() + + entryPassword := NewReturnEntry() + entryPassword.Password = true + entryPassword.PlaceHolder = "Password" + entryPassword.OnChanged = func(s string) { + if s == "" { + btnConfirm.Text = dismiss + btnConfirm.Disable() + btnConfirm.Refresh() + } else { + btnConfirm.Text = dismiss + btnConfirm.Enable() + btnConfirm.Refresh() + } + } + + subHeader := canvas.NewText(subText, colors.Account) + if password { + subText = "Confirm Password" + subHeader.TextSize = 22 + } else { + subHeader.TextSize = 18 + entryPassword.Hide() + btnConfirm.Enable() + btnConfirm.Refresh() + } + + subHeader.Text = subText + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + subHeader.Refresh() + + linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + callback(false) + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } + + btnConfirm.OnTapped = func() { + btnConfirm.Disable() + if password { + if engram.Disk.Check_Password(entryPassword.Text) { + callback(true) + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } else { + btnConfirm.Text = "Invalid Password..." + btnConfirm.Refresh() + } + } else { + callback(true) + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } + } + + entryPassword.OnReturn = btnConfirm.OnTapped + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + subHeader, + widget.NewLabel(""), + container.NewCenter( + container.NewStack( + span, + entryPassword, + ), + ), + rectSpacer, + rectSpacer, + btnConfirm, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + + if password { + session.Window.Canvas().Focus(entryPassword) + } +} + +// Color for the TELA likes ratio and individual rating numbers +func telaRatingColor(r uint64) color.Color { + if r > 65 { + return colors.Green + } else if r > 32 { + return colors.Yellow + } else { + return colors.Red + } +} + +// Color for the TELA average rating number hexagon +func telaHexagonColor(r float64) fyne.Resource { + if r > 6.5 { + return resourceTelaHexagonGreen + } else if r > 3.2 { + return resourceTelaHexagonYellow + } else { + return resourceTelaHexagonRed + } +} + +// Display ratings overview and the details of each rating for the TELA SCID +func viewTELARatingsOverlay(name, scid string) (err error) { + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + + header := canvas.NewText("TELA RATINGS", colors.Gray) + header.TextSize = 16 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + if len(name) > 30 { + name = fmt.Sprintf("%s...", name[0:30]) + } + + nameHdr := canvas.NewText(name, colors.Account) + nameHdr.Alignment = fyne.TextAlignCenter + nameHdr.TextStyle = fyne.TextStyle{Bold: true} + + labelSCID := canvas.NewText(" SMART CONTRACT ID", colors.Gray) + labelSCID.TextSize = 14 + labelSCID.Alignment = fyne.TextAlignLeading + labelSCID.TextStyle = fyne.TextStyle{Bold: true} + + textSCID := widget.NewRichTextWithText(scid) + textSCID.Wrapping = fyne.TextWrapWord + + textLikes := widget.NewRichTextFromMarkdown("Likes:") + textDislikes := widget.NewRichTextFromMarkdown("Dislikes:") + textAverage := widget.NewRichTextFromMarkdown("Average:") + + ratingsBox := container.NewVBox(labelSCID, textSCID) + + ratings, err := tela.GetRating(scid, session.Daemon, 0) + if err != nil { + removeOverlays() + logger.Errorf("[Engram] GetRating: %s\n", err) + err = fmt.Errorf("error could not get ratings") + return + } + + removeOverlays() + overlay := session.Window.Canvas().Overlays() + + ratingsBox.Add(container.NewHBox(textLikes, canvas.NewText(fmt.Sprintf("%d", ratings.Likes), colors.Green))) + ratingsBox.Add(container.NewHBox(textDislikes, canvas.NewText(fmt.Sprintf("%d", ratings.Dislikes), colors.Red))) + ratingsBox.Add(container.NewHBox(textAverage, canvas.NewText(fmt.Sprintf("%0.1f/10", ratings.Average), colors.Account))) + + linkRate := widget.NewHyperlinkWithStyle("Rate SCID", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + linkRate.OnTapped = func() { + rateTELAOverlay(name, scid) + } + linkRate.Hide() + + // Check if wallet has rated SCID + if gnomon.Index != nil { + ratingStore, _ := gnomon.GetSCIDValuesByKey(scid, engram.Disk.GetAddress().String()) + if ratingStore == nil { + linkRate.Show() + } + } + + linkBack := widget.NewHyperlinkWithStyle("Back to Application", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } + + labelSeparator := widget.NewRichTextFromMarkdown("") + labelSeparator.Wrapping = fyne.TextWrapOff + labelSeparator.ParseMarkdown("---") + labelSeparator2 := widget.NewRichTextFromMarkdown("") + labelSeparator2.Wrapping = fyne.TextWrapOff + labelSeparator2.ParseMarkdown("---") + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + userRatingsBox := container.NewVBox() + + for _, r := range ratings.Ratings { + ratingString, err := tela.Ratings.ParseString(r.Rating) + if err != nil { + ratingString = fmt.Sprintf("%d", r.Rating) + } + + labelSeparator := widget.NewRichTextFromMarkdown("") + labelSeparator.Wrapping = fyne.TextWrapOff + labelSeparator.ParseMarkdown("---") + + labelAddress := widget.NewRichTextFromMarkdown(r.Address) + labelAddress.Wrapping = fyne.TextWrapWord + + userRatingsBox.Add( + container.NewVBox( + labelAddress, + container.NewHBox(widget.NewRichTextFromMarkdown("Height:"), canvas.NewText(fmt.Sprintf("%d", r.Height), colors.Account)), + container.NewHBox(widget.NewRichTextFromMarkdown("Rating:"), canvas.NewText(fmt.Sprintf("%d", r.Rating), telaRatingColor(r.Rating))), + widget.NewRichTextFromMarkdown(ratingString), + labelSeparator, + ), + ) + } + + userRatingsBoxScroll := container.NewVScroll( + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + ratingsBox, + rectWidth90, + container.NewHBox( + linkRate, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator2, + rectSpacer, + rectSpacer, + userRatingsBox, + ), + layout.NewSpacer(), + ), + ) + userRatingsBoxScroll.SetMinSize(fyne.NewSize(ui.Width*0.80, ui.Height*0.50)) + + overlayCont := container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + rectSpacer, + container.NewCenter( + nameHdr, + ), + rectSpacer, + rectSpacer, + rectSpacer, + labelSeparator, + rectSpacer, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + ), + userRatingsBoxScroll, + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ) + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewBorder( + nil, + bottom, + nil, + nil, + overlayCont, + ), + ), + ) + + return +} + +// TELA smart contract rating overlay with password confirmation +func rateTELAOverlay(name, scid string) { + overlay := session.Window.Canvas().Overlays() + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + + selectSpacer := canvas.NewRectangle(color.Transparent) + selectSpacer.SetMinSize(fyne.NewSize(80, 5)) + + header := canvas.NewText("RATE TELA INDEX", colors.Gray) + header.TextSize = 16 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + if len(name) > 30 { + name = fmt.Sprintf("%s...", name[0:30]) + } + + nameHdr := canvas.NewText(name, colors.Account) + nameHdr.Alignment = fyne.TextAlignCenter + nameHdr.TextStyle = fyne.TextStyle{Bold: true} + + btnConfirm := widget.NewButton("Rate", nil) + btnConfirm.Disable() + + var telaCategories, negativeDetails, positiveDetails []string + for i := uint64(0); i < 10; i++ { + telaCategories = append(telaCategories, tela.Ratings.Category(i)) + } + categorySelect := widget.NewSelect(telaCategories, nil) + + scidLabel := canvas.NewText(" SMART CONTRACT ID", colors.Gray) + scidLabel.TextSize = 14 + scidLabel.Alignment = fyne.TextAlignCenter + scidLabel.TextStyle = fyne.TextStyle{Bold: true} + + scidText := widget.NewRichTextFromMarkdown(scid) + scidText.Wrapping = fyne.TextWrapWord + + errorText := canvas.NewText(" ", colors.Green) + errorText.TextSize = 12 + errorText.Alignment = fyne.TextAlignCenter + + for i := uint64(0); i < 10; i++ { + negativeDetails = append(negativeDetails, tela.Ratings.Detail(i, false)) + positiveDetails = append(positiveDetails, tela.Ratings.Detail(i, true)) + } + negativeSelect := widget.NewSelect(negativeDetails, nil) + positiveSelect := widget.NewSelect(positiveDetails, nil) + + categoryHeader := canvas.NewText("Category", colors.Account) + categoryHeader.Alignment = fyne.TextAlignLeading + categoryHeader.TextStyle = fyne.TextStyle{Bold: true} + categoryHeader.Refresh() + + detailHeader := canvas.NewText("Detail", colors.Account) + detailHeader.Alignment = fyne.TextAlignLeading + detailHeader.TextStyle = fyne.TextStyle{Bold: true} + detailHeader.Refresh() + + ratingHeader := canvas.NewText("Rating", colors.Account) + ratingHeader.Alignment = fyne.TextAlignLeading + ratingHeader.TextStyle = fyne.TextStyle{Bold: true} + ratingHeader.Refresh() + + ratingText := widget.NewRichTextFromMarkdown("") + ratingText.Wrapping = fyne.TextWrapWord + + linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } + + btnConfirm.OnTapped = func() { + errorText.Text = "" + errorText.Refresh() + btnConfirm.Disable() + if gnomon.Index != nil { + var ratingStore []string + switch gnomon.Index.DBType { + case "gravdb": + ratingStore, _ = gnomon.Index.GravDBBackend.GetSCIDValuesByKey(scid, engram.Disk.GetAddress().String(), gnomon.Index.LastIndexedHeight, false) + case "boltdb": + ratingStore, _ = gnomon.Index.BBSBackend.GetSCIDValuesByKey(scid, engram.Disk.GetAddress().String(), gnomon.Index.LastIndexedHeight, false) + } + if ratingStore != nil { + errorText.Text = "already rated this contract" + errorText.Color = colors.Red + errorText.Refresh() + return + } + } + + category := categorySelect.SelectedIndex() + if category < 0 { + errorText.Text = "select a category" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + var detail int + if category > 4 { + detail = positiveSelect.SelectedIndex() + } else { + detail = negativeSelect.SelectedIndex() + } + + if detail < 0 { + errorText.Text = "select a detail" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + rating := (category * 10) + detail + + verificationOverlay( + true, + "", + "", + "", + func(b bool) { + if !b { + btnConfirm.Enable() + return + } + + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + + txid, err := tela.Rate(engram.Disk, scid, uint64(rating)) + if err != nil { + logger.Errorf("[Engram] Rate TX: %s\n", err) + return + } + + logger.Printf("[Engram] Rate TXID: %s\n", txid) + }, + ) + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlayCont := container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + rectSpacer, + container.NewCenter( + nameHdr, + ), + rectSpacer, + rectSpacer, + rectSpacer, + scidLabel, + scidText, + widget.NewLabel(""), + container.NewCenter( + container.NewStack( + span, + container.NewBorder( + nil, + nil, + container.NewStack( + selectSpacer, + categoryHeader, + ), + nil, + categorySelect, + ), + ), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + container.NewCenter( + container.NewStack( + span, + container.NewBorder( + nil, + nil, + container.NewStack( + selectSpacer, + detailHeader, + ), + nil, + positiveSelect, + ), + ), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + container.NewCenter( + container.NewStack( + span, + container.NewBorder( + nil, + nil, + container.NewStack( + selectSpacer, + ratingHeader, + ), + nil, + ratingText, + ), + ), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + errorText, + rectSpacer, + rectSpacer, + btnConfirm, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ) + + categorySelect.OnChanged = func(s string) { + if positiveSelect.SelectedIndex() > -1 && negativeSelect.SelectedIndex() > -1 { + btnConfirm.Enable() + } + + if categorySelect.SelectedIndex() > 4 { + overlayCont.Objects[17] = container.NewCenter( + container.NewStack( + span, + container.NewBorder( + nil, + nil, + container.NewStack( + selectSpacer, + detailHeader, + ), + nil, + + positiveSelect, + ), + ), + ) + positiveSelect.SetSelectedIndex(0) + ratingText.ParseMarkdown(fmt.Sprintf("%d", (categorySelect.SelectedIndex()*10)+positiveSelect.SelectedIndex())) + } else { + overlayCont.Objects[17] = container.NewCenter( + container.NewStack( + span, + container.NewBorder( + nil, + nil, + container.NewStack( + selectSpacer, + detailHeader, + ), + nil, + negativeSelect, + ), + ), + ) + negativeSelect.SetSelectedIndex(0) + ratingText.ParseMarkdown(fmt.Sprintf("%d", (categorySelect.SelectedIndex()*10)+negativeSelect.SelectedIndex())) + } + } + + positiveSelect.OnChanged = func(s string) { + if categorySelect.SelectedIndex() > -1 { + btnConfirm.Enable() + ratingText.ParseMarkdown(fmt.Sprintf("%d", (categorySelect.SelectedIndex()*10)+positiveSelect.SelectedIndex())) + } + } + + negativeSelect.OnChanged = func(s string) { + if categorySelect.SelectedIndex() > -1 { + btnConfirm.Enable() + ratingText.ParseMarkdown(fmt.Sprintf("%d", (categorySelect.SelectedIndex()*10)+negativeSelect.SelectedIndex())) + } + } + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + overlayCont, + ), + ), + ) +} + +// Install a new smart contract +func installSC(code string, args []rpc.Argument) (txid string, err error) { + var dest string + switch session.Network { + case NETWORK_MAINNET: + dest = "dero1qykyta6ntpd27nl0yq4xtzaf4ls6p5e9pqu0k2x4x3pqq5xavjsdxqgny8270" + case NETWORK_SIMULATOR: + dest = "deto1qyvyeyzrcm2fzf6kyq7egkes2ufgny5xn77y6typhfx9s7w3mvyd5qqynr5hx" + case NETWORK_TESTNET: + dest = "deto1qy0ehnqjpr0wxqnknyc66du2fsxyktppkr8m8e6jvplp954klfjz2qqdzcd8p" + } + + transfer := rpc.Transfer{ + Destination: dest, + Amount: 0, + Burn: 0, + } + + _, err = transfer.Payload_RPC.CheckPack(transaction.PAYLOAD0_LIMIT) + if err != nil { + logger.Errorf("[Engram] Install arguments packing err: %s\n", err) + err = fmt.Errorf("contract install pack error") + return + } + + // decode SC from base64 if possible + if sc, err := base64.StdEncoding.DecodeString(code); err == nil { + code = string(sc) + } + + args = append(args, rpc.Argument{Name: rpc.SCACTION, DataType: rpc.DataUint64, Value: uint64(rpc.SC_INSTALL)}) + args = append(args, rpc.Argument{Name: rpc.SCCODE, DataType: rpc.DataString, Value: code}) + + fees := uint64(0) + gasParams := rpc.GasEstimate_Params{ + Transfers: []rpc.Transfer{transfer}, + SC_Code: code, + SC_Value: 0, + SC_RPC: args, + Ringsize: 2, + Signer: engram.Disk.GetAddress().String(), + } + + if gas, err := getGasEstimate(gasParams); err == nil { + fees = gas + logger.Printf("[Engram] SC install fees: %d\n", fees) + } else { + // uses default fees + logger.Errorf("[Engram] Error estimating fees: %s\n", err) + } + + tx, err := engram.Disk.TransferPayload0([]rpc.Transfer{transfer}, 2, false, args, fees, false) + if err != nil { + logger.Errorf("[Engram] Error while building install transaction: %s\n", err) + err = fmt.Errorf("contract install build error") + return + } + + if err = engram.Disk.SendTransaction(tx); err != nil { + logger.Errorf("[Engram] Error while dispatching install transaction: %s\n", err) + err = fmt.Errorf("contract install dispatch error") + return + } + + txid = tx.GetHash().String() + + logger.Printf("[Engram] SC Installed: %s\n", txid) + + return +} + +// Set the Cyberdeck password +func newRPCPassword() (s string) { + r := make([]byte, 20) + _, err := rand.Read(r) + if err != nil { + panic(err) + } + + s = base64.URLEncoding.EncodeToString(r) + cyberdeck.RPC.pass = s + return +} + +// Set the Cyberdeck username +func newRPCUsername() (s string) { + r, _ := rand.Int(rand.Reader, big.NewInt(1600)) + w := mnemonics.Key_To_Words(r, "english") + l := strings.Split(string(w), " ") + s = l[len(l)-2] + cyberdeck.RPC.user = s + return +} + +// Start an RPC server to allow decentralized application communication +func toggleRPCServer(port string) { + var err error + if engram.Disk == nil { + return + } + + if cyberdeck.RPC.server != nil { + cyberdeck.RPC.server.RPCServer_Stop() + cyberdeck.RPC.server = nil + cyberdeck.RPC.status.Text = "Blocked" + cyberdeck.RPC.status.Color = colors.Gray + cyberdeck.RPC.status.Refresh() + cyberdeck.RPC.toggle.Text = "Turn On" + cyberdeck.RPC.toggle.Refresh() + status.Cyberdeck.FillColor = colors.Gray + status.Cyberdeck.StrokeColor = colors.Gray + status.Cyberdeck.Refresh() + cyberdeck.RPC.userText.Text = cyberdeck.RPC.user + cyberdeck.RPC.passText.Text = cyberdeck.RPC.pass + cyberdeck.RPC.userText.Enable() + cyberdeck.RPC.passText.Enable() + logger.Printf("[Engram] RPC server closed\n") + } else { + logger.Printf("[Engram] Starting RPC server %s\n", port) + + globals.Arguments["--rpc-bind"] = port + + if cyberdeck.RPC.user == "" { + cyberdeck.RPC.user = newRPCUsername() + } + + if cyberdeck.RPC.pass == "" { + cyberdeck.RPC.pass = newRPCPassword() + } + + globals.Arguments["--rpc-login"] = cyberdeck.RPC.user + ":" + cyberdeck.RPC.pass + + cyberdeck.RPC.server, err = rpcserver.RPCServer_Start(engram.Disk, "Cyberdeck") + if err != nil { + cyberdeck.RPC.server = nil + cyberdeck.RPC.status.Text = "Blocked" + cyberdeck.RPC.status.Color = colors.Gray + cyberdeck.RPC.status.Refresh() + cyberdeck.RPC.toggle.Text = "Turn On" + cyberdeck.RPC.toggle.Refresh() + status.Cyberdeck.FillColor = colors.Gray + status.Cyberdeck.StrokeColor = colors.Gray + status.Cyberdeck.Refresh() + cyberdeck.RPC.userText.Text = cyberdeck.RPC.user + cyberdeck.RPC.passText.Text = cyberdeck.RPC.pass + cyberdeck.RPC.userText.Enable() + cyberdeck.RPC.passText.Enable() + } else { + cyberdeck.RPC.status.Text = "Allowed" + cyberdeck.RPC.status.Color = colors.Green + cyberdeck.RPC.status.Refresh() + cyberdeck.RPC.toggle.Text = "Turn Off" + cyberdeck.RPC.toggle.Refresh() + status.Cyberdeck.FillColor = colors.Green + status.Cyberdeck.StrokeColor = colors.Green + status.Cyberdeck.Refresh() + cyberdeck.RPC.userText.Text = cyberdeck.RPC.user + cyberdeck.RPC.passText.Text = cyberdeck.RPC.pass + cyberdeck.RPC.userText.Disable() + cyberdeck.RPC.passText.Disable() + } + } +} + +// Get the latest smart contract header data (must follow the standard here: https://github.com/civilware/artificer-nfa-standard/blob/main/Headers/README.md) +func getContractHeader(scid crypto.Hash) (name string, desc string, icon string, owner string, code string) { + var headerData []*structures.SCIDVariable + var found bool + + switch gnomon.Index.DBType { + case "gravdb": + headerData = gnomon.Index.GravDBBackend.GetAllSCIDVariableDetails(scid.String()) + case "boltdb": + headerData = gnomon.Index.BBSBackend.GetAllSCIDVariableDetails(scid.String()) + } + if headerData == nil { + addIndex := make(map[string]*structures.FastSyncImport) + addIndex[scid.String()] = &structures.FastSyncImport{} + gnomon.Index.AddSCIDToIndex(addIndex, false, true) + switch gnomon.Index.DBType { + case "gravdb": + headerData = gnomon.Index.GravDBBackend.GetAllSCIDVariableDetails(scid.String()) + case "boltdb": + headerData = gnomon.Index.BBSBackend.GetAllSCIDVariableDetails(scid.String()) + } + } + + for _, h := range headerData { + switch key := h.Key.(type) { + case string: + if key == "var_header_name" { + found = true + name = h.Value.(string) + } else if name == "" && key == "nameHdr" { + found = true + name = h.Value.(string) + } + + if key == "var_header_description" { + found = true + desc = h.Value.(string) + } else if desc == "" && key == "descrHdr" { + found = true + desc = h.Value.(string) + } + + if key == "var_header_icon" { + found = true + icon = h.Value.(string) + } else if icon == "" && key == "iconURLHdr" { + found = true + icon = h.Value.(string) + } + + if key == "owner" { + owner = h.Value.(string) + } + + if key == "C" { + code = h.Value.(string) + } + } + } + + // Secondary check for headers in Gnomon SC + if !found { + switch gnomon.Index.DBType { + case "gravdb": + headerData = gnomon.Index.GravDBBackend.GetAllSCIDVariableDetails(structures.MAINNET_GNOMON_SCID) + case "boltdb": + headerData = gnomon.Index.BBSBackend.GetAllSCIDVariableDetails(structures.MAINNET_GNOMON_SCID) + } + if headerData == nil { + addIndex := make(map[string]*structures.FastSyncImport) + addIndex[structures.MAINNET_GNOMON_SCID] = &structures.FastSyncImport{} + gnomon.Index.AddSCIDToIndex(addIndex, false, true) + switch gnomon.Index.DBType { + case "gravdb": + headerData = gnomon.Index.GravDBBackend.GetAllSCIDVariableDetails(structures.MAINNET_GNOMON_SCID) + case "boltdb": + headerData = gnomon.Index.BBSBackend.GetAllSCIDVariableDetails(structures.MAINNET_GNOMON_SCID) + } + } + + for _, h := range headerData { + if strings.Contains(h.Key.(string), scid.String()) { + switch key := h.Key.(type) { + case string: + if key == scid.String() { + query := h.Value.(string) + header := strings.Split(query, ";") + + if len(header) > 2 { + name = header[0] + desc = header[1] + icon = header[2] + } + } + + if key == scid.String()+"owner" { + owner = h.Value.(string) + } + } + } + } + } + + return +} + +// Send an asset from one account to another +func transferAsset(scid crypto.Hash, ringsize uint64, address string, amount string) (txid crypto.Hash, err error) { + var amount_to_transfer uint64 + + if amount == "" { + amount = ".00001" + } + + amount_to_transfer, err = globals.ParseAmount(amount) + if err != nil { + logger.Errorf("[Transfer] Failed parsing transfer amount: %s\n", err) + return + } + + tx, err := engram.Disk.TransferPayload0([]rpc.Transfer{{SCID: scid, Amount: amount_to_transfer, Destination: address}}, ringsize, false, rpc.Arguments{}, 0, false) + if err != nil { + logger.Errorf("[Transfer] Failed to build transaction: %s\n", err) + return + } + + if err = engram.Disk.SendTransaction(tx); err != nil { + logger.Errorf("[Transfer] Failed to send asset: %s - %s\n", scid, err) + return + } + + txid = tx.GetHash() + + logger.Printf("[Transfer] Successfully sent asset: %s - TXID: %s\n", scid, tx.GetHash().String()) + return +} + +// Transfer a username to another account +func transferUsername(username string, address string) (storage uint64, err error) { + var args = rpc.Arguments{} + var dest string + + scid := crypto.HashHexToHash("0000000000000000000000000000000000000000000000000000000000000001") + + args = append(args, rpc.Argument{Name: "entrypoint", DataType: "S", Value: "TransferOwnership"}) + args = append(args, rpc.Argument{Name: "SC_ID", DataType: "H", Value: scid}) + args = append(args, rpc.Argument{Name: "SC_ACTION", DataType: "U", Value: uint64(rpc.SC_CALL)}) + args = append(args, rpc.Argument{Name: "name", DataType: "S", Value: username}) + args = append(args, rpc.Argument{Name: "newowner", DataType: "S", Value: address}) + + switch session.Network { + case NETWORK_MAINNET: + dest = "dero1qykyta6ntpd27nl0yq4xtzaf4ls6p5e9pqu0k2x4x3pqq5xavjsdxqgny8270" + case NETWORK_SIMULATOR: + dest = "deto1qyvyeyzrcm2fzf6kyq7egkes2ufgny5xn77y6typhfx9s7w3mvyd5qqynr5hx" + default: + dest = "deto1qy0ehnqjpr0wxqnknyc66du2fsxyktppkr8m8e6jvplp954klfjz2qqdzcd8p" + } + + transfer := rpc.Transfer{ + Destination: dest, + Amount: 0, + Burn: 0, + } + + gasParams := rpc.GasEstimate_Params{ + SC_RPC: args, + SC_Value: 0, + Ringsize: 2, + Signer: engram.Disk.GetAddress().String(), + Transfers: []rpc.Transfer{transfer}, + } + + storage, err = getGasEstimate(gasParams) + if err != nil { + logger.Errorf("[%s] Error estimating fees: %s\n", "TransferOwnership", err) + return + } + + tx, err := engram.Disk.TransferPayload0([]rpc.Transfer{transfer}, 2, false, args, storage, false) + if err != nil { + logger.Errorf("[%s] Error while building transaction: %s\n", "TransferOwnership", err) + return + } + + txid := tx.GetHash().String() + + err = engram.Disk.SendTransaction(tx) + if err != nil { + logger.Errorf("[%s] Error while dispatching transaction: %s\n", "TransferOwnership", err) + return + } + + walletapi.WaitNewHeightBlock() + logger.Printf("[%s] Username transfer successful - TXID: %s\n", "TransferOwnership", txid) + _ = tx + + return +} + +// Execute arbitrary exportable smart contract functions +func executeContractFunction(scid crypto.Hash, ringsize uint64, dero_amount uint64, asset_amount uint64, funcName string, params []dvm.Variable) (storage uint64, err error) { + var args = rpc.Arguments{} + var zero uint64 + var dest string + + args = append(args, rpc.Argument{Name: "entrypoint", DataType: "S", Value: funcName}) + args = append(args, rpc.Argument{Name: "SC_ID", DataType: "H", Value: scid}) + args = append(args, rpc.Argument{Name: "SC_ACTION", DataType: "U", Value: uint64(rpc.SC_CALL)}) + + for p := range params { + if params[p].Type == 0x4 { + args = append(args, rpc.Argument{Name: params[p].Name, DataType: "U", Value: params[p].ValueUint64}) + } else { + args = append(args, rpc.Argument{Name: params[p].Name, DataType: "S", Value: params[p].ValueString}) + } + } + + switch session.Network { + case NETWORK_MAINNET: + dest = "dero1qykyta6ntpd27nl0yq4xtzaf4ls6p5e9pqu0k2x4x3pqq5xavjsdxqgny8270" + case NETWORK_SIMULATOR: + dest = "deto1qyvyeyzrcm2fzf6kyq7egkes2ufgny5xn77y6typhfx9s7w3mvyd5qqynr5hx" + default: + dest = "deto1qy0ehnqjpr0wxqnknyc66du2fsxyktppkr8m8e6jvplp954klfjz2qqdzcd8p" + } + + var transfers []rpc.Transfer + + if dero_amount != zero { + burn := dero_amount + + transfer := rpc.Transfer{ + Destination: dest, + Amount: 0, + Burn: burn, + } + + transfers = append(transfers, transfer) + } + if asset_amount != zero { + burn := asset_amount + + transfer := rpc.Transfer{ + SCID: scid, + Destination: dest, + Amount: 0, + Burn: burn, + } + + transfers = append(transfers, transfer) + } + + if len(transfers) < 1 { + transfer := rpc.Transfer{ + Destination: dest, + Amount: 0, + Burn: 0, + } + + transfers = append(transfers, transfer) + } + + gasParams := rpc.GasEstimate_Params{ + SC_RPC: args, + SC_Value: 0, + Ringsize: ringsize, + Signer: engram.Disk.GetAddress().String(), + Transfers: transfers, + } + + storage, err = getGasEstimate(gasParams) + if err != nil { + logger.Errorf("[%s] Error estimating fees: %s\n", funcName, err) + return + } + + tx, err := engram.Disk.TransferPayload0(transfers, ringsize, false, args, storage, false) + if err != nil { + logger.Errorf("[%s] Error while building transaction: %s\n", funcName, err) + return + } + + err = engram.Disk.SendTransaction(tx) + if err != nil { + logger.Errorf("[%s] Error while dispatching transaction: %s\n", funcName, err) + return + } + + walletapi.WaitNewHeightBlock() + logger.Printf("[%s] Function execution successful - TXID: %s\n", funcName, tx.GetHash().String()) + _ = tx + + return +} + +// Delete the Gnomon directory +func cleanGnomonData() error { + path := filepath.Join(AppPath(), "datashards", "gnomon") + switch session.Network { + case NETWORK_TESTNET: + path = filepath.Join(AppPath(), "datashards", "gnomon_testnet") + case NETWORK_SIMULATOR: + path = filepath.Join(AppPath(), "datashards", "gnomon_simulator") + } + + dir, err := os.ReadDir(path) + if err != nil { + logger.Errorf("[Gnomon] Error purging local Gnomon data: %s\n", err) + return err + } + + for _, d := range dir { + os.RemoveAll(filepath.Join([]string{path, d.Name()}...)) + logger.Printf("[Gnomon] Local Gnomon data has been purged successfully\n") + } + + return nil +} + +// Delete the datashard directory for the active wallet +func cleanWalletData() (err error) { + path, err := GetShard() + if err != nil { + return + } + + dir, err := os.ReadDir(path) + if err != nil { + logger.Errorf("[Engram] Error purging local datashard data: %s\n", err) + return err + } + + for _, d := range dir { + os.RemoveAll(filepath.Join([]string{path, d.Name()}...)) + logger.Printf("[Engram] Local datashard data has been purged successfully\n") + } + + return nil +} + +// Get transaction data for any TXID from the daemon +func getTxData(txid string) (result rpc.GetTransaction_Result, err error) { + if engram.Disk == nil || session.Offline { + return + } + + var params rpc.GetTransaction_Params + + params.Tx_Hashes = append(params.Tx_Hashes, txid) + + rpc_client.WS, _, err = websocket.DefaultDialer.Dial("ws://"+session.Daemon+"/ws", nil) + if err != nil { + return + } + + input_output := rwc.New(rpc_client.WS) + rpc_client.RPC = jrpc2.NewClient(channel.RawJSON(input_output, input_output), nil) + + if err = rpc_client.RPC.CallResult(context.Background(), "DERO.GetTransaction", params, &result); err != nil { + logger.Errorf("[Engram] getTxData TXID: %s (Failed: %s)\n", txid, err) + return + } + + rpc_client.WS.Close() + rpc_client.RPC.Close() + + if result.Status != "OK" { + logger.Errorf("[Engram] getTxData TXID: %s (Failed: %s)\n", txid, result.Status) + return + } + + if len(result.Txs_as_hex[0]) < 50 { + return + } + + return +} + +// Methods Engram will use as XSWD noStore +func engramNoStoreMethods() []string { + return []string{ + "Subscribe", + "SignData", + "CheckSignature", + "GetDaemon", + "GetPrimaryUsername", + "query_key", + "QueryKey", + "HandleTELALinks"} +} + +// Check if method is in Engram's noStore list +func engramCanStoreMethod(method string) bool { + noStoreMethods := engramNoStoreMethods() + for _, m := range noStoreMethods { + if m == method { + return false + } + } + + return true +} + +// Set XSWD permissions to the local Graviton tree +func setPermissions() { + data, err := json.Marshal(&cyberdeck.WS.global.permissions) + if err != nil { + logger.Errorf("[Engram] setPermissions: %s\n", err) + } else { + err = StoreEncryptedValue("XSWD", []byte("Globals"), data) + if err != nil { + logger.Debugf("[Engram] setPermissions: %s\n", err) + } + } +} + +// Set all noStore methods to XSWD Ask permission +func SetDefaultPermissions() (defaults map[string]xswd.Permission) { + defaults = make(map[string]xswd.Permission) + for method := range rpcserver.WalletHandler { + defaults[method] = xswd.Ask + } + + // XSWD methods + defaults["Subscribe"] = xswd.Ask + defaults["HasMethod"] = xswd.Ask + defaults["Unsubscribe"] = xswd.Ask + defaults["GetDaemon"] = xswd.Ask + defaults["SignData"] = xswd.Ask + defaults["CheckSignature"] = xswd.Ask + + // Engram methods + defaults["GetPrimaryUsername"] = xswd.Ask + defaults["HandleTELALinks"] = xswd.Ask + + // EPOCH methods + defaults["AttemptEPOCHWithAddr"] = xswd.Ask + for method := range epoch.GetHandler() { + defaults[method] = xswd.Ask + } + + return +} + +// Get XSWD permissions from local Graviton tree and sorted wallet methods +func getPermissions() (handler map[string]xswd.Permission, methods []string) { + cyberdeck.WS.Lock() + defer cyberdeck.WS.Unlock() + + cyberdeck.WS.global.permissions = SetDefaultPermissions() + + stored, err := GetEncryptedValue("XSWD", []byte("Globals")) + if err != nil { + logger.Debugf("[Engram] getPermissions: %s\n", err) + } else { + if err := json.Unmarshal(stored, &cyberdeck.WS.global.permissions); err != nil { + logger.Errorf("[Engram] getPermissions: %s\n", err) + } + } + + for k := range cyberdeck.WS.global.permissions { + methods = append(methods, k) + } + + sort.Strings(methods) + + return cyberdeck.WS.global.permissions, methods +} + +// Start a permissioned web socket server to allow decentralized application communication +func toggleXSWD(endpoint string) { + if engram.Disk == nil { + return + } + + if cyberdeck.WS.server != nil { + cyberdeck.WS.server.Stop() + cyberdeck.WS.server = nil + cyberdeck.WS.status.Text = "Blocked" + cyberdeck.WS.status.Color = colors.Gray + cyberdeck.WS.status.Refresh() + cyberdeck.WS.toggle.Text = "Turn On" + cyberdeck.WS.toggle.Refresh() + status.Cyberdeck.FillColor = colors.Gray + status.Cyberdeck.StrokeColor = colors.Gray + status.Cyberdeck.Refresh() + cyberdeck.WS.advanced = false + cyberdeck.WS.global.enabled = false + cyberdeck.WS.global.connect = false + cyberdeck.WS.apps = []xswd.ApplicationData{} + if cyberdeck.WS.list != nil { + cyberdeck.WS.list.Refresh() + } + logger.Printf("[Engram] XSWD server closed\n") + } else { + _, portNum, err := net.SplitHostPort(endpoint) + if err != nil { + logger.Errorf("[Engram] Invalid XSWD server endpoint: %s\n", err) + return + } + + portInt, err := strconv.Atoi(portNum) + if err != nil { + logger.Errorf("[Engram] Invalid XSWD server port: %s\n", err) + return + } + + logger.Printf("[Engram] Starting XSWD server %s\n", endpoint) + + noStoreMethods := engramNoStoreMethods() + + cyberdeck.WS.server = xswd.NewXSWDServerWithPort(portInt, engram.Disk, false, noStoreMethods, func(ad *xswd.ApplicationData) bool { + return XSWDPrompt(ad) + }, func(ad *xswd.ApplicationData, r *jrpc2.Request) xswd.Permission { + return AskPermissionForRequest(ad, r) + }) + + cyberdeck.WS.toggle.Disable() + cyberdeck.WS.toggle.Text = "Initializing" + cyberdeck.WS.toggle.Refresh() + time.Sleep(time.Second) + if !cyberdeck.WS.server.IsRunning() { + cyberdeck.WS.server = nil + logger.Errorf("[Engram] Error starting XSWD server\n") + cyberdeck.WS.toggle.Text = "Error starting web sockets" + cyberdeck.WS.toggle.Refresh() + go func() { + time.Sleep(time.Second * 2) + fyne.Do(func() { + fyne.Do(func() { + cyberdeck.WS.toggle.Text = "Turn On" + cyberdeck.WS.toggle.Refresh() + cyberdeck.WS.toggle.Enable() + }) + }) + }() + + return + } + cyberdeck.WS.toggle.Enable() + + if cyberdeck.WS.server == nil { + cyberdeck.WS.status.Text = "Blocked" + cyberdeck.WS.status.Color = colors.Gray + cyberdeck.WS.status.Refresh() + cyberdeck.WS.toggle.Text = "Turn On" + cyberdeck.WS.toggle.Refresh() + status.Cyberdeck.FillColor = colors.Gray + status.Cyberdeck.StrokeColor = colors.Gray + status.Cyberdeck.Refresh() + } else { + for method, h := range EngramHandler { + cyberdeck.WS.server.SetCustomMethod(method, h) + } + + cyberdeck.WS.server.SetCustomMethod("HandleTELALinks", handler.New(HandleTELALinks)) + + cyberdeck.WS.server.SetCustomMethod("AttemptEPOCHWithAddr", handler.New(AttemptEPOCHWithAddr)) + + for method, h := range epoch.GetHandler() { + cyberdeck.WS.server.SetCustomMethod(method, h) + } + + cyberdeck.WS.status.Text = "Allowed" + cyberdeck.WS.status.Color = colors.Green + cyberdeck.WS.status.Refresh() + cyberdeck.WS.toggle.Text = "Turn Off" + cyberdeck.WS.toggle.Refresh() + status.Cyberdeck.FillColor = colors.Green + status.Cyberdeck.StrokeColor = colors.Green + status.Cyberdeck.Refresh() + } + } +} + +// Prompt when an application submits request to connect to wallet with XSWD +func XSWDPrompt(ad *xswd.ApplicationData) (confirmed bool) { + if cyberdeck.WS.advanced { + // If global permissions enabled set them here + if cyberdeck.WS.global.enabled { + logger.Printf("[Engram] Applied global XSWD permissions to %s\n", ad.Name) + cyberdeck.WS.RLock() + for k, v := range cyberdeck.WS.global.permissions { + ad.Permissions[k] = v + } + cyberdeck.WS.RUnlock() + } + + // If wallet is set to connect to all requests, connect to app + if cyberdeck.WS.global.connect { + logger.Printf("[Engram] Applied automatic XSWD connection to %s\n", ad.Name) + fyne.CurrentApp().SendNotification(&fyne.Notification{Title: ad.Name, Content: "A new connection request has been approved"}) + go refreshXSWDList() + return true + } + } else { + // Restrictive mode overwrites any requested permissions to default Ask, and sets certain methods to AlwaysDeny + ad.Permissions = map[string]xswd.Permission{} + ad.Permissions["QueryKey"] = xswd.AlwaysDeny + ad.Permissions["query_key"] = xswd.AlwaysDeny + } + + overlay := session.Window.Canvas().Overlays() + + headerText := "NEW CONNECTION REQUEST" + + header := canvas.NewText(headerText, colors.Gray) + header.TextSize = 16 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + labelApp := canvas.NewText("APP NAME", colors.Gray) + labelApp.TextSize = 14 + labelApp.Alignment = fyne.TextAlignLeading + labelApp.TextStyle = fyne.TextStyle{Bold: true} + + textApp := widget.NewRichTextFromMarkdown("### " + ad.Name) + textApp.Wrapping = fyne.TextWrapWord + + labelID := canvas.NewText("APP ID", colors.Gray) + labelID.TextSize = 14 + labelID.Alignment = fyne.TextAlignLeading + labelID.TextStyle = fyne.TextStyle{Bold: true} + + textID := widget.NewRichTextFromMarkdown(ad.Id) + textID.Wrapping = fyne.TextWrapWord + + labelURL := canvas.NewText("URL", colors.Gray) + labelURL.TextSize = 14 + labelURL.Alignment = fyne.TextAlignLeading + labelURL.TextStyle = fyne.TextStyle{Bold: true} + + textURL := widget.NewRichTextFromMarkdown(ad.Url) + textURL.Wrapping = fyne.TextWrapWord + + labelPermissions := canvas.NewText("PERMISSIONS", colors.Gray) + labelPermissions.TextSize = 14 + labelPermissions.Alignment = fyne.TextAlignLeading + labelPermissions.TextStyle = fyne.TextStyle{Bold: true} + + // Get permissioned methods from xswd.ApplicationData and create permission objects + var methods []string + for k := range ad.Permissions { + methods = append(methods, k) + } + + sort.Strings(methods) + + permForm := container.NewVBox() + + textSpacer := canvas.NewRectangle(color.Transparent) + textSpacer.SetMinSize(fyne.NewSize(10, 3)) + + for _, k := range methods { + perm := ad.Permissions[k] + permColor := colors.Account + switch perm { + case xswd.AlwaysAllow: + permColor = colors.Green + case xswd.AlwaysDeny: + permColor = colors.Red + } + + textMethod := widget.NewRichTextFromMarkdown("### " + k) + textMethod.Wrapping = fyne.TextWrapWord + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.5, 2)) + + add := container.NewVBox( + textMethod, + container.NewHBox( + textSpacer, + canvas.NewText(perm.String(), permColor), + ), + textSpacer, + container.NewHBox( + sep, + ), + ) + + permForm.Add(add) + } + + if len(permForm.Objects) == 0 { + permForm.Add( + container.NewVBox( + widget.NewRichTextFromMarkdown("No permissions"), + ), + ) + } else { + permForm.Add(textSpacer) + } + + labelEvents := canvas.NewText("EVENTS", colors.Gray) + labelEvents.TextSize = 14 + labelEvents.Alignment = fyne.TextAlignLeading + labelEvents.TextStyle = fyne.TextStyle{Bold: true} + + eventsForm := container.NewVBox() + + // Get registered events from xswd.ApplicationData and create event objects + for name, b := range ad.RegisteredEvents { + eventColor := colors.Red + if b { + eventColor = colors.Green + } + + textEvent := widget.NewRichTextFromMarkdown(fmt.Sprintf("### %s", name)) + textEvent.Wrapping = fyne.TextWrapWord + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.5, 2)) + + add := container.NewVBox( + textEvent, + container.NewHBox( + textSpacer, + canvas.NewText(strconv.FormatBool(b), eventColor), + ), + textSpacer, + container.NewHBox( + sep, + ), + ) + + eventsForm.Add(add) + } + + if len(eventsForm.Objects) == 0 { + eventsForm.Add( + container.NewVBox( + widget.NewRichTextFromMarkdown("No events"), + ), + ) + } + + rectBox := canvas.NewRectangle(color.Transparent) + rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.90, ui.MaxHeight*0.48)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(0, 10)) + + options := widget.NewSelect([]string{xswd.Allow.String(), xswd.Deny.String()}, nil) + + content := container.NewStack( + container.NewBorder( + nil, + container.NewVBox( + rectSpacer, + rectSpacer, + options, + rectSpacer, + rectSpacer, + ), + nil, + nil, + container.NewStack( + rectBox, + container.NewVScroll( + container.NewVBox( + rectSpacer, + rectSpacer, + labelApp, + textApp, + rectSpacer, + labelID, + textID, + rectSpacer, + labelURL, + textURL, + rectSpacer, + labelPermissions, + permForm, + rectSpacer, + labelEvents, + eventsForm, + rectSpacer, + ), + ), + ), + ), + ) + + // Create and show connection prompt + done := make(chan struct{}) + btnDismiss := widget.NewButton("Deny", nil) + btnDismiss.OnTapped = func() { + if options.Selected == xswd.Allow.String() { + confirmed = true + } + done <- struct{}{} + } + + options.OnChanged = func(s string) { + if s == xswd.Deny.String() { + btnDismiss.Importance = widget.MediumImportance + } else { + btnDismiss.Importance = widget.HighImportance + } + btnDismiss.SetText(s) + btnDismiss.Refresh() + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + container.NewCenter( + container.NewStack( + span, + ), + ), + rectSpacer, + rectSpacer, + content, + btnDismiss, + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ), + ), + ) + + if a.Driver().Device().IsMobile() { + fyne.Do(func() { + fyne.CurrentApp().SendNotification(&fyne.Notification{Title: ad.Name, Content: "A new connection request has been received"}) + }) + } else { + fyne.Do(func() { + session.Window.RequestFocus() + }) + } + + // Wait for user input or socket close + select { + case <-done: + + case <-ad.OnClose: + + } + + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + + go refreshXSWDList() + + return +} + +// Handle incoming TELA link requests and return params to be displayed in approval prompt +func handleTELALinkRequest(linkParams TELALink_Params) (params string, err error) { + var args []string + var target string + target, args, err = tela.ParseTELALink(linkParams.TelaLink) + if err != nil { + return + } + + switch target { + case "tela": + switch args[0] { + case "open": // open TELA content similar to a hyperlink + if len(args) < 2 || len(args[1]) != 64 { + err = fmt.Errorf("/open/ request has invalid scid argument") + return + } + + // Engram will check content rating and show it in prompt + var rating tela.Rating_Result + rating, err = tela.GetRating(args[1], session.Daemon, 0) + if err != nil { + return + } + + var index tela.INDEX + index, err = tela.GetINDEXInfo(args[1], session.Daemon) + if err != nil { + return + } + + var linkDisplay TELALink_Display + linkDisplay.Name = index.NameHdr + linkDisplay.Descr = index.DescrHdr + linkDisplay.DURL = index.DURL + linkDisplay.TelaLink = linkParams.TelaLink + rating.Ratings = nil // don't need to show each individual rating in prompt + linkDisplay.Rating = &rating + + params = fmt.Sprintf("%+v", linkDisplay) + if indentParams, err := json.MarshalIndent(linkDisplay, "", " "); err == nil { + params = string(indentParams) + } + default: + err = fmt.Errorf("invalid argument: %s", args[0]) + return + } + case "engram": + if len(args) < 3 { + err = fmt.Errorf("invalid engram link format") + return + } + + switch args[0] { + case "asset": + switch args[1] { + case "manager": // open asset manager module with scid data + if len(args[2]) != 64 { + err = fmt.Errorf("/manager/ request has invalid scid argument") + return + } + default: + err = fmt.Errorf("invalid argument: %s", args[1]) + return + } + default: + err = fmt.Errorf("invalid argument: %s", args[0]) + return + } + + params = fmt.Sprintf("%+v", linkParams) + if indentParams, err := json.MarshalIndent(linkParams, "", " "); err == nil { + params = string(indentParams) // indent params if able + } + default: + err = fmt.Errorf("invalid target: %s", target) + return + } + + return +} + +// Ask permission to complete a specific request from a connected application, +// can choose to Allow, Always Allow, Deny, Always Deny the request +func AskPermissionForRequest(ad *xswd.ApplicationData, request *jrpc2.Request) (choice xswd.Permission) { + method := request.Method() + // Gnomon methods behave as AlwaysAllow + if strings.HasPrefix(method, "Gnomon.") { + return xswd.Allow + } + + // All other methods require approval + choice = xswd.Deny + + // EPOCH is not online or permissioned so Deny request + if strings.HasSuffix(method, "EPOCH") && !epoch.IsActive() { + return + } + + overlay := session.Window.Canvas().Overlays() + + headerText := "NEW PERMISSION REQUEST" + + header := canvas.NewText(headerText, colors.Gray) + header.TextSize = 16 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + labelApp := canvas.NewText("FROM", colors.Gray) + labelApp.TextSize = 14 + labelApp.Alignment = fyne.TextAlignLeading + labelApp.TextStyle = fyne.TextStyle{Bold: true} + + textApp := widget.NewRichTextFromMarkdown("### " + ad.Name) + textApp.Wrapping = fyne.TextWrapWord + + labelRequest := canvas.NewText("REQUESTING", colors.Gray) + labelRequest.TextSize = 14 + labelRequest.Alignment = fyne.TextAlignLeading + labelRequest.TextStyle = fyne.TextStyle{Bold: true} + + textRequest := widget.NewRichTextFromMarkdown("### " + method) + textRequest.Wrapping = fyne.TextWrapWord + + labelParams := canvas.NewText("PARAMETERS", colors.Gray) + labelParams.TextSize = 14 + labelParams.Alignment = fyne.TextAlignLeading + labelParams.TextStyle = fyne.TextStyle{Bold: true} + + params := "None" + if method == "HandleTELALinks" { + var linkParams TELALink_Params + err := request.UnmarshalParams(&linkParams) + if err != nil { + logger.Errorf("[Engram] Denied TELA link request %s from %s: %s\n", request.ParamString(), ad.Name, err) + return + } + + params, err = handleTELALinkRequest(linkParams) + if err != nil { + logger.Errorf("[Engram] Denied TELA link request %q from %s: %s\n", linkParams.TelaLink, ad.Name, err) + return + } + } else if request.ParamString() != "" { + params = strings.ReplaceAll(strings.Join(strings.Fields(request.ParamString()), " "), "\n", " ") + + // Unmarshall and indent params if able + var buffer interface{} + if request.UnmarshalParams(&buffer) == nil { + if indentParams, err := json.MarshalIndent(buffer, "", " "); err == nil { + params = string(indentParams) + } + } + } + + textParams := widget.NewLabel(params) + textParams.Wrapping = fyne.TextWrapWord + + rectBox := canvas.NewRectangle(color.Transparent) + rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.90, ui.MaxHeight*0.48)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(0, 10)) + + permissions := []string{ + xswd.Allow.String(), + xswd.Deny.String(), + } + + // Add AlwaysAllow option if method is !noStore + if cyberdeck.WS.server.CanStorePermission(method) { + permissions = append(permissions, xswd.AlwaysAllow.String()) + } + + permissions = append(permissions, xswd.AlwaysDeny.String()) + + options := widget.NewSelect(permissions, nil) + + content := container.NewStack( + container.NewBorder( + nil, + container.NewVBox( + rectSpacer, + rectSpacer, + options, + rectSpacer, + rectSpacer, + ), + nil, + nil, + container.NewStack( + rectBox, + container.NewVScroll( + container.NewVBox( + labelApp, + textApp, + rectSpacer, + labelRequest, + textRequest, + rectSpacer, + labelParams, + textParams, + ), + ), + ), + ), + ) + + // Create and show request prompt + done := make(chan struct{}) + btnDismiss := widget.NewButton("Deny", nil) + btnDismiss.OnTapped = func() { + switch options.Selected { + case xswd.Allow.String(): + choice = xswd.Allow + case xswd.Deny.String(): + choice = xswd.Deny + case xswd.AlwaysAllow.String(): + choice = xswd.AlwaysAllow + case xswd.AlwaysDeny.String(): + choice = xswd.AlwaysDeny + } + done <- struct{}{} + } + + options.OnChanged = func(s string) { + switch s { + case xswd.Allow.String(), xswd.AlwaysAllow.String(): + btnDismiss.Importance = widget.HighImportance + case xswd.Deny.String(), xswd.AlwaysDeny.String(): + btnDismiss.Importance = widget.MediumImportance + } + btnDismiss.SetText(s) + btnDismiss.Refresh() + } + + linkRemove := widget.NewHyperlinkWithStyle("Remove Application", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkRemove.OnTapped = func() { + verificationOverlay( + false, + ad.Name, + "Remove this application?", + "Remove", + func(b bool) { + if b { + cyberdeck.WS.server.RemoveApplication(ad) + } + }, + ) + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + container.NewCenter( + container.NewStack( + span, + ), + ), + rectSpacer, + rectSpacer, + content, + btnDismiss, + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkRemove, + layout.NewSpacer(), + ), + rectSpacer, + ), + ), + ), + ) + + if a.Driver().Device().IsMobile() { + fyne.CurrentApp().SendNotification(&fyne.Notification{Title: ad.Name, Content: "A new permission request has been received"}) + } else { + session.Window.RequestFocus() + } + + // Wait for user input or socket close + select { + case <-done: + + case <-ad.OnClose: + + } + + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + + go refreshXSWDList() + + return choice +} + +// Refresh list of connected XSWD apps +func refreshXSWDList() { + time.Sleep(time.Second) + if cyberdeck.WS.server != nil { + cyberdeck.WS.apps = cyberdeck.WS.server.GetApplications() + sort.Slice(cyberdeck.WS.apps, func(i, j int) bool { return cyberdeck.WS.apps[i].Name < cyberdeck.WS.apps[j].Name }) + if cyberdeck.WS.list != nil { + fyne.Do(func() { + cyberdeck.WS.list.UnselectAll() + cyberdeck.WS.list.FocusLost() + cyberdeck.WS.list.Refresh() + }) + } + } +} + +// Ask permission to complete a specific Engram action, using xswd permissions to match existing requests that have params to display +func AskPermissionForRequestE(headerText string, params interface{}) (choice xswd.Permission, err error) { + choice = xswd.Deny + + var paramString string + + switch p := params.(type) { + case TELALink_Params: + paramString, err = handleTELALinkRequest(p) + if err != nil { + err = fmt.Errorf("denied TELA link request %s: %s", p.TelaLink, err) + return + } + case string: + switch p { + case "TELA R OFF": + paramString = "You will be viewing all TELA content as per your TELA settings.\n\n" + paramString += "Min Likes will omit results if they are below the set likes ratio.\n\n" + paramString += "Search exclusions will omit results that include the set exclusion text in their dURL." + default: + err = fmt.Errorf("unknown Engram request param string: %s", p) + return + } + default: + err = fmt.Errorf("unknown Engram request params: %T", p) + return + } + + overlay := session.Window.Canvas().Overlays() + + header := canvas.NewText(headerText, colors.Gray) + header.TextSize = 16 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + labelApp := canvas.NewText("FROM", colors.Gray) + labelApp.TextSize = 14 + labelApp.Alignment = fyne.TextAlignLeading + labelApp.TextStyle = fyne.TextStyle{Bold: true} + + textApp := widget.NewRichTextFromMarkdown("### Engram") + textApp.Wrapping = fyne.TextWrapWord + + labelParams := canvas.NewText("PARAMETERS", colors.Gray) + labelParams.TextSize = 14 + labelParams.Alignment = fyne.TextAlignLeading + labelParams.TextStyle = fyne.TextStyle{Bold: true} + + textParams := widget.NewLabel(paramString) + textParams.Wrapping = fyne.TextWrapWord + + rectBox := canvas.NewRectangle(color.Transparent) + rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.90, ui.MaxHeight*0.48)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(0, 10)) + + permissions := []string{ + xswd.Allow.String(), + xswd.Deny.String(), + } + + options := widget.NewSelect(permissions, nil) + + content := container.NewStack( + container.NewBorder( + nil, + container.NewVBox( + rectSpacer, + rectSpacer, + options, + rectSpacer, + rectSpacer, + ), + nil, + nil, + container.NewStack( + rectBox, + container.NewVScroll( + container.NewVBox( + labelApp, + textApp, + rectSpacer, + labelParams, + textParams, + rectSpacer, + ), + ), + ), + ), + ) + + // Create and show request prompt + done := make(chan struct{}) + btnDismiss := widget.NewButton("Deny", nil) + btnDismiss.OnTapped = func() { + switch options.Selected { + case xswd.Allow.String(): + choice = xswd.Allow + default: + choice = xswd.Deny + } + done <- struct{}{} + } + + options.OnChanged = func(s string) { + switch s { + case xswd.Allow.String(): + btnDismiss.Importance = widget.HighImportance + default: + btnDismiss.Importance = widget.MediumImportance + } + btnDismiss.SetText(s) + btnDismiss.Refresh() + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + container.NewCenter( + container.NewStack( + span, + ), + ), + rectSpacer, + rectSpacer, + rectSpacer, + content, + btnDismiss, + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ), + ), + ) + + // Wait for user input + <-done + + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + + return +} + +func isASCII(s string) bool { + for _, c := range s { + if c > unicode.MaxASCII { + return false + } + } + return true +} + +// Wrapper for serving TELA content toggling tela.updates if disabled, updated content should be checked for and presented to the user before calling serveTELAUpdates +func serveTELAUpdates(scid string) (link string, err error) { + var toggledUpdates bool + if !tela.UpdatesAllowed() { + tela.AllowUpdates(true) + toggledUpdates = true + } + + link, err = tela.ServeTELA(scid, session.Daemon) + if toggledUpdates { + tela.AllowUpdates(false) + } + + return +} + +// Convert TELA error to shortened string for display +func telaErrorToString(err error) string { + str := "serving TELA" + if strings.Contains(err.Error(), "user defined no updates and content has been updated to") { + str = "content has been updated" + } else if strings.Contains(err.Error(), "already exists") { + str = "content already exists" + } + + return fmt.Sprintf("%s %s", "error", str) +} + +// Get the ratio of likes for a TELA SCID, if ratio < minLines an error will be returned +func getLikesRatio(scid, dURL, searchExclusions string, minLikes float64) (ratio float64, ratings tela.Rating_Result, err error) { + if gnomon.Index == nil { + err = fmt.Errorf("gnomon is not online") + return + } + + err = telaFilterSearchExclusions(dURL, searchExclusions) + if err != nil { + return + } + + _, up := gnomon.GetSCIDValuesByKey(scid, "likes") + if up == nil { + err = fmt.Errorf("could not get %s likes", scid) + return + } + + _, down := gnomon.GetSCIDValuesByKey(scid, "dislikes") + if down == nil { + err = fmt.Errorf("could not get %s dislikes", scid) + return + } + + ratings.Likes = up[0] + ratings.Dislikes = down[0] + + total := float64(up[0] + down[0]) + if total == 0 { + ratio = 50 + } else { + ratio = (float64(up[0]) / total) * 100 + } + + if ratio < minLikes { + err = fmt.Errorf("%s is below min rating setting", scid) + } + + return +} + +// Check if a search exclusion is found in a TELA dURL +func telaFilterSearchExclusions(dURL, searchExclusions string) (err error) { + for _, split := range strings.Split(searchExclusions, ",") { + exclude := strings.TrimSpace(split) + if exclude != "" && strings.Contains(dURL, exclude) { + err = fmt.Errorf("found search exclusion %q in dURL %s", exclude, dURL) + return + } + } + + return +} + +// Sort and return search display strings for list widget +func telaSearchDisplayAll(telaSearch []INDEXwithRatings, sortBy string) (display []string) { + switch sortBy { + case "Z-A": + sort.Slice(telaSearch, func(i, j int) bool { + return telaSearch[i].NameHdr > telaSearch[j].NameHdr + }) + case "A-Z": + sort.Slice(telaSearch, func(i, j int) bool { + return telaSearch[i].NameHdr < telaSearch[j].NameHdr + }) + default: // Ratings + sort.Slice(telaSearch, func(i, j int) bool { + if telaSearch[i].ratings.Likes != telaSearch[j].ratings.Likes { + return telaSearch[i].ratings.Likes > telaSearch[j].ratings.Likes + } + + return telaSearch[i].ratings.Dislikes < telaSearch[j].ratings.Dislikes + }) + } + + for _, ind := range telaSearch { + display = append(display, ind.NameHdr+";;;"+ind.SCID) + } + + return +} + +// Validate the URL as URI or SC image and return it as a canvas.Image +func handleImageURL(nameHdr, imageURL string, size fyne.Size) (image *canvas.Image, err error) { + scImage, err := tela.ValidateImageURL(imageURL, session.Daemon) + if err != nil { + return + } + + var resource fyne.Resource + image = canvas.NewImageFromResource(nil) + + if scImage != "" { + resource = fyne.NewStaticResource(nameHdr, []byte(scImage)) + } else { + resource, err = fyne.LoadResourceFromURLString(imageURL) + if err != nil { + return + } + } + + image.Resource = resource + image.SetMinSize(size) + image.FillMode = canvas.ImageFillContain + image.Refresh() + + return +} + +// Convert session.Domain to string for display +func sessionDomainToString(domain string) string { + str := strings.TrimPrefix(domain, "app.") + switch str { + // case "main": + // case "create": + // case "restore": + // case "settings": + case "wallet": + return "Dashboard" + // case "register": + case "explorer": + return "Asset Explorer" + case "manager": + return "Asset Manager" + case "send", "transfers", "messages", "cyberdeck", "Identity", "datapad": + return fmt.Sprintf("%s%s", strings.ToUpper(str[0:1]), str[1:]) + case "tela", "tela.manager": + return "TELA" + case "service": + return "Services" + case "sign", "verify": + return "File Manager" + case "messages.contact": + return "Message Contact" + case "cyberdeck.manager": + return "Cyberdeck Manager" + case "cyberdeck.permissions": + return "Cyberdeck Settings" + case "sc.builder": + return "Contract Builder" + case "sc.editor": + return "Contract Editor" + default: + return "" + } +} + +// Add EPOCH session values to the account total stores +func storeEPOCHTotal(timeout time.Duration) { + epochSession, err := epoch.GetSession(timeout) + if err == nil { + cyberdeck.EPOCH.total.Hashes += epochSession.Hashes + cyberdeck.EPOCH.total.MiniBlocks += epochSession.MiniBlocks + + var eMar []byte + if eMar, err = json.Marshal(cyberdeck.EPOCH.total); err == nil { + err = StoreEncryptedValue("Cyberdeck", []byte("EPOCH"), eMar) + } + } + + if err != nil { + logger.Errorf("[EPOCH] Storing total: %s\n", err) + } +} + +// Store account EPOCH session and stop EPOCH +func stopEPOCH() { + if cyberdeck.EPOCH.enabled { + storeEPOCHTotal(time.Second * 4) + } + + epoch.StopGetWork() + cyberdeck.EPOCH.enabled = false + //cyberdeck.EPOCH.allowWithAddress = false +} + +// Check if value exists within a string array/slice +func scidExist(s []string, str string) bool { + for _, v := range s { + if v == str { + return true + } + } + + return false +} diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..694f8c8 --- /dev/null +++ b/go.sum @@ -0,0 +1,337 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +fyne.io/fyne/v2 v2.6.2 h1:RPgwmXWn+EuP/TKwO7w5p73ILVC26qHD9j3CZUZNwgM= +fyne.io/fyne/v2 v2.6.2/go.mod h1:9IJ8uWgzfcMossFoUkLiOrUIEtaDvF4nML114WiCtXU= +fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg= +fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= +fyne.io/x/fyne v0.0.0-20250418202416-58a230ad1acb h1:2BazNmb/kwgqRdvE9L+NgW8sfoWGn3iy1Ox8R4+CSmc= +fyne.io/x/fyne v0.0.0-20250418202416-58a230ad1acb/go.mod h1:u3LF1EkElytjOT8OHxft16trctGndF9qpsoH6YIDOUU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/VictoriaMetrics/metrics v1.23.1 h1:/j8DzeJBxSpL2qSIdqnRFLvQQhbJyJbbEi22yMm7oL0= +github.com/VictoriaMetrics/metrics v1.23.1/go.mod h1:rAr/llLpEnAdTehiNlUxKgnjcOuROSzpw0GvjpEbvFc= +github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho= +github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II= +github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= +github.com/cenkalti/hub v1.0.2 h1:Nqv9TNaA9boeO2wQFW8o87BY3zKthtnzXmWGmJqhAV8= +github.com/cenkalti/hub v1.0.2/go.mod h1:8LAFAZcCasb83vfxatMUnZHRoQcffho2ELpHb+kaTJU= +github.com/cenkalti/rpc2 v1.0.4 h1:MJWmm7mbt8r/ZkQS+qr/e2KMMrhMLPr/62CYZIHybdI= +github.com/cenkalti/rpc2 v1.0.4/go.mod h1:2yfU5b86vOr16+iY1jN3MvT6Kxc9Nf8j5iZWwUf7iaw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/civilware/Gnomon v0.0.0-20240403103529-8b2fdb2b3106 h1:9NSEj0KUC/Xlaw7uk/CIswEevheDNojrG6JoMPXv3G8= +github.com/civilware/Gnomon v0.0.0-20240403103529-8b2fdb2b3106/go.mod h1:B0/D3D/FVqqugHZ0fO0da2AW+5MiO82uGLOZcmS0CFk= +github.com/civilware/epoch v0.0.0-20241002060739-1ed2fc6f74cb h1:ZM5hGZMiUvheyVjzJYgvY/A34NeDZ2WO8x7oMQA6Oyc= +github.com/civilware/epoch v0.0.0-20241002060739-1ed2fc6f74cb/go.mod h1:aATobu9h/6BK9C+1e/DRBi6+dUNrHFqmykhhrxQvbTQ= +github.com/civilware/tela v0.0.0-20250806221602-aa892d2ff8d4 h1:Coa5YQoaFcOd4fBEtUhbj3Kth3TX+9s5okjfulEis58= +github.com/civilware/tela v0.0.0-20250806221602-aa892d2ff8d4/go.mod h1:J+maI4zPRe7zpSpTyHDopUg3eQRqCKffvBIRnOhipB8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= +github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= +github.com/creachadair/jrpc2 v0.35.4 h1:5ELLV7CMKLfALzkKNsQ//ngZLWDbEmAXgTgkL3JXAcU= +github.com/creachadair/jrpc2 v0.35.4/go.mod h1:a53Cer/NMD1y8P9UB2XbuOLRELKRLDf8u7bRi4v1qsE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= +github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= +github.com/deroproject/derohe v0.0.0-20250813215012-9b6a8b82c839 h1:RWh3dbb/Z29Non+otA9su7AfYpHPpadHzY6v5JNforg= +github.com/deroproject/derohe v0.0.0-20250813215012-9b6a8b82c839/go.mod h1:EWHh1VkXRnCHvyGML98kXhngDFYebmOhk/9kZ1ATJ1c= +github.com/deroproject/graviton v0.0.0-20220130070622-2c248a53b2e1 h1:nsiNx83HYmRmYpYO37pUzSTmB7p9PFtGBl4FyD+a0jg= +github.com/deroproject/graviton v0.0.0-20220130070622-2c248a53b2e1/go.mod h1:a4u6QJtGGIADg1JwujD77UtaAyhIxg14+I0C7xjyQcc= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8= +github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= +github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs= +github.com/fyne-io/gl-js v0.2.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI= +github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk= +github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk= +github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA= +github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM= +github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw= +github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= +github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= +github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= +github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc= +github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU= +github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8= +github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= +github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A= +github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0= +github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8= +github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE= +github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= +github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M= +github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/reedsolomon v1.10.0/go.mod h1:qHMIzMkuZUWqIh8mS/GruPdo3u0qwX2jk/LH440ON7Y= +github.com/klauspost/reedsolomon v1.12.5 h1:4cJuyH926If33BeDgiZpI5OU0pE+wUHZvMSyNGqN73Y= +github.com/klauspost/reedsolomon v1.12.5/go.mod h1:LkXRjLYGM8K/iQfujYnaPeDmhZLqkrGUyG9p7zs5L68= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lesismal/llib v1.2.2 h1:ZoVgP9J58Ju3Yue5jtj8ybWl+BKqoVmdRaN1mNwG5Gc= +github.com/lesismal/llib v1.2.2/go.mod h1:70tFXXe7P1FZ02AU9l8LgSOK7d7sRrpnkUr3rd3gKSg= +github.com/lesismal/nbio v1.6.7 h1:EeiH0Vn0v5NG7masYNWugibPdNZZYYBPa0pGj4GOrbg= +github.com/lesismal/nbio v1.6.7/go.mod h1:mBn1rSIZ+cmOILhvP+/1Mb/JimgA+1LQudlHJUb/aNA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ= +github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= +github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA= +github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= +github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= +github.com/templexxx/cpu v0.0.9 h1:cGGLK8twbc1J1S/fHnZW7BylXYaFP+0fR2s+nzsFDiU= +github.com/templexxx/cpu v0.0.9/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= +github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg= +github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= +github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= +github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ= +github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xtaci/kcp-go/v5 v5.6.2 h1:pSXMa5MOsb+EIZKe4sDBqlTExu2A/2Z+DFhoX2qtt2A= +github.com/xtaci/kcp-go/v5 v5.6.2/go.mod h1:LsinWoru+lWWJHb+EM9HeuqYxV6bb9rNcK12v67jYzQ= +github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= +github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= +github.com/ybbus/jsonrpc v2.1.2+incompatible h1:V4mkE9qhbDQ92/MLMIhlhMSbz8jNXdagC3xBR5NDwaQ= +github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= +go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513122933-cd7d49e622d5/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= +golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +mvdan.cc/xurls/v2 v2.4.0 h1:tzxjVAj+wSBmDcF6zBB7/myTy3gX9xvi8Tyr28AuQgc= +mvdan.cc/xurls/v2 v2.4.0/go.mod h1:+GEjq9uNjqs8LQfM9nVnM8rff0OQ5Iash5rzX+N1CSg= diff --git a/layouts.go b/layouts.go index 722cab4..b7d5c25 100644 --- a/layouts.go +++ b/layouts.go @@ -1,17051 +1,16533 @@ -// Copyright 2023-2024 DERO Foundation. All rights reserved. -// Use of this source code in any form is governed by RESEARCH license. -// license can be found in the LICENSE file. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package main - -import ( - "crypto/sha1" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "image/color" - "net" - "net/url" - "os" - "path/filepath" - "regexp" - "runtime" - "sort" - "strconv" - "strings" - "sync" - "time" - "unicode" - "unicode/utf8" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/canvas" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/data/binding" - "fyne.io/fyne/v2/dialog" - "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/storage" - "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" - x "fyne.io/x/fyne/widget" - "github.com/civilware/Gnomon/structures" - "github.com/civilware/epoch" - "github.com/civilware/tela" - "github.com/civilware/tela/logger" - "github.com/deroproject/derohe/cryptography/crypto" - "github.com/deroproject/derohe/dvm" - "github.com/deroproject/derohe/globals" - "github.com/deroproject/derohe/rpc" - "github.com/deroproject/derohe/walletapi" - "github.com/deroproject/derohe/walletapi/mnemonics" - "github.com/deroproject/derohe/walletapi/xswd" - "github.com/deroproject/graviton" - qrcode "github.com/skip2/go-qrcode" -) - -func layoutMain() fyne.CanvasObject { - // Set theme - a.Settings().SetTheme(themes.main) - session.Domain = "app.main" - session.Path = "" - session.Password = "" - - // Define objects - - btnLogin := widget.NewButton("Connect", nil) - - if session.Error != "" { - btnLogin.Text = session.Error - btnLogin.Disable() - btnLogin.Refresh() - session.Error = "" - } - - btnLogin.OnTapped = func() { - if session.Path == "" { - btnLogin.Text = "No account selected..." - btnLogin.Disable() - btnLogin.Refresh() - } else if session.Password == "" { - btnLogin.Text = "Invalid password..." - btnLogin.Disable() - btnLogin.Refresh() - } else { - if !session.Offline { - btnLogin.Text = "Connect" - } else { - btnLogin.Text = "Decrypt" - } - btnLogin.Enable() - btnLogin.Refresh() - login() - btnLogin.Text = session.Error - btnLogin.Disable() - btnLogin.Refresh() - session.Error = "" - } - } - - btnLogin.Disable() - - session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { - if session.Domain == "app.main" || session.Domain == "app.register" { - if k.Name == fyne.KeyReturn { - if session.Path == "" { - btnLogin.Text = "No account selected..." - btnLogin.Disable() - btnLogin.Refresh() - } else if session.Password == "" { - btnLogin.Text = "Invalid password..." - btnLogin.Disable() - btnLogin.Refresh() - } else { - if !session.Offline { - btnLogin.Text = "Connect" - } else { - btnLogin.Text = "Decrypt" - } - btnLogin.Enable() - btnLogin.Refresh() - login() - btnLogin.Text = "Invalid password..." - btnLogin.Disable() - btnLogin.Refresh() - session.Error = "" - } - } - } else { - return - } - }) - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkCreate := widget.NewHyperlinkWithStyle("Create a new account", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkCreate.OnTapped = func() { - session.Domain = "app.create" - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutNewAccount()) - removeOverlays() - } - - linkRecover := widget.NewHyperlinkWithStyle("Recover an existing account", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkRecover.OnTapped = func() { - session.Domain = "app.restore" - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutRestore()) - removeOverlays() - } - - linkSettings := widget.NewHyperlinkWithStyle("Settings", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkSettings.OnTapped = func() { - session.Domain = "app.settings" - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutSettings()) - removeOverlays() - } - - modeData := binding.BindBool(&session.Offline) - mode := widget.NewCheckWithData(" Offline Mode", modeData) - mode.OnChanged = func(b bool) { - if b { - session.Offline = true - btnLogin.Text = "Decrypt" - btnLogin.Refresh() - } else { - session.Offline = false - btnLogin.Text = "Connect" - btnLogin.Refresh() - } - } - - footer := canvas.NewText("© 2025 DERO FOUNDATION | VERSION "+version.String(), colors.Gray) - footer.TextSize = 10 - footer.Alignment = fyne.TextAlignCenter - footer.TextStyle = fyne.TextStyle{Bold: true} - - wPassword := NewReturnEntry() - wPassword.OnReturn = btnLogin.OnTapped - wPassword.Password = true - wPassword.OnChanged = func(s string) { - session.Error = "" - if !session.Offline { - btnLogin.Text = "Connect" - } else { - btnLogin.Text = "Decrypt" - } - btnLogin.Enable() - btnLogin.Refresh() - session.Password = s - - if len(s) < 1 { - btnLogin.Disable() - btnLogin.Refresh() - } else if session.Path == "" { - btnLogin.Disable() - btnLogin.Refresh() - } else { - btnLogin.Enable() - } - - btnLogin.Refresh() - } - wPassword.SetPlaceHolder("Password") - - // Get account databases in app directory - list, err := GetAccounts() - if err != nil { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutAlert(2)) - } - - // Populate the accounts in dropdown menu - wAccount := widget.NewSelect(list, nil) - wAccount.PlaceHolder = "(Select Account)" - wAccount.OnChanged = func(s string) { - session.Error = "" - if !session.Offline { - btnLogin.Text = "Connect" - } else { - btnLogin.Text = "Decrypt" - } - btnLogin.Refresh() - - // OnChange set wallet path - switch session.Network { - case NETWORK_TESTNET: - session.Path = filepath.Join(AppPath(), "testnet") + string(filepath.Separator) + s - case NETWORK_SIMULATOR: - session.Path = filepath.Join(AppPath(), "testnet_simulator") + string(filepath.Separator) + s - default: - session.Path = filepath.Join(AppPath(), "mainnet") + string(filepath.Separator) + s - } - - if session.Password != "" { - btnLogin.Enable() - } else { - btnLogin.Disable() - } - - session.Window.Canvas().Focus(wPassword) - - btnLogin.Refresh() - } - - if len(list) < 1 { - wAccount.Disable() - wPassword.Disable() - } else { - wAccount.Enable() - } - - wSpacer := widget.NewLabel(" ") - - rectStatus := canvas.NewRectangle(color.Transparent) - rectStatus.SetMinSize(fyne.NewSize(10, 10)) - - headerBlock := canvas.NewRectangle(color.Transparent) - headerBlock.SetMinSize(fyne.NewSize(ui.Width, ui.MaxHeight*0.2)) - - headerBox := canvas.NewRectangle(color.Transparent) - headerBox.SetMinSize(fyne.NewSize(ui.Width, 1)) - - frame := &iframe{} - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(ui.Width, 5)) - - status.Connection.FillColor = colors.Gray - status.Cyberdeck.FillColor = colors.Gray - status.Gnomon.FillColor = colors.Gray - status.EPOCH.FillColor = colors.Gray - status.Sync.FillColor = colors.Gray - - form := container.NewStack( - res.mainBg, - container.NewVBox( - wSpacer, - container.NewStack( - headerBlock, - ), - rectSpacer, - rectSpacer, - wAccount, - rectSpacer, - wPassword, - rectSpacer, - mode, - rectSpacer, - rectSpacer, - btnLogin, - wSpacer, - container.NewStack( - container.NewHBox( - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - ), - ), - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - linkCreate, - layout.NewSpacer(), - ), - ), - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - linkRecover, - layout.NewSpacer(), - ), - ), - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - linkSettings, - layout.NewSpacer(), - ), - ), - ), - ) - - layout := container.NewStack( - frame, - container.NewBorder( - container.NewVBox( - container.NewCenter( - form, - ), - ), - container.NewVBox( - footer, - wSpacer, - ), - nil, - nil, - ), - ) - - return NewVScroll(layout) -} - -func layoutDashboard() fyne.CanvasObject { - resizeWindow(ui.MaxWidth, ui.MaxHeight) - - session.Dashboard = "main" - session.Domain = "app.wallet" - - session.Balance, _ = engram.Disk.Get_Balance() - session.BalanceText = canvas.NewText(walletapi.FormatMoney(session.Balance), colors.Green) - session.BalanceText.TextSize = 28 - session.BalanceText.TextStyle = fyne.TextStyle{Bold: true} - - network := "" - switch session.Network { - case NETWORK_TESTNET: - network = " T E S T N E T " - case NETWORK_SIMULATOR: - network = " S I M U L A T O R " - default: - network = " M A I N N E T " - } - - rectStatus := canvas.NewRectangle(color.Transparent) - rectStatus.SetMinSize(fyne.NewSize(10, 10)) - - frame := &iframe{} - - balanceCenter := container.NewCenter( - container.NewCenter( - session.BalanceText, - ), - ) - - path := strings.Split(session.Path, string(filepath.Separator)) - accountName := canvas.NewText(path[len(path)-1], colors.Green) - accountName.TextStyle = fyne.TextStyle{Bold: true} - accountName.TextSize = 18 - - gramSend := widget.NewButton(" Send ", nil) - - heading := canvas.NewText("B A L A N C E", colors.Gray) - heading.TextSize = 16 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - sendDesc := canvas.NewText("Add Transfer Details", colors.Gray) - sendDesc.TextSize = 18 - sendDesc.Alignment = fyne.TextAlignCenter - sendDesc.TextStyle = fyne.TextStyle{Bold: true} - - sendHeading := canvas.NewText("Send Money", colors.Green) - sendHeading.TextSize = 22 - sendHeading.Alignment = fyne.TextAlignCenter - sendHeading.TextStyle = fyne.TextStyle{Bold: true} - - headerLabel := canvas.NewText(" "+network+" ", colors.Gray) - headerLabel.TextSize = 11 - headerLabel.Alignment = fyne.TextAlignCenter - headerLabel.TextStyle = fyne.TextStyle{Bold: true} - - statusLabel := canvas.NewText(" S T A T U S ", colors.Gray) - statusLabel.TextSize = 11 - statusLabel.Alignment = fyne.TextAlignCenter - statusLabel.TextStyle = fyne.TextStyle{Bold: true} - - daemonLabel := canvas.NewText("OFFLINE", colors.Gray) - daemonLabel.TextSize = 12 - daemonLabel.Alignment = fyne.TextAlignCenter - daemonLabel.TextStyle = fyne.TextStyle{Bold: false} - - cyberdeckText := "CYBERDECK" - if cyberdeck.WS.server != nil { - cyberdeckText = "CYBERDECK (WS)" - } else if cyberdeck.RPC.server != nil { - cyberdeckText = "CYBERDECK (RPC)" - } else { - status.Cyberdeck.FillColor = colors.Gray - status.Cyberdeck.Refresh() - } - - cyberdeckLabel := canvas.NewText(cyberdeckText, colors.Gray) - cyberdeckLabel.TextSize = 12 - cyberdeckLabel.Alignment = fyne.TextAlignTrailing - cyberdeckLabel.TextStyle = fyne.TextStyle{Bold: false} - - gnomonLabel := canvas.NewText("GNOMON", colors.Gray) - gnomonLabel.TextSize = 12 - gnomonLabel.Alignment = fyne.TextAlignCenter - gnomonLabel.TextStyle = fyne.TextStyle{Bold: false} - - epochLabel := canvas.NewText("EPOCH", colors.Gray) - epochLabel.TextSize = 12 - epochLabel.Alignment = fyne.TextAlignTrailing - epochLabel.TextStyle = fyne.TextStyle{Bold: false} - if !epoch.IsActive() { - if cyberdeck.EPOCH.err != nil { - status.EPOCH.FillColor = colors.Red - status.EPOCH.Refresh() - } else { - status.EPOCH.FillColor = colors.Gray - status.EPOCH.Refresh() - } - } - - telaLabel := canvas.NewText("TELA", colors.Gray) - telaLabel.TextSize = 12 - telaLabel.Alignment = fyne.TextAlignCenter - telaLabel.TextStyle = fyne.TextStyle{Bold: false} - - telaStatus := canvas.NewCircle(colors.Gray) - if len(tela.GetServerInfo()) > 0 { - telaStatus.FillColor = colors.Green - } - - animationCanvas := canvas.NewCircle(color.Transparent) - - if !session.Offline { - if len(session.Daemon) > 30 { - daemonLabel.Text = "..." + session.Daemon[len(session.Daemon)-27:] - } else { - daemonLabel.Text = session.Daemon - } - - animationStatus := canvas.NewColorRGBAAnimation( - color.Transparent, - colors.Yellow, - time.Second, - func(c color.Color) { - animationCanvas.FillColor = c - animationCanvas.Refresh() - }) - - animationStatus.RepeatCount = fyne.AnimationRepeatForever - animationStatus.AutoReverse = true - animationStatus.Start() - } - - session.WalletHeight = engram.Disk.Get_Height() - session.StatusText = canvas.NewText(fmt.Sprintf("%d", session.WalletHeight), colors.Gray) - session.StatusText.TextSize = 12 - session.StatusText.Alignment = fyne.TextAlignTrailing - session.StatusText.TextStyle = fyne.TextStyle{Bold: false} - - menuLabel := canvas.NewText(" M O D U L E S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkLogout := widget.NewHyperlinkWithStyle("Sign Out", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkLogout.OnTapped = func() { - closeWallet() - } - - linkHistory := widget.NewHyperlinkWithStyle("View History", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkHistory.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutHistory()) - removeOverlays() - } - - menu := widget.NewSelect([]string{"Identity", "My Account", "Messages", "Transfers", "Asset Explorer", "Services", "Cyberdeck", "File Manager", "Contract Builder", "Datapad", "TELA", " "}, nil) - menu.PlaceHolder = "Select Module ..." - menu.OnChanged = func(s string) { - if s == "My Account" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutAccount()) - removeOverlays() - } else if s == "Transfers" { - session.Window.Canvas().SetContent(layoutTransition()) - session.Window.Canvas().SetContent(layoutTransfers()) - removeOverlays() - } else if s == "Asset Explorer" { - session.Window.Canvas().SetContent(layoutTransition()) - session.Window.Canvas().SetContent(layoutAssetExplorer()) - removeOverlays() - } else if s == "File Manager" { - session.Window.Canvas().SetContent(layoutTransition()) - session.Window.Canvas().SetContent(layoutFileManager()) - removeOverlays() - } else if s == "Contract Builder" { - session.Window.Canvas().SetContent(layoutTransition()) - session.Window.Canvas().SetContent(layoutContractBuilder("")) - removeOverlays() - } else if s == "Datapad" { - session.Window.Canvas().SetContent(layoutTransition()) - session.Window.Canvas().SetContent(layoutDatapad()) - removeOverlays() - } else if s == "Messages" { - session.Window.Canvas().SetContent(layoutTransition()) - session.Window.Canvas().SetContent(layoutMessages()) - removeOverlays() - } else if s == "Cyberdeck" { - session.Window.Canvas().SetContent(layoutTransition()) - session.Window.Canvas().SetContent(layoutCyberdeck()) - removeOverlays() - } else if s == "Identity" { - session.Window.Canvas().SetContent(layoutTransition()) - session.Window.Canvas().SetContent(layoutIdentity()) - removeOverlays() - } else if s == "Services" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutServiceAddress()) - removeOverlays() - } else if s == "TELA" { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutTELA()) - removeOverlays() - } else { - session.Window.Canvas().SetContent(layoutTransition()) - session.Window.Canvas().SetContent(layoutDashboard()) - removeOverlays() - } - - session.LastDomain = session.Window.Content() - } - - res.gram.SetMinSize(fyne.NewSize(ui.Width, 150)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - - rectSquare := canvas.NewRectangle(color.Transparent) - rectSquare.SetMinSize(fyne.NewSize(5, 5)) - - rectOffset := canvas.NewRectangle(color.Transparent) - rectOffset.SetMinSize(fyne.NewSize(81, 1)) - - deroForm := container.NewVBox( - rectSpacer, - res.gram, - rectSpacer, - container.NewStack( - container.NewHBox( - line1, - layout.NewSpacer(), - headerLabel, - layout.NewSpacer(), - line2, - ), - ), - rectSpacer, - rectSpacer, - heading, - rectSpacer, - balanceCenter, - rectSpacer, - rectSpacer, - gramSend, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkHistory, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - container.NewHBox( - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - ), - rectSpacer, - rectSpacer, - menu, - rectSpacer, - rectSpacer, - container.NewHBox( - line1, - layout.NewSpacer(), - statusLabel, - layout.NewSpacer(), - line2, - ), - rectSpacer, - rectSpacer, - container.NewVBox( - container.NewHBox( - container.NewStack( - rectStatus, - status.Connection, - ), - rectSquare, - daemonLabel, - layout.NewSpacer(), - container.NewStack( - rectOffset, - session.StatusText, - ), - rectSquare, - container.NewStack( - rectStatus, - animationCanvas, - status.Sync, - ), - ), - rectOffset, - container.NewHBox( - container.NewStack( - rectStatus, - animationCanvas, - status.Gnomon, - ), - rectSquare, - gnomonLabel, - layout.NewSpacer(), - container.NewStack( - rectOffset, - epochLabel, - ), - rectSquare, - container.NewStack( - rectStatus, - animationCanvas, - status.EPOCH, - ), - ), - rectOffset, - container.NewHBox( - container.NewStack( - rectStatus, - telaStatus, - ), - rectSquare, - telaLabel, - layout.NewSpacer(), - container.NewStack( - rectOffset, - cyberdeckLabel, - ), - rectSquare, - container.NewStack( - rectStatus, - status.Cyberdeck, - ), - ), - ), - ) - - grid := container.NewCenter( - deroForm, - ) - - gramSend.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutSend()) - removeOverlays() - } - - session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { - if session.Domain != "app.wallet" { - return - } - - if k.Name == fyne.KeyRight { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutCyberdeck()) - removeOverlays() - } else if k.Name == fyne.KeyLeft { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutIdentity()) - removeOverlays() - } else if k.Name == fyne.KeyUp { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutTransfers()) - removeOverlays() - } else if k.Name == fyne.KeyDown { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMessages()) - removeOverlays() - } - }) - - top := container.NewCenter( - layout.NewSpacer(), - grid, - layout.NewSpacer(), - ) - - bottom := container.NewStack( - container.NewVBox( - container.NewCenter( - linkLogout, - ), - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - c := container.NewBorder( - top, - bottom, - nil, - nil, - ) - - layout := container.NewStack( - frame, - c, - ) - - return NewVScroll(layout) -} - -func layoutSend() fyne.CanvasObject { - session.Domain = "app.send" - - wSpacer := widget.NewLabel(" ") - frame := &iframe{} - - btnSend := widget.NewButton("Save", nil) - - wAmount := widget.NewEntry() - wAmount.SetPlaceHolder("Amount") - - wMessage := widget.NewEntry() - wMessage.SetValidationError(nil) - wMessage.SetPlaceHolder("Message") - wMessage.Validator = func(s string) error { - bytes := []byte(s) - if len(bytes) <= 130 { - tx.Comment = s - wMessage.SetValidationError(nil) - return nil - } else { - err := errors.New("message too long") - wMessage.SetValidationError(err) - return err - } - } - - wPaymentID := widget.NewEntry() - wPaymentID.Validator = func(s string) (err error) { - tx.PaymentID, err = strconv.ParseUint(s, 10, 64) - if err != nil { - wPaymentID.SetValidationError(err) - tx.PaymentID = 0 - } - - return - } - wPaymentID.SetPlaceHolder("Payment ID / Service Port") - - options := []string{"Anonymity Set: 2 (None)", "Anonymity Set: 4 (Low)", "Anonymity Set: 8 (Low)", "Anonymity Set: 16 (Recommended)", "Anonymity Set: 32 (Medium)", "Anonymity Set: 64 (High)", "Anonymity Set: 128 (High)"} - wRings := widget.NewSelect(options, nil) - - wReceiver := widget.NewEntry() - wReceiver.SetPlaceHolder("Receiver username or address") - wReceiver.SetValidationError(nil) - wReceiver.Validator = func(s string) error { - address, err := globals.ParseValidateAddress(s) - if err != nil { - tx.Address = nil - addr, _ := checkUsername(s, -1) - if addr == "" { - btnSend.Disable() - err = errors.New("invalid username or address") - wReceiver.SetValidationError(err) - tx.Address = nil - return err - } else { - wReceiver.SetValidationError(nil) - tx.Address, _ = globals.ParseValidateAddress(addr) - if tx.Amount != 0 { - balance, _ := engram.Disk.Get_Balance() - if tx.Amount <= balance { - btnSend.Enable() - } - } - } - } else { - if address.IsIntegratedAddress() { - tx.Address = address - - if address.Arguments.HasValue(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { - amount := address.Arguments[address.Arguments.Index(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64)].Value - tx.Amount = amount.(uint64) - wAmount.Text = globals.FormatMoney(amount.(uint64)) - if amount.(uint64) != 0.00000 { - wAmount.Disable() - } - wAmount.Refresh() - } - - if address.Arguments.HasValue(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { - port := address.Arguments[address.Arguments.Index(rpc.RPC_DESTINATION_PORT, rpc.DataUint64)].Value - tx.PaymentID = port.(uint64) - wPaymentID.Text = strconv.FormatUint(port.(uint64), 10) - wPaymentID.Disable() - wPaymentID.Refresh() - } - - if address.Arguments.HasValue(rpc.RPC_COMMENT, rpc.DataString) { - comment := address.Arguments[address.Arguments.Index(rpc.RPC_COMMENT, rpc.DataString)].Value - tx.Comment = comment.(string) - wMessage.Text = comment.(string) - if comment.(string) != "" { - wMessage.Disable() - } - wMessage.Refresh() - } - - if tx.Ringsize == 0 { - wRings.SetSelected("Anonymity Set: 16 (Recommended)") - } - - if tx.Amount != 0 { - balance, _ := engram.Disk.Get_Balance() - if tx.Amount <= balance { - btnSend.Enable() - } - } - } else { - tx.Address = address - wReceiver.SetValidationError(nil) - if tx.Amount != 0 { - balance, _ := engram.Disk.Get_Balance() - if tx.Amount <= balance { - btnSend.Enable() - } - } - } - } - return nil - } - - /* - // TODO - wAll := widget.NewCheck(" All", func(b bool) { - if b { - tx.Amount = engram.Disk.GetAccount().Balance_Mature - wAmount.SetText(walletapi.FormatMoney(tx.Amount)) - } else { - tx.Amount = 0 - wAmount.SetText("") - } - }) - */ - - wAmount.Validator = func(s string) error { - if s == "" { - tx.Amount = 0 - wAmount.SetValidationError(errors.New("invalid transaction amount")) - btnSend.Disable() - } else { - balance, _ := engram.Disk.Get_Balance() - entry, err := globals.ParseAmount(s) - if err != nil { - tx.Amount = 0 - wAmount.SetValidationError(errors.New("invalid transaction amount")) - btnSend.Disable() - return errors.New("invalid transaction amount") - } - - if entry == 0 { - tx.Amount = 0 - wAmount.SetValidationError(errors.New("invalid transaction amount")) - btnSend.Disable() - return errors.New("invalid transaction amount") - } - - if entry <= balance { - tx.Amount = entry - wAmount.SetValidationError(nil) - if wReceiver.Validate() == nil { - btnSend.Enable() - } - } else { - tx.Amount = 0 - btnSend.Disable() - wAmount.SetValidationError(errors.New("insufficient funds")) - } - return nil - } - return errors.New("invalid transaction amount") - } - - wAmount.SetValidationError(nil) - - wRings.PlaceHolder = "(Select Anonymity Set)" - if tx.Ringsize < 2 { - tx.Ringsize = 16 - } else if len(tx.Pending) > 0 { - rsIndex := 3 - switch tx.Ringsize { - case 2: - rsIndex = 0 - case 4: - rsIndex = 1 - case 8: - rsIndex = 2 - case 16: - rsIndex = 3 - case 32: - rsIndex = 4 - case 64: - rsIndex = 5 - case 128: - rsIndex = 6 - } - wRings.SetSelectedIndex(rsIndex) - } - - wRings.OnChanged = func(s string) { - var err error - regex := regexp.MustCompile("[0-9]+") - result := regex.FindAllString(s, -1) - tx.Ringsize, err = strconv.ParseUint(result[0], 10, 64) - if err != nil { - tx.Ringsize = 16 - wRings.SetSelected(options[3]) - } - session.Window.Canvas().Focus(wReceiver) - } - - btnSend.OnTapped = func() { - _, err := globals.ParseAmount(wAmount.Text) - if tx.Address != nil { - if wRings != nil && err == nil && tx.Address != nil { - err = addTransfer() - if err == nil { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutTransfers()) - removeOverlays() - } - } else { - wReceiver.SetValidationError(errors.New("invalid address")) - wReceiver.Refresh() - } - } - } - - sendHeading := canvas.NewText("S E N D M O N E Y", colors.Gray) - sendHeading.TextSize = 16 - sendHeading.Alignment = fyne.TextAlignCenter - sendHeading.TextStyle = fyne.TextStyle{Bold: true} - - optionalLabel := canvas.NewText(" O P T I O N A L ", colors.Gray) - optionalLabel.TextSize = 11 - optionalLabel.Alignment = fyne.TextAlignCenter - optionalLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkCancel := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - - rectList := canvas.NewRectangle(color.Transparent) - rectList.SetMinSize(fyne.NewSize(ui.Width, 260)) - - rect300 := canvas.NewRectangle(color.Transparent) - rect300.SetMinSize(fyne.NewSize(ui.Width, 30)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - - form := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - rect300, - sendHeading, - ), - rectSpacer, - rectSpacer, - wRings, - rectSpacer, - wReceiver, - wAmount, - rectSpacer, - rectSpacer, - container.NewHBox( - line1, - layout.NewSpacer(), - optionalLabel, - layout.NewSpacer(), - line2, - ), - rectSpacer, - rectSpacer, - wPaymentID, - wMessage, - wSpacer, - ) - - grid := container.NewCenter( - form, - ) - - linkCancel.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - if len(tx.Pending) == 0 { - tx = Transfers{} - } - } - - top := container.NewCenter( - layout.NewSpacer(), - grid, - layout.NewSpacer(), - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rect300, - btnSend, - ), - layout.NewSpacer(), - ), - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewHBox( - layout.NewSpacer(), - linkCancel, - layout.NewSpacer(), - ), - layout.NewSpacer(), - ), - wSpacer, - ), - ) - - c := container.NewBorder( - top, - bottom, - nil, - nil, - ) - - layout := container.NewStack( - frame, - c, - ) - - return NewVScroll(layout) -} - -func layoutServiceAddress() fyne.CanvasObject { - session.Domain = "app.service" - - wSpacer := widget.NewLabel(" ") - frame := &iframe{} - - btnCreate := widget.NewButton("Create", nil) - - wPaymentID := widget.NewEntry() - - wReceiver := widget.NewEntry() - wReceiver.Text = engram.Disk.GetAddress().String() - wReceiver.Disable() - - tx.Address, _ = globals.ParseValidateAddress(engram.Disk.GetAddress().String()) - - wReceiver.SetPlaceHolder("Receiver username or address") - wReceiver.SetValidationError(nil) - - wAmount := widget.NewEntry() - wAmount.SetPlaceHolder("Amount") - - wMessage := widget.NewEntry() - wMessage.SetPlaceHolder("Message") - wMessage.Validator = func(s string) (err error) { - bytes := []byte(s) - if len(bytes) <= 130 { - tx.Comment = s - } else { - err = errors.New("message too long") - wMessage.SetValidationError(err) - } - - return - } - - wAmount.Validator = func(s string) error { - if s == "" { - tx.Amount = 0 - wAmount.SetValidationError(errors.New("invalid transaction amount")) - btnCreate.Disable() - } else { - amount, err := globals.ParseAmount(s) - if err != nil { - tx.Amount = 0 - wAmount.SetValidationError(errors.New("invalid transaction amount")) - btnCreate.Disable() - return errors.New("invalid transaction amount") - } - wAmount.SetValidationError(nil) - tx.Amount = amount - btnCreate.Enable() - - return nil - } - return errors.New("invalid transaction amount") - } - - wAmount.SetValidationError(nil) - - wPaymentID.Validator = func(s string) (err error) { - tx.PaymentID, err = strconv.ParseUint(s, 10, 64) - if err != nil { - tx.PaymentID = 0 - btnCreate.Disable() - wPaymentID.SetValidationError(err) - return - } else { - if wReceiver.Text != "" { - btnCreate.Enable() - wPaymentID.SetValidationError(nil) - return - } else { - err = errors.New("empty payment id") - wPaymentID.SetValidationError(err) - return - } - } - } - wPaymentID.SetPlaceHolder("Payment ID / Service Port") - - sendHeading := canvas.NewText("S E R V I C E A D D R E S S", colors.Gray) - sendHeading.TextSize = 16 - sendHeading.Alignment = fyne.TextAlignCenter - sendHeading.TextStyle = fyne.TextStyle{Bold: true} - - optionalLabel := canvas.NewText(" O P T I O N A L ", colors.Gray) - optionalLabel.TextSize = 11 - optionalLabel.Alignment = fyne.TextAlignCenter - optionalLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkCancel := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - - rectList := canvas.NewRectangle(color.Transparent) - rectList.SetMinSize(fyne.NewSize(ui.Width, 260)) - - rect300 := canvas.NewRectangle(color.Transparent) - rect300.SetMinSize(fyne.NewSize(ui.Width, 30)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - - btnCreate.OnTapped = func() { - var err error - if tx.Address != nil && tx.PaymentID != 0 { - if wAmount.Text != "" { - _, err = globals.ParseAmount(wAmount.Text) - } - - if err == nil { - header := canvas.NewText("CREATE SERVICE ADDRESS", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - subHeader := canvas.NewText("Successfully Created", colors.Account) - subHeader.TextSize = 22 - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - - labelAddress := canvas.NewText("------------- INTEGRATED ADDRESS -------------", colors.Gray) - labelAddress.TextSize = 12 - labelAddress.Alignment = fyne.TextAlignCenter - labelAddress.TextStyle = fyne.TextStyle{Bold: true} - - btnCopy := widget.NewButton("Copy Service Address", nil) - - valueAddress := widget.NewRichTextFromMarkdown("") - valueAddress.Wrapping = fyne.TextWrapBreak - - address := engram.Disk.GetRandomIAddress8() - address.Arguments = nil - address.Arguments = append(address.Arguments, rpc.Argument{Name: rpc.RPC_NEEDS_REPLYBACK_ADDRESS, DataType: rpc.DataUint64, Value: uint64(1)}) - address.Arguments = append(address.Arguments, rpc.Argument{Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: tx.Amount}) - address.Arguments = append(address.Arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: tx.PaymentID}) - address.Arguments = append(address.Arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: tx.Comment}) - - err := address.Arguments.Validate_Arguments() - if err != nil { - logger.Errorf("[Service Address] Error: %s\n", err) - subHeader.Text = "Error" - subHeader.Refresh() - btnCopy.Disable() - } else { - logger.Printf("[Service Address] New Integrated Address: %s\n", address.String()) - logger.Printf("[Service Address] Arguments: %s\n", address.Arguments) - - valueAddress.ParseMarkdown("" + address.String()) - valueAddress.Refresh() - } - - btnCopy.OnTapped = func() { - a.Clipboard().SetContent(address.String()) - } - - linkClose := widget.NewHyperlinkWithStyle("Go Back", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - overlay := session.Window.Canvas().Overlays() - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } - - var imageQR *canvas.Image - - qr, err := qrcode.New(address.String(), qrcode.Highest) - if err != nil { - - } else { - qr.BackgroundColor = colors.DarkMatter - qr.ForegroundColor = colors.Green - } - - imageQR = canvas.NewImageFromImage(qr.Image(int(ui.Width * 0.65))) - imageQR.SetMinSize(fyne.NewSize(ui.Width*0.65, ui.Width*0.65)) - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay := session.Window.Canvas().Overlays() - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - subHeader, - rectSpacer, - rectSpacer, - rectSpacer, - labelAddress, - rectSpacer, - valueAddress, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - imageQR, - layout.NewSpacer(), - ), - widget.NewLabel(""), - btnCopy, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - } else { - wReceiver.SetValidationError(errors.New("invalid address")) - wReceiver.Refresh() - } - } - } - - form := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - rect300, - sendHeading, - ), - rectSpacer, - rectSpacer, - wReceiver, - wPaymentID, - rectSpacer, - rectSpacer, - container.NewHBox( - line1, - layout.NewSpacer(), - optionalLabel, - layout.NewSpacer(), - line2, - ), - rectSpacer, - rectSpacer, - wAmount, - wMessage, - wSpacer, - ) - - grid := container.NewCenter( - form, - ) - - linkCancel.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } - - top := container.NewCenter( - layout.NewSpacer(), - grid, - layout.NewSpacer(), - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rect300, - btnCreate, - ), - layout.NewSpacer(), - ), - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewHBox( - layout.NewSpacer(), - linkCancel, - layout.NewSpacer(), - ), - layout.NewSpacer(), - ), - wSpacer, - ), - ) - - c := container.NewBorder( - top, - bottom, - nil, - nil, - ) - - layout := container.NewStack( - frame, - c, - ) - - return NewVScroll(layout) -} - -func layoutNewAccount() fyne.CanvasObject { - resizeWindow(ui.MaxWidth, ui.MaxHeight) - a.Settings().SetTheme(themes.alt) - - session.Domain = "app.register" - session.Language = -1 - session.Error = "" - session.Name = "" - session.Password = "" - session.PasswordConfirm = "" - - languages := mnemonics.Language_List() - - errorText := canvas.NewText(" ", colors.Green) - errorText.TextSize = 12 - errorText.Alignment = fyne.TextAlignCenter - - btnCreate := widget.NewButton("Create", nil) - btnCreate.Disable() - - linkCancel := widget.NewHyperlinkWithStyle("Return to Login", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkCancel.OnTapped = func() { - session.Domain = "app.main" - session.Error = "" - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMain()) - removeOverlays() - } - - btnCopySeed := widget.NewButton("Copy Recovery Words", nil) - btnCopyAddress := widget.NewButton("Copy Address", nil) - - if !a.Driver().Device().IsMobile() { - session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { - if session.Domain != "app.register" { - return - } - - if k.Name == fyne.KeyReturn { - errorText.Text = "" - errorText.Refresh() - create() - errorText.Text = session.Error - errorText.Refresh() - } - }) - } - - wPassword := widget.NewEntry() - wPassword.Password = true - wPassword.OnChanged = func(s string) { - session.Error = "" - errorText.Text = "" - errorText.Refresh() - session.Password = s - - if len(session.Password) > 0 && session.Password == session.PasswordConfirm && !findAccount() && session.Language != -1 { - btnCreate.Enable() - btnCreate.Refresh() - } else { - btnCreate.Disable() - btnCreate.Refresh() - } - } - wPassword.SetPlaceHolder("Password") - wPassword.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) - - wPasswordConfirm := widget.NewEntry() - wPasswordConfirm.Password = true - wPasswordConfirm.OnChanged = func(s string) { - session.Error = "" - errorText.Text = "" - errorText.Refresh() - session.PasswordConfirm = s - - if len(session.Password) > 0 && session.Password == session.PasswordConfirm && !findAccount() && session.Language != -1 { - btnCreate.Enable() - btnCreate.Refresh() - } else { - btnCreate.Disable() - btnCreate.Refresh() - } - } - wPasswordConfirm.SetPlaceHolder("Confirm Password") - wPasswordConfirm.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) - - wAccount := widget.NewEntry() - wAccount.SetPlaceHolder("Account Name") - wAccount.Validator = func(s string) (err error) { - session.Error = "" - errorText.Text = "" - errorText.Refresh() - - if len(s) > 25 { - err = errors.New("account name is too long") - wAccount.SetText(session.Name) - wAccount.Refresh() - return - } - - err = checkDir() - if err != nil { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutAlert(2)) - return - } - - switch getNetwork() { - case NETWORK_TESTNET: - session.Path = filepath.Join(AppPath(), "testnet", s+".db") - case NETWORK_SIMULATOR: - session.Path = filepath.Join(AppPath(), "testnet_simulator", s+".db") - default: - session.Path = filepath.Join(AppPath(), "mainnet", s+".db") - } - session.Name = s - - if findAccount() { - err = errors.New("account name already exists") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } else { - errorText.Text = "" - errorText.Refresh() - } - - if len(session.Password) > 0 && session.Password == session.PasswordConfirm && !findAccount() && session.Language != -1 { - btnCreate.Enable() - btnCreate.Refresh() - } else { - btnCreate.Disable() - btnCreate.Refresh() - } - return nil - } - - wAccount.OnChanged = func(s string) { - wAccount.Validate() - } - - wLanguage := widget.NewSelect(languages, nil) - wLanguage.OnChanged = func(s string) { - index := wLanguage.SelectedIndex() - session.Language = index - session.Window.Canvas().Focus(wAccount) - - if len(session.Password) > 0 && session.Password == session.PasswordConfirm && !findAccount() && session.Language != -1 { - btnCreate.Enable() - btnCreate.Refresh() - } else { - btnCreate.Disable() - btnCreate.Refresh() - } - } - wLanguage.PlaceHolder = "(Select Language)" - - wSpacer := widget.NewLabel(" ") - heading := canvas.NewText("New Account", colors.Green) - heading.TextSize = 22 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - heading2 := canvas.NewText("Recovery", colors.Green) - heading2.TextSize = 22 - heading2.Alignment = fyne.TextAlignCenter - heading2.TextStyle = fyne.TextStyle{Bold: true} - - rectStatus := canvas.NewRectangle(color.Transparent) - rectStatus.SetMinSize(fyne.NewSize(10, 10)) - - rectHeader := canvas.NewRectangle(color.Transparent) - rectHeader.SetMinSize(fyne.NewSize(ui.Width, 10)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(ui.Width, 5)) - - grid := container.NewVBox() - grid.Objects = nil - - header := container.NewVBox( - wSpacer, - heading, - rectSpacer, - rectSpacer, - ) - - form := container.NewVBox( - wLanguage, - rectSpacer, - wAccount, - wPassword, - wPasswordConfirm, - rectSpacer, - errorText, - rectSpacer, - btnCreate, - ) - - footer := container.NewVBox( - container.NewHBox( - layout.NewSpacer(), - linkCancel, - layout.NewSpacer(), - ), - wSpacer, - ) - - body := widget.NewLabel("Please save the following 25 recovery words in a safe place. These are the keys to your account, so never share them with anyone.") - body.Wrapping = fyne.TextWrapWord - body.Alignment = fyne.TextAlignCenter - body.TextStyle = fyne.TextStyle{Bold: true} - - formSuccess := container.NewVBox( - body, - wSpacer, - container.NewCenter(grid), - rectSpacer, - errorText, - rectSpacer, - btnCopyAddress, - btnCopySeed, - rectSpacer, - ) - - formSuccess.Hide() - - scrollBox := container.NewVScroll( - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - formSuccess, - form, - ), - layout.NewSpacer(), - ), - ) - scrollBox.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.70)) - - btnCreate.OnTapped = func() { - if findAccount() { - errorText.Text = "Account name already exists." - errorText.Color = colors.Red - errorText.Refresh() - return - } else { - errorText.Text = "" - errorText.Refresh() - } - - address, seed, err := create() - if err != nil { - errorText.Text = session.Error - errorText.Refresh() - return - } - - formatted := strings.Split(seed, " ") - - rect := canvas.NewRectangle(color.RGBA{21, 27, 36, 255}) - rect.SetMinSize(fyne.NewSize(ui.Width, 25)) - - for i := 0; i < len(formatted); i++ { - pos := fmt.Sprintf("%d", i+1) - word := strings.ReplaceAll(formatted[i], " ", "") - grid.Add(container.NewStack( - rect, - container.NewHBox( - widget.NewLabel(" "), - widget.NewLabelWithStyle(pos, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), - layout.NewSpacer(), - widget.NewLabelWithStyle(word, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), - widget.NewLabel(" "), - ), - ), - ) - } - - btnCopySeed.OnTapped = func() { - a.Clipboard().SetContent(seed) - } - - btnCopyAddress.OnTapped = func() { - a.Clipboard().SetContent(address) - } - - form.Hide() - form.Refresh() - formSuccess.Show() - formSuccess.Refresh() - grid.Refresh() - scrollBox.Refresh() - session.Window.Canvas().Content().Refresh() - session.Window.Canvas().Refresh(session.Window.Content()) - } - - layout := container.NewBorder( - container.NewVBox( - header, - scrollBox, - ), - footer, - nil, - nil, - ) - return NewVScroll(layout) -} - -func layoutRestore() fyne.CanvasObject { - resizeWindow(ui.MaxWidth, ui.MaxHeight) - a.Settings().SetTheme(themes.alt) - - session.Domain = "app.restore" - session.Language = -1 - session.Error = "" - session.Name = "" - session.Password = "" - session.PasswordConfirm = "" - - var seed [25]string - - scrollBox := container.NewVScroll(nil) - - errorText := canvas.NewText(" ", colors.Green) - errorText.TextSize = 12 - errorText.Alignment = fyne.TextAlignCenter - - btnCreate := widget.NewButton("Recover", nil) - btnCreate.Disable() - - linkReturn := widget.NewHyperlinkWithStyle("Return to Login", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkReturn.OnTapped = func() { - session.Domain = "app.main" - session.Error = "" - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMain()) - removeOverlays() - } - - btnCopyAddress := widget.NewButton("Copy Address", nil) - - wPassword := NewMobileEntry() - wPassword.OnFocusGained = func() { - offset := wPassword.Position().Y - if offset-scrollBox.Offset.Y > scrollBox.MinSize().Height { - scrollBox.Offset = fyne.NewPos(0, offset) - scrollBox.Refresh() - } - } - - wPassword.Password = true - wPassword.OnChanged = func(s string) { - session.Error = "" - errorText.Text = "" - errorText.Refresh() - session.Password = s - - if len(session.Password) > 0 && session.Password == session.PasswordConfirm && session.Name != "" { - - } else { - btnCreate.Disable() - btnCreate.Refresh() - } - } - wPassword.SetPlaceHolder("Password") - wPassword.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) - - wPasswordConfirm := NewMobileEntry() - wPasswordConfirm.OnFocusGained = func() { - offset := wPasswordConfirm.Position().Y - if offset-scrollBox.Offset.Y > scrollBox.MinSize().Height { - scrollBox.Offset = fyne.NewPos(0, offset) - scrollBox.Refresh() - } - } - - wPasswordConfirm.Password = true - wPasswordConfirm.OnChanged = func(s string) { - session.Error = "" - errorText.Text = "" - errorText.Refresh() - session.PasswordConfirm = s - - if len(session.Password) > 0 && session.Password == session.PasswordConfirm && session.Name != "" { - - } else { - btnCreate.Disable() - btnCreate.Refresh() - } - } - wPasswordConfirm.SetPlaceHolder("Confirm Password") - wPasswordConfirm.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) - - recoveryType := widget.NewSelect([]string{"Recovery Words", "Secret Hex Key", "Import File"}, nil) - recoveryType.PlaceHolder = "(Recovery Type)" - recoveryType.SetSelectedIndex(0) - recoveryType.OnChanged = func(s string) { - errorText.Text = "" - errorText.Refresh() - } - - wAccount := NewMobileEntry() - wAccount.OnFocusGained = func() { - scrollBox.Offset = fyne.NewPos(0, 0) - scrollBox.Refresh() - } - - wLanguage := widget.NewSelect(mnemonics.Language_List(), nil) - wLanguage.OnChanged = func(s string) { - index := wLanguage.SelectedIndex() - session.Language = index - session.Window.Canvas().Focus(wAccount) - errorText.Text = "" - errorText.Refresh() - } - wLanguage.PlaceHolder = "(Select Language)" - wLanguage.Hide() - - wAccount.SetPlaceHolder("Account Name") - wAccount.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) - wAccount.Validator = func(s string) (err error) { - session.Error = "" - errorText.Text = "" - errorText.Refresh() - - if len(s) > 25 { - err = errors.New("account name is too long") - wAccount.SetText(session.Name) - wAccount.Refresh() - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - err = checkDir() - if err != nil { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutAlert(2)) - return - } - - switch getNetwork() { - case NETWORK_TESTNET: - session.Path = filepath.Join(AppPath(), "testnet") + string(filepath.Separator) + s + ".db" - case NETWORK_SIMULATOR: - session.Path = filepath.Join(AppPath(), "testnet_simulator") + string(filepath.Separator) + s + ".db" - default: - session.Path = filepath.Join(AppPath(), "mainnet") + string(filepath.Separator) + s + ".db" - } - session.Name = s - - if findAccount() { - err = errors.New("account name already exists") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if len(session.Password) > 0 && session.Password == session.PasswordConfirm && session.Name != "" { - - } else { - btnCreate.Disable() - btnCreate.Refresh() - } - - if s != "" { - recoveryType.Disable() - } else { - recoveryType.Enable() - } - - return nil - } - - wSpacer := widget.NewLabel(" ") - heading := canvas.NewText("Recover Account", colors.Green) - heading.TextSize = 22 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - heading2 := canvas.NewText("Success", colors.Green) - heading2.TextSize = 22 - heading2.Alignment = fyne.TextAlignCenter - heading2.TextStyle = fyne.TextStyle{Bold: true} - - rectStatus := canvas.NewRectangle(color.Transparent) - rectStatus.SetMinSize(fyne.NewSize(10, 10)) - - rectHeader := canvas.NewRectangle(color.Transparent) - rectHeader.SetMinSize(fyne.NewSize(ui.Width, 10)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(ui.Width, 5)) - - status.Connection.FillColor = colors.Gray - status.Cyberdeck.FillColor = colors.Gray - status.Gnomon.FillColor = colors.Gray - status.Sync.FillColor = colors.Gray - - grid := container.NewVBox() - grid.Objects = nil - - word1 := NewMobileEntry() - word1.PlaceHolder = "Seed Word 1" - word1.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[0] = s - word1.Text = s - return nil - } - word1.OnFocusGained = func() { - offset := word1.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word2 := NewMobileEntry() - word2.PlaceHolder = "Seed Word 2" - word2.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[1] = s - word2.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word2.OnFocusGained = func() { - offset := word2.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word3 := NewMobileEntry() - word3.PlaceHolder = "Seed Word 3" - word3.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[2] = s - word3.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word3.OnFocusGained = func() { - offset := word3.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word4 := NewMobileEntry() - word4.PlaceHolder = "Seed Word 4" - word4.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[3] = s - word4.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word4.OnFocusGained = func() { - offset := word4.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word5 := NewMobileEntry() - word5.PlaceHolder = "Seed Word 5" - word5.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[4] = s - word5.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word5.OnFocusGained = func() { - offset := word5.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word6 := NewMobileEntry() - word6.PlaceHolder = "Seed Word 6" - word6.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[5] = s - word6.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word6.OnFocusGained = func() { - offset := word6.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word7 := NewMobileEntry() - word7.PlaceHolder = "Seed Word 7" - word7.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[6] = s - word7.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word7.OnFocusGained = func() { - offset := word7.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word8 := NewMobileEntry() - word8.PlaceHolder = "Seed Word 8" - word8.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[7] = s - word8.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word8.OnFocusGained = func() { - offset := word8.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word9 := NewMobileEntry() - word9.PlaceHolder = "Seed Word 9" - word9.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[8] = s - word9.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word9.OnFocusGained = func() { - offset := word9.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word10 := NewMobileEntry() - word10.PlaceHolder = "Seed Word 10" - word10.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[9] = s - word10.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word10.OnFocusGained = func() { - offset := word10.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word11 := NewMobileEntry() - word11.PlaceHolder = "Seed Word 11" - word11.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[10] = s - word11.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word11.OnFocusGained = func() { - offset := word11.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word12 := NewMobileEntry() - word12.PlaceHolder = "Seed Word 12" - word12.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[11] = s - word12.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word12.OnFocusGained = func() { - offset := word12.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word13 := NewMobileEntry() - word13.PlaceHolder = "Seed Word 13" - word13.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[12] = s - word13.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word13.OnFocusGained = func() { - offset := word13.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word14 := NewMobileEntry() - word14.PlaceHolder = "Seed Word 14" - word14.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[13] = s - word14.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word14.OnFocusGained = func() { - offset := word14.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word15 := NewMobileEntry() - word15.PlaceHolder = "Seed Word 15" - word15.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[14] = s - word15.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word15.OnFocusGained = func() { - offset := word15.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word16 := NewMobileEntry() - word16.PlaceHolder = "Seed Word 16" - word16.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[15] = s - word16.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word16.OnFocusGained = func() { - offset := word16.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word17 := NewMobileEntry() - word17.PlaceHolder = "Seed Word 17" - word17.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[16] = s - word17.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word17.OnFocusGained = func() { - offset := word17.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word18 := NewMobileEntry() - word18.PlaceHolder = "Seed Word 18" - word18.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[17] = s - word18.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word18.OnFocusGained = func() { - offset := word18.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word19 := NewMobileEntry() - word19.PlaceHolder = "Seed Word 19" - word19.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[18] = s - word19.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word19.OnFocusGained = func() { - offset := word19.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word20 := NewMobileEntry() - word20.PlaceHolder = "Seed Word 20" - word20.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[19] = s - word20.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word20.OnFocusGained = func() { - offset := word20.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word21 := NewMobileEntry() - word21.PlaceHolder = "Seed Word 21" - word21.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[20] = s - word21.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word21.OnFocusGained = func() { - offset := word21.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word22 := NewMobileEntry() - word22.PlaceHolder = "Seed Word 22" - word22.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[21] = s - word22.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word22.OnFocusGained = func() { - offset := word22.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word23 := NewMobileEntry() - word23.PlaceHolder = "Seed Word 23" - word23.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[22] = s - word23.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word23.OnFocusGained = func() { - offset := word23.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word24 := NewMobileEntry() - word24.PlaceHolder = "Seed Word 24" - word24.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[23] = s - word24.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word24.OnFocusGained = func() { - offset := word24.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - word25 := NewMobileEntry() - word25.PlaceHolder = "Seed Word 25" - word25.Validator = func(s string) error { - s = strings.TrimSpace(s) - if !checkSeedWord(s) { - btnCreate.Disable() - return errors.New("invalid seed word") - } - seed[24] = s - word25.Text = s - - var list []string - for s := range seed { - if seed[s] != "" { - list = append(list, seed[s]) - } - } - - if len(list) == 25 { - btnCreate.Enable() - } - - return nil - } - word25.OnFocusGained = func() { - offset := word25.Position().Y - scrollBox.Offset.Y = offset + 10 - scrollBox.Refresh() - } - - hexEntry := widget.NewEntry() - hexEntry.SetPlaceHolder("Secret Key (64 character hex)") - hexEntry.Validator = func(s string) (err error) { - _, err = hex.DecodeString(s) - if len(s) > 64 || err != nil { - err = errors.New("invalid hex key") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - btnCreate.Disable() - - return - } - - errorText.Text = "" - errorText.Refresh() - if s != "" { - btnCreate.Enable() - } - - return - } - - hexSpacer := canvas.NewRectangle(color.Transparent) - hexSpacer.SetMinSize(fyne.NewSize(ui.Width, 91)) - - hexForm := container.NewVBox( - rectSpacer, - hexEntry, - hexSpacer, - errorText, - ) - - wordsForm := container.NewVBox( - word1, - rectSpacer, - word2, - rectSpacer, - word3, - rectSpacer, - word4, - rectSpacer, - word5, - rectSpacer, - word6, - rectSpacer, - word7, - rectSpacer, - word8, - rectSpacer, - word9, - rectSpacer, - word10, - rectSpacer, - word11, - rectSpacer, - word12, - rectSpacer, - word13, - rectSpacer, - word14, - rectSpacer, - word15, - rectSpacer, - word16, - rectSpacer, - word17, - rectSpacer, - word18, - rectSpacer, - word19, - rectSpacer, - word20, - rectSpacer, - word21, - rectSpacer, - word22, - rectSpacer, - word23, - rectSpacer, - word24, - rectSpacer, - word25, - rectSpacer, - errorText, - rectSpacer, - ) - - // Create a new form for account/password inputs - recoveryForm := container.NewVBox( - wLanguage, - rectSpacer, - rectSpacer, - wAccount, - wPassword, - wPasswordConfirm, - rectSpacer, - rectSpacer, - wordsForm, - ) - - importFileText := canvas.NewText(" ", colors.Green) - importFileText.TextSize = 12 - importFileText.Alignment = fyne.TextAlignCenter - - importFileForm := container.NewVBox( - rectSpacer, - rectSpacer, - errorText, - rectSpacer, - rectSpacer, - importFileText, - rectSpacer, - rectSpacer, - ) - - form := container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - recoveryType, - rectSpacer, - recoveryForm, - ), - layout.NewSpacer(), - ) - - recoveryType.OnChanged = func(s string) { - errorText.Text = "" - errorText.Refresh() - - switch s { - case "Secret Hex Key": - wLanguage.Show() - form.Objects[1].(*fyne.Container).Objects[2] = recoveryForm - recoveryForm.Objects[8] = hexForm - case "Recovery Words": - wLanguage.Hide() - form.Objects[1].(*fyne.Container).Objects[2] = recoveryForm - recoveryForm.Objects[8] = wordsForm - case "Import File": - btnCreate.Disable() - importFileText.Text = "" - importFileText.Refresh() - form.Objects[1].(*fyne.Container).Objects[2] = importFileForm - dialogFileImport := dialog.NewFileOpen(func(uri fyne.URIReadCloser, err error) { - if err != nil { - logger.Errorf("[Engram] File dialog: %s\n", err) - errorText.Text = "could not import wallet file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if uri == nil { - return // Canceled - } - - fileName := uri.URI().String() - if uri.URI().MimeType() != "text/plain" { - logger.Errorf("[Engram] Cannot import file %s\n", fileName) - errorText.Text = "cannot import file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if a.Driver().Device().IsMobile() { - fileName = uri.URI().Name() - } else { - fileName = filepath.Base(strings.Replace(fileName, "file://", "", -1)) - } - - if !strings.HasSuffix(fileName, ".db") { - logger.Errorf("[Engram] Engram requires .db wallet file\n") - errorText.Text = "invalid wallet file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - filedata, err := readFromURI(uri) - if err != nil { - logger.Errorf("[Engram] Cannot read URI file data for %s: %s\n", fileName, err) - errorText.Text = "cannot read file data" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - filePath := "" - switch session.Network { - case NETWORK_TESTNET: - filePath = filepath.Join(AppPath(), "testnet", fileName) - case NETWORK_SIMULATOR: - filePath = filepath.Join(AppPath(), "testnet_simulator", fileName) - default: - filePath = filepath.Join(AppPath(), "mainnet", fileName) - } - - if _, err = os.Stat(filePath); !os.IsNotExist(err) { - logger.Errorf("[Engram] Wallet file %q already exists\n", fileName) - errorText.Text = "wallet file already exists" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - err = os.WriteFile(filePath, filedata, 0600) - if err != nil { - logger.Errorf("[Engram] Importing file %s: %s\n", fileName, err) - errorText.Text = "error importing wallet file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - errorText.Text = fmt.Sprintf("%s wallet file imported successfully", strings.ToLower(session.Network)) - errorText.Color = colors.Green - errorText.Refresh() - - if len(fileName) > 50 { - fileName = fileName[0:50] + "..." - } - - importFileText.Text = fileName - importFileText.Color = colors.Green - importFileText.Refresh() - - }, session.Window) - - if !a.Driver().Device().IsMobile() { - // Open file browser in current directory - uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) - if err == nil { - dialogFileImport.SetLocation(uri) - } else { - logger.Errorf("[Engram] Could not open current directory %s\n", err) - } - } - - dialogFileImport.SetFilter(storage.NewExtensionFileFilter([]string{".db"})) - dialogFileImport.SetView(dialog.ListView) - dialogFileImport.Resize(fyne.NewSize(ui.Width, ui.Height)) - dialogFileImport.Show() - } - } - - body := widget.NewLabel("Your account has been successfully recovered. ") - body.Wrapping = fyne.TextWrapWord - body.Alignment = fyne.TextAlignCenter - body.TextStyle = fyne.TextStyle{Bold: true} - - formSuccess := container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - rectSpacer, - rectSpacer, - heading2, - rectSpacer, - body, - rectSpacer, - rectSpacer, - container.NewCenter(grid), - rectSpacer, - rectSpacer, - btnCopyAddress, - rectSpacer, - ), - layout.NewSpacer(), - ) - - formSuccess.Hide() - - scrollBox = container.NewVScroll( - container.NewStack( - rectHeader, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - form, - formSuccess, - ), - layout.NewSpacer(), - ), - ), - ) - - scrollBox.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.65)) - - btnCreate.OnTapped = func() { - if engram.Disk != nil { - closeWallet() - } - - var err error - - if findAccount() { - err = errors.New("account name already exists") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } else { - errorText.Text = "" - errorText.Refresh() - } - - getNetwork() - - var language string - var temp *walletapi.Wallet_Disk - - if recoveryType.SelectedIndex() == 1 { - if wAccount.Text == "" { - err = errors.New("enter account name") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if wLanguage.SelectedIndex() < 0 { - err = errors.New("select seed language") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - go func() { - wLanguage.FocusGained() - time.Sleep(time.Second) - wLanguage.FocusLost() - }() - return - } - - if wPassword.Text == "" { - err = errors.New("enter and confirm a password") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if session.Password != session.PasswordConfirm { - err = errors.New("passwords do not match") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if hexEntry.Text == "" { - err = errors.New("enter a valid hex key") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if len(hexEntry.Text) > 64 { - err = errors.New("key must be less than 65 chars") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - hexKey, err := hex.DecodeString(hexEntry.Text) - if err != nil { - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - temp, err = walletapi.Create_Encrypted_Wallet(session.Path, session.Password, new(crypto.BNRed).SetBytes(hexKey)) - if err != nil { - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - language = wLanguage.Selected - - } else { - if wAccount.Text == "" { - err = errors.New("enter account name") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if wPassword.Text == "" { - err = errors.New("enter and confirm a password") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if session.Password != session.PasswordConfirm { - err = errors.New("passwords do not match") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - var words string - - for i := 0; i < 25; i++ { - words += seed[i] + " " - } - - language, _, err = mnemonics.Words_To_Key(words) - if err != nil { - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - temp, err = walletapi.Create_Encrypted_Wallet_From_Recovery_Words(session.Path, session.Password, words) - if err != nil { - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - } - - engram.Disk = temp - - if session.Network == NETWORK_MAINNET { - engram.Disk.SetNetwork(true) - } else { - engram.Disk.SetNetwork(false) - } - - engram.Disk.SetSeedLanguage(language) - - address := engram.Disk.GetAddress().String() - - btnCopyAddress.OnTapped = func() { - a.Clipboard().SetContent(address) - } - - engram.Disk.Get_Balance_Rescan() - engram.Disk.Save_Wallet() - engram.Disk.Close_Encrypted_Wallet() - - session.WalletOpen = false - engram.Disk = nil - session.Path = "" - session.Name = "" - tx = Transfers{} - - btnCreate.Hide() - form.Hide() - form.Refresh() - formSuccess.Show() - formSuccess.Refresh() - grid.Refresh() - scrollBox.Refresh() - session.Window.Canvas().Content().Refresh() - session.Window.Canvas().Refresh(session.Window.Content()) - } - - header := container.NewVBox( - rectSpacer, - rectSpacer, - heading, - rectSpacer, - rectSpacer, - ) - - rect1 := canvas.NewRectangle(color.Transparent) - rect1.SetMinSize(fyne.NewSize(ui.Width, 1)) - - footer := container.NewCenter( - rect1, - container.NewVBox( - btnCreate, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkReturn, - layout.NewSpacer(), - ), - wSpacer, - ), - ) - - layout := container.NewBorder( - container.NewVBox( - header, - scrollBox, - rectSpacer, - ), - footer, - nil, - nil, - ) - return NewVScroll(layout) -} - -func layoutAssetExplorer() fyne.CanvasObject { - session.Domain = "app.explorer" - - var data []string - var listData binding.StringList - var listBox *widget.List - - frame := &iframe{} - rectLeft := canvas.NewRectangle(color.Transparent) - rectLeft.SetMinSize(fyne.NewSize(ui.Width*0.40, 35)) - rectRight := canvas.NewRectangle(color.Transparent) - rectRight.SetMinSize(fyne.NewSize(ui.Width*0.58, 35)) - rectList := canvas.NewRectangle(color.Transparent) - rectList.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.45)) - rectWidth := canvas.NewRectangle(color.Transparent) - rectWidth.SetMinSize(fyne.NewSize(ui.Width, 10)) - - heading := canvas.NewText("A S S E T E X P L O R E R", colors.Gray) - heading.TextSize = 16 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - results := canvas.NewText("", colors.Green) - results.TextSize = 14 - - listData = binding.BindStringList(&data) - listBox = widget.NewListWithData(listData, - func() fyne.CanvasObject { - return container.NewStack( - container.NewHBox( - container.NewStack( - rectLeft, - widget.NewLabel(""), - ), - container.NewStack( - rectRight, - widget.NewLabel(""), - ), - ), - ) - }, - func(di binding.DataItem, co fyne.CanvasObject) { - dat := di.(binding.String) - str, err := dat.Get() - if err != nil { - return - } - - split := strings.Split(str, ";;;") - - co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[0]) - co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[1]) - //co.(*fyne.Container).Objects[3].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[3]) - }) - - menu := widget.NewSelect([]string{"My Assets", "Search By SCID"}, nil) - menu.PlaceHolder = "(Select One)" - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - entrySCID := widget.NewEntry() - entrySCID.PlaceHolder = "Search by SCID" - entrySCID.Disable() - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - btnSearch := widget.NewButton("Search", nil) - btnSearch.OnTapped = func() { - - } - - linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } - - linkClearHistory := widget.NewHyperlinkWithStyle("Clear All", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: false}) - linkClearHistory.OnTapped = func() { - shard, err := GetShard() - if err != nil { - return - } - - store, err := graviton.NewDiskStore(shard) - if err != nil { - return - } - - ss, err := store.LoadSnapshot(0) - - if err != nil { - return - } - - tree, err := ss.GetTree("Explorer History") - if err != nil { - return - } - - c := tree.Cursor() - - for k, _, err := c.First(); err == nil; k, _, err = c.Next() { - DeleteKey(tree.GetName(), k) - } - - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutAssetExplorer()) - } - - btnMyAssets := widget.NewButton("My Assets", nil) - btnMyAssets.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMyAssets()) - } - - layoutExplorer := container.NewStack( - rectWidth, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - rectSpacer, - container.NewHBox( - results, - layout.NewSpacer(), - linkClearHistory, - ), - rectSpacer, - rectSpacer, - entrySCID, - rectSpacer, - rectSpacer, - container.NewStack( - rectList, - listBox, - ), - rectSpacer, - rectSpacer, - btnMyAssets, - ), - layout.NewSpacer(), - ), - ) - - listing := layoutExplorer - - var assetData []string - - found := 0 - assetData = nil - - results.Text = fmt.Sprintf(" Results: %d", found) - results.Color = colors.Green - results.Refresh() - - listData.Set(nil) - - if session.Offline { - results.Text = " Disabled in offline mode." - results.Color = colors.Gray - results.Refresh() - } else if gnomon.Index == nil { - results.Text = " Gnomon is inactive." - results.Color = colors.Gray - results.Refresh() - } - - entrySCID.OnChanged = func(s string) { - if entrySCID.Text != "" && len(s) == 64 { - showLoadingOverlay() - - var result []*structures.SCIDVariable - switch gnomon.Index.DBType { - case "gravdb": - result = gnomon.Index.GravDBBackend.GetSCIDVariableDetailsAtTopoheight(s, engram.Disk.Get_Daemon_TopoHeight()) - case "boltdb": - result = gnomon.Index.BBSBackend.GetSCIDVariableDetailsAtTopoheight(s, engram.Disk.Get_Daemon_TopoHeight()) - } - - if len(result) == 0 { - _, err := getTxData(s) - if err != nil { - return - } - } - - err := StoreEncryptedValue("Explorer History", []byte(s), []byte("")) - if err != nil { - logger.Errorf("[Asset Explorer] Error saving search result: %s\n", err) - return - } - - scid := crypto.HashHexToHash(s) - - bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(scid, -1, engram.Disk.GetAddress().String()) - if err != nil { - bal = 0 - } - - title, desc, _, _, _ := getContractHeader(scid) - - if title == "" { - title = scid.String() - } - - if len(title) > 18 { - title = title[0:18] + "..." - } - - if desc == "" { - desc = "N/A" - } - - if len(desc) > 40 { - desc = desc[0:40] + "..." - } - - assetData = append(data, globals.FormatMoney(bal)+";;;"+title+";;;"+desc+";;;;;;"+scid.String()) - listData.Set(assetData) - found += 1 - - /* - overlay := session.Window.Canvas().Overlays() - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - overlay.Add( - container.NewStack( - &iframe{}, - layoutAssetManager(s), - ), - ) - overlay.Top().Show() - - entrySCID.Text = "" - entrySCID.Refresh() - - results.Text = fmt.Sprintf(" Results: %d", found) - results.Color = colors.Green - results.Refresh() - */ - - entrySCID.SetText("") - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutAssetManager(s)) - removeOverlays() - } - } - - go func() { - if engram.Disk != nil && gnomon.Index != nil { - for gnomon.Index.LastIndexedHeight < int64(engram.Disk.Get_Daemon_Height()) { - if session.Domain != "app.explorer" { - break - } - entrySCID.Disable() - results.Text = " Gnomon is syncing..." - results.Color = colors.Yellow - - fyne.Do(func() { - results.Refresh() - }) - - time.Sleep(time.Second * 1) - } - - fyne.Do(func() { - entrySCID.Enable() - results.Text = " Loading previous scan history..." - results.Color = colors.Yellow - results.Refresh() - }) - - shard, err := GetShard() - if err != nil { - return - } - - store, err := graviton.NewDiskStore(shard) - if err != nil { - return - } - - ss, err := store.LoadSnapshot(0) - - if err != nil { - return - } - - tree, err := ss.GetTree("Explorer History") - if err != nil { - return - } - - c := tree.Cursor() - - for k, _, err := c.First(); err == nil; k, _, err = c.Next() { - scid := crypto.HashHexToHash(string(k)) - - bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(scid, -1, engram.Disk.GetAddress().String()) - if err != nil { - bal = 0 - } - - title, desc, _, _, _ := getContractHeader(scid) - - if title == "" { - title = scid.String() - } - - if len(title) > 18 { - title = title[0:18] + "..." - } - - if desc == "" { - desc = "N/A" - } - - if len(desc) > 40 { - desc = desc[0:40] + "..." - } - - assetData = append(data, globals.FormatMoney(bal)+";;;"+title+";;;"+desc+";;;;;;"+scid.String()) - listData.Set(assetData) - found += 1 - } - } - - listData.Set(assetData) - - listBox.OnSelected = func(id widget.ListItemID) { - split := strings.Split(assetData[id], ";;;") - /* - overlay := session.Window.Canvas().Overlays() - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - overlay.Add( - container.NewStack( - &iframe{}, - layoutAssetManager(split[4]), - ), - ) - overlay.Top().Show() - listBox.UnselectAll() - */ - - listBox.UnselectAll() - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutAssetManager(split[4])) - } - - fyne.Do(func() { - results.Text = fmt.Sprintf(" Search History: %d", found) - results.Color = colors.Green - results.Refresh() - listBox.Refresh() - }) - }() - - top := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - heading, - ), - rectSpacer, - rectSpacer, - container.NewCenter( - listing, - ), - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewStack( - frame, - container.NewBorder( - top, - bottom, - nil, - nil, - ), - ) - - return NewVScroll(layout) -} - -func layoutMyAssets() fyne.CanvasObject { - var data []string - var listData binding.StringList - var listBox *widget.List - - frame := &iframe{} - rectLeft := canvas.NewRectangle(color.Transparent) - rectLeft.SetMinSize(fyne.NewSize(ui.Width*0.40, 35)) - rectRight := canvas.NewRectangle(color.Transparent) - rectRight.SetMinSize(fyne.NewSize(ui.Width*0.59, 35)) - rectList := canvas.NewRectangle(color.Transparent) - rectList.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.56)) - rectWidth := canvas.NewRectangle(color.Transparent) - rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth, 10)) - - heading := canvas.NewText("M Y A S S E T S", colors.Gray) - heading.TextSize = 16 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - results := canvas.NewText("", colors.Green) - results.TextSize = 13 - - labelLastScan := canvas.NewText("", colors.Green) - labelLastScan.TextSize = 13 - - listData = binding.BindStringList(&data) - listBox = widget.NewListWithData(listData, - func() fyne.CanvasObject { - return container.NewStack( - container.NewHBox( - container.NewStack( - rectLeft, - widget.NewLabel(""), - ), - container.NewStack( - rectRight, - widget.NewLabel(""), - ), - ), - ) - }, - func(di binding.DataItem, co fyne.CanvasObject) { - dat := di.(binding.String) - str, err := dat.Get() - if err != nil { - return - } - - split := strings.Split(str, ";;;") - - co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[0]) - co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[1]) - //co.(*fyne.Container).Objects[3].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[3]) - }) - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - entrySCID := widget.NewEntry() - entrySCID.PlaceHolder = "Search by SCID" - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to Asset Explorer", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutAssetExplorer()) - removeOverlays() - } - - btnRescan := widget.NewButton("Rescan Blockchain", nil) - btnRescan.Disable() - - layoutAssets := container.NewStack( - rectWidth, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - rectSpacer, - container.NewHBox( - results, - layout.NewSpacer(), - labelLastScan, - ), - rectSpacer, - rectSpacer, - container.NewStack( - rectList, - listBox, - ), - rectSpacer, - rectSpacer, - btnRescan, - ), - layout.NewSpacer(), - ), - ) - - listing := layoutAssets - - var assetData []string - assetCount := 0 - assetTotal := 0 - owned := 0 - - owned = 0 - assetData = nil - listData.Set(nil) - - if session.Offline { - results.Text = " Asset tracking is disabled in offline mode." - results.Color = colors.Gray - results.Refresh() - } else if gnomon.Index == nil { - results.Text = " Asset tracking is disabled. Gnomon is inactive." - results.Color = colors.Gray - results.Refresh() - } - - go func() { - if engram.Disk != nil && gnomon.Index != nil { - if gnomon.Index.LastIndexedHeight < int64(engram.Disk.Get_Daemon_Height()) { - fyne.Do(func() { - btnRescan.Disable() - }) - } else { - fyne.Do(func() { - btnRescan.Enable() - }) - } - - results.Text = " Gathering an index of smart contracts... " - results.Color = colors.Yellow - fyne.Do(func() { - results.Refresh() - }) - - for gnomon.Index.LastIndexedHeight < int64(engram.Disk.Get_Daemon_Height()) { - results.Text = fmt.Sprintf(" Gnomon is syncing... [%d / %d]", gnomon.Index.LastIndexedHeight, int64(engram.Disk.Get_Daemon_Height())) - results.Color = colors.Yellow - - fyne.Do(func() { - results.Refresh() - }) - - time.Sleep(time.Second * 1) - } - - results.Text = " Loading previous scan results..." - results.Color = colors.Yellow - - fyne.Do(func() { - results.Refresh() - }) - - var assetList map[string]string - var zerobal uint64 - - shard, err := GetShard() - if err != nil { - return - } - - store, err := graviton.NewDiskStore(shard) - if err != nil { - return - } - - ss, err := store.LoadSnapshot(0) - - if err != nil { - return - } - - tree, err := ss.GetTree("My Assets") - if err != nil { - return - } - - c := tree.Cursor() - - for k, _, err := c.First(); err == nil; k, _, err = c.Next() { - scid := string(k) - - hash := crypto.HashHexToHash(scid) - - bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(hash, -1, engram.Disk.GetAddress().String()) - if err != nil { - return - } else { - title, desc, _, _, _ := getContractHeader(hash) - - if title == "" { - title = scid - } - - if len(title) > 18 { - title = title[0:18] + "..." - } - - if desc == "" { - desc = "N/A" - } - - if len(desc) > 40 { - desc = desc[0:40] + "..." - } - - balance := globals.FormatMoney(bal) - assetData = append(data, balance+";;;"+title+";;;"+desc+";;;;;;"+scid) - listData.Set(assetData) - owned += 1 - } - } - - rescan := func() { - fyne.Do(func() { - btnRescan.Disable() - }) - assetTotal = 0 - assetCount = 0 - - t := time.Now() - timeNow := string(t.Format(time.RFC822)) - StoreEncryptedValue("Asset Scan", []byte("Last Scan"), []byte(timeNow)) - - results.Text = " Indexing..." - results.Color = colors.Yellow - - fyne.Do(func() { - results.Refresh() - }) - - owned = 0 - - assetData = []string{} - listBox.UnselectAll() - listData.Set(assetData) - - if gnomon.Index != nil { - switch gnomon.Index.DBType { - case "gravdb": - assetList = gnomon.Index.GravDBBackend.GetAllOwnersAndSCIDs() - case "boltdb": - assetList = gnomon.Index.BBSBackend.GetAllOwnersAndSCIDs() - } - - for len(assetList) < 5 { - logger.Printf("[Gnomon] Asset Scan Status: [%d / %d / %d]\n", gnomon.Index.LastIndexedHeight, engram.Disk.Get_Daemon_Height(), len(assetList)) - results.Color = colors.Yellow - switch gnomon.Index.DBType { - case "gravdb": - assetList = gnomon.Index.GravDBBackend.GetAllOwnersAndSCIDs() - case "boltdb": - assetList = gnomon.Index.BBSBackend.GetAllOwnersAndSCIDs() - } - time.Sleep(time.Second * 5) - } - } - - results.Text = " Scanning results..." - results.Color = colors.Yellow - - fyne.Do(func() { - results.Refresh() - }) - - if gnomon.Index != nil { - switch gnomon.Index.DBType { - case "gravdb": - assetList = gnomon.Index.GravDBBackend.GetAllOwnersAndSCIDs() - case "boltdb": - assetList = gnomon.Index.BBSBackend.GetAllOwnersAndSCIDs() - } - } - - contracts := []crypto.Hash{} - - for sc := range assetList { - scid := crypto.HashHexToHash(sc) - - if !scid.IsZero() { - assetCount += 1 - contracts = append(contracts, scid) - } - } - - wg := sync.WaitGroup{} - maxWorkers := 50 - lastJob := 0 - - parse: - - if lastJob+maxWorkers > len(contracts) { - maxWorkers = assetCount - lastJob - } - - wg.Add(maxWorkers) - - // Parse each smart contract ID and check for a balance - for i := 0; i < maxWorkers; i++ { - index := lastJob - go func(i int) { - defer wg.Done() - - scid := contracts[index] - - desc := "" - title := "" - - assetTotal += 1 - - results.Text = " Scanning... " + fmt.Sprintf("%d / %d", assetTotal, assetCount) - results.Color = colors.Yellow - - fyne.Do(func() { - results.Refresh() - }) - - bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(scid, -1, engram.Disk.GetAddress().String()) - if err != nil { - return - } else { - balance := globals.FormatMoney(bal) - - if bal != zerobal { - err = StoreEncryptedValue("My Assets", []byte(scid.String()), []byte(balance)) - if err != nil { - logger.Errorf("[History] Failed to store asset: %s\n", err) - } - - title, desc, _, _, _ = getContractHeader(scid) - - if title == "" { - title = scid.String() - } - - if len(title) > 20 { - title = title[0:20] + "..." - } - - if desc == "" { - desc = "N/A" - } - - if len(desc) > 40 { - desc = desc[0:40] + "..." - } - - owned += 1 - assetData = append(assetData, balance+";;;"+title+";;;"+desc+";;;;;;"+scid.String()) - listData.Set(assetData) - logger.Printf("[Assets] Found asset: %s\n", scid.String()) - } - } - }(i) - - lastJob += 1 - } - - wg.Wait() - - if lastJob < len(contracts) { - goto parse - } - - results.Text = fmt.Sprintf(" Owned Assets: %d", owned) - results.Color = colors.Green - - labelLastScan.Text = fmt.Sprintf(" %s", timeNow) - labelLastScan.Color = colors.Green - - fyne.Do(func() { - listData.Set(assetData) - btnRescan.Enable() - - results.Refresh() - labelLastScan.Refresh() - }) - } - - btnRescan.OnTapped = func() { - go rescan() - } - - lastScan, _ := GetEncryptedValue("Asset Scan", []byte("Last Scan")) - - if len(assetData) == 0 && len(lastScan) == 0 { - rescan() - } - - if len(lastScan) > 0 { - results.Text = fmt.Sprintf(" Owned Assets: %d", owned) - labelLastScan.Text = fmt.Sprintf(" %s", lastScan) - } else { - results.Text = fmt.Sprintf(" Owned Assets: %d", owned) - labelLastScan.Text = "" - } - - results.Color = colors.Green - - fyne.Do(func() { - results.Refresh() - labelLastScan.Refresh() - listData.Set(assetData) - }) - - listBox.OnSelected = func(id widget.ListItemID) { - split := strings.Split(assetData[id], ";;;") - - /* - overlay := session.Window.Canvas().Overlays() - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - overlay.Add( - container.NewStack( - &iframe{}, - layoutAssetManager(split[4]), - ), - ) - overlay.Top().Show() - listBox.UnselectAll() - */ - - fyne.Do(func() { - listBox.UnselectAll() - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutAssetManager(split[4])) - }) - } - - fyne.Do(func() { - listBox.Refresh() - btnRescan.Enable() - }) - } - }() - - top := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - heading, - ), - rectSpacer, - rectSpacer, - container.NewCenter( - listing, - ), - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewStack( - frame, - container.NewBorder( - top, - bottom, - nil, - nil, - ), - ) - - return NewVScroll(layout) -} - -func layoutAssetManager(scid string) fyne.CanvasObject { - captureDomain := session.Domain - session.Domain = "app.manager" - - wSpacer := widget.NewLabel(" ") - - frame := &iframe{} - - rectBox := canvas.NewRectangle(color.Transparent) - rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.58)) - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) - - heading := canvas.NewText("Asset Manager", colors.Green) - heading.TextSize = 22 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - labelSigner := canvas.NewText(" SMART CONTRACT AUTHOR", colors.Gray) - labelSigner.TextSize = 14 - labelSigner.Alignment = fyne.TextAlignLeading - labelSigner.TextStyle = fyne.TextStyle{Bold: true} - - labelOwner := canvas.NewText(" SMART CONTRACT OWNER", colors.Gray) - labelOwner.TextSize = 14 - labelOwner.Alignment = fyne.TextAlignLeading - labelOwner.TextStyle = fyne.TextStyle{Bold: true} - - labelSCID := canvas.NewText(" SMART CONTRACT ID", colors.Gray) - labelSCID.TextSize = 14 - labelSCID.Alignment = fyne.TextAlignLeading - labelSCID.TextStyle = fyne.TextStyle{Bold: true} - - labelBalance := canvas.NewText(" ASSET BALANCE", colors.Gray) - labelBalance.TextSize = 14 - labelBalance.Alignment = fyne.TextAlignLeading - labelBalance.TextStyle = fyne.TextStyle{Bold: true} - - labelTransfer := canvas.NewText(" TRANSFER ASSET", colors.Gray) - labelTransfer.TextSize = 14 - labelTransfer.Alignment = fyne.TextAlignLeading - labelTransfer.TextStyle = fyne.TextStyle{Bold: true} - - labelExecute := canvas.NewText(" EXECUTE ACTION", colors.Gray) - labelExecute.TextSize = 14 - labelExecute.Alignment = fyne.TextAlignLeading - labelExecute.TextStyle = fyne.TextStyle{Bold: true} - - var ringsize uint64 - var err error - - options := []string{"Anonymity Set: 2 (None)", "Anonymity Set: 4 (Low)", "Anonymity Set: 8 (Low)", "Anonymity Set: 16 (Recommended)", "Anonymity Set: 32 (Medium)", "Anonymity Set: 64 (High)", "Anonymity Set: 128 (High)"} - - selectRingSize := widget.NewSelect(options, nil) - selectRingSize.OnChanged = func(s string) { - regex := regexp.MustCompile("[0-9]+") - result := regex.FindAllString(selectRingSize.Selected, -1) - ringsize, err = strconv.ParseUint(result[0], 10, 64) - if err != nil { - ringsize = 2 - } - } - - selectRingSize.SetSelectedIndex(3) - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - entryAddress := widget.NewEntry() - entryAddress.PlaceHolder = "Username or Address" - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - sc := widget.NewLabel(scid) - sc.Wrapping = fyne.TextWrap(fyne.TextWrapWord) - - hash := crypto.HashHexToHash(scid) - name, desc, icon, owner, code := getContractHeader(hash) - - image := canvas.NewImageFromResource(resourceBlankPng) - image.SetMinSize(fyne.NewSize(ui.Width*0.3, ui.Width*0.3)) - image.FillMode = canvas.ImageFillContain - - if icon != "" { - if img, err := handleImageURL(name, icon, fyne.NewSize(ui.Width*0.3, ui.Width*0.3)); err == nil { - image = img - } else { - logger.Errorf("[Engram] Could not validate icon image: %s\n", err) - } - } - - if owner == "" { - owner = "--" - } - - signer := "--" - - result, err := getTxData(scid) - if err != nil { - signer = "--" - } else { - signer = result.Txs[0].Signer - } - - labelSeparator := widget.NewRichTextFromMarkdown("") - labelSeparator.Wrapping = fyne.TextWrapOff - labelSeparator.ParseMarkdown("---") - - labelSeparator2 := widget.NewRichTextFromMarkdown("") - labelSeparator2.Wrapping = fyne.TextWrapOff - labelSeparator2.ParseMarkdown("---") - - labelSeparator3 := widget.NewRichTextFromMarkdown("") - labelSeparator3.Wrapping = fyne.TextWrapOff - labelSeparator3.ParseMarkdown("---") - - labelSeparator4 := widget.NewRichTextFromMarkdown("") - labelSeparator4.Wrapping = fyne.TextWrapOff - labelSeparator4.ParseMarkdown("---") - - labelSeparator5 := widget.NewRichTextFromMarkdown("") - labelSeparator5.Wrapping = fyne.TextWrapOff - labelSeparator5.ParseMarkdown("---") - - labelSeparator6 := widget.NewRichTextFromMarkdown("") - labelSeparator6.Wrapping = fyne.TextWrapOff - labelSeparator6.ParseMarkdown("---") - - if name == "" { - name = "--" - } - - labelName := widget.NewRichText(&widget.TextSegment{ - Text: name, - Style: widget.RichTextStyle{ - Alignment: fyne.TextAlignCenter, - ColorName: theme.ColorNameForeground, - SizeName: theme.SizeNameHeadingText, - TextStyle: fyne.TextStyle{Bold: true}, - }}) - labelName.Wrapping = fyne.TextWrapWord - - labelDesc := widget.NewRichText(&widget.TextSegment{ - Text: desc, - Style: widget.RichTextStyle{ - Alignment: fyne.TextAlignCenter, - ColorName: theme.ColorNameForeground, - TextStyle: fyne.TextStyle{Bold: false}, - }}) - labelDesc.Wrapping = fyne.TextWrapWord - - textSigner := widget.NewRichTextFromMarkdown(owner) - textSigner.Wrapping = fyne.TextWrapWord - textSigner.ParseMarkdown(signer) - - textOwner := widget.NewRichTextFromMarkdown(owner) - textOwner.Wrapping = fyne.TextWrapWord - textOwner.ParseMarkdown(owner) - - btnSend := widget.NewButton("Send Asset", nil) - - entryAddress.Validator = func(s string) error { - btnSend.Text = "Send Asset" - btnSend.Refresh() - _, err := globals.ParseValidateAddress(s) - if err != nil { - go func() { - exists, err := checkUsername(s, -1) - if err != nil && exists == "" { - fyne.Do(func() { - btnSend.Disable() - entryAddress.SetValidationError(errors.New("invalid username or address")) - }) - } else { - fyne.Do(func() { - entryAddress.SetValidationError(nil) - btnSend.Enable() - }) - } - }() - } else { - entryAddress.SetValidationError(nil) - btnSend.Enable() - } - return nil - } - - entryAmount := widget.NewEntry() - entryAmount.PlaceHolder = "Asset Amount (Numbers Only)" - entryAmount.Validator = func(s string) error { - if s != "" { - amount, err := globals.ParseAmount(s) - if err != nil { - btnSend.Disable() - entryAmount.SetValidationError(errors.New("invalid amount entered")) - return err - } else { - bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(hash, -1, engram.Disk.GetAddress().String()) - if err != nil { - btnSend.Disable() - entryAmount.SetValidationError(errors.New("error parsing asset balance")) - return err - } else { - if amount > bal || amount == 0 { - err = errors.New("insufficient asset balance") - btnSend.Text = "Insufficient transfer amount..." - btnSend.Disable() - entryAmount.SetValidationError(err) - return err - } - } - } - } - - btnSend.Text = "Send Asset" - btnSend.Enable() - entryAmount.SetValidationError(nil) - - return nil - } - - var zerobal uint64 - - balance := canvas.NewText(fmt.Sprintf(" %d", zerobal), colors.Green) - balance.TextSize = 20 - balance.TextStyle = fyne.TextStyle{Bold: true} - - btnSend.OnTapped = func() { - btnSend.Text = "Setting up transfer..." - btnSend.Disable() - btnSend.Refresh() - entryAddress.Disable() - entryAmount.Disable() - selectRingSize.Disable() - - txid, err := transferAsset(hash, ringsize, entryAddress.Text, entryAmount.Text) - if err != nil { - entryAddress.Text = "" - entryAddress.Refresh() - entryAmount.Text = "" - entryAmount.Refresh() - btnSend.Text = "Transaction Failed..." - btnSend.Disable() - btnSend.Refresh() - } else { - entryAddress.Text = "" - entryAddress.Refresh() - entryAmount.Text = "" - entryAmount.Refresh() - btnSend.Text = "Confirming..." - btnSend.Disable() - btnSend.Refresh() - - go func() { - walletapi.WaitNewHeightBlock() - sHeight := walletapi.Get_Daemon_Height() - - for session.Domain == "app.manager" { - var zeroscid crypto.Hash - _, result := engram.Disk.Get_Payments_TXID(zeroscid, txid.String()) - - if result.TXID != txid.String() { - time.Sleep(time.Second * 1) - } else { - break - } - } - - // If we go DEFAULT_CONFIRMATION_TIMEOUT blocks without exiting 'Confirming...' loop, display failed to transfer and break - if walletapi.Get_Daemon_Height() > sHeight+int64(DEFAULT_CONFIRMATION_TIMEOUT) { - fyne.Do(func() { - entryAddress.Text = "" - entryAddress.Refresh() - entryAmount.Text = "" - entryAmount.Refresh() - btnSend.Text = "Transaction Failed..." - btnSend.Disable() - btnSend.Refresh() - }) - - return - } - - // If daemon height has incremented, print retry counters into button space - if walletapi.Get_Daemon_Height()-sHeight > 0 { - btnSend.Text = fmt.Sprintf("Confirming... (%d/%d)", walletapi.Get_Daemon_Height()-sHeight, DEFAULT_CONFIRMATION_TIMEOUT) - fyne.Do(func() { - btnSend.Refresh() - }) - } - - bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(hash, -1, engram.Disk.GetAddress().String()) - if err == nil { - err = StoreEncryptedValue("My Assets", []byte(hash.String()), []byte(globals.FormatMoney(bal))) - if err != nil { - logger.Errorf("[Asset] Error storing new asset balance for: %s\n", hash) - } - balance.Text = " " + globals.FormatMoney(bal) - - fyne.Do(func() { - balance.Refresh() - }) - } - - if bal != zerobal { - fyne.Do(func() { - btnSend.Text = "Send Asset" - btnSend.Enable() - btnSend.Refresh() - entryAddress.Text = "" - entryAddress.Enable() - entryAddress.Refresh() - entryAmount.Text = "" - entryAmount.Enable() - entryAmount.Refresh() - selectRingSize.Enable() - }) - } else { - fyne.Do(func() { - btnSend.Text = "You do not own this asset" - btnSend.Disable() - btnSend.Refresh() - }) - } - }() - } - } - - bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(hash, -1, engram.Disk.GetAddress().String()) - if err == nil { - balance.Text = " " + globals.FormatMoney(bal) - balance.Refresh() - - if bal == zerobal { - entryAddress.Disable() - entryAmount.Disable() - selectRingSize.Disable() - btnSend.Text = "You do not own this asset" - btnSend.Disable() - } - } - - if captureDomain == "app.manager" { // was already on manager and opened it again so go back option is to explorer - captureDomain = "app.explorer" - } - - linkBack := widget.NewHyperlinkWithStyle(fmt.Sprintf("Back to %s", sessionDomainToString(captureDomain)), nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - removeOverlays() - capture := session.Window.Content() - session.Window.SetContent(layoutTransition()) - if captureDomain == "app.explorer" { - session.Window.SetContent(layoutAssetExplorer()) - } else { - session.Window.SetContent(session.LastDomain) - session.Domain = captureDomain - } - session.LastDomain = capture - } - - image = canvas.NewImageFromResource(resourceBlankPng) - image.SetMinSize(fyne.NewSize(ui.Width*0.3, ui.Width*0.3)) - image.FillMode = canvas.ImageFillContain - - if icon != "" { - var path fyne.Resource - path, err = fyne.LoadResourceFromURLString(icon) - if err != nil { - image.Resource = resourceBlankPng - } else { - image.Resource = path - } - - image.SetMinSize(fyne.NewSize(ui.Width*0.3, ui.Width*0.3)) - image.FillMode = canvas.ImageFillContain - image.Refresh() - } - - if name == "" { - labelName.ParseMarkdown("## --") - } - - if desc == "" { - labelDesc = widget.NewRichText(&widget.TextSegment{ - Text: "No description provided", - Style: widget.RichTextStyle{ - Alignment: fyne.TextAlignCenter, - ColorName: theme.ColorNameForeground, - TextStyle: fyne.TextStyle{Italic: true}, - }}) - labelDesc.Wrapping = fyne.TextWrapWord - } - - if bal != zerobal { - btnSend.Text = "Send Asset" - btnSend.Enable() - } else { - btnSend.Text = "You do not own this asset" - btnSend.Disable() - } - btnSend.Refresh() - - linkCopySigner := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - linkCopySigner.OnTapped = func() { - a.Clipboard().SetContent(signer) - } - - linkCopyOwner := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - linkCopyOwner.OnTapped = func() { - a.Clipboard().SetContent(owner) - } - - linkMessageAuthor := widget.NewHyperlinkWithStyle("Message the Author", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - linkMessageAuthor.OnTapped = func() { - if signer != "" && signer != "--" { - messages.Contact = signer - session.Window.Canvas().SetContent(layoutTransition()) - removeOverlays() - session.Window.Canvas().SetContent(layoutPM()) - } - } - - linkMessageOwner := widget.NewHyperlinkWithStyle("Message the Owner", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - linkMessageOwner.OnTapped = func() { - if owner != "" && owner != "--" { - messages.Contact = owner - session.Window.Canvas().SetContent(layoutTransition()) - removeOverlays() - session.Window.Canvas().SetContent(layoutPM()) - } - } - - linkCopySCID := widget.NewHyperlinkWithStyle("Copy SCID", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - linkCopySCID.OnTapped = func() { - a.Clipboard().SetContent(scid) - } - - linkView := widget.NewHyperlinkWithStyle("View in Explorer", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - linkView.OnTapped = func() { - if engram.Disk.GetNetwork() { - link, _ := url.Parse("https://explorer.derofoundation.org/tx/" + scid) - _ = fyne.CurrentApp().OpenURL(link) - } else { - link, _ := url.Parse("https://testnetexplorer.derofoundation.org/tx/" + scid) - _ = fyne.CurrentApp().OpenURL(link) - } - } - - // Now let's parse the smart contract code for exported functions - - var contract dvm.SmartContract - var signerFunctions []string - var deroFunctions []string - var assetFunctions []string - - contract, _, err = dvm.ParseSmartContract(code) - if err != nil { - contract = dvm.SmartContract{} - } - - data := []string{} - - for f := range contract.Functions { - r, _ := utf8.DecodeRuneInString(contract.Functions[f].Name) - - if !unicode.IsUpper(r) { - logger.Debugf("[DVM] Function %s is not an exported function - skipping it\n", contract.Functions[f].Name) - } else if contract.Functions[f].Name == "Initialize" || contract.Functions[f].Name == "InitializePrivate" { - logger.Debugf("[DVM] Function %s is an initialization function - skipping it\n", contract.Functions[f].Name) - } else { - data = append(data, contract.Functions[f].Name) - } - - for l := range contract.Functions[f].Lines { - for i := range contract.Functions[f].Lines[l] { - if contract.Functions[f].Lines[l][i] == "SIGNER" && contract.Functions[f].Lines[l][i+1] == "(" { - signerFunctions = append(signerFunctions, contract.Functions[f].Name) - } - - if contract.Functions[f].Lines[l][i] == "DEROVALUE" && contract.Functions[f].Lines[l][i+1] == "(" { - deroFunctions = append(deroFunctions, contract.Functions[f].Name) - } - - if contract.Functions[f].Lines[l][i] == "ASSETVALUE" && contract.Functions[f].Lines[l][i+1] == "(" { - assetFunctions = append(assetFunctions, contract.Functions[f].Name) - } - } - } - } - - sort.Strings(data) - data = append(data, " ") - - var paramList []fyne.Widget - var dero_amount uint64 - var asset_amount uint64 - - functionList := widget.NewSelect(data, nil) - functionList.OnChanged = func(s string) { - if s == " " { - functionList.ClearSelected() - return - } - - var params []dvm.Variable - - overlay := session.Window.Canvas().Overlays() - - options := []string{"Anonymity Set: 2 (None)", "Anonymity Set: 4 (Low)", "Anonymity Set: 8 (Low)", "Anonymity Set: 16 (Recommended)", "Anonymity Set: 32 (Medium)", "Anonymity Set: 64 (High)", "Anonymity Set: 128 (High)"} - - var ringsize uint64 - - signerRequired := false - - selectRingMembers := widget.NewSelect(options, nil) - selectRingMembers.PlaceHolder = "(Select Anonymity Set)" - - for f := range contract.Functions { - if contract.Functions[f].Name == s { - params = contract.Functions[f].Params - - header := canvas.NewText("EXECUTE CONTRACT FUNCTION", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - funcName := canvas.NewText(s, colors.Account) - funcName.TextSize = 22 - funcName.Alignment = fyne.TextAlignCenter - funcName.TextStyle = fyne.TextStyle{Bold: true} - - linkClose := widget.NewHyperlinkWithStyle("Close", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - dero_amount = 0 - asset_amount = 0 - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - entryDEROValue := widget.NewEntry() - entryDEROValue.PlaceHolder = "DERO Amount (Numbers Only)" - entryDEROValue.Validator = func(s string) error { - dero_amount, err = globals.ParseAmount(s) - if err != nil { - entryDEROValue.SetValidationError(err) - return err - } - - return nil - } - - entryAssetValue := widget.NewEntry() - entryAssetValue.PlaceHolder = "Asset Amount (Numbers Only)" - entryAssetValue.Validator = func(s string) error { - asset_amount, err = globals.ParseAmount(s) - if err != nil { - entryAssetValue.SetValidationError(err) - return err - } - - return nil - } - - a := container.NewStack( - span, - entryAssetValue, - ) - - d := container.NewStack( - span, - entryDEROValue, - ) - - paramsContainer := container.NewVBox() - - existsDEROValue := false - existsAssetValue := false - - // Scan code for ASSETVALUE and DEROVALUE - for l := range contract.Functions[f].Lines { - for i := range contract.Functions[f].Lines[l] { - - for v := range paramList { - if paramList[v] == entryDEROValue { - existsDEROValue = true - } else if paramList[v] == entryAssetValue { - existsAssetValue = true - } - } - - if contract.Functions[f].Lines[l][i] == "DEROVALUE" && contract.Functions[f].Lines[l][i+1] == "(" && !existsDEROValue { - paramList = append(paramList, entryDEROValue) - paramsContainer.Add(d) - paramsContainer.Refresh() - existsDEROValue = true - logger.Debugf("[DVM] Added DEROVALUE: %s\n", contract.Functions[f].Lines[l][i]) - } else if len(deroFunctions) > 0 { - for df := range deroFunctions { - if contract.Functions[f].Lines[l][i] == deroFunctions[df] && contract.Functions[f].Lines[l][i+1] == "(" && !existsDEROValue { - paramList = append(paramList, entryDEROValue) - paramsContainer.Add(d) - paramsContainer.Refresh() - existsDEROValue = true - logger.Debugf("[DVM] Added DEROVALUE: %s - Func: %s\n", contract.Functions[f].Lines[l][i], deroFunctions[df]) - } - } - } - - if contract.Functions[f].Lines[l][i] == "ASSETVALUE" && contract.Functions[f].Lines[l][i+1] == "(" && !existsAssetValue { - paramList = append(paramList, entryAssetValue) - paramsContainer.Add(a) - paramsContainer.Refresh() - existsAssetValue = true - logger.Debugf("[DVM] Added ASSETVALUE: %s\n", contract.Functions[f].Lines[l][i]) - } else if len(assetFunctions) > 0 { - for af := range assetFunctions { - if contract.Functions[f].Lines[l][i] == assetFunctions[af] && contract.Functions[f].Lines[l][i+1] == "(" && !existsAssetValue { - paramList = append(paramList, entryAssetValue) - paramsContainer.Add(a) - paramsContainer.Refresh() - existsAssetValue = true - logger.Debugf("[DVM] Added ASSETVALUE: %s\n", contract.Functions[f].Lines[l][i]) - } - } - } - - for si := range signerFunctions { - if contract.Functions[f].Lines[l][i] == "SIGNER" && contract.Functions[f].Lines[l][i+1] == "(" { - signerRequired = true - } else if contract.Functions[f].Lines[l][i] == signerFunctions[si] && contract.Functions[f].Lines[l][i+1] == "(" { - signerRequired = true - } - } - } - } - - selectRingMembers.OnChanged = func(s string) { - if signerRequired { - ringsize = 2 - } else { - regex := regexp.MustCompile("[0-9]+") - result := regex.FindAllString(selectRingMembers.Selected, -1) - ringsize, err = strconv.ParseUint(result[0], 10, 64) - if err != nil { - ringsize = 2 - } - } - } - - if signerRequired { - selectRingMembers.SetSelectedIndex(0) - } else { - selectRingMembers.SetSelectedIndex(3) - } - - btnExecute := widget.NewButton("Execute", nil) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - container.NewCenter( - funcName, - ), - wSpacer, - selectRingMembers, - rectSpacer, - rectSpacer, - paramsContainer, - rectSpacer, - rectSpacer, - btnExecute, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - - for p := range params { - entry := widget.NewEntry() - entry.PlaceHolder = params[p].Name - if params[p].Type == 0x4 { - entry.PlaceHolder = params[p].Name + " (Numbers Only)" - } - entry.Validator = func(s string) error { - for p := range params { - if params[p].Type == 0x5 { - if params[p].Name == entry.PlaceHolder { - logger.Debugf("[%s] String: %s\n", params[p].Name, s) - params[p].ValueString = s - } - } else if params[p].Type == 0x4 { - if params[p].Name+" (Numbers Only)" == entry.PlaceHolder { - amount, err := globals.ParseAmount(s) - if err != nil { - logger.Debugf("[%s] Param error: %s\n", params[p].Name, err) - entry.SetValidationError(err) - return err - } else { - logger.Debugf("[%s] Amount: %d\n", params[p].Name, amount) - params[p].ValueUint64 = amount - } - } - } - } - - return nil - } - - c := container.NewStack( - span, - entry, - ) - - paramList = append(paramList, entry) - paramsContainer.Add(c) - paramsContainer.Refresh() - - } - - btnExecute.OnTapped = func() { - for f := range contract.Functions { - if contract.Functions[f].Name == funcName.Text { - params = contract.Functions[f].Params - } - } - - var err error - - if signerRequired { - ringsize = 2 - } else { - regex := regexp.MustCompile("[0-9]+") - result := regex.FindAllString(selectRingMembers.Selected, -1) - ringsize, err = strconv.ParseUint(result[0], 10, 64) - if err != nil { - ringsize = 2 - selectRingMembers.SetSelected(options[3]) - } - } - - logger.Printf("[Engram] Ringsize: %d\n", ringsize) - - btnExecute.Text = "Executing..." - btnExecute.Disable() - btnExecute.Refresh() - - storage, err := executeContractFunction(hash, ringsize, dero_amount, asset_amount, funcName.Text, params) - if err != nil { - if strings.Contains(err.Error(), "somehow the tx could not be built") { - btnExecute.Text = fmt.Sprintf("Insufficient Balance: Need %v", globals.FormatMoney(storage)) - } else if strings.Contains(err.Error(), "Discarded knowingly") { - btnExecute.Text = "Error... discarded knowingly" - } else if strings.Contains(err.Error(), "Recovered in function") { - btnExecute.Text = "Error... invalid input" - } else { - btnExecute.Text = "Error executing function..." - } - btnExecute.Disable() - btnExecute.Refresh() - } else { - btnExecute.Text = "Function executed successfully!" - btnExecute.Disable() - btnExecute.Refresh() - } - } - - if signerRequired { - selectRingMembers.SetSelectedIndex(0) - selectRingMembers.Disable() - } - - paramsContainer.Refresh() - overlay.Top().Show() - functionList.ClearSelected() - } - } - } - - center := container.NewStack( - rectBox, - container.NewVScroll( - container.NewStack( - rectWidth90, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - /* - container.NewHBox( - image, - rectSpacer, - container.NewVBox( - layout.NewSpacer(), - labelName, - layout.NewSpacer(), - ), - layout.NewSpacer(), - ), - */ - container.NewHBox( - layout.NewSpacer(), - image, - layout.NewSpacer(), - ), - rectSpacer, - - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - labelName, - ), - layout.NewSpacer(), - ), - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - labelDesc, - ), - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator, - rectSpacer, - rectSpacer, - labelSigner, - rectSpacer, - textSigner, - container.NewHBox( - linkMessageAuthor, - layout.NewSpacer(), - ), - container.NewHBox( - linkCopySigner, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator2, - rectSpacer, - rectSpacer, - labelOwner, - rectSpacer, - textOwner, - container.NewHBox( - linkMessageOwner, - layout.NewSpacer(), - ), - container.NewHBox( - linkCopyOwner, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator3, - rectSpacer, - rectSpacer, - labelSCID, - rectSpacer, - container.NewStack( - rectWidth90, - sc, - ), - container.NewHBox( - linkView, - layout.NewSpacer(), - ), - container.NewHBox( - linkCopySCID, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator6, - rectSpacer, - rectSpacer, - labelExecute, - rectSpacer, - rectSpacer, - functionList, - rectSpacer, - rectSpacer, - labelSeparator4, - rectSpacer, - rectSpacer, - labelBalance, - rectSpacer, - balance, - rectSpacer, - rectSpacer, - labelSeparator5, - rectSpacer, - rectSpacer, - labelTransfer, - rectSpacer, - rectSpacer, - rectSpacer, - selectRingSize, - rectSpacer, - entryAddress, - rectSpacer, - entryAmount, - rectSpacer, - btnSend, - wSpacer, - ), - layout.NewSpacer(), - ), - ), - ), - rectSpacer, - rectSpacer, - ) - - top := container.NewVBox( - rectSpacer, - rectSpacer, - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewStack( - frame, - container.NewBorder( - top, - bottom, - nil, - center, - ), - ) - - return NewVScroll(layout) -} - -func layoutTransfers() fyne.CanvasObject { - session.Domain = "app.transfers" - - wSpacer := widget.NewLabel(" ") - - sendTitle := canvas.NewText("T R A N S F E R S", colors.Gray) - sendTitle.TextStyle = fyne.TextStyle{Bold: true} - sendTitle.TextSize = 16 - - sendDesc := canvas.NewText("", colors.Gray) - sendDesc.TextSize = 18 - sendDesc.Alignment = fyne.TextAlignCenter - sendDesc.TextStyle = fyne.TextStyle{Bold: true} - - sendHeading := canvas.NewText("S A V E D T R A N S F E R S", colors.Gray) - sendHeading.TextSize = 16 - sendHeading.Alignment = fyne.TextAlignCenter - sendHeading.TextStyle = fyne.TextStyle{Bold: true} - - rectStatus := canvas.NewRectangle(color.Transparent) - rectStatus.SetMinSize(fyne.NewSize(10, 10)) - rect := canvas.NewRectangle(color.Transparent) - rect.SetMinSize(fyne.NewSize(ui.Width, 20)) - frame := &iframe{} - rect.SetMinSize(fyne.NewSize(ui.Width, 30)) - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - rect.SetMinSize(fyne.NewSize(10, 10)) - rectEmpty := canvas.NewRectangle(color.Transparent) - rectEmpty.SetMinSize(fyne.NewSize(10, 10)) - rectList := canvas.NewRectangle(color.Transparent) - rectList.SetMinSize(fyne.NewSize(ui.Width, 35)) - rectListBox := canvas.NewRectangle(color.Transparent) - rectListBox.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.53)) - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } - - var pendingList []string - - for i := 0; i < len(tx.Pending); i++ { - pendingList = append(pendingList, strconv.Itoa(i)+","+globals.FormatMoney(tx.Pending[i].Amount)+","+tx.Pending[i].Destination) - } - - data := binding.BindStringList(&pendingList) - - scrollBox := widget.NewListWithData(data, - func() fyne.CanvasObject { - c := container.NewStack( - rectList, - container.NewHBox( - canvas.NewText("", colors.Account), - layout.NewSpacer(), - canvas.NewText("", colors.Account), - ), - ) - return c - }, - func(di binding.DataItem, co fyne.CanvasObject) { - dat := di.(binding.String) - str, err := dat.Get() - if err != nil { - return - } - dataItem := strings.SplitN(str, ",", 3) - dest := dataItem[2] - dest = " " + dest[0:4] + " ... " + dest[len(dataItem[2])-10:] - co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[0].(*canvas.Text).Text = dest - co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[0].(*canvas.Text).TextSize = 17 - co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[0].(*canvas.Text).TextStyle.Bold = true - co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[2].(*canvas.Text).Text = dataItem[1] + " " - co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[2].(*canvas.Text).TextSize = 17 - co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[2].(*canvas.Text).TextStyle.Bold = true - }) - - scrollBox.OnSelected = func(id widget.ListItemID) { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutTransfersDetail(id)) - } - - btnSend := widget.NewButton("Send Transfers", nil) - - btnClear := widget.NewButton("Clear", func() { - pendingList = pendingList[:0] - tx = Transfers{} - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutTransfers()) - }) - - if len(pendingList) > 0 { - btnClear.Enable() - btnSend.Enable() - } else { - btnClear.Disable() - btnSend.Disable() - } - - if session.Offline { - btnSend.Text = "Disabled in Offline Mode" - btnSend.Disable() - } - - btnSend.OnTapped = func() { - overlay := session.Window.Canvas().Overlays() - - header := canvas.NewText("ACCOUNT VERIFICATION REQUIRED", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - subHeader := canvas.NewText("Confirm Password", colors.Account) - subHeader.TextSize = 22 - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - - linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - overlay := session.Window.Canvas().Overlays() - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } - - btnSubmit := widget.NewButton("Submit", nil) - - entryPassword := NewReturnEntry() - entryPassword.Password = true - entryPassword.PlaceHolder = "Password" - entryPassword.OnChanged = func(s string) { - if s == "" { - btnSubmit.Text = "Submit" - btnSubmit.Disable() - btnSubmit.Refresh() - } else { - btnSubmit.Text = "Submit" - btnSubmit.Enable() - btnSubmit.Refresh() - } - } - - btnSubmit.OnTapped = func() { - if engram.Disk.Check_Password(entryPassword.Text) { - removeOverlays() - if len(tx.Pending) == 0 { - return - } else { - btnSend.Text = "Setting up transfer..." - btnSend.Disable() - btnSend.Refresh() - txid, err := sendTransfers() - if err != nil { - btnSend.Text = "Send Transfers" - btnSend.Enable() - btnSend.Refresh() - return - } - - go func() { - fyne.Do(func() { - btnClear.Disable() - btnSend.Text = "Confirming..." - btnSend.Refresh() - }) - - walletapi.WaitNewHeightBlock() - sHeight := walletapi.Get_Daemon_Height() - - for session.Domain == "app.transfers" { - var zeroscid crypto.Hash - _, result := engram.Disk.Get_Payments_TXID(zeroscid, txid.String()) - - if result.TXID == txid.String() { - fyne.Do(func() { - btnSend.Text = "Transfer Successful!" - btnSend.Refresh() - }) - - break - } - - // If we go DEFAULT_CONFIRMATION_TIMEOUT blocks without exiting 'Confirming...' loop, display failed to transfer and break - if walletapi.Get_Daemon_Height() > sHeight+int64(DEFAULT_CONFIRMATION_TIMEOUT) { - fyne.Do(func() { - btnSend.Text = "Transfer failed..." - btnSend.Disable() - btnSend.Refresh() - }) - break - } - - // If daemon height has incremented, print retry counters into button space - if walletapi.Get_Daemon_Height()-sHeight > 0 { - fyne.Do(func() { - btnSend.Text = fmt.Sprintf("Confirming... (%d/%d)", walletapi.Get_Daemon_Height()-sHeight, DEFAULT_CONFIRMATION_TIMEOUT) - btnSend.Refresh() - }) - } - - time.Sleep(time.Second * 1) - } - }() - - pendingList = pendingList[:0] - fyne.Do(func() { - data.Reload() - btnSend.Disable() - btnClear.Disable() - }) - } - } else { - fyne.Do(func() { - btnSubmit.Text = "Invalid Password..." - btnSubmit.Disable() - btnSubmit.Refresh() - }) - } - } - - btnSubmit.Disable() - - entryPassword.OnReturn = btnSubmit.OnTapped - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - subHeader, - widget.NewLabel(""), - container.NewCenter( - container.NewStack( - span, - entryPassword, - ), - ), - rectSpacer, - rectSpacer, - btnSubmit, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - - session.Window.Canvas().Focus(entryPassword) - } - - session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { - if session.Domain != "app.transfers" { - return - } - - if k.Name == fyne.KeyDown { - session.Dashboard = "main" - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } - }) - - sendForm := container.NewVBox( - rectSpacer, - rectSpacer, - sendHeading, - rectSpacer, - rectSpacer, - container.NewStack( - rectListBox, - scrollBox, - ), - wSpacer, - btnSend, - rectSpacer, - btnClear, - rectSpacer, - rectSpacer, - ) - - gridItem1 := container.NewCenter( - sendForm, - ) - - gridItem2 := container.NewCenter() - - gridItem3 := container.NewCenter() - - gridItem4 := container.NewCenter() - - gridItem1.Hidden = false - gridItem2.Hidden = true - gridItem3.Hidden = true - gridItem4.Hidden = true - - features := container.NewCenter( - layout.NewSpacer(), - gridItem1, - layout.NewSpacer(), - gridItem2, - layout.NewSpacer(), - gridItem3, - layout.NewSpacer(), - gridItem4, - layout.NewSpacer(), - ) - - bottom := container.NewStack( - container.NewVBox( - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - c := container.NewBorder( - features, - bottom, - nil, - nil, - ) - - layout := container.NewStack( - frame, - c, - ) - - return NewVScroll(layout) -} - -func layoutTransfersDetail(index int) fyne.CanvasObject { - wSpacer := widget.NewLabel(" ") - - rectWidth := canvas.NewRectangle(color.Transparent) - rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, 10)) - - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) - - frame := &iframe{} - - heading := canvas.NewText("T R A N S F E R D E T A I L", colors.Gray) - heading.TextSize = 16 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - labelDestination := canvas.NewText(" RECEIVER ADDRESS", colors.Gray) - labelDestination.TextSize = 14 - labelDestination.Alignment = fyne.TextAlignLeading - labelDestination.TextStyle = fyne.TextStyle{Bold: true} - - labelAmount := canvas.NewText(" AMOUNT", colors.Gray) - labelAmount.TextSize = 14 - labelAmount.Alignment = fyne.TextAlignLeading - labelAmount.TextStyle = fyne.TextStyle{Bold: true} - - labelService := canvas.NewText(" SERVICE ADDRESS", colors.Gray) - labelService.TextSize = 14 - labelService.Alignment = fyne.TextAlignLeading - labelService.TextStyle = fyne.TextStyle{Bold: true} - - labelDestPort := canvas.NewText(" DESTINATION PORT", colors.Gray) - labelDestPort.TextSize = 14 - labelDestPort.TextStyle = fyne.TextStyle{Bold: true} - - labelSourcePort := canvas.NewText(" SOURCE PORT", colors.Gray) - labelSourcePort.TextSize = 14 - labelSourcePort.TextStyle = fyne.TextStyle{Bold: true} - - labelFees := canvas.NewText(" TRANSACTION FEES", colors.Gray) - labelFees.TextSize = 14 - labelFees.TextStyle = fyne.TextStyle{Bold: true} - - labelPayload := canvas.NewText(" PAYLOAD", colors.Gray) - labelPayload.TextSize = 14 - labelPayload.TextStyle = fyne.TextStyle{Bold: true} - - labelReply := canvas.NewText(" REPLY ADDRESS", colors.Gray) - labelReply.TextSize = 14 - labelReply.TextStyle = fyne.TextStyle{Bold: true} - - labelSeparator := widget.NewRichTextFromMarkdown("") - labelSeparator.Wrapping = fyne.TextWrapOff - labelSeparator.ParseMarkdown("---") - - labelSeparator2 := widget.NewRichTextFromMarkdown("") - labelSeparator2.Wrapping = fyne.TextWrapOff - labelSeparator2.ParseMarkdown("---") - - labelSeparator3 := widget.NewRichTextFromMarkdown("") - labelSeparator3.Wrapping = fyne.TextWrapOff - labelSeparator3.ParseMarkdown("---") - - labelSeparator4 := widget.NewRichTextFromMarkdown("") - labelSeparator4.Wrapping = fyne.TextWrapOff - labelSeparator4.ParseMarkdown("---") - - labelSeparator5 := widget.NewRichTextFromMarkdown("") - labelSeparator5.Wrapping = fyne.TextWrapOff - labelSeparator5.ParseMarkdown("---") - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - details := tx.Pending[index] - - valueDestination := widget.NewRichTextFromMarkdown("--") - valueDestination.Wrapping = fyne.TextWrapBreak - - valueType := widget.NewRichTextFromMarkdown("--") - valueType.Wrapping = fyne.TextWrapOff - - if details.Destination != "" { - address, _ := globals.ParseValidateAddress(details.Destination) - if address.IsIntegratedAddress() { - valueDestination.ParseMarkdown(address.BaseAddress().String()) - valueType.ParseMarkdown("### SERVICE") - } else { - valueDestination.ParseMarkdown(details.Destination) - valueType.ParseMarkdown("### NORMAL") - } - } - - valueReply := widget.NewRichTextFromMarkdown("--") - valueReply.Wrapping = fyne.TextWrapBreak - - if details.Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { - if details.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) != "" { - valueReply.ParseMarkdown("" + details.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string)) - } - } - - valuePayload := widget.NewRichTextFromMarkdown("--") - valuePayload.Wrapping = fyne.TextWrapBreak - - if details.Payload_RPC.HasValue(rpc.RPC_COMMENT, rpc.DataString) { - if details.Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string) != "" { - valuePayload.ParseMarkdown("" + details.Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string)) - } - } - - valueAmount := canvas.NewText("", colors.Account) - valueAmount.TextSize = 22 - valueAmount.TextStyle = fyne.TextStyle{Bold: true} - valueAmount.Text = " " + globals.FormatMoney(details.Amount) - - valueDestPort := canvas.NewText("", colors.Account) - valueDestPort.TextSize = 22 - valueDestPort.TextStyle = fyne.TextStyle{Bold: true} - - if details.Payload_RPC.HasValue(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { - port := fmt.Sprintf("%d", details.Payload_RPC.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64)) - valueDestPort.Text = " " + port - } else { - valueDestPort.Text = " 0" - } - - linkBack := widget.NewHyperlinkWithStyle("Back to Transfers", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutTransfers()) - } - - btnDelete := widget.NewButton("Cancel Transfer", nil) - btnDelete.OnTapped = func() { - if len(tx.Pending) > index+1 { - tx.Pending = append(tx.Pending[:index], tx.Pending[index+1:]...) - } else if len(tx.Pending) == 1 { - tx = Transfers{} - } else { - tx.Pending = tx.Pending[:index] - } - - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutTransfers()) - } - - top := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - heading, - ), - rectSpacer, - container.NewCenter( - valueType, - ), - rectSpacer, - rectSpacer, - ) - - center := container.NewStack( - container.NewVScroll( - container.NewStack( - rectWidth, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - rectSpacer, - rectSpacer, - labelDestination, - rectSpacer, - valueDestination, - rectSpacer, - rectSpacer, - labelSeparator, - rectSpacer, - rectSpacer, - labelAmount, - rectSpacer, - container.NewStack( - rectWidth90, - valueAmount, - ), - rectSpacer, - rectSpacer, - labelSeparator2, - rectSpacer, - rectSpacer, - labelReply, - rectSpacer, - valueReply, - rectSpacer, - rectSpacer, - labelSeparator3, - rectSpacer, - rectSpacer, - labelPayload, - rectSpacer, - container.NewStack( - rectWidth90, - valuePayload, - ), - rectSpacer, - rectSpacer, - labelSeparator4, - rectSpacer, - rectSpacer, - labelDestPort, - rectSpacer, - container.NewStack( - rectWidth90, - valueDestPort, - ), - wSpacer, - ), - layout.NewSpacer(), - ), - ), - ), - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - rectWidth, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - btnDelete, - ), - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewStack( - frame, - container.NewBorder( - top, - bottom, - nil, - center, - ), - ) - - return NewVScroll(layout) -} - -func layoutTransition() fyne.CanvasObject { - frame := &iframe{} - resizeWindow(ui.MaxWidth, ui.MaxHeight) - - rect := canvas.NewRectangle(color.Transparent) - rect.SetMinSize(fyne.NewSize(ui.Width*0.45, ui.Width*0.45)) - - if res.loading == nil { - res.loading, _ = x.NewAnimatedGifFromResource(resourceLoadingGif) - res.loading.SetMinSize(fyne.NewSize(ui.Width*0.45, ui.Width*0.45)) - res.loading.Resize(fyne.NewSize(ui.Width*0.45, ui.Width*0.45)) - } - - res.loading.Start() - - layout := container.NewStack( - frame, - container.NewCenter( - rect, - res.loading, - ), - ) - - return NewVScroll(layout) -} - -func layoutSettings() fyne.CanvasObject { - stopGnomon() - rect := canvas.NewRectangle(color.Transparent) - rect.SetMinSize(fyne.NewSize(ui.Width, 10)) - rectScroll := canvas.NewRectangle(color.Transparent) - rectScroll.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.65)) - frame := &iframe{} - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - - heading := canvas.NewText("My Settings", colors.Green) - heading.TextSize = 22 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - labelNetwork := canvas.NewText("NETWORK", colors.Gray) - labelNetwork.TextStyle = fyne.TextStyle{Bold: true} - labelNetwork.TextSize = 14 - - labelNode := canvas.NewText("CONNECTION", colors.Gray) - labelNode.TextStyle = fyne.TextStyle{Bold: true} - labelNode.TextSize = 14 - - labelSecurity := canvas.NewText("SECURITY", colors.Gray) - labelSecurity.TextStyle = fyne.TextStyle{Bold: true} - labelSecurity.TextSize = 14 - - labelGnomon := canvas.NewText("GNOMON", colors.Gray) - labelGnomon.TextStyle = fyne.TextStyle{Bold: true} - labelGnomon.TextSize = 14 - - textGnomon := widget.NewRichTextWithText("Gnomon scans and indexes blockchain data in order to unlock more features, like native asset tracking.") - textGnomon.Wrapping = fyne.TextWrapWord - - textCyberdeck := widget.NewRichTextWithText("A username and password is required in order to allow application connectivity.") - textCyberdeck.Wrapping = fyne.TextWrapWord - - btnRestore := widget.NewButton("Restore Defaults", nil) - btnDelete := widget.NewButton("Clear Local Data", nil) - - entryAddress := widget.NewEntry() - entryAddress.Validator = func(s string) (err error) { - /* - _, err := net.ResolveTCPAddr("tcp", s) - */ - regex := `^(?:[a-zA-Z0-9]{1,62}(?:[-\.][a-zA-Z0-9]{1,62})+)(:\d+)?$` - test := regexp.MustCompile(regex) - - // Trim off http, https, wss, ws to validate regex on 'actual' uri for connection. If none match, s is just s as normal - var ssplit string - if strings.HasPrefix(s, "https") { - ssplit = strings.TrimPrefix(strings.ToLower(s), "https://") - } else if strings.HasPrefix(s, "http") { - ssplit = strings.TrimPrefix(strings.ToLower(s), "http://") - } else if strings.HasPrefix(s, "wss") { - ssplit = strings.TrimPrefix(strings.ToLower(s), "wss://") - } else if strings.HasPrefix(s, "ws") { - ssplit = strings.TrimPrefix(strings.ToLower(s), "ws://") - } else { - // s is s - ssplit = s - } - - if test.MatchString(ssplit) { - entryAddress.SetValidationError(nil) - setDaemon(s) - } else { - err = errors.New("invalid host name") - entryAddress.SetValidationError(err) - } - - return - } - entryAddress.PlaceHolder = "0.0.0.0:10102" - entryAddress.SetText(getDaemon()) - entryAddress.Refresh() - - selectNodes := widget.NewSelect(nil, nil) - selectNodes.PlaceHolder = "Select Public Node ..." - switch session.Network { - case NETWORK_TESTNET: - selectNodes.Options = []string{"testnetexplorer.derofoundation.org:40402", "127.0.0.1:40402"} - case NETWORK_SIMULATOR: - selectNodes.Options = []string{"127.0.0.1:20000"} - selectNodes.PlaceHolder = "Select Simulator Node ..." - default: - selectNodes.Options = []string{"node.derofoundation.org:11012", "127.0.0.1:10102"} - } - selectNodes.OnChanged = func(s string) { - if s != "" { - err := setDaemon(s) - if err == nil { - entryAddress.Text = s - entryAddress.Refresh() - } - selectNodes.ClearSelected() - } - } - - labelScan := widget.NewRichTextFromMarkdown("Enter the number of past blocks that the wallet should scan:") - labelScan.Wrapping = fyne.TextWrapWord - - entryScan := widget.NewEntry() - entryScan.PlaceHolder = "# of Latest Blocks (Optional)" - entryScan.Validator = func(s string) (err error) { - blocks, err := strconv.ParseInt(s, 10, 64) - if err != nil { - entryScan.SetValidationError(err) - } else { - entryScan.SetValidationError(nil) - if blocks > 0 { - session.TrackRecentBlocks = blocks - } else { - session.TrackRecentBlocks = 0 - } - } - - return - } - - if session.TrackRecentBlocks > 0 { - blocks := strconv.FormatInt(session.TrackRecentBlocks, 10) - entryScan.Text = blocks - entryScan.Refresh() - } - - radioNetwork := widget.NewRadioGroup([]string{NETWORK_MAINNET, NETWORK_TESTNET, NETWORK_SIMULATOR}, nil) - radioNetwork.Required = true - radioNetwork.Horizontal = false - radioNetwork.OnChanged = func(s string) { - if s == NETWORK_TESTNET { - setNetwork(s) - selectNodes.Options = []string{"testnetexplorer.derofoundation.org:40402", "127.0.0.1:40402"} - selectNodes.PlaceHolder = "Select Public Node ..." - } else if s == NETWORK_SIMULATOR { - setNetwork(s) - selectNodes.Options = []string{"127.0.0.1:20000"} - selectNodes.PlaceHolder = "Select Simulator Node ..." - } else { - setNetwork(NETWORK_MAINNET) - selectNodes.Options = []string{"node.derofoundation.org:11012", "127.0.0.1:10102"} - selectNodes.PlaceHolder = "Select Public Node ..." - } - - // Change globals.Config mainnet/testnet to match network - globals.InitNetwork() - - selectNodes.Refresh() - } - - net, _ := GetValue("settings", []byte("network")) - - if string(net) == NETWORK_TESTNET { - radioNetwork.SetSelected(NETWORK_TESTNET) - } else if string(net) == NETWORK_SIMULATOR { - radioNetwork.SetSelected(NETWORK_SIMULATOR) - } else { - radioNetwork.SetSelected(NETWORK_MAINNET) - } - - radioNetwork.Refresh() - - entryUser := widget.NewEntry() - entryUser.PlaceHolder = "Username" - entryUser.SetText(cyberdeck.RPC.user) - - entryPass := widget.NewEntry() - entryPass.PlaceHolder = "Password" - entryPass.Password = true - entryPass.SetText(cyberdeck.RPC.pass) - - entryUser.OnChanged = func(s string) { - cyberdeck.RPC.user = s - } - - entryPass.OnChanged = func(s string) { - cyberdeck.RPC.pass = s - } - - checkGnomon := widget.NewCheck("Enable Gnomon", nil) - checkGnomon.OnChanged = func(b bool) { - if b { - StoreValue("settings", []byte("gnomon"), []byte("1")) - checkGnomon.Checked = true - gnomon.Active = 1 - } else { - StoreValue("settings", []byte("gnomon"), []byte("0")) - checkGnomon.Checked = false - gnomon.Active = 0 - } - } - - gmn, err := GetValue("settings", []byte("gnomon")) - if err != nil { - gnomon.Active = 1 - StoreValue("settings", []byte("gnomon"), []byte("1")) - checkGnomon.Checked = true - } - - if string(gmn) == "1" { - checkGnomon.Checked = true - } else { - checkGnomon.Checked = false - } - - labelBack := widget.NewHyperlinkWithStyle("Return to Login", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - labelBack.OnTapped = func() { - network := radioNetwork.Selected - if network == NETWORK_TESTNET { - setNetwork(network) - } else if network == NETWORK_SIMULATOR { - setNetwork(network) - } else { - setNetwork(NETWORK_MAINNET) - } - setDaemon(entryAddress.Text) - - initSettings() - - resizeWindow(ui.MaxWidth, ui.MaxHeight) - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMain()) - removeOverlays() - } - - btnRestore.OnTapped = func() { - setNetwork(NETWORK_MAINNET) - setDaemon(DEFAULT_REMOTE_DAEMON) - setAuthMode("true") - setGnomon("1") - - resizeWindow(ui.MaxWidth, ui.MaxHeight) - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutSettings()) - removeOverlays() - } - - statusText := canvas.NewText("", colors.Account) - statusText.TextSize = 12 - - btnDelete.OnTapped = func() { - err := cleanGnomonData() - if err != nil { - if parseError, ok := err.(*os.PathError); !ok { - err = fmt.Errorf("error clearing local %s data", session.Network) - } else { - err = parseError.Err - } - - statusText.Color = colors.Red - statusText.Text = err.Error() - statusText.Refresh() - return - } - - statusText.Color = colors.Green - statusText.Text = fmt.Sprintf("Gnomon %s data successfully deleted.", strings.ToLower(session.Network)) - statusText.Refresh() - } - - formSettings := container.NewVBox( - labelNetwork, - rectSpacer, - radioNetwork, - widget.NewLabel(""), - labelNode, - rectSpacer, - rectSpacer, - selectNodes, - rectSpacer, - entryAddress, - rectSpacer, - rectSpacer, - labelScan, - rectSpacer, - entryScan, - widget.NewLabel(""), - labelSecurity, - rectSpacer, - textCyberdeck, - rectSpacer, - entryUser, - rectSpacer, - entryPass, - rectSpacer, - widget.NewLabel(""), - labelGnomon, - rectSpacer, - textGnomon, - rectSpacer, - checkGnomon, - rectSpacer, - statusText, - rectSpacer, - rectSpacer, - btnDelete, - rectSpacer, - btnRestore, - ) - - scrollBox := container.NewVScroll( - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectScroll, - formSettings, - ), - layout.NewSpacer(), - ), - ) - - scrollBox.SetMinSize(fyne.NewSize(ui.MaxWidth, ui.Height*0.68)) - - gridItem1 := container.NewCenter( - container.NewVBox( - widget.NewLabel(""), - heading, - widget.NewLabel(""), - scrollBox, - rectSpacer, - rectSpacer, - ), - ) - - features := container.NewCenter( - layout.NewSpacer(), - gridItem1, - layout.NewSpacer(), - ) - - footer := container.NewVBox( - container.NewHBox( - layout.NewSpacer(), - labelBack, - layout.NewSpacer(), - ), - widget.NewLabel(" "), - ) - - c := container.NewBorder( - features, - footer, - nil, - nil, - ) - - layout := container.NewStack( - frame, - c, - ) - - return NewVScroll(layout) -} - -func layoutMessages() fyne.CanvasObject { - session.Domain = "app.messages" - - if !walletapi.Connected { - session.Window.SetContent(layoutSettings()) - } - - title := canvas.NewText("M Y C O N T A C T S", colors.Gray) - title.TextStyle = fyne.TextStyle{Bold: true} - title.TextSize = 16 - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - checkLimit := widget.NewCheck(" Show only recent messages", nil) - checkLimit.OnChanged = func(b bool) { - if b { - session.LimitMessages = true - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMessages()) - removeOverlays() - } else { - session.LimitMessages = false - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMessages()) - removeOverlays() - } - } - - if session.LimitMessages { - checkLimit.Checked = true - } - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } - - rectStatus := canvas.NewRectangle(color.Transparent) - rectStatus.SetMinSize(fyne.NewSize(10, 10)) - rectEmpty := canvas.NewRectangle(color.Transparent) - rectEmpty.SetMinSize(fyne.NewSize(10, 10)) - rect := canvas.NewRectangle(color.Transparent) - rect.SetMinSize(fyne.NewSize(ui.Width, 20)) - frame := &iframe{} - rect.SetMinSize(fyne.NewSize(ui.Width, 30)) - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - rect.SetMinSize(fyne.NewSize(10, 10)) - rectList := canvas.NewRectangle(color.Transparent) - rectList.SetMinSize(fyne.NewSize(ui.Width, 35)) - rectListBox := canvas.NewRectangle(color.Transparent) - rectListBox.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.43)) - - messages.Data = nil - - var height uint64 - - if session.LimitMessages { - height = engram.Disk.Get_Height() - 1000000 - } else { - height = 0 - } - - data := getMessages(height) - temp := data - - list := binding.BindStringList(&data) - - msgbox.List = widget.NewListWithData(list, - func() fyne.CanvasObject { - c := container.NewVBox( - widget.NewLabel(""), - ) - return c - }, - func(di binding.DataItem, co fyne.CanvasObject) { - dat := di.(binding.String) - str, err := dat.Get() - if err != nil { - return - } - dataItem := strings.Split(str, "~~~") - short := dataItem[0] - address := short[len(short)-DEFAULT_USERADDR_SHORTEN_LENGTH:] - username := dataItem[1] - // If a username is longer than what *would* be a 'short' address of ...xyzxyzxyzx (e.g. 13), then shorten as well to be similar sizing - if len(username) > DEFAULT_USERADDR_SHORTEN_LENGTH+3 { - username = "..." + username[len(username)-DEFAULT_USERADDR_SHORTEN_LENGTH:] - } - - if username == "" { - co.(*fyne.Container).Objects[0].(*widget.Label).SetText("..." + address) - } else { - co.(*fyne.Container).Objects[0].(*widget.Label).SetText(username) - } - co.(*fyne.Container).Objects[0].(*widget.Label).Wrapping = fyne.TextWrapWord - co.(*fyne.Container).Objects[0].(*widget.Label).TextStyle.Bold = false - co.(*fyne.Container).Objects[0].(*widget.Label).Alignment = fyne.TextAlignLeading - }) - - msgbox.List.OnSelected = func(id widget.ListItemID) { - msgbox.List.UnselectAll() - split := strings.Split(data[id], "~~~") - if split[1] == "" { - messages.Contact = split[0] - } else { - messages.Contact = split[1] - } - - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutPM()) - removeOverlays() - } - - searchList := []string{} - - entrySearch := widget.NewEntry() - entrySearch.PlaceHolder = "Search for a Contact" - entrySearch.OnChanged = func(s string) { - s = strings.ToLower(s) - searchList = []string{} - if s == "" { - data = temp - list.Reload() - } else { - for _, d := range temp { - tempd := strings.ToLower(d) - split := strings.Split(tempd, "~~~") - - if split[1] == "" { - if strings.Contains(split[0], s) { - searchList = append(searchList, d) - } - } else { - if strings.Contains(split[1], s) { - searchList = append(searchList, d) - } - } - } - - data = searchList - list.Reload() - } - } - - btnSend := widget.NewButton("New Message", func() { - _, err := globals.ParseValidateAddress(messages.Contact) - if err != nil { - //_, err := engram.Disk.NameToAddress(messages.Contact) - _, err := checkUsername(messages.Contact, -1) - if err != nil { - return - } - } - - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutPM()) - removeOverlays() - }) - btnSend.Disable() - - entryDest := widget.NewEntry() - entryDest.MultiLine = false - entryDest.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) - entryDest.PlaceHolder = "Username or Address" - entryDest.Validator = func(s string) error { - if len(s) > 0 { - _, err := globals.ParseValidateAddress(s) - if err != nil { - btnSend.Disable() - //_, err := engram.Disk.NameToAddress(s) - _, err = checkUsername(s, -1) - if err != nil { - btnSend.Disable() - return errors.New("invalid username or address") - } else { - messages.Contact = s - btnSend.Enable() - return nil - } - } else { - btnSend.Enable() - messages.Contact = s - return nil - } - } - - return errors.New("invalid username or address") - } - - messageForm := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - title, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - entrySearch, - rectSpacer, - rectSpacer, - container.NewStack( - rectListBox, - msgbox.List, - ), - rectSpacer, - entryDest, - rectSpacer, - btnSend, - rectSpacer, - checkLimit, - ) - - gridItem1 := container.NewCenter( - messageForm, - ) - - gridItem2 := container.NewCenter() - - gridItem3 := container.NewCenter() - - gridItem4 := container.NewCenter() - - gridItem1.Hidden = false - gridItem2.Hidden = true - gridItem3.Hidden = true - gridItem4.Hidden = true - - features := container.NewCenter( - layout.NewSpacer(), - gridItem1, - layout.NewSpacer(), - gridItem2, - layout.NewSpacer(), - gridItem3, - layout.NewSpacer(), - gridItem4, - layout.NewSpacer(), - ) - - session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { - if session.Domain != "app.messages" { - return - } - - if k.Name == fyne.KeyUp { - session.Dashboard = "main" - - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } else if k.Name == fyne.KeyF5 { - session.Window.SetContent(layoutMessages()) - removeOverlays() - } - }) - - subContainer := container.NewStack( - container.NewVBox( - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - c := container.NewBorder( - features, - subContainer, - nil, - nil, - ) - - layout := container.NewStack( - frame, - c, - ) - - return NewVScroll(layout) -} - -func layoutPM() fyne.CanvasObject { - session.Domain = "app.messages.contact" - - if !walletapi.Connected { - session.Window.SetContent(layoutSettings()) - } - - getPrimaryUsername() - - contactAddress := "" - - // So message contact sizes are not overblown from UI - _, err := globals.ParseValidateAddress(messages.Contact) - if err != nil { - //_, err := engram.Disk.NameToAddress(messages.Contact) - _, err := checkUsername(messages.Contact, -1) - if err == nil { - contactAddress = messages.Contact - } - } /* else { - short := messages.Contact[len(messages.Contact)-10:] - contactAddress = "..." + short - }*/ - - // Safety, even though valid addresses are sized enough but usernames may not be - if len(messages.Contact) > DEFAULT_USERADDR_SHORTEN_LENGTH+3 { - short := messages.Contact[len(messages.Contact)-DEFAULT_USERADDR_SHORTEN_LENGTH:] - contactAddress = "..." + short - } - - title := canvas.NewText("M E S S A G E S", colors.Gray) - title.TextStyle = fyne.TextStyle{Bold: true} - title.TextSize = 16 - - heading := canvas.NewText(contactAddress, colors.Green) - heading.TextSize = 22 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - lastActive := canvas.NewText("", colors.Gray) - lastActive.TextSize = 12 - lastActive.Alignment = fyne.TextAlignCenter - lastActive.TextStyle = fyne.TextStyle{Bold: false} - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to Messages", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMessages()) - removeOverlays() - } - - rectStatus := canvas.NewRectangle(color.Transparent) - rectStatus.SetMinSize(fyne.NewSize(10, 10)) - rectEmpty := canvas.NewRectangle(color.Transparent) - rectEmpty.SetMinSize(fyne.NewSize(10, 10)) - rect := canvas.NewRectangle(color.Transparent) - rect.SetMinSize(fyne.NewSize(ui.Width*0.7, 30)) - frame := &iframe{} - subframe := canvas.NewRectangle(color.Transparent) - subframe.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.51)) - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - rect.SetMinSize(fyne.NewSize(10, 10)) - rectList := canvas.NewRectangle(color.Transparent) - rectList.SetMinSize(fyne.NewSize(ui.Width, 35)) - rectListBox := canvas.NewRectangle(color.Transparent) - rectListBox.SetMinSize(fyne.NewSize(ui.Width*0.42, 30)) - rectOutbound := canvas.NewRectangle(color.Transparent) - rectOutbound.SetMinSize(fyne.NewSize(ui.Width*0.166, 30)) - - messages.Data = nil - - chats := container.NewVBox() - - chatFrame := container.NewStack( - rectListBox, - container.NewStack( - chats, - ), - ) - - chatbox := container.NewVScroll( - container.NewStack( - chatFrame, - ), - ) - - var e *fyne.Container - var height uint64 - - if session.LimitMessages { - height = engram.Disk.Get_Height() - 1000000 - } else { - height = 0 - } - - data := getMessagesFromUser(messages.Contact, height) - - for d := range data { - if data[d].Incoming { - if data[d].Payload_RPC.Has(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { - if data[d].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) == "" { - - } else { - t := data[d].Time - time := string(t.Format(time.RFC822)) - comment := data[d].Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string) - links := getTextURL(comment) - - for i := range links { - if comment == links[i] { - if len(links[i]) > 25 { - comment = `[ ` + links[i][0:25] + "..." + ` ](` + links[i] + `)` - } else { - comment = `[ ` + links[i] + ` ](` + links[i] + `)` - } - } else { - linkText := "" - split := strings.Split(comment, links[i]) - if len(links[i]) > 25 { - linkText = links[i][0:25] + "..." - } else { - linkText = links[i] - } - comment = `` + split[0] + `[link]` + split[1] + "\n\n›" + `[ ` + linkText + ` ](` + links[i] + `)` - } - } - messages.Data = append(messages.Data, data[d].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string)+";;;;"+comment+";;;;"+time) - } - } - } else { - t := data[d].Time - time := string(t.Format(time.RFC822)) - comment := data[d].Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string) - links := getTextURL(comment) - - for i := range links { - if comment == links[i] { - if len(links[i]) > 25 { - comment = `[ ` + links[i][0:25] + "..." + ` ](` + links[i] + `)` - } else { - comment = `[ ` + links[i] + ` ](` + links[i] + `)` - } - } else { - linkText := "" - split := strings.Split(comment, links[i]) - if len(links[i]) > 25 { - linkText = links[i][0:25] + "..." - } else { - linkText = links[i] - } - comment = `` + split[0] + `[link]` + split[1] + "\n\n›" + `[ ` + linkText + ` ](` + links[i] + `)` - } - } - messages.Data = append(messages.Data, engram.Disk.GetAddress().String()+";;;;"+comment+";;;;"+time) - } - } - - if len(data) > 0 { - for m := range messages.Data { - var sender string - split := strings.Split(messages.Data[m], ";;;;") - mdata := widget.NewRichTextFromMarkdown("") - mdata.Wrapping = fyne.TextWrapWord - datetime := canvas.NewText("", colors.Green) - datetime.TextSize = 11 - boxColor := colors.Flint - rect := canvas.NewRectangle(boxColor) - rect.SetMinSize(fyne.NewSize(ui.Width*0.80, 30)) - rect.CornerRadius = 5.0 - rect5 := canvas.NewRectangle(color.Transparent) - rect5.SetMinSize(fyne.NewSize(5, 5)) - - //uname, err := engram.Disk.NameToAddress(split[0]) - uname, err := checkUsername(split[0], -1) - if err != nil { - sender = split[0] - } else { - sender = uname - } - - if sender == engram.Disk.GetAddress().String() { - rect.FillColor = colors.DarkGreen - mdata.ParseMarkdown(split[1]) - datetime.Text = split[2] - e = container.NewBorder( - nil, - container.NewVBox( - container.NewHBox( - layout.NewSpacer(), - datetime, - rect5, - ), - rect5, - ), - rectOutbound, - container.NewStack( - rect, - container.NewVBox( - mdata, - ), - ), - ) - } else { - rect.FillColor = colors.Flint - mdata.ParseMarkdown(split[1]) - datetime.Text = split[2] - e = container.NewBorder( - nil, - container.NewVBox( - container.NewHBox( - rect5, - datetime, - layout.NewSpacer(), - ), - rect5, - ), - container.NewStack( - rect, - container.NewVBox( - mdata, - ), - ), - rectOutbound, - ) - } - - lastActive.Text = "Last Updated: " + time.Now().Format(time.RFC822) - lastActive.Refresh() - - chats.Add(e) - chats.Refresh() - chatbox.Refresh() - chatbox.ScrollToBottom() - } - } - - btnSend := widget.NewButton("Send", nil) - btnSend.Disable() - - entry := widget.NewEntry() - entry.MultiLine = false - entry.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) - entry.PlaceHolder = "Message" - entry.OnChanged = func(s string) { - messages.Message = s - contact := messages.Contact - //check, err := engram.Disk.NameToAddress(messages.Contact) - check, err := checkUsername(messages.Contact, -1) - if err == nil { - contact = check - } - - _, err = globals.ParseValidateAddress(contact) - if err != nil { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMessages()) - removeOverlays() - return - } - - err = checkMessagePack(messages.Message, session.Username, contact) - if err != nil { - btnSend.Text = "Message too long..." - btnSend.Disable() - btnSend.Refresh() - return - } else { - if messages.Message == "" { - btnSend.Text = "Send" - btnSend.Disable() - btnSend.Refresh() - } else { - btnSend.Text = "Send" - btnSend.Enable() - btnSend.Refresh() - } - } - } - - btnSend.OnTapped = func() { - if messages.Message == "" { - return - } - contact := "" - _, err := globals.ParseValidateAddress(messages.Contact) - if err != nil { - //check, err := engram.Disk.NameToAddress(messages.Contact) - check, err := checkUsername(messages.Contact, -1) - if err != nil { - logger.Errorf("[Message] Failed to send: %s\n", err) - btnSend.Text = "Failed to verify address..." - btnSend.Disable() - btnSend.Refresh() - return - } - contact = check - } else { - contact = messages.Contact - } - - btnSend.Text = "Setting up transfer..." - btnSend.Disable() - btnSend.Refresh() - - txid, err := sendMessage(messages.Message, session.Username, contact) - if err != nil { - logger.Errorf("[Message] Failed to send: %s\n", err) - btnSend.Text = "Failed to send message..." - btnSend.Disable() - btnSend.Refresh() - return - } - - logger.Printf("[Message] Dispatched transaction successfully to: %s\n", messages.Contact) - btnSend.Text = "Confirming..." - btnSend.Disable() - btnSend.Refresh() - messages.Message = "" - entry.Text = "" - entry.Refresh() - - go func() { - walletapi.WaitNewHeightBlock() - sHeight := walletapi.Get_Daemon_Height() - var success bool - for session.Domain == "app.messages.contact" { - var zeroscid crypto.Hash - _, result := engram.Disk.Get_Payments_TXID(zeroscid, txid.String()) - - if result.TXID != txid.String() { - time.Sleep(time.Second * 1) - } else { - success = true - } - - // If we go DEFAULT_CONFIRMATION_TIMEOUT blocks without exiting 'Confirming...' loop, display failed to transfer and break - if walletapi.Get_Daemon_Height() > sHeight+int64(DEFAULT_CONFIRMATION_TIMEOUT) { - fyne.Do(func() { - btnSend.Text = "Failed to send message..." - btnSend.Disable() - btnSend.Refresh() - }) - - break - } - - // If daemon height has incremented, print retry counters into button space - if walletapi.Get_Daemon_Height()-sHeight > 0 { - fyne.Do(func() { - btnSend.Text = fmt.Sprintf("Confirming... (%d/%d)", walletapi.Get_Daemon_Height()-sHeight, DEFAULT_CONFIRMATION_TIMEOUT) - btnSend.Refresh() - }) - } - - // If success, reload page w/ latest content. Otherwise retain the Failure message for UX relay - if success { - fyne.Do(func() { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutPM()) - }) - - break - } else { - time.Sleep(time.Second * 1) - } - } - }() - } - - messageForm := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - heading, - layout.NewSpacer(), - ), - rectSpacer, - lastActive, - rectSpacer, - rectSpacer, - container.NewStack( - subframe, - chatbox, - ), - rectSpacer, - rectSpacer, - entry, - rectSpacer, - btnSend, - rectSpacer, - rectSpacer, - ) - - gridItem1 := container.NewCenter( - messageForm, - ) - - gridItem2 := container.NewCenter() - - gridItem3 := container.NewCenter() - - gridItem4 := container.NewCenter() - - gridItem1.Hidden = false - gridItem2.Hidden = true - gridItem3.Hidden = true - gridItem4.Hidden = true - - features := container.NewCenter( - layout.NewSpacer(), - gridItem1, - layout.NewSpacer(), - gridItem2, - layout.NewSpacer(), - gridItem3, - layout.NewSpacer(), - gridItem4, - layout.NewSpacer(), - ) - - session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { - if session.Domain != "app.messages.contact" { - return - } - - if k.Name == fyne.KeyUp { - session.Dashboard = "app.messages" - messages.Contact = "" - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMessages()) - removeOverlays() - } else if k.Name == fyne.KeyEscape { - session.Dashboard = "app.messages" - messages.Contact = "" - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMessages()) - removeOverlays() - } else if k.Name == fyne.KeyF5 { - session.Window.SetContent(layoutPM()) - } - }) - - subContainer := container.NewStack( - container.NewVBox( - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - c := container.NewBorder( - features, - subContainer, - nil, - nil, - ) - - layout := container.NewStack( - frame, - c, - ) - - return NewVScroll(layout) -} - -func layoutCyberdeck() fyne.CanvasObject { - session.Domain = "app.cyberdeck" - - go refreshXSWDList() - - wSpacer := widget.NewLabel(" ") - - title := canvas.NewText("C Y B E R D E C K", colors.Gray) - title.TextStyle = fyne.TextStyle{Bold: true} - title.TextSize = 16 - - rect := canvas.NewRectangle(color.Transparent) - rect.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.20)) - - frame := &iframe{} - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 0)) - - rpcLabel := canvas.NewText(" C O N F I G U R A T I O N ", colors.Gray) - rpcLabel.TextSize = 11 - rpcLabel.Alignment = fyne.TextAlignCenter - rpcLabel.TextStyle = fyne.TextStyle{Bold: true} - - wsLabel := canvas.NewText(" C O N F I G U R A T I O N ", colors.Gray) - wsLabel.TextSize = 11 - wsLabel.Alignment = fyne.TextAlignCenter - wsLabel.TextStyle = fyne.TextStyle{Bold: true} - - labelConnections := canvas.NewText(" C O N N E C T I O N S ", colors.Gray) - labelConnections.TextSize = 11 - labelConnections.Alignment = fyne.TextAlignCenter - labelConnections.TextStyle = fyne.TextStyle{Bold: true} - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep1 := canvas.NewRectangle(colors.Gray) - sep1.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep1, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } - - shortShard := canvas.NewText("APPLICATION CONNECTIONS", colors.Gray) - shortShard.TextStyle = fyne.TextStyle{Bold: true} - shortShard.TextSize = 12 - - linkColor := colors.Green - - if cyberdeck.RPC.server == nil { - session.Link = "Blocked" - linkColor = colors.Gray - } - - cyberdeck.RPC.status = canvas.NewText(session.Link, linkColor) - cyberdeck.RPC.status.TextSize = 22 - cyberdeck.RPC.status.TextStyle = fyne.TextStyle{Bold: true} - - serverStatus := canvas.NewText("APPLICATION CONNECTIONS", colors.Gray) - serverStatus.TextSize = 12 - serverStatus.Alignment = fyne.TextAlignCenter - serverStatus.TextStyle = fyne.TextStyle{Bold: true} - - linkCenter := container.NewCenter( - cyberdeck.RPC.status, - ) - - cyberdeck.RPC.userText = widget.NewEntry() - cyberdeck.RPC.userText.PlaceHolder = "Username" - cyberdeck.RPC.userText.OnChanged = func(s string) { - if len(s) > 1 { - cyberdeck.RPC.user = s - } - } - - cyberdeck.RPC.passText = widget.NewEntry() - cyberdeck.RPC.passText.Password = true - cyberdeck.RPC.passText.PlaceHolder = "Password" - cyberdeck.RPC.passText.OnChanged = func(s string) { - if len(s) > 1 { - cyberdeck.RPC.pass = s - } - } - - cyberdeck.RPC.portText = widget.NewEntry() - cyberdeck.RPC.portText.PlaceHolder = "0.0.0.0:10103" - cyberdeck.RPC.portText.Validator = func(s string) (err error) { - regex := `^(?:[a-zA-Z0-9]{1,62}(?:[-\.][a-zA-Z0-9]{1,62})+)(:\d+)?$` - test := regexp.MustCompile(regex) - if test.MatchString(s) { - cyberdeck.RPC.portText.SetValidationError(nil) - } else { - err = errors.New("invalid host name") - cyberdeck.RPC.portText.SetValidationError(err) - } - - return - } - cyberdeck.RPC.portText.SetText(getCyberdeck("RPC")) - - linkColor = colors.Green - - if cyberdeck.WS.server == nil { - session.Link = "Blocked" - linkColor = colors.Gray - } - - cyberdeck.WS.status = canvas.NewText(session.Link, linkColor) - cyberdeck.WS.status.TextSize = 22 - cyberdeck.WS.status.TextStyle = fyne.TextStyle{Bold: true} - - deckChoice := widget.NewSelect([]string{"Web Sockets (WS)", "Remote Procedure Calls (RPC)"}, nil) - - cyberdeck.RPC.toggle = widget.NewButton("Turn On", nil) - cyberdeck.RPC.toggle.OnTapped = func() { - switch session.Network { - case NETWORK_TESTNET: - if cyberdeck.RPC.portText.Validate() != nil { - cyberdeck.RPC.port = fmt.Sprintf("127.0.0.1:%d", DEFAULT_TESTNET_WALLET_PORT) - cyberdeck.RPC.portText.SetText(cyberdeck.RPC.port) - } else { - cyberdeck.RPC.port = cyberdeck.RPC.portText.Text - } - case NETWORK_SIMULATOR: - if cyberdeck.RPC.portText.Validate() != nil { - cyberdeck.RPC.port = fmt.Sprintf("127.0.0.1:%d", DEFAULT_SIMULATOR_WALLET_PORT) - cyberdeck.RPC.portText.SetText(cyberdeck.RPC.port) - } else { - cyberdeck.RPC.port = cyberdeck.RPC.portText.Text - } - default: - if cyberdeck.RPC.portText.Validate() != nil { - cyberdeck.RPC.port = fmt.Sprintf("127.0.0.1:%d", DEFAULT_WALLET_PORT) - cyberdeck.RPC.portText.SetText(cyberdeck.RPC.port) - } else { - cyberdeck.RPC.port = cyberdeck.RPC.portText.Text - } - } - - toggleRPCServer(cyberdeck.RPC.port) - if cyberdeck.RPC.server != nil { - setCyberdeck(cyberdeck.RPC.port, "RPC") - deckChoice.Disable() - cyberdeck.RPC.portText.Disable() - } else { - deckChoice.Enable() - cyberdeck.RPC.portText.Enable() - } - } - - if cyberdeck.WS.portText == nil { - cyberdeck.WS.portText = widget.NewEntry() - cyberdeck.WS.portText.PlaceHolder = "0.0.0.0:44326" - cyberdeck.WS.portText.Validator = func(s string) (err error) { - regex := `^(?:[a-zA-Z0-9]{1,62}(?:[-\.][a-zA-Z0-9]{1,62})+)(:\d+)?$` - test := regexp.MustCompile(regex) - if test.MatchString(s) { - cyberdeck.WS.portText.SetValidationError(nil) - } else { - err = errors.New("invalid host name") - cyberdeck.WS.portText.SetValidationError(err) - } - - return - } - } - - cyberdeck.WS.toggle = widget.NewButton("Turn On", nil) - cyberdeck.WS.toggle.OnTapped = func() { - if cyberdeck.WS.portText.Validate() != nil { - cyberdeck.WS.port = fmt.Sprintf("127.0.0.1:%d", xswd.XSWD_PORT) - cyberdeck.WS.portText.SetText(cyberdeck.WS.port) - } else { - _, err := net.ResolveTCPAddr("tcp", cyberdeck.WS.port) - if err != nil { - logger.Errorf("[Cyberdeck] XSWD port: %s\n", err) - cyberdeck.WS.port = fmt.Sprintf("127.0.0.1:%d", xswd.XSWD_PORT) - cyberdeck.WS.portText.SetText(cyberdeck.WS.port) - } else { - cyberdeck.WS.port = cyberdeck.WS.portText.Text - } - } - - cyberdeck.EPOCH.err = nil - toggleXSWD(cyberdeck.WS.port) - if cyberdeck.WS.server != nil { - setCyberdeck(cyberdeck.WS.port, "WS") - cyberdeck.WS.portText.Disable() - deckChoice.Disable() - if cyberdeck.EPOCH.enabled { - /* - if cyberdeck.EPOCH.allowWithAddress { - // If address is defined by dApp, GetWork will be started and stopped upon each WS call - logger.Printf("[EPOCH] dApp addresses are enabled\n") - return - } - */ - - err := epoch.StartGetWork(engram.Disk.GetAddress().String(), session.Daemon) - if err != nil { - logger.Errorf("[EPOCH] Connecting: %s\n", err) - cyberdeck.EPOCH.err = err - } else { - cyberdeck.EPOCH.err = nil - setCyberdeck(epoch.GetPort(), "EPOCH") - } - } - } else { - stopEPOCH() - cyberdeck.WS.portText.Enable() - deckChoice.Enable() - } - } - - if session.Offline { - cyberdeck.RPC.toggle.Text = "Disabled in Offline Mode" - cyberdeck.RPC.toggle.Disable() - cyberdeck.RPC.portText.Disable() - cyberdeck.WS.toggle.Text = "Disabled in Offline Mode" - cyberdeck.WS.toggle.Disable() - cyberdeck.WS.portText.Disable() - } else { - if cyberdeck.RPC.server != nil { - cyberdeck.RPC.status.Text = "Allowed" - cyberdeck.RPC.status.Color = colors.Green - cyberdeck.RPC.toggle.Text = "Turn Off" - cyberdeck.RPC.userText.Disable() - cyberdeck.RPC.passText.Disable() - cyberdeck.RPC.portText.Disable() - deckChoice.Disable() - } else { - cyberdeck.RPC.status.Text = "Blocked" - cyberdeck.RPC.status.Color = colors.Gray - cyberdeck.RPC.toggle.Text = "Turn On" - cyberdeck.RPC.userText.Enable() - cyberdeck.RPC.passText.Enable() - cyberdeck.RPC.portText.Enable() - } - - if cyberdeck.WS.server != nil { - cyberdeck.WS.status.Text = "Allowed" - cyberdeck.WS.status.Color = colors.Green - cyberdeck.WS.toggle.Text = "Turn Off" - cyberdeck.WS.portText.Disable() - deckChoice.Disable() - } else { - cyberdeck.WS.status.Text = "Blocked" - cyberdeck.WS.status.Color = colors.Gray - cyberdeck.WS.toggle.Text = "Turn On" - cyberdeck.WS.portText.Enable() - } - } - - cyberdeck.RPC.userText.SetText(cyberdeck.RPC.user) - cyberdeck.RPC.passText.SetText(cyberdeck.RPC.pass) - - linkCopy := widget.NewHyperlinkWithStyle("Copy Credentials", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkCopy.OnTapped = func() { - a.Clipboard().SetContent(cyberdeck.RPC.user + ":" + cyberdeck.RPC.pass) - } - - linkPermissions := widget.NewHyperlinkWithStyle("Settings", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkPermissions.OnTapped = func() { - //if cyberdeck.WS.server != nil { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutXSWDPermissions()) - removeOverlays() - //} - } - - /* - linkApps := widget.NewHyperlinkWithStyle("View Connections", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkApps.OnTapped = func() { - if cyberdeck.WS.server != nil { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutXSWDConnections()) - removeOverlays() - } - } - */ - - cyberdeck.WS.list = widget.NewList( - func() int { - return len(cyberdeck.WS.apps) - }, - func() fyne.CanvasObject { - return container.NewVBox( - widget.NewLabel(""), - //widget.NewLabel(""), - ) - }, - func(li widget.ListItemID, co fyne.CanvasObject) { - app := cyberdeck.WS.apps[li] - - fyne.Do(func() { - co.(*fyne.Container).Objects[0].(*widget.Label).SetText(app.Name) - //co.(*fyne.Container).Objects[1].(*widget.Label).SetText(app.Id) - }) - }, - ) - - cyberdeck.WS.list.OnSelected = func(id widget.ListItemID) { - cyberdeck.WS.list.UnselectAll() - cyberdeck.WS.list.FocusLost() - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutXSWDAppManager(&cyberdeck.WS.apps[id])) - removeOverlays() - } - - xswdForm := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - wsLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - container.NewCenter( - layout.NewSpacer(), - container.NewCenter( - container.NewVBox( - rectWidth90, - rectSpacer, - container.NewCenter( - cyberdeck.WS.status, - ), - rectSpacer, - serverStatus, - wSpacer, - cyberdeck.WS.toggle, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkPermissions, - layout.NewSpacer(), - ), - ), - ), - ), - container.NewStack( - rectWidth90, - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - labelConnections, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - container.NewStack( - rect, - cyberdeck.WS.list, - ), - ), - ), - ), - layout.NewSpacer(), - ) - - rpcForm := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - rpcLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - container.NewCenter( - layout.NewSpacer(), - container.NewCenter( - container.NewVBox( - rectWidth90, - rectSpacer, - linkCenter, - rectSpacer, - serverStatus, - wSpacer, - cyberdeck.RPC.toggle, - wSpacer, - cyberdeck.RPC.portText, - rectSpacer, - cyberdeck.RPC.userText, - rectSpacer, - cyberdeck.RPC.passText, - wSpacer, - container.NewHBox( - layout.NewSpacer(), - linkCopy, - layout.NewSpacer(), - ), - ), - ), - layout.NewSpacer(), - ), - ) - - deckFeatures := container.NewStack() - if cyberdeck.RPC.server != nil { - deckFeatures.Add(rpcForm) - deckChoice.SetSelectedIndex(1) - } else { - deckFeatures.Add(xswdForm) - deckChoice.SetSelectedIndex(0) - } - - deckChoice.OnChanged = func(s string) { - if s == "Remote Procedure Calls (RPC)" { - deckFeatures.Objects[0] = rpcForm - } else { - deckFeatures.Objects[0] = xswdForm - } - } - - deckForm := container.NewVScroll( - container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - container.NewVBox( - title, - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - container.NewStack( - rectWidth90, - deckChoice, - ), - ), - container.NewBorder( - deckFeatures, - nil, - nil, - nil, - ), - ), - ), - ) - - deckForm.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.80)) - - session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { - if k.Name == fyne.KeyLeft { - session.Dashboard = "main" - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } - }) - - subContainer := container.NewStack( - container.NewVBox( - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - c := container.NewBorder( - deckForm, - subContainer, - nil, - nil, - ) - - layout := container.NewStack( - frame, - c, - ) - - return NewVScroll(layout) -} - -// Layout details of an app connected through web socket -func layoutXSWDAppManager(ad *xswd.ApplicationData) fyne.CanvasObject { - session.Domain = "app.cyberdeck.manager" - - frame := &iframe{} - - rectBox := canvas.NewRectangle(color.Transparent) - rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.58)) - - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - labelName := widget.NewRichText(&widget.TextSegment{ - Text: ad.Name, - Style: widget.RichTextStyle{ - Alignment: fyne.TextAlignCenter, - ColorName: theme.ColorNameForeground, - SizeName: theme.SizeNameHeadingText, - TextStyle: fyne.TextStyle{Bold: true}, - }}) - labelName.Wrapping = fyne.TextWrapWord - - labelDesc := widget.NewRichText(&widget.TextSegment{ - Text: ad.Description, - Style: widget.RichTextStyle{ - Alignment: fyne.TextAlignCenter, - ColorName: theme.ColorNameForeground, - TextStyle: fyne.TextStyle{Bold: false}, - }}) - labelDesc.Wrapping = fyne.TextWrapWord - - labelID := canvas.NewText(" APP ID", colors.Gray) - labelID.TextSize = 14 - labelID.Alignment = fyne.TextAlignLeading - labelID.TextStyle = fyne.TextStyle{Bold: true} - - textID := widget.NewRichTextFromMarkdown(ad.Id) - textID.Wrapping = fyne.TextWrapWord - - labelSignature := canvas.NewText(" SIGNATURE", colors.Gray) - labelSignature.TextSize = 14 - labelSignature.Alignment = fyne.TextAlignLeading - labelSignature.TextStyle = fyne.TextStyle{Bold: true} - - textSignature := widget.NewRichTextFromMarkdown("") - textSignature.Wrapping = fyne.TextWrapWord - - labelURL := canvas.NewText(" URL", colors.Gray) - labelURL.TextSize = 14 - labelURL.Alignment = fyne.TextAlignLeading - labelURL.TextStyle = fyne.TextStyle{Bold: true} - - textURL := widget.NewRichTextFromMarkdown(ad.Url) - textURL.Wrapping = fyne.TextWrapWord - - labelPermissions := canvas.NewText(" PERMISSIONS", colors.Gray) - labelPermissions.TextSize = 14 - labelPermissions.Alignment = fyne.TextAlignLeading - labelPermissions.TextStyle = fyne.TextStyle{Bold: true} - - labelEvents := canvas.NewText(" EVENTS", colors.Gray) - labelEvents.TextSize = 14 - labelEvents.Alignment = fyne.TextAlignLeading - labelEvents.TextStyle = fyne.TextStyle{Bold: true} - - labelSeparator := widget.NewRichTextFromMarkdown("") - labelSeparator.Wrapping = fyne.TextWrapOff - labelSeparator.ParseMarkdown("---") - labelSeparator2 := widget.NewRichTextFromMarkdown("") - labelSeparator2.Wrapping = fyne.TextWrapOff - labelSeparator2.ParseMarkdown("---") - labelSeparator3 := widget.NewRichTextFromMarkdown("") - labelSeparator3.Wrapping = fyne.TextWrapOff - labelSeparator3.ParseMarkdown("---") - labelSeparator4 := widget.NewRichTextFromMarkdown("") - labelSeparator4.Wrapping = fyne.TextWrapOff - labelSeparator4.ParseMarkdown("---") - labelSeparator5 := widget.NewRichTextFromMarkdown("") - labelSeparator5.Wrapping = fyne.TextWrapOff - labelSeparator5.ParseMarkdown("---") - labelSeparator6 := widget.NewRichTextFromMarkdown("") - labelSeparator6.Wrapping = fyne.TextWrapOff - labelSeparator6.ParseMarkdown("---") - - signatureItems := container.NewVBox( - labelSeparator2, - rectSpacer, - rectSpacer, - labelSignature, - textSignature, - rectSpacer, - rectSpacer, - ) - - // Show signature result if one exists - signatureItems.Hide() - if len(ad.Signature) > 0 { - signatureItems.Show() - _, message, err := engram.Disk.CheckSignature(ad.Signature) - if err != nil { - textSignature.ParseMarkdown(err.Error()) - } else { - textSignature.ParseMarkdown(strings.TrimSpace(string(message))) - } - } - - // Find Permissions for connected app and build UI object - var methods []string - for k := range ad.Permissions { - methods = append(methods, k) - } - - permissionItems := container.NewVBox() - - permissions := []string{ - xswd.Ask.String(), - xswd.Allow.String(), - xswd.Deny.String(), - xswd.AlwaysAllow.String(), - xswd.AlwaysDeny.String(), - } - - if len(methods) > 0 { - sort.Strings(methods) - for _, name := range methods { - permission := widget.NewSelect(permissions, nil) - permission.SetSelected(ad.Permissions[name].String()) - permission.Disable() - permissionItems.Add(container.NewBorder(nil, nil, widget.NewRichTextFromMarkdown("### "+name), permission)) - } - } else { - permissionItems.Add(container.NewBorder(nil, nil, widget.NewRichTextFromMarkdown("No Permissions"), nil)) - } - - // Find RegisteredEvents for connected app and build UI object - var events []rpc.EventType - for k := range ad.RegisteredEvents { - events = append(events, k) - } - - eventItems := container.NewVBox() - - if len(events) > 0 { - sort.Slice(events, func(i, j int) bool { return events[i] < events[j] }) - for _, name := range events { - event := widget.NewSelect([]string{"false", "true"}, nil) - event.SetSelected(strconv.FormatBool(ad.RegisteredEvents[name])) - event.Disable() - eventItems.Add(container.NewBorder(nil, nil, widget.NewRichTextFromMarkdown(fmt.Sprintf("### %s", name)), event)) - } - } else { - eventItems.Add(container.NewBorder(nil, nil, widget.NewRichTextFromMarkdown("No Events"), nil)) - } - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to Cyberdeck", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - removeOverlays() - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutCyberdeck()) - } - - image := canvas.NewImageFromResource(resourceWebsocketPng) - image.SetMinSize(fyne.NewSize(ui.Width*0.25, ui.Width*0.25)) - image.FillMode = canvas.ImageFillContain - - // Check if the application is TELA - telaURL := "http://localhost" - if strings.HasPrefix(ad.Url, telaURL) { - for _, serv := range tela.GetServerInfo() { - if strings.HasPrefix(ad.Url, telaURL+serv.Address) { - name, _, icon, _, _ := getContractHeader(crypto.HashHexToHash(serv.SCID)) - if icon != "" { - if img, err := handleImageURL(name, icon, fyne.NewSize(ui.Width*0.25, ui.Width*0.25)); err == nil { - image = img - } else { - logger.Errorf("[Engram] Could not validate icon image: %s\n", err) - } - } - - break - } - } - } - - linkURL := widget.NewHyperlinkWithStyle("Open in browser", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - linkURL.OnTapped = func() { - link, err := url.Parse(ad.Url) - if err != nil { - logger.Errorf("[Engram] Error parsing XSWD application URL: %s\n", err) - return - } - _ = fyne.CurrentApp().OpenURL(link) - } - - btnRemove := widget.NewButton("Remove", nil) - btnRemove.OnTapped = func() { - if cyberdeck.WS.server != nil && len(cyberdeck.WS.apps) > 0 { - cyberdeck.WS.server.RemoveApplication(ad) - removeOverlays() - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutCyberdeck()) - } - } - - center := container.NewStack( - rectBox, - container.NewVScroll( - container.NewStack( - rectWidth90, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - container.NewHBox( - layout.NewSpacer(), - image, - layout.NewSpacer(), - ), - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - labelName, - ), - layout.NewSpacer(), - ), - labelDesc, - rectSpacer, - rectSpacer, - labelSeparator, - rectSpacer, - rectSpacer, - labelID, - textID, - rectSpacer, - rectSpacer, - signatureItems, - labelSeparator3, - rectSpacer, - rectSpacer, - labelURL, - rectSpacer, - textURL, - container.NewHBox( - layout.NewSpacer(), - ), - container.NewHBox( - linkURL, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator4, - rectSpacer, - rectSpacer, - labelPermissions, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - ), - permissionItems, - rectSpacer, - rectSpacer, - labelSeparator5, - rectSpacer, - rectSpacer, - labelEvents, - rectSpacer, - eventItems, - container.NewStack( - rectWidth90, - ), - rectSpacer, - rectSpacer, - labelSeparator6, - rectSpacer, - rectSpacer, - btnRemove, - rectSpacer, - rectSpacer, - ), - layout.NewSpacer(), - ), - ), - ), - rectSpacer, - rectSpacer, - ) - - top := container.NewVBox( - rectSpacer, - rectSpacer, - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewStack( - frame, - container.NewBorder( - top, - bottom, - nil, - center, - ), - ) - - return NewVScroll(layout) -} - -// Layout XSWD permissions settings -func layoutXSWDPermissions() fyne.CanvasObject { - session.Domain = "app.cyberdeck.permissions" - - wSpacer := widget.NewLabel(" ") - - frame := &iframe{} - - rect := canvas.NewRectangle(color.Transparent) - rect.SetMinSize(fyne.NewSize(ui.Width, 20)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 0)) - - title := canvas.NewText("G L O B A L P E R M I S S I O N S", colors.Gray) - title.TextStyle = fyne.TextStyle{Bold: true} - title.TextSize = 16 - - xswdLabel := canvas.NewText("W E B S O C K E T S", colors.Gray) - xswdLabel.TextSize = 11 - xswdLabel.Alignment = fyne.TextAlignCenter - xswdLabel.TextStyle = fyne.TextStyle{Bold: true} - - labelMethods := canvas.NewText(" METHODS", colors.Gray) - labelMethods.TextSize = 14 - labelMethods.Alignment = fyne.TextAlignLeading - labelMethods.TextStyle = fyne.TextStyle{Bold: true} - - labelConnection := canvas.NewText(" CONNECTIONS", colors.Gray) - labelConnection.TextSize = 14 - labelConnection.Alignment = fyne.TextAlignLeading - labelConnection.TextStyle = fyne.TextStyle{Bold: true} - - labelEpoch := canvas.NewText(" EPOCH", colors.Gray) - labelEpoch.TextSize = 14 - labelEpoch.Alignment = fyne.TextAlignLeading - labelEpoch.TextStyle = fyne.TextStyle{Bold: true} - - permissionInfo := canvas.NewText("APPLY ON CONNECTION", colors.Gray) - permissionInfo.TextSize = 12 - permissionInfo.Alignment = fyne.TextAlignCenter - permissionInfo.TextStyle = fyne.TextStyle{Bold: true} - - btnDefaults := widget.NewButton("Restore Defaults", nil) - - wMode := widget.NewCheck("Restrictive Mode", nil) - - wConnection := widget.NewSelect([]string{xswd.Ask.String(), xswd.Allow.String()}, nil) - - wGlobalPermissions := widget.NewSelect([]string{"Off", "Apply"}, nil) - - wEpoch := widget.NewSelect([]string{xswd.Deny.String(), xswd.Allow.String()}, nil) - - wEpochAddress := widget.NewSelect([]string{"My Address", "dApp Chooses"}, nil) - - if cyberdeck.EPOCH.enabled { - wEpoch.SetSelectedIndex(1) - } else { - wEpoch.SetSelectedIndex(0) - wEpochAddress.Disable() - } - - if cyberdeck.EPOCH.allowWithAddress { - wEpochAddress.SetSelectedIndex(1) - } else { - wEpochAddress.SetSelectedIndex(0) - } - - wEpoch.OnChanged = func(s string) { - if s == xswd.Allow.String() { - cyberdeck.EPOCH.enabled = true - wEpochAddress.Enable() - return - } - - cyberdeck.EPOCH.enabled = false - wEpochAddress.SetSelectedIndex(0) - wEpochAddress.Disable() - } - - /* - wEpochAddress.OnChanged = func(s string) { - if s == "dApp Chooses" { - cyberdeck.EPOCH.allowWithAddress = true - return - } - - cyberdeck.EPOCH.allowWithAddress = false - } - */ - - cyberdeck.EPOCH.enabled = true - cyberdeck.EPOCH.allowWithAddress = true - - spacerEpoch := canvas.NewRectangle(color.Transparent) - spacerEpoch.SetMinSize(fyne.NewSize(140, 0)) - - entryEpochWork := widget.NewEntry() - entryEpochWork.SetPlaceHolder(":10100") - entryEpochWork.SetText(epoch.GetPort()) - entryEpochWork.Validator = func(s string) (err error) { - i, err := strconv.Atoi(s) - if err != nil { - return fmt.Errorf("invalid port") - } - - return epoch.SetPort(i) - } - - entryEpochHash := widget.NewEntry() - entryEpochHash.SetPlaceHolder("Max hashes") - entryEpochHash.SetText(strconv.Itoa(epoch.GetMaxHashes())) - entryEpochHash.Validator = func(s string) (err error) { - i, err := strconv.Atoi(s) - if err != nil { - return fmt.Errorf("invalid hash value") - } - - return epoch.SetMaxHashes(i) - } - - wEpochPower := widget.NewSelect([]string{"Less", "More"}, nil) - wEpochPower.SetSelectedIndex(0) - if epoch.GetMaxThreads() > 2 { - wEpochPower.SetSelectedIndex(1) - } - - wEpochPower.OnChanged = func(s string) { - if s == "More" { - half := runtime.NumCPU() / 2 - if half > epoch.DEFAULT_MAX_THREADS { - epoch.SetMaxThreads(half) - } - - return - } - - epoch.SetMaxThreads(epoch.DEFAULT_MAX_THREADS) - } - - if session.Offline { - wMode.Disable() - wEpoch.Disable() - wEpochAddress.Disable() - entryEpochWork.Disable() - entryEpochHash.Disable() - wEpochPower.Disable() - } else if cyberdeck.WS.server != nil { - wEpoch.Disable() - wEpochAddress.Disable() - entryEpochWork.Disable() - entryEpochHash.Disable() - wEpochPower.Disable() - } - - if cyberdeck.WS.advanced { - wMode.SetChecked(false) - if cyberdeck.WS.global.enabled { - wGlobalPermissions.SetSelectedIndex(1) - if cyberdeck.WS.global.connect { - wConnection.SetSelectedIndex(1) - } else { - wConnection.SetSelectedIndex(0) - } - } else { - wGlobalPermissions.SetSelectedIndex(0) - wConnection.SetSelectedIndex(0) - wConnection.Disable() - btnDefaults.Disable() - } - } else { - wMode.SetChecked(true) - wConnection.SetSelectedIndex(0) - wConnection.Disable() - wGlobalPermissions.SetSelectedIndex(0) - wGlobalPermissions.Disable() - btnDefaults.Disable() - } - - wMode.OnChanged = func(b bool) { - cyberdeck.WS.advanced = !b // inverse as check box is for restrictive mode on/off - if cyberdeck.WS.advanced { - wGlobalPermissions.Enable() - } else { - wGlobalPermissions.SetSelectedIndex(0) // calling this here resets and disables wConnection - wGlobalPermissions.Disable() - } - } - - wConnection.OnChanged = func(s string) { - if s == xswd.Allow.String() { - cyberdeck.WS.global.connect = true - } else { - cyberdeck.WS.global.connect = false - } - } - - formItems := container.NewVBox() - - permissions := []string{ - xswd.Ask.String(), - xswd.AlwaysAllow.String(), - xswd.AlwaysDeny.String(), - } - - noStorePermissions := []string{ - xswd.Ask.String(), - xswd.AlwaysDeny.String(), - } - - // Permissions select on changed func - onChanged := func(n string) func(s string) { - return func(s string) { - cyberdeck.WS.Lock() - defer cyberdeck.WS.Unlock() - - switch s { - case xswd.Ask.String(): - cyberdeck.WS.global.permissions[n] = xswd.Ask - case xswd.AlwaysAllow.String(): - cyberdeck.WS.global.permissions[n] = xswd.AlwaysAllow - case xswd.AlwaysDeny.String(): - cyberdeck.WS.global.permissions[n] = xswd.AlwaysDeny - default: - cyberdeck.WS.global.permissions[n] = xswd.Ask - } - } - } - - stored, methods := getPermissions() - for _, name := range methods { - n := name - permission := widget.NewSelect([]string{}, nil) - if engramCanStoreMethod(n) { - permission.SetOptions(permissions) - } else { - permission.SetOptions(noStorePermissions) - } - - if cyberdeck.WS.global.enabled { - permission.SetSelected(stored[n].String()) - permission.OnChanged = onChanged(n) - } else { - permission.SetSelectedIndex(0) - permission.Disable() - } - formItems.Add(container.NewBorder(nil, nil, widget.NewRichTextFromMarkdown("### "+n), permission)) - } - - statusText := "Disabled" - statusColor := colors.Gray - if cyberdeck.WS.global.enabled { - statusText = "Enabled" - statusColor = colors.Green - } - - cyberdeck.WS.global.status = canvas.NewText(statusText, statusColor) - cyberdeck.WS.global.status.TextSize = 22 - cyberdeck.WS.global.status.TextStyle = fyne.TextStyle{Bold: true} - - btnDefaults.OnTapped = func() { - if !cyberdeck.WS.global.enabled { - return - } - - header := canvas.NewText("RESTORE DEFAULT PERMISSIONS", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - subHeader := canvas.NewText("Are you sure?", colors.Account) - subHeader.TextSize = 22 - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - - linkCancel := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkCancel.OnTapped = func() { - removeOverlays() - } - - btnSubmit := widget.NewButton("Restore Defaults", nil) - btnSubmit.OnTapped = func() { - wConnection.SetSelectedIndex(0) - for _, obj := range formItems.Objects { - obj.(*fyne.Container).Objects[1].(*widget.Select).SetSelectedIndex(0) - } - removeOverlays() - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay := session.Window.Canvas().Overlays() - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - subHeader, - widget.NewLabel(""), - btnSubmit, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkCancel, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - } - - wGlobalPermissions.OnChanged = func(s string) { - if s != "Apply" { - setPermissions() - btnDefaults.Disable() - cyberdeck.WS.global.status.Text = "Disabled" - cyberdeck.WS.global.status.Color = colors.Gray - cyberdeck.WS.global.status.Refresh() - cyberdeck.WS.global.enabled = false - wConnection.SetSelectedIndex(0) - wConnection.Disable() - for _, obj := range formItems.Objects { - obj.(*fyne.Container).Objects[1].(*widget.Select).OnChanged = nil - obj.(*fyne.Container).Objects[1].(*widget.Select).SetSelectedIndex(0) - obj.(*fyne.Container).Objects[1].(*widget.Select).Disable() - } - } else { - cyberdeck.WS.global.status.Text = "Enabled" - cyberdeck.WS.global.status.Color = colors.Green - cyberdeck.WS.global.status.Refresh() - cyberdeck.WS.global.enabled = true - wConnection.Enable() - btnDefaults.Enable() - go func() { - stored, _ := getPermissions() - for _, obj := range formItems.Objects { - fyne.Do(func() { - name := obj.(*fyne.Container).Objects[0].(*widget.RichText).String() - obj.(*fyne.Container).Objects[1].(*widget.Select).SetSelected(stored[name].String()) - obj.(*fyne.Container).Objects[1].(*widget.Select).Enable() - obj.(*fyne.Container).Objects[1].(*widget.Select).OnChanged = onChanged(name) - }) - } - }() - } - } - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to Cyberdeck", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - setPermissions() - removeOverlays() - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutCyberdeck()) - } - - // Initialized in layoutCyberdeck() - cyberdeck.WS.portText.SetText(getCyberdeck("WS")) - - center := container.NewVScroll( - container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - container.NewVBox( - title, - rectSpacer, - ), - ), - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - xswdLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - container.NewCenter( - container.NewVBox( - rectWidth90, - rectSpacer, - container.NewCenter( - cyberdeck.WS.global.status, - ), - rectSpacer, - container.NewCenter( - permissionInfo, - ), - ), - ), - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewCenter( - container.NewVBox( - container.NewBorder( - nil, - nil, - nil, - nil, - container.NewCenter(wMode), - ), - rectSpacer, - cyberdeck.WS.portText, - rectSpacer, - labelConnection, - rectSpacer, - container.NewBorder( - nil, - nil, - widget.NewRichTextFromMarkdown("### Type"), - wConnection, - ), - container.NewBorder( - nil, - nil, - widget.NewRichTextFromMarkdown("### Global Permissions"), - wGlobalPermissions, - ), - wSpacer, - labelEpoch, - rectSpacer, - /* - container.NewBorder( - nil, - nil, - widget.NewRichTextFromMarkdown("### Preference"), - wEpoch, - ), - container.NewBorder( - nil, - nil, - widget.NewRichTextFromMarkdown("### Reward Address"), - wEpochAddress, - ), - */ - container.NewBorder( - nil, - nil, - widget.NewRichTextFromMarkdown("### Get Work"), - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - spacerEpoch, - entryEpochWork, - ), - ), - ), - container.NewBorder( - nil, - nil, - widget.NewRichTextFromMarkdown("### Max Hashes"), - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - spacerEpoch, - entryEpochHash, - ), - ), - ), - container.NewBorder( - nil, - nil, - widget.NewRichTextFromMarkdown("### Power"), - wEpochPower, - ), - wSpacer, - labelMethods, - rectSpacer, - container.NewCenter( - formItems, - ), - wSpacer, - ), - ), - layout.NewSpacer(), - ), - container.NewCenter( - container.NewVBox( - btnDefaults, - rectWidth90, - ), - ), - wSpacer, - ), - ), - ) - center.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.80)) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewStack( - frame, - container.NewBorder( - center, - bottom, - nil, - nil, - ), - ) - - return NewVScroll(layout) -} - -func layoutIdentity() fyne.CanvasObject { - session.Domain = "app.Identity" - title := canvas.NewText("I D E N T I T Y", colors.Gray) - title.TextStyle = fyne.TextStyle{Bold: true} - title.TextSize = 16 - - heading := canvas.NewText("My Contacts", colors.Green) - heading.TextSize = 22 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - frame := &iframe{} - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - rectList := canvas.NewRectangle(color.Transparent) - rectList.SetMinSize(fyne.NewSize(ui.Width, 35)) - rectListBox := canvas.NewRectangle(color.Transparent) - rectListBox.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.44)) - - shortShard := canvas.NewText("PRIMARY USERNAME", colors.Gray) - shortShard.TextStyle = fyne.TextStyle{Bold: true} - shortShard.TextSize = 12 - - idCenter := container.NewCenter( - shortShard, - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } - - //entryReg := NewMobileEntry() - entryReg := widget.NewEntry() - entryReg.MultiLine = false - entryReg.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) - - userData, err := queryUsernames(engram.Disk.GetAddress().String()) - if err != nil { - userData, err = getUsernames() - if err != nil { - userData = nil - } - } - - userList := binding.BindStringList(&userData) - - btnReg := widget.NewButton(" Register ", nil) - btnReg.Disable() - btnReg.OnTapped = func() { - if len(session.NewUser) > 5 { - valid, _ := checkUsername(session.NewUser, -1) - if valid == "" { - btnReg.Text = "Confirming..." - btnReg.Disable() - btnReg.Refresh() - entryReg.Disable() - storage, err := registerUsername(session.NewUser) - if err != nil { - if strings.Contains(err.Error(), "somehow the tx could not be built") { - btnReg.Text = fmt.Sprintf("Insufficient Balance: Need %v", globals.FormatMoney(storage)) - } else { - btnReg.Text = "Unable to register..." - } - btnReg.Refresh() - logger.Errorf("[Username] %s\n", err) - } else { - go func() { - fyne.Do(func() { - entryReg.Text = "" - entryReg.Refresh() - }) - - walletapi.WaitNewHeightBlock() - sHeight := walletapi.Get_Daemon_Height() - - for { - if session.Domain == "app.Identity" { - //vars, _, _, err := gnomon.Index.RPC.GetSCVariables("0000000000000000000000000000000000000000000000000000000000000001", engram.Disk.Get_Daemon_TopoHeight(), nil, []string{session.NewUser}, nil, false) - usernames, err := queryUsernames(engram.Disk.GetAddress().String()) - if err != nil { - logger.Errorf("[Username] Error querying usernames: %s\n", err) - - fyne.Do(func() { - btnReg.Text = "Error querying usernames" - btnReg.Refresh() - }) - - return - } - - for u := range usernames { - if usernames[u] == session.NewUser { - logger.Printf("[Username] Successfully registered username: %s\n", session.NewUser) - _ = tx - - fyne.Do(func() { - btnReg.Text = "Registration successful!" - btnReg.Refresh() - session.NewUser = "" - session.Window.SetContent(layoutIdentity()) - }) - - return - } - } - - // If we go DEFAULT_CONFIRMATION_TIMEOUT blocks without exiting 'Confirming...' loop, display failed to transfer and break - if walletapi.Get_Daemon_Height() > sHeight+int64(DEFAULT_CONFIRMATION_TIMEOUT) { - fyne.Do(func() { - btnReg.Text = "Unable to register..." - btnReg.Refresh() - }) - - break - } - - // If daemon height has incremented, print retry counters into button space - if walletapi.Get_Daemon_Height()-sHeight > 0 { - fyne.Do(func() { - btnReg.Text = fmt.Sprintf("Confirming... (%d/%d)", walletapi.Get_Daemon_Height()-sHeight, DEFAULT_CONFIRMATION_TIMEOUT) - btnReg.Refresh() - }) - } - } else { - break - } - - time.Sleep(time.Second * 1) - } - }() - } - } - } - } - - entryReg.PlaceHolder = "New Username" - entryReg.Validator = func(s string) error { - btnReg.Text = " Register " - btnReg.Enable() - btnReg.Refresh() - session.NewUser = s - // Name Service SCID Logic - // 15 IF STRLEN(name) >= 64 THEN GOTO 50 // skip names misuse - // 20 IF STRLEN(name) >= 6 THEN GOTO 40 - if len(s) > 5 && len(s) < 64 { - valid, _ := checkUsername(s, -1) - if valid == "" { - btnReg.Enable() - btnReg.Refresh() - } else { - btnReg.Disable() - err := errors.New("username already exists") - entryReg.SetValidationError(err) - btnReg.Refresh() - return err - } - } else { - btnReg.Disable() - err := errors.New("username too short need a minimum of six characters") - entryReg.SetValidationError(err) - btnReg.Refresh() - return err - } - - return nil - } - - userBox := widget.NewListWithData(userList, - func() fyne.CanvasObject { - c := container.NewVBox( - widget.NewLabel(""), - ) - return c - }, - func(di binding.DataItem, co fyne.CanvasObject) { - dat := di.(binding.String) - str, err := dat.Get() - if err != nil { - return - } - - if len(str) > DEFAULT_USERADDR_SHORTEN_LENGTH+3 { - str = "..." + str[len(str)-DEFAULT_USERADDR_SHORTEN_LENGTH:] - } - - co.(*fyne.Container).Objects[0].(*widget.Label).SetText(str) - co.(*fyne.Container).Objects[0].(*widget.Label).Wrapping = fyne.TextWrapWord - co.(*fyne.Container).Objects[0].(*widget.Label).TextStyle.Bold = false - co.(*fyne.Container).Objects[0].(*widget.Label).Alignment = fyne.TextAlignLeading - }) - - err = getPrimaryUsername() - if err != nil { - session.Username = "" - } - - dispUsername := session.Username - if len(session.Username) > DEFAULT_USERADDR_SHORTEN_LENGTH+3 { - dispUsername = "..." + dispUsername[len(dispUsername)-DEFAULT_USERADDR_SHORTEN_LENGTH:] - } - - textUsername := canvas.NewText(dispUsername, colors.Green) - textUsername.TextStyle = fyne.TextStyle{Bold: true} - textUsername.TextSize = 22 - - if session.Username == "" { - textUsername.Text = "---" - textUsername.Refresh() - } /* else { - for u := range userData { - if userData[u] == session.Username { - userBox.Select(u) - userBox.ScrollTo(u) - } - } - }*/ - - userBox.OnSelected = func(id widget.ListItemID) { - overlay := session.Window.Canvas().Overlays() - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - overlay.Add(layoutIdentityDetail(userData[id])) - userBox.UnselectAll() - } - - shardForm := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - container.NewVBox( - title, - rectSpacer, - ), - ), - rectSpacer, - container.NewStack( - container.NewCenter( - textUsername, - ), - ), - rectSpacer, - idCenter, - rectSpacer, - rectSpacer, - container.NewStack( - rectListBox, - userBox, - ), - rectSpacer, - entryReg, - rectSpacer, - btnReg, - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ) - - gridItem1 := container.NewCenter( - shardForm, - ) - - features := container.NewCenter( - layout.NewSpacer(), - gridItem1, - layout.NewSpacer(), - ) - - session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { - if k.Name == fyne.KeyRight { - session.Dashboard = "main" - - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } else if k.Name == fyne.KeyF5 { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutIdentity()) - removeOverlays() - } - }) - - subContainer := container.NewStack( - container.NewVBox( - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - c := container.NewBorder( - features, - subContainer, - nil, - nil, - ) - - layout := container.NewStack( - frame, - c, - ) - - return NewVScroll(layout) -} - -func layoutIdentityDetail(username string) fyne.CanvasObject { - var address string - - wSpacer := widget.NewLabel(" ") - - rectWidth := canvas.NewRectangle(color.Transparent) - rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, 10)) - - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) - - frame := &iframe{} - - heading := canvas.NewText("I D E N T I T Y D E T A I L", colors.Gray) - heading.TextSize = 16 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - labelUsername := canvas.NewText("REGISTERED USERNAME", colors.Gray) - labelUsername.TextSize = 11 - labelUsername.Alignment = fyne.TextAlignCenter - labelUsername.TextStyle = fyne.TextStyle{Bold: true} - - labelTransfer := canvas.NewText(" T R A N S F E R ", colors.Gray) - labelTransfer.TextSize = 11 - labelTransfer.Alignment = fyne.TextAlignCenter - labelTransfer.TextStyle = fyne.TextStyle{Bold: true} - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to Identity", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - removeOverlays() - } - - valueUsername := canvas.NewText(username, colors.Green) - valueUsername.TextSize = 22 - valueUsername.TextStyle = fyne.TextStyle{Bold: true} - valueUsername.Alignment = fyne.TextAlignCenter - - btnSetPrimary := widget.NewButton("Set Primary Username", nil) - btnSetPrimary.OnTapped = func() { - setPrimaryUsername(username) - session.Username = username - //session.Window.SetContent(layoutIdentity()) - removeOverlays() - } - - btnSend := widget.NewButton("Transfer Username", nil) - - inputAddress := widget.NewEntry() - inputAddress.PlaceHolder = "Receiver Username or Address" - inputAddress.Validator = func(s string) error { - btnSend.Text = "Transfer Username" - btnSend.Enable() - btnSend.Refresh() - address, _ = checkUsername(s, -1) - if address == "" { - _, err := globals.ParseValidateAddress(s) - if err != nil { - btnSend.Disable() - btnSend.Refresh() - err := errors.New("address does not exist") - inputAddress.SetValidationError(err) - inputAddress.Refresh() - return err - } else { - btnSend.Enable() - btnSend.Refresh() - address = s - } - } else { - btnSend.Enable() - btnSend.Refresh() - } - - return nil - } - - btnSend.OnTapped = func() { - if address != "" && address != engram.Disk.GetAddress().String() { - btnSend.Text = "Setting up transfer..." - btnSend.Disable() - btnSend.Refresh() - inputAddress.Disable() - inputAddress.Refresh() - btnSetPrimary.Disable() - storage, err := transferUsername(username, address) - if err != nil { - address = "" - if strings.Contains(err.Error(), "somehow the tx could not be built") { - btnSend.Text = fmt.Sprintf("Insufficient Balance: Need %v", globals.FormatMoney(storage)) - } else { - btnSend.Text = "Transfer failed..." - } - btnSend.Disable() - btnSend.Refresh() - inputAddress.Enable() - inputAddress.Refresh() - btnSetPrimary.Enable() - } else { - btnSend.Text = "Confirming..." - btnSend.Refresh() - go func() { - walletapi.WaitNewHeightBlock() - sHeight := walletapi.Get_Daemon_Height() - - for { - found := false - if session.Domain == "app.Identity" { - usernames, err := queryUsernames(engram.Disk.GetAddress().String()) - if err != nil { - logger.Errorf("[Username] Error querying usernames: %s\n", err) - fyne.Do(func() { - btnSend.Text = "Error querying usernames" - btnSend.Refresh() - btnSetPrimary.Enable() - }) - - return - } - - for u := range usernames { - if usernames[u] == username { - found = true - } - } - - if !found { - logger.Printf("[TransferOwnership] %s was successfully transferred to: %s\n", username, address) - fyne.Do(func() { - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutIdentity()) - removeOverlays() - }) - - break - } - - // If we go DEFAULT_CONFIRMATION_TIMEOUT blocks without exiting 'Confirming...' loop, display failed to transfer and break - if walletapi.Get_Daemon_Height() > sHeight+int64(DEFAULT_CONFIRMATION_TIMEOUT) { - logger.Errorf("[TransferOwnership] %s was unsuccessful in transferring to: %s\n", username, address) - fyne.Do(func() { - btnSend.Text = "Unable to transfer..." - btnSend.Refresh() - btnSetPrimary.Enable() - }) - - break - } - - // If daemon height has incremented, print retry counters into button space - if walletapi.Get_Daemon_Height()-sHeight > 0 { - fyne.Do(func() { - btnSend.Text = fmt.Sprintf("Confirming... (%d/%d)", walletapi.Get_Daemon_Height()-sHeight, DEFAULT_CONFIRMATION_TIMEOUT) - btnSend.Refresh() - }) - } - } else { - break - } - - time.Sleep(time.Second * 1) - } - }() - } - } - } - - top := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - heading, - ), - rectSpacer, - rectSpacer, - ) - - center := container.NewStack( - container.NewVScroll( - container.NewStack( - rectWidth, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - rectSpacer, - valueUsername, - rectSpacer, - labelUsername, - wSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - btnSetPrimary, - ), - layout.NewSpacer(), - ), - wSpacer, - container.NewStack( - rectWidth, - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - labelTransfer, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - wSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - inputAddress, - ), - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - btnSend, - ), - layout.NewSpacer(), - ), - ), - layout.NewSpacer(), - ), - ), - ), - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewStack( - frame, - container.NewBorder( - top, - bottom, - nil, - center, - ), - ) - - return layout -} - -func layoutWaiting(title *canvas.Text, heading *canvas.Text, sub *canvas.Text, link *widget.Hyperlink) fyne.CanvasObject { - rect := canvas.NewRectangle(color.Transparent) - rect.SetMinSize(fyne.NewSize(ui.Width*0.6, ui.Height*0.35)) - rect2 := canvas.NewRectangle(color.Transparent) - rect2.SetMinSize(fyne.NewSize(ui.Width, 1)) - frame := canvas.NewRectangle(color.Transparent) - frame.SetMinSize(fyne.NewSize(ui.Width, ui.Height)) - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - label := canvas.NewText("PROOF-OF-WORK", colors.Gray) - label.TextStyle = fyne.TextStyle{Bold: true} - label.TextSize = 12 - hashes := canvas.NewText(fmt.Sprintf("%d", session.RegHashes), colors.Account) - hashes.TextSize = 18 - - go func() { - for engram.Disk != nil { - fyne.Do(func() { - hashes.Text = fmt.Sprintf("%d", session.RegHashes) - hashes.Refresh() - }) - } - }() - - session.Gif, _ = x.NewAnimatedGifFromResource(resourceAnimation2Gif) - session.Gif.SetMinSize(rect.MinSize()) - session.Gif.Resize(rect.MinSize()) - session.Gif.Start() - - waitForm := container.NewVBox( - widget.NewLabel(""), - container.NewHBox( - layout.NewSpacer(), - title, - layout.NewSpacer(), - ), - widget.NewLabel(""), - heading, - rectSpacer, - sub, - widget.NewLabel(""), - container.NewStack( - session.Gif, - ), - widget.NewLabel(""), - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - container.NewCenter( - rect2, - hashes, - ), - rectSpacer, - container.NewCenter( - rect2, - label, - ), - ), - layout.NewSpacer(), - ), - ) - - grid := container.NewHBox( - layout.NewSpacer(), - waitForm, - layout.NewSpacer(), - ) - - footer := container.NewVBox( - container.NewHBox( - layout.NewSpacer(), - link, - layout.NewSpacer(), - ), - widget.NewLabel(""), - ) - - c := container.NewBorder( - grid, - footer, - nil, - nil, - ) - - layout := container.NewStack( - frame, - c, - ) - - return NewVScroll(layout) -} - -func layoutAlert(t int) fyne.CanvasObject { - rect := canvas.NewRectangle(color.Transparent) - rect.SetMinSize(fyne.NewSize(ui.Width*0.6, ui.Width*0.35)) - frame := &iframe{} - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - wSpacer := widget.NewLabel(" ") - - title := canvas.NewText("", colors.Gray) - title.TextStyle = fyne.TextStyle{Bold: true} - title.TextSize = 16 - title.Alignment = fyne.TextAlignCenter - - heading := canvas.NewText("", colors.Red) - heading.TextStyle = fyne.TextStyle{Bold: true} - heading.TextSize = 22 - heading.Alignment = fyne.TextAlignCenter - - sub := widget.NewRichTextFromMarkdown("") - sub.Wrapping = fyne.TextWrapWord - - labelSettings := widget.NewHyperlinkWithStyle("Review Settings", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - - if t == 1 { - title.Text = "E R R O R" - heading.Text = "Connection Failure" - sub.ParseMarkdown("Connection to " + session.Daemon + " has failed. Please review your settings and try again.") - labelSettings.Text = "Review Settings" - labelSettings.OnTapped = func() { - session.Window.SetContent(layoutSettings()) - } - } else if t == 2 { - title.Text = "E R R O R" - heading.Text = "Write Failure" - sub.ParseMarkdown("Could not write data to disk, please check to make sure Engram has the proper permissions and/or you have unzipped the contents.") - labelSettings.Text = "Review Settings" - labelSettings.OnTapped = func() { - session.Window.SetContent(layoutMain()) - } - } else { - title.Text = "E R R O R" - heading.Text = "ID-10T Error Protocol" - sub.ParseMarkdown("System malfunction... Please... Find... Help...") - labelSettings.Text = "Review Settings" - labelSettings.OnTapped = func() { - session.Window.SetContent(layoutSettings()) - } - } - - rectHeader := canvas.NewRectangle(color.Transparent) - rectHeader.SetMinSize(fyne.NewSize(ui.Width, 1)) - - session.Gif, _ = x.NewAnimatedGifFromResource(resourceAnimation2Gif) - session.Gif.SetMinSize(rect.MinSize()) - session.Gif.Start() - - alertForm := container.NewVBox( - wSpacer, - wSpacer, - rectHeader, - container.NewStack( - rect, - res.red_alert, - ), - heading, - rectSpacer, - sub, - widget.NewLabel(""), - ) - - footer := container.NewVBox( - container.NewHBox( - layout.NewSpacer(), - labelSettings, - layout.NewSpacer(), - ), - wSpacer, - ) - - features := container.NewCenter( - layout.NewSpacer(), - alertForm, - layout.NewSpacer(), - ) - - c := container.NewBorder( - features, - footer, - nil, - nil, - ) - - layout := container.NewStack( - frame, - c, - ) - - return NewVScroll(layout) -} - -func layoutHistory() fyne.CanvasObject { - var data []string - var entries []rpc.Entry - var zeroscid crypto.Hash - var listData binding.StringList - var listBox *widget.List - var txid string - - view := "" - - header := canvas.NewText(" Transaction History", colors.Green) - header.TextSize = 22 - header.TextStyle = fyne.TextStyle{Bold: true} - - details_header := canvas.NewText(" Transaction Detail", colors.Green) - details_header.TextSize = 22 - details_header.TextStyle = fyne.TextStyle{Bold: true} - - frame := &iframe{} - rectWidth := canvas.NewRectangle(color.Transparent) - rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth, 10)) - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) - - heading := canvas.NewText("H I S T O R Y", colors.Gray) - heading.TextSize = 16 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - rect := canvas.NewRectangle(color.Transparent) - rect.SetMinSize(fyne.NewSize(ui.Width*0.3, 35)) - - rectMid := canvas.NewRectangle(color.Transparent) - rectMid.SetMinSize(fyne.NewSize(ui.Width*0.35, 35)) - - results := canvas.NewText("", colors.Green) - results.TextSize = 13 - - listData = binding.BindStringList(&data) - listBox = widget.NewListWithData(listData, - func() fyne.CanvasObject { - return container.NewHBox( - container.NewStack( - rect, - widget.NewLabel(""), - ), - container.NewStack( - rectMid, - widget.NewLabel(""), - ), - container.NewStack( - rect, - widget.NewLabel(""), - ), - ) - }, - func(di binding.DataItem, co fyne.CanvasObject) { - dat := di.(binding.String) - str, err := dat.Get() - if err != nil { - return - } - - split := strings.Split(str, ";;;") - - co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[0]) - co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[1]) - co.(*fyne.Container).Objects[2].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[3]) - }) - - menu := widget.NewSelect([]string{"Normal", "Coinbase", "Messages"}, nil) - menu.PlaceHolder = "(Select Transaction Type)" - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - rectList := canvas.NewRectangle(color.Transparent) - rectList.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.60)) - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } - - label := canvas.NewText(view, colors.Account) - label.TextSize = 15 - label.TextStyle = fyne.TextStyle{Bold: true} - - menu.OnChanged = func(s string) { - switch s { - case "Normal": - listBox.UnselectAll() - results.Text = " Scanning..." - results.Refresh() - count := 0 - data = nil - listData.Set(nil) - entries = engram.Disk.Show_Transfers(zeroscid, false, true, true, 0, engram.Disk.Get_Height(), "", "", 0, 0) - - if entries != nil { - go func() { - for e := range entries { - var height string - var direction string - var stamp string - - entries[e].ProcessPayload() - - if !entries[e].Coinbase { - timefmt := entries[e].Time - //stamp = string(timefmt.Format(time.RFC822)) - stamp = timefmt.Format("2006-01-02") - height = strconv.FormatUint(entries[e].Height, 10) - amount := "" - txid = entries[e].TXID - - if !entries[e].Incoming { - direction = "Sent" - amount = "(" + globals.FormatMoney(entries[e].Amount) + ")" - } else { - direction = "Received" - amount = globals.FormatMoney(entries[e].Amount) - } - - count += 1 - data = append(data, direction+";;;"+amount+";;;"+height+";;;"+stamp+";;;"+txid) - } - } - - results.Text = fmt.Sprintf(" Results: %d", count) - - listData.Set(data) - - listBox.OnSelected = func(id widget.ListItemID) { - //var zeroscid crypto.Hash - split := strings.Split(data[id], ";;;") - var zeroscid crypto.Hash - _, result := engram.Disk.Get_Payments_TXID(zeroscid, split[4]) - - if result.TXID == "" { - label.Text = "---" - } else { - label.Text = result.TXID - } - - fyne.Do(func() { - label.Refresh() - }) - - overlay := session.Window.Canvas().Overlays() - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - overlay.Add(layoutHistoryDetail(split[4])) - listBox.UnselectAll() - } - - fyne.Do(func() { - results.Refresh() - listBox.Refresh() - listBox.ScrollToBottom() - }) - }() - } else { - results.Text = fmt.Sprintf(" Results: %d", count) - results.Refresh() - } - case "Coinbase": - listBox.UnselectAll() - results.Text = " Scanning..." - results.Refresh() - count := 0 - data = nil - listData.Set(nil) - entries = engram.Disk.Show_Transfers(zeroscid, true, true, true, 0, engram.Disk.Get_Height(), "", "", 0, 0) - - if entries != nil { - go func() { - for e := range entries { - var height string - var direction string - var stamp string - - entries[e].ProcessPayload() - - if entries[e].Coinbase { - direction = "Network" - timefmt := entries[e].Time - stamp = timefmt.Format("2006-01-02") - height = strconv.FormatUint(entries[e].Height, 10) - amount := globals.FormatMoney(entries[e].Amount) - txid = entries[e].TXID - - count += 1 - data = append(data, direction+";;;"+amount+";;;"+height+";;;"+stamp+";;;"+txid) - } - } - - results.Text = fmt.Sprintf(" Results: %d", count) - - listData.Set(data) - - listBox.OnSelected = func(id widget.ListItemID) { - listBox.UnselectAll() - } - - fyne.Do(func() { - results.Refresh() - listBox.Refresh() - listBox.ScrollToBottom() - }) - }() - } else { - results.Text = fmt.Sprintf(" Results: %d", count) - - fyne.Do(func() { - results.Refresh() - }) - } - case "Messages": - listBox.UnselectAll() - results.Text = " Scanning..." - results.Refresh() - count := 0 - data = nil - listData.Set(nil) - entries = engram.Disk.Get_Payments_DestinationPort(zeroscid, uint64(1337), 0) - - if entries != nil { - go func() { - for e := range entries { - var stamp string - var direction string - var comment string - - entries[e].ProcessPayload() - - timefmt := entries[e].Time - //stamp = string(timefmt.Format(time.RFC822)) - stamp = timefmt.Format("2006-01-02") - - temp := entries[e].Incoming - if !temp { - direction = "Sent " - } else { - direction = "Received" - } - if entries[e].Payload_RPC.HasValue(rpc.RPC_COMMENT, rpc.DataString) { - contact := "" - username := "" - if entries[e].Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { - contact = entries[e].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) - if len(contact) > 10 { - username = contact[0:10] + ".." - } else { - username = contact - } - } - - comment = entries[e].Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string) - if len(comment) > 10 { - comment = comment[0:10] + ".." - } - - txid = entries[e].TXID - count += 1 - data = append(data, direction+";;;"+username+";;;"+comment+";;;"+stamp+";;;"+txid+";;;"+contact) - } - } - - results.Text = fmt.Sprintf(" Results: %d", count) - - listData.Set(data) - - listBox.OnSelected = func(id widget.ListItemID) { - split := strings.Split(data[id], ";;;") - overlay := session.Window.Canvas().Overlays() - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - overlay.Add(layoutHistoryDetail(split[4])) - listBox.UnselectAll() - listBox.Refresh() - } - - fyne.Do(func() { - results.Refresh() - listBox.Refresh() - listBox.ScrollToBottom() - }) - }() - } else { - results.Text = fmt.Sprintf(" Results: %d", count) - - fyne.Do(func() { - results.Refresh() - }) - } - default: - - } - } - - center := container.NewStack( - rectWidth, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - menu, - rectSpacer, - results, - rectSpacer, - rectSpacer, - container.NewStack( - rectList, - listBox, - ), - ), - layout.NewSpacer(), - ), - ) - - top := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - heading, - ), - rectSpacer, - rectSpacer, - container.NewCenter( - center, - ), - ) - - bottom := container.NewStack( - container.NewVBox( - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewStack( - frame, - container.NewBorder( - top, - bottom, - nil, - nil, - ), - ) - - return NewVScroll(layout) -} - -func layoutHistoryDetail(txid string) fyne.CanvasObject { - wSpacer := widget.NewLabel(" ") - - rectWidth := canvas.NewRectangle(color.Transparent) - rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, 10)) - - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) - - frame := &iframe{} - - heading := canvas.NewText("T R A N S A C T I O N D E T A I L", colors.Gray) - heading.TextSize = 16 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - labelTXID := canvas.NewText(" TRANSACTION ID", colors.Gray) - labelTXID.TextSize = 14 - labelTXID.Alignment = fyne.TextAlignLeading - labelTXID.TextStyle = fyne.TextStyle{Bold: true} - - labelAmount := canvas.NewText(" AMOUNT", colors.Gray) - labelAmount.TextSize = 14 - labelAmount.Alignment = fyne.TextAlignLeading - labelAmount.TextStyle = fyne.TextStyle{Bold: true} - - labelDirection := canvas.NewText(" PAYMENT DIRECTION", colors.Gray) - labelDirection.TextSize = 14 - labelDirection.Alignment = fyne.TextAlignLeading - labelDirection.TextStyle = fyne.TextStyle{Bold: true} - - labelMember := canvas.NewText("", colors.Gray) - labelMember.TextSize = 14 - labelMember.Alignment = fyne.TextAlignLeading - labelMember.TextStyle = fyne.TextStyle{Bold: true} - - labeliMember := canvas.NewText("", colors.Gray) - labeliMember.TextSize = 14 - labeliMember.Alignment = fyne.TextAlignLeading - labeliMember.TextStyle = fyne.TextStyle{Bold: true} - - labelProof := canvas.NewText(" TRANSACTION PROOF", colors.Gray) - labelProof.TextSize = 14 - labelProof.Alignment = fyne.TextAlignLeading - labelProof.TextStyle = fyne.TextStyle{Bold: true} - - labelDestPort := canvas.NewText(" DESTINATION PORT", colors.Gray) - labelDestPort.TextSize = 14 - labelDestPort.TextStyle = fyne.TextStyle{Bold: true} - - labelSourcePort := canvas.NewText(" SOURCE PORT", colors.Gray) - labelSourcePort.TextSize = 14 - labelSourcePort.TextStyle = fyne.TextStyle{Bold: true} - - labelFees := canvas.NewText(" TRANSACTION FEES", colors.Gray) - labelFees.TextSize = 14 - labelFees.TextStyle = fyne.TextStyle{Bold: true} - - labelPayload := canvas.NewText(" PAYLOAD", colors.Gray) - labelPayload.TextSize = 14 - labelPayload.TextStyle = fyne.TextStyle{Bold: true} - - labelHeight := canvas.NewText(" BLOCK HEIGHT", colors.Gray) - labelHeight.TextSize = 14 - labelHeight.TextStyle = fyne.TextStyle{Bold: true} - - labelReply := canvas.NewText(" REPLY ADDRESS", colors.Gray) - labelReply.TextSize = 14 - labelReply.TextStyle = fyne.TextStyle{Bold: true} - - labelSeparator := widget.NewRichTextFromMarkdown("") - labelSeparator.Wrapping = fyne.TextWrapOff - labelSeparator.ParseMarkdown("---") - - labelSeparator2 := widget.NewRichTextFromMarkdown("") - labelSeparator2.Wrapping = fyne.TextWrapOff - labelSeparator2.ParseMarkdown("---") - - labelSeparator3 := widget.NewRichTextFromMarkdown("") - labelSeparator3.Wrapping = fyne.TextWrapOff - labelSeparator3.ParseMarkdown("---") - - labelSeparator4 := widget.NewRichTextFromMarkdown("") - labelSeparator4.Wrapping = fyne.TextWrapOff - labelSeparator4.ParseMarkdown("---") - - labelSeparator5 := widget.NewRichTextFromMarkdown("") - labelSeparator5.Wrapping = fyne.TextWrapOff - labelSeparator5.ParseMarkdown("---") - - labelSeparator6 := widget.NewRichTextFromMarkdown("") - labelSeparator6.Wrapping = fyne.TextWrapOff - labelSeparator6.ParseMarkdown("---") - - labelSeparator7 := widget.NewRichTextFromMarkdown("") - labelSeparator7.Wrapping = fyne.TextWrapOff - labelSeparator7.ParseMarkdown("---") - - labelSeparator8 := widget.NewRichTextFromMarkdown("") - labelSeparator8.Wrapping = fyne.TextWrapOff - labelSeparator8.ParseMarkdown("---") - - labelSeparator9 := widget.NewRichTextFromMarkdown("") - labelSeparator9.Wrapping = fyne.TextWrapOff - labelSeparator9.ParseMarkdown("---") - - labelSeparator10 := widget.NewRichTextFromMarkdown("") - labelSeparator10.Wrapping = fyne.TextWrapOff - labelSeparator10.ParseMarkdown("---") - - labelSeparator11 := widget.NewRichTextFromMarkdown("") - labelSeparator11.Wrapping = fyne.TextWrapOff - labelSeparator11.ParseMarkdown("---") - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - var zeroscid crypto.Hash - _, details := engram.Disk.Get_Payments_TXID(zeroscid, txid) - - stamp := string(details.Time.Format(time.RFC822)) - height := strconv.FormatUint(details.Height, 10) - - valueMember := widget.NewRichTextFromMarkdown(" ") - valueMember.Wrapping = fyne.TextWrapBreak - - valueiMember := widget.NewRichTextFromMarkdown("--") - valueiMember.Wrapping = fyne.TextWrapBreak - - valueReply := widget.NewRichTextFromMarkdown("--") - valueReply.Wrapping = fyne.TextWrapBreak - - if details.Payload_RPC.HasValue(rpc.RPC_REPLYBACK_ADDRESS, rpc.DataAddress) { - address := details.Payload_RPC.Value(rpc.RPC_REPLYBACK_ADDRESS, rpc.DataAddress).(rpc.Address) - valueReply.ParseMarkdown("" + address.String()) - } else if details.Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) && details.DestinationPort == 1337 { - address := details.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) - valueReply.ParseMarkdown("" + address) - } - - valuePayload := widget.NewRichTextFromMarkdown("--") - valuePayload.Wrapping = fyne.TextWrapBreak - - if details.Payload_RPC.HasValue(rpc.RPC_COMMENT, rpc.DataString) { - if details.Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string) != "" { - valuePayload.ParseMarkdown("" + details.Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string)) - } - } - - valueAmount := canvas.NewText("", colors.Account) - valueAmount.TextSize = 22 - valueAmount.TextStyle = fyne.TextStyle{Bold: true} - - valueDirection := canvas.NewText("", colors.Account) - valueDirection.TextSize = 22 - valueDirection.TextStyle = fyne.TextStyle{Bold: true} - if details.Incoming { - valueDirection.Text = " Received" - labelMember.Text = " SENDER ADDRESS" - if details.Sender == "" || details.Sender == engram.Disk.GetAddress().String() { - valueMember.ParseMarkdown("--") - } else { - valueMember.ParseMarkdown("" + details.Sender) - } - - if details.Amount == 0 { - valueAmount.Color = colors.Account - valueAmount.Text = " 0.00000" - } else { - valueAmount.Color = colors.Green - valueAmount.Text = " + " + globals.FormatMoney(details.Amount) - } - } else { - valueDirection.Text = " Sent" - labelMember.Text = " RECEIVER ADDRESS" - valueMember.ParseMarkdown("" + details.Destination) - - if details.Amount == 0 { - valueAmount.Color = colors.Account - valueAmount.Text = " 0.00000" - } else { - valueAmount.Color = colors.Account - valueAmount.Text = " - " + globals.FormatMoney(details.Amount) - } - } - - labeliMember.Text = " INTEGRATED ADDRESS" - var idest string - if details.Destination == "" { - // We are the recipient - idest = engram.Disk.GetAddress().String() - } else { - idest = details.Destination - } - iaddr, _ := rpc.NewAddress(idest) - if iaddr != nil { - var iargs rpc.Arguments - for _, v := range details.Payload_RPC { - if !iargs.HasValue(v.Name, v.DataType) { - // Skip the reply back addr that was injected, but 'reverse' this to be what the original payload was which requests the reply addr - if v.Name == rpc.RPC_REPLYBACK_ADDRESS { - iargs = append(iargs, rpc.Argument{Name: rpc.RPC_NEEDS_REPLYBACK_ADDRESS, DataType: rpc.DataUint64, Value: uint64(1)}) - } else { - iargs = append(iargs, rpc.Argument{Name: v.Name, DataType: v.DataType, Value: v.Value}) - } - } - } - - // If value transfer 'V' doesn't exist, we add it here. - if !iargs.HasValue(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { - iargs = append(iargs, rpc.Argument{Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: details.Amount}) - } - - iaddr.Arguments = iargs - - // Check to see if integrated addr creation makes an actual integrated addr - if iaddr.String() != details.Destination && iaddr.IsIntegratedAddress() { - valueiMember.ParseMarkdown("" + iaddr.String()) - } - } - - valueTime := canvas.NewText(stamp, colors.Account) - valueTime.TextSize = 14 - valueTime.TextStyle = fyne.TextStyle{Bold: true} - - valueFees := canvas.NewText(" "+globals.FormatMoney(details.Fees), colors.Account) - valueFees.TextSize = 22 - valueFees.TextStyle = fyne.TextStyle{Bold: true} - - valueHeight := canvas.NewText(" "+height, colors.Account) - valueHeight.TextSize = 22 - valueHeight.TextStyle = fyne.TextStyle{Bold: true} - - valueTXID := widget.NewRichTextFromMarkdown("") - valueTXID.Wrapping = fyne.TextWrapBreak - valueTXID.ParseMarkdown("" + txid) - - valuePort := canvas.NewText("", colors.Account) - valuePort.TextSize = 22 - valuePort.TextStyle = fyne.TextStyle{Bold: true} - valuePort.Text = " " + strconv.FormatUint(details.DestinationPort, 10) - - valueSourcePort := canvas.NewText("", colors.Account) - valueSourcePort.TextSize = 22 - valueSourcePort.TextStyle = fyne.TextStyle{Bold: true} - valueSourcePort.Text = " " + strconv.FormatUint(details.SourcePort, 10) - - btnView := widget.NewButton("View in Explorer", nil) - btnView.OnTapped = func() { - if engram.Disk.GetNetwork() { - link, _ := url.Parse("https://explorer.derofoundation.org/tx/" + txid) - _ = fyne.CurrentApp().OpenURL(link) - } else { - link, _ := url.Parse("https://testnetexplorer.derofoundation.org/tx/" + txid) - _ = fyne.CurrentApp().OpenURL(link) - } - } - - linkBack := widget.NewHyperlinkWithStyle("Back to History", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - overlay := session.Window.Canvas().Overlays() - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } - - linkAddress := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkAddress.OnTapped = func() { - a.Clipboard().SetContent(valueMember.String()) - } - - linkiAddress := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkiAddress.OnTapped = func() { - a.Clipboard().SetContent(valueiMember.String()) - } - - linkReplyAddress := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkReplyAddress.OnTapped = func() { - if replyAddress, ok := details.Payload_RPC.Value(rpc.RPC_REPLYBACK_ADDRESS, rpc.DataAddress).(rpc.Address); ok { - a.Clipboard().SetContent(replyAddress.String()) - } - } - - linkTXID := widget.NewHyperlinkWithStyle("Copy Transaction ID", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkTXID.OnTapped = func() { - a.Clipboard().SetContent(txid) - } - - linkProof := widget.NewHyperlinkWithStyle("Copy Transaction Proof", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkProof.OnTapped = func() { - a.Clipboard().SetContent(details.Proof) - } - - linkPayload := widget.NewHyperlinkWithStyle("Copy Payload", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkPayload.OnTapped = func() { - if _, ok := details.Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string); ok { - a.Clipboard().SetContent(details.Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string)) - } - } - - top := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - heading, - ), - rectSpacer, - container.NewCenter( - valueTime, - ), - rectSpacer, - rectSpacer, - ) - - center := container.NewStack( - container.NewVScroll( - container.NewStack( - rectWidth, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - rectSpacer, - labelDirection, - rectSpacer, - valueDirection, - rectSpacer, - rectSpacer, - labelSeparator, - rectSpacer, - rectSpacer, - labelAmount, - rectSpacer, - container.NewStack( - rectWidth90, - valueAmount, - ), - rectSpacer, - rectSpacer, - labelSeparator2, - rectSpacer, - rectSpacer, - labelTXID, - rectSpacer, - container.NewStack( - rectWidth90, - valueTXID, - ), - container.NewVBox( - container.NewHBox( - linkTXID, - layout.NewSpacer(), - ), - container.NewHBox( - linkProof, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - labelSeparator3, - rectSpacer, - rectSpacer, - labelMember, - rectSpacer, - valueMember, - container.NewHBox( - linkAddress, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator4, - rectSpacer, - rectSpacer, - labeliMember, - rectSpacer, - valueiMember, - container.NewHBox( - linkiAddress, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator5, - rectSpacer, - rectSpacer, - labelReply, - rectSpacer, - valueReply, - container.NewHBox( - linkReplyAddress, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator6, - rectSpacer, - rectSpacer, - labelHeight, - rectSpacer, - container.NewStack( - rectWidth90, - valueHeight, - ), - rectSpacer, - rectSpacer, - labelSeparator7, - rectSpacer, - rectSpacer, - labelFees, - rectSpacer, - container.NewStack( - rectWidth90, - valueFees, - ), - rectSpacer, - rectSpacer, - labelSeparator8, - rectSpacer, - rectSpacer, - labelPayload, - rectSpacer, - container.NewStack( - rectWidth90, - valuePayload, - ), - container.NewVBox( - container.NewHBox( - linkPayload, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - labelSeparator9, - rectSpacer, - rectSpacer, - labelDestPort, - rectSpacer, - container.NewStack( - rectWidth90, - valuePort, - ), - rectSpacer, - rectSpacer, - labelSeparator10, - rectSpacer, - rectSpacer, - labelSourcePort, - rectSpacer, - container.NewStack( - rectWidth90, - valueSourcePort, - ), - rectSpacer, - rectSpacer, - labelSeparator11, - rectSpacer, - rectSpacer, - btnView, - wSpacer, - ), - layout.NewSpacer(), - ), - ), - ), - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewStack( - frame, - container.NewBorder( - top, - bottom, - nil, - center, - ), - ) - - return layout -} - -func layoutDatapad() fyne.CanvasObject { - session.Domain = "app.datapad" - title := canvas.NewText("D A T A P A D", colors.Gray) - title.TextStyle = fyne.TextStyle{Bold: true} - title.TextSize = 16 - - heading := canvas.NewText("", colors.Green) - heading.TextSize = 22 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - entryNewPad := widget.NewEntry() - entryNewPad.MultiLine = false - entryNewPad.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) - - btnAdd := widget.NewButton(" Create ", nil) - btnAdd.Disable() - btnAdd.OnTapped = func() { - err := StoreEncryptedValue("Datapads", []byte(entryNewPad.Text), []byte("")) - if err != nil { - btnAdd.Text = "Error creating new Datapad" - btnAdd.Disable() - btnAdd.Refresh() - } else { - session.Datapad = entryNewPad.Text - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDatapad()) - removeOverlays() - } - } - - entryNewPad.PlaceHolder = "Datapad Name" - entryNewPad.Validator = func(s string) error { - session.Datapad = s - if len(s) > 0 { - _, err := GetEncryptedValue("Datapads", []byte(s)) - if err == nil { - btnAdd.Text = "Datapad already exists" - btnAdd.Disable() - btnAdd.Refresh() - err := errors.New("username already exists") - entryNewPad.SetValidationError(err) - return err - } else { - btnAdd.Text = "Create" - btnAdd.Enable() - btnAdd.Refresh() - return nil - } - } else { - btnAdd.Text = "Create" - btnAdd.Disable() - err := errors.New("please enter a datapad name") - entryNewPad.SetValidationError(err) - btnAdd.Refresh() - return err - } - } - entryNewPad.OnChanged = func(s string) { - entryNewPad.Validate() - } - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } - - frame := &iframe{} - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - rectList := canvas.NewRectangle(color.Transparent) - rectList.SetMinSize(fyne.NewSize(ui.Width, 35)) - rectListBox := canvas.NewRectangle(color.Transparent) - rectListBox.SetMinSize(fyne.NewSize(ui.Width, 350)) - - var padData []string - - shard, err := GetShard() - if err != nil { - padData = []string{} - } - - store, err := graviton.NewDiskStore(shard) - if err != nil { - padData = []string{} - } - - ss, err := store.LoadSnapshot(0) - - if err != nil { - padData = []string{} - } - - tree, err := ss.GetTree("Datapads") - if err != nil { - padData = []string{} - } - - cursor := tree.Cursor() - - for k, _, err := cursor.First(); err == nil; k, _, err = cursor.Next() { - if string(k) != "" { - padData = append(padData, string(k)) - } - } - - padList := binding.BindStringList(&padData) - - padBox := widget.NewListWithData(padList, - func() fyne.CanvasObject { - c := container.NewVBox( - widget.NewLabel(""), - ) - return c - }, - func(di binding.DataItem, co fyne.CanvasObject) { - dat := di.(binding.String) - str, err := dat.Get() - if err != nil { - return - } - - co.(*fyne.Container).Objects[0].(*widget.Label).SetText(str) - co.(*fyne.Container).Objects[0].(*widget.Label).Wrapping = fyne.TextWrapWord - co.(*fyne.Container).Objects[0].(*widget.Label).TextStyle.Bold = false - co.(*fyne.Container).Objects[0].(*widget.Label).Alignment = fyne.TextAlignLeading - }) - - padBox.OnSelected = func(id widget.ListItemID) { - session.Datapad = padData[id] - overlay := session.Window.Canvas().Overlays() - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - overlay.Add( - container.NewStack( - &iframe{}, - layoutPad(), - ), - ) - overlay.Top().Show() - padBox.UnselectAll() - padBox.Refresh() - } - - shardForm := container.NewVBox( - rectSpacer, - rectSpacer, - rectSpacer, - container.NewCenter(container.NewVBox(title, rectSpacer)), - rectSpacer, - rectSpacer, - container.NewStack( - rectListBox, - padBox, - ), - rectSpacer, - entryNewPad, - rectSpacer, - btnAdd, - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ) - - gridItem1 := container.NewCenter( - shardForm, - ) - - features := container.NewCenter( - layout.NewSpacer(), - gridItem1, - layout.NewSpacer(), - ) - - subContainer := container.NewStack( - container.NewVBox( - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - c := container.NewBorder( - features, - subContainer, - nil, - nil, - ) - - layout := container.NewStack( - frame, - c, - ) - - return NewVScroll(layout) -} - -func layoutPad() fyne.CanvasObject { - rectWidth := canvas.NewRectangle(color.Transparent) - rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth, 10)) - - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) - - rectEntry := canvas.NewRectangle(color.Transparent) - rectEntry.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.52)) - - heading := canvas.NewText(session.Datapad, colors.Green) - heading.TextSize = 20 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - selectOptions := widget.NewSelect([]string{"Clear", "Export (Plaintext)", "Import From File", "Delete"}, nil) - selectOptions.PlaceHolder = "Select an Option ..." - - data, err := GetEncryptedValue("Datapads", []byte(session.Datapad)) - if err != nil { - data = nil - } - - overlay := session.Window.Canvas().Overlays() - - btnSave := widget.NewButton("Save", nil) - - entryPad := widget.NewEntry() - entryPad.Wrapping = fyne.TextWrapWord - - errorText := canvas.NewText(" ", colors.Green) - errorText.TextSize = 12 - errorText.Alignment = fyne.TextAlignCenter - - selectOptions.OnChanged = func(s string) { - errorText.Text = "" - errorText.Refresh() - - if s == "Clear" { - header := canvas.NewText("DATAPAD RESET REQUESTED", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - subHeader := canvas.NewText("Clear Datapad?", colors.Account) - subHeader.TextSize = 22 - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - - linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - overlay := session.Window.Canvas().Overlays() - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - selectOptions.Selected = "Select an Option ..." - selectOptions.Refresh() - } - - btnSubmit := widget.NewButton("Clear", nil) - - btnSubmit.OnTapped = func() { - if session.Datapad != "" { - err := StoreEncryptedValue("Datapads", []byte(session.Datapad), []byte("")) - if err != nil { - logger.Errorf("[Datapad] Err: %s\n", err) - selectOptions.Selected = "Select an Option ..." - selectOptions.Refresh() - return - } - - selectOptions.Selected = "Select an Option ..." - selectOptions.Refresh() - entryPad.Text = "" - entryPad.Refresh() - } - - errorText.Text = "datapad cleared" - errorText.Color = colors.Green - errorText.Refresh() - - overlay := session.Window.Canvas().Overlays() - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - selectOptions.Selected = "Select an Option ..." - selectOptions.Refresh() - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - subHeader, - widget.NewLabel(""), - btnSubmit, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - } else if s == "Export (Plaintext)" { - selectOptions.Selected = "Select an Option ..." - selectOptions.Refresh() - - dialogFileSave := dialog.NewFileSave(func(uri fyne.URIWriteCloser, err error) { - if err != nil { - logger.Errorf("[Engram] File dialog: %s\n", err) - errorText.Text = "could not export datapad" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if uri == nil { - return // Canceled - } - - data := []byte(entryPad.Text) - _, err = writeToURI(data, uri) - if err != nil { - logger.Errorf("[Engram] Exporting datapad %s: %s\n", session.Datapad, err) - errorText.Text = "error exporting datapad" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - errorText.Text = "exported datapad successfully" - errorText.Color = colors.Green - errorText.Refresh() - - }, session.Window) - - if !a.Driver().Device().IsMobile() { - // Open file browser in current directory - uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) - if err == nil { - dialogFileSave.SetLocation(uri) - } else { - logger.Errorf("[Engram] Could not open current directory %s\n", err) - } - } - - // dialogFileSave.SetFilter(storage.NewMimeTypeFileFilter([]string{"text/*"})) - dialogFileSave.SetView(dialog.ListView) - dialogFileSave.SetFileName(fmt.Sprintf("%s.txt", session.Datapad)) - dialogFileSave.Resize(fyne.NewSize(ui.Width, ui.Height)) - dialogFileSave.Show() - } else if s == "Import From File" { - selectOptions.Selected = "Select an Option ..." - selectOptions.Refresh() - - dialogFileImport := dialog.NewFileOpen(func(uri fyne.URIReadCloser, err error) { - if err != nil { - logger.Errorf("[Engram] File dialog: %s\n", err) - errorText.Text = "could not import file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if uri == nil { - return // Canceled - } - - fileName := uri.URI().String() - if !strings.Contains(uri.URI().MimeType(), "text/") { - logger.Errorf("[Engram] Cannot import file %s\n", fileName) - errorText.Text = "cannot import file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if a.Driver().Device().IsMobile() { - fileName = uri.URI().Name() - } else { - fileName = filepath.Base(strings.Replace(fileName, "file://", "", -1)) - } - - filedata, err := readFromURI(uri) - if err != nil { - logger.Errorf("[Engram] Cannot read URI file data for %s: %s\n", fileName, err) - errorText.Text = "cannot read file data" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if !isASCII(string(filedata)) { - errorText.Text = "invalid file data" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if entryPad.Text == "" { - entryPad.SetText(string(filedata)) - } else { - entryPad.SetText(fmt.Sprintf("%s\n\n%s", entryPad.Text, string(filedata))) - } - - errorText.Text = "file data imported successfully" - errorText.Color = colors.Green - errorText.Refresh() - - }, session.Window) - - if !a.Driver().Device().IsMobile() { - // Open file browser in current directory - uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) - if err == nil { - dialogFileImport.SetLocation(uri) - } else { - logger.Errorf("[Engram] Could not open current directory %s\n", err) - } - } - - // dialogFileSave.SetFilter(storage.NewMimeTypeFileFilter([]string{"text/*"})) - dialogFileImport.SetView(dialog.ListView) - dialogFileImport.Resize(fyne.NewSize(ui.Width, ui.Height)) - dialogFileImport.Show() - } else if s == "Delete" { - header := canvas.NewText("DATAPAD DELETION REQUESTED", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - subHeader := canvas.NewText("Delete Datapad?", colors.Account) - subHeader.TextSize = 22 - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - - linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - overlay := session.Window.Canvas().Overlays() - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - selectOptions.Selected = "Select an Option ..." - selectOptions.Refresh() - } - - btnSubmit := widget.NewButton("Delete", nil) - - btnSubmit.OnTapped = func() { - if session.Datapad != "" { - err := DeleteKey("Datapads", []byte(session.Datapad)) - if err != nil { - selectOptions.Selected = "Select an Option ..." - selectOptions.Refresh() - logger.Errorf("[Datapad] Error deleting %s: %s\n", session.Datapad, err) - } else { - session.Datapad = "" - session.DatapadChanged = false - removeOverlays() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDatapad()) - } - } - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - subHeader, - widget.NewLabel(""), - btnSubmit, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - } else { - session.Datapad = "" - session.DatapadChanged = false - overlay := session.Window.Canvas().Overlays() - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - selectOptions.Selected = "Select an Option ..." - selectOptions.Refresh() - } - } - - btnSave.OnTapped = func() { - err = StoreEncryptedValue("Datapads", []byte(session.Datapad), []byte(entryPad.Text)) - if err != nil { - btnSave.Disable() - errorText.Text = "- FAILED -" - errorText.Color = colors.Red - errorText.Refresh() - } else { - session.DatapadChanged = false - btnSave.Disable() - heading.Text = session.Datapad - heading.Refresh() - errorText.Text = "- SAVED -" - errorText.Color = colors.Green - errorText.Refresh() - } - } - - session.DatapadChanged = false - - btnSave.Disable() - - entryPad.MultiLine = true - entryPad.Text = string(data) - entryPad.OnChanged = func(s string) { - errorText.Text = "" - errorText.Refresh() - session.DatapadChanged = true - heading.Text = session.Datapad + "*" - heading.Refresh() - btnSave.Enable() - } - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to Datapad", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - if session.DatapadChanged { - header := canvas.NewText("DATAPAD CHANGE DETECTED", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - subHeader := canvas.NewText("Save Datapad?", colors.Account) - subHeader.TextSize = 22 - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - - linkClose := widget.NewHyperlinkWithStyle("Discard Changes", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - session.Datapad = "" - session.DatapadChanged = false - removeOverlays() - } - - btnSubmit := widget.NewButton("Save", nil) - - btnSubmit.OnTapped = func() { - err = StoreEncryptedValue("Datapads", []byte(session.Datapad), []byte(entryPad.Text)) - if err != nil { - btnSave.Disable() - errorText.Text = "error saving datapad" - errorText.Color = colors.Red - errorText.Refresh() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } else { - session.Datapad = "" - session.DatapadChanged = false - removeOverlays() - } - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - subHeader, - widget.NewLabel(""), - btnSubmit, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - } else { - session.Datapad = "" - session.DatapadChanged = false - overlay := session.Window.Canvas().Overlays() - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } - } - - top := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - heading, - ), - rectSpacer, - container.NewCenter( - container.NewStack( - rectWidth90, - selectOptions, - ), - ), - rectSpacer, - ) - - center := container.NewStack( - rectWidth, - container.NewCenter( - container.NewVBox( - container.NewStack( - rectEntry, - entryPad, - ), - rectSpacer, - errorText, - rectSpacer, - btnSave, - rectSpacer, - ), - ), - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewBorder( - top, - bottom, - nil, - nil, - center, - ) - - return NewVScroll(layout) -} - -func layoutAccount() fyne.CanvasObject { - rectWidth := canvas.NewRectangle(color.Transparent) - rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, 10)) - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) - rectBox := canvas.NewRectangle(color.Transparent) - rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.80)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(10, 5)) - - title := canvas.NewText("M Y A C C O U N T", colors.Gray) - title.TextStyle = fyne.TextStyle{Bold: true} - title.TextSize = 16 - - heading := canvas.NewText(engram.Disk.GetAddress().String()[0:5]+"..."+engram.Disk.GetAddress().String()[len(engram.Disk.GetAddress().String())-10:len(engram.Disk.GetAddress().String())], colors.Green) - heading.TextSize = 22 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - labelPassword := canvas.NewText("N E W P A S S W O R D", colors.Gray) - labelPassword.TextStyle = fyne.TextStyle{Bold: true} - labelPassword.TextSize = 11 - labelPassword.Alignment = fyne.TextAlignCenter - - labelDatashard := canvas.NewText("D A T A S H A R D", colors.Gray) - labelDatashard.TextStyle = fyne.TextStyle{Bold: true} - labelDatashard.TextSize = 11 - labelDatashard.Alignment = fyne.TextAlignCenter - - headerDatashard := canvas.NewText("DATASHARD ID", colors.Gray) - headerDatashard.TextSize = 16 - headerDatashard.Alignment = fyne.TextAlignCenter - headerDatashard.TextStyle = fyne.TextStyle{Bold: true} - - address := engram.Disk.GetAddress().String() - shardID := fmt.Sprintf("%x", sha1.Sum([]byte(address))) - - textDatashard := widget.NewRichTextFromMarkdown("### " + shardID) - textDatashard.Wrapping = fyne.TextWrapWord - - textDatashardDesc := widget.NewRichTextFromMarkdown("Datashards hold encrypted data and stores it locally on your device. Each datashard is unique and can only be decrypted by the account it is associated with. Examples of data stored include:") - textDatashardDesc.Wrapping = fyne.TextWrapWord - - textDatashardDesc2 := widget.NewRichTextFromMarkdown("* Datapad entries\n* Saved search history\n* Asset scan results\n* Account settings") - textDatashardDesc2.Wrapping = fyne.TextWrapWord - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - labelRecovery := canvas.NewText("A C C O U N T O P T I O N S", colors.Gray) - labelRecovery.TextSize = 11 - labelRecovery.Alignment = fyne.TextAlignCenter - labelRecovery.TextStyle = fyne.TextStyle{Bold: true} - - labelEpoch := canvas.NewText("E P O C H", colors.Gray) - labelEpoch.TextSize = 11 - labelEpoch.Alignment = fyne.TextAlignCenter - labelEpoch.TextStyle = fyne.TextStyle{Bold: true} - - spacerEpoch := canvas.NewRectangle(color.Transparent) - spacerEpoch.SetMinSize(fyne.NewSize(140, 0)) - - wEpoch := widget.NewSelect([]string{"Session", "Total"}, nil) - wEpoch.SetSelected("Session") - - epochSession, _ := epoch.GetSession(time.Second * 4) - - labelEpochHashes := widget.NewRichTextFromMarkdown("### Hashes") - labelEpochHashes.Wrapping = fyne.TextWrapWord - - epochHashes := fmt.Sprintf("%.1fK", float64(epochSession.Hashes)/1000) - textEpochHashes := widget.NewRichTextFromMarkdown(epochHashes) - textEpochHashes.Wrapping = fyne.TextWrapWord - - labelEpochBlocks := widget.NewRichTextFromMarkdown("### Miniblocks") - labelEpochBlocks.Wrapping = fyne.TextWrapWord - - epochBlocks := fmt.Sprintf("%d", epochSession.MiniBlocks) - textEpochBlocks := widget.NewRichTextFromMarkdown(epochBlocks) - textEpochBlocks.Wrapping = fyne.TextWrapWord - - wEpoch.OnChanged = func(s string) { - epochSession, _ := epoch.GetSession(time.Second * 4) - if s == "Total" { - total := epoch.GetSessionEPOCH_Result{ - Hashes: cyberdeck.EPOCH.total.Hashes, - MiniBlocks: cyberdeck.EPOCH.total.MiniBlocks, - } - - if epoch.IsActive() { - total.Hashes += epochSession.Hashes - total.MiniBlocks += epochSession.MiniBlocks - } - - textEpochHashes.ParseMarkdown(epoch.HashesToString(total.Hashes)) - textEpochBlocks.ParseMarkdown(fmt.Sprintf("%d", total.MiniBlocks)) - - return - } - - textEpochHashes.ParseMarkdown(epoch.HashesToString(epochSession.Hashes)) - textEpochBlocks.ParseMarkdown(fmt.Sprintf("%d", epochSession.MiniBlocks)) - } - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - formEpoch := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - labelEpoch, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - container.NewVBox( - rectSpacer, - wEpoch, - container.NewHBox( - container.NewStack( - spacerEpoch, - labelEpochHashes, - ), - container.NewStack( - spacerEpoch, - textEpochHashes, - ), - ), - container.NewHBox( - container.NewStack( - spacerEpoch, - labelEpochBlocks, - ), - container.NewStack( - spacerEpoch, - textEpochBlocks, - ), - ), - ), - ), - layout.NewSpacer(), - ), - ), - ) - - if session.Offline { - formEpoch.Hide() - } - - linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } - - linkCopyAddress := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkCopyAddress.OnTapped = func() { - a.Clipboard().SetContent(engram.Disk.GetAddress().String()) - } - - btnClear := widget.NewButton("Delete Datashard", nil) - btnClear.OnTapped = func() { - header := canvas.NewText("DATASHARD DELETION REQUESTED", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - subHeader := canvas.NewText("Are you sure?", colors.Account) - subHeader.TextSize = 22 - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - - linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - session.Datapad = "" - session.DatapadChanged = false - removeOverlays() - } - - btnSubmit := widget.NewButton("Delete Datashard", nil) - - btnSubmit.OnTapped = func() { - err := cleanWalletData() - if err != nil { - btnSubmit.Text = "Error deleting datashard" - btnSubmit.Disable() - btnSubmit.Refresh() - } else { - btnSubmit.Text = "Deletion successful!" - btnSubmit.Disable() - btnSubmit.Refresh() - removeOverlays() - } - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay := session.Window.Canvas().Overlays() - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - subHeader, - widget.NewLabel(""), - btnSubmit, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - } - - errorText := canvas.NewText(" ", colors.Green) - errorText.TextSize = 12 - errorText.Alignment = fyne.TextAlignCenter - - optionsList := []string{"Recovery Words (Seed)", "Recovery Hex Keys", "Change Password", "Export Wallet File"} - selectOptions := widget.NewSelect(optionsList, nil) - selectOptions.PlaceHolder = "(Select one)" - - selectOptions.OnChanged = func(s string) { - errorText.Text = "" - errorText.Refresh() - - if s == "Recovery Words (Seed)" { - overlay := session.Window.Canvas().Overlays() - - header := canvas.NewText("ACCOUNT VERIFICATION REQUIRED", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - subHeader := canvas.NewText("Confirm Password", colors.Account) - subHeader.TextSize = 22 - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - - linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - overlay := session.Window.Canvas().Overlays() - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - selectOptions.ClearSelected() - } - - btnConfirm := widget.NewButton("Submit", nil) - - entryPassword := NewReturnEntry() - entryPassword.Password = true - entryPassword.PlaceHolder = "Password" - entryPassword.OnChanged = func(s string) { - if s == "" { - btnConfirm.Text = "Submit" - btnConfirm.Disable() - btnConfirm.Refresh() - } else { - btnConfirm.Text = "Submit" - btnConfirm.Enable() - btnConfirm.Refresh() - } - } - - btnConfirm.OnTapped = func() { - selectOptions.ClearSelected() - if engram.Disk.Check_Password(entryPassword.Text) { - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - layoutRecovery(), - ) - } else { - btnConfirm.Text = "Invalid Password..." - btnConfirm.Disable() - btnConfirm.Refresh() - } - } - - entryPassword.OnReturn = btnConfirm.OnTapped - - btnConfirm.Disable() - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - subHeader, - widget.NewLabel(""), - container.NewCenter( - container.NewStack( - span, - entryPassword, - ), - ), - rectSpacer, - rectSpacer, - btnConfirm, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - - session.Window.Canvas().Focus(entryPassword) - - } else if s == "Recovery Hex Keys" { - overlay := session.Window.Canvas().Overlays() - - header := canvas.NewText("ACCOUNT VERIFICATION REQUIRED", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - subHeader := canvas.NewText("Confirm Password", colors.Account) - subHeader.TextSize = 22 - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - - linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - overlay := session.Window.Canvas().Overlays() - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - selectOptions.SetSelected("(Select One)") - } - - btnConfirm := widget.NewButton("Submit", nil) - - entryPassword := NewReturnEntry() - entryPassword.Password = true - entryPassword.PlaceHolder = "Password" - entryPassword.OnChanged = func(s string) { - if s == "" { - btnConfirm.Text = "Submit" - btnConfirm.Disable() - btnConfirm.Refresh() - } else { - btnConfirm.Text = "Submit" - btnConfirm.Enable() - btnConfirm.Refresh() - } - } - - btnConfirm.OnTapped = func() { - selectOptions.ClearSelected() - if engram.Disk.Check_Password(entryPassword.Text) { - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - layoutRecoveryHex(), - ) - } else { - btnConfirm.Text = "Invalid Password..." - btnConfirm.Disable() - btnConfirm.Refresh() - } - } - - entryPassword.OnReturn = btnConfirm.OnTapped - - btnConfirm.Disable() - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - subHeader, - widget.NewLabel(""), - container.NewCenter( - container.NewStack( - span, - entryPassword, - ), - ), - rectSpacer, - rectSpacer, - btnConfirm, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - - session.Window.Canvas().Focus(entryPassword) - - } else if s == "Change Password" { - overlay := session.Window.Canvas().Overlays() - - header := canvas.NewText("ACCOUNT AUTHORIZATION REQUEST", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - subHeader := canvas.NewText("Change Password", colors.Account) - subHeader.TextSize = 22 - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - - linkClose := widget.NewHyperlinkWithStyle("Close", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - overlay := session.Window.Canvas().Overlays() - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - selectOptions.ClearSelected() - } - - btnChange := widget.NewButton("Submit", nil) - btnChange.Disable() - - curPass := widget.NewEntry() - curPass.Password = true - curPass.PlaceHolder = "Current Password" - curPass.OnChanged = func(s string) { - btnChange.Text = "Submit" - btnChange.Enable() - btnChange.Refresh() - } - - newPass := widget.NewEntry() - newPass.Password = true - newPass.PlaceHolder = "New Password" - newPass.OnChanged = func(s string) { - btnChange.Text = "Submit" - btnChange.Enable() - btnChange.Refresh() - } - - confirm := widget.NewEntry() - confirm.Password = true - confirm.PlaceHolder = "Confirm Password" - confirm.OnChanged = func(s string) { - btnChange.Text = "Submit" - btnChange.Enable() - btnChange.Refresh() - } - - btnChange.OnTapped = func() { - if engram.Disk.Check_Password(curPass.Text) { - if newPass.Text == confirm.Text && newPass.Text != "" { - err := engram.Disk.Set_Encrypted_Wallet_Password(newPass.Text) - if err != nil { - btnChange.Text = "Error changing password" - btnChange.Disable() - btnChange.Refresh() - } else { - curPass.Text = "" - curPass.Refresh() - newPass.Text = "" - newPass.Refresh() - confirm.Text = "" - confirm.Refresh() - btnChange.Text = "Password Updated" - btnChange.Disable() - btnChange.Refresh() - engram.Disk.Save_Wallet() - } - } else { - btnChange.Text = "Passwords do not match" - btnChange.Disable() - btnChange.Refresh() - } - } else { - btnChange.Text = "Incorrect password entered" - btnChange.Disable() - btnChange.Refresh() - } - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - subHeader, - widget.NewLabel(""), - container.NewCenter( - container.NewStack( - span, - curPass, - ), - ), - widget.NewLabel(""), - widget.NewSeparator(), - widget.NewLabel(""), - newPass, - rectSpacer, - confirm, - rectSpacer, - rectSpacer, - btnChange, - widget.NewLabel(""), - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - - } else if s == "Export Wallet File" { - verificationOverlay( - true, - "", - "", - "", - func(b bool) { - if b { - go func() { - dialogFileSave := dialog.NewFileSave(func(uri fyne.URIWriteCloser, err error) { - if err != nil { - logger.Errorf("[Engram] File dialog: %s\n", err) - fyne.Do(func() { - errorText.Text = "could not export wallet file" - errorText.Color = colors.Red - errorText.Refresh() - }) - - return - } - - if uri == nil { - return // Canceled - } - - data, err := os.ReadFile(session.Path) - if err != nil { - logger.Errorf("[Engram] Reading wallet file %s: %s\n", session.Path, err) - fyne.Do(func() { - errorText.Text = "error reading wallet file" - errorText.Color = colors.Red - errorText.Refresh() - }) - - return - } - - _, err = writeToURI(data, uri) - if err != nil { - logger.Errorf("[Engram] Exporting %s: %s\n", session.Path, err) - fyne.Do(func() { - errorText.Text = "error exporting wallet file" - errorText.Color = colors.Red - errorText.Refresh() - }) - - return - } - - fyne.Do(func() { - errorText.Text = "exported wallet file successfully" - errorText.Color = colors.Green - errorText.Refresh() - }) - - }, session.Window) - - if !a.Driver().Device().IsMobile() { - // Open file browser in current directory - uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) - if err == nil { - dialogFileSave.SetLocation(uri) - } else { - logger.Errorf("[Engram] Could not open current directory %s\n", err) - } - } - - fyne.Do(func() { - dialogFileSave.SetFilter(storage.NewExtensionFileFilter([]string{".db"})) - dialogFileSave.SetView(dialog.ListView) - dialogFileSave.SetFileName(filepath.Base(session.Path)) - dialogFileSave.Resize(fyne.NewSize(ui.Width, ui.Height)) - dialogFileSave.Show() - }) - }() - } - }, - ) - } - } - - var imageQR *canvas.Image - - qr, err := qrcode.New(engram.Disk.GetAddress().String(), qrcode.Highest) - if err != nil { - - } else { - qr.BackgroundColor = colors.DarkMatter - qr.ForegroundColor = colors.Green - } - - imageQR = canvas.NewImageFromImage(qr.Image(int(ui.Width * 0.65))) - imageQR.SetMinSize(fyne.NewSize(ui.Width*0.65, ui.Width*0.65)) - - features := container.NewStack( - rectBox, - container.NewVScroll( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - container.NewVBox( - title, - rectSpacer, - ), - ), - rectSpacer, - heading, - container.NewHBox( - layout.NewSpacer(), - linkCopyAddress, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - container.NewStack( - container.NewCenter( - imageQR, - ), - ), - widget.NewLabel(""), - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - labelRecovery, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - selectOptions, - rectWidth90, - errorText, - ), - layout.NewSpacer(), - ), - ), - formEpoch, - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - labelDatashard, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - container.NewVBox( - container.NewStack( - layout.NewSpacer(), - headerDatashard, - layout.NewSpacer(), - ), - rectSpacer, - container.NewStack( - layout.NewSpacer(), - textDatashard, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - widget.NewSeparator(), - rectSpacer, - rectSpacer, - container.NewStack( - layout.NewSpacer(), - textDatashardDesc, - layout.NewSpacer(), - ), - rectSpacer, - container.NewStack( - layout.NewSpacer(), - textDatashardDesc2, - layout.NewSpacer(), - ), - ), - ), - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - btnClear, - ), - layout.NewSpacer(), - ), - ), - widget.NewLabel(""), - ), - ), - ) - - bottom := container.NewStack( - container.NewVBox( - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewBorder( - features, - bottom, - nil, - nil, - ) - - return NewVScroll(layout) -} - -func layoutRecovery() fyne.CanvasObject { - wSpacer := widget.NewLabel(" ") - heading := canvas.NewText("Recovery Words", colors.Green) - heading.TextSize = 22 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - rectStatus := canvas.NewRectangle(color.Transparent) - rectStatus.SetMinSize(fyne.NewSize(10, 10)) - - rectHeader := canvas.NewRectangle(color.Transparent) - rectHeader.SetMinSize(fyne.NewSize(ui.Width, 10)) - - linkCancel := widget.NewHyperlinkWithStyle("Back to My Account", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkCancel.OnTapped = func() { - removeOverlays() - } - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(ui.Width, 5)) - - grid := container.NewVBox() - grid.Objects = nil - - header := container.NewVBox( - rectSpacer, - rectSpacer, - heading, - rectSpacer, - rectSpacer, - ) - - footer := container.NewVBox( - wSpacer, - container.NewHBox( - layout.NewSpacer(), - linkCancel, - layout.NewSpacer(), - ), - wSpacer, - ) - - body := widget.NewLabel("Please save the following 25 recovery words in a safe place. Never share them with anyone.") - body.Wrapping = fyne.TextWrapWord - body.Alignment = fyne.TextAlignCenter - body.TextStyle = fyne.TextStyle{Bold: true} - - btnCopySeed := widget.NewButton("Copy Recovery Words", nil) - - form := container.NewVBox( - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectHeader, - body, - ), - layout.NewSpacer(), - ), - wSpacer, - container.NewCenter(grid), - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectHeader, - btnCopySeed, - ), - layout.NewSpacer(), - ), - rectSpacer, - ) - - scrollBox := container.NewVScroll( - container.NewStack( - form, - ), - ) - scrollBox.SetMinSize(fyne.NewSize(ui.MaxWidth, ui.Height*0.74)) - - formatted := strings.Split(engram.Disk.GetSeed(), " ") - - rect := canvas.NewRectangle(color.RGBA{19, 25, 34, 255}) - rect.SetMinSize(fyne.NewSize(ui.Width, 25)) - - for i := 0; i < len(formatted); i++ { - pos := fmt.Sprintf("%d", i+1) - word := strings.ReplaceAll(formatted[i], " ", "") - grid.Add(container.NewStack( - rect, - container.NewHBox( - widget.NewLabel(" "), - widget.NewLabelWithStyle(pos, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), - layout.NewSpacer(), - widget.NewLabelWithStyle(word, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), - widget.NewLabel(" "), - ), - ), - ) - } - - btnCopySeed.OnTapped = func() { - a.Clipboard().SetContent(engram.Disk.GetSeed()) - } - - layout := container.NewStack( - &iframe{}, - container.NewVBox( - header, - scrollBox, - footer, - ), - ) - - return layout -} - -func layoutRecoveryHex() fyne.CanvasObject { - wSpacer := widget.NewLabel(" ") - heading := canvas.NewText("Recovery Hex Keys", colors.Green) - heading.TextSize = 22 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - rectStatus := canvas.NewRectangle(color.Transparent) - rectStatus.SetMinSize(fyne.NewSize(10, 10)) - - rectHeader := canvas.NewRectangle(color.Transparent) - rectHeader.SetMinSize(fyne.NewSize(ui.Width, 10)) - - linkCancel := widget.NewHyperlinkWithStyle("Back to My Account", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkCancel.OnTapped = func() { - removeOverlays() - } - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(ui.Width, 5)) - - grid := container.NewVBox() - grid.Objects = nil - - header := container.NewVBox( - rectSpacer, - rectSpacer, - heading, - rectSpacer, - rectSpacer, - ) - - footer := container.NewVBox( - wSpacer, - container.NewHBox( - layout.NewSpacer(), - linkCancel, - layout.NewSpacer(), - ), - wSpacer, - ) - - body := widget.NewLabel("Please save the following hex secret key in a safe place. Never share your secret key with anyone.") - body.Wrapping = fyne.TextWrapWord - body.Alignment = fyne.TextAlignCenter - body.TextStyle = fyne.TextStyle{Bold: true} - - form := container.NewVBox( - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectHeader, - body, - ), - layout.NewSpacer(), - ), - wSpacer, - container.NewCenter(grid), - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectHeader, - ), - layout.NewSpacer(), - ), - rectSpacer, - ) - - scrollBox := container.NewVScroll( - container.NewStack( - form, - ), - ) - scrollBox.SetMinSize(fyne.NewSize(ui.MaxWidth, ui.Height*0.74)) - - keys := engram.Disk.Get_Keys() - key := fmt.Sprintf("0000000000000000000000000000000000000000000000%s", keys.Secret.Text(16)) - secret := key[len(key)-64:] - public := keys.Public.StringHex() - - textSecret := widget.NewRichTextFromMarkdown(secret) - textSecret.Wrapping = fyne.TextWrapWord - - linkCopySecret := widget.NewHyperlinkWithStyle("Copy Secret Key", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - - textPublic := widget.NewRichTextFromMarkdown(public) - textPublic.Wrapping = fyne.TextWrapWord - - linkCopyPublic := widget.NewHyperlinkWithStyle("Copy Public Key", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - - labelSecret := canvas.NewText(" SECRET KEY", colors.Gray) - labelSecret.TextSize = 14 - labelSecret.Alignment = fyne.TextAlignLeading - labelSecret.TextStyle = fyne.TextStyle{Bold: true} - - labelPublic := canvas.NewText(" PUBLIC KEY", colors.Gray) - labelPublic.TextSize = 14 - labelPublic.Alignment = fyne.TextAlignLeading - labelPublic.TextStyle = fyne.TextStyle{Bold: true} - - labelSeparator := widget.NewRichTextFromMarkdown("") - labelSeparator.Wrapping = fyne.TextWrapOff - labelSeparator.ParseMarkdown("---") - - grid.Add(container.NewVBox( - labelSecret, - rectSpacer, - textSecret, - rectSpacer, - container.NewHBox( - linkCopySecret, - ), - rectSpacer, - rectSpacer, - labelSeparator, - rectSpacer, - rectSpacer, - rectSpacer, - )) - - grid.Add(container.NewVBox( - labelPublic, - rectSpacer, - textPublic, - rectSpacer, - container.NewHBox( - linkCopyPublic, - ), - )) - - linkCopySecret.OnTapped = func() { - a.Clipboard().SetContent(secret) - } - - linkCopyPublic.OnTapped = func() { - a.Clipboard().SetContent(public) - } - - layout := container.NewStack( - &iframe{}, - container.NewVBox( - header, - scrollBox, - footer, - ), - ) - - return layout -} - -func layoutFrame() fyne.CanvasObject { - entry := widget.NewEntry() - layout := container.NewStack(entry) - - resizeWindow(ui.MaxWidth, ui.MaxHeight) - session.Window.SetContent(layout) - session.Window.SetFixedSize(false) - - go func() { - time.Sleep(time.Second * 2) - removeOverlays() - - ui.MaxWidth = entry.Size().Width - ui.MaxHeight = entry.Size().Height - lastOrientation := a.Driver().Device().Orientation() - initialOrientationVertical := fyne.IsVertical(lastOrientation) - - ui.Width = ui.MaxWidth * 0.9 - ui.Height = ui.MaxHeight - ui.Padding = ui.MaxWidth * 0.05 - if fyne.IsHorizontal(lastOrientation) { - // Smaller if horizontal for swipe scroll - ui.MaxWidth = ui.MaxWidth * 0.7 - ui.Width = ui.MaxWidth * 0.7 - ui.Padding = ui.MaxWidth * 0.15 - } - - resizeWindow(ui.MaxWidth, ui.MaxHeight) - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutMain()) - - frameWidth := ui.MaxWidth - frameHeight := ui.MaxHeight - - // Mobile loop checking if orientation has changed - for a.Driver() != nil { - currentOrientation := a.Driver().Device().Orientation() - if lastOrientation != currentOrientation { - if initialOrientationVertical { - if fyne.IsVertical(lastOrientation) && !fyne.IsVertical(currentOrientation) { - ui.MaxWidth = frameHeight - ui.MaxHeight = frameWidth - } else { - ui.MaxWidth = frameWidth - ui.MaxHeight = frameHeight - } - } else { - if fyne.IsHorizontal(lastOrientation) && !fyne.IsHorizontal(currentOrientation) { - ui.MaxWidth = frameHeight - ui.MaxHeight = frameWidth - } else { - ui.MaxWidth = frameWidth - ui.MaxHeight = frameHeight - } - } - - ui.Width = ui.MaxWidth * 0.9 - ui.Height = ui.MaxHeight - ui.Padding = ui.MaxWidth * 0.05 - if fyne.IsHorizontal(currentOrientation) { - ui.MaxWidth = ui.MaxWidth * 0.7 - ui.Width = ui.MaxWidth * 0.7 - ui.Padding = ui.MaxWidth * 0.15 - } - - lastOrientation = currentOrientation - resizeWindow(ui.MaxWidth, ui.MaxHeight) - } - time.Sleep(time.Second / 2) - } - }() - - overlays := session.Window.Canvas().Overlays() - overlays.Add( - container.NewStack( - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - return container.NewVScroll(layout) -} - -func layoutFileManager() fyne.CanvasObject { - session.Domain = "app.sign" - - frame := &iframe{} - - rectBox := canvas.NewRectangle(color.Transparent) - rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.9, ui.MaxHeight*0.34)) - rectWidth100 := canvas.NewRectangle(color.Transparent) - rectWidth100.SetMinSize(fyne.NewSize(ui.Width*0.99, 10)) - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width*0.9, 10)) - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - heading := canvas.NewText("F I L E M A N A G E R", colors.Gray) - heading.TextSize = 16 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - labelResults := canvas.NewText(" RESULTS", colors.Gray) - labelResults.TextSize = 14 - labelResults.Alignment = fyne.TextAlignLeading - labelResults.TextStyle = fyne.TextStyle{Bold: true} - - signedResults := []string{} - signedData := binding.BindStringList(&signedResults) - signedList := widget.NewListWithData(signedData, - func() fyne.CanvasObject { - return container.NewStack( - container.NewHBox( - container.NewStack( - rectWidth90, - widget.NewLabel(""), - ), - ), - ) - }, - func(di binding.DataItem, co fyne.CanvasObject) { - dat := di.(binding.String) - str, err := dat.Get() - if err != nil { - return - } - - split := strings.Split(str, "/") - pos := len(split) - 1 - name := strings.Split(split[pos], ";;;") - - co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(name[0]) - }, - ) - - verifiedResults := []string{} - verifiedData := binding.BindStringList(&verifiedResults) - verifiedList := widget.NewListWithData(verifiedData, - func() fyne.CanvasObject { - return container.NewStack( - container.NewHBox( - container.NewStack( - rectWidth90, - widget.NewLabel(""), - ), - ), - ) - }, - func(di binding.DataItem, co fyne.CanvasObject) { - dat := di.(binding.String) - str, err := dat.Get() - if err != nil { - return - } - - split := strings.Split(str, "/") - pos := len(split) - 1 - name := strings.Split(split[pos], ";;;") - - co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(name[0]) - }, - ) - - errorText := canvas.NewText(" ", colors.Green) - errorText.TextSize = 12 - errorText.Alignment = fyne.TextAlignCenter - - dialogBrowse := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) { - if err != nil { - logger.Errorf("[Engram] Open file dialog: %s\n", err) - errorText.Text = "could not open file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if uc == nil { - return - } - - if session.Domain == "app.sign" { - inputFileName := uc.URI().Name() - outputFileName := inputFileName + ".signed" - - go func() { - dialogFileSign := dialog.NewFileSave(func(uri fyne.URIWriteCloser, err error) { - if err != nil { - logger.Errorf("[Engram] Save file dialog: %s\n", err) - fyne.Do(func() { - errorText.Text = "could not open signed file" - errorText.Color = colors.Red - errorText.Refresh() - }) - - return - } - - if uri == nil { - return // Canceled - } - - filedata, err := readFromURI(uc) - if err != nil { - logger.Errorf("[Engram] Cannot read file data for %s: %s\n", inputFileName, err) - fyne.Do(func() { - errorText.Text = "could not read file" - errorText.Color = colors.Red - errorText.Refresh() - }) - - return - } - - _, err = writeToURI(engram.Disk.SignData(filedata), uri) - if err != nil { - logger.Errorf("[Engram] Cannot sign %s: %s\n", inputFileName, err) - fyne.Do(func() { - errorText.Text = "could not write signed file" - errorText.Color = colors.Red - errorText.Refresh() - }) - - return - } - - outputFile := uri.URI().Name() - if a.Driver().Device().IsMobile() { - // Mobile uses content access name on save dialog - outputFile = outputFileName - } - - logger.Printf("[Engram] Successfully signed file: %s\n", outputFile) - - fyne.Do(func() { - errorText.Text = "signed file successfully" - errorText.Color = colors.Green - errorText.Refresh() - - signedResults = append(signedResults, outputFile) - signedData.Set(signedResults) - signedList.Refresh() - - signedLen := len(signedResults) - labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", signedLen, signedLen) - labelResults.Refresh() - }) - - }, session.Window) - - if !a.Driver().Device().IsMobile() { - // Open file browser in current directory - uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) - if err == nil { - dialogFileSign.SetLocation(uri) - } else { - logger.Errorf("[Engram] Could not open current directory %s\n", err) - } - } - - fyne.Do(func() { - dialogFileSign.SetFilter(storage.NewExtensionFileFilter([]string{".signed"})) - dialogFileSign.SetView(dialog.ListView) - dialogFileSign.SetFileName(outputFileName) - dialogFileSign.Resize(fyne.NewSize(ui.Width, ui.Height)) - dialogFileSign.SetConfirmText("Save Sign") - dialogFileSign.Show() - }) - }() - } else { - fileName := uc.URI().Name() - if !strings.HasSuffix(fileName, ".signed") { - errorText.Text = "verifying requires a .signed file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - filedata, err := readFromURI(uc) - if err != nil { - logger.Errorf("[Engram] Cannot read file data for %s: %s\n", fileName, err) - errorText.Text = "could not read file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - // Trim off .signed from file because engram.Disk.CheckFileSignature() adds it back on anyways - https://github.com/deroproject/derohe/blob/main/walletapi/wallet.go#L709 - fileName = strings.TrimSuffix(fileName, ".signed") - signer, message, err := engram.Disk.CheckSignature(filedata) - if err != nil { - logger.Errorf("[Engram] Signature verification failed for %s: %s\n", fileName, err) - errorText.Text = "signature verification failed" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - logger.Printf("[Engram] %s signed by: %s\n", fileName, signer.String()) - if isASCII(string(message)) { - fmt.Println(string(message)) - } - - errorText.Text = "verified file successfully" - errorText.Color = colors.Green - errorText.Refresh() - - verifiedResults = append(verifiedResults, fileName+";;;"+signer.String()) - verifiedData.Set(verifiedResults) - verifiedList.Refresh() - - verifiedLen := len(verifiedResults) - labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", verifiedLen, verifiedLen) - labelResults.Refresh() - } - }, session.Window) - - if !a.Driver().Device().IsMobile() { - // Open file browser in current directory - uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) - if err == nil { - dialogBrowse.SetLocation(uri) - } else { - logger.Errorf("[Engram] Could not open current directory %s\n", err) - } - } - - dialogBrowse.Resize(fyne.NewSize(ui.Width, ui.Height)) - dialogBrowse.SetView(dialog.ListView) - - signedList.OnSelected = func(id widget.ListItemID) { - errorText.Text = "" - errorText.Refresh() - } - - verifiedList.OnSelected = func(id widget.ListItemID) { - errorText.Text = "" - errorText.Refresh() - - if session.Domain == "app.verify" { - split := strings.Split(verifiedResults[id], ";;;") - filepath := strings.Split(split[0], "/") - filename := filepath[len(filepath)-1] - filename = strings.Replace(filename, ".signed", "", -1) - - rectSpan := canvas.NewRectangle(color.Transparent) - rectSpan.SetMinSize(fyne.NewSize(ui.Width*0.99, 10)) - - header := canvas.NewText("S I G N A T U R E D E T A I L", colors.Gray) - header.TextSize = 16 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - labelStatus := canvas.NewText(" VERIFICATION STATUS", colors.Gray) - labelStatus.TextSize = 12 - labelStatus.TextStyle = fyne.TextStyle{Bold: true} - labelStatus.Alignment = fyne.TextAlignCenter - - valueStatus := canvas.NewText(" Verified", colors.Green) - valueStatus.TextSize = 22 - valueStatus.TextStyle = fyne.TextStyle{Bold: true} - valueStatus.Alignment = fyne.TextAlignCenter - - labelFilename := canvas.NewText(" FILENAME", colors.Gray) - labelFilename.TextSize = 14 - labelFilename.TextStyle = fyne.TextStyle{Bold: true} - - valueFilename := widget.NewRichTextFromMarkdown(filename) - valueFilename.Wrapping = fyne.TextWrapBreak - - labelSigner := canvas.NewText(" SIGNER ADDRESS", colors.Gray) - labelSigner.TextSize = 14 - labelSigner.TextStyle = fyne.TextStyle{Bold: true} - - valueSigner := widget.NewRichTextFromMarkdown(split[1]) - valueSigner.Wrapping = fyne.TextWrapBreak - - labelSeparator := widget.NewRichTextFromMarkdown("") - labelSeparator.Wrapping = fyne.TextWrapOff - labelSeparator.ParseMarkdown("---") - - labelSeparator2 := widget.NewRichTextFromMarkdown("") - labelSeparator2.Wrapping = fyne.TextWrapOff - labelSeparator2.ParseMarkdown("---") - - labelSeparator3 := widget.NewRichTextFromMarkdown("") - labelSeparator3.Wrapping = fyne.TextWrapOff - labelSeparator3.ParseMarkdown("---") - - linkBack := widget.NewHyperlinkWithStyle("Hide Details", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - removeOverlays() - } - - overlay := session.Window.Canvas().Overlays() - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - overlay.Add( - container.NewStack( - &iframe{}, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - rectSpan, - rectSpacer, - header, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - valueStatus, - rectSpacer, - labelStatus, - ), - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator, - rectSpacer, - rectSpacer, - labelFilename, - rectSpacer, - valueFilename, - rectSpacer, - rectSpacer, - labelSeparator2, - rectSpacer, - rectSpacer, - labelSigner, - rectSpacer, - valueSigner, - rectSpacer, - rectSpacer, - labelSeparator3, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - ), - layout.NewSpacer(), - ), - ), - ) - overlay.Top().Show() - - verifiedList.UnselectAll() - } - } - - btnBrowse := widget.NewButton("Browse Files", nil) - btnBrowse.OnTapped = func() { - errorText.Text = "" - errorText.Refresh() - if session.Domain == "app.sign" { - dialogBrowse.SetFilter(nil) - dialogBrowse.SetConfirmText("Open") - } else { - dialogBrowse.SetFilter(storage.NewExtensionFileFilter([]string{".signed"})) - dialogBrowse.SetConfirmText("Verify") - } - - dialogBrowse.Show() - } - - labelAction := canvas.NewText("( DRAG-AND-DROP ENABLED )", colors.Gray) - labelAction.TextSize = 12 - labelAction.Alignment = fyne.TextAlignLeading - labelAction.TextStyle = fyne.TextStyle{Bold: true} - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - entryAddress := widget.NewEntry() - entryAddress.PlaceHolder = "Username or Address" - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - labelSeparator := widget.NewRichTextFromMarkdown("") - labelSeparator.Wrapping = fyne.TextWrapOff - labelSeparator.ParseMarkdown("---") - - labelSeparator2 := widget.NewRichTextFromMarkdown("") - labelSeparator2.Wrapping = fyne.TextWrapOff - labelSeparator2.ParseMarkdown("---") - - labelSeparator3 := widget.NewRichTextFromMarkdown("") - labelSeparator3.Wrapping = fyne.TextWrapOff - labelSeparator3.ParseMarkdown("---") - - labelSeparator4 := widget.NewRichTextFromMarkdown("") - labelSeparator4.Wrapping = fyne.TextWrapOff - labelSeparator4.ParseMarkdown("---") - - linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - removeOverlays() - capture := session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - session.Domain = "app.wallet" - session.LastDomain = capture - } - - selectType := widget.NewSelect([]string{"Sign Files", "Verify Signed Files"}, nil) - selectType.SetSelected("Sign Files") - - // Handle drag & drop files for file signing/verifying - session.Window.SetOnDropped(func(p fyne.Position, files []fyne.URI) { - errorText.Text = "" - errorText.Refresh() - - if session.Domain == "app.sign" { - if a.Driver().Device().IsMobile() { - if len(files) > 1 { - errorText.Text = "single file only" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - inputFileName := files[0].Name() - - dialogFileSign := dialog.NewFileSave(func(uri fyne.URIWriteCloser, err error) { - if err != nil { - logger.Errorf("[Engram] File dialog: %s\n", err) - errorText.Text = "could not open signed file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if uri == nil { - return // Canceled - } - - uc, err := storage.Reader(files[0]) - if err != nil { - logger.Errorf("[Engram] Cannot create reader for %s: %s\n", inputFileName, err) - errorText.Text = "could not access file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - filedata, err := readFromURI(uc) - if err != nil { - logger.Errorf("[Engram] Cannot read file data for %s: %s\n", inputFileName, err) - errorText.Text = "could not read file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - _, err = writeToURI(engram.Disk.SignData(filedata), uri) - if err != nil { - logger.Errorf("[Engram] Cannot sign %s: %s\n", inputFileName, err) - errorText.Text = "could not write signed file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - // Mobile uses content access name on save dialog - outputFile := inputFileName + ".signed" - - logger.Printf("[Engram] Successfully signed file: %s\n", outputFile) - - errorText.Text = "signed file successfully" - errorText.Color = colors.Green - errorText.Refresh() - - signedResults = append(signedResults, outputFile) - signedData.Set(signedResults) - signedList.Refresh() - - signedLen := len(signedResults) - labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", signedLen, signedLen) - labelResults.Refresh() - - }, session.Window) - - dialogFileSign.SetFilter(storage.NewExtensionFileFilter([]string{".signed"})) - dialogFileSign.SetView(dialog.ListView) - dialogFileSign.SetFileName(inputFileName) - dialogFileSign.Resize(fyne.NewSize(ui.Width, ui.Height)) - dialogFileSign.SetConfirmText("Save Sign") - dialogFileSign.Show() - } else { - singedLen := len(signedResults) - count := 1 + singedLen - - for i, f := range files { - inputFileName := f.Name() - - uc, err := storage.Reader(f) - if err != nil { - logger.Errorf("[Engram] Cannot create reader for %s: %s\n", inputFileName, err) - errorText.Text = fmt.Sprintf("could not access file %d", i) - errorText.Color = colors.Red - errorText.Refresh() - continue - } - - filedata, err := readFromURI(uc) - if err != nil { - logger.Errorf("[Engram] Cannot read file data for %s: %s\n", inputFileName, err) - errorText.Text = fmt.Sprintf("could not read file %d", i) - errorText.Color = colors.Red - errorText.Refresh() - continue - } - - outputfile := inputFileName + ".signed" - - if err := os.WriteFile(outputfile, engram.Disk.SignData(filedata), 0600); err != nil { - logger.Errorf("[Engram] Cannot sign %s: %s\n", inputFileName, err) - errorText.Text = fmt.Sprintf("cannot sign file %d", i) - errorText.Color = colors.Red - errorText.Refresh() - } else { - logger.Printf("[Engram] Successfully signed file: %s\n", outputfile) - labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", count, len(files)+singedLen) - labelResults.Refresh() - signedResults = append(signedResults, outputfile) - count += 1 - } - } - - signedData.Set(signedResults) - signedList.Refresh() - } - } else if session.Domain == "app.verify" { - if a.Driver().Device().IsMobile() { - dialogVerify := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) { - errorText.Text = "" - if uc != nil { - fileName := uc.URI().Name() - if filepath.Ext(fileName) != ".signed" { - errorText.Text = "requires a .signed file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - filedata, err := readFromURI(uc) - if err != nil { - logger.Errorf("[Engram] Cannot read URI file data for %s: %s\n", fileName, err) - errorText.Text = "cannot read file data" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - signer, message, err := engram.Disk.CheckSignature(filedata) - if err != nil { - logger.Errorf("[Engram] Signature verification failed for %s: %s\n", fileName, err) - errorText.Text = "signature verification failed" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - logger.Printf("[Engram] %s signed by: %s\n", fileName, signer.String()) - if isASCII(string(message)) { - fmt.Println(string(message)) - } - - errorText.Text = "verified file successfully" - errorText.Color = colors.Green - errorText.Refresh() - - verifiedResults = append(verifiedResults, fileName+";;;"+signer.String()) - verifiedData.Set(verifiedResults) - verifiedList.Refresh() - - verifiedLen := len(verifiedResults) - labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", verifiedLen, verifiedLen) - labelResults.Refresh() - } - }, session.Window) - - dialogVerify.Resize(fyne.NewSize(ui.Width, ui.Height)) - dialogVerify.SetView(dialog.ListView) - dialogVerify.Show() - } else { - verifiedLen := len(verifiedResults) - count := 1 + verifiedLen - - for i, f := range files { - inputFileName := f.Name() - - uc, err := storage.Reader(f) - if err != nil { - logger.Errorf("[Engram] Cannot create reader for %s: %s\n", inputFileName, err) - errorText.Text = fmt.Sprintf("could not access file %d", i) - errorText.Color = colors.Red - errorText.Refresh() - continue - } - - filedata, err := readFromURI(uc) - if err != nil { - logger.Errorf("[Engram] Cannot read file data for %s: %s\n", inputFileName, err) - errorText.Text = fmt.Sprintf("could not read file %d", i) - errorText.Color = colors.Red - errorText.Refresh() - continue - } - - outputfile := strings.TrimSuffix(inputFileName, ".signed") - - if signer, message, err := engram.Disk.CheckSignature(filedata); err != nil { - logger.Errorf("[Engram] Signature verification failed for %s: %s\n", inputFileName, err) - errorText.Text = fmt.Sprintf("signature verification %d failed", i) - errorText.Color = colors.Red - errorText.Refresh() - } else { - logger.Printf("[Engram] Signed by: %s\n", signer.String()) - - if isASCII(string(message)) { - logger.Printf("[Engram] Message for %s: %s\n", inputFileName, signer.String()) - } - - if err := os.WriteFile(outputfile, message, 0600); err != nil { - logger.Errorf("[Engram] Cannot write output file for %s: %s\n", outputfile, err) - continue - } - - logger.Printf("[Engram] Successfully wrote message to file: %s\n", outputfile) - - labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", count, len(files)+verifiedLen) - labelResults.Refresh() - verifiedResults = append(verifiedResults, inputFileName+";;;"+signer.String()) - count += 1 - } - } - - verifiedData.Set(verifiedResults) - verifiedList.Refresh() - } - } - }) - - top := container.NewVBox( - rectSpacer, - rectSpacer, - heading, - ) - - center := container.NewStack( - rectWidth100, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - container.NewVBox( - rectSpacer, - rectSpacer, - selectType, - rectSpacer, - rectSpacer, - btnBrowse, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - labelAction, - layout.NewSpacer(), - ), - rectSpacer, - errorText, - rectSpacer, - labelSeparator, - rectSpacer, - rectSpacer, - labelResults, - rectSpacer, - rectSpacer, - container.NewStack( - rectBox, - signedList, - ), - rectSpacer, - ), - ), - layout.NewSpacer(), - ), - ) - - selectType.OnChanged = func(s string) { - if s == "Sign Files" { - session.Domain = "app.sign" - signedList.UnselectAll() - center.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[18].(*fyne.Container).Objects[1] = signedList - signedData.Set(signedResults) - signedList.Refresh() - signedLen := len(signedResults) - labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", signedLen, signedLen) - labelResults.Refresh() - } else { - session.Domain = "app.verify" - center.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[18].(*fyne.Container).Objects[1] = verifiedList - verifiedData.Set(verifiedResults) - verifiedList.Refresh() - verifiedLen := len(verifiedResults) - labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", verifiedLen, verifiedLen) - labelResults.Refresh() - } - - errorText.Text = "" - errorText.Refresh() - } - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - body := container.NewVBox( - top, - center, - ) - - layout := container.NewStack( - frame, - container.NewBorder( - body, - bottom, - nil, - nil, - ), - ) - - return NewVScroll(layout) -} - -func layoutContractBuilder(promptText string) fyne.CanvasObject { - session.Domain = "app.sc.builder" - - frame := &iframe{} - - rectBox := canvas.NewRectangle(color.Transparent) - rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.9, ui.MaxHeight*0.35)) - - rectWidth100 := canvas.NewRectangle(color.Transparent) - rectWidth100.SetMinSize(fyne.NewSize(ui.Width*0.99, 10)) - - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width*0.9, 10)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - heading := canvas.NewText("C O N T R A C T B U I L D E R", colors.Gray) - heading.TextSize = 16 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - errorText := canvas.NewText(promptText, colors.Red) - errorText.TextSize = 12 - errorText.Alignment = fyne.TextAlignCenter - - // Open .bas SC from file browser - dialogBrowse := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) { - errorText.Text = "" - if uc != nil { - filename := uc.URI().Name() - if uc.URI().MimeType() != "text/plain" { - logger.Errorf("[Engram] Cannot open file %s in contract builder\n", filename) - errorText.Text = "cannot open file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if filepath.Ext(filename) != ".bas" { - errorText.Text = "requires a .bas file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - filedata, err := readFromURI(uc) - if err != nil { - logger.Errorf("[Engram] Cannot read URI file data for %s: %s\n", filename, err) - errorText.Text = "cannot read file data" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if !isASCII(string(filedata)) { - errorText.Text = "invalid file data" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - removeOverlays() - capture := session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutContractEditor(strings.TrimSuffix(filename, ".bas"), string(filedata))) - session.LastDomain = capture - } - }, session.Window) - - if !a.Driver().Device().IsMobile() { - // Open file browser in current directory - uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) - if err == nil { - dialogBrowse.SetLocation(uri) - } else { - logger.Errorf("[Engram] Could not open current directory %s\n", err) - } - } - - // Resize browser to app size and add SC file filter - dialogBrowse.Resize(fyne.NewSize(ui.Width, ui.Height)) - dialogBrowse.SetFilter(storage.NewExtensionFileFilter([]string{".bas"})) - dialogBrowse.SetView(dialog.ListView) - - btnBrowse := widget.NewButton("Browse Files", nil) - btnBrowse.OnTapped = func() { - dialogBrowse.Show() - } - - btnEditor := widget.NewButton("Open Editor", nil) - btnEditor.OnTapped = func() { - removeOverlays() - capture := session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutContractEditor("", "")) - session.LastDomain = capture - } - - labelAction := canvas.NewText("( DRAG-AND-DROP ENABLED )", colors.Gray) - labelAction.TextSize = 12 - labelAction.Alignment = fyne.TextAlignLeading - labelAction.TextStyle = fyne.TextStyle{Bold: true} - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - labelSeparator := widget.NewRichTextFromMarkdown("") - labelSeparator.Wrapping = fyne.TextWrapOff - labelSeparator.ParseMarkdown("---") - - linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - removeOverlays() - capture := session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - session.LastDomain = capture - } - - // Handle drag & drop files for smart contracts - session.Window.SetOnDropped(func(p fyne.Position, files []fyne.URI) { - if session.Domain == "app.sc.builder" { - errorText.Text = "" - errorText.Refresh() - - if len(files) > 1 { - errorText.Text = "single .bas file only" - errorText.Color = colors.Red - errorText.Refresh() - return - } else { - uri, err := storage.Reader(files[0]) - if err != nil { - errorText.Text = "could not read dropped file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - filename := files[0].Name() - if filepath.Ext(filename) != ".bas" { - errorText.Text = "requires a .bas file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - filedata, err := readFromURI(uri) - if err != nil { - logger.Errorf("[Engram] Cannot read file data for %s: %s\n", filename, err) - errorText.Text = "cannot read file data" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - go func() { - fyne.Do(func() { - removeOverlays() - capture := session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutContractEditor(strings.TrimSuffix(filepath.Base(filename), ".bas"), string(filedata))) - session.LastDomain = capture - }) - }() - } - } - }) - - entryClone := widget.NewEntry() - entryClone.SetPlaceHolder("Clone SCID") - if session.Offline { - entryClone.Disable() - entryClone.SetText("Cloning disabled in offline mode") - } - - entryClone.OnChanged = func(s string) { - if len(s) == 64 { - removeOverlays() - capture := session.Window.Content() - session.Window.SetContent(layoutTransition()) - - code, err := getContractCode(s) - if err != nil { - logger.Errorf("[Engram] Clone SC: %s\n", err) - errorText.Text = "cannot get contract for clone" - errorText.Color = colors.Red - errorText.Refresh() - session.Window.SetContent(layoutContractBuilder(errorText.Text)) - return - } - - if code == "" { - errorText.Text = "contract does not exists" - errorText.Color = colors.Red - errorText.Refresh() - session.Window.SetContent(layoutContractBuilder(errorText.Text)) - return - } - - session.Window.SetContent(layoutContractEditor("", code)) - session.LastDomain = capture - } else { - if s == "" { - errorText.Text = "" - errorText.Refresh() - } else { - errorText.Text = "not a valid scid" - errorText.Color = colors.Red - errorText.Refresh() - } - } - } - - top := container.NewVBox( - rectSpacer, - rectSpacer, - heading, - ) - - center := container.NewStack( - rectWidth100, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - container.NewVBox( - rectSpacer, - rectSpacer, - entryClone, - errorText, - rectSpacer, - btnBrowse, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - labelAction, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - btnEditor, - rectSpacer, - labelSeparator, - rectSpacer, - rectBox, - ), - ), - layout.NewSpacer(), - ), - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - body := container.NewVBox( - top, - center, - ) - - layout := container.NewStack( - frame, - container.NewBorder( - body, - bottom, - nil, - nil, - ), - ) - - return NewVScroll(layout) -} - -func layoutContractEditor(filename, filedata string) fyne.CanvasObject { - session.Domain = "app.sc.editor" - - frame := &iframe{} - - rectBox := canvas.NewRectangle(color.Transparent) - rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.9, ui.MaxHeight*0.35)) - - rectWidth100 := canvas.NewRectangle(color.Transparent) - rectWidth100.SetMinSize(fyne.NewSize(ui.Width*0.99, 10)) - - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width*0.9, 10)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - rectCode := canvas.NewRectangle(color.Transparent) - rectCode.SetMinSize(fyne.NewSize(ui.MaxWidth*0.9, ui.MaxHeight*0.35)) - - heading := canvas.NewText("C O N T R A C T E D I T O R", colors.Green) - heading.TextSize = 16 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - labelHeaders := canvas.NewText(" HEADERS", colors.Gray) - labelHeaders.TextSize = 14 - labelHeaders.Alignment = fyne.TextAlignLeading - labelHeaders.TextStyle = fyne.TextStyle{Bold: true} - - labelCode := canvas.NewText(" CODE (DVM-BASIC)", colors.Gray) - labelCode.TextSize = 14 - labelCode.Alignment = fyne.TextAlignLeading - labelCode.TextStyle = fyne.TextStyle{Bold: true} - - labelCodeSize := canvas.NewText("(0.0KB) ", colors.Green) - labelCodeSize.TextSize = 12 - labelCodeSize.Alignment = fyne.TextAlignTrailing - - errorText := canvas.NewText(" ", colors.Green) - errorText.TextSize = 12 - errorText.Alignment = fyne.TextAlignCenter - - var nameHdr, iconURLHdr, descrHdr string - nameHdr = filename - - // Get headers from contract code initialize func - if filedata != "" { - contract, _, err := dvm.ParseSmartContract(filedata) - if err == nil { - for n, f := range contract.Functions { - if n == "InitializePrivate" || n == "Initialize" { - for _, line := range f.Lines { - if len(line) < 6 { - // Line is to short to be a STORE - continue - } - - for i, parts := range line { - if parts == "STORE" { - // Find if code is storing headers - header := tela.Header(line[i+2]) - if header == tela.HEADER_NAME || header == tela.HEADER_NAME_V2 { - nameHdr = strings.Trim(line[i+4], `"`) - } else if header == tela.HEADER_ICON_URL || header == tela.HEADER_ICON_URL_V2 { - iconURLHdr = strings.Trim(line[i+4], `"`) - } else if header == tela.HEADER_DESCRIPTION || header == tela.HEADER_DESCRIPTION_V2 { - descrHdr = strings.Trim(line[i+4], `"`) - } - } - } - } - } - } - } - } - - entryName := widget.NewEntry() - entryName.SetText(nameHdr) - entryName.SetPlaceHolder("Name") - entryName.Validator = func(s string) (err error) { - if s == "" { - err = fmt.Errorf("enter a name") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - errorText.Text = "" - errorText.Refresh() - - return nil - } - - entryIcon := widget.NewEntry() - entryIcon.SetPlaceHolder("Icon") - entryIcon.SetText(iconURLHdr) - entryIcon.Validator = func(s string) (err error) { - if s == "" { - err = fmt.Errorf("enter icon URL") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - errorText.Text = "" - errorText.Refresh() - - return nil - } - - var entryUpdated bool - entryDescription := widget.NewEntry() - entryDescription.SetPlaceHolder("Description") - entryDescription.SetText(descrHdr) - entryDescription.Validator = func(s string) (err error) { - if s == "" && entryUpdated { - err = fmt.Errorf("enter description") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - entryUpdated = true - - errorText.Text = "" - errorText.Refresh() - - return nil - } - - var unsavedChanges bool - entryCode := widget.NewMultiLineEntry() - entryCode.SetPlaceHolder("Code") - entryCode.Wrapping = fyne.TextWrapWord - entryCode.OnChanged = func(s string) { - errorText.Text = "" - errorText.Refresh() - - size := tela.GetCodeSizeInKB(s) - - labelCodeSize.Text = fmt.Sprintf("(%.2fKB) ", size) - if size > 20 { - labelCodeSize.Color = colors.Red - errorText.Text = "contract size is to large" - errorText.Color = colors.Red - errorText.Refresh() - } else if size > 18.5 { - labelCodeSize.Color = colors.Yellow - } else { - labelCodeSize.Color = colors.Green - } - labelCodeSize.Refresh() - - if s != filedata { - unsavedChanges = true - } else { - unsavedChanges = false - } - } - - entryCode.SetText(filedata) - - options := []string{"Initialize", "Set Headers", "New Function", "Parse", "Format", "Clear", "Export"} - if !session.Offline { - splice := append([]string{"Import Function"}, options[3:]...) - options = append(options[:3], splice...) - options = append(options, "Install") - } - - selectEditor := widget.NewSelect(options, nil) - - entryForm := container.NewVBox( - rectSpacer, - selectEditor, - rectSpacer, - container.NewBorder( - nil, - nil, - labelCode, - labelCodeSize, - nil, - ), - container.NewStack( - rectCode, - entryCode, - ), - errorText, - rectSpacer, - labelHeaders, - rectSpacer, - entryName, - entryIcon, - entryDescription, - ) - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - labelSeparator := widget.NewRichTextFromMarkdown("") - labelSeparator.Wrapping = fyne.TextWrapOff - labelSeparator.ParseMarkdown("---") - - linkBack := widget.NewHyperlinkWithStyle("Back to Contract Builder", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - if unsavedChanges { - verificationOverlay( - false, - "CONTRACT EDITOR", - "Leave with unsaved changes", - "Confirm", - func(b bool) { - if b { - capture := session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutContractBuilder("")) - session.LastDomain = capture - } - }, - ) - } else { - removeOverlays() - capture := session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutContractBuilder("")) - session.LastDomain = capture - } - } - - selectEditor.OnChanged = func(s string) { - errorText.Text = "" - errorText.Refresh() - - switch s { - case "Initialize": // Set entry text with new starter initialize func - if entryCode.Text == "" { - entryCode.SetText(dvmInitFuncExample()) - errorText.Text = "new initialize function created" - errorText.Color = colors.Green - errorText.Refresh() - return - } - - verificationOverlay( - false, - "CONTRACT EDITOR", - "Reset to default initialize function", - "Confirm", - func(b bool) { - if b { - entryCode.SetText(dvmInitFuncExample()) - errorText.Text = "new initialize function created" - errorText.Color = colors.Green - errorText.Refresh() - } - }, - ) - case "New Function": // Add a new starter initialize func to code entry - increment := 1 - var hasInitFunc bool - fn := tela.GetSmartContractFuncNames(entryCode.Text) - for _, n := range fn { - // Increment function number if new() already esists - if strings.TrimRight(n, "0123456789") == "new" { - increment++ - } - - if n == "InitializePrivate" || n == "Initialize" { - hasInitFunc = true - } - } - - if !hasInitFunc { - errorText.Text = "no initialize function" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if strings.HasSuffix(entryCode.Text, "\n") { - entryCode.SetText(entryCode.Text + "\n" + dvmFuncExample(increment)) - } else { - entryCode.SetText(entryCode.Text + "\n\n" + dvmFuncExample(increment)) - } - - errorText.Text = "new function added" - errorText.Color = colors.Green - errorText.Refresh() - case "Import Function": // Import a function from an on-chain scid - var hasInitFunc bool - fn := tela.GetSmartContractFuncNames(entryCode.Text) - for _, n := range fn { - if n == "InitializePrivate" || n == "Initialize" { - hasInitFunc = true - break - } - } - - entryEntrypoint := widget.NewEntry() - entryEntrypoint.SetPlaceHolder("Function name") - entryEntrypoint.Validator = func(s string) (err error) { - if s == "" || (len(s) > 0 && !unicode.IsLetter(rune(s[0]))) { - return fmt.Errorf("invalid function name") - } - - return nil - } - - entrySCID := widget.NewEntry() - entrySCID.SetPlaceHolder("SCID") - entrySCID.Validator = func(s string) (err error) { - if len(s) != 64 { - return fmt.Errorf("not a valid scid") - } - - return nil - } - - overlay := session.Window.Canvas().Overlays() - - header := canvas.NewText("CONTRACT EDITOR", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - subHeader := canvas.NewText("Import an existing function", colors.Account) - subHeader.TextSize = 22 - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - - linkCancel := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkCancel.OnTapped = func() { - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - paramsContainer := container.NewVBox(entrySCID, entryEntrypoint) - - btnImport := widget.NewButton("Import", nil) - btnImport.OnTapped = func() { - if entrySCID.Validate() != nil { - entrySCID.FocusGained() - entrySCID.FocusLost() - return - } - - if entryEntrypoint.Validate() != nil { - entryEntrypoint.FocusGained() - entryEntrypoint.FocusLost() - return - } - - defer removeOverlays() - - if !hasInitFunc { - if entryEntrypoint.Text != "InitializePrivate" && entryEntrypoint.Text != "Initialize" { - errorText.Text = "need initializing function first" - errorText.Color = colors.Red - errorText.Refresh() - return - } - } - - code, err := getContractCode(entrySCID.Text) - if err != nil { - logger.Errorf("[Engram] Editor import function error: %s\n", err) - errorText.Text = "cannot get contract for function import" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if code == "" { - errorText.Text = "contract does not exists" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - entrypoint := entryEntrypoint.Text - contract, pos, err := dvm.ParseSmartContract(code) - if err != nil { - logger.Errorf("[Engram] Editor import parsing error: %s %s\n", err, pos) - errorText.Text = fmt.Sprintf("error parsing contract %s", pos) - errorText.Color = colors.Red - errorText.Refresh() - return - } - - var tempSC dvm.SmartContract - tempSC.Functions = make(map[string]dvm.Function) - - for name, f := range contract.Functions { - if name == entrypoint { - tempSC.Functions[name] = f - break - } - } - - if tempSC.Functions[entrypoint].LineNumbers == nil { - errorText.Text = "function not found on scid" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - formatted, err := tela.FormatSmartContract(tempSC, fmt.Sprintf("Function %s", entrypoint)) - if err != nil { - logger.Errorf("[Engram] Editor import formatting error: %s\n", err) - errorText.Text = "could not parse dvm to string" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if entryCode.Text == "" { - entryCode.SetText(formatted) - } else if strings.HasSuffix(entryCode.Text, "\n") { - entryCode.SetText(entryCode.Text + "\n" + formatted) - } else { - entryCode.SetText(entryCode.Text + "\n\n" + formatted) - } - - errorText.Text = "imported function successfully" - errorText.Color = colors.Green - errorText.Refresh() - } - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - container.NewCenter( - subHeader, - ), - widget.NewLabel(""), - rectSpacer, - rectSpacer, - paramsContainer, - rectSpacer, - rectSpacer, - btnImport, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkCancel, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - case "Clear": // Clears SC code entry - verificationOverlay( - false, - "CONTRACT EDITOR", - "Clear code entry", - "Confirm", - func(b bool) { - if b { - entryCode.SetText("") - errorText.Text = "contract code cleared" - errorText.Color = colors.Green - errorText.Refresh() - } - }, - ) - case "Parse": // Parse SC for errors - if entryCode.Text == "" { - errorText.Text = "contract code is empty" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - _, pos, err := dvm.ParseSmartContract(entryCode.Text) - if err != nil { - errorText.Text = fmt.Sprintf("error parsing contract %s", pos) - errorText.Color = colors.Red - errorText.Refresh() - logger.Errorf("[Engram] Parse SC: %s %s\n", err, pos) - return - } - - errorText.Text = "contract parsed successfully" - errorText.Color = colors.Green - errorText.Refresh() - case "Set Headers": // Set Artificer standard headers into initialize func - if entryCode.Text == "" { - errorText.Text = "contract code is empty" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - contract, pos, err := dvm.ParseSmartContract(entryCode.Text) - if err != nil { - errorText.Text = fmt.Sprintf("error parsing contract %s", pos) - errorText.Color = colors.Red - errorText.Refresh() - logger.Errorf("[Engram] Set SC Headers: %s %s\n", err, pos) - return - } - - if entryName.Validate() == nil && entryIcon.Validate() == nil && entryDescription.Validate() == nil { - // Create add header func to use later in confirmations - addFunction := func() { - var haveHeader [uint64(3)]bool - for name, function := range contract.Functions { - // Find initialize func - if name == "Initialize" || name == "InitializePrivate" { - for _, line := range function.Lines { - if len(line) < 6 { - // Line is to short to be a STORE - continue - } - - for i, parts := range line { - if parts == "STORE" { - // Find if code is storing headers and update vars with header entry value - header := tela.Header(line[i+2]) - if header == tela.HEADER_NAME || header == tela.HEADER_NAME_V2 { - haveHeader[0] = true - line[i+4] = fmt.Sprintf(`"%s"`, entryName.Text) - } else if header == tela.HEADER_ICON_URL || header == tela.HEADER_ICON_URL_V2 { - haveHeader[1] = true - line[i+4] = fmt.Sprintf(`"%s"`, entryIcon.Text) - } else if header == tela.HEADER_DESCRIPTION || header == tela.HEADER_DESCRIPTION_V2 { - haveHeader[2] = true - line[i+4] = fmt.Sprintf(`"%s"`, entryDescription.Text) - } - } - } - } - } - } - - // Check if any headers are missing - var needToAdd, hasInitFunc bool - for _, hh := range haveHeader { - if !hh { - needToAdd = true - break - } - } - - // SC has all headers already, update the code entry - if !needToAdd { - code, err := tela.FormatSmartContract(contract, entryCode.Text) - if err != nil { - logger.Errorf("[Engram] Format code error: %s\n", err) - err = errors.New("could not parse dvm to string") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - entryCode.SetText(code) - - errorText.Text = "headers updated" - errorText.Color = colors.Green - errorText.Refresh() - return - } - - // SC is missing one or more headers so they will be added into initialize func - for name, function := range contract.Functions { - if name == "Initialize" || name == "InitializePrivate" { - hasInitFunc = true - - lineLen := len(function.LineNumbers) - indexEnd := lineLen - 1 - - // Starting from the last line number loop upwards - for i := 0; i < lineLen; i++ { - index := indexEnd - i - if index < 0 { - break - } - - line := function.Lines[function.LineNumbers[index]] - if len(line) < 1 { - continue - } - - // If line is RETURN 0 will inject headers here and push RETURN 0 line down if there is room - if line[0] == "RETURN" && line[1] == "0" { - if index-1 < 0 { - err = errors.New("no room for header lines") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } else if i > 0 && function.LineNumbers[index+1] < function.LineNumbers[index]+4 { - err = fmt.Errorf("no room for header lines below %d", function.LineNumbers[index]) - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } else { - var addedLines, skipedLines uint64 - for u := uint64(1); u < 5; u++ { - addLineNum := function.LineNumbers[index] + (u - 1) - skipedLines - switch u { - case 1: // nameHdr - if !haveHeader[0] { - function.Lines[addLineNum] = []string{"STORE", "(", `"var_header_name"`, ",", fmt.Sprintf(`"%s"`, entryName.Text), ")"} - addedLines++ - } else { - // Count skip if we have already to subtract to line number - skipedLines++ - continue - } - case 2: // iconURLHdr - if !haveHeader[1] { - function.Lines[addLineNum] = []string{"STORE", "(", `"var_header_icon"`, ",", fmt.Sprintf(`"%s"`, entryIcon.Text), ")"} - if skipedLines != 1 { - function.LineNumbers = append(function.LineNumbers, addLineNum) - } - addedLines++ - } else { - skipedLines++ - continue - } - case 3: // descrHdr - if !haveHeader[2] { - function.Lines[addLineNum] = []string{"STORE", "(", `"var_header_description"`, ",", fmt.Sprintf(`"%s"`, entryDescription.Text), ")"} - if skipedLines != 2 { - function.LineNumbers = append(function.LineNumbers, addLineNum) - } - addedLines++ - } - case 4: - function.Lines[addLineNum] = []string{"RETURN", "0"} - function.LineNumbers = append(function.LineNumbers, addLineNum) - } - } - - // If changes were made sort line numbers and add them to index - if addedLines > 0 { - sort.Slice(function.LineNumbers, func(i, j int) bool { - return function.LineNumbers[i] < function.LineNumbers[j] - }) - - for u, ln := range function.LineNumbers { - function.LinesNumberIndex[ln] = uint64(u) - } - - contract.Functions[name] = function - } - - // fmt.Println("Lines", contract.Functions[name].Lines) - // fmt.Println("LineNumbers", contract.Functions[name].LineNumbers) - // fmt.Println("LineNumberIndex", contract.Functions[name].LinesNumberIndex) - - break - } - } - } - } - } - - if !hasInitFunc { - err = errors.New("no initialize function") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - code, err := tela.FormatSmartContract(contract, entryCode.Text) - if err != nil { - logger.Errorf("[Engram] Format code error: %s\n", err) - err = errors.New("could not parse dvm to string") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if code == entryCode.Text { - errorText.Text = "did not change headers" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - entryCode.SetText(code) - - errorText.Text = "contract headers added successfully" - errorText.Color = colors.Green - errorText.Refresh() - } - - codeCheck, err := tela.FormatSmartContract(contract, entryCode.Text) - if err != nil { - logger.Errorf("[Engram] Format code error: %s\n", err) - err = errors.New("could not parse dvm to string") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - // Warn user that code will be formatted if headers are added - if codeCheck != entryCode.Text { - verificationOverlay( - false, - "CONTRACT EDITOR", - "Setting headers formats your code", - "Confirm", - func(b bool) { - if b { - addFunction() - } - }, - ) - } else { - addFunction() - } - } - case "Format": // Format SC code - if entryCode.Text == "" { - errorText.Text = "contract code is empty" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - contract, pos, err := dvm.ParseSmartContract(entryCode.Text) - if err != nil { - errorText.Text = fmt.Sprintf("error parsing contract %s", pos) - errorText.Color = colors.Red - errorText.Refresh() - logger.Errorf("[Engram] Format: %s %s\n", err, pos) - return - } - - code, err := tela.FormatSmartContract(contract, entryCode.Text) - if err != nil { - logger.Errorf("[Engram] Format code error: %s\n", err) - err = errors.New("could not parse dvm to string") - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if code == entryCode.Text { - errorText.Text = "contract code is formatted" - errorText.Color = colors.Green - errorText.Refresh() - return - } - - verificationOverlay( - false, - "CONTRACT EDITOR", - "Remove whitespace and comments", - "Confirm", - func(b bool) { - if b { - entryCode.SetText(code) - - errorText.Text = "contract code formatted successfully" - errorText.Color = colors.Green - errorText.Refresh() - } - }, - ) - case "Export": // Export SC to file - if entryCode.Text == "" { - errorText.Text = "contract code is empty" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - exportFileName := fmt.Sprintf("%s.bas", entryName.Text) - - data := []byte(entryCode.Text) - dialogFileSave := dialog.NewFileSave(func(uri fyne.URIWriteCloser, err error) { - if err != nil { - logger.Errorf("[Engram] File dialog: %s\n", err) - errorText.Text = "could not export contract file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if uri == nil { - return // Canceled - } - - _, err = writeToURI(data, uri) - if err != nil { - logger.Errorf("[Engram] Exporting %s: %s\n", exportFileName, err) - errorText.Text = "error exporting contract file" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - unsavedChanges = false - filedata = entryCode.Text - errorText.Text = "exported contract file successfully" - errorText.Color = colors.Green - errorText.Refresh() - - }, session.Window) - - if !a.Driver().Device().IsMobile() { - // Open file browser in current directory - uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) - if err == nil { - dialogFileSave.SetLocation(uri) - } else { - logger.Errorf("[Engram] Could not open current directory %s\n", err) - } - } - - dialogFileSave.SetFilter(storage.NewExtensionFileFilter([]string{".bas"})) - dialogFileSave.SetView(dialog.ListView) - dialogFileSave.SetFileName(exportFileName) - dialogFileSave.Resize(fyne.NewSize(ui.Width, ui.Height)) - dialogFileSave.Show() - case "Install": // Install SC - code := entryCode.Text - if code == "" { - errorText.Text = "contract code is empty" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - contract, pos, err := dvm.ParseSmartContract(code) - if err != nil { - logger.Errorf("[Engram] Install SC: %s %s\n", err, pos) - errorText.Text = fmt.Sprintf("error parsing contract %s", pos) - errorText.Color = colors.Red - errorText.Refresh() - return - } - - var entrypoint string - var args []rpc.Argument - for name, function := range contract.Functions { - if name == "InitializePrivate" || name == "Initialize" { - entrypoint = name - for _, v := range function.Params { - switch v.Type { - case 0x4: - args = append(args, rpc.Argument{Name: v.Name, DataType: rpc.DataUint64, Value: v.ValueUint64}) - case 0x5: - args = append(args, rpc.Argument{Name: v.Name, DataType: rpc.DataString, Value: v.ValueString}) - } - } - } - } - - if entrypoint == "" { - errorText.Text = "missing initializing entrypoint" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - function := contract.Functions[entrypoint] - - var paramList []fyne.Widget - if len(function.Params) > 0 { - params := function.Params - for i := range params { - p := i - entry := widget.NewEntry() - entry.PlaceHolder = params[p].Name - if params[p].Type == 0x4 { - entry.PlaceHolder = params[p].Name + " (Numbers Only)" - } - - entry.Validator = func(s string) error { - switch params[p].Type { - case 0x5: - return nil - case 0x4: - if params[p].Name+" (Numbers Only)" == entry.PlaceHolder { - amount, err := globals.ParseAmount(s) - if err != nil { - logger.Debugf("[%s] Param error: %s\n", params[p].Name, err) - return err - } else { - logger.Debugf("[%s] Amount: %d\n", params[p].Name, amount) - } - } - } - - return nil - } - - paramList = append(paramList, entry) - } - - overlay := session.Window.Canvas().Overlays() - - header := canvas.NewText("INSTALL SMART CONTRACT", colors.Gray) - header.TextSize = 14 - header.Alignment = fyne.TextAlignCenter - header.TextStyle = fyne.TextStyle{Bold: true} - - subHeader := canvas.NewText(fmt.Sprintf("%s params", entrypoint), colors.Account) - subHeader.TextSize = 22 - subHeader.Alignment = fyne.TextAlignCenter - subHeader.TextStyle = fyne.TextStyle{Bold: true} - - linkClose := widget.NewHyperlinkWithStyle("Close", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkClose.OnTapped = func() { - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - } - - span := canvas.NewRectangle(color.Transparent) - span.SetMinSize(fyne.NewSize(ui.Width, 10)) - - overlay.Add( - container.NewStack( - &iframe{}, - canvas.NewRectangle(colors.DarkMatter), - ), - ) - - paramsContainer := container.NewVBox() - - btnInstall := widget.NewButton("Install", nil) - - overlay.Add( - container.NewStack( - &iframe{}, - container.NewCenter( - container.NewVBox( - span, - container.NewCenter( - header, - ), - rectSpacer, - rectSpacer, - container.NewCenter( - subHeader, - ), - widget.NewLabel(""), - //selectRingMembers, - rectSpacer, - rectSpacer, - paramsContainer, - rectSpacer, - rectSpacer, - btnInstall, - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkClose, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - ), - ), - ), - ) - - for _, w := range paramList { - c := container.NewStack( - span, - w, - ) - - paramsContainer.Add(c) - paramsContainer.Refresh() - } - - btnInstall.OnTapped = func() { - validated := true - for _, w := range paramList { - entry, ok := w.(*widget.Entry) - if !ok { - continue - } - - if entry.Validate() != nil { - entry.FocusGained() - entry.FocusLost() - validated = false - break - } - } - - if !validated { - return - } - - btnInstall.Text = "Installing..." - btnInstall.Disable() - btnInstall.Refresh() - - verificationOverlay( - true, - "CONTRACT EDITOR", - "", - "", - func(b bool) { - if b { - _, err := installSC(code, args) - if err != nil { - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - unsavedChanges = false - errorText.Text = "contract installed successfully" - errorText.Color = colors.Green - errorText.Refresh() - } - - overlay.Top().Hide() - overlay.Remove(overlay.Top()) - overlay.Remove(overlay.Top()) - }, - ) - } - - paramsContainer.Refresh() - overlay.Top().Show() - } else { - verificationOverlay( - true, - "CONTRACT EDITOR", - "", - "", - func(b bool) { - if b { - _, err := installSC(code, args) - if err != nil { - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - return - } - - unsavedChanges = false - errorText.Text = "contract installed successfully" - errorText.Color = colors.Green - errorText.Refresh() - } - }, - ) - } - } - } - - top := container.NewVBox( - rectSpacer, - rectSpacer, - heading, - ) - - center := container.NewStack( - rectWidth100, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - container.NewVBox( - rectSpacer, - container.NewStack( - rectBox, - entryForm, - ), - rectSpacer, - ), - ), - layout.NewSpacer(), - ), - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - body := container.NewVBox( - top, - center, - ) - - layout := container.NewStack( - frame, - container.NewBorder( - body, - bottom, - nil, - nil, - ), - ) - - return NewVScroll(layout) -} - -func layoutTELA() fyne.CanvasObject { - session.Domain = "app.tela" - - var history []string - var historyData binding.StringList - var historyList *widget.List - - var searching []string - var searchData binding.StringList - var searchList *widget.List - - var serving []string - var servingData binding.StringList - var servingList *widget.List - - frame := &iframe{} - rectLeft := canvas.NewRectangle(color.Transparent) - rectLeft.SetMinSize(fyne.NewSize(ui.Width*0.40, 35)) - - rectRight := canvas.NewRectangle(color.Transparent) - rectRight.SetMinSize(fyne.NewSize(ui.Width*0.58, 35)) - - rectList := canvas.NewRectangle(color.Transparent) - rectList.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.36)) - - rectWidth := canvas.NewRectangle(color.Transparent) - rectWidth.SetMinSize(fyne.NewSize(ui.Width, 10)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - heading := canvas.NewText("T E L A B R O W S E R", colors.Gray) - heading.TextSize = 16 - heading.Alignment = fyne.TextAlignCenter - heading.TextStyle = fyne.TextStyle{Bold: true} - - results := canvas.NewText("", colors.Green) - results.TextSize = 13 - - labelLastScan := canvas.NewText("", colors.Green) - labelLastScan.TextSize = 13 - - errorText := canvas.NewText(" ", colors.Green) - errorText.TextSize = 12 - errorText.Alignment = fyne.TextAlignCenter - - historyData = binding.BindStringList(&history) - historyList = widget.NewListWithData(historyData, - func() fyne.CanvasObject { - return container.NewStack( - container.NewVBox( - container.NewStack( - widget.NewLabel(""), - ), - ), - ) - }, - func(di binding.DataItem, co fyne.CanvasObject) { - dat := di.(binding.String) - str, err := dat.Get() - if err != nil { - return - } - - split := strings.Split(str, ";;;") - - co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(split[0]) - }, - ) - - searchData = binding.BindStringList(&searching) - searchList = widget.NewListWithData(searchData, - func() fyne.CanvasObject { - return container.NewStack( - container.NewVBox( - container.NewStack( - widget.NewLabel(""), - ), - ), - ) - }, - func(di binding.DataItem, co fyne.CanvasObject) { - dat := di.(binding.String) - str, err := dat.Get() - if err != nil { - return - } - - split := strings.Split(str, ";;;") - - co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(split[0]) - }, - ) - - servingData = binding.BindStringList(&serving) - servingList = widget.NewListWithData(servingData, - func() fyne.CanvasObject { - return container.NewStack( - container.NewHBox( - container.NewStack( - rectLeft, - widget.NewLabel(""), - ), - container.NewStack( - rectRight, - widget.NewLabel(""), - ), - ), - ) - }, - func(di binding.DataItem, co fyne.CanvasObject) { - dat := di.(binding.String) - str, err := dat.Get() - if err != nil { - return - } - - split := strings.Split(str, ";;;") - - co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[1]) - co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[0]) - }, - ) - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - entryHistory := widget.NewEntry() - entryHistory.PlaceHolder = "Search History" - entryHistory.Disable() - - entryServeSCID := widget.NewEntry() - entryServeSCID.PlaceHolder = "Serve by SCID" - - entryAddSCID := widget.NewEntry() - entryAddSCID.PlaceHolder = "Add SCID" - entryAddSCID.OnChanged = func(s string) { - errorText.Text = "" - errorText.Refresh() - if len(s) == 64 { - if gnomon.Index != nil { - if gnomon.GetAllSCIDVariableDetails(s) != nil { - errorText.Text = "scid already exists" - errorText.Color = colors.Yellow - errorText.Refresh() - return - } - - code, err := getContractCode(s) - if err != nil || code == "" { - errorText.Text = "could not get scid" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - err = gnomon.AddSCIDToIndex(s) - if err != nil { - errorText.Text = "error adding scid" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - entryAddSCID.SetText("") - errorText.Text = "scid added" - errorText.Color = colors.Green - errorText.Refresh() - } - } - } - - entrySearchCompletions := []string{"author:", "durl:", "name:", "my:"} - entrySearch := x.NewCompletionEntry(entrySearchCompletions) - entrySearch.PlaceHolder = "Search TELA" - entrySearch.Disable() - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - var isSearching bool - - linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - session.Domain = "app.wallet" // break any loops now - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutDashboard()) - removeOverlays() - } - - linkClearHistory := widget.NewHyperlinkWithStyle("Clear All", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: false}) - linkClearHistory.OnTapped = func() { - verificationOverlay( - false, - "TELA BROWSER", - "Clear history?", - "Confirm", - func(b bool) { - if b { - if gnomon.Index == nil || session.Offline { - return - } - - shard, err := GetShard() - if err != nil { - return - } - - store, err := graviton.NewDiskStore(shard) - if err != nil { - return - } - - ss, err := store.LoadSnapshot(0) - if err != nil { - return - } - - tree, err := ss.GetTree("TELA History") - if err != nil { - return - } - - c := tree.Cursor() - - for k, _, err := c.First(); err == nil; k, _, err = c.Next() { - DeleteKey(tree.GetName(), k) - } - - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(layoutTELA()) - } - }, - ) - } - - wSelect := widget.NewSelect([]string{"History", "Active", "Search", "Settings"}, nil) - wSelect.SetSelectedIndex(0) - - btnShutdown := widget.NewButton("Shutdown TELA", nil) - - var restrictiveMode, rescanRecheck bool - var lastScan, searchExclusions, sortBy string - var minLikes float64 - var telaSCIDs []string - var telaSearch []INDEXwithRatings - var sAll = map[string]bool{} - - getSearchResults := func() { - fyne.Do(func() { - entrySearch.Disable() - entryAddSCID.Disable() - }) - if isSearching { - return - } - - isSearching = true - - // Already scanned - if len(telaSearch) > 0 { - searching = telaSearchDisplayAll(telaSearch, sortBy) - searchData.Set(searching) - - results.Text = fmt.Sprintf(" TELA SCIDs: %d", len(telaSearch)) - results.Color = colors.Green - fyne.Do(func() { - entrySearch.Enable() - entryAddSCID.Enable() - }) - - labelLastScan.Text = fmt.Sprintf(" %s", lastScan) - labelLastScan.Color = colors.Green - isSearching = false - - fyne.Do(func() { - results.Refresh() - labelLastScan.Refresh() - }) - - return - } - - telaSearch = []INDEXwithRatings{} - searchData.Set(nil) - labelLastScan.Text = "" - - fyne.Do(func() { - btnShutdown.Disable() - labelLastScan.Refresh() - }) - - defer func() { - isSearching = false - if !session.Offline && gnomon.Index != nil { - if btnShutdown.Disabled() { - fyne.Do(func() { - btnShutdown.Enable() - }) - } - } - }() - - if gnomon.Index == nil { - return - } - - for gnomon.Index.LastIndexedHeight < int64(engram.Disk.Get_Daemon_Height()) { - if !strings.Contains(session.Domain, ".tela") { - return - } - - fyne.Do(func() { - entrySearch.Disable() - entryAddSCID.Disable() - }) - results.Text = " Gnomon is syncing..." - results.Color = colors.Yellow - - fyne.Do(func() { - results.Refresh() - }) - - time.Sleep(time.Second) - } - - if !restrictiveMode { - storedAllSCIDs, err := GetEncryptedValue("TELA Search", []byte("Searched SCIDs")) - if err != nil { - // Nothing stored, scan for SCIDs - sAll = map[string]bool{} - logger.Debugf("[Engram] Could not get stored TELA Searched SCIDs: %s\n", err) - } else { - json.Unmarshal(storedAllSCIDs, &sAll) - } - } - - storedSCIDs, err := GetEncryptedValue("TELA Search", []byte("SCIDs")) - if err != nil { - // Nothing stored, scan for SCIDs - telaSCIDs = []string{} - logger.Debugf("[Engram] Could not get stored TELA SCIDs: %s\n", err) - } else { - // Have stored SCIDs - json.Unmarshal(storedSCIDs, &telaSCIDs) - - results.Text = " Scanning..." - results.Color = colors.Yellow - - fyne.Do(func() { - results.Refresh() - }) - - for i, sc := range telaSCIDs { - if index, err := tela.GetINDEXInfo(sc, session.Daemon); err == nil { - if gnomon.GetAllSCIDVariableDetails(sc) == nil { - results.Text = fmt.Sprintf(" Adding... (%d / %d)", i, len(telaSCIDs)) - results.Color = colors.Yellow - fyne.Do(func() { - results.Refresh() - }) - - gnomon.AddSCIDToIndex(sc) - } - - if !restrictiveMode { - _, ratings, err := getLikesRatio(sc, index.DURL, searchExclusions, minLikes) - if err != nil { - continue - } - - telaSearch = append(telaSearch, INDEXwithRatings{ratings: ratings, INDEX: index}) - } - } - } - - // If recheck is false, run a rescan that pulls in any new contracts when first OnChanged to Search - if rescanRecheck && (len(telaSearch) > 0 || len(telaSCIDs) > 0) { - searching = telaSearchDisplayAll(telaSearch, sortBy) - searchData.Set(searching) - - results.Text = fmt.Sprintf(" TELA SCIDs: %d", len(telaSearch)) - results.Color = colors.Green - fyne.Do(func() { - entrySearch.Enable() - entryAddSCID.Enable() - }) - - if last, err := GetEncryptedValue("TELA Search", []byte("Last Scan")); err == nil { - lastScan = string(last) - labelLastScan.Text = fmt.Sprintf(" %s", lastScan) - labelLastScan.Color = colors.Green - } - - if restrictiveMode && len(telaSearch) < 1 { - errorText.Text = "TELA is in restrictive mode" - errorText.Color = colors.Yellow - } - - fyne.Do(func() { - results.Refresh() - labelLastScan.Refresh() - errorText.Refresh() - }) - - return - } - } - - var wg sync.WaitGroup - - var all = map[string]string{} - if restrictiveMode { - for _, sc := range telaSCIDs { - all[sc] = "" - } - } else { - all = gnomon.GetAllOwnersAndSCIDs() - } - - allLen := len(all) - scanned := 0 - workers := make(chan struct{}, runtime.NumCPU()) - - for sc := range all { - workers <- struct{}{} - if gnomon.Index == nil || !strings.Contains(session.Domain, ".tela") { - break - } - - scanned++ - results.Text = fmt.Sprintf(" Scanning... (%d / %d)", scanned, allLen) - results.Color = colors.Yellow - - fyne.Do(func() { - results.Refresh() - }) - - wg.Add(1) - go func(scid string) { - defer func() { - <-workers - wg.Done() - }() - - // Restrictive mode rechecks everytime, if rescan recheck is disabled SCIDs that have already been scanned won't be rechecked for faster list population - if !restrictiveMode && !rescanRecheck && (sAll[scid] || scidExist(telaSCIDs, scid)) { - return - } - - vs, _, err := gnomon.Index.GetSCIDValuesByKey([]*structures.SCIDVariable{}, scid, "telaVersion", gnomon.Index.ChainHeight) - if err != nil { - return - } - - if vs != nil { - if index, err := tela.GetINDEXInfo(scid, session.Daemon); err == nil { - if len(index.DOCs) > 0 { - if strings.HasSuffix(index.DURL, tela.TAG_LIBRARY) || strings.HasSuffix(index.DURL, tela.TAG_DOC_SHARDS) || strings.HasSuffix(index.DURL, tela.TAG_BOOTSTRAP) { - return - } - - if gnomon.GetAllSCIDVariableDetails(scid) == nil { - gnomon.AddSCIDToIndex(scid) - } - - // In restrictive mode, the list is initialzed from telaSCIDs - if !restrictiveMode { - telaSCIDs = append(telaSCIDs, scid) - } - - _, ratings, err := getLikesRatio(scid, index.DURL, searchExclusions, minLikes) - if err != nil { - return - } - - telaSearch = append(telaSearch, INDEXwithRatings{ratings: ratings, INDEX: index}) - } - } - } - }(sc) - } - - if !strings.Contains(session.Domain, ".tela") { - return - } - - wg.Wait() - - searching = telaSearchDisplayAll(telaSearch, sortBy) - searchData.Set(searching) - - results.Text = fmt.Sprintf(" TELA SCIDs: %d", len(telaSearch)) - results.Color = colors.Green - - fyne.Do(func() { - results.Refresh() - }) - - timeNow := time.Now().Format(time.RFC822) - StoreEncryptedValue("TELA Search", []byte("Last Scan"), []byte(timeNow)) - if storeSCIDs, err := json.Marshal(telaSCIDs); err == nil { - StoreEncryptedValue("TELA Search", []byte("SCIDs"), storeSCIDs) - } - - if !restrictiveMode && !rescanRecheck { - for sc := range all { - sAll[sc] = true - } - - if sAllSCIDs, err := json.Marshal(sAll); err == nil { - StoreEncryptedValue("TELA Search", []byte("Searched SCIDs"), sAllSCIDs) - } - } else if restrictiveMode && len(searching) < 1 { - errorText.Text = "TELA is in restrictive mode" - errorText.Color = colors.Yellow - errorText.Refresh() - } - - lastScan = timeNow - labelLastScan.Text = fmt.Sprintf(" %s", lastScan) - labelLastScan.Color = colors.Green - - fyne.Do(func() { - labelLastScan.Refresh() - entrySearch.Enable() - entryAddSCID.Enable() - }) - } - - entrySearch.OnChanged = func(s string) { - errorText.Text = "" - errorText.Refresh() - - if s == "" { - go getSearchResults() - if !a.Driver().Device().IsMobile() { - entrySearch.HideCompletion() - } - - return - } - - if !a.Driver().Device().IsMobile() { - if len(s) < 3 { - entrySearch.SetOptions(append([]string{s}, entrySearchCompletions...)) - entrySearch.ShowCompletion() - } else { - entrySearch.HideCompletion() - } - } - - var queryResult []INDEXwithRatings - query := strings.Split(s, ":") - if len(query) < 2 { - if len(s) == 64 { - // Search scid - for _, ind := range telaSearch { - _, _, err := getLikesRatio(ind.SCID, ind.DURL, searchExclusions, minLikes) - if err != nil { - continue - } - - if ind.SCID == s { - queryResult = append(queryResult, ind) - break - } - } - } else { - // Search all - for _, ind := range telaSearch { - _, _, err := getLikesRatio(ind.SCID, ind.DURL, searchExclusions, minLikes) - if err != nil { - continue - } - - data := []string{ - ind.NameHdr, - ind.DescrHdr, - ind.DURL, - ind.SCID, - } - - for _, split := range data { - if strings.Contains(split, s) { - queryResult = append(queryResult, ind) - break - } - } - } - } - - searching = telaSearchDisplayAll(queryResult, sortBy) - searchData.Set(searching) - searchList.Refresh() - - results.Text = fmt.Sprintf(" TELA SCIDs: %d", len(queryResult)) - results.Color = colors.Green - results.Refresh() - entrySearch.Enable() - - return - } - - switch query[0] { - case "name": - for _, ind := range telaSearch { - _, _, err := getLikesRatio(ind.SCID, ind.DURL, searchExclusions, minLikes) - if err != nil { - continue - } - - if strings.Contains(ind.NameHdr, query[1]) { - queryResult = append(queryResult, ind) - } - } - case "durl": - for _, ind := range telaSearch { - _, _, err := getLikesRatio(ind.SCID, ind.DURL, searchExclusions, minLikes) - if err != nil { - continue - } - - if strings.Contains(ind.DURL, query[1]) { - queryResult = append(queryResult, ind) - } - } - case "my": - for _, ind := range telaSearch { - if ind.Author == engram.Disk.GetAddress().String() { - queryResult = append(queryResult, ind) - } - } - case "author": - if len(query[1]) != 66 { - return - } - - _, err := globals.ParseValidateAddress(query[1]) - if err != nil { - return - } - - for _, ind := range telaSearch { - _, _, err := getLikesRatio(ind.SCID, ind.DURL, searchExclusions, minLikes) - if err != nil { - continue - } - - if ind.Author == query[1] { - queryResult = append(queryResult, ind) - } - } - default: - errorText.Text = "unknown search prefix" - errorText.Color = colors.Red - errorText.Refresh() - - return - } - - searching = telaSearchDisplayAll(queryResult, sortBy) - searchData.Set(searching) - searchList.Refresh() - - results.Text = fmt.Sprintf(" TELA SCIDs: %d", len(queryResult)) - results.Color = colors.Green - results.Refresh() - entrySearch.Enable() - } - - entryAddSCID.OnChanged = func(s string) { - if len(s) == 64 { - defer entryAddSCID.SetText("") - bootstrapIndex, err := tela.GetINDEXInfo(s, session.Daemon) - if err != nil { - logger.Errorf("[GetINDEXInfo] Bootstrap: %s\n", err) - errorText.Text = "could not get bootstrap SCID" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - if !strings.HasSuffix(bootstrapIndex.DURL, tela.TAG_BOOTSTRAP) { - logger.Errorf("[Engram] SCID %s is not a TELA bootstrap INDEX\n", s) - errorText.Text = "invalid bootstrap SCID" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - storeSCIDs, err := json.Marshal(bootstrapIndex.DOCs) - if err != nil { - logger.Errorf("[Engram] Could not marshal bootstrap: %s\n", err) - errorText.Text = "error initializing bootstrap" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - err = StoreEncryptedValue("TELA Search", []byte("SCIDs"), storeSCIDs) - if err != nil { - logger.Errorf("[Engram] Could store bootstrap: %s\n", err) - errorText.Text = "error storing bootstrap" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - telaSCIDs = bootstrapIndex.DOCs - errorText.Text = "bootstrap initialized" - errorText.Color = colors.Green - errorText.Refresh() - - go getSearchResults() - } - } - - // Refresh the active server list - refreshServerList := func() { - time.Sleep(time.Second * 2) - var serversRunning []string - for _, serv := range tela.GetServerInfo() { - serversRunning = append(serversRunning, serv.Name+";;;"+serv.Address+";;;;;;"+serv.SCID) - } - - sort.Strings(serversRunning) - servingData.Set(serversRunning) - servingList.Refresh() - if !isSearching && wSelect.Selected == "Active" { - results.Text = fmt.Sprintf(" Active Servers: %d", len(serversRunning)) - results.Color = colors.Green - results.Refresh() - } - } - - btnShutdown.OnTapped = func() { - switch btnShutdown.Text { - case "Rescan Blockchain": - verificationOverlay( - false, - "TELA BROWSER", - "Rescan blockchain?", - "Confirm", - func(b bool) { - if b { - if isSearching { - return - } - - telaSearch = []INDEXwithRatings{} - telaSCIDs = []string{} - if rescanRecheck { - DeleteKey("TELA Search", []byte("SCIDs")) - DeleteKey("TELA Search", []byte("Searched SCIDs")) - } - errorText.Text = "" - errorText.Refresh() - go getSearchResults() - } - }, - ) - default: - verificationOverlay( - false, - "TELA BROWSER", - "Shutdown all active TELA servers?", - "Confirm", - func(b bool) { - if b { - tela.ShutdownTELA() - servingData.Set(nil) - errorText.Text = "" - errorText.Refresh() - } - }, - ) - } - - go refreshServerList() - } - - entrySpacer := canvas.NewRectangle(color.Transparent) - entrySpacer.SetMinSize(fyne.NewSize(140, 0)) - - entryPort := widget.NewEntry() - entryPort.SetText(strconv.Itoa(tela.PortStart())) - entryPort.Validator = func(s string) (err error) { - i, err := strconv.Atoi(s) - if err != nil { - return fmt.Errorf("invalid port") - } - - return tela.SetPortStart(i) - } - - entryMinLikes := widget.NewEntry() - entryMinLikes.SetPlaceHolder("Likes %") - if storedMinLikes, err := GetEncryptedValue("TELA Settings", []byte("Min Likes")); err == nil { - if f, err := strconv.ParseFloat(string(storedMinLikes), 64); err == nil { - minLikes = f - entryMinLikes.SetText(string(storedMinLikes)) - } - } else { - minLikes = 30 - entryMinLikes.SetText("30") - } - - entryMinLikes.Validator = func(s string) (err error) { - i, err := strconv.Atoi(s) - if err != nil { - return fmt.Errorf("invalid percent") - } - - if i < 0 || i > 100 { - err = fmt.Errorf("must be 0 to 100") - return - } - - // Clear search results but keep scids - telaSearch = []INDEXwithRatings{} - - minLikes = float64(i) - StoreEncryptedValue("TELA Settings", []byte("Min Likes"), []byte(s)) - - return - } - - entryExclusions := widget.NewEntry() - entryExclusions.SetPlaceHolder("dURL Exclusions (exclude1,exclude2)") - if storedExclusions, err := GetEncryptedValue("TELA Settings", []byte("Exclusions")); err == nil { - searchExclusions = string(storedExclusions) - entryExclusions.SetText(searchExclusions) - } - - entryExclusions.OnChanged = func(s string) { - if s != "" { - StoreEncryptedValue("TELA Settings", []byte("Exclusions"), []byte(s)) - } else { - DeleteKey("TELA Settings", []byte("Exclusions")) - } - - // Clear search results but keep scids - telaSearch = []INDEXwithRatings{} - - searchExclusions = s - } - - wUpdates := widget.NewSelect([]string{xswd.Deny.String(), xswd.Allow.String()}, nil) - if tela.UpdatesAllowed() { - wUpdates.SetSelectedIndex(1) - } else { - wUpdates.SetSelectedIndex(0) - } - - wUpdates.OnChanged = func(s string) { - if s == xswd.Allow.String() { - tela.AllowUpdates(true) - } else { - tela.AllowUpdates(false) - } - } - - if storedRescanRecheck, err := GetEncryptedValue("TELA Settings", []byte("Rescan Recheck")); err == nil { - if string(storedRescanRecheck) == "Yes" { - rescanRecheck = true - } else { - rescanRecheck = false - } - } - - wRescanRecheck := widget.NewSelect([]string{"No", "Yes"}, nil) - if rescanRecheck { - wRescanRecheck.SetSelectedIndex(1) - } else { - wRescanRecheck.SetSelectedIndex(0) - } - - wRescanRecheck.OnChanged = func(s string) { - if s == "Yes" { - rescanRecheck = true - } else { - rescanRecheck = false - } - - StoreEncryptedValue("TELA Settings", []byte("Rescan Recheck"), []byte(s)) - } - - sortByOptions := []string{"Ratings", "A-Z", "Z-A"} - wSortBy := widget.NewSelect(sortByOptions, nil) - if storedSortBy, err := GetEncryptedValue("TELA Settings", []byte("Sort By")); err == nil { - sortBy = string(storedSortBy) - } else { - sortBy = sortByOptions[0] - } - - wSortBy.SetSelected(sortBy) - wSortBy.OnChanged = func(s string) { - if s != "" { - // Clear search results but keep scids - telaSearch = []INDEXwithRatings{} - sortBy = s - StoreEncryptedValue("TELA Settings", []byte("Sort By"), []byte(s)) - } - } - - historyBox := container.NewStack( - rectList, - historyList, - ) - - searchBox := container.NewStack( - rectList, - searchList, - ) - - servingBox := container.NewStack( - rectList, - servingList, - ) - - linkSpacer := canvas.NewRectangle(color.Transparent) - linkSpacer.SetMinSize(fyne.NewSize(0, 40)) - - wMode := widget.NewCheck("Restrictive Mode", nil) - wMode.SetChecked(true) - restrictiveMode = true - if storedTelaMode, err := GetEncryptedValue("TELA Settings", []byte("Mode")); err == nil { - switch string(storedTelaMode) { - case "Unrestrictive": - wMode.SetChecked(false) - restrictiveMode = false - default: - // in restrictive mode - } - } - - linkResetDefaults := widget.NewHyperlinkWithStyle("Reset Default Settings", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkResetDefaults.OnTapped = func() { - verificationOverlay( - false, - "TELA BROWSER", - "Reset to default settings?", - "Confirm", - func(b bool) { - if b { - wMode.SetChecked(true) - wUpdates.SetSelectedIndex(0) - wRescanRecheck.SetSelectedIndex(0) - wSortBy.SetSelectedIndex(0) - entryPort.SetText(strconv.Itoa(tela.DEFAULT_PORT_START)) - entryMinLikes.SetText("30") - entryExclusions.SetText("") - } - }, - ) - } - - linkSearchClear := widget.NewHyperlinkWithStyle("Delete Search Data", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkSearchClear.OnTapped = func() { - verificationOverlay( - false, - "TELA BROWSER", - "Delete stored search data?", - "Confirm", - func(b bool) { - if b { - telaSearch = []INDEXwithRatings{} - telaSCIDs = []string{} - DeleteKey("TELA Search", []byte("SCIDs")) - DeleteKey("TELA Search", []byte("Searched SCIDs")) - DeleteKey("TELA Search", []byte("Last Scan")) - linkSearchClear.Hide() - } - }, - ) - } - - wMode.OnChanged = func(b bool) { - if b { - restrictiveMode = true - DeleteKey("TELA Settings", []byte("Mode")) - return - } - - go func() { - unrestrictedPermission, err := AskPermissionForRequestE("TELA Unresctricted Mode", "TELA R OFF") - if err != nil { - logger.Errorf("[Engram] TELA unrestricted mode: %s\n", err) - errorText.Text = "error requesting permission" - errorText.Color = colors.Red - - fyne.Do(func() { - errorText.Refresh() - }) - - return - } - - if unrestrictedPermission != xswd.Allow { - wMode.SetChecked(true) - return - } - - StoreEncryptedValue("TELA Settings", []byte("Mode"), []byte("Unrestrictive")) - restrictiveMode = false - telaSearch = []INDEXwithRatings{} - telaSCIDs = []string{} - DeleteKey("TELA Search", []byte("SCIDs")) - DeleteKey("TELA Search", []byte("Searched SCIDs")) - DeleteKey("TELA Search", []byte("Last Scan")) - }() - } - - settingsBox := container.NewVScroll( - container.NewStack( - rectList, - container.NewBorder( - nil, - nil, - nil, - layout.NewSpacer(), - container.NewVBox( - container.NewBorder( - nil, - nil, - nil, - nil, - container.NewCenter(wMode), - ), - rectSpacer, - container.NewBorder( - nil, - nil, - widget.NewRichTextFromMarkdown("### Allow Content Updates"), - wUpdates, - ), - container.NewBorder( - nil, - nil, - widget.NewRichTextFromMarkdown("### Rescan Recheck"), - wRescanRecheck, - ), - container.NewBorder( - nil, - nil, - widget.NewRichTextFromMarkdown("### Sort By"), - wSortBy, - ), - container.NewBorder( - nil, - nil, - widget.NewRichTextFromMarkdown("### Start Port Range"), - container.NewStack( - entrySpacer, - entryPort, - ), - ), - container.NewBorder( - nil, - nil, - widget.NewRichTextFromMarkdown("### Search Min Likes %"), - container.NewStack( - entrySpacer, - entryMinLikes, - ), - ), - container.NewBorder( - widget.NewRichTextFromMarkdown("### Search Exclusions"), - nil, - nil, - nil, - entryExclusions, - ), - rectSpacer, - rectSpacer, - container.NewStack( - linkSpacer, - linkResetDefaults, - ), - rectSpacer, - container.NewStack( - linkSpacer, - linkSearchClear, - ), - rectSpacer, - ), - ), - ), - ) - settingsBox.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.36)) - - headerSpacer := canvas.NewRectangle(color.Transparent) - headerSpacer.SetMinSize(fyne.NewSize(0, 35)) - - layoutBrowser := container.NewStack( - rectWidth, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - rectSpacer, - container.NewHBox( - results, - layout.NewSpacer(), - headerSpacer, - linkClearHistory, - ), - rectSpacer, - rectSpacer, - entryHistory, - errorText, - rectSpacer, - wSelect, - rectSpacer, - historyBox, - rectSpacer, - rectSpacer, - rectSpacer, - btnShutdown, - ), - layout.NewSpacer(), - ), - ) - - var historyFound = true - var historyResults []string - - getHistoryResults := func() { - if !historyFound { - return - } - - historyFound = false - historyResults = nil - historyData.Set(nil) - defer func() { - historyFound = true - }() - - if engram.Disk != nil && gnomon.Index != nil { - for gnomon.Index.LastIndexedHeight < int64(engram.Disk.Get_Daemon_Height()) { - if !strings.Contains(session.Domain, ".tela") { - return - } - - results.Text = " Gnomon is syncing..." - results.Color = colors.Yellow - - fyne.Do(func() { - entryHistory.Disable() - results.Refresh() - }) - - time.Sleep(time.Second) - } - - results.Text = " Loading previous search history..." - results.Color = colors.Yellow - - fyne.Do(func() { - entryHistory.Enable() - results.Refresh() - }) - - shard, err := GetShard() - if err != nil { - return - } - - store, err := graviton.NewDiskStore(shard) - if err != nil { - return - } - - ss, err := store.LoadSnapshot(0) - - if err != nil { - return - } - - tree, err := ss.GetTree("TELA History") - if err != nil { - return - } - - c := tree.Cursor() - - for k, _, err := c.First(); err == nil; k, _, err = c.Next() { - scid := crypto.HashHexToHash(string(k)) - - title, desc, _, _, _ := getContractHeader(scid) - - if title == "" { - title = scid.String() - } - - if len(title) > 36 { - title = title[0:36] + "..." - } - - if desc == "" { - desc = "N/A" - } - - if len(desc) > 40 { - desc = desc[0:40] + "..." - } - - historyResults = append(historyResults, title+";;;"+desc+";;;;;;"+scid.String()) - } - - sort.Strings(historyResults) - history = historyResults - historyData.Set(history) - - results.Text = fmt.Sprintf(" Search History: %d", len(historyResults)) - results.Color = colors.Green - - fyne.Do(func() { - historyList.Refresh() - results.Refresh() - btnShutdown.Enable() - }) - } - } - - entryHistory.OnChanged = func(s string) { - if s == "" { - go getHistoryResults() - return - } - - var queryResult []string - for _, data := range history { - for _, split := range strings.Split(data, ";;;") { - if strings.Contains(split, s) { - queryResult = append(queryResult, data) - break - } - } - } - - sort.Strings(queryResult) - history = queryResult - historyData.Set(history) - - results.Text = fmt.Sprintf(" Search History: %d", len(queryResult)) - results.Color = colors.Green - entryHistory.Enable() - - fyne.Do(func() { - historyList.Refresh() - results.Refresh() - }) - } - - wSelect.OnChanged = func(s string) { - errorText.Text = "" - errorText.Refresh() - if !session.Offline { - btnShutdown.Enable() - } - - switch s { - case "Active": - servingData.Set(nil) - - var serversRunning []string - for _, serv := range tela.GetServerInfo() { - serversRunning = append(serversRunning, serv.Name+";;;"+serv.Address+";;;;;;"+serv.SCID) - } - - sort.Strings(serversRunning) - servingData.Set(serversRunning) - - if !isSearching { - if session.Offline { - results.Text = " Disabled in offline mode." - results.Color = colors.Gray - results.Refresh() - } else { - results.Text = fmt.Sprintf(" Active Servers: %d", len(serversRunning)) - results.Color = colors.Green - results.Refresh() - } - } - - labelLastScan.Text = "" - labelLastScan.Refresh() - layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[3] = labelLastScan - layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[4] = entryServeSCID - layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[9] = servingBox - btnShutdown.Text = "Shutdown TELA" - btnShutdown.Refresh() - case "History": - if gnomon.Index == nil { - results.Text = " Gnomon is inactive." - results.Color = colors.Gray - results.Refresh() - } - - if isSearching { - linkClearHistory.Hide() - } else { - go getHistoryResults() - linkClearHistory.Show() - } - - layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[3] = linkClearHistory - layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[4] = entryHistory - layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[9] = historyBox - btnShutdown.Text = "Shutdown TELA" - btnShutdown.Refresh() - servingList.UnselectAll() - case "Search": - if gnomon.Index == nil { - results.Text = " Gnomon is inactive." - results.Color = colors.Gray - results.Refresh() - } - - go getSearchResults() - - layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[3] = labelLastScan - layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[4] = entrySearch - layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[9] = searchBox - btnShutdown.Text = "Rescan Blockchain" - btnShutdown.Refresh() - if isSearching { - btnShutdown.Disable() - } - case "Settings": - if !isSearching { - if session.Offline { - results.Text = " Disabled in offline mode." - results.Color = colors.Gray - results.Refresh() - } else { - results.Text = fmt.Sprintf(" Active Servers: %d", len(tela.GetServerInfo())) - results.Color = colors.Green - results.Refresh() - linkResetDefaults.Show() - if gnomon.Index != nil { - wRescanRecheck.Enable() - entryMinLikes.Enable() - entryExclusions.Enable() - } - - if _, err := GetEncryptedValue("TELA Search", []byte("SCIDs")); err == nil { - linkSearchClear.Show() - } else { - linkSearchClear.Hide() - } - } - } else { - wRescanRecheck.Disable() - entryMinLikes.Disable() - entryExclusions.Disable() - linkResetDefaults.Hide() - linkSearchClear.Hide() - } - - labelLastScan.Text = "" - labelLastScan.Refresh() - layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[3] = labelLastScan - layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[4] = entryAddSCID - layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[9] = settingsBox - btnShutdown.Text = "Shutdown TELA" - btnShutdown.Refresh() - servingList.UnselectAll() - } - } - - if session.Offline { - results.Text = " Disabled in offline mode." - results.Color = colors.Gray - results.Refresh() - wUpdates.Disable() - entryServeSCID.Disable() - entryAddSCID.Disable() - wRescanRecheck.Disable() - entryExclusions.Disable() - entryMinLikes.Disable() - entryPort.Disable() - btnShutdown.Disable() - } else if gnomon.Index == nil { - results.Text = " Gnomon is inactive." - results.Color = colors.Gray - results.Refresh() - entryAddSCID.Disable() - wRescanRecheck.Disable() - entryExclusions.Disable() - entryMinLikes.Disable() - } - - entryServeSCID.OnChanged = func(s string) { - errorText.Text = "" - errorText.Refresh() - if len(s) == 64 { - go func() { - // Create a TELALink to parse and get its ratings for user to verifiy before serving the content - telaLink := TELALink_Params{TelaLink: fmt.Sprintf("tela://open/%s", s)} - linkPermission, err := AskPermissionForRequestE("Open TELA Link", telaLink) - if err != nil { - logger.Errorf("[Engram] Open TELA link: %s\n", err) - errorText.Text = "error could not open TELA" - errorText.Color = colors.Red - - fyne.Do(func() { - errorText.Refresh() - }) - - return - } - - if linkPermission != xswd.Allow { - entryServeSCID.SetText("") - return - } - - showLoadingOverlay() - defer func() { - go refreshServerList() - }() - - var index tela.INDEX - - // If serving without Gnomon, scid will not end up in history - if gnomon.Index != nil { - result := gnomon.GetAllSCIDVariableDetails(s) - if len(result) == 0 { - _, err := getTxData(s) - if err != nil { - return - } - } - - index.NameHdr, index.DescrHdr, _, _, _ = getContractHeader(crypto.HashHexToHash(s)) - - if index.NameHdr == "" { - index.NameHdr = s - } - - if len(index.NameHdr) > 36 { - index.NameHdr = index.NameHdr[0:36] + "..." - } - - if index.DescrHdr == "" { - index.DescrHdr = "N/A" - } - - if len(index.DescrHdr) > 40 { - index.DescrHdr = index.DescrHdr[0:40] + "..." - } - } - - entryServeSCID.SetText("") - - if link, err := tela.ServeTELA(s, session.Daemon); err == nil { - url, err := url.Parse(link) - if err != nil { - logger.Errorf("[Engram] TELA URL parse: %s\n", err) - errorText.Text = "error could parse URL" - errorText.Color = colors.Red - - fyne.Do(func() { - errorText.Refresh() - }) - - return // If url is not valid, scid won't be saved in history - } else { - err = fyne.CurrentApp().OpenURL(url) - if err != nil { - errorText.Text = "error could not open browser" - errorText.Color = colors.Red - } - } - - if gnomon.Index != nil { - historyResults = append(historyResults, index.NameHdr+";;;"+index.DescrHdr+";;;;;;"+s) - sort.Strings(historyResults) - history = historyResults - historyData.Set(history) - - results.Text = fmt.Sprintf(" Search History: %d", len(historyResults)) - results.Color = colors.Green - - err = StoreEncryptedValue("TELA History", []byte(s), []byte("")) - if err != nil { - logger.Errorf("[Engram] Error saving TELA search result: %s\n", err) - } - } - } else { - if strings.Contains(err.Error(), "user defined no updates and content has been updated to") { - removeOverlays() - - // Create a TELALink to parse and get its ratings for user to verifiy before serving updated content - telaLink := TELALink_Params{TelaLink: fmt.Sprintf("tela://open/%s", s)} - linkPermission, err := AskPermissionForRequestE("Allow Updated Content", telaLink) - if err != nil { - logger.Errorf("[Engram] Open TELA link: %s\n", err) - errorText.Text = "error could not open TELA" - errorText.Color = colors.Red - - fyne.Do(func() { - errorText.Refresh() - }) - - return - } - - if linkPermission != xswd.Allow { - entryServeSCID.SetText("") - return - } - - link, err := serveTELAUpdates(s) - if err != nil { - logger.Errorf("[Engram] Error serving TELA: %s\n", err) - errorText.Text = telaErrorToString(err) - errorText.Color = colors.Red - - fyne.Do(func() { - errorText.Refresh() - }) - - return - } - - url, err := url.Parse(link) - if err != nil { - logger.Errorf("[Engram] TELA URL parse: %s\n", err) - errorText.Text = "error could parse URL" - errorText.Color = colors.Red - - fyne.Do(func() { - errorText.Refresh() - }) - - return - } else { - err = fyne.CurrentApp().OpenURL(url) - if err != nil { - errorText.Text = "error could not open browser" - errorText.Color = colors.Red - } - } - - if gnomon.Index != nil { - historyResults = append(historyResults, index.NameHdr+";;;"+index.DescrHdr+";;;;;;"+s) - sort.Strings(historyResults) - history = historyResults - historyData.Set(history) - fyne.Do(func() { - historyList.Refresh() - }) - - results.Text = fmt.Sprintf(" Search History: %d", len(historyResults)) - results.Color = colors.Green - - err = StoreEncryptedValue("TELA History", []byte(s), []byte("")) - if err != nil { - logger.Errorf("[Engram] Error saving TELA search result: %s\n", err) - } - } - - return - } - - logger.Errorf("[Engram] Error serving TELA: %s\n", err) - errorText.Text = telaErrorToString(err) - errorText.Color = colors.Red - } - - fyne.Do(func() { - historyList.Refresh() - errorText.Refresh() - results.Refresh() - }) - - removeOverlays() - }() - } - } - - go getHistoryResults() - - historyList.OnSelected = func(id widget.ListItemID) { - errorText.Text = "" - errorText.Refresh() - showLoadingOverlay() - defer removeOverlays() - - split := strings.Split(history[id], ";;;") - if len(split) < 4 || len(split[3]) != 64 { - logger.Errorf("[Engram] TELA Invalid SCID\n") - errorText.Text = "invalid TELA scid" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - index, err := tela.GetINDEXInfo(split[3], session.Daemon) - if err != nil { - logger.Errorf("[Engram] GetINDEXInfo: %s\n", err) - errorText.Text = "invalid INDEX scid" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - historyList.UnselectAll() - historyList.FocusLost() - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTELAManager(index, refreshServerList)) - } - - searchList.OnSelected = func(id widget.ListItemID) { - errorText.Text = "" - errorText.Refresh() - showLoadingOverlay() - defer removeOverlays() - - split := strings.Split(searching[id], ";;;") - if len(split) < 2 || len(split[1]) != 64 { - logger.Errorf("[Engram] TELA Invalid SCID\n") - errorText.Text = "invalid TELA scid" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - index, err := tela.GetINDEXInfo(split[1], session.Daemon) - if err != nil { - logger.Errorf("[Engram] GetINDEXInfo: %s\n", err) - errorText.Text = "invalid INDEX scid" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - searchList.UnselectAll() - searchList.FocusLost() - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTELAManager(index, refreshServerList)) - } - - servingList.OnSelected = func(id widget.ListItemID) { - errorText.Text = "" - errorText.Refresh() - showLoadingOverlay() - defer removeOverlays() - - split := strings.Split(serving[id], ";;;") - if len(split) < 4 || len(split[3]) != 64 { - logger.Errorf("[Engram] TELA Invalid SCID\n") - errorText.Text = "invalid TELA scid" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - index, err := tela.GetINDEXInfo(split[3], session.Daemon) - if err != nil { - logger.Errorf("[Engram] GetINDEXInfo: %s\n", err) - errorText.Text = "invalid INDEX scid" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - servingList.UnselectAll() - servingList.FocusLost() - session.LastDomain = session.Window.Content() - session.Window.SetContent(layoutTELAManager(index, refreshServerList)) - } - - top := container.NewVBox( - rectSpacer, - rectSpacer, - container.NewCenter( - heading, - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layoutBrowser, - ), - ) - - bottom := container.NewStack( - container.NewVBox( - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewStack( - frame, - container.NewBorder( - top, - bottom, - nil, - nil, - ), - ) - - return NewVScroll(layout) -} - -// Layout details of a TELA INDEX -func layoutTELAManager(index tela.INDEX, callback func()) fyne.CanvasObject { - session.Domain = "app.tela.manager" - - frame := &iframe{} - - rectBox := canvas.NewRectangle(color.Transparent) - rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.58)) - - rectWidth90 := canvas.NewRectangle(color.Transparent) - rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) - - rectSpacer := canvas.NewRectangle(color.Transparent) - rectSpacer.SetMinSize(fyne.NewSize(6, 5)) - - labelName := widget.NewRichText(&widget.TextSegment{ - Text: index.NameHdr, - Style: widget.RichTextStyle{ - Alignment: fyne.TextAlignCenter, - ColorName: theme.ColorNameForeground, - SizeName: theme.SizeNameHeadingText, - TextStyle: fyne.TextStyle{Bold: true}, - }}) - labelName.Wrapping = fyne.TextWrapWord - - labelDesc := widget.NewRichText(&widget.TextSegment{ - Text: index.DescrHdr, - Style: widget.RichTextStyle{ - Alignment: fyne.TextAlignCenter, - ColorName: theme.ColorNameForeground, - TextStyle: fyne.TextStyle{Bold: false}, - }}) - labelDesc.Wrapping = fyne.TextWrapWord - - labelDURL := canvas.NewText(" DURL", colors.Gray) - labelDURL.TextSize = 14 - labelDURL.Alignment = fyne.TextAlignLeading - labelDURL.TextStyle = fyne.TextStyle{Bold: true} - - textDURL := widget.NewRichTextFromMarkdown(index.DURL) - textDURL.Wrapping = fyne.TextWrapWord - - labelSCID := canvas.NewText(" SMART CONTRACT ID", colors.Gray) - labelSCID.TextSize = 14 - labelSCID.Alignment = fyne.TextAlignLeading - labelSCID.TextStyle = fyne.TextStyle{Bold: true} - - textSCID := widget.NewRichTextFromMarkdown(index.SCID) - textSCID.Wrapping = fyne.TextWrapWord - - linkViewExplorer := widget.NewHyperlinkWithStyle("View in Explorer", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - linkViewExplorer.OnTapped = func() { - if engram.Disk.GetNetwork() { - link, _ := url.Parse("https://explorer.derofoundation.org/tx/" + index.SCID) - _ = fyne.CurrentApp().OpenURL(link) - } else { - link, _ := url.Parse("https://testnetexplorer.derofoundation.org/tx/" + index.SCID) - _ = fyne.CurrentApp().OpenURL(link) - } - } - - linkCopySCID := widget.NewHyperlinkWithStyle("Copy SCID", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkCopySCID.OnTapped = func() { - a.Clipboard().SetContent(index.SCID) - } - - labelAuthor := canvas.NewText(" SMART CONTRACT AUTHOR", colors.Gray) - labelAuthor.TextSize = 14 - labelAuthor.Alignment = fyne.TextAlignLeading - labelAuthor.TextStyle = fyne.TextStyle{Bold: true} - - author := index.Author - if author == "anon" { - author = "--" - } - textAuthor := widget.NewRichTextFromMarkdown(author) - textAuthor.Wrapping = fyne.TextWrapWord - - linkMessageAuthor := widget.NewHyperlinkWithStyle("Message the Author", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - linkMessageAuthor.OnTapped = func() { - if index.Author != "" { - messages.Contact = index.Author - session.Window.Canvas().SetContent(layoutTransition()) - removeOverlays() - session.Window.Canvas().SetContent(layoutPM()) - } - } - - linkCopyAuthor := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkCopyAuthor.OnTapped = func() { - a.Clipboard().SetContent(index.Author) - } - - labelStatus := canvas.NewText("APPLICATION STATUS", colors.Gray) - labelStatus.TextSize = 14 - labelStatus.Alignment = fyne.TextAlignCenter - labelStatus.TextStyle = fyne.TextStyle{Bold: true} - - textStatus := canvas.NewText("Offline", colors.Gray) - textStatus.TextSize = 22 - textStatus.Alignment = fyne.TextAlignCenter - textStatus.TextStyle = fyne.TextStyle{Bold: true} - - labelSeparator := widget.NewRichTextFromMarkdown("") - labelSeparator.Wrapping = fyne.TextWrapOff - labelSeparator.ParseMarkdown("---") - labelSeparator2 := widget.NewRichTextFromMarkdown("") - labelSeparator2.Wrapping = fyne.TextWrapOff - labelSeparator2.ParseMarkdown("---") - labelSeparator3 := widget.NewRichTextFromMarkdown("") - labelSeparator3.Wrapping = fyne.TextWrapOff - labelSeparator3.ParseMarkdown("---") - labelSeparator4 := widget.NewRichTextFromMarkdown("") - labelSeparator4.Wrapping = fyne.TextWrapOff - labelSeparator4.ParseMarkdown("---") - labelSeparator5 := widget.NewRichTextFromMarkdown("") - labelSeparator5.Wrapping = fyne.TextWrapOff - labelSeparator5.ParseMarkdown("---") - // labelSeparator6 := widget.NewRichTextFromMarkdown("") - // labelSeparator6.Wrapping = fyne.TextWrapOff - // labelSeparator6.ParseMarkdown("---") - - menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) - menuLabel.TextSize = 11 - menuLabel.Alignment = fyne.TextAlignCenter - menuLabel.TextStyle = fyne.TextStyle{Bold: true} - - sep := canvas.NewRectangle(colors.Gray) - sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line1 := container.NewVBox( - layout.NewSpacer(), - sep, - layout.NewSpacer(), - ) - - sep2 := canvas.NewRectangle(colors.Gray) - sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) - - line2 := container.NewVBox( - layout.NewSpacer(), - sep2, - layout.NewSpacer(), - ) - - linkBack := widget.NewHyperlinkWithStyle("Back to TELA", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkBack.OnTapped = func() { - removeOverlays() - capture := session.Window.Content() - session.Window.SetContent(layoutTransition()) - session.Window.SetContent(session.LastDomain) - session.Domain = "app.tela" - session.LastDomain = capture - go callback() - } - - image := canvas.NewImageFromResource(resourceTelaIcon) - image.SetMinSize(fyne.NewSize(ui.Width*0.3, ui.Width*0.3)) - image.FillMode = canvas.ImageFillContain - - _, _, iconURLHdr, _, _ := getContractHeader(crypto.HashHexToHash(index.SCID)) - if iconURLHdr == "" && index.IconHdr != "" { - iconURLHdr = index.IconHdr - } - - if iconURLHdr != "" { - if img, err := handleImageURL(index.NameHdr, iconURLHdr, fyne.NewSize(ui.Width*0.3, ui.Width*0.3)); err == nil { - image = img - } else { - logger.Errorf("[Engram] Could not validate icon image: %s\n", err) - } - } - - errorText := canvas.NewText(" ", colors.Green) - errorText.TextSize = 12 - errorText.Alignment = fyne.TextAlignCenter - - spacerStatus := canvas.NewRectangle(color.Transparent) - spacerStatus.SetMinSize(fyne.NewSize(0, 34)) - - linkOpenInBrowser := widget.NewHyperlinkWithStyle("Open in Browser", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) - linkOpenInBrowser.Hide() - linkOpenInBrowser.OnTapped = func() { - params := fmt.Sprintf("tela://open/%s", index.SCID) - var toggledUpdates bool - if !tela.UpdatesAllowed() { - // user has accepted updated content when serving, call AllowUpdates because OpenTELALink returns error on any updated content - tela.AllowUpdates(true) - toggledUpdates = true - } - - link, err := tela.OpenTELALink(params, session.Daemon) - if toggledUpdates { - tela.AllowUpdates(false) - } - if err != nil { - logger.Errorf("[Engram] handling TELA link: %s\n", err) - errorText.Text = "error handling TELA link" - errorText.Color = colors.Red - errorText.Refresh() - return - } - - url, err := url.Parse(link) - if err != nil { - logger.Errorf("[Engram] TELA URL parse: %s\n", err) - errorText.Text = "error could parse URL" - errorText.Color = colors.Red - errorText.Refresh() - } else { - err = fyne.CurrentApp().OpenURL(url) - if err != nil { - errorText.Text = "error could not open browser" - errorText.Color = colors.Red - errorText.Refresh() - } - } - } - - btnServer := widget.NewButton("Start Application", nil) - - if tela.HasServer(index.DURL) { - textStatus.Text = "Running" - textStatus.Color = colors.Green - textStatus.Refresh() - btnServer.Text = "Shutdown Application" - btnServer.Refresh() - linkOpenInBrowser.Show() - } - - btnServer.OnTapped = func() { - if btnServer.Text != "Start Application" { - tela.ShutdownServer(index.DURL) - errorText.Text = "" - errorText.Refresh() - textStatus.Text = "Offline" - textStatus.Color = colors.Gray - textStatus.Refresh() - btnServer.Text = "Start Application" - btnServer.Refresh() - linkOpenInBrowser.Hide() - } else { - showLoadingOverlay() - - if link, err := tela.ServeTELA(index.SCID, session.Daemon); err == nil { - url, err := url.Parse(link) - if err != nil { - logger.Errorf("[Engram] TELA URL parse: %s\n", err) - errorText.Text = "error could parse URL" - errorText.Color = colors.Red - errorText.Refresh() - } else { - err = fyne.CurrentApp().OpenURL(url) - if err != nil { - errorText.Text = "error could not open browser" - errorText.Color = colors.Red - errorText.Refresh() - } - } - - textStatus.Text = "Running" - textStatus.Color = colors.Green - textStatus.Refresh() - btnServer.Text = "Shutdown Application" - btnServer.Refresh() - linkOpenInBrowser.Show() - - err = StoreEncryptedValue("TELA History", []byte(index.SCID), []byte("")) - if err != nil { - logger.Errorf("[Engram] Error saving TELA search result: %s\n", err) - } - } else { - if strings.Contains(err.Error(), "user defined no updates and content has been updated to") { - removeOverlays() - - go func() { - // Create a TELALink to parse and get its ratings for user to verifiy before serving updated content - telaLink := TELALink_Params{TelaLink: fmt.Sprintf("tela://open/%s", index.SCID)} - linkPermission, err := AskPermissionForRequestE("Allow Updated Content", telaLink) - if err != nil { - logger.Errorf("[Engram] Open TELA link: %s\n", err) - fyne.Do(func() { - errorText.Text = "error could not open TELA" - errorText.Color = colors.Red - errorText.Refresh() - }) - - return - } - - if linkPermission != xswd.Allow { - return - } - - link, err := serveTELAUpdates(index.SCID) - if err != nil { - logger.Errorf("[Engram] Error serving TELA: %s\n", err) - fyne.Do(func() { - errorText.Text = telaErrorToString(err) - errorText.Color = colors.Red - errorText.Refresh() - }) - return - } - - url, err := url.Parse(link) - if err != nil { - logger.Errorf("[Engram] TELA URL parse: %s\n", err) - fyne.Do(func() { - errorText.Text = "error could parse URL" - errorText.Color = colors.Red - errorText.Refresh() - }) - } else { - err = fyne.CurrentApp().OpenURL(url) - if err != nil { - fyne.Do(func() { - errorText.Text = "error could not open browser" - errorText.Color = colors.Red - errorText.Refresh() - }) - } - } - - fyne.Do(func() { - textStatus.Text = " Online" - textStatus.Color = colors.Green - textStatus.Refresh() - btnServer.Text = "Shutdown Application" - btnServer.Refresh() - linkOpenInBrowser.Show() - }) - - err = StoreEncryptedValue("TELA History", []byte(index.SCID), []byte("")) - if err != nil { - logger.Errorf("[Engram] Error saving TELA search result: %s\n", err) - } - }() - - return - } - - logger.Errorf("[Engram] Error serving TELA: %s\n", err) - errorText.Text = telaErrorToString(err) - errorText.Color = colors.Red - errorText.Refresh() - } - - removeOverlays() - } - } - - ratings, err := tela.GetRating(index.SCID, session.Daemon, 0) - if err != nil { - logger.Errorf("[Engram] GetRating: %s\n", err) - } - - labelRatingAverage := canvas.NewText(fmt.Sprintf("%.1f", ratings.Average), colors.Account) - labelRatingAverage.TextSize = 24 - labelRatingAverage.Alignment = fyne.TextAlignCenter - labelRatingAverage.TextStyle = fyne.TextStyle{Bold: true} - - linkTelaRatings := widget.NewHyperlinkWithStyle("View All Ratings", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) - linkTelaRatings.OnTapped = func() { - showLoadingOverlay() - err := viewTELARatingsOverlay(index.NameHdr, index.SCID) - if err != nil { - errorText.Text = err.Error() - errorText.Color = colors.Red - errorText.Refresh() - } - } - - hexagonImg := canvas.NewImageFromResource(telaHexagonColor(ratings.Average)) - hexagonImg.SetMinSize(fyne.NewSize(80, 86)) - - center := container.NewStack( - rectBox, - container.NewVScroll( - container.NewStack( - rectWidth90, - container.NewHBox( - layout.NewSpacer(), - container.NewVBox( - container.NewCenter( - image, - ), - rectSpacer, - labelName, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - rectWidth90, - labelDesc, - ), - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator, - rectSpacer, - rectSpacer, - labelDURL, - textDURL, - rectSpacer, - rectSpacer, - labelSeparator2, - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - container.NewStack( - hexagonImg, - container.NewCenter( - labelRatingAverage, - ), - ), - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewHBox( - layout.NewSpacer(), - linkTelaRatings, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator3, - rectSpacer, - rectSpacer, - labelStatus, - rectSpacer, - textStatus, - rectSpacer, - rectSpacer, - btnServer, - rectSpacer, - linkOpenInBrowser, - rectSpacer, - errorText, - rectSpacer, - rectSpacer, - labelSeparator4, - rectSpacer, - rectSpacer, - labelAuthor, - textAuthor, - container.NewHBox( - linkMessageAuthor, - layout.NewSpacer(), - ), - container.NewHBox( - linkCopyAuthor, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - labelSeparator5, - rectSpacer, - rectSpacer, - labelSCID, - textSCID, - container.NewHBox( - linkViewExplorer, - layout.NewSpacer(), - ), - container.NewHBox( - linkCopySCID, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - container.NewStack( - rectWidth90, - ), - ), - layout.NewSpacer(), - ), - ), - ), - rectSpacer, - rectSpacer, - ) - - top := container.NewVBox( - rectSpacer, - rectSpacer, - ) - - bottom := container.NewStack( - container.NewVBox( - rectSpacer, - rectSpacer, - container.NewStack( - container.NewHBox( - layout.NewSpacer(), - line1, - layout.NewSpacer(), - menuLabel, - layout.NewSpacer(), - line2, - layout.NewSpacer(), - ), - ), - rectSpacer, - rectSpacer, - container.NewCenter( - layout.NewSpacer(), - linkBack, - layout.NewSpacer(), - ), - rectSpacer, - rectSpacer, - rectSpacer, - rectSpacer, - ), - ) - - layout := container.NewStack( - frame, - container.NewBorder( - top, - bottom, - nil, - center, - ), - ) - - return NewVScroll(layout) -} +// Copyright 2023-2024 DERO Foundation. All rights reserved. +// Use of this source code in any form is governed by RESEARCH license. +// license can be found in the LICENSE file. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package main + +import ( + "crypto/sha1" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "image/color" + "net" + "net/url" + "os" + "path/filepath" + "regexp" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "time" + "unicode" + "unicode/utf8" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/storage" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + x "fyne.io/x/fyne/widget" + "github.com/civilware/Gnomon/structures" + "github.com/civilware/epoch" + "github.com/civilware/tela" + "github.com/civilware/tela/logger" + "github.com/deroproject/derohe/cryptography/crypto" + "github.com/deroproject/derohe/dvm" + "github.com/deroproject/derohe/globals" + "github.com/deroproject/derohe/rpc" + "github.com/deroproject/derohe/walletapi" + "github.com/deroproject/derohe/walletapi/mnemonics" + "github.com/deroproject/derohe/walletapi/xswd" + "github.com/deroproject/graviton" + qrcode "github.com/skip2/go-qrcode" +) + +func layoutMain() fyne.CanvasObject { + // Set theme + a.Settings().SetTheme(themes.main) + session.Domain = "app.main" + session.Path = "" + session.Password = "" + + // Define objects + + btnLogin := widget.NewButton("Connect", nil) + + if session.Error != "" { + btnLogin.Text = session.Error + btnLogin.Disable() + btnLogin.Refresh() + session.Error = "" + } + + btnLogin.OnTapped = func() { + if session.Path == "" { + btnLogin.Text = "No account selected..." + btnLogin.Disable() + btnLogin.Refresh() + } else if session.Password == "" { + btnLogin.Text = "Invalid password..." + btnLogin.Disable() + btnLogin.Refresh() + } else { + if !session.Offline { + btnLogin.Text = "Connect" + } else { + btnLogin.Text = "Decrypt" + } + btnLogin.Enable() + btnLogin.Refresh() + login() + btnLogin.Text = session.Error + btnLogin.Disable() + btnLogin.Refresh() + session.Error = "" + } + } + + btnLogin.Disable() + + session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { + if session.Domain == "app.main" || session.Domain == "app.register" { + if k.Name == fyne.KeyReturn { + if session.Path == "" { + btnLogin.Text = "No account selected..." + btnLogin.Disable() + btnLogin.Refresh() + } else if session.Password == "" { + btnLogin.Text = "Invalid password..." + btnLogin.Disable() + btnLogin.Refresh() + } else { + if !session.Offline { + btnLogin.Text = "Connect" + } else { + btnLogin.Text = "Decrypt" + } + btnLogin.Enable() + btnLogin.Refresh() + login() + btnLogin.Text = "Invalid password..." + btnLogin.Disable() + btnLogin.Refresh() + session.Error = "" + } + } + } else { + return + } + }) + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkCreate := widget.NewHyperlinkWithStyle("Create New Account", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkCreate.OnTapped = func() { + session.Domain = "app.create" + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutNewAccount()) + removeOverlays() + } + + linkRecover := widget.NewHyperlinkWithStyle("Recover Account", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkRecover.OnTapped = func() { + session.Domain = "app.restore" + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutRestore()) + removeOverlays() + } + + linkSettings := widget.NewHyperlinkWithStyle("Settings", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkSettings.OnTapped = func() { + session.Domain = "app.settings" + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutSettings()) + removeOverlays() + } + + modeData := binding.BindBool(&session.Offline) + mode := widget.NewCheckWithData(" Offline Mode", modeData) + mode.OnChanged = func(b bool) { + if b { + session.Offline = true + btnLogin.Text = "Decrypt" + btnLogin.Refresh() + } else { + session.Offline = false + btnLogin.Text = "Connect" + btnLogin.Refresh() + } + } + + footer := canvas.NewText("© 2025 DERO FOUNDATION | VERSION "+version.String(), colors.Gray) + footer.TextSize = 10 + footer.Alignment = fyne.TextAlignCenter + footer.TextStyle = fyne.TextStyle{Bold: true} + + wPassword := NewReturnEntry() + wPassword.OnReturn = btnLogin.OnTapped + wPassword.Password = true + wPassword.OnChanged = func(s string) { + session.Error = "" + if !session.Offline { + btnLogin.Text = "Connect" + } else { + btnLogin.Text = "Decrypt" + } + btnLogin.Enable() + btnLogin.Refresh() + session.Password = s + + if len(s) < 1 { + btnLogin.Disable() + btnLogin.Refresh() + } else if session.Path == "" { + btnLogin.Disable() + btnLogin.Refresh() + } else { + btnLogin.Enable() + } + + btnLogin.Refresh() + } + wPassword.SetPlaceHolder("Password") + + // Get account databases in app directory + list, err := GetAccounts() + if err != nil { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutAlert(2)) + } + + // Populate the accounts in dropdown menu + wAccount := widget.NewSelect(list, nil) + wAccount.PlaceHolder = "(Select Account)" + wAccount.OnChanged = func(s string) { + session.Error = "" + if !session.Offline { + btnLogin.Text = "Connect" + } else { + btnLogin.Text = "Decrypt" + } + btnLogin.Refresh() + + // OnChange set wallet path + switch session.Network { + case NETWORK_TESTNET: + session.Path = filepath.Join(AppPath(), "testnet") + string(filepath.Separator) + s + case NETWORK_SIMULATOR: + session.Path = filepath.Join(AppPath(), "testnet_simulator") + string(filepath.Separator) + s + default: + session.Path = filepath.Join(AppPath(), "mainnet") + string(filepath.Separator) + s + } + + if session.Password != "" { + btnLogin.Enable() + } else { + btnLogin.Disable() + } + + session.Window.Canvas().Focus(wPassword) + + btnLogin.Refresh() + } + + if len(list) < 1 { + wAccount.Disable() + wPassword.Disable() + } else { + wAccount.Enable() + } + + wSpacer := widget.NewLabel(" ") + + rectStatus := canvas.NewRectangle(color.Transparent) + rectStatus.SetMinSize(fyne.NewSize(10, 10)) + + headerBlock := canvas.NewRectangle(color.Transparent) + headerBlock.SetMinSize(fyne.NewSize(ui.Width, ui.MaxHeight*0.2)) + + headerBox := canvas.NewRectangle(color.Transparent) + headerBox.SetMinSize(fyne.NewSize(ui.Width, 1)) + + frame := &iframe{} + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(ui.Width, 5)) + + status.Connection.FillColor = colors.Gray + status.Cyberdeck.FillColor = colors.Gray + status.Gnomon.FillColor = colors.Gray + status.EPOCH.FillColor = colors.Gray + status.Sync.FillColor = colors.Gray + + form := container.NewStack( + res.mainBg, + container.NewVBox( + wSpacer, + container.NewStack( + headerBlock, + ), + rectSpacer, + rectSpacer, + wAccount, + rectSpacer, + wPassword, + rectSpacer, + mode, + rectSpacer, + rectSpacer, + btnLogin, + wSpacer, + container.NewStack( + container.NewHBox( + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + ), + ), + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + linkCreate, + layout.NewSpacer(), + ), + ), + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + linkRecover, + layout.NewSpacer(), + ), + ), + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + linkSettings, + layout.NewSpacer(), + ), + ), + ), + ) + + layout := container.NewStack( + frame, + container.NewBorder( + container.NewVBox( + container.NewCenter( + form, + ), + ), + container.NewVBox( + footer, + wSpacer, + ), + nil, + nil, + ), + ) + + return NewVScroll(layout) +} + +func layoutDashboard() fyne.CanvasObject { + resizeWindow(ui.MaxWidth, ui.MaxHeight) + + session.Dashboard = "main" + session.Domain = "app.wallet" + + session.Balance, _ = engram.Disk.Get_Balance() + session.BalanceText = canvas.NewText(walletapi.FormatMoney(session.Balance), colors.Green) + session.BalanceText.TextSize = 28 + session.BalanceText.TextStyle = fyne.TextStyle{Bold: true} + + network := "" + switch session.Network { + case NETWORK_TESTNET: + network = " T E S T N E T " + case NETWORK_SIMULATOR: + network = " S I M U L A T O R " + default: + network = " M A I N N E T " + } + + rectStatus := canvas.NewRectangle(color.Transparent) + rectStatus.SetMinSize(fyne.NewSize(10, 10)) + + frame := &iframe{} + + balanceCenter := container.NewCenter( + container.NewCenter( + session.BalanceText, + ), + ) + + path := strings.Split(session.Path, string(filepath.Separator)) + accountName := canvas.NewText(path[len(path)-1], colors.Green) + accountName.TextStyle = fyne.TextStyle{Bold: true} + accountName.TextSize = 18 + + gramSend := widget.NewButton(" Send ", nil) + + heading := canvas.NewText("B A L A N C E", colors.Gray) + heading.TextSize = 16 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + sendDesc := canvas.NewText("Add Transfer Details", colors.Gray) + sendDesc.TextSize = 18 + sendDesc.Alignment = fyne.TextAlignCenter + sendDesc.TextStyle = fyne.TextStyle{Bold: true} + + sendHeading := canvas.NewText("Send Money", colors.Green) + sendHeading.TextSize = 22 + sendHeading.Alignment = fyne.TextAlignCenter + sendHeading.TextStyle = fyne.TextStyle{Bold: true} + + headerLabel := canvas.NewText(" "+network+" ", colors.Gray) + headerLabel.TextSize = 11 + headerLabel.Alignment = fyne.TextAlignCenter + headerLabel.TextStyle = fyne.TextStyle{Bold: true} + + statusLabel := canvas.NewText(" S T A T U S ", colors.Gray) + statusLabel.TextSize = 11 + statusLabel.Alignment = fyne.TextAlignCenter + statusLabel.TextStyle = fyne.TextStyle{Bold: true} + + daemonLabel := canvas.NewText("OFFLINE", colors.Gray) + daemonLabel.TextSize = 12 + daemonLabel.Alignment = fyne.TextAlignCenter + daemonLabel.TextStyle = fyne.TextStyle{Bold: false} + + cyberdeckText := "CYBERDECK" + if cyberdeck.WS.server != nil { + cyberdeckText = "CYBERDECK (WS)" + } else if cyberdeck.RPC.server != nil { + cyberdeckText = "CYBERDECK (RPC)" + } else { + status.Cyberdeck.FillColor = colors.Gray + status.Cyberdeck.Refresh() + } + + cyberdeckLabel := canvas.NewText(cyberdeckText, colors.Gray) + cyberdeckLabel.TextSize = 12 + cyberdeckLabel.Alignment = fyne.TextAlignTrailing + cyberdeckLabel.TextStyle = fyne.TextStyle{Bold: false} + + gnomonLabel := canvas.NewText("GNOMON", colors.Gray) + gnomonLabel.TextSize = 12 + gnomonLabel.Alignment = fyne.TextAlignCenter + gnomonLabel.TextStyle = fyne.TextStyle{Bold: false} + + epochLabel := canvas.NewText("EPOCH", colors.Gray) + epochLabel.TextSize = 12 + epochLabel.Alignment = fyne.TextAlignTrailing + epochLabel.TextStyle = fyne.TextStyle{Bold: false} + if !epoch.IsActive() { + if cyberdeck.EPOCH.err != nil { + status.EPOCH.FillColor = colors.Red + status.EPOCH.Refresh() + } else { + status.EPOCH.FillColor = colors.Gray + status.EPOCH.Refresh() + } + } + + telaLabel := canvas.NewText("TELA", colors.Gray) + telaLabel.TextSize = 12 + telaLabel.Alignment = fyne.TextAlignCenter + telaLabel.TextStyle = fyne.TextStyle{Bold: false} + + telaStatus := canvas.NewCircle(colors.Gray) + if len(tela.GetServerInfo()) > 0 { + telaStatus.FillColor = colors.Green + } + + animationCanvas := canvas.NewCircle(color.Transparent) + + if !session.Offline { + if len(session.Daemon) > 30 { + daemonLabel.Text = "..." + session.Daemon[len(session.Daemon)-27:] + } else { + daemonLabel.Text = session.Daemon + } + + animationStatus := canvas.NewColorRGBAAnimation( + color.Transparent, + colors.Yellow, + time.Second, + func(c color.Color) { + animationCanvas.FillColor = c + animationCanvas.Refresh() + }) + + animationStatus.RepeatCount = fyne.AnimationRepeatForever + animationStatus.AutoReverse = true + animationStatus.Start() + } + + session.WalletHeight = engram.Disk.Get_Height() + session.StatusText = canvas.NewText(fmt.Sprintf("%d", session.WalletHeight), colors.Gray) + session.StatusText.TextSize = 12 + session.StatusText.Alignment = fyne.TextAlignTrailing + session.StatusText.TextStyle = fyne.TextStyle{Bold: false} + + menuLabel := canvas.NewText(" M O D U L E S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkLogout := widget.NewHyperlinkWithStyle("Sign Out", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkLogout.OnTapped = func() { + closeWallet() + } + + linkHistory := widget.NewHyperlinkWithStyle("View History", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkHistory.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutHistory()) + removeOverlays() + } + + menu := widget.NewSelect([]string{"Identity", "My Account", "Messages", "Transfers", "Asset Explorer", "Services", "Cyberdeck", "File Manager", "Contract Builder", "Datapad", "TELA", " "}, nil) + menu.PlaceHolder = "Select Module ..." + menu.OnChanged = func(s string) { + if s == "My Account" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutAccount()) + removeOverlays() + } else if s == "Transfers" { + session.Window.Canvas().SetContent(layoutTransition()) + session.Window.Canvas().SetContent(layoutTransfers()) + removeOverlays() + } else if s == "Asset Explorer" { + session.Window.Canvas().SetContent(layoutTransition()) + session.Window.Canvas().SetContent(layoutAssetExplorer()) + removeOverlays() + } else if s == "File Manager" { + session.Window.Canvas().SetContent(layoutTransition()) + session.Window.Canvas().SetContent(layoutFileManager()) + removeOverlays() + } else if s == "Contract Builder" { + session.Window.Canvas().SetContent(layoutTransition()) + session.Window.Canvas().SetContent(layoutContractBuilder("")) + removeOverlays() + } else if s == "Datapad" { + session.Window.Canvas().SetContent(layoutTransition()) + session.Window.Canvas().SetContent(layoutDatapad()) + removeOverlays() + } else if s == "Messages" { + session.Window.Canvas().SetContent(layoutTransition()) + session.Window.Canvas().SetContent(layoutMessages()) + removeOverlays() + } else if s == "Cyberdeck" { + session.Window.Canvas().SetContent(layoutTransition()) + session.Window.Canvas().SetContent(layoutCyberdeck()) + removeOverlays() + } else if s == "Identity" { + session.Window.Canvas().SetContent(layoutTransition()) + session.Window.Canvas().SetContent(layoutIdentity()) + removeOverlays() + } else if s == "Services" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutServiceAddress()) + removeOverlays() + } else if s == "TELA" { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutTELA()) + removeOverlays() + } else { + session.Window.Canvas().SetContent(layoutTransition()) + session.Window.Canvas().SetContent(layoutDashboard()) + removeOverlays() + } + + session.LastDomain = session.Window.Content() + } + + res.gram.SetMinSize(fyne.NewSize(ui.Width, 150)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + + rectSquare := canvas.NewRectangle(color.Transparent) + rectSquare.SetMinSize(fyne.NewSize(5, 5)) + + rectOffset := canvas.NewRectangle(color.Transparent) + rectOffset.SetMinSize(fyne.NewSize(81, 1)) + + deroForm := container.NewVBox( + rectSpacer, + res.gram, + rectSpacer, + container.NewStack( + container.NewHBox( + line1, + layout.NewSpacer(), + headerLabel, + layout.NewSpacer(), + line2, + ), + ), + rectSpacer, + rectSpacer, + heading, + rectSpacer, + balanceCenter, + rectSpacer, + rectSpacer, + gramSend, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkHistory, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + container.NewHBox( + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + ), + rectSpacer, + rectSpacer, + menu, + rectSpacer, + rectSpacer, + container.NewHBox( + line1, + layout.NewSpacer(), + statusLabel, + layout.NewSpacer(), + line2, + ), + rectSpacer, + rectSpacer, + container.NewVBox( + container.NewHBox( + container.NewStack( + rectStatus, + status.Connection, + ), + rectSquare, + daemonLabel, + layout.NewSpacer(), + container.NewStack( + rectOffset, + session.StatusText, + ), + rectSquare, + container.NewStack( + rectStatus, + animationCanvas, + status.Sync, + ), + ), + rectOffset, + container.NewHBox( + container.NewStack( + rectStatus, + animationCanvas, + status.Gnomon, + ), + rectSquare, + gnomonLabel, + layout.NewSpacer(), + container.NewStack( + rectOffset, + epochLabel, + ), + rectSquare, + container.NewStack( + rectStatus, + animationCanvas, + status.EPOCH, + ), + ), + rectOffset, + container.NewHBox( + container.NewStack( + rectStatus, + telaStatus, + ), + rectSquare, + telaLabel, + layout.NewSpacer(), + container.NewStack( + rectOffset, + cyberdeckLabel, + ), + rectSquare, + container.NewStack( + rectStatus, + status.Cyberdeck, + ), + ), + ), + ) + + grid := container.NewCenter( + deroForm, + ) + + gramSend.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutSend()) + removeOverlays() + } + + session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { + if session.Domain != "app.wallet" { + return + } + + if k.Name == fyne.KeyRight { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutCyberdeck()) + removeOverlays() + } else if k.Name == fyne.KeyLeft { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutIdentity()) + removeOverlays() + } else if k.Name == fyne.KeyUp { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutTransfers()) + removeOverlays() + } else if k.Name == fyne.KeyDown { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMessages()) + removeOverlays() + } + }) + + top := container.NewCenter( + layout.NewSpacer(), + grid, + layout.NewSpacer(), + ) + + bottom := container.NewStack( + container.NewVBox( + container.NewCenter( + linkLogout, + ), + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + c := container.NewBorder( + top, + bottom, + nil, + nil, + ) + + layout := container.NewStack( + frame, + c, + ) + + return NewVScroll(layout) +} + +func layoutSend() fyne.CanvasObject { + session.Domain = "app.send" + + wSpacer := widget.NewLabel(" ") + frame := &iframe{} + + btnSend := widget.NewButton("Save", nil) + + wAmount := widget.NewEntry() + wAmount.SetPlaceHolder("Amount") + + wMessage := widget.NewEntry() + wMessage.SetValidationError(nil) + wMessage.SetPlaceHolder("Message") + wMessage.Validator = func(s string) error { + bytes := []byte(s) + if len(bytes) <= 130 { + tx.Comment = s + wMessage.SetValidationError(nil) + return nil + } else { + err := errors.New("message too long") + wMessage.SetValidationError(err) + return err + } + } + + wPaymentID := widget.NewEntry() + wPaymentID.Validator = func(s string) (err error) { + tx.PaymentID, err = strconv.ParseUint(s, 10, 64) + if err != nil { + wPaymentID.SetValidationError(err) + tx.PaymentID = 0 + } + + return + } + wPaymentID.SetPlaceHolder("Payment ID / Service Port") + + options := []string{"Anonymity Set: 2 (None)", "Anonymity Set: 4 (Low)", "Anonymity Set: 8 (Low)", "Anonymity Set: 16 (Recommended)", "Anonymity Set: 32 (Medium)", "Anonymity Set: 64 (High)", "Anonymity Set: 128 (High)"} + wRings := widget.NewSelect(options, nil) + + wReceiver := widget.NewEntry() + wReceiver.SetPlaceHolder("Receiver username or address") + wReceiver.SetValidationError(nil) + wReceiver.Validator = func(s string) error { + address, err := globals.ParseValidateAddress(s) + if err != nil { + tx.Address = nil + addr, _ := checkUsername(s, -1) + if addr == "" { + btnSend.Disable() + err = errors.New("invalid username or address") + wReceiver.SetValidationError(err) + tx.Address = nil + return err + } else { + wReceiver.SetValidationError(nil) + tx.Address, _ = globals.ParseValidateAddress(addr) + if tx.Amount != 0 { + balance, _ := engram.Disk.Get_Balance() + if tx.Amount <= balance { + btnSend.Enable() + } + } + } + } else { + if address.IsIntegratedAddress() { + tx.Address = address + + if address.Arguments.HasValue(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { + amount := address.Arguments[address.Arguments.Index(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64)].Value + tx.Amount = amount.(uint64) + wAmount.Text = globals.FormatMoney(amount.(uint64)) + if amount.(uint64) != 0.00000 { + wAmount.Disable() + } + wAmount.Refresh() + } + + if address.Arguments.HasValue(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { + port := address.Arguments[address.Arguments.Index(rpc.RPC_DESTINATION_PORT, rpc.DataUint64)].Value + tx.PaymentID = port.(uint64) + wPaymentID.Text = strconv.FormatUint(port.(uint64), 10) + wPaymentID.Disable() + wPaymentID.Refresh() + } + + if address.Arguments.HasValue(rpc.RPC_COMMENT, rpc.DataString) { + comment := address.Arguments[address.Arguments.Index(rpc.RPC_COMMENT, rpc.DataString)].Value + tx.Comment = comment.(string) + wMessage.Text = comment.(string) + if comment.(string) != "" { + wMessage.Disable() + } + wMessage.Refresh() + } + + if tx.Ringsize == 0 { + wRings.SetSelected("Anonymity Set: 16 (Recommended)") + } + + if tx.Amount != 0 { + balance, _ := engram.Disk.Get_Balance() + if tx.Amount <= balance { + btnSend.Enable() + } + } + } else { + tx.Address = address + wReceiver.SetValidationError(nil) + if tx.Amount != 0 { + balance, _ := engram.Disk.Get_Balance() + if tx.Amount <= balance { + btnSend.Enable() + } + } + } + } + return nil + } + + /* + // TODO + wAll := widget.NewCheck(" All", func(b bool) { + if b { + tx.Amount = engram.Disk.GetAccount().Balance_Mature + wAmount.SetText(walletapi.FormatMoney(tx.Amount)) + } else { + tx.Amount = 0 + wAmount.SetText("") + } + }) + */ + + wAmount.Validator = func(s string) error { + if s == "" { + tx.Amount = 0 + wAmount.SetValidationError(errors.New("invalid transaction amount")) + btnSend.Disable() + } else { + balance, _ := engram.Disk.Get_Balance() + entry, err := globals.ParseAmount(s) + if err != nil { + tx.Amount = 0 + wAmount.SetValidationError(errors.New("invalid transaction amount")) + btnSend.Disable() + return errors.New("invalid transaction amount") + } + + if entry == 0 { + tx.Amount = 0 + wAmount.SetValidationError(errors.New("invalid transaction amount")) + btnSend.Disable() + return errors.New("invalid transaction amount") + } + + if entry <= balance { + tx.Amount = entry + wAmount.SetValidationError(nil) + if wReceiver.Validate() == nil { + btnSend.Enable() + } + } else { + tx.Amount = 0 + btnSend.Disable() + wAmount.SetValidationError(errors.New("insufficient funds")) + } + return nil + } + return errors.New("invalid transaction amount") + } + + wAmount.SetValidationError(nil) + + wRings.PlaceHolder = "(Select Anonymity Set)" + if tx.Ringsize < 2 { + tx.Ringsize = 16 + } else if len(tx.Pending) > 0 { + rsIndex := 3 + switch tx.Ringsize { + case 2: + rsIndex = 0 + case 4: + rsIndex = 1 + case 8: + rsIndex = 2 + case 16: + rsIndex = 3 + case 32: + rsIndex = 4 + case 64: + rsIndex = 5 + case 128: + rsIndex = 6 + } + wRings.SetSelectedIndex(rsIndex) + } + + wRings.OnChanged = func(s string) { + var err error + regex := regexp.MustCompile("[0-9]+") + result := regex.FindAllString(s, -1) + tx.Ringsize, err = strconv.ParseUint(result[0], 10, 64) + if err != nil { + tx.Ringsize = 16 + wRings.SetSelected(options[3]) + } + session.Window.Canvas().Focus(wReceiver) + } + + btnSend.OnTapped = func() { + _, err := globals.ParseAmount(wAmount.Text) + if tx.Address != nil { + if wRings != nil && err == nil && tx.Address != nil { + err = addTransfer() + if err == nil { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutTransfers()) + removeOverlays() + } + } else { + wReceiver.SetValidationError(errors.New("invalid address")) + wReceiver.Refresh() + } + } + } + + sendHeading := canvas.NewText("S E N D M O N E Y", colors.Gray) + sendHeading.TextSize = 16 + sendHeading.Alignment = fyne.TextAlignCenter + sendHeading.TextStyle = fyne.TextStyle{Bold: true} + + optionalLabel := canvas.NewText(" O P T I O N A L ", colors.Gray) + optionalLabel.TextSize = 11 + optionalLabel.Alignment = fyne.TextAlignCenter + optionalLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkCancel := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + + rectList := canvas.NewRectangle(color.Transparent) + rectList.SetMinSize(fyne.NewSize(ui.Width, 260)) + + rect300 := canvas.NewRectangle(color.Transparent) + rect300.SetMinSize(fyne.NewSize(ui.Width, 30)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + + form := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + rect300, + sendHeading, + ), + rectSpacer, + rectSpacer, + wRings, + rectSpacer, + wReceiver, + wAmount, + rectSpacer, + rectSpacer, + container.NewHBox( + line1, + layout.NewSpacer(), + optionalLabel, + layout.NewSpacer(), + line2, + ), + rectSpacer, + rectSpacer, + wPaymentID, + wMessage, + wSpacer, + ) + + grid := container.NewCenter( + form, + ) + + linkCancel.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + if len(tx.Pending) == 0 { + tx = Transfers{} + } + } + + top := container.NewCenter( + layout.NewSpacer(), + grid, + layout.NewSpacer(), + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rect300, + btnSend, + ), + layout.NewSpacer(), + ), + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewHBox( + layout.NewSpacer(), + linkCancel, + layout.NewSpacer(), + ), + layout.NewSpacer(), + ), + wSpacer, + ), + ) + + c := container.NewBorder( + top, + bottom, + nil, + nil, + ) + + layout := container.NewStack( + frame, + c, + ) + + return NewVScroll(layout) +} + +func layoutServiceAddress() fyne.CanvasObject { + session.Domain = "app.service" + + wSpacer := widget.NewLabel(" ") + frame := &iframe{} + + btnCreate := widget.NewButton("Create", nil) + + wPaymentID := widget.NewEntry() + + wReceiver := widget.NewEntry() + wReceiver.Text = engram.Disk.GetAddress().String() + wReceiver.Disable() + + tx.Address, _ = globals.ParseValidateAddress(engram.Disk.GetAddress().String()) + + wReceiver.SetPlaceHolder("Receiver username or address") + wReceiver.SetValidationError(nil) + + wAmount := widget.NewEntry() + wAmount.SetPlaceHolder("Amount") + + wMessage := widget.NewEntry() + wMessage.SetPlaceHolder("Message") + wMessage.Validator = func(s string) (err error) { + bytes := []byte(s) + if len(bytes) <= 130 { + tx.Comment = s + } else { + err = errors.New("message too long") + wMessage.SetValidationError(err) + } + + return + } + + wAmount.Validator = func(s string) error { + if s == "" { + tx.Amount = 0 + wAmount.SetValidationError(errors.New("invalid transaction amount")) + btnCreate.Disable() + } else { + amount, err := globals.ParseAmount(s) + if err != nil { + tx.Amount = 0 + wAmount.SetValidationError(errors.New("invalid transaction amount")) + btnCreate.Disable() + return errors.New("invalid transaction amount") + } + wAmount.SetValidationError(nil) + tx.Amount = amount + btnCreate.Enable() + + return nil + } + return errors.New("invalid transaction amount") + } + + wAmount.SetValidationError(nil) + + wPaymentID.Validator = func(s string) (err error) { + tx.PaymentID, err = strconv.ParseUint(s, 10, 64) + if err != nil { + tx.PaymentID = 0 + btnCreate.Disable() + wPaymentID.SetValidationError(err) + return + } else { + if wReceiver.Text != "" { + btnCreate.Enable() + wPaymentID.SetValidationError(nil) + return + } else { + err = errors.New("empty payment id") + wPaymentID.SetValidationError(err) + return + } + } + } + wPaymentID.SetPlaceHolder("Payment ID / Service Port") + + sendHeading := canvas.NewText("S E R V I C E A D D R E S S", colors.Gray) + sendHeading.TextSize = 16 + sendHeading.Alignment = fyne.TextAlignCenter + sendHeading.TextStyle = fyne.TextStyle{Bold: true} + + optionalLabel := canvas.NewText(" O P T I O N A L ", colors.Gray) + optionalLabel.TextSize = 11 + optionalLabel.Alignment = fyne.TextAlignCenter + optionalLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkCancel := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + + rectList := canvas.NewRectangle(color.Transparent) + rectList.SetMinSize(fyne.NewSize(ui.Width, 260)) + + rect300 := canvas.NewRectangle(color.Transparent) + rect300.SetMinSize(fyne.NewSize(ui.Width, 30)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + + btnCreate.OnTapped = func() { + var err error + if tx.Address != nil && tx.PaymentID != 0 { + if wAmount.Text != "" { + _, err = globals.ParseAmount(wAmount.Text) + } + + if err == nil { + header := canvas.NewText("CREATE SERVICE ADDRESS", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + subHeader := canvas.NewText("Successfully Created", colors.Account) + subHeader.TextSize = 22 + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + + labelAddress := canvas.NewText("------------- INTEGRATED ADDRESS -------------", colors.Gray) + labelAddress.TextSize = 12 + labelAddress.Alignment = fyne.TextAlignCenter + labelAddress.TextStyle = fyne.TextStyle{Bold: true} + + btnCopy := widget.NewButton("Copy Service Address", nil) + + valueAddress := widget.NewRichTextFromMarkdown("") + valueAddress.Wrapping = fyne.TextWrapBreak + + address := engram.Disk.GetRandomIAddress8() + address.Arguments = nil + address.Arguments = append(address.Arguments, rpc.Argument{Name: rpc.RPC_NEEDS_REPLYBACK_ADDRESS, DataType: rpc.DataUint64, Value: uint64(1)}) + address.Arguments = append(address.Arguments, rpc.Argument{Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: tx.Amount}) + address.Arguments = append(address.Arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: tx.PaymentID}) + address.Arguments = append(address.Arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: tx.Comment}) + + err := address.Arguments.Validate_Arguments() + if err != nil { + logger.Errorf("[Service Address] Error: %s\n", err) + subHeader.Text = "Error" + subHeader.Refresh() + btnCopy.Disable() + } else { + logger.Printf("[Service Address] New Integrated Address: %s\n", address.String()) + logger.Printf("[Service Address] Arguments: %s\n", address.Arguments) + + valueAddress.ParseMarkdown("" + address.String()) + valueAddress.Refresh() + } + + btnCopy.OnTapped = func() { + a.Clipboard().SetContent(address.String()) + } + + linkClose := widget.NewHyperlinkWithStyle("Go Back", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + overlay := session.Window.Canvas().Overlays() + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } + + var imageQR *canvas.Image + + qr, err := qrcode.New(address.String(), qrcode.Highest) + if err != nil { + + } else { + qr.BackgroundColor = colors.DarkMatter + qr.ForegroundColor = colors.Green + } + + imageQR = canvas.NewImageFromImage(qr.Image(int(ui.Width * 0.65))) + imageQR.SetMinSize(fyne.NewSize(ui.Width*0.65, ui.Width*0.65)) + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay := session.Window.Canvas().Overlays() + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + subHeader, + rectSpacer, + rectSpacer, + rectSpacer, + labelAddress, + rectSpacer, + valueAddress, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + imageQR, + layout.NewSpacer(), + ), + widget.NewLabel(""), + btnCopy, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + } else { + wReceiver.SetValidationError(errors.New("invalid address")) + wReceiver.Refresh() + } + } + } + + form := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + rect300, + sendHeading, + ), + rectSpacer, + rectSpacer, + wReceiver, + wPaymentID, + rectSpacer, + rectSpacer, + container.NewHBox( + line1, + layout.NewSpacer(), + optionalLabel, + layout.NewSpacer(), + line2, + ), + rectSpacer, + rectSpacer, + wAmount, + wMessage, + wSpacer, + ) + + grid := container.NewCenter( + form, + ) + + linkCancel.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } + + top := container.NewCenter( + layout.NewSpacer(), + grid, + layout.NewSpacer(), + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rect300, + btnCreate, + ), + layout.NewSpacer(), + ), + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewHBox( + layout.NewSpacer(), + linkCancel, + layout.NewSpacer(), + ), + layout.NewSpacer(), + ), + wSpacer, + ), + ) + + c := container.NewBorder( + top, + bottom, + nil, + nil, + ) + + layout := container.NewStack( + frame, + c, + ) + + return NewVScroll(layout) +} + +func layoutNewAccount() fyne.CanvasObject { + resizeWindow(ui.MaxWidth, ui.MaxHeight) + a.Settings().SetTheme(themes.alt) + + session.Domain = "app.register" + session.Language = -1 + session.Error = "" + session.Name = "" + session.Password = "" + session.PasswordConfirm = "" + + languages := mnemonics.Language_List() + + errorText := canvas.NewText(" ", colors.Green) + errorText.TextSize = 12 + errorText.Alignment = fyne.TextAlignCenter + + btnCreate := widget.NewButton("Create", nil) + btnCreate.Disable() + + linkCancel := widget.NewHyperlinkWithStyle("Return to Login", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkCancel.OnTapped = func() { + session.Domain = "app.main" + session.Error = "" + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMain()) + removeOverlays() + } + + btnCopySeed := widget.NewButton("Copy Recovery Words", nil) + btnCopyAddress := widget.NewButton("Copy Address", nil) + + if !a.Driver().Device().IsMobile() { + session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { + if session.Domain != "app.register" { + return + } + + if k.Name == fyne.KeyReturn { + errorText.Text = "" + errorText.Refresh() + create() + errorText.Text = session.Error + errorText.Refresh() + } + }) + } + + wPassword := widget.NewEntry() + wPassword.Password = true + wPassword.OnChanged = func(s string) { + session.Error = "" + errorText.Text = "" + errorText.Refresh() + session.Password = s + + if len(session.Password) > 0 && session.Password == session.PasswordConfirm && !findAccount() && session.Language != -1 { + btnCreate.Enable() + btnCreate.Refresh() + } else { + btnCreate.Disable() + btnCreate.Refresh() + } + } + wPassword.SetPlaceHolder("Password") + wPassword.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) + + wPasswordConfirm := widget.NewEntry() + wPasswordConfirm.Password = true + wPasswordConfirm.OnChanged = func(s string) { + session.Error = "" + errorText.Text = "" + errorText.Refresh() + session.PasswordConfirm = s + + if len(session.Password) > 0 && session.Password == session.PasswordConfirm && !findAccount() && session.Language != -1 { + btnCreate.Enable() + btnCreate.Refresh() + } else { + btnCreate.Disable() + btnCreate.Refresh() + } + } + wPasswordConfirm.SetPlaceHolder("Confirm Password") + wPasswordConfirm.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) + + wAccount := widget.NewEntry() + wAccount.SetPlaceHolder("Account Name") + wAccount.Validator = func(s string) (err error) { + session.Error = "" + errorText.Text = "" + errorText.Refresh() + + if len(s) > 25 { + err = errors.New("account name is too long") + wAccount.SetText(session.Name) + wAccount.Refresh() + return + } + + err = checkDir() + if err != nil { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutAlert(2)) + return + } + + switch getNetwork() { + case NETWORK_TESTNET: + session.Path = filepath.Join(AppPath(), "testnet", s+".db") + case NETWORK_SIMULATOR: + session.Path = filepath.Join(AppPath(), "testnet_simulator", s+".db") + default: + session.Path = filepath.Join(AppPath(), "mainnet", s+".db") + } + session.Name = s + + if findAccount() { + err = errors.New("account name already exists") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } else { + errorText.Text = "" + errorText.Refresh() + } + + if len(session.Password) > 0 && session.Password == session.PasswordConfirm && !findAccount() && session.Language != -1 { + btnCreate.Enable() + btnCreate.Refresh() + } else { + btnCreate.Disable() + btnCreate.Refresh() + } + return nil + } + + wAccount.OnChanged = func(s string) { + wAccount.Validate() + } + + wLanguage := widget.NewSelect(languages, nil) + wLanguage.OnChanged = func(s string) { + index := wLanguage.SelectedIndex() + session.Language = index + session.Window.Canvas().Focus(wAccount) + + if len(session.Password) > 0 && session.Password == session.PasswordConfirm && !findAccount() && session.Language != -1 { + btnCreate.Enable() + btnCreate.Refresh() + } else { + btnCreate.Disable() + btnCreate.Refresh() + } + } + wLanguage.PlaceHolder = "(Select Language)" + + wSpacer := widget.NewLabel(" ") + heading := canvas.NewText("New Account", colors.Green) + heading.TextSize = 22 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + heading2 := canvas.NewText("Recovery", colors.Green) + heading2.TextSize = 22 + heading2.Alignment = fyne.TextAlignCenter + heading2.TextStyle = fyne.TextStyle{Bold: true} + + rectStatus := canvas.NewRectangle(color.Transparent) + rectStatus.SetMinSize(fyne.NewSize(10, 10)) + + rectHeader := canvas.NewRectangle(color.Transparent) + rectHeader.SetMinSize(fyne.NewSize(ui.Width, 10)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(ui.Width, 5)) + + grid := container.NewVBox() + grid.Objects = nil + + header := container.NewVBox( + wSpacer, + heading, + rectSpacer, + rectSpacer, + ) + + form := container.NewVBox( + wLanguage, + rectSpacer, + wAccount, + wPassword, + wPasswordConfirm, + rectSpacer, + errorText, + rectSpacer, + btnCreate, + ) + + footer := container.NewVBox( + container.NewHBox( + layout.NewSpacer(), + linkCancel, + layout.NewSpacer(), + ), + wSpacer, + ) + + body := widget.NewLabel("Please save the following 25 recovery words in a safe place. These are the keys to your account, so never share them with anyone.") + body.Wrapping = fyne.TextWrapWord + body.Alignment = fyne.TextAlignCenter + body.TextStyle = fyne.TextStyle{Bold: true} + + formSuccess := container.NewVBox( + body, + wSpacer, + container.NewCenter(grid), + rectSpacer, + errorText, + rectSpacer, + btnCopyAddress, + btnCopySeed, + rectSpacer, + ) + + formSuccess.Hide() + + scrollBox := container.NewVScroll( + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + formSuccess, + form, + ), + layout.NewSpacer(), + ), + ) + scrollBox.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.70)) + + btnCreate.OnTapped = func() { + if findAccount() { + errorText.Text = "Account name already exists." + errorText.Color = colors.Red + errorText.Refresh() + return + } else { + errorText.Text = "" + errorText.Refresh() + } + + address, seed, err := create() + if err != nil { + errorText.Text = session.Error + errorText.Refresh() + return + } + + formatted := strings.Split(seed, " ") + + rect := canvas.NewRectangle(color.RGBA{21, 27, 36, 255}) + rect.SetMinSize(fyne.NewSize(ui.Width, 25)) + + for i := 0; i < len(formatted); i++ { + pos := fmt.Sprintf("%d", i+1) + word := strings.ReplaceAll(formatted[i], " ", "") + grid.Add(container.NewStack( + rect, + container.NewHBox( + widget.NewLabel(" "), + widget.NewLabelWithStyle(pos, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), + layout.NewSpacer(), + widget.NewLabelWithStyle(word, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + widget.NewLabel(" "), + ), + ), + ) + } + + btnCopySeed.OnTapped = func() { + a.Clipboard().SetContent(seed) + } + + btnCopyAddress.OnTapped = func() { + a.Clipboard().SetContent(address) + } + + form.Hide() + form.Refresh() + formSuccess.Show() + formSuccess.Refresh() + grid.Refresh() + scrollBox.Refresh() + session.Window.Canvas().Content().Refresh() + session.Window.Canvas().Refresh(session.Window.Content()) + } + + layout := container.NewBorder( + container.NewVBox( + header, + scrollBox, + ), + footer, + nil, + nil, + ) + return NewVScroll(layout) +} + +func layoutRestore() fyne.CanvasObject { + resizeWindow(ui.MaxWidth, ui.MaxHeight) + a.Settings().SetTheme(themes.alt) + + session.Domain = "app.restore" + session.Language = -1 + session.Error = "" + session.Name = "" + session.Password = "" + session.PasswordConfirm = "" + + scrollBox := container.NewVScroll(nil) + + errorText := canvas.NewText(" ", colors.Green) + errorText.TextSize = 12 + errorText.Alignment = fyne.TextAlignCenter + + btnCreate := widget.NewButton("Recover", nil) + btnCreate.Disable() + + linkReturn := widget.NewHyperlinkWithStyle("Return to Login", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkReturn.OnTapped = func() { + session.Domain = "app.main" + session.Error = "" + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMain()) + removeOverlays() + } + + btnCopyAddress := widget.NewButton("Copy Address", nil) + + wPassword := NewMobileEntry() + wPassword.OnFocusGained = func() { + offset := wPassword.Position().Y + if offset-scrollBox.Offset.Y > scrollBox.MinSize().Height { + scrollBox.Offset = fyne.NewPos(0, offset) + scrollBox.Refresh() + } + } + + wPassword.Password = true + wPassword.OnChanged = func(s string) { + session.Error = "" + errorText.Text = "" + errorText.Refresh() + session.Password = s + + if len(session.Password) > 0 && session.Password == session.PasswordConfirm && session.Name != "" { + + } else { + btnCreate.Disable() + btnCreate.Refresh() + } + } + wPassword.SetPlaceHolder("Password") + wPassword.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) + + wPasswordConfirm := NewMobileEntry() + wPasswordConfirm.OnFocusGained = func() { + offset := wPasswordConfirm.Position().Y + if offset-scrollBox.Offset.Y > scrollBox.MinSize().Height { + scrollBox.Offset = fyne.NewPos(0, offset) + scrollBox.Refresh() + } + } + + wPasswordConfirm.Password = true + wPasswordConfirm.OnChanged = func(s string) { + session.Error = "" + errorText.Text = "" + errorText.Refresh() + session.PasswordConfirm = s + + if len(session.Password) > 0 && session.Password == session.PasswordConfirm && session.Name != "" { + + } else { + btnCreate.Disable() + btnCreate.Refresh() + } + } + wPasswordConfirm.SetPlaceHolder("Confirm Password") + wPasswordConfirm.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) + + recoveryType := widget.NewSelect([]string{"Recovery Words", "Secret Hex Key", "Import File"}, nil) + recoveryType.PlaceHolder = "(Recovery Type)" + recoveryType.SetSelectedIndex(0) + recoveryType.OnChanged = func(s string) { + errorText.Text = "" + errorText.Refresh() + } + + wAccount := NewMobileEntry() + wAccount.OnFocusGained = func() { + scrollBox.Offset = fyne.NewPos(0, 0) + scrollBox.Refresh() + } + + wLanguage := widget.NewSelect(mnemonics.Language_List(), nil) + wLanguage.OnChanged = func(s string) { + index := wLanguage.SelectedIndex() + session.Language = index + session.Window.Canvas().Focus(wAccount) + errorText.Text = "" + errorText.Refresh() + } + wLanguage.PlaceHolder = "(Select Language)" + wLanguage.Hide() + + wAccount.SetPlaceHolder("Account Name") + wAccount.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) + wAccount.Validator = func(s string) (err error) { + session.Error = "" + errorText.Text = "" + errorText.Refresh() + + if len(s) > 25 { + err = errors.New("account name is too long") + wAccount.SetText(session.Name) + wAccount.Refresh() + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + err = checkDir() + if err != nil { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutAlert(2)) + return + } + + switch getNetwork() { + case NETWORK_TESTNET: + session.Path = filepath.Join(AppPath(), "testnet") + string(filepath.Separator) + s + ".db" + case NETWORK_SIMULATOR: + session.Path = filepath.Join(AppPath(), "testnet_simulator") + string(filepath.Separator) + s + ".db" + default: + session.Path = filepath.Join(AppPath(), "mainnet") + string(filepath.Separator) + s + ".db" + } + session.Name = s + + if findAccount() { + err = errors.New("account name already exists") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if len(session.Password) > 0 && session.Password == session.PasswordConfirm && session.Name != "" { + + } else { + btnCreate.Disable() + btnCreate.Refresh() + } + + if s != "" { + recoveryType.Disable() + } else { + recoveryType.Enable() + } + + return nil + } + + wSpacer := widget.NewLabel(" ") + heading := canvas.NewText("Recover Account", colors.Green) + heading.TextSize = 22 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + heading2 := canvas.NewText("Success", colors.Green) + heading2.TextSize = 22 + heading2.Alignment = fyne.TextAlignCenter + heading2.TextStyle = fyne.TextStyle{Bold: true} + + rectStatus := canvas.NewRectangle(color.Transparent) + rectStatus.SetMinSize(fyne.NewSize(10, 10)) + + rectHeader := canvas.NewRectangle(color.Transparent) + rectHeader.SetMinSize(fyne.NewSize(ui.Width, 10)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(ui.Width, 5)) + + status.Connection.FillColor = colors.Gray + status.Cyberdeck.FillColor = colors.Gray + status.Gnomon.FillColor = colors.Gray + status.Sync.FillColor = colors.Gray + + grid := container.NewVBox() + grid.Objects = nil + + seedEntry := NewMobileEntry() + seedEntry.SetPlaceHolder("Recovery Phrase (25 words)") + seedEntry.MultiLine = true + seedEntry.Wrapping = fyne.TextWrapWord + seedEntry.SetMinRowsVisible(6) + + btnPasteSeed := widget.NewButtonWithIcon("", theme.ContentPasteIcon(), nil) + btnPasteSeed.OnTapped = func() { + clipboardText := a.Clipboard().Content() + if clipboardText != "" { + seedEntry.SetText(clipboardText) + } + } + + seedInfo := canvas.NewText(" ", colors.Green) + seedInfo.TextSize = 12 + seedInfo.Alignment = fyne.TextAlignCenter + + seedEntry.Validator = func(s string) (err error) { + errorText.Text = "" + errorText.Color = colors.Red + errorText.Refresh() + seedInfo.Text = "" + seedInfo.Refresh() + + s = strings.TrimSpace(s) + if s == "" { + btnCreate.Disable() + return nil + } + + words := strings.Fields(s) + wordCount := len(words) + + if wordCount != 24 && wordCount != 25 { + btnCreate.Disable() + err = errors.New("seed must contain exactly 24 or 25 words") + errorText.Text = fmt.Sprintf("%d words detected, expected 24 or 25", wordCount) + errorText.Refresh() + return err + } + + invalidWords := []string{} + for _, word := range words { + if !checkSeedWord(word) { + invalidWords = append(invalidWords, word) + } + } + + if len(invalidWords) > 0 { + btnCreate.Disable() + err = errors.New("invalid seed words detected") + if len(invalidWords) <= 3 { + errorText.Text = fmt.Sprintf("Invalid words: %s", strings.Join(invalidWords, ", ")) + } else { + errorText.Text = fmt.Sprintf("%d invalid words detected, starting with: %s...", len(invalidWords), strings.Join(invalidWords[:3], ", ")) + } + errorText.Refresh() + return err + } + + seedInfo.Text = fmt.Sprintf("%d valid words", wordCount) + seedInfo.Color = colors.Green + seedInfo.Refresh() + btnCreate.Enable() + return nil + } + + hexEntry := widget.NewEntry() + hexEntry.SetPlaceHolder("Secret Key (64 character hex)") + hexEntry.Validator = func(s string) (err error) { + _, err = hex.DecodeString(s) + if len(s) > 64 || err != nil { + err = errors.New("invalid hex key") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + btnCreate.Disable() + + return + } + + errorText.Text = "" + errorText.Refresh() + if s != "" { + btnCreate.Enable() + } + + return + } + + hexSpacer := canvas.NewRectangle(color.Transparent) + hexSpacer.SetMinSize(fyne.NewSize(ui.Width, 91)) + + hexForm := container.NewVBox( + rectSpacer, + hexEntry, + hexSpacer, + errorText, + ) + + seedForm := container.NewVBox( + rectSpacer, + seedEntry, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + btnPasteSeed, + layout.NewSpacer(), + ), + rectSpacer, + seedInfo, + rectSpacer, + errorText, + rectSpacer, + ) + + // Create a new form for account/password inputs + recoveryForm := container.NewVBox( + wLanguage, + rectSpacer, + rectSpacer, + wAccount, + wPassword, + wPasswordConfirm, + rectSpacer, + rectSpacer, + seedForm, + ) + + importFileText := canvas.NewText(" ", colors.Green) + importFileText.TextSize = 12 + importFileText.Alignment = fyne.TextAlignCenter + + importFileForm := container.NewVBox( + rectSpacer, + rectSpacer, + errorText, + rectSpacer, + rectSpacer, + importFileText, + rectSpacer, + rectSpacer, + ) + + form := container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + recoveryType, + rectSpacer, + recoveryForm, + ), + layout.NewSpacer(), + ) + + recoveryType.OnChanged = func(s string) { + errorText.Text = "" + errorText.Refresh() + + switch s { + case "Secret Hex Key": + wLanguage.Show() + form.Objects[1].(*fyne.Container).Objects[2] = recoveryForm + recoveryForm.Objects[8] = hexForm + case "Recovery Words": + wLanguage.Hide() + form.Objects[1].(*fyne.Container).Objects[2] = recoveryForm + recoveryForm.Objects[8] = seedForm + case "Import File": + btnCreate.Disable() + importFileText.Text = "" + importFileText.Refresh() + form.Objects[1].(*fyne.Container).Objects[2] = importFileForm + dialogFileImport := dialog.NewFileOpen(func(uri fyne.URIReadCloser, err error) { + if err != nil { + logger.Errorf("[Engram] File dialog: %s\n", err) + errorText.Text = "could not import wallet file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if uri == nil { + return // Canceled + } + + fileName := uri.URI().String() + if uri.URI().MimeType() != "text/plain" { + logger.Errorf("[Engram] Cannot import file %s\n", fileName) + errorText.Text = "cannot import file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if a.Driver().Device().IsMobile() { + fileName = uri.URI().Name() + } else { + fileName = filepath.Base(strings.Replace(fileName, "file://", "", -1)) + } + + if !strings.HasSuffix(fileName, ".db") { + logger.Errorf("[Engram] Engram requires .db wallet file\n") + errorText.Text = "invalid wallet file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + filedata, err := readFromURI(uri) + if err != nil { + logger.Errorf("[Engram] Cannot read URI file data for %s: %s\n", fileName, err) + errorText.Text = "cannot read file data" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + filePath := "" + switch session.Network { + case NETWORK_TESTNET: + filePath = filepath.Join(AppPath(), "testnet", fileName) + case NETWORK_SIMULATOR: + filePath = filepath.Join(AppPath(), "testnet_simulator", fileName) + default: + filePath = filepath.Join(AppPath(), "mainnet", fileName) + } + + if _, err = os.Stat(filePath); !os.IsNotExist(err) { + logger.Errorf("[Engram] Wallet file %q already exists\n", fileName) + errorText.Text = "wallet file already exists" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + err = os.WriteFile(filePath, filedata, 0600) + if err != nil { + logger.Errorf("[Engram] Importing file %s: %s\n", fileName, err) + errorText.Text = "error importing wallet file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + errorText.Text = fmt.Sprintf("%s wallet file imported successfully", strings.ToLower(session.Network)) + errorText.Color = colors.Green + errorText.Refresh() + + if len(fileName) > 50 { + fileName = fileName[0:50] + "..." + } + + importFileText.Text = fileName + importFileText.Color = colors.Green + importFileText.Refresh() + + }, session.Window) + + if !a.Driver().Device().IsMobile() { + // Open file browser in current directory + uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) + if err == nil { + dialogFileImport.SetLocation(uri) + } else { + logger.Errorf("[Engram] Could not open current directory %s\n", err) + } + } + + dialogFileImport.SetFilter(storage.NewExtensionFileFilter([]string{".db"})) + dialogFileImport.SetView(dialog.ListView) + dialogFileImport.Resize(fyne.NewSize(ui.Width, ui.Height)) + dialogFileImport.Show() + } + } + + body := widget.NewLabel("Your account has been successfully recovered. ") + body.Wrapping = fyne.TextWrapWord + body.Alignment = fyne.TextAlignCenter + body.TextStyle = fyne.TextStyle{Bold: true} + + formSuccess := container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + rectSpacer, + rectSpacer, + heading2, + rectSpacer, + body, + rectSpacer, + rectSpacer, + container.NewCenter(grid), + rectSpacer, + rectSpacer, + btnCopyAddress, + rectSpacer, + ), + layout.NewSpacer(), + ) + + formSuccess.Hide() + + scrollBox = container.NewVScroll( + container.NewStack( + rectHeader, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + form, + formSuccess, + ), + layout.NewSpacer(), + ), + ), + ) + + scrollBox.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.65)) + + btnCreate.OnTapped = func() { + if engram.Disk != nil { + closeWallet() + } + + var err error + + if findAccount() { + err = errors.New("account name already exists") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } else { + errorText.Text = "" + errorText.Refresh() + } + + getNetwork() + + var language string + var temp *walletapi.Wallet_Disk + + if recoveryType.SelectedIndex() == 1 { + if wAccount.Text == "" { + err = errors.New("enter account name") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if wLanguage.SelectedIndex() < 0 { + err = errors.New("select seed language") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + go func() { + wLanguage.FocusGained() + time.Sleep(time.Second) + wLanguage.FocusLost() + }() + return + } + + if wPassword.Text == "" { + err = errors.New("enter and confirm a password") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if session.Password != session.PasswordConfirm { + err = errors.New("passwords do not match") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if hexEntry.Text == "" { + err = errors.New("enter a valid hex key") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if len(hexEntry.Text) > 64 { + err = errors.New("key must be less than 65 chars") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + hexKey, err := hex.DecodeString(hexEntry.Text) + if err != nil { + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + temp, err = walletapi.Create_Encrypted_Wallet(session.Path, session.Password, new(crypto.BNRed).SetBytes(hexKey)) + if err != nil { + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + language = wLanguage.Selected + + } else { + if wAccount.Text == "" { + err = errors.New("enter account name") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if wPassword.Text == "" { + err = errors.New("enter and confirm a password") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if session.Password != session.PasswordConfirm { + err = errors.New("passwords do not match") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + words := strings.TrimSpace(seedEntry.Text) + + language, _, err = mnemonics.Words_To_Key(words) + if err != nil { + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + temp, err = walletapi.Create_Encrypted_Wallet_From_Recovery_Words(session.Path, session.Password, words) + if err != nil { + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + } + + engram.Disk = temp + + if session.Network == NETWORK_MAINNET { + engram.Disk.SetNetwork(true) + } else { + engram.Disk.SetNetwork(false) + } + + engram.Disk.SetSeedLanguage(language) + + address := engram.Disk.GetAddress().String() + + btnCopyAddress.OnTapped = func() { + a.Clipboard().SetContent(address) + } + + engram.Disk.Get_Balance_Rescan() + engram.Disk.Save_Wallet() + engram.Disk.Close_Encrypted_Wallet() + + session.WalletOpen = false + engram.Disk = nil + session.Path = "" + session.Name = "" + tx = Transfers{} + + btnCreate.Hide() + form.Hide() + form.Refresh() + formSuccess.Show() + formSuccess.Refresh() + grid.Refresh() + scrollBox.Refresh() + session.Window.Canvas().Content().Refresh() + session.Window.Canvas().Refresh(session.Window.Content()) + } + + header := container.NewVBox( + rectSpacer, + rectSpacer, + heading, + rectSpacer, + rectSpacer, + ) + + rect1 := canvas.NewRectangle(color.Transparent) + rect1.SetMinSize(fyne.NewSize(ui.Width, 1)) + + footer := container.NewCenter( + rect1, + container.NewVBox( + btnCreate, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkReturn, + layout.NewSpacer(), + ), + wSpacer, + ), + ) + + layout := container.NewBorder( + container.NewVBox( + header, + scrollBox, + rectSpacer, + ), + footer, + nil, + nil, + ) + return NewVScroll(layout) +} + +func layoutAssetExplorer() fyne.CanvasObject { + session.Domain = "app.explorer" + + var data []string + var listData binding.StringList + var listBox *widget.List + + frame := &iframe{} + rectLeft := canvas.NewRectangle(color.Transparent) + rectLeft.SetMinSize(fyne.NewSize(ui.Width*0.40, 35)) + rectRight := canvas.NewRectangle(color.Transparent) + rectRight.SetMinSize(fyne.NewSize(ui.Width*0.58, 35)) + rectList := canvas.NewRectangle(color.Transparent) + rectList.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.45)) + rectWidth := canvas.NewRectangle(color.Transparent) + rectWidth.SetMinSize(fyne.NewSize(ui.Width, 10)) + + heading := canvas.NewText("A S S E T E X P L O R E R", colors.Gray) + heading.TextSize = 16 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + results := canvas.NewText("", colors.Green) + results.TextSize = 14 + + listData = binding.BindStringList(&data) + listBox = widget.NewListWithData(listData, + func() fyne.CanvasObject { + return container.NewStack( + container.NewHBox( + container.NewStack( + rectLeft, + widget.NewLabel(""), + ), + container.NewStack( + rectRight, + widget.NewLabel(""), + ), + ), + ) + }, + func(di binding.DataItem, co fyne.CanvasObject) { + dat := di.(binding.String) + str, err := dat.Get() + if err != nil { + return + } + + split := strings.Split(str, ";;;") + + co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[0]) + co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[1]) + //co.(*fyne.Container).Objects[3].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[3]) + }) + + menu := widget.NewSelect([]string{"My Assets", "Search By SCID"}, nil) + menu.PlaceHolder = "(Select One)" + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + entrySCID := widget.NewEntry() + entrySCID.PlaceHolder = "Search by SCID" + entrySCID.Disable() + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + btnSearch := widget.NewButton("Search", nil) + btnSearch.OnTapped = func() { + + } + + linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } + + linkClearHistory := widget.NewHyperlinkWithStyle("Clear All", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: false}) + linkClearHistory.OnTapped = func() { + shard, err := GetShard() + if err != nil { + return + } + + store, err := graviton.NewDiskStore(shard) + if err != nil { + return + } + + ss, err := store.LoadSnapshot(0) + + if err != nil { + return + } + + tree, err := ss.GetTree("Explorer History") + if err != nil { + return + } + + c := tree.Cursor() + + for k, _, err := c.First(); err == nil; k, _, err = c.Next() { + DeleteKey(tree.GetName(), k) + } + + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutAssetExplorer()) + } + + btnMyAssets := widget.NewButton("My Assets", nil) + btnMyAssets.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMyAssets()) + } + + layoutExplorer := container.NewStack( + rectWidth, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + rectSpacer, + container.NewHBox( + results, + layout.NewSpacer(), + linkClearHistory, + ), + rectSpacer, + rectSpacer, + entrySCID, + rectSpacer, + rectSpacer, + container.NewStack( + rectList, + listBox, + ), + rectSpacer, + rectSpacer, + btnMyAssets, + ), + layout.NewSpacer(), + ), + ) + + listing := layoutExplorer + + var assetData []string + + found := 0 + assetData = nil + + results.Text = fmt.Sprintf(" Results: %d", found) + results.Color = colors.Green + results.Refresh() + + listData.Set(nil) + + if session.Offline { + results.Text = " Disabled in offline mode." + results.Color = colors.Gray + results.Refresh() + } else if gnomon.Index == nil { + results.Text = " Gnomon is inactive." + results.Color = colors.Gray + results.Refresh() + } + + entrySCID.OnChanged = func(s string) { + if entrySCID.Text != "" && len(s) == 64 { + showLoadingOverlay() + + var result []*structures.SCIDVariable + switch gnomon.Index.DBType { + case "gravdb": + result = gnomon.Index.GravDBBackend.GetSCIDVariableDetailsAtTopoheight(s, engram.Disk.Get_Daemon_TopoHeight()) + case "boltdb": + result = gnomon.Index.BBSBackend.GetSCIDVariableDetailsAtTopoheight(s, engram.Disk.Get_Daemon_TopoHeight()) + } + + if len(result) == 0 { + _, err := getTxData(s) + if err != nil { + return + } + } + + err := StoreEncryptedValue("Explorer History", []byte(s), []byte("")) + if err != nil { + logger.Errorf("[Asset Explorer] Error saving search result: %s\n", err) + return + } + + scid := crypto.HashHexToHash(s) + + bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(scid, -1, engram.Disk.GetAddress().String()) + if err != nil { + bal = 0 + } + + title, desc, _, _, _ := getContractHeader(scid) + + if title == "" { + title = scid.String() + } + + if len(title) > 18 { + title = title[0:18] + "..." + } + + if desc == "" { + desc = "N/A" + } + + if len(desc) > 40 { + desc = desc[0:40] + "..." + } + + assetData = append(data, globals.FormatMoney(bal)+";;;"+title+";;;"+desc+";;;;;;"+scid.String()) + listData.Set(assetData) + found += 1 + + /* + overlay := session.Window.Canvas().Overlays() + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + overlay.Add( + container.NewStack( + &iframe{}, + layoutAssetManager(s), + ), + ) + overlay.Top().Show() + + entrySCID.Text = "" + entrySCID.Refresh() + + results.Text = fmt.Sprintf(" Results: %d", found) + results.Color = colors.Green + results.Refresh() + */ + + entrySCID.SetText("") + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutAssetManager(s)) + removeOverlays() + } + } + + go func() { + if engram.Disk != nil && gnomon.Index != nil { + for gnomon.Index.LastIndexedHeight < int64(engram.Disk.Get_Daemon_Height()) { + if session.Domain != "app.explorer" { + break + } + entrySCID.Disable() + results.Text = " Gnomon is syncing..." + results.Color = colors.Yellow + + fyne.Do(func() { + results.Refresh() + }) + + time.Sleep(time.Second * 1) + } + + fyne.Do(func() { + entrySCID.Enable() + results.Text = " Loading previous scan history..." + results.Color = colors.Yellow + results.Refresh() + }) + + shard, err := GetShard() + if err != nil { + return + } + + store, err := graviton.NewDiskStore(shard) + if err != nil { + return + } + + ss, err := store.LoadSnapshot(0) + + if err != nil { + return + } + + tree, err := ss.GetTree("Explorer History") + if err != nil { + return + } + + c := tree.Cursor() + + for k, _, err := c.First(); err == nil; k, _, err = c.Next() { + scid := crypto.HashHexToHash(string(k)) + + bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(scid, -1, engram.Disk.GetAddress().String()) + if err != nil { + bal = 0 + } + + title, desc, _, _, _ := getContractHeader(scid) + + if title == "" { + title = scid.String() + } + + if len(title) > 18 { + title = title[0:18] + "..." + } + + if desc == "" { + desc = "N/A" + } + + if len(desc) > 40 { + desc = desc[0:40] + "..." + } + + assetData = append(data, globals.FormatMoney(bal)+";;;"+title+";;;"+desc+";;;;;;"+scid.String()) + listData.Set(assetData) + found += 1 + } + } + + listData.Set(assetData) + + listBox.OnSelected = func(id widget.ListItemID) { + split := strings.Split(assetData[id], ";;;") + /* + overlay := session.Window.Canvas().Overlays() + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + overlay.Add( + container.NewStack( + &iframe{}, + layoutAssetManager(split[4]), + ), + ) + overlay.Top().Show() + listBox.UnselectAll() + */ + + listBox.UnselectAll() + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutAssetManager(split[4])) + } + + fyne.Do(func() { + results.Text = fmt.Sprintf(" Search History: %d", found) + results.Color = colors.Green + results.Refresh() + listBox.Refresh() + }) + }() + + top := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + heading, + ), + rectSpacer, + rectSpacer, + container.NewCenter( + listing, + ), + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewStack( + frame, + container.NewBorder( + top, + bottom, + nil, + nil, + ), + ) + + return NewVScroll(layout) +} + +func layoutMyAssets() fyne.CanvasObject { + var data []string + var listData binding.StringList + var listBox *widget.List + + frame := &iframe{} + rectLeft := canvas.NewRectangle(color.Transparent) + rectLeft.SetMinSize(fyne.NewSize(ui.Width*0.40, 35)) + rectRight := canvas.NewRectangle(color.Transparent) + rectRight.SetMinSize(fyne.NewSize(ui.Width*0.59, 35)) + rectList := canvas.NewRectangle(color.Transparent) + rectList.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.56)) + rectWidth := canvas.NewRectangle(color.Transparent) + rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth, 10)) + + heading := canvas.NewText("M Y A S S E T S", colors.Gray) + heading.TextSize = 16 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + results := canvas.NewText("", colors.Green) + results.TextSize = 13 + + labelLastScan := canvas.NewText("", colors.Green) + labelLastScan.TextSize = 13 + + listData = binding.BindStringList(&data) + listBox = widget.NewListWithData(listData, + func() fyne.CanvasObject { + return container.NewStack( + container.NewHBox( + container.NewStack( + rectLeft, + widget.NewLabel(""), + ), + container.NewStack( + rectRight, + widget.NewLabel(""), + ), + ), + ) + }, + func(di binding.DataItem, co fyne.CanvasObject) { + dat := di.(binding.String) + str, err := dat.Get() + if err != nil { + return + } + + split := strings.Split(str, ";;;") + + co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[0]) + co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[1]) + //co.(*fyne.Container).Objects[3].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[3]) + }) + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + entrySCID := widget.NewEntry() + entrySCID.PlaceHolder = "Search by SCID" + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to Asset Explorer", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutAssetExplorer()) + removeOverlays() + } + + btnRescan := widget.NewButton("Rescan Blockchain", nil) + btnRescan.Disable() + + layoutAssets := container.NewStack( + rectWidth, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + rectSpacer, + container.NewHBox( + results, + layout.NewSpacer(), + labelLastScan, + ), + rectSpacer, + rectSpacer, + container.NewStack( + rectList, + listBox, + ), + rectSpacer, + rectSpacer, + btnRescan, + ), + layout.NewSpacer(), + ), + ) + + listing := layoutAssets + + var assetData []string + assetCount := 0 + assetTotal := 0 + owned := 0 + + owned = 0 + assetData = nil + listData.Set(nil) + + if session.Offline { + results.Text = " Asset tracking is disabled in offline mode." + results.Color = colors.Gray + results.Refresh() + } else if gnomon.Index == nil { + results.Text = " Asset tracking is disabled. Gnomon is inactive." + results.Color = colors.Gray + results.Refresh() + } + + go func() { + if engram.Disk != nil && gnomon.Index != nil { + if gnomon.Index.LastIndexedHeight < int64(engram.Disk.Get_Daemon_Height()) { + fyne.Do(func() { + btnRescan.Disable() + }) + } else { + fyne.Do(func() { + btnRescan.Enable() + }) + } + + results.Text = " Gathering an index of smart contracts... " + results.Color = colors.Yellow + fyne.Do(func() { + results.Refresh() + }) + + for gnomon.Index.LastIndexedHeight < int64(engram.Disk.Get_Daemon_Height()) { + results.Text = fmt.Sprintf(" Gnomon is syncing... [%d / %d]", gnomon.Index.LastIndexedHeight, int64(engram.Disk.Get_Daemon_Height())) + results.Color = colors.Yellow + + fyne.Do(func() { + results.Refresh() + }) + + time.Sleep(time.Second * 1) + } + + results.Text = " Loading previous scan results..." + results.Color = colors.Yellow + + fyne.Do(func() { + results.Refresh() + }) + + var assetList map[string]string + var zerobal uint64 + + shard, err := GetShard() + if err != nil { + return + } + + store, err := graviton.NewDiskStore(shard) + if err != nil { + return + } + + ss, err := store.LoadSnapshot(0) + + if err != nil { + return + } + + tree, err := ss.GetTree("My Assets") + if err != nil { + return + } + + c := tree.Cursor() + + for k, _, err := c.First(); err == nil; k, _, err = c.Next() { + scid := string(k) + + hash := crypto.HashHexToHash(scid) + + bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(hash, -1, engram.Disk.GetAddress().String()) + if err != nil { + return + } else { + title, desc, _, _, _ := getContractHeader(hash) + + if title == "" { + title = scid + } + + if len(title) > 18 { + title = title[0:18] + "..." + } + + if desc == "" { + desc = "N/A" + } + + if len(desc) > 40 { + desc = desc[0:40] + "..." + } + + balance := globals.FormatMoney(bal) + assetData = append(data, balance+";;;"+title+";;;"+desc+";;;;;;"+scid) + listData.Set(assetData) + owned += 1 + } + } + + rescan := func() { + fyne.Do(func() { + btnRescan.Disable() + }) + assetTotal = 0 + assetCount = 0 + + t := time.Now() + timeNow := string(t.Format(time.RFC822)) + StoreEncryptedValue("Asset Scan", []byte("Last Scan"), []byte(timeNow)) + + results.Text = " Indexing..." + results.Color = colors.Yellow + + fyne.Do(func() { + results.Refresh() + }) + + owned = 0 + + assetData = []string{} + listBox.UnselectAll() + listData.Set(assetData) + + if gnomon.Index != nil { + switch gnomon.Index.DBType { + case "gravdb": + assetList = gnomon.Index.GravDBBackend.GetAllOwnersAndSCIDs() + case "boltdb": + assetList = gnomon.Index.BBSBackend.GetAllOwnersAndSCIDs() + } + + for len(assetList) < 5 { + logger.Printf("[Gnomon] Asset Scan Status: [%d / %d / %d]\n", gnomon.Index.LastIndexedHeight, engram.Disk.Get_Daemon_Height(), len(assetList)) + results.Color = colors.Yellow + switch gnomon.Index.DBType { + case "gravdb": + assetList = gnomon.Index.GravDBBackend.GetAllOwnersAndSCIDs() + case "boltdb": + assetList = gnomon.Index.BBSBackend.GetAllOwnersAndSCIDs() + } + time.Sleep(time.Second * 5) + } + } + + results.Text = " Scanning results..." + results.Color = colors.Yellow + + fyne.Do(func() { + results.Refresh() + }) + + if gnomon.Index != nil { + switch gnomon.Index.DBType { + case "gravdb": + assetList = gnomon.Index.GravDBBackend.GetAllOwnersAndSCIDs() + case "boltdb": + assetList = gnomon.Index.BBSBackend.GetAllOwnersAndSCIDs() + } + } + + contracts := []crypto.Hash{} + + for sc := range assetList { + scid := crypto.HashHexToHash(sc) + + if !scid.IsZero() { + assetCount += 1 + contracts = append(contracts, scid) + } + } + + wg := sync.WaitGroup{} + maxWorkers := 50 + lastJob := 0 + + parse: + + if lastJob+maxWorkers > len(contracts) { + maxWorkers = assetCount - lastJob + } + + wg.Add(maxWorkers) + + // Parse each smart contract ID and check for a balance + for i := 0; i < maxWorkers; i++ { + index := lastJob + go func(i int) { + defer wg.Done() + + scid := contracts[index] + + desc := "" + title := "" + + assetTotal += 1 + + results.Text = " Scanning... " + fmt.Sprintf("%d / %d", assetTotal, assetCount) + results.Color = colors.Yellow + + fyne.Do(func() { + results.Refresh() + }) + + bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(scid, -1, engram.Disk.GetAddress().String()) + if err != nil { + return + } else { + balance := globals.FormatMoney(bal) + + if bal != zerobal { + err = StoreEncryptedValue("My Assets", []byte(scid.String()), []byte(balance)) + if err != nil { + logger.Errorf("[History] Failed to store asset: %s\n", err) + } + + title, desc, _, _, _ = getContractHeader(scid) + + if title == "" { + title = scid.String() + } + + if len(title) > 20 { + title = title[0:20] + "..." + } + + if desc == "" { + desc = "N/A" + } + + if len(desc) > 40 { + desc = desc[0:40] + "..." + } + + owned += 1 + assetData = append(assetData, balance+";;;"+title+";;;"+desc+";;;;;;"+scid.String()) + listData.Set(assetData) + logger.Printf("[Assets] Found asset: %s\n", scid.String()) + } + } + }(i) + + lastJob += 1 + } + + wg.Wait() + + if lastJob < len(contracts) { + goto parse + } + + results.Text = fmt.Sprintf(" Owned Assets: %d", owned) + results.Color = colors.Green + + labelLastScan.Text = fmt.Sprintf(" %s", timeNow) + labelLastScan.Color = colors.Green + + fyne.Do(func() { + listData.Set(assetData) + btnRescan.Enable() + + results.Refresh() + labelLastScan.Refresh() + }) + } + + btnRescan.OnTapped = func() { + go rescan() + } + + lastScan, _ := GetEncryptedValue("Asset Scan", []byte("Last Scan")) + + if len(assetData) == 0 && len(lastScan) == 0 { + rescan() + } + + if len(lastScan) > 0 { + results.Text = fmt.Sprintf(" Owned Assets: %d", owned) + labelLastScan.Text = fmt.Sprintf(" %s", lastScan) + } else { + results.Text = fmt.Sprintf(" Owned Assets: %d", owned) + labelLastScan.Text = "" + } + + results.Color = colors.Green + + fyne.Do(func() { + results.Refresh() + labelLastScan.Refresh() + listData.Set(assetData) + }) + + listBox.OnSelected = func(id widget.ListItemID) { + split := strings.Split(assetData[id], ";;;") + + /* + overlay := session.Window.Canvas().Overlays() + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + overlay.Add( + container.NewStack( + &iframe{}, + layoutAssetManager(split[4]), + ), + ) + overlay.Top().Show() + listBox.UnselectAll() + */ + + fyne.Do(func() { + listBox.UnselectAll() + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutAssetManager(split[4])) + }) + } + + fyne.Do(func() { + listBox.Refresh() + btnRescan.Enable() + }) + } + }() + + top := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + heading, + ), + rectSpacer, + rectSpacer, + container.NewCenter( + listing, + ), + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewStack( + frame, + container.NewBorder( + top, + bottom, + nil, + nil, + ), + ) + + return NewVScroll(layout) +} + +func layoutAssetManager(scid string) fyne.CanvasObject { + captureDomain := session.Domain + session.Domain = "app.manager" + + wSpacer := widget.NewLabel(" ") + + frame := &iframe{} + + rectBox := canvas.NewRectangle(color.Transparent) + rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.58)) + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) + + heading := canvas.NewText("Asset Manager", colors.Green) + heading.TextSize = 22 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + labelSigner := canvas.NewText(" SMART CONTRACT AUTHOR", colors.Gray) + labelSigner.TextSize = 14 + labelSigner.Alignment = fyne.TextAlignLeading + labelSigner.TextStyle = fyne.TextStyle{Bold: true} + + labelOwner := canvas.NewText(" SMART CONTRACT OWNER", colors.Gray) + labelOwner.TextSize = 14 + labelOwner.Alignment = fyne.TextAlignLeading + labelOwner.TextStyle = fyne.TextStyle{Bold: true} + + labelSCID := canvas.NewText(" SMART CONTRACT ID", colors.Gray) + labelSCID.TextSize = 14 + labelSCID.Alignment = fyne.TextAlignLeading + labelSCID.TextStyle = fyne.TextStyle{Bold: true} + + labelBalance := canvas.NewText(" ASSET BALANCE", colors.Gray) + labelBalance.TextSize = 14 + labelBalance.Alignment = fyne.TextAlignLeading + labelBalance.TextStyle = fyne.TextStyle{Bold: true} + + labelTransfer := canvas.NewText(" TRANSFER ASSET", colors.Gray) + labelTransfer.TextSize = 14 + labelTransfer.Alignment = fyne.TextAlignLeading + labelTransfer.TextStyle = fyne.TextStyle{Bold: true} + + labelExecute := canvas.NewText(" EXECUTE ACTION", colors.Gray) + labelExecute.TextSize = 14 + labelExecute.Alignment = fyne.TextAlignLeading + labelExecute.TextStyle = fyne.TextStyle{Bold: true} + + var ringsize uint64 + var err error + + options := []string{"Anonymity Set: 2 (None)", "Anonymity Set: 4 (Low)", "Anonymity Set: 8 (Low)", "Anonymity Set: 16 (Recommended)", "Anonymity Set: 32 (Medium)", "Anonymity Set: 64 (High)", "Anonymity Set: 128 (High)"} + + selectRingSize := widget.NewSelect(options, nil) + selectRingSize.OnChanged = func(s string) { + regex := regexp.MustCompile("[0-9]+") + result := regex.FindAllString(selectRingSize.Selected, -1) + ringsize, err = strconv.ParseUint(result[0], 10, 64) + if err != nil { + ringsize = 2 + } + } + + selectRingSize.SetSelectedIndex(3) + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + entryAddress := widget.NewEntry() + entryAddress.PlaceHolder = "Username or Address" + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + sc := widget.NewLabel(scid) + sc.Wrapping = fyne.TextWrap(fyne.TextWrapWord) + + hash := crypto.HashHexToHash(scid) + name, desc, icon, owner, code := getContractHeader(hash) + + image := canvas.NewImageFromResource(resourceBlankPng) + image.SetMinSize(fyne.NewSize(ui.Width*0.3, ui.Width*0.3)) + image.FillMode = canvas.ImageFillContain + + if icon != "" { + if img, err := handleImageURL(name, icon, fyne.NewSize(ui.Width*0.3, ui.Width*0.3)); err == nil { + image = img + } else { + logger.Errorf("[Engram] Could not validate icon image: %s\n", err) + } + } + + if owner == "" { + owner = "--" + } + + signer := "--" + + result, err := getTxData(scid) + if err != nil { + signer = "--" + } else { + signer = result.Txs[0].Signer + } + + labelSeparator := widget.NewRichTextFromMarkdown("") + labelSeparator.Wrapping = fyne.TextWrapOff + labelSeparator.ParseMarkdown("---") + + labelSeparator2 := widget.NewRichTextFromMarkdown("") + labelSeparator2.Wrapping = fyne.TextWrapOff + labelSeparator2.ParseMarkdown("---") + + labelSeparator3 := widget.NewRichTextFromMarkdown("") + labelSeparator3.Wrapping = fyne.TextWrapOff + labelSeparator3.ParseMarkdown("---") + + labelSeparator4 := widget.NewRichTextFromMarkdown("") + labelSeparator4.Wrapping = fyne.TextWrapOff + labelSeparator4.ParseMarkdown("---") + + labelSeparator5 := widget.NewRichTextFromMarkdown("") + labelSeparator5.Wrapping = fyne.TextWrapOff + labelSeparator5.ParseMarkdown("---") + + labelSeparator6 := widget.NewRichTextFromMarkdown("") + labelSeparator6.Wrapping = fyne.TextWrapOff + labelSeparator6.ParseMarkdown("---") + + if name == "" { + name = "--" + } + + labelName := widget.NewRichText(&widget.TextSegment{ + Text: name, + Style: widget.RichTextStyle{ + Alignment: fyne.TextAlignCenter, + ColorName: theme.ColorNameForeground, + SizeName: theme.SizeNameHeadingText, + TextStyle: fyne.TextStyle{Bold: true}, + }}) + labelName.Wrapping = fyne.TextWrapWord + + labelDesc := widget.NewRichText(&widget.TextSegment{ + Text: desc, + Style: widget.RichTextStyle{ + Alignment: fyne.TextAlignCenter, + ColorName: theme.ColorNameForeground, + TextStyle: fyne.TextStyle{Bold: false}, + }}) + labelDesc.Wrapping = fyne.TextWrapWord + + textSigner := widget.NewRichTextFromMarkdown(owner) + textSigner.Wrapping = fyne.TextWrapWord + textSigner.ParseMarkdown(signer) + + textOwner := widget.NewRichTextFromMarkdown(owner) + textOwner.Wrapping = fyne.TextWrapWord + textOwner.ParseMarkdown(owner) + + btnSend := widget.NewButton("Send Asset", nil) + + entryAddress.Validator = func(s string) error { + btnSend.Text = "Send Asset" + btnSend.Refresh() + _, err := globals.ParseValidateAddress(s) + if err != nil { + go func() { + exists, err := checkUsername(s, -1) + if err != nil && exists == "" { + fyne.Do(func() { + btnSend.Disable() + entryAddress.SetValidationError(errors.New("invalid username or address")) + }) + } else { + fyne.Do(func() { + entryAddress.SetValidationError(nil) + btnSend.Enable() + }) + } + }() + } else { + entryAddress.SetValidationError(nil) + btnSend.Enable() + } + return nil + } + + entryAmount := widget.NewEntry() + entryAmount.PlaceHolder = "Asset Amount (Numbers Only)" + entryAmount.Validator = func(s string) error { + if s != "" { + amount, err := globals.ParseAmount(s) + if err != nil { + btnSend.Disable() + entryAmount.SetValidationError(errors.New("invalid amount entered")) + return err + } else { + bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(hash, -1, engram.Disk.GetAddress().String()) + if err != nil { + btnSend.Disable() + entryAmount.SetValidationError(errors.New("error parsing asset balance")) + return err + } else { + if amount > bal || amount == 0 { + err = errors.New("insufficient asset balance") + btnSend.Text = "Insufficient transfer amount..." + btnSend.Disable() + entryAmount.SetValidationError(err) + return err + } + } + } + } + + btnSend.Text = "Send Asset" + btnSend.Enable() + entryAmount.SetValidationError(nil) + + return nil + } + + var zerobal uint64 + + balance := canvas.NewText(fmt.Sprintf(" %d", zerobal), colors.Green) + balance.TextSize = 20 + balance.TextStyle = fyne.TextStyle{Bold: true} + + btnSend.OnTapped = func() { + btnSend.Text = "Setting up transfer..." + btnSend.Disable() + btnSend.Refresh() + entryAddress.Disable() + entryAmount.Disable() + selectRingSize.Disable() + + txid, err := transferAsset(hash, ringsize, entryAddress.Text, entryAmount.Text) + if err != nil { + entryAddress.Text = "" + entryAddress.Refresh() + entryAmount.Text = "" + entryAmount.Refresh() + btnSend.Text = "Transaction Failed..." + btnSend.Disable() + btnSend.Refresh() + } else { + entryAddress.Text = "" + entryAddress.Refresh() + entryAmount.Text = "" + entryAmount.Refresh() + btnSend.Text = "Confirming..." + btnSend.Disable() + btnSend.Refresh() + + go func() { + walletapi.WaitNewHeightBlock() + sHeight := walletapi.Get_Daemon_Height() + + for session.Domain == "app.manager" { + var zeroscid crypto.Hash + _, result := engram.Disk.Get_Payments_TXID(zeroscid, txid.String()) + + if result.TXID != txid.String() { + time.Sleep(time.Second * 1) + } else { + break + } + } + + // If we go DEFAULT_CONFIRMATION_TIMEOUT blocks without exiting 'Confirming...' loop, display failed to transfer and break + if walletapi.Get_Daemon_Height() > sHeight+int64(DEFAULT_CONFIRMATION_TIMEOUT) { + fyne.Do(func() { + entryAddress.Text = "" + entryAddress.Refresh() + entryAmount.Text = "" + entryAmount.Refresh() + btnSend.Text = "Transaction Failed..." + btnSend.Disable() + btnSend.Refresh() + }) + + return + } + + // If daemon height has incremented, print retry counters into button space + if walletapi.Get_Daemon_Height()-sHeight > 0 { + btnSend.Text = fmt.Sprintf("Confirming... (%d/%d)", walletapi.Get_Daemon_Height()-sHeight, DEFAULT_CONFIRMATION_TIMEOUT) + fyne.Do(func() { + btnSend.Refresh() + }) + } + + bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(hash, -1, engram.Disk.GetAddress().String()) + if err == nil { + err = StoreEncryptedValue("My Assets", []byte(hash.String()), []byte(globals.FormatMoney(bal))) + if err != nil { + logger.Errorf("[Asset] Error storing new asset balance for: %s\n", hash) + } + balance.Text = " " + globals.FormatMoney(bal) + + fyne.Do(func() { + balance.Refresh() + }) + } + + if bal != zerobal { + fyne.Do(func() { + btnSend.Text = "Send Asset" + btnSend.Enable() + btnSend.Refresh() + entryAddress.Text = "" + entryAddress.Enable() + entryAddress.Refresh() + entryAmount.Text = "" + entryAmount.Enable() + entryAmount.Refresh() + selectRingSize.Enable() + }) + } else { + fyne.Do(func() { + btnSend.Text = "You do not own this asset" + btnSend.Disable() + btnSend.Refresh() + }) + } + }() + } + } + + bal, _, err := engram.Disk.GetDecryptedBalanceAtTopoHeight(hash, -1, engram.Disk.GetAddress().String()) + if err == nil { + balance.Text = " " + globals.FormatMoney(bal) + balance.Refresh() + + if bal == zerobal { + entryAddress.Disable() + entryAmount.Disable() + selectRingSize.Disable() + btnSend.Text = "You do not own this asset" + btnSend.Disable() + } + } + + if captureDomain == "app.manager" { // was already on manager and opened it again so go back option is to explorer + captureDomain = "app.explorer" + } + + linkBack := widget.NewHyperlinkWithStyle(fmt.Sprintf("Back to %s", sessionDomainToString(captureDomain)), nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + removeOverlays() + capture := session.Window.Content() + session.Window.SetContent(layoutTransition()) + if captureDomain == "app.explorer" { + session.Window.SetContent(layoutAssetExplorer()) + } else { + session.Window.SetContent(session.LastDomain) + session.Domain = captureDomain + } + session.LastDomain = capture + } + + image = canvas.NewImageFromResource(resourceBlankPng) + image.SetMinSize(fyne.NewSize(ui.Width*0.3, ui.Width*0.3)) + image.FillMode = canvas.ImageFillContain + + if icon != "" { + var path fyne.Resource + path, err = fyne.LoadResourceFromURLString(icon) + if err != nil { + image.Resource = resourceBlankPng + } else { + image.Resource = path + } + + image.SetMinSize(fyne.NewSize(ui.Width*0.3, ui.Width*0.3)) + image.FillMode = canvas.ImageFillContain + image.Refresh() + } + + if name == "" { + labelName.ParseMarkdown("## --") + } + + if desc == "" { + labelDesc = widget.NewRichText(&widget.TextSegment{ + Text: "No description provided", + Style: widget.RichTextStyle{ + Alignment: fyne.TextAlignCenter, + ColorName: theme.ColorNameForeground, + TextStyle: fyne.TextStyle{Italic: true}, + }}) + labelDesc.Wrapping = fyne.TextWrapWord + } + + if bal != zerobal { + btnSend.Text = "Send Asset" + btnSend.Enable() + } else { + btnSend.Text = "You do not own this asset" + btnSend.Disable() + } + btnSend.Refresh() + + linkCopySigner := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + linkCopySigner.OnTapped = func() { + a.Clipboard().SetContent(signer) + } + + linkCopyOwner := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + linkCopyOwner.OnTapped = func() { + a.Clipboard().SetContent(owner) + } + + linkMessageAuthor := widget.NewHyperlinkWithStyle("Message the Author", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + linkMessageAuthor.OnTapped = func() { + if signer != "" && signer != "--" { + messages.Contact = signer + session.Window.Canvas().SetContent(layoutTransition()) + removeOverlays() + session.Window.Canvas().SetContent(layoutPM()) + } + } + + linkMessageOwner := widget.NewHyperlinkWithStyle("Message the Owner", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + linkMessageOwner.OnTapped = func() { + if owner != "" && owner != "--" { + messages.Contact = owner + session.Window.Canvas().SetContent(layoutTransition()) + removeOverlays() + session.Window.Canvas().SetContent(layoutPM()) + } + } + + linkCopySCID := widget.NewHyperlinkWithStyle("Copy SCID", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + linkCopySCID.OnTapped = func() { + a.Clipboard().SetContent(scid) + } + + linkView := widget.NewHyperlinkWithStyle("View in Explorer", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + linkView.OnTapped = func() { + if engram.Disk.GetNetwork() { + link, _ := url.Parse("https://explorer.derofoundation.org/tx/" + scid) + _ = fyne.CurrentApp().OpenURL(link) + } else { + link, _ := url.Parse("https://testnetexplorer.derofoundation.org/tx/" + scid) + _ = fyne.CurrentApp().OpenURL(link) + } + } + + // Now let's parse the smart contract code for exported functions + + var contract dvm.SmartContract + var signerFunctions []string + var deroFunctions []string + var assetFunctions []string + + contract, _, err = dvm.ParseSmartContract(code) + if err != nil { + contract = dvm.SmartContract{} + } + + data := []string{} + + for f := range contract.Functions { + r, _ := utf8.DecodeRuneInString(contract.Functions[f].Name) + + if !unicode.IsUpper(r) { + logger.Debugf("[DVM] Function %s is not an exported function - skipping it\n", contract.Functions[f].Name) + } else if contract.Functions[f].Name == "Initialize" || contract.Functions[f].Name == "InitializePrivate" { + logger.Debugf("[DVM] Function %s is an initialization function - skipping it\n", contract.Functions[f].Name) + } else { + data = append(data, contract.Functions[f].Name) + } + + for l := range contract.Functions[f].Lines { + for i := range contract.Functions[f].Lines[l] { + if contract.Functions[f].Lines[l][i] == "SIGNER" && contract.Functions[f].Lines[l][i+1] == "(" { + signerFunctions = append(signerFunctions, contract.Functions[f].Name) + } + + if contract.Functions[f].Lines[l][i] == "DEROVALUE" && contract.Functions[f].Lines[l][i+1] == "(" { + deroFunctions = append(deroFunctions, contract.Functions[f].Name) + } + + if contract.Functions[f].Lines[l][i] == "ASSETVALUE" && contract.Functions[f].Lines[l][i+1] == "(" { + assetFunctions = append(assetFunctions, contract.Functions[f].Name) + } + } + } + } + + sort.Strings(data) + data = append(data, " ") + + var paramList []fyne.Widget + var dero_amount uint64 + var asset_amount uint64 + + functionList := widget.NewSelect(data, nil) + functionList.OnChanged = func(s string) { + if s == " " { + functionList.ClearSelected() + return + } + + var params []dvm.Variable + + overlay := session.Window.Canvas().Overlays() + + options := []string{"Anonymity Set: 2 (None)", "Anonymity Set: 4 (Low)", "Anonymity Set: 8 (Low)", "Anonymity Set: 16 (Recommended)", "Anonymity Set: 32 (Medium)", "Anonymity Set: 64 (High)", "Anonymity Set: 128 (High)"} + + var ringsize uint64 + + signerRequired := false + + selectRingMembers := widget.NewSelect(options, nil) + selectRingMembers.PlaceHolder = "(Select Anonymity Set)" + + for f := range contract.Functions { + if contract.Functions[f].Name == s { + params = contract.Functions[f].Params + + header := canvas.NewText("EXECUTE CONTRACT FUNCTION", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + funcName := canvas.NewText(s, colors.Account) + funcName.TextSize = 22 + funcName.Alignment = fyne.TextAlignCenter + funcName.TextStyle = fyne.TextStyle{Bold: true} + + linkClose := widget.NewHyperlinkWithStyle("Close", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + dero_amount = 0 + asset_amount = 0 + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + entryDEROValue := widget.NewEntry() + entryDEROValue.PlaceHolder = "DERO Amount (Numbers Only)" + entryDEROValue.Validator = func(s string) error { + dero_amount, err = globals.ParseAmount(s) + if err != nil { + entryDEROValue.SetValidationError(err) + return err + } + + return nil + } + + entryAssetValue := widget.NewEntry() + entryAssetValue.PlaceHolder = "Asset Amount (Numbers Only)" + entryAssetValue.Validator = func(s string) error { + asset_amount, err = globals.ParseAmount(s) + if err != nil { + entryAssetValue.SetValidationError(err) + return err + } + + return nil + } + + a := container.NewStack( + span, + entryAssetValue, + ) + + d := container.NewStack( + span, + entryDEROValue, + ) + + paramsContainer := container.NewVBox() + + existsDEROValue := false + existsAssetValue := false + + // Scan code for ASSETVALUE and DEROVALUE + for l := range contract.Functions[f].Lines { + for i := range contract.Functions[f].Lines[l] { + + for v := range paramList { + if paramList[v] == entryDEROValue { + existsDEROValue = true + } else if paramList[v] == entryAssetValue { + existsAssetValue = true + } + } + + if contract.Functions[f].Lines[l][i] == "DEROVALUE" && contract.Functions[f].Lines[l][i+1] == "(" && !existsDEROValue { + paramList = append(paramList, entryDEROValue) + paramsContainer.Add(d) + paramsContainer.Refresh() + existsDEROValue = true + logger.Debugf("[DVM] Added DEROVALUE: %s\n", contract.Functions[f].Lines[l][i]) + } else if len(deroFunctions) > 0 { + for df := range deroFunctions { + if contract.Functions[f].Lines[l][i] == deroFunctions[df] && contract.Functions[f].Lines[l][i+1] == "(" && !existsDEROValue { + paramList = append(paramList, entryDEROValue) + paramsContainer.Add(d) + paramsContainer.Refresh() + existsDEROValue = true + logger.Debugf("[DVM] Added DEROVALUE: %s - Func: %s\n", contract.Functions[f].Lines[l][i], deroFunctions[df]) + } + } + } + + if contract.Functions[f].Lines[l][i] == "ASSETVALUE" && contract.Functions[f].Lines[l][i+1] == "(" && !existsAssetValue { + paramList = append(paramList, entryAssetValue) + paramsContainer.Add(a) + paramsContainer.Refresh() + existsAssetValue = true + logger.Debugf("[DVM] Added ASSETVALUE: %s\n", contract.Functions[f].Lines[l][i]) + } else if len(assetFunctions) > 0 { + for af := range assetFunctions { + if contract.Functions[f].Lines[l][i] == assetFunctions[af] && contract.Functions[f].Lines[l][i+1] == "(" && !existsAssetValue { + paramList = append(paramList, entryAssetValue) + paramsContainer.Add(a) + paramsContainer.Refresh() + existsAssetValue = true + logger.Debugf("[DVM] Added ASSETVALUE: %s\n", contract.Functions[f].Lines[l][i]) + } + } + } + + for si := range signerFunctions { + if contract.Functions[f].Lines[l][i] == "SIGNER" && contract.Functions[f].Lines[l][i+1] == "(" { + signerRequired = true + } else if contract.Functions[f].Lines[l][i] == signerFunctions[si] && contract.Functions[f].Lines[l][i+1] == "(" { + signerRequired = true + } + } + } + } + + selectRingMembers.OnChanged = func(s string) { + if signerRequired { + ringsize = 2 + } else { + regex := regexp.MustCompile("[0-9]+") + result := regex.FindAllString(selectRingMembers.Selected, -1) + ringsize, err = strconv.ParseUint(result[0], 10, 64) + if err != nil { + ringsize = 2 + } + } + } + + if signerRequired { + selectRingMembers.SetSelectedIndex(0) + } else { + selectRingMembers.SetSelectedIndex(3) + } + + btnExecute := widget.NewButton("Execute", nil) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + container.NewCenter( + funcName, + ), + wSpacer, + selectRingMembers, + rectSpacer, + rectSpacer, + paramsContainer, + rectSpacer, + rectSpacer, + btnExecute, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + + for p := range params { + entry := widget.NewEntry() + entry.PlaceHolder = params[p].Name + if params[p].Type == 0x4 { + entry.PlaceHolder = params[p].Name + " (Numbers Only)" + } + entry.Validator = func(s string) error { + for p := range params { + if params[p].Type == 0x5 { + if params[p].Name == entry.PlaceHolder { + logger.Debugf("[%s] String: %s\n", params[p].Name, s) + params[p].ValueString = s + } + } else if params[p].Type == 0x4 { + if params[p].Name+" (Numbers Only)" == entry.PlaceHolder { + amount, err := globals.ParseAmount(s) + if err != nil { + logger.Debugf("[%s] Param error: %s\n", params[p].Name, err) + entry.SetValidationError(err) + return err + } else { + logger.Debugf("[%s] Amount: %d\n", params[p].Name, amount) + params[p].ValueUint64 = amount + } + } + } + } + + return nil + } + + c := container.NewStack( + span, + entry, + ) + + paramList = append(paramList, entry) + paramsContainer.Add(c) + paramsContainer.Refresh() + + } + + btnExecute.OnTapped = func() { + for f := range contract.Functions { + if contract.Functions[f].Name == funcName.Text { + params = contract.Functions[f].Params + } + } + + var err error + + if signerRequired { + ringsize = 2 + } else { + regex := regexp.MustCompile("[0-9]+") + result := regex.FindAllString(selectRingMembers.Selected, -1) + ringsize, err = strconv.ParseUint(result[0], 10, 64) + if err != nil { + ringsize = 2 + selectRingMembers.SetSelected(options[3]) + } + } + + logger.Printf("[Engram] Ringsize: %d\n", ringsize) + + btnExecute.Text = "Executing..." + btnExecute.Disable() + btnExecute.Refresh() + + storage, err := executeContractFunction(hash, ringsize, dero_amount, asset_amount, funcName.Text, params) + if err != nil { + if strings.Contains(err.Error(), "somehow the tx could not be built") { + btnExecute.Text = fmt.Sprintf("Insufficient Balance: Need %v", globals.FormatMoney(storage)) + } else if strings.Contains(err.Error(), "Discarded knowingly") { + btnExecute.Text = "Error... discarded knowingly" + } else if strings.Contains(err.Error(), "Recovered in function") { + btnExecute.Text = "Error... invalid input" + } else { + btnExecute.Text = "Error executing function..." + } + btnExecute.Disable() + btnExecute.Refresh() + } else { + btnExecute.Text = "Function executed successfully!" + btnExecute.Disable() + btnExecute.Refresh() + } + } + + if signerRequired { + selectRingMembers.SetSelectedIndex(0) + selectRingMembers.Disable() + } + + paramsContainer.Refresh() + overlay.Top().Show() + functionList.ClearSelected() + } + } + } + + center := container.NewStack( + rectBox, + container.NewVScroll( + container.NewStack( + rectWidth90, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + /* + container.NewHBox( + image, + rectSpacer, + container.NewVBox( + layout.NewSpacer(), + labelName, + layout.NewSpacer(), + ), + layout.NewSpacer(), + ), + */ + container.NewHBox( + layout.NewSpacer(), + image, + layout.NewSpacer(), + ), + rectSpacer, + + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + labelName, + ), + layout.NewSpacer(), + ), + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + labelDesc, + ), + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator, + rectSpacer, + rectSpacer, + labelSigner, + rectSpacer, + textSigner, + container.NewHBox( + linkMessageAuthor, + layout.NewSpacer(), + ), + container.NewHBox( + linkCopySigner, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator2, + rectSpacer, + rectSpacer, + labelOwner, + rectSpacer, + textOwner, + container.NewHBox( + linkMessageOwner, + layout.NewSpacer(), + ), + container.NewHBox( + linkCopyOwner, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator3, + rectSpacer, + rectSpacer, + labelSCID, + rectSpacer, + container.NewStack( + rectWidth90, + sc, + ), + container.NewHBox( + linkView, + layout.NewSpacer(), + ), + container.NewHBox( + linkCopySCID, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator6, + rectSpacer, + rectSpacer, + labelExecute, + rectSpacer, + rectSpacer, + functionList, + rectSpacer, + rectSpacer, + labelSeparator4, + rectSpacer, + rectSpacer, + labelBalance, + rectSpacer, + balance, + rectSpacer, + rectSpacer, + labelSeparator5, + rectSpacer, + rectSpacer, + labelTransfer, + rectSpacer, + rectSpacer, + rectSpacer, + selectRingSize, + rectSpacer, + entryAddress, + rectSpacer, + entryAmount, + rectSpacer, + btnSend, + wSpacer, + ), + layout.NewSpacer(), + ), + ), + ), + rectSpacer, + rectSpacer, + ) + + top := container.NewVBox( + rectSpacer, + rectSpacer, + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewStack( + frame, + container.NewBorder( + top, + bottom, + nil, + center, + ), + ) + + return NewVScroll(layout) +} + +func layoutTransfers() fyne.CanvasObject { + session.Domain = "app.transfers" + + wSpacer := widget.NewLabel(" ") + + sendTitle := canvas.NewText("T R A N S F E R S", colors.Gray) + sendTitle.TextStyle = fyne.TextStyle{Bold: true} + sendTitle.TextSize = 16 + + sendDesc := canvas.NewText("", colors.Gray) + sendDesc.TextSize = 18 + sendDesc.Alignment = fyne.TextAlignCenter + sendDesc.TextStyle = fyne.TextStyle{Bold: true} + + sendHeading := canvas.NewText("S A V E D T R A N S F E R S", colors.Gray) + sendHeading.TextSize = 16 + sendHeading.Alignment = fyne.TextAlignCenter + sendHeading.TextStyle = fyne.TextStyle{Bold: true} + + rectStatus := canvas.NewRectangle(color.Transparent) + rectStatus.SetMinSize(fyne.NewSize(10, 10)) + rect := canvas.NewRectangle(color.Transparent) + rect.SetMinSize(fyne.NewSize(ui.Width, 20)) + frame := &iframe{} + rect.SetMinSize(fyne.NewSize(ui.Width, 30)) + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + rect.SetMinSize(fyne.NewSize(10, 10)) + rectEmpty := canvas.NewRectangle(color.Transparent) + rectEmpty.SetMinSize(fyne.NewSize(10, 10)) + rectList := canvas.NewRectangle(color.Transparent) + rectList.SetMinSize(fyne.NewSize(ui.Width, 35)) + rectListBox := canvas.NewRectangle(color.Transparent) + rectListBox.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.53)) + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } + + var pendingList []string + + for i := 0; i < len(tx.Pending); i++ { + pendingList = append(pendingList, strconv.Itoa(i)+","+globals.FormatMoney(tx.Pending[i].Amount)+","+tx.Pending[i].Destination) + } + + data := binding.BindStringList(&pendingList) + + scrollBox := widget.NewListWithData(data, + func() fyne.CanvasObject { + c := container.NewStack( + rectList, + container.NewHBox( + canvas.NewText("", colors.Account), + layout.NewSpacer(), + canvas.NewText("", colors.Account), + ), + ) + return c + }, + func(di binding.DataItem, co fyne.CanvasObject) { + dat := di.(binding.String) + str, err := dat.Get() + if err != nil { + return + } + dataItem := strings.SplitN(str, ",", 3) + dest := dataItem[2] + dest = " " + dest[0:4] + " ... " + dest[len(dataItem[2])-10:] + co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[0].(*canvas.Text).Text = dest + co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[0].(*canvas.Text).TextSize = 17 + co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[0].(*canvas.Text).TextStyle.Bold = true + co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[2].(*canvas.Text).Text = dataItem[1] + " " + co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[2].(*canvas.Text).TextSize = 17 + co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[2].(*canvas.Text).TextStyle.Bold = true + }) + + scrollBox.OnSelected = func(id widget.ListItemID) { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutTransfersDetail(id)) + } + + btnSend := widget.NewButton("Send Transfers", nil) + + btnClear := widget.NewButton("Clear", func() { + pendingList = pendingList[:0] + tx = Transfers{} + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutTransfers()) + }) + + if len(pendingList) > 0 { + btnClear.Enable() + btnSend.Enable() + } else { + btnClear.Disable() + btnSend.Disable() + } + + if session.Offline { + btnSend.Text = "Disabled in Offline Mode" + btnSend.Disable() + } + + btnSend.OnTapped = func() { + overlay := session.Window.Canvas().Overlays() + + header := canvas.NewText("ACCOUNT VERIFICATION REQUIRED", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + subHeader := canvas.NewText("Confirm Password", colors.Account) + subHeader.TextSize = 22 + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + + linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + overlay := session.Window.Canvas().Overlays() + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } + + btnSubmit := widget.NewButton("Submit", nil) + + entryPassword := NewReturnEntry() + entryPassword.Password = true + entryPassword.PlaceHolder = "Password" + entryPassword.OnChanged = func(s string) { + if s == "" { + btnSubmit.Text = "Submit" + btnSubmit.Disable() + btnSubmit.Refresh() + } else { + btnSubmit.Text = "Submit" + btnSubmit.Enable() + btnSubmit.Refresh() + } + } + + btnSubmit.OnTapped = func() { + if engram.Disk.Check_Password(entryPassword.Text) { + removeOverlays() + if len(tx.Pending) == 0 { + return + } else { + btnSend.Text = "Setting up transfer..." + btnSend.Disable() + btnSend.Refresh() + txid, err := sendTransfers() + if err != nil { + btnSend.Text = "Send Transfers" + btnSend.Enable() + btnSend.Refresh() + return + } + + go func() { + fyne.Do(func() { + btnClear.Disable() + btnSend.Text = "Confirming..." + btnSend.Refresh() + }) + + walletapi.WaitNewHeightBlock() + sHeight := walletapi.Get_Daemon_Height() + + for session.Domain == "app.transfers" { + var zeroscid crypto.Hash + _, result := engram.Disk.Get_Payments_TXID(zeroscid, txid.String()) + + if result.TXID == txid.String() { + fyne.Do(func() { + btnSend.Text = "Transfer Successful!" + btnSend.Refresh() + }) + + break + } + + // If we go DEFAULT_CONFIRMATION_TIMEOUT blocks without exiting 'Confirming...' loop, display failed to transfer and break + if walletapi.Get_Daemon_Height() > sHeight+int64(DEFAULT_CONFIRMATION_TIMEOUT) { + fyne.Do(func() { + btnSend.Text = "Transfer failed..." + btnSend.Disable() + btnSend.Refresh() + }) + break + } + + // If daemon height has incremented, print retry counters into button space + if walletapi.Get_Daemon_Height()-sHeight > 0 { + fyne.Do(func() { + btnSend.Text = fmt.Sprintf("Confirming... (%d/%d)", walletapi.Get_Daemon_Height()-sHeight, DEFAULT_CONFIRMATION_TIMEOUT) + btnSend.Refresh() + }) + } + + time.Sleep(time.Second * 1) + } + }() + + pendingList = pendingList[:0] + fyne.Do(func() { + data.Reload() + btnSend.Disable() + btnClear.Disable() + }) + } + } else { + fyne.Do(func() { + btnSubmit.Text = "Invalid Password..." + btnSubmit.Disable() + btnSubmit.Refresh() + }) + } + } + + btnSubmit.Disable() + + entryPassword.OnReturn = btnSubmit.OnTapped + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + subHeader, + widget.NewLabel(""), + container.NewCenter( + container.NewStack( + span, + entryPassword, + ), + ), + rectSpacer, + rectSpacer, + btnSubmit, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + + session.Window.Canvas().Focus(entryPassword) + } + + session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { + if session.Domain != "app.transfers" { + return + } + + if k.Name == fyne.KeyDown { + session.Dashboard = "main" + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } + }) + + sendForm := container.NewVBox( + rectSpacer, + rectSpacer, + sendHeading, + rectSpacer, + rectSpacer, + container.NewStack( + rectListBox, + scrollBox, + ), + wSpacer, + btnSend, + rectSpacer, + btnClear, + rectSpacer, + rectSpacer, + ) + + gridItem1 := container.NewCenter( + sendForm, + ) + + gridItem2 := container.NewCenter() + + gridItem3 := container.NewCenter() + + gridItem4 := container.NewCenter() + + gridItem1.Hidden = false + gridItem2.Hidden = true + gridItem3.Hidden = true + gridItem4.Hidden = true + + features := container.NewCenter( + layout.NewSpacer(), + gridItem1, + layout.NewSpacer(), + gridItem2, + layout.NewSpacer(), + gridItem3, + layout.NewSpacer(), + gridItem4, + layout.NewSpacer(), + ) + + bottom := container.NewStack( + container.NewVBox( + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + c := container.NewBorder( + features, + bottom, + nil, + nil, + ) + + layout := container.NewStack( + frame, + c, + ) + + return NewVScroll(layout) +} + +func layoutTransfersDetail(index int) fyne.CanvasObject { + wSpacer := widget.NewLabel(" ") + + rectWidth := canvas.NewRectangle(color.Transparent) + rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, 10)) + + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) + + frame := &iframe{} + + heading := canvas.NewText("T R A N S F E R D E T A I L", colors.Gray) + heading.TextSize = 16 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + labelDestination := canvas.NewText(" RECEIVER ADDRESS", colors.Gray) + labelDestination.TextSize = 14 + labelDestination.Alignment = fyne.TextAlignLeading + labelDestination.TextStyle = fyne.TextStyle{Bold: true} + + labelAmount := canvas.NewText(" AMOUNT", colors.Gray) + labelAmount.TextSize = 14 + labelAmount.Alignment = fyne.TextAlignLeading + labelAmount.TextStyle = fyne.TextStyle{Bold: true} + + labelService := canvas.NewText(" SERVICE ADDRESS", colors.Gray) + labelService.TextSize = 14 + labelService.Alignment = fyne.TextAlignLeading + labelService.TextStyle = fyne.TextStyle{Bold: true} + + labelDestPort := canvas.NewText(" DESTINATION PORT", colors.Gray) + labelDestPort.TextSize = 14 + labelDestPort.TextStyle = fyne.TextStyle{Bold: true} + + labelSourcePort := canvas.NewText(" SOURCE PORT", colors.Gray) + labelSourcePort.TextSize = 14 + labelSourcePort.TextStyle = fyne.TextStyle{Bold: true} + + labelFees := canvas.NewText(" TRANSACTION FEES", colors.Gray) + labelFees.TextSize = 14 + labelFees.TextStyle = fyne.TextStyle{Bold: true} + + labelPayload := canvas.NewText(" PAYLOAD", colors.Gray) + labelPayload.TextSize = 14 + labelPayload.TextStyle = fyne.TextStyle{Bold: true} + + labelReply := canvas.NewText(" REPLY ADDRESS", colors.Gray) + labelReply.TextSize = 14 + labelReply.TextStyle = fyne.TextStyle{Bold: true} + + labelSeparator := widget.NewRichTextFromMarkdown("") + labelSeparator.Wrapping = fyne.TextWrapOff + labelSeparator.ParseMarkdown("---") + + labelSeparator2 := widget.NewRichTextFromMarkdown("") + labelSeparator2.Wrapping = fyne.TextWrapOff + labelSeparator2.ParseMarkdown("---") + + labelSeparator3 := widget.NewRichTextFromMarkdown("") + labelSeparator3.Wrapping = fyne.TextWrapOff + labelSeparator3.ParseMarkdown("---") + + labelSeparator4 := widget.NewRichTextFromMarkdown("") + labelSeparator4.Wrapping = fyne.TextWrapOff + labelSeparator4.ParseMarkdown("---") + + labelSeparator5 := widget.NewRichTextFromMarkdown("") + labelSeparator5.Wrapping = fyne.TextWrapOff + labelSeparator5.ParseMarkdown("---") + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + details := tx.Pending[index] + + valueDestination := widget.NewRichTextFromMarkdown("--") + valueDestination.Wrapping = fyne.TextWrapBreak + + valueType := widget.NewRichTextFromMarkdown("--") + valueType.Wrapping = fyne.TextWrapOff + + if details.Destination != "" { + address, _ := globals.ParseValidateAddress(details.Destination) + if address.IsIntegratedAddress() { + valueDestination.ParseMarkdown(address.BaseAddress().String()) + valueType.ParseMarkdown("### SERVICE") + } else { + valueDestination.ParseMarkdown(details.Destination) + valueType.ParseMarkdown("### NORMAL") + } + } + + valueReply := widget.NewRichTextFromMarkdown("--") + valueReply.Wrapping = fyne.TextWrapBreak + + if details.Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { + if details.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) != "" { + valueReply.ParseMarkdown("" + details.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string)) + } + } + + valuePayload := widget.NewRichTextFromMarkdown("--") + valuePayload.Wrapping = fyne.TextWrapBreak + + if details.Payload_RPC.HasValue(rpc.RPC_COMMENT, rpc.DataString) { + if details.Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string) != "" { + valuePayload.ParseMarkdown("" + details.Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string)) + } + } + + valueAmount := canvas.NewText("", colors.Account) + valueAmount.TextSize = 22 + valueAmount.TextStyle = fyne.TextStyle{Bold: true} + valueAmount.Text = " " + globals.FormatMoney(details.Amount) + + valueDestPort := canvas.NewText("", colors.Account) + valueDestPort.TextSize = 22 + valueDestPort.TextStyle = fyne.TextStyle{Bold: true} + + if details.Payload_RPC.HasValue(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { + port := fmt.Sprintf("%d", details.Payload_RPC.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64)) + valueDestPort.Text = " " + port + } else { + valueDestPort.Text = " 0" + } + + linkBack := widget.NewHyperlinkWithStyle("Back to Transfers", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutTransfers()) + } + + btnDelete := widget.NewButton("Cancel Transfer", nil) + btnDelete.OnTapped = func() { + if len(tx.Pending) > index+1 { + tx.Pending = append(tx.Pending[:index], tx.Pending[index+1:]...) + } else if len(tx.Pending) == 1 { + tx = Transfers{} + } else { + tx.Pending = tx.Pending[:index] + } + + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutTransfers()) + } + + top := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + heading, + ), + rectSpacer, + container.NewCenter( + valueType, + ), + rectSpacer, + rectSpacer, + ) + + center := container.NewStack( + container.NewVScroll( + container.NewStack( + rectWidth, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + rectSpacer, + rectSpacer, + labelDestination, + rectSpacer, + valueDestination, + rectSpacer, + rectSpacer, + labelSeparator, + rectSpacer, + rectSpacer, + labelAmount, + rectSpacer, + container.NewStack( + rectWidth90, + valueAmount, + ), + rectSpacer, + rectSpacer, + labelSeparator2, + rectSpacer, + rectSpacer, + labelReply, + rectSpacer, + valueReply, + rectSpacer, + rectSpacer, + labelSeparator3, + rectSpacer, + rectSpacer, + labelPayload, + rectSpacer, + container.NewStack( + rectWidth90, + valuePayload, + ), + rectSpacer, + rectSpacer, + labelSeparator4, + rectSpacer, + rectSpacer, + labelDestPort, + rectSpacer, + container.NewStack( + rectWidth90, + valueDestPort, + ), + wSpacer, + ), + layout.NewSpacer(), + ), + ), + ), + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + rectWidth, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + btnDelete, + ), + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewStack( + frame, + container.NewBorder( + top, + bottom, + nil, + center, + ), + ) + + return NewVScroll(layout) +} + +func layoutTransition() fyne.CanvasObject { + frame := &iframe{} + resizeWindow(ui.MaxWidth, ui.MaxHeight) + + rect := canvas.NewRectangle(color.Transparent) + rect.SetMinSize(fyne.NewSize(ui.Width*0.45, ui.Width*0.45)) + + if res.loading == nil { + res.loading, _ = x.NewAnimatedGifFromResource(resourceLoadingGif) + res.loading.SetMinSize(fyne.NewSize(ui.Width*0.45, ui.Width*0.45)) + res.loading.Resize(fyne.NewSize(ui.Width*0.45, ui.Width*0.45)) + } + + res.loading.Start() + + layout := container.NewStack( + frame, + container.NewCenter( + rect, + res.loading, + ), + ) + + return NewVScroll(layout) +} + +func layoutSettings() fyne.CanvasObject { + stopGnomon() + rectScroll := canvas.NewRectangle(color.Transparent) + rectScroll.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.65)) + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + + heading := canvas.NewText("Settings", colors.Green) + heading.TextSize = 22 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + labelNetwork := canvas.NewText("NETWORK", colors.Gray) + labelNetwork.TextStyle = fyne.TextStyle{Bold: true} + labelNetwork.TextSize = 14 + + labelNode := canvas.NewText("CONNECTION", colors.Gray) + labelNode.TextStyle = fyne.TextStyle{Bold: true} + labelNode.TextSize = 14 + + labelSecurity := canvas.NewText("SECURITY", colors.Gray) + labelSecurity.TextStyle = fyne.TextStyle{Bold: true} + labelSecurity.TextSize = 14 + + labelGnomon := canvas.NewText("GNOMON", colors.Gray) + labelGnomon.TextStyle = fyne.TextStyle{Bold: true} + labelGnomon.TextSize = 14 + + textGnomon := widget.NewRichTextWithText("Gnomon scans and indexes blockchain data in order to unlock more features, like native asset tracking.") + textGnomon.Wrapping = fyne.TextWrapWord + + textCyberdeck := widget.NewRichTextWithText("A username and password is required in order to allow application connectivity.") + textCyberdeck.Wrapping = fyne.TextWrapWord + + btnRestore := widget.NewButton("Restore Defaults", nil) + btnDelete := widget.NewButton("Clear Local Data", nil) + + type NodeItem struct { + Address string + Status string + } + + mainnetNodes := []NodeItem{ + {Address: "node.derofoundation.org:11012", Status: "unknown"}, + {Address: "community-pools.mysrv.cloud:10102", Status: "unknown"}, + {Address: "127.0.0.1:10102", Status: "unknown"}, + } + testnetNodes := []NodeItem{ + {Address: "testnetexplorer.derofoundation.org:40402", Status: "unknown"}, + {Address: "127.0.0.1:40402", Status: "unknown"}, + } + simulatorNodes := []NodeItem{ + {Address: "127.0.0.1:20000", Status: "unknown"}, + } + + getNodesKey := func(network string) string { + switch network { + case NETWORK_TESTNET: + return "testnet_nodes" + case NETWORK_SIMULATOR: + return "simulator_nodes" + default: + return "mainnet_nodes" + } + } + + getDefaultNodes := func(network string) []NodeItem { + switch network { + case NETWORK_TESTNET: + return testnetNodes + case NETWORK_SIMULATOR: + return simulatorNodes + default: + return mainnetNodes + } + } + + loadNodesForNetwork := func(network string) []NodeItem { + nodesKey := getNodesKey(network) + if data, err := GetValue("settings", []byte(nodesKey)); err == nil && len(data) > 0 { + var savedNodes []NodeItem + if err := json.Unmarshal(data, &savedNodes); err == nil && len(savedNodes) > 0 { + return savedNodes + } + } + return getDefaultNodes(network) + } + + currentNetwork := getNetwork() + nodeData := loadNodesForNetwork(currentNetwork) + + nodeContainer := container.NewVBox() + + var updateNodeContainer func() + + updateNodeContainer = func() { + nodeContainer.Objects = nil + + for i := range nodeData { + i := i // capture loop variable + item := &nodeData[i] + + var iconResource fyne.Resource + switch item.Status { + case "connected": + iconResource = theme.ConfirmIcon() + case "failed": + iconResource = theme.CancelIcon() + } + + rowIcon := widget.NewIcon(iconResource) + + removeBtn := widget.NewButtonWithIcon("", theme.ContentRemoveIcon(), func() { + if len(nodeData) <= 1 { + return + } + + removedAddress := item.Address + wasConnected := item.Status == "connected" || getDaemon() == removedAddress + + nodeData = append(nodeData[:i], nodeData[i+1:]...) + + if wasConnected { + newIndex := i - 1 + if newIndex < 0 { + newIndex = 0 + } + if newIndex >= len(nodeData) { + newIndex = len(nodeData) - 1 + } + nodeData[newIndex].Status = "connected" + setDaemon(nodeData[newIndex].Address) + + for j := range nodeData { + if j != newIndex { + nodeData[j].Status = "unknown" + } + } + } + + if data, err := json.Marshal(nodeData); err == nil { + StoreValue("settings", []byte(getNodesKey(session.Network)), data) + } + + updateNodeContainer() + }) + removeBtn.Importance = widget.MediumImportance + if len(nodeData) <= 1 { + removeBtn.Disable() + } + + addressLabel := widget.NewLabel(item.Address) + addressLabel.Truncation = fyne.TextTruncateEllipsis + + row := container.NewBorder( + nil, nil, nil, + container.NewHBox(rowIcon, removeBtn), + addressLabel, + ) + + tapBtn := widget.NewButton("", func() { + if testNodeConnection(item.Address) { + item.Status = "connected" + setDaemon(item.Address) + + for j := range nodeData { + if j != i { + nodeData[j].Status = "unknown" + } + } + + if data, err := json.Marshal(nodeData); err == nil { + StoreValue("settings", []byte(getNodesKey(session.Network)), data) + } + } else { + item.Status = "failed" + } + updateNodeContainer() + }) + tapBtn.Importance = widget.LowImportance + tapBtn.Alignment = widget.ButtonAlignLeading + tapBtn.Text = "" + + clickableRow := container.NewMax( + tapBtn, + row, + ) + + nodeContainer.Add(clickableRow) + } + nodeContainer.Refresh() + } + + currentDaemon := getDaemon() + for i := range nodeData { + if nodeData[i].Address == currentDaemon { + nodeData[i].Status = "connected" + } + } + updateNodeContainer() + + getNodePlaceholder := func(network string) string { + switch network { + case NETWORK_TESTNET: + return "hostname:40402" + case NETWORK_SIMULATOR: + return "hostname:20000" + default: + return "hostname:10102" + } + } + + entryCustomNode := widget.NewEntry() + entryCustomNode.PlaceHolder = getNodePlaceholder(currentNetwork) + + showNodeError := func(err error) { + entryCustomNode.Validator = func(s string) error { + return err + } + entryCustomNode.SetValidationError(err) + entryCustomNode.FocusGained() + entryCustomNode.FocusLost() + } + + clearNodeError := func() { + entryCustomNode.Validator = nil + entryCustomNode.SetValidationError(nil) + entryCustomNode.Refresh() + } + + btnAddNode := widget.NewButtonWithIcon("", theme.ContentAddIcon(), func() { + nodeAddress := strings.TrimSpace(entryCustomNode.Text) + nodeAddress = strings.ReplaceAll(nodeAddress, " ", "") + if nodeAddress != "" { + // Check for duplicate + for _, node := range nodeData { + if node.Address == nodeAddress { + showNodeError(errors.New("node already exists")) + return + } + } + + if testNodeConnectionTimeout(nodeAddress, 500*time.Millisecond) { + clearNodeError() + + for i := range nodeData { + nodeData[i].Status = "unknown" + } + + nodeData = append(nodeData, NodeItem{ + Address: nodeAddress, + Status: "connected", + }) + setDaemon(nodeAddress) + + if data, err := json.Marshal(nodeData); err == nil { + StoreValue("settings", []byte(getNodesKey(session.Network)), data) + } + + entryCustomNode.Text = "" + entryCustomNode.Refresh() + updateNodeContainer() + } else { + showNodeError(errors.New("node unreachable")) + } + } + }) + btnAddNode.Importance = widget.MediumImportance + btnAddNode.Disable() + + entryCustomNode.OnChanged = func(s string) { + clearNodeError() + s = strings.TrimSpace(s) + if s != "" { + btnAddNode.Enable() + } else { + btnAddNode.Disable() + } + } + + entrySection := container.NewBorder(nil, nil, nil, btnAddNode, entryCustomNode) + entryWrapper := container.NewStack( + canvas.NewRectangle(color.Transparent), + entrySection, + ) + entryWrapper.Resize(fyne.NewSize(ui.Width*0.9, 35)) + + labelScan := widget.NewRichTextFromMarkdown("Enter the number of past blocks that the wallet should scan:") + labelScan.Wrapping = fyne.TextWrapWord + + entryScan := widget.NewEntry() + entryScan.PlaceHolder = "# of Latest Blocks (Optional)" + entryScan.Validator = func(s string) error { + if s == "" { + return nil + } + _, err := strconv.ParseInt(s, 10, 64) + return err + } + entryScan.OnChanged = func(s string) { + if s == "" { + session.TrackRecentBlocks = 0 + return + } + if blocks, err := strconv.ParseInt(s, 10, 64); err == nil && blocks > 0 { + session.TrackRecentBlocks = blocks + } else { + session.TrackRecentBlocks = 0 + } + } + + if session.TrackRecentBlocks > 0 { + blocks := strconv.FormatInt(session.TrackRecentBlocks, 10) + entryScan.Text = blocks + entryScan.Refresh() + } + + radioNetwork := widget.NewRadioGroup([]string{NETWORK_MAINNET, NETWORK_TESTNET, NETWORK_SIMULATOR}, nil) + radioNetwork.Required = true + radioNetwork.Horizontal = true + radioNetwork.OnChanged = func(s string) { + if s != NETWORK_TESTNET && s != NETWORK_SIMULATOR { + s = NETWORK_MAINNET + } + setNetwork(s) + + nodeData = loadNodesForNetwork(s) + + for i := range nodeData { + if nodeData[i].Address == getDaemon() { + nodeData[i].Status = "connected" + } else { + nodeData[i].Status = "unknown" + } + } + + globals.InitNetwork() + + entryCustomNode.PlaceHolder = getNodePlaceholder(s) + clearNodeError() + + updateNodeContainer() + } + + net, _ := GetValue("settings", []byte("network")) + switch string(net) { + case NETWORK_TESTNET, NETWORK_SIMULATOR: + radioNetwork.SetSelected(string(net)) + default: + radioNetwork.SetSelected(NETWORK_MAINNET) + } + + entryUser := widget.NewEntry() + entryUser.PlaceHolder = "Username" + entryUser.SetText(cyberdeck.RPC.user) + + entryPass := widget.NewEntry() + entryPass.PlaceHolder = "Password" + entryPass.Password = true + entryPass.SetText(cyberdeck.RPC.pass) + + entryUser.OnChanged = func(s string) { + cyberdeck.RPC.user = s + StoreValue("settings", []byte("rpc_user"), []byte(s)) + } + + entryPass.OnChanged = func(s string) { + cyberdeck.RPC.pass = s + StoreValue("settings", []byte("rpc_pass"), []byte(s)) + } + + checkGnomon := widget.NewCheck("Enable Gnomon", nil) + checkGnomon.OnChanged = func(b bool) { + if b { + StoreValue("settings", []byte("gnomon"), []byte("1")) + gnomon.Active = 1 + } else { + StoreValue("settings", []byte("gnomon"), []byte("0")) + gnomon.Active = 0 + } + } + + gmn, err := GetValue("settings", []byte("gnomon")) + if err != nil || string(gmn) == "1" { + gnomon.Active = 1 + checkGnomon.Checked = true + if err != nil { + StoreValue("settings", []byte("gnomon"), []byte("1")) + } + } else { + gnomon.Active = 0 + checkGnomon.Checked = false + } + + labelBack := widget.NewHyperlinkWithStyle("Return to Login", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + labelBack.OnTapped = func() { + initSettings() + + resizeWindow(ui.MaxWidth, ui.MaxHeight) + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMain()) + removeOverlays() + } + + btnRestore.OnTapped = func() { + restoreLabel := widget.NewLabel("Reset all settings to defaults?") + restoreLabel.Wrapping = fyne.TextWrapWord + dialog.ShowCustomConfirm("Restore Defaults", "No", "Yes", restoreLabel, func(confirmed bool) { + if confirmed { + return + } + + setNetwork(NETWORK_MAINNET) + setDaemon(DEFAULT_REMOTE_DAEMON) + setAuthMode("true") + setGnomon("1") + + // Clear saved nodes for all networks + StoreValue("settings", []byte("mainnet_nodes"), []byte{}) + StoreValue("settings", []byte("testnet_nodes"), []byte{}) + StoreValue("settings", []byte("simulator_nodes"), []byte{}) + + // Regenerate RPC credentials + cyberdeck.RPC.user = newRPCUsername() + cyberdeck.RPC.pass = newRPCPassword() + StoreValue("settings", []byte("rpc_user"), []byte(cyberdeck.RPC.user)) + StoreValue("settings", []byte("rpc_pass"), []byte(cyberdeck.RPC.pass)) + + resizeWindow(ui.MaxWidth, ui.MaxHeight) + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutSettings()) + removeOverlays() + }, session.Window) + } + + statusText := canvas.NewText("", colors.Account) + statusText.TextSize = 12 + + btnDelete.OnTapped = func() { + clearLabel := widget.NewLabel(fmt.Sprintf("Delete all local %s data?", strings.ToLower(session.Network))) + clearLabel.Wrapping = fyne.TextWrapWord + dialog.ShowCustomConfirm("Clear Local Data", "No", "Yes", clearLabel, func(confirmed bool) { + if confirmed { + return + } + + err := cleanGnomonData() + if err != nil { + if parseError, ok := err.(*os.PathError); !ok { + err = fmt.Errorf("error clearing local %s data", session.Network) + } else { + err = parseError.Err + } + + statusText.Color = colors.Red + statusText.Text = err.Error() + statusText.Refresh() + return + } + + statusText.Color = colors.Green + statusText.Text = fmt.Sprintf("Gnomon %s data successfully deleted.", strings.ToLower(session.Network)) + statusText.Refresh() + }, session.Window) + } + + formSettings := container.NewVBox( + labelNetwork, + rectSpacer, + radioNetwork, + widget.NewLabel(""), + labelNode, + rectSpacer, + rectSpacer, + entryWrapper, + rectSpacer, + nodeContainer, + rectSpacer, + labelScan, + rectSpacer, + entryScan, + widget.NewLabel(""), + labelSecurity, + rectSpacer, + textCyberdeck, + rectSpacer, + entryUser, + rectSpacer, + entryPass, + rectSpacer, + widget.NewLabel(""), + labelGnomon, + rectSpacer, + textGnomon, + rectSpacer, + checkGnomon, + rectSpacer, + statusText, + rectSpacer, + rectSpacer, + btnDelete, + rectSpacer, + btnRestore, + ) + + scrollBox := container.NewVScroll( + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectScroll, + formSettings, + ), + layout.NewSpacer(), + ), + ) + + scrollBox.SetMinSize(fyne.NewSize(ui.MaxWidth, ui.Height*0.68)) + + gridItem1 := container.NewCenter( + container.NewVBox( + widget.NewLabel(""), + heading, + widget.NewLabel(""), + scrollBox, + rectSpacer, + rectSpacer, + ), + ) + + features := container.NewCenter( + layout.NewSpacer(), + gridItem1, + layout.NewSpacer(), + ) + + footer := container.NewVBox( + container.NewHBox( + layout.NewSpacer(), + labelBack, + layout.NewSpacer(), + ), + widget.NewLabel(" "), + ) + + c := container.NewBorder( + features, + footer, + nil, + nil, + ) + + return NewVScroll(c) +} + +func layoutMessages() fyne.CanvasObject { + session.Domain = "app.messages" + + if !walletapi.Connected { + session.Window.SetContent(layoutSettings()) + } + + title := canvas.NewText("M Y C O N T A C T S", colors.Gray) + title.TextStyle = fyne.TextStyle{Bold: true} + title.TextSize = 16 + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + checkLimit := widget.NewCheck(" Show only recent messages", nil) + checkLimit.OnChanged = func(b bool) { + if b { + session.LimitMessages = true + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMessages()) + removeOverlays() + } else { + session.LimitMessages = false + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMessages()) + removeOverlays() + } + } + + if session.LimitMessages { + checkLimit.Checked = true + } + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } + + rectStatus := canvas.NewRectangle(color.Transparent) + rectStatus.SetMinSize(fyne.NewSize(10, 10)) + rectEmpty := canvas.NewRectangle(color.Transparent) + rectEmpty.SetMinSize(fyne.NewSize(10, 10)) + rect := canvas.NewRectangle(color.Transparent) + rect.SetMinSize(fyne.NewSize(ui.Width, 20)) + frame := &iframe{} + rect.SetMinSize(fyne.NewSize(ui.Width, 30)) + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + rect.SetMinSize(fyne.NewSize(10, 10)) + rectList := canvas.NewRectangle(color.Transparent) + rectList.SetMinSize(fyne.NewSize(ui.Width, 35)) + rectListBox := canvas.NewRectangle(color.Transparent) + rectListBox.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.43)) + + messages.Data = nil + + var height uint64 + + if session.LimitMessages { + height = engram.Disk.Get_Height() - 1000000 + } else { + height = 0 + } + + data := getMessages(height) + temp := data + + list := binding.BindStringList(&data) + + msgbox.List = widget.NewListWithData(list, + func() fyne.CanvasObject { + c := container.NewVBox( + widget.NewLabel(""), + ) + return c + }, + func(di binding.DataItem, co fyne.CanvasObject) { + dat := di.(binding.String) + str, err := dat.Get() + if err != nil { + return + } + dataItem := strings.Split(str, "~~~") + short := dataItem[0] + address := short[len(short)-DEFAULT_USERADDR_SHORTEN_LENGTH:] + username := dataItem[1] + // If a username is longer than what *would* be a 'short' address of ...xyzxyzxyzx (e.g. 13), then shorten as well to be similar sizing + if len(username) > DEFAULT_USERADDR_SHORTEN_LENGTH+3 { + username = "..." + username[len(username)-DEFAULT_USERADDR_SHORTEN_LENGTH:] + } + + if username == "" { + co.(*fyne.Container).Objects[0].(*widget.Label).SetText("..." + address) + } else { + co.(*fyne.Container).Objects[0].(*widget.Label).SetText(username) + } + co.(*fyne.Container).Objects[0].(*widget.Label).Wrapping = fyne.TextWrapWord + co.(*fyne.Container).Objects[0].(*widget.Label).TextStyle.Bold = false + co.(*fyne.Container).Objects[0].(*widget.Label).Alignment = fyne.TextAlignLeading + }) + + msgbox.List.OnSelected = func(id widget.ListItemID) { + msgbox.List.UnselectAll() + split := strings.Split(data[id], "~~~") + if split[1] == "" { + messages.Contact = split[0] + } else { + messages.Contact = split[1] + } + + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutPM()) + removeOverlays() + } + + searchList := []string{} + + entrySearch := widget.NewEntry() + entrySearch.PlaceHolder = "Search for a Contact" + entrySearch.OnChanged = func(s string) { + s = strings.ToLower(s) + searchList = []string{} + if s == "" { + data = temp + list.Reload() + } else { + for _, d := range temp { + tempd := strings.ToLower(d) + split := strings.Split(tempd, "~~~") + + if split[1] == "" { + if strings.Contains(split[0], s) { + searchList = append(searchList, d) + } + } else { + if strings.Contains(split[1], s) { + searchList = append(searchList, d) + } + } + } + + data = searchList + list.Reload() + } + } + + btnSend := widget.NewButton("New Message", func() { + _, err := globals.ParseValidateAddress(messages.Contact) + if err != nil { + //_, err := engram.Disk.NameToAddress(messages.Contact) + _, err := checkUsername(messages.Contact, -1) + if err != nil { + return + } + } + + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutPM()) + removeOverlays() + }) + btnSend.Disable() + + entryDest := widget.NewEntry() + entryDest.MultiLine = false + entryDest.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) + entryDest.PlaceHolder = "Username or Address" + entryDest.Validator = func(s string) error { + if len(s) > 0 { + _, err := globals.ParseValidateAddress(s) + if err != nil { + btnSend.Disable() + //_, err := engram.Disk.NameToAddress(s) + _, err = checkUsername(s, -1) + if err != nil { + btnSend.Disable() + return errors.New("invalid username or address") + } else { + messages.Contact = s + btnSend.Enable() + return nil + } + } else { + btnSend.Enable() + messages.Contact = s + return nil + } + } + + return errors.New("invalid username or address") + } + + messageForm := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + title, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + entrySearch, + rectSpacer, + rectSpacer, + container.NewStack( + rectListBox, + msgbox.List, + ), + rectSpacer, + entryDest, + rectSpacer, + btnSend, + rectSpacer, + checkLimit, + ) + + gridItem1 := container.NewCenter( + messageForm, + ) + + gridItem2 := container.NewCenter() + + gridItem3 := container.NewCenter() + + gridItem4 := container.NewCenter() + + gridItem1.Hidden = false + gridItem2.Hidden = true + gridItem3.Hidden = true + gridItem4.Hidden = true + + features := container.NewCenter( + layout.NewSpacer(), + gridItem1, + layout.NewSpacer(), + gridItem2, + layout.NewSpacer(), + gridItem3, + layout.NewSpacer(), + gridItem4, + layout.NewSpacer(), + ) + + session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { + if session.Domain != "app.messages" { + return + } + + if k.Name == fyne.KeyUp { + session.Dashboard = "main" + + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } else if k.Name == fyne.KeyF5 { + session.Window.SetContent(layoutMessages()) + removeOverlays() + } + }) + + subContainer := container.NewStack( + container.NewVBox( + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + c := container.NewBorder( + features, + subContainer, + nil, + nil, + ) + + layout := container.NewStack( + frame, + c, + ) + + return NewVScroll(layout) +} + +func layoutPM() fyne.CanvasObject { + session.Domain = "app.messages.contact" + + if !walletapi.Connected { + session.Window.SetContent(layoutSettings()) + } + + getPrimaryUsername() + + contactAddress := "" + + // So message contact sizes are not overblown from UI + _, err := globals.ParseValidateAddress(messages.Contact) + if err != nil { + //_, err := engram.Disk.NameToAddress(messages.Contact) + _, err := checkUsername(messages.Contact, -1) + if err == nil { + contactAddress = messages.Contact + } + } /* else { + short := messages.Contact[len(messages.Contact)-10:] + contactAddress = "..." + short + }*/ + + // Safety, even though valid addresses are sized enough but usernames may not be + if len(messages.Contact) > DEFAULT_USERADDR_SHORTEN_LENGTH+3 { + short := messages.Contact[len(messages.Contact)-DEFAULT_USERADDR_SHORTEN_LENGTH:] + contactAddress = "..." + short + } + + title := canvas.NewText("M E S S A G E S", colors.Gray) + title.TextStyle = fyne.TextStyle{Bold: true} + title.TextSize = 16 + + heading := canvas.NewText(contactAddress, colors.Green) + heading.TextSize = 22 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + lastActive := canvas.NewText("", colors.Gray) + lastActive.TextSize = 12 + lastActive.Alignment = fyne.TextAlignCenter + lastActive.TextStyle = fyne.TextStyle{Bold: false} + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to Messages", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMessages()) + removeOverlays() + } + + rectStatus := canvas.NewRectangle(color.Transparent) + rectStatus.SetMinSize(fyne.NewSize(10, 10)) + rectEmpty := canvas.NewRectangle(color.Transparent) + rectEmpty.SetMinSize(fyne.NewSize(10, 10)) + rect := canvas.NewRectangle(color.Transparent) + rect.SetMinSize(fyne.NewSize(ui.Width*0.7, 30)) + frame := &iframe{} + subframe := canvas.NewRectangle(color.Transparent) + subframe.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.51)) + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + rect.SetMinSize(fyne.NewSize(10, 10)) + rectList := canvas.NewRectangle(color.Transparent) + rectList.SetMinSize(fyne.NewSize(ui.Width, 35)) + rectListBox := canvas.NewRectangle(color.Transparent) + rectListBox.SetMinSize(fyne.NewSize(ui.Width*0.42, 30)) + rectOutbound := canvas.NewRectangle(color.Transparent) + rectOutbound.SetMinSize(fyne.NewSize(ui.Width*0.166, 30)) + + messages.Data = nil + + chats := container.NewVBox() + + chatFrame := container.NewStack( + rectListBox, + container.NewStack( + chats, + ), + ) + + chatbox := container.NewVScroll( + container.NewStack( + chatFrame, + ), + ) + + var e *fyne.Container + var height uint64 + + if session.LimitMessages { + height = engram.Disk.Get_Height() - 1000000 + } else { + height = 0 + } + + data := getMessagesFromUser(messages.Contact, height) + + for d := range data { + if data[d].Incoming { + if data[d].Payload_RPC.Has(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { + if data[d].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) == "" { + + } else { + t := data[d].Time + time := string(t.Format(time.RFC822)) + comment := data[d].Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string) + links := getTextURL(comment) + + for i := range links { + if comment == links[i] { + if len(links[i]) > 25 { + comment = `[ ` + links[i][0:25] + "..." + ` ](` + links[i] + `)` + } else { + comment = `[ ` + links[i] + ` ](` + links[i] + `)` + } + } else { + linkText := "" + split := strings.Split(comment, links[i]) + if len(links[i]) > 25 { + linkText = links[i][0:25] + "..." + } else { + linkText = links[i] + } + comment = `` + split[0] + `[link]` + split[1] + "\n\n›" + `[ ` + linkText + ` ](` + links[i] + `)` + } + } + messages.Data = append(messages.Data, data[d].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string)+";;;;"+comment+";;;;"+time) + } + } + } else { + t := data[d].Time + time := string(t.Format(time.RFC822)) + comment := data[d].Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string) + links := getTextURL(comment) + + for i := range links { + if comment == links[i] { + if len(links[i]) > 25 { + comment = `[ ` + links[i][0:25] + "..." + ` ](` + links[i] + `)` + } else { + comment = `[ ` + links[i] + ` ](` + links[i] + `)` + } + } else { + linkText := "" + split := strings.Split(comment, links[i]) + if len(links[i]) > 25 { + linkText = links[i][0:25] + "..." + } else { + linkText = links[i] + } + comment = `` + split[0] + `[link]` + split[1] + "\n\n›" + `[ ` + linkText + ` ](` + links[i] + `)` + } + } + messages.Data = append(messages.Data, engram.Disk.GetAddress().String()+";;;;"+comment+";;;;"+time) + } + } + + if len(data) > 0 { + for m := range messages.Data { + var sender string + split := strings.Split(messages.Data[m], ";;;;") + mdata := widget.NewRichTextFromMarkdown("") + mdata.Wrapping = fyne.TextWrapWord + datetime := canvas.NewText("", colors.Green) + datetime.TextSize = 11 + boxColor := colors.Flint + rect := canvas.NewRectangle(boxColor) + rect.SetMinSize(fyne.NewSize(ui.Width*0.80, 30)) + rect.CornerRadius = 5.0 + rect5 := canvas.NewRectangle(color.Transparent) + rect5.SetMinSize(fyne.NewSize(5, 5)) + + //uname, err := engram.Disk.NameToAddress(split[0]) + uname, err := checkUsername(split[0], -1) + if err != nil { + sender = split[0] + } else { + sender = uname + } + + if sender == engram.Disk.GetAddress().String() { + rect.FillColor = colors.DarkGreen + mdata.ParseMarkdown(split[1]) + datetime.Text = split[2] + e = container.NewBorder( + nil, + container.NewVBox( + container.NewHBox( + layout.NewSpacer(), + datetime, + rect5, + ), + rect5, + ), + rectOutbound, + container.NewStack( + rect, + container.NewVBox( + mdata, + ), + ), + ) + } else { + rect.FillColor = colors.Flint + mdata.ParseMarkdown(split[1]) + datetime.Text = split[2] + e = container.NewBorder( + nil, + container.NewVBox( + container.NewHBox( + rect5, + datetime, + layout.NewSpacer(), + ), + rect5, + ), + container.NewStack( + rect, + container.NewVBox( + mdata, + ), + ), + rectOutbound, + ) + } + + lastActive.Text = "Last Updated: " + time.Now().Format(time.RFC822) + lastActive.Refresh() + + chats.Add(e) + chats.Refresh() + chatbox.Refresh() + chatbox.ScrollToBottom() + } + } + + btnSend := widget.NewButton("Send", nil) + btnSend.Disable() + + entry := widget.NewEntry() + entry.MultiLine = false + entry.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) + entry.PlaceHolder = "Message" + entry.OnChanged = func(s string) { + messages.Message = s + contact := messages.Contact + //check, err := engram.Disk.NameToAddress(messages.Contact) + check, err := checkUsername(messages.Contact, -1) + if err == nil { + contact = check + } + + _, err = globals.ParseValidateAddress(contact) + if err != nil { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMessages()) + removeOverlays() + return + } + + err = checkMessagePack(messages.Message, session.Username, contact) + if err != nil { + btnSend.Text = "Message too long..." + btnSend.Disable() + btnSend.Refresh() + return + } else { + if messages.Message == "" { + btnSend.Text = "Send" + btnSend.Disable() + btnSend.Refresh() + } else { + btnSend.Text = "Send" + btnSend.Enable() + btnSend.Refresh() + } + } + } + + btnSend.OnTapped = func() { + if messages.Message == "" { + return + } + contact := "" + _, err := globals.ParseValidateAddress(messages.Contact) + if err != nil { + //check, err := engram.Disk.NameToAddress(messages.Contact) + check, err := checkUsername(messages.Contact, -1) + if err != nil { + logger.Errorf("[Message] Failed to send: %s\n", err) + btnSend.Text = "Failed to verify address..." + btnSend.Disable() + btnSend.Refresh() + return + } + contact = check + } else { + contact = messages.Contact + } + + btnSend.Text = "Setting up transfer..." + btnSend.Disable() + btnSend.Refresh() + + txid, err := sendMessage(messages.Message, session.Username, contact) + if err != nil { + logger.Errorf("[Message] Failed to send: %s\n", err) + btnSend.Text = "Failed to send message..." + btnSend.Disable() + btnSend.Refresh() + return + } + + logger.Printf("[Message] Dispatched transaction successfully to: %s\n", messages.Contact) + btnSend.Text = "Confirming..." + btnSend.Disable() + btnSend.Refresh() + messages.Message = "" + entry.Text = "" + entry.Refresh() + + go func() { + walletapi.WaitNewHeightBlock() + sHeight := walletapi.Get_Daemon_Height() + var success bool + for session.Domain == "app.messages.contact" { + var zeroscid crypto.Hash + _, result := engram.Disk.Get_Payments_TXID(zeroscid, txid.String()) + + if result.TXID != txid.String() { + time.Sleep(time.Second * 1) + } else { + success = true + } + + // If we go DEFAULT_CONFIRMATION_TIMEOUT blocks without exiting 'Confirming...' loop, display failed to transfer and break + if walletapi.Get_Daemon_Height() > sHeight+int64(DEFAULT_CONFIRMATION_TIMEOUT) { + fyne.Do(func() { + btnSend.Text = "Failed to send message..." + btnSend.Disable() + btnSend.Refresh() + }) + + break + } + + // If daemon height has incremented, print retry counters into button space + if walletapi.Get_Daemon_Height()-sHeight > 0 { + fyne.Do(func() { + btnSend.Text = fmt.Sprintf("Confirming... (%d/%d)", walletapi.Get_Daemon_Height()-sHeight, DEFAULT_CONFIRMATION_TIMEOUT) + btnSend.Refresh() + }) + } + + // If success, reload page w/ latest content. Otherwise retain the Failure message for UX relay + if success { + fyne.Do(func() { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutPM()) + }) + + break + } else { + time.Sleep(time.Second * 1) + } + } + }() + } + + messageForm := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + heading, + layout.NewSpacer(), + ), + rectSpacer, + lastActive, + rectSpacer, + rectSpacer, + container.NewStack( + subframe, + chatbox, + ), + rectSpacer, + rectSpacer, + entry, + rectSpacer, + btnSend, + rectSpacer, + rectSpacer, + ) + + gridItem1 := container.NewCenter( + messageForm, + ) + + gridItem2 := container.NewCenter() + + gridItem3 := container.NewCenter() + + gridItem4 := container.NewCenter() + + gridItem1.Hidden = false + gridItem2.Hidden = true + gridItem3.Hidden = true + gridItem4.Hidden = true + + features := container.NewCenter( + layout.NewSpacer(), + gridItem1, + layout.NewSpacer(), + gridItem2, + layout.NewSpacer(), + gridItem3, + layout.NewSpacer(), + gridItem4, + layout.NewSpacer(), + ) + + session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { + if session.Domain != "app.messages.contact" { + return + } + + if k.Name == fyne.KeyUp { + session.Dashboard = "app.messages" + messages.Contact = "" + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMessages()) + removeOverlays() + } else if k.Name == fyne.KeyEscape { + session.Dashboard = "app.messages" + messages.Contact = "" + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMessages()) + removeOverlays() + } else if k.Name == fyne.KeyF5 { + session.Window.SetContent(layoutPM()) + } + }) + + subContainer := container.NewStack( + container.NewVBox( + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + c := container.NewBorder( + features, + subContainer, + nil, + nil, + ) + + layout := container.NewStack( + frame, + c, + ) + + return NewVScroll(layout) +} + +func layoutCyberdeck() fyne.CanvasObject { + session.Domain = "app.cyberdeck" + + go refreshXSWDList() + + wSpacer := widget.NewLabel(" ") + + title := canvas.NewText("C Y B E R D E C K", colors.Gray) + title.TextStyle = fyne.TextStyle{Bold: true} + title.TextSize = 16 + + rect := canvas.NewRectangle(color.Transparent) + rect.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.20)) + + frame := &iframe{} + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 0)) + + rpcLabel := canvas.NewText(" C O N F I G U R A T I O N ", colors.Gray) + rpcLabel.TextSize = 11 + rpcLabel.Alignment = fyne.TextAlignCenter + rpcLabel.TextStyle = fyne.TextStyle{Bold: true} + + wsLabel := canvas.NewText(" C O N F I G U R A T I O N ", colors.Gray) + wsLabel.TextSize = 11 + wsLabel.Alignment = fyne.TextAlignCenter + wsLabel.TextStyle = fyne.TextStyle{Bold: true} + + labelConnections := canvas.NewText(" C O N N E C T I O N S ", colors.Gray) + labelConnections.TextSize = 11 + labelConnections.Alignment = fyne.TextAlignCenter + labelConnections.TextStyle = fyne.TextStyle{Bold: true} + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep1 := canvas.NewRectangle(colors.Gray) + sep1.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep1, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } + + shortShard := canvas.NewText("APPLICATION CONNECTIONS", colors.Gray) + shortShard.TextStyle = fyne.TextStyle{Bold: true} + shortShard.TextSize = 12 + + linkColor := colors.Green + + if cyberdeck.RPC.server == nil { + session.Link = "Blocked" + linkColor = colors.Gray + } + + cyberdeck.RPC.status = canvas.NewText(session.Link, linkColor) + cyberdeck.RPC.status.TextSize = 22 + cyberdeck.RPC.status.TextStyle = fyne.TextStyle{Bold: true} + + serverStatus := canvas.NewText("APPLICATION CONNECTIONS", colors.Gray) + serverStatus.TextSize = 12 + serverStatus.Alignment = fyne.TextAlignCenter + serverStatus.TextStyle = fyne.TextStyle{Bold: true} + + linkCenter := container.NewCenter( + cyberdeck.RPC.status, + ) + + cyberdeck.RPC.userText = widget.NewEntry() + cyberdeck.RPC.userText.PlaceHolder = "Username" + cyberdeck.RPC.userText.OnChanged = func(s string) { + if len(s) > 1 { + cyberdeck.RPC.user = s + } + } + + cyberdeck.RPC.passText = widget.NewEntry() + cyberdeck.RPC.passText.Password = true + cyberdeck.RPC.passText.PlaceHolder = "Password" + cyberdeck.RPC.passText.OnChanged = func(s string) { + if len(s) > 1 { + cyberdeck.RPC.pass = s + } + } + + cyberdeck.RPC.portText = widget.NewEntry() + cyberdeck.RPC.portText.PlaceHolder = "0.0.0.0:10103" + cyberdeck.RPC.portText.Validator = func(s string) (err error) { + regex := `^(?:[a-zA-Z0-9]{1,62}(?:[-\.][a-zA-Z0-9]{1,62})+)(:\d+)?$` + test := regexp.MustCompile(regex) + if test.MatchString(s) { + cyberdeck.RPC.portText.SetValidationError(nil) + } else { + err = errors.New("invalid host name") + cyberdeck.RPC.portText.SetValidationError(err) + } + + return + } + cyberdeck.RPC.portText.SetText(getCyberdeck("RPC")) + + linkColor = colors.Green + + if cyberdeck.WS.server == nil { + session.Link = "Blocked" + linkColor = colors.Gray + } + + cyberdeck.WS.status = canvas.NewText(session.Link, linkColor) + cyberdeck.WS.status.TextSize = 22 + cyberdeck.WS.status.TextStyle = fyne.TextStyle{Bold: true} + + deckChoice := widget.NewSelect([]string{"Web Sockets (WS)", "Remote Procedure Calls (RPC)"}, nil) + + cyberdeck.RPC.toggle = widget.NewButton("Turn On", nil) + cyberdeck.RPC.toggle.OnTapped = func() { + switch session.Network { + case NETWORK_TESTNET: + if cyberdeck.RPC.portText.Validate() != nil { + cyberdeck.RPC.port = fmt.Sprintf("127.0.0.1:%d", DEFAULT_TESTNET_WALLET_PORT) + cyberdeck.RPC.portText.SetText(cyberdeck.RPC.port) + } else { + cyberdeck.RPC.port = cyberdeck.RPC.portText.Text + } + case NETWORK_SIMULATOR: + if cyberdeck.RPC.portText.Validate() != nil { + cyberdeck.RPC.port = fmt.Sprintf("127.0.0.1:%d", DEFAULT_SIMULATOR_WALLET_PORT) + cyberdeck.RPC.portText.SetText(cyberdeck.RPC.port) + } else { + cyberdeck.RPC.port = cyberdeck.RPC.portText.Text + } + default: + if cyberdeck.RPC.portText.Validate() != nil { + cyberdeck.RPC.port = fmt.Sprintf("127.0.0.1:%d", DEFAULT_WALLET_PORT) + cyberdeck.RPC.portText.SetText(cyberdeck.RPC.port) + } else { + cyberdeck.RPC.port = cyberdeck.RPC.portText.Text + } + } + + toggleRPCServer(cyberdeck.RPC.port) + if cyberdeck.RPC.server != nil { + setCyberdeck(cyberdeck.RPC.port, "RPC") + deckChoice.Disable() + cyberdeck.RPC.portText.Disable() + } else { + deckChoice.Enable() + cyberdeck.RPC.portText.Enable() + } + } + + if cyberdeck.WS.portText == nil { + cyberdeck.WS.portText = widget.NewEntry() + cyberdeck.WS.portText.PlaceHolder = "0.0.0.0:44326" + cyberdeck.WS.portText.Validator = func(s string) (err error) { + regex := `^(?:[a-zA-Z0-9]{1,62}(?:[-\.][a-zA-Z0-9]{1,62})+)(:\d+)?$` + test := regexp.MustCompile(regex) + if test.MatchString(s) { + cyberdeck.WS.portText.SetValidationError(nil) + } else { + err = errors.New("invalid host name") + cyberdeck.WS.portText.SetValidationError(err) + } + + return + } + } + + cyberdeck.WS.toggle = widget.NewButton("Turn On", nil) + cyberdeck.WS.toggle.OnTapped = func() { + if cyberdeck.WS.portText.Validate() != nil { + cyberdeck.WS.port = fmt.Sprintf("127.0.0.1:%d", xswd.XSWD_PORT) + cyberdeck.WS.portText.SetText(cyberdeck.WS.port) + } else { + _, err := net.ResolveTCPAddr("tcp", cyberdeck.WS.port) + if err != nil { + logger.Errorf("[Cyberdeck] XSWD port: %s\n", err) + cyberdeck.WS.port = fmt.Sprintf("127.0.0.1:%d", xswd.XSWD_PORT) + cyberdeck.WS.portText.SetText(cyberdeck.WS.port) + } else { + cyberdeck.WS.port = cyberdeck.WS.portText.Text + } + } + + cyberdeck.EPOCH.err = nil + toggleXSWD(cyberdeck.WS.port) + if cyberdeck.WS.server != nil { + setCyberdeck(cyberdeck.WS.port, "WS") + cyberdeck.WS.portText.Disable() + deckChoice.Disable() + if cyberdeck.EPOCH.enabled { + /* + if cyberdeck.EPOCH.allowWithAddress { + // If address is defined by dApp, GetWork will be started and stopped upon each WS call + logger.Printf("[EPOCH] dApp addresses are enabled\n") + return + } + */ + + err := epoch.StartGetWork(engram.Disk.GetAddress().String(), session.Daemon) + if err != nil { + logger.Errorf("[EPOCH] Connecting: %s\n", err) + cyberdeck.EPOCH.err = err + } else { + cyberdeck.EPOCH.err = nil + setCyberdeck(epoch.GetPort(), "EPOCH") + } + } + } else { + stopEPOCH() + cyberdeck.WS.portText.Enable() + deckChoice.Enable() + } + } + + if session.Offline { + cyberdeck.RPC.toggle.Text = "Disabled in Offline Mode" + cyberdeck.RPC.toggle.Disable() + cyberdeck.RPC.portText.Disable() + cyberdeck.WS.toggle.Text = "Disabled in Offline Mode" + cyberdeck.WS.toggle.Disable() + cyberdeck.WS.portText.Disable() + } else { + if cyberdeck.RPC.server != nil { + cyberdeck.RPC.status.Text = "Allowed" + cyberdeck.RPC.status.Color = colors.Green + cyberdeck.RPC.toggle.Text = "Turn Off" + cyberdeck.RPC.userText.Disable() + cyberdeck.RPC.passText.Disable() + cyberdeck.RPC.portText.Disable() + deckChoice.Disable() + } else { + cyberdeck.RPC.status.Text = "Blocked" + cyberdeck.RPC.status.Color = colors.Gray + cyberdeck.RPC.toggle.Text = "Turn On" + cyberdeck.RPC.userText.Enable() + cyberdeck.RPC.passText.Enable() + cyberdeck.RPC.portText.Enable() + } + + if cyberdeck.WS.server != nil { + cyberdeck.WS.status.Text = "Allowed" + cyberdeck.WS.status.Color = colors.Green + cyberdeck.WS.toggle.Text = "Turn Off" + cyberdeck.WS.portText.Disable() + deckChoice.Disable() + } else { + cyberdeck.WS.status.Text = "Blocked" + cyberdeck.WS.status.Color = colors.Gray + cyberdeck.WS.toggle.Text = "Turn On" + cyberdeck.WS.portText.Enable() + } + } + + cyberdeck.RPC.userText.SetText(cyberdeck.RPC.user) + cyberdeck.RPC.passText.SetText(cyberdeck.RPC.pass) + + linkCopy := widget.NewHyperlinkWithStyle("Copy Credentials", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkCopy.OnTapped = func() { + a.Clipboard().SetContent(cyberdeck.RPC.user + ":" + cyberdeck.RPC.pass) + } + + linkPermissions := widget.NewHyperlinkWithStyle("Settings", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkPermissions.OnTapped = func() { + //if cyberdeck.WS.server != nil { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutXSWDPermissions()) + removeOverlays() + //} + } + + /* + linkApps := widget.NewHyperlinkWithStyle("View Connections", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkApps.OnTapped = func() { + if cyberdeck.WS.server != nil { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutXSWDConnections()) + removeOverlays() + } + } + */ + + cyberdeck.WS.list = widget.NewList( + func() int { + return len(cyberdeck.WS.apps) + }, + func() fyne.CanvasObject { + return container.NewVBox( + widget.NewLabel(""), + //widget.NewLabel(""), + ) + }, + func(li widget.ListItemID, co fyne.CanvasObject) { + app := cyberdeck.WS.apps[li] + + fyne.Do(func() { + co.(*fyne.Container).Objects[0].(*widget.Label).SetText(app.Name) + //co.(*fyne.Container).Objects[1].(*widget.Label).SetText(app.Id) + }) + }, + ) + + cyberdeck.WS.list.OnSelected = func(id widget.ListItemID) { + cyberdeck.WS.list.UnselectAll() + cyberdeck.WS.list.FocusLost() + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutXSWDAppManager(&cyberdeck.WS.apps[id])) + removeOverlays() + } + + xswdForm := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + wsLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + container.NewCenter( + layout.NewSpacer(), + container.NewCenter( + container.NewVBox( + rectWidth90, + rectSpacer, + container.NewCenter( + cyberdeck.WS.status, + ), + rectSpacer, + serverStatus, + wSpacer, + cyberdeck.WS.toggle, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkPermissions, + layout.NewSpacer(), + ), + ), + ), + ), + container.NewStack( + rectWidth90, + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + labelConnections, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + container.NewStack( + rect, + cyberdeck.WS.list, + ), + ), + ), + ), + layout.NewSpacer(), + ) + + rpcForm := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + rpcLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + container.NewCenter( + layout.NewSpacer(), + container.NewCenter( + container.NewVBox( + rectWidth90, + rectSpacer, + linkCenter, + rectSpacer, + serverStatus, + wSpacer, + cyberdeck.RPC.toggle, + wSpacer, + cyberdeck.RPC.portText, + rectSpacer, + cyberdeck.RPC.userText, + rectSpacer, + cyberdeck.RPC.passText, + wSpacer, + container.NewHBox( + layout.NewSpacer(), + linkCopy, + layout.NewSpacer(), + ), + ), + ), + layout.NewSpacer(), + ), + ) + + deckFeatures := container.NewStack() + if cyberdeck.RPC.server != nil { + deckFeatures.Add(rpcForm) + deckChoice.SetSelectedIndex(1) + } else { + deckFeatures.Add(xswdForm) + deckChoice.SetSelectedIndex(0) + } + + deckChoice.OnChanged = func(s string) { + if s == "Remote Procedure Calls (RPC)" { + deckFeatures.Objects[0] = rpcForm + } else { + deckFeatures.Objects[0] = xswdForm + } + } + + deckForm := container.NewVScroll( + container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + container.NewVBox( + title, + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + container.NewStack( + rectWidth90, + deckChoice, + ), + ), + container.NewBorder( + deckFeatures, + nil, + nil, + nil, + ), + ), + ), + ) + + deckForm.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.80)) + + session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { + if k.Name == fyne.KeyLeft { + session.Dashboard = "main" + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } + }) + + subContainer := container.NewStack( + container.NewVBox( + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + c := container.NewBorder( + deckForm, + subContainer, + nil, + nil, + ) + + layout := container.NewStack( + frame, + c, + ) + + return NewVScroll(layout) +} + +// Layout details of an app connected through web socket +func layoutXSWDAppManager(ad *xswd.ApplicationData) fyne.CanvasObject { + session.Domain = "app.cyberdeck.manager" + + frame := &iframe{} + + rectBox := canvas.NewRectangle(color.Transparent) + rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.58)) + + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + labelName := widget.NewRichText(&widget.TextSegment{ + Text: ad.Name, + Style: widget.RichTextStyle{ + Alignment: fyne.TextAlignCenter, + ColorName: theme.ColorNameForeground, + SizeName: theme.SizeNameHeadingText, + TextStyle: fyne.TextStyle{Bold: true}, + }}) + labelName.Wrapping = fyne.TextWrapWord + + labelDesc := widget.NewRichText(&widget.TextSegment{ + Text: ad.Description, + Style: widget.RichTextStyle{ + Alignment: fyne.TextAlignCenter, + ColorName: theme.ColorNameForeground, + TextStyle: fyne.TextStyle{Bold: false}, + }}) + labelDesc.Wrapping = fyne.TextWrapWord + + labelID := canvas.NewText(" APP ID", colors.Gray) + labelID.TextSize = 14 + labelID.Alignment = fyne.TextAlignLeading + labelID.TextStyle = fyne.TextStyle{Bold: true} + + textID := widget.NewRichTextFromMarkdown(ad.Id) + textID.Wrapping = fyne.TextWrapWord + + labelSignature := canvas.NewText(" SIGNATURE", colors.Gray) + labelSignature.TextSize = 14 + labelSignature.Alignment = fyne.TextAlignLeading + labelSignature.TextStyle = fyne.TextStyle{Bold: true} + + textSignature := widget.NewRichTextFromMarkdown("") + textSignature.Wrapping = fyne.TextWrapWord + + labelURL := canvas.NewText(" URL", colors.Gray) + labelURL.TextSize = 14 + labelURL.Alignment = fyne.TextAlignLeading + labelURL.TextStyle = fyne.TextStyle{Bold: true} + + textURL := widget.NewRichTextFromMarkdown(ad.Url) + textURL.Wrapping = fyne.TextWrapWord + + labelPermissions := canvas.NewText(" PERMISSIONS", colors.Gray) + labelPermissions.TextSize = 14 + labelPermissions.Alignment = fyne.TextAlignLeading + labelPermissions.TextStyle = fyne.TextStyle{Bold: true} + + labelEvents := canvas.NewText(" EVENTS", colors.Gray) + labelEvents.TextSize = 14 + labelEvents.Alignment = fyne.TextAlignLeading + labelEvents.TextStyle = fyne.TextStyle{Bold: true} + + labelSeparator := widget.NewRichTextFromMarkdown("") + labelSeparator.Wrapping = fyne.TextWrapOff + labelSeparator.ParseMarkdown("---") + labelSeparator2 := widget.NewRichTextFromMarkdown("") + labelSeparator2.Wrapping = fyne.TextWrapOff + labelSeparator2.ParseMarkdown("---") + labelSeparator3 := widget.NewRichTextFromMarkdown("") + labelSeparator3.Wrapping = fyne.TextWrapOff + labelSeparator3.ParseMarkdown("---") + labelSeparator4 := widget.NewRichTextFromMarkdown("") + labelSeparator4.Wrapping = fyne.TextWrapOff + labelSeparator4.ParseMarkdown("---") + labelSeparator5 := widget.NewRichTextFromMarkdown("") + labelSeparator5.Wrapping = fyne.TextWrapOff + labelSeparator5.ParseMarkdown("---") + labelSeparator6 := widget.NewRichTextFromMarkdown("") + labelSeparator6.Wrapping = fyne.TextWrapOff + labelSeparator6.ParseMarkdown("---") + + signatureItems := container.NewVBox( + labelSeparator2, + rectSpacer, + rectSpacer, + labelSignature, + textSignature, + rectSpacer, + rectSpacer, + ) + + // Show signature result if one exists + signatureItems.Hide() + if len(ad.Signature) > 0 { + signatureItems.Show() + _, message, err := engram.Disk.CheckSignature(ad.Signature) + if err != nil { + textSignature.ParseMarkdown(err.Error()) + } else { + textSignature.ParseMarkdown(strings.TrimSpace(string(message))) + } + } + + // Find Permissions for connected app and build UI object + var methods []string + for k := range ad.Permissions { + methods = append(methods, k) + } + + permissionItems := container.NewVBox() + + permissions := []string{ + xswd.Ask.String(), + xswd.Allow.String(), + xswd.Deny.String(), + xswd.AlwaysAllow.String(), + xswd.AlwaysDeny.String(), + } + + if len(methods) > 0 { + sort.Strings(methods) + for _, name := range methods { + permission := widget.NewSelect(permissions, nil) + permission.SetSelected(ad.Permissions[name].String()) + permission.Disable() + permissionItems.Add(container.NewBorder(nil, nil, widget.NewRichTextFromMarkdown("### "+name), permission)) + } + } else { + permissionItems.Add(container.NewBorder(nil, nil, widget.NewRichTextFromMarkdown("No Permissions"), nil)) + } + + // Find RegisteredEvents for connected app and build UI object + var events []rpc.EventType + for k := range ad.RegisteredEvents { + events = append(events, k) + } + + eventItems := container.NewVBox() + + if len(events) > 0 { + sort.Slice(events, func(i, j int) bool { return events[i] < events[j] }) + for _, name := range events { + event := widget.NewSelect([]string{"false", "true"}, nil) + event.SetSelected(strconv.FormatBool(ad.RegisteredEvents[name])) + event.Disable() + eventItems.Add(container.NewBorder(nil, nil, widget.NewRichTextFromMarkdown(fmt.Sprintf("### %s", name)), event)) + } + } else { + eventItems.Add(container.NewBorder(nil, nil, widget.NewRichTextFromMarkdown("No Events"), nil)) + } + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to Cyberdeck", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + removeOverlays() + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutCyberdeck()) + } + + image := canvas.NewImageFromResource(resourceWebsocketPng) + image.SetMinSize(fyne.NewSize(ui.Width*0.25, ui.Width*0.25)) + image.FillMode = canvas.ImageFillContain + + // Check if the application is TELA + telaURL := "http://localhost" + if strings.HasPrefix(ad.Url, telaURL) { + for _, serv := range tela.GetServerInfo() { + if strings.HasPrefix(ad.Url, telaURL+serv.Address) { + name, _, icon, _, _ := getContractHeader(crypto.HashHexToHash(serv.SCID)) + if icon != "" { + if img, err := handleImageURL(name, icon, fyne.NewSize(ui.Width*0.25, ui.Width*0.25)); err == nil { + image = img + } else { + logger.Errorf("[Engram] Could not validate icon image: %s\n", err) + } + } + + break + } + } + } + + linkURL := widget.NewHyperlinkWithStyle("Open in browser", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + linkURL.OnTapped = func() { + link, err := url.Parse(ad.Url) + if err != nil { + logger.Errorf("[Engram] Error parsing XSWD application URL: %s\n", err) + return + } + _ = fyne.CurrentApp().OpenURL(link) + } + + btnRemove := widget.NewButton("Remove", nil) + btnRemove.OnTapped = func() { + if cyberdeck.WS.server != nil && len(cyberdeck.WS.apps) > 0 { + cyberdeck.WS.server.RemoveApplication(ad) + removeOverlays() + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutCyberdeck()) + } + } + + center := container.NewStack( + rectBox, + container.NewVScroll( + container.NewStack( + rectWidth90, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + container.NewHBox( + layout.NewSpacer(), + image, + layout.NewSpacer(), + ), + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + labelName, + ), + layout.NewSpacer(), + ), + labelDesc, + rectSpacer, + rectSpacer, + labelSeparator, + rectSpacer, + rectSpacer, + labelID, + textID, + rectSpacer, + rectSpacer, + signatureItems, + labelSeparator3, + rectSpacer, + rectSpacer, + labelURL, + rectSpacer, + textURL, + container.NewHBox( + layout.NewSpacer(), + ), + container.NewHBox( + linkURL, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator4, + rectSpacer, + rectSpacer, + labelPermissions, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + ), + permissionItems, + rectSpacer, + rectSpacer, + labelSeparator5, + rectSpacer, + rectSpacer, + labelEvents, + rectSpacer, + eventItems, + container.NewStack( + rectWidth90, + ), + rectSpacer, + rectSpacer, + labelSeparator6, + rectSpacer, + rectSpacer, + btnRemove, + rectSpacer, + rectSpacer, + ), + layout.NewSpacer(), + ), + ), + ), + rectSpacer, + rectSpacer, + ) + + top := container.NewVBox( + rectSpacer, + rectSpacer, + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewStack( + frame, + container.NewBorder( + top, + bottom, + nil, + center, + ), + ) + + return NewVScroll(layout) +} + +// Layout XSWD permissions settings +func layoutXSWDPermissions() fyne.CanvasObject { + session.Domain = "app.cyberdeck.permissions" + + wSpacer := widget.NewLabel(" ") + + frame := &iframe{} + + rect := canvas.NewRectangle(color.Transparent) + rect.SetMinSize(fyne.NewSize(ui.Width, 20)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 0)) + + title := canvas.NewText("G L O B A L P E R M I S S I O N S", colors.Gray) + title.TextStyle = fyne.TextStyle{Bold: true} + title.TextSize = 16 + + xswdLabel := canvas.NewText("W E B S O C K E T S", colors.Gray) + xswdLabel.TextSize = 11 + xswdLabel.Alignment = fyne.TextAlignCenter + xswdLabel.TextStyle = fyne.TextStyle{Bold: true} + + labelMethods := canvas.NewText(" METHODS", colors.Gray) + labelMethods.TextSize = 14 + labelMethods.Alignment = fyne.TextAlignLeading + labelMethods.TextStyle = fyne.TextStyle{Bold: true} + + labelConnection := canvas.NewText(" CONNECTIONS", colors.Gray) + labelConnection.TextSize = 14 + labelConnection.Alignment = fyne.TextAlignLeading + labelConnection.TextStyle = fyne.TextStyle{Bold: true} + + labelEpoch := canvas.NewText(" EPOCH", colors.Gray) + labelEpoch.TextSize = 14 + labelEpoch.Alignment = fyne.TextAlignLeading + labelEpoch.TextStyle = fyne.TextStyle{Bold: true} + + permissionInfo := canvas.NewText("APPLY ON CONNECTION", colors.Gray) + permissionInfo.TextSize = 12 + permissionInfo.Alignment = fyne.TextAlignCenter + permissionInfo.TextStyle = fyne.TextStyle{Bold: true} + + btnDefaults := widget.NewButton("Restore Defaults", nil) + + wMode := widget.NewCheck("Restrictive Mode", nil) + + wConnection := widget.NewSelect([]string{xswd.Ask.String(), xswd.Allow.String()}, nil) + + wGlobalPermissions := widget.NewSelect([]string{"Off", "Apply"}, nil) + + wEpoch := widget.NewSelect([]string{xswd.Deny.String(), xswd.Allow.String()}, nil) + + wEpochAddress := widget.NewSelect([]string{"My Address", "dApp Chooses"}, nil) + + /* + if cyberdeck.EPOCH.enabled { + wEpoch.SetSelectedIndex(1) + } else { + wEpoch.SetSelectedIndex(0) + wEpochAddress.Disable() + } + + if cyberdeck.EPOCH.allowWithAddress { + wEpochAddress.SetSelectedIndex(1) + } else { + wEpochAddress.SetSelectedIndex(0) + } + + wEpoch.OnChanged = func(s string) { + if s == xswd.Allow.String() { + cyberdeck.EPOCH.enabled = true + wEpochAddress.Enable() + return + } + + cyberdeck.EPOCH.enabled = false + wEpochAddress.SetSelectedIndex(0) + wEpochAddress.Disable() + } + + wEpochAddress.OnChanged = func(s string) { + if s == "dApp Chooses" { + cyberdeck.EPOCH.allowWithAddress = true + return + } + + cyberdeck.EPOCH.allowWithAddress = false + } + */ + + spacerEpoch := canvas.NewRectangle(color.Transparent) + spacerEpoch.SetMinSize(fyne.NewSize(140, 0)) + + entryEpochWork := widget.NewEntry() + entryEpochWork.SetPlaceHolder(":10100") + entryEpochWork.SetText(epoch.GetPort()) + entryEpochWork.Validator = func(s string) (err error) { + i, err := strconv.Atoi(s) + if err != nil { + return fmt.Errorf("invalid port") + } + + return epoch.SetPort(i) + } + + entryEpochHash := widget.NewEntry() + entryEpochHash.SetPlaceHolder("Max hashes") + entryEpochHash.SetText(strconv.Itoa(epoch.GetMaxHashes())) + entryEpochHash.Validator = func(s string) (err error) { + i, err := strconv.Atoi(s) + if err != nil { + return fmt.Errorf("invalid hash value") + } + + return epoch.SetMaxHashes(i) + } + + wEpochPower := widget.NewSelect([]string{"Less", "More"}, nil) + wEpochPower.SetSelectedIndex(0) + if epoch.GetMaxThreads() > 2 { + wEpochPower.SetSelectedIndex(1) + } + + wEpochPower.OnChanged = func(s string) { + if s == "More" { + half := runtime.NumCPU() / 2 + if half > epoch.DEFAULT_MAX_THREADS { + epoch.SetMaxThreads(half) + } + + return + } + + epoch.SetMaxThreads(epoch.DEFAULT_MAX_THREADS) + } + + if session.Offline { + wMode.Disable() + wEpoch.Disable() + wEpochAddress.Disable() + entryEpochWork.Disable() + entryEpochHash.Disable() + wEpochPower.Disable() + } else if cyberdeck.WS.server != nil { + wEpoch.Disable() + wEpochAddress.Disable() + entryEpochWork.Disable() + entryEpochHash.Disable() + wEpochPower.Disable() + } + + if cyberdeck.WS.advanced { + wMode.SetChecked(false) + if cyberdeck.WS.global.enabled { + wGlobalPermissions.SetSelectedIndex(1) + if cyberdeck.WS.global.connect { + wConnection.SetSelectedIndex(1) + } else { + wConnection.SetSelectedIndex(0) + } + } else { + wGlobalPermissions.SetSelectedIndex(0) + wConnection.SetSelectedIndex(0) + wConnection.Disable() + btnDefaults.Disable() + } + } else { + wMode.SetChecked(true) + wConnection.SetSelectedIndex(0) + wConnection.Disable() + wGlobalPermissions.SetSelectedIndex(0) + wGlobalPermissions.Disable() + btnDefaults.Disable() + } + + wMode.OnChanged = func(b bool) { + cyberdeck.WS.advanced = !b // inverse as check box is for restrictive mode on/off + if cyberdeck.WS.advanced { + wGlobalPermissions.Enable() + } else { + wGlobalPermissions.SetSelectedIndex(0) // calling this here resets and disables wConnection + wGlobalPermissions.Disable() + } + } + + wConnection.OnChanged = func(s string) { + if s == xswd.Allow.String() { + cyberdeck.WS.global.connect = true + } else { + cyberdeck.WS.global.connect = false + } + } + + formItems := container.NewVBox() + + permissions := []string{ + xswd.Ask.String(), + xswd.AlwaysAllow.String(), + xswd.AlwaysDeny.String(), + } + + noStorePermissions := []string{ + xswd.Ask.String(), + xswd.AlwaysDeny.String(), + } + + // Permissions select on changed func + onChanged := func(n string) func(s string) { + return func(s string) { + cyberdeck.WS.Lock() + defer cyberdeck.WS.Unlock() + + switch s { + case xswd.Ask.String(): + cyberdeck.WS.global.permissions[n] = xswd.Ask + case xswd.AlwaysAllow.String(): + cyberdeck.WS.global.permissions[n] = xswd.AlwaysAllow + case xswd.AlwaysDeny.String(): + cyberdeck.WS.global.permissions[n] = xswd.AlwaysDeny + default: + cyberdeck.WS.global.permissions[n] = xswd.Ask + } + } + } + + stored, methods := getPermissions() + for _, name := range methods { + n := name + permission := widget.NewSelect([]string{}, nil) + if engramCanStoreMethod(n) { + permission.SetOptions(permissions) + } else { + permission.SetOptions(noStorePermissions) + } + + if cyberdeck.WS.global.enabled { + permission.SetSelected(stored[n].String()) + permission.OnChanged = onChanged(n) + } else { + permission.SetSelectedIndex(0) + permission.Disable() + } + formItems.Add(container.NewBorder(nil, nil, widget.NewRichTextFromMarkdown("### "+n), permission)) + } + + statusText := "Disabled" + statusColor := colors.Gray + if cyberdeck.WS.global.enabled { + statusText = "Enabled" + statusColor = colors.Green + } + + cyberdeck.WS.global.status = canvas.NewText(statusText, statusColor) + cyberdeck.WS.global.status.TextSize = 22 + cyberdeck.WS.global.status.TextStyle = fyne.TextStyle{Bold: true} + + btnDefaults.OnTapped = func() { + if !cyberdeck.WS.global.enabled { + return + } + + header := canvas.NewText("RESTORE DEFAULT PERMISSIONS", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + subHeader := canvas.NewText("Are you sure?", colors.Account) + subHeader.TextSize = 22 + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + + linkCancel := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkCancel.OnTapped = func() { + removeOverlays() + } + + btnSubmit := widget.NewButton("Restore Defaults", nil) + btnSubmit.OnTapped = func() { + wConnection.SetSelectedIndex(0) + for _, obj := range formItems.Objects { + obj.(*fyne.Container).Objects[1].(*widget.Select).SetSelectedIndex(0) + } + removeOverlays() + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay := session.Window.Canvas().Overlays() + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + subHeader, + widget.NewLabel(""), + btnSubmit, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkCancel, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + } + + wGlobalPermissions.OnChanged = func(s string) { + if s != "Apply" { + setPermissions() + btnDefaults.Disable() + cyberdeck.WS.global.status.Text = "Disabled" + cyberdeck.WS.global.status.Color = colors.Gray + cyberdeck.WS.global.status.Refresh() + cyberdeck.WS.global.enabled = false + wConnection.SetSelectedIndex(0) + wConnection.Disable() + for _, obj := range formItems.Objects { + obj.(*fyne.Container).Objects[1].(*widget.Select).OnChanged = nil + obj.(*fyne.Container).Objects[1].(*widget.Select).SetSelectedIndex(0) + obj.(*fyne.Container).Objects[1].(*widget.Select).Disable() + } + } else { + cyberdeck.WS.global.status.Text = "Enabled" + cyberdeck.WS.global.status.Color = colors.Green + cyberdeck.WS.global.status.Refresh() + cyberdeck.WS.global.enabled = true + wConnection.Enable() + btnDefaults.Enable() + go func() { + stored, _ := getPermissions() + for _, obj := range formItems.Objects { + fyne.Do(func() { + name := obj.(*fyne.Container).Objects[0].(*widget.RichText).String() + obj.(*fyne.Container).Objects[1].(*widget.Select).SetSelected(stored[name].String()) + obj.(*fyne.Container).Objects[1].(*widget.Select).Enable() + obj.(*fyne.Container).Objects[1].(*widget.Select).OnChanged = onChanged(name) + }) + } + }() + } + } + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to Cyberdeck", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + setPermissions() + removeOverlays() + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutCyberdeck()) + } + + // Initialized in layoutCyberdeck() + cyberdeck.WS.portText.SetText(getCyberdeck("WS")) + + center := container.NewVScroll( + container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + container.NewVBox( + title, + rectSpacer, + ), + ), + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + xswdLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + container.NewCenter( + container.NewVBox( + rectWidth90, + rectSpacer, + container.NewCenter( + cyberdeck.WS.global.status, + ), + rectSpacer, + container.NewCenter( + permissionInfo, + ), + ), + ), + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewCenter( + container.NewVBox( + container.NewBorder( + nil, + nil, + nil, + nil, + container.NewCenter(wMode), + ), + rectSpacer, + cyberdeck.WS.portText, + rectSpacer, + labelConnection, + rectSpacer, + container.NewBorder( + nil, + nil, + widget.NewRichTextFromMarkdown("### Type"), + wConnection, + ), + container.NewBorder( + nil, + nil, + widget.NewRichTextFromMarkdown("### Global Permissions"), + wGlobalPermissions, + ), + wSpacer, + labelEpoch, + rectSpacer, + /* + container.NewBorder( + nil, + nil, + widget.NewRichTextFromMarkdown("### Preference"), + wEpoch, + ), + container.NewBorder( + nil, + nil, + widget.NewRichTextFromMarkdown("### Reward Address"), + wEpochAddress, + ), + */ + container.NewBorder( + nil, + nil, + widget.NewRichTextFromMarkdown("### Get Work"), + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + spacerEpoch, + entryEpochWork, + ), + ), + ), + container.NewBorder( + nil, + nil, + widget.NewRichTextFromMarkdown("### Max Hashes"), + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + spacerEpoch, + entryEpochHash, + ), + ), + ), + container.NewBorder( + nil, + nil, + widget.NewRichTextFromMarkdown("### Power"), + wEpochPower, + ), + wSpacer, + labelMethods, + rectSpacer, + container.NewCenter( + formItems, + ), + wSpacer, + ), + ), + layout.NewSpacer(), + ), + container.NewCenter( + container.NewVBox( + btnDefaults, + rectWidth90, + ), + ), + wSpacer, + ), + ), + ) + center.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.80)) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewStack( + frame, + container.NewBorder( + center, + bottom, + nil, + nil, + ), + ) + + return NewVScroll(layout) +} + +func layoutIdentity() fyne.CanvasObject { + session.Domain = "app.Identity" + title := canvas.NewText("I D E N T I T Y", colors.Gray) + title.TextStyle = fyne.TextStyle{Bold: true} + title.TextSize = 16 + + heading := canvas.NewText("My Contacts", colors.Green) + heading.TextSize = 22 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + frame := &iframe{} + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + rectList := canvas.NewRectangle(color.Transparent) + rectList.SetMinSize(fyne.NewSize(ui.Width, 35)) + rectListBox := canvas.NewRectangle(color.Transparent) + rectListBox.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.44)) + + shortShard := canvas.NewText("PRIMARY USERNAME", colors.Gray) + shortShard.TextStyle = fyne.TextStyle{Bold: true} + shortShard.TextSize = 12 + + idCenter := container.NewCenter( + shortShard, + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } + + //entryReg := NewMobileEntry() + entryReg := widget.NewEntry() + entryReg.MultiLine = false + entryReg.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) + + userData, err := queryUsernames(engram.Disk.GetAddress().String()) + if err != nil { + userData, err = getUsernames() + if err != nil { + userData = nil + } + } + + userList := binding.BindStringList(&userData) + + btnReg := widget.NewButton(" Register ", nil) + btnReg.Disable() + btnReg.OnTapped = func() { + if len(session.NewUser) > 5 { + valid, _ := checkUsername(session.NewUser, -1) + if valid == "" { + btnReg.Text = "Confirming..." + btnReg.Disable() + btnReg.Refresh() + entryReg.Disable() + storage, err := registerUsername(session.NewUser) + if err != nil { + if strings.Contains(err.Error(), "somehow the tx could not be built") { + btnReg.Text = fmt.Sprintf("Insufficient Balance: Need %v", globals.FormatMoney(storage)) + } else { + btnReg.Text = "Unable to register..." + } + btnReg.Refresh() + logger.Errorf("[Username] %s\n", err) + } else { + go func() { + fyne.Do(func() { + entryReg.Text = "" + entryReg.Refresh() + }) + + walletapi.WaitNewHeightBlock() + sHeight := walletapi.Get_Daemon_Height() + + for { + if session.Domain == "app.Identity" { + //vars, _, _, err := gnomon.Index.RPC.GetSCVariables("0000000000000000000000000000000000000000000000000000000000000001", engram.Disk.Get_Daemon_TopoHeight(), nil, []string{session.NewUser}, nil, false) + usernames, err := queryUsernames(engram.Disk.GetAddress().String()) + if err != nil { + logger.Errorf("[Username] Error querying usernames: %s\n", err) + + fyne.Do(func() { + btnReg.Text = "Error querying usernames" + btnReg.Refresh() + }) + + return + } + + for u := range usernames { + if usernames[u] == session.NewUser { + logger.Printf("[Username] Successfully registered username: %s\n", session.NewUser) + _ = tx + + fyne.Do(func() { + btnReg.Text = "Registration successful!" + btnReg.Refresh() + session.NewUser = "" + session.Window.SetContent(layoutIdentity()) + }) + + return + } + } + + // If we go DEFAULT_CONFIRMATION_TIMEOUT blocks without exiting 'Confirming...' loop, display failed to transfer and break + if walletapi.Get_Daemon_Height() > sHeight+int64(DEFAULT_CONFIRMATION_TIMEOUT) { + fyne.Do(func() { + btnReg.Text = "Unable to register..." + btnReg.Refresh() + }) + + break + } + + // If daemon height has incremented, print retry counters into button space + if walletapi.Get_Daemon_Height()-sHeight > 0 { + fyne.Do(func() { + btnReg.Text = fmt.Sprintf("Confirming... (%d/%d)", walletapi.Get_Daemon_Height()-sHeight, DEFAULT_CONFIRMATION_TIMEOUT) + btnReg.Refresh() + }) + } + } else { + break + } + + time.Sleep(time.Second * 1) + } + }() + } + } + } + } + + entryReg.PlaceHolder = "New Username" + entryReg.Validator = func(s string) error { + btnReg.Text = " Register " + btnReg.Enable() + btnReg.Refresh() + session.NewUser = s + // Name Service SCID Logic + // 15 IF STRLEN(name) >= 64 THEN GOTO 50 // skip names misuse + // 20 IF STRLEN(name) >= 6 THEN GOTO 40 + if len(s) > 5 && len(s) < 64 { + valid, _ := checkUsername(s, -1) + if valid == "" { + btnReg.Enable() + btnReg.Refresh() + } else { + btnReg.Disable() + err := errors.New("username already exists") + entryReg.SetValidationError(err) + btnReg.Refresh() + return err + } + } else { + btnReg.Disable() + err := errors.New("username too short need a minimum of six characters") + entryReg.SetValidationError(err) + btnReg.Refresh() + return err + } + + return nil + } + + userBox := widget.NewListWithData(userList, + func() fyne.CanvasObject { + c := container.NewVBox( + widget.NewLabel(""), + ) + return c + }, + func(di binding.DataItem, co fyne.CanvasObject) { + dat := di.(binding.String) + str, err := dat.Get() + if err != nil { + return + } + + if len(str) > DEFAULT_USERADDR_SHORTEN_LENGTH+3 { + str = "..." + str[len(str)-DEFAULT_USERADDR_SHORTEN_LENGTH:] + } + + co.(*fyne.Container).Objects[0].(*widget.Label).SetText(str) + co.(*fyne.Container).Objects[0].(*widget.Label).Wrapping = fyne.TextWrapWord + co.(*fyne.Container).Objects[0].(*widget.Label).TextStyle.Bold = false + co.(*fyne.Container).Objects[0].(*widget.Label).Alignment = fyne.TextAlignLeading + }) + + err = getPrimaryUsername() + if err != nil { + session.Username = "" + } + + dispUsername := session.Username + if len(session.Username) > DEFAULT_USERADDR_SHORTEN_LENGTH+3 { + dispUsername = "..." + dispUsername[len(dispUsername)-DEFAULT_USERADDR_SHORTEN_LENGTH:] + } + + textUsername := canvas.NewText(dispUsername, colors.Green) + textUsername.TextStyle = fyne.TextStyle{Bold: true} + textUsername.TextSize = 22 + + if session.Username == "" { + textUsername.Text = "---" + textUsername.Refresh() + } /* else { + for u := range userData { + if userData[u] == session.Username { + userBox.Select(u) + userBox.ScrollTo(u) + } + } + }*/ + + userBox.OnSelected = func(id widget.ListItemID) { + overlay := session.Window.Canvas().Overlays() + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + overlay.Add(layoutIdentityDetail(userData[id])) + userBox.UnselectAll() + } + + shardForm := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + container.NewVBox( + title, + rectSpacer, + ), + ), + rectSpacer, + container.NewStack( + container.NewCenter( + textUsername, + ), + ), + rectSpacer, + idCenter, + rectSpacer, + rectSpacer, + container.NewStack( + rectListBox, + userBox, + ), + rectSpacer, + entryReg, + rectSpacer, + btnReg, + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ) + + gridItem1 := container.NewCenter( + shardForm, + ) + + features := container.NewCenter( + layout.NewSpacer(), + gridItem1, + layout.NewSpacer(), + ) + + session.Window.Canvas().SetOnTypedKey(func(k *fyne.KeyEvent) { + if k.Name == fyne.KeyRight { + session.Dashboard = "main" + + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } else if k.Name == fyne.KeyF5 { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutIdentity()) + removeOverlays() + } + }) + + subContainer := container.NewStack( + container.NewVBox( + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + c := container.NewBorder( + features, + subContainer, + nil, + nil, + ) + + layout := container.NewStack( + frame, + c, + ) + + return NewVScroll(layout) +} + +func layoutIdentityDetail(username string) fyne.CanvasObject { + var address string + + wSpacer := widget.NewLabel(" ") + + rectWidth := canvas.NewRectangle(color.Transparent) + rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, 10)) + + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) + + frame := &iframe{} + + heading := canvas.NewText("I D E N T I T Y D E T A I L", colors.Gray) + heading.TextSize = 16 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + labelUsername := canvas.NewText("REGISTERED USERNAME", colors.Gray) + labelUsername.TextSize = 11 + labelUsername.Alignment = fyne.TextAlignCenter + labelUsername.TextStyle = fyne.TextStyle{Bold: true} + + labelTransfer := canvas.NewText(" T R A N S F E R ", colors.Gray) + labelTransfer.TextSize = 11 + labelTransfer.Alignment = fyne.TextAlignCenter + labelTransfer.TextStyle = fyne.TextStyle{Bold: true} + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to Identity", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + removeOverlays() + } + + valueUsername := canvas.NewText(username, colors.Green) + valueUsername.TextSize = 22 + valueUsername.TextStyle = fyne.TextStyle{Bold: true} + valueUsername.Alignment = fyne.TextAlignCenter + + btnSetPrimary := widget.NewButton("Set Primary Username", nil) + btnSetPrimary.OnTapped = func() { + setPrimaryUsername(username) + session.Username = username + //session.Window.SetContent(layoutIdentity()) + removeOverlays() + } + + btnSend := widget.NewButton("Transfer Username", nil) + + inputAddress := widget.NewEntry() + inputAddress.PlaceHolder = "Receiver Username or Address" + inputAddress.Validator = func(s string) error { + btnSend.Text = "Transfer Username" + btnSend.Enable() + btnSend.Refresh() + address, _ = checkUsername(s, -1) + if address == "" { + _, err := globals.ParseValidateAddress(s) + if err != nil { + btnSend.Disable() + btnSend.Refresh() + err := errors.New("address does not exist") + inputAddress.SetValidationError(err) + inputAddress.Refresh() + return err + } else { + btnSend.Enable() + btnSend.Refresh() + address = s + } + } else { + btnSend.Enable() + btnSend.Refresh() + } + + return nil + } + + btnSend.OnTapped = func() { + if address != "" && address != engram.Disk.GetAddress().String() { + btnSend.Text = "Setting up transfer..." + btnSend.Disable() + btnSend.Refresh() + inputAddress.Disable() + inputAddress.Refresh() + btnSetPrimary.Disable() + storage, err := transferUsername(username, address) + if err != nil { + address = "" + if strings.Contains(err.Error(), "somehow the tx could not be built") { + btnSend.Text = fmt.Sprintf("Insufficient Balance: Need %v", globals.FormatMoney(storage)) + } else { + btnSend.Text = "Transfer failed..." + } + btnSend.Disable() + btnSend.Refresh() + inputAddress.Enable() + inputAddress.Refresh() + btnSetPrimary.Enable() + } else { + btnSend.Text = "Confirming..." + btnSend.Refresh() + go func() { + walletapi.WaitNewHeightBlock() + sHeight := walletapi.Get_Daemon_Height() + + for { + found := false + if session.Domain == "app.Identity" { + usernames, err := queryUsernames(engram.Disk.GetAddress().String()) + if err != nil { + logger.Errorf("[Username] Error querying usernames: %s\n", err) + fyne.Do(func() { + btnSend.Text = "Error querying usernames" + btnSend.Refresh() + btnSetPrimary.Enable() + }) + + return + } + + for u := range usernames { + if usernames[u] == username { + found = true + } + } + + if !found { + logger.Printf("[TransferOwnership] %s was successfully transferred to: %s\n", username, address) + fyne.Do(func() { + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutIdentity()) + removeOverlays() + }) + + break + } + + // If we go DEFAULT_CONFIRMATION_TIMEOUT blocks without exiting 'Confirming...' loop, display failed to transfer and break + if walletapi.Get_Daemon_Height() > sHeight+int64(DEFAULT_CONFIRMATION_TIMEOUT) { + logger.Errorf("[TransferOwnership] %s was unsuccessful in transferring to: %s\n", username, address) + fyne.Do(func() { + btnSend.Text = "Unable to transfer..." + btnSend.Refresh() + btnSetPrimary.Enable() + }) + + break + } + + // If daemon height has incremented, print retry counters into button space + if walletapi.Get_Daemon_Height()-sHeight > 0 { + fyne.Do(func() { + btnSend.Text = fmt.Sprintf("Confirming... (%d/%d)", walletapi.Get_Daemon_Height()-sHeight, DEFAULT_CONFIRMATION_TIMEOUT) + btnSend.Refresh() + }) + } + } else { + break + } + + time.Sleep(time.Second * 1) + } + }() + } + } + } + + top := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + heading, + ), + rectSpacer, + rectSpacer, + ) + + center := container.NewStack( + container.NewVScroll( + container.NewStack( + rectWidth, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + rectSpacer, + valueUsername, + rectSpacer, + labelUsername, + wSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + btnSetPrimary, + ), + layout.NewSpacer(), + ), + wSpacer, + container.NewStack( + rectWidth, + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + labelTransfer, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + wSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + inputAddress, + ), + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + btnSend, + ), + layout.NewSpacer(), + ), + ), + layout.NewSpacer(), + ), + ), + ), + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewStack( + frame, + container.NewBorder( + top, + bottom, + nil, + center, + ), + ) + + return layout +} + +func layoutWaiting(title *canvas.Text, heading *canvas.Text, sub *canvas.Text, link *widget.Hyperlink) fyne.CanvasObject { + rect := canvas.NewRectangle(color.Transparent) + rect.SetMinSize(fyne.NewSize(ui.Width*0.6, ui.Height*0.35)) + rect2 := canvas.NewRectangle(color.Transparent) + rect2.SetMinSize(fyne.NewSize(ui.Width, 1)) + frame := canvas.NewRectangle(color.Transparent) + frame.SetMinSize(fyne.NewSize(ui.Width, ui.Height)) + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + label := canvas.NewText("PROOF-OF-WORK", colors.Gray) + label.TextStyle = fyne.TextStyle{Bold: true} + label.TextSize = 12 + hashes := canvas.NewText(fmt.Sprintf("%d", session.RegHashes), colors.Account) + hashes.TextSize = 18 + + go func() { + for engram.Disk != nil { + fyne.Do(func() { + hashes.Text = fmt.Sprintf("%d", session.RegHashes) + hashes.Refresh() + }) + } + }() + + session.Gif, _ = x.NewAnimatedGifFromResource(resourceAnimation2Gif) + session.Gif.SetMinSize(rect.MinSize()) + session.Gif.Resize(rect.MinSize()) + session.Gif.Start() + + waitForm := container.NewVBox( + widget.NewLabel(""), + container.NewHBox( + layout.NewSpacer(), + title, + layout.NewSpacer(), + ), + widget.NewLabel(""), + heading, + rectSpacer, + sub, + widget.NewLabel(""), + container.NewStack( + session.Gif, + ), + widget.NewLabel(""), + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + container.NewCenter( + rect2, + hashes, + ), + rectSpacer, + container.NewCenter( + rect2, + label, + ), + ), + layout.NewSpacer(), + ), + ) + + grid := container.NewHBox( + layout.NewSpacer(), + waitForm, + layout.NewSpacer(), + ) + + footer := container.NewVBox( + container.NewHBox( + layout.NewSpacer(), + link, + layout.NewSpacer(), + ), + widget.NewLabel(""), + ) + + c := container.NewBorder( + grid, + footer, + nil, + nil, + ) + + layout := container.NewStack( + frame, + c, + ) + + return NewVScroll(layout) +} + +func layoutAlert(t int) fyne.CanvasObject { + rect := canvas.NewRectangle(color.Transparent) + rect.SetMinSize(fyne.NewSize(ui.Width*0.6, ui.Width*0.35)) + frame := &iframe{} + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + wSpacer := widget.NewLabel(" ") + + title := canvas.NewText("", colors.Gray) + title.TextStyle = fyne.TextStyle{Bold: true} + title.TextSize = 16 + title.Alignment = fyne.TextAlignCenter + + heading := canvas.NewText("", colors.Red) + heading.TextStyle = fyne.TextStyle{Bold: true} + heading.TextSize = 22 + heading.Alignment = fyne.TextAlignCenter + + sub := widget.NewRichTextFromMarkdown("") + sub.Wrapping = fyne.TextWrapWord + + labelSettings := widget.NewHyperlinkWithStyle("Review Settings", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + + if t == 1 { + title.Text = "E R R O R" + heading.Text = "Connection Failure" + sub.ParseMarkdown("Connection to " + session.Daemon + " has failed. Please review your settings and try again.") + labelSettings.Text = "Review Settings" + labelSettings.OnTapped = func() { + session.Window.SetContent(layoutSettings()) + } + } else if t == 2 { + title.Text = "E R R O R" + heading.Text = "Write Failure" + sub.ParseMarkdown("Could not write data to disk, please check to make sure Engram has the proper permissions and/or you have unzipped the contents.") + labelSettings.Text = "Review Settings" + labelSettings.OnTapped = func() { + session.Window.SetContent(layoutMain()) + } + } else { + title.Text = "E R R O R" + heading.Text = "ID-10T Error Protocol" + sub.ParseMarkdown("System malfunction... Please... Find... Help...") + labelSettings.Text = "Review Settings" + labelSettings.OnTapped = func() { + session.Window.SetContent(layoutSettings()) + } + } + + rectHeader := canvas.NewRectangle(color.Transparent) + rectHeader.SetMinSize(fyne.NewSize(ui.Width, 1)) + + session.Gif, _ = x.NewAnimatedGifFromResource(resourceAnimation2Gif) + session.Gif.SetMinSize(rect.MinSize()) + session.Gif.Start() + + alertForm := container.NewVBox( + wSpacer, + wSpacer, + rectHeader, + container.NewStack( + rect, + res.red_alert, + ), + heading, + rectSpacer, + sub, + widget.NewLabel(""), + ) + + footer := container.NewVBox( + container.NewHBox( + layout.NewSpacer(), + labelSettings, + layout.NewSpacer(), + ), + wSpacer, + ) + + features := container.NewCenter( + layout.NewSpacer(), + alertForm, + layout.NewSpacer(), + ) + + c := container.NewBorder( + features, + footer, + nil, + nil, + ) + + layout := container.NewStack( + frame, + c, + ) + + return NewVScroll(layout) +} + +func layoutHistory() fyne.CanvasObject { + var data []string + var entries []rpc.Entry + var zeroscid crypto.Hash + var listData binding.StringList + var listBox *widget.List + var txid string + + view := "" + + header := canvas.NewText(" Transaction History", colors.Green) + header.TextSize = 22 + header.TextStyle = fyne.TextStyle{Bold: true} + + details_header := canvas.NewText(" Transaction Detail", colors.Green) + details_header.TextSize = 22 + details_header.TextStyle = fyne.TextStyle{Bold: true} + + frame := &iframe{} + rectWidth := canvas.NewRectangle(color.Transparent) + rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth, 10)) + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) + + heading := canvas.NewText("H I S T O R Y", colors.Gray) + heading.TextSize = 16 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + rect := canvas.NewRectangle(color.Transparent) + rect.SetMinSize(fyne.NewSize(ui.Width*0.3, 35)) + + rectMid := canvas.NewRectangle(color.Transparent) + rectMid.SetMinSize(fyne.NewSize(ui.Width*0.35, 35)) + + results := canvas.NewText("", colors.Green) + results.TextSize = 13 + + listData = binding.BindStringList(&data) + listBox = widget.NewListWithData(listData, + func() fyne.CanvasObject { + return container.NewHBox( + container.NewStack( + rect, + widget.NewLabel(""), + ), + container.NewStack( + rectMid, + widget.NewLabel(""), + ), + container.NewStack( + rect, + widget.NewLabel(""), + ), + ) + }, + func(di binding.DataItem, co fyne.CanvasObject) { + dat := di.(binding.String) + str, err := dat.Get() + if err != nil { + return + } + + split := strings.Split(str, ";;;") + + co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[0]) + co.(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[1]) + co.(*fyne.Container).Objects[2].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[3]) + }) + + menu := widget.NewSelect([]string{"Normal", "Coinbase", "Messages"}, nil) + menu.PlaceHolder = "(Select Transaction Type)" + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + rectList := canvas.NewRectangle(color.Transparent) + rectList.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.60)) + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } + + label := canvas.NewText(view, colors.Account) + label.TextSize = 15 + label.TextStyle = fyne.TextStyle{Bold: true} + + menu.OnChanged = func(s string) { + switch s { + case "Normal": + listBox.UnselectAll() + results.Text = " Scanning..." + results.Refresh() + count := 0 + data = nil + listData.Set(nil) + entries = engram.Disk.Show_Transfers(zeroscid, false, true, true, 0, engram.Disk.Get_Height(), "", "", 0, 0) + + if entries != nil { + go func() { + for e := range entries { + var height string + var direction string + var stamp string + + entries[e].ProcessPayload() + + if !entries[e].Coinbase { + timefmt := entries[e].Time + //stamp = string(timefmt.Format(time.RFC822)) + stamp = timefmt.Format("2006-01-02") + height = strconv.FormatUint(entries[e].Height, 10) + amount := "" + txid = entries[e].TXID + + if !entries[e].Incoming { + direction = "Sent" + amount = "(" + globals.FormatMoney(entries[e].Amount) + ")" + } else { + direction = "Received" + amount = globals.FormatMoney(entries[e].Amount) + } + + count += 1 + data = append(data, direction+";;;"+amount+";;;"+height+";;;"+stamp+";;;"+txid) + } + } + + results.Text = fmt.Sprintf(" Results: %d", count) + + listData.Set(data) + + listBox.OnSelected = func(id widget.ListItemID) { + //var zeroscid crypto.Hash + split := strings.Split(data[id], ";;;") + var zeroscid crypto.Hash + _, result := engram.Disk.Get_Payments_TXID(zeroscid, split[4]) + + if result.TXID == "" { + label.Text = "---" + } else { + label.Text = result.TXID + } + + fyne.Do(func() { + label.Refresh() + }) + + overlay := session.Window.Canvas().Overlays() + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + overlay.Add(layoutHistoryDetail(split[4])) + listBox.UnselectAll() + } + + fyne.Do(func() { + results.Refresh() + listBox.Refresh() + listBox.ScrollToBottom() + }) + }() + } else { + results.Text = fmt.Sprintf(" Results: %d", count) + results.Refresh() + } + case "Coinbase": + listBox.UnselectAll() + results.Text = " Scanning..." + results.Refresh() + count := 0 + data = nil + listData.Set(nil) + entries = engram.Disk.Show_Transfers(zeroscid, true, true, true, 0, engram.Disk.Get_Height(), "", "", 0, 0) + + if entries != nil { + go func() { + for e := range entries { + var height string + var direction string + var stamp string + + entries[e].ProcessPayload() + + if entries[e].Coinbase { + direction = "Network" + timefmt := entries[e].Time + stamp = timefmt.Format("2006-01-02") + height = strconv.FormatUint(entries[e].Height, 10) + amount := globals.FormatMoney(entries[e].Amount) + txid = entries[e].TXID + + count += 1 + data = append(data, direction+";;;"+amount+";;;"+height+";;;"+stamp+";;;"+txid) + } + } + + results.Text = fmt.Sprintf(" Results: %d", count) + + listData.Set(data) + + listBox.OnSelected = func(id widget.ListItemID) { + listBox.UnselectAll() + } + + fyne.Do(func() { + results.Refresh() + listBox.Refresh() + listBox.ScrollToBottom() + }) + }() + } else { + results.Text = fmt.Sprintf(" Results: %d", count) + + fyne.Do(func() { + results.Refresh() + }) + } + case "Messages": + listBox.UnselectAll() + results.Text = " Scanning..." + results.Refresh() + count := 0 + data = nil + listData.Set(nil) + entries = engram.Disk.Get_Payments_DestinationPort(zeroscid, uint64(1337), 0) + + if entries != nil { + go func() { + for e := range entries { + var stamp string + var direction string + var comment string + + entries[e].ProcessPayload() + + timefmt := entries[e].Time + //stamp = string(timefmt.Format(time.RFC822)) + stamp = timefmt.Format("2006-01-02") + + temp := entries[e].Incoming + if !temp { + direction = "Sent " + } else { + direction = "Received" + } + if entries[e].Payload_RPC.HasValue(rpc.RPC_COMMENT, rpc.DataString) { + contact := "" + username := "" + if entries[e].Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) { + contact = entries[e].Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) + if len(contact) > 10 { + username = contact[0:10] + ".." + } else { + username = contact + } + } + + comment = entries[e].Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string) + if len(comment) > 10 { + comment = comment[0:10] + ".." + } + + txid = entries[e].TXID + count += 1 + data = append(data, direction+";;;"+username+";;;"+comment+";;;"+stamp+";;;"+txid+";;;"+contact) + } + } + + results.Text = fmt.Sprintf(" Results: %d", count) + + listData.Set(data) + + listBox.OnSelected = func(id widget.ListItemID) { + split := strings.Split(data[id], ";;;") + overlay := session.Window.Canvas().Overlays() + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + overlay.Add(layoutHistoryDetail(split[4])) + listBox.UnselectAll() + listBox.Refresh() + } + + fyne.Do(func() { + results.Refresh() + listBox.Refresh() + listBox.ScrollToBottom() + }) + }() + } else { + results.Text = fmt.Sprintf(" Results: %d", count) + + fyne.Do(func() { + results.Refresh() + }) + } + default: + + } + } + + center := container.NewStack( + rectWidth, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + menu, + rectSpacer, + results, + rectSpacer, + rectSpacer, + container.NewStack( + rectList, + listBox, + ), + ), + layout.NewSpacer(), + ), + ) + + top := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + heading, + ), + rectSpacer, + rectSpacer, + container.NewCenter( + center, + ), + ) + + bottom := container.NewStack( + container.NewVBox( + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewStack( + frame, + container.NewBorder( + top, + bottom, + nil, + nil, + ), + ) + + return NewVScroll(layout) +} + +func layoutHistoryDetail(txid string) fyne.CanvasObject { + wSpacer := widget.NewLabel(" ") + + rectWidth := canvas.NewRectangle(color.Transparent) + rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, 10)) + + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) + + frame := &iframe{} + + heading := canvas.NewText("T R A N S A C T I O N D E T A I L", colors.Gray) + heading.TextSize = 16 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + labelTXID := canvas.NewText(" TRANSACTION ID", colors.Gray) + labelTXID.TextSize = 14 + labelTXID.Alignment = fyne.TextAlignLeading + labelTXID.TextStyle = fyne.TextStyle{Bold: true} + + labelAmount := canvas.NewText(" AMOUNT", colors.Gray) + labelAmount.TextSize = 14 + labelAmount.Alignment = fyne.TextAlignLeading + labelAmount.TextStyle = fyne.TextStyle{Bold: true} + + labelDirection := canvas.NewText(" PAYMENT DIRECTION", colors.Gray) + labelDirection.TextSize = 14 + labelDirection.Alignment = fyne.TextAlignLeading + labelDirection.TextStyle = fyne.TextStyle{Bold: true} + + labelMember := canvas.NewText("", colors.Gray) + labelMember.TextSize = 14 + labelMember.Alignment = fyne.TextAlignLeading + labelMember.TextStyle = fyne.TextStyle{Bold: true} + + labeliMember := canvas.NewText("", colors.Gray) + labeliMember.TextSize = 14 + labeliMember.Alignment = fyne.TextAlignLeading + labeliMember.TextStyle = fyne.TextStyle{Bold: true} + + labelProof := canvas.NewText(" TRANSACTION PROOF", colors.Gray) + labelProof.TextSize = 14 + labelProof.Alignment = fyne.TextAlignLeading + labelProof.TextStyle = fyne.TextStyle{Bold: true} + + labelDestPort := canvas.NewText(" DESTINATION PORT", colors.Gray) + labelDestPort.TextSize = 14 + labelDestPort.TextStyle = fyne.TextStyle{Bold: true} + + labelSourcePort := canvas.NewText(" SOURCE PORT", colors.Gray) + labelSourcePort.TextSize = 14 + labelSourcePort.TextStyle = fyne.TextStyle{Bold: true} + + labelFees := canvas.NewText(" TRANSACTION FEES", colors.Gray) + labelFees.TextSize = 14 + labelFees.TextStyle = fyne.TextStyle{Bold: true} + + labelPayload := canvas.NewText(" PAYLOAD", colors.Gray) + labelPayload.TextSize = 14 + labelPayload.TextStyle = fyne.TextStyle{Bold: true} + + labelHeight := canvas.NewText(" BLOCK HEIGHT", colors.Gray) + labelHeight.TextSize = 14 + labelHeight.TextStyle = fyne.TextStyle{Bold: true} + + labelReply := canvas.NewText(" REPLY ADDRESS", colors.Gray) + labelReply.TextSize = 14 + labelReply.TextStyle = fyne.TextStyle{Bold: true} + + labelSeparator := widget.NewRichTextFromMarkdown("") + labelSeparator.Wrapping = fyne.TextWrapOff + labelSeparator.ParseMarkdown("---") + + labelSeparator2 := widget.NewRichTextFromMarkdown("") + labelSeparator2.Wrapping = fyne.TextWrapOff + labelSeparator2.ParseMarkdown("---") + + labelSeparator3 := widget.NewRichTextFromMarkdown("") + labelSeparator3.Wrapping = fyne.TextWrapOff + labelSeparator3.ParseMarkdown("---") + + labelSeparator4 := widget.NewRichTextFromMarkdown("") + labelSeparator4.Wrapping = fyne.TextWrapOff + labelSeparator4.ParseMarkdown("---") + + labelSeparator5 := widget.NewRichTextFromMarkdown("") + labelSeparator5.Wrapping = fyne.TextWrapOff + labelSeparator5.ParseMarkdown("---") + + labelSeparator6 := widget.NewRichTextFromMarkdown("") + labelSeparator6.Wrapping = fyne.TextWrapOff + labelSeparator6.ParseMarkdown("---") + + labelSeparator7 := widget.NewRichTextFromMarkdown("") + labelSeparator7.Wrapping = fyne.TextWrapOff + labelSeparator7.ParseMarkdown("---") + + labelSeparator8 := widget.NewRichTextFromMarkdown("") + labelSeparator8.Wrapping = fyne.TextWrapOff + labelSeparator8.ParseMarkdown("---") + + labelSeparator9 := widget.NewRichTextFromMarkdown("") + labelSeparator9.Wrapping = fyne.TextWrapOff + labelSeparator9.ParseMarkdown("---") + + labelSeparator10 := widget.NewRichTextFromMarkdown("") + labelSeparator10.Wrapping = fyne.TextWrapOff + labelSeparator10.ParseMarkdown("---") + + labelSeparator11 := widget.NewRichTextFromMarkdown("") + labelSeparator11.Wrapping = fyne.TextWrapOff + labelSeparator11.ParseMarkdown("---") + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + var zeroscid crypto.Hash + _, details := engram.Disk.Get_Payments_TXID(zeroscid, txid) + + stamp := string(details.Time.Format(time.RFC822)) + height := strconv.FormatUint(details.Height, 10) + + valueMember := widget.NewRichTextFromMarkdown(" ") + valueMember.Wrapping = fyne.TextWrapBreak + + valueiMember := widget.NewRichTextFromMarkdown("--") + valueiMember.Wrapping = fyne.TextWrapBreak + + valueReply := widget.NewRichTextFromMarkdown("--") + valueReply.Wrapping = fyne.TextWrapBreak + + if details.Payload_RPC.HasValue(rpc.RPC_REPLYBACK_ADDRESS, rpc.DataAddress) { + address := details.Payload_RPC.Value(rpc.RPC_REPLYBACK_ADDRESS, rpc.DataAddress).(rpc.Address) + valueReply.ParseMarkdown("" + address.String()) + } else if details.Payload_RPC.HasValue(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString) && details.DestinationPort == 1337 { + address := details.Payload_RPC.Value(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataString).(string) + valueReply.ParseMarkdown("" + address) + } + + valuePayload := widget.NewRichTextFromMarkdown("--") + valuePayload.Wrapping = fyne.TextWrapBreak + + if details.Payload_RPC.HasValue(rpc.RPC_COMMENT, rpc.DataString) { + if details.Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string) != "" { + valuePayload.ParseMarkdown("" + details.Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string)) + } + } + + valueAmount := canvas.NewText("", colors.Account) + valueAmount.TextSize = 22 + valueAmount.TextStyle = fyne.TextStyle{Bold: true} + + valueDirection := canvas.NewText("", colors.Account) + valueDirection.TextSize = 22 + valueDirection.TextStyle = fyne.TextStyle{Bold: true} + if details.Incoming { + valueDirection.Text = " Received" + labelMember.Text = " SENDER ADDRESS" + if details.Sender == "" || details.Sender == engram.Disk.GetAddress().String() { + valueMember.ParseMarkdown("--") + } else { + valueMember.ParseMarkdown("" + details.Sender) + } + + if details.Amount == 0 { + valueAmount.Color = colors.Account + valueAmount.Text = " 0.00000" + } else { + valueAmount.Color = colors.Green + valueAmount.Text = " + " + globals.FormatMoney(details.Amount) + } + } else { + valueDirection.Text = " Sent" + labelMember.Text = " RECEIVER ADDRESS" + valueMember.ParseMarkdown("" + details.Destination) + + if details.Amount == 0 { + valueAmount.Color = colors.Account + valueAmount.Text = " 0.00000" + } else { + valueAmount.Color = colors.Account + valueAmount.Text = " - " + globals.FormatMoney(details.Amount) + } + } + + labeliMember.Text = " INTEGRATED ADDRESS" + var idest string + if details.Destination == "" { + // We are the recipient + idest = engram.Disk.GetAddress().String() + } else { + idest = details.Destination + } + iaddr, _ := rpc.NewAddress(idest) + if iaddr != nil { + var iargs rpc.Arguments + for _, v := range details.Payload_RPC { + if !iargs.HasValue(v.Name, v.DataType) { + // Skip the reply back addr that was injected, but 'reverse' this to be what the original payload was which requests the reply addr + if v.Name == rpc.RPC_REPLYBACK_ADDRESS { + iargs = append(iargs, rpc.Argument{Name: rpc.RPC_NEEDS_REPLYBACK_ADDRESS, DataType: rpc.DataUint64, Value: uint64(1)}) + } else { + iargs = append(iargs, rpc.Argument{Name: v.Name, DataType: v.DataType, Value: v.Value}) + } + } + } + + // If value transfer 'V' doesn't exist, we add it here. + if !iargs.HasValue(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { + iargs = append(iargs, rpc.Argument{Name: rpc.RPC_VALUE_TRANSFER, DataType: rpc.DataUint64, Value: details.Amount}) + } + + iaddr.Arguments = iargs + + // Check to see if integrated addr creation makes an actual integrated addr + if iaddr.String() != details.Destination && iaddr.IsIntegratedAddress() { + valueiMember.ParseMarkdown("" + iaddr.String()) + } + } + + valueTime := canvas.NewText(stamp, colors.Account) + valueTime.TextSize = 14 + valueTime.TextStyle = fyne.TextStyle{Bold: true} + + valueFees := canvas.NewText(" "+globals.FormatMoney(details.Fees), colors.Account) + valueFees.TextSize = 22 + valueFees.TextStyle = fyne.TextStyle{Bold: true} + + valueHeight := canvas.NewText(" "+height, colors.Account) + valueHeight.TextSize = 22 + valueHeight.TextStyle = fyne.TextStyle{Bold: true} + + valueTXID := widget.NewRichTextFromMarkdown("") + valueTXID.Wrapping = fyne.TextWrapBreak + valueTXID.ParseMarkdown("" + txid) + + valuePort := canvas.NewText("", colors.Account) + valuePort.TextSize = 22 + valuePort.TextStyle = fyne.TextStyle{Bold: true} + valuePort.Text = " " + strconv.FormatUint(details.DestinationPort, 10) + + valueSourcePort := canvas.NewText("", colors.Account) + valueSourcePort.TextSize = 22 + valueSourcePort.TextStyle = fyne.TextStyle{Bold: true} + valueSourcePort.Text = " " + strconv.FormatUint(details.SourcePort, 10) + + btnView := widget.NewButton("View in Explorer", nil) + btnView.OnTapped = func() { + if engram.Disk.GetNetwork() { + link, _ := url.Parse("https://explorer.derofoundation.org/tx/" + txid) + _ = fyne.CurrentApp().OpenURL(link) + } else { + link, _ := url.Parse("https://testnetexplorer.derofoundation.org/tx/" + txid) + _ = fyne.CurrentApp().OpenURL(link) + } + } + + linkBack := widget.NewHyperlinkWithStyle("Back to History", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + overlay := session.Window.Canvas().Overlays() + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } + + linkAddress := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkAddress.OnTapped = func() { + a.Clipboard().SetContent(valueMember.String()) + } + + linkiAddress := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkiAddress.OnTapped = func() { + a.Clipboard().SetContent(valueiMember.String()) + } + + linkReplyAddress := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkReplyAddress.OnTapped = func() { + if replyAddress, ok := details.Payload_RPC.Value(rpc.RPC_REPLYBACK_ADDRESS, rpc.DataAddress).(rpc.Address); ok { + a.Clipboard().SetContent(replyAddress.String()) + } + } + + linkTXID := widget.NewHyperlinkWithStyle("Copy Transaction ID", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkTXID.OnTapped = func() { + a.Clipboard().SetContent(txid) + } + + linkProof := widget.NewHyperlinkWithStyle("Copy Transaction Proof", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkProof.OnTapped = func() { + a.Clipboard().SetContent(details.Proof) + } + + linkPayload := widget.NewHyperlinkWithStyle("Copy Payload", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkPayload.OnTapped = func() { + if _, ok := details.Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string); ok { + a.Clipboard().SetContent(details.Payload_RPC.Value(rpc.RPC_COMMENT, rpc.DataString).(string)) + } + } + + top := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + heading, + ), + rectSpacer, + container.NewCenter( + valueTime, + ), + rectSpacer, + rectSpacer, + ) + + center := container.NewStack( + container.NewVScroll( + container.NewStack( + rectWidth, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + rectSpacer, + labelDirection, + rectSpacer, + valueDirection, + rectSpacer, + rectSpacer, + labelSeparator, + rectSpacer, + rectSpacer, + labelAmount, + rectSpacer, + container.NewStack( + rectWidth90, + valueAmount, + ), + rectSpacer, + rectSpacer, + labelSeparator2, + rectSpacer, + rectSpacer, + labelTXID, + rectSpacer, + container.NewStack( + rectWidth90, + valueTXID, + ), + container.NewVBox( + container.NewHBox( + linkTXID, + layout.NewSpacer(), + ), + container.NewHBox( + linkProof, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + labelSeparator3, + rectSpacer, + rectSpacer, + labelMember, + rectSpacer, + valueMember, + container.NewHBox( + linkAddress, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator4, + rectSpacer, + rectSpacer, + labeliMember, + rectSpacer, + valueiMember, + container.NewHBox( + linkiAddress, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator5, + rectSpacer, + rectSpacer, + labelReply, + rectSpacer, + valueReply, + container.NewHBox( + linkReplyAddress, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator6, + rectSpacer, + rectSpacer, + labelHeight, + rectSpacer, + container.NewStack( + rectWidth90, + valueHeight, + ), + rectSpacer, + rectSpacer, + labelSeparator7, + rectSpacer, + rectSpacer, + labelFees, + rectSpacer, + container.NewStack( + rectWidth90, + valueFees, + ), + rectSpacer, + rectSpacer, + labelSeparator8, + rectSpacer, + rectSpacer, + labelPayload, + rectSpacer, + container.NewStack( + rectWidth90, + valuePayload, + ), + container.NewVBox( + container.NewHBox( + linkPayload, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + labelSeparator9, + rectSpacer, + rectSpacer, + labelDestPort, + rectSpacer, + container.NewStack( + rectWidth90, + valuePort, + ), + rectSpacer, + rectSpacer, + labelSeparator10, + rectSpacer, + rectSpacer, + labelSourcePort, + rectSpacer, + container.NewStack( + rectWidth90, + valueSourcePort, + ), + rectSpacer, + rectSpacer, + labelSeparator11, + rectSpacer, + rectSpacer, + btnView, + wSpacer, + ), + layout.NewSpacer(), + ), + ), + ), + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewStack( + frame, + container.NewBorder( + top, + bottom, + nil, + center, + ), + ) + + return layout +} + +func layoutDatapad() fyne.CanvasObject { + session.Domain = "app.datapad" + title := canvas.NewText("D A T A P A D", colors.Gray) + title.TextStyle = fyne.TextStyle{Bold: true} + title.TextSize = 16 + + heading := canvas.NewText("", colors.Green) + heading.TextSize = 22 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + entryNewPad := widget.NewEntry() + entryNewPad.MultiLine = false + entryNewPad.Wrapping = fyne.TextWrap(fyne.TextTruncateClip) + + btnAdd := widget.NewButton(" Create ", nil) + btnAdd.Disable() + btnAdd.OnTapped = func() { + err := StoreEncryptedValue("Datapads", []byte(entryNewPad.Text), []byte("")) + if err != nil { + btnAdd.Text = "Error creating new Datapad" + btnAdd.Disable() + btnAdd.Refresh() + } else { + session.Datapad = entryNewPad.Text + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDatapad()) + removeOverlays() + } + } + + entryNewPad.PlaceHolder = "Datapad Name" + entryNewPad.Validator = func(s string) error { + session.Datapad = s + if len(s) > 0 { + _, err := GetEncryptedValue("Datapads", []byte(s)) + if err == nil { + btnAdd.Text = "Datapad already exists" + btnAdd.Disable() + btnAdd.Refresh() + err := errors.New("username already exists") + entryNewPad.SetValidationError(err) + return err + } else { + btnAdd.Text = "Create" + btnAdd.Enable() + btnAdd.Refresh() + return nil + } + } else { + btnAdd.Text = "Create" + btnAdd.Disable() + err := errors.New("please enter a datapad name") + entryNewPad.SetValidationError(err) + btnAdd.Refresh() + return err + } + } + entryNewPad.OnChanged = func(s string) { + entryNewPad.Validate() + } + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } + + frame := &iframe{} + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + rectList := canvas.NewRectangle(color.Transparent) + rectList.SetMinSize(fyne.NewSize(ui.Width, 35)) + rectListBox := canvas.NewRectangle(color.Transparent) + rectListBox.SetMinSize(fyne.NewSize(ui.Width, 350)) + + var padData []string + + shard, err := GetShard() + if err != nil { + padData = []string{} + } + + store, err := graviton.NewDiskStore(shard) + if err != nil { + padData = []string{} + } + + ss, err := store.LoadSnapshot(0) + + if err != nil { + padData = []string{} + } + + tree, err := ss.GetTree("Datapads") + if err != nil { + padData = []string{} + } + + cursor := tree.Cursor() + + for k, _, err := cursor.First(); err == nil; k, _, err = cursor.Next() { + if string(k) != "" { + padData = append(padData, string(k)) + } + } + + padList := binding.BindStringList(&padData) + + padBox := widget.NewListWithData(padList, + func() fyne.CanvasObject { + c := container.NewVBox( + widget.NewLabel(""), + ) + return c + }, + func(di binding.DataItem, co fyne.CanvasObject) { + dat := di.(binding.String) + str, err := dat.Get() + if err != nil { + return + } + + co.(*fyne.Container).Objects[0].(*widget.Label).SetText(str) + co.(*fyne.Container).Objects[0].(*widget.Label).Wrapping = fyne.TextWrapWord + co.(*fyne.Container).Objects[0].(*widget.Label).TextStyle.Bold = false + co.(*fyne.Container).Objects[0].(*widget.Label).Alignment = fyne.TextAlignLeading + }) + + padBox.OnSelected = func(id widget.ListItemID) { + session.Datapad = padData[id] + overlay := session.Window.Canvas().Overlays() + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + overlay.Add( + container.NewStack( + &iframe{}, + layoutPad(), + ), + ) + overlay.Top().Show() + padBox.UnselectAll() + padBox.Refresh() + } + + shardForm := container.NewVBox( + rectSpacer, + rectSpacer, + rectSpacer, + container.NewCenter(container.NewVBox(title, rectSpacer)), + rectSpacer, + rectSpacer, + container.NewStack( + rectListBox, + padBox, + ), + rectSpacer, + entryNewPad, + rectSpacer, + btnAdd, + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ) + + gridItem1 := container.NewCenter( + shardForm, + ) + + features := container.NewCenter( + layout.NewSpacer(), + gridItem1, + layout.NewSpacer(), + ) + + subContainer := container.NewStack( + container.NewVBox( + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + c := container.NewBorder( + features, + subContainer, + nil, + nil, + ) + + layout := container.NewStack( + frame, + c, + ) + + return NewVScroll(layout) +} + +func layoutPad() fyne.CanvasObject { + rectWidth := canvas.NewRectangle(color.Transparent) + rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth, 10)) + + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) + + rectEntry := canvas.NewRectangle(color.Transparent) + rectEntry.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.52)) + + heading := canvas.NewText(session.Datapad, colors.Green) + heading.TextSize = 20 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + selectOptions := widget.NewSelect([]string{"Clear", "Export (Plaintext)", "Import From File", "Delete"}, nil) + selectOptions.PlaceHolder = "Select an Option ..." + + data, err := GetEncryptedValue("Datapads", []byte(session.Datapad)) + if err != nil { + data = nil + } + + overlay := session.Window.Canvas().Overlays() + + btnSave := widget.NewButton("Save", nil) + + entryPad := widget.NewEntry() + entryPad.Wrapping = fyne.TextWrapWord + + errorText := canvas.NewText(" ", colors.Green) + errorText.TextSize = 12 + errorText.Alignment = fyne.TextAlignCenter + + selectOptions.OnChanged = func(s string) { + errorText.Text = "" + errorText.Refresh() + + if s == "Clear" { + header := canvas.NewText("DATAPAD RESET REQUESTED", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + subHeader := canvas.NewText("Clear Datapad?", colors.Account) + subHeader.TextSize = 22 + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + + linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + overlay := session.Window.Canvas().Overlays() + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + selectOptions.Selected = "Select an Option ..." + selectOptions.Refresh() + } + + btnSubmit := widget.NewButton("Clear", nil) + + btnSubmit.OnTapped = func() { + if session.Datapad != "" { + err := StoreEncryptedValue("Datapads", []byte(session.Datapad), []byte("")) + if err != nil { + logger.Errorf("[Datapad] Err: %s\n", err) + selectOptions.Selected = "Select an Option ..." + selectOptions.Refresh() + return + } + + selectOptions.Selected = "Select an Option ..." + selectOptions.Refresh() + entryPad.Text = "" + entryPad.Refresh() + } + + errorText.Text = "datapad cleared" + errorText.Color = colors.Green + errorText.Refresh() + + overlay := session.Window.Canvas().Overlays() + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + selectOptions.Selected = "Select an Option ..." + selectOptions.Refresh() + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + subHeader, + widget.NewLabel(""), + btnSubmit, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + } else if s == "Export (Plaintext)" { + selectOptions.Selected = "Select an Option ..." + selectOptions.Refresh() + + dialogFileSave := dialog.NewFileSave(func(uri fyne.URIWriteCloser, err error) { + if err != nil { + logger.Errorf("[Engram] File dialog: %s\n", err) + errorText.Text = "could not export datapad" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if uri == nil { + return // Canceled + } + + data := []byte(entryPad.Text) + _, err = writeToURI(data, uri) + if err != nil { + logger.Errorf("[Engram] Exporting datapad %s: %s\n", session.Datapad, err) + errorText.Text = "error exporting datapad" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + errorText.Text = "exported datapad successfully" + errorText.Color = colors.Green + errorText.Refresh() + + }, session.Window) + + if !a.Driver().Device().IsMobile() { + // Open file browser in current directory + uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) + if err == nil { + dialogFileSave.SetLocation(uri) + } else { + logger.Errorf("[Engram] Could not open current directory %s\n", err) + } + } + + // dialogFileSave.SetFilter(storage.NewMimeTypeFileFilter([]string{"text/*"})) + dialogFileSave.SetView(dialog.ListView) + dialogFileSave.SetFileName(fmt.Sprintf("%s.txt", session.Datapad)) + dialogFileSave.Resize(fyne.NewSize(ui.Width, ui.Height)) + dialogFileSave.Show() + } else if s == "Import From File" { + selectOptions.Selected = "Select an Option ..." + selectOptions.Refresh() + + dialogFileImport := dialog.NewFileOpen(func(uri fyne.URIReadCloser, err error) { + if err != nil { + logger.Errorf("[Engram] File dialog: %s\n", err) + errorText.Text = "could not import file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if uri == nil { + return // Canceled + } + + fileName := uri.URI().String() + if !strings.Contains(uri.URI().MimeType(), "text/") { + logger.Errorf("[Engram] Cannot import file %s\n", fileName) + errorText.Text = "cannot import file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if a.Driver().Device().IsMobile() { + fileName = uri.URI().Name() + } else { + fileName = filepath.Base(strings.Replace(fileName, "file://", "", -1)) + } + + filedata, err := readFromURI(uri) + if err != nil { + logger.Errorf("[Engram] Cannot read URI file data for %s: %s\n", fileName, err) + errorText.Text = "cannot read file data" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if !isASCII(string(filedata)) { + errorText.Text = "invalid file data" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if entryPad.Text == "" { + entryPad.SetText(string(filedata)) + } else { + entryPad.SetText(fmt.Sprintf("%s\n\n%s", entryPad.Text, string(filedata))) + } + + errorText.Text = "file data imported successfully" + errorText.Color = colors.Green + errorText.Refresh() + + }, session.Window) + + if !a.Driver().Device().IsMobile() { + // Open file browser in current directory + uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) + if err == nil { + dialogFileImport.SetLocation(uri) + } else { + logger.Errorf("[Engram] Could not open current directory %s\n", err) + } + } + + // dialogFileSave.SetFilter(storage.NewMimeTypeFileFilter([]string{"text/*"})) + dialogFileImport.SetView(dialog.ListView) + dialogFileImport.Resize(fyne.NewSize(ui.Width, ui.Height)) + dialogFileImport.Show() + } else if s == "Delete" { + header := canvas.NewText("DATAPAD DELETION REQUESTED", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + subHeader := canvas.NewText("Delete Datapad?", colors.Account) + subHeader.TextSize = 22 + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + + linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + overlay := session.Window.Canvas().Overlays() + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + selectOptions.Selected = "Select an Option ..." + selectOptions.Refresh() + } + + btnSubmit := widget.NewButton("Delete", nil) + + btnSubmit.OnTapped = func() { + if session.Datapad != "" { + err := DeleteKey("Datapads", []byte(session.Datapad)) + if err != nil { + selectOptions.Selected = "Select an Option ..." + selectOptions.Refresh() + logger.Errorf("[Datapad] Error deleting %s: %s\n", session.Datapad, err) + } else { + session.Datapad = "" + session.DatapadChanged = false + removeOverlays() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDatapad()) + } + } + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + subHeader, + widget.NewLabel(""), + btnSubmit, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + } else { + session.Datapad = "" + session.DatapadChanged = false + overlay := session.Window.Canvas().Overlays() + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + selectOptions.Selected = "Select an Option ..." + selectOptions.Refresh() + } + } + + btnSave.OnTapped = func() { + err = StoreEncryptedValue("Datapads", []byte(session.Datapad), []byte(entryPad.Text)) + if err != nil { + btnSave.Disable() + errorText.Text = "- FAILED -" + errorText.Color = colors.Red + errorText.Refresh() + } else { + session.DatapadChanged = false + btnSave.Disable() + heading.Text = session.Datapad + heading.Refresh() + errorText.Text = "- SAVED -" + errorText.Color = colors.Green + errorText.Refresh() + } + } + + session.DatapadChanged = false + + btnSave.Disable() + + entryPad.MultiLine = true + entryPad.Text = string(data) + entryPad.OnChanged = func(s string) { + errorText.Text = "" + errorText.Refresh() + session.DatapadChanged = true + heading.Text = session.Datapad + "*" + heading.Refresh() + btnSave.Enable() + } + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to Datapad", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + if session.DatapadChanged { + header := canvas.NewText("DATAPAD CHANGE DETECTED", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + subHeader := canvas.NewText("Save Datapad?", colors.Account) + subHeader.TextSize = 22 + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + + linkClose := widget.NewHyperlinkWithStyle("Discard Changes", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + session.Datapad = "" + session.DatapadChanged = false + removeOverlays() + } + + btnSubmit := widget.NewButton("Save", nil) + + btnSubmit.OnTapped = func() { + err = StoreEncryptedValue("Datapads", []byte(session.Datapad), []byte(entryPad.Text)) + if err != nil { + btnSave.Disable() + errorText.Text = "error saving datapad" + errorText.Color = colors.Red + errorText.Refresh() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } else { + session.Datapad = "" + session.DatapadChanged = false + removeOverlays() + } + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + subHeader, + widget.NewLabel(""), + btnSubmit, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + } else { + session.Datapad = "" + session.DatapadChanged = false + overlay := session.Window.Canvas().Overlays() + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } + } + + top := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + heading, + ), + rectSpacer, + container.NewCenter( + container.NewStack( + rectWidth90, + selectOptions, + ), + ), + rectSpacer, + ) + + center := container.NewStack( + rectWidth, + container.NewCenter( + container.NewVBox( + container.NewStack( + rectEntry, + entryPad, + ), + rectSpacer, + errorText, + rectSpacer, + btnSave, + rectSpacer, + ), + ), + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewBorder( + top, + bottom, + nil, + nil, + center, + ) + + return NewVScroll(layout) +} + +func layoutAccount() fyne.CanvasObject { + rectWidth := canvas.NewRectangle(color.Transparent) + rectWidth.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, 10)) + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) + rectBox := canvas.NewRectangle(color.Transparent) + rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.80)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(10, 5)) + + title := canvas.NewText("M Y A C C O U N T", colors.Gray) + title.TextStyle = fyne.TextStyle{Bold: true} + title.TextSize = 16 + + heading := canvas.NewText(engram.Disk.GetAddress().String()[0:5]+"..."+engram.Disk.GetAddress().String()[len(engram.Disk.GetAddress().String())-10:len(engram.Disk.GetAddress().String())], colors.Green) + heading.TextSize = 22 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + labelPassword := canvas.NewText("N E W P A S S W O R D", colors.Gray) + labelPassword.TextStyle = fyne.TextStyle{Bold: true} + labelPassword.TextSize = 11 + labelPassword.Alignment = fyne.TextAlignCenter + + labelDatashard := canvas.NewText("D A T A S H A R D", colors.Gray) + labelDatashard.TextStyle = fyne.TextStyle{Bold: true} + labelDatashard.TextSize = 11 + labelDatashard.Alignment = fyne.TextAlignCenter + + headerDatashard := canvas.NewText("DATASHARD ID", colors.Gray) + headerDatashard.TextSize = 16 + headerDatashard.Alignment = fyne.TextAlignCenter + headerDatashard.TextStyle = fyne.TextStyle{Bold: true} + + address := engram.Disk.GetAddress().String() + shardID := fmt.Sprintf("%x", sha1.Sum([]byte(address))) + + textDatashard := widget.NewRichTextFromMarkdown("### " + shardID) + textDatashard.Wrapping = fyne.TextWrapWord + + textDatashardDesc := widget.NewRichTextFromMarkdown("Datashards hold encrypted data and stores it locally on your device. Each datashard is unique and can only be decrypted by the account it is associated with. Examples of data stored include:") + textDatashardDesc.Wrapping = fyne.TextWrapWord + + textDatashardDesc2 := widget.NewRichTextFromMarkdown("* Datapad entries\n* Saved search history\n* Asset scan results\n* Account settings") + textDatashardDesc2.Wrapping = fyne.TextWrapWord + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + labelRecovery := canvas.NewText("A C C O U N T O P T I O N S", colors.Gray) + labelRecovery.TextSize = 11 + labelRecovery.Alignment = fyne.TextAlignCenter + labelRecovery.TextStyle = fyne.TextStyle{Bold: true} + + labelEpoch := canvas.NewText("E P O C H", colors.Gray) + labelEpoch.TextSize = 11 + labelEpoch.Alignment = fyne.TextAlignCenter + labelEpoch.TextStyle = fyne.TextStyle{Bold: true} + + spacerEpoch := canvas.NewRectangle(color.Transparent) + spacerEpoch.SetMinSize(fyne.NewSize(140, 0)) + + wEpoch := widget.NewSelect([]string{"Session", "Total"}, nil) + wEpoch.SetSelected("Session") + + epochSession, _ := epoch.GetSession(time.Second * 4) + + labelEpochHashes := widget.NewRichTextFromMarkdown("### Hashes") + labelEpochHashes.Wrapping = fyne.TextWrapWord + + epochHashes := fmt.Sprintf("%.1fK", float64(epochSession.Hashes)/1000) + textEpochHashes := widget.NewRichTextFromMarkdown(epochHashes) + textEpochHashes.Wrapping = fyne.TextWrapWord + + labelEpochBlocks := widget.NewRichTextFromMarkdown("### Miniblocks") + labelEpochBlocks.Wrapping = fyne.TextWrapWord + + epochBlocks := fmt.Sprintf("%d", epochSession.MiniBlocks) + textEpochBlocks := widget.NewRichTextFromMarkdown(epochBlocks) + textEpochBlocks.Wrapping = fyne.TextWrapWord + + wEpoch.OnChanged = func(s string) { + epochSession, _ := epoch.GetSession(time.Second * 4) + if s == "Total" { + total := epoch.GetSessionEPOCH_Result{ + Hashes: cyberdeck.EPOCH.total.Hashes, + MiniBlocks: cyberdeck.EPOCH.total.MiniBlocks, + } + + if epoch.IsActive() { + total.Hashes += epochSession.Hashes + total.MiniBlocks += epochSession.MiniBlocks + } + + textEpochHashes.ParseMarkdown(epoch.HashesToString(total.Hashes)) + textEpochBlocks.ParseMarkdown(fmt.Sprintf("%d", total.MiniBlocks)) + + return + } + + textEpochHashes.ParseMarkdown(epoch.HashesToString(epochSession.Hashes)) + textEpochBlocks.ParseMarkdown(fmt.Sprintf("%d", epochSession.MiniBlocks)) + } + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + formEpoch := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + labelEpoch, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + container.NewVBox( + rectSpacer, + wEpoch, + container.NewHBox( + container.NewStack( + spacerEpoch, + labelEpochHashes, + ), + container.NewStack( + spacerEpoch, + textEpochHashes, + ), + ), + container.NewHBox( + container.NewStack( + spacerEpoch, + labelEpochBlocks, + ), + container.NewStack( + spacerEpoch, + textEpochBlocks, + ), + ), + ), + ), + layout.NewSpacer(), + ), + ), + ) + + if session.Offline { + formEpoch.Hide() + } + + linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } + + linkCopyAddress := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkCopyAddress.OnTapped = func() { + a.Clipboard().SetContent(engram.Disk.GetAddress().String()) + } + + btnClear := widget.NewButton("Delete Datashard", nil) + btnClear.OnTapped = func() { + header := canvas.NewText("DATASHARD DELETION REQUESTED", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + subHeader := canvas.NewText("Are you sure?", colors.Account) + subHeader.TextSize = 22 + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + + linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + session.Datapad = "" + session.DatapadChanged = false + removeOverlays() + } + + btnSubmit := widget.NewButton("Delete Datashard", nil) + + btnSubmit.OnTapped = func() { + err := cleanWalletData() + if err != nil { + btnSubmit.Text = "Error deleting datashard" + btnSubmit.Disable() + btnSubmit.Refresh() + } else { + btnSubmit.Text = "Deletion successful!" + btnSubmit.Disable() + btnSubmit.Refresh() + removeOverlays() + } + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay := session.Window.Canvas().Overlays() + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + subHeader, + widget.NewLabel(""), + btnSubmit, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + } + + errorText := canvas.NewText(" ", colors.Green) + errorText.TextSize = 12 + errorText.Alignment = fyne.TextAlignCenter + + optionsList := []string{"Recovery Words (Seed)", "Recovery Hex Keys", "Change Password", "Export Wallet File"} + selectOptions := widget.NewSelect(optionsList, nil) + selectOptions.PlaceHolder = "(Select one)" + + selectOptions.OnChanged = func(s string) { + errorText.Text = "" + errorText.Refresh() + + if s == "Recovery Words (Seed)" { + overlay := session.Window.Canvas().Overlays() + + header := canvas.NewText("ACCOUNT VERIFICATION REQUIRED", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + subHeader := canvas.NewText("Confirm Password", colors.Account) + subHeader.TextSize = 22 + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + + linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + overlay := session.Window.Canvas().Overlays() + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + selectOptions.ClearSelected() + } + + btnConfirm := widget.NewButton("Submit", nil) + + entryPassword := NewReturnEntry() + entryPassword.Password = true + entryPassword.PlaceHolder = "Password" + entryPassword.OnChanged = func(s string) { + if s == "" { + btnConfirm.Text = "Submit" + btnConfirm.Disable() + btnConfirm.Refresh() + } else { + btnConfirm.Text = "Submit" + btnConfirm.Enable() + btnConfirm.Refresh() + } + } + + btnConfirm.OnTapped = func() { + selectOptions.ClearSelected() + if engram.Disk.Check_Password(entryPassword.Text) { + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + layoutRecovery(), + ) + } else { + btnConfirm.Text = "Invalid Password..." + btnConfirm.Disable() + btnConfirm.Refresh() + } + } + + entryPassword.OnReturn = btnConfirm.OnTapped + + btnConfirm.Disable() + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + subHeader, + widget.NewLabel(""), + container.NewCenter( + container.NewStack( + span, + entryPassword, + ), + ), + rectSpacer, + rectSpacer, + btnConfirm, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + + session.Window.Canvas().Focus(entryPassword) + + } else if s == "Recovery Hex Keys" { + overlay := session.Window.Canvas().Overlays() + + header := canvas.NewText("ACCOUNT VERIFICATION REQUIRED", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + subHeader := canvas.NewText("Confirm Password", colors.Account) + subHeader.TextSize = 22 + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + + linkClose := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + overlay := session.Window.Canvas().Overlays() + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + selectOptions.SetSelected("(Select One)") + } + + btnConfirm := widget.NewButton("Submit", nil) + + entryPassword := NewReturnEntry() + entryPassword.Password = true + entryPassword.PlaceHolder = "Password" + entryPassword.OnChanged = func(s string) { + if s == "" { + btnConfirm.Text = "Submit" + btnConfirm.Disable() + btnConfirm.Refresh() + } else { + btnConfirm.Text = "Submit" + btnConfirm.Enable() + btnConfirm.Refresh() + } + } + + btnConfirm.OnTapped = func() { + selectOptions.ClearSelected() + if engram.Disk.Check_Password(entryPassword.Text) { + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + layoutRecoveryHex(), + ) + } else { + btnConfirm.Text = "Invalid Password..." + btnConfirm.Disable() + btnConfirm.Refresh() + } + } + + entryPassword.OnReturn = btnConfirm.OnTapped + + btnConfirm.Disable() + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + subHeader, + widget.NewLabel(""), + container.NewCenter( + container.NewStack( + span, + entryPassword, + ), + ), + rectSpacer, + rectSpacer, + btnConfirm, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + + session.Window.Canvas().Focus(entryPassword) + + } else if s == "Change Password" { + overlay := session.Window.Canvas().Overlays() + + header := canvas.NewText("ACCOUNT AUTHORIZATION REQUEST", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + subHeader := canvas.NewText("Change Password", colors.Account) + subHeader.TextSize = 22 + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + + linkClose := widget.NewHyperlinkWithStyle("Close", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + overlay := session.Window.Canvas().Overlays() + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + selectOptions.ClearSelected() + } + + btnChange := widget.NewButton("Submit", nil) + btnChange.Disable() + + curPass := widget.NewEntry() + curPass.Password = true + curPass.PlaceHolder = "Current Password" + curPass.OnChanged = func(s string) { + btnChange.Text = "Submit" + btnChange.Enable() + btnChange.Refresh() + } + + newPass := widget.NewEntry() + newPass.Password = true + newPass.PlaceHolder = "New Password" + newPass.OnChanged = func(s string) { + btnChange.Text = "Submit" + btnChange.Enable() + btnChange.Refresh() + } + + confirm := widget.NewEntry() + confirm.Password = true + confirm.PlaceHolder = "Confirm Password" + confirm.OnChanged = func(s string) { + btnChange.Text = "Submit" + btnChange.Enable() + btnChange.Refresh() + } + + btnChange.OnTapped = func() { + if engram.Disk.Check_Password(curPass.Text) { + if newPass.Text == confirm.Text && newPass.Text != "" { + err := engram.Disk.Set_Encrypted_Wallet_Password(newPass.Text) + if err != nil { + btnChange.Text = "Error changing password" + btnChange.Disable() + btnChange.Refresh() + } else { + curPass.Text = "" + curPass.Refresh() + newPass.Text = "" + newPass.Refresh() + confirm.Text = "" + confirm.Refresh() + btnChange.Text = "Password Updated" + btnChange.Disable() + btnChange.Refresh() + engram.Disk.Save_Wallet() + } + } else { + btnChange.Text = "Passwords do not match" + btnChange.Disable() + btnChange.Refresh() + } + } else { + btnChange.Text = "Incorrect password entered" + btnChange.Disable() + btnChange.Refresh() + } + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + subHeader, + widget.NewLabel(""), + container.NewCenter( + container.NewStack( + span, + curPass, + ), + ), + widget.NewLabel(""), + widget.NewSeparator(), + widget.NewLabel(""), + newPass, + rectSpacer, + confirm, + rectSpacer, + rectSpacer, + btnChange, + widget.NewLabel(""), + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + + } else if s == "Export Wallet File" { + verificationOverlay( + true, + "", + "", + "", + func(b bool) { + if b { + go func() { + dialogFileSave := dialog.NewFileSave(func(uri fyne.URIWriteCloser, err error) { + if err != nil { + logger.Errorf("[Engram] File dialog: %s\n", err) + fyne.Do(func() { + errorText.Text = "could not export wallet file" + errorText.Color = colors.Red + errorText.Refresh() + }) + + return + } + + if uri == nil { + return // Canceled + } + + data, err := os.ReadFile(session.Path) + if err != nil { + logger.Errorf("[Engram] Reading wallet file %s: %s\n", session.Path, err) + fyne.Do(func() { + errorText.Text = "error reading wallet file" + errorText.Color = colors.Red + errorText.Refresh() + }) + + return + } + + _, err = writeToURI(data, uri) + if err != nil { + logger.Errorf("[Engram] Exporting %s: %s\n", session.Path, err) + fyne.Do(func() { + errorText.Text = "error exporting wallet file" + errorText.Color = colors.Red + errorText.Refresh() + }) + + return + } + + fyne.Do(func() { + errorText.Text = "exported wallet file successfully" + errorText.Color = colors.Green + errorText.Refresh() + }) + + }, session.Window) + + if !a.Driver().Device().IsMobile() { + // Open file browser in current directory + uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) + if err == nil { + dialogFileSave.SetLocation(uri) + } else { + logger.Errorf("[Engram] Could not open current directory %s\n", err) + } + } + + fyne.Do(func() { + dialogFileSave.SetFilter(storage.NewExtensionFileFilter([]string{".db"})) + dialogFileSave.SetView(dialog.ListView) + dialogFileSave.SetFileName(filepath.Base(session.Path)) + dialogFileSave.Resize(fyne.NewSize(ui.Width, ui.Height)) + dialogFileSave.Show() + }) + }() + } + }, + ) + } + } + + var imageQR *canvas.Image + + qr, err := qrcode.New(engram.Disk.GetAddress().String(), qrcode.Highest) + if err != nil { + + } else { + qr.BackgroundColor = colors.DarkMatter + qr.ForegroundColor = colors.Green + } + + imageQR = canvas.NewImageFromImage(qr.Image(int(ui.Width * 0.65))) + imageQR.SetMinSize(fyne.NewSize(ui.Width*0.65, ui.Width*0.65)) + + features := container.NewStack( + rectBox, + container.NewVScroll( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + container.NewVBox( + title, + rectSpacer, + ), + ), + rectSpacer, + heading, + container.NewHBox( + layout.NewSpacer(), + linkCopyAddress, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + container.NewStack( + container.NewCenter( + imageQR, + ), + ), + widget.NewLabel(""), + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + labelRecovery, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + selectOptions, + rectWidth90, + errorText, + ), + layout.NewSpacer(), + ), + ), + formEpoch, + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + labelDatashard, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + container.NewVBox( + container.NewStack( + layout.NewSpacer(), + headerDatashard, + layout.NewSpacer(), + ), + rectSpacer, + container.NewStack( + layout.NewSpacer(), + textDatashard, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + widget.NewSeparator(), + rectSpacer, + rectSpacer, + container.NewStack( + layout.NewSpacer(), + textDatashardDesc, + layout.NewSpacer(), + ), + rectSpacer, + container.NewStack( + layout.NewSpacer(), + textDatashardDesc2, + layout.NewSpacer(), + ), + ), + ), + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + btnClear, + ), + layout.NewSpacer(), + ), + ), + widget.NewLabel(""), + ), + ), + ) + + bottom := container.NewStack( + container.NewVBox( + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewBorder( + features, + bottom, + nil, + nil, + ) + + return NewVScroll(layout) +} + +func layoutRecovery() fyne.CanvasObject { + wSpacer := widget.NewLabel(" ") + heading := canvas.NewText("Recovery Words", colors.Green) + heading.TextSize = 22 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + rectStatus := canvas.NewRectangle(color.Transparent) + rectStatus.SetMinSize(fyne.NewSize(10, 10)) + + rectHeader := canvas.NewRectangle(color.Transparent) + rectHeader.SetMinSize(fyne.NewSize(ui.Width, 10)) + + linkCancel := widget.NewHyperlinkWithStyle("Back to My Account", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkCancel.OnTapped = func() { + removeOverlays() + } + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(ui.Width, 5)) + + grid := container.NewVBox() + grid.Objects = nil + + header := container.NewVBox( + rectSpacer, + rectSpacer, + heading, + rectSpacer, + rectSpacer, + ) + + footer := container.NewVBox( + wSpacer, + container.NewHBox( + layout.NewSpacer(), + linkCancel, + layout.NewSpacer(), + ), + wSpacer, + ) + + body := widget.NewLabel("Please save the following 25 recovery words in a safe place. Never share them with anyone.") + body.Wrapping = fyne.TextWrapWord + body.Alignment = fyne.TextAlignCenter + body.TextStyle = fyne.TextStyle{Bold: true} + + btnCopySeed := widget.NewButton("Copy Recovery Words", nil) + + form := container.NewVBox( + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectHeader, + body, + ), + layout.NewSpacer(), + ), + wSpacer, + container.NewCenter(grid), + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectHeader, + btnCopySeed, + ), + layout.NewSpacer(), + ), + rectSpacer, + ) + + scrollBox := container.NewVScroll( + container.NewStack( + form, + ), + ) + scrollBox.SetMinSize(fyne.NewSize(ui.MaxWidth, ui.Height*0.74)) + + formatted := strings.Split(engram.Disk.GetSeed(), " ") + + rect := canvas.NewRectangle(color.RGBA{19, 25, 34, 255}) + rect.SetMinSize(fyne.NewSize(ui.Width, 25)) + + for i := 0; i < len(formatted); i++ { + pos := fmt.Sprintf("%d", i+1) + word := strings.ReplaceAll(formatted[i], " ", "") + grid.Add(container.NewStack( + rect, + container.NewHBox( + widget.NewLabel(" "), + widget.NewLabelWithStyle(pos, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), + layout.NewSpacer(), + widget.NewLabelWithStyle(word, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + widget.NewLabel(" "), + ), + ), + ) + } + + btnCopySeed.OnTapped = func() { + a.Clipboard().SetContent(engram.Disk.GetSeed()) + } + + layout := container.NewStack( + &iframe{}, + container.NewVBox( + header, + scrollBox, + footer, + ), + ) + + return layout +} + +func layoutRecoveryHex() fyne.CanvasObject { + wSpacer := widget.NewLabel(" ") + heading := canvas.NewText("Recovery Hex Keys", colors.Green) + heading.TextSize = 22 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + rectStatus := canvas.NewRectangle(color.Transparent) + rectStatus.SetMinSize(fyne.NewSize(10, 10)) + + rectHeader := canvas.NewRectangle(color.Transparent) + rectHeader.SetMinSize(fyne.NewSize(ui.Width, 10)) + + linkCancel := widget.NewHyperlinkWithStyle("Back to My Account", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkCancel.OnTapped = func() { + removeOverlays() + } + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(ui.Width, 5)) + + grid := container.NewVBox() + grid.Objects = nil + + header := container.NewVBox( + rectSpacer, + rectSpacer, + heading, + rectSpacer, + rectSpacer, + ) + + footer := container.NewVBox( + wSpacer, + container.NewHBox( + layout.NewSpacer(), + linkCancel, + layout.NewSpacer(), + ), + wSpacer, + ) + + body := widget.NewLabel("Please save the following hex secret key in a safe place. Never share your secret key with anyone.") + body.Wrapping = fyne.TextWrapWord + body.Alignment = fyne.TextAlignCenter + body.TextStyle = fyne.TextStyle{Bold: true} + + form := container.NewVBox( + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectHeader, + body, + ), + layout.NewSpacer(), + ), + wSpacer, + container.NewCenter(grid), + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectHeader, + ), + layout.NewSpacer(), + ), + rectSpacer, + ) + + scrollBox := container.NewVScroll( + container.NewStack( + form, + ), + ) + scrollBox.SetMinSize(fyne.NewSize(ui.MaxWidth, ui.Height*0.74)) + + keys := engram.Disk.Get_Keys() + key := fmt.Sprintf("0000000000000000000000000000000000000000000000%s", keys.Secret.Text(16)) + secret := key[len(key)-64:] + public := keys.Public.StringHex() + + textSecret := widget.NewRichTextFromMarkdown(secret) + textSecret.Wrapping = fyne.TextWrapWord + + linkCopySecret := widget.NewHyperlinkWithStyle("Copy Secret Key", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + + textPublic := widget.NewRichTextFromMarkdown(public) + textPublic.Wrapping = fyne.TextWrapWord + + linkCopyPublic := widget.NewHyperlinkWithStyle("Copy Public Key", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + + labelSecret := canvas.NewText(" SECRET KEY", colors.Gray) + labelSecret.TextSize = 14 + labelSecret.Alignment = fyne.TextAlignLeading + labelSecret.TextStyle = fyne.TextStyle{Bold: true} + + labelPublic := canvas.NewText(" PUBLIC KEY", colors.Gray) + labelPublic.TextSize = 14 + labelPublic.Alignment = fyne.TextAlignLeading + labelPublic.TextStyle = fyne.TextStyle{Bold: true} + + labelSeparator := widget.NewRichTextFromMarkdown("") + labelSeparator.Wrapping = fyne.TextWrapOff + labelSeparator.ParseMarkdown("---") + + grid.Add(container.NewVBox( + labelSecret, + rectSpacer, + textSecret, + rectSpacer, + container.NewHBox( + linkCopySecret, + ), + rectSpacer, + rectSpacer, + labelSeparator, + rectSpacer, + rectSpacer, + rectSpacer, + )) + + grid.Add(container.NewVBox( + labelPublic, + rectSpacer, + textPublic, + rectSpacer, + container.NewHBox( + linkCopyPublic, + ), + )) + + linkCopySecret.OnTapped = func() { + a.Clipboard().SetContent(secret) + } + + linkCopyPublic.OnTapped = func() { + a.Clipboard().SetContent(public) + } + + layout := container.NewStack( + &iframe{}, + container.NewVBox( + header, + scrollBox, + footer, + ), + ) + + return layout +} + +func layoutFrame() fyne.CanvasObject { + entry := widget.NewEntry() + layout := container.NewStack(entry) + + resizeWindow(ui.MaxWidth, ui.MaxHeight) + session.Window.SetContent(layout) + session.Window.SetFixedSize(false) + + go func() { + time.Sleep(time.Second * 2) + removeOverlays() + + ui.MaxWidth = entry.Size().Width + ui.MaxHeight = entry.Size().Height + lastOrientation := a.Driver().Device().Orientation() + initialOrientationVertical := fyne.IsVertical(lastOrientation) + + ui.Width = ui.MaxWidth * 0.9 + ui.Height = ui.MaxHeight + ui.Padding = ui.MaxWidth * 0.05 + if fyne.IsHorizontal(lastOrientation) { + // Smaller if horizontal for swipe scroll + ui.MaxWidth = ui.MaxWidth * 0.7 + ui.Width = ui.MaxWidth * 0.7 + ui.Padding = ui.MaxWidth * 0.15 + } + + resizeWindow(ui.MaxWidth, ui.MaxHeight) + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutMain()) + + frameWidth := ui.MaxWidth + frameHeight := ui.MaxHeight + + // Mobile loop checking if orientation has changed + for a.Driver() != nil { + currentOrientation := a.Driver().Device().Orientation() + if lastOrientation != currentOrientation { + if initialOrientationVertical { + if fyne.IsVertical(lastOrientation) && !fyne.IsVertical(currentOrientation) { + ui.MaxWidth = frameHeight + ui.MaxHeight = frameWidth + } else { + ui.MaxWidth = frameWidth + ui.MaxHeight = frameHeight + } + } else { + if fyne.IsHorizontal(lastOrientation) && !fyne.IsHorizontal(currentOrientation) { + ui.MaxWidth = frameHeight + ui.MaxHeight = frameWidth + } else { + ui.MaxWidth = frameWidth + ui.MaxHeight = frameHeight + } + } + + ui.Width = ui.MaxWidth * 0.9 + ui.Height = ui.MaxHeight + ui.Padding = ui.MaxWidth * 0.05 + if fyne.IsHorizontal(currentOrientation) { + ui.MaxWidth = ui.MaxWidth * 0.7 + ui.Width = ui.MaxWidth * 0.7 + ui.Padding = ui.MaxWidth * 0.15 + } + + lastOrientation = currentOrientation + resizeWindow(ui.MaxWidth, ui.MaxHeight) + } + time.Sleep(time.Second / 2) + } + }() + + overlays := session.Window.Canvas().Overlays() + overlays.Add( + container.NewStack( + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + return container.NewVScroll(layout) +} + +func layoutFileManager() fyne.CanvasObject { + session.Domain = "app.sign" + + frame := &iframe{} + + rectBox := canvas.NewRectangle(color.Transparent) + rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.9, ui.MaxHeight*0.34)) + rectWidth100 := canvas.NewRectangle(color.Transparent) + rectWidth100.SetMinSize(fyne.NewSize(ui.Width*0.99, 10)) + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width*0.9, 10)) + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + heading := canvas.NewText("F I L E M A N A G E R", colors.Gray) + heading.TextSize = 16 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + labelResults := canvas.NewText(" RESULTS", colors.Gray) + labelResults.TextSize = 14 + labelResults.Alignment = fyne.TextAlignLeading + labelResults.TextStyle = fyne.TextStyle{Bold: true} + + signedResults := []string{} + signedData := binding.BindStringList(&signedResults) + signedList := widget.NewListWithData(signedData, + func() fyne.CanvasObject { + return container.NewStack( + container.NewHBox( + container.NewStack( + rectWidth90, + widget.NewLabel(""), + ), + ), + ) + }, + func(di binding.DataItem, co fyne.CanvasObject) { + dat := di.(binding.String) + str, err := dat.Get() + if err != nil { + return + } + + split := strings.Split(str, "/") + pos := len(split) - 1 + name := strings.Split(split[pos], ";;;") + + co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(name[0]) + }, + ) + + verifiedResults := []string{} + verifiedData := binding.BindStringList(&verifiedResults) + verifiedList := widget.NewListWithData(verifiedData, + func() fyne.CanvasObject { + return container.NewStack( + container.NewHBox( + container.NewStack( + rectWidth90, + widget.NewLabel(""), + ), + ), + ) + }, + func(di binding.DataItem, co fyne.CanvasObject) { + dat := di.(binding.String) + str, err := dat.Get() + if err != nil { + return + } + + split := strings.Split(str, "/") + pos := len(split) - 1 + name := strings.Split(split[pos], ";;;") + + co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(name[0]) + }, + ) + + errorText := canvas.NewText(" ", colors.Green) + errorText.TextSize = 12 + errorText.Alignment = fyne.TextAlignCenter + + dialogBrowse := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) { + if err != nil { + logger.Errorf("[Engram] Open file dialog: %s\n", err) + errorText.Text = "could not open file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if uc == nil { + return + } + + if session.Domain == "app.sign" { + inputFileName := uc.URI().Name() + outputFileName := inputFileName + ".signed" + + go func() { + dialogFileSign := dialog.NewFileSave(func(uri fyne.URIWriteCloser, err error) { + if err != nil { + logger.Errorf("[Engram] Save file dialog: %s\n", err) + fyne.Do(func() { + errorText.Text = "could not open signed file" + errorText.Color = colors.Red + errorText.Refresh() + }) + + return + } + + if uri == nil { + return // Canceled + } + + filedata, err := readFromURI(uc) + if err != nil { + logger.Errorf("[Engram] Cannot read file data for %s: %s\n", inputFileName, err) + fyne.Do(func() { + errorText.Text = "could not read file" + errorText.Color = colors.Red + errorText.Refresh() + }) + + return + } + + _, err = writeToURI(engram.Disk.SignData(filedata), uri) + if err != nil { + logger.Errorf("[Engram] Cannot sign %s: %s\n", inputFileName, err) + fyne.Do(func() { + errorText.Text = "could not write signed file" + errorText.Color = colors.Red + errorText.Refresh() + }) + + return + } + + outputFile := uri.URI().Name() + if a.Driver().Device().IsMobile() { + // Mobile uses content access name on save dialog + outputFile = outputFileName + } + + logger.Printf("[Engram] Successfully signed file: %s\n", outputFile) + + fyne.Do(func() { + errorText.Text = "signed file successfully" + errorText.Color = colors.Green + errorText.Refresh() + + signedResults = append(signedResults, outputFile) + signedData.Set(signedResults) + signedList.Refresh() + + signedLen := len(signedResults) + labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", signedLen, signedLen) + labelResults.Refresh() + }) + + }, session.Window) + + if !a.Driver().Device().IsMobile() { + // Open file browser in current directory + uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) + if err == nil { + dialogFileSign.SetLocation(uri) + } else { + logger.Errorf("[Engram] Could not open current directory %s\n", err) + } + } + + fyne.Do(func() { + dialogFileSign.SetFilter(storage.NewExtensionFileFilter([]string{".signed"})) + dialogFileSign.SetView(dialog.ListView) + dialogFileSign.SetFileName(outputFileName) + dialogFileSign.Resize(fyne.NewSize(ui.Width, ui.Height)) + dialogFileSign.SetConfirmText("Save Sign") + dialogFileSign.Show() + }) + }() + } else { + fileName := uc.URI().Name() + if !strings.HasSuffix(fileName, ".signed") { + errorText.Text = "verifying requires a .signed file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + filedata, err := readFromURI(uc) + if err != nil { + logger.Errorf("[Engram] Cannot read file data for %s: %s\n", fileName, err) + errorText.Text = "could not read file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + // Trim off .signed from file because engram.Disk.CheckFileSignature() adds it back on anyways - https://github.com/deroproject/derohe/blob/main/walletapi/wallet.go#L709 + fileName = strings.TrimSuffix(fileName, ".signed") + signer, message, err := engram.Disk.CheckSignature(filedata) + if err != nil { + logger.Errorf("[Engram] Signature verification failed for %s: %s\n", fileName, err) + errorText.Text = "signature verification failed" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + logger.Printf("[Engram] %s signed by: %s\n", fileName, signer.String()) + if isASCII(string(message)) { + fmt.Println(string(message)) + } + + errorText.Text = "verified file successfully" + errorText.Color = colors.Green + errorText.Refresh() + + verifiedResults = append(verifiedResults, fileName+";;;"+signer.String()) + verifiedData.Set(verifiedResults) + verifiedList.Refresh() + + verifiedLen := len(verifiedResults) + labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", verifiedLen, verifiedLen) + labelResults.Refresh() + } + }, session.Window) + + if !a.Driver().Device().IsMobile() { + // Open file browser in current directory + uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) + if err == nil { + dialogBrowse.SetLocation(uri) + } else { + logger.Errorf("[Engram] Could not open current directory %s\n", err) + } + } + + dialogBrowse.Resize(fyne.NewSize(ui.Width, ui.Height)) + dialogBrowse.SetView(dialog.ListView) + + signedList.OnSelected = func(id widget.ListItemID) { + errorText.Text = "" + errorText.Refresh() + } + + verifiedList.OnSelected = func(id widget.ListItemID) { + errorText.Text = "" + errorText.Refresh() + + if session.Domain == "app.verify" { + split := strings.Split(verifiedResults[id], ";;;") + filepath := strings.Split(split[0], "/") + filename := filepath[len(filepath)-1] + filename = strings.Replace(filename, ".signed", "", -1) + + rectSpan := canvas.NewRectangle(color.Transparent) + rectSpan.SetMinSize(fyne.NewSize(ui.Width*0.99, 10)) + + header := canvas.NewText("S I G N A T U R E D E T A I L", colors.Gray) + header.TextSize = 16 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + labelStatus := canvas.NewText(" VERIFICATION STATUS", colors.Gray) + labelStatus.TextSize = 12 + labelStatus.TextStyle = fyne.TextStyle{Bold: true} + labelStatus.Alignment = fyne.TextAlignCenter + + valueStatus := canvas.NewText(" Verified", colors.Green) + valueStatus.TextSize = 22 + valueStatus.TextStyle = fyne.TextStyle{Bold: true} + valueStatus.Alignment = fyne.TextAlignCenter + + labelFilename := canvas.NewText(" FILENAME", colors.Gray) + labelFilename.TextSize = 14 + labelFilename.TextStyle = fyne.TextStyle{Bold: true} + + valueFilename := widget.NewRichTextFromMarkdown(filename) + valueFilename.Wrapping = fyne.TextWrapBreak + + labelSigner := canvas.NewText(" SIGNER ADDRESS", colors.Gray) + labelSigner.TextSize = 14 + labelSigner.TextStyle = fyne.TextStyle{Bold: true} + + valueSigner := widget.NewRichTextFromMarkdown(split[1]) + valueSigner.Wrapping = fyne.TextWrapBreak + + labelSeparator := widget.NewRichTextFromMarkdown("") + labelSeparator.Wrapping = fyne.TextWrapOff + labelSeparator.ParseMarkdown("---") + + labelSeparator2 := widget.NewRichTextFromMarkdown("") + labelSeparator2.Wrapping = fyne.TextWrapOff + labelSeparator2.ParseMarkdown("---") + + labelSeparator3 := widget.NewRichTextFromMarkdown("") + labelSeparator3.Wrapping = fyne.TextWrapOff + labelSeparator3.ParseMarkdown("---") + + linkBack := widget.NewHyperlinkWithStyle("Hide Details", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + removeOverlays() + } + + overlay := session.Window.Canvas().Overlays() + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + overlay.Add( + container.NewStack( + &iframe{}, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + rectSpan, + rectSpacer, + header, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + valueStatus, + rectSpacer, + labelStatus, + ), + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator, + rectSpacer, + rectSpacer, + labelFilename, + rectSpacer, + valueFilename, + rectSpacer, + rectSpacer, + labelSeparator2, + rectSpacer, + rectSpacer, + labelSigner, + rectSpacer, + valueSigner, + rectSpacer, + rectSpacer, + labelSeparator3, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + ), + layout.NewSpacer(), + ), + ), + ) + overlay.Top().Show() + + verifiedList.UnselectAll() + } + } + + btnBrowse := widget.NewButton("Browse Files", nil) + btnBrowse.OnTapped = func() { + errorText.Text = "" + errorText.Refresh() + if session.Domain == "app.sign" { + dialogBrowse.SetFilter(nil) + dialogBrowse.SetConfirmText("Open") + } else { + dialogBrowse.SetFilter(storage.NewExtensionFileFilter([]string{".signed"})) + dialogBrowse.SetConfirmText("Verify") + } + + dialogBrowse.Show() + } + + labelAction := canvas.NewText("( DRAG-AND-DROP ENABLED )", colors.Gray) + labelAction.TextSize = 12 + labelAction.Alignment = fyne.TextAlignLeading + labelAction.TextStyle = fyne.TextStyle{Bold: true} + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + entryAddress := widget.NewEntry() + entryAddress.PlaceHolder = "Username or Address" + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + labelSeparator := widget.NewRichTextFromMarkdown("") + labelSeparator.Wrapping = fyne.TextWrapOff + labelSeparator.ParseMarkdown("---") + + labelSeparator2 := widget.NewRichTextFromMarkdown("") + labelSeparator2.Wrapping = fyne.TextWrapOff + labelSeparator2.ParseMarkdown("---") + + labelSeparator3 := widget.NewRichTextFromMarkdown("") + labelSeparator3.Wrapping = fyne.TextWrapOff + labelSeparator3.ParseMarkdown("---") + + labelSeparator4 := widget.NewRichTextFromMarkdown("") + labelSeparator4.Wrapping = fyne.TextWrapOff + labelSeparator4.ParseMarkdown("---") + + linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + removeOverlays() + capture := session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + session.Domain = "app.wallet" + session.LastDomain = capture + } + + selectType := widget.NewSelect([]string{"Sign Files", "Verify Signed Files"}, nil) + selectType.SetSelected("Sign Files") + + // Handle drag & drop files for file signing/verifying + session.Window.SetOnDropped(func(p fyne.Position, files []fyne.URI) { + errorText.Text = "" + errorText.Refresh() + + if session.Domain == "app.sign" { + if a.Driver().Device().IsMobile() { + if len(files) > 1 { + errorText.Text = "single file only" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + inputFileName := files[0].Name() + + dialogFileSign := dialog.NewFileSave(func(uri fyne.URIWriteCloser, err error) { + if err != nil { + logger.Errorf("[Engram] File dialog: %s\n", err) + errorText.Text = "could not open signed file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if uri == nil { + return // Canceled + } + + uc, err := storage.Reader(files[0]) + if err != nil { + logger.Errorf("[Engram] Cannot create reader for %s: %s\n", inputFileName, err) + errorText.Text = "could not access file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + filedata, err := readFromURI(uc) + if err != nil { + logger.Errorf("[Engram] Cannot read file data for %s: %s\n", inputFileName, err) + errorText.Text = "could not read file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + _, err = writeToURI(engram.Disk.SignData(filedata), uri) + if err != nil { + logger.Errorf("[Engram] Cannot sign %s: %s\n", inputFileName, err) + errorText.Text = "could not write signed file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + // Mobile uses content access name on save dialog + outputFile := inputFileName + ".signed" + + logger.Printf("[Engram] Successfully signed file: %s\n", outputFile) + + errorText.Text = "signed file successfully" + errorText.Color = colors.Green + errorText.Refresh() + + signedResults = append(signedResults, outputFile) + signedData.Set(signedResults) + signedList.Refresh() + + signedLen := len(signedResults) + labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", signedLen, signedLen) + labelResults.Refresh() + + }, session.Window) + + dialogFileSign.SetFilter(storage.NewExtensionFileFilter([]string{".signed"})) + dialogFileSign.SetView(dialog.ListView) + dialogFileSign.SetFileName(inputFileName) + dialogFileSign.Resize(fyne.NewSize(ui.Width, ui.Height)) + dialogFileSign.SetConfirmText("Save Sign") + dialogFileSign.Show() + } else { + singedLen := len(signedResults) + count := 1 + singedLen + + for i, f := range files { + inputFileName := f.Name() + + uc, err := storage.Reader(f) + if err != nil { + logger.Errorf("[Engram] Cannot create reader for %s: %s\n", inputFileName, err) + errorText.Text = fmt.Sprintf("could not access file %d", i) + errorText.Color = colors.Red + errorText.Refresh() + continue + } + + filedata, err := readFromURI(uc) + if err != nil { + logger.Errorf("[Engram] Cannot read file data for %s: %s\n", inputFileName, err) + errorText.Text = fmt.Sprintf("could not read file %d", i) + errorText.Color = colors.Red + errorText.Refresh() + continue + } + + outputfile := inputFileName + ".signed" + + if err := os.WriteFile(outputfile, engram.Disk.SignData(filedata), 0600); err != nil { + logger.Errorf("[Engram] Cannot sign %s: %s\n", inputFileName, err) + errorText.Text = fmt.Sprintf("cannot sign file %d", i) + errorText.Color = colors.Red + errorText.Refresh() + } else { + logger.Printf("[Engram] Successfully signed file: %s\n", outputfile) + labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", count, len(files)+singedLen) + labelResults.Refresh() + signedResults = append(signedResults, outputfile) + count += 1 + } + } + + signedData.Set(signedResults) + signedList.Refresh() + } + } else if session.Domain == "app.verify" { + if a.Driver().Device().IsMobile() { + dialogVerify := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) { + errorText.Text = "" + if uc != nil { + fileName := uc.URI().Name() + if filepath.Ext(fileName) != ".signed" { + errorText.Text = "requires a .signed file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + filedata, err := readFromURI(uc) + if err != nil { + logger.Errorf("[Engram] Cannot read URI file data for %s: %s\n", fileName, err) + errorText.Text = "cannot read file data" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + signer, message, err := engram.Disk.CheckSignature(filedata) + if err != nil { + logger.Errorf("[Engram] Signature verification failed for %s: %s\n", fileName, err) + errorText.Text = "signature verification failed" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + logger.Printf("[Engram] %s signed by: %s\n", fileName, signer.String()) + if isASCII(string(message)) { + fmt.Println(string(message)) + } + + errorText.Text = "verified file successfully" + errorText.Color = colors.Green + errorText.Refresh() + + verifiedResults = append(verifiedResults, fileName+";;;"+signer.String()) + verifiedData.Set(verifiedResults) + verifiedList.Refresh() + + verifiedLen := len(verifiedResults) + labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", verifiedLen, verifiedLen) + labelResults.Refresh() + } + }, session.Window) + + dialogVerify.Resize(fyne.NewSize(ui.Width, ui.Height)) + dialogVerify.SetView(dialog.ListView) + dialogVerify.Show() + } else { + verifiedLen := len(verifiedResults) + count := 1 + verifiedLen + + for i, f := range files { + inputFileName := f.Name() + + uc, err := storage.Reader(f) + if err != nil { + logger.Errorf("[Engram] Cannot create reader for %s: %s\n", inputFileName, err) + errorText.Text = fmt.Sprintf("could not access file %d", i) + errorText.Color = colors.Red + errorText.Refresh() + continue + } + + filedata, err := readFromURI(uc) + if err != nil { + logger.Errorf("[Engram] Cannot read file data for %s: %s\n", inputFileName, err) + errorText.Text = fmt.Sprintf("could not read file %d", i) + errorText.Color = colors.Red + errorText.Refresh() + continue + } + + outputfile := strings.TrimSuffix(inputFileName, ".signed") + + if signer, message, err := engram.Disk.CheckSignature(filedata); err != nil { + logger.Errorf("[Engram] Signature verification failed for %s: %s\n", inputFileName, err) + errorText.Text = fmt.Sprintf("signature verification %d failed", i) + errorText.Color = colors.Red + errorText.Refresh() + } else { + logger.Printf("[Engram] Signed by: %s\n", signer.String()) + + if isASCII(string(message)) { + logger.Printf("[Engram] Message for %s: %s\n", inputFileName, signer.String()) + } + + if err := os.WriteFile(outputfile, message, 0600); err != nil { + logger.Errorf("[Engram] Cannot write output file for %s: %s\n", outputfile, err) + continue + } + + logger.Printf("[Engram] Successfully wrote message to file: %s\n", outputfile) + + labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", count, len(files)+verifiedLen) + labelResults.Refresh() + verifiedResults = append(verifiedResults, inputFileName+";;;"+signer.String()) + count += 1 + } + } + + verifiedData.Set(verifiedResults) + verifiedList.Refresh() + } + } + }) + + top := container.NewVBox( + rectSpacer, + rectSpacer, + heading, + ) + + center := container.NewStack( + rectWidth100, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + container.NewVBox( + rectSpacer, + rectSpacer, + selectType, + rectSpacer, + rectSpacer, + btnBrowse, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + labelAction, + layout.NewSpacer(), + ), + rectSpacer, + errorText, + rectSpacer, + labelSeparator, + rectSpacer, + rectSpacer, + labelResults, + rectSpacer, + rectSpacer, + container.NewStack( + rectBox, + signedList, + ), + rectSpacer, + ), + ), + layout.NewSpacer(), + ), + ) + + selectType.OnChanged = func(s string) { + if s == "Sign Files" { + session.Domain = "app.sign" + signedList.UnselectAll() + center.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[18].(*fyne.Container).Objects[1] = signedList + signedData.Set(signedResults) + signedList.Refresh() + signedLen := len(signedResults) + labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", signedLen, signedLen) + labelResults.Refresh() + } else { + session.Domain = "app.verify" + center.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[18].(*fyne.Container).Objects[1] = verifiedList + verifiedData.Set(verifiedResults) + verifiedList.Refresh() + verifiedLen := len(verifiedResults) + labelResults.Text = fmt.Sprintf(" RESULTS (%d / %d)", verifiedLen, verifiedLen) + labelResults.Refresh() + } + + errorText.Text = "" + errorText.Refresh() + } + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + body := container.NewVBox( + top, + center, + ) + + layout := container.NewStack( + frame, + container.NewBorder( + body, + bottom, + nil, + nil, + ), + ) + + return NewVScroll(layout) +} + +func layoutContractBuilder(promptText string) fyne.CanvasObject { + session.Domain = "app.sc.builder" + + frame := &iframe{} + + rectBox := canvas.NewRectangle(color.Transparent) + rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.9, ui.MaxHeight*0.35)) + + rectWidth100 := canvas.NewRectangle(color.Transparent) + rectWidth100.SetMinSize(fyne.NewSize(ui.Width*0.99, 10)) + + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width*0.9, 10)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + heading := canvas.NewText("C O N T R A C T B U I L D E R", colors.Gray) + heading.TextSize = 16 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + errorText := canvas.NewText(promptText, colors.Red) + errorText.TextSize = 12 + errorText.Alignment = fyne.TextAlignCenter + + // Open .bas SC from file browser + dialogBrowse := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) { + errorText.Text = "" + if uc != nil { + filename := uc.URI().Name() + if uc.URI().MimeType() != "text/plain" { + logger.Errorf("[Engram] Cannot open file %s in contract builder\n", filename) + errorText.Text = "cannot open file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if filepath.Ext(filename) != ".bas" { + errorText.Text = "requires a .bas file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + filedata, err := readFromURI(uc) + if err != nil { + logger.Errorf("[Engram] Cannot read URI file data for %s: %s\n", filename, err) + errorText.Text = "cannot read file data" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if !isASCII(string(filedata)) { + errorText.Text = "invalid file data" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + removeOverlays() + capture := session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutContractEditor(strings.TrimSuffix(filename, ".bas"), string(filedata))) + session.LastDomain = capture + } + }, session.Window) + + if !a.Driver().Device().IsMobile() { + // Open file browser in current directory + uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) + if err == nil { + dialogBrowse.SetLocation(uri) + } else { + logger.Errorf("[Engram] Could not open current directory %s\n", err) + } + } + + // Resize browser to app size and add SC file filter + dialogBrowse.Resize(fyne.NewSize(ui.Width, ui.Height)) + dialogBrowse.SetFilter(storage.NewExtensionFileFilter([]string{".bas"})) + dialogBrowse.SetView(dialog.ListView) + + btnBrowse := widget.NewButton("Browse Files", nil) + btnBrowse.OnTapped = func() { + dialogBrowse.Show() + } + + btnEditor := widget.NewButton("Open Editor", nil) + btnEditor.OnTapped = func() { + removeOverlays() + capture := session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutContractEditor("", "")) + session.LastDomain = capture + } + + labelAction := canvas.NewText("( DRAG-AND-DROP ENABLED )", colors.Gray) + labelAction.TextSize = 12 + labelAction.Alignment = fyne.TextAlignLeading + labelAction.TextStyle = fyne.TextStyle{Bold: true} + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + labelSeparator := widget.NewRichTextFromMarkdown("") + labelSeparator.Wrapping = fyne.TextWrapOff + labelSeparator.ParseMarkdown("---") + + linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + removeOverlays() + capture := session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + session.LastDomain = capture + } + + // Handle drag & drop files for smart contracts + session.Window.SetOnDropped(func(p fyne.Position, files []fyne.URI) { + if session.Domain == "app.sc.builder" { + errorText.Text = "" + errorText.Refresh() + + if len(files) > 1 { + errorText.Text = "single .bas file only" + errorText.Color = colors.Red + errorText.Refresh() + return + } else { + uri, err := storage.Reader(files[0]) + if err != nil { + errorText.Text = "could not read dropped file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + filename := files[0].Name() + if filepath.Ext(filename) != ".bas" { + errorText.Text = "requires a .bas file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + filedata, err := readFromURI(uri) + if err != nil { + logger.Errorf("[Engram] Cannot read file data for %s: %s\n", filename, err) + errorText.Text = "cannot read file data" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + go func() { + fyne.Do(func() { + removeOverlays() + capture := session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutContractEditor(strings.TrimSuffix(filepath.Base(filename), ".bas"), string(filedata))) + session.LastDomain = capture + }) + }() + } + } + }) + + entryClone := widget.NewEntry() + entryClone.SetPlaceHolder("Clone SCID") + if session.Offline { + entryClone.Disable() + entryClone.SetText("Cloning disabled in offline mode") + } + + entryClone.OnChanged = func(s string) { + if len(s) == 64 { + removeOverlays() + capture := session.Window.Content() + session.Window.SetContent(layoutTransition()) + + code, err := getContractCode(s) + if err != nil { + logger.Errorf("[Engram] Clone SC: %s\n", err) + errorText.Text = "cannot get contract for clone" + errorText.Color = colors.Red + errorText.Refresh() + session.Window.SetContent(layoutContractBuilder(errorText.Text)) + return + } + + if code == "" { + errorText.Text = "contract does not exists" + errorText.Color = colors.Red + errorText.Refresh() + session.Window.SetContent(layoutContractBuilder(errorText.Text)) + return + } + + session.Window.SetContent(layoutContractEditor("", code)) + session.LastDomain = capture + } else { + if s == "" { + errorText.Text = "" + errorText.Refresh() + } else { + errorText.Text = "not a valid scid" + errorText.Color = colors.Red + errorText.Refresh() + } + } + } + + top := container.NewVBox( + rectSpacer, + rectSpacer, + heading, + ) + + center := container.NewStack( + rectWidth100, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + container.NewVBox( + rectSpacer, + rectSpacer, + entryClone, + errorText, + rectSpacer, + btnBrowse, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + labelAction, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + btnEditor, + rectSpacer, + labelSeparator, + rectSpacer, + rectBox, + ), + ), + layout.NewSpacer(), + ), + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + body := container.NewVBox( + top, + center, + ) + + layout := container.NewStack( + frame, + container.NewBorder( + body, + bottom, + nil, + nil, + ), + ) + + return NewVScroll(layout) +} + +func layoutContractEditor(filename, filedata string) fyne.CanvasObject { + session.Domain = "app.sc.editor" + + frame := &iframe{} + + rectBox := canvas.NewRectangle(color.Transparent) + rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.9, ui.MaxHeight*0.35)) + + rectWidth100 := canvas.NewRectangle(color.Transparent) + rectWidth100.SetMinSize(fyne.NewSize(ui.Width*0.99, 10)) + + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width*0.9, 10)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + rectCode := canvas.NewRectangle(color.Transparent) + rectCode.SetMinSize(fyne.NewSize(ui.MaxWidth*0.9, ui.MaxHeight*0.35)) + + heading := canvas.NewText("C O N T R A C T E D I T O R", colors.Green) + heading.TextSize = 16 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + labelHeaders := canvas.NewText(" HEADERS", colors.Gray) + labelHeaders.TextSize = 14 + labelHeaders.Alignment = fyne.TextAlignLeading + labelHeaders.TextStyle = fyne.TextStyle{Bold: true} + + labelCode := canvas.NewText(" CODE (DVM-BASIC)", colors.Gray) + labelCode.TextSize = 14 + labelCode.Alignment = fyne.TextAlignLeading + labelCode.TextStyle = fyne.TextStyle{Bold: true} + + labelCodeSize := canvas.NewText("(0.0KB) ", colors.Green) + labelCodeSize.TextSize = 12 + labelCodeSize.Alignment = fyne.TextAlignTrailing + + errorText := canvas.NewText(" ", colors.Green) + errorText.TextSize = 12 + errorText.Alignment = fyne.TextAlignCenter + + var nameHdr, iconURLHdr, descrHdr string + nameHdr = filename + + // Get headers from contract code initialize func + if filedata != "" { + contract, _, err := dvm.ParseSmartContract(filedata) + if err == nil { + for n, f := range contract.Functions { + if n == "InitializePrivate" || n == "Initialize" { + for _, line := range f.Lines { + if len(line) < 6 { + // Line is to short to be a STORE + continue + } + + for i, parts := range line { + if parts == "STORE" { + // Find if code is storing headers + header := tela.Header(line[i+2]) + if header == tela.HEADER_NAME || header == tela.HEADER_NAME_V2 { + nameHdr = strings.Trim(line[i+4], `"`) + } else if header == tela.HEADER_ICON_URL || header == tela.HEADER_ICON_URL_V2 { + iconURLHdr = strings.Trim(line[i+4], `"`) + } else if header == tela.HEADER_DESCRIPTION || header == tela.HEADER_DESCRIPTION_V2 { + descrHdr = strings.Trim(line[i+4], `"`) + } + } + } + } + } + } + } + } + + entryName := widget.NewEntry() + entryName.SetText(nameHdr) + entryName.SetPlaceHolder("Name") + entryName.Validator = func(s string) (err error) { + if s == "" { + err = fmt.Errorf("enter a name") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + errorText.Text = "" + errorText.Refresh() + + return nil + } + + entryIcon := widget.NewEntry() + entryIcon.SetPlaceHolder("Icon") + entryIcon.SetText(iconURLHdr) + entryIcon.Validator = func(s string) (err error) { + if s == "" { + err = fmt.Errorf("enter icon URL") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + errorText.Text = "" + errorText.Refresh() + + return nil + } + + var entryUpdated bool + entryDescription := widget.NewEntry() + entryDescription.SetPlaceHolder("Description") + entryDescription.SetText(descrHdr) + entryDescription.Validator = func(s string) (err error) { + if s == "" && entryUpdated { + err = fmt.Errorf("enter description") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + entryUpdated = true + + errorText.Text = "" + errorText.Refresh() + + return nil + } + + var unsavedChanges bool + entryCode := widget.NewMultiLineEntry() + entryCode.SetPlaceHolder("Code") + entryCode.Wrapping = fyne.TextWrapWord + entryCode.OnChanged = func(s string) { + errorText.Text = "" + errorText.Refresh() + + size := tela.GetCodeSizeInKB(s) + + labelCodeSize.Text = fmt.Sprintf("(%.2fKB) ", size) + if size > 20 { + labelCodeSize.Color = colors.Red + errorText.Text = "contract size is to large" + errorText.Color = colors.Red + errorText.Refresh() + } else if size > 18.5 { + labelCodeSize.Color = colors.Yellow + } else { + labelCodeSize.Color = colors.Green + } + labelCodeSize.Refresh() + + if s != filedata { + unsavedChanges = true + } else { + unsavedChanges = false + } + } + + entryCode.SetText(filedata) + + options := []string{"Initialize", "Set Headers", "New Function", "Parse", "Format", "Clear", "Export"} + if !session.Offline { + splice := append([]string{"Import Function"}, options[3:]...) + options = append(options[:3], splice...) + options = append(options, "Install") + } + + selectEditor := widget.NewSelect(options, nil) + + entryForm := container.NewVBox( + rectSpacer, + selectEditor, + rectSpacer, + container.NewBorder( + nil, + nil, + labelCode, + labelCodeSize, + nil, + ), + container.NewStack( + rectCode, + entryCode, + ), + errorText, + rectSpacer, + labelHeaders, + rectSpacer, + entryName, + entryIcon, + entryDescription, + ) + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + labelSeparator := widget.NewRichTextFromMarkdown("") + labelSeparator.Wrapping = fyne.TextWrapOff + labelSeparator.ParseMarkdown("---") + + linkBack := widget.NewHyperlinkWithStyle("Back to Contract Builder", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + if unsavedChanges { + verificationOverlay( + false, + "CONTRACT EDITOR", + "Leave with unsaved changes", + "Confirm", + func(b bool) { + if b { + capture := session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutContractBuilder("")) + session.LastDomain = capture + } + }, + ) + } else { + removeOverlays() + capture := session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutContractBuilder("")) + session.LastDomain = capture + } + } + + selectEditor.OnChanged = func(s string) { + errorText.Text = "" + errorText.Refresh() + + switch s { + case "Initialize": // Set entry text with new starter initialize func + if entryCode.Text == "" { + entryCode.SetText(dvmInitFuncExample()) + errorText.Text = "new initialize function created" + errorText.Color = colors.Green + errorText.Refresh() + return + } + + verificationOverlay( + false, + "CONTRACT EDITOR", + "Reset to default initialize function", + "Confirm", + func(b bool) { + if b { + entryCode.SetText(dvmInitFuncExample()) + errorText.Text = "new initialize function created" + errorText.Color = colors.Green + errorText.Refresh() + } + }, + ) + case "New Function": // Add a new starter initialize func to code entry + increment := 1 + var hasInitFunc bool + fn := tela.GetSmartContractFuncNames(entryCode.Text) + for _, n := range fn { + // Increment function number if new() already esists + if strings.TrimRight(n, "0123456789") == "new" { + increment++ + } + + if n == "InitializePrivate" || n == "Initialize" { + hasInitFunc = true + } + } + + if !hasInitFunc { + errorText.Text = "no initialize function" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if strings.HasSuffix(entryCode.Text, "\n") { + entryCode.SetText(entryCode.Text + "\n" + dvmFuncExample(increment)) + } else { + entryCode.SetText(entryCode.Text + "\n\n" + dvmFuncExample(increment)) + } + + errorText.Text = "new function added" + errorText.Color = colors.Green + errorText.Refresh() + case "Import Function": // Import a function from an on-chain scid + var hasInitFunc bool + fn := tela.GetSmartContractFuncNames(entryCode.Text) + for _, n := range fn { + if n == "InitializePrivate" || n == "Initialize" { + hasInitFunc = true + break + } + } + + entryEntrypoint := widget.NewEntry() + entryEntrypoint.SetPlaceHolder("Function name") + entryEntrypoint.Validator = func(s string) (err error) { + if s == "" || (len(s) > 0 && !unicode.IsLetter(rune(s[0]))) { + return fmt.Errorf("invalid function name") + } + + return nil + } + + entrySCID := widget.NewEntry() + entrySCID.SetPlaceHolder("SCID") + entrySCID.Validator = func(s string) (err error) { + if len(s) != 64 { + return fmt.Errorf("not a valid scid") + } + + return nil + } + + overlay := session.Window.Canvas().Overlays() + + header := canvas.NewText("CONTRACT EDITOR", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + subHeader := canvas.NewText("Import an existing function", colors.Account) + subHeader.TextSize = 22 + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + + linkCancel := widget.NewHyperlinkWithStyle("Cancel", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkCancel.OnTapped = func() { + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + paramsContainer := container.NewVBox(entrySCID, entryEntrypoint) + + btnImport := widget.NewButton("Import", nil) + btnImport.OnTapped = func() { + if entrySCID.Validate() != nil { + entrySCID.FocusGained() + entrySCID.FocusLost() + return + } + + if entryEntrypoint.Validate() != nil { + entryEntrypoint.FocusGained() + entryEntrypoint.FocusLost() + return + } + + defer removeOverlays() + + if !hasInitFunc { + if entryEntrypoint.Text != "InitializePrivate" && entryEntrypoint.Text != "Initialize" { + errorText.Text = "need initializing function first" + errorText.Color = colors.Red + errorText.Refresh() + return + } + } + + code, err := getContractCode(entrySCID.Text) + if err != nil { + logger.Errorf("[Engram] Editor import function error: %s\n", err) + errorText.Text = "cannot get contract for function import" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if code == "" { + errorText.Text = "contract does not exists" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + entrypoint := entryEntrypoint.Text + contract, pos, err := dvm.ParseSmartContract(code) + if err != nil { + logger.Errorf("[Engram] Editor import parsing error: %s %s\n", err, pos) + errorText.Text = fmt.Sprintf("error parsing contract %s", pos) + errorText.Color = colors.Red + errorText.Refresh() + return + } + + var tempSC dvm.SmartContract + tempSC.Functions = make(map[string]dvm.Function) + + for name, f := range contract.Functions { + if name == entrypoint { + tempSC.Functions[name] = f + break + } + } + + if tempSC.Functions[entrypoint].LineNumbers == nil { + errorText.Text = "function not found on scid" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + formatted, err := tela.FormatSmartContract(tempSC, fmt.Sprintf("Function %s", entrypoint)) + if err != nil { + logger.Errorf("[Engram] Editor import formatting error: %s\n", err) + errorText.Text = "could not parse dvm to string" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if entryCode.Text == "" { + entryCode.SetText(formatted) + } else if strings.HasSuffix(entryCode.Text, "\n") { + entryCode.SetText(entryCode.Text + "\n" + formatted) + } else { + entryCode.SetText(entryCode.Text + "\n\n" + formatted) + } + + errorText.Text = "imported function successfully" + errorText.Color = colors.Green + errorText.Refresh() + } + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + container.NewCenter( + subHeader, + ), + widget.NewLabel(""), + rectSpacer, + rectSpacer, + paramsContainer, + rectSpacer, + rectSpacer, + btnImport, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkCancel, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + case "Clear": // Clears SC code entry + verificationOverlay( + false, + "CONTRACT EDITOR", + "Clear code entry", + "Confirm", + func(b bool) { + if b { + entryCode.SetText("") + errorText.Text = "contract code cleared" + errorText.Color = colors.Green + errorText.Refresh() + } + }, + ) + case "Parse": // Parse SC for errors + if entryCode.Text == "" { + errorText.Text = "contract code is empty" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + _, pos, err := dvm.ParseSmartContract(entryCode.Text) + if err != nil { + errorText.Text = fmt.Sprintf("error parsing contract %s", pos) + errorText.Color = colors.Red + errorText.Refresh() + logger.Errorf("[Engram] Parse SC: %s %s\n", err, pos) + return + } + + errorText.Text = "contract parsed successfully" + errorText.Color = colors.Green + errorText.Refresh() + case "Set Headers": // Set Artificer standard headers into initialize func + if entryCode.Text == "" { + errorText.Text = "contract code is empty" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + contract, pos, err := dvm.ParseSmartContract(entryCode.Text) + if err != nil { + errorText.Text = fmt.Sprintf("error parsing contract %s", pos) + errorText.Color = colors.Red + errorText.Refresh() + logger.Errorf("[Engram] Set SC Headers: %s %s\n", err, pos) + return + } + + if entryName.Validate() == nil && entryIcon.Validate() == nil && entryDescription.Validate() == nil { + // Create add header func to use later in confirmations + addFunction := func() { + var haveHeader [uint64(3)]bool + for name, function := range contract.Functions { + // Find initialize func + if name == "Initialize" || name == "InitializePrivate" { + for _, line := range function.Lines { + if len(line) < 6 { + // Line is to short to be a STORE + continue + } + + for i, parts := range line { + if parts == "STORE" { + // Find if code is storing headers and update vars with header entry value + header := tela.Header(line[i+2]) + if header == tela.HEADER_NAME || header == tela.HEADER_NAME_V2 { + haveHeader[0] = true + line[i+4] = fmt.Sprintf(`"%s"`, entryName.Text) + } else if header == tela.HEADER_ICON_URL || header == tela.HEADER_ICON_URL_V2 { + haveHeader[1] = true + line[i+4] = fmt.Sprintf(`"%s"`, entryIcon.Text) + } else if header == tela.HEADER_DESCRIPTION || header == tela.HEADER_DESCRIPTION_V2 { + haveHeader[2] = true + line[i+4] = fmt.Sprintf(`"%s"`, entryDescription.Text) + } + } + } + } + } + } + + // Check if any headers are missing + var needToAdd, hasInitFunc bool + for _, hh := range haveHeader { + if !hh { + needToAdd = true + break + } + } + + // SC has all headers already, update the code entry + if !needToAdd { + code, err := tela.FormatSmartContract(contract, entryCode.Text) + if err != nil { + logger.Errorf("[Engram] Format code error: %s\n", err) + err = errors.New("could not parse dvm to string") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + entryCode.SetText(code) + + errorText.Text = "headers updated" + errorText.Color = colors.Green + errorText.Refresh() + return + } + + // SC is missing one or more headers so they will be added into initialize func + for name, function := range contract.Functions { + if name == "Initialize" || name == "InitializePrivate" { + hasInitFunc = true + + lineLen := len(function.LineNumbers) + indexEnd := lineLen - 1 + + // Starting from the last line number loop upwards + for i := 0; i < lineLen; i++ { + index := indexEnd - i + if index < 0 { + break + } + + line := function.Lines[function.LineNumbers[index]] + if len(line) < 1 { + continue + } + + // If line is RETURN 0 will inject headers here and push RETURN 0 line down if there is room + if line[0] == "RETURN" && line[1] == "0" { + if index-1 < 0 { + err = errors.New("no room for header lines") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } else if i > 0 && function.LineNumbers[index+1] < function.LineNumbers[index]+4 { + err = fmt.Errorf("no room for header lines below %d", function.LineNumbers[index]) + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } else { + var addedLines, skipedLines uint64 + for u := uint64(1); u < 5; u++ { + addLineNum := function.LineNumbers[index] + (u - 1) - skipedLines + switch u { + case 1: // nameHdr + if !haveHeader[0] { + function.Lines[addLineNum] = []string{"STORE", "(", `"var_header_name"`, ",", fmt.Sprintf(`"%s"`, entryName.Text), ")"} + addedLines++ + } else { + // Count skip if we have already to subtract to line number + skipedLines++ + continue + } + case 2: // iconURLHdr + if !haveHeader[1] { + function.Lines[addLineNum] = []string{"STORE", "(", `"var_header_icon"`, ",", fmt.Sprintf(`"%s"`, entryIcon.Text), ")"} + if skipedLines != 1 { + function.LineNumbers = append(function.LineNumbers, addLineNum) + } + addedLines++ + } else { + skipedLines++ + continue + } + case 3: // descrHdr + if !haveHeader[2] { + function.Lines[addLineNum] = []string{"STORE", "(", `"var_header_description"`, ",", fmt.Sprintf(`"%s"`, entryDescription.Text), ")"} + if skipedLines != 2 { + function.LineNumbers = append(function.LineNumbers, addLineNum) + } + addedLines++ + } + case 4: + function.Lines[addLineNum] = []string{"RETURN", "0"} + function.LineNumbers = append(function.LineNumbers, addLineNum) + } + } + + // If changes were made sort line numbers and add them to index + if addedLines > 0 { + sort.Slice(function.LineNumbers, func(i, j int) bool { + return function.LineNumbers[i] < function.LineNumbers[j] + }) + + for u, ln := range function.LineNumbers { + function.LinesNumberIndex[ln] = uint64(u) + } + + contract.Functions[name] = function + } + + // fmt.Println("Lines", contract.Functions[name].Lines) + // fmt.Println("LineNumbers", contract.Functions[name].LineNumbers) + // fmt.Println("LineNumberIndex", contract.Functions[name].LinesNumberIndex) + + break + } + } + } + } + } + + if !hasInitFunc { + err = errors.New("no initialize function") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + code, err := tela.FormatSmartContract(contract, entryCode.Text) + if err != nil { + logger.Errorf("[Engram] Format code error: %s\n", err) + err = errors.New("could not parse dvm to string") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if code == entryCode.Text { + errorText.Text = "did not change headers" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + entryCode.SetText(code) + + errorText.Text = "contract headers added successfully" + errorText.Color = colors.Green + errorText.Refresh() + } + + codeCheck, err := tela.FormatSmartContract(contract, entryCode.Text) + if err != nil { + logger.Errorf("[Engram] Format code error: %s\n", err) + err = errors.New("could not parse dvm to string") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + // Warn user that code will be formatted if headers are added + if codeCheck != entryCode.Text { + verificationOverlay( + false, + "CONTRACT EDITOR", + "Setting headers formats your code", + "Confirm", + func(b bool) { + if b { + addFunction() + } + }, + ) + } else { + addFunction() + } + } + case "Format": // Format SC code + if entryCode.Text == "" { + errorText.Text = "contract code is empty" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + contract, pos, err := dvm.ParseSmartContract(entryCode.Text) + if err != nil { + errorText.Text = fmt.Sprintf("error parsing contract %s", pos) + errorText.Color = colors.Red + errorText.Refresh() + logger.Errorf("[Engram] Format: %s %s\n", err, pos) + return + } + + code, err := tela.FormatSmartContract(contract, entryCode.Text) + if err != nil { + logger.Errorf("[Engram] Format code error: %s\n", err) + err = errors.New("could not parse dvm to string") + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if code == entryCode.Text { + errorText.Text = "contract code is formatted" + errorText.Color = colors.Green + errorText.Refresh() + return + } + + verificationOverlay( + false, + "CONTRACT EDITOR", + "Remove whitespace and comments", + "Confirm", + func(b bool) { + if b { + entryCode.SetText(code) + + errorText.Text = "contract code formatted successfully" + errorText.Color = colors.Green + errorText.Refresh() + } + }, + ) + case "Export": // Export SC to file + if entryCode.Text == "" { + errorText.Text = "contract code is empty" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + exportFileName := fmt.Sprintf("%s.bas", entryName.Text) + + data := []byte(entryCode.Text) + dialogFileSave := dialog.NewFileSave(func(uri fyne.URIWriteCloser, err error) { + if err != nil { + logger.Errorf("[Engram] File dialog: %s\n", err) + errorText.Text = "could not export contract file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if uri == nil { + return // Canceled + } + + _, err = writeToURI(data, uri) + if err != nil { + logger.Errorf("[Engram] Exporting %s: %s\n", exportFileName, err) + errorText.Text = "error exporting contract file" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + unsavedChanges = false + filedata = entryCode.Text + errorText.Text = "exported contract file successfully" + errorText.Color = colors.Green + errorText.Refresh() + + }, session.Window) + + if !a.Driver().Device().IsMobile() { + // Open file browser in current directory + uri, err := storage.ListerForURI(storage.NewFileURI(AppPath())) + if err == nil { + dialogFileSave.SetLocation(uri) + } else { + logger.Errorf("[Engram] Could not open current directory %s\n", err) + } + } + + dialogFileSave.SetFilter(storage.NewExtensionFileFilter([]string{".bas"})) + dialogFileSave.SetView(dialog.ListView) + dialogFileSave.SetFileName(exportFileName) + dialogFileSave.Resize(fyne.NewSize(ui.Width, ui.Height)) + dialogFileSave.Show() + case "Install": // Install SC + code := entryCode.Text + if code == "" { + errorText.Text = "contract code is empty" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + contract, pos, err := dvm.ParseSmartContract(code) + if err != nil { + logger.Errorf("[Engram] Install SC: %s %s\n", err, pos) + errorText.Text = fmt.Sprintf("error parsing contract %s", pos) + errorText.Color = colors.Red + errorText.Refresh() + return + } + + var entrypoint string + var args []rpc.Argument + for name, function := range contract.Functions { + if name == "InitializePrivate" || name == "Initialize" { + entrypoint = name + for _, v := range function.Params { + switch v.Type { + case 0x4: + args = append(args, rpc.Argument{Name: v.Name, DataType: rpc.DataUint64, Value: v.ValueUint64}) + case 0x5: + args = append(args, rpc.Argument{Name: v.Name, DataType: rpc.DataString, Value: v.ValueString}) + } + } + } + } + + if entrypoint == "" { + errorText.Text = "missing initializing entrypoint" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + function := contract.Functions[entrypoint] + + var paramList []fyne.Widget + if len(function.Params) > 0 { + params := function.Params + for i := range params { + p := i + entry := widget.NewEntry() + entry.PlaceHolder = params[p].Name + if params[p].Type == 0x4 { + entry.PlaceHolder = params[p].Name + " (Numbers Only)" + } + + entry.Validator = func(s string) error { + switch params[p].Type { + case 0x5: + return nil + case 0x4: + if params[p].Name+" (Numbers Only)" == entry.PlaceHolder { + amount, err := globals.ParseAmount(s) + if err != nil { + logger.Debugf("[%s] Param error: %s\n", params[p].Name, err) + return err + } else { + logger.Debugf("[%s] Amount: %d\n", params[p].Name, amount) + } + } + } + + return nil + } + + paramList = append(paramList, entry) + } + + overlay := session.Window.Canvas().Overlays() + + header := canvas.NewText("INSTALL SMART CONTRACT", colors.Gray) + header.TextSize = 14 + header.Alignment = fyne.TextAlignCenter + header.TextStyle = fyne.TextStyle{Bold: true} + + subHeader := canvas.NewText(fmt.Sprintf("%s params", entrypoint), colors.Account) + subHeader.TextSize = 22 + subHeader.Alignment = fyne.TextAlignCenter + subHeader.TextStyle = fyne.TextStyle{Bold: true} + + linkClose := widget.NewHyperlinkWithStyle("Close", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkClose.OnTapped = func() { + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + } + + span := canvas.NewRectangle(color.Transparent) + span.SetMinSize(fyne.NewSize(ui.Width, 10)) + + overlay.Add( + container.NewStack( + &iframe{}, + canvas.NewRectangle(colors.DarkMatter), + ), + ) + + paramsContainer := container.NewVBox() + + btnInstall := widget.NewButton("Install", nil) + + overlay.Add( + container.NewStack( + &iframe{}, + container.NewCenter( + container.NewVBox( + span, + container.NewCenter( + header, + ), + rectSpacer, + rectSpacer, + container.NewCenter( + subHeader, + ), + widget.NewLabel(""), + //selectRingMembers, + rectSpacer, + rectSpacer, + paramsContainer, + rectSpacer, + rectSpacer, + btnInstall, + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkClose, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + ), + ), + ), + ) + + for _, w := range paramList { + c := container.NewStack( + span, + w, + ) + + paramsContainer.Add(c) + paramsContainer.Refresh() + } + + btnInstall.OnTapped = func() { + validated := true + for _, w := range paramList { + entry, ok := w.(*widget.Entry) + if !ok { + continue + } + + if entry.Validate() != nil { + entry.FocusGained() + entry.FocusLost() + validated = false + break + } + } + + if !validated { + return + } + + btnInstall.Text = "Installing..." + btnInstall.Disable() + btnInstall.Refresh() + + verificationOverlay( + true, + "CONTRACT EDITOR", + "", + "", + func(b bool) { + if b { + _, err := installSC(code, args) + if err != nil { + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + unsavedChanges = false + errorText.Text = "contract installed successfully" + errorText.Color = colors.Green + errorText.Refresh() + } + + overlay.Top().Hide() + overlay.Remove(overlay.Top()) + overlay.Remove(overlay.Top()) + }, + ) + } + + paramsContainer.Refresh() + overlay.Top().Show() + } else { + verificationOverlay( + true, + "CONTRACT EDITOR", + "", + "", + func(b bool) { + if b { + _, err := installSC(code, args) + if err != nil { + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + return + } + + unsavedChanges = false + errorText.Text = "contract installed successfully" + errorText.Color = colors.Green + errorText.Refresh() + } + }, + ) + } + } + } + + top := container.NewVBox( + rectSpacer, + rectSpacer, + heading, + ) + + center := container.NewStack( + rectWidth100, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + container.NewVBox( + rectSpacer, + container.NewStack( + rectBox, + entryForm, + ), + rectSpacer, + ), + ), + layout.NewSpacer(), + ), + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + body := container.NewVBox( + top, + center, + ) + + layout := container.NewStack( + frame, + container.NewBorder( + body, + bottom, + nil, + nil, + ), + ) + + return NewVScroll(layout) +} + +func layoutTELA() fyne.CanvasObject { + session.Domain = "app.tela" + + var history []string + var historyData binding.StringList + var historyList *widget.List + + var searching []string + var searchData binding.StringList + var searchList *widget.List + + var serving []string + var servingData binding.StringList + var servingList *widget.List + + frame := &iframe{} + rectLeft := canvas.NewRectangle(color.Transparent) + rectLeft.SetMinSize(fyne.NewSize(ui.Width*0.40, 35)) + + rectRight := canvas.NewRectangle(color.Transparent) + rectRight.SetMinSize(fyne.NewSize(ui.Width*0.58, 35)) + + rectList := canvas.NewRectangle(color.Transparent) + rectList.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.36)) + + rectWidth := canvas.NewRectangle(color.Transparent) + rectWidth.SetMinSize(fyne.NewSize(ui.Width, 10)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + heading := canvas.NewText("T E L A B R O W S E R", colors.Gray) + heading.TextSize = 16 + heading.Alignment = fyne.TextAlignCenter + heading.TextStyle = fyne.TextStyle{Bold: true} + + results := canvas.NewText("", colors.Green) + results.TextSize = 13 + + labelLastScan := canvas.NewText("", colors.Green) + labelLastScan.TextSize = 13 + + errorText := canvas.NewText(" ", colors.Green) + errorText.TextSize = 12 + errorText.Alignment = fyne.TextAlignCenter + + historyData = binding.BindStringList(&history) + historyList = widget.NewListWithData(historyData, + func() fyne.CanvasObject { + return container.NewStack( + container.NewVBox( + container.NewStack( + widget.NewLabel(""), + ), + ), + ) + }, + func(di binding.DataItem, co fyne.CanvasObject) { + dat := di.(binding.String) + str, err := dat.Get() + if err != nil { + return + } + + split := strings.Split(str, ";;;") + + co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(split[0]) + }, + ) + + searchData = binding.BindStringList(&searching) + searchList = widget.NewListWithData(searchData, + func() fyne.CanvasObject { + return container.NewStack( + container.NewVBox( + container.NewStack( + widget.NewLabel(""), + ), + ), + ) + }, + func(di binding.DataItem, co fyne.CanvasObject) { + dat := di.(binding.String) + str, err := dat.Get() + if err != nil { + return + } + + split := strings.Split(str, ";;;") + + co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*widget.Label).SetText(split[0]) + }, + ) + + servingData = binding.BindStringList(&serving) + servingList = widget.NewListWithData(servingData, + func() fyne.CanvasObject { + return container.NewStack( + container.NewHBox( + container.NewStack( + rectLeft, + widget.NewLabel(""), + ), + container.NewStack( + rectRight, + widget.NewLabel(""), + ), + ), + ) + }, + func(di binding.DataItem, co fyne.CanvasObject) { + dat := di.(binding.String) + str, err := dat.Get() + if err != nil { + return + } + + split := strings.Split(str, ";;;") + + co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[1]) + co.(*fyne.Container).Objects[0].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*widget.Label).SetText(split[0]) + }, + ) + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + entryHistory := widget.NewEntry() + entryHistory.PlaceHolder = "Search History" + entryHistory.Disable() + + entryServeSCID := widget.NewEntry() + entryServeSCID.PlaceHolder = "Serve by SCID" + + entryAddSCID := widget.NewEntry() + entryAddSCID.PlaceHolder = "Add SCID" + entryAddSCID.OnChanged = func(s string) { + errorText.Text = "" + errorText.Refresh() + if len(s) == 64 { + if gnomon.Index != nil { + if gnomon.GetAllSCIDVariableDetails(s) != nil { + errorText.Text = "scid already exists" + errorText.Color = colors.Yellow + errorText.Refresh() + return + } + + code, err := getContractCode(s) + if err != nil || code == "" { + errorText.Text = "could not get scid" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + err = gnomon.AddSCIDToIndex(s) + if err != nil { + errorText.Text = "error adding scid" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + entryAddSCID.SetText("") + errorText.Text = "scid added" + errorText.Color = colors.Green + errorText.Refresh() + } + } + } + + entrySearchCompletions := []string{"author:", "durl:", "name:", "my:"} + entrySearch := x.NewCompletionEntry(entrySearchCompletions) + entrySearch.PlaceHolder = "Search TELA" + entrySearch.Disable() + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + var isSearching bool + + linkBack := widget.NewHyperlinkWithStyle("Back to Dashboard", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + session.Domain = "app.wallet" // break any loops now + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutDashboard()) + removeOverlays() + } + + linkClearHistory := widget.NewHyperlinkWithStyle("Clear All", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: false}) + linkClearHistory.OnTapped = func() { + verificationOverlay( + false, + "TELA BROWSER", + "Clear history?", + "Confirm", + func(b bool) { + if b { + if gnomon.Index == nil || session.Offline { + return + } + + shard, err := GetShard() + if err != nil { + return + } + + store, err := graviton.NewDiskStore(shard) + if err != nil { + return + } + + ss, err := store.LoadSnapshot(0) + if err != nil { + return + } + + tree, err := ss.GetTree("TELA History") + if err != nil { + return + } + + c := tree.Cursor() + + for k, _, err := c.First(); err == nil; k, _, err = c.Next() { + DeleteKey(tree.GetName(), k) + } + + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(layoutTELA()) + } + }, + ) + } + + wSelect := widget.NewSelect([]string{"History", "Active", "Search", "Settings"}, nil) + wSelect.SetSelectedIndex(0) + + btnShutdown := widget.NewButton("Shutdown TELA", nil) + + var restrictiveMode, rescanRecheck bool + var lastScan, searchExclusions, sortBy string + var minLikes float64 + var telaSCIDs []string + var telaSearch []INDEXwithRatings + var sAll = map[string]bool{} + + getSearchResults := func() { + fyne.Do(func() { + entrySearch.Disable() + entryAddSCID.Disable() + }) + if isSearching { + return + } + + isSearching = true + + // Already scanned + if len(telaSearch) > 0 { + searching = telaSearchDisplayAll(telaSearch, sortBy) + searchData.Set(searching) + + results.Text = fmt.Sprintf(" TELA SCIDs: %d", len(telaSearch)) + results.Color = colors.Green + fyne.Do(func() { + entrySearch.Enable() + entryAddSCID.Enable() + }) + + labelLastScan.Text = fmt.Sprintf(" %s", lastScan) + labelLastScan.Color = colors.Green + isSearching = false + + fyne.Do(func() { + results.Refresh() + labelLastScan.Refresh() + }) + + return + } + + telaSearch = []INDEXwithRatings{} + searchData.Set(nil) + labelLastScan.Text = "" + + fyne.Do(func() { + btnShutdown.Disable() + labelLastScan.Refresh() + }) + + defer func() { + isSearching = false + if !session.Offline && gnomon.Index != nil { + if btnShutdown.Disabled() { + fyne.Do(func() { + btnShutdown.Enable() + }) + } + } + }() + + if gnomon.Index == nil { + return + } + + for gnomon.Index.LastIndexedHeight < int64(engram.Disk.Get_Daemon_Height()) { + if !strings.Contains(session.Domain, ".tela") { + return + } + + fyne.Do(func() { + entrySearch.Disable() + entryAddSCID.Disable() + }) + results.Text = " Gnomon is syncing..." + results.Color = colors.Yellow + + fyne.Do(func() { + results.Refresh() + }) + + time.Sleep(time.Second) + } + + if !restrictiveMode { + storedAllSCIDs, err := GetEncryptedValue("TELA Search", []byte("Searched SCIDs")) + if err != nil { + // Nothing stored, scan for SCIDs + sAll = map[string]bool{} + logger.Debugf("[Engram] Could not get stored TELA Searched SCIDs: %s\n", err) + } else { + json.Unmarshal(storedAllSCIDs, &sAll) + } + } + + storedSCIDs, err := GetEncryptedValue("TELA Search", []byte("SCIDs")) + if err != nil { + // Nothing stored, scan for SCIDs + telaSCIDs = []string{} + logger.Debugf("[Engram] Could not get stored TELA SCIDs: %s\n", err) + } else { + // Have stored SCIDs + json.Unmarshal(storedSCIDs, &telaSCIDs) + + results.Text = " Scanning..." + results.Color = colors.Yellow + + fyne.Do(func() { + results.Refresh() + }) + + for i, sc := range telaSCIDs { + if index, err := tela.GetINDEXInfo(sc, session.Daemon); err == nil { + if gnomon.GetAllSCIDVariableDetails(sc) == nil { + results.Text = fmt.Sprintf(" Adding... (%d / %d)", i, len(telaSCIDs)) + results.Color = colors.Yellow + fyne.Do(func() { + results.Refresh() + }) + + gnomon.AddSCIDToIndex(sc) + } + + if !restrictiveMode { + _, ratings, err := getLikesRatio(sc, index.DURL, searchExclusions, minLikes) + if err != nil { + continue + } + + telaSearch = append(telaSearch, INDEXwithRatings{ratings: ratings, INDEX: index}) + } + } + } + + // If recheck is false, run a rescan that pulls in any new contracts when first OnChanged to Search + if rescanRecheck && (len(telaSearch) > 0 || len(telaSCIDs) > 0) { + searching = telaSearchDisplayAll(telaSearch, sortBy) + searchData.Set(searching) + + results.Text = fmt.Sprintf(" TELA SCIDs: %d", len(telaSearch)) + results.Color = colors.Green + fyne.Do(func() { + entrySearch.Enable() + entryAddSCID.Enable() + }) + + if last, err := GetEncryptedValue("TELA Search", []byte("Last Scan")); err == nil { + lastScan = string(last) + labelLastScan.Text = fmt.Sprintf(" %s", lastScan) + labelLastScan.Color = colors.Green + } + + if restrictiveMode && len(telaSearch) < 1 { + errorText.Text = "TELA is in restrictive mode" + errorText.Color = colors.Yellow + } + + fyne.Do(func() { + results.Refresh() + labelLastScan.Refresh() + errorText.Refresh() + }) + + return + } + } + + var wg sync.WaitGroup + + var all = map[string]string{} + if restrictiveMode { + for _, sc := range telaSCIDs { + all[sc] = "" + } + } else { + all = gnomon.GetAllOwnersAndSCIDs() + } + + allLen := len(all) + scanned := 0 + workers := make(chan struct{}, runtime.NumCPU()) + + for sc := range all { + workers <- struct{}{} + if gnomon.Index == nil || !strings.Contains(session.Domain, ".tela") { + break + } + + scanned++ + results.Text = fmt.Sprintf(" Scanning... (%d / %d)", scanned, allLen) + results.Color = colors.Yellow + + fyne.Do(func() { + results.Refresh() + }) + + wg.Add(1) + go func(scid string) { + defer func() { + <-workers + wg.Done() + }() + + // Restrictive mode rechecks everytime, if rescan recheck is disabled SCIDs that have already been scanned won't be rechecked for faster list population + if !restrictiveMode && !rescanRecheck && (sAll[scid] || scidExist(telaSCIDs, scid)) { + return + } + + vs, _, err := gnomon.Index.GetSCIDValuesByKey([]*structures.SCIDVariable{}, scid, "telaVersion", gnomon.Index.ChainHeight) + if err != nil { + return + } + + if vs != nil { + if index, err := tela.GetINDEXInfo(scid, session.Daemon); err == nil { + if len(index.DOCs) > 0 { + if strings.HasSuffix(index.DURL, tela.TAG_LIBRARY) || strings.HasSuffix(index.DURL, tela.TAG_DOC_SHARDS) || strings.HasSuffix(index.DURL, tela.TAG_BOOTSTRAP) { + return + } + + if gnomon.GetAllSCIDVariableDetails(scid) == nil { + gnomon.AddSCIDToIndex(scid) + } + + // In restrictive mode, the list is initialzed from telaSCIDs + if !restrictiveMode { + telaSCIDs = append(telaSCIDs, scid) + } + + _, ratings, err := getLikesRatio(scid, index.DURL, searchExclusions, minLikes) + if err != nil { + return + } + + telaSearch = append(telaSearch, INDEXwithRatings{ratings: ratings, INDEX: index}) + } + } + } + }(sc) + } + + if !strings.Contains(session.Domain, ".tela") { + return + } + + wg.Wait() + + searching = telaSearchDisplayAll(telaSearch, sortBy) + searchData.Set(searching) + + results.Text = fmt.Sprintf(" TELA SCIDs: %d", len(telaSearch)) + results.Color = colors.Green + + fyne.Do(func() { + results.Refresh() + }) + + timeNow := time.Now().Format(time.RFC822) + StoreEncryptedValue("TELA Search", []byte("Last Scan"), []byte(timeNow)) + if storeSCIDs, err := json.Marshal(telaSCIDs); err == nil { + StoreEncryptedValue("TELA Search", []byte("SCIDs"), storeSCIDs) + } + + if !restrictiveMode && !rescanRecheck { + for sc := range all { + sAll[sc] = true + } + + if sAllSCIDs, err := json.Marshal(sAll); err == nil { + StoreEncryptedValue("TELA Search", []byte("Searched SCIDs"), sAllSCIDs) + } + } else if restrictiveMode && len(searching) < 1 { + errorText.Text = "TELA is in restrictive mode" + errorText.Color = colors.Yellow + errorText.Refresh() + } + + lastScan = timeNow + labelLastScan.Text = fmt.Sprintf(" %s", lastScan) + labelLastScan.Color = colors.Green + + fyne.Do(func() { + labelLastScan.Refresh() + entrySearch.Enable() + entryAddSCID.Enable() + }) + } + + entrySearch.OnChanged = func(s string) { + errorText.Text = "" + errorText.Refresh() + + if s == "" { + go getSearchResults() + if !a.Driver().Device().IsMobile() { + entrySearch.HideCompletion() + } + + return + } + + if !a.Driver().Device().IsMobile() { + if len(s) < 3 { + entrySearch.SetOptions(append([]string{s}, entrySearchCompletions...)) + entrySearch.ShowCompletion() + } else { + entrySearch.HideCompletion() + } + } + + var queryResult []INDEXwithRatings + query := strings.Split(s, ":") + if len(query) < 2 { + if len(s) == 64 { + // Search scid + for _, ind := range telaSearch { + _, _, err := getLikesRatio(ind.SCID, ind.DURL, searchExclusions, minLikes) + if err != nil { + continue + } + + if ind.SCID == s { + queryResult = append(queryResult, ind) + break + } + } + } else { + // Search all + for _, ind := range telaSearch { + _, _, err := getLikesRatio(ind.SCID, ind.DURL, searchExclusions, minLikes) + if err != nil { + continue + } + + data := []string{ + ind.NameHdr, + ind.DescrHdr, + ind.DURL, + ind.SCID, + } + + for _, split := range data { + if strings.Contains(split, s) { + queryResult = append(queryResult, ind) + break + } + } + } + } + + searching = telaSearchDisplayAll(queryResult, sortBy) + searchData.Set(searching) + searchList.Refresh() + + results.Text = fmt.Sprintf(" TELA SCIDs: %d", len(queryResult)) + results.Color = colors.Green + results.Refresh() + entrySearch.Enable() + + return + } + + switch query[0] { + case "name": + for _, ind := range telaSearch { + _, _, err := getLikesRatio(ind.SCID, ind.DURL, searchExclusions, minLikes) + if err != nil { + continue + } + + if strings.Contains(ind.NameHdr, query[1]) { + queryResult = append(queryResult, ind) + } + } + case "durl": + for _, ind := range telaSearch { + _, _, err := getLikesRatio(ind.SCID, ind.DURL, searchExclusions, minLikes) + if err != nil { + continue + } + + if strings.Contains(ind.DURL, query[1]) { + queryResult = append(queryResult, ind) + } + } + case "my": + for _, ind := range telaSearch { + if ind.Author == engram.Disk.GetAddress().String() { + queryResult = append(queryResult, ind) + } + } + case "author": + if len(query[1]) != 66 { + return + } + + _, err := globals.ParseValidateAddress(query[1]) + if err != nil { + return + } + + for _, ind := range telaSearch { + _, _, err := getLikesRatio(ind.SCID, ind.DURL, searchExclusions, minLikes) + if err != nil { + continue + } + + if ind.Author == query[1] { + queryResult = append(queryResult, ind) + } + } + default: + errorText.Text = "unknown search prefix" + errorText.Color = colors.Red + errorText.Refresh() + + return + } + + searching = telaSearchDisplayAll(queryResult, sortBy) + searchData.Set(searching) + searchList.Refresh() + + results.Text = fmt.Sprintf(" TELA SCIDs: %d", len(queryResult)) + results.Color = colors.Green + results.Refresh() + entrySearch.Enable() + } + + entryAddSCID.OnChanged = func(s string) { + if len(s) == 64 { + defer entryAddSCID.SetText("") + bootstrapIndex, err := tela.GetINDEXInfo(s, session.Daemon) + if err != nil { + logger.Errorf("[GetINDEXInfo] Bootstrap: %s\n", err) + errorText.Text = "could not get bootstrap SCID" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + if !strings.HasSuffix(bootstrapIndex.DURL, tela.TAG_BOOTSTRAP) { + logger.Errorf("[Engram] SCID %s is not a TELA bootstrap INDEX\n", s) + errorText.Text = "invalid bootstrap SCID" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + storeSCIDs, err := json.Marshal(bootstrapIndex.DOCs) + if err != nil { + logger.Errorf("[Engram] Could not marshal bootstrap: %s\n", err) + errorText.Text = "error initializing bootstrap" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + err = StoreEncryptedValue("TELA Search", []byte("SCIDs"), storeSCIDs) + if err != nil { + logger.Errorf("[Engram] Could store bootstrap: %s\n", err) + errorText.Text = "error storing bootstrap" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + telaSCIDs = bootstrapIndex.DOCs + errorText.Text = "bootstrap initialized" + errorText.Color = colors.Green + errorText.Refresh() + + go getSearchResults() + } + } + + // Refresh the active server list + refreshServerList := func() { + time.Sleep(time.Second * 2) + var serversRunning []string + for _, serv := range tela.GetServerInfo() { + serversRunning = append(serversRunning, serv.Name+";;;"+serv.Address+";;;;;;"+serv.SCID) + } + + sort.Strings(serversRunning) + servingData.Set(serversRunning) + servingList.Refresh() + if !isSearching && wSelect.Selected == "Active" { + results.Text = fmt.Sprintf(" Active Servers: %d", len(serversRunning)) + results.Color = colors.Green + results.Refresh() + } + } + + btnShutdown.OnTapped = func() { + switch btnShutdown.Text { + case "Rescan Blockchain": + verificationOverlay( + false, + "TELA BROWSER", + "Rescan blockchain?", + "Confirm", + func(b bool) { + if b { + if isSearching { + return + } + + telaSearch = []INDEXwithRatings{} + telaSCIDs = []string{} + if rescanRecheck { + DeleteKey("TELA Search", []byte("SCIDs")) + DeleteKey("TELA Search", []byte("Searched SCIDs")) + } + errorText.Text = "" + errorText.Refresh() + go getSearchResults() + } + }, + ) + default: + verificationOverlay( + false, + "TELA BROWSER", + "Shutdown all active TELA servers?", + "Confirm", + func(b bool) { + if b { + tela.ShutdownTELA() + servingData.Set(nil) + errorText.Text = "" + errorText.Refresh() + } + }, + ) + } + + go refreshServerList() + } + + entrySpacer := canvas.NewRectangle(color.Transparent) + entrySpacer.SetMinSize(fyne.NewSize(140, 0)) + + entryPort := widget.NewEntry() + entryPort.SetText(strconv.Itoa(tela.PortStart())) + entryPort.Validator = func(s string) (err error) { + i, err := strconv.Atoi(s) + if err != nil { + return fmt.Errorf("invalid port") + } + + return tela.SetPortStart(i) + } + + entryMinLikes := widget.NewEntry() + entryMinLikes.SetPlaceHolder("Likes %") + if storedMinLikes, err := GetEncryptedValue("TELA Settings", []byte("Min Likes")); err == nil { + if f, err := strconv.ParseFloat(string(storedMinLikes), 64); err == nil { + minLikes = f + entryMinLikes.SetText(string(storedMinLikes)) + } + } else { + minLikes = 30 + entryMinLikes.SetText("30") + } + + entryMinLikes.Validator = func(s string) (err error) { + i, err := strconv.Atoi(s) + if err != nil { + return fmt.Errorf("invalid percent") + } + + if i < 0 || i > 100 { + err = fmt.Errorf("must be 0 to 100") + return + } + + // Clear search results but keep scids + telaSearch = []INDEXwithRatings{} + + minLikes = float64(i) + StoreEncryptedValue("TELA Settings", []byte("Min Likes"), []byte(s)) + + return + } + + entryExclusions := widget.NewEntry() + entryExclusions.SetPlaceHolder("dURL Exclusions (exclude1,exclude2)") + if storedExclusions, err := GetEncryptedValue("TELA Settings", []byte("Exclusions")); err == nil { + searchExclusions = string(storedExclusions) + entryExclusions.SetText(searchExclusions) + } + + entryExclusions.OnChanged = func(s string) { + if s != "" { + StoreEncryptedValue("TELA Settings", []byte("Exclusions"), []byte(s)) + } else { + DeleteKey("TELA Settings", []byte("Exclusions")) + } + + // Clear search results but keep scids + telaSearch = []INDEXwithRatings{} + + searchExclusions = s + } + + wUpdates := widget.NewSelect([]string{xswd.Deny.String(), xswd.Allow.String()}, nil) + if tela.UpdatesAllowed() { + wUpdates.SetSelectedIndex(1) + } else { + wUpdates.SetSelectedIndex(0) + } + + wUpdates.OnChanged = func(s string) { + if s == xswd.Allow.String() { + tela.AllowUpdates(true) + } else { + tela.AllowUpdates(false) + } + } + + if storedRescanRecheck, err := GetEncryptedValue("TELA Settings", []byte("Rescan Recheck")); err == nil { + if string(storedRescanRecheck) == "Yes" { + rescanRecheck = true + } else { + rescanRecheck = false + } + } + + wRescanRecheck := widget.NewSelect([]string{"No", "Yes"}, nil) + if rescanRecheck { + wRescanRecheck.SetSelectedIndex(1) + } else { + wRescanRecheck.SetSelectedIndex(0) + } + + wRescanRecheck.OnChanged = func(s string) { + if s == "Yes" { + rescanRecheck = true + } else { + rescanRecheck = false + } + + StoreEncryptedValue("TELA Settings", []byte("Rescan Recheck"), []byte(s)) + } + + sortByOptions := []string{"Ratings", "A-Z", "Z-A"} + wSortBy := widget.NewSelect(sortByOptions, nil) + if storedSortBy, err := GetEncryptedValue("TELA Settings", []byte("Sort By")); err == nil { + sortBy = string(storedSortBy) + } else { + sortBy = sortByOptions[0] + } + + wSortBy.SetSelected(sortBy) + wSortBy.OnChanged = func(s string) { + if s != "" { + // Clear search results but keep scids + telaSearch = []INDEXwithRatings{} + sortBy = s + StoreEncryptedValue("TELA Settings", []byte("Sort By"), []byte(s)) + } + } + + historyBox := container.NewStack( + rectList, + historyList, + ) + + searchBox := container.NewStack( + rectList, + searchList, + ) + + servingBox := container.NewStack( + rectList, + servingList, + ) + + linkSpacer := canvas.NewRectangle(color.Transparent) + linkSpacer.SetMinSize(fyne.NewSize(0, 40)) + + wMode := widget.NewCheck("Restrictive Mode", nil) + wMode.SetChecked(true) + restrictiveMode = true + if storedTelaMode, err := GetEncryptedValue("TELA Settings", []byte("Mode")); err == nil { + switch string(storedTelaMode) { + case "Unrestrictive": + wMode.SetChecked(false) + restrictiveMode = false + default: + // in restrictive mode + } + } + + linkResetDefaults := widget.NewHyperlinkWithStyle("Reset Default Settings", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkResetDefaults.OnTapped = func() { + verificationOverlay( + false, + "TELA BROWSER", + "Reset to default settings?", + "Confirm", + func(b bool) { + if b { + wMode.SetChecked(true) + wUpdates.SetSelectedIndex(0) + wRescanRecheck.SetSelectedIndex(0) + wSortBy.SetSelectedIndex(0) + entryPort.SetText(strconv.Itoa(tela.DEFAULT_PORT_START)) + entryMinLikes.SetText("30") + entryExclusions.SetText("") + } + }, + ) + } + + linkSearchClear := widget.NewHyperlinkWithStyle("Delete Search Data", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkSearchClear.OnTapped = func() { + verificationOverlay( + false, + "TELA BROWSER", + "Delete stored search data?", + "Confirm", + func(b bool) { + if b { + telaSearch = []INDEXwithRatings{} + telaSCIDs = []string{} + DeleteKey("TELA Search", []byte("SCIDs")) + DeleteKey("TELA Search", []byte("Searched SCIDs")) + DeleteKey("TELA Search", []byte("Last Scan")) + linkSearchClear.Hide() + } + }, + ) + } + + wMode.OnChanged = func(b bool) { + if b { + restrictiveMode = true + DeleteKey("TELA Settings", []byte("Mode")) + return + } + + go func() { + unrestrictedPermission, err := AskPermissionForRequestE("TELA Unresctricted Mode", "TELA R OFF") + if err != nil { + logger.Errorf("[Engram] TELA unrestricted mode: %s\n", err) + errorText.Text = "error requesting permission" + errorText.Color = colors.Red + + fyne.Do(func() { + errorText.Refresh() + }) + + return + } + + if unrestrictedPermission != xswd.Allow { + wMode.SetChecked(true) + return + } + + StoreEncryptedValue("TELA Settings", []byte("Mode"), []byte("Unrestrictive")) + restrictiveMode = false + telaSearch = []INDEXwithRatings{} + telaSCIDs = []string{} + DeleteKey("TELA Search", []byte("SCIDs")) + DeleteKey("TELA Search", []byte("Searched SCIDs")) + DeleteKey("TELA Search", []byte("Last Scan")) + }() + } + + settingsBox := container.NewVScroll( + container.NewStack( + rectList, + container.NewBorder( + nil, + nil, + nil, + layout.NewSpacer(), + container.NewVBox( + container.NewBorder( + nil, + nil, + nil, + nil, + container.NewCenter(wMode), + ), + rectSpacer, + container.NewBorder( + nil, + nil, + widget.NewRichTextFromMarkdown("### Allow Content Updates"), + wUpdates, + ), + container.NewBorder( + nil, + nil, + widget.NewRichTextFromMarkdown("### Rescan Recheck"), + wRescanRecheck, + ), + container.NewBorder( + nil, + nil, + widget.NewRichTextFromMarkdown("### Sort By"), + wSortBy, + ), + container.NewBorder( + nil, + nil, + widget.NewRichTextFromMarkdown("### Start Port Range"), + container.NewStack( + entrySpacer, + entryPort, + ), + ), + container.NewBorder( + nil, + nil, + widget.NewRichTextFromMarkdown("### Search Min Likes %"), + container.NewStack( + entrySpacer, + entryMinLikes, + ), + ), + container.NewBorder( + widget.NewRichTextFromMarkdown("### Search Exclusions"), + nil, + nil, + nil, + entryExclusions, + ), + rectSpacer, + rectSpacer, + container.NewStack( + linkSpacer, + linkResetDefaults, + ), + rectSpacer, + container.NewStack( + linkSpacer, + linkSearchClear, + ), + rectSpacer, + ), + ), + ), + ) + settingsBox.SetMinSize(fyne.NewSize(ui.Width, ui.Height*0.36)) + + headerSpacer := canvas.NewRectangle(color.Transparent) + headerSpacer.SetMinSize(fyne.NewSize(0, 35)) + + layoutBrowser := container.NewStack( + rectWidth, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + rectSpacer, + container.NewHBox( + results, + layout.NewSpacer(), + headerSpacer, + linkClearHistory, + ), + rectSpacer, + rectSpacer, + entryHistory, + errorText, + rectSpacer, + wSelect, + rectSpacer, + historyBox, + rectSpacer, + rectSpacer, + rectSpacer, + btnShutdown, + ), + layout.NewSpacer(), + ), + ) + + var historyFound = true + var historyResults []string + + getHistoryResults := func() { + if !historyFound { + return + } + + historyFound = false + historyResults = nil + historyData.Set(nil) + defer func() { + historyFound = true + }() + + if engram.Disk != nil && gnomon.Index != nil { + for gnomon.Index.LastIndexedHeight < int64(engram.Disk.Get_Daemon_Height()) { + if !strings.Contains(session.Domain, ".tela") { + return + } + + results.Text = " Gnomon is syncing..." + results.Color = colors.Yellow + + fyne.Do(func() { + entryHistory.Disable() + results.Refresh() + }) + + time.Sleep(time.Second) + } + + results.Text = " Loading previous search history..." + results.Color = colors.Yellow + + fyne.Do(func() { + entryHistory.Enable() + results.Refresh() + }) + + shard, err := GetShard() + if err != nil { + return + } + + store, err := graviton.NewDiskStore(shard) + if err != nil { + return + } + + ss, err := store.LoadSnapshot(0) + + if err != nil { + return + } + + tree, err := ss.GetTree("TELA History") + if err != nil { + return + } + + c := tree.Cursor() + + for k, _, err := c.First(); err == nil; k, _, err = c.Next() { + scid := crypto.HashHexToHash(string(k)) + + title, desc, _, _, _ := getContractHeader(scid) + + if title == "" { + title = scid.String() + } + + if len(title) > 36 { + title = title[0:36] + "..." + } + + if desc == "" { + desc = "N/A" + } + + if len(desc) > 40 { + desc = desc[0:40] + "..." + } + + historyResults = append(historyResults, title+";;;"+desc+";;;;;;"+scid.String()) + } + + sort.Strings(historyResults) + history = historyResults + historyData.Set(history) + + results.Text = fmt.Sprintf(" Search History: %d", len(historyResults)) + results.Color = colors.Green + + fyne.Do(func() { + historyList.Refresh() + results.Refresh() + btnShutdown.Enable() + }) + } + } + + entryHistory.OnChanged = func(s string) { + if s == "" { + go getHistoryResults() + return + } + + var queryResult []string + for _, data := range history { + for _, split := range strings.Split(data, ";;;") { + if strings.Contains(split, s) { + queryResult = append(queryResult, data) + break + } + } + } + + sort.Strings(queryResult) + history = queryResult + historyData.Set(history) + + results.Text = fmt.Sprintf(" Search History: %d", len(queryResult)) + results.Color = colors.Green + entryHistory.Enable() + + fyne.Do(func() { + historyList.Refresh() + results.Refresh() + }) + } + + wSelect.OnChanged = func(s string) { + errorText.Text = "" + errorText.Refresh() + if !session.Offline { + btnShutdown.Enable() + } + + switch s { + case "Active": + servingData.Set(nil) + + var serversRunning []string + for _, serv := range tela.GetServerInfo() { + serversRunning = append(serversRunning, serv.Name+";;;"+serv.Address+";;;;;;"+serv.SCID) + } + + sort.Strings(serversRunning) + servingData.Set(serversRunning) + + if !isSearching { + if session.Offline { + results.Text = " Disabled in offline mode." + results.Color = colors.Gray + results.Refresh() + } else { + results.Text = fmt.Sprintf(" Active Servers: %d", len(serversRunning)) + results.Color = colors.Green + results.Refresh() + } + } + + labelLastScan.Text = "" + labelLastScan.Refresh() + layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[3] = labelLastScan + layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[4] = entryServeSCID + layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[9] = servingBox + btnShutdown.Text = "Shutdown TELA" + btnShutdown.Refresh() + case "History": + if gnomon.Index == nil { + results.Text = " Gnomon is inactive." + results.Color = colors.Gray + results.Refresh() + } + + if isSearching { + linkClearHistory.Hide() + } else { + go getHistoryResults() + linkClearHistory.Show() + } + + layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[3] = linkClearHistory + layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[4] = entryHistory + layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[9] = historyBox + btnShutdown.Text = "Shutdown TELA" + btnShutdown.Refresh() + servingList.UnselectAll() + case "Search": + if gnomon.Index == nil { + results.Text = " Gnomon is inactive." + results.Color = colors.Gray + results.Refresh() + } + + go getSearchResults() + + layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[3] = labelLastScan + layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[4] = entrySearch + layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[9] = searchBox + btnShutdown.Text = "Rescan Blockchain" + btnShutdown.Refresh() + if isSearching { + btnShutdown.Disable() + } + case "Settings": + if !isSearching { + if session.Offline { + results.Text = " Disabled in offline mode." + results.Color = colors.Gray + results.Refresh() + } else { + results.Text = fmt.Sprintf(" Active Servers: %d", len(tela.GetServerInfo())) + results.Color = colors.Green + results.Refresh() + linkResetDefaults.Show() + if gnomon.Index != nil { + wRescanRecheck.Enable() + entryMinLikes.Enable() + entryExclusions.Enable() + } + + if _, err := GetEncryptedValue("TELA Search", []byte("SCIDs")); err == nil { + linkSearchClear.Show() + } else { + linkSearchClear.Hide() + } + } + } else { + wRescanRecheck.Disable() + entryMinLikes.Disable() + entryExclusions.Disable() + linkResetDefaults.Hide() + linkSearchClear.Hide() + } + + labelLastScan.Text = "" + labelLastScan.Refresh() + layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[3] = labelLastScan + layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[4] = entryAddSCID + layoutBrowser.Objects[1].(*fyne.Container).Objects[1].(*fyne.Container).Objects[9] = settingsBox + btnShutdown.Text = "Shutdown TELA" + btnShutdown.Refresh() + servingList.UnselectAll() + } + } + + if session.Offline { + results.Text = " Disabled in offline mode." + results.Color = colors.Gray + results.Refresh() + wUpdates.Disable() + entryServeSCID.Disable() + entryAddSCID.Disable() + wRescanRecheck.Disable() + entryExclusions.Disable() + entryMinLikes.Disable() + entryPort.Disable() + btnShutdown.Disable() + } else if gnomon.Index == nil { + results.Text = " Gnomon is inactive." + results.Color = colors.Gray + results.Refresh() + entryAddSCID.Disable() + wRescanRecheck.Disable() + entryExclusions.Disable() + entryMinLikes.Disable() + } + + entryServeSCID.OnChanged = func(s string) { + errorText.Text = "" + errorText.Refresh() + if len(s) == 64 { + go func() { + // Create a TELALink to parse and get its ratings for user to verifiy before serving the content + telaLink := TELALink_Params{TelaLink: fmt.Sprintf("tela://open/%s", s)} + linkPermission, err := AskPermissionForRequestE("Open TELA Link", telaLink) + if err != nil { + logger.Errorf("[Engram] Open TELA link: %s\n", err) + errorText.Text = "error could not open TELA" + errorText.Color = colors.Red + + fyne.Do(func() { + errorText.Refresh() + }) + + return + } + + if linkPermission != xswd.Allow { + entryServeSCID.SetText("") + return + } + + showLoadingOverlay() + defer func() { + go refreshServerList() + }() + + var index tela.INDEX + + // If serving without Gnomon, scid will not end up in history + if gnomon.Index != nil { + result := gnomon.GetAllSCIDVariableDetails(s) + if len(result) == 0 { + _, err := getTxData(s) + if err != nil { + return + } + } + + index.NameHdr, index.DescrHdr, _, _, _ = getContractHeader(crypto.HashHexToHash(s)) + + if index.NameHdr == "" { + index.NameHdr = s + } + + if len(index.NameHdr) > 36 { + index.NameHdr = index.NameHdr[0:36] + "..." + } + + if index.DescrHdr == "" { + index.DescrHdr = "N/A" + } + + if len(index.DescrHdr) > 40 { + index.DescrHdr = index.DescrHdr[0:40] + "..." + } + } + + entryServeSCID.SetText("") + + if link, err := tela.ServeTELA(s, session.Daemon); err == nil { + url, err := url.Parse(link) + if err != nil { + logger.Errorf("[Engram] TELA URL parse: %s\n", err) + errorText.Text = "error could parse URL" + errorText.Color = colors.Red + + fyne.Do(func() { + errorText.Refresh() + }) + + return // If url is not valid, scid won't be saved in history + } else { + err = fyne.CurrentApp().OpenURL(url) + if err != nil { + errorText.Text = "error could not open browser" + errorText.Color = colors.Red + } + } + + if gnomon.Index != nil { + historyResults = append(historyResults, index.NameHdr+";;;"+index.DescrHdr+";;;;;;"+s) + sort.Strings(historyResults) + history = historyResults + historyData.Set(history) + + results.Text = fmt.Sprintf(" Search History: %d", len(historyResults)) + results.Color = colors.Green + + err = StoreEncryptedValue("TELA History", []byte(s), []byte("")) + if err != nil { + logger.Errorf("[Engram] Error saving TELA search result: %s\n", err) + } + } + } else { + if strings.Contains(err.Error(), "user defined no updates and content has been updated to") { + removeOverlays() + + // Create a TELALink to parse and get its ratings for user to verifiy before serving updated content + telaLink := TELALink_Params{TelaLink: fmt.Sprintf("tela://open/%s", s)} + linkPermission, err := AskPermissionForRequestE("Allow Updated Content", telaLink) + if err != nil { + logger.Errorf("[Engram] Open TELA link: %s\n", err) + errorText.Text = "error could not open TELA" + errorText.Color = colors.Red + + fyne.Do(func() { + errorText.Refresh() + }) + + return + } + + if linkPermission != xswd.Allow { + entryServeSCID.SetText("") + return + } + + link, err := serveTELAUpdates(s) + if err != nil { + logger.Errorf("[Engram] Error serving TELA: %s\n", err) + errorText.Text = telaErrorToString(err) + errorText.Color = colors.Red + + fyne.Do(func() { + errorText.Refresh() + }) + + return + } + + url, err := url.Parse(link) + if err != nil { + logger.Errorf("[Engram] TELA URL parse: %s\n", err) + errorText.Text = "error could parse URL" + errorText.Color = colors.Red + + fyne.Do(func() { + errorText.Refresh() + }) + + return + } else { + err = fyne.CurrentApp().OpenURL(url) + if err != nil { + errorText.Text = "error could not open browser" + errorText.Color = colors.Red + } + } + + if gnomon.Index != nil { + historyResults = append(historyResults, index.NameHdr+";;;"+index.DescrHdr+";;;;;;"+s) + sort.Strings(historyResults) + history = historyResults + historyData.Set(history) + fyne.Do(func() { + historyList.Refresh() + }) + + results.Text = fmt.Sprintf(" Search History: %d", len(historyResults)) + results.Color = colors.Green + + err = StoreEncryptedValue("TELA History", []byte(s), []byte("")) + if err != nil { + logger.Errorf("[Engram] Error saving TELA search result: %s\n", err) + } + } + + return + } + + logger.Errorf("[Engram] Error serving TELA: %s\n", err) + errorText.Text = telaErrorToString(err) + errorText.Color = colors.Red + } + + fyne.Do(func() { + historyList.Refresh() + errorText.Refresh() + results.Refresh() + }) + + removeOverlays() + }() + } + } + + go getHistoryResults() + + historyList.OnSelected = func(id widget.ListItemID) { + errorText.Text = "" + errorText.Refresh() + showLoadingOverlay() + defer removeOverlays() + + split := strings.Split(history[id], ";;;") + if len(split) < 4 || len(split[3]) != 64 { + logger.Errorf("[Engram] TELA Invalid SCID\n") + errorText.Text = "invalid TELA scid" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + index, err := tela.GetINDEXInfo(split[3], session.Daemon) + if err != nil { + logger.Errorf("[Engram] GetINDEXInfo: %s\n", err) + errorText.Text = "invalid INDEX scid" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + historyList.UnselectAll() + historyList.FocusLost() + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTELAManager(index, refreshServerList)) + } + + searchList.OnSelected = func(id widget.ListItemID) { + errorText.Text = "" + errorText.Refresh() + showLoadingOverlay() + defer removeOverlays() + + split := strings.Split(searching[id], ";;;") + if len(split) < 2 || len(split[1]) != 64 { + logger.Errorf("[Engram] TELA Invalid SCID\n") + errorText.Text = "invalid TELA scid" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + index, err := tela.GetINDEXInfo(split[1], session.Daemon) + if err != nil { + logger.Errorf("[Engram] GetINDEXInfo: %s\n", err) + errorText.Text = "invalid INDEX scid" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + searchList.UnselectAll() + searchList.FocusLost() + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTELAManager(index, refreshServerList)) + } + + servingList.OnSelected = func(id widget.ListItemID) { + errorText.Text = "" + errorText.Refresh() + showLoadingOverlay() + defer removeOverlays() + + split := strings.Split(serving[id], ";;;") + if len(split) < 4 || len(split[3]) != 64 { + logger.Errorf("[Engram] TELA Invalid SCID\n") + errorText.Text = "invalid TELA scid" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + index, err := tela.GetINDEXInfo(split[3], session.Daemon) + if err != nil { + logger.Errorf("[Engram] GetINDEXInfo: %s\n", err) + errorText.Text = "invalid INDEX scid" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + servingList.UnselectAll() + servingList.FocusLost() + session.LastDomain = session.Window.Content() + session.Window.SetContent(layoutTELAManager(index, refreshServerList)) + } + + top := container.NewVBox( + rectSpacer, + rectSpacer, + container.NewCenter( + heading, + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layoutBrowser, + ), + ) + + bottom := container.NewStack( + container.NewVBox( + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewStack( + frame, + container.NewBorder( + top, + bottom, + nil, + nil, + ), + ) + + return NewVScroll(layout) +} + +// Layout details of a TELA INDEX +func layoutTELAManager(index tela.INDEX, callback func()) fyne.CanvasObject { + session.Domain = "app.tela.manager" + + frame := &iframe{} + + rectBox := canvas.NewRectangle(color.Transparent) + rectBox.SetMinSize(fyne.NewSize(ui.MaxWidth*0.99, ui.MaxHeight*0.58)) + + rectWidth90 := canvas.NewRectangle(color.Transparent) + rectWidth90.SetMinSize(fyne.NewSize(ui.Width, 10)) + + rectSpacer := canvas.NewRectangle(color.Transparent) + rectSpacer.SetMinSize(fyne.NewSize(6, 5)) + + labelName := widget.NewRichText(&widget.TextSegment{ + Text: index.NameHdr, + Style: widget.RichTextStyle{ + Alignment: fyne.TextAlignCenter, + ColorName: theme.ColorNameForeground, + SizeName: theme.SizeNameHeadingText, + TextStyle: fyne.TextStyle{Bold: true}, + }}) + labelName.Wrapping = fyne.TextWrapWord + + labelDesc := widget.NewRichText(&widget.TextSegment{ + Text: index.DescrHdr, + Style: widget.RichTextStyle{ + Alignment: fyne.TextAlignCenter, + ColorName: theme.ColorNameForeground, + TextStyle: fyne.TextStyle{Bold: false}, + }}) + labelDesc.Wrapping = fyne.TextWrapWord + + labelDURL := canvas.NewText(" DURL", colors.Gray) + labelDURL.TextSize = 14 + labelDURL.Alignment = fyne.TextAlignLeading + labelDURL.TextStyle = fyne.TextStyle{Bold: true} + + textDURL := widget.NewRichTextFromMarkdown(index.DURL) + textDURL.Wrapping = fyne.TextWrapWord + + labelSCID := canvas.NewText(" SMART CONTRACT ID", colors.Gray) + labelSCID.TextSize = 14 + labelSCID.Alignment = fyne.TextAlignLeading + labelSCID.TextStyle = fyne.TextStyle{Bold: true} + + textSCID := widget.NewRichTextFromMarkdown(index.SCID) + textSCID.Wrapping = fyne.TextWrapWord + + linkViewExplorer := widget.NewHyperlinkWithStyle("View in Explorer", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + linkViewExplorer.OnTapped = func() { + if engram.Disk.GetNetwork() { + link, _ := url.Parse("https://explorer.derofoundation.org/tx/" + index.SCID) + _ = fyne.CurrentApp().OpenURL(link) + } else { + link, _ := url.Parse("https://testnetexplorer.derofoundation.org/tx/" + index.SCID) + _ = fyne.CurrentApp().OpenURL(link) + } + } + + linkCopySCID := widget.NewHyperlinkWithStyle("Copy SCID", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkCopySCID.OnTapped = func() { + a.Clipboard().SetContent(index.SCID) + } + + labelAuthor := canvas.NewText(" SMART CONTRACT AUTHOR", colors.Gray) + labelAuthor.TextSize = 14 + labelAuthor.Alignment = fyne.TextAlignLeading + labelAuthor.TextStyle = fyne.TextStyle{Bold: true} + + author := index.Author + if author == "anon" { + author = "--" + } + textAuthor := widget.NewRichTextFromMarkdown(author) + textAuthor.Wrapping = fyne.TextWrapWord + + linkMessageAuthor := widget.NewHyperlinkWithStyle("Message the Author", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + linkMessageAuthor.OnTapped = func() { + if index.Author != "" { + messages.Contact = index.Author + session.Window.Canvas().SetContent(layoutTransition()) + removeOverlays() + session.Window.Canvas().SetContent(layoutPM()) + } + } + + linkCopyAuthor := widget.NewHyperlinkWithStyle("Copy Address", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkCopyAuthor.OnTapped = func() { + a.Clipboard().SetContent(index.Author) + } + + labelStatus := canvas.NewText("APPLICATION STATUS", colors.Gray) + labelStatus.TextSize = 14 + labelStatus.Alignment = fyne.TextAlignCenter + labelStatus.TextStyle = fyne.TextStyle{Bold: true} + + textStatus := canvas.NewText("Offline", colors.Gray) + textStatus.TextSize = 22 + textStatus.Alignment = fyne.TextAlignCenter + textStatus.TextStyle = fyne.TextStyle{Bold: true} + + labelSeparator := widget.NewRichTextFromMarkdown("") + labelSeparator.Wrapping = fyne.TextWrapOff + labelSeparator.ParseMarkdown("---") + labelSeparator2 := widget.NewRichTextFromMarkdown("") + labelSeparator2.Wrapping = fyne.TextWrapOff + labelSeparator2.ParseMarkdown("---") + labelSeparator3 := widget.NewRichTextFromMarkdown("") + labelSeparator3.Wrapping = fyne.TextWrapOff + labelSeparator3.ParseMarkdown("---") + labelSeparator4 := widget.NewRichTextFromMarkdown("") + labelSeparator4.Wrapping = fyne.TextWrapOff + labelSeparator4.ParseMarkdown("---") + labelSeparator5 := widget.NewRichTextFromMarkdown("") + labelSeparator5.Wrapping = fyne.TextWrapOff + labelSeparator5.ParseMarkdown("---") + // labelSeparator6 := widget.NewRichTextFromMarkdown("") + // labelSeparator6.Wrapping = fyne.TextWrapOff + // labelSeparator6.ParseMarkdown("---") + + menuLabel := canvas.NewText(" M O R E O P T I O N S ", colors.Gray) + menuLabel.TextSize = 11 + menuLabel.Alignment = fyne.TextAlignCenter + menuLabel.TextStyle = fyne.TextStyle{Bold: true} + + sep := canvas.NewRectangle(colors.Gray) + sep.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line1 := container.NewVBox( + layout.NewSpacer(), + sep, + layout.NewSpacer(), + ) + + sep2 := canvas.NewRectangle(colors.Gray) + sep2.SetMinSize(fyne.NewSize(ui.Width*0.2, 2)) + + line2 := container.NewVBox( + layout.NewSpacer(), + sep2, + layout.NewSpacer(), + ) + + linkBack := widget.NewHyperlinkWithStyle("Back to TELA", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkBack.OnTapped = func() { + removeOverlays() + capture := session.Window.Content() + session.Window.SetContent(layoutTransition()) + session.Window.SetContent(session.LastDomain) + session.Domain = "app.tela" + session.LastDomain = capture + go callback() + } + + image := canvas.NewImageFromResource(resourceTelaIcon) + image.SetMinSize(fyne.NewSize(ui.Width*0.3, ui.Width*0.3)) + image.FillMode = canvas.ImageFillContain + + _, _, iconURLHdr, _, _ := getContractHeader(crypto.HashHexToHash(index.SCID)) + if iconURLHdr == "" && index.IconHdr != "" { + iconURLHdr = index.IconHdr + } + + if iconURLHdr != "" { + if img, err := handleImageURL(index.NameHdr, iconURLHdr, fyne.NewSize(ui.Width*0.3, ui.Width*0.3)); err == nil { + image = img + } else { + logger.Errorf("[Engram] Could not validate icon image: %s\n", err) + } + } + + errorText := canvas.NewText(" ", colors.Green) + errorText.TextSize = 12 + errorText.Alignment = fyne.TextAlignCenter + + spacerStatus := canvas.NewRectangle(color.Transparent) + spacerStatus.SetMinSize(fyne.NewSize(0, 34)) + + linkOpenInBrowser := widget.NewHyperlinkWithStyle("Open in Browser", nil, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + linkOpenInBrowser.Hide() + linkOpenInBrowser.OnTapped = func() { + params := fmt.Sprintf("tela://open/%s", index.SCID) + var toggledUpdates bool + if !tela.UpdatesAllowed() { + // user has accepted updated content when serving, call AllowUpdates because OpenTELALink returns error on any updated content + tela.AllowUpdates(true) + toggledUpdates = true + } + + link, err := tela.OpenTELALink(params, session.Daemon) + if toggledUpdates { + tela.AllowUpdates(false) + } + if err != nil { + logger.Errorf("[Engram] handling TELA link: %s\n", err) + errorText.Text = "error handling TELA link" + errorText.Color = colors.Red + errorText.Refresh() + return + } + + url, err := url.Parse(link) + if err != nil { + logger.Errorf("[Engram] TELA URL parse: %s\n", err) + errorText.Text = "error could parse URL" + errorText.Color = colors.Red + errorText.Refresh() + } else { + err = fyne.CurrentApp().OpenURL(url) + if err != nil { + errorText.Text = "error could not open browser" + errorText.Color = colors.Red + errorText.Refresh() + } + } + } + + btnServer := widget.NewButton("Start Application", nil) + + if tela.HasServer(index.DURL) { + textStatus.Text = "Running" + textStatus.Color = colors.Green + textStatus.Refresh() + btnServer.Text = "Shutdown Application" + btnServer.Refresh() + linkOpenInBrowser.Show() + } + + btnServer.OnTapped = func() { + if btnServer.Text != "Start Application" { + tela.ShutdownServer(index.DURL) + errorText.Text = "" + errorText.Refresh() + textStatus.Text = "Offline" + textStatus.Color = colors.Gray + textStatus.Refresh() + btnServer.Text = "Start Application" + btnServer.Refresh() + linkOpenInBrowser.Hide() + } else { + showLoadingOverlay() + + if link, err := tela.ServeTELA(index.SCID, session.Daemon); err == nil { + url, err := url.Parse(link) + if err != nil { + logger.Errorf("[Engram] TELA URL parse: %s\n", err) + errorText.Text = "error could parse URL" + errorText.Color = colors.Red + errorText.Refresh() + } else { + err = fyne.CurrentApp().OpenURL(url) + if err != nil { + errorText.Text = "error could not open browser" + errorText.Color = colors.Red + errorText.Refresh() + } + } + + textStatus.Text = "Running" + textStatus.Color = colors.Green + textStatus.Refresh() + btnServer.Text = "Shutdown Application" + btnServer.Refresh() + linkOpenInBrowser.Show() + + err = StoreEncryptedValue("TELA History", []byte(index.SCID), []byte("")) + if err != nil { + logger.Errorf("[Engram] Error saving TELA search result: %s\n", err) + } + } else { + if strings.Contains(err.Error(), "user defined no updates and content has been updated to") { + removeOverlays() + + go func() { + // Create a TELALink to parse and get its ratings for user to verifiy before serving updated content + telaLink := TELALink_Params{TelaLink: fmt.Sprintf("tela://open/%s", index.SCID)} + linkPermission, err := AskPermissionForRequestE("Allow Updated Content", telaLink) + if err != nil { + logger.Errorf("[Engram] Open TELA link: %s\n", err) + fyne.Do(func() { + errorText.Text = "error could not open TELA" + errorText.Color = colors.Red + errorText.Refresh() + }) + + return + } + + if linkPermission != xswd.Allow { + return + } + + link, err := serveTELAUpdates(index.SCID) + if err != nil { + logger.Errorf("[Engram] Error serving TELA: %s\n", err) + fyne.Do(func() { + errorText.Text = telaErrorToString(err) + errorText.Color = colors.Red + errorText.Refresh() + }) + return + } + + url, err := url.Parse(link) + if err != nil { + logger.Errorf("[Engram] TELA URL parse: %s\n", err) + fyne.Do(func() { + errorText.Text = "error could parse URL" + errorText.Color = colors.Red + errorText.Refresh() + }) + } else { + err = fyne.CurrentApp().OpenURL(url) + if err != nil { + fyne.Do(func() { + errorText.Text = "error could not open browser" + errorText.Color = colors.Red + errorText.Refresh() + }) + } + } + + fyne.Do(func() { + textStatus.Text = " Online" + textStatus.Color = colors.Green + textStatus.Refresh() + btnServer.Text = "Shutdown Application" + btnServer.Refresh() + linkOpenInBrowser.Show() + }) + + err = StoreEncryptedValue("TELA History", []byte(index.SCID), []byte("")) + if err != nil { + logger.Errorf("[Engram] Error saving TELA search result: %s\n", err) + } + }() + + return + } + + logger.Errorf("[Engram] Error serving TELA: %s\n", err) + errorText.Text = telaErrorToString(err) + errorText.Color = colors.Red + errorText.Refresh() + } + + removeOverlays() + } + } + + ratings, err := tela.GetRating(index.SCID, session.Daemon, 0) + if err != nil { + logger.Errorf("[Engram] GetRating: %s\n", err) + } + + labelRatingAverage := canvas.NewText(fmt.Sprintf("%.1f", ratings.Average), colors.Account) + labelRatingAverage.TextSize = 24 + labelRatingAverage.Alignment = fyne.TextAlignCenter + labelRatingAverage.TextStyle = fyne.TextStyle{Bold: true} + + linkTelaRatings := widget.NewHyperlinkWithStyle("View All Ratings", nil, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + linkTelaRatings.OnTapped = func() { + showLoadingOverlay() + err := viewTELARatingsOverlay(index.NameHdr, index.SCID) + if err != nil { + errorText.Text = err.Error() + errorText.Color = colors.Red + errorText.Refresh() + } + } + + hexagonImg := canvas.NewImageFromResource(telaHexagonColor(ratings.Average)) + hexagonImg.SetMinSize(fyne.NewSize(80, 86)) + + center := container.NewStack( + rectBox, + container.NewVScroll( + container.NewStack( + rectWidth90, + container.NewHBox( + layout.NewSpacer(), + container.NewVBox( + container.NewCenter( + image, + ), + rectSpacer, + labelName, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + rectWidth90, + labelDesc, + ), + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator, + rectSpacer, + rectSpacer, + labelDURL, + textDURL, + rectSpacer, + rectSpacer, + labelSeparator2, + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + container.NewStack( + hexagonImg, + container.NewCenter( + labelRatingAverage, + ), + ), + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewHBox( + layout.NewSpacer(), + linkTelaRatings, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator3, + rectSpacer, + rectSpacer, + labelStatus, + rectSpacer, + textStatus, + rectSpacer, + rectSpacer, + btnServer, + rectSpacer, + linkOpenInBrowser, + rectSpacer, + errorText, + rectSpacer, + rectSpacer, + labelSeparator4, + rectSpacer, + rectSpacer, + labelAuthor, + textAuthor, + container.NewHBox( + linkMessageAuthor, + layout.NewSpacer(), + ), + container.NewHBox( + linkCopyAuthor, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + labelSeparator5, + rectSpacer, + rectSpacer, + labelSCID, + textSCID, + container.NewHBox( + linkViewExplorer, + layout.NewSpacer(), + ), + container.NewHBox( + linkCopySCID, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + container.NewStack( + rectWidth90, + ), + ), + layout.NewSpacer(), + ), + ), + ), + rectSpacer, + rectSpacer, + ) + + top := container.NewVBox( + rectSpacer, + rectSpacer, + ) + + bottom := container.NewStack( + container.NewVBox( + rectSpacer, + rectSpacer, + container.NewStack( + container.NewHBox( + layout.NewSpacer(), + line1, + layout.NewSpacer(), + menuLabel, + layout.NewSpacer(), + line2, + layout.NewSpacer(), + ), + ), + rectSpacer, + rectSpacer, + container.NewCenter( + layout.NewSpacer(), + linkBack, + layout.NewSpacer(), + ), + rectSpacer, + rectSpacer, + rectSpacer, + rectSpacer, + ), + ) + + layout := container.NewStack( + frame, + container.NewBorder( + top, + bottom, + nil, + center, + ), + ) + + return NewVScroll(layout) +}