Move ImageLoader module into main module.

This commit is contained in:
tzugen 2021-06-07 00:22:29 +02:00
parent f8efb6d592
commit 9161f9dc99
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
25 changed files with 232 additions and 207 deletions

View File

@ -1,29 +0,0 @@
apply from: bootstrap.androidModule
android {
buildFeatures {
buildConfig = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
api project(':core:domain')
api project(':core:subsonic-api')
api(other.picasso) {
exclude group: "com.android.support"
}
testImplementation testing.kotlinJunit
testImplementation testing.mockito
testImplementation testing.mockitoInline
testImplementation testing.mockitoKotlin
testImplementation testing.kluent
testImplementation testing.robolectric
}

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.moire.ultrasonic.subsonic.loader.image">
</manifest>

View File

@ -1,5 +1,4 @@
include ':core:domain' include ':core:domain'
include ':core:subsonic-api' include ':core:subsonic-api'
include ':core:subsonic-api-image-loader'
include ':core:cache' include ':core:cache'
include ':ultrasonic' include ':ultrasonic'

View File

@ -70,9 +70,12 @@ tasks.withType(Test) {
dependencies { dependencies {
implementation project(':core:domain') implementation project(':core:domain')
implementation project(':core:subsonic-api') implementation project(':core:subsonic-api')
implementation project(':core:subsonic-api-image-loader')
implementation project(':core:cache') implementation project(':core:cache')
api(other.picasso) {
exclude group: "com.android.support"
}
implementation androidSupport.core implementation androidSupport.core
implementation androidSupport.support implementation androidSupport.support
implementation androidSupport.design implementation androidSupport.design
@ -103,8 +106,12 @@ dependencies {
testImplementation testing.junit testImplementation testing.junit
testRuntimeOnly testing.junitVintage testRuntimeOnly testing.junitVintage
testImplementation testing.kotlinJunit testImplementation testing.kotlinJunit
testImplementation testing.mockitoKotlin
testImplementation testing.kluent testImplementation testing.kluent
testImplementation testing.mockito
testImplementation testing.mockitoInline
testImplementation testing.mockitoKotlin
testImplementation testing.robolectric
implementation other.dexter implementation other.dexter
implementation other.timber implementation other.timber
} }

View File

@ -108,7 +108,7 @@ public class NowPlayingFragment extends Fragment {
String title = song.getTitle(); String title = song.getTitle();
String artist = song.getArtist(); String artist = song.getArtist();
imageLoader.getValue().getImageLoader().loadImage(nowPlayingAlbumArtImage, song, false, Util.getNotificationImageSize(getContext()), false, true); imageLoader.getValue().getImageLoader().loadImage(nowPlayingAlbumArtImage, song, false, Util.getNotificationImageSize(getContext()));
nowPlayingTrack.setText(title); nowPlayingTrack.setText(title);
nowPlayingArtist.setText(artist); nowPlayingArtist.setText(artist);

View File

@ -1306,7 +1306,7 @@ public class PlayerFragment extends Fragment implements GestureDetector.OnGestur
artistTextView.setText(currentSong.getArtist()); artistTextView.setText(currentSong.getArtist());
downloadTrackTextView.setText(trackFormat); downloadTrackTextView.setText(trackFormat);
downloadTotalDurationTextView.setText(duration); downloadTotalDurationTextView.setText(duration);
imageLoaderProvider.getValue().getImageLoader().loadImage(albumArtImageView, currentSong, true, 0, false, true); imageLoaderProvider.getValue().getImageLoader().loadImage(albumArtImageView, currentSong, true, 0);
displaySongRating(); displaySongRating();
} }
@ -1318,7 +1318,7 @@ public class PlayerFragment extends Fragment implements GestureDetector.OnGestur
artistTextView.setText(null); artistTextView.setText(null);
downloadTrackTextView.setText(null); downloadTrackTextView.setText(null);
downloadTotalDurationTextView.setText(null); downloadTotalDurationTextView.setText(null);
imageLoaderProvider.getValue().getImageLoader().loadImage(albumArtImageView, null, true, 0, false, true); imageLoaderProvider.getValue().getImageLoader().loadImage(albumArtImageView, null, true, 0);
} }
} }

View File

