Merge pull request #524 from dweymouth/feature/toasts
Show success/error "toast" message when adding tracks to playlists
This commit is contained in:
@@ -239,5 +239,10 @@
|
|||||||
"Sept": "Sept",
|
"Sept": "Sept",
|
||||||
"Oct": "Okt",
|
"Oct": "Okt",
|
||||||
"Nov": "Nov",
|
"Nov": "Nov",
|
||||||
"Dec": "Dec"
|
"Dec": "Dec",
|
||||||
|
|
||||||
|
"playlist.addedtracks": {
|
||||||
|
"one": "Added {{.trackCount}} track to playlist",
|
||||||
|
"other": "Added {{.trackCount}} tracks to playlist"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"All": "All",
|
"All": "All",
|
||||||
"All Tracks": "All Tracks",
|
"All Tracks": "All Tracks",
|
||||||
"Alt. URL": "Alt. URL",
|
"Alt. URL": "Alt. URL",
|
||||||
|
"An error occurred adding tracks to the playlist": "An error occurred adding tracks to the playlist",
|
||||||
"Are you sure you want to delete the server": "Are you sure you want to delete the server",
|
"Are you sure you want to delete the server": "Are you sure you want to delete the server",
|
||||||
"Artist": "Artist",
|
"Artist": "Artist",
|
||||||
"Artist (A-Z)": "Artist (A-Z)",
|
"Artist (A-Z)": "Artist (A-Z)",
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
"Enter": "Enter",
|
"Enter": "Enter",
|
||||||
"EP": "EP",
|
"EP": "EP",
|
||||||
"Equalizer": "Equalizer",
|
"Equalizer": "Equalizer",
|
||||||
|
"Error": "Error",
|
||||||
"Exclusive mode": "Exclusive mode",
|
"Exclusive mode": "Exclusive mode",
|
||||||
"Favorites": "Favorites",
|
"Favorites": "Favorites",
|
||||||
"Field Recording": "Field Recording",
|
"Field Recording": "Field Recording",
|
||||||
@@ -188,6 +190,7 @@
|
|||||||
"Spoken Word": "Spoken Word",
|
"Spoken Word": "Spoken Word",
|
||||||
"Startup page": "Startup page",
|
"Startup page": "Startup page",
|
||||||
"Stopped": "Stopped",
|
"Stopped": "Stopped",
|
||||||
|
"Success": "Success",
|
||||||
"Support the project": "Support the project",
|
"Support the project": "Support the project",
|
||||||
"Switch Servers": "Switch Servers",
|
"Switch Servers": "Switch Servers",
|
||||||
"Testing connection": "Testing connection",
|
"Testing connection": "Testing connection",
|
||||||
@@ -242,5 +245,10 @@
|
|||||||
"Sept": "Sept",
|
"Sept": "Sept",
|
||||||
"Oct": "Oct",
|
"Oct": "Oct",
|
||||||
"Nov": "Nov",
|
"Nov": "Nov",
|
||||||
"Dec": "Dec"
|
"Dec": "Dec",
|
||||||
|
|
||||||
|
"playlist.addedtracks": {
|
||||||
|
"one": "Added one track to playlist",
|
||||||
|
"other": "Added {{.trackCount}} tracks to playlist"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"All": "Todos",
|
"All": "Todos",
|
||||||
"All Tracks": "Todas las pistas",
|
"All Tracks": "Todas las pistas",
|
||||||
"Alt. URL": "URL alternativa",
|
"Alt. URL": "URL alternativa",
|
||||||
|
"An error occurred adding tracks to the playlist": "Ha ocurrido un error al añadir pistas a la lista de reproducción",
|
||||||
"Are you sure you want to delete the server": "¿Estás seguro de que quieres eliminar el servidor?",
|
"Are you sure you want to delete the server": "¿Estás seguro de que quieres eliminar el servidor?",
|
||||||
"Artist": "Artista",
|
"Artist": "Artista",
|
||||||
"Artist (A-Z)": "Artista (A-Z)",
|
"Artist (A-Z)": "Artista (A-Z)",
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
"Enter": "Ingresar",
|
"Enter": "Ingresar",
|
||||||
"EP": "EP",
|
"EP": "EP",
|
||||||
"Equalizer": "Ecualizador",
|
"Equalizer": "Ecualizador",
|
||||||
|
"Error": "Error",
|
||||||
"Exclusive mode": "Modo exclusivo",
|
"Exclusive mode": "Modo exclusivo",
|
||||||
"Favorites": "Favoritos",
|
"Favorites": "Favoritos",
|
||||||
"Field Recording": "Grabación en campo",
|
"Field Recording": "Grabación en campo",
|
||||||
@@ -187,6 +189,7 @@
|
|||||||
"Spoken Word": "Palabra Hablada",
|
"Spoken Word": "Palabra Hablada",
|
||||||
"Startup page": "Página de inicio",
|
"Startup page": "Página de inicio",
|
||||||
"Stopped": "Detenida",
|
"Stopped": "Detenida",
|
||||||
|
"Success": "Éxito",
|
||||||
"Support the project": "Apoya el proyecto",
|
"Support the project": "Apoya el proyecto",
|
||||||
"Switch Servers": "Cambiar servidores",
|
"Switch Servers": "Cambiar servidores",
|
||||||
"Testing connection": "Probando conexión",
|
"Testing connection": "Probando conexión",
|
||||||
@@ -241,5 +244,10 @@
|
|||||||
"Sept": "Set",
|
"Sept": "Set",
|
||||||
"Oct": "Oct",
|
"Oct": "Oct",
|
||||||
"Nov": "Nov",
|
"Nov": "Nov",
|
||||||
"Dec": "Dic"
|
"Dec": "Dic",
|
||||||
|
|
||||||
|
"playlist.addedtracks": {
|
||||||
|
"one": "Se ha añadido una pista a la lista de reproducción",
|
||||||
|
"other": "Se han añadido {{.trackCount}} pistas a la lista de reproducción"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,5 +240,10 @@
|
|||||||
"Sept": "Sept",
|
"Sept": "Sept",
|
||||||
"Oct": "Oct",
|
"Oct": "Oct",
|
||||||
"Nov": "Nov",
|
"Nov": "Nov",
|
||||||
"Dec": "Déc"
|
"Dec": "Déc",
|
||||||
|
|
||||||
|
"playlist.addedtracks": {
|
||||||
|
"one": "Une piste ajoutée à la liste de lecture",
|
||||||
|
"other": "{{.trackCount}} pistes ajoutées à la liste de lecture"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,5 +238,10 @@
|
|||||||
"Sept": "Set",
|
"Sept": "Set",
|
||||||
"Oct": "Ott",
|
"Oct": "Ott",
|
||||||
"Nov": "Nov",
|
"Nov": "Nov",
|
||||||
"Dec": "Dic"
|
"Dec": "Dic",
|
||||||
|
|
||||||
|
"playlist.addedtracks": {
|
||||||
|
"one": "Added {{.trackCount}} track to playlist",
|
||||||
|
"other": "Added {{.trackCount}} tracks to playlist"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,5 +228,10 @@
|
|||||||
"Remove from queue": "キューから削除",
|
"Remove from queue": "キューから削除",
|
||||||
"Play song radio": "曲をラジオで再生",
|
"Play song radio": "曲をラジオで再生",
|
||||||
"to":"to",
|
"to":"to",
|
||||||
"by":"by"
|
"by":"by",
|
||||||
|
|
||||||
|
"playlist.addedtracks": {
|
||||||
|
"one": "Added {{.trackCount}} track to playlist",
|
||||||
|
"other": "Added {{.trackCount}} tracks to playlist"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,5 +228,10 @@
|
|||||||
"Remove from queue": "Usuń z kolejki odtwarzania",
|
"Remove from queue": "Usuń z kolejki odtwarzania",
|
||||||
"Play song radio": "Odtwórz mix utworów",
|
"Play song radio": "Odtwórz mix utworów",
|
||||||
"to":"do",
|
"to":"do",
|
||||||
"by":"przez"
|
"by":"przez",
|
||||||
|
|
||||||
|
"playlist.addedtracks": {
|
||||||
|
"one": "Added {{.trackCount}} track to playlist",
|
||||||
|
"other": "Added {{.trackCount}} tracks to playlist"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,5 +228,10 @@
|
|||||||
"Remove from queue": "Remover da fila",
|
"Remove from queue": "Remover da fila",
|
||||||
"Play song radio": "Reproduzir rádio da faixa",
|
"Play song radio": "Reproduzir rádio da faixa",
|
||||||
"to":"a",
|
"to":"a",
|
||||||
"by":"de"
|
"by":"de",
|
||||||
|
|
||||||
|
"playlist.addedtracks": {
|
||||||
|
"one": "Added {{.trackCount}} track to playlist",
|
||||||
|
"other": "Added {{.trackCount}} tracks to playlist"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,5 +230,10 @@
|
|||||||
"Sept": "Sept",
|
"Sept": "Sept",
|
||||||
"Oct": "Oct",
|
"Oct": "Oct",
|
||||||
"Nov": "Noiem",
|
"Nov": "Noiem",
|
||||||
"Dec": "Dec"
|
"Dec": "Dec",
|
||||||
|
|
||||||
|
"playlist.addedtracks": {
|
||||||
|
"one": "Added {{.trackCount}} track to playlist",
|
||||||
|
"other": "Added {{.trackCount}} tracks to playlist"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,5 +216,10 @@
|
|||||||
"Unset favorite": "取消收藏",
|
"Unset favorite": "取消收藏",
|
||||||
"Remove from queue": "从队列中移除",
|
"Remove from queue": "从队列中移除",
|
||||||
"Size": "大小",
|
"Size": "大小",
|
||||||
"Play song radio": "播放歌曲电台"
|
"Play song radio": "播放歌曲电台",
|
||||||
|
|
||||||
|
"playlist.addedtracks": {
|
||||||
|
"one": "Added {{.trackCount}} track to playlist",
|
||||||
|
"other": "Added {{.trackCount}} tracks to playlist"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,5 +216,10 @@
|
|||||||
"Unset favorite": "取消收藏",
|
"Unset favorite": "取消收藏",
|
||||||
"Remove from queue": "从队列中移除",
|
"Remove from queue": "从队列中移除",
|
||||||
"Size": "大小",
|
"Size": "大小",
|
||||||
"Play song radio": "播放歌曲电台"
|
"Play song radio": "播放歌曲电台",
|
||||||
|
|
||||||
|
"playlist.addedtracks": {
|
||||||
|
"one": "Added {{.trackCount}} track to playlist",
|
||||||
|
"other": "Added {{.trackCount}} tracks to playlist"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -39,17 +40,25 @@ type NavigationHandler func(Route)
|
|||||||
|
|
||||||
type CurPageFunc func() Route
|
type CurPageFunc func() Route
|
||||||
|
|
||||||
|
type ToastProvider interface {
|
||||||
|
ShowSuccessToast(string)
|
||||||
|
ShowErrorToast(string)
|
||||||
|
}
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
visualizationData
|
visualizationData
|
||||||
AppVersion string
|
AppVersion string
|
||||||
App *backend.App
|
App *backend.App
|
||||||
MainWindow fyne.Window
|
MainWindow fyne.Window
|
||||||
|
|
||||||
|
// dependencies injected from MainWindow
|
||||||
NavHandler NavigationHandler
|
NavHandler NavigationHandler
|
||||||
CurPageFunc CurPageFunc
|
CurPageFunc CurPageFunc
|
||||||
ReloadFunc func()
|
ReloadFunc func()
|
||||||
RefreshPageFunc func()
|
RefreshPageFunc func()
|
||||||
SelectAllPageFunc func()
|
SelectAllPageFunc func()
|
||||||
UnselectAllPageFunc func()
|
UnselectAllPageFunc func()
|
||||||
|
ToastProvider ToastProvider
|
||||||
|
|
||||||
popUpQueueMutex sync.Mutex
|
popUpQueueMutex sync.Mutex
|
||||||
popUpQueue *widget.PopUp
|
popUpQueue *widget.PopUp
|
||||||
@@ -297,10 +306,28 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
|
|||||||
m.doModalClosed()
|
m.doModalClosed()
|
||||||
})
|
})
|
||||||
sp.SetOnNavigateTo(func(contentType mediaprovider.ContentType, id string) {
|
sp.SetOnNavigateTo(func(contentType mediaprovider.ContentType, id string) {
|
||||||
|
notifySuccess := func(n int) {
|
||||||
|
msg := lang.LocalizePluralKey("playlist.addedtracks",
|
||||||
|
"Added tracks to playlist", n, map[string]string{"trackCount": strconv.Itoa(n)})
|
||||||
|
m.ToastProvider.ShowSuccessToast(msg)
|
||||||
|
}
|
||||||
|
notifyError := func() {
|
||||||
|
m.ToastProvider.ShowErrorToast(
|
||||||
|
lang.L("An error occurred adding tracks to the playlist"),
|
||||||
|
)
|
||||||
|
}
|
||||||
pop.Hide()
|
pop.Hide()
|
||||||
m.App.Config.Application.AddToPlaylistSkipDuplicates = sp.SkipDuplicates
|
m.App.Config.Application.AddToPlaylistSkipDuplicates = sp.SkipDuplicates
|
||||||
if id == "" /* creating new playlist */ {
|
if id == "" /* creating new playlist */ {
|
||||||
go m.App.ServerManager.Server.CreatePlaylist(sp.SearchDialog.SearchQuery(), trackIDs)
|
go func() {
|
||||||
|
err := m.App.ServerManager.Server.CreatePlaylist(sp.SearchDialog.SearchQuery(), trackIDs)
|
||||||
|
if err == nil {
|
||||||
|
notifySuccess(len(trackIDs))
|
||||||
|
} else {
|
||||||
|
log.Println("error adding tracks to playlist: %s", err.Error())
|
||||||
|
notifyError()
|
||||||
|
}
|
||||||
|
}()
|
||||||
} else {
|
} else {
|
||||||
m.App.Config.Application.DefaultPlaylistID = id
|
m.App.Config.Application.DefaultPlaylistID = id
|
||||||
if sp.SkipDuplicates {
|
if sp.SkipDuplicates {
|
||||||
@@ -308,6 +335,7 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
|
|||||||
currentTrackIDs := make(map[string]struct{})
|
currentTrackIDs := make(map[string]struct{})
|
||||||
if selectedPlaylist, err := m.App.ServerManager.Server.GetPlaylist(id); err != nil {
|
if selectedPlaylist, err := m.App.ServerManager.Server.GetPlaylist(id); err != nil {
|
||||||
log.Printf("error getting playlist: %s", err.Error())
|
log.Printf("error getting playlist: %s", err.Error())
|
||||||
|
notifyError()
|
||||||
} else {
|
} else {
|
||||||
for _, track := range selectedPlaylist.Tracks {
|
for _, track := range selectedPlaylist.Tracks {
|
||||||
currentTrackIDs[track.ID] = struct{}{}
|
currentTrackIDs[track.ID] = struct{}{}
|
||||||
@@ -316,11 +344,25 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
|
|||||||
_, ok := currentTrackIDs[trackID]
|
_, ok := currentTrackIDs[trackID]
|
||||||
return !ok
|
return !ok
|
||||||
})
|
})
|
||||||
m.App.ServerManager.Server.AddPlaylistTracks(id, filterTrackIDs)
|
err := m.App.ServerManager.Server.AddPlaylistTracks(id, filterTrackIDs)
|
||||||
|
if err == nil {
|
||||||
|
notifySuccess(len(filterTrackIDs))
|
||||||
|
} else {
|
||||||
|
log.Println("error adding tracks to playlist: %s", err.Error())
|
||||||
|
notifyError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
go m.App.ServerManager.Server.AddPlaylistTracks(id, trackIDs)
|
go func() {
|
||||||
|
err := m.App.ServerManager.Server.AddPlaylistTracks(id, trackIDs)
|
||||||
|
if err == nil {
|
||||||
|
notifySuccess(len(trackIDs))
|
||||||
|
} else {
|
||||||
|
log.Println("error adding tracks to playlist: %s", err.Error())
|
||||||
|
notifyError()
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -34,6 +34,7 @@ type MainWindow struct {
|
|||||||
Controller *controller.Controller
|
Controller *controller.Controller
|
||||||
BrowsingPane *browsing.BrowsingPane
|
BrowsingPane *browsing.BrowsingPane
|
||||||
BottomPanel *BottomPanel
|
BottomPanel *BottomPanel
|
||||||
|
ToastOverlay *ToastOverlay
|
||||||
|
|
||||||
theme *theme.MyTheme
|
theme *theme.MyTheme
|
||||||
haveSystemTray bool
|
haveSystemTray bool
|
||||||
@@ -61,6 +62,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
|
|||||||
}
|
}
|
||||||
m.Controller = controller.New(app, appVersion, m.Window)
|
m.Controller = controller.New(app, appVersion, m.Window)
|
||||||
m.BrowsingPane = browsing.NewBrowsingPane(app, m.Controller, func() { m.Router.NavigateTo(m.StartupPage()) })
|
m.BrowsingPane = browsing.NewBrowsingPane(app, m.Controller, func() { m.Router.NavigateTo(m.StartupPage()) })
|
||||||
|
m.ToastOverlay = NewToastOverlay()
|
||||||
m.Router = browsing.NewRouter(app, m.Controller, m.BrowsingPane)
|
m.Router = browsing.NewRouter(app, m.Controller, m.BrowsingPane)
|
||||||
// inject controller dependencies
|
// inject controller dependencies
|
||||||
m.Controller.NavHandler = m.Router.NavigateTo
|
m.Controller.NavHandler = m.Router.NavigateTo
|
||||||
@@ -69,6 +71,7 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
|
|||||||
m.Controller.RefreshPageFunc = m.BrowsingPane.RefreshPage
|
m.Controller.RefreshPageFunc = m.BrowsingPane.RefreshPage
|
||||||
m.Controller.SelectAllPageFunc = m.BrowsingPane.SelectAll
|
m.Controller.SelectAllPageFunc = m.BrowsingPane.SelectAll
|
||||||
m.Controller.UnselectAllPageFunc = m.BrowsingPane.UnselectAll
|
m.Controller.UnselectAllPageFunc = m.BrowsingPane.UnselectAll
|
||||||
|
m.Controller.ToastProvider = m.ToastOverlay
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
// Fyne will extract out an "About" menu item and
|
// Fyne will extract out an "About" menu item and
|
||||||
@@ -137,10 +140,12 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
|
|||||||
m.BrowsingPane.DisableNavigationButtons()
|
m.BrowsingPane.DisableNavigationButtons()
|
||||||
m.addShortcuts()
|
m.addShortcuts()
|
||||||
|
|
||||||
m.content = newMainWindowContent(container.NewBorder(nil, m.BottomPanel, nil, nil, m.BrowsingPane),
|
center := container.NewStack(m.BrowsingPane, m.ToastOverlay)
|
||||||
|
m.content = newMainWindowContent(container.NewBorder(nil, m.BottomPanel, nil, nil, center),
|
||||||
m.Controller.UnselectAll)
|
m.Controller.UnselectAll)
|
||||||
m.Window.SetContent(fynetooltip.AddWindowToolTipLayer(m.content, m.Window.Canvas()))
|
m.Window.SetContent(fynetooltip.AddWindowToolTipLayer(m.content, m.Window.Canvas()))
|
||||||
m.setInitialSize()
|
m.setInitialSize()
|
||||||
|
|
||||||
m.Window.SetCloseIntercept(func() {
|
m.Window.SetCloseIntercept(func() {
|
||||||
m.SaveWindowSize()
|
m.SaveWindowSize()
|
||||||
// save settings in case we crash during shutdown
|
// save settings in case we crash during shutdown
|
||||||
|
|||||||
@@ -0,0 +1,269 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/canvas"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/driver/desktop"
|
||||||
|
"fyne.io/fyne/v2/lang"
|
||||||
|
"fyne.io/fyne/v2/layout"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
"github.com/dweymouth/supersonic/ui/util"
|
||||||
|
"github.com/dweymouth/supersonic/ui/widgets"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
toastAutoDismissalTime = 3 * time.Second
|
||||||
|
toastAnimationDuration = 100 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
type ToastOverlay struct {
|
||||||
|
widget.BaseWidget
|
||||||
|
|
||||||
|
currentToast *toast
|
||||||
|
currentToastAnim *fyne.Animation
|
||||||
|
dismissCancel context.CancelFunc
|
||||||
|
|
||||||
|
container *fyne.Container
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewToastOverlay() *ToastOverlay {
|
||||||
|
t := &ToastOverlay{container: container.NewWithoutLayout()}
|
||||||
|
t.container.Objects = make([]fyne.CanvasObject, 0, 1)
|
||||||
|
t.ExtendBaseWidget(t)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ToastOverlay) ShowSuccessToast(message string) {
|
||||||
|
t.showToast(false, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ToastOverlay) ShowErrorToast(message string) {
|
||||||
|
t.showToast(true, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ToastOverlay) showToast(isErr bool, message string) {
|
||||||
|
t.cancelPreviousToast()
|
||||||
|
|
||||||
|
t.currentToast = newToast(isErr, message, t.dismissToast)
|
||||||
|
t.container.Objects = append(t.container.Objects, t.currentToast)
|
||||||
|
|
||||||
|
s := t.Size()
|
||||||
|
min := t.currentToast.MinSize()
|
||||||
|
pad := theme.Padding()
|
||||||
|
t.currentToast.Resize(min)
|
||||||
|
endPos := fyne.NewPos(s.Width-min.Width-pad, s.Height-min.Height-pad)
|
||||||
|
startPos := fyne.NewPos(s.Width, endPos.Y)
|
||||||
|
f := t.makeToastAnimFunc(endPos, false)
|
||||||
|
t.currentToastAnim = canvas.NewPositionAnimation(startPos, endPos, toastAnimationDuration, f)
|
||||||
|
t.currentToastAnim.Curve = fyne.AnimationEaseOut
|
||||||
|
t.currentToastAnim.Start()
|
||||||
|
t.Refresh()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
t.dismissCancel = cancel // always canceled by dismissToast
|
||||||
|
go func() {
|
||||||
|
time.Sleep(toastAutoDismissalTime)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
t.dismissToast()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ToastOverlay) makeToastAnimFunc(endPos fyne.Position, dismissal bool) func(fyne.Position) {
|
||||||
|
return func(p fyne.Position) {
|
||||||
|
if ct := t.currentToast; ct != nil {
|
||||||
|
ct.Move(p)
|
||||||
|
}
|
||||||
|
if p == endPos {
|
||||||
|
t.currentToastAnim = nil
|
||||||
|
if dismissal {
|
||||||
|
t.cancelPreviousToast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ToastOverlay) Resize(size fyne.Size) {
|
||||||
|
if t.currentToast != nil && t.currentToastAnim == nil {
|
||||||
|
// move to the anchor position
|
||||||
|
min := t.currentToast.MinSize()
|
||||||
|
pad := theme.Padding()
|
||||||
|
t.currentToast.Move(fyne.NewPos(size.Width-min.Width-pad, size.Height-min.Height-pad))
|
||||||
|
} // else if animation is running -- well, hope this doesn't happen ;)
|
||||||
|
|
||||||
|
t.BaseWidget.Resize(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ToastOverlay) cancelPreviousToast() {
|
||||||
|
if t.currentToast == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if t.currentToastAnim != nil {
|
||||||
|
t.currentToastAnim.Stop()
|
||||||
|
t.currentToastAnim = nil
|
||||||
|
}
|
||||||
|
t.container.Objects[0] = nil
|
||||||
|
t.container.Objects = t.container.Objects[:0]
|
||||||
|
t.currentToast = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ToastOverlay) dismissToast() {
|
||||||
|
if t.currentToast == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if t.dismissCancel != nil {
|
||||||
|
t.dismissCancel()
|
||||||
|
t.dismissCancel = nil
|
||||||
|
}
|
||||||
|
if t.currentToastAnim != nil {
|
||||||
|
t.currentToastAnim.Stop()
|
||||||
|
}
|
||||||
|
s := t.Size()
|
||||||
|
min := t.currentToast.MinSize()
|
||||||
|
pad := theme.Padding()
|
||||||
|
startPos := fyne.NewPos(s.Width-min.Width-pad, s.Height-min.Height-pad)
|
||||||
|
endPos := fyne.NewPos(s.Width, startPos.Y)
|
||||||
|
f := t.makeToastAnimFunc(endPos, true)
|
||||||
|
t.currentToastAnim = canvas.NewPositionAnimation(startPos, endPos, toastAnimationDuration, f)
|
||||||
|
t.currentToastAnim.Curve = fyne.AnimationEaseIn
|
||||||
|
t.currentToastAnim.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ToastOverlay) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
return widget.NewSimpleRenderer(t.container)
|
||||||
|
}
|
||||||
|
|
||||||
|
type toast struct {
|
||||||
|
widget.BaseWidget
|
||||||
|
|
||||||
|
isErr bool
|
||||||
|
message string
|
||||||
|
onDismiss func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newToast(isErr bool, message string, onDismiss func()) *toast {
|
||||||
|
t := &toast{isErr: isErr, message: message, onDismiss: onDismiss}
|
||||||
|
t.ExtendBaseWidget(t)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *toast) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
return newToastRenderer(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *toast) Dismiss() {
|
||||||
|
if t.onDismiss != nil {
|
||||||
|
t.onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// swallow all tap/mouse events because toast is transparent
|
||||||
|
var (
|
||||||
|
_ fyne.Tappable = (*toast)(nil)
|
||||||
|
_ fyne.SecondaryTappable = (*toast)(nil)
|
||||||
|
_ desktop.Hoverable = (*toast)(nil)
|
||||||
|
_ desktop.Mouseable = (*toast)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*toast) Tapped(*fyne.PointEvent) {}
|
||||||
|
func (*toast) TappedSecondary(*fyne.PointEvent) {}
|
||||||
|
func (*toast) MouseIn(*desktop.MouseEvent) {}
|
||||||
|
func (*toast) MouseOut() {}
|
||||||
|
func (*toast) MouseMoved(*desktop.MouseEvent) {}
|
||||||
|
func (*toast) MouseUp(*desktop.MouseEvent) {}
|
||||||
|
func (*toast) MouseDown(*desktop.MouseEvent) {}
|
||||||
|
|
||||||
|
type toastRenderer struct {
|
||||||
|
container *fyne.Container
|
||||||
|
background *canvas.Rectangle
|
||||||
|
accent *canvas.Rectangle
|
||||||
|
accentColor fyne.ThemeColorName
|
||||||
|
}
|
||||||
|
|
||||||
|
func newToastRenderer(t *toast) *toastRenderer {
|
||||||
|
title := lang.L("Success")
|
||||||
|
accentColor := theme.ColorNamePrimary
|
||||||
|
if t.isErr {
|
||||||
|
title = lang.L("Error")
|
||||||
|
accentColor = theme.ColorNameError
|
||||||
|
}
|
||||||
|
|
||||||
|
th := fyne.CurrentApp().Settings().Theme()
|
||||||
|
v := fyne.CurrentApp().Settings().ThemeVariant()
|
||||||
|
background := canvas.NewRectangle(th.Color(theme.ColorNameOverlayBackground, v))
|
||||||
|
background.CornerRadius = th.Size(theme.SizeNameInputRadius)
|
||||||
|
background.StrokeColor = th.Color(theme.ColorNameInputBorder, v)
|
||||||
|
background.StrokeWidth = th.Size(theme.SizeNameInputBorder) * 2
|
||||||
|
accent := canvas.NewRectangle(th.Color(accentColor, v))
|
||||||
|
accent.SetMinSize(fyne.NewSize(4, 1))
|
||||||
|
|
||||||
|
close := widgets.NewIconButton(theme.CancelIcon(), t.Dismiss)
|
||||||
|
close.IconSize = widgets.IconButtonSizeSmaller
|
||||||
|
|
||||||
|
titleText := widget.NewRichTextWithText(title)
|
||||||
|
titleText.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyleSubHeading
|
||||||
|
|
||||||
|
pad := theme.Padding()
|
||||||
|
return &toastRenderer{
|
||||||
|
background: background,
|
||||||
|
accent: accent,
|
||||||
|
accentColor: accentColor,
|
||||||
|
container: container.NewStack(
|
||||||
|
background,
|
||||||
|
container.New(&layout.CustomPaddedLayout{
|
||||||
|
TopPadding: 2 * pad,
|
||||||
|
BottomPadding: 2 * pad,
|
||||||
|
LeftPadding: 2 * pad,
|
||||||
|
RightPadding: pad,
|
||||||
|
},
|
||||||
|
container.NewBorder(nil, nil, accent, nil,
|
||||||
|
container.NewVBox(
|
||||||
|
container.NewBorder(nil, nil, nil, container.NewHBox(close, util.NewHSpace(2)), titleText),
|
||||||
|
widget.NewLabel(t.message),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fyne.WidgetRenderer = (*toastRenderer)(nil)
|
||||||
|
|
||||||
|
func (*toastRenderer) Destroy() {}
|
||||||
|
|
||||||
|
func (t *toastRenderer) Layout(s fyne.Size) {
|
||||||
|
t.container.Layout.Layout(t.container.Objects, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *toastRenderer) MinSize() fyne.Size {
|
||||||
|
return t.container.MinSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *toastRenderer) Objects() []fyne.CanvasObject {
|
||||||
|
return t.container.Objects
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *toastRenderer) Refresh() {
|
||||||
|
th := fyne.CurrentApp().Settings().Theme()
|
||||||
|
v := fyne.CurrentApp().Settings().ThemeVariant()
|
||||||
|
|
||||||
|
t.background.FillColor = th.Color(theme.ColorNameOverlayBackground, v)
|
||||||
|
t.background.CornerRadius = th.Size(theme.SizeNameInputRadius)
|
||||||
|
t.background.StrokeColor = th.Color(theme.ColorNameInputBorder, v)
|
||||||
|
t.background.StrokeWidth = th.Size(theme.SizeNameInputBorder) * 2
|
||||||
|
t.background.Refresh()
|
||||||
|
|
||||||
|
t.accent.FillColor = th.Color(t.accentColor, v)
|
||||||
|
t.accent.Refresh()
|
||||||
|
|
||||||
|
canvas.Refresh(t.container)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user