image: use di w/coil
Use dependency injection with Coil. This allows me to use the coil-base artifact which should remove a bit of superfluous dexcode, assuming dagger uses less. It probably doesn't.
This commit is contained in:
parent
63e5a7ee69
commit
dd2017c510
13 changed files with 112 additions and 81 deletions
|
@ -113,7 +113,7 @@ dependencies {
|
|||
implementation fileTree(dir: "libs", include: ["extension-*.aar"])
|
||||
|
||||
// Image loading
|
||||
implementation "io.coil-kt:coil:2.1.0"
|
||||
implementation "io.coil-kt:coil-base:2.1.0"
|
||||
|
||||
// Material
|
||||
// Locked below 1.7.0-alpha03 to avoid the same ripple bug
|
||||
|
|
|
@ -22,17 +22,9 @@ import android.content.Intent
|
|||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
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
|
||||
import org.oxycblt.auxio.image.extractor.ErrorCrossfadeTransitionFactory
|
||||
import org.oxycblt.auxio.image.extractor.GenreImageFetcher
|
||||
import org.oxycblt.auxio.image.extractor.MusicKeyer
|
||||
import org.oxycblt.auxio.playback.PlaybackSettings
|
||||
import org.oxycblt.auxio.ui.UISettings
|
||||
|
||||
|
@ -41,7 +33,7 @@ import org.oxycblt.auxio.ui.UISettings
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@HiltAndroidApp
|
||||
class Auxio : Application(), ImageLoaderFactory {
|
||||
class Auxio : Application() {
|
||||
@Inject lateinit var imageSettings: ImageSettings
|
||||
@Inject lateinit var playbackSettings: PlaybackSettings
|
||||
@Inject lateinit var uiSettings: UISettings
|
||||
|
@ -68,22 +60,6 @@ class Auxio : Application(), ImageLoaderFactory {
|
|||
.build()))
|
||||
}
|
||||
|
||||
override fun newImageLoader() =
|
||||
ImageLoader.Builder(applicationContext)
|
||||
.components {
|
||||
// Add fetchers for Music components to make them usable with ImageRequest
|
||||
add(MusicKeyer())
|
||||
add(AlbumCoverFetcher.SongFactory())
|
||||
add(AlbumCoverFetcher.AlbumFactory())
|
||||
add(ArtistImageFetcher.Factory())
|
||||
add(GenreImageFetcher.Factory())
|
||||
}
|
||||
// Use our own crossfade with error drawable support
|
||||
.transitionFactory(ErrorCrossfadeTransitionFactory())
|
||||
// Not downloading anything, so no disk-caching
|
||||
.diskCachePolicy(CachePolicy.DISABLED)
|
||||
.build()
|
||||
|
||||
companion object {
|
||||
/** The [Intent] name for the "Shuffle All" shortcut. */
|
||||
const val INTENT_KEY_SHORTCUT_SHUFFLE = BuildConfig.APPLICATION_ID + ".action.SHUFFLE_ALL"
|
||||
|
|
|
@ -20,10 +20,12 @@ package org.oxycblt.auxio.image
|
|||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import coil.imageLoader
|
||||
import coil.ImageLoader
|
||||
import coil.request.Disposable
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Size
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import org.oxycblt.auxio.image.extractor.SquareFrameTransform
|
||||
import org.oxycblt.auxio.music.Song
|
||||
|
||||
|
@ -38,7 +40,12 @@ import org.oxycblt.auxio.music.Song
|
|||
* @param context [Context] required to load images.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class BitmapProvider(private val context: Context) {
|
||||
class BitmapProvider
|
||||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val imageLoader: ImageLoader
|
||||
) {
|
||||
/**
|
||||
* An extension of [Disposable] with an additional [Target] to deliver the final [Bitmap] to.
|
||||
*/
|
||||
|
@ -94,7 +101,7 @@ class BitmapProvider(private val context: Context) {
|
|||
onSuccess = {
|
||||
synchronized(this) {
|
||||
if (currentHandle == handle) {
|
||||
// Has not been superceded by a new request, can deliver
|
||||
// Has not been superseded by a new request, can deliver
|
||||
// this result.
|
||||
target.onCompleted(it.toBitmap())
|
||||
}
|
||||
|
@ -103,13 +110,13 @@ class BitmapProvider(private val context: Context) {
|
|||
onError = {
|
||||
synchronized(this) {
|
||||
if (currentHandle == handle) {
|
||||
// Has not been superceded by a new request, can deliver
|
||||
// Has not been superseded by a new request, can deliver
|
||||
// this result.
|
||||
target.onCompleted(null)
|
||||
}
|
||||
}
|
||||
})
|
||||
currentRequest = Request(context.imageLoader.enqueue(imageRequest.build()), target)
|
||||
currentRequest = Request(imageLoader.enqueue(imageRequest.build()), target)
|
||||
}
|
||||
|
||||
/** Release this instance, cancelling any currently running operations. */
|
||||
|
|
|
@ -17,13 +17,52 @@
|
|||
|
||||
package org.oxycblt.auxio.image
|
||||
|
||||
import android.content.Context
|
||||
import coil.ImageLoader
|
||||
import coil.request.CachePolicy
|
||||
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 javax.inject.Singleton
|
||||
import org.oxycblt.auxio.image.extractor.AlbumCoverFetcher
|
||||
import org.oxycblt.auxio.image.extractor.ArtistImageFetcher
|
||||
import org.oxycblt.auxio.image.extractor.ErrorCrossfadeTransitionFactory
|
||||
import org.oxycblt.auxio.image.extractor.GenreImageFetcher
|
||||
import org.oxycblt.auxio.image.extractor.MusicKeyer
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface ImageModule {
|
||||
@Binds fun settings(imageSettings: ImageSettingsImpl): ImageSettings
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class CoilModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun imageLoader(
|
||||
@ApplicationContext context: Context,
|
||||
songFactory: AlbumCoverFetcher.SongFactory,
|
||||
albumFactory: AlbumCoverFetcher.AlbumFactory,
|
||||
artistFactory: ArtistImageFetcher.Factory,
|
||||
genreFactory: GenreImageFetcher.Factory
|
||||
) =
|
||||
ImageLoader.Builder(context)
|
||||
.components {
|
||||
// Add fetchers for Music components to make them usable with ImageRequest
|
||||
add(MusicKeyer())
|
||||
add(songFactory)
|
||||
add(albumFactory)
|
||||
add(artistFactory)
|
||||
add(genreFactory)
|
||||
}
|
||||
// Use our own crossfade with error drawable support
|
||||
.transitionFactory(ErrorCrossfadeTransitionFactory())
|
||||
// Not downloading anything, so no disk-caching
|
||||
.diskCachePolicy(CachePolicy.DISABLED)
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -37,14 +37,6 @@ interface ImageSettings : Settings<ImageSettings.Listener> {
|
|||
/** Called when [coverMode] changes. */
|
||||
fun onCoverModeChanged() {}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Get a framework-backed implementation.
|
||||
* @param context [Context] required.
|
||||
*/
|
||||
fun from(context: Context): ImageSettings = ImageSettingsImpl(context)
|
||||
}
|
||||
}
|
||||
|
||||
class ImageSettingsImpl @Inject constructor(@ApplicationContext context: Context) :
|
||||
|
|
|
@ -29,8 +29,9 @@ import androidx.annotation.StringRes
|
|||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import coil.dispose
|
||||
import coil.load
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.util.CoilUtils
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
@ -60,6 +61,7 @@ class StyledImageView
|
|||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||
AppCompatImageView(context, attrs, defStyleAttr) {
|
||||
@Inject lateinit var imageLoader: ImageLoader
|
||||
@Inject lateinit var uiSettings: UISettings
|
||||
|
||||
init {
|
||||
|
@ -125,13 +127,16 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
* field for the name of the [Music].
|
||||
*/
|
||||
private fun bindImpl(music: Music, @DrawableRes errorRes: Int, @StringRes descRes: Int) {
|
||||
val request =
|
||||
ImageRequest.Builder(context)
|
||||
.data(music)
|
||||
.error(StyledDrawable(context, context.getDrawableCompat(errorRes)))
|
||||
.transformations(SquareFrameTransform.INSTANCE)
|
||||
.target(this)
|
||||
.build()
|
||||
// Dispose of any previous image request and load a new image.
|
||||
dispose()
|
||||
load(music) {
|
||||
error(StyledDrawable(context, context.getDrawableCompat(errorRes)))
|
||||
transformations(SquareFrameTransform.INSTANCE)
|
||||
}
|
||||
|
||||
CoilUtils.dispose(this)
|
||||
imageLoader.enqueue(request)
|
||||
// Update the content description to the specified resource.
|
||||
contentDescription = context.getString(descRes, music.resolveName(context))
|
||||
}
|
||||
|
|
|
@ -27,9 +27,11 @@ import coil.fetch.SourceResult
|
|||
import coil.key.Keyer
|
||||
import coil.request.Options
|
||||
import coil.size.Size
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.min
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.oxycblt.auxio.image.ImageSettings
|
||||
import org.oxycblt.auxio.list.Sort
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Artist
|
||||
|
@ -57,9 +59,13 @@ class MusicKeyer : Keyer<Music> {
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class AlbumCoverFetcher
|
||||
private constructor(private val context: Context, private val album: Album) : Fetcher {
|
||||
private constructor(
|
||||
private val context: Context,
|
||||
private val imageSettings: ImageSettings,
|
||||
private val album: Album
|
||||
) : Fetcher {
|
||||
override suspend fun fetch(): FetchResult? =
|
||||
Covers.fetch(context, album)?.run {
|
||||
Covers.fetch(context, imageSettings, album)?.run {
|
||||
SourceResult(
|
||||
source = ImageSource(source().buffer(), context),
|
||||
mimeType = null,
|
||||
|
@ -67,15 +73,17 @@ private constructor(private val context: Context, private val album: Album) : Fe
|
|||
}
|
||||
|
||||
/** A [Fetcher.Factory] implementation that works with [Song]s. */
|
||||
class SongFactory : Fetcher.Factory<Song> {
|
||||
class SongFactory @Inject constructor(private val imageSettings: ImageSettings) :
|
||||
Fetcher.Factory<Song> {
|
||||
override fun create(data: Song, options: Options, imageLoader: ImageLoader) =
|
||||
AlbumCoverFetcher(options.context, data.album)
|
||||
AlbumCoverFetcher(options.context, imageSettings, data.album)
|
||||
}
|
||||
|
||||
/** A [Fetcher.Factory] implementation that works with [Album]s. */
|
||||
class AlbumFactory : Fetcher.Factory<Album> {
|
||||
class AlbumFactory @Inject constructor(private val imageSettings: ImageSettings) :
|
||||
Fetcher.Factory<Album> {
|
||||
override fun create(data: Album, options: Options, imageLoader: ImageLoader) =
|
||||
AlbumCoverFetcher(options.context, data)
|
||||
AlbumCoverFetcher(options.context, imageSettings, data)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,20 +94,23 @@ private constructor(private val context: Context, private val album: Album) : Fe
|
|||
class ArtistImageFetcher
|
||||
private constructor(
|
||||
private val context: Context,
|
||||
private val imageSettings: ImageSettings,
|
||||
private val size: Size,
|
||||
private val artist: Artist
|
||||
) : Fetcher {
|
||||
override suspend fun fetch(): FetchResult? {
|
||||
// Pick the "most prominent" albums (i.e albums with the most songs) to show in the image.
|
||||
val albums = Sort(Sort.Mode.ByCount, Sort.Direction.DESCENDING).albums(artist.albums)
|
||||
val results = albums.mapAtMostNotNull(4) { album -> Covers.fetch(context, album) }
|
||||
val results =
|
||||
albums.mapAtMostNotNull(4) { album -> Covers.fetch(context, imageSettings, album) }
|
||||
return Images.createMosaic(context, results, size)
|
||||
}
|
||||
|
||||
/** [Fetcher.Factory] implementation. */
|
||||
class Factory : Fetcher.Factory<Artist> {
|
||||
class Factory @Inject constructor(private val imageSettings: ImageSettings) :
|
||||
Fetcher.Factory<Artist> {
|
||||
override fun create(data: Artist, options: Options, imageLoader: ImageLoader) =
|
||||
ArtistImageFetcher(options.context, options.size, data)
|
||||
ArtistImageFetcher(options.context, imageSettings, options.size, data)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,18 +121,20 @@ private constructor(
|
|||
class GenreImageFetcher
|
||||
private constructor(
|
||||
private val context: Context,
|
||||
private val imageSettings: ImageSettings,
|
||||
private val size: Size,
|
||||
private val genre: Genre
|
||||
) : Fetcher {
|
||||
override suspend fun fetch(): FetchResult? {
|
||||
val results = genre.albums.mapAtMostNotNull(4) { Covers.fetch(context, it) }
|
||||
val results = genre.albums.mapAtMostNotNull(4) { Covers.fetch(context, imageSettings, it) }
|
||||
return Images.createMosaic(context, results, size)
|
||||
}
|
||||
|
||||
/** [Fetcher.Factory] implementation. */
|
||||
class Factory : Fetcher.Factory<Genre> {
|
||||
class Factory @Inject constructor(private val imageSettings: ImageSettings) :
|
||||
Fetcher.Factory<Genre> {
|
||||
override fun create(data: Genre, options: Options, imageLoader: ImageLoader) =
|
||||
GenreImageFetcher(options.context, options.size, data)
|
||||
GenreImageFetcher(options.context, imageSettings, options.size, data)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,13 +42,14 @@ object Covers {
|
|||
/**
|
||||
* Fetch an album cover, respecting the current cover configuration.
|
||||
* @param context [Context] required to load the image.
|
||||
* @param imageSettings [ImageSettings] required to obtain configuration information.
|
||||
* @param album [Album] to load the cover from.
|
||||
* @return An [InputStream] of image data if the cover loading was successful, null if the cover
|
||||
* loading failed or should not occur.
|
||||
*/
|
||||
suspend fun fetch(context: Context, album: Album): InputStream? {
|
||||
suspend fun fetch(context: Context, imageSettings: ImageSettings, album: Album): InputStream? {
|
||||
return try {
|
||||
when (ImageSettings.from(context).coverMode) {
|
||||
when (imageSettings.coverMode) {
|
||||
CoverMode.OFF -> null
|
||||
CoverMode.MEDIA_STORE -> fetchMediaStoreCovers(context, album)
|
||||
CoverMode.QUALITY -> fetchQualityCovers(context, album)
|
||||
|
|
|
@ -25,7 +25,7 @@ import android.os.IBinder
|
|||
import android.os.Looper
|
||||
import android.os.PowerManager
|
||||
import android.provider.MediaStore
|
||||
import coil.imageLoader
|
||||
import coil.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
@ -68,6 +68,7 @@ class IndexerService : Service(), Indexer.Controller, MusicSettings.Listener {
|
|||
private lateinit var wakeLock: PowerManager.WakeLock
|
||||
private lateinit var indexerContentObserver: SystemContentObserver
|
||||
@Inject lateinit var musicSettings: MusicSettings
|
||||
@Inject lateinit var imageLoader: ImageLoader
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
|
|
@ -52,8 +52,9 @@ class MediaSessionComponent
|
|||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val bitmapProvider: BitmapProvider,
|
||||
private val playbackManager: PlaybackStateManager,
|
||||
private val playbackSettings: PlaybackSettings
|
||||
private val playbackSettings: PlaybackSettings,
|
||||
) :
|
||||
MediaSessionCompat.Callback(),
|
||||
PlaybackStateManager.Listener,
|
||||
|
@ -66,7 +67,6 @@ constructor(
|
|||
}
|
||||
|
||||
private val notification = NotificationComponent(context, mediaSession.sessionToken)
|
||||
private val provider = BitmapProvider(context)
|
||||
|
||||
private var listener: Listener? = null
|
||||
|
||||
|
@ -98,7 +98,7 @@ constructor(
|
|||
*/
|
||||
fun release() {
|
||||
listener = null
|
||||
provider.release()
|
||||
bitmapProvider.release()
|
||||
playbackSettings.unregisterListener(this)
|
||||
playbackManager.removeListener(this)
|
||||
mediaSession.apply {
|
||||
|
@ -148,7 +148,7 @@ constructor(
|
|||
override fun onStateChanged(state: InternalPlayer.State) {
|
||||
invalidateSessionState()
|
||||
notification.updatePlaying(playbackManager.playerState.isPlaying)
|
||||
if (!provider.isBusy) {
|
||||
if (!bitmapProvider.isBusy) {
|
||||
listener?.onPostNotification(notification)
|
||||
}
|
||||
}
|
||||
|
@ -321,7 +321,7 @@ constructor(
|
|||
// We are normally supposed to use URIs for album art, but that removes some of the
|
||||
// nice things we can do like square cropping or high quality covers. Instead,
|
||||
// we load a full-size bitmap into the media session and take the performance hit.
|
||||
provider.load(
|
||||
bitmapProvider.load(
|
||||
song,
|
||||
object : BitmapProvider.Target {
|
||||
override fun onCompleted(bitmap: Bitmap?) {
|
||||
|
@ -416,7 +416,7 @@ constructor(
|
|||
else -> notification.updateRepeatMode(playbackManager.repeatMode)
|
||||
}
|
||||
|
||||
if (!provider.isBusy) {
|
||||
if (!bitmapProvider.isBusy) {
|
||||
listener?.onPostNotification(notification)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ package org.oxycblt.auxio.settings.categories
|
|||
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.Preference
|
||||
import coil.Coil
|
||||
import coil.ImageLoader
|
||||
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
|
||||
|
@ -28,7 +30,10 @@ import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
|
|||
* "Content" settings.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class MusicPreferenceFragment : BasePreferenceFragment(R.xml.preferences_music) {
|
||||
@Inject lateinit var imageLoader: ImageLoader
|
||||
|
||||
override fun onOpenDialogPreference(preference: WrappedDialogPreference) {
|
||||
if (preference.key == getString(R.string.set_key_separators)) {
|
||||
findNavController().navigate(MusicPreferenceFragmentDirections.goToSeparatorsDialog())
|
||||
|
@ -39,7 +44,7 @@ class MusicPreferenceFragment : BasePreferenceFragment(R.xml.preferences_music)
|
|||
if (preference.key == getString(R.string.set_key_cover_mode)) {
|
||||
preference.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _, _ ->
|
||||
Coil.imageLoader(requireContext()).memoryCache?.clear()
|
||||
imageLoader.memoryCache?.clear()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,14 +46,6 @@ interface UISettings : Settings<UISettings.Listener> {
|
|||
/** Called when [roundMode] changes. */
|
||||
fun onRoundModeChanged()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Get a framework-backed implementation.
|
||||
* @param context [Context] required.
|
||||
*/
|
||||
fun from(context: Context): UISettings = UISettingsImpl(context)
|
||||
}
|
||||
}
|
||||
|
||||
class UISettingsImpl @Inject constructor(@ApplicationContext context: Context) :
|
||||
|
|
|
@ -48,11 +48,11 @@ class WidgetComponent
|
|||
constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val imageSettings: ImageSettings,
|
||||
private val bitmapProvider: BitmapProvider,
|
||||
private val playbackManager: PlaybackStateManager,
|
||||
private val uiSettings: UISettings
|
||||
) : PlaybackStateManager.Listener, UISettings.Listener, ImageSettings.Listener {
|
||||
private val widgetProvider = WidgetProvider()
|
||||
private val provider = BitmapProvider(context)
|
||||
|
||||
init {
|
||||
playbackManager.addListener(this)
|
||||
|
@ -74,7 +74,7 @@ constructor(
|
|||
val repeatMode = playbackManager.repeatMode
|
||||
val isShuffled = playbackManager.queue.isShuffled
|
||||
|
||||
provider.load(
|
||||
bitmapProvider.load(
|
||||
song,
|
||||
object : BitmapProvider.Target {
|
||||
override fun onConfigRequest(builder: ImageRequest.Builder): ImageRequest.Builder {
|
||||
|
@ -112,7 +112,7 @@ constructor(
|
|||
|
||||
/** Release this instance, preventing any further events from updating the widget instances. */
|
||||
fun release() {
|
||||
provider.release()
|
||||
bitmapProvider.release()
|
||||
imageSettings.unregisterListener(this)
|
||||
playbackManager.removeListener(this)
|
||||
uiSettings.unregisterListener(this)
|
||||
|
|
Loading…
Reference in a new issue