@ -28,7 +28,7 @@ import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicService;
import org.moire.ultrasonic.service.MusicServiceFactory; import org.moire.ultrasonic.service.MusicServiceFactory;
import org.moire.ultrasonic.util.ImageLoader; import org.moire.ultrasonic.imageloader.ImageLoader;
import org.moire.ultrasonic.util.Util; import org.moire.ultrasonic.util.Util;
/** /**
@ -109,7 +109,7 @@ public class AlbumView extends UpdateView
public void setAlbum(final MusicDirectory.Entry album) public void setAlbum(final MusicDirectory.Entry album)
{ {
viewHolder.cover_art.setTag(album); viewHolder.cover_art.setTag(album);
imageLoader.loadImage(viewHolder.cover_art, album, false, 0, false, true); imageLoader.loadImage(viewHolder.cover_art, album, false, 0);
this.entry = album; this.entry = album;
String title = album.getTitle(); String title = album.getTitle();

View File

@ -1,6 +1,7 @@
package org.moire.ultrasonic.view; package org.moire.ultrasonic.view;
import android.content.Context; import android.content.Context;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.text.util.Linkify; import android.text.util.Linkify;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -12,8 +13,8 @@ import android.widget.TextView;
import org.moire.ultrasonic.R; import org.moire.ultrasonic.R;
import org.moire.ultrasonic.data.ActiveServerProvider; import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.ChatMessage; import org.moire.ultrasonic.domain.ChatMessage;
import org.moire.ultrasonic.imageloader.ImageLoader;
import org.moire.ultrasonic.subsonic.ImageLoaderProvider; import org.moire.ultrasonic.subsonic.ImageLoaderProvider;
import org.moire.ultrasonic.util.ImageLoader;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.Date; import java.util.Date;
@ -33,7 +34,7 @@ public class ChatAdapter extends ArrayAdapter<ChatMessage>
private static final Pattern phoneMatcher = Pattern.compile(phoneRegex); private static final Pattern phoneMatcher = Pattern.compile(phoneRegex);
private final Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class); private final Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
private final Lazy<ImageLoaderProvider> imageLoader = inject(ImageLoaderProvider.class); private final Lazy<ImageLoaderProvider> imageLoaderProvider = inject(ImageLoaderProvider.class);
public ChatAdapter(Context context, List<ChatMessage> messages) public ChatAdapter(Context context, List<ChatMessage> messages)
{ {
@ -95,11 +96,11 @@ public class ChatAdapter extends ArrayAdapter<ChatMessage>
DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(context); DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(context);
String messageTimeFormatted = String.format("[%s]", timeFormat.format(messageTime)); String messageTimeFormatted = String.format("[%s]", timeFormat.format(messageTime));
ImageLoader imageLoaderInstance = imageLoader.getValue().getImageLoader(); ImageLoader imageLoader = imageLoaderProvider.getValue().getImageLoader();
if (imageLoaderInstance != null) if (holder.avatar != null && !TextUtils.isEmpty(messageUser))
{ {
imageLoaderInstance.loadAvatarImage(holder.avatar, messageUser, false, holder.avatar.getWidth(), false, true); imageLoader.loadAvatarImage(holder.avatar, messageUser);
} }
holder.username.setText(messageUser); holder.username.setText(messageUser);

View File

@ -28,7 +28,7 @@ import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.domain.MusicDirectory.Entry;
import org.moire.ultrasonic.util.ImageLoader; import org.moire.ultrasonic.imageloader.ImageLoader;
import java.util.List; import java.util.List;

View File

@ -22,7 +22,7 @@ import org.moire.ultrasonic.subsonic.DownloadHandler
import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
import org.moire.ultrasonic.subsonic.ShareHandler import org.moire.ultrasonic.subsonic.ShareHandler
import org.moire.ultrasonic.subsonic.VideoPlayer import org.moire.ultrasonic.subsonic.VideoPlayer
import org.moire.ultrasonic.subsonic.loader.image.SubsonicImageLoader import org.moire.ultrasonic.imageloader.SubsonicImageLoader
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
/** /**
@ -77,8 +77,6 @@ val musicServiceModule = module {
OfflineMusicService() OfflineMusicService()
} }
single { SubsonicImageLoader(androidContext(), get()) }
single { DownloadHandler(get(), get()) } single { DownloadHandler(get(), get()) }
single { NetworkAndStorageChecker(androidContext()) } single { NetworkAndStorageChecker(androidContext()) }
single { VideoPlayer() } single { VideoPlayer() }

View File

@ -15,7 +15,7 @@ import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.util.ImageLoader import org.moire.ultrasonic.imageloader.ImageLoader
/** /**
* Creates a Row in a RecyclerView which contains the details of an Album * Creates a Row in a RecyclerView which contains the details of an Album
@ -57,7 +57,7 @@ class AlbumRowAdapter(
imageLoader.loadImage( imageLoader.loadImage(
holder.coverArt, holder.coverArt,
MusicDirectory.Entry("-1").apply { coverArt = holder.coverArtId }, MusicDirectory.Entry("-1").apply { coverArt = holder.coverArtId },
false, 0, false, true, R.drawable.unknown_album false, 0, R.drawable.unknown_album
) )
} }
} }

View File

@ -15,7 +15,7 @@ import java.text.Collator
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.util.ImageLoader import org.moire.ultrasonic.imageloader.ImageLoader
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
/** /**
@ -62,7 +62,7 @@ class ArtistRowAdapter(
imageLoader.loadImage( imageLoader.loadImage(
holder.coverArt, holder.coverArt,
MusicDirectory.Entry("-1").apply { coverArt = holder.coverArtId }, MusicDirectory.Entry("-1").apply { coverArt = holder.coverArtId },
false, 0, false, true, R.drawable.ic_contact_picture false, 0, R.drawable.ic_contact_picture
) )
} else { } else {
holder.coverArt.visibility = View.GONE holder.coverArt.visibility = View.GONE

View File

@ -763,7 +763,7 @@ class TrackCollectionFragment : Fragment() {
val artworkSelection = random.nextInt(entries.size) val artworkSelection = random.nextInt(entries.size)
imageLoaderProvider.getImageLoader().loadImage( imageLoaderProvider.getImageLoader().loadImage(
coverArtView, entries[artworkSelection], false, coverArtView, entries[artworkSelection], false,
Util.getAlbumImageSize(context), false, true Util.getAlbumImageSize(context)
) )
val albumHeader = AlbumHeader.processEntries(context, entries) val albumHeader = AlbumHeader.processEntries(context, entries)

View File

@ -1,4 +1,4 @@
package org.moire.ultrasonic.subsonic.loader.image package org.moire.ultrasonic.imageloader
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import com.squareup.picasso.Request import com.squareup.picasso.Request
@ -15,9 +15,7 @@ class AvatarRequestHandler(
) : RequestHandler() { ) : RequestHandler() {
override fun canHandleRequest(data: Request): Boolean { override fun canHandleRequest(data: Request): Boolean {
return with(data.uri) { return with(data.uri) {
scheme == SCHEME && scheme == SCHEME && path == "/$AVATAR_PATH"
authority == AUTHORITY &&
path == "/$AVATAR_PATH"
} }
} }

View File

@ -1,4 +1,4 @@
package org.moire.ultrasonic.subsonic.loader.image package org.moire.ultrasonic.imageloader
import com.squareup.picasso.Picasso.LoadedFrom.NETWORK import com.squareup.picasso.Picasso.LoadedFrom.NETWORK
import com.squareup.picasso.Request import com.squareup.picasso.Request
@ -14,7 +14,6 @@ class CoverArtRequestHandler(private val apiClient: SubsonicAPIClient) : Request
override fun canHandleRequest(data: Request): Boolean { override fun canHandleRequest(data: Request): Boolean {
return with(data.uri) { return with(data.uri) {
scheme == SCHEME && scheme == SCHEME &&
authority == AUTHORITY &&
path == "/$COVER_ART_PATH" path == "/$COVER_ART_PATH"
} }
} }

View File

@ -0,0 +1,157 @@
package org.moire.ultrasonic.imageloader
import android.content.Context
import android.view.View
import android.widget.ImageView
import com.squareup.picasso.Picasso
import com.squareup.picasso.RequestCreator
import org.moire.ultrasonic.BuildConfig
import org.moire.ultrasonic.R
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.domain.MusicDirectory
import java.io.File
/**
* Our new image loader which uses Picasso as a backend.
*/
class ImageLoader(
context: Context,
apiClient: SubsonicAPIClient,
private val config: ImageLoaderConfig
) {
private val picasso = Picasso.Builder(context)
.addRequestHandler(CoverArtRequestHandler(apiClient))
.addRequestHandler(AvatarRequestHandler(apiClient))
.build().apply {
setIndicatorsEnabled(BuildConfig.DEBUG)
Picasso.setSingletonInstance(this)
}
private fun load(request: ImageRequest) = when (request) {
is ImageRequest.CoverArt -> loadCoverArt(request)
is ImageRequest.Avatar -> loadAvatar(request)
}
private fun loadCoverArt(request: ImageRequest.CoverArt) {
picasso.load(createLoadCoverArtRequest(request.entityId, request.size.toLong()))
.addPlaceholder(request)
.addError(request)
.stableKey("${request.entityId}-${request.size}")
.into(request.imageView)
}
private fun loadAvatar(request: ImageRequest.Avatar) {
picasso.load(createLoadAvatarRequest(request.username))
.addPlaceholder(request)
.addError(request)
.stableKey(request.username)
.into(request.imageView)
}
private fun RequestCreator.addPlaceholder(request: ImageRequest): RequestCreator {
if (request.placeHolderDrawableRes != null) {
placeholder(request.placeHolderDrawableRes)
}
return this
}
private fun RequestCreator.addError(request: ImageRequest): RequestCreator {
if (request.errorDrawableRes != null) {
error(request.errorDrawableRes)
}
return this
}
/**
* Load the cover of a given entry into an ImageView
*/
@JvmOverloads
fun loadImage(
view: View?,
entry: MusicDirectory.Entry?,
large: Boolean,
size: Int,
defaultResourceId: Int = R.drawable.unknown_album
) {
val id = entry?.coverArt
val requestedSize = resolveSize(size, large)
if (id != null && id.isNotEmpty() && view is ImageView) {
val request = ImageRequest.CoverArt(
id, view, requestedSize,
placeHolderDrawableRes = defaultResourceId,
errorDrawableRes = defaultResourceId
)
load(request)
}
}
/**
* Load the avatar of a given user into an ImageView
*/
fun loadAvatarImage(
view: ImageView,
username: String
) {
if (username.isNotEmpty()) {
val request = ImageRequest.Avatar(
username, view,
placeHolderDrawableRes = R.drawable.ic_contact_picture,
errorDrawableRes = R.drawable.ic_contact_picture
)
load(request)
}
}
private fun resolveSize(requested: Int, large: Boolean): Int {
if (requested <= 0) {
return if (large) config.largeSize else config.defaultSize
} else {
return requested
}
}
}
/**
* Data classes to hold all the info we need later on to process the request
*/
sealed class ImageRequest(
val placeHolderDrawableRes: Int? = null,
val errorDrawableRes: Int? = null,
val imageView: ImageView
) {
class CoverArt(
val entityId: String,
imageView: ImageView,
val size: Int,
placeHolderDrawableRes: Int? = null,
errorDrawableRes: Int? = null,
) : ImageRequest(
placeHolderDrawableRes,
errorDrawableRes,
imageView
)
class Avatar(
val username: String,
imageView: ImageView,
placeHolderDrawableRes: Int? = null,
errorDrawableRes: Int? = null
) : ImageRequest(
placeHolderDrawableRes,
errorDrawableRes,
imageView
)
}
/**
* Used to configure an instance of the ImageLoader
*/
data class ImageLoaderConfig (
val largeSize: Int = 0,
val defaultSize: Int = 0,
val cacheFolder: File?
)

