diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 7b03de1ec..448ac933e 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -29,9 +29,9 @@ jobs: ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Build Debug APK with Gradle + - name: Build debug APK with Gradle run: ./gradlew app:packageDebug - - name: Upload a Build Artifact + - name: Upload debug APK artifact uses: actions/upload-artifact@v3.1.1 with: name: Auxio_Canary diff --git a/CHANGELOG.md b/CHANGELOG.md index e979d1af3..92e6b3fad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ - Value lists are now properly localized - Queue no longer primarily shows previous songs when opened +#### What's Fixed +- Fixed mangled multi-value ID3v2 tags when UTF-16 is used + ## 3.0.0 #### What's New diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt index 769db5c4f..8cc214b14 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt @@ -227,7 +227,7 @@ private class AlbumSongViewHolder private constructor(private val binding: ItemA * @param listener A [SelectableListListener] to bind interactions to. */ fun bind(song: Song, listener: SelectableListListener) { - listener.bind(this, song, binding.songMenu) + listener.bind(song, this, menuButton = binding.songMenu) binding.songTrack.apply { if (song.track != null) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt index d9ab4fbe8..84aedabfb 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt @@ -184,7 +184,7 @@ private class ArtistAlbumViewHolder private constructor(private val binding: Ite * @param listener An [SelectableListListener] to bind interactions to. */ fun bind(album: Album, listener: SelectableListListener) { - listener.bind(this, album, binding.parentMenu) + listener.bind(album, this, menuButton = binding.parentMenu) binding.parentImage.bind(album) binding.parentName.text = album.resolveName(binding.context) binding.parentInfo.text = @@ -236,7 +236,7 @@ private class ArtistSongViewHolder private constructor(private val binding: Item * @param listener An [SelectableListListener] to bind interactions to. */ fun bind(song: Song, listener: SelectableListListener) { - listener.bind(this, song, binding.songMenu) + listener.bind(song, this, menuButton = binding.songMenu) binding.songAlbumCover.bind(song) binding.songName.text = song.resolveName(binding.context) binding.songInfo.text = song.album.resolveName(binding.context) diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index d83ee3618..6f6c89c92 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -354,6 +354,7 @@ class HomeFragment : } } is Indexer.Response.NoMusic -> { + // TODO: Move this state to the list fragments (makes life easier) logD("Updating UI to Response.NoMusic state") binding.homeIndexingStatus.text = context.getString(R.string.err_no_music) diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt index 76f7cf95d..1e74ad396 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt @@ -17,6 +17,7 @@ package org.oxycblt.auxio.home.tabs +import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.util.logE @@ -25,7 +26,7 @@ import org.oxycblt.auxio.util.logE * @param mode The type of list in the home view this instance corresponds to. * @author Alexander Capehart (OxygenCobalt) */ -sealed class Tab(open val mode: MusicMode) { +sealed class Tab(open val mode: MusicMode) : Item { /** * A visible tab. This will be visible in the home and tab configuration views. * @param mode The type of list in the home view this instance corresponds to. diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt index 18025e3b1..756f8fea1 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt @@ -18,21 +18,22 @@ package org.oxycblt.auxio.home.tabs import android.annotation.SuppressLint -import android.view.MotionEvent import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemTabBinding +import org.oxycblt.auxio.list.EditableListListener import org.oxycblt.auxio.list.recycler.DialogRecyclerView import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.util.inflater /** * A [RecyclerView.Adapter] that displays an array of [Tab]s open for configuration. - * @param listener A [Listener] for tab interactions. + * @param listener A [EditableListListener] for tab interactions. */ -class TabAdapter(private val listener: Listener) : RecyclerView.Adapter() { +class TabAdapter(private val listener: EditableListListener) : + RecyclerView.Adapter() { /** The current array of [Tab]s. */ var tabs = arrayOf() private set @@ -75,23 +76,6 @@ class TabAdapter(private val listener: Listener) : RecyclerView.Adapter Invisible and vice versa). - * @param tabMode The [MusicMode] of the tab clicked. - */ - fun onToggleVisibility(tabMode: MusicMode) - - /** - * Called when the drag handle on a [RecyclerView.ViewHolder] is clicked, requesting that a - * drag should be started. - * @param viewHolder The [RecyclerView.ViewHolder] to start dragging. - */ - fun onPickUp(viewHolder: RecyclerView.ViewHolder) - } - private companion object { val PAYLOAD_TAB_CHANGED = Any() } @@ -106,12 +90,11 @@ class TabViewHolder private constructor(private val binding: ItemTabBinding) : /** * Bind new data to this instance. * @param tab The new [Tab] to bind. - * @param listener A [TabAdapter.Listener] to bind interactions to. + * @param listener A [EditableListListener] to bind interactions to. */ @SuppressLint("ClickableViewAccessibility") - fun bind(tab: Tab, listener: TabAdapter.Listener) { - binding.root.setOnClickListener { listener.onToggleVisibility(tab.mode) } - + fun bind(tab: Tab, listener: EditableListListener) { + listener.bind(tab, this, dragHandle = binding.tabDragHandle) binding.tabCheckBox.apply { // Update the CheckBox name to align with the mode setText( @@ -126,15 +109,6 @@ class TabViewHolder private constructor(private val binding: ItemTabBinding) : // the tab data since they are in the same data structure (Tab) isChecked = tab is Tab.Visible } - - // Set up the drag handle to start a drag whenever it is touched. - binding.tabDragHandle.setOnTouchListener { _, motionEvent -> - binding.tabDragHandle.performClick() - if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) { - listener.onPickUp(this) - true - } else false - } } companion object { diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt index 063086b15..6c9706762 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt @@ -25,7 +25,8 @@ import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogTabsBinding -import org.oxycblt.auxio.music.MusicMode +import org.oxycblt.auxio.list.EditableListListener +import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.ViewBindingDialogFragment import org.oxycblt.auxio.util.logD @@ -34,7 +35,7 @@ import org.oxycblt.auxio.util.logD * A [ViewBindingDialogFragment] that allows the user to modify the home [Tab] configuration. * @author Alexander Capehart (OxygenCobalt) */ -class TabCustomizeDialog : ViewBindingDialogFragment(), TabAdapter.Listener { +class TabCustomizeDialog : ViewBindingDialogFragment(), EditableListListener { private val tabAdapter = TabAdapter(this) private var touchHelper: ItemTouchHelper? = null @@ -80,12 +81,11 @@ class TabCustomizeDialog : ViewBindingDialogFragment(), TabAd binding.tabRecycler.adapter = null } - override fun onToggleVisibility(tabMode: MusicMode) { - logD("Toggling tab $tabMode") - + override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { + check(item is Tab) { "Unexpected datatype: ${item::class.java}" } // We will need the exact index of the tab to update on in order to // notify the adapter of the change. - val index = tabAdapter.tabs.indexOfFirst { it.mode == tabMode } + val index = tabAdapter.tabs.indexOfFirst { it.mode == item.mode } val tab = tabAdapter.tabs[index] tabAdapter.setTab( index, diff --git a/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt b/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt index 5b9e814a9..11fa59b22 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/ImageGroup.kt @@ -107,7 +107,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr // Playback indicator should sit above the inner StyledImageView and custom view/ addView(playbackIndicatorView) - // Selction indicator should never be obscured, so place it at the top. + // Selection indicator should never be obscured, so place it at the top. addView( selectionIndicatorView, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply { diff --git a/app/src/main/java/org/oxycblt/auxio/list/ListFragment.kt b/app/src/main/java/org/oxycblt/auxio/list/ListFragment.kt index f8071816e..250992d04 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/ListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/ListFragment.kt @@ -22,6 +22,7 @@ import android.view.View import androidx.annotation.MenuRes import androidx.appcompat.widget.PopupMenu import androidx.fragment.app.activityViewModels +import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R @@ -53,7 +54,7 @@ abstract class ListFragment : SelectionFragment(), Selecta */ abstract fun onRealClick(music: Music) - override fun onClick(item: Item) { + override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { check(item is Music) { "Unexpected datatype: ${item::class.simpleName}" } if (selectionModel.selected.value.isNotEmpty()) { // Map clicking an item to selecting an item when items are already selected. diff --git a/app/src/main/java/org/oxycblt/auxio/list/Listeners.kt b/app/src/main/java/org/oxycblt/auxio/list/Listeners.kt index 926fb6904..c8f9ebb6d 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/Listeners.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/Listeners.kt @@ -17,8 +17,8 @@ package org.oxycblt.auxio.list +import android.view.MotionEvent import android.view.View -import android.widget.Button import androidx.recyclerview.widget.RecyclerView /** @@ -26,13 +26,63 @@ import androidx.recyclerview.widget.RecyclerView * @author Alexander Capehart (OxygenCobalt) */ interface ClickableListListener { - // TODO: Supply a ViewHolder on clicks - // (allows editable lists to be standardized into a listener.) /** * Called when an [Item] in the list is clicked. * @param item The [Item] that was clicked. + * @param viewHolder The [RecyclerView.ViewHolder] of the item that was clicked. */ - fun onClick(item: Item) + fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) + + /** + * Binds this instance to a list item. + * @param item The [Item] that this list entry is bound to. + * @param viewHolder The [RecyclerView.ViewHolder] of the item that was clicked. + * @param bodyView The [View] containing the main body of the list item. Any click events on + * this [View] are routed to the listener. Defaults to the root view. + */ + fun bind( + item: Item, + viewHolder: RecyclerView.ViewHolder, + bodyView: View = viewHolder.itemView + ) { + bodyView.setOnClickListener { onClick(item, viewHolder) } + } +} + +/** + * An extension of [ClickableListListener] that enables list editing functionality. + * @author Alexander Capehart (OxygenCobalt) + */ +interface EditableListListener : ClickableListListener { + /** + * Called when a [RecyclerView.ViewHolder] requests that it should be dragged. + * @param viewHolder The [RecyclerView.ViewHolder] that should start being dragged. + */ + fun onPickUp(viewHolder: RecyclerView.ViewHolder) + + /** + * Binds this instance to a list item. + * @param item The [Item] that this list entry is bound to. + * @param viewHolder The [RecyclerView.ViewHolder] to bind. + * @param bodyView The [View] containing the main body of the list item. Any click events on + * this [View] are routed to the listener. Defaults to the root view. + * @param dragHandle A touchable [View]. Any drag on this view will start a drag event. + */ + fun bind( + item: Item, + viewHolder: RecyclerView.ViewHolder, + bodyView: View = viewHolder.itemView, + dragHandle: View + ) { + bind(item, viewHolder, bodyView) + dragHandle.setOnTouchListener { _, motionEvent -> + dragHandle.performClick() + if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) { + onPickUp(viewHolder) + true + } else false + } + } } /** @@ -55,19 +105,23 @@ interface SelectableListListener : ClickableListListener { /** * Binds this instance to a list item. - * @param viewHolder The [RecyclerView.ViewHolder] to bind. * @param item The [Item] that this list entry is bound to. - * @param menuButton A [Button] that opens a menu. + * @param viewHolder The [RecyclerView.ViewHolder] to bind. + * @param bodyView The [View] containing the main body of the list item. Any click events on + * this [View] are routed to the listener. Defaults to the root view. + * @param menuButton A clickable [View]. Any click events on this [View] will open a menu. */ - fun bind(viewHolder: RecyclerView.ViewHolder, item: Item, menuButton: Button) { - viewHolder.itemView.apply { - // Map clicks to the click listener. - setOnClickListener { onClick(item) } - // Map long clicks to the selection listener. - setOnLongClickListener { - onSelect(item) - true - } + fun bind( + item: Item, + viewHolder: RecyclerView.ViewHolder, + bodyView: View = viewHolder.itemView, + menuButton: View + ) { + bind(item, viewHolder, bodyView) + // Map long clicks to the selection listener. + bodyView.setOnLongClickListener { + onSelect(item) + true } // Map the menu button to the menu opening listener. menuButton.setOnClickListener { onOpenMenu(item, it) } diff --git a/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt b/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt index f8fa090b8..15628c41c 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt @@ -46,7 +46,7 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) : * @param listener An [SelectableListListener] to bind interactions to. */ fun bind(song: Song, listener: SelectableListListener) { - listener.bind(this, song, binding.songMenu) + listener.bind(song, this, menuButton = binding.songMenu) binding.songAlbumCover.bind(song) binding.songName.text = song.resolveName(binding.context) binding.songInfo.text = song.resolveArtistContents(binding.context) @@ -93,7 +93,7 @@ class AlbumViewHolder private constructor(private val binding: ItemParentBinding * @param listener An [SelectableListListener] to bind interactions to. */ fun bind(album: Album, listener: SelectableListListener) { - listener.bind(this, album, binding.parentMenu) + listener.bind(album, this, menuButton = binding.parentMenu) binding.parentImage.bind(album) binding.parentName.text = album.resolveName(binding.context) binding.parentInfo.text = album.resolveArtistContents(binding.context) @@ -142,7 +142,7 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin * @param listener An [SelectableListListener] to bind interactions to. */ fun bind(artist: Artist, listener: SelectableListListener) { - listener.bind(this, artist, binding.parentMenu) + listener.bind(artist, this, menuButton = binding.parentMenu) binding.parentImage.bind(artist) binding.parentName.text = artist.resolveName(binding.context) binding.parentInfo.text = @@ -201,7 +201,7 @@ class GenreViewHolder private constructor(private val binding: ItemParentBinding * @param listener An [SelectableListListener] to bind interactions to. */ fun bind(genre: Genre, listener: SelectableListListener) { - listener.bind(this, genre, binding.parentMenu) + listener.bind(genre, this, menuButton = binding.parentMenu) binding.parentImage.bind(genre) binding.parentName.text = genre.resolveName(binding.context) binding.parentInfo.text = diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index 50ad6b6d8..62e05abe6 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -282,7 +282,7 @@ sealed class Music : Item { private companion object { /** Cached collator instance re-used with [makeCollationKeyImpl]. */ - val COLLATOR = Collator.getInstance().apply { strength = Collator.PRIMARY } + val COLLATOR: Collator = Collator.getInstance().apply { strength = Collator.PRIMARY } } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistChoiceAdapter.kt index f2bbf540d..71bdc09b4 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistChoiceAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistChoiceAdapter.kt @@ -68,7 +68,7 @@ class ArtistChoiceViewHolder(private val binding: ItemPickerChoiceBinding) : * @param listener A [ClickableListListener] to bind interactions to. */ fun bind(artist: Artist, listener: ClickableListListener) { - binding.root.setOnClickListener { listener.onClick(artist) } + listener.bind(artist, this) binding.pickerImage.bind(artist) binding.pickerName.text = artist.resolveName(binding.context) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistNavigationPickerDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistNavigationPickerDialog.kt index a8ea49687..bebfd66da 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistNavigationPickerDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistNavigationPickerDialog.kt @@ -20,6 +20,7 @@ package org.oxycblt.auxio.music.picker import android.os.Bundle import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.navArgs +import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.DialogMusicPickerBinding import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.music.Artist @@ -40,8 +41,8 @@ class ArtistNavigationPickerDialog : ArtistPickerDialog() { super.onBindingCreated(binding, savedInstanceState) } - override fun onClick(item: Item) { - super.onClick(item) + override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { + super.onClick(item, viewHolder) check(item is Artist) { "Unexpected datatype: ${item::class.simpleName}" } // User made a choice, navigate to it. navModel.exploreNavigateTo(item) diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPickerDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPickerDialog.kt index 30b5dd996..0bf780537 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPickerDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPickerDialog.kt @@ -22,6 +22,7 @@ import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogMusicPickerBinding import org.oxycblt.auxio.list.ClickableListListener @@ -67,7 +68,7 @@ abstract class ArtistPickerDialog : binding.pickerRecycler.adapter = null } - override fun onClick(item: Item) { + override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { findNavController().navigateUp() } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPlaybackPickerDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPlaybackPickerDialog.kt index b81e2604a..24ed8af43 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPlaybackPickerDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/ArtistPlaybackPickerDialog.kt @@ -19,6 +19,7 @@ package org.oxycblt.auxio.music.picker import android.os.Bundle import androidx.navigation.fragment.navArgs +import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.DialogMusicPickerBinding import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.music.Artist @@ -41,8 +42,8 @@ class ArtistPlaybackPickerDialog : ArtistPickerDialog() { super.onBindingCreated(binding, savedInstanceState) } - override fun onClick(item: Item) { - super.onClick(item) + override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { + super.onClick(item, viewHolder) // User made a choice, play the given song from that artist. check(item is Artist) { "Unexpected datatype: ${item::class.simpleName}" } val song = pickerModel.currentItem.value diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/GenreChoiceAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/GenreChoiceAdapter.kt index 527c9e9d9..49b5c758a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/GenreChoiceAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/GenreChoiceAdapter.kt @@ -68,7 +68,7 @@ class GenreChoiceViewHolder(private val binding: ItemPickerChoiceBinding) : * @param listener A [ClickableListListener] to bind interactions to. */ fun bind(genre: Genre, listener: ClickableListListener) { - binding.root.setOnClickListener { listener.onClick(genre) } + listener.bind(genre, this) binding.pickerImage.bind(genre) binding.pickerName.text = genre.resolveName(binding.context) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/GenrePlaybackPickerDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/GenrePlaybackPickerDialog.kt index 9ba9368fe..0a197ab0e 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/GenrePlaybackPickerDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/GenrePlaybackPickerDialog.kt @@ -23,6 +23,7 @@ import androidx.appcompat.app.AlertDialog import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs +import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogMusicPickerBinding import org.oxycblt.auxio.list.ClickableListListener @@ -74,7 +75,7 @@ class GenrePlaybackPickerDialog : binding.pickerRecycler.adapter = null } - override fun onClick(item: Item) { + override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { // User made a choice, play the given song from that genre. check(item is Genre) { "Unexpected datatype: ${item::class.simpleName}" } val song = pickerModel.currentItem.value diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt index d9d34fa02..67d301c36 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt @@ -72,7 +72,6 @@ class IndexingNotification(private val context: Context) : // Determinate state, show an active progress meter. Since these updates arrive // highly rapidly, only update every 1.5 seconds to prevent notification rate // limiting. - // TODO: Can I port this to the playback notification somehow? val now = SystemClock.elapsedRealtime() if (lastUpdateTime > -1 && (now - lastUpdateTime) < 1500) { return false diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt index f3ef164bd..50548c430 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt @@ -198,7 +198,7 @@ class IndexerService : // 2. If a non-foreground service is killed, the app will probably still be alive, // and thus the music library will not be updated at all. // TODO: Assuming I unify this with PlaybackService, it's possible that I won't need - // this anymore. + // this anymore, or at least I only have to use it when the app task is not removed. if (!foregroundManager.tryStartForeground(observingNotification)) { observingNotification.post() } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt index 3a629f8b7..c3d69cd57 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -102,6 +102,7 @@ class PlaybackPanelFragment : binding.playbackSeekBar.listener = this // Set up actions + // TODO: Add better playback button accessibility binding.playbackRepeat.setOnClickListener { playbackModel.toggleRepeatMode() } binding.playbackSkipPrev.setOnClickListener { playbackModel.prev() } binding.playbackPlayPause.setOnClickListener { playbackModel.toggleIsPlaying() } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt index cc2f7c3d1..baa4aa76c 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -19,7 +19,6 @@ package org.oxycblt.auxio.playback.queue import android.annotation.SuppressLint import android.graphics.drawable.LayerDrawable -import android.view.MotionEvent import android.view.View import android.view.ViewGroup import androidx.core.view.isInvisible @@ -27,6 +26,7 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.shape.MaterialShapeDrawable import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemQueueSongBinding +import org.oxycblt.auxio.list.EditableListListener import org.oxycblt.auxio.list.recycler.PlayingIndicatorAdapter import org.oxycblt.auxio.list.recycler.SongViewHolder import org.oxycblt.auxio.list.recycler.SyncListDiffer @@ -38,10 +38,11 @@ import org.oxycblt.auxio.util.inflater /** * A [RecyclerView.Adapter] that shows an editable list of queue items. - * @param listener A [Listener] to bind interactions to. + * @param listener A [EditableListListener] to bind interactions to. * @author Alexander Capehart (OxygenCobalt) */ -class QueueAdapter(private val listener: Listener) : RecyclerView.Adapter() { +class QueueAdapter(private val listener: EditableListListener) : + RecyclerView.Adapter() { private var differ = SyncListDiffer(this, QueueSongViewHolder.DIFF_CALLBACK) // Since PlayingIndicator adapter relies on an item value, we cannot use it for this // adapter, as one item can appear at several points in the UI. Use a similar implementation @@ -121,22 +122,6 @@ class QueueAdapter(private val listener: Listener) : RecyclerView.Adapter - binding.songDragHandle.performClick() - if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) { - listener.onPickUp(this) - true - } else false - } } override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index e12fa0f0f..826d04270 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -26,6 +26,8 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlin.math.min import org.oxycblt.auxio.databinding.FragmentQueueBinding +import org.oxycblt.auxio.list.EditableListListener +import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.ViewBindingFragment @@ -37,7 +39,7 @@ import org.oxycblt.auxio.util.logD * A [ViewBindingFragment] that displays an editable queue. * @author Alexander Capehart (OxygenCobalt) */ -class QueueFragment : ViewBindingFragment(), QueueAdapter.Listener { +class QueueFragment : ViewBindingFragment(), EditableListListener { private val queueModel: QueueViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels() private val queueAdapter = QueueAdapter(this) @@ -79,8 +81,7 @@ class QueueFragment : ViewBindingFragment(), QueueAdapter. binding.queueRecycler.adapter = null } - override fun onClick(viewHolder: RecyclerView.ViewHolder) { - // Clicking on a queue item should start playing it. + override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { queueModel.goto(viewHolder.bindingAdapterPosition) } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentAdapter.kt b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentAdapter.kt index 8ac060be5..a4c1e6015 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentAdapter.kt @@ -94,13 +94,13 @@ class AccentViewHolder private constructor(private val binding: ItemAccentBindin * @param listener A [ClickableListListener] to bind interactions to. */ fun bind(accent: Accent, listener: ClickableListListener) { + listener.bind(accent, this, binding.accent) binding.accent.apply { - setOnClickListener { listener.onClick(accent) } - backgroundTintList = context.getColorCompat(accent.primary) // Add a Tooltip based on the content description so that the purpose of this // button can be clear. contentDescription = context.getString(accent.name) TooltipCompat.setTooltipText(this, contentDescription) + backgroundTintList = context.getColorCompat(accent.primary) } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt index 1f3048725..2cb3c93d4 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt @@ -20,6 +20,7 @@ package org.oxycblt.auxio.ui.accent import android.os.Bundle import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog +import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogAccentBinding @@ -79,7 +80,7 @@ class AccentCustomizeDialog : binding.accentRecycler.adapter = null } - override fun onClick(item: Item) { + override fun onClick(item: Item, viewHolder: RecyclerView.ViewHolder) { check(item is Accent) { "Unexpected datatype: ${item::class.java}" } accentAdapter.setSelectedAccent(item) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 30261473b..7b03934ee 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -60,9 +60,9 @@ Mixtapes Mixtape - + Mixes - + Mix @@ -341,7 +341,10 @@ - + %1$s, %2$s @@ -365,7 +368,7 @@ Albums loaded: %d Artists loaded: %d Genres loaded: %d - + Total duration: %s diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index e55914587..0d9401379 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -1,4 +1,4 @@ -Auxio is a local music player with a fast, reliable UI/UX without the many useless features present in other music players. Built off of Exoplayer, Auxio has superior library support and listening quality compared to other apps that use outdated android functionality. In short, It plays music. +Auxio is a local music player with a fast, reliable UI/UX without the many useless features present in other music players. Built off of Exoplayer, Auxio has superior library support and listening quality compared to other apps that use outdated android functionality. In short, It plays music. Features diff --git a/prebuild.py b/prebuild.py index f8353d1a7..09e632feb 100755 --- a/prebuild.py +++ b/prebuild.py @@ -19,13 +19,14 @@ import re # WARNING: THE EXOPLAYER VERSION MUST BE KEPT IN LOCK-STEP WITH THE FLAC EXTENSION AND # THE GRADLE DEPENDENCY. IF NOT, VERY UNFRIENDLY BUILD FAILURES AND CRASHES MAY ENSUE. -# EXO_VERSION = "2.18.1" +# EXO_VERSION = "2.18.2" FLAC_VERSION = "1.3.2" -FATAL="\033[1;31m" -WARN="\033[1;91m" -INFO="\033[1;94m" -OK="\033[1;92m" +OK="\033[1;32m" # Bold green +FATAL="\033[1;31m" # Bold red +WARN="\033[1;33m" # Bold yellow +RUN="\033[1;34m" # Bold blue +INFO="\033[1m" # Bold white NC="\033[0m" # We do some shell scripting later on, so we can't support windows. @@ -36,7 +37,7 @@ if system not in ["Linux", "Darwin"]: sys.exit(1) def sh(cmd): - print(INFO + "execute: " + NC + cmd) + print(RUN + "execute: " + NC + cmd) code = subprocess.call(["sh", "-c", "set -e; " + cmd]) if code != 0: print(FATAL + "fatal:" + NC + " command failed with exit code " + str(code)) @@ -79,11 +80,11 @@ if ndk_path is None or not os.path.isfile(os.path.join(ndk_path, "ndk-build")): "candidates were found however:") for i, candidate in enumerate(candidates): print("[" + str(i) + "] " + candidate) - print(WARN + "info:" + NC + " NDK r21e is recommended for this script. Other " + + print(INFO + "info:" + NC + " NDK r21e is recommended for this script. Other " + "NDKs may result in unexpected behavior.") try: ndk_path = candidates[int(input("enter the ndk to use [default 0]: "))] - except: + except ValueError: ndk_path = candidates[0] else: print(FATAL + "fatal:" + NC + " the android ndk was not installed at a " +