#28: Auto update favorites list

- Also removes duplicate favorite listeners
This commit is contained in:
Ryan Harg 2021-09-10 13:36:28 +02:00
parent 687c61ed13
commit d8f8c3c193
No known key found for this signature in database
GPG Key ID: 89106F3A84E6958C
13 changed files with 109 additions and 123 deletions

View File

@ -3,9 +3,11 @@ package audio.funkwhale.ffa.activities
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import audio.funkwhale.ffa.adapters.FavoriteListener
import audio.funkwhale.ffa.adapters.SearchAdapter import audio.funkwhale.ffa.adapters.SearchAdapter
import audio.funkwhale.ffa.databinding.ActivitySearchBinding import audio.funkwhale.ffa.databinding.ActivitySearchBinding
import audio.funkwhale.ffa.fragments.AddToPlaylistDialog import audio.funkwhale.ffa.fragments.AddToPlaylistDialog
@ -51,7 +53,12 @@ class SearchActivity : AppCompatActivity() {
setContentView(binding.root) setContentView(binding.root)
adapter = adapter =
SearchAdapter(layoutInflater, this, SearchResultClickListener(), FavoriteListener()).also { SearchAdapter(
layoutInflater,
this,
SearchResultClickListener(),
FavoriteListener(favoritesRepository)
).also {
binding.results.layoutManager = LinearLayoutManager(this) binding.results.layoutManager = LinearLayoutManager(this)
binding.results.adapter = it binding.results.adapter = it
} }
@ -90,59 +97,59 @@ class SearchActivity : AppCompatActivity() {
tracksRepository = TracksSearchRepository(this@SearchActivity, "") tracksRepository = TracksSearchRepository(this@SearchActivity, "")
favoritesRepository = FavoritesRepository(this@SearchActivity) favoritesRepository = FavoritesRepository(this@SearchActivity)
binding.search.setOnQueryTextListener(object : binding.search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(rawQuery: String?): Boolean {
binding.search.clearFocus()
rawQuery?.let { override fun onQueryTextSubmit(rawQuery: String?): Boolean {
done = 0 binding.search.clearFocus()
val query = URLEncoder.encode(it, "UTF-8") rawQuery?.let {
done = 0
artistsRepository.query = query.lowercase(Locale.ROOT) val query = URLEncoder.encode(it, "UTF-8")
albumsRepository.query = query.lowercase(Locale.ROOT)
tracksRepository.query = query.lowercase(Locale.ROOT)
binding.searchSpinner.visibility = View.VISIBLE artistsRepository.query = query.lowercase(Locale.ROOT)
binding.searchEmpty.visibility = View.GONE albumsRepository.query = query.lowercase(Locale.ROOT)
binding.searchNoResults.visibility = View.GONE tracksRepository.query = query.lowercase(Locale.ROOT)
adapter.artists.clear() binding.searchSpinner.visibility = View.VISIBLE
adapter.albums.clear() binding.searchEmpty.visibility = View.GONE
adapter.tracks.clear() binding.searchNoResults.visibility = View.GONE
adapter.notifyDataSetChanged()
artistsRepository.fetch(Repository.Origin.Network.origin) adapter.artists.clear()
.untilNetwork(lifecycleScope) { artists, _, _, _ -> adapter.albums.clear()
done++ adapter.tracks.clear()
adapter.notifyDataSetChanged()
adapter.artists.addAll(artists) artistsRepository.fetch(Repository.Origin.Network.origin)
refresh() .untilNetwork(lifecycleScope) { artists, _, _, _ ->
} done++
albumsRepository.fetch(Repository.Origin.Network.origin) adapter.artists.addAll(artists)
.untilNetwork(lifecycleScope) { albums, _, _, _ -> refresh()
done++ }
adapter.albums.addAll(albums) albumsRepository.fetch(Repository.Origin.Network.origin)
refresh() .untilNetwork(lifecycleScope) { albums, _, _, _ ->
} done++
tracksRepository.fetch(Repository.Origin.Network.origin) adapter.albums.addAll(albums)
.untilNetwork(lifecycleScope) { tracks, _, _, _ -> refresh()
done++ }
adapter.tracks.addAll(tracks) tracksRepository.fetch(Repository.Origin.Network.origin)
refresh() .untilNetwork(lifecycleScope) { tracks, _, _, _ ->
} done++
}
return true adapter.tracks.addAll(tracks)
refresh()
}
} }
override fun onQueryTextChange(newText: String?) = true return true
}) }
override fun onQueryTextChange(newText: String?) = true
})
} }
private fun refresh() { private fun refresh() {
@ -187,13 +194,4 @@ class SearchActivity : AppCompatActivity() {
AlbumsFragment.openTracks(this@SearchActivity, album) AlbumsFragment.openTracks(this@SearchActivity, album)
} }
} }
inner class FavoriteListener : SearchAdapter.OnFavoriteListener {
override fun onToggleFavorite(id: Int, state: Boolean) {
when (state) {
true -> favoritesRepository.addFavorite(id)
false -> favoritesRepository.deleteFavorite(id)
}
}
}
} }

