[gnome-music/wip/jfelder/gtk4-v3] GTK4 port




commit 147d28b51fd5dfcd6ad55c8713a2d1484111d4f3
Author: Jean Felder <jfelder src gnome org>
Date:   Thu Feb 17 10:01:25 2022 +0100

    GTK4 port
    
    Co-authored-by: Marinus Schraal <mschraal gnome org>

 data/org.gnome.Music.css                        |  66 ++---
 data/org.gnome.Music.gresource.xml              |   8 +-
 data/org.gnome.Music.gschema.xml                |   5 -
 data/ui/AboutDialog.ui.in                       |   2 +-
 data/ui/AlbumCover.ui                           |  22 +-
 data/ui/AlbumCoverListItem.ui                   |  51 ++++
 data/ui/AlbumWidget.ui                          | 229 +++++++---------
 data/ui/AlbumsView.ui                           |  69 ++---
 data/ui/AppMenu.ui                              |  36 +--
 data/ui/ArtistAlbumsWidget.ui                   |  20 +-
 data/ui/ArtistSearchTile.ui                     |  26 +-
 data/ui/ArtistTile.ui                           |  12 +-
 data/ui/ArtistsView.ui                          |  31 +--
 data/ui/DiscBox.ui                              |  18 +-
 data/ui/DiscListItem.ui                         |  29 ++
 data/ui/EmptyView.ui                            |  23 +-
 data/ui/HeaderBar.ui                            | 162 +++++-------
 data/ui/InitialState.ui                         |  33 +++
 data/ui/LastfmDialog.ui                         |  16 +-
 data/ui/LoadingNotification.ui                  |   4 +-
 data/ui/NotificationsPopup.ui                   |   3 -
 data/ui/PlayerToolbar.ui                        | 129 +++------
 data/ui/PlaylistControls.ui                     | 107 +++-----
 data/ui/PlaylistDialog.ui                       | 136 +++++-----
 data/ui/PlaylistDialogRow.ui                    |   7 +-
 data/ui/PlaylistNotification.ui                 |  15 +-
 data/ui/PlaylistTile.ui                         |  10 +-
 data/ui/PlaylistsView.ui                        |  25 +-
 data/ui/PlaylistsWidget.ui                      |  13 +-
 data/ui/SearchHeaderBar.ui                      |  97 +++----
 data/ui/SearchView.ui                           | 335 +++++++++++-------------
 data/ui/SelectionBarMenuButton.ui               |  10 +-
 data/ui/SelectionToolbar.ui                     |   9 +-
 data/ui/SongListItem.ui                         |  72 +++++
 data/ui/SongWidget.ui                           | 159 ++++++-----
 data/ui/SongWidgetMenu.ui                       |  58 ++--
 data/ui/SongsView.ui                            | 124 +--------
 data/ui/TwoLineTip.ui                           |   9 +-
 data/ui/Window.ui                               |  23 +-
 data/ui/help-overlay.ui                         |  18 --
 gnome-music.in                                  |  22 +-
 gnomemusic/application.py                       |  16 +-
 gnomemusic/artcache.py                          |  48 ++--
 gnomemusic/corealbum.py                         |  21 +-
 gnomemusic/coreartist.py                        |  19 +-
 gnomemusic/coredisc.py                          |  18 +-
 gnomemusic/coregrilo.py                         |  12 +-
 gnomemusic/coremodel.py                         | 186 ++++++-------
 gnomemusic/coverpaintable.py                    | 118 +++++++++
 gnomemusic/defaulticon.py                       | 105 +-------
 gnomemusic/grilowrappers/grlsearchwrapper.py    |   8 +-
 gnomemusic/grilowrappers/grltrackerplaylists.py |  26 +-
 gnomemusic/grilowrappers/grltrackerwrapper.py   |  50 ++--
 gnomemusic/player.py                            |  13 +-
 gnomemusic/songliststore.py                     | 116 --------
 gnomemusic/utils.py                             |  34 ++-
 gnomemusic/views/albumsview.py                  | 259 +++++++++---------
 gnomemusic/views/artistsview.py                 | 148 ++++-------
 gnomemusic/views/emptyview.py                   |  12 +-
 gnomemusic/views/playlistsview.py               |   2 +-
 gnomemusic/views/searchview.py                  | 182 +++----------
 gnomemusic/views/songsview.py                   | 308 ++++++++++++----------
 gnomemusic/widgets/albumcover.py                |   2 -
 gnomemusic/widgets/albumwidget.py               |   6 +-
 gnomemusic/widgets/appmenu.py                   |   2 +-
 gnomemusic/widgets/artistalbumswidget.py        |  58 ++--
 gnomemusic/widgets/artistsearchtile.py          |  10 +-
 gnomemusic/widgets/artisttile.py                |  33 ++-
 gnomemusic/widgets/artstack.py                  |  24 +-
 gnomemusic/widgets/discbox.py                   |  24 +-
 gnomemusic/widgets/headerbar.py                 |  42 ++-
 gnomemusic/widgets/lastfmdialog.py              |   6 +-
 gnomemusic/widgets/notificationspopup.py        |   9 +-
 gnomemusic/widgets/playertoolbar.py             |  27 +-
 gnomemusic/widgets/playlistdialog.py            |   3 +-
 gnomemusic/widgets/searchheaderbar.py           |  28 +-
 gnomemusic/widgets/smoothscale.py               |  12 +-
 gnomemusic/widgets/songwidget.py                | 106 ++++----
 gnomemusic/widgets/songwidgetmenu.py            |  52 ++--
 gnomemusic/widgets/starhandlerwidget.py         |   6 +-
 gnomemusic/window.py                            |  27 +-
 gnomemusic/windowplacement.py                   |  39 ++-
 meson.build                                     |  15 +-
 org.gnome.Music.json                            |  11 +-
 po/POTFILES.skip                                |   1 -
 85 files changed, 1979 insertions(+), 2508 deletions(-)
---
diff --git a/data/org.gnome.Music.css b/data/org.gnome.Music.css
index 9e2cea716..04f4b5e66 100644
--- a/data/org.gnome.Music.css
+++ b/data/org.gnome.Music.css
@@ -25,43 +25,12 @@
 }
 
 /* PlayerToolbar */
-.border-solid {
-    border-style: solid;
-}
-
-.semi-circular {
-    border-radius: 20px;
-    -gtk-outline-radius: 20px;
-}
-
-.semi-circular:hover {
-    border-top-width: 0px;
-    border-bottom-width: 0px;
-    border-left-width: 0px;
-    border-right-width: 0px;
-}
-
-.pill {
-    border-radius: 9999px;
-    -gtk-outline-radius: 9999px;
-}
 
 .smooth-scale {
     padding-top: 0px;
     padding-bottom: 0px;
 }
 
-/* FIXME: Remove once songsview is ported to the new style */
-.songs-list-old {
-    box-shadow: inset 0 -1px shade(@borders, 1.30);
-    background-color: @theme_bg_color;
-}
-
-.songs-list-old:selected {
-    color: @theme_fg_color;
-    border-color: mix(@theme_fg_color, @theme_bg_color, 0.5);
-}
-
 /* We use background-image as a workaround on the StarImage widget to
    enable transitions between the non-starred and starred icons. */
 .star {
@@ -80,8 +49,6 @@
     transition: 100ms linear;
 }
 
-.content-view { background-color: @theme_bg; }
-
 .grey-image {
     color: alpha(@theme_fg_color, 0.3);
 }
@@ -96,16 +63,6 @@
   font-weight: bold;
 }
 
-/* Lists style */
-
-/* workaround to avoid a black background issue
-in AlbumWidget and PlaylistsView
-https://gitlab.gnome.org/GNOME/gtk/issues/694
-*/
-list {
-    background-color: transparent;
-}
-
 .playlistdialog-row:selected {
     color: @theme_fg_color;
     background-color: @theme_insensitive_bg_color;
@@ -134,8 +91,27 @@ list {
     font-weight: bold;
 }
 
-/* SongWidget */
+/* SongsView */
+/* boxed-list style does not work for
+GtkListView at the moment */
+.songs-list {
+    border-left: 1px solid @borders;
+    border-right: 1px solid @borders;
+    border-top: 1px solid @borders;
+}
+
+.songs-list:first-child {
+    border-top-left-radius: 12px;
+    border-top-right-radius: 12px;
+}
+
+.songs-list:last-child {
+    border-bottom-left-radius: 12px;
+    border-bottom-right-radius: 12px;
+}
 
-.songwidget {
+/* SongWidget and SongsView rows*/
+.songwidget,
+.songs-list row {
   padding: 12px;
 }
diff --git a/data/org.gnome.Music.gresource.xml b/data/org.gnome.Music.gresource.xml
index a04c6bee6..d7f26d177 100644
--- a/data/org.gnome.Music.gresource.xml
+++ b/data/org.gnome.Music.gresource.xml
@@ -3,11 +3,10 @@
   <gresource prefix="/org/gnome/Music">
     <file alias="gtk/help-overlay.ui" preprocess="xml-stripblanks">ui/help-overlay.ui</file>
     <file>org.gnome.Music.css</file>
-    <file>icons/music-playlist-symbolic.svg</file>
-    <file>icons/music-artist-symbolic.svg</file>
     <file>icons/welcome-music.svg</file>
     <file preprocess="xml-stripblanks">ui/AboutDialog.ui</file>
     <file preprocess="xml-stripblanks">ui/AlbumCover.ui</file>
+    <file preprocess="xml-stripblanks">ui/AlbumCoverListItem.ui</file>
     <file preprocess="xml-stripblanks">ui/AlbumWidget.ui</file>
     <file preprocess="xml-stripblanks">ui/AlbumsView.ui</file>
     <file preprocess="xml-stripblanks">ui/ArtistAlbumsWidget.ui</file>
@@ -33,10 +32,15 @@
     <file preprocess="xml-stripblanks">ui/SearchView.ui</file>
     <file preprocess="xml-stripblanks">ui/SelectionBarMenuButton.ui</file>
     <file preprocess="xml-stripblanks">ui/SelectionToolbar.ui</file>
+    <file preprocess="xml-stripblanks">ui/SongListItem.ui</file>
     <file preprocess="xml-stripblanks">ui/SongsView.ui</file>
     <file preprocess="xml-stripblanks">ui/SongWidget.ui</file>
     <file preprocess="xml-stripblanks">ui/SongWidgetMenu.ui</file>
     <file preprocess="xml-stripblanks">ui/TwoLineTip.ui</file>
     <file preprocess="xml-stripblanks">ui/Window.ui</file>
   </gresource>
+  <gresource prefix="/org/gnome/Music/icons/scalable/actions">
+    <file alias="music-playlist-symbolic.svg">icons/music-playlist-symbolic.svg</file>
+    <file alias="music-artist-symbolic.svg">icons/music-artist-symbolic.svg</file>
+  </gresource>
 </gresources>
diff --git a/data/org.gnome.Music.gschema.xml b/data/org.gnome.Music.gschema.xml
index 31eed34b4..d92cf2306 100644
--- a/data/org.gnome.Music.gschema.xml
+++ b/data/org.gnome.Music.gschema.xml
@@ -12,11 +12,6 @@
             <summary>Window size</summary>
             <description>Window size (width and height).</description>
         </key>
-        <key type="ai" name="window-position">
-            <default>[]</default>
-            <summary>Window position</summary>
-            <description>Window position (x and y).</description>
-        </key>
         <key type="b" name="window-maximized">
             <default>true</default>
             <summary>Window maximized</summary>
diff --git a/data/ui/AboutDialog.ui.in b/data/ui/AboutDialog.ui.in
index 5c626a1f8..fc7cb60d9 100644
--- a/data/ui/AboutDialog.ui.in
+++ b/data/ui/AboutDialog.ui.in
@@ -2,7 +2,7 @@
 <interface>
   <!-- interface-requires gtk+ 3.0 -->
   <template class="AboutDialog" parent="GtkAboutDialog">
-    <property name="can_focus">False</property>
+    <property name="focusable">False</property>
     <property name="modal">True</property>
     <property name="program_name">@PROGRAM_NAME@</property>
     <property name="version">@PACKAGE_VERSION@</property>
diff --git a/data/ui/AlbumCover.ui b/data/ui/AlbumCover.ui
index a4e1738d7..6ff054e91 100644
--- a/data/ui/AlbumCover.ui
+++ b/data/ui/AlbumCover.ui
@@ -1,24 +1,20 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.18"/>
   <template class="AlbumCover" parent="GtkFlowBoxChild">
     <child>
       <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="has_tooltip">True</property>
         <property name="valign">start</property>
         <property name="orientation">vertical</property>
         <signal name="query-tooltip" handler="_on_tooltip_query"/>
         <child>
           <object class="GtkOverlay">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="margin-bottom">4</property>
             <child>
               <object class="ArtStack" id="_art_stack">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="vexpand">True</property>
                 <property name="valign">end</property>
                 <property name="halign">center</property>
@@ -26,19 +22,20 @@
             </child>
             <child type="overlay">
               <object class="GtkCheckButton" id="_check">
-                <property name="can_focus">True</property>
+                <property name="focusable">True</property>
                 <property name="receives_default">False</property>
                 <property name="halign">end</property>
                 <property name="valign">end</property>
-                <property name="draw_indicator">True</property>
+                <style>
+                  <class name="selection-mode"/>
+                </style>
               </object>
             </child>
           </object>
         </child>
         <child>
           <object class="GtkLabel" id="_title_label">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="justify">center</property>
             <property name="wrap">True</property>
             <property name="ellipsize">middle</property>
@@ -48,8 +45,7 @@
         </child>
         <child>
           <object class="GtkLabel" id="_artist_label">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="ellipsize">middle</property>
             <property name="max_width_chars">20</property>
             <style>
diff --git a/data/ui/AlbumCoverListItem.ui b/data/ui/AlbumCoverListItem.ui
new file mode 100644
index 000000000..95329aabe
--- /dev/null
+++ b/data/ui/AlbumCoverListItem.ui
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk" version="4.0"/>
+  <object class="GtkBox" id="_album_cover">
+    <property name="focusable">False</property>
+    <property name="has_tooltip">True</property>
+    <property name="valign">start</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkOverlay">
+        <child>
+          <object class="ArtStack" id="_art_stack">
+            <property name="focusable">False</property>
+            <property name="vexpand">True</property>
+            <property name="valign">end</property>
+            <property name="halign">center</property>
+          </object>
+        </child>
+        <child type="overlay">
+          <object class="GtkCheckButton" id="_check">
+            <property name="visible">False</property>
+            <property name="halign">end</property>
+            <property name="valign">end</property>
+            <style>
+              <class name="selection-mode"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <property name="justify">center</property>
+        <property name="wrap">True</property>
+        <property name="ellipsize">middle</property>
+        <property name="max_width_chars">20</property>
+        <property name="lines">2</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel" id="_artist_label">
+        <property name="ellipsize">middle</property>
+        <property name="max_width_chars">20</property>
+        <style>
+          <class name="albumcover-artist-label"/>
+          <class name="dim-label"/>
+        </style>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/data/ui/AlbumWidget.ui b/data/ui/AlbumWidget.ui
index 0b4388a89..b795601d4 100644
--- a/data/ui/AlbumWidget.ui
+++ b/data/ui/AlbumWidget.ui
@@ -1,163 +1,132 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
 <interface>
-  <requires lib="gtk+" version="3.12"/>
-  <template class="AlbumWidget" parent="HdyClamp">
-    <property name="margin-bottom">48</property>
-    <property name="margin-top">48</property>
-    <property name="maximum-size">1000</property>
-    <property name="orientation">horizontal</property>
-    <property name="visible">True</property>
+  <template class="AlbumWidget" parent="AdwBin">
     <child>
-      <object class="GtkBox">
-        <property name="halign">fill</property>
-        <property name="orientation">vertical</property>
-        <property name="visible">True</property>
+      <object class="AdwClamp">
+        <property name="margin-bottom">48</property>
+        <property name="margin-top">48</property>
+        <property name="maximum-size">1000</property>
         <child>
-          <object class="GtkBox" id="albumInfo">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+          <object class="GtkBox">
             <property name="halign">fill</property>
-            <property name="hexpand">True</property>
-            <property name="spacing">32</property>
+            <property name="orientation">vertical</property>
             <child>
-              <object class="ArtStack" id="_art_stack">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="halign">center</property>
-                <property name="valign">start</property>
-                <property name="hhomogeneous">False</property>
-                <property name="vhomogeneous">False</property>
-              </object>
-            </child>
-            <child>
-              <object class="GtkBox" id="albumDetails">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="halign">center</property>
-                <property name="valign">start</property>
-                <property name="orientation">vertical</property>
-                <property name="margin-top">18</property>
-                <child>
-                  <object class="GtkLabel" id="_title_label">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="ellipsize">middle</property>
-                    <property name="margin-bottom">18</property>
-                    <style>
-                      <class name="title-1"/>
-                    </style>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="_artist_label">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="ellipsize">middle</property>
-                    <property name="margin-bottom">12</property>
-                    <style>
-                      <class name="title-3"/>
-                    </style>
-                  </object>
-                </child>
+              <object class="GtkBox" id="albumInfo">
+                <property name="halign">fill</property>
+                <property name="hexpand">True</property>
+                <property name="spacing">32</property>
                 <child>
-                  <object class="GtkLabel" id="_released_label">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="use_markup">True</property>
-                    <property name="margin-bottom">12</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
+                  <object class="ArtStack" id="_art_stack">
+                    <property name="halign">center</property>
+                    <property name="valign">start</property>
+                    <property name="hhomogeneous">False</property>
+                    <property name="vhomogeneous">False</property>
                   </object>
                 </child>
                 <child>
-                  <object class="GtkLabel" id="_composer_label">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="ellipsize">end</property>
-                    <property name="margin-bottom">12</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkBox">
-                    <property name="orientation">horizontal</property>
-                    <property name="visible">True</property>
-                    <property name="spacing">12</property>
-                    <property name="margin-top">6</property>
+                  <object class="GtkBox" id="albumDetails">
+                    <property name="halign">center</property>
+                    <property name="valign">start</property>
+                    <property name="orientation">vertical</property>
+                    <property name="margin-top">18</property>
+                    <child>
+                      <object class="GtkLabel" id="_title_label">
+                        <property name="focusable">False</property>
+                        <property name="halign">start</property>
+                        <property name="ellipsize">middle</property>
+                        <property name="margin-bottom">18</property>
+                        <style>
+                          <class name="title-1"/>
+                        </style>
+                      </object>
+                    </child>
                     <child>
-                      <object class="GtkButton" id="_play_button">
-                        <property name="width-request">44</property>
-                        <property name="height-request">44</property>
-                        <property name="visible">True</property>
-                        <property name="can-focus">True</property>
-                        <property name="receives-default">True</property>
-                        <property name="image">_play_image</property>
-                        <property name="always_show_image">True</property>
-                        <property name="tooltip-text" translatable="yes">Play</property>
-                        <property name="valign">center</property>
-                        <signal name="clicked" handler="_on_play_button_clicked" swapped="no"/>
+                      <object class="GtkLabel" id="_artist_label">
+                        <property name="focusable">False</property>
+                        <property name="halign">start</property>
+                        <property name="ellipsize">middle</property>
+                        <property name="margin-bottom">12</property>
                         <style>
-                          <class name="circular"/>
+                          <class name="title-3"/>
                         </style>
                       </object>
                     </child>
                     <child>
-                      <object class="GtkMenuButton" id="_menu_button">
-                        <property name="width-request">44</property>
-                        <property name="height-request">44</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">True</property>
+                      <object class="GtkLabel" id="_released_label">
+                        <property name="focusable">False</property>
                         <property name="halign">start</property>
-                        <property name="valign">center</property>
-                        <property name="focus_on_click">False</property>
-                        <property name="menu-model">album_menu</property>
-                        <property name="direction">none</property>
-                        <property name="use_popover">True</property>
-                        <property name="image">_view_more_image</property>
+                        <property name="use_markup">True</property>
+                        <property name="margin-bottom">12</property>
                         <style>
-                          <class name="image-button"/>
-                          <class name="circular"/>
+                          <class name="dim-label"/>
                         </style>
                       </object>
                     </child>
+                    <child>
+                      <object class="GtkLabel" id="_composer_label">
+                        <property name="focusable">False</property>
+                        <property name="halign">start</property>
+                        <property name="ellipsize">end</property>
+                        <property name="margin-bottom">12</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="orientation">horizontal</property>
+                        <property name="spacing">12</property>
+                        <property name="margin-top">6</property>
+                        <child>
+                          <object class="GtkButton" id="_play_button">
+                            <property name="width-request">44</property>
+                            <property name="height-request">44</property>
+                            <property name="icon-name">media-playback-start-symbolic</property>
+                            <property name="tooltip-text" translatable="yes">Play</property>
+                            <property name="valign">center</property>
+                            <signal name="clicked" handler="_on_play_button_clicked" swapped="no"/>
+                            <style>
+                              <class name="circular"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkMenuButton" id="_menu_button">
+                            <property name="width-request">44</property>
+                            <property name="height-request">44</property>
+                            <property name="halign">start</property>
+                            <property name="valign">center</property>
+                            <property name="focus_on_click">False</property>
+                            <property name="menu-model">album_menu</property>
+                            <property name="direction">none</property>
+                            <property name="icon_name">view-more-symbolic</property>
+                            <style>
+                              <class name="circular"/>
+                            </style>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
                   </object>
                 </child>
               </object>
             </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkListBox" id="_disc_list_box">
-            <property name="can_focus">False</property>
-            <property name="margin-top">48</property>
-            <property name="selection_mode">0</property>
-            <property name="visible">True</property>
+            <child>
+              <object class="GtkListBox" id="_disc_list_box">
+                <property name="focusable">False</property>
+                <property name="margin-top">48</property>
+                <property name="selection_mode">0</property>
+                <style>
+                  <class name="background"/>
+                </style>
+              </object>
+            </child>
           </object>
         </child>
       </object>
     </child>
   </template>
-  <object class="GtkImage" id="_view_more_image">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">view-more-symbolic</property>
-    <property name="icon_size">4</property>
-  </object>
-  <object class="GtkImage" id="_play_image">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">media-playback-start-symbolic</property>
-    <property name="icon_size">4</property>
-  </object>
   <menu id="album_menu">
     <item>
       <attribute name="label" translatable="yes">_Play</attribute>
diff --git a/data/ui/AlbumsView.ui b/data/ui/AlbumsView.ui
index a0832384c..259b519c0 100644
--- a/data/ui/AlbumsView.ui
+++ b/data/ui/AlbumsView.ui
@@ -1,55 +1,38 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.18"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="AlbumsView" parent="GtkStack">
-    <property name="visible">True</property>
     <child>
-      <object class="GtkScrolledWindow" id="_scrolled_window">
-        <property name="visible">True</property>
-        <child>
-          <object class="GtkFlowBox" id="_flowbox">
-            <property name="column_spacing">20</property>
-            <property name="halign">fill</property>
-            <property name="hexpand">True</property>
-            <property name="homogeneous">True</property>
-            <property name="margin-bottom">18</property>
-            <property name="margin-end">18</property>
-            <property name="margin-start">18</property>
-            <property name="margin-top">18</property>
-            <property name="max-children-per-line">20</property>
-            <property name="min-children-per-line">1</property>
-            <property name="row_spacing">12</property>
-            <property name="selection-mode">none</property>
-            <property name="valign">start</property>
-            <property name="visible">True</property>
-            <signal name="selected-children-changed" handler="_on_selected_children_changed" swapped="no"/>
-            <style>
-              <class name="content-view"/>
-            </style>
+      <object class="GtkStackPage">
+        <property name="name">grid</property>
+        <property name="child">
+          <object class="GtkScrolledWindow" id="_scrolled_window">
+            <child>
+              <object class="GtkGridView" id="_gridview">
+                <property name="max-columns">20</property>
+              </object>
+            </child>
           </object>
-        </child>
+        </property>
       </object>
-      <packing>
-        <property name="name">grid</property>
-      </packing>
     </child>
     <child>
-      <object class="GtkScrolledWindow" id="_album_scrolled_window">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <property name="hscrollbar_policy">never</property>
-      </object>
-      <packing>
+      <object class="GtkStackPage">
         <property name="name">widget</property>
-      </packing>
+        <property name="child">
+          <object class="GtkScrolledWindow" id="_album_scrolled_window">
+            <property name="focusable">False</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <property name="hscrollbar_policy">never</property>
+            <child>
+              <object class="GtkViewport">
+                <property name="scroll-to-focus">True</property>
+              </object>
+            </child>
+          </object>
+        </property>
+      </object>
     </child>
   </template>
-  <object class="GtkGestureLongPress" id="_flowbox_long_press">
-    <property name="propagation-phase">capture</property>
-    <property name="widget">_flowbox</property>
-    <signal name="begin" handler="_on_flowbox_press_begin" swapped="no"/>
-    <signal name="cancel" handler="_on_flowbox_press_cancel" swapped="no"/>
-  </object>
 </interface>
diff --git a/data/ui/AppMenu.ui b/data/ui/AppMenu.ui
index 71ae356b9..70f9e7f58 100644
--- a/data/ui/AppMenu.ui
+++ b/data/ui/AppMenu.ui
@@ -1,24 +1,24 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.1 -->
 <interface>
-  <requires lib="gtk+" version="3.20"/>
-  <template class="AppMenu" parent="GtkPopoverMenu">
-    <property name="can_focus">False</property>
+  <template class="AppMenu" parent="GtkPopover">
+    <property name="focusable">False</property>
+    <style>
+      <class name="menu"/>
+    </style>
     <child>
       <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="margin-bottom">6</property>
         <property name="margin-end">6</property>
         <property name="margin-start">6</property>
         <property name="margin-top">6</property>
+        <property name="spacing">6</property>
         <property name="orientation">vertical</property>
         <child>
           <object class="GtkModelButton" id="lastfm_account_button">
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="halign">fill</property>
             <property name="hexpand">False</property>
-            <property name="visible">True</property>
             <property name="action_name">app.lastfm-configure</property>
             <property name="text" translatable="yes">Last.fm Account</property>
           </object>
@@ -26,38 +26,33 @@
         <child>
           <object class="GtkBox" id="_lastfm_box">
             <property name="margin-end">5</property>
-            <property name="margin-start">5</property>
             <property name="visible">False</property>
             <child>
               <object class="GtkLabel">
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="margin-end">12</property>
                 <property name="halign">start</property>
                 <property name="hexpand">False</property>
-                <property name="visible">True</property>
                 <property name="label" translatable="yes">Report Music Listening</property>
               </object>
             </child>
             <child>
               <object class="GtkSwitch" id="_lastfm_switch">
-                <property name="can_focus">True</property>
+                <property name="focusable">True</property>
                 <property name="halign">end</property>
-                <property name="visible">True</property>
               </object>
             </child>
           </object>
         </child>
         <child>
           <object class="GtkSeparator">
-            <property name="visible">True</property>
           </object>
         </child>
         <child>
           <object class="GtkModelButton" id="_keyboard_shortcuts_model_button">
             <property name="halign">fill</property>
             <property name="hexpand">False</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
+            <property name="focusable">True</property>
             <property name="receives_default">True</property>
             <property name="action_name">win.show-help-overlay</property>
             <property name="text" translatable="yes">_Keyboard Shortcuts</property>
@@ -67,8 +62,7 @@
           <object class="GtkModelButton" id="_help_model_button">
             <property name="halign">fill</property>
             <property name="hexpand">False</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
+            <property name="focusable">True</property>
             <property name="receives_default">True</property>
             <property name="action_name">app.help</property>
             <property name="text" translatable="yes">_Help</property>
@@ -78,17 +72,13 @@
           <object class="GtkModelButton" id="_about_model_button">
             <property name="halign">fill</property>
             <property name="hexpand">False</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
+            <property name="focusable">True</property>
             <property name="receives_default">True</property>
             <property name="action_name">app.about</property>
             <property name="text" translatable="yes">_About Music</property>
           </object>
         </child>
       </object>
-      <packing>
-        <property name="submenu">main</property>
-      </packing>
     </child>
   </template>
 </interface>
diff --git a/data/ui/ArtistAlbumsWidget.ui b/data/ui/ArtistAlbumsWidget.ui
index ae6a4cf54..f38c85b91 100644
--- a/data/ui/ArtistAlbumsWidget.ui
+++ b/data/ui/ArtistAlbumsWidget.ui
@@ -1,13 +1,19 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.18"/>
-  <template class="ArtistAlbumsWidget" parent="HdyClamp">
-    <property name="maximum-size">1000</property>
-    <property name="orientation">horizontal</property>
-    <property name="visible">True</property>
+  <requires lib="gtk" version="4.0"/>
+  <template class="ArtistAlbumsWidget" parent="GtkBox">
+    <property name="orientation">vertical</property>
     <child>
-      <object class="GtkListBox" id="_listbox">
-        <property name="visible">True</property>
+      <object class="AdwClamp">
+        <property name="maximum-size">1000</property>
+        <property name="orientation">horizontal</property>
+        <child>
+          <object class="GtkListBox" id="_listbox">
+            <style>
+              <class name="background"/>
+            </style>
+          </object>
+        </child>
       </object>
     </child>
   </template>
diff --git a/data/ui/ArtistSearchTile.ui b/data/ui/ArtistSearchTile.ui
index dac94c566..5d46e8b70 100644
--- a/data/ui/ArtistSearchTile.ui
+++ b/data/ui/ArtistSearchTile.ui
@@ -1,51 +1,39 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.18"/>
   <template class="ArtistSearchTile" parent="GtkFlowBoxChild">
     <child>
       <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="has_tooltip">True</property>
         <property name="valign">start</property>
         <property name="orientation">vertical</property>
         <signal name="query-tooltip" handler="_on_tooltip_query" swapped="no"/>
         <child>
           <object class="GtkOverlay">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="margin-bottom">4</property>
             <child>
-              <object class="GtkEventBox" id="_events">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <signal name="button-release-event" handler="_on_artist_event" swapped="no"/>
+              <object class="ArtStack" id="_art_stack">
+                <property name="hexpand">True</property>
                 <child>
-                  <object class="ArtStack" id="_art_stack">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="vexpand">True</property>
-                    <property name="valign">end</property>
-                    <property name="halign">center</property>
+                  <object class="GtkGestureClick">
+                    <signal name="released" handler="_on_artist_event" swapped="no"/>
                   </object>
                 </child>
               </object>
             </child>
             <child type="overlay">
               <object class="GtkCheckButton" id="_check">
-                <property name="can_focus">True</property>
                 <property name="receives_default">False</property>
                 <property name="halign">end</property>
                 <property name="valign">end</property>
-                <property name="draw_indicator">True</property>
+                <style>
+                  <class name="selection-mode"/>
+                </style>
               </object>
             </child>
           </object>
         </child>
         <child>
           <object class="GtkLabel" id="_artist_label">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="justify">center</property>
             <property name="wrap">True</property>
             <property name="ellipsize">middle</property>
diff --git a/data/ui/ArtistTile.ui b/data/ui/ArtistTile.ui
index c7af54deb..28d4ecad1 100644
--- a/data/ui/ArtistTile.ui
+++ b/data/ui/ArtistTile.ui
@@ -1,22 +1,19 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <template class="ArtistTile" parent="GtkListBoxRow">
-    <property name="can_focus">False</property>
-    <property name="visible">True</property>
+  <template class="ArtistTile" parent="GtkBox">
+    <property name="focusable">False</property>
     <child>
       <object class="GtkBox">
         <property name="orientation">horizontal</property>
-        <property name="visible">True</property>
         <property name="margin-start">10</property>
         <child>
           <object class="ArtStack" id="_art_stack">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
           </object>
         </child>
         <child>
           <object class="GtkLabel" id="_label">
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="ellipsize">end</property>
             <property name="halign">start</property>
             <property name="hexpand">False</property>
@@ -24,7 +21,6 @@
             <property name="margin-end">10</property>
             <property name="margin-start">10</property>
             <property name="margin-top">16</property>
-            <property name="visible">True</property>
           </object>
         </child>
       </object>
diff --git a/data/ui/ArtistsView.ui b/data/ui/ArtistsView.ui
index 2e82fc833..8053288fe 100644
--- a/data/ui/ArtistsView.ui
+++ b/data/ui/ArtistsView.ui
@@ -1,39 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.18"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="ArtistsView" parent="GtkPaned">
-    <property name="visible">True</property>
+    <property name="shrink-start-child">0</property>
     <child>
       <object class="GtkScrolledWindow">
-        <property name="visible">True</property>
         <property name="width_request">220</property>
-        <style>
-          <class name="sidebar"/>
-        </style>
         <child>
-          <object class="GtkListBox" id="_sidebar">
-            <property name="selection_mode">single</property>
-            <property name="visible">True</property>
-            <signal name="row-activated" handler="_on_artist_activated" swapped="no"/>
+          <object class="GtkListView" id="_sidebar">
+            <property name="single-click-activate">True</property>
+            <signal name="activate" handler="_on_artist_activated" swapped="no"/>
+            <style>
+              <class name="navigation-sidebar"/>
+            </style>
           </object>
         </child>
       </object>
-      <packing>
-        <property name="shrink">False</property>
-      </packing>
     </child>
     <child>
-      <object class="GtkScrolledWindow" id="_artist_container">
+      <object class="GtkScrolledWindow" id="_artist_view">
         <property name="hexpand">True</property>
         <property name="vexpand">True</property>
-        <property name="visible">True</property>
-        <child>
-          <object class="GtkStack" id="_artist_view">
-            <property name="transition-type">crossfade</property>
-            <property name="vhomogeneous">False</property>
-            <property name="visible">True</property>
-          </object>
-        </child>
       </object>
     </child>
   </template>
diff --git a/data/ui/DiscBox.ui b/data/ui/DiscBox.ui
index 164d8dcbe..4899ecf88 100644
--- a/data/ui/DiscBox.ui
+++ b/data/ui/DiscBox.ui
@@ -1,23 +1,18 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
 <interface>
-  <requires lib="gtk+" version="3.12"/>
+  <requires lib="gtk" version="4.0"/>
   <template parent="GtkListBoxRow" class="DiscBox">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
+    <property name="focusable">False</property>
     <property name="activatable">False</property>
     <property name="selectable">False</property>
     <child>
       <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="orientation">vertical</property>
         <child>
           <object class="GtkLabel" id="_disc_label">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="halign">fill</property>
-            <!-- <property name="hexpand">True</property> -->
             <property name="xalign">0.0</property>
             <style>
               <class name="disc-label"/>
@@ -26,13 +21,12 @@
         </child>
         <child>
           <object class="GtkListBox" id="_list_box">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="valign">start</property>
             <property name="selection_mode">none</property>
             <signal name="row-activated" handler="_song_activated" swapped="no"/>
             <style>
-              <class name="content"/>
+              <class name="boxed-list"/>
             </style>
           </object>
         </child>
diff --git a/data/ui/DiscListItem.ui b/data/ui/DiscListItem.ui
new file mode 100644
index 000000000..dd3c85f34
--- /dev/null
+++ b/data/ui/DiscListItem.ui
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="4.0"/>
+  <template class="DiscListItem" parent="GtkListItem">
+    <property name="child">
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkLabel">
+            <binding name="label">
+              <lookup name="disc_nr" type="CoreDisc">
+                <lookup name="item">GtkListItem</lookup>
+              </lookup>
+            </binding>
+          </object>
+        </child>
+        <child>
+          <object class="DiscBox" id="_disc_box">
+            <binding name="model">
+              <lookup name="model" type="CoreDisc">
+                <lookup name="item">GtkListItem</lookup>
+              </lookup>
+            </binding>
+          </object>
+        </child>
+      </object>
+    </property>
+  </template>
+</interface>
diff --git a/data/ui/EmptyView.ui b/data/ui/EmptyView.ui
index 2b58c3796..2be102a91 100644
--- a/data/ui/EmptyView.ui
+++ b/data/ui/EmptyView.ui
@@ -1,12 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.0 -->
   <template class="EmptyView" parent="GtkStack">
-    <property name="transition_type">crossfade</property>
     <property name="visible">False</property>
     <child>
-      <object class="HdyStatusPage" id="_status_page">
-        <property name="visible">True</property>
+      <object class="AdwStatusPage" id="_status_page">
         <property name="hexpand">True</property>
         <property name="vexpand">True</property>
         <property name="icon_name">emblem-music-symbolic</property>
@@ -14,39 +11,29 @@
     </child>
   </template>
   <object class="GtkBox" id="_initial_state">
-    <property name="visible">False</property>
-    <property name="can_focus">False</property>
     <property name="valign">start</property>
     <property name="orientation">vertical</property>
     <child>
-      <object class="GtkImage">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="resource">/org/gnome/Music/icons/welcome-music.svg</property>
-        <property name="height-request">300</property>
-        <property name="width-request">400</property>
+      <object class="GtkPicture">
+        <property name="can-shrink">true</property>
+        <property name="keep-aspect-ratio">true</property>
+        <property name="file">resource:///org/gnome/Music/icons/welcome-music.svg</property>
       </object>
     </child>
     <child>
       <object class="GtkLabel" id="_title_label">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="justify">center</property>
         <property name="label" translatable="yes">Welcome to Music</property>
         <style>
-          <class name="title"/>
           <class name="large-title"/>
         </style>
       </object>
     </child>
     <child>
       <object class="GtkLabel" id="_description_label">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="justify">center</property>
         <property name="use-markup">True</property>
         <style>
-          <class name="description"/>
           <class name="body"/>
         </style>
       </object>
diff --git a/data/ui/HeaderBar.ui b/data/ui/HeaderBar.ui
index 4ffd0db56..3f03a3ef7 100644
--- a/data/ui/HeaderBar.ui
+++ b/data/ui/HeaderBar.ui
@@ -1,123 +1,91 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 3.10 -->
-  <template class="HeaderBar" parent="HdyHeaderBar">
-    <property name="visible">True</property>
-    <property name="vexpand">False</property>
-    <style>
-      <class name="titlebar"/>
-    </style>
+  <template class="HeaderBar" parent="AdwBin">
     <child>
-      <object class="GtkMenuButton" id="_menu_button">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="valign">center</property>
-        <property name="sensitive">True</property>
-        <property name="tooltip_text" translatable="yes">Menu</property>
+      <object class="AdwHeaderBar" id="_headerbar">
+        <property name="vexpand">False</property>
         <style>
-          <class name="image-button"/>
+          <class name="titlebar"/>
         </style>
-        <child>
-          <object class="GtkImage" id="_menu_button_image">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+        <child type="end">
+          <object class="GtkMenuButton" id="_menu_button">
+            <property name="focusable">False</property>
+            <property name="valign">center</property>
+            <property name="sensitive">True</property>
             <property name="icon-name">open-menu-symbolic</property>
-            <property name="icon-size">1</property>
+            <property name="tooltip_text" translatable="yes">Menu</property>
           </object>
         </child>
-      </object>
-      <packing>
-        <property name="pack_type">end</property>
-      </packing>
-    </child>
-    <child>
-      <object class="GtkToggleButton" id="_select_button">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="valign">center</property>
-        <property name="sensitive">True</property>
-        <property name="tooltip_text" translatable="yes">Select</property>
-        <style>
-          <class name="image-button"/>
-        </style>
-        <child>
-          <object class="GtkImage" id="_select_button_image">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="icon-name">object-select-symbolic</property>
-            <property name="icon-size">1</property>
+        <child type="end">
+          <object class="GtkToggleButton" id="_select_button">
+            <property name="focusable">False</property>
+            <property name="valign">center</property>
+            <property name="sensitive">True</property>
+            <property name="icon-name">selection-mode-symbolic</property>
+            <property name="tooltip_text" translatable="yes">Select</property>
+          </object>
+        </child>
+        <child type="end">
+          <object class="GtkButton" id="_cancel_button">
+            <property name="visible">False</property>
+            <property name="focusable">False</property>
+            <property name="label" translatable="yes">_Cancel</property>
+            <property name="use_underline">True</property>
+            <property name="valign">center</property>
+            <property name="sensitive">True</property>
+            <signal name="clicked" handler="_on_cancel_button_clicked" swapped="no"/>
+          </object>
+        </child>
+        <child type="end">
+          <object class="GtkToggleButton" id="_search_button">
+            <property name="focusable">False</property>
+            <property name="valign">center</property>
+            <property name="sensitive">True</property>
+            <property name="icon-name">edit-find-symbolic</property>
+            <property name="tooltip_text" translatable="yes">Search</property>
+          </object>
+        </child>
+        <child type="start">
+          <object class="GtkButton" id="_back_button">
+            <property name="focusable">False</property>
+            <property name="valign">center</property>
+            <property name="sensitive">True</property>
+            <property name="icon-name">go-previous-symbolic</property>
+            <property name="tooltip_text" translatable="yes">Back</property>
+            <signal name="clicked" handler="_on_back_button_clicked" swapped="no"/>
           </object>
         </child>
       </object>
-      <packing>
-        <property name="pack_type">end</property>
-      </packing>
-    </child>
-    <child>
-      <object class="GtkButton" id="_cancel_button">
-        <property name="visible">False</property>
-        <property name="can_focus">False</property>
-        <property name="label" translatable="yes">_Cancel</property>
-        <property name="use_underline">True</property>
-        <property name="valign">center</property>
-        <property name="sensitive">True</property>
-        <signal name="clicked" handler="_on_cancel_button_clicked" swapped="no"/>
-        <style>
-          <class name="text-button"/>
-        </style>
-      </object>
-      <packing>
-        <property name="pack_type">end</property>
-      </packing>
     </child>
+  </template>
+  <object class="GtkBox" id="_label_title_box">
+    <property name="orientation">vertical</property>
     <child>
-      <object class="GtkToggleButton" id="_search_button">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="valign">center</property>
-        <property name="sensitive">True</property>
-        <property name="tooltip_text" translatable="yes">Search</property>
+      <object class="GtkLabel" id="_label_title">
+        <property name="single-line-mode">True</property>
+        <property name="ellipsize">end</property>
+        <property name="width-chars">5</property>
+        <property name="vexpand">True</property>
+        <property name="yalign">1.0</property>
         <style>
-          <class name="image-button"/>
+          <class name="title"/>
         </style>
-        <child>
-          <object class="GtkImage" id="_search_button_image">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="icon-name">edit-find-symbolic</property>
-            <property name="icon-size">1</property>
-          </object>
-        </child>
       </object>
-      <packing>
-        <property name="pack_type">end</property>
-      </packing>
     </child>
     <child>
-      <object class="GtkButton" id="_back_button">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="valign">center</property>
-        <property name="sensitive">True</property>
-        <property name="tooltip_text" translatable="yes">Back</property>
-        <signal name="clicked" handler="_on_back_button_clicked" swapped="no"/>
+      <object class="GtkLabel" id="_label_subtitle">
+        <property name="single-line-mode">True</property>
+        <property name="ellipsize">end</property>
+        <property name="width-chars">5</property>
+        <property name="vexpand">True</property>
+        <property name="yalign">0.0</property>
         <style>
-          <class name="image-button"/>
+          <class name="subtitle"/>
         </style>
-        <child>
-          <object class="GtkImage" id="_back_button_image">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="icon-name">go-previous-symbolic</property>
-            <property name="icon-size">1</property>
-          </object>
-        </child>
       </object>
-      <packing>
-        <property name="pack_type">start</property>
-      </packing>
     </child>
-  </template>
+  </object>
   <object class="GtkSizeGroup" id="size1">
     <property name="mode">vertical</property>
     <widgets>
diff --git a/data/ui/InitialState.ui b/data/ui/InitialState.ui
new file mode 100644
index 000000000..1b10fc40b
--- /dev/null
+++ b/data/ui/InitialState.ui
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <object class="InitialState" id="_initial_state">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="valign">start</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkLabel" id="_title_label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="justify">center</property>
+        <property name="text" translatable="yes">Welcome to Music</property>
+        <style>
+          <class name="title"/>
+          <class name="large-title"/>
+        </style>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel" id="_description_label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="justify">center</property>
+        <property name="text" translatable="yes">The contents of your Music Folder will appear 
here</property>
+        <style>
+          <class name="description"/>
+          <class name="body"/>
+        </style>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/data/ui/LastfmDialog.ui b/data/ui/LastfmDialog.ui
index 6096f608d..926c78724 100644
--- a/data/ui/LastfmDialog.ui
+++ b/data/ui/LastfmDialog.ui
@@ -1,15 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <template class="LastfmDialog" parent="GtkDialog">
-    <property name="can_focus">False</property>
+    <property name="focusable">False</property>
     <property name="destroy_with_parent">True</property>
     <property name="modal">True</property>
     <property name="resizable">False</property>
     <property name="title" translatable="yes">Last.fm Account</property>
-    <property name="type_hint">dialog</property>
     <property name="valign">start</property>
-    <property name="window_position">center-on-parent</property>
-    <child internal-child="vbox">
+    <child internal-child="content_area">
       <object class="GtkBox">
         <property name="margin-bottom">16</property>
         <property name="margin-end">16</property>
@@ -17,13 +15,11 @@
         <property name="margin-top">16</property>
         <property name="orientation">vertical</property>
         <property name="valign">start</property>
-        <property name="visible">True</property>
         <child>
           <object class="GtkLabel" id="introduction_label">
             <property name="halign">start</property>
             <property name="label" translatable="yes">Last.fm is a music discovery service that gives you 
personalised recommendations based on the music you listen to.</property>
             <property name="margin-bottom">16</property>
-            <property name="visible">True</property>
             <property name="max_width_chars">60</property>
             <property name="wrap">True</property>
             <property name="xalign">0</property>
@@ -34,7 +30,6 @@
             <property name="halign">start</property>
             <property name="label" translatable="yes">Music Reporting Not Setup</property>
             <property name="margin-bottom">8</property>
-            <property name="visible">True</property>
             <property name="max_width_chars">60</property>
             <property name="wrap">True</property>
             <property name="xalign">0</property>
@@ -48,7 +43,6 @@
             <property name="halign">start</property>
             <property name="label" translatable="yes">Login to your Last.fm account to report your music 
listening.</property>
             <property name="margin-bottom">12</property>
-            <property name="visible">True</property>
             <property name="max_width_chars">60</property>
             <property name="wrap">True</property>
             <property name="xalign">0</property>
@@ -56,17 +50,13 @@
         </child>
         <child>
           <object class="GtkButton" id="_action_button">
+            <signal name="clicked" handler="_on_action_button_clicked" swapped="no"/>
             <property name="halign">start</property>
             <property name="label" translatable="yes">Login</property>
             <property name="margin-bottom">8</property>
-            <property name="visible">True</property>
           </object>
         </child>
       </object>
     </child>
   </template>
-  <object class="GtkGestureMultiPress" id="_action_button_gesture">
-    <property name="widget">_action_button</property>
-    <signal name="released" handler="_on_action_button_clicked" swapped="no"/>
-  </object>
 </interface>
diff --git a/data/ui/LoadingNotification.ui b/data/ui/LoadingNotification.ui
index 3af3a1acb..ae57d2f69 100644
--- a/data/ui/LoadingNotification.ui
+++ b/data/ui/LoadingNotification.ui
@@ -4,13 +4,11 @@
     <property name="column_spacing">18</property>
     <child>
       <object class="GtkSpinner">
-        <property name="visible">True</property>
-        <property name="active">True</property>
+        <property name="spinning">True</property>
       </object>
     </child>
     <child>
       <object class="GtkLabel">
-        <property name="visible">True</property>
         <property name="halign">start</property>
         <property name="hexpand">True</property>
         <property name="label" translatable="yes">Loading</property>
diff --git a/data/ui/NotificationsPopup.ui b/data/ui/NotificationsPopup.ui
index 5215c413c..b34ea3784 100644
--- a/data/ui/NotificationsPopup.ui
+++ b/data/ui/NotificationsPopup.ui
@@ -1,15 +1,12 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <template class="NotificationsPopup" parent="GtkRevealer">
-    <property name="visible">True</property>
     <property name="halign">center</property>
     <property name="valign">start</property>
     <child>
       <object class="GtkFrame">
-        <property name="visible">True</property>
         <child>
           <object class="GtkBox" id="_box">
-            <property name="visible">True</property>
             <property name="orientation">vertical</property>
             <property name="spacing">6</property>
           </object>
diff --git a/data/ui/PlayerToolbar.ui b/data/ui/PlayerToolbar.ui
index 3742d8957..c11ce6c4e 100644
--- a/data/ui/PlayerToolbar.ui
+++ b/data/ui/PlayerToolbar.ui
@@ -1,40 +1,12 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.12"/>
-  <object class="GtkImage" id="next_image">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">media-skip-forward-symbolic</property>
-    <property name="icon_size">1</property>
-  </object>
-  <object class="GtkImage" id="_pause_image">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">media-playback-pause-symbolic</property>
-    <property name="icon_size">3</property>
-    <property name="margin-bottom">4</property>
-    <property name="margin-top">4</property>
-  </object>
-  <object class="GtkImage" id="_play_image">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">media-playback-start-symbolic</property>
-    <property name="icon_size">3</property>
-    <property name="margin-bottom">4</property>
-    <property name="margin-top">4</property>
-  </object>
-  <object class="GtkImage" id="previous_image">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">media-skip-backward-symbolic</property>
-    <property name="icon_size">1</property>
-  </object>
+  <requires lib="gtk" version="4.0"/>
   <template class="PlayerToolbar" parent="GtkActionBar">
-    <property name="can_focus">False</property>
+    <property name="focusable">False</property>
+    <property name="revealed">False</property>
     <child>
       <object class="GtkBox" id="_song_info_box">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="halign">center</property>
         <property name="has_tooltip">True</property>
         <property name="valign">center</property>
@@ -46,14 +18,12 @@
         <signal name="query-tooltip" handler="_on_tooltip_query"/>
         <child>
           <object class="ArtStack" id="_art_stack">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
           </object>
         </child>
         <child>
           <object class="GtkBox" id="nowplaying_labels">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="halign">center</property>
             <property name="valign">center</property>
             <property name="orientation">vertical</property>
@@ -61,8 +31,7 @@
             <property name="spacing">3</property>
             <child>
               <object class="GtkLabel" id="_title_label">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="halign">start</property>
                 <property name="ellipsize">middle</property>
                 <property name="max_width_chars">28</property>
@@ -73,8 +42,7 @@
             </child>
             <child>
               <object class="GtkLabel" id="_artist_label">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="halign">start</property>
                 <property name="ellipsize">middle</property>
                 <property name="max_width_chars">28</property>
@@ -86,89 +54,77 @@
     </child>
     <child type="center">
       <object class="GtkBox" id="_buttons_and_scale">
+        <property name="hexpand">True</property>
         <property name="orientation">vertical</property>
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="margin-bottom">6</property>
         <property name="margin-end">6</property>
         <property name="margin-start">6</property>
         <property name="margin-top">6</property>
         <child>
           <object class="GtkBox" id="buttons">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="halign">center</property>
             <property name="spacing">4</property>
             <child>
               <object class="GtkButton" id="_prev_button">
-                <property name="width_request">42</property>
-                <property name="visible">True</property>
                 <property name="sensitive">False</property>
-                <property name="can_focus">True</property>
+                <property name="focusable">True</property>
                 <property name="receives_default">True</property>
-                <property name="image">previous_image</property>
-                <property name="always_show_image">True</property>
+                <property name="icon_name">media-skip-backward-symbolic</property>
                 <property name="tooltip_text" translatable="yes">Previous</property>
+                <property name="valign">center</property>
                 <signal name="clicked" handler="_on_prev_button_clicked" swapped="no"/>
                 <style>
-                  <class name="flat"/>
-                  <class name="semi-circular"/>
+                  <class name="circular"/>
                 </style>
               </object>
             </child>
             <child>
               <object class="GtkButton" id="_play_button">
-                <property name="width_request">80</property>
-                <property name="visible">True</property>
                 <property name="sensitive">False</property>
-                <property name="can_focus">True</property>
+                <property name="focusable">True</property>
                 <property name="receives_default">True</property>
-                <property name="image">_play_image</property>
-                <property name="always_show_image">True</property>
                 <property name="tooltip_text" translatable="yes">Play</property>
                 <signal name="clicked" handler="_on_play_button_clicked" swapped="no"/>
                 <style>
-                  <class name="border-solid"/>
                   <class name="pill"/>
                 </style>
+                <child>
+                  <object class="GtkImage" id="_play_pause_image">
+                    <property name="icon_name">media-playback-start-symbolic</property>
+                    <property name="icon_size">2</property>
+                  </object>
+                </child>
               </object>
             </child>
             <child>
               <object class="GtkButton" id="_next_button">
-                <property name="width_request">42</property>
-                <property name="visible">True</property>
                 <property name="sensitive">False</property>
-                <property name="can_focus">True</property>
+                <property name="focusable">True</property>
                 <property name="receives_default">True</property>
-                <property name="image">next_image</property>
-                <property name="always_show_image">True</property>
+                <property name="icon_name">media-skip-forward-symbolic</property>
                 <property name="tooltip_text" translatable="yes">Next</property>
+                <property name="valign">center</property>
                 <signal name="clicked" handler="_on_next_button_clicked" swapped="no"/>
                 <style>
-                  <class name="flat"/>
-                  <class name="semi-circular"/>
+                  <class name="circular"/>
                 </style>
               </object>
             </child>
-            <style>
-              <class name="linked"/>
-            </style>
           </object>
         </child>
         <child>
-          <object class="HdyClamp">
+          <object class="AdwClamp">
             <property name="maximum-size">1000</property>
-            <property name="visible">True</property>
             <child>
               <object class="GtkBox" id="scale_and_timer">
-                <property name="visible">True</property>
                 <property name="orientation">horizontal</property>
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="margin_top">12</property>
                 <child>
                   <object class="GtkLabel" id="_progress_time_label">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                    <property name="focusable">False</property>
                     <property name="halign">start</property>
                     <property name="valign">center</property>
                     <property name="label">0∶00</property>
@@ -179,8 +135,7 @@
                 </child>
                 <child>
                   <object class="SmoothScale" id="_progress_scale">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
+                    <property name="focusable">True</property>
                     <property name="valign">center</property>
                     <property name="hexpand">True</property>
                     <property name="draw_value">False</property>
@@ -192,8 +147,7 @@
                 </child>
                 <child>
                   <object class="GtkLabel" id="_duration_label">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                    <property name="focusable">False</property>
                     <property name="halign">start</property>
                     <property name="valign">center</property>
                     <property name="label">0∶00</property>
@@ -208,11 +162,10 @@
         </child>
       </object>
     </child>
-    <child>
+    <child type="end">
       <object class="GtkBox" id="menuBox">
         <property name="height_request">34</property>
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="halign">end</property>
         <property name="valign">center</property>
         <property name="margin-bottom">6</property>
@@ -221,27 +174,22 @@
         <property name="margin-top">6</property>
         <child>
           <object class="GtkMenuButton" id="_repeat_menu_button">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
+            <property name="focusable">True</property>
             <property name="receives_default">True</property>
-            <property name="use_popover">True</property>
             <child>
               <object class="GtkBox" id="replayBox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="spacing">6</property>
                 <child>
                   <object class="GtkImage" id="_repeat_image">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                    <property name="focusable">False</property>
                     <property name="icon_name">media-playlist-consecutive-symbolic</property>
                     <property name="icon_size">1</property>
                   </object>
                 </child>
                 <child>
                   <object class="GtkImage" id="downArrow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                    <property name="focusable">False</property>
                     <property name="icon_name">pan-down-symbolic</property>
                     <property name="icon_size">1</property>
                   </object>
@@ -251,9 +199,6 @@
           </object>
         </child>
       </object>
-      <packing>
-        <property name="pack-type">end</property>
-      </packing>
     </child>
   </template>
 </interface>
diff --git a/data/ui/PlaylistControls.ui b/data/ui/PlaylistControls.ui
index 836241124..04ea750b3 100644
--- a/data/ui/PlaylistControls.ui
+++ b/data/ui/PlaylistControls.ui
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.12 -->
   <menu id="playlistMenu">
     <item>
       <attribute name="label" translatable="yes">_Play</attribute>
@@ -15,30 +14,15 @@
       <attribute name="action">win.playlist_rename</attribute>
     </item>
   </menu>
-  <object class="GtkImage" id="_view_more_image">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">view-more-symbolic</property>
-    <property name="icon_size">4</property>
-  </object>
-  <object class="GtkImage" id="_play_image">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">media-playback-start-symbolic</property>
-    <property name="icon_size">4</property>
-  </object>
   <template class="PlaylistControls" parent="GtkBox">
     <property name="orientation">vertical</property>
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
+    <property name="focusable">False</property>
     <property name="margin-top">30</property>
     <child>
       <object class="GtkStack" id="_name_stack">
-        <property name="visible">True</property>
         <child>
           <object class="GtkLabel" id="_name_label">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="hexpand">True</property>
             <property name="xalign">0</property>
             <property name="label" translatable="yes">Playlist Name</property>
@@ -50,50 +34,49 @@
           </object>
         </child>
         <child>
-          <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="orientation">horizontal</property>
-            <style>
-              <class name="linked"/>
-            </style>
-            <child>
-              <object class="GtkEntry" id="_rename_entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="is_focus">True</property>
-                <property name="has_focus">True</property>
-                <property name="receives_default">True</property>
-                <signal name="activate" handler="_on_playlist_renamed" swapped="no"/>
-                <signal name="changed" handler="_on_rename_entry_changed" swapped="no"/>
-              </object>
-            </child>
-            <child>
-              <object class="GtkButton" id="_rename_done_button">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="has_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="label" translatable="yes">_Done</property>
-                <property name="use_underline">True</property>
-                <property name="sensitive">True</property>
-                <signal name="clicked" handler="_on_playlist_renamed" swapped="no" />
+          <object class="GtkStackPage">
+            <property name="name">renaming_dialog</property>
+            <property name="child">
+              <object class="GtkBox">
+                <property name="orientation">horizontal</property>
                 <style>
-                  <class name="suggested-action"/>
+                  <class name="linked"/>
                 </style>
+                <child>
+                  <object class="GtkEntry" id="_rename_entry">
+                    <child>
+                      <object class="GtkEventControllerKey" id="_rename_controller">
+                        <signal name="key-pressed" handler="_on_rename_entry_key_pressed" swapped="no"/>
+                      </object>
+                    </child>
+                    <property name="focusable">True</property>
+                    <property name="receives_default">True</property>
+                    <signal name="activate" handler="_on_playlist_renamed" swapped="no"/>
+                    <signal name="changed" handler="_on_rename_entry_changed" swapped="no"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton" id="_rename_done_button">
+                    <property name="focusable">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="label" translatable="yes">_Done</property>
+                    <property name="use_underline">True</property>
+                    <property name="sensitive">True</property>
+                    <signal name="clicked" handler="_on_playlist_renamed" swapped="no" />
+                    <style>
+                      <class name="suggested-action"/>
+                    </style>
+                  </object>
+                </child>
               </object>
-            </child>
+            </property>
           </object>
-          <packing>
-            <property name="name">renaming_dialog</property>
-          </packing>
         </child>
       </object>
     </child>
     <child>
       <object class="GtkLabel" id="_songs_count_label">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="halign">start</property>
         <property name="label"></property>
         <property name="margin-bottom">4</property>
@@ -105,7 +88,6 @@
     <child>
       <object class="GtkBox">
         <property name="orientation">horizontal</property>
-        <property name="visible">True</property>
         <property name="spacing">12</property>
         <property name="margin-top">18</property>
         <property name="margin-bottom">8</property>
@@ -113,11 +95,9 @@
           <object class="GtkButton" id="_play_button">
             <property name="height-request">44</property>
             <property name="width-request">44</property>
-            <property name="visible">True</property>
-            <property name="can-focus">True</property>
+            <property name="focusable">True</property>
             <property name="receives-default">True</property>
-            <property name="image">_play_image</property>
-            <property name="always_show_image">True</property>
+            <property name="icon_name">media-playback-start-symbolic</property>
             <property name="tooltip-text" translatable="yes">Play</property>
             <property name="valign">center</property>
             <signal name="clicked" handler="_on_play_button_clicked" swapped="no"/>
@@ -130,18 +110,15 @@
           <object class="GtkMenuButton" id="_menubutton">
             <property name="height-request">44</property>
             <property name="width-request">44</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
+            <property name="focusable">True</property>
             <property name="receives_default">True</property>
             <property name="halign">start</property>
             <property name="valign">center</property>
             <property name="focus_on_click">False</property>
             <property name="menu-model">playlistMenu</property>
             <property name="direction">none</property>
-            <property name="use_popover">True</property>
-            <property name="image">_view_more_image</property>
+            <property name="icon_name">view-more-symbolic</property>
             <style>
-              <class name="image-button"/>
               <class name="circular"/>
             </style>
           </object>
@@ -149,8 +126,4 @@
       </object>
     </child>
   </template>
-  <object class="GtkEventControllerKey" id="_rename_controller">
-    <property name="widget">_rename_entry</property>
-    <signal name="key-pressed" handler="_on_rename_entry_key_pressed" swapped="no"/>
-  </object>
 </interface>
diff --git a/data/ui/PlaylistDialog.ui b/data/ui/PlaylistDialog.ui
index 655322c12..f039604dd 100644
--- a/data/ui/PlaylistDialog.ui
+++ b/data/ui/PlaylistDialog.ui
@@ -1,42 +1,30 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
 <interface>
-  <requires lib="gtk+" version="3.10"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="PlaylistDialog" parent="GtkDialog">
     <property name="width_request">400</property>
     <property name="height_request">500</property>
-    <property name="can_focus">False</property>
+    <property name="focusable">False</property>
     <property name="modal">True</property>
     <property name="destroy_with_parent">True</property>
-    <property name="type_hint">dialog</property>
-    <child internal-child="vbox">
+    <child internal-child="content_area">
       <object class="GtkBox" id="dialog-vbox">
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="orientation">vertical</property>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox">
-            <property name="can_focus">False</property>
-          </object>
-          <packing>
-            <property name="position">0</property>
-          </packing>
-        </child>
         <child>
           <object class="GtkStack" id="_add_playlist_stack">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="hexpand">True</property>
             <property name="vexpand">True</property>
             <property name="transition_duration">250</property>
             <child>
               <object class="GtkBox" id="_empty_box">
                 <property name="visible">False</property>
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="orientation">vertical</property>
                 <child>
                   <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                    <property name="focusable">False</property>
                     <property name="orientation">vertical</property>
                     <property name="valign">fill</property>
                     <property name="vexpand">True</property>
@@ -46,8 +34,7 @@
                     <property name="margin-top">18</property>
                     <child>
                       <object class="GtkImage" id="image">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
+                        <property name="focusable">False</property>
                         <property name="valign">center</property>
                         <property name="pixel_size">100</property>
                         <property name="icon_name">emblem-music-symbolic</property>
@@ -61,8 +48,7 @@
                     </child>
                     <child>
                       <object class="GtkLabel" id="label">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
+                        <property name="focusable">False</property>
                         <property name="label" translatable="yes">Enter a name for your first 
playlist</property>
                         <property name="valign">end</property>
                       </object>
@@ -71,8 +57,7 @@
                       <object class="GtkEntry" id="_first_playlist_entry">
                         <property name="width_request">300</property>
                         <property name="height_request">10</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
+                        <property name="focusable">True</property>
                         <property name="halign">center</property>
                         <property name="margin-bottom">16</property>
                         <property name="margin-end">18</property>
@@ -80,7 +65,11 @@
                         <property name="margin-top">18</property>
                         <signal name="activate" handler="_on_editing_done" swapped="no"/>
                         <signal name="changed" handler="_on_add_playlist_entry_changed" swapped="no"/>
-                        <signal name="focus-in-event" handler="_on_add_playlist_entry_focused" swapped="no"/>
+                        <child>
+                          <object class="GtkEventControllerFocus">
+                            <signal name="enter" handler="_on_add_playlist_entry_focused" swapped="no"/>
+                          </object>
+                        </child>
                       </object>
                     </child>
                     <child>
@@ -89,9 +78,8 @@
                         <property name="use_underline">True</property>
                         <property name="width_request">120</property>
                         <property name="height_request">25</property>
-                        <property name="visible">True</property>
                         <property name="sensitive">False</property>
-                        <property name="can_focus">True</property>
+                        <property name="focusable">True</property>
                         <property name="receives_default">True</property>
                         <property name="halign">center</property>
                         <property name="valign">center</property>
@@ -105,8 +93,8 @@
                   </object>
                 </child>
                 <child>
-                  <object class="GtkButtonBox">
-                    <property name="can_focus">False</property>
+                  <object class="GtkBox">
+                    <property name="focusable">False</property>
                   </object>
                 </child>
               </object>
@@ -114,63 +102,65 @@
             <child>
               <object class="GtkBox" id="_normal_box">
                 <property name="visible">False</property>
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="orientation">vertical</property>
                 <child>
                   <object class="GtkScrolledWindow" id="scrolledwindow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
+                    <property name="focusable">True</property>
                     <property name="vexpand">True</property>
                     <child>
-                      <object class="GtkListBox" id="_listbox">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="selection_mode">single</property>
-                        <property name="valign">start</property>
-                        <signal name="selected-rows-changed" handler="_on_selected_rows_changed" 
swapped="no"/>
+                      <object class="GtkViewport">
+                        <property name="scroll-to-focus">True</property>
+                        <child>
+                          <object class="GtkListBox" id="_listbox">
+                            <property name="focusable">True</property>
+                            <property name="selection_mode">single</property>
+                            <property name="valign">start</property>
+                            <signal name="selected-rows-changed" handler="_on_selected_rows_changed" 
swapped="no"/>
+                          </object>
+                        </child>
                       </object>
                     </child>
                   </object>
                 </child>
                 <child>
                   <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                    <property name="focusable">False</property>
                     <property name="orientation">vertical</property>
                     <child>
                       <object class="GtkSeparator">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
+                        <property name="focusable">False</property>
                       </object>
                     </child>
                     <child>
                       <object class="GtkBox" id="new-playlist-hbox">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
+                        <property name="focusable">False</property>
                         <property name="margin-bottom">6</property>
                         <property name="margin-end">6</property>
                         <property name="margin-start">6</property>
                         <property name="margin-top">6</property>
                         <child>
                           <object class="GtkEntry" id="_new_playlist_entry">
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
+                            <property name="focusable">True</property>
                             <property name="hexpand">True</property>
                             <property name="placeholder_text" translatable="yes">New Playlist…</property>
                             <signal name="activate" handler="_on_editing_done" swapped="no"/>
                             <signal name="changed" handler="_on_add_playlist_entry_changed" swapped="no"/>
-                            <signal name="focus-in-event" handler="_on_add_playlist_entry_focused" 
swapped="no"/>
                             <style>
                               <class name="linked"/>
                             </style>
+                            <child>
+                              <object class="GtkEventControllerFocus">
+                                <signal name="enter" handler="_on_add_playlist_entry_focused" swapped="no"/>
+                              </object>
+                            </child>
                           </object>
                         </child>
                         <child>
                           <object class="GtkButton" id="_new_playlist_button">
                             <property name="label" translatable="yes">Add</property>
-                            <property name="visible">True</property>
                             <property name="sensitive">False</property>
-                            <property name="can_focus">True</property>
+                            <property name="focusable">True</property>
                             <property name="receives_default">False</property>
                             <signal name="clicked" handler="_on_editing_done" swapped="no"/>
                             <style>
@@ -185,18 +175,11 @@
                       </object>
                     </child>
                   </object>
-                  <packing>
-                    <property name="pack_type">end</property>
-                    <property name="position">0</property>
-                  </packing>
                 </child>
                 <child>
-                  <object class="GtkButtonBox">
-                    <property name="can_focus">False</property>
+                  <object class="GtkBox">
+                    <property name="focusable">False</property>
                   </object>
-                  <packing>
-                    <property name="position">2</property>
-                  </packing>
                 </child>
               </object>
             </child>
@@ -204,41 +187,46 @@
         </child>
       </object>
     </child>
+    <child internal-child="action_area">
+      <object class="GtkBox">
+        <property name="focusable">False</property>
+      </object>
+    </child>
   </template>
   <object class="GtkHeaderBar" id="_title_bar">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="title" translatable="yes">Add to Playlist</property>
+    <property name="focusable">False</property>
+    <property name="title-widget">
+      <object class="GtkLabel">
+        <property name="label" translatable="yes">Add to Playlist</property>
+        <property name="single-line-mode">True</property>
+        <property name="ellipsize">end</property>
+        <property name="width-chars">5</property>
+        <style>
+          <class name="title"/>
+        </style>
+      </object>
+    </property>
     <child>
       <object class="GtkButton" id="_cancel_button">
         <property name="label" translatable="yes">_Cancel</property>
         <property name="use_underline">True</property>
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="receives_default">False</property>
         <signal name="clicked" handler="_on_cancel_button_clicked" swapped="no"/>
-        <style>
-          <class name="text-button"/>
-        </style>
       </object>
     </child>
-    <child>
+    <child type="end">
       <object class="GtkButton" id="_select_button">
         <property name="label" translatable="yes">_Add</property>
         <property name="sensitive">False</property>
         <property name="use_underline">True</property>
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="receives_default">False</property>
         <signal name="clicked" handler="_on_selection" swapped="no"/>
         <style>
           <class name="suggested-action"/>
-          <class name="text-button"/>
         </style>
       </object>
-      <packing>
-        <property name="pack_type">end</property>
-      </packing>
     </child>
   </object>
 </interface>
diff --git a/data/ui/PlaylistDialogRow.ui b/data/ui/PlaylistDialogRow.ui
index 0810e0f6e..c9c961755 100644
--- a/data/ui/PlaylistDialogRow.ui
+++ b/data/ui/PlaylistDialogRow.ui
@@ -1,13 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <template class="PlaylistDialogRow" parent="GtkListBoxRow">
-    <property name="visible">True</property>
     <style>
       <class name="playlistdialog-row"/>
     </style>
     <child>
       <object class="GtkBox" id="hbox">
-        <property name="visible">True</property>
         <child>
           <object class="GtkLabel" id="_label">
             <property name="ellipsize">end</property>
@@ -15,14 +13,13 @@
             <property name="margin-end">8</property>
             <property name="margin-start">8</property>
             <property name="margin-top">8</property>
-            <property name="visible">True</property>
             <property name="xalign">0.0</property>
           </object>
         </child>
         <child>
           <object class="GtkImage" id="_selection_icon">
-            <property name="icon-name">object-select-symbolic</property>
-            <property name="icon-size">2</property>
+            <property name="icon-name">selection-mode-symbolic</property>
+            <property name="icon-size">1</property>
           </object>
         </child>
       </object>
diff --git a/data/ui/PlaylistNotification.ui b/data/ui/PlaylistNotification.ui
index b10ee723e..c1ae97c6a 100644
--- a/data/ui/PlaylistNotification.ui
+++ b/data/ui/PlaylistNotification.ui
@@ -1,17 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <object class="GtkImage" id="close_notification_window">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">window-close-symbolic</property>
-    <property name="icon_size">1</property>
-  </object>
   <template class="PlaylistNotification" parent="GtkGrid">
-    <property name="visible">True</property>
     <property name="column_spacing">18</property>
     <child>
       <object class="GtkLabel" id="_label">
-        <property name="visible">True</property>
         <property name="halign">start</property>
         <property name="hexpand">True</property>
         <property name="label" translatable="yes"></property>
@@ -21,17 +13,12 @@
       <object class="GtkButton">
         <property name="label" translatable="yes">_Undo</property>
         <property name="use_underline">True</property>
-        <property name="visible">True</property>
         <signal name="clicked" handler="_undo_deletion" swapped="no"/>
       </object>
     </child>
     <child>
       <object class="GtkButton">
-        <property name="always_show_image">True</property>
-        <property name="image">close_notification_window</property>
-        <property name="image_position">right</property>
-        <property name="relief">none</property>
-        <property name="visible">True</property>
+        <property name="icon-name">window-close-symbolic</property>
         <signal name="clicked" handler="_close_notification" swapped="no"/>
       </object>
     </child>
diff --git a/data/ui/PlaylistTile.ui b/data/ui/PlaylistTile.ui
index 16fb590e0..2375857f0 100644
--- a/data/ui/PlaylistTile.ui
+++ b/data/ui/PlaylistTile.ui
@@ -1,17 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <template class="PlaylistTile" parent="GtkListBoxRow">
-    <property name="visible">True</property>
     <child>
       <object class="GtkBox">
         <property name="orientation">horizontal</property>
-        <property name="visible">True</property>
         <property name="margin-start">10</property>
         <child>
           <object class="GtkImage" id="_icon">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="icon_size">4</property>
+            <property name="focusable">False</property>
+            <property name="icon-size">normal</property>
             <property name="valign">center</property>
             <style>
               <class name="playlist-icon"/>
@@ -20,7 +17,7 @@
         </child>
         <child>
           <object class="GtkLabel" id="_label">
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="ellipsize">end</property>
             <property name="halign">start</property>
             <property name="hexpand">False</property>
@@ -28,7 +25,6 @@
             <property name="margin-end">10</property>
             <property name="margin-start">10</property>
             <property name="margin-top">16</property>
-            <property name="visible">True</property>
           </object>
         </child>
       </object>
diff --git a/data/ui/PlaylistsView.ui b/data/ui/PlaylistsView.ui
index 8cb743e17..a1ac17fc9 100644
--- a/data/ui/PlaylistsView.ui
+++ b/data/ui/PlaylistsView.ui
@@ -1,26 +1,25 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.18"/>
   <template class="PlaylistsView" parent="GtkPaned">
-    <property name="visible">True</property>
+    <property name="shrink-start-child">0</property>
     <child>
       <object class="GtkScrolledWindow">
-        <property name="visible">True</property>
         <property name="width-request">220</property>
-        <style>
-          <class name="sidebar"/>
-        </style>
         <child>
-          <object class="GtkListBox" id="_sidebar">
-            <property name="selection-mode">single</property>
-            <property name="visible">True</property>
-            <signal name="row-activated" handler="_on_playlist_activated" swapped="no"/>
+          <object class="GtkViewport">
+            <property name="scroll-to-focus">True</property>
+            <child>
+              <object class="GtkListBox" id="_sidebar">
+                <property name="selection-mode">single</property>
+                <signal name="row-activated" handler="_on_playlist_activated" swapped="no"/>
+                <style>
+                  <class name="navigation-sidebar"/>
+                </style>
+              </object>
+            </child>
           </object>
         </child>
       </object>
-      <packing>
-        <property name="shrink">False</property>
-      </packing>
     </child>
   </template>
 </interface>
diff --git a/data/ui/PlaylistsWidget.ui b/data/ui/PlaylistsWidget.ui
index 721e89b55..7a9e3cfbf 100644
--- a/data/ui/PlaylistsWidget.ui
+++ b/data/ui/PlaylistsWidget.ui
@@ -2,14 +2,11 @@
 <interface>
   <template class="PlaylistsWidget" parent="GtkBox">
     <property name="orientation">vertical</property>
-    <property name="visible">True</property>
     <child>
-      <object class="HdyClamp">
+      <object class="AdwClamp">
         <property name="maximum-size">1000</property>
-        <property name="visible">True</property>
         <child>
           <object class="PlaylistControls" id="_pl_ctrls">
-            <property name="visible">True</property>
           </object>
         </child>
       </object>
@@ -17,19 +14,17 @@
     <child>
       <object class="GtkScrolledWindow" id="playlist-container">
         <property name="vexpand">True</property>
-        <property name="visible">True</property>
         <child>
-          <object class="HdyClamp">
+          <object class="AdwClamp">
             <property name="maximum-size">1000</property>
-            <property name="visible">True</property>
+            <property name="valign">start</property>
             <child>
               <object class="GtkListBox" id="_songs_list">
                 <property name="margin-bottom">20</property>
                 <property name="margin-top">20</property>
-                <property name="visible">True</property>
                 <signal name="row-activated" handler="_on_song_activated" swapped="no"/>
                 <style>
-                  <class name="content"/>
+                  <class name="boxed-list"/>
                 </style>
               </object>
             </child>
diff --git a/data/ui/SearchHeaderBar.ui b/data/ui/SearchHeaderBar.ui
index 37c70b669..8d26d706a 100644
--- a/data/ui/SearchHeaderBar.ui
+++ b/data/ui/SearchHeaderBar.ui
@@ -1,81 +1,48 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.10 -->
-  <template class="SearchHeaderBar" parent="HdyHeaderBar">
-    <property name="visible">True</property>
-    <property name="vexpand">False</property>
-    <style>
-      <class name="titlebar"/>
-    </style>
+  <template class="SearchHeaderBar" parent="AdwBin">
     <child>
-      <object class="GtkToggleButton" id="_select_button">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="valign">center</property>
-        <property name="sensitive">True</property>
-        <property name="tooltip_text" translatable="yes">Select</property>
+      <object class="AdwHeaderBar" id="_headerbar">
+        <property name="vexpand">False</property>
         <style>
-          <class name="image-button"/>
+          <class name="titlebar"/>
         </style>
-        <child>
-          <object class="GtkImage" id="_select_button_image">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="icon-name">object-select-symbolic</property>
-            <property name="icon-size">1</property>
+        <child type="end">
+          <object class="GtkToggleButton" id="_select_button">
+            <property name="focusable">False</property>
+            <property name="valign">center</property>
+            <property name="sensitive">True</property>
+            <property name="icon-name">selection-mode-symbolic</property>
+            <property name="tooltip_text" translatable="yes">Select</property>
           </object>
         </child>
-      </object>
-      <packing>
-        <property name="pack_type">end</property>
-      </packing>
-    </child>
-    <child>
-      <object class="GtkButton" id="_cancel_button">
-        <property name="visible">False</property>
-        <property name="can_focus">False</property>
-        <property name="label" translatable="yes">_Cancel</property>
-        <property name="use_underline">True</property>
-        <property name="valign">center</property>
-        <property name="sensitive">True</property>
-        <signal name="clicked" handler="_on_cancel_button_clicked" swapped="no"/>
-        <style>
-          <class name="text-button"/>
-        </style>
-      </object>
-      <packing>
-        <property name="pack_type">end</property>
-      </packing>
-    </child>
-    <child>
-      <object class="GtkToggleButton" id="_search_button">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="valign">center</property>
-        <property name="sensitive">True</property>
-        <property name="tooltip_text" translatable="yes">Search</property>
-        <style>
-          <class name="image-button"/>
-        </style>
-        <child>
-          <object class="GtkImage" id="_search_button_image">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+        <child type="end">
+          <object class="GtkButton" id="_cancel_button">
+            <property name="focusable">False</property>
+            <property name="label" translatable="yes">_Cancel</property>
+            <property name="use_underline">True</property>
+            <property name="valign">center</property>
+            <property name="sensitive">True</property>
+            <signal name="clicked" handler="_on_cancel_button_clicked" swapped="no"/>
+          </object>
+        </child>
+        <child type="end">
+          <object class="GtkToggleButton" id="_search_button">
+            <property name="focusable">False</property>
+            <property name="valign">center</property>
+            <property name="sensitive">True</property>
             <property name="icon-name">edit-find-symbolic</property>
-            <property name="icon-size">1</property>
+            <property name="tooltip_text" translatable="yes">Search</property>
           </object>
         </child>
       </object>
-      <packing>
-        <property name="pack_type">end</property>
-      </packing>
     </child>
   </template>
   <object class="GtkSizeGroup" id="size1">
-      <property name="mode">vertical</property>
-      <widgets>
-        <widget name="_search_button"/>
-        <widget name="_cancel_button"/>
-      </widgets>
+    <property name="mode">vertical</property>
+    <widgets>
+      <widget name="_search_button"/>
+      <widget name="_cancel_button"/>
+    </widgets>
   </object>
 </interface>
diff --git a/data/ui/SearchView.ui b/data/ui/SearchView.ui
index d7dc09dd1..8a2af34bd 100644
--- a/data/ui/SearchView.ui
+++ b/data/ui/SearchView.ui
@@ -1,170 +1,138 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <object class="GtkImage" id="view_all_image_artist">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">go-next-symbolic</property>
-    <property name="icon_size">1</property>
-  </object>
-  <object class="GtkImage" id="view_all_image_album">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="icon_name">go-next-symbolic</property>
-    <property name="icon_size">1</property>
-  </object>
   <template class="SearchView" parent="GtkStack">
-    <property name="visible">True</property>
     <child>
       <object class="GtkScrolledWindow" id="_search_results">
         <property name="hexpand">True</property>
         <property name="vexpand">True</property>
-        <property name="visible">True</property>
         <child>
-          <object class="HdyClamp">
-            <property name="maximum-size">1600</property>
-            <property name="visible">True</property>
+          <object class="GtkViewport">
+            <property name="scroll-to-focus">True</property>
             <child>
-              <object class="GtkBox" id="container">
-                <property name="halign">fill</property>
-                <property name="hexpand">True</property>
-                <property name="margin-bottom">20</property>
-                <property name="margin-end">120</property>
-                <property name="margin-start">120</property>
-                <property name="margin-top">20</property>
-                <property name="orientation">vertical</property>
-                <property name="visible">True</property>
+              <object class="AdwClamp">
+                <property name="maximum-size">1600</property>
                 <child>
-                  <object class="GtkBox" id="_artist_header">
+                  <object class="GtkBox" id="container">
                     <property name="halign">fill</property>
                     <property name="hexpand">True</property>
-                    <property name="homogeneous">True</property>
-                    <property name="orientation">horizontal</property>
-                    <property name="visible">True</property>
+                    <property name="margin-bottom">20</property>
+                    <property name="margin-end">120</property>
+                    <property name="margin-start">120</property>
+                    <property name="margin-top">20</property>
+                    <property name="orientation">vertical</property>
                     <child>
-                      <object class="GtkLabel">
-                        <property name="can_focus">False</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">Artists</property>
-                        <property name="visible">True</property>
-                        <style>
-                          <class name="search-header"/>
-                        </style>
+                      <object class="GtkBox" id="_artist_header">
+                        <property name="halign">fill</property>
+                        <property name="hexpand">True</property>
+                        <property name="homogeneous">True</property>
+                        <property name="orientation">horizontal</property>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="focusable">False</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes">Artists</property>
+                            <style>
+                              <class name="search-header"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="_view_all_artists">
+                            <property name="halign">end</property>
+                            <property name="label" translatable="yes">View All</property>
+                            <property name="icon-name">go-next-symbolic</property>
+                            <signal name="clicked" handler="_on_all_artists_clicked" swapped="no"/>
+                          </object>
+                        </child>
                       </object>
                     </child>
                     <child>
-                      <object class="GtkButton" id="_view_all_artists">
-                        <property name="halign">end</property>
-                        <property name="label" translatable="yes">View All</property>
-                        <property name="always_show_image">True</property>
-                        <property name="image">view_all_image_artist</property>
-                        <property name="image_position">right</property>
-                        <property name="visible">True</property>
-                        <signal name="button-release-event" handler="_on_all_artists_clicked" swapped="no"/>
+                      <object class="GtkFlowBox" id="_artist_flowbox">
+                        <property name="column_spacing">6</property>
+                        <property name="halign">fill</property>
+                        <property name="hexpand">True</property>
+                        <property name="homogeneous">True</property>
+                        <property name="margin-bottom">18</property>
+                        <property name="margin-top">18</property>
+                        <property name="max-children-per-line">6</property>
+                        <property name="min-children-per-line">1</property>
+                        <property name="row_spacing">12</property>
+                        <property name="selection-mode">none</property>
+                        <property name="valign">start</property>
+                        <signal name="child-activated" handler="_on_artist_activated" swapped="no"/>
                       </object>
                     </child>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkFlowBox" id="_artist_flowbox">
-                    <property name="column_spacing">6</property>
-                    <property name="halign">fill</property>
-                    <property name="hexpand">True</property>
-                    <property name="homogeneous">True</property>
-                    <property name="margin-bottom">18</property>
-                    <property name="margin-top">18</property>
-                    <property name="max-children-per-line">6</property>
-                    <property name="min-children-per-line">1</property>
-                    <property name="row_spacing">12</property>
-                    <property name="selection-mode">none</property>
-                    <property name="valign">start</property>
-                    <property name="visible">True</property>
-                    <signal name="child-activated" handler="_on_artist_activated" swapped="no"/>
-                    <style>
-                      <class name="content-view"/>
-                    </style>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkBox" id="_album_header">
-                    <property name="halign">fill</property>
-                    <property name="hexpand">True</property>
-                    <property name="homogeneous">True</property>
-                    <property name="orientation">horizontal</property>
-                    <property name="visible">True</property>
                     <child>
-                      <object class="GtkLabel">
-                        <property name="can_focus">False</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">Albums</property>
-                        <property name="visible">True</property>
-                        <style>
-                          <class name="search-header"/>
-                        </style>
+                      <object class="GtkBox" id="_album_header">
+                        <property name="halign">fill</property>
+                        <property name="hexpand">True</property>
+                        <property name="homogeneous">True</property>
+                        <property name="orientation">horizontal</property>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="focusable">False</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes">Albums</property>
+                            <style>
+                              <class name="search-header"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="_view_all_albums">
+                            <property name="halign">end</property>
+                            <property name="label" translatable="yes">View All</property>
+                            <property name="icon_name">go-next-symbolic</property>
+                            <signal name="clicked" handler="_on_all_albums_clicked" swapped="no"/>
+                          </object>
+                        </child>
                       </object>
                     </child>
                     <child>
-                      <object class="GtkButton" id="_view_all_albums">
-                        <property name="halign">end</property>
-                        <property name="label" translatable="yes">View All</property>
-                        <property name="always_show_image">True</property>
-                        <property name="image">view_all_image_album</property>
-                        <property name="image_position">right</property>
-                        <property name="visible">True</property>
-                        <signal name="button-release-event" handler="_on_all_albums_clicked" swapped="no"/>
+                      <object class="GtkFlowBox" id="_album_flowbox">
+                        <property name="halign">fill</property>
+                        <property name="hexpand">True</property>
+                        <property name="valign">start</property>
+                        <property name="homogeneous">True</property>
+                        <property name="min_children_per_line">1</property>
+                        <property name="max_children_per_line">6</property>
+                        <property name="margin-bottom">18</property>
+                        <property name="margin-top">18</property>
+                        <property name="row_spacing">12</property>
+                        <property name="column_spacing">6</property>
+                        <property name="selection_mode">none</property>
+                        <signal name="child-activated" handler="_on_album_activated" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="_songs_header">
+                        <property name="halign">fill</property>
+                        <property name="hexpand">True</property>
+                        <property name="homogeneous">True</property>
+                        <property name="orientation">horizontal</property>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="focusable">False</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes">Songs</property>
+                            <style>
+                              <class name="search-header"/>
+                            </style>
+                          </object>
+                        </child>
                       </object>
                     </child>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkFlowBox" id="_album_flowbox">
-                    <property name="halign">fill</property>
-                    <property name="hexpand">True</property>
-                    <property name="valign">start</property>
-                    <property name="homogeneous">True</property>
-                    <property name="min_children_per_line">1</property>
-                    <property name="max_children_per_line">6</property>
-                    <property name="margin-bottom">18</property>
-                    <property name="margin-top">18</property>
-                    <property name="row_spacing">12</property>
-                    <property name="column_spacing">6</property>
-                    <property name="selection_mode">none</property>
-                    <property name="visible">True</property>
-                    <signal name="child-activated" handler="_on_album_activated" swapped="no"/>
-                    <style>
-                      <class name="content-view"/>
-                    </style>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkBox" id="_songs_header">
-                    <property name="halign">fill</property>
-                    <property name="hexpand">True</property>
-                    <property name="homogeneous">True</property>
-                    <property name="orientation">horizontal</property>
-                    <property name="visible">True</property>
                     <child>
-                      <object class="GtkLabel">
-                        <property name="can_focus">False</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">Songs</property>
-                        <property name="visible">True</property>
+                      <object class="GtkListBox" id="_songs_listbox">
+                        <property name="margin-top">20</property>
+                        <signal name="row-activated" handler="_song_activated" swapped="no"/>
                         <style>
-                          <class name="search-header"/>
+                          <class name="boxed-list"/>
                         </style>
                       </object>
                     </child>
                   </object>
                 </child>
-                <child>
-                  <object class="GtkListBox" id="_songs_listbox">
-                    <property name="margin-top">20</property>
-                    <property name="visible">True</property>
-                    <signal name="row-activated" handler="_song_activated" swapped="no"/>
-                    <style>
-                      <class name="content"/>
-                    </style>
-                  </object>
-                </child>
               </object>
             </child>
           </object>
@@ -175,58 +143,53 @@
       <object class="GtkScrolledWindow" id="_all_search_results">
         <property name="hexpand">True</property>
         <property name="vexpand">True</property>
-        <property name="visible">True</property>
         <child>
-          <object class="HdyClamp">
-            <property name="maximum-size">1600</property>
-            <property name="visible">True</property>
+          <object class="GtkViewport">
+            <property name="scroll-to-focus">True</property>
             <child>
-              <object class="GtkBox">
-                <property name="halign">fill</property>
-                <property name="hexpand">True</property>
-                <property name="margin-bottom">20</property>
-                <property name="margin-end">120</property>
-                <property name="margin-start">120</property>
-                <property name="margin-top">20</property>
-                <property name="orientation">vertical</property>
-                <property name="visible">True</property>
+              <object class="AdwClamp">
+                <property name="maximum-size">1600</property>
                 <child>
-                  <object class="GtkFlowBox" id="_artist_all_flowbox">
-                    <property name="column_spacing">6</property>
+                  <object class="GtkBox">
                     <property name="halign">fill</property>
                     <property name="hexpand">True</property>
-                    <property name="homogeneous">True</property>
-                    <property name="margin-bottom">18</property>
-                    <property name="margin-top">18</property>
-                    <property name="max-children-per-line">6</property>
-                    <property name="min-children-per-line">1</property>
-                    <property name="row_spacing">12</property>
-                    <property name="selection-mode">none</property>
-                    <property name="valign">start</property>
-                    <signal name="child-activated" handler="_on_artist_activated" swapped="no"/>
-                    <style>
-                      <class name="content-view"/>
-                    </style>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkFlowBox" id="_album_all_flowbox">
-                    <property name="halign">fill</property>
-                    <property name="hexpand">True</property>
-                    <property name="valign">start</property>
-                    <property name="homogeneous">True</property>
-                    <property name="min_children_per_line">1</property>
-                    <property name="max_children_per_line">6</property>
-                    <property name="margin-bottom">18</property>
-                    <property name="margin-top">18</property>
-                    <property name="row_spacing">12</property>
-                    <property name="column_spacing">6</property>
-                    <property name="selection_mode">none</property>
-                    <property name="visible">True</property>
-                    <signal name="child-activated" handler="_on_album_activated" swapped="no"/>
-                    <style>
-                      <class name="content-view"/>
-                    </style>
+                    <property name="margin-bottom">20</property>
+                    <property name="margin-end">120</property>
+                    <property name="margin-start">120</property>
+                    <property name="margin-top">20</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkFlowBox" id="_artist_all_flowbox">
+                        <property name="column_spacing">6</property>
+                        <property name="halign">fill</property>
+                        <property name="hexpand">True</property>
+                        <property name="homogeneous">True</property>
+                        <property name="margin-bottom">18</property>
+                        <property name="margin-top">18</property>
+                        <property name="max-children-per-line">6</property>
+                        <property name="min-children-per-line">1</property>
+                        <property name="row_spacing">12</property>
+                        <property name="selection-mode">none</property>
+                        <property name="valign">start</property>
+                        <signal name="child-activated" handler="_on_artist_activated" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkFlowBox" id="_album_all_flowbox">
+                        <property name="halign">fill</property>
+                        <property name="hexpand">True</property>
+                        <property name="valign">start</property>
+                        <property name="homogeneous">True</property>
+                        <property name="min_children_per_line">1</property>
+                        <property name="max_children_per_line">6</property>
+                        <property name="margin-bottom">18</property>
+                        <property name="margin-top">18</property>
+                        <property name="row_spacing">12</property>
+                        <property name="column_spacing">6</property>
+                        <property name="selection_mode">none</property>
+                        <signal name="child-activated" handler="_on_album_activated" swapped="no"/>
+                      </object>
+                    </child>
                   </object>
                 </child>
               </object>
@@ -237,8 +200,12 @@
     </child>
     <child>
       <object class="GtkScrolledWindow" id="_scrolled_album_widget">
-        <property name="visible">True</property>
         <property name="hscrollbar_policy">never</property>
+        <child>
+          <object class="GtkViewport">
+            <property name="scroll-to-focus">True</property>
+          </object>
+        </child>
       </object>
     </child>
   </template>
diff --git a/data/ui/SelectionBarMenuButton.ui b/data/ui/SelectionBarMenuButton.ui
index 25c3c529c..464311602 100644
--- a/data/ui/SelectionBarMenuButton.ui
+++ b/data/ui/SelectionBarMenuButton.ui
@@ -15,25 +15,19 @@
   </menu>
   <template class="SelectionBarMenuButton" parent="GtkMenuButton">
     <property name="menu-model">selection-menu</property>
-    <property name="visible">True</property>
-    <property name="can-focus">True</property>
+    <property name="focusable">True</property>
     <child>
       <object class="GtkBox" id="selection-menu-button-box">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
+        <property name="focusable">True</property>
         <property name="orientation">horizontal</property>
         <property name="spacing">6</property>
         <child>
           <object class="GtkLabel" id="_menu_label">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
             <property name="label" translatable="yes">Click on items to select them</property>
           </object>
         </child>
         <child>
           <object class="GtkImage">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
             <property name="icon-name">pan-down-symbolic</property>
           </object>
         </child>
diff --git a/data/ui/SelectionToolbar.ui b/data/ui/SelectionToolbar.ui
index ec64876c1..852b73676 100644
--- a/data/ui/SelectionToolbar.ui
+++ b/data/ui/SelectionToolbar.ui
@@ -1,20 +1,15 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <!-- interface-requires gtk+ 3.12 -->
   <template class="SelectionToolbar" parent="GtkActionBar">
     <property name="visible">False</property>
-    <property name="can_focus">False</property>
+    <property name="focusable">False</property>
     <child>
       <object class="GtkButton" id="_add_to_playlist_button">
         <property name="label" translatable="yes">_Add to Playlist</property>
         <property name="use_underline">True</property>
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
+        <property name="focusable">True</property>
         <property name="receives_default">True</property>
         <signal name="clicked" handler="_on_add_to_playlist_button_clicked" swapped="no"/>
-        <style>
-          <class name="text-button"/>
-        </style>
       </object>
     </child>
   </template>
diff --git a/data/ui/SongListItem.ui b/data/ui/SongListItem.ui
new file mode 100644
index 000000000..3d906d61a
--- /dev/null
+++ b/data/ui/SongListItem.ui
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="4.0"/>
+  <object class="GtkBox" id="_song_box">
+    <property name="focusable">False</property>
+    <property name="valign">start</property>
+    <child>
+      <object class="GtkCheckButton" id="_check">
+        <property name="visible">False</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkBox">
+        <property name="homogeneous">True</property>
+        <child>
+          <object class="GtkLabel" id="_title_label">
+            <property name="halign">start</property>
+            <property name="hexpand">True</property>
+            <property name="ellipsize">end</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="_album_label">
+            <property name="ellipsize">end</property>
+            <property name="halign">start</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="_artist_label">
+            <property name="ellipsize">end</property>
+            <property name="halign">start</property>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel" id="_duration_label">
+        <property name="halign">start</property>
+        <property name="single_line_mode">True</property>
+        <property name="width_chars">5</property>
+        <property name="xalign">1.0</property>
+        <attributes>
+          <attribute name="font-features" value="tnum=1"/>
+        </attributes>
+      </object>
+    </child>
+    <child>
+      <object class="GtkBox" id="_star_box">
+        <property name="focusable">0</property>
+        <property name="halign">end</property>
+        <property name="valign">center</property>
+        <child>
+          <object class="StarImage" id="_star_image">
+            <property name="focusable">False</property>
+            <property name="valign">center</property>
+            <property name="margin-end">12</property>
+            <property name="margin-start">12</property>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkMenuButton" id="_menu_button">
+        <property name="focusable">True</property>
+        <property name="icon-name">view-more-symbolic</property>
+        <style>
+          <class name="flat"/>
+        </style>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/data/ui/SongWidget.ui b/data/ui/SongWidget.ui
index 56708a392..482fd763f 100644
--- a/data/ui/SongWidget.ui
+++ b/data/ui/SongWidget.ui
@@ -1,59 +1,42 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.0 -->
 <interface>
-  <requires lib="gtk+" version="3.10"/>
+  <requires lib="gtk" version="4.0"/>
   <template class="SongWidget" parent="GtkListBoxRow">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
+    <property name="focusable">False</property>
     <property name="selectable">False</property>
-    <signal name="drag_data_received" handler="_on_drag_data_received"/>
     <child>
       <object class="GtkBox" id="box1">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="spacing">6</property>
         <child>
-          <object class="GtkEventBox" id="_dnd_eventbox">
+          <object class="GtkImage" id="_dnd_icon">
             <property name="visible">False</property>
-            <signal name="drag-begin" handler="_on_drag_begin"/>
-            <signal name="drag-end" handler="_on_drag_end"/>
-            <signal name="drag_data_get" handler="_on_drag_data_get"/>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="icon-name">list-drag-handle-symbolic</property>
-                <style>
-                  <class name="drag-handle"/>
-                </style>
-              </object>
-            </child>
+            <property name="icon-name">list-drag-handle-symbolic</property>
+            <style>
+              <class name="drag-handle"/>
+            </style>
           </object>
         </child>
         <child>
           <object class="GtkBox" id="box3">
             <property name="width_request">48</property>
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="homogeneous">True</property>
             <child>
               <object class="GtkImage" id="_play_icon">
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="icon_size">1</property>
               </object>
             </child>
             <child>
               <object class="GtkCheckButton" id="_select_button">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="receives_default">False</property>
-                <property name="draw_indicator">True</property>
-                <signal name="toggled" handler="_on_select_button_toggled"/>
               </object>
             </child>
             <child>
               <object class="GtkLabel" id="_number_label">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="halign">end</property>
                 <property name="justify">right</property>
                 <style>
@@ -65,13 +48,11 @@
         </child>
         <child>
           <object class="GtkBox" id="title_box">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="hexpand">True</property>
             <child>
-              <object class="DzlBoldingLabel" id="_title_label">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
+              <object class="GtkLabel" id="_title_label">
+                <property name="focusable">False</property>
                 <property name="xalign">0</property>
                 <property name="halign">start</property>
                 <property name="hexpand">True</property>
@@ -86,12 +67,11 @@
         <child>
           <object class="GtkBox" id="_artist_box">
             <property name="visible">False</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="hexpand">True</property>
             <child>
               <object class="GtkLabel" id="_artist_label">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="xalign">0</property>
                 <property name="halign">start</property>
                 <property name="hexpand">True</property>
@@ -105,14 +85,13 @@
         </child>
         <child>
           <object class="GtkBox" id="_album_duration_box">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="focusable">False</property>
             <property name="hexpand">True</property>
             <property name="spacing">6</property>
             <child>
               <object class="GtkLabel" id="_album_label">
                 <property name="visible">False</property>
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="xalign">0</property>
                 <property name="halign">start</property>
                 <property name="hexpand">True</property>
@@ -123,8 +102,7 @@
             </child>
             <child>
               <object class="GtkLabel" id="_duration_label">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="focusable">False</property>
                 <property name="halign">end</property>
                 <property name="hexpand">True</property>
                 <property name="single_line_mode">True</property>
@@ -137,59 +115,84 @@
         </child>
         <child>
           <object class="GtkStack" id="_star_stack">
-            <property name="can-focus">False</property>
-            <property name="visible">True</property>
+            <property name="focusable">False</property>
             <child>
-              <object class="GtkEventBox" id="_star_eventbox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="halign">end</property>
-                <property name="valign">center</property>
-                <property name="visible_window">True</property>
-                <signal name="button-release-event" handler="_on_star_toggle" swapped="no"/>
-                <child>
-                  <object class="StarImage" id="_star_image">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+              <object class="GtkStackPage">
+                <property name="name">star</property>
+                <property name="child">
+                  <object class="GtkBox" id="_star_box">
+                    <property name="focusable">0</property>
+                    <property name="halign">end</property>
                     <property name="valign">center</property>
-                    <property name="margin-end">12</property>
-                    <property name="margin-start">12</property>
+                    <child>
+                      <object class="GtkGestureClick">
+                        <property name="button">1</property>
+                        <property name="propagation-phase">capture</property>
+                        <signal name="released" handler="_on_star_toggle" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkEventControllerMotion">
+                        <signal name="enter" handler="_on_star_hover" swapped="no"/>
+                        <signal name="leave" handler="_on_star_unhover" swapped="no"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="StarImage" id="_star_image">
+                        <property name="focusable">False</property>
+                        <property name="valign">center</property>
+                        <property name="margin-end">12</property>
+                        <property name="margin-start">12</property>
+                      </object>
+                    </child>
                   </object>
-                </child>
+                </property>
               </object>
-              <packing>
-                <property name="name">star</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkBox">
-                <property name="visible">True</property>
-              </object>
-              <packing>
+              <object class="GtkStackPage">
                 <property name="name">empty</property>
-                <property name="position">1</property>
-              </packing>
+                <property name="child">
+                  <object class="GtkBox"/>
+                </property>
+              </object>
             </child>
           </object>
         </child>
         <child>
           <object class="GtkMenuButton" id="_menu_button">
             <property name="visible">False</property>
-            <property name="can-focus">True</property>
+            <property name="focusable">True</property>
+            <property name="icon-name">view-more-symbolic</property>
             <style>
               <class name="flat"/>
             </style>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="icon-name">view-more-symbolic</property>
-              </object>
-            </child>
           </object>
         </child>
       </object>
     </child>
+    <child>
+      <object class="GtkGestureClick">
+        <property name="button">1</property>
+        <signal name="released" handler="_on_click" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkDragSource" id="_drag_source">
+        <property name="actions">move</property>
+        <property name="propagation-phase">none</property>
+        <signal name="prepare" handler="_on_drag_prepare" swapped="no"/>
+        <signal name="drag-begin" handler="_on_drag_begin" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkDropTarget">
+        <property name="actions">move</property>
+        <property name="formats">SongWidget</property>
+        <property name="preload">True</property>
+        <signal name="drop" handler="_on_drop" swapped="no"/>
+      </object>
+    </child>
     <style>
       <class name="songwidget"/>
     </style>
@@ -202,10 +205,4 @@
       <widget name="_album_duration_box"/>
     </widgets>
   </object>
-  <object class="GtkEventControllerMotion" id="_controller_motion">
-    <property name="propagation-phase">target</property>
-    <property name="widget">_star_eventbox</property>
-    <signal name="enter" handler="_on_star_hover" swapped="no"/>
-    <signal name="leave" handler="_on_star_unhover" swapped="no"/>
-  </object>
 </interface>
diff --git a/data/ui/SongWidgetMenu.ui b/data/ui/SongWidgetMenu.ui
index 8868176f2..17c7a34a1 100644
--- a/data/ui/SongWidgetMenu.ui
+++ b/data/ui/SongWidgetMenu.ui
@@ -1,49 +1,23 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <template class="SongWidgetMenu" parent="GtkPopoverMenu">
-    <property name="modal">True</property>
+    <property name="autohide">True</property>
     <property name="position">bottom</property>
     <property name="visible">False</property>
-    <child>
-      <object class="GtkBox">
-        <property name="can-focus">False</property>
-        <property name="margin-bottom">6</property>
-        <property name="margin-end">6</property>
-        <property name="margin-start">6</property>
-        <property name="margin-top">6</property>
-        <property name="orientation">vertical</property>
-        <property name="visible">True</property>
-        <child>
-          <object class="GtkModelButton">
-            <property name="visible">True</property>
-            <property name="text" translatable="yes">Play</property>
-            <signal name="clicked" handler="_on_play_clicked" swapped="no"/>
-            <style>
-              <class name="flat"/>
-            </style>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton">
-            <property name="visible">True</property>
-            <property name="text" translatable="yes">Add to Playlist…</property>
-            <signal name="clicked" handler="_on_add_to_playlist_clicked" swapped="no"/>
-            <style>
-              <class name="flat"/>
-            </style>
-          </object>
-        </child>
-        <child>
-          <object class="GtkModelButton" id="_remove_from_playlist_button">
-            <property name="visible">True</property>
-            <property name="text" translatable="yes">Remove From Playlist</property>
-            <signal name="clicked" handler="_on_remove_from_playlist_clicked" swapped="no"/>
-            <style>
-              <class name="flat"/>
-            </style>
-          </object>
-        </child>
-      </object>
-    </child>
+    <property name="menu-model">song-menu</property>
   </template>
+  <menu id="song-menu">
+    <item>
+      <attribute name="label" translatable="yes">_Play</attribute>
+      <attribute name="action">songwidget.play</attribute>
+    </item>
+    <item>
+      <attribute name="label" translatable="yes">_Add to Playlist…</attribute>
+      <attribute name="action">songwidget.add_playlist</attribute>
+    </item>
+    <item>
+      <attribute name="label" translatable="yes">_Remove from Playlist</attribute>
+      <attribute name="action">songwidget.remove_playlist</attribute>
+    </item>
+  </menu>
 </interface>
diff --git a/data/ui/SongsView.ui b/data/ui/SongsView.ui
index 223df3758..714423f8a 100644
--- a/data/ui/SongsView.ui
+++ b/data/ui/SongsView.ui
@@ -1,123 +1,27 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.18"/>
-  <template class="SongsView" parent="GtkScrolledWindow">
-    <property name="hexpand">True</property>
-    <property name="vexpand">True</property>
-    <property name="visible">True</property>
+  <requires lib="gtk" version="4.0"/>
+  <template class="SongsView" parent="GtkBox">
+    <property name="margin-bottom">48</property>
+    <property name="margin-top">48</property>
     <child>
-      <object class="GtkTreeView" id="_songs_view">
-        <property name="activate-on-single-click">True</property>
-        <property name="headers_visible">False</property>
-        <property name="valign">start</property>
-        <property name="visible">True</property>
-        <signal name="row-activated" handler="_on_item_activated" swapped="no"/>
-        <style>
-          <class name="songs-list-old"/>
-        </style>
-        <child internal-child="selection">
-          <object class="GtkTreeSelection">
-            <property name="mode">single</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_now_playing_column">
-            <property name="fixed_width">48</property>
-            <property name="visible">True</property>
-            <child>
-              <object class="GtkCellRendererPixbuf" id="_now_playing_cell">
-                <property name="xalign">0.5</property>
-                <property name="xpad">0</property>
-                <property name="yalign">0.5</property>
-              </object>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_selection_column">
-            <property name="fixed_width">48</property>
-            <property name="visible">False</property>
-            <child>
-              <object class="GtkCellRendererToggle">
-              </object>
-              <attributes>
-                <attribute name="active">1</attribute>
-              </attributes>
-            </child>
-          </object>
-        </child>
+      <object class="AdwClampScrollable" id="_adw_clamp_scrollable">
+        <property name="hexpand">True</property>
+        <property name="vexpand">True</property>
+        <property name="maximum-size">1000</property>
         <child>
-          <object class="GtkTreeViewColumn" id="_title_column">
-            <property name="expand">True</property>
-            <property name="visible">True</property>
+          <object class="GtkScrolledWindow">
             <child>
-              <object class="GtkCellRendererText">
-                <property name="ellipsize">end</property>
-                <property name="height">48</property>
-                <property name="xalign">0</property>
-                <property name="xpad">0</property>
-                <property name="yalign">0.5</property>
+              <object class="GtkListView" id="_listview">
+                <property name="show-separators">True</property>
+                <style>
+                  <class name="songs-list"/>
+                </style>
               </object>
-              <attributes>
-                <attribute name="text">2</attribute>
-              </attributes>
             </child>
           </object>
         </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_artist_column">
-            <property name="expand">True</property>
-            <property name="visible">True</property>
-            <child>
-              <object class="GtkCellRendererText">
-                <property name="ellipsize">end</property>
-                <property name="xpad">32</property>
-              </object>
-              <attributes>
-                <attribute name="text">3</attribute>
-              </attributes>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_album_column">
-            <property name="expand">True</property>
-            <property name="visible">True</property>
-            <child>
-              <object class="GtkCellRendererText">
-                <property name="ellipsize">end</property>
-                <property name="xpad">32</property>
-              </object>
-              <attributes>
-                <attribute name="text">4</attribute>
-              </attributes>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_duration_column">
-            <property name="visible">True</property>
-            <child>
-              <object class="GtkCellRendererText" id="_duration_renderer">
-                <property name="xalign">1</property>
-              </object>
-              <attributes>
-                <attribute name="text">5</attribute>
-              </attributes>
-            </child>
-          </object>
-        </child>
-        <child>
-          <object class="GtkTreeViewColumn" id="_star_column">
-            <property name="visible">True</property>
-          </object>
-        </child>
       </object>
     </child>
   </template>
-  <object class="GtkGestureMultiPress" id="_songs_ctrlr">
-    <property name="widget">_songs_view</property>
-    <property name="propagation-phase">capture</property>
-    <signal name="released" handler="_on_view_clicked" swapped="no"/>
-  </object>
 </interface>
diff --git a/data/ui/TwoLineTip.ui b/data/ui/TwoLineTip.ui
index 54cb04e9e..2ebece590 100644
--- a/data/ui/TwoLineTip.ui
+++ b/data/ui/TwoLineTip.ui
@@ -1,15 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <template class="TwoLineTip" parent="GtkBox">
-    <property name="can_focus">False</property>
+    <property name="focusable">False</property>
     <property name="vexpand">False</property>
     <property name="orientation">vertical</property>
-    <property name="visible">True</property>
     <child>
       <object class="GtkLabel" id="_title_label">
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="halign">start</property>
-        <property name="visible">True</property>
         <style>
           <class name="tooltip-title"/>
         </style>
@@ -17,9 +15,8 @@
     </child>
     <child>
       <object class="GtkLabel" id="_subtitle_label">
-        <property name="can_focus">False</property>
+        <property name="focusable">False</property>
         <property name="halign">start</property>
-        <property name="visible">True</property>
       </object>
     </child>
   </template>
diff --git a/data/ui/Window.ui b/data/ui/Window.ui
index 276952595..92052f1bd 100644
--- a/data/ui/Window.ui
+++ b/data/ui/Window.ui
@@ -1,30 +1,30 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <template class="Window" parent="HdyApplicationWindow">
+  <template class="Window" parent="AdwApplicationWindow">
     <property name="default-height">500</property>
     <property name="default-width">300</property>
+    <child>
+      <object class="GtkEventControllerKey">
+        <property name="propagation-phase">capture</property>
+        <signal name="key-pressed" handler="_on_key_press" swapped="no"/>
+      </object>
+    </child>
     <child>
       <object class="GtkBox">
         <property name="orientation">vertical</property>
-        <property name="visible">True</property>
         <child>
           <object class="GtkStack" id="_headerbar_stack">
             <property name="transition-type">crossfade</property>
-            <property name="visible">True</property>
           </object>
         </child>
         <child>
           <object class="GtkOverlay" id="_overlay">
             <property name="vexpand">True</property>
-            <property name="visible">True</property>
             <child>
-              <object class="GtkStack" id="_stack">
-                <property name="can-focus">False</property>
+              <object class="AdwViewStack" id="_stack">
+                <property name="focusable">False</property>
                 <property name="hhomogeneous">False</property>
                 <property name="vhomogeneous">False</property>
-                <property name="transition-duration">100</property>
-                <property name="transition-type">crossfade</property>
-                <property name="visible">True</property>
               </object>
             </child>
             <child type="overlay">
@@ -45,9 +45,4 @@
       </object>
     </child>
   </template>
-  <object class="GtkEventControllerKey" id="_key_controller">
-    <property name="widget">Window</property>
-    <property name="propagation-phase">capture</property>
-    <signal name="key-pressed" handler="_on_key_press" swapped="no"/>
-  </object>
 </interface>
diff --git a/data/ui/help-overlay.ui b/data/ui/help-overlay.ui
index 86b0f0fc8..f7123b775 100644
--- a/data/ui/help-overlay.ui
+++ b/data/ui/help-overlay.ui
@@ -4,37 +4,31 @@
     <property name="modal">True</property>
     <child>
       <object class="GtkShortcutsSection">
-        <property name="visible">True</property>
         <property name="section-name">shortcuts</property>
         <property name="max-height">17</property>
         <child>
           <object class="GtkShortcutsGroup">
-            <property name="visible">True</property>
             <property name="title" translatable="yes" context="shortcut window">General</property>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Close window</property>
                 <property name="accelerator">&lt;Primary&gt;Q</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Search</property>
                 <property name="accelerator">&lt;Primary&gt;F</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Help</property>
                 <property name="accelerator">F1</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Shortcuts</property>
                 <property name="accelerator">&lt;Primary&gt;question</property>
               </object>
@@ -43,39 +37,33 @@
         </child>
         <child>
           <object class="GtkShortcutsGroup">
-            <property name="visible">True</property>
             <property name="title" translatable="yes" context="shortcut window">Playback</property>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Play/Pause</property>
                 <property name="accelerator">&lt;Ctrl&gt;space</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Next song</property>
                 <property name="accelerator">&lt;Ctrl&gt;N</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Previous song</property>
                 <property name="accelerator">&lt;Ctrl&gt;B</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Toggle repeat</property>
                 <property name="accelerator">&lt;Ctrl&gt;R</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Toggle shuffle</property>
                 <property name="accelerator">&lt;Ctrl&gt;S</property>
               </object>
@@ -84,39 +72,33 @@
         </child>
         <child>
           <object class="GtkShortcutsGroup">
-            <property name="visible">True</property>
             <property name="title" translatable="yes" context="shortcut window">Navigation</property>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Go to Albums</property>
                 <property name="accelerator">&lt;Alt&gt;1</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Go to Artists</property>
                 <property name="accelerator">&lt;Alt&gt;2</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Go to Songs</property>
                 <property name="accelerator">&lt;Alt&gt;3</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Go to 
Playlists</property>
                 <property name="accelerator">&lt;Alt&gt;4</property>
               </object>
             </child>
             <child>
               <object class="GtkShortcutsShortcut">
-                <property name="visible">True</property>
                 <property name="title" translatable="yes" context="shortcut window">Go back</property>
                 <property name="accelerator">&lt;Alt&gt;Left</property>
               </object>
diff --git a/gnome-music.in b/gnome-music.in
index ca1a3ddac..3cc186f00 100755
--- a/gnome-music.in
+++ b/gnome-music.in
@@ -40,30 +40,19 @@ if _LOCAL:
 
 import gi
 
-gi.require_version('Gtk', '3.0')
+gi.require_version("Adw", "1")
+gi.require_version('Gtk', '4.0')
 gi.require_version('GIRepository', '2.0')
 gi.require_version('Gst', '1.0')
-gi.require_version("Handy", "1")
-from gi.repository import GIRepository, Gio, Gtk, Gst, Handy
+from gi.repository import Adw, GIRepository, Gio, Gtk, Gst
 
 Gst.init(None)
-Handy.init()
+Adw.init()
 
 LOCALE_DIR = '@localedir@'
 PKGDATA_DIR = '@pkgdatadir@'
 
 
-def set_gfm():
-    """Configures application to use gfm."""
-    gfm_libdir = '@gfmlibdir@'
-    if _LOCAL:
-        gfm_typelibdir = '@gfmlibdir@'
-    else:
-        gfm_typelibdir = '@gfmlibdir@/girepository-1.0'
-
-    GIRepository.Repository.prepend_search_path(gfm_typelibdir)
-    GIRepository.Repository.prepend_library_path(gfm_libdir)
-
 def set_exception_hook():
     """Configures sys.excepthook to enforce Gtk application exiting."""
 
@@ -110,8 +99,7 @@ def run_application():
 
 def main():
     """Sets environment and runs GNOME Music."""
-    set_gfm()
-    set_exception_hook()
+    # set_exception_hook()
     set_internationalization()
     set_resources()
     return run_application()
diff --git a/gnomemusic/application.py b/gnomemusic/application.py
index 341e8e1d5..aeb2da9c3 100644
--- a/gnomemusic/application.py
+++ b/gnomemusic/application.py
@@ -33,7 +33,7 @@
 from typing import Optional
 from gettext import gettext as _
 
-from gi.repository import Gtk, Gio, GLib, Gdk, GObject, Handy
+from gi.repository import Adw, Gtk, Gio, GLib, Gdk, GObject
 
 from gnomemusic.coregrilo import CoreGrilo
 from gnomemusic.coremodel import CoreModel
@@ -62,7 +62,6 @@ class Application(Gtk.Application):
         GLib.set_prgname(application_id)
         GLib.setenv("PULSE_PROP_media.role", "music", True)
 
-        self._init_style()
         self._window = None
 
         self._log = MusicLogger()
@@ -87,10 +86,10 @@ class Application(Gtk.Application):
     def _init_style(self):
         css_provider = Gtk.CssProvider()
         css_provider.load_from_resource('/org/gnome/Music/org.gnome.Music.css')
-        screen = Gdk.Screen.get_default()
-        style_context = Gtk.StyleContext()
-        style_context.add_provider_for_screen(
-            screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+        display = Gdk.Display.get_default()
+        style_context = self._window.get_style_context()
+        style_context.add_provider_for_display(
+            display, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
 
     @GObject.Property(
         type=CoreGrilo, default=None, flags=GObject.ParamFlags.READABLE)
@@ -234,8 +233,8 @@ class Application(Gtk.Application):
 
     def do_startup(self):
         Gtk.Application.do_startup(self)
-        Handy.StyleManager.get_default().set_color_scheme(
-            Handy.ColorScheme.PREFER_LIGHT)
+        Adw.StyleManager.get_default().set_color_scheme(
+            Adw.ColorScheme.PREFER_LIGHT)
         self._set_actions()
 
     def _quit(self, action=None, param=None):
@@ -246,6 +245,7 @@ class Application(Gtk.Application):
             self._window = Window(self)
             self.notify("window")
             self._window.set_default_icon_name(self.props.application_id)
+            self._init_style()
             if self.props.application_id == "org.gnome.Music.Devel":
                 self._window.get_style_context().add_class('devel')
             MPRIS(self)
diff --git a/gnomemusic/artcache.py b/gnomemusic/artcache.py
index 07ef47765..bb7d9c635 100644
--- a/gnomemusic/artcache.py
+++ b/gnomemusic/artcache.py
@@ -24,10 +24,9 @@
 
 from gi.repository import Gdk, GdkPixbuf, Gio, Gtk, GLib, GObject
 
-from gnomemusic.corealbum import CoreAlbum
 from gnomemusic.coreartist import CoreArtist
-from gnomemusic.coresong import CoreSong
-from gnomemusic.defaulticon import DefaultIcon, make_icon_frame
+from gnomemusic.coverpaintable import CoverPaintable
+from gnomemusic.defaulticon import DefaultIcon
 from gnomemusic.musiclogger import MusicLogger
 from gnomemusic.utils import ArtSize, DefaultIconType
 
@@ -36,7 +35,7 @@ class ArtCache(GObject.GObject):
     """Handles retrieval of MediaArt cache art
 
     Uses signals to indicate success or failure and always returns a
-    Cairo.Surface.
+    CoverPaintable.
     """
 
     __gtype_name__ = "ArtCache"
@@ -58,8 +57,8 @@ class ArtCache(GObject.GObject):
         self._widget = widget
 
         self._coreobject = None
-        self._default_icon = None
-        self._surface = None
+        self._icon_type = DefaultIconType.ALBUM
+        self._paintable = None
 
     def start(self, coreobject, size):
         """Start the cache query
@@ -71,16 +70,14 @@ class ArtCache(GObject.GObject):
         self._size = size
 
         if isinstance(coreobject, CoreArtist):
-            self._default_icon = DefaultIcon(self._widget).get(
-                DefaultIconType.ARTIST, self._size)
-        elif (isinstance(coreobject, CoreAlbum)
-                or isinstance(coreobject, CoreSong)):
-            self._default_icon = DefaultIcon(self._widget).get(
-                DefaultIconType.ALBUM, self._size)
+            self._icon_type = DefaultIconType.ARTIST
+
+        self._paintable = DefaultIcon(self._widget).get(
+            self._icon_type, self._size)
 
         thumbnail_uri = coreobject.props.thumbnail
         if thumbnail_uri == "generic":
-            self.emit("finished", self._default_icon)
+            self.emit("finished", self._paintable)
             return
 
         thumb_file = Gio.File.new_for_uri(thumbnail_uri)
@@ -89,7 +86,7 @@ class ArtCache(GObject.GObject):
                 GLib.PRIORITY_DEFAULT_IDLE, None, self._open_stream, None)
             return
 
-        self.emit("finished", self._default_icon)
+        self.emit("finished", self._paintable)
 
     def _open_stream(self, thumb_file, result, arguments):
         try:
@@ -97,7 +94,7 @@ class ArtCache(GObject.GObject):
         except GLib.Error as error:
             self._log.warning(
                 "Error: {}, {}".format(error.domain, error.message))
-            self.emit("finished", self._default_icon)
+            self.emit("finished", self._paintable)
             return
 
         GdkPixbuf.Pixbuf.new_from_stream_async(
@@ -109,23 +106,18 @@ class ArtCache(GObject.GObject):
         except GLib.Error as error:
             self._log.warning(
                 "Error: {}, {}".format(error.domain, error.message))
-            self.emit("finished", self._default_icon)
+            self.emit("finished", self._paintable)
             return
 
+        texture = Gdk.Texture.new_for_pixbuf(pixbuf)
+        if texture:
+            self._paintable = CoverPaintable(
+                self._size, self._widget, icon_type=self._icon_type,
+                texture=texture)
+
         stream.close_async(
             GLib.PRIORITY_DEFAULT_IDLE, None, self._close_stream, None)
 
-        scale = self._widget.props.scale_factor
-        surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, None)
-        if isinstance(self._coreobject, CoreArtist):
-            surface = make_icon_frame(
-                surface, self._size, scale, round_shape=True)
-        elif (isinstance(self._coreobject, CoreAlbum)
-                or isinstance(self._coreobject, CoreSong)):
-            surface = make_icon_frame(surface, self._size, scale)
-
-        self._surface = surface
-
     def _close_stream(self, stream, result, data):
         try:
             stream.close_finish(result)
@@ -133,4 +125,4 @@ class ArtCache(GObject.GObject):
             self._log.warning(
                 "Error: {}, {}".format(error.domain, error.message))
 
-        self.emit("finished", self._surface)
+        self.emit("finished", self._paintable)
diff --git a/gnomemusic/corealbum.py b/gnomemusic/corealbum.py
index d0cb51deb..27008184e 100644
--- a/gnomemusic/corealbum.py
+++ b/gnomemusic/corealbum.py
@@ -22,9 +22,11 @@
 # code, but you are not obligated to do so.  If you do not wish to do so,
 # delete this exception statement from your version.
 
+from __future__ import annotations
+
 import gi
-gi.require_versions({"Gfm": "0.1", "Grl": "0.3"})
-from gi.repository import Gfm, Gio, Grl, GObject
+gi.require_versions({"Grl": "0.3"})
+from gi.repository import Gio, Grl, Gtk, GObject
 
 import gnomemusic.utils as utils
 
@@ -75,20 +77,21 @@ class CoreAlbum(GObject.GObject):
 
     def _get_album_model(self):
         disc_model = Gio.ListStore()
-        disc_model_sort = Gfm.SortListModel.new(disc_model)
+        disc_model_sort = Gtk.SortListModel.new(disc_model)
 
-        def _disc_order_sort(disc_a, disc_b):
+        def _disc_order_sort(disc_a, disc_b, data=None):
             return disc_a.props.disc_nr - disc_b.props.disc_nr
 
-        disc_model_sort.set_sort_func(
-            utils.wrap_list_store_sort_func(_disc_order_sort))
+        disc_sorter = Gtk.CustomSorter()
+        disc_sorter.set_sort_func(_disc_order_sort)
+        disc_model_sort.set_sorter(disc_sorter)
 
         self._coregrilo.get_album_discs(self.props.media, disc_model)
 
         return disc_model_sort
 
     @GObject.Property(
-        type=Gfm.SortListModel, default=None,
+        type=Gtk.SortListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def model(self):
         if self._model is None:
@@ -158,3 +161,7 @@ class CoreAlbum(GObject.GObject):
 
         if self._thumbnail != "generic":
             self.props.media.set_thumbnail(self._thumbnail)
+
+    @GObject.Property(type=object, flags=GObject.ParamFlags.READABLE)
+    def corealbum(self) -> CoreAlbum:
+        return self
diff --git a/gnomemusic/coreartist.py b/gnomemusic/coreartist.py
index 62915a9f0..90a38d3d9 100644
--- a/gnomemusic/coreartist.py
+++ b/gnomemusic/coreartist.py
@@ -23,8 +23,8 @@
 # delete this exception statement from your version.
 
 import gi
-gi.require_versions({"Gfm": "0.1", "Grl": "0.3"})
-from gi.repository import Gfm, Grl, GObject
+gi.require_versions({"Grl": "0.3"})
+from gi.repository import Grl, Gtk, GObject
 
 from gnomemusic.artistart import ArtistArt
 import gnomemusic.utils as utils
@@ -59,24 +59,25 @@ class CoreArtist(GObject.GObject):
         self.props.artist = utils.get_artist_name(media)
 
     def _get_artist_album_model(self):
-        albums_model_filter = Gfm.FilterListModel.new(
+        albums_model_filter = Gtk.FilterListModel.new(
             self._coremodel.props.albums)
-        albums_model_filter.set_filter_func(lambda a: False)
+        albums_model_filter.set_filter(Gtk.AnyFilter())
 
-        albums_model_sort = Gfm.SortListModel.new(albums_model_filter)
+        albums_model_sort = Gtk.SortListModel.new(albums_model_filter)
 
         self._coregrilo.get_artist_albums(
             self.props.media, albums_model_filter)
 
-        def _album_sort(album_a, album_b):
+        def _album_sort(album_a, album_b, data=None):
             return album_a.props.year > album_b.props.year
 
-        albums_model_sort.set_sort_func(
-            utils.wrap_list_store_sort_func(_album_sort))
+        albums_sorter = Gtk.CustomSorter()
+        albums_sorter.set_sort_func(_album_sort)
+        albums_model_sort.set_sorter(albums_sorter)
 
         return albums_model_sort
 
-    @GObject.Property(type=Gfm.SortListModel, default=None)
+    @GObject.Property(type=Gtk.SortListModel, default=None)
     def model(self):
         if self._model is None:
             self._model = self._get_artist_album_model()
diff --git a/gnomemusic/coredisc.py b/gnomemusic/coredisc.py
index 4446a6a22..09780018b 100644
--- a/gnomemusic/coredisc.py
+++ b/gnomemusic/coredisc.py
@@ -22,9 +22,7 @@
 # code, but you are not obligated to do so.  If you do not wish to do so,
 # delete this exception statement from your version.
 
-from gi.repository import GObject, Gio, Gfm, Grl
-
-import gnomemusic.utils as utils
+from gi.repository import GObject, Gio, Grl, Gtk
 
 
 class CoreDisc(GObject.GObject):
@@ -59,16 +57,18 @@ class CoreDisc(GObject.GObject):
 
     @GObject.Property(type=Gio.ListModel, default=None)
     def model(self):
-        def _disc_sort(song_a, song_b):
+        def _disc_sort(song_a, song_b, data=None):
             return song_a.props.track_number - song_b.props.track_number
 
         if self._model is None:
-            self._filter_model = Gfm.FilterListModel.new(
+            self._filter_model = Gtk.FilterListModel.new(
                 self._coremodel.props.songs)
-            self._filter_model.set_filter_func(lambda a: False)
-            self._model = Gfm.SortListModel.new(self._filter_model)
-            self._model.set_sort_func(
-                utils.wrap_list_store_sort_func(_disc_sort))
+            self._filter_model.set_filter(Gtk.AnyFilter())
+
+            self._model = Gtk.SortListModel.new(self._filter_model)
+            disc_sorter = Gtk.CustomSorter()
+            disc_sorter.set_sort_func(_disc_sort)
+            self._model.set_sorter(disc_sorter)
 
             self._model.connect("items-changed", self._on_disc_changed)
 
diff --git a/gnomemusic/coregrilo.py b/gnomemusic/coregrilo.py
index b94f4d1ff..e5c17acab 100644
--- a/gnomemusic/coregrilo.py
+++ b/gnomemusic/coregrilo.py
@@ -25,8 +25,8 @@
 import weakref
 
 import gi
-gi.require_versions({"Grl": "0.3", "Gfm": "0.1"})
-from gi.repository import Grl, GLib, GObject, Gfm
+gi.require_version("Grl", "0.3")
+from gi.repository import Grl, GLib, GObject, Gtk
 
 from gnomemusic.grilowrappers.grlsearchwrapper import GrlSearchWrapper
 from gnomemusic.grilowrappers.grltrackerwrapper import GrlTrackerWrapper
@@ -178,7 +178,7 @@ class CoreGrilo(GObject.GObject):
         """Get all album by an artist
 
         :param Grl.Media media: A Grilo Media item that represents Artist
-        :param Gfm.FilterListModel filter_model: The model to fill
+        :param Gtk.FilterListModel filter_model: The model to fill
         """
         source = media.get_source()
         self._wrappers[source].get_artist_albums(media, filter_model)
@@ -187,19 +187,19 @@ class CoreGrilo(GObject.GObject):
         """Get all discs from an album
 
         :param Grl.Media media: A Grilo Media item that represents Album
-        :param Gfm.SortListModel disc_model: The model to fill
+        :param Gtk.SortListModel disc_model: The model to fill
         """
         source = media.get_source()
         self._wrappers[source].get_album_discs(media, disc_model)
 
     def get_album_disc(
             self, media: Grl.Media, discnr: int,
-            model: Gfm.FilterListModel) -> None:
+            model: Gtk.FilterListModel) -> None:
         """Get all songs from an album disc
 
         :param Grl.Media media: An album
         :param int discnr: The disc number
-        :param Gfm.FilterListModel model: The model to fill
+        :param Gtk.FilterListModel model: The model to fill
         """
         source = media.get_source()
         self._wrappers[source].get_album_disc(media, discnr, model)
diff --git a/gnomemusic/coremodel.py b/gnomemusic/coremodel.py
index b9110a8d4..97a930405 100644
--- a/gnomemusic/coremodel.py
+++ b/gnomemusic/coremodel.py
@@ -23,19 +23,16 @@
 # delete this exception statement from your version.
 
 from __future__ import annotations
-from typing import Optional, Union
+from typing import Any, Optional, Union
 import typing
 
-import gi
-gi.require_version("Gfm", "0.1")
-from gi.repository import GLib, GObject, Gio, Gfm, Gtk
+from gi.repository import GLib, GObject, Gio, Gtk
 
 from gnomemusic.corealbum import CoreAlbum
 from gnomemusic.coreartist import CoreArtist
 from gnomemusic.coresong import CoreSong
 from gnomemusic.grilowrappers.grltrackerplaylists import Playlist
 from gnomemusic.player import PlayerPlaylist
-from gnomemusic.songliststore import SongListStore
 from gnomemusic.widgets.songwidget import SongWidget
 if typing.TYPE_CHECKING:
     from gnomemusic.application import Application
@@ -85,86 +82,91 @@ class CoreModel(GObject.GObject):
         """
         super().__init__()
 
-        self._flatten_model: Optional[Gfm.FlattenListModel] = None
+        self._application = application
+        self._flatten_model: Optional[Gtk.FlattenListModel] = None
         self._player_signal_id = 0
         self._current_playlist_model: Optional[Union[
-            Gfm.FlattenListModel, Gfm.SortListModel, Gio.ListModel]] = None
+            Gtk.FlattenListModel, Gtk.SortListModel, Gio.ListModel]] = None
         self._previous_playlist_model: Optional[Union[
-            Gfm.FlattenListModel, Gfm.SortListModel, Gio.ListModel]] = None
+            Gtk.FlattenListModel, Gtk.SortListModel, Gio.ListModel]] = None
 
         self._songs_model_proxy: Gio.ListStore = Gio.ListStore.new(
             Gio.ListModel)
-        self._songs_model: Gfm.FlattenListModel = Gfm.FlattenListModel.new(
-            CoreSong, self._songs_model_proxy)
-        self._songliststore = SongListStore(self._songs_model)
-
-        self._application = application
+        self._flatten_songs_model = Gtk.FlattenListModel.new(
+            self._songs_model_proxy)
+        self._songs_model = Gtk.SortListModel.new(self._flatten_songs_model)
+        sorter = Gtk.CustomSorter()
+        sorter.set_sort_func(self._songs_sort)
+        self._songs_model.props.incremental = True
+        self._songs_model.set_sorter(sorter)
 
         self._albums_model_proxy: Gio.ListStore = Gio.ListStore.new(
             Gio.ListModel)
-        self._albums_model: Gfm.FlattenListModel = Gfm.FlattenListModel.new(
-            CoreAlbum, self._albums_model_proxy)
-        self._albums_model_sort: Gfm.SortListModel = Gfm.SortListModel.new(
+        self._albums_model: Gtk.FlattenListModel = Gtk.FlattenListModel.new(
+            self._albums_model_proxy)
+        self._albums_model_sort: Gtk.SortListModel = Gtk.SortListModel.new(
             self._albums_model)
-        self._albums_model_sort.set_sort_func(
-            utils.wrap_list_store_sort_func(self._albums_sort))
+        albums_sorter = Gtk.CustomSorter()
+        albums_sorter.set_sort_func(self._albums_sort)
+        self._albums_model_sort.set_sorter(albums_sorter)
 
         self._artists_model_proxy: Gio.ListStore = Gio.ListStore.new(
             Gio.ListModel)
-        self._artists_model: Gfm.FlattenListModel = Gfm.FlattenListModel.new(
-            CoreArtist, self._artists_model_proxy)
-        self._artists_model_sort: Gfm.SortListModel = Gfm.SortListModel.new(
+        self._artists_model: Gtk.FlattenListModel = Gtk.FlattenListModel.new(
+            self._artists_model_proxy)
+        self._artists_model_sort: Gtk.SortListModel = Gtk.SortListModel.new(
             self._artists_model)
-        self._artists_model_sort.set_sort_func(
-            utils.wrap_list_store_sort_func(self._artist_sort))
+        artists_sorter = Gtk.CustomSorter()
+        artists_sorter.set_sort_func(self._artist_sort)
+        self._artists_model_sort.set_sorter(artists_sorter)
 
         self._playlist_model: Gio.ListStore = Gio.ListStore.new(CoreSong)
-        self._playlist_model_sort: Gfm.SortListModel = Gfm.SortListModel.new(
+        self._playlist_model_sort: Gtk.SortListModel = Gtk.SortListModel.new(
             self._playlist_model)
-        self._playlist_model_recent: Gfm.SliceListModel = (
-            Gfm.SliceListModel.new(
+        self._playlist_model_recent: Gtk.SliceListModel = (
+            Gtk.SliceListModel.new(
                 self._playlist_model_sort, 0, self._recent_size))
         self._active_core_object: Optional[Union[
             CoreAlbum, CoreArtist, Playlist]] = None
 
         self._songs_search_proxy: Gio.ListStore = Gio.ListStore.new(
-            Gfm.FilterListModel)
-        self._songs_search_flatten: Gfm.FlattenListModel = (
-            Gfm.FlattenListModel.new(CoreSong))
+            Gtk.FilterListModel)
+        self._songs_search_flatten: Gtk.FlattenListModel = (
+            Gtk.FlattenListModel())
         self._songs_search_flatten.set_model(self._songs_search_proxy)
 
         self._albums_search_proxy: Gio.Liststore = Gio.ListStore.new(
-            Gfm.FilterListModel)
-        self._albums_search_flatten: Gfm.FlattenListModel = (
-            Gfm.FlattenListModel.new(CoreAlbum))
+            Gtk.FilterListModel)
+        self._albums_search_flatten: Gtk.FlattenListModel = (
+            Gtk.FlattenListModel())
         self._albums_search_flatten.set_model(self._albums_search_proxy)
-
-        self._albums_search_filter: Gfm.FilterListModel = (
-            Gfm.FilterListModel.new(self._albums_search_flatten))
+        self._albums_search_filter: Gtk.FilterListModel = (
+            Gtk.FilterListModel.new(Gtk.AnyFilter()))
 
         self._artists_search_proxy: Gio.Liststore = Gio.ListStore.new(
-            Gfm.FilterListModel)
-        self._artists_search_flatten: Gfm.FlattenListModel = (
-            Gfm.FlattenListModel.new(CoreArtist))
+            Gtk.FilterListModel)
+        self._artists_search_flatten: Gtk.FlattenListModel = (
+            Gtk.FlattenListModel())
         self._artists_search_flatten.set_model(self._artists_search_proxy)
-
-        self._artists_search_filter: Gfm.FilterListModel = (
-            Gfm.FilterListModel.new(self._artists_search_flatten))
+        self._artists_search_filter: Gtk.FilterListModel = (
+            Gtk.FilterListModel.new(Gtk.AnyFilter()))
 
         self._playlists_model: Gio.ListStore = Gio.ListStore.new(Playlist)
-        self._playlists_model_filter: Gfm.FilterListModel = (
-            Gfm.FilterListModel.new(self._playlists_model))
-        self._playlists_model_sort: Gfm.SortListModel = Gfm.SortListModel.new(
+        self._playlists_model_filter: Gtk.FilterListModel = (
+            Gtk.FilterListModel.new(self._playlists_model))
+        self._playlists_model_sort: Gtk.SortListModel = Gtk.SortListModel.new(
             self._playlists_model_filter)
-        self._playlists_model_sort.set_sort_func(
-            utils.wrap_list_store_sort_func(self._playlists_sort))
-
-        self._user_playlists_model_filter: Gfm.FilterListModel = (
-            Gfm.FilterListModel.new(self._playlists_model))
-        self._user_playlists_model_sort: Gfm.SortListModel = (
-            Gfm.SortListModel.new(self._user_playlists_model_filter))
-        self._user_playlists_model_sort.set_sort_func(
-            utils.wrap_list_store_sort_func(self._playlists_sort))
+        playlists_sorter = Gtk.CustomSorter()
+        playlists_sorter.set_sort_func(self._playlists_sort)
+        self._playlists_model_sort.set_sorter(playlists_sorter)
+
+        self._user_playlists_model_filter: Gtk.FilterListModel = (
+            Gtk.FilterListModel.new(self._playlists_model))
+        self._user_playlists_model_sort: Gtk.SortListModel = (
+            Gtk.SortListModel.new(self._user_playlists_model_filter))
+        user_playlists_sorter = Gtk.CustomSorter()
+        user_playlists_sorter.set_sort_func(self._playlists_sort)
+        self._user_playlists_model_sort.set_sorter(user_playlists_sorter)
 
         self._search: Search = application.props.search
 
@@ -183,18 +185,33 @@ class CoreModel(GObject.GObject):
         else:
             self.props.songs_available = False
 
-    def _filter_selected(self, coresong):
-        return coresong.props.selected
-
-    def _albums_sort(self, album_a, album_b):
+    def _songs_sort(
+            self, song_a: CoreSong, song_b: CoreSong, data: Any = None) -> int:
+        title_a = song_a.props.title
+        title_b = song_b.props.title
+        song_cmp = (utils.normalize_caseless(title_a)
+                    == utils.normalize_caseless(title_b))
+        if not song_cmp:
+            return utils.natural_sort_names(title_a, title_b)
+
+        artist_a = song_a.props.artist
+        artist_b = song_b.props.artist
+        artist_cmp = (utils.normalize_caseless(artist_a)
+                      == utils.normalize_caseless(artist_b))
+        if not artist_cmp:
+            return utils.natural_sort_names(artist_a, artist_b)
+
+        return utils.natural_sort_names(song_a.props.album, song_b.props.album)
+
+    def _albums_sort(self, album_a, album_b, data=None):
         return utils.natural_sort_names(
             album_a.props.title, album_b.props.title)
 
-    def _artist_sort(self, artist_a, artist_b):
+    def _artist_sort(self, artist_a, artist_b, data=None):
         return utils.natural_sort_names(
             artist_a.props.artist, artist_b.props.artist)
 
-    def _playlists_sort(self, playlist_a, playlist_b):
+    def _playlists_sort(self, playlist_a, playlist_b, data=None):
         if playlist_a.props.is_smart:
             if not playlist_b.props.is_smart:
                 return -1
@@ -260,8 +277,7 @@ class CoreModel(GObject.GObject):
             for disc in model:
                 proxy_model.append(disc.props.model)
 
-            self._flatten_model = Gfm.FlattenListModel.new(
-                CoreSong, proxy_model)
+            self._flatten_model = Gtk.FlattenListModel.new(proxy_model)
             self._current_playlist_model = self._flatten_model
 
             for model_song in self._flatten_model:
@@ -276,8 +292,7 @@ class CoreModel(GObject.GObject):
                 for disc in artist_album.model:
                     proxy_model.append(disc.props.model)
 
-            self._flatten_model = Gfm.FlattenListModel.new(
-                CoreSong, proxy_model)
+            self._flatten_model = Gtk.FlattenListModel.new(proxy_model)
             self._current_playlist_model = self._flatten_model
 
             for model_song in self._flatten_model:
@@ -286,9 +301,9 @@ class CoreModel(GObject.GObject):
                 songs_added.append(song)
 
         elif playlist_type == PlayerPlaylist.Type.SONGS:
-            self._current_playlist_model = self._songliststore.props.model
+            self._current_playlist_model = self._songs_model
 
-            for song in self._songliststore.props.model:
+            for song in self._songs_model:
                 songs_added.append(song)
 
                 if song.props.state == SongWidget.State.PLAYING:
@@ -363,7 +378,7 @@ class CoreModel(GObject.GObject):
         return self._songs_model
 
     @GObject.Property(
-        type=Gfm.FlattenListModel, default=None,
+        type=Gtk.FlattenListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def songs_proxy(self):
         return self._songs_model_proxy
@@ -374,7 +389,7 @@ class CoreModel(GObject.GObject):
         return self._albums_model
 
     @GObject.Property(
-        type=Gfm.FlattenListModel, default=None,
+        type=Gtk.FlattenListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def albums_proxy(self):
         return self._albums_model_proxy
@@ -385,7 +400,7 @@ class CoreModel(GObject.GObject):
         return self._artists_model
 
     @GObject.Property(
-        type=Gfm.FlattenListModel, default=None,
+        type=Gtk.FlattenListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def artists_proxy(self):
         return self._artists_model_proxy
@@ -396,25 +411,25 @@ class CoreModel(GObject.GObject):
         return self._playlist_model
 
     @GObject.Property(
-        type=Gfm.SortListModel, default=None,
+        type=Gtk.SortListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def albums_sort(self):
         return self._albums_model_sort
 
     @GObject.Property(
-        type=Gfm.SortListModel, default=None,
+        type=Gtk.SortListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def artists_sort(self):
         return self._artists_model_sort
 
     @GObject.Property(
-        type=Gfm.SortListModel, default=None,
+        type=Gtk.SortListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def playlist_sort(self):
         return self._playlist_model_sort
 
     @GObject.Property(
-        type=Gfm.SliceListModel, default=None,
+        type=Gtk.SliceListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def recent_playlist(self):
         return self._playlist_model_recent
@@ -426,7 +441,7 @@ class CoreModel(GObject.GObject):
         return self._recent_size // 2
 
     @GObject.Property(
-        type=Gfm.FilterListModel, default=None,
+        type=Gtk.FilterListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def songs_search(self):
         return self._songs_search_flatten
@@ -438,13 +453,13 @@ class CoreModel(GObject.GObject):
         return self._songs_search_proxy
 
     @GObject.Property(
-        type=Gfm.FlattenListModel, default=None,
+        type=Gtk.FlattenListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
-    def albums_search(self) -> Gfm.FlattenListModel:
+    def albums_search(self) -> Gtk.FlattenListModel:
         return self._albums_search_flatten
 
     @GObject.Property(
-        type=Gfm.FilterListModel, default=None,
+        type=Gtk.FilterListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def albums_search_filter(self):
         return self._albums_search_filter
@@ -455,13 +470,13 @@ class CoreModel(GObject.GObject):
         return self._albums_search_proxy
 
     @GObject.Property(
-        type=Gfm.FlattenListModel, default=None,
+        type=Gtk.FlattenListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
-    def artists_search(self) -> Gfm.FlattenListModel:
+    def artists_search(self) -> Gtk.FlattenListModel:
         return self._artists_search_flatten
 
     @GObject.Property(
-        type=Gfm.FilterListModel, default=None,
+        type=Gtk.FilterListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def artists_search_filter(self):
         return self._artists_search_filter
@@ -471,36 +486,31 @@ class CoreModel(GObject.GObject):
     def artists_search_proxy(self) -> Gio.ListStore:
         return self._artists_search_proxy
 
-    @GObject.Property(
-        type=Gtk.ListStore, default=None, flags=GObject.ParamFlags.READABLE)
-    def songs_gtkliststore(self):
-        return self._songliststore
-
     @GObject.Property(
         type=Gio.ListStore, default=None, flags=GObject.ParamFlags.READABLE)
     def playlists(self):
         return self._playlists_model
 
     @GObject.Property(
-        type=Gfm.SortListModel, default=None,
+        type=Gtk.SortListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def playlists_sort(self):
         return self._playlists_model_sort
 
     @GObject.Property(
-        type=Gfm.SortListModel, default=None,
+        type=Gtk.SortListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def playlists_filter(self):
         return self._playlists_model_filter
 
     @GObject.Property(
-        type=Gfm.SortListModel, default=None,
+        type=Gtk.SortListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def user_playlists_sort(self):
         return self._user_playlists_model_sort
 
     @GObject.Property(
-        type=Gfm.SortListModel, default=None,
+        type=Gtk.SortListModel, default=None,
         flags=GObject.ParamFlags.READABLE)
     def user_playlists_filter(self):
         return self._user_playlists_model_filter
diff --git a/gnomemusic/coverpaintable.py b/gnomemusic/coverpaintable.py
new file mode 100644
index 000000000..540f2371d
--- /dev/null
+++ b/gnomemusic/coverpaintable.py
@@ -0,0 +1,118 @@
+# Copyright 2022 The GNOME Music developers
+#
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music.  This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered.  If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so.  If you do not wish to do so,
+# delete this exception statement from your version.
+
+from __future__ import annotations
+
+import gi
+gi.require_versions({"Gdk": "4.0", "Gtk": "4.0", "Gsk": "4.0"})
+from gi.repository import Gsk, Gtk, GObject, Graphene, Gdk
+
+from gnomemusic.utils import ArtSize, DefaultIconType
+
+
+class CoverPaintable(GObject.GObject, Gdk.Paintable):
+    """An album/artist cover or placeholder
+
+    Provides the full looks. Rounded corners for albums and round for
+    artists.
+    """
+
+    __gtype_name__ = "CoverPaintable"
+
+    def __init__(
+            self, art_size: ArtSize, widget: Gtk.Widget,
+            icon_type: DefaultIconType = DefaultIconType.ALBUM,
+            texture: Gdk.Texture = None, dark: bool = False) -> None:
+        """Initiliaze CoverPaintable
+
+        :param ArtSize art_size: Size of the cover
+        :param Gtk.Widget widget: Widget using the cover
+        :param DefaultIconType icon_type: Type of cover
+        :param Gdk.Texture texture: Texture to use or None for
+            placeholder
+        :param bool dark: Dark mode
+        """
+        super().__init__()
+
+        self._art_size = art_size
+        self._dark = dark
+        self._icon_theme = Gtk.IconTheme.new().get_for_display(
+            widget.get_display())
+        self._icon_type = icon_type
+        self._texture = texture
+        self._widget = widget
+
+    def do_snapshot(self, snapshot: Gtk.Snapshot, w: int, h: int) -> None:
+        if self._icon_type == DefaultIconType.ARTIST:
+            radius = 90.0
+        elif self._art_size == ArtSize.SMALL:
+            radius = 4.5
+        else:
+            radius = 9.0
+
+        w_s = w
+        h_s = h
+        if self._texture is not None:
+            ratio = self._texture.get_height() / self._texture.get_width()
+            # Scale down the image according to the biggest axis
+            if ratio > 1:
+                w = int(w / ratio)
+            else:
+                h = int(h * ratio)
+
+        rect = Graphene.Rect().init((w_s - w) / 2, (h_s - h) / 2, w, h)
+        rounded_rect = Gsk.RoundedRect()
+        rounded_rect.init_from_rect(rect, radius)
+        snapshot.push_rounded_clip(rounded_rect)
+
+        if self._texture is not None:
+            snapshot.append_texture(self._texture, rect)
+        else:
+            i_s = 1 / 3  # Icon scale
+            icon_pt = self._icon_theme.lookup_icon(
+                self._icon_type.value, None, w * i_s,
+                self._widget.props.scale_factor, 0, 0)
+
+            bg_color = Gdk.RGBA(1, 1, 1, 1)
+            if self._dark:
+                bg_color = Gdk.RGBA(0.3, 0.3, 0.3, 1)
+
+            snapshot.append_color(bg_color, Graphene.Rect().init(0, 0, w, h))
+            snapshot.translate(
+                Graphene.Point().init(
+                    (w / 2) - (w * (i_s / 2)), (h / 2) - (h * (i_s / 2))))
+            snapshot.push_opacity(0.7)
+            icon_pt.snapshot(snapshot, w * i_s, h * i_s)
+            snapshot.pop()
+
+        snapshot.pop()
+
+    def do_get_flags(self) -> Gdk.PaintableFlags:
+        return Gdk.PaintableFlags.SIZE | Gdk.PaintableFlags.CONTENTS
+
+    def do_get_intrinsic_height(self) -> int:
+        return self._art_size.height
+
+    def do_get_intrinsic_width(self) -> int:
+        return self._art_size.width
diff --git a/gnomemusic/defaulticon.py b/gnomemusic/defaulticon.py
index b39ac60b7..29377a184 100644
--- a/gnomemusic/defaulticon.py
+++ b/gnomemusic/defaulticon.py
@@ -24,89 +24,18 @@
 
 from __future__ import annotations
 
-from math import pi
 from typing import Dict, Tuple
 
-import cairo
-from gi.repository import Gtk, GObject, Gdk, Handy
+from gi.repository import Adw, Gtk, GObject
 
+from gnomemusic.coverpaintable import CoverPaintable
 from gnomemusic.utils import ArtSize, DefaultIconType
 
 
-def make_icon_frame(
-        icon_surface, art_size=None, scale=1, default_icon=False,
-        round_shape=False, dark=False):
-    """Create an Art frame, square or round.
-
-    :param cairo.Surface icon_surface: The surface to use
-    :param art_size: The size of the art
-    :param int scale: The scale of the art
-    :param bool default_icon: Indicates of this is a default icon
-    :param bool round_shape: Square or round indicator
-    :param bool dark: Theme dark mode
-
-    :return: The framed surface
-    :rtype: cairo.Surface
-    """
-    degrees = pi / 180
-    if art_size == ArtSize.SMALL:
-        radius = 4.5
-    else:
-        radius = 9
-    icon_w = icon_surface.get_width()
-    icon_h = icon_surface.get_height()
-    ratio = icon_h / icon_w
-
-    # Scale down the image according to the biggest axis
-    if ratio > 1:
-        w = int(art_size.width / ratio)
-        h = art_size.height
-    else:
-        w = art_size.width
-        h = int(art_size.height * ratio)
-
-    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w * scale, h * scale)
-    surface.set_device_scale(scale, scale)
-    ctx = cairo.Context(surface)
-
-    if round_shape:
-        ctx.arc(w / 2, h / 2, w / 2, 0, 2 * pi)
-    else:
-        ctx.arc(w - radius, radius, radius, -90 * degrees, 0 * degrees)
-        ctx.arc(
-            w - radius, h - radius, radius, 0 * degrees, 90 * degrees)
-        ctx.arc(radius, h - radius, radius, 90 * degrees, 180 * degrees)
-        ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees)
-
-    if dark:
-        fill_color = Gdk.RGBA(0.28, 0.28, 0.28, 1.0)
-        icon_color = Gdk.RGBA(1.0, 1.0, 1.0, 0.5)
-    else:
-        fill_color = Gdk.RGBA(1.0, 1.0, 1.0, 1.0)
-        icon_color = Gdk.RGBA(0.0, 0.0, 0.0, 0.3)
-
-    if default_icon:
-        ctx.set_source_rgb(fill_color.red, fill_color.green, fill_color.blue)
-        ctx.fill()
-        ctx.set_source_rgba(
-            icon_color.red, icon_color.green, icon_color.blue,
-            icon_color.alpha)
-        ctx.mask_surface(icon_surface, w / 3, h / 3)
-        ctx.fill()
-    else:
-        ctx.scale((w * scale) / icon_w, (h * scale) / icon_h)
-        ctx.set_source_surface(icon_surface, 0, 0)
-        ctx.fill()
-
-    return surface
-
-
 class DefaultIcon(GObject.GObject):
     """Provides the symbolic fallback icons."""
     _cache: Dict[
-        Tuple[DefaultIconType, ArtSize, int, bool], cairo.ImageSurface] = {}
-
-    _default_theme = Gtk.IconTheme.get_default()
+        Tuple[DefaultIconType, ArtSize, int, bool], CoverPaintable] = {}
 
     def __init__(self, widget: Gtk.Widget) -> None:
         """Initialize DefaultIcon
@@ -118,37 +47,31 @@ class DefaultIcon(GObject.GObject):
         self._widget = widget
 
     def _make_default_icon(
-            self, icon_type: DefaultIconType, art_size: ArtSize, scale: int,
-            dark: bool) -> cairo.ImageSurface:
-        icon_info = self._default_theme.lookup_icon_for_scale(
-            icon_type.value, art_size.width / 3, scale, 0)
-        icon = icon_info.load_surface()
-
-        round_shape = icon_type == DefaultIconType.ARTIST
-        icon_surface = make_icon_frame(
-            icon, art_size, scale, True, round_shape, dark)
+            self, icon_type: DefaultIconType, art_size: ArtSize,
+            dark: bool) -> CoverPaintable:
+        paintable = CoverPaintable(
+            art_size, self._widget, icon_type=icon_type, dark=dark)
 
-        return icon_surface
+        return paintable
 
     def get(self, icon_type: DefaultIconType,
-            art_size: ArtSize) -> cairo.ImageSurface:
+            art_size: ArtSize) -> CoverPaintable:
         """Returns the requested symbolic icon
 
-        Returns a cairo surface of the requested symbolic icon in the
-        given size and shape.
+        Returns a paintable of the requested symbolic icon in the
+        given size, shape and color.
 
         :param DefaultIconType icon_type: The type of icon
         :param ArtSize art_size: The size requested
 
         :return: The symbolic icon
-        :rtype: cairo.ImageSurface
+        :rtype: CoverPaintable
         """
-        dark = Handy.StyleManager.get_default().props.dark
+        dark = Adw.StyleManager.get_default().props.dark
         scale = self._widget.props.scale_factor
 
         if (icon_type, art_size, scale, dark) not in self._cache.keys():
-            new_icon = self._make_default_icon(
-                icon_type, art_size, scale, dark)
+            new_icon = self._make_default_icon(icon_type, art_size, dark)
             self._cache[(icon_type, art_size, scale, dark)] = new_icon
 
         return self._cache[(icon_type, art_size, scale, dark)]
diff --git a/gnomemusic/grilowrappers/grlsearchwrapper.py b/gnomemusic/grilowrappers/grlsearchwrapper.py
index 5a90fa6f9..9db9149ec 100644
--- a/gnomemusic/grilowrappers/grlsearchwrapper.py
+++ b/gnomemusic/grilowrappers/grlsearchwrapper.py
@@ -23,8 +23,8 @@
 # delete this exception statement from your version.
 
 import gi
-gi.require_versions({"Gfm": "0.1", "Grl": "0.3"})
-from gi.repository import Gfm, Gio, Grl, GObject
+gi.require_versions({"Grl": "0.3"})
+from gi.repository import Gio, Grl, Gtk, GObject
 
 from gnomemusic.coresong import CoreSong
 
@@ -75,9 +75,9 @@ class GrlSearchWrapper(GObject.GObject):
         self._song_search_store = Gio.ListStore.new(CoreSong)
         # FIXME: Workaround for adding the right list type to the proxy
         # list model.
-        self._song_search_model = Gfm.FilterListModel.new(
+        self._song_search_model = Gtk.FilterListModel.new(
             self._song_search_store)
-        self._song_search_model.set_filter_func(lambda a: True)
+        self._song_search_model.set_filter(Gtk.AnyFilter())
         self._song_search_proxy.append(self._song_search_model)
 
         self._fast_options = Grl.OperationOptions()
diff --git a/gnomemusic/grilowrappers/grltrackerplaylists.py b/gnomemusic/grilowrappers/grltrackerplaylists.py
index 9e8f26344..4d80aa493 100644
--- a/gnomemusic/grilowrappers/grltrackerplaylists.py
+++ b/gnomemusic/grilowrappers/grltrackerplaylists.py
@@ -27,8 +27,8 @@ import time
 from gettext import gettext as _
 
 import gi
-gi.require_versions({"Grl": "0.3"})
-from gi.repository import Gio, Grl, GLib, GObject, Tracker
+gi.require_versions({"Grl": "0.3", "Tracker": "3.0"})
+from gi.repository import Gio, Grl, Gtk, GLib, GObject, Tracker
 
 from gnomemusic.coresong import CoreSong
 import gnomemusic.utils as utils
@@ -68,7 +68,9 @@ class GrlTrackerPlaylists(GObject.GObject):
         self._notificationmanager = application.props.notificationmanager
         self._window = application.props.window
 
-        self._user_model_filter.set_filter_func(self._user_playlists_filter)
+        user_playlists_filter = Gtk.CustomFilter()
+        user_playlists_filter.set_filter_func(self._user_playlists_filter)
+        self._user_model_filter.set_filter(user_playlists_filter)
 
         self._fast_options = Grl.OperationOptions()
         self._fast_options.set_resolution_flags(
@@ -146,7 +148,9 @@ class GrlTrackerPlaylists(GObject.GObject):
         :param Playlist playlist: playlist
         """
         self._pls_todelete.append(playlist)
-        self._model_filter.set_filter_func(self._playlists_filter)
+        playlists_filter = Gtk.CustomFilter()
+        playlists_filter.set_filter_func(self._playlists_filter)
+        self._model_filter.set_filter(playlists_filter)
 
     def finish_playlist_deletion(self, playlist, deleted):
         """Removes playlist from the list of playlists to delete
@@ -154,11 +158,15 @@ class GrlTrackerPlaylists(GObject.GObject):
         :param Playlist playlist: playlist
         :param bool deleted: indicates if the playlist has been deleted
         """
+        playlists_filter = Gtk.CustomFilter()
+        playlists_filter.set_filter_func(self._playlists_filter)
+        user_playlists_filter = Gtk.CustomFilter()
+        user_playlists_filter.set_filter_func(self._playlists_filter)
+
         self._pls_todelete.remove(playlist)
         if deleted is False:
-            self._model_filter.set_filter_func(self._playlists_filter)
-            self._user_model_filter.set_filter_func(
-                self._user_playlists_filter)
+            self._model_filter.set_filter(playlists_filter)
+            self._user_model_filter.set_filter(user_playlists_filter)
             return
 
         def _delete_cb(conn, res, data):
@@ -173,7 +181,9 @@ class GrlTrackerPlaylists(GObject.GObject):
                         self._model.remove(idx)
                         break
 
-            self._model_filter.set_filter_func(self._playlists_filter)
+            playlists_filter = Gtk.CustomFilter()
+            playlists_filter.set_filter_func(self._playlists_filter)
+            self._model_filter.set_filter(playlists_filter)
             self._notificationmanager.pop_loading()
 
         self._notificationmanager.push_loading()
diff --git a/gnomemusic/grilowrappers/grltrackerwrapper.py b/gnomemusic/grilowrappers/grltrackerwrapper.py
index a49db09e0..680ba409f 100644
--- a/gnomemusic/grilowrappers/grltrackerwrapper.py
+++ b/gnomemusic/grilowrappers/grltrackerwrapper.py
@@ -27,8 +27,8 @@ from typing import Callable, Dict, List, Optional
 import typing
 
 import gi
-gi.require_versions({"Gfm": "0.1", "Grl": "0.3", "Tracker": "3.0"})
-from gi.repository import Gfm, Gio, Grl, GLib, GObject, Tracker
+gi.require_versions({"Grl": "0.3", "Tracker": "3.0"})
+from gi.repository import Grl, Gio, Gtk, GLib, GObject, Tracker
 
 from gnomemusic.asyncqueue import AsyncQueue
 from gnomemusic.corealbum import CoreAlbum
@@ -127,19 +127,19 @@ class GrlTrackerWrapper(GObject.GObject):
         self._notificationmanager: NotificationManager = (
             application.props.notificationmanager)
 
-        self._songs_search: Gfm.FilterListModel = Gfm.FilterListModel.new(
+        self._songs_search: Gtk.FilterListModel = Gtk.FilterListModel.new(
             self._songs_model)
-        self._songs_search.set_filter_func(lambda a: False)
+        self._songs_search.set_filter(Gtk.AnyFilter())
         cm.props.songs_search_proxy.append(self._songs_search)
 
-        self._albums_search: Gfm.FilterListModel = Gfm.FilterListModel.new(
+        self._albums_search: Gtk.FilterListModel = Gtk.FilterListModel.new(
             self._albums_model)
-        self._albums_search.set_filter_func(lambda a: False)
+        self._albums_search.set_filter(Gtk.AnyFilter())
         cm.props.albums_search_proxy.append(self._albums_search)
 
-        self._artists_search: Gfm.FilterListModel = Gfm.FilterListModel.new(
+        self._artists_search: Gtk.FilterListModel = Gtk.FilterListModel.new(
             self._artists_model)
-        self._artists_search.set_filter_func(lambda a: False)
+        self._artists_search.set_filter(Gtk.AnyFilter())
         cm.props.artists_search_proxy.append(self._artists_search)
 
         self._fast_options: Grl.OperationOptions = Grl.OperationOptions()
@@ -457,7 +457,7 @@ class GrlTrackerWrapper(GObject.GObject):
                 ?urn nao:hasTag ?tag .
                 FILTER (?tag = nao:predefined-tag-favorite)
             }}
-        }}
+        }} ORDER BY ?title ?artist
         """.split())
 
         return query
@@ -668,11 +668,11 @@ class GrlTrackerWrapper(GObject.GObject):
             query, metadata_keys, self._fast_options, _add_to_artists_model)
 
     def get_artist_albums(
-            self, media: Grl.Source, model: Gfm.FilterListModel) -> None:
+            self, media: Grl.Source, model: Gtk.FilterListModel) -> None:
         """Get all albums by an artist
 
         :param Grl.Media media: The media with the artist id
-        :param Gfm.FilterListModel model: The model to fill
+        :param Gtk.FilterListModel model: The model to fill
         """
         self._notificationmanager.push_loading()
         artist_id = media.get_id()
@@ -721,7 +721,9 @@ class GrlTrackerWrapper(GObject.GObject):
                 return
 
             if not media:
-                model.set_filter_func(albums_filter, albums)
+                custom_filter = Gtk.CustomFilter()
+                custom_filter.set_filter_func(albums_filter, albums)
+                model.set_filter(custom_filter)
                 self._notificationmanager.pop_loading()
                 return
 
@@ -739,11 +741,11 @@ class GrlTrackerWrapper(GObject.GObject):
             query, [Grl.METADATA_KEY_TITLE], self._fast_options, query_cb)
 
     def get_album_discs(
-            self, media: Grl.Media, disc_model: Gfm.SortListModel) -> None:
+            self, media: Grl.Media, disc_model: Gtk.SortListModel) -> None:
         """Get all discs of an album
 
         :param Grl.Media media: The media with the album id
-        :param Gfm.SortListModel disc_model: The model to fill
+        :param Gtk.SortListModel disc_model: The model to fill
         """
         self._notificationmanager.push_loading()
         album_id = media.get_id()
@@ -798,12 +800,12 @@ class GrlTrackerWrapper(GObject.GObject):
 
     def get_album_disc(
             self, media: Grl.Media, disc_nr: int,
-            model: Gfm.FilterListModel) -> None:
+            model: Gtk.FilterListModel) -> None:
         """Get all songs of an album disc
 
         :param Grl.Media media: The media with the album id
         :param int disc_nr: The disc number
-        :param Gfm.FilterListModel model: The model to fill
+        :param Gtk.FilterListModel model: The model to fill
         """
         album_id = media.get_id()
 
@@ -882,7 +884,9 @@ class GrlTrackerWrapper(GObject.GObject):
                 return
 
             if media is None:
-                model.set_filter_func(_filter_func)
+                custom_filter = Gtk.CustomFilter()
+                custom_filter.set_filter_func(_filter_func)
+                model.set_filter(custom_filter)
                 return
 
             disc_song_ids.append(media.get_source() + media.get_id())
@@ -968,7 +972,9 @@ class GrlTrackerWrapper(GObject.GObject):
                 return
 
             if not media:
-                self._artists_search.set_filter_func(artist_filter)
+                custom_filter = Gtk.CustomFilter()
+                custom_filter.set_filter_func(artist_filter)
+                self._artists_search.set_filter(custom_filter)
                 self._notificationmanager.pop_loading()
                 return
 
@@ -1037,7 +1043,9 @@ class GrlTrackerWrapper(GObject.GObject):
                 return
 
             if not media:
-                self._albums_search.set_filter_func(album_filter)
+                custom_filter = Gtk.CustomFilter()
+                custom_filter.set_filter_func(album_filter)
+                self._albums_search.set_filter(custom_filter)
                 self._notificationmanager.pop_loading()
                 return
 
@@ -1111,7 +1119,9 @@ class GrlTrackerWrapper(GObject.GObject):
                 return
 
             if not media:
-                self._songs_search.set_filter_func(songs_filter)
+                custom_filter = Gtk.CustomFilter()
+                custom_filter.set_filter_func(songs_filter)
+                self._songs_search.set_filter(custom_filter)
                 self._notificationmanager.pop_loading()
                 return
 
diff --git a/gnomemusic/player.py b/gnomemusic/player.py
index 372d30103..4b0bdf162 100644
--- a/gnomemusic/player.py
+++ b/gnomemusic/player.py
@@ -30,12 +30,11 @@ import typing
 
 import gi
 gi.require_version('GstPbutils', '1.0')
-from gi.repository import GObject, GstPbutils
+from gi.repository import GObject, GstPbutils, Gtk
 
 from gnomemusic.coresong import CoreSong
 from gnomemusic.gstplayer import GstPlayer, Playback
 from gnomemusic.widgets.songwidget import SongWidget
-import gnomemusic.utils as utils
 
 
 class RepeatMode(Enum):
@@ -277,16 +276,18 @@ class PlayerPlaylist(GObject.GObject):
         self._model_recent.set_offset(offset)
 
     def _on_repeat_mode_changed(self, klass, param):
-        def _shuffle_sort(song_a, song_b):
+        def _shuffle_sort(song_a, song_b, data=None):
             return song_a.shuffle_pos < song_b.shuffle_pos
 
         if self.props.repeat_mode == RepeatMode.SHUFFLE:
             for idx, coresong in enumerate(self._model):
                 coresong.update_shuffle_pos()
-            self._model.set_sort_func(
-                utils.wrap_list_store_sort_func(_shuffle_sort))
+
+            songs_sorter = Gtk.CustomSorter()
+            songs_sorter.set_sort_func(_shuffle_sort)
+            self._model.set_sorter(songs_sorter)
         elif self.props.repeat_mode in [RepeatMode.NONE, RepeatMode.ALL]:
-            self._model.set_sort_func(None)
+            self._model.set_sorter(None)
 
     def _validate_song(self, coresong):
         # Song is being processed or has already been processed.
diff --git a/gnomemusic/utils.py b/gnomemusic/utils.py
index 7a87bb1ff..7cb98a433 100644
--- a/gnomemusic/utils.py
+++ b/gnomemusic/utils.py
@@ -23,12 +23,12 @@
 # delete this exception statement from your version.
 
 from enum import Enum, IntEnum
+from typing import List
 import re
 import unicodedata
 
 from gettext import gettext as _
-from gi.repository import Gio, GLib
-from gi._gi import pygobject_new_full
+from gi.repository import Gio, GLib, Gtk
 
 from gnomemusic.musiclogger import MusicLogger
 
@@ -55,7 +55,7 @@ class CoreObjectType(Enum):
 
 class DefaultIconType(Enum):
     ALBUM = "folder-music-symbolic"
-    ARTIST = "avatar-default-symbolic"
+    ARTIST = "music-artist-symbolic"
 
 
 class SongStateIcon(Enum):
@@ -185,7 +185,7 @@ def normalize_caseless(text):
     return unicodedata.normalize("NFKD", text.casefold())
 
 
-def natural_sort_names(name_a, name_b):
+def natural_sort_names(name_a: str, name_b: str) -> int:
     """Natural order comparison of two strings.
 
     A natural order is an alphabetical order which takes into account
@@ -196,22 +196,18 @@ def natural_sort_names(name_a, name_b):
 
     :param str name_a: first string to compare
     :param str name_b: second string to compare
-    :returns: False if name_a should be before name_b. True otherwise.
-    :rtype: boolean
+    :returns: Gtk Ordering
+    :rtype: int
     """
-    def _extract_numbers(text):
+    def _extract_numbers(text: str) -> List[str]:
         return [int(tmp) if tmp.isdigit() else tmp
                 for tmp in re.split(r"(\d+)", normalize_caseless(text))]
 
-    return _extract_numbers(name_b) < _extract_numbers(name_a)
-
-
-def wrap_list_store_sort_func(func):
-    """PyGI wrapper for SortListModel set_sort_func.
-    """
-    def wrap(a, b, *user_data):
-        a = pygobject_new_full(a, False)
-        b = pygobject_new_full(b, False)
-        return func(a, b, *user_data)
-
-    return wrap
+    extract_a = _extract_numbers(name_a)
+    extract_b = _extract_numbers(name_b)
+    if extract_a < extract_b:
+        return Gtk.Ordering.SMALLER
+    elif extract_a > extract_b:
+        return Gtk.Ordering.LARGER
+    else:
+        return Gtk.Ordering.EQUAL
diff --git a/gnomemusic/views/albumsview.py b/gnomemusic/views/albumsview.py
index 507d75bc6..66a5a0baa 100644
--- a/gnomemusic/views/albumsview.py
+++ b/gnomemusic/views/albumsview.py
@@ -23,16 +23,16 @@
 # delete this exception statement from your version.
 
 from __future__ import annotations
-import math
 import typing
 
 from gettext import gettext as _
-from gi.repository import Gdk, GLib, GObject, Gtk
+from gi.repository import GObject, Gtk
+from typing import Dict, List
 
 from gnomemusic.widgets.headerbar import HeaderBar
-from gnomemusic.widgets.albumcover import AlbumCover
 from gnomemusic.widgets.albumwidget import AlbumWidget
 if typing.TYPE_CHECKING:
+    from gnomemusic.application import Application
     from gnomemusic.corealbum import CoreAlbum
 
 
@@ -55,10 +55,9 @@ class AlbumsView(Gtk.Stack):
 
     _album_scrolled_window = Gtk.Template.Child()
     _scrolled_window = Gtk.Template.Child()
-    _flowbox = Gtk.Template.Child()
-    _flowbox_long_press = Gtk.Template.Child()
+    _gridview = Gtk.Template.Child()
 
-    def __init__(self, application):
+    def __init__(self, application: Application) -> None:
         """Initialize AlbumsView
 
         :param application: The Application object
@@ -70,17 +69,31 @@ class AlbumsView(Gtk.Stack):
         self._application = application
         self._window = application.props.window
         self._headerbar = self._window._headerbar
-        self._adjustment_timeout_id = 0
-        self._viewport = self._scrolled_window.get_child()
-        self._widget_counter = 1
-        self._ctrl_hold = False
 
-        model = self._application.props.coremodel.props.albums_sort
-        self._flowbox.bind_model(model, self._create_album_cover)
-        self._flowbox.set_hadjustment(self._scrolled_window.get_hadjustment())
-        self._flowbox.set_vadjustment(self._scrolled_window.get_vadjustment())
-        self._flowbox.connect("child-activated", self._on_child_activated)
+        self._list_item_bindings: Dict[
+            Gtk.ListItem, List[GObject.Binding]] = {}
+        self._list_item_star_controllers: Dict[
+            Gtk.ListItem, List[GObject.Binding]] = {}
 
+        list_item_factory = Gtk.SignalListItemFactory()
+        list_item_factory.connect("setup", self._setup_list_item)
+        list_item_factory.connect("bind", self._bind_list_item)
+
+        self._gridview.props.factory = list_item_factory
+
+        self._selection_model = Gtk.MultiSelection.new(
+            self._application.props.coremodel.props.albums_sort)
+        self._gridview.props.model = self._selection_model
+
+        self._gridview.connect("activate", self._on_album_activated)
+
+        self.bind_property(
+            "selection-mode", self._gridview, "single-click-activate",
+            GObject.BindingFlags.SYNC_CREATE
+            | GObject.BindingFlags.INVERT_BOOLEAN)
+        self.bind_property(
+            "selection-mode", self._gridview, "enable-rubberband",
+            GObject.BindingFlags.SYNC_CREATE)
         self.bind_property(
             "selection-mode", self._window, "selection-mode",
             GObject.BindingFlags.DEFAULT)
@@ -93,67 +106,12 @@ class AlbumsView(Gtk.Stack):
             "selection-mode", self, "selection-mode",
             GObject.BindingFlags.BIDIRECTIONAL)
 
-        self._album_scrolled_window.add(self._album_widget)
+        viewport = self._album_scrolled_window.get_first_child()
+        viewport.set_child(self._album_widget)
 
         self.connect(
             "notify::search-mode-active", self._on_search_mode_changed)
 
-        self._scrolled_window.props.vadjustment.connect(
-            "value-changed", self._on_vadjustment_changed)
-        self._scrolled_window.props.vadjustment.connect(
-            "changed", self._on_vadjustment_changed)
-
-    def _on_vadjustment_changed(self, adjustment):
-        if self._adjustment_timeout_id != 0:
-            GLib.source_remove(self._adjustment_timeout_id)
-            self._adjustment_timeout_id = 0
-
-        self._adjustment_timeout_id = GLib.timeout_add(
-            200, self._retrieve_covers, adjustment.props.value,
-            priority=GLib.PRIORITY_DEFAULT - 10)
-
-    def _retrieve_covers(self, old_adjustment):
-        adjustment = self._scrolled_window.props.vadjustment.props.value
-
-        if old_adjustment != adjustment:
-            return GLib.SOURCE_CONTINUE
-
-        first_cover = self._flowbox.get_child_at_index(0)
-        if first_cover is None:
-            return GLib.SOURCE_REMOVE
-
-        cover_size, _ = first_cover.get_allocated_size()
-        if cover_size.width == 0 or cover_size.height == 0:
-            return GLib.SOURCE_REMOVE
-
-        viewport_size, _ = self._viewport.get_allocated_size()
-
-        h_space = self._flowbox.get_column_spacing()
-        v_space = self._flowbox.get_row_spacing()
-        nr_cols = (
-            (viewport_size.width + h_space) // (cover_size.width + h_space))
-
-        top_left_cover = self._flowbox.get_child_at_index(
-            nr_cols * (adjustment // (cover_size.height + v_space)))
-
-        covers_col = math.ceil(viewport_size.width / cover_size.width)
-        covers_row = math.ceil(viewport_size.height / cover_size.height)
-
-        children = self._flowbox.get_children()
-        retrieve_list = []
-        for i, albumcover in enumerate(children):
-            if top_left_cover == albumcover:
-                retrieve_covers = covers_row * covers_col
-                retrieve_list = children[i:i + retrieve_covers]
-                break
-
-        for albumcover in retrieve_list:
-            albumcover.retrieve()
-
-        self._adjustment_timeout_id = 0
-
-        return GLib.SOURCE_REMOVE
-
     def _on_selection_mode_changed(self, widget, data=None):
         selection_mode = self._window.props.selection_mode
         if (selection_mode == self.props.selection_mode
@@ -163,7 +121,6 @@ class AlbumsView(Gtk.Stack):
         self.props.selection_mode = selection_mode
         if not self.props.selection_mode:
             self.deselect_all()
-            self._flowbox.props.selection_mode = Gtk.SelectionMode.NONE
 
     def _on_search_mode_changed(self, klass, param):
         if (not self.props.search_mode_active
@@ -171,32 +128,17 @@ class AlbumsView(Gtk.Stack):
                 and self.get_visible_child_name() == "widget"):
             self._set_album_headerbar(self._album_widget.props.corealbum)
 
-    def _create_album_cover(self, corealbum: CoreAlbum) -> AlbumCover:
-        album_cover = AlbumCover(corealbum)
-
-        self.bind_property(
-            "selection-mode", album_cover, "selection-mode",
-            GObject.BindingFlags.SYNC_CREATE
-            | GObject.BindingFlags.BIDIRECTIONAL)
-
-        # NOTE: Adding SYNC_CREATE here will trigger all the nested
-        # models to be created. This will slow down initial start,
-        # but will improve initial 'select all' speed.
-        album_cover.bind_property(
-            "selected", corealbum, "selected",
-            GObject.BindingFlags.BIDIRECTIONAL)
-
-        GLib.timeout_add(
-            self._widget_counter * 250, album_cover.retrieve,
-            priority=GLib.PRIORITY_LOW)
-        self._widget_counter = self._widget_counter + 1
-
-        return album_cover
-
     def _back_button_clicked(self, widget, data=None):
         self._headerbar.state = HeaderBar.State.MAIN
         self.props.visible_child = self._scrolled_window
 
+    def _on_album_activated(self, widget, position):
+        corealbum = widget.props.model[position]
+
+        self._album_widget.props.corealbum = corealbum
+        self._set_album_headerbar(corealbum)
+        self.props.visible_child = self._album_scrolled_window
+
     def _on_child_activated(self, widget, child, user_data=None):
         corealbum = child.props.corealbum
         if self.props.selection_mode:
@@ -208,48 +150,10 @@ class AlbumsView(Gtk.Stack):
         self._set_album_headerbar(corealbum)
         self.set_visible_child_name("widget")
 
-    def _set_album_headerbar(self, corealbum):
+    def _set_album_headerbar(self, corealbum: CoreAlbum) -> None:
         self._headerbar.props.state = HeaderBar.State.CHILD
-        self._headerbar.props.title = corealbum.props.title
-        self._headerbar.props.subtitle = corealbum.props.artist
-
-    @Gtk.Template.Callback()
-    def _on_flowbox_press_begin(self, gesture, sequence):
-        event = gesture.get_last_event(sequence)
-        ok, state = event.get_state()
-        if ((ok is True
-             and state == Gdk.ModifierType.CONTROL_MASK)
-                or self.props.selection_mode is True):
-            self._flowbox.props.selection_mode = Gtk.SelectionMode.MULTIPLE
-            if state == Gdk.ModifierType.CONTROL_MASK:
-                self._ctrl_hold = True
-
-    @Gtk.Template.Callback()
-    def _on_flowbox_press_cancel(self, gesture, sequence):
-        self._flowbox.props.selection_mode = Gtk.SelectionMode.NONE
-
-    @Gtk.Template.Callback()
-    def _on_selected_children_changed(self, flowbox):
-        if self._flowbox.props.selection_mode == Gtk.SelectionMode.NONE:
-            return
-
-        if self.props.selection_mode is False:
-            self.props.selection_mode = True
-
-        rubberband_selection = len(self._flowbox.get_selected_children()) > 1
-        with self._application.props.coreselection.freeze_notify():
-            if (rubberband_selection
-                    and not self._ctrl_hold):
-                self.deselect_all()
-            for child in self._flowbox.get_selected_children():
-                if (self._ctrl_hold is True
-                        or not rubberband_selection):
-                    child.props.selected = not child.props.selected
-                else:
-                    child.props.selected = True
-
-        self._ctrl_hold = False
-        self._flowbox.props.selection_mode = Gtk.SelectionMode.NONE
+        self._headerbar.set_label_title(
+            corealbum.props.title, corealbum.props.artist)
 
     def _toggle_all_selection(self, selected):
         """Selects or deselects all items.
@@ -261,11 +165,90 @@ class AlbumsView(Gtk.Stack):
                 else:
                     self._album_widget.deselect_all()
             else:
-                for child in self._flowbox.get_children():
-                    child.props.selected = selected
+                if selected:
+                    self._selection_model.select_all()
+                else:
+                    self._selection_model.unselect_all()
 
     def select_all(self):
         self._toggle_all_selection(True)
 
     def deselect_all(self):
         self._toggle_all_selection(False)
+
+    def _setup_list_item(
+            self, factory: Gtk.SignalListItemFactory,
+            list_item: Gtk.ListItem) -> None:
+        builder = Gtk.Builder.new_from_resource(
+            "/org/gnome/Music/ui/AlbumCoverListItem.ui")
+        list_item.props.child = builder.get_object("_album_cover")
+
+        self.bind_property(
+            "selection-mode", list_item, "selectable",
+            GObject.BindingFlags.SYNC_CREATE)
+        self.bind_property(
+            "selection-mode", list_item, "activatable",
+            GObject.BindingFlags.SYNC_CREATE
+            | GObject.BindingFlags.INVERT_BOOLEAN)
+
+    def _bind_list_item(
+            self, factory: Gtk.SignalListItemFactory,
+            list_item: Gtk.ListItem) -> None:
+        album_cover = list_item.props.child
+        corealbum = list_item.props.item
+
+        art_stack = album_cover.get_first_child().get_first_child()
+        check = art_stack.get_next_sibling()
+        album_label = album_cover.get_first_child().get_next_sibling()
+        artist_label = album_label.get_next_sibling()
+
+        b1 = corealbum.bind_property(
+            "corealbum", art_stack, "coreobject",
+            GObject.BindingFlags.SYNC_CREATE)
+        b2 = corealbum.bind_property(
+            "title", album_label, "label", GObject.BindingFlags.SYNC_CREATE)
+        b3 = corealbum.bind_property(
+            "artist", artist_label, "label", GObject.BindingFlags.SYNC_CREATE)
+
+        b4 = list_item.bind_property(
+            "selected", corealbum, "selected",
+            GObject.BindingFlags.SYNC_CREATE)
+        b5 = list_item.bind_property(
+            "selected", check, "active", GObject.BindingFlags.SYNC_CREATE)
+        b6 = self.bind_property(
+            "selection-mode", check, "visible",
+            GObject.BindingFlags.SYNC_CREATE)
+
+        def on_activated(widget, value):
+            if check.props.active:
+                self._selection_model.select_item(
+                    list_item.get_position(), False)
+            else:
+                self._selection_model.unselect_item(
+                    list_item.get_position())
+
+        # the listitem selected property is read-only.
+        # It cannot be bound from the check active property.
+        # It is necessary to update the selection model in order
+        # to update it.
+        check.connect("notify::active", on_activated)
+
+        self._list_item_bindings[list_item] = [b1, b2, b3, b4, b5, b6]
+
+    def _unbind_list_item(
+            self, factory: Gtk.SignalListItemFactory,
+            list_item: Gtk.ListItem) -> None:
+        bindings = self._list_item_bindings.pop(list_item)
+        [binding.unbind() for binding in bindings]
+
+        album_cover = list_item.props.child
+
+        art_stack = album_cover.get_first_child().get_first_child()
+        check = art_stack.get_next_sibling()
+
+        signal_id, detail_id = GObject.signal_parse_name(
+            "notify::active", check, True)
+        handler_id = GObject.signal_handler_find(
+            check, GObject.SignalMatchType.ID, signal_id, detail_id, None, 0,
+            0)
+        check.disconnect(handler_id)
diff --git a/gnomemusic/views/artistsview.py b/gnomemusic/views/artistsview.py
index 16a736b8a..b4c928099 100644
--- a/gnomemusic/views/artistsview.py
+++ b/gnomemusic/views/artistsview.py
@@ -22,11 +22,16 @@
 # code, but you are not obligated to do so.  If you do not wish to do so,
 # delete this exception statement from your version.
 
+from __future__ import annotations
+import typing
+
 from gettext import gettext as _
-from gi.repository import GLib, GObject, Gtk
+from gi.repository import GObject, Gtk
 
 from gnomemusic.widgets.artistalbumswidget import ArtistAlbumsWidget
 from gnomemusic.widgets.artisttile import ArtistTile
+if typing.TYPE_CHECKING:
+    from gnomemusic.application import Application
 
 
 @Gtk.Template(resource_path="/org/gnome/Music/ui/ArtistsView.ui")
@@ -45,11 +50,10 @@ class ArtistsView(Gtk.Paned):
     title = GObject.Property(
         type=str, default=_("Artists"), flags=GObject.ParamFlags.READABLE)
 
-    _artist_container = Gtk.Template.Child()
     _artist_view = Gtk.Template.Child()
     _sidebar = Gtk.Template.Child()
 
-    def __init__(self, application):
+    def __init__(self, application: Application) -> None:
         """Initialize
 
         :param GtkApplication application: The application object
@@ -58,10 +62,6 @@ class ArtistsView(Gtk.Paned):
 
         self.props.name = "artists"
 
-        self._application = application
-        self._loaded_artists = []
-        self._widget_counter = 1
-
         # This indicates if the current list has been empty and has
         # had no user interaction since.
         self._untouched_list = True
@@ -70,104 +70,58 @@ class ArtistsView(Gtk.Paned):
         self._coremodel = application.props.coremodel
         self._model = self._coremodel.props.artists_sort
 
-        self._sidebar.bind_model(self._model, self._create_widget)
+        self._selection_model = Gtk.SingleSelection.new(self._model)
+        self._sidebar.props.model = self._selection_model
+        artist_item_factory = Gtk.SignalListItemFactory()
+        artist_item_factory.connect("setup", self._on_list_view_setup)
+        artist_item_factory.connect("bind", self._on_list_view_bind)
+        self._sidebar.props.factory = artist_item_factory
 
-        self._model.connect_after(
-            "items-changed", self._on_model_items_changed)
-        self._on_model_items_changed(self._model, 0, 0, 0)
-
-        self._selection_mode = False
+        self._artist_album = ArtistAlbumsWidget(application)
+        self._artist_view.props.child = self._artist_album
 
+        self.bind_property(
+            "selection-mode", self._artist_album, "selection-mode",
+            GObject.BindingFlags.SYNC_CREATE
+            | GObject.BindingFlags.BIDIRECTIONAL)
         self._window.bind_property(
             "selection-mode", self, "selection-mode",
             GObject.BindingFlags.BIDIRECTIONAL)
 
-    def _create_widget(self, coreartist):
-        row = ArtistTile(coreartist)
-        row.props.text = coreartist.props.artist
-
-        GLib.timeout_add(
-            self._widget_counter * 300, row.retrieve,
-            priority=GLib.PRIORITY_LOW)
-        self._widget_counter = self._widget_counter + 1
+        self._selection_model.connect_after(
+            "items-changed", self._on_model_items_changed)
+        self._on_model_items_changed(self._selection_model, 0, 0, 0)
 
-        return row
+        self._selection_mode = False
 
-    def _on_model_items_changed(self, model, position, removed, added):
+    def _on_list_view_setup(
+            self, factory: Gtk.SignalListItemFactory,
+            list_item: Gtk.ListItem) -> None:
+        list_item.props.child = ArtistTile()
+
+    def _on_list_view_bind(
+            self, factory: Gtk.SignalListItemFactory,
+            list_item: Gtk.ListItem) -> None:
+        coreartist = list_item.props.item
+        artist_tile = list_item.props.child
+        artist_tile.props.coreartist = coreartist
+
+    def _on_model_items_changed(
+            self, model: Gtk.SingleSelection, position: int, removed: int,
+            added: int) -> None:
         if model.get_n_items() == 0:
             self._untouched_list = True
-            return
+            # FIXME: Add an empty state.
         elif self._untouched_list is True:
-            first_row = self._sidebar.get_row_at_index(0)
-            if first_row is None:
-                return
-
-            self._sidebar.select_row(first_row)
-            self._on_artist_activated(self._sidebar, first_row, True)
-            return
-
-        if removed == 0:
-            return
-
-        removed_artist = None
-        artists = [coreartist.props.artist for coreartist in model]
-        for artist in self._loaded_artists:
-            if artist not in artists:
-                removed_artist = artist
-                break
-
-        if removed_artist is None:
-            return
-
-        self._loaded_artists.remove(removed_artist)
-        if self._artist_view.get_visible_child_name() == removed_artist:
-            row_next = (self._sidebar.get_row_at_index(position)
-                        or self._sidebar.get_row_at_index(position - 1))
-            if row_next:
-                self._sidebar.select_row(row_next)
-                self._on_artist_activated(self._sidebar, row_next, True)
-
-        removed_artist_page = self._artist_view.get_child_by_name(
-            removed_artist)
-        self._artist_view.remove(removed_artist_page)
+            self._untouched_list = False
+            self._sidebar.emit("activate", 0)
 
     @Gtk.Template.Callback()
-    def _on_artist_activated(self, sidebar, row, data=None, untouched=False):
+    def _on_artist_activated(
+            self, sidebar: Gtk.ListView, position: int) -> None:
         """Initializes new artist album widgets"""
-        # On application start the first row of ArtistView is activated
-        # to show an intial artist. When this happens while any of the
-        # views are in selection mode, this artist will be incorrectly
-        # selected.
-        # When selecting items check that the current visible view is
-        # ArtistsView, to circumvent this issue.
-        if (self.props.selection_mode
-                and self._window.props.active_view is self):
-            return
-
-        if untouched is False:
-            self._untouched_list = False
-
-        # Prepare a new artist_albums_widget here
-        coreartist = row.props.coreartist
-        if coreartist.props.artist in self._loaded_artists:
-            scroll_vadjustment = self._artist_container.props.vadjustment
-            scroll_vadjustment.props.value = 0.
-            self._artist_view.set_visible_child_name(coreartist.props.artist)
-            return
-
-        artist_albums = ArtistAlbumsWidget(coreartist, self._application)
-
-        self.bind_property(
-            "selection-mode", artist_albums, "selection-mode",
-            GObject.BindingFlags.SYNC_CREATE
-            | GObject.BindingFlags.BIDIRECTIONAL)
-
-        self._artist_view.add_named(artist_albums, coreartist.props.artist)
-        scroll_vadjustment = self._artist_container.props.vadjustment
-        scroll_vadjustment.props.value = 0.
-        self._artist_view.set_visible_child(artist_albums)
-
-        self._loaded_artists.append(coreartist.props.artist)
+        coreartist = self._selection_model.get_item(position)
+        self._artist_album.props.coreartist = coreartist
 
     @GObject.Property(type=bool, default=False)
     def selection_mode(self):
@@ -195,12 +149,12 @@ class ArtistsView(Gtk.Paned):
 
     def select_all(self) -> None:
         """Select all items"""
-        artist_tile = self._sidebar.get_selected_row()
-        if artist_tile:
-            artist_tile.props.coreartist.props.selected = True
+        coreartist = self._selection_model.get_selected_item()
+        if coreartist:
+            coreartist.props.selected = True
 
     def deselect_all(self) -> None:
         """Deselect all items"""
-        artist_tile = self._sidebar.get_selected_row()
-        if artist_tile:
-            artist_tile.props.coreartist.props.selected = False
+        coreartist = self._selection_model.get_selected_item()
+        if coreartist:
+            coreartist.props.selected = False
diff --git a/gnomemusic/views/emptyview.py b/gnomemusic/views/emptyview.py
index 737dbc67d..e1dc068a2 100644
--- a/gnomemusic/views/emptyview.py
+++ b/gnomemusic/views/emptyview.py
@@ -76,12 +76,12 @@ class EmptyView(Gtk.Stack):
         folder_text = _("The contents of your {} will appear here.")
         self._content_text = folder_text.format(href_text)
 
-        # Hack to get to HdyClamp, so it can be hidden for the
+        # Hack to get to AdwClamp, so it can be hidden for the
         # initial state.
-        child_of_child = self._status_page.get_child().get_child()
-        self._hdy_clamp = child_of_child.get_child().get_children()[0]
+        child_of_child = self._status_page.get_first_child().get_first_child()
+        self._adw_clamp = child_of_child.get_first_child().get_first_child()
 
-        self._status_page.add(self._initial_state)
+        self._status_page.set_child(self._initial_state)
 
         self._state = EmptyView.State.EMPTY
 
@@ -102,7 +102,7 @@ class EmptyView(Gtk.Stack):
         """
         self._state = value
 
-        self._hdy_clamp.props.visible = True
+        self._adw_clamp.props.visible = True
         self._initial_state.props.visible = False
 
         if self._state == EmptyView.State.EMPTY:
@@ -115,7 +115,7 @@ class EmptyView(Gtk.Stack):
             self._set_tracker_outdated_state()
 
     def _set_empty_state(self):
-        self._hdy_clamp.props.visible = False
+        self._adw_clamp.props.visible = False
         self._initial_state.props.visible = True
 
         self._description_label.props.label = self._content_text
diff --git a/gnomemusic/views/playlistsview.py b/gnomemusic/views/playlistsview.py
index 4c1a2f782..58c107e7c 100644
--- a/gnomemusic/views/playlistsview.py
+++ b/gnomemusic/views/playlistsview.py
@@ -63,7 +63,7 @@ class PlaylistsView(Gtk.Paned):
         self._untouched_list = True
 
         self._playlist_widget = PlaylistsWidget(application, self)
-        self.add(self._playlist_widget)
+        self.props.end_child = self._playlist_widget
 
         self._sidebar.set_header_func(self._sidebar_header_func)
         self._sidebar.bind_model(self._model, self._add_playlist_to_sidebar)
diff --git a/gnomemusic/views/searchview.py b/gnomemusic/views/searchview.py
index e9d2cca23..617e39405 100644
--- a/gnomemusic/views/searchview.py
+++ b/gnomemusic/views/searchview.py
@@ -28,9 +28,10 @@ from gettext import gettext as _
 from typing import Optional
 import typing
 
-from gi.repository import Gdk, GObject, Gtk
+from gi.repository import GObject, Gtk
 
 from gnomemusic.search import Search
+from gnomemusic.utils import ArtSize
 from gnomemusic.widgets.albumcover import AlbumCover
 from gnomemusic.widgets.albumwidget import AlbumWidget
 from gnomemusic.widgets.headerbar import HeaderBar
@@ -94,41 +95,32 @@ class SearchView(Gtk.Stack):
         self._application = application
         self._coremodel = application.props.coremodel
         self._model = self._coremodel.props.songs_search
-        self._album_model = self._coremodel.props.albums_search
-        self._album_filter = self._coremodel.props.albums_search_filter
-        self._album_filter.set_filter_func(
-            self._core_filter, self._album_model, 12)
+        self._player = self._application.props.player
+        self._window = application.props.window
+        self._headerbar = self._window._headerbar
 
+        self._album_model = self._coremodel.props.albums_search
         self._artist_model = self._coremodel.props.artists_search
-        self._artist_filter = self._coremodel.props.artists_search_filter
-        self._artist_filter.set_filter_func(
-            self._core_filter, self._artist_model, 6)
+
+        self._album_slice = Gtk.SliceListModel.new(self._album_model, 0, 8)
+        self._artist_slice = Gtk.SliceListModel.new(self._artist_model, 0, 6)
 
         self._model.connect_after(
             "items-changed", self._on_model_items_changed)
         self._songs_listbox.bind_model(self._model, self._create_song_widget)
         self._on_model_items_changed(self._model, 0, 0, 0)
 
-        self._album_filter.connect_after(
+        self._album_slice.connect_after(
             "items-changed", self._on_album_model_items_changed)
         self._album_flowbox.bind_model(
-            self._album_filter, self._create_album_cover)
-        self._album_flowbox.connect(
-            "size-allocate", self._on_album_flowbox_size_allocate)
-        self._on_album_model_items_changed(self._album_filter, 0, 0, 0)
+            self._album_slice, self._create_album_cover)
+        self._application.props.window.connect(
+            "notify::default-width", self._on_window_width_change)
 
-        self._artist_filter.connect_after(
+        self._artist_slice.connect_after(
             "items-changed", self._on_artist_model_items_changed)
         self._artist_flowbox.bind_model(
-            self._artist_filter, self._create_artist_widget)
-        self._artist_flowbox.connect(
-            "size-allocate", self._on_artist_flowbox_size_allocate)
-        self._on_artist_model_items_changed(self._artist_filter, 0, 0, 0)
-
-        self._player = self._application.props.player
-
-        self._window = application.props.window
-        self._headerbar = self._window._headerbar
+            self._artist_slice, self._create_artist_widget)
 
         self.connect("notify::selection-mode", self._on_selection_mode_changed)
 
@@ -140,7 +132,8 @@ class SearchView(Gtk.Stack):
         self._album_widget.bind_property(
             "selection-mode", self, "selection-mode",
             GObject.BindingFlags.BIDIRECTIONAL)
-        self._scrolled_album_widget.add(self._album_widget)
+        viewport = self._scrolled_album_widget.get_first_child()
+        viewport.set_child(self._album_widget)
 
         self._scrolled_artist_window: Optional[Gtk.ScrolledWindow] = None
 
@@ -206,11 +199,6 @@ class SearchView(Gtk.Stack):
         nr_albums = self._album_model.get_n_items()
         self._view_all_albums.props.visible = (nr_albums > model.get_n_items())
 
-        def set_child_visible(child):
-            child.props.visible = True
-
-        self._album_flowbox.foreach(set_child_visible)
-
     def _on_artist_model_items_changed(self, model, position, removed, added):
         items_found = model.get_n_items() > 0
         self._artist_header.props.visible = items_found
@@ -221,11 +209,6 @@ class SearchView(Gtk.Stack):
         self._view_all_artists.props.visible = (
             nr_artists > model.get_n_items())
 
-        def set_child_visible(child):
-            child.props.visible = True
-
-        self._artist_flowbox.foreach(set_child_visible)
-
     def _on_model_items_changed(self, model, position, removed, added):
         items_found = model.get_n_items() > 0
         self._songs_header.props.visible = items_found
@@ -247,106 +230,26 @@ class SearchView(Gtk.Stack):
     @Gtk.Template.Callback()
     def _song_activated(
             self, list_box: Gtk.ListBox, song_widget: SongWidget) -> bool:
-        if song_widget.props.select_click:
-            song_widget.props.select_click = False
-            return True
-
-        event = Gtk.get_current_event()
-        (_, state) = event.get_state()
-        mod_mask = Gtk.accelerator_get_default_mod_mask()
-        if ((state & mod_mask) == Gdk.ModifierType.CONTROL_MASK
-                and not self.props.selection_mode):
-            self.props.selection_mode = True
-            song_widget.props.select_click = True
-            song_widget.props.coresong.props.selected = True
-            return True
-
+        coresong = song_widget.props.coresong
         if self.props.selection_mode:
-            song_widget.props.select_click = True
-            selection_state = song_widget.props.selected
+            selection_state = coresong.props.selected
             song_widget.props.selected = not selection_state
-            song_widget.props.coresong.props.selected = not selection_state
+            coresong.props.selected = not selection_state
             return True
 
-        (_, button) = event.get_button()
-        if (button == Gdk.BUTTON_PRIMARY
-                and not self.props.selection_mode):
-            coresong = song_widget.props.coresong
-            self._coremodel.props.active_core_object = coresong
-            self._player.play(coresong)
+        self._coremodel.props.active_core_object = coresong
+        self._player.play(coresong)
 
         return True
 
-    def _on_album_flowbox_size_allocate(self, widget, allocation, data=None):
-        nb_children = self._album_filter.get_n_items()
-        if nb_children == 0:
-            return
-
-        first_child = self._album_flowbox.get_child_at_index(0)
-        child_height = first_child.get_allocation().height
-        if allocation.height > 2.5 * child_height:
-            for i in range(nb_children - 1, -1, -1):
-                child = self._album_flowbox.get_child_at_index(i)
-                if child.props.visible is True:
-                    child.props.visible = False
-                    return
-
-        children_hidden = False
-        for idx in range(nb_children):
-            child = self._album_flowbox.get_child_at_index(idx)
-            if not child.props.visible:
-                children_hidden = True
-                break
-        if children_hidden is False:
-            return
-
-        last_visible_child = self._album_flowbox.get_child_at_index(idx - 1)
-        first_row_last = self._album_flowbox.get_child_at_index((idx - 1) // 2)
-        second_row_pos = last_visible_child.get_allocation().x
-        first_row_pos = first_row_last.get_allocation().x
-        child_width = last_visible_child.get_allocation().width
-        nb_children_to_add = (first_row_pos - second_row_pos) // child_width
-        nb_children_to_add = min(nb_children_to_add + idx, nb_children)
-        for i in range(idx, nb_children_to_add):
-            child = self._album_flowbox.get_child_at_index(i)
-            child.props.visible = True
-
-    def _on_artist_flowbox_size_allocate(self, widget, allocation, data=None):
-        nb_children = self._artist_filter.get_n_items()
-        if nb_children == 0:
-            return
-
-        first_child = self._artist_flowbox.get_child_at_index(0)
-        # FIXME: It looks like it is possible that the widget is not
-        # yet created, resulting in a crash with first_child being
-        # None.
-        # Look for a cleaner solution.
-        if first_child is None:
-            return
-
-        child_height = first_child.get_allocation().height
-        if allocation.height > 1.5 * child_height:
-            for i in range(nb_children - 1, -1, -1):
-                child = self._artist_flowbox.get_child_at_index(i)
-                if child.props.visible is True:
-                    child.props.visible = False
-                    return
-
-        children_hidden = False
-        for idx in range(nb_children):
-            child = self._artist_flowbox.get_child_at_index(idx)
-            if not child.props.visible:
-                children_hidden = True
-                break
-        if children_hidden is False:
-            return
+    def _on_window_width_change(self, widget, value):
+        allocation = self._album_flowbox.get_allocation()
+        # FIXME: Just a bit of guesswork here.
+        padding = 32
+        items_per_row = allocation.width // (ArtSize.MEDIUM.width + padding)
 
-        last_child = self._artist_flowbox.get_child_at_index(idx - 1)
-        last_child_allocation = last_child.get_allocation()
-        child_width = last_child_allocation.width
-        if (last_child_allocation.x + 2 * child_width) < allocation.width:
-            child = self._artist_flowbox.get_child_at_index(idx)
-            child.props.visible = True
+        self._album_slice.props.size = 2 * items_per_row
+        self._artist_slice.props.size = items_per_row
 
     @Gtk.Template.Callback()
     def _on_album_activated(self, widget, child, user_data=None):
@@ -360,8 +263,8 @@ class SearchView(Gtk.Stack):
 
         self.props.state = SearchView.State.ALBUM
         self._headerbar.props.state = HeaderBar.State.SEARCH
-        self._headerbar.props.title = corealbum.props.title
-        self._headerbar.props.subtitle = corealbum.props.artist
+        self._headerbar.set_label_title(
+            corealbum.props.title, corealbum.props.artist)
 
         self.set_visible_child(self._scrolled_album_widget)
         self.props.search_mode_active = False
@@ -372,15 +275,15 @@ class SearchView(Gtk.Stack):
         if self.props.selection_mode:
             return
 
-        artist_albums_widget = ArtistAlbumsWidget(
-            coreartist, self._application)
+        artist_albums_widget = ArtistAlbumsWidget(self._application)
+        artist_albums_widget.props.coreartist = coreartist
         # FIXME: Recreating a view here. Alternate solution is used
         # in AlbumsView: one view created and an update function.
         # Settle on one design.
         self._scrolled_artist_window = Gtk.ScrolledWindow()
-        self._scrolled_artist_window.add(artist_albums_widget)
+        self._scrolled_artist_window.props.child = artist_albums_widget
         self._scrolled_artist_window.props.visible = True
-        self.add(self._scrolled_artist_window)
+        self.add_child(self._scrolled_artist_window)
         artist_albums_widget.show()
 
         self.bind_property(
@@ -389,18 +292,16 @@ class SearchView(Gtk.Stack):
 
         self.props.state = SearchView.State.ARTIST
         self._headerbar.props.state = HeaderBar.State.SEARCH
-        self._headerbar.props.title = coreartist.props.artist
-        self._headerbar.props.subtitle = None
+        self._headerbar.set_label_title(coreartist.props.artist, "")
 
         self.set_visible_child(self._scrolled_artist_window)
         self.props.search_mode_active = False
 
     @Gtk.Template.Callback()
-    def _on_all_artists_clicked(self, widget, event, user_data=None):
+    def _on_all_artists_clicked(self, widget, user_data=None):
         self.props.state = SearchView.State.ALL_ARTISTS
         self._headerbar.props.state = HeaderBar.State.SEARCH
-        self._headerbar.props.title = _("Artists Results")
-        self._headerbar.props.subtitle = None
+        self._headerbar.set_label_title(_("Artists Results"), "")
 
         self._artist_all_flowbox.props.visible = True
         self._album_all_flowbox.props.visible = False
@@ -411,11 +312,10 @@ class SearchView(Gtk.Stack):
         self.props.search_mode_active = False
 
     @Gtk.Template.Callback()
-    def _on_all_albums_clicked(self, widget, event, user_data=None):
+    def _on_all_albums_clicked(self, widget, user_data=None):
         self.props.state = SearchView.State.ALL_ALBUMS
         self._headerbar.props.state = HeaderBar.State.SEARCH
-        self._headerbar.props.title = _("Albums Results")
-        self._headerbar.props.subtitle = None
+        self._headerbar.set_label_title(_("Albums Results"), "")
 
         self._artist_all_flowbox.props.visible = False
         self._album_all_flowbox.props.visible = True
@@ -466,8 +366,6 @@ class SearchView(Gtk.Stack):
             return
         elif self.get_visible_child() == self._scrolled_artist_window:
             self.remove(self._scrolled_artist_window)
-            self._scrolled_artist_window.destroy()
-            self._scrolled_artist_window = None
 
         self.set_visible_child(self._search_results)
         self.props.search_mode_active = True
diff --git a/gnomemusic/views/songsview.py b/gnomemusic/views/songsview.py
index ee7752ee4..c6521660c 100644
--- a/gnomemusic/views/songsview.py
+++ b/gnomemusic/views/songsview.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2016 The GNOME Music Developers
+# Copyright 2022 The GNOME Music Developers
 #
 # GNOME Music is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -22,16 +22,21 @@
 # code, but you are not obligated to do so.  If you do not wish to do so,
 # delete this exception statement from your version.
 
+from __future__ import annotations
+import typing
+
 from gettext import gettext as _
-from gi.repository import Gdk, GObject, Gtk, Pango
+from gi.repository import GObject, Gtk
+from typing import Dict, List
 
-from gnomemusic.coresong import CoreSong
-from gnomemusic.utils import SongStateIcon
-from gnomemusic.widgets.starhandlerwidget import StarHandlerWidget
+from gnomemusic.widgets.songwidgetmenu import SongWidgetMenu
+import gnomemusic.utils as utils
+if typing.TYPE_CHECKING:
+    from gnomemusic.application import Application
 
 
 @Gtk.Template(resource_path="/org/gnome/Music/ui/SongsView.ui")
-class SongsView(Gtk.ScrolledWindow):
+class SongsView(Gtk.Box):
     """Main view of all songs sorted artistwise
 
     Consists all songs along with songname, star, length, artist
@@ -46,14 +51,9 @@ class SongsView(Gtk.ScrolledWindow):
     title = GObject.Property(
         type=str, default=_("Songs"), flags=GObject.ParamFlags.READABLE)
 
-    _duration_renderer = Gtk.Template.Child()
-    _now_playing_column = Gtk.Template.Child()
-    _now_playing_cell = Gtk.Template.Child()
-    _songs_ctrlr = Gtk.Template.Child()
-    _songs_view = Gtk.Template.Child()
-    _star_column = Gtk.Template.Child()
+    _listview = Gtk.Template.Child()
 
-    def __init__(self, application):
+    def __init__(self, application: Application) -> None:
         """Initialize
 
         :param GtkApplication window: The application object
@@ -62,50 +62,168 @@ class SongsView(Gtk.ScrolledWindow):
 
         self.props.name = "songs"
 
-        self._window = application.props.window
+        self._application = application
         self._coremodel = application.props.coremodel
+        self._coreselection = application.props.coreselection
+        self._player = application.props.player
+        self._window = application.props.window
 
-        self._iter_to_clean = None
-        self._set_list_renderers()
+        self._list_item_bindings: Dict[
+            Gtk.ListItem, List[GObject.Binding]] = {}
+        self._list_item_star_controllers: Dict[
+            Gtk.ListItem, List[GObject.Binding]] = {}
 
-        self._playlist_model = self._coremodel.props.playlist_sort
-        self._songs_view.props.model = self._coremodel.props.songs_gtkliststore
-        self._model = self._songs_view.props.model
+        self._selection_model = Gtk.MultiSelection.new(
+            self._coremodel.props.songs)
 
-        self._player = application.props.player
-        self._player.connect('song-changed', self._update_model)
+        list_item_factory = Gtk.SignalListItemFactory()
+        list_item_factory.connect("setup", self._setup_list_item)
+        list_item_factory.connect("bind", self._bind_list_item)
+        list_item_factory.connect("unbind", self._unbind_list_item)
+
+        self._listview.props.factory = list_item_factory
+        self._listview.props.model = self._selection_model
+
+        self._listview.connect("activate", self._on_song_activated)
 
         self._selection_mode = False
 
+        self.bind_property(
+            "selection-mode", self._listview, "single-click-activate",
+            GObject.BindingFlags.SYNC_CREATE
+            | GObject.BindingFlags.INVERT_BOOLEAN)
+        self.bind_property(
+            "selection-mode", self._listview, "enable-rubberband",
+            GObject.BindingFlags.SYNC_CREATE)
         self._window.bind_property(
             "selection-mode", self, "selection-mode",
             GObject.BindingFlags.BIDIRECTIONAL)
 
-    def _set_list_renderers(self):
-        self._now_playing_column.set_cell_data_func(
-            self._now_playing_cell, self._on_list_widget_icon_render, None)
+    def _on_song_activated(self, widget, position):
+        coresong = widget.props.model[position]
 
-        self._star_handler = StarHandlerWidget(self, 6)
-        self._star_handler.add_star_renderers(self._star_column)
-
-        attrs = Pango.AttrList()
-        attrs.insert(Pango.AttrFontFeatures.new("tnum=1"))
-        self._duration_renderer.props.attributes = attrs
-
-    def _on_list_widget_icon_render(self, col, cell, model, itr, data):
-        current_song = self._player.props.current_song
-        if current_song is None:
-            return
+        self._coremodel.props.active_core_object = coresong
+        self._player.play(coresong)
 
-        coresong = model[itr][7]
-        if coresong.props.validation == CoreSong.Validation.FAILED:
-            cell.props.icon_name = SongStateIcon.ERROR.value
-            cell.props.visible = True
-        elif coresong.props.grlid == current_song.props.grlid:
-            cell.props.icon_name = SongStateIcon.PLAYING.value
-            cell.props.visible = True
-        else:
-            cell.props.visible = False
+    def _setup_list_item(
+            self, factory: Gtk.SignalListItemFactory,
+            list_item: Gtk.ListItem) -> None:
+        builder = Gtk.Builder.new_from_resource(
+            "/org/gnome/Music/ui/SongListItem.ui")
+        list_item.props.child = builder.get_object("_song_box")
+
+        self.bind_property(
+            "selection-mode", list_item, "selectable",
+            GObject.BindingFlags.SYNC_CREATE)
+        self.bind_property(
+            "selection-mode", list_item, "activatable",
+            GObject.BindingFlags.SYNC_CREATE
+            | GObject.BindingFlags.INVERT_BOOLEAN)
+
+    def _bind_list_item(
+            self, factory: Gtk.SignalListItemFactory,
+            list_item: Gtk.ListItem) -> None:
+        list_row = list_item.props.child
+        coresong = list_item.props.item
+
+        check = list_row.get_first_child()
+        info_box = check.get_next_sibling()
+        duration_label = info_box.get_next_sibling()
+        star_box = duration_label.get_next_sibling()
+        star_image = star_box.get_first_child()
+        title_label = info_box.get_first_child()
+        album_label = title_label.get_next_sibling()
+        artist_label = album_label.get_next_sibling()
+        menu_button = star_box.get_next_sibling()
+
+        def _on_star_toggle(
+                controller: Gtk.GestureClick, n_press: int, x: float,
+                y: float) -> None:
+            controller.set_state(Gtk.EventSequenceState.CLAIMED)
+            coresong.props.favorite = not coresong.props.favorite
+            star_image.props.favorite = coresong.props.favorite
+
+        star_click = Gtk.GestureClick()
+        star_click.props.button = 1
+        star_click.connect("released", _on_star_toggle)
+        star_image.add_controller(star_click)
+
+        def _on_star_enter(
+                controller: Gtk.EventControllerMotion, x: float,
+                y: float) -> None:
+            star_image.props.hover = True
+
+        def _on_star_leave(controller: Gtk.EventControllerMotion) -> None:
+            star_image.props.hover = False
+
+        star_hover = Gtk.EventControllerMotion()
+        star_hover.connect("enter", _on_star_enter)
+        star_hover.connect("leave", _on_star_leave)
+        star_image.add_controller(star_hover)
+
+        menu_button.props.popover = SongWidgetMenu(
+            self._application, list_row, coresong)
+
+        b1 = coresong.bind_property(
+            "title", title_label, "label", GObject.BindingFlags.SYNC_CREATE)
+        b2 = coresong.bind_property(
+            "album", album_label, "label", GObject.BindingFlags.SYNC_CREATE)
+        b3 = coresong.bind_property(
+            "artist", artist_label, "label", GObject.BindingFlags.SYNC_CREATE)
+
+        b4 = coresong.bind_property(
+            "favorite", star_image, "favorite")
+
+        duration_label.props.label = utils.seconds_to_string(
+            coresong.props.duration)
+
+        b5 = list_item.bind_property(
+            "selected", coresong, "selected", GObject.BindingFlags.SYNC_CREATE)
+        b6 = list_item.bind_property(
+            "selected", check, "active", GObject.BindingFlags.SYNC_CREATE)
+        b7 = self.bind_property(
+            "selection-mode", check, "visible",
+            GObject.BindingFlags.SYNC_CREATE)
+
+        def on_activated(widget, value):
+            if check.props.active:
+                self._selection_model.select_item(
+                    list_item.get_position(), False)
+            else:
+                self._selection_model.unselect_item(
+                    list_item.get_position())
+
+        # The listitem selected property is read-only.
+        # It cannot be bound from the check active property.
+        # It is necessary to update the selection model in order
+        # to update it.
+        check.connect("notify::active", on_activated)
+
+        self._list_item_bindings[list_item] = [b1, b2, b3, b4, b5, b6, b7]
+        self._list_item_star_controllers[list_item] = [star_click, star_hover]
+
+    def _unbind_list_item(
+            self, factory: Gtk.SignalListItemFactory,
+            list_item: Gtk.ListItem) -> None:
+        bindings = self._list_item_bindings.pop(list_item)
+        [binding.unbind() for binding in bindings]
+
+        list_row = list_item.props.child
+        check = list_row.get_first_child()
+        info_box = check.get_next_sibling()
+        duration_label = info_box.get_next_sibling()
+        star_box = duration_label.get_next_sibling()
+        star_image = star_box.get_first_child()
+
+        controllers = self._list_item_star_controllers.pop(list_item)
+        [star_image.remove_controller(ctrl) for ctrl in controllers]
+
+        signal_id, detail_id = GObject.signal_parse_name(
+            "notify::active", check, True)
+        handler_id = GObject.signal_handler_find(
+            check, GObject.SignalMatchType.ID, signal_id, detail_id, None, 0,
+            0)
+        check.disconnect(handler_id)
 
     @GObject.Property(type=bool, default=False)
     def selection_mode(self):
@@ -130,101 +248,17 @@ class SongsView(Gtk.ScrolledWindow):
         if self._selection_mode is False:
             self.deselect_all()
 
-        cols = self._songs_view.get_columns()
-        cols[1].props.visible = self._selection_mode
-
-    @Gtk.Template.Callback()
-    def _on_item_activated(self, treeview, path, column):
-        """Action performed when clicking on a song
-
-        clicking on star column toggles favorite
-        clicking on an other columns launches player
-
-        :param Gtk.TreeView treeview: self._songs_view
-        :param Gtk.TreePath path: activated row index
-        :param Gtk.TreeViewColumn column: activated column
+    def _toggle_all_selection(self, selected):
+        """Selects or deselects all items.
         """
-        if self._star_handler.star_renderer_click:
-            self._star_handler.star_renderer_click = False
-            return
-
-        if self.props.selection_mode:
-            return
-
-        itr = self._model.get_iter(path)
-        coresong = self._model[itr][7]
-        self._coremodel.props.active_core_object = coresong
-
-        self._player.play(coresong)
-
-    @Gtk.Template.Callback()
-    def _on_view_clicked(self, gesture, n_press, x, y):
-        """Ctrl+click on self._songs_view triggers selection mode."""
-        _, state = Gtk.get_current_event_state()
-        modifiers = Gtk.accelerator_get_default_mod_mask()
-        if (state & modifiers == Gdk.ModifierType.CONTROL_MASK
-                and not self.props.selection_mode):
-            self.props.selection_mode = True
-
-        # FIXME: In selection mode, star clicks might still trigger
-        # activation.
-        if self.props.selection_mode:
-            path = self._songs_view.get_path_at_pos(x, y)
-            if path is None:
-                return
-
-            iter_ = self._model.get_iter(path[0])
-            new_fav_status = not self._model[iter_][1]
-            self._model[iter_][1] = new_fav_status
-            self._model[iter_][7].props.selected = new_fav_status
-
-    def _update_model(self, player):
-        """Updates model when the song changes
-
-        :param Player player: The main player object
-        """
-        # iter_to_clean is necessary because of a bug in GtkTreeView
-        # See https://gitlab.gnome.org/GNOME/gtk/issues/503
-        if self._iter_to_clean:
-            self._model[self._iter_to_clean][9] = False
-
-        index = self._player.props.position
-        current_coresong = self._playlist_model[index]
-        for idx, liststore in enumerate(self._model):
-            if liststore[7] == current_coresong:
-                break
-
-        iter_ = self._model.get_iter_from_string(str(idx))
-        path = self._model.get_path(iter_)
-        self._model[iter_][9] = True
-        self._songs_view.scroll_to_cell(path, None, True, 0.5, 0.5)
-
-        if self._model[iter_][0] != SongStateIcon.ERROR.value:
-            self._iter_to_clean = iter_.copy()
-
-        return False
-
-    @GObject.Property(
-        type=Gtk.ListStore, default=None, flags=GObject.ParamFlags.READABLE)
-    def model(self):
-        """Get songs view model
-
-        :returns: songs view model
-        :rtype: Gtk.ListStore
-        """
-        return self._model
-
-    def _select(self, value):
-        with self._model.freeze_notify():
-            itr = self._model.iter_children(None)
-            while itr is not None:
-                self._model[itr][7].props.selected = value
-                self._model[itr][1] = value
-
-                itr = self._model.iter_next(itr)
+        with self._coreselection.freeze_notify():
+            if selected:
+                self._selection_model.select_all()
+            else:
+                self._selection_model.unselect_all()
 
     def select_all(self):
-        self._select(True)
+        self._toggle_all_selection(True)
 
     def deselect_all(self):
-        self._select(False)
+        self._toggle_all_selection(False)
diff --git a/gnomemusic/widgets/albumcover.py b/gnomemusic/widgets/albumcover.py
index 6b0dd1dcf..e707f3bc4 100644
--- a/gnomemusic/widgets/albumcover.py
+++ b/gnomemusic/widgets/albumcover.py
@@ -84,8 +84,6 @@ class AlbumCover(Gtk.FlowBoxChild):
         self._art_stack.props.size = ArtSize.MEDIUM
         self._art_stack.props.art_type = DefaultIconType.ALBUM
 
-        self.show()
-
     def retrieve(self):
         """Start retrieving the actual album cover
 
diff --git a/gnomemusic/widgets/albumwidget.py b/gnomemusic/widgets/albumwidget.py
index 03f8c254e..6b2fb78ba 100644
--- a/gnomemusic/widgets/albumwidget.py
+++ b/gnomemusic/widgets/albumwidget.py
@@ -27,7 +27,7 @@ from gettext import ngettext
 from typing import Optional, Union
 import typing
 
-from gi.repository import Gfm, Gio, GLib, GObject, Gtk, Handy
+from gi.repository import Adw, Gio, GLib, GObject, Gtk
 
 from gnomemusic.corealbum import CoreAlbum
 from gnomemusic.utils import ArtSize, DefaultIconType
@@ -42,7 +42,7 @@ if typing.TYPE_CHECKING:
 
 
 @Gtk.Template(resource_path='/org/gnome/Music/ui/AlbumWidget.ui')
-class AlbumWidget(Handy.Clamp):
+class AlbumWidget(Adw.Bin):
     """Album widget.
 
     The album widget consists of an image with the album art
@@ -203,7 +203,7 @@ class AlbumWidget(Handy.Clamp):
         return disc_box
 
     def _on_model_items_changed(
-            self, model: Gfm.SortListModel, position: int, removed: int,
+            self, model: Gtk.SortListModel, position: int, removed: int,
             added: int) -> None:
         n_items: int = model.get_n_items()
         if n_items == 1:
diff --git a/gnomemusic/widgets/appmenu.py b/gnomemusic/widgets/appmenu.py
index b14641626..c0baecb72 100644
--- a/gnomemusic/widgets/appmenu.py
+++ b/gnomemusic/widgets/appmenu.py
@@ -28,7 +28,7 @@ from gnomemusic.scrobbler import GoaLastFM
 
 
 @Gtk.Template(resource_path="/org/gnome/Music/ui/AppMenu.ui")
-class AppMenu(Gtk.PopoverMenu):
+class AppMenu(Gtk.Popover):
     """AppMenu shown from the HeaderBar within the main view"""
 
     __gtype_name__ = "AppMenu"
diff --git a/gnomemusic/widgets/artistalbumswidget.py b/gnomemusic/widgets/artistalbumswidget.py
index 4fce09c30..74255d62a 100644
--- a/gnomemusic/widgets/artistalbumswidget.py
+++ b/gnomemusic/widgets/artistalbumswidget.py
@@ -23,18 +23,19 @@
 # delete this exception statement from your version.
 
 from __future__ import annotations
+from typing import Optional
 import typing
 
-from gi.repository import GObject, Gtk, Handy
+from gi.repository import GObject, Gtk
 
+from gnomemusic.coreartist import CoreArtist
 from gnomemusic.widgets.albumwidget import AlbumWidget
 if typing.TYPE_CHECKING:
     from gnomemusic.application import Application
-    from gnomemusic.coreartist import CoreArtist
 
 
 @Gtk.Template(resource_path="/org/gnome/Music/ui/ArtistAlbumsWidget.ui")
-class ArtistAlbumsWidget(Handy.Clamp):
+class ArtistAlbumsWidget(Gtk.Box):
     """Widget containing all albums by an artist
 
     A vertical list of AlbumWidget, containing all the albums
@@ -48,34 +49,33 @@ class ArtistAlbumsWidget(Handy.Clamp):
 
     selection_mode = GObject.Property(type=bool, default=False)
 
-    def __init__(
-            self, coreartist: CoreArtist, application: Application) -> None:
+    def __init__(self, application: Application) -> None:
         """Initialize the ArtistAlbumsWidget
 
-        :param CoreArtist coreartist: The CoreArtist object
         :param Aplication application: The Application object
         """
         super().__init__()
 
         self._application = application
-        self._artist = coreartist
-        self._model = coreartist.props.model
-        self._player = self._application.props.player
+        self._coreartist: Optional[CoreArtist] = None
 
-        self._listbox.bind_model(self._model, self._add_album)
+    def _update_model(self) -> None:
+        if self._coreartist is not None:
+            self._listbox.bind_model(
+                self._coreartist.props.model, self._add_album)
 
     def _add_album(self, corealbum):
         row = Gtk.ListBoxRow()
         row.props.selectable = False
         row.props.activatable = False
-        row.props.can_focus = False
+        row.props.focusable = False
 
         widget = AlbumWidget(self._application)
         widget.props.corealbum = corealbum
-        widget.props.active_coreobject = self._artist
+        widget.props.active_coreobject = self._coreartist
         widget.props.show_artist_label = False
 
-        self._artist.bind_property(
+        self._coreartist.bind_property(
             "selected", corealbum, "selected",
             GObject.BindingFlags.SYNC_CREATE)
         self.bind_property(
@@ -83,19 +83,37 @@ class ArtistAlbumsWidget(Handy.Clamp):
             GObject.BindingFlags.BIDIRECTIONAL
             | GObject.BindingFlags.SYNC_CREATE)
 
-        row.add(widget)
+        row.set_child(widget)
 
         return row
 
     def select_all(self) -> None:
         """Select all items"""
-        self._artist.props.selected = True
+        if self._coreartist is not None:
+            self._coreartist.props.selected = True
 
     def deselect_all(self) -> None:
         """Deselect all items"""
-        self._artist.props.selected = False
+        if self._coreartist is not None:
+            self._coreartist.props.selected = False
+
+    @GObject.Property(
+        type=CoreArtist, flags=GObject.ParamFlags.READWRITE, default=None)
+    def coreartist(self) -> CoreArtist:
+        """Current CoreArtist object
+
+        :param CoreArtist coreartist: The CoreArtist object
+        :rtype: CoreArtist
+        """
+        return self._coreartist
+
+    @coreartist.setter  # type: ignore
+    def coreartist(self, coreartist: CoreArtist) -> None:
+        """Sets the CoreArtist for the widget
+
+        :param CoreArtist coreartist: The CoreArtist to use
+        """
+        if self._coreartist is not coreartist:
+            self._coreartist = coreartist
 
-    @GObject.Property(type=str, flags=GObject.ParamFlags.READABLE)
-    def artist(self) -> str:
-        """Artist name"""
-        return self._artist.props.artist
+            self._update_model()
diff --git a/gnomemusic/widgets/artistsearchtile.py b/gnomemusic/widgets/artistsearchtile.py
index 7bcecfc01..329bf1d36 100644
--- a/gnomemusic/widgets/artistsearchtile.py
+++ b/gnomemusic/widgets/artistsearchtile.py
@@ -42,7 +42,6 @@ class ArtistSearchTile(Gtk.FlowBoxChild):
     _artist_label = Gtk.Template.Child()
     _art_stack = Gtk.Template.Child()
     _check = Gtk.Template.Child()
-    _events = Gtk.Template.Child()
 
     coreartist = GObject.Property(
         type=CoreArtist, default=None, flags=GObject.ParamFlags.READWRITE)
@@ -83,14 +82,11 @@ class ArtistSearchTile(Gtk.FlowBoxChild):
             "selection-mode", self._check, "visible",
             GObject.BindingFlags.BIDIRECTIONAL)
 
-        self._events.add_events(Gdk.EventMask.TOUCH_MASK)
-
-        self.show()
-
     @Gtk.Template.Callback()
-    def _on_artist_event(self, evbox, event, data=None):
+    def _on_artist_event(self, gesture_click, n_press, x, y):
+        state = gesture_click.get_current_event_state()
         modifiers = Gtk.accelerator_get_default_mod_mask()
-        if ((event.get_state() & modifiers) == Gdk.ModifierType.CONTROL_MASK
+        if (state & modifiers == Gdk.ModifierType.CONTROL_MASK
                 and not self.props.selection_mode):
             self.props.selection_mode = True
 
diff --git a/gnomemusic/widgets/artisttile.py b/gnomemusic/widgets/artisttile.py
index 17ea1b6ef..7ed4d7093 100644
--- a/gnomemusic/widgets/artisttile.py
+++ b/gnomemusic/widgets/artisttile.py
@@ -22,6 +22,9 @@
 # code, but you are not obligated to do so.  If you do not wish to do so,
 # delete this exception statement from your version.
 
+from __future__ import annotations
+from typing import Optional
+
 from gi.repository import GObject, Gtk
 
 from gnomemusic.coreartist import CoreArtist
@@ -29,7 +32,7 @@ from gnomemusic.utils import ArtSize, DefaultIconType
 
 
 @Gtk.Template(resource_path='/org/gnome/Music/ui/ArtistTile.ui')
-class ArtistTile(Gtk.ListBoxRow):
+class ArtistTile(Gtk.Box):
     """Row for sidebars
 
     Contains a label and an optional checkbox.
@@ -40,13 +43,13 @@ class ArtistTile(Gtk.ListBoxRow):
     _art_stack = Gtk.Template.Child()
     _label = Gtk.Template.Child()
 
-    coreartist = GObject.Property(type=CoreArtist, default=None)
     text = GObject.Property(type=str, default='')
 
-    def __init__(self, coreartist=None):
+    def __init__(self) -> None:
+        """Initialise ArtistTile"""
         super().__init__()
 
-        self.props.coreartist = coreartist
+        self._coreartist: Optional[CoreArtist] = None
 
         self._art_stack.props.size = ArtSize.XSMALL
         self._art_stack.props.art_type = DefaultIconType.ARTIST
@@ -54,9 +57,23 @@ class ArtistTile(Gtk.ListBoxRow):
         self.bind_property('text', self._label, 'label')
         self.bind_property('text', self._label, 'tooltip-text')
 
-        self.show()
+    @GObject.Property(
+        type=CoreArtist, flags=GObject.ParamFlags.READWRITE, default=None)
+    def coreartist(self) -> CoreArtist:
+        """CoreArtist to use for ArtistTile
 
-    def retrieve(self):
-        """Start retrieving artist art
+        :returns: The artist object
+        :rtype: CoreArtist
         """
-        self._art_stack.props.coreobject = self.props.coreartist
+        return self._coreartist
+
+    @coreartist.setter  # type: ignore
+    def coreartist(self, coreartist: CoreArtist) -> None:
+        """CoreArtist setter
+
+        :param CoreArtist coreartist: The coreartist to use
+        """
+        self._coreartist = coreartist
+
+        self._art_stack.props.coreobject = coreartist
+        self.props.text = coreartist.props.artist
diff --git a/gnomemusic/widgets/artstack.py b/gnomemusic/widgets/artstack.py
index 2f805f2a4..8daa12c48 100644
--- a/gnomemusic/widgets/artstack.py
+++ b/gnomemusic/widgets/artstack.py
@@ -26,10 +26,7 @@ from __future__ import annotations
 from typing import Optional, Union
 import typing
 
-from gi.repository import GObject, Gtk, Handy
-if typing.TYPE_CHECKING:
-    from cairo import ImageSurface
-
+from gi.repository import Adw, GObject, Gtk
 
 from gnomemusic.asyncqueue import AsyncQueue
 from gnomemusic.artcache import ArtCache
@@ -82,7 +79,7 @@ class ArtStack(Gtk.Stack):
         self.props.size = size
 
         self.connect("destroy", self._on_destroy)
-        Handy.StyleManager.get_default().connect(
+        Adw.StyleManager.get_default().connect(
             "notify::dark", self._on_dark_changed)
 
     @GObject.Property(type=object, flags=GObject.ParamFlags.READWRITE)
@@ -131,6 +128,9 @@ class ArtStack(Gtk.Stack):
 
     @coreobject.setter  # type: ignore
     def coreobject(self, coreobject: CoreObject) -> None:
+        if coreobject is self._coreobject:
+            return
+
         if self._thumbnail_id != 0:
             self._coreobject.disconnect(self._thumbnail_id)
             self._thumbnail_id = 0
@@ -143,7 +143,7 @@ class ArtStack(Gtk.Stack):
             self._on_thumbnail_changed(self._coreobject, None)
 
     def _on_dark_changed(
-            self, style_manager: Handy.StyleManager,
+            self, style_manager: Adw.StyleManager,
             pspec: GObject.ParamSpecBoolean) -> None:
         default_icon = DefaultIcon(self).get(self._art_type, self._size)
 
@@ -163,24 +163,26 @@ class ArtStack(Gtk.Stack):
 
         self._async_queue.queue(self._cache, coreobject, self._size)
 
-    def _swap_thumbnails(self, surface: ImageSurface, animate: bool) -> None:
+    def _swap_thumbnails(
+            self, paintable: Gtk.Paintable, animate: bool) -> None:
         if self.props.visible_child_name == "B":
-            self._cover_a.props.surface = surface
+            self._cover_a.props.paintable = paintable
             if animate:
                 self.set_visible_child_full(
                     "A", Gtk.StackTransitionType.CROSSFADE)
             else:
                 self.props.visible_child_name = "A"
         else:
-            self._cover_b.props.surface = surface
+            self._cover_b.props.paintable = paintable
             if animate:
                 self.set_visible_child_full(
                     "B", Gtk.StackTransitionType.CROSSFADE)
             else:
                 self.props.visible_child_name = "B"
 
-    def _on_cache_result(self, cache: ArtCache, surface: ImageSurface) -> None:
-        self._swap_thumbnails(surface, True)
+    def _on_cache_result(
+            self, cache: ArtCache, paintable: Gtk.Paintable) -> None:
+        self._swap_thumbnails(paintable, True)
 
     def _on_destroy(self, widget: ArtStack) -> None:
         # If the stack is destroyed while the art is updated, an error
diff --git a/gnomemusic/widgets/discbox.py b/gnomemusic/widgets/discbox.py
index 487ac52e1..85bcd379d 100644
--- a/gnomemusic/widgets/discbox.py
+++ b/gnomemusic/widgets/discbox.py
@@ -107,28 +107,10 @@ class DiscBox(Gtk.ListBoxRow):
     @Gtk.Template.Callback()
     def _song_activated(
             self, list_box: Gtk.ListBox, song_widget: SongWidget) -> bool:
-        if song_widget.props.select_click:
-            song_widget.props.select_click = False
-            return True
-
-        event = Gtk.get_current_event()
-        (_, state) = event.get_state()
-        mod_mask = Gtk.accelerator_get_default_mod_mask()
-        if ((state & mod_mask) == Gdk.ModifierType.CONTROL_MASK
-                and not self.props.selection_mode):
-            self.props.selection_mode = True
-            song_widget.props.select_click = True
-            song_widget.props.coresong.props.selected = True
-            return True
-
-        (_, button) = event.get_button()
-        if (button == Gdk.BUTTON_PRIMARY
-                and not self.props.selection_mode):
-            self.emit("song-activated", song_widget)
-
         if self.props.selection_mode:
-            song_widget.props.select_click = True
             selection_state = song_widget.props.coresong.props.selected
             song_widget.props.coresong.props.selected = not selection_state
+        else:
+            self.emit("song-activated", song_widget)
 
-        return True
+        return Gdk.EVENT_STOP
diff --git a/gnomemusic/widgets/headerbar.py b/gnomemusic/widgets/headerbar.py
index 8190638d4..1b890338a 100644
--- a/gnomemusic/widgets/headerbar.py
+++ b/gnomemusic/widgets/headerbar.py
@@ -25,7 +25,7 @@
 from enum import IntEnum
 
 from gettext import gettext as _, ngettext
-from gi.repository import GObject, Gtk, Handy
+from gi.repository import Adw, GObject, Gtk
 
 from gnomemusic.widgets.appmenu import AppMenu
 
@@ -72,7 +72,7 @@ class SelectionBarMenuButton(Gtk.MenuButton):
 
 
 @Gtk.Template(resource_path="/org/gnome/Music/ui/HeaderBar.ui")
-class HeaderBar(Handy.HeaderBar):
+class HeaderBar(Adw.Bin):
     """Headerbar of the application"""
 
     class State(IntEnum):
@@ -92,12 +92,16 @@ class HeaderBar(Handy.HeaderBar):
     _select_button = Gtk.Template.Child()
     _cancel_button = Gtk.Template.Child()
     _back_button = Gtk.Template.Child()
+    _headerbar = Gtk.Template.Child()
+    _label_title_box = Gtk.Template.Child()
+    _label_title = Gtk.Template.Child()
+    _label_subtitle = Gtk.Template.Child()
     _menu_button = Gtk.Template.Child()
 
     search_mode_active = GObject.Property(type=bool, default=False)
     selected_songs_count = GObject.Property(type=int, default=0, minimum=0)
     selection_mode_allowed = GObject.Property(type=bool, default=True)
-    stack = GObject.Property(type=Gtk.Stack)
+    stack = GObject.Property(type=Adw.ViewStack)
 
     def __init__(self, application):
         """Initialize Headerbar
@@ -108,16 +112,16 @@ class HeaderBar(Handy.HeaderBar):
 
         self._selection_mode = False
 
-        self._stack_switcher = Handy.ViewSwitcher(
-            can_focus=False, halign="center")
-        self._stack_switcher.show()
+        self._stack_switcher = Adw.ViewSwitcher(
+            focusable=False, halign="center",
+            policy=Adw.ViewSwitcherPolicy.WIDE)
 
         self._selection_menu = SelectionBarMenuButton()
 
         self._menu_button.set_popover(AppMenu(application))
 
         self.bind_property(
-            "selection-mode", self, "show-close-button",
+            "selection-mode", self._headerbar, "show-end-title-buttons",
             GObject.BindingFlags.INVERT_BOOLEAN
             | GObject.BindingFlags.SYNC_CREATE)
         self.bind_property(
@@ -205,6 +209,24 @@ class HeaderBar(Handy.HeaderBar):
             self._select_button.props.sensitive = True
             self._stack_switcher.show()
 
+    def set_label_title(self, title: str, subtitle: str) -> None:
+        """Set the headerbar title-widget as two labels:
+        a title and a subtitle
+
+        :param str title: headerbar title
+        :param str subtitle: headerbar subtitle
+        :returns:
+        """
+        self._headerbar.props.title_widget = self._label_title_box
+        self._label_title.props.label = title
+        self._label_subtitle.props.label = subtitle
+        if not subtitle:
+            self._label_title.props.valign = Gtk.Align.CENTER
+            self._label_subtitle.props.visible = False
+        else:
+            self._label_title.props.valign = Gtk.Align.FILL
+            self._label_subtitle.props.visible = True
+
     @Gtk.Template.Callback()
     def _on_back_button_clicked(self, widget=None):
         self.emit('back-button-clicked')
@@ -215,11 +237,11 @@ class HeaderBar(Handy.HeaderBar):
 
     def _update(self):
         if self.props.selection_mode:
-            self.props.custom_title = self._selection_menu
+            self._headerbar.props.title_widget = self._selection_menu
         elif self.props.state != HeaderBar.State.MAIN:
-            self.props.custom_title = None
+            self._headerbar.props.title_widget = None
         else:
-            self.props.custom_title = self._stack_switcher
+            self._headerbar.props.title_widget = self._stack_switcher
 
         self._back_button.props.visible = (
             not self.props.selection_mode
diff --git a/gnomemusic/widgets/lastfmdialog.py b/gnomemusic/widgets/lastfmdialog.py
index 5ba226563..00d39ed6c 100644
--- a/gnomemusic/widgets/lastfmdialog.py
+++ b/gnomemusic/widgets/lastfmdialog.py
@@ -24,7 +24,7 @@
 
 from gettext import gettext as _
 
-from gi.repository import Gtk
+from gi.repository import Gdk, Gtk
 
 from gnomemusic.scrobbler import GoaLastFM
 
@@ -36,7 +36,6 @@ class LastfmDialog(Gtk.Dialog):
     __gtype_name__ = "LastfmDialog"
 
     _action_button = Gtk.Template.Child()
-    _action_button_gesture = Gtk.Template.Child()
     _action_label = Gtk.Template.Child()
     _status_label = Gtk.Template.Child()
 
@@ -73,5 +72,6 @@ class LastfmDialog(Gtk.Dialog):
         self._action_label.props.label = action
 
     @Gtk.Template.Callback()
-    def _on_action_button_clicked(self, widget, n_press, x, y):
+    def _on_action_button_clicked(self, btn: Gtk.Button) -> bool:
         self._lastfm_scrobbler.configure()
+        return Gdk.EVENT_STOP
diff --git a/gnomemusic/widgets/notificationspopup.py b/gnomemusic/widgets/notificationspopup.py
index 3ba9c55ca..f18e48b66 100644
--- a/gnomemusic/widgets/notificationspopup.py
+++ b/gnomemusic/widgets/notificationspopup.py
@@ -47,7 +47,7 @@ class NotificationsPopup(Gtk.Revealer):
         self._loading_notification = LoadingNotification()
         self._loading_notification.connect('visible', self._set_visibility)
         self._loading_notification.connect('invisible', self._set_visibility)
-        self._box.add(self._loading_notification)
+        self._box.append(self._loading_notification)
 
     def _hide_notifications(self, notification, remove):
         if remove:
@@ -62,8 +62,9 @@ class NotificationsPopup(Gtk.Revealer):
         deletion is in progress.
         """
         loading_finished = self._loading_notification._counter == 0
-        no_other_notif = (len(self._box.get_children()) == 1
-                          or (len(self._box.get_children()) == 2
+        box_children = [child for child in self._box]
+        no_other_notif = (len(box_children) == 1
+                          or (len(box_children) == 2
                               and notification != self._loading_notification))
         invisible = loading_finished and no_other_notif
 
@@ -98,7 +99,7 @@ class NotificationsPopup(Gtk.Revealer):
 
         :param notification: notification to display
         """
-        self._box.add(notification)
+        self._box.append(notification)
         self.show()
         self.set_reveal_child(True)
 
diff --git a/gnomemusic/widgets/playertoolbar.py b/gnomemusic/widgets/playertoolbar.py
index f148bd228..829822aee 100644
--- a/gnomemusic/widgets/playertoolbar.py
+++ b/gnomemusic/widgets/playertoolbar.py
@@ -28,6 +28,7 @@ from gi.repository import Gio, GLib, GObject, Gtk
 from gnomemusic.gstplayer import Playback
 from gnomemusic.utils import ArtSize, DefaultIconType
 from gnomemusic.player import Player, RepeatMode
+from gnomemusic.widgets.artstack import ArtStack  # noqa: F401
 from gnomemusic.widgets.smoothscale import SmoothScale  # noqa: F401
 from gnomemusic.widgets.twolinetip import TwoLineTip
 import gnomemusic.utils as utils
@@ -44,12 +45,10 @@ class PlayerToolbar(Gtk.ActionBar):
 
     _artist_label = Gtk.Template.Child()
     _art_stack = Gtk.Template.Child()
-    _buttons_and_scale = Gtk.Template.Child()
     _duration_label = Gtk.Template.Child()
     _next_button = Gtk.Template.Child()
-    _pause_image = Gtk.Template.Child()
     _play_button = Gtk.Template.Child()
-    _play_image = Gtk.Template.Child()
+    _play_pause_image = Gtk.Template.Child()
     _prev_button = Gtk.Template.Child()
     _progress_scale = Gtk.Template.Child()
     _progress_time_label = Gtk.Template.Child()
@@ -68,13 +67,6 @@ class PlayerToolbar(Gtk.ActionBar):
 
         self._tooltip = TwoLineTip()
 
-        # A centered widget has an expand child property set to False
-        # by default. It needs to be True to have a progress scale
-        # at the correct size.
-        main_container = self._buttons_and_scale.get_parent()
-        main_container.child_set_property(
-            self._buttons_and_scale, "expand", True)
-
         repeat_menu = Gio.Menu.new()
         for mode in RepeatMode:
             item = Gio.MenuItem.new()
@@ -133,7 +125,6 @@ class PlayerToolbar(Gtk.ActionBar):
         self._repeat_action.set_state(new_state)
         new_mode = new_state.get_string()
         self._player.props.repeat_mode = RepeatMode(int(new_mode))
-        self._repeat_menu_button.props.active = False
 
     @Gtk.Template.Callback()
     def _on_progress_value_changed(self, progress_scale):
@@ -158,26 +149,26 @@ class PlayerToolbar(Gtk.ActionBar):
 
     def _sync_repeat_image(self) -> None:
         self._repeat_image.set_from_icon_name(
-            self._player.props.repeat_mode.icon, Gtk.IconSize.MENU)
+            self._player.props.repeat_mode.icon)
 
     def _sync_playing(self, player, state):
         if (self._player.props.state == Playback.STOPPED
                 and not self._player.props.has_next
                 and not self._player.props.has_previous):
-            self.hide()
+            self.props.revealed = False
             return
 
-        self.show()
+        self.props.revealed = True
 
         if self._player.props.state == Playback.PLAYING:
-            image = self._pause_image
+            icon_name = "media-playback-pause-symbolic"
             tooltip = _("Pause")
         else:
-            image = self._play_image
+            icon_name = "media-playback-start-symbolic"
             tooltip = _("Play")
 
-        if self._play_button.get_image() != image:
-            self._play_button.set_image(image)
+        if self._play_pause_image.props.icon_name != icon_name:
+            self._play_pause_image.props.icon_name = icon_name
 
         self._play_button.set_tooltip_text(tooltip)
 
diff --git a/gnomemusic/widgets/playlistdialog.py b/gnomemusic/widgets/playlistdialog.py
index 2c67defb9..fa402f767 100644
--- a/gnomemusic/widgets/playlistdialog.py
+++ b/gnomemusic/widgets/playlistdialog.py
@@ -132,5 +132,6 @@ class PlaylistDialog(Gtk.Dialog):
             self._add_playlist_button.props.sensitive = False
 
     @Gtk.Template.Callback()
-    def _on_add_playlist_entry_focused(self, editable, data=None):
+    def _on_add_playlist_entry_focused(
+            self, controller: Gtk.EventControllerFocus) -> None:
         self._listbox.unselect_all()
diff --git a/gnomemusic/widgets/searchheaderbar.py b/gnomemusic/widgets/searchheaderbar.py
index d5efaea55..0e539e0eb 100644
--- a/gnomemusic/widgets/searchheaderbar.py
+++ b/gnomemusic/widgets/searchheaderbar.py
@@ -24,14 +24,14 @@
 
 from enum import IntEnum
 
-from gi.repository import GLib, GObject, Gtk, Handy
+from gi.repository import Adw, GObject, Gtk
 
 from gnomemusic.search import Search
 from gnomemusic.widgets.headerbar import HeaderBar, SelectionBarMenuButton
 
 
 @Gtk.Template(resource_path="/org/gnome/Music/ui/SearchHeaderBar.ui")
-class SearchHeaderBar(Handy.HeaderBar):
+class SearchHeaderBar(Adw.Bin):
     """SearcnHeaderbar of the application"""
 
     class State(IntEnum):
@@ -43,6 +43,7 @@ class SearchHeaderBar(Handy.HeaderBar):
 
     __gtype_name__ = "SearchHeaderBar"
 
+    _headerbar = Gtk.Template.Child()
     _search_button = Gtk.Template.Child()
     _select_button = Gtk.Template.Child()
     _cancel_button = Gtk.Template.Child()
@@ -51,14 +52,13 @@ class SearchHeaderBar(Handy.HeaderBar):
     search_state = GObject.Property(type=int, default=Search.State.NONE)
     selected_songs_count = GObject.Property(type=int, default=0, minimum=0)
     selection_mode_allowed = GObject.Property(type=bool, default=True)
-    stack = GObject.Property(type=Gtk.Stack)
+    stack = GObject.Property(type=Adw.ViewStack)
 
     def __init__(self, application):
         super().__init__()
 
         self._coregrilo = application.props.coregrilo
         self._selection_mode = False
-        self._timeout = None
 
         self._entry = Gtk.SearchEntry()
         self._entry.props.halign = Gtk.Align.CENTER
@@ -68,7 +68,7 @@ class SearchHeaderBar(Handy.HeaderBar):
         self._selection_menu = SelectionBarMenuButton()
 
         self.bind_property(
-            "selection-mode", self, "show-close-button",
+            "selection-mode", self._headerbar, "show-end-title-buttons",
             GObject.BindingFlags.INVERT_BOOLEAN
             | GObject.BindingFlags.SYNC_CREATE)
         self.bind_property(
@@ -99,7 +99,7 @@ class SearchHeaderBar(Handy.HeaderBar):
             "notify::search-mode-active", self._on_search_mode_changed)
         self.connect("notify::search-state", self._search_state_changed)
 
-        self._entry.connect("changed", self._search_entry_timeout)
+        self._entry.connect("search-changed", self._search_entry_changed)
 
     @GObject.Property(type=bool, default=False)
     def selection_mode(self):
@@ -164,9 +164,9 @@ class SearchHeaderBar(Handy.HeaderBar):
 
     def _update(self):
         if self.props.selection_mode:
-            self.props.custom_title = self._selection_menu
+            self._headerbar.props.title_widget = self._selection_menu
         else:
-            self.props.custom_title = self._entry
+            self._headerbar.props.title_widget = self._entry
 
     def _on_selection_mode_allowed_changed(self, widget, data):
         if self.props.selection_mode_allowed:
@@ -174,16 +174,7 @@ class SearchHeaderBar(Handy.HeaderBar):
         else:
             self._select_button.props.sensitive = False
 
-    def _search_entry_timeout(self, widget):
-        if self._timeout:
-            GLib.source_remove(self._timeout)
-
-        self._timeout = GLib.timeout_add(
-            500, self._search_entry_changed, widget)
-
-    def _search_entry_changed(self, widget):
-        self._timeout = None
-
+    def _search_entry_changed(self, widget: Gtk.SearchEntry) -> bool:
         search_term = self._entry.get_text()
         if search_term != "":
             self.props.stack.set_visible_child_name("search")
@@ -195,7 +186,6 @@ class SearchHeaderBar(Handy.HeaderBar):
 
     def _on_search_mode_changed(self, klass, data):
         if self.props.search_mode_active:
-            # self._search_entry.realize()
             self._entry.grab_focus()
 
     def _search_state_changed(self, klass, data):
diff --git a/gnomemusic/widgets/smoothscale.py b/gnomemusic/widgets/smoothscale.py
index d1f6d3f9d..8892c4c4f 100644
--- a/gnomemusic/widgets/smoothscale.py
+++ b/gnomemusic/widgets/smoothscale.py
@@ -46,10 +46,10 @@ class SmoothScale(Gtk.Scale):
 
         self._timeout = None
 
-        self._controller = Gtk.GestureMultiPress().new(self)
-        self._controller.props.propagation_phase = Gtk.PropagationPhase.CAPTURE
-        self._controller.connect("pressed", self._on_button_pressed)
-        self._controller.connect("released", self._on_button_released)
+        ctrl = Gtk.GestureClick()
+        ctrl.connect("pressed", self._on_button_pressed)
+        ctrl.connect("released", self._on_button_released)
+        self.add_controller(ctrl)
 
         self.connect('change-value', self._on_smooth_scale_seek)
 
@@ -162,10 +162,8 @@ class SmoothScale(Gtk.Scale):
         duration = abs(self._player.props.duration)
 
         style_context = self.get_style_context()
-        state = style_context.get_state()
-
         width = self.get_allocated_width()
-        padding = style_context.get_padding(state)
+        padding = style_context.get_padding()
         width = max(width - (padding.left + padding.right), 1)
 
         timeout_period = min(1000 * duration // width, 200)
diff --git a/gnomemusic/widgets/songwidget.py b/gnomemusic/widgets/songwidget.py
index fd255dd31..ff3dbf3f4 100644
--- a/gnomemusic/widgets/songwidget.py
+++ b/gnomemusic/widgets/songwidget.py
@@ -22,13 +22,12 @@
 # code, but you are not obligated to do so.  If you do not wish to do so,
 # delete this exception statement from your version.
 
+from __future__ import annotations
+
 from enum import IntEnum
 from typing import Optional
 
-import gi
-gi.require_version('Dazzle', '1.0')
 from gi.repository import Gdk, GObject, Gtk
-from gi.repository.Dazzle import BoldingLabel  # noqa: F401
 
 from gnomemusic import utils
 from gnomemusic.coresong import CoreSong
@@ -56,7 +55,6 @@ class SongWidget(Gtk.ListBoxRow):
     }
 
     coresong = GObject.Property(type=CoreSong, default=None)
-    select_click = GObject.Property(type=bool, default=False)
     selected = GObject.Property(type=bool, default=False)
     show_song_number = GObject.Property(type=bool, default=True)
 
@@ -64,14 +62,13 @@ class SongWidget(Gtk.ListBoxRow):
     _album_duration_box = Gtk.Template.Child()
     _artist_box = Gtk.Template.Child()
     _artist_label = Gtk.Template.Child()
-    _controller_motion = Gtk.Template.Child()
-    _dnd_eventbox = Gtk.Template.Child()
+    _dnd_icon = Gtk.Template.Child()
+    _drag_source = Gtk.Template.Child()
     _menu_button = Gtk.Template.Child()
     _select_button = Gtk.Template.Child()
     _number_label = Gtk.Template.Child()
     _title_label = Gtk.Template.Child()
     _duration_label = Gtk.Template.Child()
-    _star_eventbox = Gtk.Template.Child()
     _star_image = Gtk.Template.Child()
     _star_stack = Gtk.Template.Child()
     _play_icon = Gtk.Template.Child()
@@ -126,8 +123,7 @@ class SongWidget(Gtk.ListBoxRow):
 
         self._select_button.set_visible(False)
 
-        self._play_icon.set_from_icon_name(
-            'media-playback-start-symbolic', Gtk.IconSize.SMALL_TOOLBAR)
+        self._play_icon.set_from_icon_name("media-playback-start-symbolic")
 
         self.props.coresong.bind_property(
             'selected', self._select_button, 'active',
@@ -149,74 +145,74 @@ class SongWidget(Gtk.ListBoxRow):
         if not self.props.coresong.props.is_tracker:
             self._star_stack.props.visible_child_name = "empty"
 
-        if can_dnd is True:
-            self._dnd_eventbox.props.visible = True
-            self._drag_widget = None
-            entries = [
-                Gtk.TargetEntry.new(
-                    "GTK_EVENT_BOX", Gtk.TargetFlags.SAME_APP, 0)
-            ]
-            self._dnd_eventbox.drag_source_set(
-                Gdk.ModifierType.BUTTON1_MASK, entries,
-                Gdk.DragAction.MOVE)
-            self.drag_dest_set(
-                Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
+        self._drag_x = 0.
+        self._drag_y = 0.
+        self._drag_widget: Optional[Gtk.ListBox] = None
+        if can_dnd:
+            capture_phase = Gtk.PropagationPhase.CAPTURE
+            self._drag_source.props.propagation_phase = capture_phase
+            self._dnd_icon.props.visible = True
 
     @Gtk.Template.Callback()
-    def _on_drag_begin(self, klass, context):
-        gdk_window = self.get_window()
-        _, x, y, _ = gdk_window.get_device_position(context.get_device())
-        allocation = self.get_allocation()
+    def _on_drag_prepare(
+        self, drag_source: Gtk.DragSource, x: float,
+            y: float) -> Gdk.ContentProvider:
+        self._drag_x = x
+        self._drag_y = y
+        return Gdk.ContentProvider.new_for_value(self)
 
+    @Gtk.Template.Callback()
+    def _on_drag_begin(
+            self, drag_source: Gtk.DragSource, drag: Gdk.Drag) -> None:
+        allocation = self.get_allocation()
         self._drag_widget = Gtk.ListBox()
         self._drag_widget.set_size_request(allocation.width, allocation.height)
 
-        drag_row = SongWidget(self.props.coresong)
+        drag_row = SongWidget(self.props.coresong, False, True)
         drag_row.props.show_song_number = self.props.show_song_number
-
-        self._drag_widget.add(drag_row)
+        self._drag_widget.append(drag_row)
         self._drag_widget.drag_highlight_row(drag_row)
-        self._drag_widget.props.visible = True
-        Gtk.drag_set_icon_widget(
-            context, self._drag_widget, x - allocation.x, y - allocation.y)
 
-    @Gtk.Template.Callback()
-    def _on_drag_end(self, klass, context):
-        self._drag_widget = None
+        drag_icon = Gtk.DragIcon.get_for_drag(drag)
+        drag_icon.props.child = self._drag_widget
+        drag.set_hotspot(self._drag_x, self._drag_y)
 
     @Gtk.Template.Callback()
-    def _on_drag_data_get(self, klass, context, selection_data, info, time_):
-        row_position = self.get_index()
-        selection_data.set(
-            Gdk.Atom.intern("row_position", False), 0,
-            bytes(str(row_position), encoding="UTF8"))
+    def _on_drop(
+            self, target: Gtk.DropTarget,
+            source_widget: SongWidget, x: float, y: float) -> bool:
+        self._drag_widget = None
+        self._drag_x = 0.
+        self._drag_y = 0.
 
-    @Gtk.Template.Callback()
-    def _on_drag_data_received(
-            self, klass, context, x, y, selection_data, info, time_):
-        source_position = int(str(selection_data.get_data(), "UTF-8"))
+        source_position = source_widget.get_index()
         target_position = self.get_index()
         if source_position == target_position:
-            return
+            return False
 
         self.emit("widget-moved", source_position)
+        return True
 
     @Gtk.Template.Callback()
-    def _on_select_button_toggled(self, widget):
-        # This property is used to ignore the second click event
-        # (one event in SongWidget and the other one in select_button).
-        self.props.select_click = not self.props.select_click
+    def _on_click(
+            self, gesture_click: Gtk.GestureClick, n_click: int, x: int,
+            y: int) -> bool:
+        state = gesture_click.get_current_event_state()
+        modifiers = Gtk.accelerator_get_default_mod_mask()
+        if (state & modifiers == Gdk.ModifierType.CONTROL_MASK
+                and not self.props.selection_mode):
+            self.props.selection_mode = True
 
-    @Gtk.Template.Callback()
-    def _on_star_toggle(self, widget, event):
-        (_, button) = event.get_button()
-        if button != Gdk.BUTTON_PRIMARY:
-            return False
+        return Gdk.EVENT_STOP
 
+    @Gtk.Template.Callback()
+    def _on_star_toggle(
+            self, controller: Gtk.GestureClick, n_press: int, x: float,
+            y: float) -> bool:
+        controller.set_state(Gtk.EventSequenceState.CLAIMED)
         favorite = not self._star_image.favorite
         self._star_image.props.favorite = favorite
-
-        return True
+        return Gdk.EVENT_STOP
 
     @Gtk.Template.Callback()
     def _on_star_hover(self, controller, x, y):
diff --git a/gnomemusic/widgets/songwidgetmenu.py b/gnomemusic/widgets/songwidgetmenu.py
index 9c4e75b5e..2eddd7a72 100644
--- a/gnomemusic/widgets/songwidgetmenu.py
+++ b/gnomemusic/widgets/songwidgetmenu.py
@@ -23,19 +23,19 @@
 # delete this exception statement from your version.
 
 from __future__ import annotations
-from typing import Optional, Union
+from typing import Any, Optional, Union
 import typing
 
-from gi.repository import Gtk
+from gi.repository import Gio, Gtk
 
 from gnomemusic.grilowrappers.grltrackerplaylists import Playlist
 from gnomemusic.widgets.notificationspopup import PlaylistNotification
 from gnomemusic.widgets.playlistdialog import PlaylistDialog
+from gnomemusic.widgets.songwidget import SongWidget
 if typing.TYPE_CHECKING:
     from gnomemusic.application import Application
     from gnomemusic.corealbum import CoreAlbum
     from gnomemusic.coresong import CoreSong
-    from gnomemusic.widgets.songwidget import SongWidget
 
 
 @Gtk.Template(resource_path="/org/gnome/Music/ui/SongWidgetMenu.ui")
@@ -43,10 +43,9 @@ class SongWidgetMenu(Gtk.PopoverMenu):
 
     __gtype_name__ = "SongWidgetMenu"
 
-    _remove_from_playlist_button = Gtk.Template.Child()
-
     def __init__(
-            self, application: Application, song_widget: SongWidget,
+            self, application: Application,
+            song_widget: Union[SongWidget, Gtk.ListItem],
             coreobject: Union[CoreAlbum, CoreSong, Playlist]) -> None:
         """Menu to interact with the song of a SongWidget.
 
@@ -64,19 +63,34 @@ class SongWidgetMenu(Gtk.PopoverMenu):
 
         self._coreobject = coreobject
         self._song_widget = song_widget
-        self._coresong = song_widget.props.coresong
-
-        self._playlist_dialog: Optional[PlaylistDialog] = None
 
-        if isinstance(self._coreobject, Playlist):
-            self._remove_from_playlist_button.props.visible = True
-            self._remove_from_playlist_button.props.sensitive = (
-                not self._coreobject.props.is_smart)
+        if isinstance(song_widget, SongWidget):
+            self._coresong = song_widget.props.coresong
         else:
-            self._remove_from_playlist_button.props.visible = False
+            self._coresong = coreobject
+
+        self._playlist_dialog: Optional[PlaylistDialog] = None
 
-    @Gtk.Template.Callback()
-    def _on_play_clicked(self, widget: Gtk.Button) -> None:
+        action_group = Gio.SimpleActionGroup()
+        action_entries = [
+            ("play", self._play_song),
+            ("add_playlist", self._add_to_playlist)
+        ]
+        if (isinstance(self._coreobject, Playlist)
+                and not self._coreobject.props.is_smart):
+            action_entries.append(
+                ("remove_playlist", self._remove_from_playlist))
+        elif not isinstance(self._coreobject, Playlist):
+            self.props.menu_model.remove(2)
+
+        for name, callback in action_entries:
+            action = Gio.SimpleAction.new(name, None)
+            action.connect("activate", callback)
+            action_group.add_action(action)
+
+        self.insert_action_group("songwidget", action_group)
+
+    def _play_song(self, action: Gio.Simple, param: Any) -> None:
         self.popdown()
         signal_id = 0
 
@@ -88,8 +102,7 @@ class SongWidgetMenu(Gtk.PopoverMenu):
             "playlist-loaded", _on_playlist_loaded)
         self._coremodel.props.active_core_object = self._coreobject
 
-    @Gtk.Template.Callback()
-    def _on_add_to_playlist_clicked(self, widget: Gtk.Button) -> None:
+    def _add_to_playlist(self, action: Gio.Simple, param: Any) -> None:
 
         def on_response(dialog: PlaylistDialog, response_id: int) -> None:
             if not self._playlist_dialog:
@@ -108,8 +121,7 @@ class SongWidgetMenu(Gtk.PopoverMenu):
         self._playlist_dialog.connect("response", on_response)
         self._playlist_dialog.present()
 
-    @Gtk.Template.Callback()
-    def _on_remove_from_playlist_clicked(self, widget: Gtk.Button) -> None:
+    def _remove_from_playlist(self, action: Gio.Simple, param: Any) -> None:
         self.popdown()
         position = self._song_widget.get_index()
         notification = PlaylistNotification(  # noqa: F841
diff --git a/gnomemusic/widgets/starhandlerwidget.py b/gnomemusic/widgets/starhandlerwidget.py
index 1ea3193f0..a6881f5f7 100644
--- a/gnomemusic/widgets/starhandlerwidget.py
+++ b/gnomemusic/widgets/starhandlerwidget.py
@@ -38,10 +38,10 @@ class CellRendererStar(Gtk.CellRendererPixbuf):
         self.props.mode = Gtk.CellRendererMode.ACTIVATABLE
         self.props.xpad = 32
 
-        _, width, height = Gtk.IconSize.lookup(Gtk.IconSize.SMALL_TOOLBAR)
+        # _, width, height = Gtk.IconSize.lookup(Gtk.IconSize.NORMAL)
 
-        self._icon_width = width
-        self._icon_height = height
+        self._icon_width = 16
+        self._icon_height = 16
 
         self._show_star = 0
 
diff --git a/gnomemusic/window.py b/gnomemusic/window.py
index bf4a0b282..a79753817 100644
--- a/gnomemusic/window.py
+++ b/gnomemusic/window.py
@@ -24,7 +24,7 @@
 
 from typing import Optional
 
-from gi.repository import Gtk, Gdk, Gio, GLib, GObject, Handy
+from gi.repository import Adw, Gtk, Gdk, Gio, GLib, GObject
 from gettext import gettext as _
 
 from gnomemusic.gstplayer import Playback
@@ -48,7 +48,7 @@ from gnomemusic.windowplacement import WindowPlacement
 
 
 @Gtk.Template(resource_path="/org/gnome/Music/ui/Window.ui")
-class Window(Handy.ApplicationWindow):
+class Window(Adw.ApplicationWindow):
 
     __gtype_name__ = "Window"
 
@@ -58,7 +58,6 @@ class Window(Handy.ApplicationWindow):
 
     notifications_popup = Gtk.Template.Child()
     _headerbar_stack = Gtk.Template.Child()
-    _key_controller = Gtk.Template.Child()
     _overlay = Gtk.Template.Child()
     _player_toolbar = Gtk.Template.Child()
     _selection_toolbar = Gtk.Template.Child()
@@ -229,11 +228,11 @@ class Window(Handy.ApplicationWindow):
         if self.views[View.ALBUM] is not None:
             return
 
-        self._btn_ctrl = Gtk.GestureMultiPress().new(self)
-        self._btn_ctrl.props.propagation_phase = Gtk.PropagationPhase.CAPTURE
+        ctrl = Gtk.GestureClick().new()
         # Mouse button 8 is the back button.
-        self._btn_ctrl.props.button = 8
-        self._btn_ctrl.connect("pressed", self._on_back_button_pressed)
+        ctrl.props.button = 8
+        ctrl.connect("pressed", self._on_back_button_pressed)
+        self.add_controller(ctrl)
 
         self.views[View.EMPTY].props.state = EmptyView.State.SEARCH
 
@@ -250,9 +249,9 @@ class Window(Handy.ApplicationWindow):
         # from being displayed during startup
         for i in self.views[View.ALBUM:]:
             if i.props.title:
-                self._stack.add_titled(i, i.props.name, i.props.title)
-                self._stack.child_set_property(
-                    i, "icon-name", i.props.icon_name)
+                stackpage = self._stack.add_titled(
+                    i, i.props.name, i.props.title)
+                stackpage.props.icon_name = i.props.icon_name
             else:
                 self._stack.add_named(i, i.props.name)
 
@@ -288,7 +287,7 @@ class Window(Handy.ApplicationWindow):
         modifiers = state & Gtk.accelerator_get_default_mod_mask()
         control_mask = Gdk.ModifierType.CONTROL_MASK
         shift_mask = Gdk.ModifierType.SHIFT_MASK
-        mod1_mask = Gdk.ModifierType.MOD1_MASK
+        alt_mask = Gdk.ModifierType.ALT_MASK
         shift_ctrl_mask = control_mask | shift_mask
 
         # Ctrl+<KEY>
@@ -328,7 +327,7 @@ class Window(Handy.ApplicationWindow):
             if keyval == Gdk.KEY_A:
                 self._deselect_all()
         # Alt+<KEY>
-        elif modifiers == mod1_mask:
+        elif modifiers == alt_mask:
             # Go back from child view on Alt + Left
             if keyval == Gdk.KEY_Left:
                 self._switch_back_from_childview()
@@ -443,7 +442,7 @@ class Window(Handy.ApplicationWindow):
     def _on_selection_mode_changed(self, widget, data=None):
         if (not self.props.selection_mode
                 and self._player.state == Playback.STOPPED):
-            self._player_toolbar.hide()
+            self._player_toolbar.props.revealed = False
 
     def _on_add_to_playlist(self, widget: SelectionToolbar) -> None:
 
@@ -477,4 +476,4 @@ class Window(Handy.ApplicationWindow):
 
         :param bool visible: actionbar visibility
         """
-        self._player_toolbar.set_visible(visible)
+        self._player_toolbar.props.revealed = visible
diff --git a/gnomemusic/windowplacement.py b/gnomemusic/windowplacement.py
index 2eb5bff74..ee7889c5c 100644
--- a/gnomemusic/windowplacement.py
+++ b/gnomemusic/windowplacement.py
@@ -22,8 +22,14 @@
 # code, but you are not obligated to do so.  If you do not wish to do so,
 # delete this exception statement from your version.
 
+from __future__ import annotations
+import typing
+
 from gi.repository import GLib, GObject
 
+if typing.TYPE_CHECKING:
+    from gnomemusic.window import Window
+
 
 class WindowPlacement(GObject.GObject):
     """Main window placement
@@ -47,42 +53,35 @@ class WindowPlacement(GObject.GObject):
 
         self._restore_window_state()
 
-        self._window_placement_update_timeout = None
+        self._window_placement_update_timeout = 0
         self._window.connect('notify::is-maximized', self._on_maximized)
-        self._window.connect('configure-event', self._on_configure_event)
+        self._window.connect("notify::default-height", self._on_size_change)
+        self._window.connect("notify::default-width", self._on_size_change)
 
     def _restore_window_state(self):
         size_setting = self._settings.get_value('window-size')
         if (len(size_setting) == 2
                 and isinstance(size_setting[0], int)
                 and isinstance(size_setting[1], int)):
-            self._window.resize(size_setting[0], size_setting[1])
-
-        position_setting = self._settings.get_value('window-position')
-        if (len(position_setting) == 2
-                and isinstance(position_setting[0], int)
-                and isinstance(position_setting[1], int)):
-            self._window.move(position_setting[0], position_setting[1])
+            self._window.set_default_size(size_setting[0], size_setting[1])
 
         if self._settings.get_value('window-maximized'):
             self._window.maximize()
 
-    def _on_configure_event(self, widget, event):
-        if self._window_placement_update_timeout is None:
+    def _on_size_change(
+            self, window: Window, size: GObject.ParamSpecInt) -> None:
+        if self._window_placement_update_timeout == 0:
             self._window_placement_update_timeout = GLib.timeout_add(
-                500, self._store_size_and_position, widget)
-
-    def _store_size_and_position(self, widget):
-        size = widget.get_size()
-        self._settings.set_value(
-            'window-size', GLib.Variant('ai', [size[0], size[1]]))
+                500, self._store_size, window)
 
-        position = widget.get_position()
+    def _store_size(self, window: Window) -> bool:
+        width = window.get_width()
+        height = window.get_height()
         self._settings.set_value(
-            'window-position', GLib.Variant('ai', [position[0], position[1]]))
+            "window-size", GLib.Variant("ai", [width, height]))
 
         GLib.source_remove(self._window_placement_update_timeout)
-        self._window_placement_update_timeout = None
+        self._window_placement_update_timeout = 0
 
         return False
 
diff --git a/meson.build b/meson.build
index 6c0db1a3e..b213d6194 100644
--- a/meson.build
+++ b/meson.build
@@ -44,9 +44,8 @@ PKGLIB_DIR = join_paths(get_option('prefix'), get_option('libdir'), APPLICATION_
 dependency('glib-2.0', version: '>= 2.67.1')
 dependency('goa-1.0', version: '>= 3.35.90')
 dependency('gobject-introspection-1.0', version: '>= 1.35.0')
-dependency('gtk+-3.0', version: '>= 3.24.14')
-dependency('libhandy-1', version: '>= 1.5.0')
-dependency('libdazzle-1.0', version: '>= 3.28.0')
+dependency('gtk4', version: '>= 4.5.0')
+dependency('libadwaita-1', version: '>= 1.0')
 dependency('libmediaart-2.0', version: '>= 1.9.1')
 dependency('libsoup-2.4')
 dependency('tracker-sparql-3.0', version: '>= 2.99.3')
@@ -56,12 +55,6 @@ dependency('py3cairo', version: '>= 1.14.0')
 dependency('grilo-0.3', version: '>= 0.3.13', fallback: ['grilo', 'libgrl_dep'])
 dependency('grilo-plugins-0.3', version: '>= 0.3.12', fallback: ['grilo-plugins', 'grilo_plugins_dep'])
 
-subproject('gfm',
-    default_options: [
-        'pkgdatadir=' + PKGDATA_DIR,
-        'pkglibdir=' + PKGLIB_DIR
-    ])
-
 subdir('data/ui')
 subdir('data')
 subdir('help')
@@ -80,8 +73,6 @@ bin_config.set('pkgdatadir', PKGDATA_DIR)
 bin_config.set('localedir', join_paths(get_option('prefix'), get_option('datadir'), 'locale'))
 bin_config.set('pythondir', PYTHON_DIR)
 bin_config.set('schemasdir', PKGDATA_DIR)
-# Used for gfm
-bin_config.set('gfmlibdir', PKGLIB_DIR)
 
 bin_config.set('local_build', 'False')
 
@@ -100,8 +91,6 @@ local_config.set('pkgdatadir', join_paths(meson.current_build_dir(), 'data'))
 local_config.set('localedir', join_paths(get_option('prefix'), get_option('datadir'), 'locale'))
 local_config.set('pythondir', meson.current_source_dir())
 local_config.set('schemasdir', join_paths(meson.current_build_dir(), 'data'))
-# Used for gfm
-local_config.set('gfmlibdir', join_paths(meson.current_build_dir(), 'subprojects', 'gfm'))
 
 local_config.set('local_build', 'True')
 
diff --git a/org.gnome.Music.json b/org.gnome.Music.json
index 0a1e070f0..e4d730702 100644
--- a/org.gnome.Music.json
+++ b/org.gnome.Music.json
@@ -7,6 +7,7 @@
     "tags": ["devel", "development", "nightly"],
     "desktop-file-name-suffix": " ☢️",
     "finish-args": [
+        "--device=dri",
         "--share=ipc",
         "--share=network",
         "--socket=wayland",
@@ -43,16 +44,6 @@
                 }
             ]
         },
-        {
-            "name": "libdazzle",
-            "buildsystem": "meson",
-            "sources": [
-                {
-                    "type": "git",
-                    "url": "https://gitlab.gnome.org/GNOME/libdazzle.git";
-                }
-            ]
-        },
         {
             "name": "tracker-miners",
             "buildsystem": "meson",
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 006a563b1..72df75ebc 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -2,4 +2,3 @@
 # Please keep this file sorted alphabetically.
 data/ui/AboutDialog.ui
 data/org.gnome.Music.appdata.xml
-subprojects/gfm


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]