Merge branch 'fixSomeStrictMode' into 'develop'

Fix some strict mode

See merge request ultrasonic/ultrasonic!944
This commit is contained in:
birdbird 2023-04-01 12:06:34 +00:00
commit 13f296b5ed
20 changed files with 218 additions and 190 deletions

View File

@ -18,10 +18,11 @@ import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.drakeet.multitype.ItemViewDelegate
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.Album
import org.moire.ultrasonic.imageloader.ImageLoader
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.util.LayoutType
import org.moire.ultrasonic.util.Settings.shouldUseId3Tags
import timber.log.Timber
@ -32,7 +33,6 @@ import timber.log.Timber
open class AlbumRowDelegate(
open val onItemClick: (Album) -> Unit,
open val onContextMenuClick: (MenuItem, Album) -> Boolean,
private val imageLoader: ImageLoader
) : ItemViewDelegate<Album, AlbumRowDelegate.ListViewHolder>(), KoinComponent {
private val starDrawable: Int = R.drawable.ic_star_full
@ -58,10 +58,13 @@ open class AlbumRowDelegate(
holder.star.setImageResource(if (item.starred) starDrawable else starHollowDrawable)
holder.star.setOnClickListener { onStarClick(item, holder.star) }
imageLoader.loadImage(
holder.coverArt, item,
false, 0, R.drawable.unknown_album
)
val imageLoaderProvider: ImageLoaderProvider by inject()
imageLoaderProvider.executeOn {
it.loadImage(
holder.coverArt, item,
false, 0, R.drawable.unknown_album
)
}
}
/**
@ -148,8 +151,7 @@ open class AlbumRowDelegate(
class AlbumGridDelegate(
onItemClick: (Album) -> Unit,
onContextMenuClick: (MenuItem, Album) -> Boolean,
imageLoader: ImageLoader
) : AlbumRowDelegate(onItemClick, onContextMenuClick, imageLoader) {
onContextMenuClick: (MenuItem, Album) -> Boolean
) : AlbumRowDelegate(onItemClick, onContextMenuClick) {
override var layoutType = LayoutType.COVER
}

View File

@ -18,11 +18,12 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.drakeet.multitype.ItemViewBinder
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.ArtistOrIndex
import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.imageloader.ImageLoader
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util
@ -33,7 +34,6 @@ import org.moire.ultrasonic.util.Util
class ArtistRowBinder(
val onItemClick: (ArtistOrIndex) -> Unit,
val onContextMenuClick: (MenuItem, ArtistOrIndex) -> Boolean,
private val imageLoader: ImageLoader,
private val enableSections: Boolean = true
) : ItemViewBinder<ArtistOrIndex, ArtistRowBinder.ViewHolder>(),
KoinComponent,
@ -59,17 +59,21 @@ class ArtistRowBinder(
holder.coverArtId = item.coverArt
val imageLoaderProvider: ImageLoaderProvider by inject()
if (showArtistPicture()) {
holder.coverArt.visibility = View.VISIBLE
val key = FileUtil.getArtistArtKey(item.name, false)
imageLoader.loadImage(
view = holder.coverArt,
id = holder.coverArtId,
key = key,
large = false,
size = 0,
defaultResourceId = R.drawable.ic_contact_picture
)
imageLoaderProvider.executeOn {
it.loadImage(
view = holder.coverArt,
id = holder.coverArtId,
key = key,
large = false,
size = 0,
defaultResourceId = R.drawable.ic_contact_picture
)
}
} else {
holder.coverArt.visibility = View.GONE
}

View File

@ -52,10 +52,12 @@ class HeaderViewBinder(
val artworkSelection = random.nextInt(item.childCount)
imageLoaderProvider.getImageLoader().loadImage(
holder.coverArtView, item.entries[artworkSelection], false,
Util.getAlbumImageSize(context)
)
imageLoaderProvider.executeOn {
it.loadImage(
holder.coverArtView, item.entries[artworkSelection], false,
Util.getAlbumImageSize(context)
)
}
if (item.name != null) {
holder.titleView.isVisible = true

View File

@ -20,7 +20,9 @@ import org.moire.ultrasonic.di.mediaPlayerModule
import org.moire.ultrasonic.di.musicServiceModule
import org.moire.ultrasonic.log.FileLoggerTree
import org.moire.ultrasonic.log.TimberKoinLogger
import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Storage
import org.moire.ultrasonic.util.Util
import timber.log.Timber
import timber.log.Timber.DebugTree
@ -61,6 +63,9 @@ class UApp : MultiDexApplication() {
FileLoggerTree.plantToTimberForest()
Util.dumpSettingsToLog()
}
// Populate externalFilesDir early
FileUtil.cachedUltrasonicDirectory = FileUtil.ultrasonicDirectory
Storage.mediaRoot.value
isFirstRun = Util.isFirstRun()
}

View File

@ -11,14 +11,12 @@ import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.imageloader.ImageLoader
import org.moire.ultrasonic.log.TimberOkHttpLogger
import org.moire.ultrasonic.service.CachedMusicService
import org.moire.ultrasonic.service.MusicService
import org.moire.ultrasonic.service.OfflineMusicService
import org.moire.ultrasonic.service.RESTMusicService
import org.moire.ultrasonic.subsonic.DownloadHandler
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
import org.moire.ultrasonic.subsonic.ShareHandler
import org.moire.ultrasonic.util.Constants
@ -71,8 +69,6 @@ val musicServiceModule = module {
OfflineMusicService()
}
single { ImageLoader(androidContext(), get(), ImageLoaderProvider.config) }
single { DownloadHandler(get(), get()) }
single { NetworkAndStorageChecker(androidContext()) }
single { ShareHandler(androidContext()) }

View File

@ -204,14 +204,12 @@ class AlbumListFragment(
setLayoutType(layoutType)
val imageLoader = imageLoaderProvider.getImageLoader()
// Magic to switch between different view layouts:
// We register two delegates, one which layouts grid items and one which layouts row items
// Based on the current status of the ViewType, the right delegate is picked.
viewAdapter.register(Album::class).to(
AlbumRowDelegate(::onItemClick, ::onContextMenuItemSelected, imageLoader),
AlbumGridDelegate(::onItemClick, ::onContextMenuItemSelected, imageLoader)
AlbumRowDelegate(::onItemClick, ::onContextMenuItemSelected),
AlbumGridDelegate(::onItemClick, ::onContextMenuItemSelected)
).withKotlinClassLinker { _, _ ->
when (layoutType) {
LayoutType.COVER -> AlbumGridDelegate::class

View File

@ -53,8 +53,7 @@ class ArtistListFragment : EntryListFragment<ArtistOrIndex>() {
viewAdapter.register(
ArtistRowBinder(
{ entry -> onItemClick(entry) },
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) },
imageLoaderProvider.getImageLoader()
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) }
)
)
}

View File

@ -49,7 +49,7 @@ class NowPlayingFragment : Fragment() {
private var rxBusSubscription: Disposable? = null
private val mediaPlayerController: MediaPlayerController by inject()
private val imageLoader: ImageLoaderProvider by inject()
private val imageLoaderProvider: ImageLoaderProvider by inject()
override fun onCreate(savedInstanceState: Bundle?) {
applyTheme(this.context)
@ -97,12 +97,14 @@ class NowPlayingFragment : Fragment() {
val title = file.title
val artist = file.artist
imageLoader.getImageLoader().loadImage(
nowPlayingAlbumArtImage,
file,
false,
getNotificationImageSize(requireContext())
)
imageLoaderProvider.executeOn {
it.loadImage(
nowPlayingAlbumArtImage,
file,
false,
getNotificationImageSize(requireContext())
)
}
nowPlayingTrack!!.text = title
nowPlayingArtist!!.text = artist

View File

@ -1060,8 +1060,10 @@ class PlayerFragment :
downloadTrackTextView.text = trackFormat
downloadTotalDurationTextView.text = duration
imageLoaderProvider.getImageLoader()
.loadImage(albumArtImageView, currentSong, true, 0)
imageLoaderProvider.executeOn {
it.loadImage(albumArtImageView, currentSong, true, 0)
}
displaySongRating()
} else {
currentSong = null
@ -1072,8 +1074,9 @@ class PlayerFragment :
bitrateFormatTextView.text = null
downloadTrackTextView.text = null
downloadTotalDurationTextView.text = null
imageLoaderProvider.getImageLoader()
.loadImage(albumArtImageView, null, true, 0)
imageLoaderProvider.executeOn {
it.loadImage(albumArtImageView, null, true, 0)
}
}
}

View File

@ -103,7 +103,6 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
ArtistRowBinder(
onItemClick = ::onItemClick,
onContextMenuClick = ::onContextMenuItemSelected,
imageLoader = imageLoaderProvider.getImageLoader(),
enableSections = false
)
)
@ -111,8 +110,7 @@ class SearchFragment : MultiListFragment<Identifiable>(), KoinComponent {
viewAdapter.register(
AlbumRowDelegate(
onItemClick = ::onItemClick,
onContextMenuClick = ::onContextMenuItemSelected,
imageLoader = imageLoaderProvider.getImageLoader()
onContextMenuClick = ::onContextMenuItemSelected
)
)

View File

@ -146,8 +146,7 @@ open class TrackCollectionFragment(
viewAdapter.register(
AlbumRowDelegate(
{ entry -> onItemClick(entry) },
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) },
imageLoaderProvider.getImageLoader()
{ menuItem, entry -> onContextMenuItemSelected(menuItem, entry) }
)
)

View File

@ -1,91 +0,0 @@
package org.moire.ultrasonic.imageloader
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Build
import java.io.File
import org.moire.ultrasonic.domain.Track
import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.Util
import timber.log.Timber
@Suppress("UtilityClassWithPublicConstructor")
class BitmapUtils {
companion object {
fun getAvatarBitmapFromDisk(
username: String?,
size: Int
): Bitmap? {
if (username == null) return null
val avatarFile = FileUtil.getAvatarFile(username)
val bitmap: Bitmap? = null
if (avatarFile != null && avatarFile.exists()) {
return getBitmapFromDisk(avatarFile.path, size, bitmap)
}
return null
}
fun getAlbumArtBitmapFromDisk(
track: Track?,
size: Int
): Bitmap? {
if (track == null) return null
val albumArtFile = FileUtil.getAlbumArtFile(track)
val bitmap: Bitmap? = null
if (File(albumArtFile).exists()) {
return getBitmapFromDisk(albumArtFile, size, bitmap)
}
return null
}
fun getAlbumArtBitmapFromDisk(
filename: String,
size: Int?
): Bitmap? {
val albumArtFile = FileUtil.getAlbumArtFile(filename)
val bitmap: Bitmap? = null
if (File(albumArtFile).exists()) {
return getBitmapFromDisk(albumArtFile, size, bitmap)
}
return null
}
@Suppress("DEPRECATION")
private fun getBitmapFromDisk(
path: String,
size: Int?,
bitmap: Bitmap?
): Bitmap? {
var bitmap1 = bitmap
val opt = BitmapFactory.Options()
if (size != null && size > 0) {
// With this flag we only calculate the size first
opt.inJustDecodeBounds = true
// Decode the size
BitmapFactory.decodeFile(path, opt)
// Now set the remaining flags
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
opt.inDither = true
opt.inPreferQualityOverSpeed = true
}
opt.inSampleSize = Util.calculateInSampleSize(
opt,
size,
Util.getScaledHeight(opt.outHeight.toDouble(), opt.outWidth.toDouble(), size)
)
// Enable real decoding
opt.inJustDecodeBounds = false
}
try {
bitmap1 = BitmapFactory.decodeFile(path, opt)
} catch (expected: Exception) {
Timber.e(expected, "Exception in BitmapFactory.decodeFile()")
}
return bitmap1
}
}
}

View File

@ -1,14 +1,21 @@
package org.moire.ultrasonic.imageloader
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Build
import com.squareup.picasso.Picasso.LoadedFrom.DISK
import com.squareup.picasso.Picasso.LoadedFrom.NETWORK
import com.squareup.picasso.Request
import com.squareup.picasso.RequestHandler
import java.io.File
import java.io.IOException
import okio.source
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.FileUtil.SUFFIX_LARGE
import org.moire.ultrasonic.util.FileUtil.SUFFIX_SMALL
import org.moire.ultrasonic.util.Util
import timber.log.Timber
/**
* Loads cover arts from subsonic api.
@ -32,9 +39,9 @@ class CoverArtRequestHandler(private val client: SubsonicAPIClient) : RequestHan
// requesting the down-sized image from the network.
val key = request.stableKey!!
val largeKey = key.replace(SUFFIX_SMALL, SUFFIX_LARGE)
var cache = BitmapUtils.getAlbumArtBitmapFromDisk(largeKey, size?.toInt())
var cache = getAlbumArtBitmapFromDisk(largeKey, size?.toInt())
if (cache == null && key != largeKey) {
cache = BitmapUtils.getAlbumArtBitmapFromDisk(key, size?.toInt())
cache = getAlbumArtBitmapFromDisk(key, size?.toInt())
}
if (cache != null) {
return Result(cache, DISK)
@ -57,4 +64,54 @@ class CoverArtRequestHandler(private val client: SubsonicAPIClient) : RequestHan
// Throw an error if still not successful
throw IOException("${response.apiError}")
}
private fun getAlbumArtBitmapFromDisk(
filename: String,
size: Int?
): Bitmap? {
val albumArtFile = FileUtil.getAlbumArtFile(filename)
val bitmap: Bitmap? = null
if (File(albumArtFile).exists()) {
return getBitmapFromDisk(albumArtFile, size, bitmap)
}
return null
}
private fun getBitmapFromDisk(
path: String,
size: Int?,
bitmap: Bitmap?
): Bitmap? {
var bitmap1 = bitmap
val opt = BitmapFactory.Options()
if (size != null && size > 0) {
// With this flag we only calculate the size first
opt.inJustDecodeBounds = true
// Decode the size
BitmapFactory.decodeFile(path, opt)
// Now set the remaining flags
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
opt.inDither = true
opt.inPreferQualityOverSpeed = true
}
opt.inSampleSize = Util.calculateInSampleSize(
opt,
size,
Util.getScaledHeight(opt.outHeight.toDouble(), opt.outWidth.toDouble(), size)
)
// Enable real decoding
opt.inJustDecodeBounds = false
}
try {
bitmap1 = BitmapFactory.decodeFile(path, opt)
} catch (expected: Exception) {
Timber.e(expected, "Exception in BitmapFactory.decodeFile()")
}
return bitmap1
}
}

View File

@ -16,6 +16,8 @@ import java.io.InputStream
import java.io.OutputStream
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CountDownLatch
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.moire.ultrasonic.R
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.api.subsonic.throwOnFailure
@ -33,8 +35,8 @@ import timber.log.Timber
class ImageLoader(
context: Context,
apiClient: SubsonicAPIClient,
private val config: ImageLoaderConfig
) {
private val config: ImageLoaderConfig,
) : CoroutineScope by CoroutineScope(Dispatchers.IO) {
private val cacheInProgress: ConcurrentHashMap<String, CountDownLatch> = ConcurrentHashMap()
// Shortcut
@ -126,6 +128,7 @@ class ImageLoader(
defaultResourceId: Int = R.drawable.unknown_album
) {
val id = entry?.coverArt
// TODO getAlbumArtKey() accesses the disk from the UI thread..
val key = FileUtil.getAlbumArtKey(entry, large)
loadImage(view, id, key, large, size, defaultResourceId)

View File

@ -2,9 +2,16 @@ package org.moire.ultrasonic.log
import java.io.File
import java.io.FileWriter
import java.io.PrintWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.Util.safeClose
import timber.log.Timber
@ -14,33 +21,58 @@ import timber.log.Timber
* Subclass of the DebugTree so it inherits the Tag handling
*/
@Suppress("MagicNumber")
class FileLoggerTree : Timber.DebugTree() {
class FileLoggerTree : Timber.DebugTree(), CoroutineScope by CoroutineScope(Dispatchers.IO) {
private val dateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault())
@OptIn(ObsoleteCoroutinesApi::class)
private val logMessageActor = actor<LogMessage> {
for (msg in channel)
writeLogToFile(msg.file, msg.priority, msg.tag, msg.message, msg.t)
}
data class LogMessage(
val file: File?,
val priority: Int,
val tag: String?,
val message: String,
val t: Throwable?
)
/**
* Writes a log entry to file
*
* TODO: This seems to be writing in the main thread. Should be done in background...
* This methods sends the log entry to the coroutine actor, which then processes the entries
* in FIFO order on an IO-Thread
*/
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
var writer: FileWriter? = null
callNum++
try {
launch {
getNextLogFile()
writer = FileWriter(file, true)
val exceptionString = t?.toString() ?: ""
val time: String = dateFormat.format(Date())
synchronized(file!!) {
writer.write(
"$time: ${logLevelToString(priority)} $tag $message $exceptionString\n"
)
writer.flush()
logMessageActor.send(
LogMessage(file, priority, tag, message, t)
)
}
}
private suspend fun writeLogToFile(
file: File?,
priority: Int,
tag: String?,
message: String,
t: Throwable?
) {
val time: String = dateFormat.format(Date())
val exceptionString = t?.toString() ?: ""
withContext(Dispatchers.IO) {
var pw: PrintWriter? = null
try {
pw = PrintWriter(FileWriter(file, true))
pw.println("$time: ${logLevelToString(priority)} $tag $message $exceptionString\n")
t?.printStackTrace(pw)
} catch (all: Throwable) {
// Using base class DebugTree here, we don't want to try to log this into file
super.log(6, TAG, String.format("Failed to write log to %s", file), all)
} finally {
pw?.safeClose()
}
} catch (all: Throwable) {
// Using base class DebugTree here, we don't want to try to log this into file
super.log(6, TAG, String.format("Failed to write log to %s", file), all)
} finally {
writer.safeClose()
}
}

View File

@ -19,13 +19,13 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.domain.Track
import org.moire.ultrasonic.imageloader.ImageLoader
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.util.FileUtil
import timber.log.Timber
class AlbumArtContentProvider : ContentProvider(), KoinComponent {
private val imageLoader: ImageLoader by inject()
private val imageLoaderProvider: ImageLoaderProvider by inject()
companion object {
fun mapArtworkToContentProviderUri(track: Track?): Uri? {
@ -56,7 +56,9 @@ class AlbumArtContentProvider : ContentProvider(), KoinComponent {
val albumArtFile = FileUtil.getAlbumArtFile(parts[1])
Timber.d("AlbumArtContentProvider openFile id: %s; file: %s", parts[0], albumArtFile)
imageLoader.cacheCoverArt(parts[0], albumArtFile)
imageLoaderProvider.executeOn {
it.cacheCoverArt(parts[0], albumArtFile)
}
val file = File(albumArtFile)
if (!file.exists()) return null

View File

@ -23,7 +23,6 @@ import android.widget.RemoteViews
import org.moire.ultrasonic.R
import org.moire.ultrasonic.activity.NavigationActivity
import org.moire.ultrasonic.domain.Track
import org.moire.ultrasonic.imageloader.BitmapUtils
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver
import org.moire.ultrasonic.util.Constants
import timber.log.Timber
@ -200,17 +199,8 @@ open class UltrasonicAppWidgetProvider : AppWidgetProvider() {
}
// Set the cover art
try {
val bitmap =
if (currentSong == null) null else BitmapUtils.getAlbumArtBitmapFromDisk(
currentSong,
240
)
if (bitmap == null) {
// Set default cover art
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album)
} else {
views.setImageViewBitmap(R.id.appwidget_coverart, bitmap)
}
val uri = AlbumArtContentProvider.mapArtworkToContentProviderUri(currentSong)
views.setImageViewUri(R.id.appwidget_coverart, uri)
} catch (all: Exception) {
Timber.e(all, "Failed to load cover art")
views.setImageViewResource(R.id.appwidget_coverart, R.drawable.unknown_album)

View File

@ -257,12 +257,12 @@ class DownloadTask(
offlineDB.trackDao().insert(this)
// Download the largest size that we can display in the UI
val imageLoader = imageLoaderProvider.getImageLoader()
imageLoader.cacheCoverArt(this)
// Cache small copies of the Artist picture
directArtist?.let { imageLoader.cacheArtistPicture(it) }
compilationArtist?.let { imageLoader.cacheArtistPicture(it) }
imageLoaderProvider.executeOn { imageLoader ->
imageLoader.cacheCoverArt(this)
// Cache small copies of the Artist picture
directArtist?.let { imageLoader.cacheArtistPicture(it) }
compilationArtist?.let { imageLoader.cacheArtistPicture(it) }
}
}
private fun cacheArtist(

View File

@ -5,6 +5,7 @@ import androidx.core.content.res.ResourcesCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.qualifier.named
@ -14,11 +15,13 @@ import org.moire.ultrasonic.imageloader.ImageLoader
import org.moire.ultrasonic.imageloader.ImageLoaderConfig
import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.Util
import timber.log.Timber
/**
* Handles the lifetime of the Image Loader
*/
class ImageLoaderProvider(val context: Context) :
class
ImageLoaderProvider(val context: Context) :
KoinComponent,
CoroutineScope by CoroutineScope(Dispatchers.IO) {
private var imageLoader: ImageLoader? = null
@ -29,22 +32,39 @@ class ImageLoaderProvider(val context: Context) :
imageLoader = null
}
init {
Timber.e("Prepping Loader")
// Populate the ImageLoader async & early
launch {
getImageLoader()
}
}
@Synchronized
fun getImageLoader(): ImageLoader {
// We need to generate a new ImageLoader if the server has changed...
val currentID = get<String>(named("ServerID"))
if (imageLoader == null || currentID != serverID) {
imageLoader = get()
imageLoader = ImageLoader(UApp.applicationContext(), get(), config)
serverID = currentID
}
launch {
FileUtil.ensureAlbumArtDirectory()
launch {
FileUtil.ensureAlbumArtDirectory()
}
}
return imageLoader!!
}
fun executeOn(cb: (iL: ImageLoader) -> Unit) {
launch {
val iL = getImageLoader()
withContext(Dispatchers.Main) {
cb(iL)
}
}
}
companion object {
val config by lazy {
var defaultSize = 0

View File

@ -249,17 +249,24 @@ object FileUtil {
return dir
}
var cachedUltrasonicDirectory: File? = null
// After Android M, the location of the files must be queried differently.
// GetExternalFilesDir will always return a directory which Ultrasonic
// can access without any extra privileges.
@JvmStatic
val ultrasonicDirectory: File
get() {
// Return cached if possible
if (cachedUltrasonicDirectory != null) return cachedUltrasonicDirectory!!
@Suppress("DEPRECATION")
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) File(
cachedUltrasonicDirectory = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) File(
Environment.getExternalStorageDirectory(),
"Android/data/org.moire.ultrasonic"
) else UApp.applicationContext().getExternalFilesDir(null)!!
return cachedUltrasonicDirectory!!
}
@JvmStatic