diff --git a/ui/browsing/albumspage.go b/ui/browsing/albumspage.go index 293851a..5c7bddd 100644 --- a/ui/browsing/albumspage.go +++ b/ui/browsing/albumspage.go @@ -4,6 +4,7 @@ import ( "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/controller" + "github.com/dweymouth/supersonic/ui/dialogs" "github.com/dweymouth/supersonic/ui/util" "github.com/dweymouth/supersonic/ui/widgets" @@ -27,7 +28,9 @@ type AlbumsPage struct { grid *widgets.GridView searchGrid *widgets.GridView searcher *widgets.SearchEntry + filterBtn *widgets.AlbumFilterButton searchText string + filter backend.AlbumFilter titleDisp *widget.RichText sortOrder *selectWidget container *fyne.Container @@ -71,11 +74,12 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller, cfg.SortOrder = string(backend.AlbumSortRecentlyAdded) } a.sortOrder.Selected = cfg.SortOrder - iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected), backend.AlbumFilter{}) + iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected), a.filter) a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), im) contr.ConnectAlbumGridActions(a.grid) a.searcher = widgets.NewSearchEntry() a.searcher.OnSearched = a.OnSearched + a.filterBtn = widgets.NewAlbumFilterButton(&a.filter, func(filter *backend.AlbumFilter) fyne.CanvasObject { return dialogs.NewAlbumFilterDialog(filter) }) a.createContainer(false) return a @@ -89,7 +93,7 @@ func (a *AlbumsPage) createContainer(searchgrid bool) { g = a.searchGrid } a.container = container.NewBorder( - container.NewHBox(util.NewHSpace(6), a.titleDisp, sortVbox, layout.NewSpacer(), searchVbox, util.NewHSpace(12)), + container.NewHBox(util.NewHSpace(6), a.titleDisp, sortVbox, layout.NewSpacer(), a.filterBtn, searchVbox, util.NewHSpace(12)), nil, nil, nil, @@ -154,7 +158,7 @@ func (a *AlbumsPage) Reload() { if a.searchText != "" { a.doSearch(a.searchText) } else { - iter := a.lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected), backend.AlbumFilter{}) + iter := a.lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected), a.filter) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) a.grid.Refresh() } @@ -190,7 +194,7 @@ func (a *AlbumsPage) doSearch(query string) { func (a *AlbumsPage) onSortOrderChanged(order string) { a.cfg.SortOrder = a.sortOrder.Selected - iter := a.lm.AlbumsIter(backend.AlbumSortOrder(order), backend.AlbumFilter{}) + iter := a.lm.AlbumsIter(backend.AlbumSortOrder(order), a.filter) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) if a.searchText == "" { a.container.Objects[0] = a.grid diff --git a/ui/dialogs/albumfilterdialog.go b/ui/dialogs/albumfilterdialog.go new file mode 100644 index 0000000..88c503c --- /dev/null +++ b/ui/dialogs/albumfilterdialog.go @@ -0,0 +1,76 @@ +package dialogs + +import ( + "strconv" + "unicode" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" + "github.com/dweymouth/supersonic/backend" + "github.com/dweymouth/supersonic/ui/widgets" +) + +type AlbumFilterDialog struct { + widget.BaseWidget + + OnDismissed func() + + filter *backend.AlbumFilter + container *fyne.Container +} + +func NewAlbumFilterDialog(filter *backend.AlbumFilter) *AlbumFilterDialog { + a := &AlbumFilterDialog{filter: filter} + a.ExtendBaseWidget(a) + + // setup min and max year filters + yearValidator := func(curText string, r rune) bool { + return unicode.IsDigit(r) && len(curText) <= 3 + } + minYear := widgets.NewTextRestrictedEntry(yearValidator) + minYear.OnChanged = func(yearStr string) { + if i, err := strconv.Atoi(yearStr); err == nil { + a.filter.MinYear = i + } + } + if filter.MinYear > 0 { + minYear.Text = strconv.Itoa(filter.MinYear) + } + maxYear := widgets.NewTextRestrictedEntry(yearValidator) + maxYear.OnChanged = func(yearStr string) { + if i, err := strconv.Atoi(yearStr); err == nil { + a.filter.MaxYear = i + } + } + if filter.MaxYear > 0 { + maxYear.Text = strconv.Itoa(filter.MaxYear) + } + + var isNotFavorite *widget.Check + // setup is favorite/not favorite filters + isFavorite := widget.NewCheck("Is favorite", func(fav bool) { + if fav { + isNotFavorite.SetChecked(false) + } + a.filter.ExcludeUnfavorited = fav + }) + isNotFavorite = widget.NewCheck("Is not favorite", func(fav bool) { + if fav { + isFavorite.SetChecked(false) + } + a.filter.ExcludeFavorited = fav + }) + + a.container = container.NewVBox( + container.NewHBox(widget.NewLabel("Year from"), minYear, widget.NewLabel("to"), maxYear), + isFavorite, + isNotFavorite, + ) + + return a +} + +func (a *AlbumFilterDialog) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(a.container) +} diff --git a/ui/widgets/custombuttons.go b/ui/widgets/custombuttons.go new file mode 100644 index 0000000..172b88c --- /dev/null +++ b/ui/widgets/custombuttons.go @@ -0,0 +1,71 @@ +package widgets + +import ( + "github.com/dweymouth/supersonic/backend" + "github.com/dweymouth/supersonic/res" + "github.com/dweymouth/supersonic/ui/theme" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" +) + +type FavoriteButton struct { + widget.Button + + IsFavorited bool +} + +func NewFavoriteButton(onTapped func()) *FavoriteButton { + f := &FavoriteButton{ + Button: widget.Button{ + OnTapped: onTapped, + Icon: res.ResHeartOutlineInvertPng, + }, + } + f.ExtendBaseWidget(f) + return f +} + +func (f *FavoriteButton) Tapped(e *fyne.PointEvent) { + f.IsFavorited = !f.IsFavorited + f.Button.Tapped(e) + f.Refresh() +} + +func (f *FavoriteButton) Refresh() { + if f.IsFavorited { + f.Icon = theme.FavoriteIcon + } else { + f.Icon = theme.NotFavoriteIcon + } + f.Button.Refresh() +} + +type AlbumFilterButton struct { + widget.Button + + filter *backend.AlbumFilter + + dialogConstructor func(*backend.AlbumFilter) fyne.CanvasObject + dialog *widget.PopUp +} + +func NewAlbumFilterButton(filter *backend.AlbumFilter, dialogConstructor func(*backend.AlbumFilter) fyne.CanvasObject) *AlbumFilterButton { + a := &AlbumFilterButton{ + filter: filter, + dialogConstructor: dialogConstructor, + Button: widget.Button{ + Icon: theme.AlbumIcon, + }, + } + a.OnTapped = a.showFilterDialog + a.ExtendBaseWidget(a) + return a +} + +func (a *AlbumFilterButton) showFilterDialog() { + if a.dialog == nil { + a.dialog = widget.NewModalPopUp(a.dialogConstructor(a.filter), fyne.CurrentApp().Driver().CanvasForObject(a)) + } + a.dialog.Show() +} diff --git a/ui/widgets/favoritebutton.go b/ui/widgets/favoritebutton.go deleted file mode 100644 index 77265c4..0000000 --- a/ui/widgets/favoritebutton.go +++ /dev/null @@ -1,41 +0,0 @@ -package widgets - -import ( - "github.com/dweymouth/supersonic/res" - "github.com/dweymouth/supersonic/ui/theme" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/widget" -) - -type FavoriteButton struct { - widget.Button - - IsFavorited bool -} - -func NewFavoriteButton(onTapped func()) *FavoriteButton { - f := &FavoriteButton{ - Button: widget.Button{ - OnTapped: onTapped, - Icon: res.ResHeartOutlineInvertPng, - }, - } - f.ExtendBaseWidget(f) - return f -} - -func (f *FavoriteButton) Tapped(e *fyne.PointEvent) { - f.IsFavorited = !f.IsFavorited - f.Button.Tapped(e) - f.Refresh() -} - -func (f *FavoriteButton) Refresh() { - if f.IsFavorited { - f.Icon = theme.FavoriteIcon - } else { - f.Icon = theme.NotFavoriteIcon - } - f.Button.Refresh() -}