diff --git a/app/src/main/java/org/oxycblt/auxio/coil/AlbumArtFetcher.kt b/app/src/main/java/org/oxycblt/auxio/coil/AlbumArtFetcher.kt index 639f7d094..b45194661 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/AlbumArtFetcher.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/AlbumArtFetcher.kt @@ -57,6 +57,10 @@ class AlbumArtFetcher(private val context: Context) : Fetcher { ): FetchResult { val settingsManager = SettingsManager.getInstance() + if (!settingsManager.showCovers) { + error("Covers are disabled") + } + val result = if (settingsManager.useQualityCovers) { fetchQualityCovers(data.songs[0]) } else { diff --git a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt index e54a86b45..8aebf157f 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt @@ -28,7 +28,6 @@ import coil.Coil import coil.fetch.Fetcher import coil.request.ImageRequest import coil.size.OriginalSize -import coil.transform.RoundedCornersTransformation import org.oxycblt.auxio.R import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist @@ -44,7 +43,7 @@ import org.oxycblt.auxio.settings.SettingsManager */ @BindingAdapter("albumArt") fun ImageView.bindAlbumArt(song: Song?) { - load(song?.album, R.drawable.ic_song, AlbumArtFetcher(context)) + load(song?.album, R.drawable.ic_album, AlbumArtFetcher(context)) } /** @@ -69,13 +68,6 @@ fun ImageView.bindArtistImage(artist: Artist?) { @BindingAdapter("genreImage") fun ImageView.bindGenreImage(genre: Genre?) { load(genre, R.drawable.ic_genre, MosaicFetcher(context)) - - if (genre != null) { - contentDescription = context.getString( - R.string.desc_genre_image, - genre.resolvedName - ) - } } /** @@ -91,14 +83,7 @@ inline fun ImageView.load( @DrawableRes error: Int, fetcher: Fetcher, ) { - val settingsManager = SettingsManager.getInstance() - - if (!settingsManager.showCovers) { - setImageResource(error) - return - } - - Coil.imageLoader(context).enqueue( + val disposable = Coil.imageLoader(context).enqueue( ImageRequest.Builder(context) .target(this) .data(data) @@ -118,7 +103,6 @@ inline fun ImageView.load( fun loadBitmap( context: Context, song: Song, - cornerRadius: Float = 0f, onDone: (Bitmap?) -> Unit ) { val settingsManager = SettingsManager.getInstance() @@ -132,7 +116,6 @@ fun loadBitmap( ImageRequest.Builder(context) .data(song.album) .fetcher(AlbumArtFetcher(context)) - .transformations(RoundedCornersTransformation(cornerRadius)) .size(OriginalSize) .target( onError = { onDone(null) }, diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt index 2c7bef056..16c7b1af1 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -31,7 +31,7 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentDetailBinding -import org.oxycblt.auxio.music.Music +import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.SortMode import org.oxycblt.auxio.ui.memberBinding @@ -71,17 +71,17 @@ abstract class DetailFragment : Fragment() { /** * Shortcut method for doing setup of the detail toolbar. - * @param music Music data to use as the toolbar title + * @param data Parent data to use as the toolbar title * @param menu Menu resource to use * @param onMenuClick (Optional) a click listener for that menu */ protected fun setupToolbar( - music: Music, + data: MusicParent, @MenuRes menu: Int = -1, onMenuClick: ((itemId: Int) -> Boolean)? = null ) { binding.detailToolbar.apply { - title = music.name + title = data.resolvedName if (menu != -1) { inflateMenu(menu) diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt index aca7c3d41..e784e677e 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt @@ -52,7 +52,7 @@ import kotlin.math.sqrt class FastScrollPopupDrawable(context: Context) : Drawable() { private val paint: Paint = Paint().apply { isAntiAlias = true - color = R.attr.colorControlActivated.resolveAttr(context) + color = R.attr.colorSecondary.resolveAttr(context) style = Paint.Style.FILL } @@ -86,7 +86,7 @@ class FastScrollPopupDrawable(context: Context) : Drawable() { else -> if (!path.isConvex) { // The outline path must be convex before Q, but we may run into floating point - // error caused by calculations involving sqrt(2) or OEM implementation differences, + // errors caused by calculations involving sqrt(2) or OEM implementation differences, // so in this case we just omit the shadow instead of crashing. super.getOutline(outline) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt index ef47287ae..b558965ca 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt @@ -46,7 +46,7 @@ class PlaybackNotification private constructor( mediaToken: MediaSessionCompat.Token ) : NotificationCompat.Builder(context, CHANNEL_ID) { init { - setSmallIcon(R.drawable.ic_notif) + setSmallIcon(R.drawable.ic_auxio) setCategory(NotificationCompat.CATEGORY_SERVICE) setShowWhen(false) setSilent(true) diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt index c649710ab..d138497a5 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -36,7 +36,6 @@ import java.text.Normalizer /** * The [ViewModel] for the search functionality - * TODO: Implement fuzzy search? * @author OxygenCobalt */ class SearchViewModel : ViewModel(), MusicStore.MusicCallback { diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt index ab2f55d85..7f0244930 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt @@ -24,6 +24,7 @@ import androidx.annotation.LayoutRes import org.oxycblt.auxio.R import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.system.PlaybackService +import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.newBroadcastIntent import org.oxycblt.auxio.util.newMainIntent @@ -41,7 +42,9 @@ private fun createViews( return views } -private fun RemoteViews.applyMeta(state: WidgetState): RemoteViews { +private fun RemoteViews.applyMeta(context: Context, state: WidgetState): RemoteViews { + applyCover(context, state) + setTextViewText(R.id.widget_song, state.song.name) setTextViewText(R.id.widget_artist, state.song.album.artist.resolvedName) @@ -55,20 +58,15 @@ private fun RemoteViews.applyCover(context: Context, state: WidgetState): Remote R.id.widget_cover, context.getString(R.string.desc_album_cover, state.song.album.name) ) } else { - setImageViewResource(R.id.widget_cover, R.drawable.ic_song) + logD("WHY ARE YOU NOT WORKING") + setImageViewResource(R.id.widget_cover, R.drawable.ic_widget_album) setContentDescription(R.id.widget_cover, context.getString(R.string.desc_no_cover)) } return this } -private fun RemoteViews.applyControls(context: Context, state: WidgetState): RemoteViews { - setOnClickPendingIntent( - R.id.widget_skip_prev, - context.newBroadcastIntent( - PlaybackService.ACTION_SKIP_PREV - ) - ) +private fun RemoteViews.applyPlayControls(context: Context, state: WidgetState): RemoteViews { setOnClickPendingIntent( R.id.widget_play_pause, context.newBroadcastIntent( @@ -76,13 +74,6 @@ private fun RemoteViews.applyControls(context: Context, state: WidgetState): Rem ) ) - setOnClickPendingIntent( - R.id.widget_skip_next, - context.newBroadcastIntent( - PlaybackService.ACTION_SKIP_NEXT - ) - ) - setImageViewResource( R.id.widget_play_pause, if (state.isPlaying) { @@ -95,6 +86,26 @@ private fun RemoteViews.applyControls(context: Context, state: WidgetState): Rem return this } +private fun RemoteViews.applyControls(context: Context, state: WidgetState): RemoteViews { + applyPlayControls(context, state) + + setOnClickPendingIntent( + R.id.widget_skip_prev, + context.newBroadcastIntent( + PlaybackService.ACTION_SKIP_PREV + ) + ) + + setOnClickPendingIntent( + R.id.widget_skip_next, + context.newBroadcastIntent( + PlaybackService.ACTION_SKIP_NEXT + ) + ) + + return this +} + private fun RemoteViews.applyFullControls(context: Context, state: WidgetState): RemoteViews { applyControls(context, state) @@ -133,41 +144,59 @@ private fun RemoteViews.applyFullControls(context: Context, state: WidgetState): return this } +/** + * The default widget is displayed whenever there is no music playing. It just shows the + * message "No music playing". + */ fun createDefaultWidget(context: Context): RemoteViews { return createViews(context, R.layout.widget_default) } +/** + * The tiny widget is for an edge-case situation where a 2xN widget happens to be smaller than + * 100dp. It just shows the cover, titles, and a button. + */ fun createTinyWidget(context: Context, state: WidgetState): RemoteViews { return createViews(context, R.layout.widget_tiny) - .applyMeta(state) - .applyCover(context, state) - .applyControls(context, state) -} - -fun createWideWidget(context: Context, state: WidgetState): RemoteViews { - return createViews(context, R.layout.widget_wide) - .applyMeta(state) - .applyCover(context, state) - .applyFullControls(context, state) + .applyMeta(context, state) + .applyPlayControls(context, state) } +/** + * The small widget is for 2x2 widgets and just shows the cover art and playback controls. + * This is generally because a Medium widget is too large for this widget size and a text-only + * widget is too small for this widget size. + */ fun createSmallWidget(context: Context, state: WidgetState): RemoteViews { return createViews(context, R.layout.widget_small) - .applyMeta(state) .applyCover(context, state) .applyControls(context, state) } +/** + * The medium widget is for 2x3 widgets and shows the cover art, title/artist, and three + * controls. This is the default widget configuration. + */ fun createMediumWidget(context: Context, state: WidgetState): RemoteViews { return createViews(context, R.layout.widget_medium) - .applyMeta(state) - .applyCover(context, state) + .applyMeta(context, state) .applyControls(context, state) } -fun createLargeWidget(context: Context, state: WidgetState): RemoteViews { - return createViews(context, R.layout.widget_large) - .applyMeta(state) +/** + * The wide widget is for Nx2 widgets and is like the small widget but with more controls. + */ +fun createWideWidget(context: Context, state: WidgetState): RemoteViews { + return createViews(context, R.layout.widget_wide) .applyCover(context, state) .applyFullControls(context, state) } + +/** + * The large widget is for 3x4 widgets and shows all metadata and controls. + */ +fun createLargeWidget(context: Context, state: WidgetState): RemoteViews { + return createViews(context, R.layout.widget_large) + .applyMeta(context, state) + .applyFullControls(context, state) +} diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt index baa32fe7c..27ee2ede8 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt @@ -32,27 +32,23 @@ import android.widget.RemoteViews import androidx.core.graphics.drawable.toBitmap import coil.Coil import coil.request.ImageRequest -import coil.size.OriginalSize import coil.transform.RoundedCornersTransformation import org.oxycblt.auxio.BuildConfig +import org.oxycblt.auxio.R import org.oxycblt.auxio.coil.AlbumArtFetcher import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.util.isLandscape import org.oxycblt.auxio.util.logD +import kotlin.math.min /** * Auxio's one and only appwidget. This widget follows a more unorthodox approach, effectively - * packing what could be considered multiple widgets into a single responsive widget. All types - * are listed below: + * packing what could be considered multiple widgets into a single responsive widget. * - * - Large widgets will show cover art and all controls - * - Tall and thin widgets will show cover art and three controls - * - Wide or small widgets will display a "Stylistic" view with controls and the cover art - * - Tiny widgets [e.g landscape mode] will show cover art, text, and a play/pause control. - * - * There are some minor problems with this implementation [notably UI jittering when the widget - * picks a new layout below Android 12], but this is tolerable. It may be improved in the future. + * This widget is also able to backport it's responsive behavior to android versions below 12, + * albeit with some issues, such as UI jittering and a layout not being picked when the orientation + * changes. This is tolerable. * * For more specific details about these sub-widgets, see Forms.kt. */ @@ -92,28 +88,37 @@ class WidgetProvider : AppWidgetProvider() { } private fun loadWidgetBitmap(context: Context, song: Song, onDone: (Bitmap?) -> Unit) { - val builder = ImageRequest.Builder(context) + // Load our image so that it takes up the phone screen. This allows + // us to get stable rounded corners for every single widget image. This probably + // sacrifices quality in some way, but it's really the only good option. + // Hey google, maybe allow us to use our own views in widgets next time. That would + // be nice. + val metrics = context.resources.displayMetrics + val imageSize = min(metrics.widthPixels, metrics.heightPixels) + + val coverRequest = ImageRequest.Builder(context) .data(song.album) .fetcher(AlbumArtFetcher(context)) - .size(OriginalSize) - .target( - onError = { onDone(null) }, - onSuccess = { onDone(it.toBitmap()) } - ) + .size(imageSize) // If we are on Android 12 or higher, round out the album cover so that the widget is // cohesive. I really don't like this, but whatever. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - builder.transformations( - RoundedCornersTransformation( - context.resources.getDimensionPixelSize( - android.R.dimen.system_app_widget_inner_radius - ).toFloat() - ) + val transform = RoundedCornersTransformation( + context.resources.getDimensionPixelSize( + android.R.dimen.system_app_widget_inner_radius + ).toFloat() ) + + coverRequest.transformations(transform) } - Coil.imageLoader(context).enqueue(builder.build()) + coverRequest.target( + onError = { onDone(null) }, + onSuccess = { onDone(it.toBitmap()) } + ) + + Coil.imageLoader(context).enqueue(coverRequest.build()) } /* @@ -127,7 +132,7 @@ class WidgetProvider : AppWidgetProvider() { ) } - // / --- OVERRIDES --- + // --- OVERRIDES --- override fun onUpdate( context: Context, @@ -147,7 +152,6 @@ class WidgetProvider : AppWidgetProvider() { super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - // We can't resize the widget until we can generate the views, so request an update // from PlaybackService. requestUpdate(context) diff --git a/app/src/main/res/drawable/ic_notif.xml b/app/src/main/res/drawable/ic_auxio.xml similarity index 93% rename from app/src/main/res/drawable/ic_notif.xml rename to app/src/main/res/drawable/ic_auxio.xml index d7689cd85..d986fad3a 100644 --- a/app/src/main/res/drawable/ic_notif.xml +++ b/app/src/main/res/drawable/ic_auxio.xml @@ -1,7 +1,7 @@ + + + + + diff --git a/app/src/main/res/layout-land/fragment_playback.xml b/app/src/main/res/layout-land/fragment_playback.xml index 40eba7991..afc432fbb 100644 --- a/app/src/main/res/layout-land/fragment_playback.xml +++ b/app/src/main/res/layout-land/fragment_playback.xml @@ -45,7 +45,7 @@ app:layout_constraintDimensionRatio="1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/playback_toolbar" - tools:src="@drawable/ic_song" /> + tools:src="@drawable/ic_album" /> diff --git a/app/src/main/res/layout-xlarge-land/fragment_playback.xml b/app/src/main/res/layout-xlarge-land/fragment_playback.xml index 5857b68eb..4ab6094c4 100644 --- a/app/src/main/res/layout-xlarge-land/fragment_playback.xml +++ b/app/src/main/res/layout-xlarge-land/fragment_playback.xml @@ -47,7 +47,7 @@ app:layout_constraintDimensionRatio="1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/playback_toolbar" - tools:src="@drawable/ic_song" /> + tools:src="@drawable/ic_album" /> diff --git a/app/src/main/res/layout-xlarge/fragment_playback.xml b/app/src/main/res/layout-xlarge/fragment_playback.xml index e68e10155..2d2cdba5f 100644 --- a/app/src/main/res/layout-xlarge/fragment_playback.xml +++ b/app/src/main/res/layout-xlarge/fragment_playback.xml @@ -46,7 +46,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/playback_toolbar" - tools:src="@drawable/ic_song" /> + tools:src="@drawable/ic_album" /> + tools:text="Songs Loaded: 1616" + app:drawableTint="?attr/colorControlNormal" /> + tools:src="@drawable/ic_album" /> + tools:src="@drawable/ic_album" /> + tools:src="@drawable/ic_album" /> + tools:src="@drawable/ic_album" /> + tools:src="@drawable/ic_album" /> + tools:src="@drawable/ic_album" /> + android:src="@drawable/ic_auxio" + android:tint="?attr/colorPrimary" /> + android:src="@drawable/ic_widget_album" /> + android:paddingTop="@dimen/spacing_small" + android:paddingBottom="@dimen/spacing_small" + android:paddingStart="@dimen/spacing_medium" + android:paddingEnd="@dimen/spacing_medium"> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_song_actions.xml b/app/src/main/res/menu/menu_song_actions.xml index 93c685d8d..d300e5716 100644 --- a/app/src/main/res/menu/menu_song_actions.xml +++ b/app/src/main/res/menu/menu_song_actions.xml @@ -5,10 +5,8 @@ android:title="@string/lbl_queue_add" /> \ No newline at end of file diff --git a/app/src/main/res/values-night-v31/styles_core.xml b/app/src/main/res/values-night-v31/styles_core.xml index cccf38706..6f16649ed 100644 --- a/app/src/main/res/values-night-v31/styles_core.xml +++ b/app/src/main/res/values-night-v31/styles_core.xml @@ -83,5 +83,7 @@ ?android:attr/colorAccent ?android:attr/colorControlNormal ?android:attr/colorControlHighlight + @color/m3_sys_color_dynamic_dark_inverse_surface + @color/m3_sys_color_dynamic_dark_inverse_on_surface \ No newline at end of file diff --git a/app/src/main/res/values-v31/styles_core.xml b/app/src/main/res/values-v31/styles_core.xml index 7ee5041d0..5eff2c8a0 100644 --- a/app/src/main/res/values-v31/styles_core.xml +++ b/app/src/main/res/values-v31/styles_core.xml @@ -83,5 +83,7 @@ ?android:attr/colorAccent ?android:attr/colorControlNormal ?android:attr/colorControlHighlight + @color/m3_sys_color_dynamic_light_inverse_surface + @color/m3_sys_color_dynamic_light_inverse_on_surface \ No newline at end of file diff --git a/app/src/main/res/values/styles_core.xml b/app/src/main/res/values/styles_core.xml index 7134752c3..33ecef3a5 100644 --- a/app/src/main/res/values/styles_core.xml +++ b/app/src/main/res/values/styles_core.xml @@ -7,9 +7,7 @@ @android:color/black - +