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.ImageLoaderFactory
|
||||||
import coil.request.CachePolicy
|
import coil.request.CachePolicy
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.image.ImageSettings
|
import org.oxycblt.auxio.image.ImageSettings
|
||||||
import org.oxycblt.auxio.image.extractor.AlbumCoverFetcher
|
import org.oxycblt.auxio.image.extractor.AlbumCoverFetcher
|
||||||
import org.oxycblt.auxio.image.extractor.ArtistImageFetcher
|
import org.oxycblt.auxio.image.extractor.ArtistImageFetcher
|
||||||
|
@ -41,12 +42,16 @@ import org.oxycblt.auxio.ui.UISettings
|
||||||
*/
|
*/
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
class Auxio : Application(), ImageLoaderFactory {
|
class Auxio : Application(), ImageLoaderFactory {
|
||||||
|
@Inject lateinit var imageSettings: ImageSettings
|
||||||
|
@Inject lateinit var playbackSettings: PlaybackSettings
|
||||||
|
@Inject lateinit var uiSettings: UISettings
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
// Migrate any settings that may have changed in an app update.
|
// Migrate any settings that may have changed in an app update.
|
||||||
ImageSettings.from(this).migrate()
|
imageSettings.migrate()
|
||||||
PlaybackSettings.from(this).migrate()
|
playbackSettings.migrate()
|
||||||
UISettings.from(this).migrate()
|
uiSettings.migrate()
|
||||||
// Adding static shortcuts in a dynamic manner is better than declaring them
|
// Adding static shortcuts in a dynamic manner is better than declaring them
|
||||||
// manually, as it will properly handle the difference between debug and release
|
// manually, as it will properly handle the difference between debug and release
|
||||||
// Auxio instances.
|
// Auxio instances.
|
||||||
|
|
|
@ -26,6 +26,7 @@ import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.databinding.ActivityMainBinding
|
import org.oxycblt.auxio.databinding.ActivityMainBinding
|
||||||
import org.oxycblt.auxio.music.system.IndexerService
|
import org.oxycblt.auxio.music.system.IndexerService
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
|
@ -54,6 +55,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private val playbackModel: PlaybackViewModel by viewModels()
|
private val playbackModel: PlaybackViewModel by viewModels()
|
||||||
|
@Inject lateinit var uiSettings: UISettings
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -83,17 +85,16 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupTheme() {
|
private fun setupTheme() {
|
||||||
val settings = UISettings.from(this)
|
|
||||||
// Apply the theme configuration.
|
// 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
|
// 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.
|
// it's not possible to modify the themes at run-time.
|
||||||
if (isNight && settings.useBlackTheme) {
|
if (isNight && uiSettings.useBlackTheme) {
|
||||||
logD("Applying black theme [accent ${settings.accent}]")
|
logD("Applying black theme [accent ${uiSettings.accent}]")
|
||||||
setTheme(settings.accent.blackTheme)
|
setTheme(uiSettings.accent.blackTheme)
|
||||||
} else {
|
} else {
|
||||||
logD("Applying normal theme [accent ${settings.accent}]")
|
logD("Applying normal theme [accent ${uiSettings.accent}]")
|
||||||
setTheme(settings.accent.theme)
|
setTheme(uiSettings.accent.theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,15 +17,13 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.home
|
package org.oxycblt.auxio.home
|
||||||
|
|
||||||
import android.content.Context
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
class HomeModule {
|
interface HomeModule {
|
||||||
@Provides fun settings(@ApplicationContext context: Context) = HomeSettings.from(context)
|
@Binds fun settings(homeSettings: HomeSettingsImpl): HomeSettings
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.oxycblt.auxio.home
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.home.tabs.Tab
|
import org.oxycblt.auxio.home.tabs.Tab
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
|
@ -40,18 +42,10 @@ interface HomeSettings : Settings<HomeSettings.Listener> {
|
||||||
/** Called when the [shouldHideCollaborators] configuration changes. */
|
/** Called when the [shouldHideCollaborators] configuration changes. */
|
||||||
fun onHideCollaboratorsChanged()
|
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) :
|
class HomeSettingsImpl @Inject constructor(@ApplicationContext context: Context) :
|
||||||
Settings.Real<HomeSettings.Listener>(context), HomeSettings {
|
Settings.Impl<HomeSettings.Listener>(context), HomeSettings {
|
||||||
override var homeTabs: Array<Tab>
|
override var homeTabs: Array<Tab>
|
||||||
get() =
|
get() =
|
||||||
Tab.fromIntCode(
|
Tab.fromIntCode(
|
||||||
|
|
|
@ -23,6 +23,7 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.DialogTabsBinding
|
import org.oxycblt.auxio.databinding.DialogTabsBinding
|
||||||
|
@ -40,6 +41,7 @@ class TabCustomizeDialog :
|
||||||
ViewBindingDialogFragment<DialogTabsBinding>(), EditableListListener<Tab> {
|
ViewBindingDialogFragment<DialogTabsBinding>(), EditableListListener<Tab> {
|
||||||
private val tabAdapter = TabAdapter(this)
|
private val tabAdapter = TabAdapter(this)
|
||||||
private var touchHelper: ItemTouchHelper? = null
|
private var touchHelper: ItemTouchHelper? = null
|
||||||
|
@Inject lateinit var homeSettings: HomeSettings
|
||||||
|
|
||||||
override fun onCreateBinding(inflater: LayoutInflater) = DialogTabsBinding.inflate(inflater)
|
override fun onCreateBinding(inflater: LayoutInflater) = DialogTabsBinding.inflate(inflater)
|
||||||
|
|
||||||
|
@ -48,13 +50,13 @@ class TabCustomizeDialog :
|
||||||
.setTitle(R.string.set_lib_tabs)
|
.setTitle(R.string.set_lib_tabs)
|
||||||
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
||||||
logD("Committing tab changes")
|
logD("Committing tab changes")
|
||||||
HomeSettings.from(requireContext()).homeTabs = tabAdapter.tabs
|
homeSettings.homeTabs = tabAdapter.tabs
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.lbl_cancel, null)
|
.setNegativeButton(R.string.lbl_cancel, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindingCreated(binding: DialogTabsBinding, savedInstanceState: Bundle?) {
|
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.
|
// Try to restore a pending tab configuration that was saved prior.
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
val savedTabs = Tab.fromIntCode(savedInstanceState.getInt(KEY_TABS))
|
val savedTabs = Tab.fromIntCode(savedInstanceState.getInt(KEY_TABS))
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.oxycblt.auxio.image
|
package org.oxycblt.auxio.image
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
@ -26,6 +27,6 @@ import dagger.hilt.components.SingletonComponent
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
class ImageModule {
|
interface ImageModule {
|
||||||
@Provides fun settings(@ApplicationContext context: Context) = ImageSettings.from(context)
|
@Binds fun settings(imageSettings: ImageSettingsImpl): ImageSettings
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,12 +41,12 @@ interface ImageSettings : Settings<ImageSettings.Listener> {
|
||||||
* Get a framework-backed implementation.
|
* Get a framework-backed implementation.
|
||||||
* @param context [Context] required.
|
* @param context [Context] required.
|
||||||
*/
|
*/
|
||||||
fun from(context: Context): ImageSettings = RealImageSettings(context)
|
fun from(context: Context): ImageSettings = ImageSettingsImpl(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RealImageSettings(context: Context) :
|
class ImageSettingsImpl(context: Context) :
|
||||||
Settings.Real<ImageSettings.Listener>(context), ImageSettings {
|
Settings.Impl<ImageSettings.Listener>(context), ImageSettings {
|
||||||
override val coverMode: CoverMode
|
override val coverMode: CoverMode
|
||||||
get() =
|
get() =
|
||||||
CoverMode.fromIntCode(
|
CoverMode.fromIntCode(
|
||||||
|
|
|
@ -26,6 +26,8 @@ import androidx.annotation.AttrRes
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
import androidx.core.widget.ImageViewCompat
|
import androidx.core.widget.ImageViewCompat
|
||||||
import com.google.android.material.shape.MaterialShapeDrawable
|
import com.google.android.material.shape.MaterialShapeDrawable
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.ui.UISettings
|
import org.oxycblt.auxio.ui.UISettings
|
||||||
|
@ -41,6 +43,7 @@ import org.oxycblt.auxio.util.getDrawableCompat
|
||||||
*
|
*
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
class PlaybackIndicatorView
|
class PlaybackIndicatorView
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
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 indicatorMatrix = Matrix()
|
||||||
private val indicatorMatrixSrc = RectF()
|
private val indicatorMatrixSrc = RectF()
|
||||||
private val indicatorMatrixDst = 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
|
* 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) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
(background as? MaterialShapeDrawable)?.let { bg ->
|
(background as? MaterialShapeDrawable)?.let { bg ->
|
||||||
if (UISettings.from(context).roundMode) {
|
if (uiSettings.roundMode) {
|
||||||
bg.setCornerSize(value)
|
bg.setCornerSize(value)
|
||||||
} else {
|
} else {
|
||||||
bg.setCornerSize(0f)
|
bg.setCornerSize(0f)
|
||||||
|
|
|
@ -32,6 +32,8 @@ import androidx.core.graphics.drawable.DrawableCompat
|
||||||
import coil.dispose
|
import coil.dispose
|
||||||
import coil.load
|
import coil.load
|
||||||
import com.google.android.material.shape.MaterialShapeDrawable
|
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.R
|
||||||
import org.oxycblt.auxio.image.extractor.SquareFrameTransform
|
import org.oxycblt.auxio.image.extractor.SquareFrameTransform
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
|
@ -53,10 +55,13 @@ import org.oxycblt.auxio.util.getDrawableCompat
|
||||||
*
|
*
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
class StyledImageView
|
class StyledImageView
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||||
AppCompatImageView(context, attrs, defStyleAttr) {
|
AppCompatImageView(context, attrs, defStyleAttr) {
|
||||||
|
@Inject lateinit var uiSettings: UISettings
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Load view attributes
|
// Load view attributes
|
||||||
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
|
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
|
||||||
|
@ -81,7 +86,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
background =
|
background =
|
||||||
MaterialShapeDrawable().apply {
|
MaterialShapeDrawable().apply {
|
||||||
fillColor = context.getColorCompat(R.color.sel_cover_bg)
|
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.
|
// Only use the specified corner radius when round mode is enabled.
|
||||||
setCornerSize(cornerRadius)
|
setCornerSize(cornerRadius)
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ interface ListDiffer<T, I> {
|
||||||
class Async<T>(private val diffCallback: DiffUtil.ItemCallback<T>) :
|
class Async<T>(private val diffCallback: DiffUtil.ItemCallback<T>) :
|
||||||
Factory<T, BasicListInstructions>() {
|
Factory<T, BasicListInstructions>() {
|
||||||
override fun new(adapter: RecyclerView.Adapter<*>): ListDiffer<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>) :
|
class Blocking<T>(private val diffCallback: DiffUtil.ItemCallback<T>) :
|
||||||
Factory<T, BasicListInstructions>() {
|
Factory<T, BasicListInstructions>() {
|
||||||
override fun new(adapter: RecyclerView.Adapter<*>): ListDiffer<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)
|
protected abstract fun replaceList(newList: List<T>, onDone: () -> Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RealAsyncListDiffer<T>(
|
private class AsyncListDifferImpl<T>(
|
||||||
updateCallback: ListUpdateCallback,
|
updateCallback: ListUpdateCallback,
|
||||||
diffCallback: DiffUtil.ItemCallback<T>
|
diffCallback: DiffUtil.ItemCallback<T>
|
||||||
) : BasicListDiffer<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 updateCallback: ListUpdateCallback,
|
||||||
private val diffCallback: DiffUtil.ItemCallback<T>
|
private val diffCallback: DiffUtil.ItemCallback<T>
|
||||||
) : BasicListDiffer<T>() {
|
) : BasicListDiffer<T>() {
|
||||||
|
|
|
@ -28,7 +28,7 @@ import org.oxycblt.auxio.music.system.IndexerImpl
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
interface MusicModule {
|
interface MusicModule {
|
||||||
@Singleton @Binds fun musicRepository(musicRepository: MusicRepositoryImpl): MusicRepository
|
@Singleton @Binds fun repository(musicRepository: MusicRepositoryImpl): MusicRepository
|
||||||
@Singleton @Binds fun indexer(indexer: IndexerImpl): Indexer
|
@Singleton @Binds fun indexer(indexer: IndexerImpl): Indexer
|
||||||
@Binds fun settings(musicSettingsImpl: MusicSettingsImpl): MusicSettings
|
@Binds fun settings(musicSettingsImpl: MusicSettingsImpl): MusicSettings
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,18 +63,10 @@ interface MusicSettings : Settings<MusicSettings.Listener> {
|
||||||
/** Called when the [shouldBeObserving] configuration has changed. */
|
/** Called when the [shouldBeObserving] configuration has changed. */
|
||||||
fun onObservingChanged() {}
|
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) :
|
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)
|
private val storageManager = context.getSystemServiceCompat(StorageManager::class)
|
||||||
|
|
||||||
override var musicDirs: MusicDirectories
|
override var musicDirs: MusicDirectories
|
||||||
|
|
|
@ -17,17 +17,15 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.cache
|
package org.oxycblt.auxio.music.cache
|
||||||
|
|
||||||
import android.content.Context
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import org.oxycblt.auxio.music.extractor.CacheRepository
|
import org.oxycblt.auxio.music.extractor.CacheRepository
|
||||||
|
import org.oxycblt.auxio.music.extractor.CacheRepositoryImpl
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
class CacheModule {
|
interface CacheModule {
|
||||||
@Provides
|
@Binds fun cacheRepository(cacheRepository: CacheRepositoryImpl): CacheRepository
|
||||||
fun cacheRepository(@ApplicationContext context: Context) = CacheRepository.from(context)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,61 @@
|
||||||
package org.oxycblt.auxio.music.extractor
|
package org.oxycblt.auxio.music.extractor
|
||||||
|
|
||||||
import android.content.Context
|
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.CacheDatabase
|
||||||
import org.oxycblt.auxio.music.cache.CachedSong
|
import org.oxycblt.auxio.music.cache.CachedSong
|
||||||
import org.oxycblt.auxio.music.cache.CachedSongsDao
|
import org.oxycblt.auxio.music.cache.CachedSongsDao
|
||||||
import org.oxycblt.auxio.music.model.RawSong
|
import org.oxycblt.auxio.music.model.RawSong
|
||||||
import org.oxycblt.auxio.util.*
|
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
|
* A cache of music metadata obtained in prior music loading operations. Obtain an instance with
|
||||||
* [CacheRepository].
|
* [CacheRepository].
|
||||||
|
@ -41,7 +90,7 @@ interface Cache {
|
||||||
fun populate(rawSong: RawSong): Boolean
|
fun populate(rawSong: RawSong): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RealCache(cachedSongs: List<CachedSong>) : Cache {
|
private class CacheImpl(cachedSongs: List<CachedSong>) : Cache {
|
||||||
private val cacheMap = buildMap {
|
private val cacheMap = buildMap {
|
||||||
for (cachedSong in cachedSongs) {
|
for (cachedSong in cachedSongs) {
|
||||||
put(cachedSong.mediaStoreId, cachedSong)
|
put(cachedSong.mediaStoreId, cachedSong)
|
||||||
|
@ -69,58 +118,3 @@ private class RealCache(cachedSongs: List<CachedSong>) : Cache {
|
||||||
return false
|
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.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import com.google.android.material.checkbox.MaterialCheckBox
|
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.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.DialogSeparatorsBinding
|
import org.oxycblt.auxio.databinding.DialogSeparatorsBinding
|
||||||
|
@ -33,7 +35,10 @@ import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||||
* split tags with multiple values.
|
* split tags with multiple values.
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
class SeparatorsDialog : ViewBindingDialogFragment<DialogSeparatorsBinding>() {
|
class SeparatorsDialog : ViewBindingDialogFragment<DialogSeparatorsBinding>() {
|
||||||
|
@Inject lateinit var musicSettings: MusicSettings
|
||||||
|
|
||||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||||
DialogSeparatorsBinding.inflate(inflater)
|
DialogSeparatorsBinding.inflate(inflater)
|
||||||
|
|
||||||
|
@ -42,7 +47,7 @@ class SeparatorsDialog : ViewBindingDialogFragment<DialogSeparatorsBinding>() {
|
||||||
.setTitle(R.string.set_separators)
|
.setTitle(R.string.set_separators)
|
||||||
.setNegativeButton(R.string.lbl_cancel, null)
|
.setNegativeButton(R.string.lbl_cancel, null)
|
||||||
.setPositiveButton(R.string.lbl_save) { _, _ ->
|
.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
|
// the corresponding CheckBox for each character instead of doing an iteration
|
||||||
// through the separator list for each CheckBox.
|
// through the separator list for each CheckBox.
|
||||||
(savedInstanceState?.getString(KEY_PENDING_SEPARATORS)
|
(savedInstanceState?.getString(KEY_PENDING_SEPARATORS)
|
||||||
?: MusicSettings.from(requireContext()).multiValueSeparators)
|
?: musicSettings.multiValueSeparators)
|
||||||
.forEach {
|
.forEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
Separators.COMMA -> binding.separatorComma.isChecked = true
|
Separators.COMMA -> binding.separatorComma.isChecked = true
|
||||||
|
|
|
@ -81,7 +81,7 @@ interface MediaStoreExtractor {
|
||||||
* Create a framework-backed instance.
|
* Create a framework-backed instance.
|
||||||
* @param context [Context] required.
|
* @param context [Context] required.
|
||||||
* @param musicSettings [MusicSettings] 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 =
|
fun from(context: Context, musicSettings: MusicSettings): MediaStoreExtractor =
|
||||||
when {
|
when {
|
||||||
|
@ -94,7 +94,7 @@ interface MediaStoreExtractor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class RealMediaStoreExtractor(
|
private abstract class BaseMediaStoreExtractor(
|
||||||
protected val context: Context,
|
protected val context: Context,
|
||||||
private val musicSettings: MusicSettings
|
private val musicSettings: MusicSettings
|
||||||
) : MediaStoreExtractor {
|
) : MediaStoreExtractor {
|
||||||
|
@ -352,7 +352,7 @@ private abstract class RealMediaStoreExtractor(
|
||||||
// speed, we only want to add redundancy on known issues, not with possible issues.
|
// speed, we only want to add redundancy on known issues, not with possible issues.
|
||||||
|
|
||||||
private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSettings) :
|
private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSettings) :
|
||||||
RealMediaStoreExtractor(context, musicSettings) {
|
BaseMediaStoreExtractor(context, musicSettings) {
|
||||||
override val projection: Array<String>
|
override val projection: Array<String>
|
||||||
get() =
|
get() =
|
||||||
super.projection +
|
super.projection +
|
||||||
|
@ -385,7 +385,7 @@ private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSet
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
genreNamesMap: Map<Long, String>,
|
genreNamesMap: Map<Long, String>,
|
||||||
storageManager: StorageManager
|
storageManager: StorageManager
|
||||||
) : RealMediaStoreExtractor.Query(cursor, genreNamesMap) {
|
) : BaseMediaStoreExtractor.Query(cursor, genreNamesMap) {
|
||||||
// Set up cursor indices for later use.
|
// Set up cursor indices for later use.
|
||||||
private val trackIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TRACK)
|
private val trackIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TRACK)
|
||||||
private val dataIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATA)
|
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.
|
* @param context [Context] required to query the media database.
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
|
@ -438,7 +438,7 @@ private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSet
|
||||||
private abstract class BaseApi29MediaStoreExtractor(
|
private abstract class BaseApi29MediaStoreExtractor(
|
||||||
context: Context,
|
context: Context,
|
||||||
musicSettings: MusicSettings
|
musicSettings: MusicSettings
|
||||||
) : RealMediaStoreExtractor(context, musicSettings) {
|
) : BaseMediaStoreExtractor(context, musicSettings) {
|
||||||
override val projection: Array<String>
|
override val projection: Array<String>
|
||||||
get() =
|
get() =
|
||||||
super.projection +
|
super.projection +
|
||||||
|
@ -471,7 +471,7 @@ private abstract class BaseApi29MediaStoreExtractor(
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
genreNamesMap: Map<Long, String>,
|
genreNamesMap: Map<Long, String>,
|
||||||
storageManager: StorageManager
|
storageManager: StorageManager
|
||||||
) : RealMediaStoreExtractor.Query(cursor, genreNamesMap) {
|
) : BaseMediaStoreExtractor.Query(cursor, genreNamesMap) {
|
||||||
private val volumeIndex =
|
private val volumeIndex =
|
||||||
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.VOLUME_NAME)
|
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.VOLUME_NAME)
|
||||||
private val relativePathIndex =
|
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
|
* API
|
||||||
* 29.
|
* 29.
|
||||||
* @param context [Context] required to query the media database.
|
* @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.
|
* 30 onwards.
|
||||||
* @param context [Context] required to query the media database.
|
* @param context [Context] required to query the media database.
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
|
|
@ -29,6 +29,7 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
|
import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
|
||||||
|
@ -48,6 +49,7 @@ class MusicDirsDialog :
|
||||||
private val dirAdapter = DirectoryAdapter(this)
|
private val dirAdapter = DirectoryAdapter(this)
|
||||||
private var openDocumentTreeLauncher: ActivityResultLauncher<Uri?>? = null
|
private var openDocumentTreeLauncher: ActivityResultLauncher<Uri?>? = null
|
||||||
private var storageManager: StorageManager? = null
|
private var storageManager: StorageManager? = null
|
||||||
|
@Inject lateinit var musicSettings: MusicSettings
|
||||||
|
|
||||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||||
DialogMusicDirsBinding.inflate(inflater)
|
DialogMusicDirsBinding.inflate(inflater)
|
||||||
|
@ -57,11 +59,10 @@ class MusicDirsDialog :
|
||||||
.setTitle(R.string.set_dirs)
|
.setTitle(R.string.set_dirs)
|
||||||
.setNegativeButton(R.string.lbl_cancel, null)
|
.setNegativeButton(R.string.lbl_cancel, null)
|
||||||
.setPositiveButton(R.string.lbl_save) { _, _ ->
|
.setPositiveButton(R.string.lbl_save) { _, _ ->
|
||||||
val settings = MusicSettings.from(requireContext())
|
|
||||||
val newDirs = MusicDirectories(dirAdapter.dirs, isUiModeInclude(requireBinding()))
|
val newDirs = MusicDirectories(dirAdapter.dirs, isUiModeInclude(requireBinding()))
|
||||||
if (settings.musicDirs != newDirs) {
|
if (musicSettings.musicDirs != newDirs) {
|
||||||
logD("Committing changes")
|
logD("Committing changes")
|
||||||
settings.musicDirs = newDirs
|
musicSettings.musicDirs = newDirs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +99,7 @@ class MusicDirsDialog :
|
||||||
itemAnimator = null
|
itemAnimator = null
|
||||||
}
|
}
|
||||||
|
|
||||||
var dirs = MusicSettings.from(context).musicDirs
|
var dirs = musicSettings.musicDirs
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
val pendingDirs = savedInstanceState.getStringArrayList(KEY_PENDING_DIRS)
|
val pendingDirs = savedInstanceState.getStringArrayList(KEY_PENDING_DIRS)
|
||||||
if (pendingDirs != null) {
|
if (pendingDirs != null) {
|
||||||
|
|
|
@ -67,7 +67,7 @@ class IndexerService : Service(), Indexer.Controller, MusicSettings.Listener {
|
||||||
private lateinit var observingNotification: ObservingNotification
|
private lateinit var observingNotification: ObservingNotification
|
||||||
private lateinit var wakeLock: PowerManager.WakeLock
|
private lateinit var wakeLock: PowerManager.WakeLock
|
||||||
private lateinit var indexerContentObserver: SystemContentObserver
|
private lateinit var indexerContentObserver: SystemContentObserver
|
||||||
private lateinit var settings: MusicSettings
|
@Inject lateinit var musicSettings: MusicSettings
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.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
|
// 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.
|
// condition to cause us to load music before we were fully initialize.
|
||||||
indexerContentObserver = SystemContentObserver()
|
indexerContentObserver = SystemContentObserver()
|
||||||
settings = MusicSettings.from(this)
|
musicSettings.registerListener(this)
|
||||||
settings.registerListener(this)
|
|
||||||
indexer.registerController(this)
|
indexer.registerController(this)
|
||||||
// An indeterminate indexer and a missing library implies we are extremely early
|
// An indeterminate indexer and a missing library implies we are extremely early
|
||||||
// in app initialization so start loading music.
|
// 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
|
// Then cancel the listener-dependent components to ensure that stray reloading
|
||||||
// events will not occur.
|
// events will not occur.
|
||||||
indexerContentObserver.release()
|
indexerContentObserver.release()
|
||||||
settings.unregisterListener(this)
|
musicSettings.unregisterListener(this)
|
||||||
indexer.unregisterController(this)
|
indexer.unregisterController(this)
|
||||||
// Then cancel any remaining music loading jobs.
|
// Then cancel any remaining music loading jobs.
|
||||||
serviceJob.cancel()
|
serviceJob.cancel()
|
||||||
|
@ -197,7 +196,7 @@ class IndexerService : Service(), Indexer.Controller, MusicSettings.Listener {
|
||||||
* currently monitoring the music library for changes.
|
* currently monitoring the music library for changes.
|
||||||
*/
|
*/
|
||||||
private fun updateIdleSession() {
|
private fun updateIdleSession() {
|
||||||
if (settings.shouldBeObserving) {
|
if (musicSettings.shouldBeObserving) {
|
||||||
// There are a few reasons why we stay in the foreground with automatic rescanning:
|
// 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
|
// 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
|
// 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() {
|
override fun run() {
|
||||||
// Check here if we should even start a reindex. This is much less bug-prone than
|
// 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.
|
// registering and de-registering this component as this setting changes.
|
||||||
if (settings.shouldBeObserving) {
|
if (musicSettings.shouldBeObserving) {
|
||||||
onStartIndexing(true)
|
onStartIndexing(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,17 +23,11 @@ import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import org.oxycblt.auxio.playback.persist.PersistenceRepository
|
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
class PlaybackModule {
|
class PlaybackModule {
|
||||||
@Provides fun playbackStateManager() = PlaybackStateManager.get()
|
@Provides fun stateManager() = PlaybackStateManager.get()
|
||||||
|
|
||||||
@Provides fun settings(@ApplicationContext context: Context) = PlaybackSettings.from(context)
|
@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.
|
* Get a framework-backed implementation.
|
||||||
* @param context [Context] required.
|
* @param context [Context] required.
|
||||||
*/
|
*/
|
||||||
fun from(context: Context): PlaybackSettings = RealPlaybackSettings(context)
|
fun from(context: Context): PlaybackSettings = PlaybackSettingsImpl(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RealPlaybackSettings(context: Context) :
|
class PlaybackSettingsImpl(context: Context) :
|
||||||
Settings.Real<PlaybackSettings.Listener>(context), PlaybackSettings {
|
Settings.Impl<PlaybackSettings.Listener>(context), PlaybackSettings {
|
||||||
override val inListPlaybackMode: MusicMode
|
override val inListPlaybackMode: MusicMode
|
||||||
get() =
|
get() =
|
||||||
MusicMode.fromIntCode(
|
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
|
package org.oxycblt.auxio.playback.persist
|
||||||
|
|
||||||
import android.content.Context
|
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.MusicParent
|
||||||
import org.oxycblt.auxio.music.model.Library
|
import org.oxycblt.auxio.music.model.Library
|
||||||
import org.oxycblt.auxio.playback.queue.Queue
|
import org.oxycblt.auxio.playback.queue.Queue
|
||||||
|
@ -47,11 +49,13 @@ interface PersistenceRepository {
|
||||||
* Get a framework-backed implementation.
|
* Get a framework-backed implementation.
|
||||||
* @param context [Context] required.
|
* @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 database: PersistenceDatabase by lazy { PersistenceDatabase.getInstance(context) }
|
||||||
private val playbackStateDao: PlaybackStateDao by lazy { database.playbackStateDao() }
|
private val playbackStateDao: PlaybackStateDao by lazy { database.playbackStateDao() }
|
||||||
private val queueDao: QueueDao by lazy { database.queueDao() }
|
private val queueDao: QueueDao by lazy { database.queueDao() }
|
||||||
|
|
|
@ -287,7 +287,7 @@ interface PlaybackStateManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
val newInstance = RealPlaybackStateManager()
|
val newInstance = PlaybackStateManagerImpl()
|
||||||
INSTANCE = newInstance
|
INSTANCE = newInstance
|
||||||
return newInstance
|
return newInstance
|
||||||
}
|
}
|
||||||
|
@ -295,7 +295,7 @@ interface PlaybackStateManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RealPlaybackStateManager : PlaybackStateManager {
|
private class PlaybackStateManagerImpl : PlaybackStateManager {
|
||||||
private val listeners = mutableListOf<PlaybackStateManager.Listener>()
|
private val listeners = mutableListOf<PlaybackStateManager.Listener>()
|
||||||
@Volatile private var internalPlayer: InternalPlayer? = null
|
@Volatile private var internalPlayer: InternalPlayer? = null
|
||||||
@Volatile private var pendingAction: InternalPlayer.Action? = null
|
@Volatile private var pendingAction: InternalPlayer.Action? = null
|
||||||
|
|
|
@ -18,7 +18,9 @@
|
||||||
package org.oxycblt.auxio.search
|
package org.oxycblt.auxio.search
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import java.text.Normalizer
|
import java.text.Normalizer
|
||||||
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
|
@ -51,17 +53,10 @@ interface SearchEngine {
|
||||||
val artists: List<Artist>?,
|
val artists: List<Artist>?,
|
||||||
val genres: List<Genre>?
|
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) =
|
override suspend fun search(items: SearchEngine.Items, query: String) =
|
||||||
SearchEngine.Items(
|
SearchEngine.Items(
|
||||||
songs = items.songs?.searchListImpl(query) { q, song -> song.path.name.contains(q) },
|
songs = items.songs?.searchListImpl(query) { q, song -> song.path.name.contains(q) },
|
||||||
|
|
|
@ -17,16 +17,14 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.search
|
package org.oxycblt.auxio.search
|
||||||
|
|
||||||
import android.content.Context
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
class SearchModule {
|
interface SearchModule {
|
||||||
@Provides fun engine(@ApplicationContext context: Context) = SearchEngine.from(context)
|
@Binds fun engine(searchEngine: SearchEngineImpl): SearchEngine
|
||||||
@Provides fun settings(@ApplicationContext context: Context) = SearchSettings.from(context)
|
@Binds fun settings(searchSettings: SearchSettingsImpl): SearchSettings
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.oxycblt.auxio.search
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.music.MusicMode
|
import org.oxycblt.auxio.music.MusicMode
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
|
@ -30,18 +32,10 @@ import org.oxycblt.auxio.settings.Settings
|
||||||
interface SearchSettings : Settings<Nothing> {
|
interface SearchSettings : Settings<Nothing> {
|
||||||
/** The type of Music the search view is currently filtering to. */
|
/** The type of Music the search view is currently filtering to. */
|
||||||
var searchFilterMode: MusicMode?
|
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) :
|
class SearchSettingsImpl @Inject constructor(@ApplicationContext context: Context) :
|
||||||
Settings.Real<Nothing>(context), SearchSettings {
|
Settings.Impl<Nothing>(context), SearchSettings {
|
||||||
override var searchFilterMode: MusicMode?
|
override var searchFilterMode: MusicMode?
|
||||||
get() =
|
get() =
|
||||||
MusicMode.fromIntCode(
|
MusicMode.fromIntCode(
|
||||||
|
|
|
@ -54,7 +54,7 @@ interface Settings<L> {
|
||||||
* A framework-backed [Settings] implementation.
|
* A framework-backed [Settings] implementation.
|
||||||
* @param context [Context] required.
|
* @param context [Context] required.
|
||||||
*/
|
*/
|
||||||
abstract class Real<L>(private val context: Context) :
|
abstract class Impl<L>(private val context: Context) :
|
||||||
Settings<L>, SharedPreferences.OnSharedPreferenceChangeListener {
|
Settings<L>, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
protected val sharedPreferences: SharedPreferences =
|
protected val sharedPreferences: SharedPreferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
|
PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
|
||||||
|
|
|
@ -20,6 +20,8 @@ package org.oxycblt.auxio.settings.categories
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.settings.BasePreferenceFragment
|
import org.oxycblt.auxio.settings.BasePreferenceFragment
|
||||||
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
|
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
|
||||||
|
@ -30,7 +32,10 @@ import org.oxycblt.auxio.util.isNight
|
||||||
* Display preferences.
|
* Display preferences.
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
class UIPreferenceFragment : BasePreferenceFragment(R.xml.preferences_ui) {
|
class UIPreferenceFragment : BasePreferenceFragment(R.xml.preferences_ui) {
|
||||||
|
@Inject lateinit var uiSettings: UISettings
|
||||||
|
|
||||||
override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
|
override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
|
||||||
if (preference.key == getString(R.string.set_key_accent)) {
|
if (preference.key == getString(R.string.set_key_accent)) {
|
||||||
findNavController().navigate(UIPreferenceFragmentDirections.goToAccentDialog())
|
findNavController().navigate(UIPreferenceFragmentDirections.goToAccentDialog())
|
||||||
|
@ -47,7 +52,7 @@ class UIPreferenceFragment : BasePreferenceFragment(R.xml.preferences_ui) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getString(R.string.set_key_accent) -> {
|
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) -> {
|
getString(R.string.set_key_black_theme) -> {
|
||||||
preference.onPreferenceChangeListener =
|
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 android.os.Build
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.ui.accent.Accent
|
import org.oxycblt.auxio.ui.accent.Accent
|
||||||
|
@ -50,12 +52,12 @@ interface UISettings : Settings<UISettings.Listener> {
|
||||||
* Get a framework-backed implementation.
|
* Get a framework-backed implementation.
|
||||||
* @param context [Context] required.
|
* @param context [Context] required.
|
||||||
*/
|
*/
|
||||||
fun from(context: Context): UISettings = RealUISettings(context)
|
fun from(context: Context): UISettings = UISettingsImpl(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RealUISettings(context: Context) :
|
class UISettingsImpl @Inject constructor(@ApplicationContext context: Context) :
|
||||||
Settings.Real<UISettings.Listener>(context), UISettings {
|
Settings.Impl<UISettings.Listener>(context), UISettings {
|
||||||
override val theme: Int
|
override val theme: Int
|
||||||
get() =
|
get() =
|
||||||
sharedPreferences.getInt(
|
sharedPreferences.getInt(
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.view.LayoutInflater
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.DialogAccentBinding
|
import org.oxycblt.auxio.databinding.DialogAccentBinding
|
||||||
|
@ -39,6 +40,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
class AccentCustomizeDialog :
|
class AccentCustomizeDialog :
|
||||||
ViewBindingDialogFragment<DialogAccentBinding>(), ClickableListListener<Accent> {
|
ViewBindingDialogFragment<DialogAccentBinding>(), ClickableListListener<Accent> {
|
||||||
private var accentAdapter = AccentAdapter(this)
|
private var accentAdapter = AccentAdapter(this)
|
||||||
|
@Inject lateinit var uiSettings: UISettings
|
||||||
|
|
||||||
override fun onCreateBinding(inflater: LayoutInflater) = DialogAccentBinding.inflate(inflater)
|
override fun onCreateBinding(inflater: LayoutInflater) = DialogAccentBinding.inflate(inflater)
|
||||||
|
|
||||||
|
@ -46,14 +48,13 @@ class AccentCustomizeDialog :
|
||||||
builder
|
builder
|
||||||
.setTitle(R.string.set_accent)
|
.setTitle(R.string.set_accent)
|
||||||
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
||||||
val settings = UISettings.from(requireContext())
|
if (accentAdapter.selectedAccent == uiSettings.accent) {
|
||||||
if (accentAdapter.selectedAccent == settings.accent) {
|
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
return@setPositiveButton
|
return@setPositiveButton
|
||||||
}
|
}
|
||||||
|
|
||||||
logD("Applying new accent")
|
logD("Applying new accent")
|
||||||
settings.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
|
uiSettings.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
|
||||||
requireActivity().recreate()
|
requireActivity().recreate()
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
@ -67,7 +68,7 @@ class AccentCustomizeDialog :
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT))
|
Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT))
|
||||||
} else {
|
} else {
|
||||||
UISettings.from(requireContext()).accent
|
uiSettings.accent
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,10 +27,13 @@ import android.os.Bundle
|
||||||
import android.util.SizeF
|
import android.util.SizeF
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||||
import org.oxycblt.auxio.playback.system.PlaybackService
|
import org.oxycblt.auxio.playback.system.PlaybackService
|
||||||
|
import org.oxycblt.auxio.ui.UISettings
|
||||||
import org.oxycblt.auxio.util.*
|
import org.oxycblt.auxio.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,7 +41,10 @@ import org.oxycblt.auxio.util.*
|
||||||
* state alongside actions to control it.
|
* state alongside actions to control it.
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
|
@AndroidEntryPoint
|
||||||
class WidgetProvider : AppWidgetProvider() {
|
class WidgetProvider : AppWidgetProvider() {
|
||||||
|
@Inject lateinit var uiSettings: UISettings
|
||||||
|
|
||||||
override fun onUpdate(
|
override fun onUpdate(
|
||||||
context: Context,
|
context: Context,
|
||||||
appWidgetManager: AppWidgetManager,
|
appWidgetManager: AppWidgetManager,
|
||||||
|
@ -139,7 +145,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
*/
|
*/
|
||||||
private fun newThinLayout(context: Context, state: WidgetComponent.PlaybackState) =
|
private fun newThinLayout(context: Context, state: WidgetComponent.PlaybackState) =
|
||||||
newRemoteViews(context, R.layout.widget_thin)
|
newRemoteViews(context, R.layout.widget_thin)
|
||||||
.setupBackground(context)
|
.setupBackground()
|
||||||
.setupPlaybackState(context, state)
|
.setupPlaybackState(context, state)
|
||||||
.setupTimelineControls(context, state)
|
.setupTimelineControls(context, state)
|
||||||
|
|
||||||
|
@ -150,7 +156,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
*/
|
*/
|
||||||
private fun newSmallLayout(context: Context, state: WidgetComponent.PlaybackState) =
|
private fun newSmallLayout(context: Context, state: WidgetComponent.PlaybackState) =
|
||||||
newRemoteViews(context, R.layout.widget_small)
|
newRemoteViews(context, R.layout.widget_small)
|
||||||
.setupBar(context)
|
.setupBar()
|
||||||
.setupCover(context, state)
|
.setupCover(context, state)
|
||||||
.setupTimelineControls(context, state)
|
.setupTimelineControls(context, state)
|
||||||
|
|
||||||
|
@ -161,7 +167,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
*/
|
*/
|
||||||
private fun newMediumLayout(context: Context, state: WidgetComponent.PlaybackState) =
|
private fun newMediumLayout(context: Context, state: WidgetComponent.PlaybackState) =
|
||||||
newRemoteViews(context, R.layout.widget_medium)
|
newRemoteViews(context, R.layout.widget_medium)
|
||||||
.setupBackground(context)
|
.setupBackground()
|
||||||
.setupPlaybackState(context, state)
|
.setupPlaybackState(context, state)
|
||||||
.setupTimelineControls(context, state)
|
.setupTimelineControls(context, state)
|
||||||
|
|
||||||
|
@ -172,7 +178,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
*/
|
*/
|
||||||
private fun newWideLayout(context: Context, state: WidgetComponent.PlaybackState) =
|
private fun newWideLayout(context: Context, state: WidgetComponent.PlaybackState) =
|
||||||
newRemoteViews(context, R.layout.widget_wide)
|
newRemoteViews(context, R.layout.widget_wide)
|
||||||
.setupBar(context)
|
.setupBar()
|
||||||
.setupCover(context, state)
|
.setupCover(context, state)
|
||||||
.setupFullControls(context, state)
|
.setupFullControls(context, state)
|
||||||
|
|
||||||
|
@ -183,7 +189,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
*/
|
*/
|
||||||
private fun newLargeLayout(context: Context, state: WidgetComponent.PlaybackState) =
|
private fun newLargeLayout(context: Context, state: WidgetComponent.PlaybackState) =
|
||||||
newRemoteViews(context, R.layout.widget_large)
|
newRemoteViews(context, R.layout.widget_large)
|
||||||
.setupBackground(context)
|
.setupBackground()
|
||||||
.setupPlaybackState(context, state)
|
.setupPlaybackState(context, state)
|
||||||
.setupFullControls(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.
|
* "floating" drawable that sits in front of the cover and contains the controls.
|
||||||
* @param context [Context] required to set up the view.
|
* @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.
|
// 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.
|
// On API 31+, the bar should always be round in order to fit in with other widgets.
|
||||||
val background =
|
val background =
|
||||||
if (useRoundedRemoteViews(context)) {
|
if (useRoundedRemoteViews(uiSettings)) {
|
||||||
R.drawable.ui_widget_bar_round
|
R.drawable.ui_widget_bar_round
|
||||||
} else {
|
} else {
|
||||||
R.drawable.ui_widget_bar_system
|
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.
|
* self-explanatory, being a solid-color background that sits behind the cover and controls.
|
||||||
* @param context [Context] required to set up the view.
|
* @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.
|
// 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
|
// On API 31+, the background should always be round in order to fit in with other
|
||||||
// widgets.
|
// widgets.
|
||||||
val background =
|
val background =
|
||||||
if (useRoundedRemoteViews(context)) {
|
if (useRoundedRemoteViews(uiSettings)) {
|
||||||
R.drawable.ui_widget_bg_round
|
R.drawable.ui_widget_bg_round
|
||||||
} else {
|
} else {
|
||||||
R.drawable.ui_widget_bg_system
|
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
|
* 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.
|
* 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.
|
* @return true if to use round mode, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun useRoundedRemoteViews(context: Context) =
|
fun useRoundedRemoteViews(uiSettings: UISettings) =
|
||||||
UISettings.from(context).roundMode || Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
uiSettings.roundMode || Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||||
|
|
|
@ -117,7 +117,7 @@ class RawMusicTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun albumRaw_equals_withRealArtists() {
|
fun albumRaw_equals_withArtists() {
|
||||||
val a =
|
val a =
|
||||||
RawAlbum(
|
RawAlbum(
|
||||||
mediaStoreId = -1,
|
mediaStoreId = -1,
|
||||||
|
@ -125,7 +125,7 @@ class RawMusicTest {
|
||||||
name = "Album",
|
name = "Album",
|
||||||
sortName = null,
|
sortName = null,
|
||||||
releaseType = null,
|
releaseType = null,
|
||||||
rawArtists = listOf(RawArtist(name = "RealArtist A")))
|
rawArtists = listOf(RawArtist(name = "Artist A")))
|
||||||
val b =
|
val b =
|
||||||
RawAlbum(
|
RawAlbum(
|
||||||
mediaStoreId = -1,
|
mediaStoreId = -1,
|
||||||
|
@ -133,7 +133,7 @@ class RawMusicTest {
|
||||||
name = "Album",
|
name = "Album",
|
||||||
sortName = null,
|
sortName = null,
|
||||||
releaseType = null,
|
releaseType = null,
|
||||||
rawArtists = listOf(RawArtist(name = "RealArtist B")))
|
rawArtists = listOf(RawArtist(name = "Artist B")))
|
||||||
assertTrue(a != b)
|
assertTrue(a != b)
|
||||||
assertTrue(a.hashCode() != b.hashCode())
|
assertTrue(a.hashCode() != b.hashCode())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue