Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add in-app and system notifications #540

Merged
merged 11 commits into from
Aug 19, 2021
31 changes: 6 additions & 25 deletions ui/load/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,29 @@
// the code be isolated in the package you're calling it from? Is it really needed by other packages in the ui package?
// or you're just planning for a use case that might never used.

// todo: fix toast notifications
// todo: clean up NewLoad method

package load

import (
"image"
"time"

"golang.org/x/exp/shiny/materialdesign/icons"
"golang.org/x/text/language"
"golang.org/x/text/message"

"gioui.org/io/key"
"gioui.org/op/paint"
"gioui.org/widget"

"github.com/planetdecred/dcrlibwallet"
"github.com/planetdecred/godcr/ui/decredmaterial"
"github.com/planetdecred/godcr/ui/notification"
"github.com/planetdecred/godcr/wallet"
"golang.org/x/exp/shiny/materialdesign/icons"
"golang.org/x/text/message"
)

type DCRUSDTBittrex struct {
LastTradeRate string
}

type Toast struct {
text string
success bool
Timer *time.Timer
}

type Receiver struct {
InternalLog chan string
NotificationsUpdate chan interface{}
Expand Down Expand Up @@ -77,7 +69,7 @@ type Load struct {

Icons Icons

Toast *Toast
Toast *notification.Toast

SelectedWallet *int
SelectedAccount *int
Expand Down Expand Up @@ -195,7 +187,7 @@ func NewLoad(th *decredmaterial.Theme, decredIcons map[string]image.Image) *Load
Icons: ic,
WL: wl,
Receiver: r,
Toast: &Toast{},
Toast: notification.NewToast(th),

Printer: message.NewPrinter(language.English),
}
Expand All @@ -209,14 +201,3 @@ func (l *Load) RefreshTheme() {
l.Theme.SwitchDarkMode(isDarkModeOn)
}
}

func (l *Load) CreateToast(text string, success bool) {
l.Toast = &Toast{
text: text,
success: success,
}
}

func (l *Load) DestroyToast() {
l.Toast = nil
}
54 changes: 54 additions & 0 deletions ui/notification/system.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package notification

import (
"fmt"
"os"
"path"
"path/filepath"

"github.com/gen2brain/beeep"
)

const (
icon = "ui/assets/decredicons/qrcodeSymbol.png"
title = "Godcr"
)

type SystemNotification struct {
iconPath string
message string
}

func NewSystemNotification() (*SystemNotification, error) {
absolutePath, err := getAbsolutePath()
if err != nil {
return nil, err
}

return &SystemNotification{
iconPath: filepath.Join(absolutePath, icon),
}, nil
}

func (s *SystemNotification) Notify(message string) error {
err := beeep.Notify(title, message, s.iconPath)
if err != nil {
return err
}

return nil
}

func getAbsolutePath() (string, error) {
ex, err := os.Executable()
if err != nil {
return "", fmt.Errorf("error getting executable path: %s", err.Error())
}

exSym, err := filepath.EvalSymlinks(ex)
if err != nil {
return "", fmt.Errorf("error getting filepath after evaluating sym links")
}

return path.Dir(exSym), nil
}
114 changes: 114 additions & 0 deletions ui/notification/toast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package notification

import (
"sync"
"time"

"gioui.org/layout"
"gioui.org/op"

"github.com/planetdecred/godcr/ui/decredmaterial"
"github.com/planetdecred/godcr/ui/values"
)

type (
C = layout.Context
D = layout.Dimensions
)

type Toast struct {
sync.Mutex
theme *decredmaterial.Theme
success bool
message string
timer *time.Timer
}

type duration int32

const (
Short duration = iota
Long
)

func NewToast(th *decredmaterial.Theme) *Toast {
return &Toast{
theme: th,
}
}

func getDurationFromDelay(d duration) time.Duration {
if d == Long {
return 5 * time.Second
}
return 2 * time.Second
}

// Notify is called to display a message indicating a successful action. It updates the toast object with the toast message
// and duration. The duration parameter is optional.
func (t *Toast) Notify(message string, d ...duration) {
t.notify(message, true, d...)
}

// Notify is called to display a message indicating a failed action. It updates the toast object with the toast message
// and duration. The duration parameter is optional.
func (t *Toast) NotifyError(message string, d ...duration) {
t.notify(message, false, d...)
}

// notify updates notification parameters on the toast object. It takes the message, success and duration
// parameters.
func (t *Toast) notify(message string, success bool, d ...duration) {
var notificationDelay duration
if len(d) > 0 {
notificationDelay = d[0]
}

t.Lock()
t.message = message
t.success = success
t.timer = time.NewTimer(getDurationFromDelay(notificationDelay))
t.Unlock()
}

func (t *Toast) Layout(gtx layout.Context) layout.Dimensions {
t.handleToastDisplay(gtx)
if t.timer == nil {
return layout.Dimensions{}
}

color := t.theme.Color.Success
if !t.success {
color = t.theme.Color.Danger
}

card := t.theme.Card()
card.Color = color
return layout.Center.Layout(gtx, func(gtx C) D {
return layout.Inset{Top: values.MarginPadding65}.Layout(gtx, func(gtx C) D {
return card.Layout(gtx, func(gtx C) D {
return layout.Inset{
Top: values.MarginPadding7, Bottom: values.MarginPadding7,
Left: values.MarginPadding15, Right: values.MarginPadding15,
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
msg := t.theme.Body1(t.message)
msg.Color = t.theme.Color.Surface
return msg.Layout(gtx)
})
})
})
})
}

func (t *Toast) handleToastDisplay(gtx layout.Context) {
if t.timer == nil {
return
}

select {
case <-t.timer.C:
t.timer = nil
op.InvalidateOp{}.Add(gtx.Ops)
default:
}
}
2 changes: 1 addition & 1 deletion ui/page/privacy_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ func (pg *PrivacyPage) showModalPasswordStartAccountMixer() {
return
}
pm.Dismiss()
// common.notify("Start Successfully", true)
pg.Toast.Notify("Start Successfully")
}()

return false
Expand Down
2 changes: 1 addition & 1 deletion ui/page/proposal/proposal_vote_modal.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func (vm *voteModal) sendVotes() {
return
}
pm.Dismiss()
vm.CreateToast("Vote sent successfully, refreshing proposals!", true)
vm.Toast.Notify("Vote sent successfully, refreshing proposals!")
go vm.WL.MultiWallet.Politeia.Sync()
vm.Dismiss()
}()
Expand Down
4 changes: 2 additions & 2 deletions ui/page/seed_backup_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,13 +532,13 @@ func (pg *BackupPage) Handle() {
errMessage := "Failed to verify. Please go through every word and try again."
s := strings.Join(pg.selectedSeeds, " ")
if !dcrlibwallet.VerifySeed(s) {
pg.CreateToast(errMessage, false)
pg.Toast.NotifyError(errMessage)
return
}

err := pg.wal.VerifyWalletSeedPhrase(pg.info.Wallets[*pg.selectedWallet].ID, s, pg.privpass)
if err != nil {
pg.CreateToast(errMessage, false)
pg.Toast.NotifyError(errMessage)
return
}
pg.info.Wallets[*pg.selectedWallet].Seed = nil
Expand Down
2 changes: 1 addition & 1 deletion ui/page/send/page.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ func (pg *Page) feeEstimationError(err string) {
pg.amount.setError(invalidAmountErr)
} else {
pg.amount.setError(err)
pg.CreateToast("Error estimating transaction: "+err, false)
pg.Toast.NotifyError("Error estimating transaction: " + err)
}

pg.clearEstimates()
Expand Down
4 changes: 2 additions & 2 deletions ui/page/send/send_confirm_modal.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,10 @@ func (scm *sendConfirmModal) broadcastTransaction() {
_, err := scm.authoredTxData.txAuthor.Broadcast([]byte(password))
scm.isSending = false
if err != nil {
scm.CreateToast(err.Error(), false)
scm.Toast.NotifyError(err.Error())
return
}
scm.CreateToast("Transaction sent!", true)
scm.Toast.Notify("Transaction sent!")

scm.txSent()
scm.Dismiss()
Expand Down
4 changes: 2 additions & 2 deletions ui/page/settings_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,10 +507,10 @@ func (pg *SettingsPage) Handle() {
case err := <-pg.errorReceiver:
if err.Error() == dcrlibwallet.ErrInvalidPassphrase {
e := "Password is incorrect"
pg.CreateToast(e, false)
pg.Toast.NotifyError(e)
return
}
pg.CreateToast(err.Error(), false)
pg.Toast.NotifyError(err.Error())
default:
}
}
Expand Down
6 changes: 3 additions & 3 deletions ui/page/start_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package page
import (
"os"

"github.com/planetdecred/godcr/ui/load"
"github.com/planetdecred/godcr/ui/modal"

"gioui.org/layout"
"gioui.org/text"
"gioui.org/widget"

"github.com/planetdecred/dcrlibwallet"
"github.com/planetdecred/godcr/ui/decredmaterial"
"github.com/planetdecred/godcr/ui/load"
"github.com/planetdecred/godcr/ui/modal"
"github.com/planetdecred/godcr/ui/values"
)

Expand Down
10 changes: 5 additions & 5 deletions ui/page/tickets/purchase_modal.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func (tp *ticketPurchaseModal) OnResume() {
tp.initializeAccountSelector()
err := tp.accountSelector.SelectFirstWalletValidAccount()
if err != nil {
tp.CreateToast(err.Error(), false)
tp.Toast.NotifyError(err.Error())
}

tp.vspSelector = newVSPSelector(tp.Load).title("Select a vsp")
Expand Down Expand Up @@ -226,23 +226,23 @@ func (tp *ticketPurchaseModal) createNewVSPD() {
selectedVSP := tp.vspSelector.SelectedVSP()
vspd, err := tp.WL.NewVSPD(selectedVSP.Host, selectedAccount.WalletID, selectedAccount.Number)
if err != nil {
tp.CreateToast(err.Error(), false)
tp.Toast.NotifyError(err.Error())
}
tp.vsp = vspd
}

func (tp *ticketPurchaseModal) purchaseTickets(password []byte) {
tp.Dismiss()
tp.CreateToast(fmt.Sprintf("attempting to purchase %v ticket(s)", tp.ticketCount()), true)
tp.Toast.Notify(fmt.Sprintf("attempting to purchase %v ticket(s)", tp.ticketCount()))

go func() {
account := tp.accountSelector.SelectedAccount()
err := tp.WL.PurchaseTicket(account.WalletID, uint32(tp.ticketCount()), password, tp.vsp)
if err != nil {
tp.CreateToast(err.Error(), false)
tp.Toast.NotifyError(err.Error())
return
}
tp.CreateToast(fmt.Sprintf("%v ticket(s) purchased successfully", tp.ticketCount()), true)
tp.Toast.Notify(fmt.Sprintf("%v ticket(s) purchased successfully", tp.ticketCount()))
}()
}

Expand Down
2 changes: 1 addition & 1 deletion ui/page/tickets/vsp_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func (v *vspSelectorModal) Handle() {
go func() {
err := v.WL.AddVSP(v.inputVSP.Editor.Text())
if err != nil {
v.CreateToast(err.Error(), false)
v.Toast.NotifyError(err.Error())
} else {
v.inputVSP.Editor.SetText("")
}
Expand Down