diff --git a/README.md b/README.md index a958860ac..097c58cbb 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,6 @@ Auxio is a local music player with a fast, reliable UI/UX without the many usele I primarily built Auxio for myself, but you can use it too, I guess. -**Note: Auxio is in a point that I am largely satisfied with. The app is still maintained, but feature additions will be slow.** - ## Screenshots

diff --git a/app/src/main/java/org/oxycblt/auxio/ui/AndroidUtils.kt b/app/src/main/java/org/oxycblt/auxio/AuxioUtils.kt similarity index 86% rename from app/src/main/java/org/oxycblt/auxio/ui/AndroidUtils.kt rename to app/src/main/java/org/oxycblt/auxio/AuxioUtils.kt index abdca58b8..ec7e23766 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/AndroidUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/AuxioUtils.kt @@ -1,6 +1,6 @@ /* * Copyright (c) 2021 Auxio Project - * AndroidUtils.kt is part of Auxio. + * AuxioUtils.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 @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui +package org.oxycblt.auxio import android.app.Activity import android.app.PendingIntent @@ -46,9 +46,6 @@ import androidx.annotation.PluralsRes import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView -import org.oxycblt.auxio.MainActivity -import org.oxycblt.auxio.R -import org.oxycblt.auxio.logE import kotlin.reflect.KClass const val INTENT_REQUEST_CODE = 0xA0A0 @@ -60,7 +57,7 @@ const val INTENT_REQUEST_CODE = 0xA0A0 */ fun ImageButton.disable() { if (isEnabled) { - imageTintList = R.color.inactive.toStateList(context) + imageTintList = R.color.inactive.resolveStateList(context) isEnabled = false } } @@ -69,9 +66,24 @@ fun ImageButton.disable() { * Set a [TextView] text color, without having to resolve the resource. */ fun TextView.setTextColorResource(@ColorRes color: Int) { - setTextColor(color.toColor(context)) + setTextColor(color.resolveColor(context)) } +/** + * Get the span count for most RecyclerViews. These probably work right on most displays. Trust me. + */ +val RecyclerView.spans: Int get() = + if (context.isLandscape()) { + if (context.isXLTablet()) 3 else 2 + } else { + if (context.isXLTablet()) 2 else 1 + } + +/** + * Returns whether a recyclerview can scroll. + */ +fun RecyclerView.canScroll(): Boolean = computeVerticalScrollRange() > height + // --- CONVENIENCE --- /** @@ -80,14 +92,12 @@ fun TextView.setTextColorResource(@ColorRes color: Int) { val Context.inflater: LayoutInflater get() = LayoutInflater.from(this) /** - * Convenience method for getting a plural. - * @param pluralsRes Resource for the plural - * @param value Int value for the plural. - * @return The formatted string requested + * Returns whether the current UI is in night mode or not. This will work if the theme is + * automatic as well. */ -fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String { - return resources.getQuantityString(pluralsRes, value, value) -} +val Context.isNight: Boolean get() = + resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == + Configuration.UI_MODE_NIGHT_YES /** * Convenience method for getting a system service without nullability issues. @@ -102,80 +112,6 @@ fun Context.getSystemServiceSafe(serviceClass: KClass): T { } } -/** - * Returns whether the current UI is in night mode or not. This will work if the theme is - * automatic as well. - */ -fun Context.isNight(): Boolean { - return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == - Configuration.UI_MODE_NIGHT_YES -} - -/** - * Resolve a color. - * @param context [Context] required - * @return The resolved color, black if the resolving process failed. - */ -@ColorInt -fun @receiver:ColorRes Int.toColor(context: Context): Int { - return try { - ContextCompat.getColor(context, this) - } catch (e: Resources.NotFoundException) { - logE("Attempted color load failed: ${e.stackTraceToString()}") - - // Default to the emergency color [Black] if the loading fails. - ContextCompat.getColor(context, android.R.color.black) - } -} - -/** - * Resolve a color and turn it into a [ColorStateList] - * @param context [Context] required - * @return The resolved color as a [ColorStateList] - * @see toColor - */ -fun @receiver:ColorRes Int.toStateList(context: Context) = - ColorStateList.valueOf(toColor(context)) - -/** - * Resolve a drawable resource into a [Drawable] - */ -fun @receiver:DrawableRes Int.toDrawable(context: Context) = - ContextCompat.getDrawable(context, this) - -/** - * Resolve a drawable resource into an [AnimatedVectorDrawable] - * @see toDrawable - */ -fun @receiver:DrawableRes Int.toAnimDrawable(context: Context) = - toDrawable(context) as AnimatedVectorDrawable - -/** - * Resolve this int into a color as if it was an attribute - */ -@ColorInt -fun @receiver:AttrRes Int.resolveAttr(context: Context): Int { - // Convert the attribute into its color - val resolvedAttr = TypedValue() - context.theme.resolveAttribute(this, resolvedAttr, true) - - // Then convert it to a proper color - val color = if (resolvedAttr.resourceId != 0) { - resolvedAttr.resourceId - } else { - resolvedAttr.data - } - - return color.toColor(context) -} - -/** - * Create a toast using the provided string resource. - */ -fun Context.showToast(@StringRes str: Int) { - Toast.makeText(applicationContext, getString(str), Toast.LENGTH_SHORT).show() -} - /** * Create a broadcast [PendingIntent] */ @@ -200,6 +136,81 @@ fun Context.newMainIntent(): PendingIntent { ) } +/** + * Create a toast using the provided string resource. + */ +fun Context.showToast(@StringRes str: Int) { + Toast.makeText(applicationContext, getString(str), Toast.LENGTH_SHORT).show() +} + +/** + * Convenience method for getting a plural. + * @param pluralsRes Resource for the plural + * @param value Int value for the plural. + * @return The formatted string requested + */ +fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String { + return resources.getQuantityString(pluralsRes, value, value) +} + +/** + * Resolve a color. + * @param context [Context] required + * @return The resolved color, black if the resolving process failed. + */ +@ColorInt +fun @receiver:ColorRes Int.resolveColor(context: Context): Int { + return try { + ContextCompat.getColor(context, this) + } catch (e: Resources.NotFoundException) { + logE("Attempted color load failed: ${e.stackTraceToString()}") + + // Default to the emergency color [Black] if the loading fails. + ContextCompat.getColor(context, android.R.color.black) + } +} + +/** + * Resolve a color and turn it into a [ColorStateList] + * @param context [Context] required + * @return The resolved color as a [ColorStateList] + * @see resolveColor + */ +fun @receiver:ColorRes Int.resolveStateList(context: Context) = + ColorStateList.valueOf(resolveColor(context)) + +/** + * Resolve a drawable resource into a [Drawable] + */ +fun @receiver:DrawableRes Int.resolveDrawable(context: Context) = + requireNotNull(ContextCompat.getDrawable(context, this)) + +/** + * Resolve a drawable resource into an [AnimatedVectorDrawable] + * @see resolveDrawable + */ +fun @receiver:DrawableRes Int.toAnimDrawable(context: Context) = + resolveDrawable(context) as AnimatedVectorDrawable + +/** + * Resolve this int into a color as if it was an attribute + */ +@ColorInt +fun @receiver:AttrRes Int.resolveAttr(context: Context): Int { + // Convert the attribute into its color + val resolvedAttr = TypedValue() + context.theme.resolveAttribute(this, resolvedAttr, true) + + // Then convert it to a proper color + val color = if (resolvedAttr.resourceId != 0) { + resolvedAttr.resourceId + } else { + resolvedAttr.data + } + + return color.resolveColor(context) +} + /** * Shortcut for querying all items in a database and running [block] with the cursor returned. * Will not run if the cursor is null. @@ -235,16 +246,15 @@ fun isEdgeOn(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 /** * Determine if the device is currently in landscape. - * @param resources [Resources] required */ -fun isLandscape(resources: Resources): Boolean { +fun Context.isLandscape(): Boolean { return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE } /** * Determine if we are in tablet mode or not */ -fun isTablet(resources: Resources): Boolean { +fun Context.isTablet(): Boolean { val layout = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK return layout == Configuration.SCREENLAYOUT_SIZE_XLARGE || @@ -254,35 +264,19 @@ fun isTablet(resources: Resources): Boolean { /** * Determine if the tablet is XLARGE, ignoring normal tablets. */ -fun isXLTablet(resources: Resources): Boolean { +fun Context.isXLTablet(): Boolean { val layout = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK return layout == Configuration.SCREENLAYOUT_SIZE_XLARGE } -/** - * Get the span count for most RecyclerViews. These probably work right on most displays. Trust me. - */ -fun RecyclerView.getSpans(): Int { - return if (isLandscape(resources)) { - if (isXLTablet(resources)) 3 else 2 - } else { - if (isXLTablet(resources)) 2 else 1 - } -} - -/** - * Returns whether a recyclerview can scroll. - */ -fun RecyclerView.canScroll() = computeVerticalScrollRange() > height - /** * Check if we are in the "Irregular" landscape mode (e.g landscape, but nav bar is on the sides) * Used to disable most of edge-to-edge if that's the case, as I cant get it to work on this mode. * @return True if we are in the irregular landscape mode, false if not. */ fun Activity.isIrregularLandscape(): Boolean { - return isLandscape(resources) && !isSystemBarOnBottom(this) + return isLandscape() && !isSystemBarOnBottom(this) } /** diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index f5c25ae11..7030a611c 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -27,13 +27,11 @@ import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.databinding.DataBindingUtil +import org.oxycblt.auxio.accent.Accent import org.oxycblt.auxio.databinding.ActivityMainBinding import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.system.PlaybackService import org.oxycblt.auxio.settings.SettingsManager -import org.oxycblt.auxio.ui.Accent -import org.oxycblt.auxio.ui.isEdgeOn -import org.oxycblt.auxio.ui.isNight /** * The single [AppCompatActivity] for Auxio. @@ -66,22 +64,6 @@ class MainActivity : AppCompatActivity() { onNewIntent(intent) } - private fun setupTheme() { - // Update the current accent and theme - val settingsManager = SettingsManager.getInstance() - AppCompatDelegate.setDefaultNightMode(settingsManager.theme) - - val newAccent = Accent.set(settingsManager.accent) - - // The black theme has a completely separate set of styles since style attributes cannot - // be modified at runtime. - if (isNight() && settingsManager.useBlackTheme) { - setTheme(newAccent.blackTheme) - } else { - setTheme(newAccent.theme) - } - } - override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) @@ -102,6 +84,22 @@ class MainActivity : AppCompatActivity() { } } + private fun setupTheme() { + // Update the current accent and theme + val settingsManager = SettingsManager.getInstance() + AppCompatDelegate.setDefaultNightMode(settingsManager.theme) + + val newAccent = Accent.set(settingsManager.accent) + + // The black theme has a completely separate set of styles since style attributes cannot + // be modified at runtime. + if (isNight && settingsManager.useBlackTheme) { + setTheme(newAccent.blackTheme) + } else { + setTheme(newAccent.theme) + } + } + private fun setupEdgeToEdge(binding: ActivityMainBinding) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Do modern edge to edge, which happens to be around twice the size of the diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index b73bc9c99..ccf032694 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -32,15 +32,11 @@ import androidx.navigation.NavOptions import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.findNavController import com.google.android.material.bottomnavigation.BottomNavigationView +import org.oxycblt.auxio.accent.Accent import org.oxycblt.auxio.databinding.FragmentMainBinding import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.Accent -import org.oxycblt.auxio.ui.isLandscape -import org.oxycblt.auxio.ui.isTablet -import org.oxycblt.auxio.ui.resolveAttr -import org.oxycblt.auxio.ui.toColor /** * The primary "Home" [Fragment] for Auxio. @@ -56,7 +52,7 @@ class MainFragment : Fragment() { ): View { val binding = FragmentMainBinding.inflate(inflater) - val colorActive = Accent.get().color.toColor(requireContext()) + val colorActive = Accent.get().color.resolveColor(requireContext()) val colorInactive = ColorUtils.setAlphaComponent(colorActive, 150) // Set up the tints for the navigation icons + text @@ -86,7 +82,7 @@ class MainFragment : Fragment() { itemIconTintList = navTints itemTextColor = navTints - if (isTablet(resources) && !isLandscape(resources)) { + if (requireContext().isTablet() && !requireContext().isLandscape()) { labelVisibilityMode = BottomNavigationView.LABEL_VISIBILITY_LABELED } @@ -160,7 +156,7 @@ class MainFragment : Fragment() { if (song == null) { logD("Hiding CompactPlaybackFragment since no song is being played.") - binding.compactPlayback.visibility = if (isLandscape(resources)) { + binding.compactPlayback.visibility = if (requireContext().isLandscape()) { View.INVISIBLE } else { View.GONE diff --git a/app/src/main/java/org/oxycblt/auxio/ui/Accent.kt b/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt similarity index 96% rename from app/src/main/java/org/oxycblt/auxio/ui/Accent.kt rename to app/src/main/java/org/oxycblt/auxio/accent/Accent.kt index b8136ac56..3959a0f4a 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/Accent.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.ui +package org.oxycblt.auxio.accent import android.annotation.SuppressLint import android.content.Context @@ -27,6 +27,7 @@ import androidx.annotation.StringRes import androidx.annotation.StyleRes import androidx.core.text.HtmlCompat import org.oxycblt.auxio.R +import org.oxycblt.auxio.resolveStateList /** * A list of all possible accents. @@ -94,7 +95,7 @@ data class Accent( /** * Get a [ColorStateList] of the accent */ - fun getStateList(context: Context) = color.toStateList(context) + fun getStateList(context: Context) = color.resolveStateList(context) /** * Get the name (in bold) and the hex value of a accent. diff --git a/app/src/main/java/org/oxycblt/auxio/settings/accent/AccentAdapter.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt similarity index 90% rename from app/src/main/java/org/oxycblt/auxio/settings/accent/AccentAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt index 8153c4ac0..a2b2b1d83 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/accent/AccentAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt @@ -16,17 +16,15 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.settings.accent +package org.oxycblt.auxio.accent import android.view.ViewGroup import androidx.appcompat.widget.TooltipCompat import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemAccentBinding -import org.oxycblt.auxio.ui.ACCENTS -import org.oxycblt.auxio.ui.Accent -import org.oxycblt.auxio.ui.inflater -import org.oxycblt.auxio.ui.toStateList +import org.oxycblt.auxio.inflater +import org.oxycblt.auxio.resolveStateList /** * An adapter that displays the list of all possible accents, and highlights the current one. @@ -84,9 +82,9 @@ class AccentAdapter( selectedViewHolder?.setSelected(false) selectedViewHolder = this - R.color.surface.toStateList(context) + R.color.surface.resolveStateList(context) } else { - android.R.color.transparent.toStateList(context) + android.R.color.transparent.resolveStateList(context) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/accent/AccentDialog.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentDialog.kt similarity index 93% rename from app/src/main/java/org/oxycblt/auxio/settings/accent/AccentDialog.kt rename to app/src/main/java/org/oxycblt/auxio/accent/AccentDialog.kt index 20b25a314..b617ff1df 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/accent/AccentDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.settings.accent +package org.oxycblt.auxio.accent import android.os.Bundle import android.view.LayoutInflater @@ -27,11 +27,9 @@ import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogAccentBinding import org.oxycblt.auxio.logD +import org.oxycblt.auxio.resolveColor import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.settings.ui.LifecycleDialog -import org.oxycblt.auxio.ui.ACCENTS -import org.oxycblt.auxio.ui.Accent -import org.oxycblt.auxio.ui.toColor /** * Dialog responsible for showing the list of accents to select. @@ -93,7 +91,7 @@ class AccentDialog : LifecycleDialog() { } private fun updateAccent() { - val accentColor = pendingAccent.color.toColor(requireContext()) + val accentColor = pendingAccent.color.resolveColor(requireContext()) (requireDialog() as AlertDialog).apply { getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(accentColor) diff --git a/app/src/main/java/org/oxycblt/auxio/settings/accent/AutoGridLayoutManager.kt b/app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/settings/accent/AutoGridLayoutManager.kt rename to app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt index 61c583447..ab853c447 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/accent/AutoGridLayoutManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/accent/AutoGridLayoutManager.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.settings.accent +package org.oxycblt.auxio.accent import android.content.Context import android.util.AttributeSet diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index e220ecce6..64cad21c3 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -25,6 +25,7 @@ import android.view.ViewGroup import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import org.oxycblt.auxio.R +import org.oxycblt.auxio.canScroll import org.oxycblt.auxio.detail.adapters.AlbumDetailAdapter import org.oxycblt.auxio.logD import org.oxycblt.auxio.music.Album @@ -34,10 +35,9 @@ import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.recycler.CenterSmoothScroller +import org.oxycblt.auxio.showToast import org.oxycblt.auxio.ui.ActionMenu -import org.oxycblt.auxio.ui.canScroll import org.oxycblt.auxio.ui.newMenu -import org.oxycblt.auxio.ui.showToast /** * The [DetailFragment] for an album. 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 6db31c87c..23adc169e 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -28,8 +28,8 @@ import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.FragmentDetailBinding +import org.oxycblt.auxio.isLandscape import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.isLandscape import org.oxycblt.auxio.ui.memberBinding /** @@ -95,7 +95,7 @@ abstract class DetailFragment : Fragment() { setHasFixedSize(true) // Set up a grid if the mode is landscape - if (isLandscape(resources)) { + if (requireContext().isLandscape()) { layoutManager = GridLayoutManager(requireContext(), 2).also { it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/adapters/AlbumDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/adapters/AlbumDetailAdapter.kt index 6c8d1456d..4bfea26a6 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/adapters/AlbumDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/adapters/AlbumDetailAdapter.kt @@ -23,9 +23,12 @@ import android.view.ViewGroup import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.accent.Accent import org.oxycblt.auxio.databinding.ItemAlbumHeaderBinding import org.oxycblt.auxio.databinding.ItemAlbumSongBinding import org.oxycblt.auxio.detail.DetailViewModel +import org.oxycblt.auxio.disable +import org.oxycblt.auxio.inflater import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Song @@ -33,10 +36,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.recycler.DiffCallback import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder import org.oxycblt.auxio.recycler.viewholders.Highlightable -import org.oxycblt.auxio.ui.Accent -import org.oxycblt.auxio.ui.disable -import org.oxycblt.auxio.ui.inflater -import org.oxycblt.auxio.ui.setTextColorResource +import org.oxycblt.auxio.setTextColorResource /** * An adapter for displaying the details and [Song]s of an [Album] diff --git a/app/src/main/java/org/oxycblt/auxio/detail/adapters/ArtistDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/adapters/ArtistDetailAdapter.kt index 58420b517..4e2d59e66 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/adapters/ArtistDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/adapters/ArtistDetailAdapter.kt @@ -22,11 +22,14 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.accent.Accent import org.oxycblt.auxio.databinding.ItemActionHeaderBinding import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding import org.oxycblt.auxio.databinding.ItemArtistHeaderBinding import org.oxycblt.auxio.databinding.ItemArtistSongBinding import org.oxycblt.auxio.detail.DetailViewModel +import org.oxycblt.auxio.disable +import org.oxycblt.auxio.inflater import org.oxycblt.auxio.logD import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist @@ -37,10 +40,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.recycler.DiffCallback import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder import org.oxycblt.auxio.recycler.viewholders.Highlightable -import org.oxycblt.auxio.ui.Accent -import org.oxycblt.auxio.ui.disable -import org.oxycblt.auxio.ui.inflater -import org.oxycblt.auxio.ui.setTextColorResource +import org.oxycblt.auxio.setTextColorResource /** * An adapter for displaying the [Album]s and [Song]s of an artist. diff --git a/app/src/main/java/org/oxycblt/auxio/detail/adapters/GenreDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/adapters/GenreDetailAdapter.kt index 2ff65f73a..e8f345dc3 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/adapters/GenreDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/adapters/GenreDetailAdapter.kt @@ -23,9 +23,12 @@ import android.view.ViewGroup import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.accent.Accent import org.oxycblt.auxio.databinding.ItemGenreHeaderBinding import org.oxycblt.auxio.databinding.ItemGenreSongBinding import org.oxycblt.auxio.detail.DetailViewModel +import org.oxycblt.auxio.disable +import org.oxycblt.auxio.inflater import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song @@ -33,10 +36,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.recycler.DiffCallback import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder import org.oxycblt.auxio.recycler.viewholders.Highlightable -import org.oxycblt.auxio.ui.Accent -import org.oxycblt.auxio.ui.disable -import org.oxycblt.auxio.ui.inflater -import org.oxycblt.auxio.ui.setTextColorResource +import org.oxycblt.auxio.setTextColorResource /** * An adapter for displaying the [Song]s of a genre. diff --git a/app/src/main/java/org/oxycblt/auxio/music/BlacklistDatabase.kt b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDatabase.kt similarity index 87% rename from app/src/main/java/org/oxycblt/auxio/music/BlacklistDatabase.kt rename to app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDatabase.kt index df80b10c0..0d15ce5e1 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/BlacklistDatabase.kt +++ b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDatabase.kt @@ -16,24 +16,24 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music +package org.oxycblt.auxio.excluded import android.content.ContentValues import android.content.Context import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import androidx.core.database.sqlite.transaction +import org.oxycblt.auxio.assertBackgroundThread import org.oxycblt.auxio.logD -import org.oxycblt.auxio.ui.assertBackgroundThread -import org.oxycblt.auxio.ui.queryAll +import org.oxycblt.auxio.queryAll /** - * Database for storing blacklisted paths. + * Database for storing excluded directories. * Note that the paths stored here will not work with MediaStore unless you append a "%" at the end. * Yes. I know Room exists. But that would needlessly bloat my app and has crippling bugs. * @author OxygenCobalt */ -class BlacklistDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { +class ExcludedDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { override fun onCreate(db: SQLiteDatabase) { db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_NAME ($COLUMN_PATH TEXT NOT NULL)") } @@ -94,12 +94,12 @@ class BlacklistDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, n const val COLUMN_PATH = "COLUMN_PATH" @Volatile - private var INSTANCE: BlacklistDatabase? = null + private var INSTANCE: ExcludedDatabase? = null /** * Get/Instantiate the single instance of [PlaybackStateDatabase]. */ - fun getInstance(context: Context): BlacklistDatabase { + fun getInstance(context: Context): ExcludedDatabase { val currentInstance = INSTANCE if (currentInstance != null) { @@ -107,7 +107,7 @@ class BlacklistDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, n } synchronized(this) { - val newInstance = BlacklistDatabase(context.applicationContext) + val newInstance = ExcludedDatabase(context.applicationContext) INSTANCE = newInstance return newInstance } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistDialog.kt b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt similarity index 94% rename from app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistDialog.kt rename to app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt index 97810ac2b..ede0a3fca 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.settings.blacklist +package org.oxycblt.auxio.excluded import android.content.Intent import android.net.Uri @@ -37,16 +37,16 @@ import org.oxycblt.auxio.databinding.DialogBlacklistBinding import org.oxycblt.auxio.logD import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.settings.ui.LifecycleDialog -import org.oxycblt.auxio.ui.showToast +import org.oxycblt.auxio.showToast import kotlin.system.exitProcess /** * Dialog that manages the currently excluded directories. * @author OxygenCobalt */ -class BlacklistDialog : LifecycleDialog() { - private val blacklistModel: BlacklistViewModel by viewModels { - BlacklistViewModel.Factory(requireContext()) +class ExcludedDialog : LifecycleDialog() { + private val blacklistModel: ExcludedViewModel by viewModels { + ExcludedViewModel.Factory(requireContext()) } private val playbackModel: PlaybackViewModel by activityViewModels() @@ -58,7 +58,7 @@ class BlacklistDialog : LifecycleDialog() { ): View { val binding = DialogBlacklistBinding.inflate(inflater) - val adapter = BlacklistEntryAdapter { path -> + val adapter = ExcludedEntryAdapter { path -> blacklistModel.removePath(path) } @@ -123,6 +123,7 @@ class BlacklistDialog : LifecycleDialog() { if (path != null) { blacklistModel.addPath(path) } else { + // TODO: Tolerate this once the excluded system is modernized requireContext().showToast(R.string.err_bad_dir) } } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistEntryAdapter.kt b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedEntryAdapter.kt similarity index 92% rename from app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistEntryAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/excluded/ExcludedEntryAdapter.kt index ce8f18bc3..b6c28a181 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistEntryAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedEntryAdapter.kt @@ -16,21 +16,21 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.settings.blacklist +package org.oxycblt.auxio.excluded import android.annotation.SuppressLint import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.ItemBlacklistEntryBinding -import org.oxycblt.auxio.ui.inflater +import org.oxycblt.auxio.inflater /** * Adapter that shows the blacklist entries and their "Clear" button. * @author OxygenCobalt */ -class BlacklistEntryAdapter( +class ExcludedEntryAdapter( private val onClear: (String) -> Unit -) : RecyclerView.Adapter() { +) : RecyclerView.Adapter() { private var paths = mutableListOf() override fun getItemCount() = paths.size diff --git a/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistViewModel.kt b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedViewModel.kt similarity index 86% rename from app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistViewModel.kt rename to app/src/main/java/org/oxycblt/auxio/excluded/ExcludedViewModel.kt index 5a11aea71..8fb1ffaa8 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/blacklist/BlacklistViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedViewModel.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.settings.blacklist +package org.oxycblt.auxio.excluded import android.content.Context import androidx.lifecycle.LiveData @@ -27,18 +27,17 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.oxycblt.auxio.music.BlacklistDatabase /** - * ViewModel that acts as a wrapper around [BlacklistDatabase], allowing for the addition/removal + * ViewModel that acts as a wrapper around [ExcludedDatabase], allowing for the addition/removal * of paths. Use [Factory] to instantiate this. * @author OxygenCobalt */ -class BlacklistViewModel(context: Context) : ViewModel() { +class ExcludedViewModel(context: Context) : ViewModel() { private val mPaths = MutableLiveData(mutableListOf()) val paths: LiveData> get() = mPaths - private val blacklistDatabase = BlacklistDatabase.getInstance(context) + private val blacklistDatabase = ExcludedDatabase.getInstance(context) private var dbPaths = listOf() init { @@ -97,12 +96,12 @@ class BlacklistViewModel(context: Context) : ViewModel() { class Factory(private val context: Context) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - check(modelClass.isAssignableFrom(BlacklistViewModel::class.java)) { + check(modelClass.isAssignableFrom(ExcludedViewModel::class.java)) { "BlacklistViewModel.Factory does not support this class" } @Suppress("UNCHECKED_CAST") - return BlacklistViewModel(context) as T + return ExcludedViewModel(context) as T } } } diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt index 99d853e90..0c2f4774d 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt @@ -36,7 +36,7 @@ import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Parent import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.recycler.sliceArticle -import org.oxycblt.auxio.ui.getSpans +import org.oxycblt.auxio.spans import org.oxycblt.auxio.ui.newMenu /** @@ -78,7 +78,6 @@ class LibraryFragment : Fragment() { adapter = libraryAdapter setHasFixedSize(true) - val spans = getSpans() if (spans != 1) { layoutManager = GridLayoutManager(requireContext(), spans) } diff --git a/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt b/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt index e84566a3f..c5b64b81e 100644 --- a/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt @@ -33,7 +33,6 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentLoadingBinding import org.oxycblt.auxio.logD import org.oxycblt.auxio.music.MusicStore -import org.oxycblt.auxio.ui.isLandscape /** * Fragment that handles what to display during the loading process. @@ -62,10 +61,6 @@ class LoadingFragment : Fragment() { binding.lifecycleOwner = viewLifecycleOwner binding.loadingModel = loadingModel - // The loading panel shouldn't fit the system window on landscape as that will cause it - // to be mis-aligned with the Auxio icon. - binding.loadingPanel.fitsSystemWindows = !isLandscape(resources) - // --- VIEWMODEL SETUP --- loadingModel.doGrant.observe(viewLifecycleOwner) { doGrant -> diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index 71018a132..13b416ae0 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -27,6 +27,7 @@ import android.provider.MediaStore.Audio.Genres import android.provider.MediaStore.Audio.Media import androidx.core.database.getStringOrNull import org.oxycblt.auxio.R +import org.oxycblt.auxio.excluded.ExcludedDatabase import org.oxycblt.auxio.logD /** @@ -69,7 +70,7 @@ class MusicLoader(private val context: Context) { @Suppress("DEPRECATION") private fun buildSelector() { // TODO: Upgrade this to be compatible with Android Q. - val blacklistDatabase = BlacklistDatabase.getInstance(context) + val blacklistDatabase = ExcludedDatabase.getInstance(context) val paths = blacklistDatabase.readPaths() diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt index e387c5493..17b753714 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt @@ -27,7 +27,7 @@ import android.widget.TextView import androidx.core.text.isDigitsOnly import androidx.databinding.BindingAdapter import org.oxycblt.auxio.R -import org.oxycblt.auxio.ui.getPlural +import org.oxycblt.auxio.getPlural /** * A complete array of all the hardcoded genre values for ID3 if (cursor.count == 0) return@queryAll - val songIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_SONG_HASH) - val posIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_POSITION) - val parentIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_PARENT_HASH) - val indexIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_INDEX) - val modeIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_MODE) - val shuffleIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_IS_SHUFFLING) - val loopModeIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_LOOP_MODE) + val songIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_SONG_HASH) + val posIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_POSITION) + val parentIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_PARENT_HASH) + val indexIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_INDEX) + val modeIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_MODE) + val shuffleIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_IS_SHUFFLING) + val loopModeIndex = cursor.getColumnIndexOrThrow(DatabaseState.COLUMN_LOOP_MODE) val inUserQueueIndex = cursor.getColumnIndexOrThrow( - PlaybackState.COLUMN_IN_USER_QUEUE + DatabaseState.COLUMN_IN_USER_QUEUE ) cursor.moveToFirst() - state = PlaybackState( + state = DatabaseState( songHash = cursor.getInt(songIndex), position = cursor.getLong(posIndex), parentHash = cursor.getInt(parentIndex), @@ -174,7 +174,7 @@ class PlaybackStateDatabase(context: Context) : /** * Write a list of [queueItems] to the database, clearing the previous queue present. */ - fun writeQueue(queueItems: List) { + fun writeQueue(queueItems: List) { assertBackgroundThread() val database = writableDatabase @@ -197,10 +197,10 @@ class PlaybackStateDatabase(context: Context) : i++ val itemData = ContentValues(4).apply { - put(QueueItem.COLUMN_ID, item.id) - put(QueueItem.COLUMN_SONG_HASH, item.songHash) - put(QueueItem.COLUMN_ALBUM_HASH, item.albumHash) - put(QueueItem.COLUMN_IS_USER_QUEUE, item.isUserQueue) + put(DatabaseQueueItem.COLUMN_ID, item.id) + put(DatabaseQueueItem.COLUMN_SONG_HASH, item.songHash) + put(DatabaseQueueItem.COLUMN_ALBUM_HASH, item.albumHash) + put(DatabaseQueueItem.COLUMN_IS_USER_QUEUE, item.isUserQueue) } insert(TABLE_NAME_QUEUE, null, itemData) @@ -216,24 +216,24 @@ class PlaybackStateDatabase(context: Context) : } /** - * Read the database for any [QueueItem]s. - * @return A list of any stored [QueueItem]s. + * Read the database for any [DatabaseQueueItem]s. + * @return A list of any stored [DatabaseQueueItem]s. */ - fun readQueue(): List { + fun readQueue(): List { assertBackgroundThread() - val queueItems = mutableListOf() + val queueItems = mutableListOf() readableDatabase.queryAll(TABLE_NAME_QUEUE) { cursor -> if (cursor.count == 0) return@queryAll - val idIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_ID) - val songIdIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_SONG_HASH) - val albumIdIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_ALBUM_HASH) - val isUserQueueIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_IS_USER_QUEUE) + val idIndex = cursor.getColumnIndexOrThrow(DatabaseQueueItem.COLUMN_ID) + val songIdIndex = cursor.getColumnIndexOrThrow(DatabaseQueueItem.COLUMN_SONG_HASH) + val albumIdIndex = cursor.getColumnIndexOrThrow(DatabaseQueueItem.COLUMN_ALBUM_HASH) + val isUserQueueIndex = cursor.getColumnIndexOrThrow(DatabaseQueueItem.COLUMN_IS_USER_QUEUE) while (cursor.moveToNext()) { - queueItems += QueueItem( + queueItems += DatabaseQueueItem( id = cursor.getLong(idIndex), songHash = cursor.getInt(songIdIndex), albumHash = cursor.getInt(albumIdIndex), diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index 25cb08a58..36c6aaeb9 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -581,8 +581,8 @@ class PlaybackStateManager private constructor() { logD("Getting state from DB.") val start: Long - val playbackState: PlaybackState? - val queueItems: List + val playbackState: DatabaseState? + val queueItems: List withContext(Dispatchers.IO) { start = System.currentTimeMillis() @@ -609,11 +609,11 @@ class PlaybackStateManager private constructor() { } /** - * Pack the current state into a [PlaybackState] to be saved. - * @return A [PlaybackState] reflecting the current state. + * Pack the current state into a [DatabaseState] to be saved. + * @return A [DatabaseState] reflecting the current state. */ - private fun packToPlaybackState(): PlaybackState { - return PlaybackState( + private fun packToPlaybackState(): DatabaseState { + return DatabaseState( songHash = mSong?.hash ?: Int.MIN_VALUE, position = mPosition, parentHash = mParent?.hash ?: Int.MIN_VALUE, @@ -628,7 +628,7 @@ class PlaybackStateManager private constructor() { /** * Unpack a [playbackState] into this instance. */ - private fun unpackFromPlaybackState(playbackState: PlaybackState) { + private fun unpackFromPlaybackState(playbackState: DatabaseState) { // Turn the simplified information from PlaybackState into usable data. // Do queue setup first @@ -646,21 +646,21 @@ class PlaybackStateManager private constructor() { } /** - * Pack the queue into a list of [QueueItem]s to be saved. + * Pack the queue into a list of [DatabaseQueueItem]s to be saved. * @return A list of packed queue items. */ - private fun packQueue(): List { - val unified = mutableListOf() + private fun packQueue(): List { + val unified = mutableListOf() var queueItemId = 0L mUserQueue.forEach { song -> - unified.add(QueueItem(queueItemId, song.hash, song.album.hash, true)) + unified.add(DatabaseQueueItem(queueItemId, song.hash, song.album.hash, true)) queueItemId++ } mQueue.forEach { song -> - unified.add(QueueItem(queueItemId, song.hash, song.album.hash, false)) + unified.add(DatabaseQueueItem(queueItemId, song.hash, song.album.hash, false)) queueItemId++ } @@ -669,9 +669,9 @@ class PlaybackStateManager private constructor() { /** * Unpack a list of queue items into a queue & user queue. - * @param queueItems The list of [QueueItem]s to unpack. + * @param queueItems The list of [DatabaseQueueItem]s to unpack. */ - private fun unpackQueue(queueItems: List) { + private fun unpackQueue(queueItems: List) { for (item in queueItems) { musicStore.findSongFast(item.songHash, item.albumHash)?.let { song -> if (item.isUserQueue) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt index c06fc6a0c..e80d3b100 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt @@ -25,10 +25,10 @@ import androidx.core.animation.addListener import androidx.media.AudioFocusRequestCompat import androidx.media.AudioManagerCompat import com.google.android.exoplayer2.SimpleExoPlayer +import org.oxycblt.auxio.getSystemServiceSafe import org.oxycblt.auxio.logD import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.settings.SettingsManager -import org.oxycblt.auxio.ui.getSystemServiceSafe /** * Object that manages the AudioFocus state. 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 39cd9c399..5a17b0e72 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 @@ -31,10 +31,10 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.coil.loadBitmap import org.oxycblt.auxio.music.Parent import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.newBroadcastIntent +import org.oxycblt.auxio.newMainIntent import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.settings.SettingsManager -import org.oxycblt.auxio.ui.newBroadcastIntent -import org.oxycblt.auxio.ui.newMainIntent /** * The unified notification for [PlaybackService]. This is not self-sufficient, updates have diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 25f82464c..177f87bc2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -52,6 +52,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch import org.oxycblt.auxio.BuildConfig +import org.oxycblt.auxio.getSystemServiceSafe import org.oxycblt.auxio.logD import org.oxycblt.auxio.music.Parent import org.oxycblt.auxio.music.Song @@ -59,7 +60,6 @@ import org.oxycblt.auxio.music.toURI import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.settings.SettingsManager -import org.oxycblt.auxio.ui.getSystemServiceSafe import org.oxycblt.auxio.widgets.WidgetController import org.oxycblt.auxio.widgets.WidgetProvider diff --git a/app/src/main/java/org/oxycblt/auxio/recycler/FastScrollView.kt b/app/src/main/java/org/oxycblt/auxio/recycler/FastScrollView.kt index 331e311c6..e80750fca 100644 --- a/app/src/main/java/org/oxycblt/auxio/recycler/FastScrollView.kt +++ b/app/src/main/java/org/oxycblt/auxio/recycler/FastScrollView.kt @@ -33,13 +33,13 @@ import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.accent.Accent +import org.oxycblt.auxio.canScroll import org.oxycblt.auxio.databinding.ViewFastScrollBinding +import org.oxycblt.auxio.inflater import org.oxycblt.auxio.logD -import org.oxycblt.auxio.ui.Accent -import org.oxycblt.auxio.ui.canScroll -import org.oxycblt.auxio.ui.inflater -import org.oxycblt.auxio.ui.resolveAttr -import org.oxycblt.auxio.ui.toColor +import org.oxycblt.auxio.resolveAttr +import org.oxycblt.auxio.resolveColor import kotlin.math.ceil import kotlin.math.min import kotlin.math.roundToInt @@ -49,6 +49,8 @@ import kotlin.math.roundToInt * fast-scrollers, this one displays indicators and a thumb instead of simply a scroll bar. * This code is fundamentally an adaptation of Reddit's IndicatorFastScroll, albeit specialized * towards Auxio. The original library is here: https://github.com/reddit/IndicatorFastScroll/ + * TODO: Replace this with something similar to AndroidFastScroll [but optimized for Auxio], + * since this thumb view is a blocker to a better sort system. * @author OxygenCobalt */ class FastScrollView @JvmOverloads constructor( @@ -97,7 +99,7 @@ class FastScrollView @JvmOverloads constructor( private data class Indicator(val char: Char, val pos: Int) private var indicators = listOf() - private val activeColor = Accent.get().color.toColor(context) + private val activeColor = Accent.get().color.resolveColor(context) private val inactiveColor = android.R.attr.textColorSecondary.resolveAttr(context) // --- STATE --- diff --git a/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt b/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt index 7b029ba37..6882aeb77 100644 --- a/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt @@ -25,12 +25,12 @@ import org.oxycblt.auxio.databinding.ItemArtistBinding import org.oxycblt.auxio.databinding.ItemGenreBinding import org.oxycblt.auxio.databinding.ItemHeaderBinding import org.oxycblt.auxio.databinding.ItemSongBinding +import org.oxycblt.auxio.inflater import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.ui.inflater /** * The Shared ViewHolder for a [Song]. Instantiation should be done with [from]. diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index 8ae0ee32b..cc80aade9 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -40,7 +40,7 @@ import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel -import org.oxycblt.auxio.ui.getSpans +import org.oxycblt.auxio.spans import org.oxycblt.auxio.ui.newMenu /** @@ -90,7 +90,9 @@ class SearchFragment : Fragment() { binding.searchRecycler.apply { adapter = searchAdapter - val spans = getSpans() + + // It's expensive to calculate the spans for each position in the list, so cache it. + val spans = spans if (spans != -1) { layoutManager = GridLayoutManager(requireContext(), spans).apply { diff --git a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt index a32305267..2829389ed 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt @@ -34,7 +34,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentAboutBinding import org.oxycblt.auxio.logD import org.oxycblt.auxio.music.MusicStore -import org.oxycblt.auxio.ui.showToast +import org.oxycblt.auxio.showToast /** * A [BottomSheetDialogFragment] that shows Auxio's about screen. 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 f79779dea..74db3de55 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt @@ -21,10 +21,10 @@ package org.oxycblt.auxio.settings import android.content.SharedPreferences import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.edit +import org.oxycblt.auxio.accent.ACCENTS +import org.oxycblt.auxio.accent.Accent import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.recycler.DisplayMode -import org.oxycblt.auxio.ui.ACCENTS -import org.oxycblt.auxio.ui.Accent // A couple of utils for migrating from old settings values to the new // formats used in 1.3.2 & 1.4.0 diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt index 0488f20e4..f40a75aa5 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt @@ -28,16 +28,16 @@ import androidx.preference.PreferenceFragmentCompat import androidx.preference.children import coil.Coil import org.oxycblt.auxio.R +import org.oxycblt.auxio.accent.Accent +import org.oxycblt.auxio.accent.AccentDialog +import org.oxycblt.auxio.excluded.ExcludedDialog +import org.oxycblt.auxio.isNight import org.oxycblt.auxio.logD import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.recycler.DisplayMode -import org.oxycblt.auxio.settings.accent.AccentDialog -import org.oxycblt.auxio.settings.blacklist.BlacklistDialog import org.oxycblt.auxio.settings.ui.IntListPrefDialog import org.oxycblt.auxio.settings.ui.IntListPreference -import org.oxycblt.auxio.ui.Accent -import org.oxycblt.auxio.ui.isNight -import org.oxycblt.auxio.ui.showToast +import org.oxycblt.auxio.showToast /** * The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat]. @@ -103,7 +103,7 @@ class SettingsListFragment : PreferenceFragmentCompat() { SettingsManager.KEY_BLACK_THEME -> { onPreferenceClickListener = Preference.OnPreferenceClickListener { - if (requireContext().isNight()) { + if (requireContext().isNight) { requireActivity().recreate() } @@ -168,7 +168,7 @@ class SettingsListFragment : PreferenceFragmentCompat() { SettingsManager.KEY_BLACKLIST -> { onPreferenceClickListener = Preference.OnPreferenceClickListener { - BlacklistDialog().show(childFragmentManager, BlacklistDialog.TAG) + ExcludedDialog().show(childFragmentManager, ExcludedDialog.TAG) true } } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt index 509d70fcf..729b2c039 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt @@ -22,11 +22,11 @@ import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit import androidx.preference.PreferenceManager +import org.oxycblt.auxio.accent.ACCENTS +import org.oxycblt.auxio.accent.Accent import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.recycler.DisplayMode import org.oxycblt.auxio.recycler.SortMode -import org.oxycblt.auxio.ui.ACCENTS -import org.oxycblt.auxio.ui.Accent /** * Wrapper around the [SharedPreferences] class that writes & reads values without a context. diff --git a/app/src/main/java/org/oxycblt/auxio/settings/ui/LifecycleDialog.kt b/app/src/main/java/org/oxycblt/auxio/settings/ui/LifecycleDialog.kt index 5dc7e45c5..1d6dd1bd4 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/ui/LifecycleDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/ui/LifecycleDialog.kt @@ -27,7 +27,7 @@ import androidx.core.graphics.drawable.toDrawable import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.oxycblt.auxio.R -import org.oxycblt.auxio.ui.resolveAttr +import org.oxycblt.auxio.resolveAttr /** * A wrapper around [DialogFragment] that allows the usage of the standard Auxio lifecycle diff --git a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt index 4185cca48..a9c53f4fc 100644 --- a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt @@ -31,7 +31,7 @@ import org.oxycblt.auxio.logD import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.recycler.sliceArticle -import org.oxycblt.auxio.ui.getSpans +import org.oxycblt.auxio.spans import org.oxycblt.auxio.ui.newMenu /** @@ -66,7 +66,6 @@ class SongsFragment : Fragment() { adapter = songAdapter setHasFixedSize(true) - val spans = getSpans() if (spans != 1) { layoutManager = GridLayoutManager(requireContext(), spans) } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt index 3a8c3a85c..645bb4732 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt @@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel +import org.oxycblt.auxio.showToast /** * Extension method for creating and showing a new [ActionMenu]. diff --git a/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt b/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt index dac290503..0dc191aa0 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/MemberBinder.kt @@ -26,6 +26,8 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.OnLifecycleEvent +import org.oxycblt.auxio.assertMainThread +import org.oxycblt.auxio.inflater import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty diff --git a/app/src/main/java/org/oxycblt/auxio/ui/SlideLinearLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/SlideLinearLayout.kt index 0a01efb93..b1e91346a 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/SlideLinearLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/SlideLinearLayout.kt @@ -55,7 +55,7 @@ class SlideLinearLayout @JvmOverloads constructor( it.isAccessible = true } } catch (e: NoSuchFieldException) { - logE("Could not get mDisappearingChildren.") + logE("Could not get mDisappearingChildren. This is very ungood.") null } 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 7eaab084e..69efb2a93 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt @@ -22,10 +22,10 @@ import android.content.Context import android.widget.RemoteViews import androidx.annotation.LayoutRes import org.oxycblt.auxio.R +import org.oxycblt.auxio.newBroadcastIntent +import org.oxycblt.auxio.newMainIntent import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.system.PlaybackService -import org.oxycblt.auxio.ui.newBroadcastIntent -import org.oxycblt.auxio.ui.newMainIntent private fun createViews( context: Context, @@ -41,7 +41,7 @@ private fun createViews( return views } -private fun RemoteViews.applyMeta(context: Context, state: WidgetState) { +private fun RemoteViews.applyMeta(state: WidgetState) { setTextViewText(R.id.widget_song, state.song.name) setTextViewText(R.id.widget_artist, state.song.album.artist.name) } @@ -96,20 +96,20 @@ fun createDefaultWidget(context: Context): RemoteViews { fun createMiniWidget(context: Context, state: WidgetState): RemoteViews { val views = createViews(context, R.layout.widget_mini) - views.applyMeta(context, state) + views.applyMeta(state) return views } fun createCompactWidget(context: Context, state: WidgetState): RemoteViews { val views = createViews(context, R.layout.widget_compact) - views.applyMeta(context, state) + views.applyMeta(state) views.applyCover(context, state) return views } fun createSmallWidget(context: Context, state: WidgetState): RemoteViews { val views = createViews(context, R.layout.widget_small) - views.applyMeta(context, state) + views.applyMeta(state) views.applyCover(context, state) views.applyControls(context, state) return views @@ -117,7 +117,7 @@ fun createSmallWidget(context: Context, state: WidgetState): RemoteViews { fun createFullWidget(context: Context, state: WidgetState): RemoteViews { val views = createViews(context, R.layout.widget_full) - views.applyMeta(context, state) + views.applyMeta(state) views.applyCover(context, state) views.applyControls(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 1a167c66f..a10ca8ff0 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt @@ -30,9 +30,9 @@ import android.util.SizeF import android.widget.RemoteViews import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.coil.loadBitmap +import org.oxycblt.auxio.isLandscape import org.oxycblt.auxio.logD import org.oxycblt.auxio.playback.state.PlaybackStateManager -import org.oxycblt.auxio.ui.isLandscape /** * Auxio's one and only appwidget. This widget follows a more unorthodox approach, effectively @@ -167,7 +167,7 @@ class WidgetProvider : AppWidgetProvider() { var height: Int // Landscape/Portrait modes use different dimen bounds - if (isLandscape(context.resources)) { + if (context.isLandscape()) { width = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) height = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) } else { diff --git a/app/src/main/res/layout/dialog_accent.xml b/app/src/main/res/layout/dialog_accent.xml index eb9cf294f..9c0c8d3c4 100644 --- a/app/src/main/res/layout/dialog_accent.xml +++ b/app/src/main/res/layout/dialog_accent.xml @@ -9,7 +9,7 @@ android:layout_height="match_parent" android:overScrollMode="never" android:paddingTop="@dimen/spacing_small" - app:layoutManager="org.oxycblt.auxio.settings.accent.AutoGridLayoutManager" + app:layoutManager=".accent.AutoGridLayoutManager" app:layout_constraintBottom_toTopOf="@+id/accent_cancel" app:layout_constraintTop_toBottomOf="@+id/accent_header" tools:itemCount="18" diff --git a/info/ARCHITECTURE.md b/info/ARCHITECTURE.md index 57513dcca..6fb237b96 100644 --- a/info/ARCHITECTURE.md +++ b/info/ARCHITECTURE.md @@ -87,8 +87,10 @@ To prevent any strange bugs, all integer representations must be unique. A table Auxio's package structure is mostly based around the features, and then any sub-features or components involved with that. There are some shared packages however. A diagram of the package structure is shown below: ``` -org.oxycblt.auxio # Main UI's and logging utilities +org.oxycblt.auxio # Main UI's and utilities +├──.accent # Accent UI + Systems ├──.coil # Fetchers and utilities for Coil, contains binding adapters than be used in the user interface. +├──.blacklist # Excluded Directories UI/Systems ├──.database # Databases and their items for Auxio ├──.detail # UIs for more album/artist/genre details │ └──.adapters # RecyclerView adapters for the detail UIs, which display the header information and items @@ -103,8 +105,6 @@ org.oxycblt.auxio # Main UI's and logging utilities │ └──.viewholders # Shared ViewHolders and ViewHolder utilities ├──.search # Search UI ├──.settings # Settings UI and systems -│ ├──.blacklist # Excluded Directories UI/Systems -│ ├──.accent # Accent UI + Systems │ └──.ui # Settings-Related UIs ├──.songs # Songs UI ├──.ui # Shared user interface utilities