View File

@ -1,9 +1,8 @@
package org.moire.ultrasonic.subsonic.loader.image package org.moire.ultrasonic.imageloader
import android.net.Uri import android.net.Uri
internal const val SCHEME = "subsonic_api" internal const val SCHEME = "subsonic_api"
internal const val AUTHORITY = BuildConfig.LIBRARY_PACKAGE_NAME
internal const val COVER_ART_PATH = "cover_art" internal const val COVER_ART_PATH = "cover_art"
internal const val AVATAR_PATH = "avatar" internal const val AVATAR_PATH = "avatar"
internal const val QUERY_ID = "id" internal const val QUERY_ID = "id"
@ -17,7 +16,6 @@ internal const val QUERY_USERNAME = "username"
internal fun createLoadCoverArtRequest(entityId: String, size: Long? = 0): Uri = internal fun createLoadCoverArtRequest(entityId: String, size: Long? = 0): Uri =
Uri.Builder() Uri.Builder()
.scheme(SCHEME) .scheme(SCHEME)
.authority(AUTHORITY)
.appendPath(COVER_ART_PATH) .appendPath(COVER_ART_PATH)
.appendQueryParameter(QUERY_ID, entityId) .appendQueryParameter(QUERY_ID, entityId)
.appendQueryParameter(SIZE, size.toString()) .appendQueryParameter(SIZE, size.toString())
@ -26,7 +24,6 @@ internal fun createLoadCoverArtRequest(entityId: String, size: Long? = 0): Uri =
internal fun createLoadAvatarRequest(username: String): Uri = internal fun createLoadAvatarRequest(username: String): Uri =
Uri.Builder() Uri.Builder()
.scheme(SCHEME) .scheme(SCHEME)
.authority(AUTHORITY)
.appendPath(AVATAR_PATH) .appendPath(AVATAR_PATH)
.appendQueryParameter(QUERY_USERNAME, username) .appendQueryParameter(QUERY_USERNAME, username)
.build() .build()

View File

@ -1,12 +1,11 @@
package org.moire.ultrasonic.subsonic.loader.image package org.moire.ultrasonic.imageloader
import android.content.Context import android.content.Context
import android.widget.ImageView
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import com.squareup.picasso.RequestCreator import com.squareup.picasso.RequestCreator
import org.moire.ultrasonic.BuildConfig
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
// TODO: Implement OkHTTP disk caching
class SubsonicImageLoader( class SubsonicImageLoader(
context: Context, context: Context,
apiClient: SubsonicAPIClient apiClient: SubsonicAPIClient
@ -56,32 +55,3 @@ class SubsonicImageLoader(
return this return this
} }
} }
sealed class ImageRequest(
val placeHolderDrawableRes: Int? = null,
val errorDrawableRes: Int? = null,
val imageView: ImageView
) {
class CoverArt(
val entityId: String,
imageView: ImageView,
val size: Int,
placeHolderDrawableRes: Int? = null,
errorDrawableRes: Int? = null,
) : ImageRequest(
placeHolderDrawableRes,
errorDrawableRes,
imageView
)
class Avatar(
val username: String,
imageView: ImageView,
placeHolderDrawableRes: Int? = null,
errorDrawableRes: Int? = null
) : ImageRequest(
placeHolderDrawableRes,
errorDrawableRes,
imageView
)
}

View File

@ -1,9 +1,15 @@
package org.moire.ultrasonic.subsonic package org.moire.ultrasonic.subsonic
import android.content.Context import android.content.Context
import androidx.core.content.res.ResourcesCompat
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
import org.moire.ultrasonic.util.ImageLoader import org.moire.ultrasonic.R
import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.imageloader.ImageLoader
import org.moire.ultrasonic.imageloader.ImageLoaderConfig
import org.moire.ultrasonic.util.Util
/** /**
* Handles the lifetime of the Image Loader * Handles the lifetime of the Image Loader
@ -11,6 +17,26 @@ import org.moire.ultrasonic.util.ImageLoader
class ImageLoaderProvider(val context: Context) : KoinComponent { class ImageLoaderProvider(val context: Context) : KoinComponent {
private var imageLoader: ImageLoader? = null private var imageLoader: ImageLoader? = null
private val config by lazy {
var defaultSize = 0
val fallbackImage = ResourcesCompat.getDrawable(
UApp.applicationContext().resources, R.drawable.unknown_album, null
)
// Determine the density-dependent image sizes by taking the fallback album
// image and querying its size.
if (fallbackImage != null) {
defaultSize = fallbackImage.intrinsicHeight
}
ImageLoaderConfig(
Util.getMaxDisplayMetric(),
defaultSize,
FileUtil.getAlbumArtDirectory()
)
}
@Synchronized @Synchronized
fun clearImageLoader() { fun clearImageLoader() {
imageLoader = null imageLoader = null
@ -19,7 +45,7 @@ class ImageLoaderProvider(val context: Context) : KoinComponent {
@Synchronized @Synchronized
fun getImageLoader(): ImageLoader { fun getImageLoader(): ImageLoader {
if (imageLoader == null) { if (imageLoader == null) {
imageLoader = SubsonicImageLoaderProxy(get()) imageLoader = ImageLoader(get(), get(), config)
} }
return imageLoader!! return imageLoader!!
} }

View File

@ -1,96 +0,0 @@
package org.moire.ultrasonic.subsonic
import android.view.View
import android.widget.ImageView
import androidx.core.content.res.ResourcesCompat
import org.moire.ultrasonic.R
import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.subsonic.loader.image.ImageRequest
import org.moire.ultrasonic.subsonic.loader.image.SubsonicImageLoader
import org.moire.ultrasonic.util.ImageLoader
import org.moire.ultrasonic.util.Util
/**
* Proxy between [SubsonicImageLoader] and the main App.
* Needed to calculate values like the maximum image size,
* which we can't outside the main package.
*/
class SubsonicImageLoaderProxy(
private val subsonicImageLoader: SubsonicImageLoader
) : ImageLoader {
private var imageSizeLarge = Util.getMaxDisplayMetric()
private var imageSizeDefault = 0
override fun loadImage(
view: View?,
entry: MusicDirectory.Entry?,
large: Boolean,
size: Int,
crossFade: Boolean,
highQuality: Boolean
) {
return loadImage(view, entry, large, size, crossFade, highQuality, -1)
}
override fun loadImage(
view: View?,
entry: MusicDirectory.Entry?,
large: Boolean,
size: Int,
crossFade: Boolean,
highQuality: Boolean,
defaultResourceId: Int
) {
val id = entry?.coverArt
var requestedSize = size
val unknownImageId =
if (defaultResourceId == -1) R.drawable.unknown_album
else defaultResourceId
if (requestedSize <= 0) {
requestedSize = if (large) imageSizeLarge else imageSizeDefault
}
if (id != null && id.isNotEmpty() && view is ImageView) {
val request = ImageRequest.CoverArt(
id, view, requestedSize,
placeHolderDrawableRes = unknownImageId,
errorDrawableRes = unknownImageId
)
subsonicImageLoader.load(request)
}
}
override fun loadAvatarImage(
view: View?,
username: String?,
large: Boolean,
size: Int,
crossFade: Boolean,
highQuality: Boolean
) {
if (username != null && username.isNotEmpty() && view is ImageView) {
val request = ImageRequest.Avatar(
username, view,
placeHolderDrawableRes = R.drawable.ic_contact_picture,
errorDrawableRes = R.drawable.ic_contact_picture
)
subsonicImageLoader.load(request)
}
}
init {
val default = ResourcesCompat.getDrawable(
UApp.applicationContext().resources, R.drawable.unknown_album, null
)
// Determine the density-dependent image sizes by taking the fallback album
// image and querying its size.
if (default != null) {
imageSizeDefault = default.intrinsicHeight
}
}
}