View File

@ -0,0 +1,13 @@
package audio.funkwhale.ffa.adapters
import audio.funkwhale.ffa.repositories.FavoritesRepository
class FavoriteListener(private val repository: FavoritesRepository) {
fun onToggleFavorite(id: Int, state: Boolean) {
when (state) {
true -> repository.addFavorite(id)
false -> repository.deleteFavorite(id)
}
}
}

View File

@ -26,18 +26,14 @@ import java.util.Collections
class FavoritesAdapter( class FavoritesAdapter(
private val layoutInflater: LayoutInflater, private val layoutInflater: LayoutInflater,
private val context: Context?, private val context: Context?,
private val favoriteListener: OnFavoriteListener, private val favoriteListener: FavoriteListener,
val fromQueue: Boolean = false val fromQueue: Boolean = false,
) : FFAAdapter<Track, FavoritesAdapter.ViewHolder>() { ) : FFAAdapter<Track, FavoritesAdapter.ViewHolder>() {
init { init {
this.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY this.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
} }
interface OnFavoriteListener {
fun onToggleFavorite(id: Int, state: Boolean)
}
private lateinit var binding: RowTrackBinding private lateinit var binding: RowTrackBinding
var currentTrack: Track? = null var currentTrack: Track? = null

View File

@ -30,14 +30,10 @@ import java.util.Collections
class PlaylistTracksAdapter( class PlaylistTracksAdapter(
private val layoutInflater: LayoutInflater, private val layoutInflater: LayoutInflater,
private val context: Context?, private val context: Context?,
private val favoriteListener: OnFavoriteListener? = null, private val favoriteListener: FavoriteListener,
private val playlistListener: OnPlaylistListener? = null private val playlistListener: OnPlaylistListener
) : FFAAdapter<PlaylistTrack, PlaylistTracksAdapter.ViewHolder>() { ) : FFAAdapter<PlaylistTrack, PlaylistTracksAdapter.ViewHolder>() {
interface OnFavoriteListener {
fun onToggleFavorite(id: Int, state: Boolean)
}
private lateinit var binding: RowTrackBinding private lateinit var binding: RowTrackBinding
interface OnPlaylistListener { interface OnPlaylistListener {
@ -103,7 +99,7 @@ class PlaylistTracksAdapter(
} }
holder.favorite.setOnClickListener { holder.favorite.setOnClickListener {
favoriteListener?.let { favoriteListener.let {
favoriteListener.onToggleFavorite(track.track.id, !track.track.favorite) favoriteListener.onToggleFavorite(track.track.id, !track.track.favorite)
track.track.favorite = !track.track.favorite track.track.favorite = !track.track.favorite
@ -124,7 +120,7 @@ class PlaylistTracksAdapter(
R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(track.track))) R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(track.track)))
R.id.track_play_next -> CommandBus.send(Command.PlayNext(track.track)) R.id.track_play_next -> CommandBus.send(Command.PlayNext(track.track))
R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(track.track)) R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(track.track))
R.id.track_remove_from_playlist -> playlistListener?.onRemoveTrackFromPlaylist( R.id.track_remove_from_playlist -> playlistListener.onRemoveTrackFromPlaylist(
track.track, track.track,
position position
) )
@ -223,7 +219,7 @@ class PlaylistTracksAdapter(
viewHolder.itemView.background = ColorDrawable(Color.TRANSPARENT) viewHolder.itemView.background = ColorDrawable(Color.TRANSPARENT)
if (from != -1 && to != -1 && from != to) { if (from != -1 && to != -1 && from != to) {
playlistListener?.onMoveTrack(from, to) playlistListener.onMoveTrack(from, to)
from = -1 from = -1
to = -1 to = -1

View File

@ -30,8 +30,8 @@ import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
class SearchAdapter( class SearchAdapter(
private val layoutInflater: LayoutInflater, private val layoutInflater: LayoutInflater,
private val context: Context?, private val context: Context?,
private val listener: OnSearchResultClickListener? = null, private val listener: OnSearchResultClickListener,
private val favoriteListener: OnFavoriteListener? = null private val favoriteListener: FavoriteListener
) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() { ) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
interface OnSearchResultClickListener { interface OnSearchResultClickListener {
@ -39,10 +39,6 @@ class SearchAdapter(
fun onAlbumClick(holder: View?, album: Album) fun onAlbumClick(holder: View?, album: Album)
} }
interface OnFavoriteListener {
fun onToggleFavorite(id: Int, state: Boolean)
}
enum class ResultType { enum class ResultType {
Header, Header,
Artist, Artist,
@ -244,7 +240,7 @@ class SearchAdapter(
} }
rowTrackViewHolder?.favorite?.setOnClickListener { rowTrackViewHolder?.favorite?.setOnClickListener {
favoriteListener?.let { favoriteListener.let {
favoriteListener.onToggleFavorite(track.id, !track.favorite) favoriteListener.onToggleFavorite(track.id, !track.favorite)
tracks[position - artists.size - albums.size - sectionCount].favorite = tracks[position - artists.size - albums.size - sectionCount].favorite =
@ -341,13 +337,13 @@ class SearchAdapter(
ResultType.Artist.ordinal -> { ResultType.Artist.ordinal -> {
val position = layoutPosition - 1 val position = layoutPosition - 1
listener?.onArtistClick(view, artists[position]) listener.onArtistClick(view, artists[position])
} }
ResultType.Album.ordinal -> { ResultType.Album.ordinal -> {
val position = layoutPosition - artists.size - 2 val position = layoutPosition - artists.size - 2
listener?.onAlbumClick(view, albums[position]) listener.onAlbumClick(view, albums[position])
} }
ResultType.Track.ordinal -> { ResultType.Track.ordinal -> {

View File

@ -31,7 +31,7 @@ import java.util.Collections
class TracksAdapter( class TracksAdapter(
private val layoutInflater: LayoutInflater, private val layoutInflater: LayoutInflater,
private val context: Context?, private val context: Context?,
private val favoriteListener: OnFavoriteListener? = null, private val favoriteListener: FavoriteListener,
val fromQueue: Boolean = false val fromQueue: Boolean = false
) : FFAAdapter<Track, TracksAdapter.ViewHolder>() { ) : FFAAdapter<Track, TracksAdapter.ViewHolder>() {
@ -39,10 +39,6 @@ class TracksAdapter(
this.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY this.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
} }
interface OnFavoriteListener {
fun onToggleFavorite(id: Int, state: Boolean)
}
private lateinit var binding: RowTrackBinding private lateinit var binding: RowTrackBinding
private lateinit var touchHelper: ItemTouchHelper private lateinit var touchHelper: ItemTouchHelper
@ -101,7 +97,7 @@ class TracksAdapter(
} }
holder.favorite.setOnClickListener { holder.favorite.setOnClickListener {
favoriteListener?.let { favoriteListener.let {
favoriteListener.onToggleFavorite(track.id, !track.favorite) favoriteListener.onToggleFavorite(track.id, !track.favorite)
data[position].favorite = !track.favorite data[position].favorite = !track.favorite

View File

@ -48,6 +48,8 @@ abstract class FFAFragment<D : Any, A : FFAAdapter<D, *>>() : Fragment() {
private var moreLoading = false private var moreLoading = false
private var listener: Job? = null private var listener: Job? = null
fun <T> repository() = repository as T
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View File

@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import audio.funkwhale.ffa.adapters.FavoriteListener
import audio.funkwhale.ffa.adapters.FavoritesAdapter import audio.funkwhale.ffa.adapters.FavoritesAdapter
import audio.funkwhale.ffa.databinding.FragmentFavoritesBinding import audio.funkwhale.ffa.databinding.FragmentFavoritesBinding
import audio.funkwhale.ffa.model.Track import audio.funkwhale.ffa.model.Track
@ -41,8 +42,8 @@ class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
adapter = FavoritesAdapter(layoutInflater, context, FavoriteListener())
repository = FavoritesRepository(context) repository = FavoritesRepository(context)
adapter = FavoritesAdapter(layoutInflater, context, FavoriteListener(repository()))
watchEventBus() watchEventBus()
} }
@ -72,6 +73,7 @@ class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
} }
} }
refreshFavoritedTracks()
refreshDownloadedTracks() refreshDownloadedTracks()
} }
@ -98,6 +100,12 @@ class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
} }
} }
private fun refreshFavoritedTracks() {
lifecycleScope.launch(Main) {
update()
}
}
private suspend fun refreshDownloadedTracks() { private suspend fun refreshDownloadedTracks() {
val downloaded = TracksRepository.getDownloadedIds(exoDownloadManager) ?: listOf() val downloaded = TracksRepository.getDownloadedIds(exoDownloadManager) ?: listOf()
@ -134,15 +142,4 @@ class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
} }
} }
inner class FavoriteListener : FavoritesAdapter.OnFavoriteListener {
override fun onToggleFavorite(id: Int, state: Boolean) {
(repository as? FavoritesRepository)?.let { repository ->
when (state) {
true -> repository.addFavorite(id)
false -> repository.deleteFavorite(id)
}
}
}
}
} }

View File

@ -7,8 +7,10 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import audio.funkwhale.ffa.adapters.FavoriteListener
import audio.funkwhale.ffa.adapters.TracksAdapter import audio.funkwhale.ffa.adapters.TracksAdapter
import audio.funkwhale.ffa.databinding.PartialQueueBinding import audio.funkwhale.ffa.databinding.PartialQueueBinding
import audio.funkwhale.ffa.repositories.FavoritesRepository
import audio.funkwhale.ffa.utils.Command import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.Event import audio.funkwhale.ffa.utils.Event
@ -26,6 +28,8 @@ class LandscapeQueueFragment : Fragment() {
private var _binding: PartialQueueBinding? = null private var _binding: PartialQueueBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
lateinit var favoritesRepository: FavoritesRepository
private var adapter: TracksAdapter? = null private var adapter: TracksAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -41,7 +45,12 @@ class LandscapeQueueFragment : Fragment() {
): View { ): View {
_binding = PartialQueueBinding.inflate(inflater) _binding = PartialQueueBinding.inflate(inflater)
return binding.root.apply { return binding.root.apply {
adapter = TracksAdapter(layoutInflater, context, fromQueue = true).also { adapter = TracksAdapter(
layoutInflater,
context,
fromQueue = true,
favoriteListener = FavoriteListener(favoritesRepository)
).also {
binding.queue.layoutManager = LinearLayoutManager(context) binding.queue.layoutManager = LinearLayoutManager(context)
binding.queue.adapter = it binding.queue.adapter = it
} }

View File

@ -10,6 +10,7 @@ import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import audio.funkwhale.ffa.R import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.adapters.FavoriteListener
import audio.funkwhale.ffa.adapters.PlaylistTracksAdapter import audio.funkwhale.ffa.adapters.PlaylistTracksAdapter
import audio.funkwhale.ffa.databinding.FragmentTracksBinding import audio.funkwhale.ffa.databinding.FragmentTracksBinding
import audio.funkwhale.ffa.model.Playlist import audio.funkwhale.ffa.model.Playlist
@ -71,7 +72,7 @@ class PlaylistTracksFragment : FFAFragment<PlaylistTrack, PlaylistTracksAdapter>
albumCover = getString("albumCover") ?: "" albumCover = getString("albumCover") ?: ""
} }
adapter = PlaylistTracksAdapter(layoutInflater, context, FavoriteListener(), PlaylistListener()) adapter = PlaylistTracksAdapter(layoutInflater, context, FavoriteListener(favoritesRepository), PlaylistListener())
repository = PlaylistTracksRepository(context, albumId) repository = PlaylistTracksRepository(context, albumId)
favoritesRepository = FavoritesRepository(context) favoritesRepository = FavoritesRepository(context)
playlistsRepository = ManagementPlaylistsRepository(context) playlistsRepository = ManagementPlaylistsRepository(context)
@ -211,15 +212,6 @@ class PlaylistTracksFragment : FFAFragment<PlaylistTrack, PlaylistTracksAdapter>
} }
} }
inner class FavoriteListener : PlaylistTracksAdapter.OnFavoriteListener {
override fun onToggleFavorite(id: Int, state: Boolean) {
when (state) {
true -> favoritesRepository.addFavorite(id)
false -> favoritesRepository.deleteFavorite(id)
}
}
}
inner class PlaylistListener : PlaylistTracksAdapter.OnPlaylistListener { inner class PlaylistListener : PlaylistTracksAdapter.OnPlaylistListener {
override fun onMoveTrack(from: Int, to: Int) { override fun onMoveTrack(from: Int, to: Int) {
playlistsRepository.move(albumId, from, to) playlistsRepository.move(albumId, from, to)

View File

@ -9,6 +9,7 @@ import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import audio.funkwhale.ffa.R import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.adapters.FavoriteListener
import audio.funkwhale.ffa.adapters.TracksAdapter import audio.funkwhale.ffa.adapters.TracksAdapter
import audio.funkwhale.ffa.databinding.FragmentQueueBinding import audio.funkwhale.ffa.databinding.FragmentQueueBinding
import audio.funkwhale.ffa.repositories.FavoritesRepository import audio.funkwhale.ffa.repositories.FavoritesRepository
@ -62,7 +63,12 @@ class QueueFragment : BottomSheetDialogFragment() {
): View { ): View {
_binding = FragmentQueueBinding.inflate(inflater) _binding = FragmentQueueBinding.inflate(inflater)
return binding.root.apply { return binding.root.apply {
adapter = TracksAdapter(layoutInflater, context, FavoriteListener(), fromQueue = true).also { adapter = TracksAdapter(
layoutInflater,
context,
FavoriteListener(favoritesRepository),
fromQueue = true
).also {
binding.included.queue.layoutManager = LinearLayoutManager(context) binding.included.queue.layoutManager = LinearLayoutManager(context)
binding.included.queue.adapter = it binding.included.queue.adapter = it
} }
@ -135,13 +141,4 @@ class QueueFragment : BottomSheetDialogFragment() {
} }
} }
} }
inner class FavoriteListener : TracksAdapter.OnFavoriteListener {
override fun onToggleFavorite(id: Int, state: Boolean) {
when (state) {
true -> favoritesRepository.addFavorite(id)
false -> favoritesRepository.deleteFavorite(id)
}
}
}
} }

View File

@ -12,6 +12,7 @@ import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import audio.funkwhale.ffa.R import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.adapters.FavoriteListener
import audio.funkwhale.ffa.adapters.TracksAdapter import audio.funkwhale.ffa.adapters.TracksAdapter
import audio.funkwhale.ffa.databinding.FragmentTracksBinding import audio.funkwhale.ffa.databinding.FragmentTracksBinding
import audio.funkwhale.ffa.model.Album import audio.funkwhale.ffa.model.Album
@ -83,10 +84,11 @@ class TracksFragment : FFAFragment<Track, TracksAdapter>() {
albumCover = getString("albumCover") ?: "" albumCover = getString("albumCover") ?: ""
} }
adapter = TracksAdapter(layoutInflater, context, FavoriteListener())
repository = TracksRepository(context, albumId)
favoritesRepository = FavoritesRepository(context) favoritesRepository = FavoritesRepository(context)
favoritedRepository = FavoritedRepository(context) favoritedRepository = FavoritedRepository(context)
repository = TracksRepository(context, albumId)
adapter = TracksAdapter(layoutInflater, context, FavoriteListener(favoritesRepository))
watchEventBus() watchEventBus()
} }
@ -294,13 +296,4 @@ class TracksFragment : FFAFragment<Track, TracksAdapter>() {
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
} }
} }
inner class FavoriteListener : TracksAdapter.OnFavoriteListener {
override fun onToggleFavorite(id: Int, state: Boolean) {
when (state) {
true -> favoritesRepository.addFavorite(id)
false -> favoritesRepository.deleteFavorite(id)
}
}
}
} }

View File

@ -0,0 +1 @@
Automatically update the favorites list view (#28)