From 9161f9dc9992ec854f3a3f81a473f64cd8e7001f Mon Sep 17 00:00:00 2001 From: tzugen Date: Mon, 7 Jun 2021 00:22:29 +0200 Subject: [PATCH] Move ImageLoader module into main module. --- core/subsonic-api-image-loader/build.gradle | 29 ---- .../src/main/AndroidManifest.xml | 4 - settings.gradle | 1 - ultrasonic/build.gradle | 11 +- .../fragment/NowPlayingFragment.java | 2 +- .../ultrasonic/fragment/PlayerFragment.java | 4 +- .../org/moire/ultrasonic/view/AlbumView.java | 4 +- .../moire/ultrasonic/view/ChatAdapter.java | 11 +- .../moire/ultrasonic/view/EntryAdapter.java | 2 +- .../moire/ultrasonic/di/MusicServiceModule.kt | 4 +- .../ultrasonic/fragment/AlbumRowAdapter.kt | 4 +- .../ultrasonic/fragment/ArtistRowAdapter.kt | 4 +- .../fragment/TrackCollectionFragment.kt | 2 +- .../imageloader}/AvatarRequestHandler.kt | 6 +- .../imageloader}/CoverArtRequestHandler.kt | 3 +- .../ultrasonic/imageloader/ImageLoader.kt | 157 ++++++++++++++++++ .../ultrasonic/imageloader}/RequestCreator.kt | 5 +- .../imageloader}/SubsonicImageLoader.kt | 34 +--- .../subsonic/ImageLoaderProvider.kt | 30 +++- .../subsonic/SubsonicImageLoaderProxy.kt | 96 ----------- .../imageloader}/AvatarRequestHandlerTest.kt | 9 +- .../imageloader}/CommonFunctions.kt | 2 +- .../CoverArtRequestHandlerTest.kt | 9 +- .../imageloader}/RequestCreatorTest.kt | 6 +- .../src/test}/resources/Big_Buck_Bunny.jpeg | Bin 25 files changed, 232 insertions(+), 207 deletions(-) delete mode 100644 core/subsonic-api-image-loader/build.gradle delete mode 100644 core/subsonic-api-image-loader/src/main/AndroidManifest.xml rename {core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image => ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader}/AvatarRequestHandler.kt (85%) rename {core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image => ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader}/CoverArtRequestHandler.kt (92%) create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt rename {core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image => ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader}/RequestCreator.kt (84%) rename {core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image => ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader}/SubsonicImageLoader.kt (68%) delete mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/SubsonicImageLoaderProxy.kt rename {core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image => ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader}/AvatarRequestHandlerTest.kt (89%) rename {core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image => ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader}/CommonFunctions.kt (81%) rename {core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image => ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader}/CoverArtRequestHandlerTest.kt (91%) rename {core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image => ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader}/RequestCreatorTest.kt (73%) rename {core/subsonic-api-image-loader/src/integrationTest => ultrasonic/src/test}/resources/Big_Buck_Bunny.jpeg (100%) diff --git a/core/subsonic-api-image-loader/build.gradle b/core/subsonic-api-image-loader/build.gradle deleted file mode 100644 index dc8324b7..00000000 --- a/core/subsonic-api-image-loader/build.gradle +++ /dev/null @@ -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 -} diff --git a/core/subsonic-api-image-loader/src/main/AndroidManifest.xml b/core/subsonic-api-image-loader/src/main/AndroidManifest.xml deleted file mode 100644 index b36252b8..00000000 --- a/core/subsonic-api-image-loader/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/settings.gradle b/settings.gradle index 140eafe6..a4a9a1e1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,4 @@ include ':core:domain' include ':core:subsonic-api' -include ':core:subsonic-api-image-loader' include ':core:cache' include ':ultrasonic' diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index 3d6a9f78..997ea5fc 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -70,9 +70,12 @@ tasks.withType(Test) { dependencies { implementation project(':core:domain') implementation project(':core:subsonic-api') - implementation project(':core:subsonic-api-image-loader') implementation project(':core:cache') + api(other.picasso) { + exclude group: "com.android.support" + } + implementation androidSupport.core implementation androidSupport.support implementation androidSupport.design @@ -103,8 +106,12 @@ dependencies { testImplementation testing.junit testRuntimeOnly testing.junitVintage testImplementation testing.kotlinJunit - testImplementation testing.mockitoKotlin testImplementation testing.kluent + testImplementation testing.mockito + testImplementation testing.mockitoInline + testImplementation testing.mockitoKotlin + testImplementation testing.robolectric + implementation other.dexter implementation other.timber } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/NowPlayingFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/NowPlayingFragment.java index 05306643..fd6684d4 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/NowPlayingFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/NowPlayingFragment.java @@ -108,7 +108,7 @@ public class NowPlayingFragment extends Fragment { String title = song.getTitle(); 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); nowPlayingArtist.setText(artist); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java index 4f662d60..9b056315 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java @@ -1306,7 +1306,7 @@ public class PlayerFragment extends Fragment implements GestureDetector.OnGestur artistTextView.setText(currentSong.getArtist()); downloadTrackTextView.setText(trackFormat); downloadTotalDurationTextView.setText(duration); - imageLoaderProvider.getValue().getImageLoader().loadImage(albumArtImageView, currentSong, true, 0, false, true); + imageLoaderProvider.getValue().getImageLoader().loadImage(albumArtImageView, currentSong, true, 0); displaySongRating(); } @@ -1318,7 +1318,7 @@ public class PlayerFragment extends Fragment implements GestureDetector.OnGestur artistTextView.setText(null); downloadTrackTextView.setText(null); downloadTotalDurationTextView.setText(null); - imageLoaderProvider.getValue().getImageLoader().loadImage(albumArtImageView, null, true, 0, false, true); + imageLoaderProvider.getValue().getImageLoader().loadImage(albumArtImageView, null, true, 0); } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/AlbumView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/AlbumView.java index 21ef70b5..68abe346 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/AlbumView.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/AlbumView.java @@ -28,7 +28,7 @@ import org.moire.ultrasonic.data.ActiveServerProvider; import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicServiceFactory; -import org.moire.ultrasonic.util.ImageLoader; +import org.moire.ultrasonic.imageloader.ImageLoader; import org.moire.ultrasonic.util.Util; /** @@ -109,7 +109,7 @@ public class AlbumView extends UpdateView public void setAlbum(final MusicDirectory.Entry 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; String title = album.getTitle(); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java index 82c18a8e..6a6ccf37 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ChatAdapter.java @@ -1,6 +1,7 @@ package org.moire.ultrasonic.view; import android.content.Context; +import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.util.Linkify; import android.view.LayoutInflater; @@ -12,8 +13,8 @@ import android.widget.TextView; import org.moire.ultrasonic.R; import org.moire.ultrasonic.data.ActiveServerProvider; import org.moire.ultrasonic.domain.ChatMessage; +import org.moire.ultrasonic.imageloader.ImageLoader; import org.moire.ultrasonic.subsonic.ImageLoaderProvider; -import org.moire.ultrasonic.util.ImageLoader; import java.text.DateFormat; import java.util.Date; @@ -33,7 +34,7 @@ public class ChatAdapter extends ArrayAdapter private static final Pattern phoneMatcher = Pattern.compile(phoneRegex); private final Lazy activeServerProvider = inject(ActiveServerProvider.class); - private final Lazy imageLoader = inject(ImageLoaderProvider.class); + private final Lazy imageLoaderProvider = inject(ImageLoaderProvider.class); public ChatAdapter(Context context, List messages) { @@ -95,11 +96,11 @@ public class ChatAdapter extends ArrayAdapter DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(context); 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); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java index bd05bff8..65488e8f 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/EntryAdapter.java @@ -28,7 +28,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import org.moire.ultrasonic.domain.MusicDirectory.Entry; -import org.moire.ultrasonic.util.ImageLoader; +import org.moire.ultrasonic.imageloader.ImageLoader; import java.util.List; diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index 285e91be..9e0e746c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -22,7 +22,7 @@ import org.moire.ultrasonic.subsonic.DownloadHandler import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker import org.moire.ultrasonic.subsonic.ShareHandler 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 /** @@ -77,8 +77,6 @@ val musicServiceModule = module { OfflineMusicService() } - single { SubsonicImageLoader(androidContext(), get()) } - single { DownloadHandler(get(), get()) } single { NetworkAndStorageChecker(androidContext()) } single { VideoPlayer() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt index cae48837..22934478 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/AlbumRowAdapter.kt @@ -15,7 +15,7 @@ import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import org.moire.ultrasonic.R 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 @@ -57,7 +57,7 @@ class AlbumRowAdapter( imageLoader.loadImage( holder.coverArt, MusicDirectory.Entry("-1").apply { coverArt = holder.coverArtId }, - false, 0, false, true, R.drawable.unknown_album + false, 0, R.drawable.unknown_album ) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt index 009b41e3..3c3e3757 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ArtistRowAdapter.kt @@ -15,7 +15,7 @@ import java.text.Collator import org.moire.ultrasonic.R import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.MusicDirectory -import org.moire.ultrasonic.util.ImageLoader +import org.moire.ultrasonic.imageloader.ImageLoader import org.moire.ultrasonic.util.Util /** @@ -62,7 +62,7 @@ class ArtistRowAdapter( imageLoader.loadImage( holder.coverArt, MusicDirectory.Entry("-1").apply { coverArt = holder.coverArtId }, - false, 0, false, true, R.drawable.ic_contact_picture + false, 0, R.drawable.ic_contact_picture ) } else { holder.coverArt.visibility = View.GONE diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt index 2d544ef1..c9d7affb 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -763,7 +763,7 @@ class TrackCollectionFragment : Fragment() { val artworkSelection = random.nextInt(entries.size) imageLoaderProvider.getImageLoader().loadImage( coverArtView, entries[artworkSelection], false, - Util.getAlbumImageSize(context), false, true + Util.getAlbumImageSize(context) ) val albumHeader = AlbumHeader.processEntries(context, entries) diff --git a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/AvatarRequestHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandler.kt similarity index 85% rename from core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/AvatarRequestHandler.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandler.kt index ab8ac70e..e009f58c 100644 --- a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/AvatarRequestHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandler.kt @@ -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.Request @@ -15,9 +15,7 @@ class AvatarRequestHandler( ) : RequestHandler() { override fun canHandleRequest(data: Request): Boolean { return with(data.uri) { - scheme == SCHEME && - authority == AUTHORITY && - path == "/$AVATAR_PATH" + scheme == SCHEME && path == "/$AVATAR_PATH" } } diff --git a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt similarity index 92% rename from core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandler.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt index dfeb5286..3c091088 100644 --- a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt @@ -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.Request @@ -14,7 +14,6 @@ class CoverArtRequestHandler(private val apiClient: SubsonicAPIClient) : Request override fun canHandleRequest(data: Request): Boolean { return with(data.uri) { scheme == SCHEME && - authority == AUTHORITY && path == "/$COVER_ART_PATH" } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt new file mode 100644 index 00000000..63d1c2bb --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt @@ -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? +) diff --git a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/RequestCreator.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/RequestCreator.kt similarity index 84% rename from core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/RequestCreator.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/RequestCreator.kt index 1e57fff9..37a11f5a 100644 --- a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/RequestCreator.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/RequestCreator.kt @@ -1,9 +1,8 @@ -package org.moire.ultrasonic.subsonic.loader.image +package org.moire.ultrasonic.imageloader import android.net.Uri 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 AVATAR_PATH = "avatar" 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 = Uri.Builder() .scheme(SCHEME) - .authority(AUTHORITY) .appendPath(COVER_ART_PATH) .appendQueryParameter(QUERY_ID, entityId) .appendQueryParameter(SIZE, size.toString()) @@ -26,7 +24,6 @@ internal fun createLoadCoverArtRequest(entityId: String, size: Long? = 0): Uri = internal fun createLoadAvatarRequest(username: String): Uri = Uri.Builder() .scheme(SCHEME) - .authority(AUTHORITY) .appendPath(AVATAR_PATH) .appendQueryParameter(QUERY_USERNAME, username) .build() diff --git a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/SubsonicImageLoader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/SubsonicImageLoader.kt similarity index 68% rename from core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/SubsonicImageLoader.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/SubsonicImageLoader.kt index fcb2eba7..712dd4df 100644 --- a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/SubsonicImageLoader.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/SubsonicImageLoader.kt @@ -1,12 +1,11 @@ -package org.moire.ultrasonic.subsonic.loader.image +package org.moire.ultrasonic.imageloader import android.content.Context -import android.widget.ImageView import com.squareup.picasso.Picasso import com.squareup.picasso.RequestCreator +import org.moire.ultrasonic.BuildConfig import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient -// TODO: Implement OkHTTP disk caching class SubsonicImageLoader( context: Context, apiClient: SubsonicAPIClient @@ -56,32 +55,3 @@ class SubsonicImageLoader( 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 - ) -} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt index e1907f66..c674f1a6 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt @@ -1,9 +1,15 @@ package org.moire.ultrasonic.subsonic import android.content.Context +import androidx.core.content.res.ResourcesCompat import org.koin.core.component.KoinComponent 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 @@ -11,6 +17,26 @@ import org.moire.ultrasonic.util.ImageLoader class ImageLoaderProvider(val context: Context) : KoinComponent { 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 fun clearImageLoader() { imageLoader = null @@ -19,7 +45,7 @@ class ImageLoaderProvider(val context: Context) : KoinComponent { @Synchronized fun getImageLoader(): ImageLoader { if (imageLoader == null) { - imageLoader = SubsonicImageLoaderProxy(get()) + imageLoader = ImageLoader(get(), get(), config) } return imageLoader!! } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/SubsonicImageLoaderProxy.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/SubsonicImageLoaderProxy.kt deleted file mode 100644 index 27f6ec0a..00000000 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/SubsonicImageLoaderProxy.kt +++ /dev/null @@ -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 - } - } -} diff --git a/core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image/AvatarRequestHandlerTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt similarity index 89% rename from core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image/AvatarRequestHandlerTest.kt rename to ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt index 1a039d41..214cdad8 100644 --- a/core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image/AvatarRequestHandlerTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/AvatarRequestHandlerTest.kt @@ -1,4 +1,4 @@ -package org.moire.ultrasonic.subsonic.loader.image +package org.moire.ultrasonic.imageloader import android.net.Uri import com.nhaarman.mockito_kotlin.any @@ -21,7 +21,8 @@ import org.robolectric.annotation.Config @Config(manifest = Config.NONE) class AvatarRequestHandlerTest { private val mockSubsonicApiClient = mock() - private val handler = AvatarRequestHandler(mockSubsonicApiClient) + private val handler = + AvatarRequestHandler(mockSubsonicApiClient) @Test fun `Should accept only cover art request`() { @@ -34,7 +35,6 @@ class AvatarRequestHandlerTest { fun `Should not accept random request uri`() { val requestUri = Uri.Builder() .scheme(SCHEME) - .authority(AUTHORITY) .appendPath("something") .build() @@ -63,7 +63,8 @@ class AvatarRequestHandlerTest { whenever(mockSubsonicApiClient.getAvatar(any())) .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.source `should not be` null diff --git a/core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image/CommonFunctions.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CommonFunctions.kt similarity index 81% rename from core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image/CommonFunctions.kt rename to ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CommonFunctions.kt index 30d38c60..3850290a 100644 --- a/core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image/CommonFunctions.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CommonFunctions.kt @@ -1,4 +1,4 @@ -package org.moire.ultrasonic.subsonic.loader.image +package org.moire.ultrasonic.imageloader import java.io.InputStream import okio.Okio diff --git a/core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandlerTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt similarity index 91% rename from core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandlerTest.kt rename to ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt index 4a43fbfe..5bd6c336 100644 --- a/core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandlerTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandlerTest.kt @@ -1,4 +1,4 @@ -package org.moire.ultrasonic.subsonic.loader.image +package org.moire.ultrasonic.imageloader import android.net.Uri import com.nhaarman.mockito_kotlin.any @@ -21,7 +21,8 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class CoverArtRequestHandlerTest { private val mockSubsonicApiClientMock = mock() - private val handler = CoverArtRequestHandler(mockSubsonicApiClientMock) + private val handler = + CoverArtRequestHandler(mockSubsonicApiClientMock) @Test fun `Should accept only cover art request`() { @@ -34,7 +35,6 @@ class CoverArtRequestHandlerTest { fun `Should not accept random request uri`() { val requestUri = Uri.Builder() .scheme(SCHEME) - .authority(AUTHORITY) .appendPath("random") .build() @@ -76,7 +76,8 @@ class CoverArtRequestHandlerTest { whenever(mockSubsonicApiClientMock.getCoverArt(any(), anyOrNull())) .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.source `should not be` null diff --git a/core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image/RequestCreatorTest.kt b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/RequestCreatorTest.kt similarity index 73% rename from core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image/RequestCreatorTest.kt rename to ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/RequestCreatorTest.kt index 89b39a4d..4e4bcf5c 100644 --- a/core/subsonic-api-image-loader/src/integrationTest/kotlin/org/moire/ultrasonic/subsonic/loader/image/RequestCreatorTest.kt +++ b/ultrasonic/src/test/kotlin/org/moire/ultrasonic/imageloader/RequestCreatorTest.kt @@ -1,4 +1,4 @@ -package org.moire.ultrasonic.subsonic.loader.image +package org.moire.ultrasonic.imageloader import android.net.Uri import org.amshove.kluent.shouldBeEqualTo @@ -13,7 +13,7 @@ class RequestCreatorTest { val entityId = "299" val size = 100L 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) } @@ -21,7 +21,7 @@ class RequestCreatorTest { @Test fun `Should create valid avatar request`() { 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) } diff --git a/core/subsonic-api-image-loader/src/integrationTest/resources/Big_Buck_Bunny.jpeg b/ultrasonic/src/test/resources/Big_Buck_Bunny.jpeg similarity index 100% rename from core/subsonic-api-image-loader/src/integrationTest/resources/Big_Buck_Bunny.jpeg rename to ultrasonic/src/test/resources/Big_Buck_Bunny.jpeg