diff --git a/core/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt b/core/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt
index 54cc77dc..36e2f74c 100644
--- a/core/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt
+++ b/core/cache/src/test/kotlin/org/moire/ultrasonic/cache/BaseStorageTest.kt
@@ -1,12 +1,12 @@
package org.moire.ultrasonic.cache
-import com.nhaarman.mockito_kotlin.mock
import com.twitter.serial.util.SerializationUtils
import java.io.File
import org.amshove.kluent.`it returns`
import org.junit.Before
import org.junit.Rule
import org.junit.rules.TemporaryFolder
+import org.mockito.kotlin.mock
internal const val INTERNAL_DATA_FOLDER = "data"
internal const val INTERNAL_CACHE_FOLDER = "cache"
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/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandler.kt b/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandler.kt
deleted file mode 100644
index 7e242479..00000000
--- a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/CoverArtRequestHandler.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.moire.ultrasonic.subsonic.loader.image
-
-import com.squareup.picasso.Picasso.LoadedFrom.NETWORK
-import com.squareup.picasso.Request
-import com.squareup.picasso.RequestHandler
-import java.io.IOException
-import okio.Okio
-import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
-
-/**
- * Loads cover arts from subsonic api.
- */
-class CoverArtRequestHandler(private val apiClient: SubsonicAPIClient) : RequestHandler() {
- override fun canHandleRequest(data: Request): Boolean {
- return with(data.uri) {
- scheme == SCHEME &&
- authority == AUTHORITY &&
- path == "/$COVER_ART_PATH"
- }
- }
-
- override fun load(request: Request, networkPolicy: Int): Result {
- val id = request.uri.getQueryParameter(QUERY_ID)
- ?: throw IllegalArgumentException("Nullable id")
-
- val response = apiClient.getCoverArt(id)
- if (response.hasError() || response.stream == null) {
- throw IOException("${response.apiError}")
- } else {
- return Result(Okio.source(response.stream!!), NETWORK)
- }
- }
-}
diff --git a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/RequestCreator.kt b/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/RequestCreator.kt
deleted file mode 100644
index 9cc6799a..00000000
--- a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/RequestCreator.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.moire.ultrasonic.subsonic.loader.image
-
-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"
-internal const val QUERY_USERNAME = "username"
-
-internal fun createLoadCoverArtRequest(entityId: String): Uri = Uri.Builder()
- .scheme(SCHEME)
- .authority(AUTHORITY)
- .appendPath(COVER_ART_PATH)
- .appendQueryParameter(QUERY_ID, entityId)
- .build()
-
-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/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/SubsonicImageLoader.kt
deleted file mode 100644
index 630bbc4a..00000000
--- a/core/subsonic-api-image-loader/src/main/kotlin/org/moire/ultrasonic/subsonic/loader/image/SubsonicImageLoader.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-package org.moire.ultrasonic.subsonic.loader.image
-
-import android.content.Context
-import android.widget.ImageView
-import com.squareup.picasso.Picasso
-import com.squareup.picasso.RequestCreator
-import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
-
-class SubsonicImageLoader(
- context: Context,
- apiClient: SubsonicAPIClient
-) {
- private val picasso = Picasso.Builder(context)
- .addRequestHandler(CoverArtRequestHandler(apiClient))
- .addRequestHandler(AvatarRequestHandler(apiClient))
- .build().apply { setIndicatorsEnabled(BuildConfig.DEBUG) }
-
- 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))
- .addPlaceholder(request)
- .addError(request)
- .into(request.imageView)
- }
-
- private fun loadAvatar(request: ImageRequest.Avatar) {
- picasso.load(createLoadAvatarRequest(request.username))
- .addPlaceholder(request)
- .addError(request)
- .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
- }
-}
-
-sealed class ImageRequest(
- val placeHolderDrawableRes: Int? = null,
- val errorDrawableRes: Int? = null,
- val imageView: ImageView
-) {
- class CoverArt(
- val entityId: String,
- imageView: ImageView,
- 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/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt
index fb6ce161..6484208e 100644
--- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt
+++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt
@@ -103,6 +103,7 @@ class SubsonicAPIClient(
val api: SubsonicAPIDefinition get() = wrappedApi
/**
+ * TODO: Remove this in favour of handling the stream response inside RESTService
* Convenient method to get cover art from api using item [id] and optional maximum [size].
*
* It detects the response `Content-Type` and tries to parse subsonic error if there is one.
@@ -114,6 +115,7 @@ class SubsonicAPIClient(
}
/**
+ * TODO: Remove this in favour of handling the stream response inside RESTService
* Convenient method to get media stream from api using item [id] and optional [maxBitrate].
*
* Optionally also you can provide [offset] that stream should start from.
@@ -128,6 +130,7 @@ class SubsonicAPIClient(
}
/**
+ * TODO: Remove this in favour of handling the stream response inside RESTService
* Convenient method to get user avatar using [username].
*
* It detects the response `Content-Type` and tries to parse subsonic error if there is one.
@@ -138,6 +141,7 @@ class SubsonicAPIClient(
api.getAvatar(username).execute()
}
+ // TODO: Move this to response checker
private inline fun handleStreamResponse(apiCall: () -> Response): StreamResponse {
val response = apiCall()
return if (response.isSuccessful) {
diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt
deleted file mode 100644
index b56dc097..00000000
--- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/di/SubsonicApiModule.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.moire.ultrasonic.api.subsonic.di
-
-import org.koin.dsl.module
-import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
-
-val subsonicApiModule = module {
- single { SubsonicAPIClient(get(), get()) }
-}
diff --git a/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapperTest.kt b/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapperTest.kt
index ec3d2aac..fbf8deb7 100644
--- a/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapperTest.kt
+++ b/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapperTest.kt
@@ -1,10 +1,10 @@
package org.moire.ultrasonic.api.subsonic
-import com.nhaarman.mockito_kotlin.mock
-import com.nhaarman.mockito_kotlin.never
-import com.nhaarman.mockito_kotlin.verify
import org.amshove.kluent.`should throw`
import org.junit.Test
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.mock
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_1_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_2_0
import org.moire.ultrasonic.api.subsonic.models.AlbumListType.BY_GENRE
diff --git a/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIVersionsDeserializerTest.kt b/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIVersionsDeserializerTest.kt
index 4c1c7643..9bc48db8 100644
--- a/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIVersionsDeserializerTest.kt
+++ b/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIVersionsDeserializerTest.kt
@@ -3,13 +3,13 @@ package org.moire.ultrasonic.api.subsonic
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
-import com.nhaarman.mockito_kotlin.doReturn
-import com.nhaarman.mockito_kotlin.mock
-import com.nhaarman.mockito_kotlin.whenever
import org.amshove.kluent.`should be`
import org.amshove.kluent.`should throw`
import org.junit.Before
import org.junit.Test
+import org.mockito.Mockito.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
/**
* Unit test for [SubsonicAPIVersions.SubsonicAPIVersionsDeserializer] class.
diff --git a/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/ProxyPasswordInterceptorTest.kt b/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/ProxyPasswordInterceptorTest.kt
index f5210b6f..f9fde4b4 100644
--- a/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/ProxyPasswordInterceptorTest.kt
+++ b/core/subsonic-api/src/test/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/ProxyPasswordInterceptorTest.kt
@@ -1,9 +1,9 @@
package org.moire.ultrasonic.api.subsonic.interceptors
-import com.nhaarman.mockito_kotlin.mock
-import com.nhaarman.mockito_kotlin.verify
import okhttp3.Interceptor.Chain
import org.junit.Test
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.mock
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_12_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_13_0
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions.V1_16_0
diff --git a/dependencies.gradle b/dependencies.gradle
index aa0f257b..af91ee1f 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -35,8 +35,8 @@ ext.versions = [
junit4 : "4.13.2",
junit5 : "5.7.1",
- mockito : "3.8.0",
- mockitoKotlin : "1.5.0",
+ mockito : "3.11.0",
+ mockitoKotlin : "3.2.0",
kluent : "1.64",
apacheCodecs : "1.15",
robolectric : "4.5.1",
@@ -97,7 +97,7 @@ ext.testing = [
junit : "junit:junit:$versions.junit4",
junitVintage : "org.junit.vintage:junit-vintage-engine:$versions.junit5",
kotlinJunit : "org.jetbrains.kotlin:kotlin-test-junit:$versions.kotlin",
- mockitoKotlin : "com.nhaarman:mockito-kotlin:$versions.mockitoKotlin",
+ mockitoKotlin : "org.mockito.kotlin:mockito-kotlin:$versions.mockitoKotlin",
mockito : "org.mockito:mockito-core:$versions.mockito",
mockitoInline : "org.mockito:mockito-inline:$versions.mockito",
kluent : "org.amshove.kluent:kluent:$versions.kluent",
diff --git a/detekt-config.yml b/detekt-config.yml
index d095b0b5..47707e69 100644
--- a/detekt-config.yml
+++ b/detekt-config.yml
@@ -30,6 +30,8 @@ performance:
exceptions:
active: true
+ TooGenericExceptionCaught:
+ allowedExceptionNameRegex: '_|(all|ignore|expected).*'
empty-blocks:
active: true
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/fragment/SettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java
index fcc81219..66da521d 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java
@@ -6,6 +6,8 @@ import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.provider.SearchRecentSuggestions;
+import android.view.View;
+
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.DialogFragment;
@@ -18,9 +20,6 @@ import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
-import timber.log.Timber;
-import android.view.View;
-
import org.jetbrains.annotations.NotNull;
import org.koin.java.KoinJavaComponent;
import org.moire.ultrasonic.R;
@@ -32,12 +31,18 @@ import org.moire.ultrasonic.log.FileLoggerTree;
import org.moire.ultrasonic.provider.SearchSuggestionProvider;
import org.moire.ultrasonic.service.Consumer;
import org.moire.ultrasonic.service.MediaPlayerController;
-import org.moire.ultrasonic.subsonic.ImageLoaderProvider;
-import org.moire.ultrasonic.util.*;
+import org.moire.ultrasonic.util.Constants;
+import org.moire.ultrasonic.util.FileUtil;
+import org.moire.ultrasonic.util.PermissionUtil;
+import org.moire.ultrasonic.util.ThemeChangedEventDistributor;
+import org.moire.ultrasonic.util.TimeSpanPreference;
+import org.moire.ultrasonic.util.TimeSpanPreferenceDialogFragmentCompat;
+import org.moire.ultrasonic.util.Util;
import java.io.File;
import kotlin.Lazy;
+import timber.log.Timber;
import static org.koin.java.KoinJavaComponent.inject;
import static org.moire.ultrasonic.fragment.ServerSelectorFragment.SERVER_SELECTOR_MANAGE_MODE;
@@ -73,7 +78,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
private CheckBoxPreference sendBluetoothAlbumArt;
private CheckBoxPreference showArtistPicture;
private ListPreference viewRefresh;
- private ListPreference imageLoaderConcurrency;
private EditTextPreference sharingDefaultDescription;
private EditTextPreference sharingDefaultGreeting;
private TimeSpanPreference sharingDefaultExpiration;
@@ -84,7 +88,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
private SharedPreferences settings;
private final Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class);
- private final Lazy imageLoader = inject(ImageLoaderProvider.class);
private final Lazy permissionUtil = inject(PermissionUtil.class);
private final Lazy themeChangedEventDistributor = inject(ThemeChangedEventDistributor.class);
@@ -129,7 +132,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
sendBluetoothAlbumArt = findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART);
sendBluetoothNotifications = findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS);
viewRefresh = findPreference(Constants.PREFERENCES_KEY_VIEW_REFRESH);
- imageLoaderConcurrency = findPreference(Constants.PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY);
sharingDefaultDescription = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION);
sharingDefaultGreeting = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING);
sharingDefaultExpiration = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION);
@@ -188,8 +190,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
setMediaButtonsEnabled(sharedPreferences.getBoolean(key, true));
} else if (Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS.equals(key)) {
setBluetoothPreferences(sharedPreferences.getBoolean(key, true));
- } else if (Constants.PREFERENCES_KEY_IMAGE_LOADER_CONCURRENCY.equals(key)) {
- setImageLoaderConcurrency(Integer.parseInt(sharedPreferences.getString(key, "5")));
} else if (Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE.equals(key)) {
setDebugLogToFile(sharedPreferences.getBoolean(key, false));
} else if (Constants.PREFERENCES_KEY_ID3_TAGS.equals(key)) {
@@ -359,21 +359,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
private void setupFeatureFlagsPreferences() {
final FeatureStorage featureStorage = KoinJavaComponent.get(FeatureStorage.class);
- CheckBoxPreference ffImageLoader = (CheckBoxPreference) findPreference(
- Constants.PREFERENCES_KEY_FF_IMAGE_LOADER);
-
- if (ffImageLoader != null) {
- ffImageLoader.setChecked(featureStorage.isFeatureEnabled(Feature.NEW_IMAGE_DOWNLOADER));
- ffImageLoader.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
- @Override
- public boolean onPreferenceChange(Preference preference, Object o) {
- featureStorage.changeFeatureFlag(Feature.NEW_IMAGE_DOWNLOADER, (Boolean) o);
- imageLoader.getValue().clearImageLoader();
- return true;
- }
- });
- }
-
CheckBoxPreference useFiveStarRating = (CheckBoxPreference) findPreference(
Constants.PREFERENCES_KEY_USE_FIVE_STAR_RATING);
@@ -443,7 +428,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
chatRefreshInterval.setSummary(chatRefreshInterval.getEntry());
directoryCacheTime.setSummary(directoryCacheTime.getEntry());
viewRefresh.setSummary(viewRefresh.getEntry());
- imageLoaderConcurrency.setSummary(imageLoaderConcurrency.getEntry());
sharingDefaultExpiration.setSummary(sharingDefaultExpiration.getText());
sharingDefaultDescription.setSummary(sharingDefaultDescription.getText());
sharingDefaultGreeting.setSummary(sharingDefaultGreeting.getText());
@@ -470,14 +454,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
showArtistPicture.setEnabled(Util.getShouldUseId3Tags());
}
- private void setImageLoaderConcurrency(int concurrency) {
- ImageLoader imageLoaderInstance = imageLoader.getValue().getImageLoader();
-
- if (imageLoaderInstance != null) {
- imageLoaderInstance.stopImageLoader();
- imageLoaderInstance.setConcurrency(concurrency);
- }
- }
private void setHideMedia(boolean hide) {
File nomediaDir = new File(FileUtil.getUltrasonicDirectory(), ".nomedia");
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltrasonicAppWidgetProvider.java b/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltrasonicAppWidgetProvider.java
index acb1a193..619484e2 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltrasonicAppWidgetProvider.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltrasonicAppWidgetProvider.java
@@ -9,17 +9,18 @@ import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Environment;
-import timber.log.Timber;
import android.view.KeyEvent;
import android.widget.RemoteViews;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.activity.NavigationActivity;
import org.moire.ultrasonic.domain.MusicDirectory;
+import org.moire.ultrasonic.imageloader.BitmapUtils;
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
import org.moire.ultrasonic.service.MediaPlayerController;
import org.moire.ultrasonic.util.Constants;
-import org.moire.ultrasonic.util.FileUtil;
+
+import timber.log.Timber;
/**
* Widget Provider for the Ultrasonic Widgets
@@ -159,7 +160,7 @@ public class UltrasonicAppWidgetProvider extends AppWidgetProvider
// Set the cover art
try
{
- Bitmap bitmap = currentSong == null ? null : FileUtil.getAlbumArtBitmap(currentSong, 240, true);
+ Bitmap bitmap = currentSong == null ? null : BitmapUtils.Companion.getAlbumArtBitmapFromDisk(currentSong, 240);
if (bitmap == null)
{
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java
index c09d6edc..0b80bae3 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java
@@ -2,7 +2,6 @@ package org.moire.ultrasonic.util;
import android.os.AsyncTask;
import android.os.StatFs;
-import timber.log.Timber;
import org.moire.ultrasonic.data.ActiveServerProvider;
import org.moire.ultrasonic.domain.Playlist;
@@ -19,6 +18,7 @@ import java.util.Set;
import java.util.SortedSet;
import kotlin.Lazy;
+import timber.log.Timber;
import static org.koin.java.KoinJavaComponent.inject;
@@ -88,6 +88,7 @@ public class CacheCleaner
// No songs left in the folder
if (children.length == 1 && children[0].getPath().equals(FileUtil.getAlbumArtFile(dir).getPath()))
{
+ // Delete Artwork files
Util.delete(FileUtil.getAlbumArtFile(dir));
children = dir.listFiles();
}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java
index 95062dee..3d3f8b27 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/FileUtil.java
@@ -19,18 +19,12 @@
package org.moire.ultrasonic.util;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
-import kotlin.Lazy;
-import timber.log.Timber;
-
import org.moire.ultrasonic.app.UApp;
import org.moire.ultrasonic.domain.MusicDirectory;
-import org.moire.ultrasonic.subsonic.ImageLoaderProvider;
import java.io.File;
import java.io.FileInputStream;
@@ -42,10 +36,14 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Pattern;
+import kotlin.Lazy;
+import timber.log.Timber;
+
import static org.koin.java.KoinJavaComponent.inject;
/**
@@ -59,8 +57,9 @@ public class FileUtil
private static final List VIDEO_FILE_EXTENSIONS = Arrays.asList("flv", "mp4", "m4v", "wmv", "avi", "mov", "mpg", "mkv");
private static final List PLAYLIST_FILE_EXTENSIONS = Collections.singletonList("m3u");
private static final Pattern TITLE_WITH_TRACK = Pattern.compile("^\\d\\d-.*");
+ public static final String SUFFIX_LARGE = ".jpeg";
+ public static final String SUFFIX_SMALL = ".jpeg-small";
- private static final Lazy imageLoaderProvider = inject(ImageLoaderProvider.class);
private static final Lazy permissionUtil = inject(PermissionUtil.class);
public static File getSongFile(MusicDirectory.Entry song)
@@ -118,12 +117,49 @@ public class FileUtil
return playlistDir;
}
+ /**
+ * Get the album art file for a given album entry
+ * @param entry The album entry
+ * @return File object. Not guaranteed that it exists
+ */
public static File getAlbumArtFile(MusicDirectory.Entry entry)
{
File albumDir = getAlbumDirectory(entry);
return getAlbumArtFile(albumDir);
}
+ /**
+ * Get the cache key for a given album entry
+ * @param entry The album entry
+ * @param large Whether to get the key for the large or the default image
+ * @return String The hash key
+ */
+ public static String getAlbumArtKey(MusicDirectory.Entry entry, boolean large)
+ {
+ File albumDir = getAlbumDirectory(entry);
+
+ return getAlbumArtKey(albumDir, large);
+ }
+
+ /**
+ * Get the cache key for a given album entry
+ * @param albumDir The album directory
+ * @param large Whether to get the key for the large or the default image
+ * @return String The hash key
+ */
+ public static String getAlbumArtKey(File albumDir, boolean large)
+ {
+ if (albumDir == null) {
+ return null;
+ }
+
+ String suffix = (large) ? SUFFIX_LARGE : SUFFIX_SMALL;
+
+ return String.format(Locale.ROOT, "%s%s", Util.md5Hex(albumDir.getPath()), suffix);
+ }
+
+
+
public static File getAvatarFile(String username)
{
File albumArtDir = getAlbumArtDirectory();
@@ -134,173 +170,45 @@ public class FileUtil
}
String md5Hex = Util.md5Hex(username);
- return new File(albumArtDir, String.format("%s.jpeg", md5Hex));
+ return new File(albumArtDir, String.format("%s%s", md5Hex, SUFFIX_LARGE));
}
+ /**
+ * Get the album art file for a given album directory
+ * @param albumDir The album directory
+ * @return File object. Not guaranteed that it exists
+ */
public static File getAlbumArtFile(File albumDir)
{
File albumArtDir = getAlbumArtDirectory();
+ String key = getAlbumArtKey(albumDir, true);
- if (albumArtDir == null || albumDir == null)
+ if (key == null || albumArtDir == null)
{
return null;
}
- String md5Hex = Util.md5Hex(albumDir.getPath());
- return new File(albumArtDir, String.format("%s.jpeg", md5Hex));
+ return new File(albumArtDir, key);
}
- public static Bitmap getAvatarBitmap(String username, int size, boolean highQuality)
+
+ /**
+ * Get the album art file for a given cache key
+ * @param cacheKey The key (== the filename)
+ * @return File object. Not guaranteed that it exists
+ */
+ public static File getAlbumArtFile(String cacheKey)
{
- if (username == null) return null;
+ File albumArtDir = getAlbumArtDirectory();
- File avatarFile = getAvatarFile(username);
-
- Bitmap bitmap = null;
- ImageLoader imageLoader = imageLoaderProvider.getValue().getImageLoader();
-
- if (imageLoader != null)
+ if (albumArtDir == null || cacheKey == null)
{
- bitmap = imageLoader.getImageBitmap(username, size);
+ return null;
}
- if (bitmap != null)
- {
- return bitmap.copy(bitmap.getConfig(), false);
- }
-
- if (avatarFile != null && avatarFile.exists())
- {
- final BitmapFactory.Options opt = new BitmapFactory.Options();
-
- if (size > 0)
- {
- opt.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(avatarFile.getPath(), opt);
-
- if (highQuality)
- {
- opt.inDither = true;
- opt.inPreferQualityOverSpeed = true;
- }
-
- opt.inPurgeable = true;
- opt.inSampleSize = Util.calculateInSampleSize(opt, size, Util.getScaledHeight(opt.outHeight, opt.outWidth, size));
- opt.inJustDecodeBounds = false;
- }
-
- try
- {
- bitmap = BitmapFactory.decodeFile(avatarFile.getPath(), opt);
- }
- catch (Exception ex)
- {
- Timber.e(ex, "Exception in BitmapFactory.decodeFile()");
- }
-
- Timber.i("getAvatarBitmap %s", String.valueOf(size));
-
- if (bitmap != null)
- {
- if (imageLoader != null)
- {
- imageLoader.addImageToCache(bitmap, username, size);
- }
- }
-
- return bitmap;
- }
-
- return null;
+ return new File(albumArtDir, cacheKey);
}
- public static Bitmap getAlbumArtBitmap(MusicDirectory.Entry entry, int size, boolean highQuality)
- {
- if (entry == null) return null;
-
- File albumArtFile = getAlbumArtFile(entry);
-
- Bitmap bitmap = null;
- ImageLoader imageLoader = imageLoaderProvider.getValue().getImageLoader();
-
- if (imageLoader != null)
- {
- bitmap = imageLoader.getImageBitmap(entry, true, size);
- }
-
- if (bitmap != null)
- {
- return bitmap.copy(bitmap.getConfig(), false);
- }
-
- if (albumArtFile != null && albumArtFile.exists())
- {
- final BitmapFactory.Options opt = new BitmapFactory.Options();
-
- if (size > 0)
- {
- opt.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(albumArtFile.getPath(), opt);
-
- if (highQuality)
- {
- opt.inDither = true;
- opt.inPreferQualityOverSpeed = true;
- }
-
- opt.inPurgeable = true;
- opt.inSampleSize = Util.calculateInSampleSize(opt, size, Util.getScaledHeight(opt.outHeight, opt.outWidth, size));
- opt.inJustDecodeBounds = false;
- }
-
- try
- {
- bitmap = BitmapFactory.decodeFile(albumArtFile.getPath(), opt);
- }
- catch (Exception ex)
- {
- Timber.e(ex, "Exception in BitmapFactory.decodeFile()");
- }
-
- Timber.i("getAlbumArtBitmap %s", String.valueOf(size));
-
- if (bitmap != null)
- {
- if (imageLoader != null)
- {
- imageLoader.addImageToCache(bitmap, entry, size);
- }
- }
-
- return bitmap;
- }
-
- return null;
- }
-
- public static Bitmap getSampledBitmap(byte[] bytes, int size, boolean highQuality)
- {
- final BitmapFactory.Options opt = new BitmapFactory.Options();
-
- if (size > 0)
- {
- opt.inJustDecodeBounds = true;
- BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opt);
-
- if (highQuality)
- {
- opt.inDither = true;
- opt.inPreferQualityOverSpeed = true;
- }
-
- opt.inPurgeable = true;
- opt.inSampleSize = Util.calculateInSampleSize(opt, size, Util.getScaledHeight(opt.outHeight, opt.outWidth, size));
- opt.inJustDecodeBounds = false;
- }
-
- Timber.i("getSampledBitmap %s", String.valueOf(size));
- return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opt);
- }
public static File getAlbumArtDirectory()
{
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ImageLoader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ImageLoader.java
deleted file mode 100644
index d45c67b2..00000000
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ImageLoader.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.moire.ultrasonic.util;
-
-import android.graphics.Bitmap;
-import android.view.View;
-import org.moire.ultrasonic.domain.MusicDirectory;
-
-public interface ImageLoader {
- boolean isRunning();
-
- void setConcurrency(int concurrency);
-
- void startImageLoader();
-
- void stopImageLoader();
-
- void loadAvatarImage(View view, String username, boolean large, int size, boolean crossFade,
- boolean highQuality);
-
- void loadImage(View view, MusicDirectory.Entry entry, boolean large, int size,
- boolean crossFade, boolean highQuality);
-
- void loadImage(View view, MusicDirectory.Entry entry, boolean large, int size,
- boolean crossFade, boolean highQuality, int defaultResourceId);
-
- void cancel(String coverArt);
-
- Bitmap getImageBitmap(String username, int size);
-
- Bitmap getImageBitmap(MusicDirectory.Entry entry, boolean large, int size);
-
- void addImageToCache(Bitmap bitmap, MusicDirectory.Entry entry, int size);
-
- void addImageToCache(Bitmap bitmap, String username, int size);
-
- void clear();
-}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/LegacyImageLoader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/LegacyImageLoader.java
deleted file mode 100644
index 47654e98..00000000
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/LegacyImageLoader.java
+++ /dev/null
@@ -1,450 +0,0 @@
-/*
- This file is part of Subsonic.
-
- Subsonic is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Subsonic is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Subsonic. If not, see .
-
- Copyright 2009 (C) Sindre Mehus
- */
-package org.moire.ultrasonic.util;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.TransitionDrawable;
-import android.os.Handler;
-import android.text.TextUtils;
-import timber.log.Timber;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.core.content.res.ResourcesCompat;
-
-import org.moire.ultrasonic.R;
-import org.moire.ultrasonic.domain.MusicDirectory;
-import org.moire.ultrasonic.service.MusicService;
-import org.moire.ultrasonic.service.MusicServiceFactory;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Locale;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Asynchronous loading of images, with caching.
- *
- * There should normally be only one instance of this class.
- *
- * @author Sindre Mehus
- */
-public class LegacyImageLoader implements Runnable, ImageLoader {
- private final LRUCache cache = new LRUCache<>(150);
- private final BlockingQueue queue;
- private int imageSizeDefault;
- private final int imageSizeLarge;
- private Bitmap largeUnknownImage;
- private Bitmap unknownAvatarImage;
- private final Context context;
- private Collection threads;
- private final AtomicBoolean running = new AtomicBoolean();
- private int concurrency;
-
- public LegacyImageLoader(
- Context context,
- int concurrency
- ) {
- this.context = context;
- this.concurrency = concurrency;
- queue = new LinkedBlockingQueue<>(1000);
-
- Drawable drawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.unknown_album, null);
-
- // Determine the density-dependent image sizes.
- if (drawable != null) {
- imageSizeDefault = drawable.getIntrinsicHeight();
- }
-
- imageSizeLarge = Util.getMaxDisplayMetric();
- createLargeUnknownImage(context);
- createUnknownAvatarImage(context);
- }
-
- @Override
- public synchronized boolean isRunning() {
- return running.get() && !threads.isEmpty();
- }
-
- @Override
- public void setConcurrency(int concurrency) {
- this.concurrency = concurrency;
- }
-
- @Override
- public void startImageLoader() {
- running.set(true);
-
- threads = Collections.synchronizedCollection(new ArrayList(this.concurrency));
-
- for (int i = 0; i < this.concurrency; i++) {
- Thread thread = new Thread(this, String.format(Locale.US, "ImageLoader_%d", i));
- threads.add(thread);
- thread.start();
- }
- }
-
- @Override
- public synchronized void stopImageLoader() {
- clear();
-
- for (Thread thread : threads) {
- thread.interrupt();
- }
-
- running.set(false);
- threads.clear();
- }
-
- private void createLargeUnknownImage(Context context) {
- Drawable drawable = ResourcesCompat.getDrawable(context.getResources(), R.drawable.unknown_album, null);
- Timber.i("createLargeUnknownImage");
-
- if (drawable != null) {
- largeUnknownImage = Util.createBitmapFromDrawable(drawable);
- }
- }
-
- private void createUnknownAvatarImage(Context context) {
- Drawable contact = ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_contact_picture, null);
- unknownAvatarImage = Util.createBitmapFromDrawable(contact);
- }
-
- @Override
- public void loadAvatarImage(
- View view,
- String username,
- boolean large,
- int size,
- boolean crossFade,
- boolean highQuality
- ) {
- view.invalidate();
-
- if (username == null) {
- setUnknownAvatarImage(view);
- return;
- }
-
- if (size <= 0) {
- size = large ? imageSizeLarge : imageSizeDefault;
- }
-
- Bitmap bitmap = cache.get(getKey(username, size));
-
- if (bitmap != null) {
- setAvatarImageBitmap(view, username, bitmap, crossFade);
- return;
- }
-
- setUnknownAvatarImage(view);
-
- queue.offer(new Task(view, username, size, large, crossFade, highQuality));
- }
-
- @Override
- public void loadImage(View view, MusicDirectory.Entry entry, boolean large, int size,
- boolean crossFade, boolean highQuality) {
- loadImage(view, entry, large, size, crossFade, highQuality, -1);
- }
-
- public void loadImage(View view, MusicDirectory.Entry entry, boolean large, int size,
- boolean crossFade, boolean highQuality, int defaultResourceId) {
- view.invalidate();
-
- if (entry == null) {
- setUnknownImage(view, large, defaultResourceId);
- return;
- }
-
- String coverArt = entry.getCoverArt();
-
- if (TextUtils.isEmpty(coverArt)) {
- setUnknownImage(view, large, defaultResourceId);
- return;
- }
-
- if (size <= 0) {
- size = large ? imageSizeLarge : imageSizeDefault;
- }
-
- Bitmap bitmap = cache.get(getKey(coverArt, size));
-
- if (bitmap != null) {
- setImageBitmap(view, entry, bitmap, crossFade);
- return;
- }
-
- setUnknownImage(view, large, defaultResourceId);
-
- queue.offer(new Task(view, entry, size, large, crossFade, highQuality));
- }
-
- public void cancel(String coverArt) {
- for (Object taskObject : queue.toArray()) {
- Task task = (Task)taskObject;
- if ((task.entry.getCoverArt() != null) && (coverArt.compareTo(task.entry.getCoverArt()) == 0)) {
- queue.remove(taskObject);
- break;
- }
- }
- }
-
- private static String getKey(String coverArtId, int size) {
- return String.format(Locale.US, "%s:%d", coverArtId, size);
- }
-
- @Override
- public Bitmap getImageBitmap(String username, int size) {
- Bitmap bitmap = cache.get(getKey(username, size));
-
- if (bitmap != null && !bitmap.isRecycled()) {
- Bitmap.Config config = bitmap.getConfig();
- return bitmap.copy(config, false);
- }
-
- return null;
- }
-
- @Override
- public Bitmap getImageBitmap(MusicDirectory.Entry entry, boolean large, int size) {
- if (entry == null) {
- return null;
- }
-
- String coverArt = entry.getCoverArt();
-
- if (TextUtils.isEmpty(coverArt)) {
- return null;
- }
-
- if (size <= 0) {
- size = large ? imageSizeLarge : imageSizeDefault;
- }
-
- Bitmap bitmap = cache.get(getKey(coverArt, size));
-
- if (bitmap != null && !bitmap.isRecycled()) {
- Bitmap.Config config = bitmap.getConfig();
- return bitmap.copy(config, false);
- }
-
- return null;
- }
-
- private void setImageBitmap(
- View view,
- MusicDirectory.Entry entry,
- Bitmap bitmap,
- boolean crossFade
- ) {
- if (view instanceof ImageView) {
- ImageView imageView = (ImageView) view;
-
- MusicDirectory.Entry tagEntry = (MusicDirectory.Entry) view.getTag();
-
- // Only apply image to the view if the view is intended for this entry
- if (entry != null && tagEntry != null && !entry.equals(tagEntry)) {
- Timber.i("View is no longer valid, not setting ImageBitmap");
- return;
- }
-
- if (crossFade) {
- Drawable existingDrawable = imageView.getDrawable();
- Drawable newDrawable = Util.createDrawableFromBitmap(this.context, bitmap);
-
- if (existingDrawable == null) {
- Bitmap emptyImage = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
- existingDrawable = new BitmapDrawable(context.getResources(), emptyImage);
- }
-
- Drawable[] layers = new Drawable[]{existingDrawable, newDrawable};
-
- TransitionDrawable transitionDrawable = new TransitionDrawable(layers);
- imageView.setImageDrawable(transitionDrawable);
- transitionDrawable.startTransition(250);
- } else {
- imageView.setImageBitmap(bitmap);
- }
- }
- }
-
- private void setAvatarImageBitmap(
- View view,
- String username,
- Bitmap bitmap,
- boolean crossFade
- ) {
- if (view instanceof ImageView) {
- ImageView imageView = (ImageView) view;
-
- String tagEntry = (String) view.getTag();
-
- // Only apply image to the view if the view is intended for this entry
- if (username != null &&
- tagEntry != null &&
- !username.equals(tagEntry)) {
- Timber.i("View is no longer valid, not setting ImageBitmap");
- return;
- }
-
- if (crossFade) {
- Drawable existingDrawable = imageView.getDrawable();
- Drawable newDrawable = Util.createDrawableFromBitmap(this.context, bitmap);
-
- if (existingDrawable == null) {
- Bitmap emptyImage = Bitmap.createBitmap(
- bitmap.getWidth(),
- bitmap.getHeight(),
- Bitmap.Config.ARGB_8888
- );
- existingDrawable = new BitmapDrawable(context.getResources(), emptyImage);
- }
-
- Drawable[] layers = new Drawable[]{existingDrawable, newDrawable};
-
- TransitionDrawable transitionDrawable = new TransitionDrawable(layers);
- imageView.setImageDrawable(transitionDrawable);
- transitionDrawable.startTransition(250);
- } else {
- imageView.setImageBitmap(bitmap);
- }
- }
- }
-
- private void setUnknownAvatarImage(View view) {
- setAvatarImageBitmap(view, null, unknownAvatarImage, false);
- }
-
- private void setUnknownImage(View view, boolean large, int resId) {
- if (resId == -1) resId = R.drawable.unknown_album;
- if (large) {
- setImageBitmap(view, null, largeUnknownImage, false);
- } else {
- if (view instanceof TextView) {
- ((TextView) view).setCompoundDrawablesWithIntrinsicBounds(resId, 0, 0, 0);
- } else if (view instanceof ImageView) {
- ((ImageView) view).setImageResource(resId);
- }
- }
- }
-
- @Override
- public void addImageToCache(Bitmap bitmap, MusicDirectory.Entry entry, int size) {
- cache.put(getKey(entry.getCoverArt(), size), bitmap);
- }
-
- @Override
- public void addImageToCache(Bitmap bitmap, String username, int size) {
- cache.put(getKey(username, size), bitmap);
- }
-
- @Override
- public void clear() {
- queue.clear();
- }
-
- @Override
- public void run() {
- while (running.get()) {
- try {
- Task task = queue.take();
- task.execute();
- } catch (InterruptedException ignored) {
- running.set(false);
- break;
- } catch (Throwable x) {
- Timber.e(x, "Unexpected exception in ImageLoader.");
- }
- }
- }
-
- private class Task {
- private final View view;
- private final MusicDirectory.Entry entry;
- private final String username;
- private final Handler handler;
- private final int size;
- private final boolean saveToFile;
- private final boolean crossFade;
- private final boolean highQuality;
-
- Task(View view, MusicDirectory.Entry entry, int size, boolean saveToFile, boolean crossFade, boolean highQuality) {
- this.view = view;
- this.entry = entry;
- this.username = null;
- this.size = size;
- this.saveToFile = saveToFile;
- this.crossFade = crossFade;
- this.highQuality = highQuality;
- handler = new Handler();
- }
-
- Task(View view, String username, int size, boolean saveToFile, boolean crossFade, boolean highQuality) {
- this.view = view;
- this.entry = null;
- this.username = username;
- this.size = size;
- this.saveToFile = saveToFile;
- this.crossFade = crossFade;
- this.highQuality = highQuality;
- handler = new Handler();
- }
-
- public void execute() {
- try {
- MusicService musicService = MusicServiceFactory.getMusicService();
- final boolean isAvatar = this.username != null && this.entry == null;
- final Bitmap bitmap = this.entry != null ?
- musicService.getCoverArt(entry, size, saveToFile, highQuality) :
- musicService.getAvatar(username, size, saveToFile, highQuality);
-
- if (bitmap == null) {
- Timber.d("Found empty album art.");
- return;
- }
-
- if (isAvatar)
- addImageToCache(bitmap, username, size);
- else
- addImageToCache(bitmap, entry, size);
-
- handler.post(new Runnable() {
- @Override
- public void run() {
- if (isAvatar) {
- setAvatarImageBitmap(view, username, bitmap, crossFade);
- } else {
- setImageBitmap(view, entry, bitmap, crossFade);
- }
- }
- });
- } catch (Throwable x) {
- Timber.e(x, "Failed to download album art.");
- }
- }
- }
-}
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/activity/NavigationActivity.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt
index 7f8abc29..9f029926 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt
@@ -217,7 +217,6 @@ class NavigationActivity : AppCompatActivity() {
if (item.itemId == R.id.menu_exit) {
setResult(Constants.RESULT_CLOSE_ALL)
mediaPlayerController.stopJukeboxService()
- imageLoaderProvider.getImageLoader().stopImageLoader()
finish()
exit()
}
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..c2c0b6d2 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt
@@ -12,6 +12,7 @@ import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
import org.moire.ultrasonic.cache.PermanentFileStorage
import org.moire.ultrasonic.data.ActiveServerProvider
+import org.moire.ultrasonic.imageloader.ImageLoader
import org.moire.ultrasonic.log.TimberOkHttpLogger
import org.moire.ultrasonic.service.ApiCallResponseChecker
import org.moire.ultrasonic.service.CachedMusicService
@@ -19,10 +20,10 @@ 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.subsonic.VideoPlayer
-import org.moire.ultrasonic.subsonic.loader.image.SubsonicImageLoader
import org.moire.ultrasonic.util.Constants
/**
@@ -77,7 +78,7 @@ val musicServiceModule = module {
OfflineMusicService()
}
- single { SubsonicImageLoader(androidContext(), get()) }
+ single { ImageLoader(androidContext(), get(), ImageLoaderProvider.config) }
single { DownloadHandler(get(), get()) }
single { NetworkAndStorageChecker(androidContext()) }
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 e3974614..239fb237 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
@@ -29,7 +29,6 @@ class AlbumRowAdapter(
) : GenericRowAdapter(
onItemClick,
onContextMenuClick,
- imageLoader,
onMusicFolderUpdate
) {
@@ -56,9 +55,8 @@ class AlbumRowAdapter(
holder.coverArtId = entry.coverArt
imageLoader.loadImage(
- holder.coverArt,
- MusicDirectory.Entry("-1").apply { coverArt = holder.coverArtId },
- false, 0, false, true, R.drawable.unknown_album
+ holder.coverArt, entry,
+ 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 2e312225..69988607 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
/**
@@ -30,7 +30,6 @@ class ArtistRowAdapter(
) : GenericRowAdapter(
onItemClick,
onContextMenuClick,
- imageLoader,
onMusicFolderUpdate
),
SectionedAdapter {
@@ -62,8 +61,11 @@ class ArtistRowAdapter(
holder.coverArt.visibility = View.VISIBLE
imageLoader.loadImage(
holder.coverArt,
- MusicDirectory.Entry("-1").apply { coverArt = holder.coverArtId },
- false, 0, false, true, R.drawable.ic_contact_picture
+ MusicDirectory.Entry("-1").apply {
+ coverArt = holder.coverArtId
+ artist = itemList[listPosition].name
+ },
+ false, 0, R.drawable.ic_contact_picture
)
} else {
holder.coverArt.visibility = View.GONE
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericRowAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericRowAdapter.kt
index 414c76d0..2a26463c 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericRowAdapter.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericRowAdapter.kt
@@ -20,7 +20,6 @@ import androidx.recyclerview.widget.RecyclerView
import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.MusicFolder
-import org.moire.ultrasonic.util.ImageLoader
import org.moire.ultrasonic.view.SelectMusicFolderView
/*
@@ -29,7 +28,6 @@ import org.moire.ultrasonic.view.SelectMusicFolderView
abstract class GenericRowAdapter(
val onItemClick: (T) -> Unit,
val onContextMenuClick: (MenuItem, T) -> Boolean,
- private val imageLoader: ImageLoader,
private val onMusicFolderUpdate: (String?) -> Unit
) : RecyclerView.Adapter() {
open var itemList: List = listOf()
@@ -94,13 +92,6 @@ abstract class GenericRowAdapter(
}
}
- override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
- if ((holder is ViewHolder) && (holder.coverArtId != null)) {
- imageLoader.cancel(holder.coverArtId)
- }
- super.onViewRecycled(holder)
- }
-
abstract override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
override fun getItemCount(): Int {
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/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt
new file mode 100644
index 00000000..fddbec66
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt
@@ -0,0 +1,128 @@
+package org.moire.ultrasonic.imageloader
+
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.os.Build
+import org.moire.ultrasonic.domain.MusicDirectory
+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(
+ entry: MusicDirectory.Entry?,
+ size: Int
+ ): Bitmap? {
+ if (entry == null) return null
+ val albumArtFile = FileUtil.getAlbumArtFile(entry)
+ val bitmap: Bitmap? = null
+ if (albumArtFile != null && albumArtFile.exists()) {
+ return getBitmapFromDisk(albumArtFile.path, size, bitmap)
+ }
+ return null
+ }
+
+ fun getAlbumArtBitmapFromDisk(
+ filename: String,
+ size: Int?
+ ): Bitmap? {
+ val albumArtFile = FileUtil.getAlbumArtFile(filename)
+ val bitmap: Bitmap? = null
+ if (albumArtFile != null && albumArtFile.exists()) {
+ return getBitmapFromDisk(albumArtFile.path, size, bitmap)
+ }
+ return null
+ }
+
+ @Suppress("DEPRECATION")
+ fun getSampledBitmap(bytes: ByteArray, size: Int): Bitmap? {
+ val opt = BitmapFactory.Options()
+ if (size > 0) {
+ // With this flag we only calculate the size first
+ opt.inJustDecodeBounds = true
+
+ // Decode the size
+ BitmapFactory.decodeByteArray(bytes, 0, bytes.size, opt)
+
+ // Now set the remaining flags
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ opt.inDither = true
+ opt.inPreferQualityOverSpeed = true
+ }
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ opt.inPurgeable = true
+ }
+
+ opt.inSampleSize = Util.calculateInSampleSize(
+ opt,
+ size,
+ Util.getScaledHeight(opt.outHeight.toDouble(), opt.outWidth.toDouble(), size)
+ )
+
+ // Enable real decoding
+ opt.inJustDecodeBounds = false
+ }
+ Timber.i("getSampledBitmap %s", size.toString())
+ return BitmapFactory.decodeByteArray(bytes, 0, bytes.size, opt)
+ }
+
+ @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
+ }
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ opt.inPurgeable = 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()")
+ }
+ Timber.i("getBitmapFromDisk %s", size.toString())
+ return bitmap1
+ }
+ }
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt
new file mode 100644
index 00000000..a6aeb048
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/CoverArtRequestHandler.kt
@@ -0,0 +1,49 @@
+package org.moire.ultrasonic.imageloader
+
+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.IOException
+import okio.Okio
+import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
+import org.moire.ultrasonic.util.FileUtil.SUFFIX_LARGE
+import org.moire.ultrasonic.util.FileUtil.SUFFIX_SMALL
+
+/**
+ * Loads cover arts from subsonic api.
+ */
+class CoverArtRequestHandler(private val apiClient: SubsonicAPIClient) : RequestHandler() {
+ override fun canHandleRequest(data: Request): Boolean {
+ return with(data.uri) {
+ scheme == SCHEME &&
+ path == "/$COVER_ART_PATH"
+ }
+ }
+
+ override fun load(request: Request, networkPolicy: Int): Result {
+ val id = request.uri.getQueryParameter(QUERY_ID)
+ ?: throw IllegalArgumentException("Nullable id")
+ val size = request.uri.getQueryParameter(SIZE)?.toLong()
+
+ // Check if we have a hit in the disk cache
+ // Note: Currently we are only caching full size images on disk
+ // So we modify the key to query for the full size image,
+ // because scaling down a larger size image on the device is quicker than
+ // requesting the down-sized image from the network.
+ val key = request.stableKey!!.replace(SUFFIX_SMALL, SUFFIX_LARGE)
+ val cache = BitmapUtils.getAlbumArtBitmapFromDisk(key, size?.toInt())
+ if (cache != null) {
+ return Result(cache, DISK)
+ }
+
+ // Try to fetch the image from the API
+ val response = apiClient.getCoverArt(id, size)
+ if (!response.hasError() && response.stream != null) {
+ return Result(Okio.source(response.stream!!), NETWORK)
+ }
+
+ // Throw an error if still not successful
+ throw IOException("${response.apiError}")
+ }
+}
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..52f7e5ef
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt
@@ -0,0 +1,220 @@
+package org.moire.ultrasonic.imageloader
+
+import android.content.Context
+import android.text.TextUtils
+import android.view.View
+import android.widget.ImageView
+import com.squareup.picasso.Picasso
+import com.squareup.picasso.RequestCreator
+import java.io.File
+import java.io.FileOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import org.moire.ultrasonic.BuildConfig
+import org.moire.ultrasonic.R
+import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
+import org.moire.ultrasonic.domain.MusicDirectory
+import org.moire.ultrasonic.service.RESTMusicService
+import org.moire.ultrasonic.util.FileUtil
+import org.moire.ultrasonic.util.Util
+import timber.log.Timber
+
+/**
+ * Our new image loader which uses Picasso as a backend.
+ */
+class ImageLoader(
+ context: Context,
+ private val apiClient: SubsonicAPIClient,
+ private val config: ImageLoaderConfig
+) {
+
+ private val picasso = Picasso.Builder(context)
+ .addRequestHandler(CoverArtRequestHandler(apiClient))
+ .addRequestHandler(AvatarRequestHandler(apiClient))
+ .build().apply {
+ setIndicatorsEnabled(BuildConfig.DEBUG)
+ }
+
+ 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.cacheKey)
+ .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 key = FileUtil.getAlbumArtKey(entry, large)
+ val request = ImageRequest.CoverArt(
+ id, key, view, requestedSize,
+ placeHolderDrawableRes = defaultResourceId,
+ errorDrawableRes = defaultResourceId
+ )
+ load(request)
+ } else if (view is ImageView) {
+ view.setImageResource(defaultResourceId)
+ }
+ }
+
+ /**
+ * 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)
+ } else {
+ view.setImageResource(R.drawable.ic_contact_picture)
+ }
+ }
+
+ /**
+ * Download a cover art file and cache it on disk
+ */
+ fun cacheCoverArt(
+ entry: MusicDirectory.Entry
+ ) {
+
+ // Synchronize on the entry so that we don't download concurrently for
+ // the same song.
+ synchronized(entry) {
+ // Always download the large size..
+ val size = config.largeSize
+
+ // Check cache to avoid downloading existing files
+ val file = FileUtil.getAlbumArtFile(entry)
+
+ // Return if have a cache hit
+ if (file.exists()) return
+
+ // Can't load empty string ids
+ val id = entry.coverArt
+ if (TextUtils.isEmpty(id)) return
+
+ // Query the API
+ Timber.d("Loading cover art for: %s", entry)
+ val response = apiClient.getCoverArt(id!!, size.toLong())
+ RESTMusicService.checkStreamResponseError(response)
+
+ // Check for failure
+ if (response.stream == null) return
+
+ // Write Response stream to file
+ var inputStream: InputStream? = null
+ try {
+ inputStream = response.stream
+ val bytes = Util.toByteArray(inputStream)
+
+ var outputStream: OutputStream? = null
+ try {
+ outputStream = FileOutputStream(file)
+ outputStream.write(bytes)
+ } finally {
+ Util.close(outputStream)
+ }
+ } finally {
+ Util.close(inputStream)
+ }
+ }
+ }
+
+ 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,
+ val cacheKey: 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/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/RequestCreator.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/RequestCreator.kt
new file mode 100644
index 00000000..37a11f5a
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/RequestCreator.kt
@@ -0,0 +1,29 @@
+package org.moire.ultrasonic.imageloader
+
+import android.net.Uri
+
+internal const val SCHEME = "subsonic_api"
+internal const val COVER_ART_PATH = "cover_art"
+internal const val AVATAR_PATH = "avatar"
+internal const val QUERY_ID = "id"
+internal const val SIZE = "size"
+internal const val QUERY_USERNAME = "username"
+
+/**
+ * Picasso.load() only accepts an URI as parameter. Therefore we create a bogus URI, in which
+ * we encode the data that we need in the RequestHandler.
+ */
+internal fun createLoadCoverArtRequest(entityId: String, size: Long? = 0): Uri =
+ Uri.Builder()
+ .scheme(SCHEME)
+ .appendPath(COVER_ART_PATH)
+ .appendQueryParameter(QUERY_ID, entityId)
+ .appendQueryParameter(SIZE, size.toString())
+ .build()
+
+internal fun createLoadAvatarRequest(username: String): Uri =
+ Uri.Builder()
+ .scheme(SCHEME)
+ .appendPath(AVATAR_PATH)
+ .appendQueryParameter(QUERY_USERNAME, username)
+ .build()
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt
index 0f280258..30e5ff10 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt
@@ -6,7 +6,6 @@
*/
package org.moire.ultrasonic.service
-import android.graphics.Bitmap
import java.io.InputStream
import java.util.concurrent.TimeUnit
import org.koin.core.component.KoinComponent
@@ -255,16 +254,6 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
@Throws(Exception::class)
override fun getStarred2(): SearchResult = musicService.getStarred2()
- @Throws(Exception::class)
- override fun getCoverArt(
- entry: MusicDirectory.Entry?,
- size: Int,
- saveToFile: Boolean,
- highQuality: Boolean
- ): Bitmap? {
- return musicService.getCoverArt(entry, size, saveToFile, highQuality)
- }
-
@Throws(Exception::class)
override fun getDownloadInputStream(
song: MusicDirectory.Entry,
@@ -447,16 +436,6 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
musicService.updateShare(id, description, expires)
}
- @Throws(Exception::class)
- override fun getAvatar(
- username: String?,
- size: Int,
- saveToFile: Boolean,
- highQuality: Boolean
- ): Bitmap? {
- return musicService.getAvatar(username, size, saveToFile, highQuality)
- }
-
companion object {
private const val MUSIC_DIR_CACHE_SIZE = 100
}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt
index fd98da9b..f8e88257 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt
@@ -24,6 +24,7 @@ import org.koin.core.component.inject
import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
+import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.util.CacheCleaner
import org.moire.ultrasonic.util.CancellableTask
import org.moire.ultrasonic.util.FileUtil
@@ -59,6 +60,7 @@ class DownloadFile(
private var completeWhenDone = false
private val downloader: Downloader by inject()
+ private val imageLoaderProvider: ImageLoaderProvider by inject()
val progress: MutableLiveData = MutableLiveData(0)
@@ -275,7 +277,7 @@ class DownloadFile(
if (isCancelled) {
throw Exception(String.format("Download of '%s' was cancelled", song))
}
- downloadAndSaveCoverArt(musicService)
+ downloadAndSaveCoverArt()
}
if (isPlaying) {
@@ -329,11 +331,11 @@ class DownloadFile(
return String.format("DownloadTask (%s)", song)
}
- private fun downloadAndSaveCoverArt(musicService: MusicService) {
+ private fun downloadAndSaveCoverArt() {
try {
if (!TextUtils.isEmpty(song.coverArt)) {
- val size = Util.getMinDisplayMetric()
- musicService.getCoverArt(song, size, true, true)
+ // Download the largest size that we can display in the UI
+ imageLoaderProvider.getImageLoader().cacheCoverArt(song)
}
} catch (e: Exception) {
Timber.e(e, "Failed to get cover art.")
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt
index e8f3c4f0..cd0f16c6 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt
@@ -489,7 +489,7 @@ class MediaPlayerController(
val currentPlayingNumberOnPlaylist: Int
get() = downloader.currentPlayingIndex
- val currentDownloading: DownloadFile
+ val currentDownloading: DownloadFile?
get() = downloader.currentDownloading
val playList: List
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt
index 9893d3ae..291355f3 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt
@@ -32,6 +32,7 @@ import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.PlayerState
import org.moire.ultrasonic.domain.RepeatMode
+import org.moire.ultrasonic.imageloader.BitmapUtils
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X1
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X2
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X3
@@ -40,7 +41,6 @@ import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.util.AndroidAutoMediaBrowser
import org.moire.ultrasonic.util.Constants
-import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.NowPlayingEventDistributor
import org.moire.ultrasonic.util.ShufflePlayBuffer
import org.moire.ultrasonic.util.Util
@@ -492,9 +492,8 @@ class MediaPlayerService : MediaBrowserServiceCompat() {
if (currentPlaying != null) {
try {
val song = currentPlaying.song
- val cover = FileUtil.getAlbumArtBitmap(
- song, Util.getMinDisplayMetric(),
- true
+ val cover = BitmapUtils.getAlbumArtBitmapFromDisk(
+ song, Util.getMinDisplayMetric()
)
metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, -1L)
metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artist)
@@ -662,7 +661,7 @@ class MediaPlayerService : MediaBrowserServiceCompat() {
// Set song title, artist and cover if possible
if (song != null) {
val iconSize = (256 * context.resources.displayMetrics.density).toInt()
- val bitmap = FileUtil.getAlbumArtBitmap(song, iconSize, true)
+ val bitmap = BitmapUtils.getAlbumArtBitmapFromDisk(song, iconSize)
notificationBuilder!!.setContentTitle(song.title)
notificationBuilder!!.setContentText(song.artist)
notificationBuilder!!.setLargeIcon(bitmap)
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt
index f4f88592..b9e5f5f3 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt
@@ -6,7 +6,6 @@
*/
package org.moire.ultrasonic.service
-import android.graphics.Bitmap
import java.io.InputStream
import org.moire.ultrasonic.domain.Bookmark
import org.moire.ultrasonic.domain.ChatMessage
@@ -111,17 +110,6 @@ interface MusicService {
@Throws(Exception::class)
fun getStarred2(): SearchResult
- @Throws(Exception::class)
- fun getCoverArt(
- entry: MusicDirectory.Entry?,
- size: Int,
- saveToFile: Boolean,
- highQuality: Boolean
- ): Bitmap?
-
- @Throws(Exception::class)
- fun getAvatar(username: String?, size: Int, saveToFile: Boolean, highQuality: Boolean): Bitmap?
-
/**
* Return response [InputStream] and a [Boolean] that indicates if this response is
* partial.
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt
index f1fc7f43..a4ad2ca9 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt
@@ -6,7 +6,6 @@
*/
package org.moire.ultrasonic.service
-import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import java.io.BufferedReader
import java.io.BufferedWriter
@@ -118,34 +117,6 @@ class OfflineMusicService : MusicService, KoinComponent {
return result
}
- override fun getAvatar(
- username: String?,
- size: Int,
- saveToFile: Boolean,
- highQuality: Boolean
- ): Bitmap? {
- return try {
- val bitmap = FileUtil.getAvatarBitmap(username, size, highQuality)
- Util.scaleBitmap(bitmap, size)
- } catch (ignored: Exception) {
- null
- }
- }
-
- override fun getCoverArt(
- entry: MusicDirectory.Entry?,
- size: Int,
- saveToFile: Boolean,
- highQuality: Boolean
- ): Bitmap? {
- return try {
- val bitmap = FileUtil.getAlbumArtBitmap(entry, size, highQuality)
- Util.scaleBitmap(bitmap, size)
- } catch (ignored: Exception) {
- null
- }
- }
-
override fun search(criteria: SearchCriteria): SearchResult {
val artists: MutableList = ArrayList()
val albums: MutableList = ArrayList()
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt
index fd9f2de6..d3e3e708 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt
@@ -6,15 +6,11 @@
*/
package org.moire.ultrasonic.service
-import android.graphics.Bitmap
-import android.text.TextUtils
import java.io.BufferedWriter
import java.io.File
-import java.io.FileOutputStream
import java.io.FileWriter
import java.io.IOException
import java.io.InputStream
-import java.io.OutputStream
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException
@@ -27,7 +23,6 @@ import org.moire.ultrasonic.cache.serializers.getIndexesSerializer
import org.moire.ultrasonic.cache.serializers.getMusicFolderListSerializer
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
-import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isServerScalingEnabled
import org.moire.ultrasonic.domain.Bookmark
import org.moire.ultrasonic.domain.ChatMessage
import org.moire.ultrasonic.domain.Genre
@@ -488,83 +483,6 @@ open class RESTMusicService(
return response.body()!!.starred2.toDomainEntity()
}
- @Throws(Exception::class)
- override fun getCoverArt(
- entry: MusicDirectory.Entry?,
- size: Int,
- saveToFile: Boolean,
- highQuality: Boolean
- ): Bitmap? {
- // Synchronize on the entry so that we don't download concurrently for
- // the same song.
- if (entry == null) {
- return null
- }
-
- synchronized(entry) {
- // Use cached file, if existing.
- var bitmap = FileUtil.getAlbumArtBitmap(entry, size, highQuality)
- val serverScaling = isServerScalingEnabled()
-
- if (bitmap == null) {
- Timber.d("Loading cover art for: %s", entry)
-
- val id = entry.coverArt
-
- if (TextUtils.isEmpty(id)) {
- return null // Can't load
- }
-
- val response = subsonicAPIClient.getCoverArt(id!!, size.toLong())
- checkStreamResponseError(response)
-
- if (response.stream == null) {
- return null // Failed to load
- }
-
- var inputStream: InputStream? = null
- try {
- inputStream = response.stream
- val bytes = Util.toByteArray(inputStream)
-
- // If we aren't allowing server-side scaling, always save the file to disk
- // because it will be unmodified
- if (!serverScaling || saveToFile) {
- var outputStream: OutputStream? = null
- try {
- outputStream = FileOutputStream(
- FileUtil.getAlbumArtFile(entry)
- )
- outputStream.write(bytes)
- } finally {
- Util.close(outputStream)
- }
- }
-
- bitmap = FileUtil.getSampledBitmap(bytes, size, highQuality)
- } finally {
- Util.close(inputStream)
- }
- }
-
- // Return scaled bitmap
- return Util.scaleBitmap(bitmap, size)
- }
- }
-
- @Throws(SubsonicRESTException::class, IOException::class)
- private fun checkStreamResponseError(response: StreamResponse) {
- if (response.hasError() || response.stream == null) {
- if (response.apiError != null) {
- throw SubsonicRESTException(response.apiError!!)
- } else {
- throw IOException(
- "Failed to make endpoint request, code: " + response.responseHttpCode
- )
- }
- }
- }
-
@Throws(Exception::class)
override fun getDownloadInputStream(
song: MusicDirectory.Entry,
@@ -813,62 +731,23 @@ open class RESTMusicService(
}
}
- @Throws(Exception::class)
- override fun getAvatar(
- username: String?,
- size: Int,
- saveToFile: Boolean,
- highQuality: Boolean
- ): Bitmap? {
- // Synchronize on the username so that we don't download concurrently for
- // the same user.
- if (username == null) {
- return null
- }
-
- synchronized(username) {
- // Use cached file, if existing.
- var bitmap = FileUtil.getAvatarBitmap(username, size, highQuality)
-
- if (bitmap == null) {
- var inputStream: InputStream? = null
- try {
- val response = subsonicAPIClient.getAvatar(username)
-
- if (response.hasError()) return null
-
- inputStream = response.stream
- val bytes = Util.toByteArray(inputStream)
-
- // If we aren't allowing server-side scaling, always save the file to disk
- // because it will be unmodified
- if (saveToFile) {
- var outputStream: OutputStream? = null
-
- try {
- outputStream = FileOutputStream(
- FileUtil.getAvatarFile(username)
- )
- outputStream.write(bytes)
- } finally {
- Util.close(outputStream)
- }
- }
-
- bitmap = FileUtil.getSampledBitmap(bytes, size, highQuality)
- } finally {
- Util.close(inputStream)
- }
- }
-
- // Return scaled bitmap
- return Util.scaleBitmap(bitmap, size)
- }
- }
-
companion object {
private const val MUSIC_FOLDER_STORAGE_NAME = "music_folder"
private const val INDEXES_STORAGE_NAME = "indexes"
private const val ARTISTS_STORAGE_NAME = "artists"
+
+ // TODO: Move to response checker
+ @Throws(SubsonicRESTException::class, IOException::class)
+ fun checkStreamResponseError(response: StreamResponse) {
+ if (response.hasError() || response.stream == null) {
+ if (response.apiError != null) {
+ throw SubsonicRESTException(response.apiError!!)
+ } else {
+ throw IOException(
+ "Failed to make endpoint request, code: " + response.responseHttpCode
+ )
+ }
+ }
+ }
}
}
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 3ee6bf53..6156b781 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ImageLoaderProvider.kt
@@ -1,12 +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.featureflags.Feature
-import org.moire.ultrasonic.featureflags.FeatureStorage
-import org.moire.ultrasonic.util.ImageLoader
-import org.moire.ultrasonic.util.LegacyImageLoader
+import org.koin.core.qualifier.named
+import org.moire.ultrasonic.R
+import org.moire.ultrasonic.app.UApp
+import org.moire.ultrasonic.imageloader.ImageLoader
+import org.moire.ultrasonic.imageloader.ImageLoaderConfig
+import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.Util
/**
@@ -14,37 +17,42 @@ import org.moire.ultrasonic.util.Util
*/
class ImageLoaderProvider(val context: Context) : KoinComponent {
private var imageLoader: ImageLoader? = null
+ private var serverID: String = get(named("ServerID"))
@Synchronized
fun clearImageLoader() {
- if (
- imageLoader != null &&
- imageLoader!!.isRunning
- ) {
- imageLoader!!.clear()
- }
imageLoader = null
}
@Synchronized
fun getImageLoader(): ImageLoader {
- if (imageLoader == null || !imageLoader!!.isRunning) {
- val legacyImageLoader = LegacyImageLoader(
- context,
- Util.getImageLoaderConcurrency()
- )
- val features: FeatureStorage = get()
- val isNewImageLoaderEnabled = features.isFeatureEnabled(Feature.NEW_IMAGE_DOWNLOADER)
- imageLoader = if (isNewImageLoaderEnabled) {
- SubsonicImageLoaderProxy(
- legacyImageLoader,
- get()
- )
- } else {
- legacyImageLoader
- }
- imageLoader!!.startImageLoader()
+ // We need to generate a new ImageLoader if the server has changed...
+ val currentID = get(named("ServerID"))
+ if (imageLoader == null || currentID != serverID) {
+ imageLoader = get()
+ serverID = currentID
}
return imageLoader!!
}
+
+ companion object {
+ 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()
+ )
+ }
+ }
}
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 6d0d2b99..00000000
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/SubsonicImageLoaderProxy.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-package org.moire.ultrasonic.subsonic
-
-import android.view.View
-import android.widget.ImageView
-import org.moire.ultrasonic.R
-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.LegacyImageLoader
-
-/**
- * Temporary proxy between new [SubsonicImageLoader] and [ImageLoader] interface and old
- * [LegacyImageLoader] implementation.
- *
- * Should be removed on [LegacyImageLoader] removal.
- */
-class SubsonicImageLoaderProxy(
- legacyImageLoader: LegacyImageLoader,
- private val subsonicImageLoader: SubsonicImageLoader
-) : ImageLoader by legacyImageLoader {
- 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
- val unknownImageId =
- if (defaultResourceId == -1) R.drawable.unknown_album
- else defaultResourceId
-
- if (id != null &&
- view != null &&
- view is ImageView
- ) {
- val request = ImageRequest.CoverArt(
- id, view,
- 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 &&
- view != null &&
- view is ImageView
- ) {
- val request = ImageRequest.Avatar(
- username, view,
- placeHolderDrawableRes = R.drawable.ic_contact_picture,
- errorDrawableRes = R.drawable.ic_contact_picture
- )
- subsonicImageLoader.load(request)
- }
- }
-}
diff --git a/ultrasonic/src/main/res/values-cs/strings.xml b/ultrasonic/src/main/res/values-cs/strings.xml
index e1fc4b29..55ba92ac 100644
--- a/ultrasonic/src/main/res/values-cs/strings.xml
+++ b/ultrasonic/src/main/res/values-cs/strings.xml
@@ -379,19 +379,6 @@
Zobrazit všechny skladby umělce
Přidat nový zápis v náhledu umělců pro přístup ke všem skladbám umělce
Zobrazit umělce
- Počet vláken stahování obrázků
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
albumArt
Vícenásobné roky
Pokračovat v přehrávání po připojení bluetooth přístroje
@@ -501,10 +488,6 @@
Příznaky funkcí
- Povolit nové načítání obrázků
- Zapne novou implementaci načítání obrázků.
- Toto aktuálně neukládá obrázky dlouhodobě, ale používá pouze odkládání do paměti.
-
Používat pět hvězdiček pro hodnocení skladeb
Používat pět hvězdiček pro hodnocení skladeb
namísto jednoduchého jednohvězdičkového hodnocení.
diff --git a/ultrasonic/src/main/res/values-de/strings.xml b/ultrasonic/src/main/res/values-de/strings.xml
index ee0c65bf..aab075fe 100644
--- a/ultrasonic/src/main/res/values-de/strings.xml
+++ b/ultrasonic/src/main/res/values-de/strings.xml
@@ -376,19 +376,6 @@
Alle Titel nach Künstler sortieren
Einen neuen Eintrag in der Künstleransicht hinzufügen, um auf alle Lieder eines Künstlers zuzugreifen
Künstler zeigen
- Paralleles laden von Bildern
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
Mehrere Jahre
Server hinzufügen
@@ -434,8 +421,6 @@
Funktionseinstellungem
- Neuen Bild-Lader aktivieren
- Neuen Bild-Lader aktivieren. Bilder werden derzeit nur im Chache gespeichert.
Verwenden Sie Fünf-Sterne-Bewertung für Songs
Verwenden Sie Fünf-Sterne-Bewertungssystem für Songs
anstatt einfach Elemente zu markieren / zu entfernen.
diff --git a/ultrasonic/src/main/res/values-es/strings.xml b/ultrasonic/src/main/res/values-es/strings.xml
index 1225d7eb..35192166 100644
--- a/ultrasonic/src/main/res/values-es/strings.xml
+++ b/ultrasonic/src/main/res/values-es/strings.xml
@@ -393,19 +393,6 @@
Mostrar todas las canciones por artista
Añadir nueva entrada en la vista de artista para acceder a todas las canciones de un artista
Mostrar artista
- Concurrencia del cargador de imágenes
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
Caratula del Álbum
Múltiples años
Reanudar al conectar un dispositivo Bluetooth
@@ -503,10 +490,6 @@
Funciones experimentales
- Habilitar nuevo cargador de imágenes
- Permite la implementación de un nuevo cargador de imágenes.
- Actualmente no guarda la imagen en el almacenamiento del dispositivo y sólo utiliza caché en la memoria.
-
Use cinco estrellas para las canciones
Utilice el sistema de calificación de cinco estrellas para canciones
en lugar de simplemente destacar / desestimar elementos.
diff --git a/ultrasonic/src/main/res/values-fr/strings.xml b/ultrasonic/src/main/res/values-fr/strings.xml
index 8e9ba787..5bc3b7b7 100644
--- a/ultrasonic/src/main/res/values-fr/strings.xml
+++ b/ultrasonic/src/main/res/values-fr/strings.xml
@@ -381,19 +381,6 @@
Voir tous les titres par artiste
Ajouter une nouvelle entrée dans la vue artiste pour accéder à toutes les titres d\'un artiste
Afficher l\'artiste
- Chargements d’images simultanés
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
Pochette d\'album
Années multiples
Reprendre lorsqu’un appareil Bluetooth se connecte
@@ -491,10 +478,6 @@
Drapeaux des fonctionnalités
- Activer le nouveau chargeur d\'images
- Permet l\'implémentation d\'un nouveau chargeur d\'images.
- Actuellement, il n\'enregistre pas l\'image dans l\'appareil et n\'utilise que le cache en mémoire.
-
Utiliser les étoiles pour noter les morceaux
Utiliser un système de notation à base d\'étoiles pour les morceaux
au lieu de simplement mettre en avant les morceaux.
diff --git a/ultrasonic/src/main/res/values-hu/strings.xml b/ultrasonic/src/main/res/values-hu/strings.xml
index c913b846..5476f2c3 100644
--- a/ultrasonic/src/main/res/values-hu/strings.xml
+++ b/ultrasonic/src/main/res/values-hu/strings.xml
@@ -393,19 +393,6 @@
Az előadó összes dalának megjelenítése
Új bejegyzés hozzáadása az előadóhoz, az előadó összes dalának eléréséhez.
Ugrás az előadóhoz
- Image Loader Concurrency
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
albumArt
Több év
Folytatás Bluetooth eszköz csatlakozásakor
@@ -501,10 +488,6 @@
Jellemzők Zászlók
- Engedélyezzen új képbetöltőt
- Engedélyezi az új képbetöltő megvalósítását. Jelenleg nem
- tárolja a képet az eszköz tárolójában, és csak a memóriában tárolja a gyorsítótárat.
-
Öt csillagos értékelés használata a dalokhoz
Öt csillag használata az értékeléshez az egyszerű
csillaggal jelölés helyett.
diff --git a/ultrasonic/src/main/res/values-it/strings.xml b/ultrasonic/src/main/res/values-it/strings.xml
index 043368a8..f7031d63 100644
--- a/ultrasonic/src/main/res/values-it/strings.xml
+++ b/ultrasonic/src/main/res/values-it/strings.xml
@@ -338,18 +338,6 @@
Condividi canzoni via
MX Player
Predefinito
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1 canzone
- %d canzoni
diff --git a/ultrasonic/src/main/res/values-nl/strings.xml b/ultrasonic/src/main/res/values-nl/strings.xml
index 2b2da416..9cd9fd00 100644
--- a/ultrasonic/src/main/res/values-nl/strings.xml
+++ b/ultrasonic/src/main/res/values-nl/strings.xml
@@ -393,19 +393,6 @@
Alle nummers van artiest tonen
Item toevoegen in artiestweergave om alle nummers van een artiest te bekijken
Artiest tonen
- Aantal tegelijkertijd te laden afbeeldingen
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
Albumhoes
Meerdere jaren
Hervatten bij verbinding met bluetoothapparaat
@@ -503,10 +490,6 @@
Experimentele functies
- Nieuwe manier van afbeeldingen laden inschakelen
- Schakelt de nieuwe methode voor het laden van afbeeldingen in.
- Momenteel slaat het geen afbeeldingen op op de apparaatopslag en wordt alleen geheugencache gebruikt.
-
Gebruik vijf sterren voor nummers
Gebruik vijf sterren ratingsysteem voor liedjes
in plaats van items simpelweg in de hoofdrol te zetten / niet te verwijderen.
diff --git a/ultrasonic/src/main/res/values-pl/strings.xml b/ultrasonic/src/main/res/values-pl/strings.xml
index c09b4479..4f7b15d1 100644
--- a/ultrasonic/src/main/res/values-pl/strings.xml
+++ b/ultrasonic/src/main/res/values-pl/strings.xml
@@ -376,19 +376,6 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników
Wyświetlaj wszystkie utwory artysty
Dodaje nową pozycję w widoku artysty z wszystkimi jego utworami
Wyświetlaj artystę
- Ilość jednocześnie ładowanych obrazów
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
Okładka
Z różnych lat
Dodaj serwer
@@ -449,9 +436,6 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników
Flagi funkcji
- Włącz program ładujący nowe obrazy
- Włącza implementację modułu ładującego nowe obrazy.
-Obecnie nie zapisuje obrazów w pamięci urządzenia, tylko wykorzystuje tylko pamięć podręczną.
Użyj pięciu gwiazdek dla utworów
W przypadku utworów użyj systemu pięciu gwiazdek
zamiast po prostu grać gwiazdkami / bez gwiazd.
diff --git a/ultrasonic/src/main/res/values-pt-rBR/strings.xml b/ultrasonic/src/main/res/values-pt-rBR/strings.xml
index 826a45e4..903d7b6a 100644
--- a/ultrasonic/src/main/res/values-pt-rBR/strings.xml
+++ b/ultrasonic/src/main/res/values-pt-rBR/strings.xml
@@ -381,19 +381,6 @@
Mostrar Todas as Músicas por Artista
Adicionar nova entrada em artista para acessar todas as músicas do artista
Mostrar Artista
- Concorrência ao Carregar Imagens
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
albumArt
Anos Múltiplos
Retomar ao Conectar Dispositivo Bluetooth
@@ -489,10 +476,6 @@
Bandeiras de Recursos
- Ativar Novo Carregador de Imagens
- Permite nova implementação do carregador de imagens.
- Atualmente, ele não salva a imagem no armazenamento do dispositivo e usa apenas o cache na memória.
-
Usar Classif. de 5 estrelas Para Músicas
Use o sistema de classificação de 5 estrelas para músicas
em vez de simplesmente estrelar/não estrelar itens.
diff --git a/ultrasonic/src/main/res/values-pt/strings.xml b/ultrasonic/src/main/res/values-pt/strings.xml
index 74a7d3d4..8fb5bb12 100644
--- a/ultrasonic/src/main/res/values-pt/strings.xml
+++ b/ultrasonic/src/main/res/values-pt/strings.xml
@@ -376,19 +376,6 @@
Todas as Músicas do Artista
Adicionar nova entrada em artista para ver todas as músicas do artista
Mostrar Artista
- Concorrência ao Carregar Imagens
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
Múltiplos Anos
Adicionar Servidor
@@ -434,10 +421,6 @@
Bandeiras de recursos
- Ativar novo carregador de imagens
- Permite nova implementação do carregador de imagens.
- Atualmente, ele não salva a imagem no armazenamento do dispositivo e usa apenas o cache na memória.
-
Use classificação de cinco estrelas para músicas
Use o sistema de classificação de cinco estrelas para músicas
em vez de simplesmente estrelar / não estrelar itens.
diff --git a/ultrasonic/src/main/res/values-ru/strings.xml b/ultrasonic/src/main/res/values-ru/strings.xml
index 76d8f0c0..0897075f 100644
--- a/ultrasonic/src/main/res/values-ru/strings.xml
+++ b/ultrasonic/src/main/res/values-ru/strings.xml
@@ -368,19 +368,6 @@
Показать все треки исполнителя
Добавить новую запись в представлении исполнителя, чтобы получить доступ ко всем песням для исполнителя
Показать исполнителей
- Загрузчик совпадающих изображений
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
albumArt
Несколько лет
@@ -396,7 +383,4 @@
Флаги
- Включить новый загрузчик изображений
- Включает новую реализацию загрузчика изображений.
-В настоящее время он не сохраняет изображение в памяти устройства и использует только кэш в памяти.
diff --git a/ultrasonic/src/main/res/values-zh-rCN/strings.xml b/ultrasonic/src/main/res/values-zh-rCN/strings.xml
index 716b65f4..60067c12 100644
--- a/ultrasonic/src/main/res/values-zh-rCN/strings.xml
+++ b/ultrasonic/src/main/res/values-zh-rCN/strings.xml
@@ -264,18 +264,6 @@
MX Player
默认
分享
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
已禁用
删除文件
删除日志文件
@@ -307,5 +295,4 @@
版本不兼容,请升级 Ultrasonic 应用。
不兼容的版本。请升级Subsonic 服务。
- 启用新的图像加载器
diff --git a/ultrasonic/src/main/res/values/arrays.xml b/ultrasonic/src/main/res/values/arrays.xml
index edc5a09f..d18959e9 100644
--- a/ultrasonic/src/main/res/values/arrays.xml
+++ b/ultrasonic/src/main/res/values/arrays.xml
@@ -265,34 +265,6 @@
- @string/settings.share_hours
- @string/settings.share_days
-
- - @string/settings.image_loader_concurrency_1
- - @string/settings.image_loader_concurrency_2
- - @string/settings.image_loader_concurrency_3
- - @string/settings.image_loader_concurrency_4
- - @string/settings.image_loader_concurrency_5
- - @string/settings.image_loader_concurrency_6
- - @string/settings.image_loader_concurrency_7
- - @string/settings.image_loader_concurrency_8
- - @string/settings.image_loader_concurrency_9
- - @string/settings.image_loader_concurrency_10
- - @string/settings.image_loader_concurrency_11
- - @string/settings.image_loader_concurrency_12
-
-
- - 1
- - 2
- - 3
- - 4
- - 5
- - 6
- - 7
- - 8
- - 9
- - 10
- - 11
- - 12
-
- @string/settings.playback.bluetooth_all
- @string/settings.playback.bluetooth_a2dp
diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml
index f0733e73..62556828 100644
--- a/ultrasonic/src/main/res/values/strings.xml
+++ b/ultrasonic/src/main/res/values/strings.xml
@@ -395,19 +395,6 @@
Show All Songs By Artist
Add new entry in artist view to access all songs for an artist
Show Artist
- Image Loader Concurrency
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
albumArt
Multiple Years
http://example.com
@@ -506,10 +493,6 @@
Feature Flags
- Enable new image loader
- Enables new image loader implementation.
- Currently it doesn\'t save image in device storage and uses only cache in memory.
-
Use five star rating for songs
Use five star rating system for songs
instead of simply starring/unstarring items.
diff --git a/ultrasonic/src/main/res/xml/settings.xml b/ultrasonic/src/main/res/xml/settings.xml
index 716eb2f2..ce724706 100644
--- a/ultrasonic/src/main/res/xml/settings.xml
+++ b/ultrasonic/src/main/res/xml/settings.xml
@@ -64,13 +64,6 @@
a:key="viewRefresh"
a:title="@string/settings.view_refresh"
app:iconSpaceReserved="false"/>
-
-
()
- private val handler = AvatarRequestHandler(mockSubsonicApiClient)
+ private val mockApiClient: SubsonicAPIClient = mock()
+ private val handler = AvatarRequestHandler(mockApiClient)
@Test
fun `Should accept only cover art request`() {
@@ -34,7 +34,6 @@ class AvatarRequestHandlerTest {
fun `Should not accept random request uri`() {
val requestUri = Uri.Builder()
.scheme(SCHEME)
- .authority(AUTHORITY)
.appendPath("something")
.build()
@@ -60,10 +59,12 @@ class AvatarRequestHandlerTest {
apiError = null,
responseHttpCode = 200
)
- whenever(mockSubsonicApiClient.getAvatar(any()))
+ whenever(mockApiClient.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 72%
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..17dda53e 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,10 +1,6 @@
-package org.moire.ultrasonic.subsonic.loader.image
+package org.moire.ultrasonic.imageloader
import android.net.Uri
-import com.nhaarman.mockito_kotlin.any
-import com.nhaarman.mockito_kotlin.anyOrNull
-import com.nhaarman.mockito_kotlin.mock
-import com.nhaarman.mockito_kotlin.whenever
import com.squareup.picasso.Picasso
import com.squareup.picasso.Request
import java.io.IOException
@@ -14,14 +10,18 @@ import org.amshove.kluent.`should throw`
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
import org.moire.ultrasonic.api.subsonic.response.StreamResponse
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class CoverArtRequestHandlerTest {
- private val mockSubsonicApiClientMock = mock()
- private val handler = CoverArtRequestHandler(mockSubsonicApiClientMock)
+ private val mockApiClient: SubsonicAPIClient = mock()
+ private val handler = CoverArtRequestHandler(mockApiClient)
@Test
fun `Should accept only cover art request`() {
@@ -34,7 +34,6 @@ class CoverArtRequestHandlerTest {
fun `Should not accept random request uri`() {
val requestUri = Uri.Builder()
.scheme(SCHEME)
- .authority(AUTHORITY)
.appendPath("random")
.build()
@@ -56,8 +55,8 @@ class CoverArtRequestHandlerTest {
@Test
fun `Should throw IOException when request to api failed`() {
val streamResponse = StreamResponse(null, null, 500)
- whenever(mockSubsonicApiClientMock.getCoverArt(any(), anyOrNull()))
- .thenReturn(streamResponse)
+
+ whenever(mockApiClient.getCoverArt(any(), anyOrNull())).thenReturn(streamResponse)
val fail = {
handler.load(createLoadCoverArtRequest("some").buildRequest(), 0)
@@ -73,14 +72,16 @@ class CoverArtRequestHandlerTest {
apiError = null,
responseHttpCode = 200
)
- whenever(mockSubsonicApiClientMock.getCoverArt(any(), anyOrNull()))
- .thenReturn(streamResponse)
- val response = handler.load(createLoadCoverArtRequest("some").buildRequest(), 0)
+ whenever(mockApiClient.getCoverArt(any(), anyOrNull())).thenReturn(streamResponse)
+
+ val response = handler.load(
+ createLoadCoverArtRequest("some").buildRequest(), 0
+ )
response.loadedFrom `should be equal to` Picasso.LoadedFrom.NETWORK
response.source `should not be` null
}
- private fun Uri.buildRequest() = Request.Builder(this).build()
+ private fun Uri.buildRequest() = Request.Builder(this).stableKey("-1").build()
}
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 59%
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 b318d60d..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
@@ -11,15 +11,17 @@ class RequestCreatorTest {
@Test
fun `Should create valid load cover art request`() {
val entityId = "299"
- val expectedUri = Uri.parse("$SCHEME://$AUTHORITY/$COVER_ART_PATH?$QUERY_ID=$entityId")
+ val size = 100L
+ val expectedUri =
+ Uri.parse("$SCHEME:/$COVER_ART_PATH?$QUERY_ID=$entityId&$SIZE=$size")
- createLoadCoverArtRequest(entityId).compareTo(expectedUri).shouldBeEqualTo(0)
+ createLoadCoverArtRequest(entityId, size).compareTo(expectedUri).shouldBeEqualTo(0)
}
@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