From 84295dcf25751d6cfeb5440d39aebc883eb68956 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Thu, 9 Jun 2022 10:12:48 -0600 Subject: [PATCH] ui: completely rework icon management Completely rework the way Auxio handles icons. This is mostly two changes: 1. Removing ImageButton/StyledImageButton for MaterialButton. This is done by abusing MaterialButton's theming options to make it only show an icon. 2. Standardizing icon sizes into small, medium, and large categories. Small is the default, Medium and Large are for edge-cases like the playback icons which look horrible at 24dp. 3. Abusing the Toolbar to make it follow Material 3 guidelines. This mostly involved removing the strange icon sizing and correctly padding the view. 4. Reworking the playback bar to use more, smaller icons, making it more like a Toolbar in the process (which I like). --- .../org/oxycblt/auxio/home/HomeFragment.kt | 5 +- .../org/oxycblt/auxio/home/HomeViewModel.kt | 9 +- .../org/oxycblt/auxio/image/BaseFetcher.kt | 4 + .../java/org/oxycblt/auxio/music/Indexer.kt | 3 +- .../auxio/playback/PlaybackBarFragment.kt | 4 +- .../auxio/playback/PlaybackPanelFragment.kt | 3 +- .../playback/system/NotificationComponent.kt | 2 +- .../oxycblt/auxio/settings/SettingsCompat.kt | 1 + .../auxio/ui/IndicatorMaterialButton.kt | 54 ++++++++ .../org/oxycblt/auxio/ui/StyledImageButton.kt | 124 ------------------ .../oxycblt/auxio/ui/accent/AccentAdapter.kt | 2 +- .../ui/accent/AccentGridLayoutManager.kt | 2 +- .../{ic_shuffle_state.xml => ic_shuffle.xml} | 2 +- .../layout-land/fragment_playback_panel.xml | 30 ++--- .../fragment_playback_panel.xml | 32 ++--- .../fragment_playback_panel.xml | 32 ++--- .../layout-sw640dp/fragment_playback_bar.xml | 49 +++---- .../fragment_playback_panel.xml | 30 ++--- .../layout-w600dp/fragment_playback_bar.xml | 97 -------------- app/src/main/res/layout/fragment_detail.xml | 4 +- app/src/main/res/layout/fragment_home.xml | 7 +- .../main/res/layout/fragment_playback_bar.xml | 19 ++- .../res/layout/fragment_playback_panel.xml | 34 +++-- app/src/main/res/layout/fragment_search.xml | 2 +- app/src/main/res/layout/item_accent.xml | 12 +- app/src/main/res/layout/item_excluded_dir.xml | 17 +-- app/src/main/res/layout/item_queue_song.xml | 13 +- app/src/main/res/layout/item_sort_header.xml | 12 +- app/src/main/res/layout/item_tab.xml | 15 +-- app/src/main/res/layout/widget_large.xml | 2 +- app/src/main/res/layout/widget_wide.xml | 2 +- app/src/main/res/values-sw600dp/styles_ui.xml | 26 ++++ app/src/main/res/values-sw640dp/styles_ui.xml | 1 - app/src/main/res/values/attrs.xml | 5 - app/src/main/res/values/dimens.xml | 9 +- app/src/main/res/values/styles_android.xml | 19 +++ app/src/main/res/values/styles_core.xml | 3 + app/src/main/res/values/styles_ui.xml | 94 ++++++++++--- 38 files changed, 347 insertions(+), 434 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/ui/IndicatorMaterialButton.kt delete mode 100644 app/src/main/java/org/oxycblt/auxio/ui/StyledImageButton.kt rename app/src/main/res/drawable/{ic_shuffle_state.xml => ic_shuffle.xml} (92%) delete mode 100644 app/src/main/res/layout-w600dp/fragment_playback_bar.xml create mode 100644 app/src/main/res/values-sw600dp/styles_ui.xml 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 ab9e17810..1dadf3ad8 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -95,7 +95,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI binding.homeToolbar.alpha = 1f - (abs(offset.toFloat()) / (range.toFloat() / 2)) - binding.homePager.updatePadding( + binding.homeContent.updatePadding( bottom = binding.homeAppbar.totalScrollRange + offset) }) } @@ -246,8 +246,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI } private fun updateSortMenu(displayMode: DisplayMode, isVisible: (Int) -> Boolean) { - val sortItem = - requireNotNull(sortItem) { "Cannot update sort menu when view does not exist" } + val sortItem = requireNotNull(sortItem) { "Cannot update sort menu while detached" } val toHighlight = homeModel.getSortForDisplay(displayMode) diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index e46933aab..a936af0ff 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -30,7 +30,6 @@ import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.util.logD -import org.oxycblt.auxio.util.unlikelyToBeNull /** * The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state. @@ -107,19 +106,19 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback when (_currentTab.value) { DisplayMode.SHOW_SONGS -> { settingsManager.libSongSort = sort - _songs.value = sort.songs(unlikelyToBeNull(_songs.value)) + _songs.value = sort.songs(_songs.value) } DisplayMode.SHOW_ALBUMS -> { settingsManager.libAlbumSort = sort - _albums.value = sort.albums(unlikelyToBeNull(_albums.value)) + _albums.value = sort.albums(_albums.value) } DisplayMode.SHOW_ARTISTS -> { settingsManager.libArtistSort = sort - _artists.value = sort.artists(unlikelyToBeNull(_artists.value)) + _artists.value = sort.artists(_artists.value) } DisplayMode.SHOW_GENRES -> { settingsManager.libGenreSort = sort - _genres.value = sort.genres(unlikelyToBeNull(_genres.value)) + _genres.value = sort.genres(_genres.value) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/image/BaseFetcher.kt b/app/src/main/java/org/oxycblt/auxio/image/BaseFetcher.kt index d7e465fc2..50cfc0e67 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/BaseFetcher.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/BaseFetcher.kt @@ -75,6 +75,7 @@ abstract class BaseFetcher : Fetcher { } } catch (e: Exception) { logW("Unable to extract album art due to an error") + logW(e.stackTraceToString()) null } } @@ -114,6 +115,9 @@ abstract class BaseFetcher : Fetcher { } private fun fetchAospMetadataCovers(context: Context, album: Album): InputStream? { + // FIXME: Do not use use here, as Lollipop devices apparently do not have + // MediaMetadataRetriever implemented as AutoClosable. + MediaMetadataRetriever().use { ext -> // This call is time-consuming but it also doesn't seem to hold up the main thread, // so it's probably fine not to wrap it. diff --git a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt index 2bdfc2213..7152c70e6 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt @@ -28,7 +28,6 @@ import kotlinx.coroutines.withContext import org.oxycblt.auxio.music.backend.Api21MediaStoreBackend import org.oxycblt.auxio.music.backend.Api29MediaStoreBackend import org.oxycblt.auxio.music.backend.Api30MediaStoreBackend -import org.oxycblt.auxio.music.backend.ExoPlayerBackend import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE @@ -188,7 +187,7 @@ class Indexer { else -> Api21MediaStoreBackend() } - val songs = buildSongs(context, ExoPlayerBackend(mediaStoreBackend), generation) + val songs = buildSongs(context, mediaStoreBackend, generation) if (songs.isEmpty()) { return null } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt index 9f38e75bc..296c6fbb2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarFragment.kt @@ -75,11 +75,9 @@ class PlaybackBarFragment : ViewBindingFragment() { binding.playbackProgressBar.trackColor = requireContext().getColorStateListSafe(R.color.sel_track).defaultColor - binding.playbackSkipPrev?.setOnClickListener { playbackModel.prev() } - binding.playbackPlayPause.setOnClickListener { playbackModel.invertPlaying() } - binding.playbackSkipNext?.setOnClickListener { playbackModel.next() } + binding.playbackSkipNext.setOnClickListener { playbackModel.next() } // -- VIEWMODEL SETUP --- 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 b87223f93..0fc0b0d9a 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -32,6 +32,7 @@ import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.ViewBindingFragment +import org.oxycblt.auxio.util.getDrawableSafe import org.oxycblt.auxio.util.getSystemBarInsetsCompat import org.oxycblt.auxio.util.launch import org.oxycblt.auxio.util.logD @@ -162,7 +163,7 @@ class PlaybackPanelFragment : private fun updateRepeat(repeatMode: RepeatMode) { requireBinding().playbackRepeat.apply { isActivated = repeatMode != RepeatMode.NONE - setImageResource(repeatMode.icon) + icon = requireContext().getDrawableSafe(repeatMode.icon) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt index cb6a03176..ce5416265 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt @@ -170,7 +170,7 @@ class NotificationComponent( isShuffled: Boolean ): NotificationCompat.Action { val drawableRes = - if (isShuffled) R.drawable.ic_shuffle_state else R.drawable.ic_remote_shuffle_off + if (isShuffled) R.drawable.ic_shuffle else R.drawable.ic_remote_shuffle_off return buildAction(context, PlaybackService.ACTION_INVERT_SHUFFLE, drawableRes) } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt index 93ae2f008..af14da91b 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt @@ -74,6 +74,7 @@ fun handleAccentCompat(prefs: SharedPreferences): Accent { } } + // TODO: Default accent on android 12 should be dynamic colors return Accent.from(prefs.getInt(SettingsManager.KEY_ACCENT, 5)) } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/IndicatorMaterialButton.kt b/app/src/main/java/org/oxycblt/auxio/ui/IndicatorMaterialButton.kt new file mode 100644 index 000000000..7183286b3 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/ui/IndicatorMaterialButton.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 Auxio Project + * + * 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.ui + +import android.content.Context +import android.graphics.Canvas +import android.util.AttributeSet +import androidx.annotation.AttrRes +import com.google.android.material.button.MaterialButton +import org.oxycblt.auxio.R +import org.oxycblt.auxio.util.getDrawableSafe + +class IndicatorMaterialButton +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) : + MaterialButton(context, attrs, defStyleAttr) { + private val indicatorDrawable = context.getDrawableSafe(R.drawable.ui_indicator) + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + // Put the indicator right below the icon. + val x = (measuredWidth - indicatorDrawable.intrinsicWidth) / 2 + val y = ((measuredHeight - iconSize) / 2) + iconSize + + indicatorDrawable.bounds.set( + x, y, x + indicatorDrawable.intrinsicWidth, y + indicatorDrawable.intrinsicHeight) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + // I would use onDrawForeground but apparently that isn't called by Lollipop devices. + // This is not referenced in the documentation at all. + if (isActivated) { + indicatorDrawable.draw(canvas) + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/ui/StyledImageButton.kt b/app/src/main/java/org/oxycblt/auxio/ui/StyledImageButton.kt deleted file mode 100644 index f04401770..000000000 --- a/app/src/main/java/org/oxycblt/auxio/ui/StyledImageButton.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2022 Auxio Project - * - * 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.ui - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Matrix -import android.graphics.RectF -import android.util.AttributeSet -import androidx.annotation.AttrRes -import androidx.appcompat.widget.AppCompatImageButton -import org.oxycblt.auxio.R -import org.oxycblt.auxio.util.getDimenSizeSafe -import org.oxycblt.auxio.util.getDrawableSafe - -/** - * An [AppCompatImageButton] that applies many of the stylistic choices that Auxio uses regarding - * buttons. - * - * More specifically, this class add two features: - * - Specification of the icon size. This is to accommodate the playback buttons, which tend to be - * larger as by default the playback icons look terrible with the gobs of whitespace everywhere. - * - Addition of an indicator, which is a dot that can denote when a button is active. This is also - * useful for the playback buttons, as at times highlighting them is not enough to differentiate - * them. - * @author OxygenCobalt - * - * TODO: Remove this for Material Buttons (I hope) - */ -class StyledImageButton -@JvmOverloads -constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) : - AppCompatImageButton(context, attrs, defStyleAttr) { - private val iconSize: Int - private var hasIndicator = false - set(value) { - field = value - invalidate() - } - - private val centerMatrix = Matrix() - private val matrixSrc = RectF() - private val matrixDst = RectF() - private val indicatorDrawable = context.getDrawableSafe(R.drawable.ui_indicator) - - init { - val size = context.getDimenSizeSafe(R.dimen.size_btn_small) - minimumWidth = size - minimumHeight = size - scaleType = ScaleType.MATRIX - setBackgroundResource(R.drawable.ui_large_unbounded_ripple) - - val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageButton) - iconSize = - styledAttrs - .getDimension( - R.styleable.StyledImageButton_iconSize, - context.getDimenSizeSafe(R.dimen.size_icon_normal).toFloat()) - .toInt() - hasIndicator = styledAttrs.getBoolean(R.styleable.StyledImageButton_hasIndicator, false) - styledAttrs.recycle() - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - - // FIXME: Scale this drawable based on available space after padding - - imageMatrix = - centerMatrix.apply { - reset() - drawable?.let { drawable -> - // Android is too good to allow us to set a fixed image size, so we instead need - // to define a matrix to scale an image directly. - - // First scale the icon up to the desired size. - matrixSrc.set( - 0f, - 0f, - drawable.intrinsicWidth.toFloat(), - drawable.intrinsicHeight.toFloat()) - matrixDst.set(0f, 0f, iconSize.toFloat(), iconSize.toFloat()) - centerMatrix.setRectToRect(matrixSrc, matrixDst, Matrix.ScaleToFit.CENTER) - - // Then actually center it into the icon, which the previous call does not - // actually do. - centerMatrix.postTranslate( - (measuredWidth - iconSize) / 2f, (measuredHeight - iconSize) / 2f) - } - } - - // Put the indicator right below the icon. - val x = (measuredWidth - indicatorDrawable.intrinsicWidth) / 2 - val y = ((measuredHeight - iconSize) / 2) + iconSize - - indicatorDrawable.bounds.set( - x, y, x + indicatorDrawable.intrinsicWidth, y + indicatorDrawable.intrinsicHeight) - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - // I would use onDrawForeground but apparently that isn't called by Lollipop devices. - // This is not referenced in the documentation at all. - if (hasIndicator && isActivated) { - indicatorDrawable.draw(canvas) - } - } -} 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 4eb9cd822..a02b819b0 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 @@ -92,7 +92,7 @@ class AccentViewHolder private constructor(private val binding: ItemAccentBindin fun setSelected(isSelected: Boolean) { binding.accent.apply { isEnabled = !isSelected - imageTintList = + iconTint = if (isSelected) { context.getAttrColorSafe(R.attr.colorSurface).stateList } else { diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt index 2cb7b8cab..36513c9e1 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentGridLayoutManager.kt @@ -37,7 +37,7 @@ class AccentGridLayoutManager( ) : GridLayoutManager(context, attrs, defStyleAttr, defStyleRes) { // We use 72dp here since that's the rough size of the accent item. // This will need to be modified if this is used beyond the accent dialog. - private var columnWidth = context.pxOfDp(72f) + private var columnWidth = context.pxOfDp(64f) private var lastWidth = -1 private var lastHeight = -1 diff --git a/app/src/main/res/drawable/ic_shuffle_state.xml b/app/src/main/res/drawable/ic_shuffle.xml similarity index 92% rename from app/src/main/res/drawable/ic_shuffle_state.xml rename to app/src/main/res/drawable/ic_shuffle.xml index 95393c79f..0c2210449 100644 --- a/app/src/main/res/drawable/ic_shuffle_state.xml +++ b/app/src/main/res/drawable/ic_shuffle.xml @@ -2,7 +2,7 @@ - - - - - - @@ -123,27 +123,27 @@ app:layout_constraintTop_toBottomOf="@+id/playback_seek_bar" tools:src="@drawable/ic_pause" /> - - - - @@ -112,27 +112,27 @@ app:layout_constraintStart_toStartOf="@+id/playback_seek_bar" tools:src="@drawable/ic_pause" /> - - + tools:src="@drawable/ic_album" /> - - - - + app:layout_constraintTop_toTopOf="parent" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout-w600dp-land/fragment_playback_panel.xml b/app/src/main/res/layout-w600dp-land/fragment_playback_panel.xml index 99b17d40f..bfdaa31a8 100644 --- a/app/src/main/res/layout-w600dp-land/fragment_playback_panel.xml +++ b/app/src/main/res/layout-w600dp-land/fragment_playback_panel.xml @@ -8,7 +8,7 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_detail.xml b/app/src/main/res/layout/fragment_detail.xml index 0d43bd3de..c4a29ba54 100644 --- a/app/src/main/res/layout/fragment_detail.xml +++ b/app/src/main/res/layout/fragment_detail.xml @@ -15,9 +15,11 @@ app:liftOnScroll="true" app:liftOnScrollTargetViewId="@id/detail_recycler"> + + + style="@style/Widget.Auxio.Toolbar.Icon.Actions" /> diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 104b13dd8..7e3d572b5 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -12,7 +12,9 @@ @@ -30,6 +32,7 @@ @@ -98,7 +101,7 @@ android:layout_height="wrap_content" android:layout_margin="@dimen/spacing_medium" android:contentDescription="@string/desc_shuffle_all" - android:src="@drawable/ic_shuffle_state" /> + android:src="@drawable/ic_shuffle" /> diff --git a/app/src/main/res/layout/fragment_playback_bar.xml b/app/src/main/res/layout/fragment_playback_bar.xml index 218048803..dcf3e67d9 100644 --- a/app/src/main/res/layout/fragment_playback_bar.xml +++ b/app/src/main/res/layout/fragment_playback_bar.xml @@ -43,14 +43,25 @@ app:layout_constraintTop_toBottomOf="@+id/playback_song" tools:text="Artist Name / Album Name" /> - + +