Twelve: Run write requests on view lifecycle instead of VM one

If the request didn't complete in time the VM gets shutdown

Change-Id: I73f4dfb375f7446781f24f55cbce6664b4fbfa3d
diff --git a/app/src/main/java/org/lineageos/twelve/fragments/AddOrRemoveFromPlaylistsFragment.kt b/app/src/main/java/org/lineageos/twelve/fragments/AddOrRemoveFromPlaylistsFragment.kt
index d62ba69..a7612f3 100644
--- a/app/src/main/java/org/lineageos/twelve/fragments/AddOrRemoveFromPlaylistsFragment.kt
+++ b/app/src/main/java/org/lineageos/twelve/fragments/AddOrRemoveFromPlaylistsFragment.kt
@@ -33,6 +33,7 @@
 import org.lineageos.twelve.models.RequestStatus
 import org.lineageos.twelve.ui.dialogs.EditTextMaterialAlertDialogBuilder
 import org.lineageos.twelve.ui.recyclerview.SimpleListAdapter
+import org.lineageos.twelve.ui.views.FullscreenLoadingProgressBar
 import org.lineageos.twelve.ui.views.ListItem
 import org.lineageos.twelve.utils.PermissionsChecker
 import org.lineageos.twelve.utils.PermissionsUtils
@@ -47,6 +48,7 @@
 
     // Views
     private val createNewPlaylistButton by getViewProperty<Button>(R.id.createNewPlaylistButton)
+    private val fullscreenLoadingProgressBar by getViewProperty<FullscreenLoadingProgressBar>(R.id.fullscreenLoadingProgressBar)
     private val linearProgressIndicator by getViewProperty<LinearProgressIndicator>(R.id.linearProgressIndicator)
     private val noElementsLinearLayout by getViewProperty<LinearLayout>(R.id.noElementsLinearLayout)
     private val recyclerView by getViewProperty<RecyclerView>(R.id.recyclerView)
