Skip to content

Commit 6ced3a0

Browse files
authored
Add Glide integration (#111)
1 parent eb9b041 commit 6ced3a0

File tree

16 files changed

+409
-40
lines changed

16 files changed

+409
-40
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.cloudinary.android;
2+
3+
import com.cloudinary.Transformation;
4+
5+
/**
6+
* A class that is used for generating a cloudinary resource when creating an image request.
7+
*/
8+
public class CloudinaryRequest {
9+
10+
private String publicId;
11+
private Transformation transformation;
12+
private ResponsiveUrl responsive;
13+
14+
private CloudinaryRequest(String publicId, Transformation transformation, ResponsiveUrl responsive) {
15+
this.publicId = publicId;
16+
this.transformation = transformation;
17+
this.responsive = responsive;
18+
}
19+
20+
/**
21+
* Get the public id of the cloudinary resource
22+
*/
23+
public String getPublicId() {
24+
return publicId;
25+
}
26+
27+
/**
28+
* Get the transformation
29+
*/
30+
public Transformation getTransformation() {
31+
return transformation;
32+
}
33+
34+
/**
35+
* Get the responsive preset set for this resource.
36+
*/
37+
public ResponsiveUrl getResponsive() {
38+
return responsive;
39+
}
40+
41+
/**
42+
* Builder to construct an instance of {@link CloudinaryRequest}.
43+
*/
44+
public static class Builder {
45+
46+
private final String publicId;
47+
private Transformation transformation;
48+
private ResponsiveUrl responsive;
49+
50+
public Builder(String publicId) {
51+
this.publicId = publicId;
52+
}
53+
54+
/**
55+
* Set a transformation to be used when generating the resource.
56+
*/
57+
public Builder transformation(Transformation transformation) {
58+
this.transformation = transformation;
59+
return this;
60+
}
61+
62+
/**
63+
* Set a responsive url to be used when generating the resource.
64+
*/
65+
public Builder responsive(ResponsiveUrl responsiveUrl) {
66+
this.responsive = responsiveUrl;
67+
return this;
68+
}
69+
70+
/**
71+
* Set a responsive preset to be used when generating the resource.
72+
*/
73+
public Builder responsive(ResponsiveUrl.Preset responsivePreset) {
74+
this.responsive = MediaManager.get().responsiveUrl(responsivePreset);
75+
return this;
76+
}
77+
78+
/**
79+
* @return An instance of {@link CloudinaryRequest}.
80+
*/
81+
public CloudinaryRequest build() {
82+
return new CloudinaryRequest(publicId, transformation, responsive);
83+
}
84+
}
85+
}

core/src/main/java/com/cloudinary/android/ResponsiveUrl.java

+17-4
Original file line numberDiff line numberDiff line change
@@ -176,18 +176,31 @@ private boolean conditionsAreMet(int width, int height) {
176176
* @return The url with the responsive transformation.
177177
*/
178178
private Url buildUrl(View view, Url baseUrl) {
179+
int contentWidth = view.getWidth() - view.getPaddingLeft() - view.getPaddingRight();
180+
int contentHeight = view.getHeight() - view.getPaddingTop() - view.getPaddingBottom();
181+
182+
return buildUrl(baseUrl, contentWidth, contentHeight);
183+
}
184+
185+
/**
186+
* Construct the final url with the dimensions included as the last transformation in the url.
187+
*
188+
* @param baseUrl The base cloudinary Url to chain the transformation to.
189+
* @param width The width to adapt the image width to.
190+
* @param height The height to adapt the image height to.
191+
* @return The url with the responsive transformation.
192+
*/
193+
public Url buildUrl(Url baseUrl, int width, int height) {
179194
// add a new transformation on top of anything already there:
180195
Url url = baseUrl.clone();
181196
url.transformation().chain();
182197

183198
if (autoHeight) {
184-
int contentHeight = view.getHeight() - view.getPaddingTop() - view.getPaddingBottom();
185-
url.transformation().height(trimAndRoundUp(contentHeight));
199+
url.transformation().height(trimAndRoundUp(height));
186200
}
187201

188202
if (autoWidth) {
189-
int contentWidth = view.getWidth() - view.getPaddingLeft() - view.getPaddingRight();
190-
url.transformation().width(trimAndRoundUp(contentWidth));
203+
url.transformation().width(trimAndRoundUp(width));
191204
}
192205

193206
url.transformation().crop(cropMode).gravity(gravity);

glide-integration/build.gradle

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
apply plugin: 'com.android.library'
2+
3+
android {
4+
compileSdkVersion 29
5+
buildToolsVersion "29.0.2"
6+
7+
defaultConfig {
8+
minSdkVersion 14
9+
targetSdkVersion 29
10+
versionCode 1
11+
versionName "1.0"
12+
13+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14+
consumerProguardFiles 'consumer-rules.pro'
15+
}
16+
17+
buildTypes {
18+
release {
19+
minifyEnabled false
20+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21+
}
22+
}
23+
24+
}
25+
26+
dependencies {
27+
implementation project(':core')
28+
implementation 'com.github.bumptech.glide:glide:4.11.0'
29+
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
30+
31+
testImplementation 'junit:junit:4.12'
32+
androidTestImplementation 'androidx.test:runner:1.2.0'
33+
androidTestImplementation 'org.mockito:mockito-android:2.24.0'
34+
}
35+
36+
ext {
37+
publishArtifactId = 'cloudinary-android-glide-integration'
38+
publishArtifactName = 'Cloudinary Android Glide Integration Library'
39+
jarFileName = "glide-integration"
40+
}
41+
42+
apply from: '../publish.gradle'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.cloudinary.android.glide_integration;
2+
3+
import android.content.Context;
4+
5+
import com.bumptech.glide.load.Options;
6+
import com.bumptech.glide.load.model.GlideUrl;
7+
import com.bumptech.glide.load.model.ModelLoader;
8+
import com.cloudinary.Transformation;
9+
import com.cloudinary.android.CloudinaryRequest;
10+
import com.cloudinary.android.MediaManager;
11+
12+
import org.junit.Assert;
13+
import org.junit.Before;
14+
import org.junit.BeforeClass;
15+
import org.junit.Rule;
16+
import org.junit.Test;
17+
import org.junit.runner.RunWith;
18+
import org.mockito.ArgumentCaptor;
19+
import org.mockito.Captor;
20+
import org.mockito.Mock;
21+
import org.mockito.junit.MockitoJUnit;
22+
import org.mockito.junit.MockitoRule;
23+
24+
import java.io.InputStream;
25+
26+
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
27+
import androidx.test.platform.app.InstrumentationRegistry;
28+
29+
import static com.bumptech.glide.request.target.Target.SIZE_ORIGINAL;
30+
import static org.mockito.ArgumentMatchers.any;
31+
import static org.mockito.ArgumentMatchers.anyInt;
32+
import static org.mockito.Mockito.verify;
33+
34+
@RunWith(AndroidJUnit4ClassRunner.class)
35+
public class CloudinaryRequestModelLoaderTest {
36+
37+
private static final String TEST_CLOUD_NAME = "demo";
38+
private static final String TEST_PUBLIC_ID = "sample";
39+
40+
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
41+
@Mock private ModelLoader<GlideUrl, InputStream> urlLoader;
42+
@Captor private ArgumentCaptor<GlideUrl> captor;
43+
44+
private CloudinaryRequestModelLoader sut;
45+
46+
@BeforeClass
47+
public static void setUp() {
48+
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
49+
MediaManager.init(context);
50+
MediaManager.get().getCloudinary().config.cloudName = TEST_CLOUD_NAME;
51+
}
52+
53+
@Before
54+
public void initLoader() {
55+
sut = new CloudinaryRequestModelLoader(urlLoader);
56+
}
57+
58+
@Test
59+
public void testGeneratedUrl() {
60+
CloudinaryRequest model = new CloudinaryRequest.Builder(TEST_PUBLIC_ID).build();
61+
62+
sut.buildLoadData(model, SIZE_ORIGINAL, SIZE_ORIGINAL, new Options());
63+
64+
String expectedUrl = String.format("https://res.cloudinary.com/%s/image/upload/%s", TEST_CLOUD_NAME, TEST_PUBLIC_ID);
65+
verify(urlLoader).buildLoadData(captor.capture(), anyInt(), anyInt(), any(Options.class));
66+
Assert.assertEquals(expectedUrl, captor.getValue().toStringUrl());
67+
}
68+
69+
@Test
70+
public void testGeneratedUrlWithTransformation() {
71+
CloudinaryRequest model = new CloudinaryRequest.Builder(TEST_PUBLIC_ID)
72+
.transformation(new Transformation().width(200).height(400))
73+
.build();
74+
75+
sut.buildLoadData(model, SIZE_ORIGINAL, SIZE_ORIGINAL, new Options());
76+
77+
String expectedUrl = String.format("https://res.cloudinary.com/%s/image/upload/h_400,w_200/%s", TEST_CLOUD_NAME, TEST_PUBLIC_ID);
78+
verify(urlLoader).buildLoadData(captor.capture(), anyInt(), anyInt(), any(Options.class));
79+
Assert.assertEquals(expectedUrl, captor.getValue().toStringUrl());
80+
}
81+
82+
@Test
83+
public void testGeneratedUrlWithTransformationOption() {
84+
CloudinaryRequest model = new CloudinaryRequest.Builder(TEST_PUBLIC_ID).build();
85+
Options options = new Options().set(CloudinaryRequestModelLoader.TRANSFORMATION, new Transformation().width(200).height(400));
86+
87+
sut.buildLoadData(model, SIZE_ORIGINAL, SIZE_ORIGINAL, options);
88+
89+
String expectedUrl = String.format("https://res.cloudinary.com/%s/image/upload/h_400,w_200/%s", TEST_CLOUD_NAME, TEST_PUBLIC_ID);
90+
verify(urlLoader).buildLoadData(captor.capture(), anyInt(), anyInt(), any(Options.class));
91+
Assert.assertEquals(expectedUrl, captor.getValue().toStringUrl());
92+
}
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<manifest package="com.cloudinary.android.glide_integration" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.cloudinary.android.glide_integration;
2+
3+
import com.bumptech.glide.annotation.GlideExtension;
4+
import com.bumptech.glide.annotation.GlideOption;
5+
import com.bumptech.glide.request.BaseRequestOptions;
6+
import com.bumptech.glide.request.RequestOptions;
7+
import com.cloudinary.Transformation;
8+
import com.cloudinary.android.ResponsiveUrl;
9+
10+
import androidx.annotation.NonNull;
11+
12+
/**
13+
* Extension class which adds custom cloudinary options when building a request.
14+
*/
15+
@GlideExtension
16+
public class CloudinaryGlideExtension {
17+
18+
private CloudinaryGlideExtension() { }
19+
20+
@NonNull
21+
@GlideOption
22+
public static BaseRequestOptions<?> transformation(BaseRequestOptions<?> requestOptions, Transformation transformation) {
23+
RequestOptions options = new RequestOptions().set(CloudinaryRequestModelLoader.TRANSFORMATION, transformation);
24+
return requestOptions.apply(options);
25+
}
26+
27+
@NonNull
28+
@GlideOption
29+
public static BaseRequestOptions<?> responsive(BaseRequestOptions<?> requestOptions, ResponsiveUrl responsive) {
30+
RequestOptions options = new RequestOptions().set(CloudinaryRequestModelLoader.RESPONSIVE, responsive);
31+
return requestOptions.apply(options);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.cloudinary.android.glide_integration;
2+
3+
import android.content.Context;
4+
5+
import com.bumptech.glide.Glide;
6+
import com.bumptech.glide.Registry;
7+
import com.bumptech.glide.annotation.GlideModule;
8+
import com.bumptech.glide.module.LibraryGlideModule;
9+
import com.cloudinary.android.CloudinaryRequest;
10+
11+
import java.io.InputStream;
12+
13+
import androidx.annotation.NonNull;
14+
15+
@GlideModule
16+
public class CloudinaryLibraryGlideModule extends LibraryGlideModule {
17+
18+
@Override
19+
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
20+
registry.prepend(CloudinaryRequest.class, InputStream.class, new CloudinaryRequestModelLoader.Factory());
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.cloudinary.android.glide_integration;
2+
3+
import com.bumptech.glide.load.Option;
4+
import com.bumptech.glide.load.Options;
5+
import com.bumptech.glide.load.model.GlideUrl;
6+
import com.bumptech.glide.load.model.ModelLoader;
7+
import com.bumptech.glide.load.model.ModelLoaderFactory;
8+
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
9+
import com.cloudinary.Transformation;
10+
import com.cloudinary.Url;
11+
import com.cloudinary.android.CloudinaryRequest;
12+
import com.cloudinary.android.MediaManager;
13+
import com.cloudinary.android.ResponsiveUrl;
14+
15+
import java.io.InputStream;
16+
17+
import androidx.annotation.NonNull;
18+
import androidx.annotation.Nullable;
19+
20+
/**
21+
* A {@link ModelLoader} for translating a {@link CloudinaryRequest} into {@link java.io.InputStream} data.
22+
*/
23+
public class CloudinaryRequestModelLoader implements ModelLoader<CloudinaryRequest, InputStream> {
24+
25+
static final Option<Transformation> TRANSFORMATION =
26+
Option.memory("com.cloudinary.android.glide_cloudinary.CloudinaryRequestModelLoader.Transformation");
27+
static final Option<ResponsiveUrl> RESPONSIVE =
28+
Option.memory("com.cloudinary.android.glide_cloudinary.CloudinaryRequestModelLoader.Responsive");;
29+
30+
private ModelLoader<GlideUrl, InputStream> urlLoader;
31+
32+
public CloudinaryRequestModelLoader(ModelLoader<GlideUrl, InputStream> urlLoader) {
33+
this.urlLoader = urlLoader;
34+
}
35+
36+
@Nullable
37+
@Override
38+
public LoadData<InputStream> buildLoadData(@NonNull CloudinaryRequest model, int width, int height, @NonNull Options options) {
39+
Url url = MediaManager.get().url().publicId(model.getPublicId());
40+
41+
Transformation transformation = model.getTransformation();
42+
if (transformation == null) {
43+
transformation = options.get(TRANSFORMATION);
44+
}
45+
if (transformation != null) {
46+
url.transformation(transformation);
47+
}
48+
49+
ResponsiveUrl responsive = model.getResponsive();
50+
if (responsive == null) {
51+
responsive = options.get(RESPONSIVE);
52+
}
53+
if (responsive != null) {
54+
url = responsive.buildUrl(url, width, height);
55+
}
56+
57+
return urlLoader.buildLoadData(new GlideUrl(url.generate()), width, height, options);
58+
}
59+
60+
@Override
61+
public boolean handles(@NonNull CloudinaryRequest model) {
62+
return true;
63+
}
64+
65+
public static class Factory implements ModelLoaderFactory<CloudinaryRequest, InputStream> {
66+
67+
@NonNull
68+
@Override
69+
public ModelLoader<CloudinaryRequest, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
70+
return new CloudinaryRequestModelLoader(multiFactory.build(GlideUrl.class, InputStream.class));
71+
}
72+
73+
@Override
74+
public void teardown() {
75+
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)