diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragment.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragment.kt index 5129857dc..16ede2a8f 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragment.kt @@ -22,34 +22,71 @@ import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater import android.view.MenuInflater +import android.view.MenuItem +import androidx.annotation.IdRes import androidx.appcompat.view.menu.MenuBuilder import androidx.core.view.children -import org.oxycblt.auxio.R +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.DialogMenuBinding +import org.oxycblt.auxio.list.ClickableListListener import org.oxycblt.auxio.list.adapter.UpdateInstructions +import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.ui.ViewBindingBottomSheetDialogFragment +import org.oxycblt.auxio.util.collectImmediately +import org.oxycblt.auxio.util.logD /** - * A [ViewBindingBottomSheetDialogFragment] that displays basic music information and - * a series of options. + * A [ViewBindingBottomSheetDialogFragment] that displays basic music information and a series of + * options. + * * @author Alexander Capehart (OxygenCobalt) */ -class MenuDialogFragment : ViewBindingBottomSheetDialogFragment() { - private val menuAdapter = MenuOptionAdapter() +abstract class MenuDialogFragment : + ViewBindingBottomSheetDialogFragment(), ClickableListListener { + protected abstract val menuModel: MenuViewModel + private val menuAdapter = MenuOptionAdapter(@Suppress("LeakingThis") this) + + abstract val menuRes: Int + abstract val uid: Music.UID + abstract fun updateMusic(binding: DialogMenuBinding, music: T) + abstract fun onClick(music: T, @IdRes optionId: Int) override fun onCreateBinding(inflater: LayoutInflater) = DialogMenuBinding.inflate(inflater) override fun onBindingCreated(binding: DialogMenuBinding, savedInstanceState: Bundle?) { super.onBindingCreated(binding, savedInstanceState) - binding.menuRecycler.apply { + // --- UI SETUP --- + binding.menuOptionRecycler.apply { adapter = menuAdapter itemAnimator = null } // Avoid having to use a dummy view and rely on what AndroidX Toolbar uses. @SuppressLint("RestrictedApi") val builder = MenuBuilder(requireContext()) - MenuInflater(requireContext()).inflate(R.menu.item_song, builder) + MenuInflater(requireContext()).inflate(menuRes, builder) menuAdapter.update(builder.children.toList(), UpdateInstructions.Diff) + + // --- VIEWMODEL SETUP --- + menuModel.setMusic(uid) + collectImmediately(menuModel.currentMusic, this::updateMusic) + } + + override fun onDestroyBinding(binding: DialogMenuBinding) { + super.onDestroyBinding(binding) + binding.menuOptionRecycler.adapter = null + } + + private fun updateMusic(music: Music?) { + if (music == null) { + logD("No music to show, navigating away") + findNavController().navigateUp() + } + @Suppress("UNCHECKED_CAST") updateMusic(requireBinding(), music as T) + } + + final override fun onClick(item: MenuItem, viewHolder: RecyclerView.ViewHolder) { + @Suppress("UNCHECKED_CAST") onClick(menuModel.currentMusic.value as T, item.itemId) } } diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt new file mode 100644 index 000000000..86ee874fe --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuDialogFragmentImpl.kt @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2023 Auxio Project + * MenuDialogFragmentImpl.kt is part of Auxio. + * + * This program 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 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + */ + +package org.oxycblt.auxio.list.menu + +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.navArgs +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.R +import org.oxycblt.auxio.databinding.DialogMenuBinding +import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.Playlist +import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.resolveNames +import org.oxycblt.auxio.util.getPlural + +@AndroidEntryPoint +class SongMenuDialogFragment : MenuDialogFragment() { + override val menuModel: MenuViewModel by viewModels() + private val args: SongMenuDialogFragmentArgs by navArgs() + + override val menuRes = args.menuRes + override val uid = args.songUid + + override fun updateMusic(binding: DialogMenuBinding, music: Song) { + val context = requireContext() + binding.menuCover.bind(music) + binding.menuInfo.text = getString(R.string.lbl_song) + binding.menuName.text = music.name.resolve(context) + binding.menuInfo.text = music.artists.resolveNames(context) + } + + override fun onClick(music: Song, optionId: Int) {} +} + +@AndroidEntryPoint +class AlbumMenuDialogFragment : MenuDialogFragment() { + override val menuModel: MenuViewModel by viewModels() + private val args: AlbumMenuDialogFragmentArgs by navArgs() + + override val menuRes = args.menuRes + override val uid = args.albumUid + + override fun updateMusic(binding: DialogMenuBinding, music: Album) { + val context = requireContext() + binding.menuCover.bind(music) + binding.menuInfo.text = getString(music.releaseType.stringRes) + binding.menuName.text = music.name.resolve(context) + binding.menuInfo.text = music.artists.resolveNames(context) + } + + override fun onClick(music: Album, optionId: Int) {} +} + +@AndroidEntryPoint +class ArtistMenuDialogFragment : MenuDialogFragment() { + override val menuModel: MenuViewModel by viewModels() + private val args: ArtistMenuDialogFragmentArgs by navArgs() + + override val menuRes = args.menuRes + override val uid = args.artistUid + + override fun updateMusic(binding: DialogMenuBinding, music: Artist) { + val context = requireContext() + binding.menuCover.bind(music) + binding.menuInfo.text = getString(R.string.lbl_artist) + binding.menuName.text = music.name.resolve(context) + binding.menuInfo.text = + getString( + R.string.fmt_two, + context.getPlural(R.plurals.fmt_album_count, music.albums.size), + if (music.songs.isNotEmpty()) { + context.getPlural(R.plurals.fmt_song_count, music.songs.size) + } else { + getString(R.string.def_song_count) + }) + } + + override fun onClick(music: Artist, optionId: Int) {} +} + +@AndroidEntryPoint +class GenreMenuDialogFragment : MenuDialogFragment() { + override val menuModel: MenuViewModel by viewModels() + private val args: GenreMenuDialogFragmentArgs by navArgs() + + override val menuRes = args.menuRes + override val uid = args.genreUid + + override fun updateMusic(binding: DialogMenuBinding, music: Genre) { + val context = requireContext() + binding.menuCover.bind(music) + binding.menuInfo.text = getString(R.string.lbl_genre) + binding.menuName.text = music.name.resolve(context) + binding.menuInfo.text = + getString( + R.string.fmt_two, + context.getPlural(R.plurals.fmt_artist_count, music.artists.size), + context.getPlural(R.plurals.fmt_song_count, music.songs.size)) + } + + override fun onClick(music: Genre, optionId: Int) {} +} + +@AndroidEntryPoint +class PlaylistMenuDialogFragment : MenuDialogFragment() { + override val menuModel: MenuViewModel by viewModels() + private val args: PlaylistMenuDialogFragmentArgs by navArgs() + + override val menuRes = args.menuRes + override val uid = args.playlistUid + + override fun updateMusic(binding: DialogMenuBinding, music: Playlist) { + val context = requireContext() + binding.menuCover.bind(music) + binding.menuInfo.text = getString(R.string.lbl_genre) + binding.menuName.text = music.name.resolve(context) + binding.menuInfo.text = context.getPlural(R.plurals.fmt_song_count, music.songs.size) + } + + override fun onClick(music: Playlist, optionId: Int) {} +} diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuOptionAdapter.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuOptionAdapter.kt index a9e23f11e..e5d7b2a85 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuOptionAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuOptionAdapter.kt @@ -22,15 +22,18 @@ import android.view.MenuItem import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import org.oxycblt.auxio.databinding.ItemMenuOptionBinding +import org.oxycblt.auxio.list.ClickableListListener import org.oxycblt.auxio.list.adapter.FlexibleListAdapter import org.oxycblt.auxio.list.recycler.DialogRecyclerView import org.oxycblt.auxio.util.inflater /** * Displays a list of [MenuItem]s as custom list items. + * + * @param listener A [MenuOptionAdapter] to bind interactions to. * @author Alexander Capehart (OxygenCobalt) */ -class MenuOptionAdapter : +class MenuOptionAdapter(private val listener: ClickableListListener) : FlexibleListAdapter(MenuOptionViewHolder.DIFF_CALLBACK) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = MenuOptionViewHolder.from(parent) @@ -42,6 +45,7 @@ class MenuOptionAdapter : /** * A [DialogRecyclerView.ViewHolder] that displays a list of menu options based on [MenuItem]. + * * @author Alexander Capehart (OxygenCobalt) */ class MenuOptionViewHolder private constructor(private val binding: ItemMenuOptionBinding) : diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt new file mode 100644 index 000000000..91fccff25 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuViewModel.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 Auxio Project + * MenuViewModel.kt is part of Auxio. + * + * This program 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 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + */ + +package org.oxycblt.auxio.list.menu + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.oxycblt.auxio.music.Music +import org.oxycblt.auxio.music.MusicRepository +import org.oxycblt.auxio.util.logW + +@HiltViewModel +class MenuViewModel @Inject constructor(private val musicRepository: MusicRepository) : + ViewModel(), MusicRepository.UpdateListener { + private val _currentMusic = MutableStateFlow(null) + val currentMusic: StateFlow = _currentMusic + + init { + musicRepository.addUpdateListener(this) + } + + override fun onMusicChanges(changes: MusicRepository.Changes) { + _currentMusic.value = _currentMusic.value?.let { musicRepository.find(it.uid) } + } + + override fun onCleared() { + musicRepository.removeUpdateListener(this) + } + + fun setMusic(uid: Music.UID) { + _currentMusic.value = musicRepository.find(uid) + if (_currentMusic.value == null) { + logW("Given Music UID to show was invalid") + } + } +} diff --git a/app/src/main/res/layout/dialog_menu.xml b/app/src/main/res/layout/dialog_menu.xml index 72534ff15..f7dbc37af 100644 --- a/app/src/main/res/layout/dialog_menu.xml +++ b/app/src/main/res/layout/dialog_menu.xml @@ -1,24 +1,60 @@ - + + + android:textAppearance="@style/TextAppearance.Auxio.LabelLarge" + android:textColor="?attr/colorSecondary" + android:layout_marginStart="@dimen/spacing_medium" + app:layout_constraintBottom_toTopOf="@+id/menu_name" + app:layout_constraintStart_toEndOf="@+id/menu_cover" + app:layout_constraintTop_toTopOf="@+id/menu_cover" + app:layout_constraintVertical_chainStyle="packed" + tools:text="Type" /> + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/navigation/main.xml b/app/src/main/res/navigation/main.xml index 9d0736663..ea498b24f 100644 --- a/app/src/main/res/navigation/main.xml +++ b/app/src/main/res/navigation/main.xml @@ -30,9 +30,6 @@ - @@ -301,10 +298,69 @@ + android:id="@+id/song_menu_dialog" + android:name="org.oxycblt.auxio.list.menu.SongMenuDialogFragment" + android:label="song_menu_dialog" + tools:layout="@layout/dialog_menu"> + + + + + + + + + + + + + + + + + + + + + + + 48dp - 56dp + 56dp + 92dp 128dp 192dp 256dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 95e6c348e..099d0a9ea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,6 +20,7 @@ Grant Songs + Song All songs Albums diff --git a/app/src/main/res/values/styles_ui.xml b/app/src/main/res/values/styles_ui.xml index 105b4aab5..18ecf8a2f 100644 --- a/app/src/main/res/values/styles_ui.xml +++ b/app/src/main/res/values/styles_ui.xml @@ -60,8 +60,14 @@ + +