@@ -64,9 +66,13 @@
                     item?.let {
                         when (it === addNewPlaylistItem) {
                             true -> openCreateNewPlaylistDialog()
-                            false -> when (it.second) {
-                                true -> viewModel.removeFromPlaylist(it.first.uri)
-                                false -> viewModel.addToPlaylist(it.first.uri)
+                            false -> viewLifecycleOwner.lifecycleScope.launch {
+                                fullscreenLoadingProgressBar.withProgress {
+                                    when (it.second) {
+                                        true -> viewModel.removeFromPlaylist(it.first.uri)
+                                        false -> viewModel.addToPlaylist(it.first.uri)
+                                    }
+                                }
                             }
                         }
                     }
@@ -175,7 +181,11 @@
     private fun openCreateNewPlaylistDialog() {
         EditTextMaterialAlertDialogBuilder(requireContext())
             .setPositiveButton(R.string.create_playlist_confirm) { text ->
-                viewModel.createPlaylist(text)
+                viewLifecycleOwner.lifecycleScope.launch {
+                    fullscreenLoadingProgressBar.withProgress {
+                        viewModel.createPlaylist(text)
+                    }
+                }
             }
             .setTitle(R.string.create_playlist)
             .setNegativeButton(android.R.string.cancel, null)
diff --git a/app/src/main/java/org/lineageos/twelve/fragments/AudioBottomSheetDialogFragment.kt b/app/src/main/java/org/lineageos/twelve/fragments/AudioBottomSheetDialogFragment.kt
index be2863b..176f042 100644
--- a/app/src/main/java/org/lineageos/twelve/fragments/AudioBottomSheetDialogFragment.kt
+++ b/app/src/main/java/org/lineageos/twelve/fragments/AudioBottomSheetDialogFragment.kt
@@ -24,6 +24,7 @@
 import org.lineageos.twelve.ext.getParcelable
 import org.lineageos.twelve.ext.getViewProperty
 import org.lineageos.twelve.models.RequestStatus
+import org.lineageos.twelve.ui.views.FullscreenLoadingProgressBar
 import org.lineageos.twelve.ui.views.ListItem
 import org.lineageos.twelve.utils.PermissionsChecker
 import org.lineageos.twelve.utils.PermissionsUtils
@@ -43,6 +44,7 @@
     private val addToQueueListItem by getViewProperty<ListItem>(R.id.addToQueueListItem)
     private val artistNameTextView by getViewProperty<TextView>(R.id.artistNameTextView)
     private val albumTitleTextView by getViewProperty<TextView>(R.id.albumTitleTextView)
+    private val fullscreenLoadingProgressBar by getViewProperty<FullscreenLoadingProgressBar>(R.id.fullscreenLoadingProgressBar)
     private val openAlbumListItem by getViewProperty<ListItem>(R.id.openAlbumListItem)
     private val openArtistListItem by getViewProperty<ListItem>(R.id.openArtistListItem)
     private val playNextListItem by getViewProperty<ListItem>(R.id.playNextListItem)
@@ -69,11 +71,15 @@
 
         removeFromPlaylistListItem.isVisible = playlistUri != null
         removeFromPlaylistListItem.setOnClickListener {
-            playlistUri?.let {
-                viewModel.removeFromPlaylist(it)
-            }
+            viewLifecycleOwner.lifecycleScope.launch {
+                fullscreenLoadingProgressBar.withProgress {
+                    playlistUri?.let {
+                        viewModel.removeFromPlaylist(it)
 
-            findNavController().navigateUp()
+                        findNavController().navigateUp()
+                    }
+                }
+            }
         }
 
         addOrRemoveFromPlaylistsListItem.setOnClickListener {
diff --git a/app/src/main/java/org/lineageos/twelve/fragments/ManageProviderFragment.kt b/app/src/main/java/org/lineageos/twelve/fragments/ManageProviderFragment.kt
index a0236a3..bb21b23 100644
--- a/app/src/main/java/org/lineageos/twelve/fragments/ManageProviderFragment.kt
+++ b/app/src/main/java/org/lineageos/twelve/fragments/ManageProviderFragment.kt
@@ -38,6 +38,7 @@
 import org.lineageos.twelve.models.ProviderType
 import org.lineageos.twelve.models.RequestStatus
 import org.lineageos.twelve.ui.recyclerview.SimpleListAdapter
+import org.lineageos.twelve.ui.views.FullscreenLoadingProgressBar
 import org.lineageos.twelve.viewmodels.ManageProviderViewModel
 
 /**
@@ -51,6 +52,7 @@
     private val argumentsRecyclerView by getViewProperty<RecyclerView>(R.id.argumentsRecyclerView)
     private val confirmMaterialButton by getViewProperty<MaterialButton>(R.id.confirmMaterialButton)
     private val deleteMaterialButton by getViewProperty<MaterialButton>(R.id.deleteMaterialButton)
+    private val fullscreenLoadingProgressBar by getViewProperty<FullscreenLoadingProgressBar>(R.id.fullscreenLoadingProgressBar)
     private val providerNameTextInputLayout by getViewProperty<TextInputLayout>(R.id.providerNameTextInputLayout)
     private val providerTypeAutoCompleteTextView by getViewProperty<MaterialAutoCompleteTextView>(R.id.providerTypeAutoCompleteTextView)
     private val providerTypeTextInputLayout by getViewProperty<TextInputLayout>(R.id.providerTypeTextInputLayout)
@@ -181,13 +183,17 @@
                 return@setOnClickListener
             }
 
-            if (viewModel.inEditMode.value) {
-                viewModel.updateProvider(name, providerArguments)
-            } else {
-                viewModel.addProvider(providerType, name, providerArguments)
-            }
+            viewLifecycleOwner.lifecycleScope.launch {
+                fullscreenLoadingProgressBar.withProgress {
+                    if (viewModel.inEditMode.value) {
+                        viewModel.updateProvider(name, providerArguments)
+                    } else {
+                        viewModel.addProvider(providerType, name, providerArguments)
+                    }
 
-            findNavController().navigateUp()
+                    findNavController().navigateUp()
+                }
+            }
         }
 
         deleteMaterialButton.setOnClickListener {
@@ -331,7 +337,11 @@
         MaterialAlertDialogBuilder(requireContext())
             .setMessage(R.string.delete_provider_confirmation)
             .setPositiveButton(android.R.string.ok) { _, _ ->
-                viewModel.deleteProvider()
+                viewLifecycleOwner.lifecycleScope.launch {
+                    fullscreenLoadingProgressBar.withProgress {
+                        viewModel.deleteProvider()
+                    }
+                }
             }
             .setNegativeButton(android.R.string.cancel) { _, _ ->
                 // Do nothing
diff --git a/app/src/main/java/org/lineageos/twelve/fragments/PlaylistFragment.kt b/app/src/main/java/org/lineageos/twelve/fragments/PlaylistFragment.kt
index 0b894c2..45d16c7 100644
--- a/app/src/main/java/org/lineageos/twelve/fragments/PlaylistFragment.kt
+++ b/app/src/main/java/org/lineageos/twelve/fragments/PlaylistFragment.kt
@@ -41,6 +41,7 @@
 import org.lineageos.twelve.ui.dialogs.EditTextMaterialAlertDialogBuilder
 import org.lineageos.twelve.ui.recyclerview.SimpleListAdapter
 import org.lineageos.twelve.ui.recyclerview.UniqueItemDiffCallback
+import org.lineageos.twelve.ui.views.FullscreenLoadingProgressBar
 import org.lineageos.twelve.ui.views.ListItem
 import org.lineageos.twelve.utils.PermissionsChecker
 import org.lineageos.twelve.utils.PermissionsUtils
@@ -55,6 +56,7 @@
     private val viewModel by viewModels<PlaylistViewModel>()
 
     // Views
+    private val fullscreenLoadingProgressBar by getViewProperty<FullscreenLoadingProgressBar>(R.id.fullscreenLoadingProgressBar)
     private val infoNestedScrollView by getViewProperty<NestedScrollView?>(R.id.infoNestedScrollView)
     private val linearProgressIndicator by getViewProperty<LinearProgressIndicator>(R.id.linearProgressIndicator)
     private val noElementsNestedScrollView by getViewProperty<NestedScrollView>(R.id.noElementsNestedScrollView)
@@ -291,7 +293,11 @@
         EditTextMaterialAlertDialogBuilder(requireContext())
             .setText(toolbar.title.toString())
             .setPositiveButton(R.string.rename_playlist_positive) { text ->
-                viewModel.renamePlaylist(text)
+                viewLifecycleOwner.lifecycleScope.launch {
+                    fullscreenLoadingProgressBar.withProgress {
+                        viewModel.renamePlaylist(text)
+                    }
+                }
             }
             .setTitle(R.string.rename_playlist)
             .setNegativeButton(android.R.string.cancel, null)
@@ -303,7 +309,11 @@
             .setTitle(R.string.delete_playlist)
             .setMessage(R.string.delete_playlist_message)
             .setPositiveButton(android.R.string.ok) { _, _ ->
-                viewModel.deletePlaylist()
+                viewLifecycleOwner.lifecycleScope.launch {
+                    fullscreenLoadingProgressBar.withProgress {
+                        viewModel.deletePlaylist()
+                    }
+                }
             }
             .setNegativeButton(android.R.string.cancel, null)
             .show()
diff --git a/app/src/main/java/org/lineageos/twelve/fragments/PlaylistsFragment.kt b/app/src/main/java/org/lineageos/twelve/fragments/PlaylistsFragment.kt
index 957a915..958b60e 100644
--- a/app/src/main/java/org/lineageos/twelve/fragments/PlaylistsFragment.kt
+++ b/app/src/main/java/org/lineageos/twelve/fragments/PlaylistsFragment.kt
@@ -30,6 +30,7 @@
 import org.lineageos.twelve.ui.dialogs.EditTextMaterialAlertDialogBuilder
 import org.lineageos.twelve.ui.recyclerview.SimpleListAdapter
 import org.lineageos.twelve.ui.recyclerview.UniqueItemDiffCallback
+import org.lineageos.twelve.ui.views.FullscreenLoadingProgressBar
 import org.lineageos.twelve.ui.views.ListItem
 import org.lineageos.twelve.utils.PermissionsChecker
 import org.lineageos.twelve.utils.PermissionsUtils
@@ -44,6 +45,7 @@
 
     // Views
     private val createNewPlaylistButton by getViewProperty<Button>(R.id.createNewPlaylistButton)
+    private val fullscreenLoadingProgressBar by getViewProperty<FullscreenLoadingProgressBar>(R.id.fullscreenLoadingProgressBar)
     private val linearProgressIndicator by getViewProperty<LinearProgressIndicator>(R.id.linearProgressIndicator)
     private val noElementsLinearLayout by getViewProperty<LinearLayout>(R.id.noElementsLinearLayout)
     private val recyclerView by getViewProperty<RecyclerView>(R.id.recyclerView)
@@ -153,7 +155,11 @@
     private fun openCreateNewPlaylistDialog() {
         EditTextMaterialAlertDialogBuilder(requireContext())
             .setPositiveButton(R.string.create_playlist_confirm) { text ->
-                viewModel.createPlaylist(text)
+                viewLifecycleOwner.lifecycleScope.launch {
+                    fullscreenLoadingProgressBar.withProgress {
+                        viewModel.createPlaylist(text)
+                    }
+                }
             }
             .setTitle(R.string.create_playlist)
             .setNegativeButton(android.R.string.cancel, null)
diff --git a/app/src/main/java/org/lineageos/twelve/viewmodels/AddOrRemoveFromPlaylistsViewModel.kt b/app/src/main/java/org/lineageos/twelve/viewmodels/AddOrRemoveFromPlaylistsViewModel.kt
index 8375b19..3e4b6cf 100644
--- a/app/src/main/java/org/lineageos/twelve/viewmodels/AddOrRemoveFromPlaylistsViewModel.kt
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/AddOrRemoveFromPlaylistsViewModel.kt
@@ -15,6 +15,7 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.lineageos.twelve.models.RequestStatus
 
 class AddOrRemoveFromPlaylistsViewModel(application: Application) : AudioViewModel(application) {
@@ -34,10 +35,12 @@
     /**
      * Create a new playlist in the same provider as the audio.
      */
-    fun createPlaylist(name: String) = viewModelScope.launch {
-        audioUri.value?.let {
-            mediaRepository.getProviderOfMediaItems(it)?.let { provider ->
-                mediaRepository.createPlaylist(provider, name)
+    suspend fun createPlaylist(name: String) {
+        withContext(Dispatchers.IO) {
+            audioUri.value?.let {
+                mediaRepository.getProviderOfMediaItems(it)?.let { provider ->
+                    mediaRepository.createPlaylist(provider, name)
+                }
             }
         }
     }
diff --git a/app/src/main/java/org/lineageos/twelve/viewmodels/AudioViewModel.kt b/app/src/main/java/org/lineageos/twelve/viewmodels/AudioViewModel.kt
index d5c394a..5783d47 100644
--- a/app/src/main/java/org/lineageos/twelve/viewmodels/AudioViewModel.kt
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/AudioViewModel.kt
@@ -17,6 +17,7 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.lineageos.twelve.models.Audio
 import org.lineageos.twelve.models.RequestStatus
 
@@ -40,11 +41,11 @@
         this.audioUri.value = audioUri
     }
 
-    fun addToQueue(audio: Audio) = viewModelScope.launch {
+    fun addToQueue(audio: Audio) {
         mediaController.value?.addMediaItem(audio.toMedia3MediaItem())
     }
 
-    fun playNext(audio: Audio) = viewModelScope.launch {
+    fun playNext(audio: Audio) {
         mediaController.value?.let { controller ->
             controller.addMediaItem(
                 controller.currentMediaItemIndex + 1,
@@ -53,15 +54,19 @@
         }
     }
 
-    fun addToPlaylist(playlistUri: Uri) = viewModelScope.launch {
+    suspend fun addToPlaylist(playlistUri: Uri) {
         audioUri.value?.let {
-            mediaRepository.addAudioToPlaylist(playlistUri, it)
+            withContext(Dispatchers.IO) {
+                mediaRepository.addAudioToPlaylist(playlistUri, it)
+            }
         }
     }
 
-    fun removeFromPlaylist(playlistUri: Uri) = viewModelScope.launch {
+    suspend fun removeFromPlaylist(playlistUri: Uri) {
         audioUri.value?.let {
-            mediaRepository.removeAudioFromPlaylist(playlistUri, it)
+            withContext(Dispatchers.IO) {
+                mediaRepository.removeAudioFromPlaylist(playlistUri, it)
+            }
         }
     }
 }
diff --git a/app/src/main/java/org/lineageos/twelve/viewmodels/ManageProviderViewModel.kt b/app/src/main/java/org/lineageos/twelve/viewmodels/ManageProviderViewModel.kt
index bb0b907..a9412b7 100644
--- a/app/src/main/java/org/lineageos/twelve/viewmodels/ManageProviderViewModel.kt
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/ManageProviderViewModel.kt
@@ -20,6 +20,7 @@
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.lineageos.twelve.datasources.MediaError
 import org.lineageos.twelve.models.ProviderType
 import org.lineageos.twelve.models.RequestStatus
@@ -137,27 +138,33 @@
     /**
      * Add a new provider.
      */
-    fun addProvider(
+    suspend fun addProvider(
         providerType: ProviderType, name: String, arguments: Bundle
-    ) = viewModelScope.launch {
-        mediaRepository.addProvider(providerType, name, arguments)
+    ) {
+        withContext(Dispatchers.IO) {
+            mediaRepository.addProvider(providerType, name, arguments)
+        }
     }
 
     /**
      * Update the provider.
      */
-    fun updateProvider(name: String, arguments: Bundle) = viewModelScope.launch {
-        val (providerType, providerTypeId) = providerIds.value ?: return@launch
+    suspend fun updateProvider(name: String, arguments: Bundle) {
+        val (providerType, providerTypeId) = providerIds.value ?: return
 
-        mediaRepository.updateProvider(providerType, providerTypeId, name, arguments)
+        withContext(Dispatchers.IO) {
+            mediaRepository.updateProvider(providerType, providerTypeId, name, arguments)
+        }
     }
 
     /**
      * Delete the provider.
      */
-    fun deleteProvider() = viewModelScope.launch {
-        val (providerType, providerTypeId) = providerIds.value ?: return@launch
+    suspend fun deleteProvider() {
+        val (providerType, providerTypeId) = providerIds.value ?: return
 
-        mediaRepository.deleteProvider(providerType, providerTypeId)
+        withContext(Dispatchers.IO) {
+            mediaRepository.deleteProvider(providerType, providerTypeId)
+        }
     }
 }
diff --git a/app/src/main/java/org/lineageos/twelve/viewmodels/PlaylistViewModel.kt b/app/src/main/java/org/lineageos/twelve/viewmodels/PlaylistViewModel.kt
index 3a4269e..fee56b5 100644
--- a/app/src/main/java/org/lineageos/twelve/viewmodels/PlaylistViewModel.kt
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/PlaylistViewModel.kt
@@ -17,6 +17,7 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.lineageos.twelve.models.RequestStatus
 
 class PlaylistViewModel(application: Application) : TwelveViewModel(application) {
@@ -39,15 +40,19 @@
         this.playlistUri.value = playlistUri
     }
 
-    fun renamePlaylist(name: String) = viewModelScope.launch {
+    suspend fun renamePlaylist(name: String) {
         playlistUri.value?.let { playlistUri ->
-            mediaRepository.renamePlaylist(playlistUri, name)
+            withContext(Dispatchers.IO) {
+                mediaRepository.renamePlaylist(playlistUri, name)
+            }
         }
     }
 
-    fun deletePlaylist() = viewModelScope.launch {
+    suspend fun deletePlaylist() {
         playlistUri.value?.let { playlistUri ->
-            mediaRepository.deletePlaylist(playlistUri)
+            withContext(Dispatchers.IO) {
+                mediaRepository.deletePlaylist(playlistUri)
+            }
         }
     }
 
diff --git a/app/src/main/java/org/lineageos/twelve/viewmodels/PlaylistsViewModel.kt b/app/src/main/java/org/lineageos/twelve/viewmodels/PlaylistsViewModel.kt
index 07ec2c5..1d1fcba 100644
--- a/app/src/main/java/org/lineageos/twelve/viewmodels/PlaylistsViewModel.kt
+++ b/app/src/main/java/org/lineageos/twelve/viewmodels/PlaylistsViewModel.kt
@@ -11,7 +11,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.lineageos.twelve.models.RequestStatus
 
 class PlaylistsViewModel(application: Application) : TwelveViewModel(application) {
@@ -23,7 +23,9 @@
             RequestStatus.Loading()
         )
 
-    fun createPlaylist(name: String) = viewModelScope.launch {
-        mediaRepository.createPlaylist(mediaRepository.navigationProvider.value, name)
+    suspend fun createPlaylist(name: String) {
+        withContext(Dispatchers.IO) {
+            mediaRepository.createPlaylist(mediaRepository.navigationProvider.value, name)
+        }
     }
 }
diff --git a/app/src/main/res/layout-land/fragment_playlist.xml b/app/src/main/res/layout-land/fragment_playlist.xml
index fb3ec0e..bc9f969 100644
--- a/app/src/main/res/layout-land/fragment_playlist.xml
+++ b/app/src/main/res/layout-land/fragment_playlist.xml
@@ -114,4 +114,11 @@
 
     </FrameLayout>
 
+    <org.lineageos.twelve.ui.views.FullscreenLoadingProgressBar
+        android:id="@+id/fullscreenLoadingProgressBar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fitsSystemWindows="true"
+        android:visibility="gone" />
+
 </androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/fragment_add_or_remove_from_playlists.xml b/app/src/main/res/layout/fragment_add_or_remove_from_playlists.xml
index 25b9428..1945b62 100644
--- a/app/src/main/res/layout/fragment_add_or_remove_from_playlists.xml
+++ b/app/src/main/res/layout/fragment_add_or_remove_from_playlists.xml
@@ -62,4 +62,11 @@
         android:indeterminate="true"
         app:layout_behavior="@string/appbar_scrolling_view_behavior" />
 
+    <org.lineageos.twelve.ui.views.FullscreenLoadingProgressBar
+        android:id="@+id/fullscreenLoadingProgressBar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fitsSystemWindows="true"
+        android:visibility="gone" />
+
 </androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/fragment_audio_bottom_sheet_dialog.xml b/app/src/main/res/layout/fragment_audio_bottom_sheet_dialog.xml
index 7521b81..0f1b7af 100644
--- a/app/src/main/res/layout/fragment_audio_bottom_sheet_dialog.xml
+++ b/app/src/main/res/layout/fragment_audio_bottom_sheet_dialog.xml
@@ -3,89 +3,101 @@
      SPDX-FileCopyrightText: 2024 The LineageOS Project
      SPDX-License-Identifier: Apache-2.0
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:paddingBottom="8dp">
+    android:layout_height="match_parent">
 
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginHorizontal="20dp"
-        android:layout_marginVertical="16dp"
-        android:orientation="vertical">
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:paddingBottom="8dp">
 
-        <TextView
-            android:id="@+id/titleTextView"
+        <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginBottom="4dp"
-            android:textAppearance="?attr/textAppearanceTitleLarge"
-            android:textColor="?attr/colorOnSurface" />
+            android:layout_marginHorizontal="20dp"
+            android:layout_marginVertical="16dp"
+            android:orientation="vertical">
 
-        <TextView
-            android:id="@+id/artistNameTextView"
+            <TextView
+                android:id="@+id/titleTextView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="4dp"
+                android:textAppearance="?attr/textAppearanceTitleLarge"
+                android:textColor="?attr/colorOnSurface" />
+
+            <TextView
+                android:id="@+id/artistNameTextView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textAppearance="?attr/textAppearanceBodyMedium"
+                android:textColor="?attr/colorOnSurface" />
+
+            <TextView
+                android:id="@+id/albumTitleTextView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textAppearance="?attr/textAppearanceBodyMedium"
+                android:textColor="?attr/colorOnSurface" />
+
+        </LinearLayout>
+
+        <com.google.android.material.divider.MaterialDivider
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <org.lineageos.twelve.ui.views.ListItem
+            android:id="@+id/addToQueueListItem"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:textAppearance="?attr/textAppearanceBodyMedium"
-            android:textColor="?attr/colorOnSurface" />
+            app:headlineText="@string/audio_add_to_queue"
+            app:leadingIconImage="@drawable/ic_add_to_queue" />
 
-        <TextView
-            android:id="@+id/albumTitleTextView"
+        <org.lineageos.twelve.ui.views.ListItem
+            android:id="@+id/playNextListItem"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:textAppearance="?attr/textAppearanceBodyMedium"
-            android:textColor="?attr/colorOnSurface" />
+            app:headlineText="@string/audio_play_next"
+            app:leadingIconImage="@drawable/ic_queue_play_next" />
+
+        <org.lineageos.twelve.ui.views.ListItem
+            android:id="@+id/removeFromPlaylistListItem"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            app:headlineText="@string/audio_remove_from_playlist"
+            app:leadingIconImage="@drawable/ic_playlist_remove" />
+
+        <org.lineageos.twelve.ui.views.ListItem
+            android:id="@+id/addOrRemoveFromPlaylistsListItem"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:headlineText="@string/audio_add_or_remove_from_playlists"
+            app:leadingIconImage="@drawable/ic_playlist_add" />
+
+        <org.lineageos.twelve.ui.views.ListItem
+            android:id="@+id/openAlbumListItem"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:headlineText="@string/open_album"
+            app:leadingIconImage="@drawable/ic_album" />
+
+        <org.lineageos.twelve.ui.views.ListItem
+            android:id="@+id/openArtistListItem"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:headlineText="@string/open_artist"
+            app:leadingIconImage="@drawable/ic_person" />
 
     </LinearLayout>
 
-    <com.google.android.material.divider.MaterialDivider
+    <org.lineageos.twelve.ui.views.FullscreenLoadingProgressBar
+        android:id="@+id/fullscreenLoadingProgressBar"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
+        android:layout_height="match_parent"
+        android:visibility="gone" />
 
-    <org.lineageos.twelve.ui.views.ListItem
-        android:id="@+id/addToQueueListItem"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:headlineText="@string/audio_add_to_queue"
-        app:leadingIconImage="@drawable/ic_add_to_queue" />
-
-    <org.lineageos.twelve.ui.views.ListItem
-        android:id="@+id/playNextListItem"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:headlineText="@string/audio_play_next"
-        app:leadingIconImage="@drawable/ic_queue_play_next" />
-
-    <org.lineageos.twelve.ui.views.ListItem
-        android:id="@+id/removeFromPlaylistListItem"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        app:headlineText="@string/audio_remove_from_playlist"
-        app:leadingIconImage="@drawable/ic_playlist_remove" />
-
-    <org.lineageos.twelve.ui.views.ListItem
-        android:id="@+id/addOrRemoveFromPlaylistsListItem"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:headlineText="@string/audio_add_or_remove_from_playlists"
-        app:leadingIconImage="@drawable/ic_playlist_add" />
-
-    <org.lineageos.twelve.ui.views.ListItem
-        android:id="@+id/openAlbumListItem"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:headlineText="@string/open_album"
-        app:leadingIconImage="@drawable/ic_album" />
-
-    <org.lineageos.twelve.ui.views.ListItem
-        android:id="@+id/openArtistListItem"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:headlineText="@string/open_artist"
-        app:leadingIconImage="@drawable/ic_person" />
-
-</LinearLayout>
+</FrameLayout>
diff --git a/app/src/main/res/layout/fragment_manage_provider.xml b/app/src/main/res/layout/fragment_manage_provider.xml
index fb9f469..6eb9060 100644
--- a/app/src/main/res/layout/fragment_manage_provider.xml
+++ b/app/src/main/res/layout/fragment_manage_provider.xml
@@ -96,4 +96,11 @@
 
     </androidx.core.widget.NestedScrollView>
 
+    <org.lineageos.twelve.ui.views.FullscreenLoadingProgressBar
+        android:id="@+id/fullscreenLoadingProgressBar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fitsSystemWindows="true"
+        android:visibility="gone" />
+
 </androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/fragment_playlist.xml b/app/src/main/res/layout/fragment_playlist.xml
index d2a124b..1a08671 100644
--- a/app/src/main/res/layout/fragment_playlist.xml
+++ b/app/src/main/res/layout/fragment_playlist.xml
@@ -116,4 +116,11 @@
 
     </FrameLayout>
 
+    <org.lineageos.twelve.ui.views.FullscreenLoadingProgressBar
+        android:id="@+id/fullscreenLoadingProgressBar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fitsSystemWindows="true"
+        android:visibility="gone" />
+
 </androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/layout/fragment_playlists.xml b/app/src/main/res/layout/fragment_playlists.xml
index 87a59bb..830186c 100644
--- a/app/src/main/res/layout/fragment_playlists.xml
+++ b/app/src/main/res/layout/fragment_playlists.xml
@@ -47,4 +47,10 @@
         android:layout_height="wrap_content"
         android:indeterminate="true" />
 
+    <org.lineageos.twelve.ui.views.FullscreenLoadingProgressBar
+        android:id="@+id/fullscreenLoadingProgressBar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone" />
+
 </FrameLayout>