From 833ddceba49bb3c83e838c194bc57035a83bdf37 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Sat, 11 Feb 2023 16:29:47 -0700 Subject: [PATCH] all: use binds di Use @Binds more heavily with dependency injection, whee currently reasonable. Reduces the amount of boilerplate "fun from" functions that need to be used. --- app/src/main/java/org/oxycblt/auxio/Auxio.kt | 11 +- .../java/org/oxycblt/auxio/MainActivity.kt | 15 +-- .../java/org/oxycblt/auxio/home/HomeModule.kt | 8 +- .../org/oxycblt/auxio/home/HomeSettings.kt | 14 +-- .../auxio/home/tabs/TabCustomizeDialog.kt | 6 +- .../org/oxycblt/auxio/image/ImageModule.kt | 5 +- .../org/oxycblt/auxio/image/ImageSettings.kt | 6 +- .../auxio/image/PlaybackIndicatorView.kt | 6 +- .../oxycblt/auxio/image/StyledImageView.kt | 7 +- .../oxycblt/auxio/list/adapter/ListDiffer.kt | 8 +- .../org/oxycblt/auxio/music/MusicModule.kt | 2 +- .../org/oxycblt/auxio/music/MusicSettings.kt | 10 +- .../oxycblt/auxio/music/cache/CacheModule.kt | 10 +- .../auxio/music/cache/CacheRepository.kt | 106 +++++++++--------- .../auxio/music/metadata/SeparatorsDialog.kt | 9 +- .../music/storage/MediaStoreExtractor.kt | 18 +-- .../auxio/music/storage/MusicDirsDialog.kt | 9 +- .../auxio/music/system/IndexerService.kt | 11 +- .../oxycblt/auxio/playback/PlaybackModule.kt | 8 +- .../auxio/playback/PlaybackSettings.kt | 6 +- .../playback/persist/PersistenceModule.kt | 29 +++++ .../playback/persist/PersistenceRepository.kt | 8 +- .../playback/state/PlaybackStateManager.kt | 4 +- .../org/oxycblt/auxio/search/SearchEngine.kt | 13 +-- .../org/oxycblt/auxio/search/SearchModule.kt | 10 +- .../oxycblt/auxio/search/SearchSettings.kt | 14 +-- .../org/oxycblt/auxio/settings/Settings.kt | 2 +- .../categories/UIPreferenceFragment.kt | 7 +- .../java/org/oxycblt/auxio/ui/UIModule.kt | 29 +++++ .../java/org/oxycblt/auxio/ui/UISettings.kt | 8 +- .../auxio/ui/accent/AccentCustomizeDialog.kt | 9 +- .../oxycblt/auxio/widgets/WidgetProvider.kt | 24 ++-- .../org/oxycblt/auxio/widgets/WidgetUtil.kt | 6 +- .../oxycblt/auxio/music/model/RawMusicTest.kt | 6 +- 34 files changed, 250 insertions(+), 194 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceModule.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/ui/UIModule.kt diff --git a/app/src/main/java/org/oxycblt/auxio/Auxio.kt b/app/src/main/java/org/oxycblt/auxio/Auxio.kt index a545b8b18..913f7022a 100644 --- a/app/src/main/java/org/oxycblt/auxio/Auxio.kt +++ b/app/src/main/java/org/oxycblt/auxio/Auxio.kt @@ -26,6 +26,7 @@ import coil.ImageLoader import coil.ImageLoaderFactory import coil.request.CachePolicy import dagger.hilt.android.HiltAndroidApp +import javax.inject.Inject import org.oxycblt.auxio.image.ImageSettings import org.oxycblt.auxio.image.extractor.AlbumCoverFetcher import org.oxycblt.auxio.image.extractor.ArtistImageFetcher @@ -41,12 +42,16 @@ import org.oxycblt.auxio.ui.UISettings */ @HiltAndroidApp class Auxio : Application(), ImageLoaderFactory { + @Inject lateinit var imageSettings: ImageSettings + @Inject lateinit var playbackSettings: PlaybackSettings + @Inject lateinit var uiSettings: UISettings + override fun onCreate() { super.onCreate() // Migrate any settings that may have changed in an app update. - ImageSettings.from(this).migrate() - PlaybackSettings.from(this).migrate() - UISettings.from(this).migrate() + imageSettings.migrate() + playbackSettings.migrate() + uiSettings.migrate() // Adding static shortcuts in a dynamic manner is better than declaring them // manually, as it will properly handle the difference between debug and release // Auxio instances. diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index 1f13a7d16..d956bb4ce 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -26,6 +26,7 @@ import androidx.appcompat.app.AppCompatDelegate import androidx.core.view.WindowCompat import androidx.core.view.updatePadding import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import org.oxycblt.auxio.databinding.ActivityMainBinding import org.oxycblt.auxio.music.system.IndexerService import org.oxycblt.auxio.playback.PlaybackViewModel @@ -54,6 +55,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat @AndroidEntryPoint class MainActivity : AppCompatActivity() { private val playbackModel: PlaybackViewModel by viewModels() + @Inject lateinit var uiSettings: UISettings override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -83,17 +85,16 @@ class MainActivity : AppCompatActivity() { } private fun setupTheme() { - val settings = UISettings.from(this) // Apply the theme configuration. - AppCompatDelegate.setDefaultNightMode(settings.theme) + AppCompatDelegate.setDefaultNightMode(uiSettings.theme) // Apply the color scheme. The black theme requires it's own set of themes since // it's not possible to modify the themes at run-time. - if (isNight && settings.useBlackTheme) { - logD("Applying black theme [accent ${settings.accent}]") - setTheme(settings.accent.blackTheme) + if (isNight && uiSettings.useBlackTheme) { + logD("Applying black theme [accent ${uiSettings.accent}]") + setTheme(uiSettings.accent.blackTheme) } else { - logD("Applying normal theme [accent ${settings.accent}]") - setTheme(settings.accent.theme) + logD("Applying normal theme [accent ${uiSettings.accent}]") + setTheme(uiSettings.accent.theme) } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeModule.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeModule.kt index 7956a8754..ddff79aa7 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeModule.kt @@ -17,15 +17,13 @@ package org.oxycblt.auxio.home -import android.content.Context +import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -class HomeModule { - @Provides fun settings(@ApplicationContext context: Context) = HomeSettings.from(context) +interface HomeModule { + @Binds fun settings(homeSettings: HomeSettingsImpl): HomeSettings } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeSettings.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeSettings.kt index ff946df1a..2499b5918 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeSettings.kt @@ -19,6 +19,8 @@ package org.oxycblt.auxio.home import android.content.Context import androidx.core.content.edit +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject import org.oxycblt.auxio.R import org.oxycblt.auxio.home.tabs.Tab import org.oxycblt.auxio.settings.Settings @@ -40,18 +42,10 @@ interface HomeSettings : Settings { /** Called when the [shouldHideCollaborators] configuration changes. */ fun onHideCollaboratorsChanged() } - - companion object { - /** - * Get a framework-backed implementation. - * @param context [Context] required. - */ - fun from(context: Context): HomeSettings = RealHomeSettings(context) - } } -private class RealHomeSettings(context: Context) : - Settings.Real(context), HomeSettings { +class HomeSettingsImpl @Inject constructor(@ApplicationContext context: Context) : + Settings.Impl(context), HomeSettings { override var homeTabs: Array get() = Tab.fromIntCode( diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt index 595b17096..516a54257 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt @@ -23,6 +23,7 @@ import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogTabsBinding @@ -40,6 +41,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment(), EditableListListener { private val tabAdapter = TabAdapter(this) private var touchHelper: ItemTouchHelper? = null + @Inject lateinit var homeSettings: HomeSettings override fun onCreateBinding(inflater: LayoutInflater) = DialogTabsBinding.inflate(inflater) @@ -48,13 +50,13 @@ class TabCustomizeDialog : .setTitle(R.string.set_lib_tabs) .setPositiveButton(R.string.lbl_ok) { _, _ -> logD("Committing tab changes") - HomeSettings.from(requireContext()).homeTabs = tabAdapter.tabs + homeSettings.homeTabs = tabAdapter.tabs } .setNegativeButton(R.string.lbl_cancel, null) } override fun onBindingCreated(binding: DialogTabsBinding, savedInstanceState: Bundle?) { - var tabs = HomeSettings.from(requireContext()).homeTabs + var tabs = homeSettings.homeTabs // Try to restore a pending tab configuration that was saved prior. if (savedInstanceState != null) { val savedTabs = Tab.fromIntCode(savedInstanceState.getInt(KEY_TABS)) diff --git a/app/src/main/java/org/oxycblt/auxio/image/ImageModule.kt b/app/src/main/java/org/oxycblt/auxio/image/ImageModule.kt index c3a569f3b..71728590a 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/ImageModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/ImageModule.kt @@ -18,6 +18,7 @@ package org.oxycblt.auxio.image import android.content.Context +import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -26,6 +27,6 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -class ImageModule { - @Provides fun settings(@ApplicationContext context: Context) = ImageSettings.from(context) +interface ImageModule { + @Binds fun settings(imageSettings: ImageSettingsImpl): ImageSettings } diff --git a/app/src/main/java/org/oxycblt/auxio/image/ImageSettings.kt b/app/src/main/java/org/oxycblt/auxio/image/ImageSettings.kt index bfc679150..09cf49fa4 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/ImageSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/ImageSettings.kt @@ -41,12 +41,12 @@ interface ImageSettings : Settings { * Get a framework-backed implementation. * @param context [Context] required. */ - fun from(context: Context): ImageSettings = RealImageSettings(context) + fun from(context: Context): ImageSettings = ImageSettingsImpl(context) } } -private class RealImageSettings(context: Context) : - Settings.Real(context), ImageSettings { +class ImageSettingsImpl(context: Context) : + Settings.Impl(context), ImageSettings { override val coverMode: CoverMode get() = CoverMode.fromIntCode( diff --git a/app/src/main/java/org/oxycblt/auxio/image/PlaybackIndicatorView.kt b/app/src/main/java/org/oxycblt/auxio/image/PlaybackIndicatorView.kt index 5da781bb3..0c2fe5b98 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/PlaybackIndicatorView.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/PlaybackIndicatorView.kt @@ -26,6 +26,8 @@ import androidx.annotation.AttrRes import androidx.appcompat.widget.AppCompatImageView import androidx.core.widget.ImageViewCompat import com.google.android.material.shape.MaterialShapeDrawable +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import kotlin.math.max import org.oxycblt.auxio.R import org.oxycblt.auxio.ui.UISettings @@ -41,6 +43,7 @@ import org.oxycblt.auxio.util.getDrawableCompat * * @author Alexander Capehart (OxygenCobalt) */ +@AndroidEntryPoint class PlaybackIndicatorView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) : @@ -52,6 +55,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr private val indicatorMatrix = Matrix() private val indicatorMatrixSrc = RectF() private val indicatorMatrixDst = RectF() + @Inject lateinit var uiSettings: UISettings /** * The corner radius of this view. This allows the outer ImageGroup to apply it's corner radius @@ -61,7 +65,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr set(value) { field = value (background as? MaterialShapeDrawable)?.let { bg -> - if (UISettings.from(context).roundMode) { + if (uiSettings.roundMode) { bg.setCornerSize(value) } else { bg.setCornerSize(0f) diff --git a/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt b/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt index d838a7b63..ed78c47e5 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/StyledImageView.kt @@ -32,6 +32,8 @@ import androidx.core.graphics.drawable.DrawableCompat import coil.dispose import coil.load import com.google.android.material.shape.MaterialShapeDrawable +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import org.oxycblt.auxio.R import org.oxycblt.auxio.image.extractor.SquareFrameTransform import org.oxycblt.auxio.music.Album @@ -53,10 +55,13 @@ import org.oxycblt.auxio.util.getDrawableCompat * * @author Alexander Capehart (OxygenCobalt) */ +@AndroidEntryPoint class StyledImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) : AppCompatImageView(context, attrs, defStyleAttr) { + @Inject lateinit var uiSettings: UISettings + init { // Load view attributes val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView) @@ -81,7 +86,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr background = MaterialShapeDrawable().apply { fillColor = context.getColorCompat(R.color.sel_cover_bg) - if (UISettings.from(context).roundMode) { + if (uiSettings.roundMode) { // Only use the specified corner radius when round mode is enabled. setCornerSize(cornerRadius) } diff --git a/app/src/main/java/org/oxycblt/auxio/list/adapter/ListDiffer.kt b/app/src/main/java/org/oxycblt/auxio/list/adapter/ListDiffer.kt index e2be45833..7c5207e6c 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/adapter/ListDiffer.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/adapter/ListDiffer.kt @@ -63,7 +63,7 @@ interface ListDiffer { class Async(private val diffCallback: DiffUtil.ItemCallback) : Factory() { override fun new(adapter: RecyclerView.Adapter<*>): ListDiffer = - RealAsyncListDiffer(AdapterListUpdateCallback(adapter), diffCallback) + AsyncListDifferImpl(AdapterListUpdateCallback(adapter), diffCallback) } /** @@ -75,7 +75,7 @@ interface ListDiffer { class Blocking(private val diffCallback: DiffUtil.ItemCallback) : Factory() { override fun new(adapter: RecyclerView.Adapter<*>): ListDiffer = - RealBlockingListDiffer(AdapterListUpdateCallback(adapter), diffCallback) + BlockingListDifferImpl(AdapterListUpdateCallback(adapter), diffCallback) } } @@ -113,7 +113,7 @@ private abstract class BasicListDiffer : ListDiffer protected abstract fun replaceList(newList: List, onDone: () -> Unit) } -private class RealAsyncListDiffer( +private class AsyncListDifferImpl( updateCallback: ListUpdateCallback, diffCallback: DiffUtil.ItemCallback ) : BasicListDiffer() { @@ -132,7 +132,7 @@ private class RealAsyncListDiffer( } } -private class RealBlockingListDiffer( +private class BlockingListDifferImpl( private val updateCallback: ListUpdateCallback, private val diffCallback: DiffUtil.ItemCallback ) : BasicListDiffer() { diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicModule.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicModule.kt index 159a28b7d..2f91cfdba 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicModule.kt @@ -28,7 +28,7 @@ import org.oxycblt.auxio.music.system.IndexerImpl @Module @InstallIn(SingletonComponent::class) interface MusicModule { - @Singleton @Binds fun musicRepository(musicRepository: MusicRepositoryImpl): MusicRepository + @Singleton @Binds fun repository(musicRepository: MusicRepositoryImpl): MusicRepository @Singleton @Binds fun indexer(indexer: IndexerImpl): Indexer @Binds fun settings(musicSettingsImpl: MusicSettingsImpl): MusicSettings } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt index a36ec925d..c954d1696 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt @@ -63,18 +63,10 @@ interface MusicSettings : Settings { /** Called when the [shouldBeObserving] configuration has changed. */ fun onObservingChanged() {} } - - companion object { - /** - * Get a framework-backed implementation. - * @param context [Context] required. - */ - fun from(context: Context): MusicSettings = MusicSettingsImpl(context) - } } class MusicSettingsImpl @Inject constructor(@ApplicationContext context: Context) : - Settings.Real(context), MusicSettings { + Settings.Impl(context), MusicSettings { private val storageManager = context.getSystemServiceCompat(StorageManager::class) override var musicDirs: MusicDirectories diff --git a/app/src/main/java/org/oxycblt/auxio/music/cache/CacheModule.kt b/app/src/main/java/org/oxycblt/auxio/music/cache/CacheModule.kt index 9c50e12ca..c03e5799f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/cache/CacheModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/cache/CacheModule.kt @@ -17,17 +17,15 @@ package org.oxycblt.auxio.music.cache -import android.content.Context +import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import org.oxycblt.auxio.music.extractor.CacheRepository +import org.oxycblt.auxio.music.extractor.CacheRepositoryImpl @Module @InstallIn(SingletonComponent::class) -class CacheModule { - @Provides - fun cacheRepository(@ApplicationContext context: Context) = CacheRepository.from(context) +interface CacheModule { + @Binds fun cacheRepository(cacheRepository: CacheRepositoryImpl): CacheRepository } diff --git a/app/src/main/java/org/oxycblt/auxio/music/cache/CacheRepository.kt b/app/src/main/java/org/oxycblt/auxio/music/cache/CacheRepository.kt index 131cde74d..8e63f663c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/cache/CacheRepository.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/cache/CacheRepository.kt @@ -18,12 +18,61 @@ package org.oxycblt.auxio.music.extractor import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject import org.oxycblt.auxio.music.cache.CacheDatabase import org.oxycblt.auxio.music.cache.CachedSong import org.oxycblt.auxio.music.cache.CachedSongsDao import org.oxycblt.auxio.music.model.RawSong import org.oxycblt.auxio.util.* +/** + * A repository allowing access to cached metadata obtained in prior music loading operations. + * @author Alexander Capehart (OxygenCobalt) + */ +interface CacheRepository { + /** + * Read the current [Cache], if it exists. + * @return The stored [Cache], or null if it could not be obtained. + */ + suspend fun readCache(): Cache? + + /** + * Write the list of newly-loaded [RawSong]s to the cache, replacing the prior data. + * @param rawSongs The [rawSongs] to write to the cache. + */ + suspend fun writeCache(rawSongs: List) +} + +class CacheRepositoryImpl @Inject constructor(@ApplicationContext private val context: Context) : + CacheRepository { + private val cachedSongsDao: CachedSongsDao by lazy { + CacheDatabase.getInstance(context).cachedSongsDao() + } + + override suspend fun readCache(): Cache? = + try { + // Faster to load the whole database into memory than do a query on each + // populate call. + CacheImpl(cachedSongsDao.readSongs()) + } catch (e: Exception) { + logE("Unable to load cache database.") + logE(e.stackTraceToString()) + null + } + + override suspend fun writeCache(rawSongs: List) { + try { + // Still write out whatever data was extracted. + cachedSongsDao.nukeSongs() + cachedSongsDao.insertSongs(rawSongs.map(CachedSong::fromRaw)) + } catch (e: Exception) { + logE("Unable to save cache database.") + logE(e.stackTraceToString()) + } + } +} + /** * A cache of music metadata obtained in prior music loading operations. Obtain an instance with * [CacheRepository]. @@ -41,7 +90,7 @@ interface Cache { fun populate(rawSong: RawSong): Boolean } -private class RealCache(cachedSongs: List) : Cache { +private class CacheImpl(cachedSongs: List) : Cache { private val cacheMap = buildMap { for (cachedSong in cachedSongs) { put(cachedSong.mediaStoreId, cachedSong) @@ -69,58 +118,3 @@ private class RealCache(cachedSongs: List) : Cache { return false } } - -/** - * A repository allowing access to cached metadata obtained in prior music loading operations. - * @author Alexander Capehart (OxygenCobalt) - */ -interface CacheRepository { - /** - * Read the current [Cache], if it exists. - * @return The stored [Cache], or null if it could not be obtained. - */ - suspend fun readCache(): Cache? - - /** - * Write the list of newly-loaded [RawSong]s to the cache, replacing the prior data. - * @param rawSongs The [rawSongs] to write to the cache. - */ - suspend fun writeCache(rawSongs: List) - - companion object { - /** - * Create a framework-backed instance. - * @param context [Context] required. - * @return A new instance. - */ - fun from(context: Context): CacheRepository = RealCacheRepository(context) - } -} - -private class RealCacheRepository(private val context: Context) : CacheRepository { - private val cachedSongsDao: CachedSongsDao by lazy { - CacheDatabase.getInstance(context).cachedSongsDao() - } - - override suspend fun readCache() = - try { - // Faster to load the whole database into memory than do a query on each - // populate call. - RealCache(cachedSongsDao.readSongs()) - } catch (e: Exception) { - logE("Unable to load cache database.") - logE(e.stackTraceToString()) - null - } - - override suspend fun writeCache(rawSongs: List) { - try { - // Still write out whatever data was extracted. - cachedSongsDao.nukeSongs() - cachedSongsDao.insertSongs(rawSongs.map(CachedSong::fromRaw)) - } catch (e: Exception) { - logE("Unable to save cache database.") - logE(e.stackTraceToString()) - } - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/music/metadata/SeparatorsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/metadata/SeparatorsDialog.kt index e13a9768f..05ac292e2 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/metadata/SeparatorsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/metadata/SeparatorsDialog.kt @@ -22,6 +22,8 @@ import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog import androidx.core.view.children import com.google.android.material.checkbox.MaterialCheckBox +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogSeparatorsBinding @@ -33,7 +35,10 @@ import org.oxycblt.auxio.ui.ViewBindingDialogFragment * split tags with multiple values. * @author Alexander Capehart (OxygenCobalt) */ +@AndroidEntryPoint class SeparatorsDialog : ViewBindingDialogFragment() { + @Inject lateinit var musicSettings: MusicSettings + override fun onCreateBinding(inflater: LayoutInflater) = DialogSeparatorsBinding.inflate(inflater) @@ -42,7 +47,7 @@ class SeparatorsDialog : ViewBindingDialogFragment() { .setTitle(R.string.set_separators) .setNegativeButton(R.string.lbl_cancel, null) .setPositiveButton(R.string.lbl_save) { _, _ -> - MusicSettings.from(requireContext()).multiValueSeparators = getCurrentSeparators() + musicSettings.multiValueSeparators = getCurrentSeparators() } } @@ -59,7 +64,7 @@ class SeparatorsDialog : ViewBindingDialogFragment() { // the corresponding CheckBox for each character instead of doing an iteration // through the separator list for each CheckBox. (savedInstanceState?.getString(KEY_PENDING_SEPARATORS) - ?: MusicSettings.from(requireContext()).multiValueSeparators) + ?: musicSettings.multiValueSeparators) .forEach { when (it) { Separators.COMMA -> binding.separatorComma.isChecked = true diff --git a/app/src/main/java/org/oxycblt/auxio/music/storage/MediaStoreExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/storage/MediaStoreExtractor.kt index 931bba9ed..3c0391c2f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/storage/MediaStoreExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/storage/MediaStoreExtractor.kt @@ -81,7 +81,7 @@ interface MediaStoreExtractor { * Create a framework-backed instance. * @param context [Context] required. * @param musicSettings [MusicSettings] required. - * @return A new [RealMediaStoreExtractor] that will work best on the device's API level. + * @return A new [MediaStoreExtractor] that will work best on the device's API level. */ fun from(context: Context, musicSettings: MusicSettings): MediaStoreExtractor = when { @@ -94,7 +94,7 @@ interface MediaStoreExtractor { } } -private abstract class RealMediaStoreExtractor( +private abstract class BaseMediaStoreExtractor( protected val context: Context, private val musicSettings: MusicSettings ) : MediaStoreExtractor { @@ -352,7 +352,7 @@ private abstract class RealMediaStoreExtractor( // speed, we only want to add redundancy on known issues, not with possible issues. private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSettings) : - RealMediaStoreExtractor(context, musicSettings) { + BaseMediaStoreExtractor(context, musicSettings) { override val projection: Array get() = super.projection + @@ -385,7 +385,7 @@ private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSet cursor: Cursor, genreNamesMap: Map, storageManager: StorageManager - ) : RealMediaStoreExtractor.Query(cursor, genreNamesMap) { + ) : BaseMediaStoreExtractor.Query(cursor, genreNamesMap) { // Set up cursor indices for later use. private val trackIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TRACK) private val dataIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATA) @@ -430,7 +430,7 @@ private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSet } /** - * A [RealMediaStoreExtractor] that implements common behavior supported from API 29 onwards. + * A [BaseMediaStoreExtractor] that implements common behavior supported from API 29 onwards. * @param context [Context] required to query the media database. * @author Alexander Capehart (OxygenCobalt) */ @@ -438,7 +438,7 @@ private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSet private abstract class BaseApi29MediaStoreExtractor( context: Context, musicSettings: MusicSettings -) : RealMediaStoreExtractor(context, musicSettings) { +) : BaseMediaStoreExtractor(context, musicSettings) { override val projection: Array get() = super.projection + @@ -471,7 +471,7 @@ private abstract class BaseApi29MediaStoreExtractor( cursor: Cursor, genreNamesMap: Map, storageManager: StorageManager - ) : RealMediaStoreExtractor.Query(cursor, genreNamesMap) { + ) : BaseMediaStoreExtractor.Query(cursor, genreNamesMap) { private val volumeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.VOLUME_NAME) private val relativePathIndex = @@ -493,7 +493,7 @@ private abstract class BaseApi29MediaStoreExtractor( } /** - * A [RealMediaStoreExtractor] that completes the music loading process in a way compatible with at + * A [BaseMediaStoreExtractor] that completes the music loading process in a way compatible with at * API * 29. * @param context [Context] required to query the media database. @@ -533,7 +533,7 @@ private class Api29MediaStoreExtractor(context: Context, musicSettings: MusicSet } /** - * A [RealMediaStoreExtractor] that completes the music loading process in a way compatible from API + * A [BaseMediaStoreExtractor] that completes the music loading process in a way compatible from API * 30 onwards. * @param context [Context] required to query the media database. * @author Alexander Capehart (OxygenCobalt) diff --git a/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt index c09043b4c..f6ea77464 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt @@ -29,6 +29,7 @@ import androidx.appcompat.app.AlertDialog import androidx.core.view.ViewCompat import androidx.core.view.isVisible import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogMusicDirsBinding @@ -48,6 +49,7 @@ class MusicDirsDialog : private val dirAdapter = DirectoryAdapter(this) private var openDocumentTreeLauncher: ActivityResultLauncher? = null private var storageManager: StorageManager? = null + @Inject lateinit var musicSettings: MusicSettings override fun onCreateBinding(inflater: LayoutInflater) = DialogMusicDirsBinding.inflate(inflater) @@ -57,11 +59,10 @@ class MusicDirsDialog : .setTitle(R.string.set_dirs) .setNegativeButton(R.string.lbl_cancel, null) .setPositiveButton(R.string.lbl_save) { _, _ -> - val settings = MusicSettings.from(requireContext()) val newDirs = MusicDirectories(dirAdapter.dirs, isUiModeInclude(requireBinding())) - if (settings.musicDirs != newDirs) { + if (musicSettings.musicDirs != newDirs) { logD("Committing changes") - settings.musicDirs = newDirs + musicSettings.musicDirs = newDirs } } } @@ -98,7 +99,7 @@ class MusicDirsDialog : itemAnimator = null } - var dirs = MusicSettings.from(context).musicDirs + var dirs = musicSettings.musicDirs if (savedInstanceState != null) { val pendingDirs = savedInstanceState.getStringArrayList(KEY_PENDING_DIRS) if (pendingDirs != null) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt index b52f6329d..f7553dfda 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt @@ -67,7 +67,7 @@ class IndexerService : Service(), Indexer.Controller, MusicSettings.Listener { private lateinit var observingNotification: ObservingNotification private lateinit var wakeLock: PowerManager.WakeLock private lateinit var indexerContentObserver: SystemContentObserver - private lateinit var settings: MusicSettings + @Inject lateinit var musicSettings: MusicSettings override fun onCreate() { super.onCreate() @@ -82,8 +82,7 @@ class IndexerService : Service(), Indexer.Controller, MusicSettings.Listener { // Initialize any listener-dependent components last as we wouldn't want a listener race // condition to cause us to load music before we were fully initialize. indexerContentObserver = SystemContentObserver() - settings = MusicSettings.from(this) - settings.registerListener(this) + musicSettings.registerListener(this) indexer.registerController(this) // An indeterminate indexer and a missing library implies we are extremely early // in app initialization so start loading music. @@ -107,7 +106,7 @@ class IndexerService : Service(), Indexer.Controller, MusicSettings.Listener { // Then cancel the listener-dependent components to ensure that stray reloading // events will not occur. indexerContentObserver.release() - settings.unregisterListener(this) + musicSettings.unregisterListener(this) indexer.unregisterController(this) // Then cancel any remaining music loading jobs. serviceJob.cancel() @@ -197,7 +196,7 @@ class IndexerService : Service(), Indexer.Controller, MusicSettings.Listener { * currently monitoring the music library for changes. */ private fun updateIdleSession() { - if (settings.shouldBeObserving) { + if (musicSettings.shouldBeObserving) { // There are a few reasons why we stay in the foreground with automatic rescanning: // 1. Newer versions of Android have become more and more restrictive regarding // how a foreground service starts. Thus, it's best to go foreground now so that @@ -287,7 +286,7 @@ class IndexerService : Service(), Indexer.Controller, MusicSettings.Listener { override fun run() { // Check here if we should even start a reindex. This is much less bug-prone than // registering and de-registering this component as this setting changes. - if (settings.shouldBeObserving) { + if (musicSettings.shouldBeObserving) { onStartIndexing(true) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackModule.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackModule.kt index e7f4217b4..6bacbbb81 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackModule.kt @@ -23,17 +23,11 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import org.oxycblt.auxio.playback.persist.PersistenceRepository import org.oxycblt.auxio.playback.state.PlaybackStateManager @Module @InstallIn(SingletonComponent::class) class PlaybackModule { - @Provides fun playbackStateManager() = PlaybackStateManager.get() - + @Provides fun stateManager() = PlaybackStateManager.get() @Provides fun settings(@ApplicationContext context: Context) = PlaybackSettings.from(context) - - @Provides - fun persistenceRepository(@ApplicationContext context: Context) = - PersistenceRepository.from(context) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt index 91acc839c..866db08bf 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSettings.kt @@ -71,12 +71,12 @@ interface PlaybackSettings : Settings { * Get a framework-backed implementation. * @param context [Context] required. */ - fun from(context: Context): PlaybackSettings = RealPlaybackSettings(context) + fun from(context: Context): PlaybackSettings = PlaybackSettingsImpl(context) } } -private class RealPlaybackSettings(context: Context) : - Settings.Real(context), PlaybackSettings { +class PlaybackSettingsImpl(context: Context) : + Settings.Impl(context), PlaybackSettings { override val inListPlaybackMode: MusicMode get() = MusicMode.fromIntCode( diff --git a/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceModule.kt b/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceModule.kt new file mode 100644 index 000000000..91334dcf3 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceModule.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 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.playback.persist + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +interface PersistenceModule { + @Binds fun repository(persistenceRepository: PersistenceRepositoryImpl): PersistenceRepository +} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceRepository.kt b/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceRepository.kt index 1839eaa25..7ba630780 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceRepository.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/persist/PersistenceRepository.kt @@ -18,6 +18,8 @@ package org.oxycblt.auxio.playback.persist import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.model.Library import org.oxycblt.auxio.playback.queue.Queue @@ -47,11 +49,13 @@ interface PersistenceRepository { * Get a framework-backed implementation. * @param context [Context] required. */ - fun from(context: Context): PersistenceRepository = RealPersistenceRepository(context) + fun from(context: Context): PersistenceRepository = PersistenceRepositoryImpl(context) } } -private class RealPersistenceRepository(private val context: Context) : PersistenceRepository { +class PersistenceRepositoryImpl +@Inject +constructor(@ApplicationContext private val context: Context) : PersistenceRepository { private val database: PersistenceDatabase by lazy { PersistenceDatabase.getInstance(context) } private val playbackStateDao: PlaybackStateDao by lazy { database.playbackStateDao() } private val queueDao: QueueDao by lazy { database.queueDao() } 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 454e2d7be..ea5690946 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 @@ -287,7 +287,7 @@ interface PlaybackStateManager { } synchronized(this) { - val newInstance = RealPlaybackStateManager() + val newInstance = PlaybackStateManagerImpl() INSTANCE = newInstance return newInstance } @@ -295,7 +295,7 @@ interface PlaybackStateManager { } } -private class RealPlaybackStateManager : PlaybackStateManager { +private class PlaybackStateManagerImpl : PlaybackStateManager { private val listeners = mutableListOf() @Volatile private var internalPlayer: InternalPlayer? = null @Volatile private var pendingAction: InternalPlayer.Action? = null diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchEngine.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchEngine.kt index 59aba052e..d52b9ef9c 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchEngine.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchEngine.kt @@ -18,7 +18,9 @@ package org.oxycblt.auxio.search import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import java.text.Normalizer +import javax.inject.Inject import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre @@ -51,17 +53,10 @@ interface SearchEngine { val artists: List?, val genres: List? ) - - companion object { - /** - * Get a framework-backed implementation. - * @param context [Context] required. - */ - fun from(context: Context): SearchEngine = RealSearchEngine(context) - } } -private class RealSearchEngine(private val context: Context) : SearchEngine { +class SearchEngineImpl @Inject constructor(@ApplicationContext private val context: Context) : + SearchEngine { override suspend fun search(items: SearchEngine.Items, query: String) = SearchEngine.Items( songs = items.songs?.searchListImpl(query) { q, song -> song.path.name.contains(q) }, diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchModule.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchModule.kt index f3b0b2276..dd973e751 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchModule.kt @@ -17,16 +17,14 @@ package org.oxycblt.auxio.search -import android.content.Context +import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -class SearchModule { - @Provides fun engine(@ApplicationContext context: Context) = SearchEngine.from(context) - @Provides fun settings(@ApplicationContext context: Context) = SearchSettings.from(context) +interface SearchModule { + @Binds fun engine(searchEngine: SearchEngineImpl): SearchEngine + @Binds fun settings(searchSettings: SearchSettingsImpl): SearchSettings } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchSettings.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchSettings.kt index 700d1b586..edd22439f 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchSettings.kt @@ -19,6 +19,8 @@ package org.oxycblt.auxio.search import android.content.Context import androidx.core.content.edit +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject import org.oxycblt.auxio.R import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.settings.Settings @@ -30,18 +32,10 @@ import org.oxycblt.auxio.settings.Settings interface SearchSettings : Settings { /** The type of Music the search view is currently filtering to. */ var searchFilterMode: MusicMode? - - companion object { - /** - * Get a framework-backed implementation. - * @param context [Context] required. - */ - fun from(context: Context): SearchSettings = RealSearchSettings(context) - } } -private class RealSearchSettings(context: Context) : - Settings.Real(context), SearchSettings { +class SearchSettingsImpl @Inject constructor(@ApplicationContext context: Context) : + Settings.Impl(context), SearchSettings { override var searchFilterMode: MusicMode? get() = MusicMode.fromIntCode( diff --git a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt index 92a81fa26..1552becd3 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt @@ -54,7 +54,7 @@ interface Settings { * A framework-backed [Settings] implementation. * @param context [Context] required. */ - abstract class Real(private val context: Context) : + abstract class Impl(private val context: Context) : Settings, SharedPreferences.OnSharedPreferenceChangeListener { protected val sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.applicationContext) diff --git a/app/src/main/java/org/oxycblt/auxio/settings/categories/UIPreferenceFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/categories/UIPreferenceFragment.kt index 39f727cb8..4e564e9d5 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/categories/UIPreferenceFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/categories/UIPreferenceFragment.kt @@ -20,6 +20,8 @@ package org.oxycblt.auxio.settings.categories import androidx.appcompat.app.AppCompatDelegate import androidx.navigation.fragment.findNavController import androidx.preference.Preference +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import org.oxycblt.auxio.R import org.oxycblt.auxio.settings.BasePreferenceFragment import org.oxycblt.auxio.settings.ui.WrappedDialogPreference @@ -30,7 +32,10 @@ import org.oxycblt.auxio.util.isNight * Display preferences. * @author Alexander Capehart (OxygenCobalt) */ +@AndroidEntryPoint class UIPreferenceFragment : BasePreferenceFragment(R.xml.preferences_ui) { + @Inject lateinit var uiSettings: UISettings + override fun onOpenDialogPreference(preference: WrappedDialogPreference) { if (preference.key == getString(R.string.set_key_accent)) { findNavController().navigate(UIPreferenceFragmentDirections.goToAccentDialog()) @@ -47,7 +52,7 @@ class UIPreferenceFragment : BasePreferenceFragment(R.xml.preferences_ui) { } } getString(R.string.set_key_accent) -> { - preference.summary = getString(UISettings.from(requireContext()).accent.name) + preference.summary = getString(uiSettings.accent.name) } getString(R.string.set_key_black_theme) -> { preference.onPreferenceChangeListener = diff --git a/app/src/main/java/org/oxycblt/auxio/ui/UIModule.kt b/app/src/main/java/org/oxycblt/auxio/ui/UIModule.kt new file mode 100644 index 000000000..335459c17 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/ui/UIModule.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 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 dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +interface UIModule { + @Binds fun settings(uiSettings: UISettingsImpl): UISettings +} diff --git a/app/src/main/java/org/oxycblt/auxio/ui/UISettings.kt b/app/src/main/java/org/oxycblt/auxio/ui/UISettings.kt index 826207549..34c07d2af 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/UISettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/UISettings.kt @@ -21,6 +21,8 @@ import android.content.Context import android.os.Build import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.edit +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject import org.oxycblt.auxio.R import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.accent.Accent @@ -50,12 +52,12 @@ interface UISettings : Settings { * Get a framework-backed implementation. * @param context [Context] required. */ - fun from(context: Context): UISettings = RealUISettings(context) + fun from(context: Context): UISettings = UISettingsImpl(context) } } -private class RealUISettings(context: Context) : - Settings.Real(context), UISettings { +class UISettingsImpl @Inject constructor(@ApplicationContext context: Context) : + Settings.Impl(context), UISettings { override val theme: Int get() = sharedPreferences.getInt( diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt index 6056e76e6..1c9a042f5 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt @@ -22,6 +22,7 @@ import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogAccentBinding @@ -39,6 +40,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull class AccentCustomizeDialog : ViewBindingDialogFragment(), ClickableListListener { private var accentAdapter = AccentAdapter(this) + @Inject lateinit var uiSettings: UISettings override fun onCreateBinding(inflater: LayoutInflater) = DialogAccentBinding.inflate(inflater) @@ -46,14 +48,13 @@ class AccentCustomizeDialog : builder .setTitle(R.string.set_accent) .setPositiveButton(R.string.lbl_ok) { _, _ -> - val settings = UISettings.from(requireContext()) - if (accentAdapter.selectedAccent == settings.accent) { + if (accentAdapter.selectedAccent == uiSettings.accent) { // Nothing to do. return@setPositiveButton } logD("Applying new accent") - settings.accent = unlikelyToBeNull(accentAdapter.selectedAccent) + uiSettings.accent = unlikelyToBeNull(accentAdapter.selectedAccent) requireActivity().recreate() dismiss() } @@ -67,7 +68,7 @@ class AccentCustomizeDialog : if (savedInstanceState != null) { Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT)) } else { - UISettings.from(requireContext()).accent + uiSettings.accent }) } 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 a333ec2f0..07f0fdc32 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt @@ -27,10 +27,13 @@ import android.os.Bundle import android.util.SizeF import android.view.View import android.widget.RemoteViews +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.playback.system.PlaybackService +import org.oxycblt.auxio.ui.UISettings import org.oxycblt.auxio.util.* /** @@ -38,7 +41,10 @@ import org.oxycblt.auxio.util.* * state alongside actions to control it. * @author Alexander Capehart (OxygenCobalt) */ +@AndroidEntryPoint class WidgetProvider : AppWidgetProvider() { + @Inject lateinit var uiSettings: UISettings + override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, @@ -139,7 +145,7 @@ class WidgetProvider : AppWidgetProvider() { */ private fun newThinLayout(context: Context, state: WidgetComponent.PlaybackState) = newRemoteViews(context, R.layout.widget_thin) - .setupBackground(context) + .setupBackground() .setupPlaybackState(context, state) .setupTimelineControls(context, state) @@ -150,7 +156,7 @@ class WidgetProvider : AppWidgetProvider() { */ private fun newSmallLayout(context: Context, state: WidgetComponent.PlaybackState) = newRemoteViews(context, R.layout.widget_small) - .setupBar(context) + .setupBar() .setupCover(context, state) .setupTimelineControls(context, state) @@ -161,7 +167,7 @@ class WidgetProvider : AppWidgetProvider() { */ private fun newMediumLayout(context: Context, state: WidgetComponent.PlaybackState) = newRemoteViews(context, R.layout.widget_medium) - .setupBackground(context) + .setupBackground() .setupPlaybackState(context, state) .setupTimelineControls(context, state) @@ -172,7 +178,7 @@ class WidgetProvider : AppWidgetProvider() { */ private fun newWideLayout(context: Context, state: WidgetComponent.PlaybackState) = newRemoteViews(context, R.layout.widget_wide) - .setupBar(context) + .setupBar() .setupCover(context, state) .setupFullControls(context, state) @@ -183,7 +189,7 @@ class WidgetProvider : AppWidgetProvider() { */ private fun newLargeLayout(context: Context, state: WidgetComponent.PlaybackState) = newRemoteViews(context, R.layout.widget_large) - .setupBackground(context) + .setupBackground() .setupPlaybackState(context, state) .setupFullControls(context, state) @@ -192,11 +198,11 @@ class WidgetProvider : AppWidgetProvider() { * "floating" drawable that sits in front of the cover and contains the controls. * @param context [Context] required to set up the view. */ - private fun RemoteViews.setupBar(context: Context): RemoteViews { + private fun RemoteViews.setupBar(): RemoteViews { // Below API 31, enable a rounded bar only if round mode is enabled. // On API 31+, the bar should always be round in order to fit in with other widgets. val background = - if (useRoundedRemoteViews(context)) { + if (useRoundedRemoteViews(uiSettings)) { R.drawable.ui_widget_bar_round } else { R.drawable.ui_widget_bar_system @@ -210,12 +216,12 @@ class WidgetProvider : AppWidgetProvider() { * self-explanatory, being a solid-color background that sits behind the cover and controls. * @param context [Context] required to set up the view. */ - private fun RemoteViews.setupBackground(context: Context): RemoteViews { + private fun RemoteViews.setupBackground(): RemoteViews { // Below API 31, enable a rounded background only if round mode is enabled. // On API 31+, the background should always be round in order to fit in with other // widgets. val background = - if (useRoundedRemoteViews(context)) { + if (useRoundedRemoteViews(uiSettings)) { R.drawable.ui_widget_bg_round } else { R.drawable.ui_widget_bg_system diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt index 2fe319470..3a478b6ff 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetUtil.kt @@ -137,8 +137,8 @@ fun AppWidgetManager.updateAppWidgetCompat( /** * Returns whether rounded UI elements are appropriate for the widget, either based on the current * settings or if the widget has to fit in aesthetically with other widgets. - * @param context [Context] configuration to use. + * @param [uiSettings] [UISettings] required to obtain round mode configuration. * @return true if to use round mode, false otherwise. */ -fun useRoundedRemoteViews(context: Context) = - UISettings.from(context).roundMode || Build.VERSION.SDK_INT >= Build.VERSION_CODES.S +fun useRoundedRemoteViews(uiSettings: UISettings) = + uiSettings.roundMode || Build.VERSION.SDK_INT >= Build.VERSION_CODES.S diff --git a/app/src/test/java/org/oxycblt/auxio/music/model/RawMusicTest.kt b/app/src/test/java/org/oxycblt/auxio/music/model/RawMusicTest.kt index fdfa8a659..413aecbfa 100644 --- a/app/src/test/java/org/oxycblt/auxio/music/model/RawMusicTest.kt +++ b/app/src/test/java/org/oxycblt/auxio/music/model/RawMusicTest.kt @@ -117,7 +117,7 @@ class RawMusicTest { } @Test - fun albumRaw_equals_withRealArtists() { + fun albumRaw_equals_withArtists() { val a = RawAlbum( mediaStoreId = -1, @@ -125,7 +125,7 @@ class RawMusicTest { name = "Album", sortName = null, releaseType = null, - rawArtists = listOf(RawArtist(name = "RealArtist A"))) + rawArtists = listOf(RawArtist(name = "Artist A"))) val b = RawAlbum( mediaStoreId = -1, @@ -133,7 +133,7 @@ class RawMusicTest { name = "Album", sortName = null, releaseType = null, - rawArtists = listOf(RawArtist(name = "RealArtist B"))) + rawArtists = listOf(RawArtist(name = "Artist B"))) assertTrue(a != b) assertTrue(a.hashCode() != b.hashCode()) }