View File

@ -1,4 +1,4 @@
package org.moire.ultrasonic.subsonic.loader.image package org.moire.ultrasonic.imageloader
import android.net.Uri import android.net.Uri
import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.any
@ -21,7 +21,8 @@ import org.robolectric.annotation.Config
@Config(manifest = Config.NONE) @Config(manifest = Config.NONE)
class AvatarRequestHandlerTest { class AvatarRequestHandlerTest {
private val mockSubsonicApiClient = mock<SubsonicAPIClient>() private val mockSubsonicApiClient = mock<SubsonicAPIClient>()
private val handler = AvatarRequestHandler(mockSubsonicApiClient) private val handler =
AvatarRequestHandler(mockSubsonicApiClient)
@Test @Test
fun `Should accept only cover art request`() { fun `Should accept only cover art request`() {
@ -34,7 +35,6 @@ class AvatarRequestHandlerTest {
fun `Should not accept random request uri`() { fun `Should not accept random request uri`() {
val requestUri = Uri.Builder() val requestUri = Uri.Builder()
.scheme(SCHEME) .scheme(SCHEME)
.authority(AUTHORITY)
.appendPath("something") .appendPath("something")
.build() .build()
@ -63,7 +63,8 @@ class AvatarRequestHandlerTest {
whenever(mockSubsonicApiClient.getAvatar(any())) whenever(mockSubsonicApiClient.getAvatar(any()))
.thenReturn(streamResponse) .thenReturn(streamResponse)
val response = handler.load(createLoadAvatarRequest("some-username").buildRequest(), 0) val response = handler.load(
createLoadAvatarRequest("some-username").buildRequest(), 0)
response.loadedFrom `should be equal to` Picasso.LoadedFrom.NETWORK response.loadedFrom `should be equal to` Picasso.LoadedFrom.NETWORK
response.source `should not be` null response.source `should not be` null

View File

@ -1,4 +1,4 @@
package org.moire.ultrasonic.subsonic.loader.image package org.moire.ultrasonic.imageloader
import java.io.InputStream import java.io.InputStream
import okio.Okio import okio.Okio

View File

@ -1,4 +1,4 @@
package org.moire.ultrasonic.subsonic.loader.image package org.moire.ultrasonic.imageloader
import android.net.Uri import android.net.Uri
import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.any
@ -21,7 +21,8 @@ import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
class CoverArtRequestHandlerTest { class CoverArtRequestHandlerTest {
private val mockSubsonicApiClientMock = mock<SubsonicAPIClient>() private val mockSubsonicApiClientMock = mock<SubsonicAPIClient>()
private val handler = CoverArtRequestHandler(mockSubsonicApiClientMock) private val handler =
CoverArtRequestHandler(mockSubsonicApiClientMock)
@Test @Test
fun `Should accept only cover art request`() { fun `Should accept only cover art request`() {
@ -34,7 +35,6 @@ class CoverArtRequestHandlerTest {
fun `Should not accept random request uri`() { fun `Should not accept random request uri`() {
val requestUri = Uri.Builder() val requestUri = Uri.Builder()
.scheme(SCHEME) .scheme(SCHEME)
.authority(AUTHORITY)
.appendPath("random") .appendPath("random")
.build() .build()
@ -76,7 +76,8 @@ class CoverArtRequestHandlerTest {
whenever(mockSubsonicApiClientMock.getCoverArt(any(), anyOrNull())) whenever(mockSubsonicApiClientMock.getCoverArt(any(), anyOrNull()))
.thenReturn(streamResponse) .thenReturn(streamResponse)
val response = handler.load(createLoadCoverArtRequest("some").buildRequest(), 0) val response = handler.load(
createLoadCoverArtRequest("some").buildRequest(), 0)
response.loadedFrom `should be equal to` Picasso.LoadedFrom.NETWORK response.loadedFrom `should be equal to` Picasso.LoadedFrom.NETWORK
response.source `should not be` null response.source `should not be` null

View File

@ -1,4 +1,4 @@
package org.moire.ultrasonic.subsonic.loader.image package org.moire.ultrasonic.imageloader
import android.net.Uri import android.net.Uri
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
@ -13,7 +13,7 @@ class RequestCreatorTest {
val entityId = "299" val entityId = "299"
val size = 100L val size = 100L
val expectedUri = val expectedUri =
Uri.parse("$SCHEME://$AUTHORITY/$COVER_ART_PATH?$QUERY_ID=$entityId&$SIZE=$size") Uri.parse("$SCHEME:/$COVER_ART_PATH?$QUERY_ID=$entityId&$SIZE=$size")
createLoadCoverArtRequest(entityId, size).compareTo(expectedUri).shouldBeEqualTo(0) createLoadCoverArtRequest(entityId, size).compareTo(expectedUri).shouldBeEqualTo(0)
} }
@ -21,7 +21,7 @@ class RequestCreatorTest {
@Test @Test
fun `Should create valid avatar request`() { fun `Should create valid avatar request`() {
val username = "some-username" val username = "some-username"
val expectedUri = Uri.parse("$SCHEME://$AUTHORITY/$AVATAR_PATH?$QUERY_USERNAME=$username") val expectedUri = Uri.parse("$SCHEME:/$AVATAR_PATH?$QUERY_USERNAME=$username")
createLoadAvatarRequest(username).compareTo(expectedUri).shouldBeEqualTo(0) createLoadAvatarRequest(username).compareTo(expectedUri).shouldBeEqualTo(0)
} }

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB