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.
This commit is contained in:
parent
6e55801513
commit
833ddceba4
34 changed files with 250 additions and 194 deletions
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<HomeSettings.Listener> {
|
|||
/** 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<HomeSettings.Listener>(context), HomeSettings {
|
||||
class HomeSettingsImpl @Inject constructor(@ApplicationContext context: Context) :
|
||||
Settings.Impl<HomeSettings.Listener>(context), HomeSettings {
|
||||
override var homeTabs: Array<Tab>
|
||||
get() =
|
||||
Tab.fromIntCode(
|
||||
|
|
|
@ -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<DialogTabsBinding>(), EditableListListener<Tab> {
|
||||
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))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -41,12 +41,12 @@ interface ImageSettings : Settings<ImageSettings.Listener> {
|
|||
* 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<ImageSettings.Listener>(context), ImageSettings {
|
||||
class ImageSettingsImpl(context: Context) :
|
||||
Settings.Impl<ImageSettings.Listener>(context), ImageSettings {
|
||||
override val coverMode: CoverMode
|
||||
get() =
|
||||
CoverMode.fromIntCode(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ interface ListDiffer<T, I> {
|
|||
class Async<T>(private val diffCallback: DiffUtil.ItemCallback<T>) :
|
||||
Factory<T, BasicListInstructions>() {
|
||||
override fun new(adapter: RecyclerView.Adapter<*>): ListDiffer<T, BasicListInstructions> =
|
||||
RealAsyncListDiffer(AdapterListUpdateCallback(adapter), diffCallback)
|
||||
AsyncListDifferImpl(AdapterListUpdateCallback(adapter), diffCallback)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,7 +75,7 @@ interface ListDiffer<T, I> {
|
|||
class Blocking<T>(private val diffCallback: DiffUtil.ItemCallback<T>) :
|
||||
Factory<T, BasicListInstructions>() {
|
||||
override fun new(adapter: RecyclerView.Adapter<*>): ListDiffer<T, BasicListInstructions> =
|
||||
RealBlockingListDiffer(AdapterListUpdateCallback(adapter), diffCallback)
|
||||
BlockingListDifferImpl(AdapterListUpdateCallback(adapter), diffCallback)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,7 @@ private abstract class BasicListDiffer<T> : ListDiffer<T, BasicListInstructions>
|
|||
protected abstract fun replaceList(newList: List<T>, onDone: () -> Unit)
|
||||
}
|
||||
|
||||
private class RealAsyncListDiffer<T>(
|
||||
private class AsyncListDifferImpl<T>(
|
||||
updateCallback: ListUpdateCallback,
|
||||
diffCallback: DiffUtil.ItemCallback<T>
|
||||
) : BasicListDiffer<T>() {
|
||||
|
@ -132,7 +132,7 @@ private class RealAsyncListDiffer<T>(
|
|||
}
|
||||
}
|
||||
|
||||
private class RealBlockingListDiffer<T>(
|
||||
private class BlockingListDifferImpl<T>(
|
||||
private val updateCallback: ListUpdateCallback,
|
||||
private val diffCallback: DiffUtil.ItemCallback<T>
|
||||
) : BasicListDiffer<T>() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -63,18 +63,10 @@ interface MusicSettings : Settings<MusicSettings.Listener> {
|
|||
/** 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<MusicSettings.Listener>(context), MusicSettings {
|
||||
Settings.Impl<MusicSettings.Listener>(context), MusicSettings {
|
||||
private val storageManager = context.getSystemServiceCompat(StorageManager::class)
|
||||
|
||||
override var musicDirs: MusicDirectories
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<RawSong>)
|
||||
}
|
||||
|
||||
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<RawSong>) {
|
||||
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<CachedSong>) : Cache {
|
||||
private class CacheImpl(cachedSongs: List<CachedSong>) : Cache {
|
||||
private val cacheMap = buildMap {
|
||||
for (cachedSong in cachedSongs) {
|
||||
put(cachedSong.mediaStoreId, cachedSong)
|
||||
|
@ -69,58 +118,3 @@ private class RealCache(cachedSongs: List<CachedSong>) : 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<RawSong>)
|
||||
|
||||
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<RawSong>) {
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<DialogSeparatorsBinding>() {
|
||||
@Inject lateinit var musicSettings: MusicSettings
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||
DialogSeparatorsBinding.inflate(inflater)
|
||||
|
||||
|
@ -42,7 +47,7 @@ class SeparatorsDialog : ViewBindingDialogFragment<DialogSeparatorsBinding>() {
|
|||
.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<DialogSeparatorsBinding>() {
|
|||
// 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
|
||||
|
|
|
@ -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<String>
|
||||
get() =
|
||||
super.projection +
|
||||
|
@ -385,7 +385,7 @@ private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSet
|
|||
cursor: Cursor,
|
||||
genreNamesMap: Map<Long, String>,
|
||||
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<String>
|
||||
get() =
|
||||
super.projection +
|
||||
|
@ -471,7 +471,7 @@ private abstract class BaseApi29MediaStoreExtractor(
|
|||
cursor: Cursor,
|
||||
genreNamesMap: Map<Long, String>,
|
||||
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)
|
||||
|
|
|
@ -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<Uri?>? = 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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -71,12 +71,12 @@ interface PlaybackSettings : Settings<PlaybackSettings.Listener> {
|
|||
* 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<PlaybackSettings.Listener>(context), PlaybackSettings {
|
||||
class PlaybackSettingsImpl(context: Context) :
|
||||
Settings.Impl<PlaybackSettings.Listener>(context), PlaybackSettings {
|
||||
override val inListPlaybackMode: MusicMode
|
||||
get() =
|
||||
MusicMode.fromIntCode(
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
|
@ -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() }
|
||||
|
|
|
@ -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<PlaybackStateManager.Listener>()
|
||||
@Volatile private var internalPlayer: InternalPlayer? = null
|
||||
@Volatile private var pendingAction: InternalPlayer.Action? = null
|
||||
|
|
|
@ -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<Artist>?,
|
||||
val genres: List<Genre>?
|
||||
)
|
||||
|
||||
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) },
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<Nothing> {
|
||||
/** 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<Nothing>(context), SearchSettings {
|
||||
class SearchSettingsImpl @Inject constructor(@ApplicationContext context: Context) :
|
||||
Settings.Impl<Nothing>(context), SearchSettings {
|
||||
override var searchFilterMode: MusicMode?
|
||||
get() =
|
||||
MusicMode.fromIntCode(
|
||||
|
|
|
@ -54,7 +54,7 @@ interface Settings<L> {
|
|||
* A framework-backed [Settings] implementation.
|
||||
* @param context [Context] required.
|
||||
*/
|
||||
abstract class Real<L>(private val context: Context) :
|
||||
abstract class Impl<L>(private val context: Context) :
|
||||
Settings<L>, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
protected val sharedPreferences: SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
|
||||
|
|
|
@ -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 =
|
||||
|
|
29
app/src/main/java/org/oxycblt/auxio/ui/UIModule.kt
Normal file
29
app/src/main/java/org/oxycblt/auxio/ui/UIModule.kt
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
|
@ -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<UISettings.Listener> {
|
|||
* 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<UISettings.Listener>(context), UISettings {
|
||||
class UISettingsImpl @Inject constructor(@ApplicationContext context: Context) :
|
||||
Settings.Impl<UISettings.Listener>(context), UISettings {
|
||||
override val theme: Int
|
||||
get() =
|
||||
sharedPreferences.getInt(
|
||||
|
|
|
@ -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<DialogAccentBinding>(), ClickableListListener<Accent> {
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue