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
+29 -6
View File
@@ -39,17 +39,24 @@ type NavigationHandler func(Route)
type CurPageFunc func() Route
type ToastProvider interface {
ShowSuccessToast(string)
}
type Controller struct {
visualizationData
AppVersion string
App *backend.App
MainWindow fyne.Window
AppVersion string
App *backend.App
MainWindow fyne.Window
// dependencies injected from MainWindow
NavHandler NavigationHandler
CurPageFunc CurPageFunc
ReloadFunc func()
RefreshPageFunc func()
SelectAllPageFunc func()
UnselectAllPageFunc func()
ToastProvider ToastProvider
popUpQueueMutex sync.Mutex
popUpQueue *widget.PopUp
@@ -300,7 +307,13 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
pop.Hide()
m.App.Config.Application.AddToPlaylistSkipDuplicates = sp.SkipDuplicates
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 {
m.App.Config.Application.DefaultPlaylistID = id
if sp.SkipDuplicates {
@@ -316,11 +329,21 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
_, ok := currentTrackIDs[trackID]
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 {
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.BrowsingPane = browsing.NewBrowsingPane(app, m.Controller, func() { m.Router.NavigateTo(m.StartupPage()) })
m.ToastOverlay = NewToastOverlay()
m.Router = browsing.NewRouter(app, m.Controller, m.BrowsingPane)
// inject controller dependencies
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.SelectAllPageFunc = m.BrowsingPane.SelectAll
m.Controller.UnselectAllPageFunc = m.BrowsingPane.UnselectAll
m.Controller.ToastProvider = m.ToastOverlay
if runtime.GOOS == "darwin" {
// 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.addShortcuts()
m.ToastOverlay = NewToastOverlay()
center := container.NewStack(m.BrowsingPane, m.ToastOverlay)
m.content = newMainWindowContent(container.NewBorder(nil, m.BottomPanel, nil, nil, center),
m.Controller.UnselectAll)
m.Window.SetContent(fynetooltip.AddWindowToolTipLayer(m.content, m.Window.Canvas()))
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.SaveWindowSize()
// save settings in case we crash during shutdown
+76 -14
View File
@@ -1,6 +1,7 @@
package ui
import (
"context"
"time"
"fyne.io/fyne/v2"
@@ -11,6 +12,8 @@ import (
"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"
)
type ToastOverlay struct {
@@ -18,6 +21,7 @@ type ToastOverlay struct {
currentToast *toast
currentToastAnim *fyne.Animation
dismissCancel context.CancelFunc
container *fyne.Container
}
@@ -32,7 +36,7 @@ func NewToastOverlay() *ToastOverlay {
func (t *ToastOverlay) ShowSuccessToast(message string) {
t.cancelPreviousToast()
t.currentToast = newToast(false, message)
t.currentToast = newToast(false, message, t.dismissToast)
t.container.Objects = append(t.container.Objects, t.currentToast)
s := t.Size()
@@ -41,17 +45,38 @@ func (t *ToastOverlay) ShowSuccessToast(message string) {
t.currentToast.Resize(min)
endPos := fyne.NewPos(s.Width-min.Width-pad, s.Height-min.Height-pad)
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 {
ct.Move(p)
}
if p == endPos {
t.currentToastAnim = nil
if dismissal {
t.cancelPreviousToast()
}
}
})
t.currentToastAnim.Curve = fyne.AnimationEaseOut
t.currentToastAnim.Start()
t.Refresh()
}
}
func (t *ToastOverlay) Resize(size fyne.Size) {
@@ -73,10 +98,33 @@ func (t *ToastOverlay) cancelPreviousToast() {
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, 100*time.Millisecond, f)
t.currentToastAnim.Curve = fyne.AnimationEaseIn
t.currentToastAnim.Start()
}
func (t *ToastOverlay) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(t.container)
}
@@ -84,12 +132,13 @@ func (t *ToastOverlay) CreateRenderer() fyne.WidgetRenderer {
type toast struct {
widget.BaseWidget
isErr bool
message string
isErr bool
message string
onDismiss func()
}
func newToast(isErr bool, message string) *toast {
t := &toast{isErr: isErr, message: message}
func newToast(isErr bool, message string, onDismiss func()) *toast {
t := &toast{isErr: isErr, message: message, onDismiss: onDismiss}
t.ExtendBaseWidget(t)
return t
}
@@ -98,6 +147,12 @@ 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)
@@ -138,6 +193,12 @@ func newToastRenderer(t *toast) *toastRenderer {
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,
@@ -152,10 +213,11 @@ func newToastRenderer(t *toast) *toastRenderer {
RightPadding: pad,
},
container.NewBorder(nil, nil, accent, nil,
widget.NewRichText(
&widget.TextSegment{Text: title, Style: widget.RichTextStyleSubHeading},
&widget.TextSegment{Text: t.message},
)),
container.NewVBox(
container.NewBorder(nil, nil, nil, container.NewHBox(close, util.NewHSpace(2)), titleText),
widget.NewLabel(t.message),
),
),
),
),
}