From 9b2fee47f1bb566d433c613c9ab3d30fe3b27d75 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Mon, 7 Aug 2023 17:28:24 -0700 Subject: [PATCH] refactor: shared base impl between albums and genre pages --- ui/browsing/albumspage.go | 252 ++++------------------------- ui/browsing/genrepage.go | 206 +++--------------------- ui/browsing/gridviewpage.go | 307 ++++++++++++++++++++++++++++++++++++ 3 files changed, 366 insertions(+), 399 deletions(-) create mode 100644 ui/browsing/gridviewpage.go diff --git a/ui/browsing/albumspage.go b/ui/browsing/albumspage.go index 6cd3479..50629ad 100644 --- a/ui/browsing/albumspage.go +++ b/ui/browsing/albumspage.go @@ -1,6 +1,8 @@ package browsing import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/sharedutil" @@ -8,238 +10,52 @@ import ( myTheme "github.com/dweymouth/supersonic/ui/theme" "github.com/dweymouth/supersonic/ui/util" "github.com/dweymouth/supersonic/ui/widgets" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" ) -var _ fyne.Widget = (*AlbumsPage)(nil) - -type AlbumsPage struct { - widget.BaseWidget - - cfg *backend.AlbumsPageConfig - pool *util.WidgetPool - contr *controller.Controller - pm *backend.PlaybackManager - im *backend.ImageManager - mp mediaprovider.MediaProvider - grid *widgets.GridView - gridState *widgets.GridViewState - searchGridState *widgets.GridViewState - searcher *widgets.SearchEntry - filterBtn *widgets.AlbumFilterButton - searchText string - filter mediaprovider.AlbumFilter - titleDisp *widget.RichText - sortOrder *selectWidget - container *fyne.Container +type albumsPageAdapter struct { + cfg *backend.AlbumsPageConfig + contr *controller.Controller + mp mediaprovider.MediaProvider + pm *backend.PlaybackManager } -type selectWidget struct { - widget.Select +func NewAlbumsPage(cfg *backend.AlbumsPageConfig, pool *util.WidgetPool, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) Page { + adapter := &albumsPageAdapter{cfg: cfg, contr: contr, mp: mp, pm: pm} + return NewGridViewPage(adapter, pool, mp, im) } -func NewSelect(options []string, onChanged func(string)) *selectWidget { - s := &selectWidget{ - Select: widget.Select{ - Options: options, - OnChanged: onChanged, - }, +func (a *albumsPageAdapter) Title() string { return "Albums" } + +func (a *albumsPageAdapter) Filter() *mediaprovider.AlbumFilter { + return &mediaprovider.AlbumFilter{} +} + +func (a *albumsPageAdapter) PlaceholderResource() fyne.Resource { return myTheme.AlbumIcon } + +func (a *albumsPageAdapter) Route() controller.Route { return controller.AlbumsRoute() } + +func (a *albumsPageAdapter) SortOrders() ([]string, string) { + orders := a.contr.App.ServerManager.Server.AlbumSortOrders() + if !sharedutil.SliceContains(a.mp.AlbumSortOrders(), a.cfg.SortOrder) { + a.cfg.SortOrder = string(a.mp.AlbumSortOrders()[0]) } - s.ExtendBaseWidget(s) - return s + return orders, a.cfg.SortOrder } -func (s *selectWidget) MinSize() fyne.Size { - return fyne.NewSize(170, s.Select.MinSize().Height) +func (a *albumsPageAdapter) SaveSortOrder(order string) { + a.cfg.SortOrder = order } -func NewAlbumsPage(cfg *backend.AlbumsPageConfig, pool *util.WidgetPool, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) *AlbumsPage { - a := &AlbumsPage{ - pool: pool, - cfg: cfg, - contr: contr, - pm: pm, - mp: mp, - im: im, - } - a.ExtendBaseWidget(a) +func (a *albumsPageAdapter) ActionButton() *widget.Button { return nil } - a.titleDisp = widget.NewRichTextWithText("Albums") - a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{ - SizeName: theme.SizeNameHeadingText, - } - a.sortOrder = NewSelect(mp.AlbumSortOrders(), a.onSortOrderChanged) - if !sharedutil.SliceContains(mp.AlbumSortOrders(), cfg.SortOrder) { - cfg.SortOrder = string(mp.AlbumSortOrders()[0]) - } - a.sortOrder.Selected = cfg.SortOrder - iter := mp.IterateAlbums(a.sortOrder.Selected, a.filter) - if g := pool.Obtain(util.WidgetTypeGridView); g != nil { - a.grid = g.(*widgets.GridView) - a.grid.Placeholder = myTheme.AlbumIcon - a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) - } else { - a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), im, myTheme.AlbumIcon) - } - contr.ConnectAlbumGridActions(a.grid) - a.createSearchAndFilter() - a.createContainer() - - return a +func (a *albumsPageAdapter) Iter(sortOrder string, filter mediaprovider.AlbumFilter) widgets.GridViewIterator { + return widgets.NewGridViewAlbumIterator(a.mp.IterateAlbums(sortOrder, filter)) } -func (a *AlbumsPage) createSearchAndFilter() { - a.searcher = widgets.NewSearchEntry() - a.searcher.Text = a.searchText - a.searcher.OnSearched = a.OnSearched - a.filterBtn = widgets.NewAlbumFilterButton(&a.filter, a.mp.GetGenres) - a.filterBtn.OnChanged = a.Reload +func (a *albumsPageAdapter) SearchIter(query string, filter mediaprovider.AlbumFilter) widgets.GridViewIterator { + return widgets.NewGridViewAlbumIterator(a.mp.SearchAlbums(query, filter)) } -func (a *AlbumsPage) createContainer() { - searchVbox := container.NewVBox(layout.NewSpacer(), a.searcher, layout.NewSpacer()) - sortVbox := container.NewVBox(layout.NewSpacer(), a.sortOrder, layout.NewSpacer()) - a.container = container.NewBorder( - container.NewHBox(util.NewHSpace(6), a.titleDisp, sortVbox, layout.NewSpacer(), container.NewCenter(a.filterBtn), searchVbox, util.NewHSpace(12)), - nil, - nil, - nil, - a.grid, - ) -} - -func restoreAlbumsPage(saved *savedAlbumsPage) *AlbumsPage { - a := &AlbumsPage{ - cfg: saved.cfg, - pool: saved.pool, - contr: saved.contr, - pm: saved.pm, - mp: saved.mp, - im: saved.im, - gridState: saved.gridState, - searchGridState: saved.searchGridState, - searchText: saved.searchText, - filter: saved.filter, - } - a.ExtendBaseWidget(a) - - a.titleDisp = widget.NewRichTextWithText("Albums") - a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{ - SizeName: theme.SizeNameHeadingText, - } - a.sortOrder = NewSelect(a.mp.AlbumSortOrders(), nil) - a.sortOrder.Selected = saved.sortOrder - a.sortOrder.OnChanged = a.onSortOrderChanged - state := saved.gridState - if a.searchText != "" { - a.sortOrder.Disable() - state = saved.searchGridState - } - if g := a.pool.Obtain(util.WidgetTypeGridView); g != nil { - a.grid = g.(*widgets.GridView) - a.grid.ResetFromState(state) - } else { - a.grid = widgets.NewGridViewFromState(state) - } - a.createSearchAndFilter() - a.createContainer() - - return a -} - -func (a *AlbumsPage) OnSearched(query string) { - if query == "" { - a.sortOrder.Enable() - a.grid.ResetFromState(a.gridState) - a.searchGridState = nil - } else { - a.sortOrder.Disable() - a.doSearch(query) - } - a.searchText = query -} - -func (a *AlbumsPage) Route() controller.Route { - return controller.AlbumsRoute() -} - -var _ Searchable = (*AlbumsPage)(nil) - -func (a *AlbumsPage) SearchWidget() fyne.Focusable { - return a.searcher -} - -func (a *AlbumsPage) Reload() { - if a.searchText != "" { - a.doSearch(a.searchText) - } else { - iter := a.mp.IterateAlbums(a.sortOrder.Selected, a.filter) - a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) - } -} - -func (a *AlbumsPage) Save() SavedPage { - sa := &savedAlbumsPage{ - cfg: a.cfg, - pool: a.pool, - contr: a.contr, - pm: a.pm, - mp: a.mp, - im: a.im, - searchText: a.searchText, - filter: a.filter, - sortOrder: a.sortOrder.Selected, - gridState: a.gridState, - searchGridState: a.searchGridState, - } - if a.searchText == "" { - sa.gridState = a.grid.SaveToState() - } else { - sa.searchGridState = a.grid.SaveToState() - } - a.grid.Clear() - a.pool.Release(util.WidgetTypeGridView, a.grid) - return sa -} - -func (a *AlbumsPage) doSearch(query string) { - if a.searchText == "" { - a.gridState = a.grid.SaveToState() - } - iter := widgets.NewGridViewAlbumIterator(a.mp.SearchAlbums(query, a.filter)) - a.grid.Reset(iter) -} - -func (a *AlbumsPage) onSortOrderChanged(order string) { - a.cfg.SortOrder = a.sortOrder.Selected - iter := a.mp.IterateAlbums(order, a.filter) - a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) -} - -func (a *AlbumsPage) CreateRenderer() fyne.WidgetRenderer { - a.ExtendBaseWidget(a) - return widget.NewSimpleRenderer(a.container) -} - -type savedAlbumsPage struct { - searchText string - filter mediaprovider.AlbumFilter - cfg *backend.AlbumsPageConfig - pool *util.WidgetPool - contr *controller.Controller - pm *backend.PlaybackManager - mp mediaprovider.MediaProvider - im *backend.ImageManager - sortOrder string - gridState *widgets.GridViewState - searchGridState *widgets.GridViewState -} - -func (s *savedAlbumsPage) Restore() Page { - return restoreAlbumsPage(s) +func (a *albumsPageAdapter) ConnectGridActions(gv *widgets.GridView) { + a.contr.ConnectAlbumGridActions(gv) } diff --git a/ui/browsing/genrepage.go b/ui/browsing/genrepage.go index 48b0275..380466f 100644 --- a/ui/browsing/genrepage.go +++ b/ui/browsing/genrepage.go @@ -9,204 +9,48 @@ import ( "github.com/dweymouth/supersonic/ui/widgets" "fyne.io/fyne/v2" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) -// TODO: there is a lot of code duplication between this and albumspage. Refactor? -type GenrePage struct { - widget.BaseWidget - - genre string - pool *util.WidgetPool - contr *controller.Controller - im *backend.ImageManager - pm *backend.PlaybackManager - mp mediaprovider.MediaProvider - grid *widgets.GridView - gridState *widgets.GridViewState - searchGridState *widgets.GridViewState - searcher *widgets.SearchEntry - searchText string - filter mediaprovider.AlbumFilter - filterBtn *widgets.AlbumFilterButton - titleDisp *widget.RichText - playRandom *widget.Button - - container *fyne.Container +type genrePageAdapter struct { + genre string + contr *controller.Controller + mp mediaprovider.MediaProvider + pm *backend.PlaybackManager } -func NewGenrePage(genre string, pool *util.WidgetPool, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) *GenrePage { - g := &GenrePage{ - genre: genre, - pool: pool, - filter: mediaprovider.AlbumFilter{Genres: []string{genre}}, - contr: contr, - pm: pm, - mp: mp, - im: im, - } - g.ExtendBaseWidget(g) - - g.titleDisp = widget.NewRichTextWithText(genre) - g.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{ - SizeName: theme.SizeNameHeadingText, - } - g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs) - iter := widgets.NewGridViewAlbumIterator(g.mp.IterateAlbums("", g.filter)) - if gv := pool.Obtain(util.WidgetTypeGridView); gv != nil { - g.grid = gv.(*widgets.GridView) - g.grid.Placeholder = myTheme.AlbumIcon - g.grid.Reset(iter) - } else { - g.grid = widgets.NewGridView(iter, g.im, myTheme.AlbumIcon) - } - g.contr.ConnectAlbumGridActions(g.grid) - g.createSearchAndFilter() - g.createContainer() - - return g +func NewGenrePage(genre string, pool *util.WidgetPool, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) Page { + adapter := &genrePageAdapter{genre: genre, contr: contr, mp: mp, pm: pm} + return NewGridViewPage(adapter, pool, mp, im) } -func (g *GenrePage) createSearchAndFilter() { - g.searcher = widgets.NewSearchEntry() - g.searcher.Text = g.searchText - g.searcher.OnSearched = g.OnSearched - // genre filter is disabled for this page, so no need to actually call genre list fetching function - g.filterBtn = widgets.NewAlbumFilterButton(&g.filter, func() ([]*mediaprovider.Genre, error) { return nil, nil }) - g.filterBtn.GenreDisabled = true - g.filterBtn.OnChanged = g.Reload +func (g *genrePageAdapter) Title() string { return g.genre } + +func (g *genrePageAdapter) Filter() *mediaprovider.AlbumFilter { + return &mediaprovider.AlbumFilter{Genres: []string{g.genre}} } -func (g *GenrePage) createContainer() { - searchVbox := container.NewVBox(layout.NewSpacer(), g.searcher, layout.NewSpacer()) - playRandomVbox := container.NewVBox(layout.NewSpacer(), g.playRandom, layout.NewSpacer()) - g.container = container.NewBorder( - container.NewHBox(util.NewHSpace(6), - g.titleDisp, playRandomVbox, layout.NewSpacer(), container.NewCenter(g.filterBtn), searchVbox, util.NewHSpace(15)), - nil, nil, nil, g.grid, - ) +func (g *genrePageAdapter) PlaceholderResource() fyne.Resource { + return myTheme.AlbumIcon } -func restoreGenrePage(saved *savedGenrePage) *GenrePage { - g := &GenrePage{ - genre: saved.genre, - pool: saved.pool, - contr: saved.contr, - pm: saved.pm, - mp: saved.mp, - im: saved.im, - gridState: saved.gridState, - searchGridState: saved.searchGridState, - searchText: saved.searchText, - filter: saved.filter, - } - g.ExtendBaseWidget(g) - - g.titleDisp = widget.NewRichTextWithText(g.genre) - g.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{ - SizeName: theme.SizeNameHeadingText, - } - g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs) - state := saved.gridState - if g.searchText != "" { - state = saved.searchGridState - } - if gv := g.pool.Obtain(util.WidgetTypeGridView); gv != nil { - g.grid = gv.(*widgets.GridView) - g.grid.Placeholder = myTheme.AlbumIcon - g.grid.ResetFromState(state) - } else { - g.grid = widgets.NewGridViewFromState(state) - } - g.createSearchAndFilter() - g.createContainer() - - return g +func (g *genrePageAdapter) Route() controller.Route { + return controller.GenreRoute(g.genre) } -func (g *GenrePage) CreateRenderer() fyne.WidgetRenderer { - return widget.NewSimpleRenderer(g.container) +func (g *genrePageAdapter) ActionButton() *widget.Button { + fn := func() { go g.pm.PlayRandomSongs(g.genre) } + return widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, fn) } -func (a *GenrePage) Route() controller.Route { - return controller.GenreRoute(a.genre) +func (a *genrePageAdapter) Iter(sortOrder string, filter mediaprovider.AlbumFilter) widgets.GridViewIterator { + return widgets.NewGridViewAlbumIterator(a.mp.IterateAlbums(sortOrder, filter)) } -func (g *GenrePage) Reload() { - if g.searchText != "" { - g.doSearch(g.searchText) - } else { - iter := g.mp.IterateAlbums("", g.filter) - g.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) - } +func (a *genrePageAdapter) SearchIter(query string, filter mediaprovider.AlbumFilter) widgets.GridViewIterator { + return widgets.NewGridViewAlbumIterator(a.mp.SearchAlbums(query, filter)) } -func (g *GenrePage) Save() SavedPage { - sg := &savedGenrePage{ - genre: g.genre, - pool: g.pool, - filter: g.filter, - searchText: g.searchText, - contr: g.contr, - pm: g.pm, - mp: g.mp, - im: g.im, - gridState: g.gridState, - searchGridState: g.searchGridState, - } - if g.searchText != "" { - sg.searchGridState = g.grid.SaveToState() - } else { - sg.gridState = g.grid.SaveToState() - } - g.grid.Clear() - g.pool.Release(util.WidgetTypeGridView, g.grid) - return sg -} - -var _ Searchable = (*AlbumsPage)(nil) - -func (g *GenrePage) SearchWidget() fyne.Focusable { - return g.searcher -} - -func (g *GenrePage) OnSearched(query string) { - if query == "" { - g.grid.ResetFromState(g.gridState) - } else { - g.doSearch(query) - } - g.searchText = query -} - -func (g *GenrePage) doSearch(query string) { - if g.searchText == "" { - g.gridState = g.grid.SaveToState() - } - iter := g.mp.SearchAlbums(query, g.filter) - g.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) -} - -func (g *GenrePage) playRandomSongs() { - go g.pm.PlayRandomSongs(g.genre) -} - -type savedGenrePage struct { - genre string - searchText string - pool *util.WidgetPool - filter mediaprovider.AlbumFilter - contr *controller.Controller - pm *backend.PlaybackManager - mp mediaprovider.MediaProvider - im *backend.ImageManager - gridState *widgets.GridViewState - searchGridState *widgets.GridViewState -} - -func (s *savedGenrePage) Restore() Page { - return restoreGenrePage(s) +func (g *genrePageAdapter) ConnectGridActions(gv *widgets.GridView) { + g.contr.ConnectAlbumGridActions(gv) } diff --git a/ui/browsing/gridviewpage.go b/ui/browsing/gridviewpage.go new file mode 100644 index 0000000..b462d13 --- /dev/null +++ b/ui/browsing/gridviewpage.go @@ -0,0 +1,307 @@ +package browsing + +import ( + "github.com/dweymouth/supersonic/backend" + "github.com/dweymouth/supersonic/backend/mediaprovider" + "github.com/dweymouth/supersonic/ui/controller" + "github.com/dweymouth/supersonic/ui/util" + "github.com/dweymouth/supersonic/ui/widgets" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +var _ Page = (*GridViewPage)(nil) + +// Base widget for grid view pages +type GridViewPage struct { + widget.BaseWidget + + adapter GridViewPageAdapter + pool *util.WidgetPool + mp mediaprovider.MediaProvider + im *backend.ImageManager + + grid *widgets.GridView + gridState *widgets.GridViewState + searchGridState *widgets.GridViewState + + title *widget.RichText + sortOrder *sortOrderSelect + filterBtn *widgets.AlbumFilterButton + filter *mediaprovider.AlbumFilter + searcher *widgets.SearchEntry + searchText string + + container *fyne.Container +} + +// Base type for pages that show an iterable GridView +type GridViewPageAdapter interface { + // Returns the title for the page + Title() string + + // Returns the base album filter for this page, if any. + // A filterable page with no base filters applied should return a zero-valued + // *AlbumFilter, *not* nil. (Nil means unfilterable and no filter button created.) + Filter() *mediaprovider.AlbumFilter + + // Returns the cover placeholder resource for the page + PlaceholderResource() fyne.Resource + + // Returns the route for the page + Route() controller.Route + + // Returns the ActionButton for this page, if any + ActionButton() *widget.Button + + // Returns the iterator for the given sortOrder and filter. + // (Non-album pages can ignore the filter argument) + Iter(sortOrder string, filter mediaprovider.AlbumFilter) widgets.GridViewIterator + + // Returns the iterator for the given search query and filter. + SearchIter(query string, filter mediaprovider.AlbumFilter) widgets.GridViewIterator + + // Function that connects the GridView callbacks to the appropriate action handlers. + ConnectGridActions(*widgets.GridView) +} + +type SortableGridViewPageAdapter interface { + // Returns the list of sort orders and the initially selected sort order + SortOrders() ([]string, string) + + // Saves the given sort order setting. + SaveSortOrder(string) +} + +type sortOrderSelect struct { + widget.Select +} + +func NewSortOrderSelect(options []string, onChanged func(string)) *sortOrderSelect { + s := &sortOrderSelect{ + Select: widget.Select{ + Options: options, + OnChanged: onChanged, + }, + } + s.ExtendBaseWidget(s) + return s +} + +func (s *sortOrderSelect) MinSize() fyne.Size { + return fyne.NewSize(170, s.Select.MinSize().Height) +} + +func NewGridViewPage( + adapter GridViewPageAdapter, + pool *util.WidgetPool, + mp mediaprovider.MediaProvider, + im *backend.ImageManager, +) *GridViewPage { + gp := &GridViewPage{ + adapter: adapter, + pool: pool, + mp: mp, + im: im, + filter: adapter.Filter(), + } + gp.ExtendBaseWidget(gp) + gp.createTitleAndSort() + + iter := adapter.Iter(gp.getSortOrder(), gp.getFilter()) + if g := pool.Obtain(util.WidgetTypeGridView); g != nil { + gp.grid = g.(*widgets.GridView) + gp.grid.Placeholder = adapter.PlaceholderResource() + gp.grid.Reset(iter) + } else { + gp.grid = widgets.NewGridView(iter, im, adapter.PlaceholderResource()) + } + adapter.ConnectGridActions(gp.grid) + gp.createSearchAndFilter() + gp.createContainer() + return gp +} + +func (g *GridViewPage) createTitleAndSort() { + g.title = widget.NewRichText(&widget.TextSegment{ + Text: g.adapter.Title(), + Style: widget.RichTextStyle{SizeName: theme.SizeNameHeadingText}, + }) + if s, ok := g.adapter.(SortableGridViewPageAdapter); ok { + sorts, selected := s.SortOrders() + g.sortOrder = NewSortOrderSelect(sorts, g.onSortOrderChanged) + g.sortOrder.Selected = selected + } +} + +func (g *GridViewPage) createSearchAndFilter() { + g.searcher = widgets.NewSearchEntry() + g.searcher.Text = g.searchText + g.searcher.OnSearched = g.OnSearched + if g.filter != nil { + disableGenres := len(g.filter.Genres) > 0 + genreFn := g.mp.GetGenres + if disableGenres { + // genre filter is disabled for this page, so no need to actually call genre list fetching function + genreFn = func() ([]*mediaprovider.Genre, error) { return nil, nil } + } + g.filterBtn = widgets.NewAlbumFilterButton(g.filter, genreFn) + g.filterBtn.GenreDisabled = disableGenres + g.filterBtn.OnChanged = g.Reload + } +} + +func (g *GridViewPage) createContainer() { + header := container.NewHBox(util.NewHSpace(6), g.title) + if g.sortOrder != nil { + sortVbox := container.NewVBox(layout.NewSpacer(), g.sortOrder, layout.NewSpacer()) + header.Add(sortVbox) + } + if b := g.adapter.ActionButton(); b != nil { + btnVbox := container.NewVBox(layout.NewSpacer(), b, layout.NewSpacer()) + header.Add(btnVbox) + } + header.Add(layout.NewSpacer()) + if g.filterBtn != nil { + header.Add(container.NewCenter(g.filterBtn)) + } + searchVbox := container.NewVBox(layout.NewSpacer(), g.searcher, layout.NewSpacer()) + header.Add(searchVbox) + header.Add(util.NewHSpace(12)) + g.container = container.NewBorder(header, nil, nil, nil, g.grid) +} + +func (g *GridViewPage) Reload() { + if g.searchText != "" { + g.doSearch(g.searchText) + } else { + g.grid.Reset(g.adapter.Iter(g.getSortOrder(), g.getFilter())) + } +} + +func (g *GridViewPage) Route() controller.Route { + return g.adapter.Route() +} + +var _ Searchable = (*GridViewPage)(nil) + +func (g *GridViewPage) SearchWidget() fyne.Focusable { + return g.searcher +} + +func (g *GridViewPage) OnSearched(query string) { + if query == "" { + if g.sortOrder != nil { + g.sortOrder.Enable() + } + g.grid.ResetFromState(g.gridState) + g.searchGridState = nil + } else { + if g.sortOrder != nil { + g.sortOrder.Disable() + } + g.doSearch(query) + } + g.searchText = query +} + +func (g *GridViewPage) doSearch(query string) { + if g.searchText == "" { + g.gridState = g.grid.SaveToState() + } + g.grid.Reset(g.adapter.SearchIter(query, g.getFilter())) +} + +func (g *GridViewPage) onSortOrderChanged(order string) { + g.adapter.(SortableGridViewPageAdapter).SaveSortOrder(g.getSortOrder()) + g.grid.Reset(g.adapter.Iter(g.getSortOrder(), g.getFilter())) +} + +func (g *GridViewPage) getFilter() mediaprovider.AlbumFilter { + if g.filter != nil { + return *g.filter + } + return mediaprovider.AlbumFilter{} +} + +func (g *GridViewPage) getSortOrder() string { + if g.sortOrder != nil { + return g.sortOrder.Selected + } + return "" +} + +func (g *GridViewPage) CreateRenderer() fyne.WidgetRenderer { + return widget.NewSimpleRenderer(g.container) +} + +type savedGridViewPage struct { + adapter GridViewPageAdapter + im *backend.ImageManager + mp mediaprovider.MediaProvider + searchText string + filter *mediaprovider.AlbumFilter + pool *util.WidgetPool + sortOrder string + gridState *widgets.GridViewState + searchGridState *widgets.GridViewState +} + +func (g *GridViewPage) Save() SavedPage { + sa := &savedGridViewPage{ + adapter: g.adapter, + pool: g.pool, + mp: g.mp, + im: g.im, + searchText: g.searchText, + filter: g.filter, + sortOrder: g.getSortOrder(), + gridState: g.gridState, + searchGridState: g.searchGridState, + } + if g.searchText == "" { + sa.gridState = g.grid.SaveToState() + } else { + sa.searchGridState = g.grid.SaveToState() + } + g.grid.Clear() + g.pool.Release(util.WidgetTypeGridView, g.grid) + return sa +} + +func (s *savedGridViewPage) Restore() Page { + gp := &GridViewPage{ + adapter: s.adapter, + pool: s.pool, + mp: s.mp, + im: s.im, + gridState: s.gridState, + searchGridState: s.searchGridState, + searchText: s.searchText, + filter: s.filter, + } + gp.ExtendBaseWidget(gp) + + gp.createTitleAndSort() + state := s.gridState + if s.searchText != "" { + if gp.sortOrder != nil { + gp.sortOrder.Disable() + } + state = s.searchGridState + } + if g := gp.pool.Obtain(util.WidgetTypeGridView); g != nil { + gp.grid = g.(*widgets.GridView) + gp.grid.ResetFromState(state) + } else { + gp.grid = widgets.NewGridViewFromState(state) + } + gp.adapter.ConnectGridActions(gp.grid) + gp.createSearchAndFilter() + gp.createContainer() + return gp +}