toast dismissal, hook up to playlist workflow (success case)

This commit is contained in:
Drew Weymouth
2025-01-05 11:01:07 -08:00
parent a7ed3eed8f
commit 18913780bf
3 changed files with 107 additions and 27 deletions
+26 -3
View File
@@ -39,17 +39,24 @@ type NavigationHandler func(Route)
type CurPageFunc func() Route type CurPageFunc func() Route
type ToastProvider interface {
ShowSuccessToast(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
@@ -300,7 +307,13 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
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 {
// TODO: translate, adjust by plurality
m.ToastProvider.ShowSuccessToast(fmt.Sprintf("Added %d tracks to playlist", len(trackIDs)))
}
}()
} else { } else {
m.App.Config.Application.DefaultPlaylistID = id m.App.Config.Application.DefaultPlaylistID = id
if sp.SkipDuplicates { if sp.SkipDuplicates {
@@ -316,11 +329,21 @@ 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 {
// TODO: translate, adjust by plurality
m.ToastProvider.ShowSuccessToast(fmt.Sprintf("Added %d tracks to playlist", len(filterTrackIDs)))
}
} }
}() }()
} else { } else {
go m.App.ServerManager.Server.AddPlaylistTracks(id, trackIDs) go func() {
err := m.App.ServerManager.Server.AddPlaylistTracks(id, trackIDs)
if err == nil {
// TODO: translate, adjust by plurality
m.ToastProvider.ShowSuccessToast(fmt.Sprintf("Added %d tracks to playlist", len(trackIDs)))
}
}()
} }
} }
+2 -7
View File
@@ -62,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
@@ -70,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
@@ -138,19 +140,12 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
m.BrowsingPane.DisableNavigationButtons() m.BrowsingPane.DisableNavigationButtons()
m.addShortcuts() m.addShortcuts()
m.ToastOverlay = NewToastOverlay()
center := container.NewStack(m.BrowsingPane, m.ToastOverlay) center := container.NewStack(m.BrowsingPane, m.ToastOverlay)
m.content = newMainWindowContent(container.NewBorder(nil, m.BottomPanel, nil, nil, center), 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()
// TODO: Remove me!!
go func() {
time.Sleep(2 * time.Second)
m.ToastOverlay.ShowSuccessToast("Added 20 songs to playlist.")
}()
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
+74 -12
View File
@@ -1,6 +1,7 @@
package ui package ui
import ( import (
"context"
"time" "time"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
@@ -11,6 +12,8 @@ import (
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"github.com/dweymouth/supersonic/ui/util"
"github.com/dweymouth/supersonic/ui/widgets"
) )
type ToastOverlay struct { type ToastOverlay struct {
@@ -18,6 +21,7 @@ type ToastOverlay struct {
currentToast *toast currentToast *toast
currentToastAnim *fyne.Animation currentToastAnim *fyne.Animation
dismissCancel context.CancelFunc
container *fyne.Container container *fyne.Container
} }
@@ -32,7 +36,7 @@ func NewToastOverlay() *ToastOverlay {
func (t *ToastOverlay) ShowSuccessToast(message string) { func (t *ToastOverlay) ShowSuccessToast(message string) {
t.cancelPreviousToast() t.cancelPreviousToast()
t.currentToast = newToast(false, message) t.currentToast = newToast(false, message, t.dismissToast)
t.container.Objects = append(t.container.Objects, t.currentToast) t.container.Objects = append(t.container.Objects, t.currentToast)
s := t.Size() s := t.Size()
@@ -41,17 +45,38 @@ func (t *ToastOverlay) ShowSuccessToast(message string) {
t.currentToast.Resize(min) t.currentToast.Resize(min)
endPos := fyne.NewPos(s.Width-min.Width-pad, s.Height-min.Height-pad) endPos := fyne.NewPos(s.Width-min.Width-pad, s.Height-min.Height-pad)
startPos := fyne.NewPos(s.Width, endPos.Y) startPos := fyne.NewPos(s.Width, endPos.Y)
t.currentToastAnim = canvas.NewPositionAnimation(startPos, endPos, 100*time.Millisecond, func(p fyne.Position) { f := t.makeToastAnimFunc(endPos, false)
t.currentToastAnim = canvas.NewPositionAnimation(startPos, endPos, 100*time.Millisecond, 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(2 * time.Second)
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 { if ct := t.currentToast; ct != nil {
ct.Move(p) ct.Move(p)
} }
if p == endPos { if p == endPos {
t.currentToastAnim = nil t.currentToastAnim = nil
if dismissal {
t.cancelPreviousToast()
}
}
} }
})
t.currentToastAnim.Curve = fyne.AnimationEaseOut
t.currentToastAnim.Start()
t.Refresh()
} }
func (t *ToastOverlay) Resize(size fyne.Size) { func (t *ToastOverlay) Resize(size fyne.Size) {
@@ -73,10 +98,33 @@ func (t *ToastOverlay) cancelPreviousToast() {
t.currentToastAnim.Stop() t.currentToastAnim.Stop()
t.currentToastAnim = nil t.currentToastAnim = nil
} }
t.container.Objects[0] = nil
t.container.Objects = t.container.Objects[:0] t.container.Objects = t.container.Objects[:0]
t.currentToast = nil 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, 100*time.Millisecond, f)
t.currentToastAnim.Curve = fyne.AnimationEaseIn
t.currentToastAnim.Start()
}
func (t *ToastOverlay) CreateRenderer() fyne.WidgetRenderer { func (t *ToastOverlay) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(t.container) return widget.NewSimpleRenderer(t.container)
} }
@@ -86,10 +134,11 @@ type toast struct {
isErr bool isErr bool
message string message string
onDismiss func()
} }
func newToast(isErr bool, message string) *toast { func newToast(isErr bool, message string, onDismiss func()) *toast {
t := &toast{isErr: isErr, message: message} t := &toast{isErr: isErr, message: message, onDismiss: onDismiss}
t.ExtendBaseWidget(t) t.ExtendBaseWidget(t)
return t return t
} }
@@ -98,6 +147,12 @@ func (t *toast) CreateRenderer() fyne.WidgetRenderer {
return newToastRenderer(t) return newToastRenderer(t)
} }
func (t *toast) Dismiss() {
if t.onDismiss != nil {
t.onDismiss()
}
}
// swallow all tap/mouse events because toast is transparent // swallow all tap/mouse events because toast is transparent
var ( var (
_ fyne.Tappable = (*toast)(nil) _ fyne.Tappable = (*toast)(nil)
@@ -138,6 +193,12 @@ func newToastRenderer(t *toast) *toastRenderer {
accent := canvas.NewRectangle(th.Color(accentColor, v)) accent := canvas.NewRectangle(th.Color(accentColor, v))
accent.SetMinSize(fyne.NewSize(4, 1)) 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() pad := theme.Padding()
return &toastRenderer{ return &toastRenderer{
background: background, background: background,
@@ -152,10 +213,11 @@ func newToastRenderer(t *toast) *toastRenderer {
RightPadding: pad, RightPadding: pad,
}, },
container.NewBorder(nil, nil, accent, nil, container.NewBorder(nil, nil, accent, nil,
widget.NewRichText( container.NewVBox(
&widget.TextSegment{Text: title, Style: widget.RichTextStyleSubHeading}, container.NewBorder(nil, nil, nil, container.NewHBox(close, util.NewHSpace(2)), titleText),
&widget.TextSegment{Text: t.message}, widget.NewLabel(t.message),
)), ),
),
), ),
), ),
} }