Skip to content

Glide Integration #111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions core/src/main/java/com/cloudinary/android/CloudinaryRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.cloudinary.android;

import com.cloudinary.Transformation;

/**
* A class that is used for generating a cloudinary resource when creating an image request.
*/
public class CloudinaryRequest {

private String publicId;
private Transformation transformation;
private ResponsiveUrl responsive;

private CloudinaryRequest(String publicId, Transformation transformation, ResponsiveUrl responsive) {
this.publicId = publicId;
this.transformation = transformation;
this.responsive = responsive;
}

/**
* Get the public id of the cloudinary resource
*/
public String getPublicId() {
return publicId;
}

/**
* Get the transformation
*/
public Transformation getTransformation() {
return transformation;
}

/**
* Get the responsive preset set for this resource.
*/
public ResponsiveUrl getResponsive() {
return responsive;
}

/**
* Builder to construct an instance of {@link CloudinaryRequest}.
*/
public static class Builder {

private final String publicId;
private Transformation transformation;
private ResponsiveUrl responsive;

public Builder(String publicId) {
this.publicId = publicId;
}

/**
* Set a transformation to be used when generating the resource.
*/
public Builder transformation(Transformation transformation) {
this.transformation = transformation;
return this;
}

/**
* Set a responsive url to be used when generating the resource.
*/
public Builder responsive(ResponsiveUrl responsiveUrl) {
this.responsive = responsiveUrl;
return this;
}

/**
* Set a responsive preset to be used when generating the resource.
*/
public Builder responsive(ResponsiveUrl.Preset responsivePreset) {
this.responsive = MediaManager.get().responsiveUrl(responsivePreset);
return this;
}

/**
* @return An instance of {@link CloudinaryRequest}.
*/
public CloudinaryRequest build() {
return new CloudinaryRequest(publicId, transformation, responsive);
}
}
}
21 changes: 17 additions & 4 deletions core/src/main/java/com/cloudinary/android/ResponsiveUrl.java
Original file line number Diff line number Diff line change
Expand Up @@ -176,18 +176,31 @@ private boolean conditionsAreMet(int width, int height) {
* @return The url with the responsive transformation.
*/
private Url buildUrl(View view, Url baseUrl) {
int contentWidth = view.getWidth() - view.getPaddingLeft() - view.getPaddingRight();
int contentHeight = view.getHeight() - view.getPaddingTop() - view.getPaddingBottom();

return buildUrl(baseUrl, contentWidth, contentHeight);
}

/**
* Construct the final url with the dimensions included as the last transformation in the url.
*
* @param baseUrl The base cloudinary Url to chain the transformation to.
* @param width The width to adapt the image width to.
* @param height The height to adapt the image height to.
* @return The url with the responsive transformation.
*/
public Url buildUrl(Url baseUrl, int width, int height) {
// add a new transformation on top of anything already there:
Url url = baseUrl.clone();
url.transformation().chain();

if (autoHeight) {
int contentHeight = view.getHeight() - view.getPaddingTop() - view.getPaddingBottom();
url.transformation().height(trimAndRoundUp(contentHeight));
url.transformation().height(trimAndRoundUp(height));
}

if (autoWidth) {
int contentWidth = view.getWidth() - view.getPaddingLeft() - view.getPaddingRight();
url.transformation().width(trimAndRoundUp(contentWidth));
url.transformation().width(trimAndRoundUp(width));
}

url.transformation().crop(cropMode).gravity(gravity);
Expand Down
42 changes: 42 additions & 0 deletions glide-integration/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
apply plugin: 'com.android.library'

android {
compileSdkVersion 29
buildToolsVersion "29.0.2"

defaultConfig {
minSdkVersion 14
targetSdkVersion 29
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

}

dependencies {
implementation project(':core')
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'

testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'org.mockito:mockito-android:2.24.0'
}

ext {
publishArtifactId = 'cloudinary-android-glide-integration'
publishArtifactName = 'Cloudinary Android Glide Integration Library'
jarFileName = "glide-integration"
}

apply from: '../publish.gradle'
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.cloudinary.android.glide_integration;

import android.content.Context;

import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.cloudinary.Transformation;
import com.cloudinary.android.CloudinaryRequest;
import com.cloudinary.android.MediaManager;

import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.io.InputStream;

import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
import androidx.test.platform.app.InstrumentationRegistry;

import static com.bumptech.glide.request.target.Target.SIZE_ORIGINAL;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.verify;

@RunWith(AndroidJUnit4ClassRunner.class)
public class CloudinaryRequestModelLoaderTest {

private static final String TEST_CLOUD_NAME = "demo";
private static final String TEST_PUBLIC_ID = "sample";

@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
@Mock private ModelLoader<GlideUrl, InputStream> urlLoader;
@Captor private ArgumentCaptor<GlideUrl> captor;

private CloudinaryRequestModelLoader sut;

@BeforeClass
public static void setUp() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
MediaManager.init(context);
MediaManager.get().getCloudinary().config.cloudName = TEST_CLOUD_NAME;
}

@Before
public void initLoader() {
sut = new CloudinaryRequestModelLoader(urlLoader);
}

@Test
public void testGeneratedUrl() {
CloudinaryRequest model = new CloudinaryRequest.Builder(TEST_PUBLIC_ID).build();

sut.buildLoadData(model, SIZE_ORIGINAL, SIZE_ORIGINAL, new Options());

String expectedUrl = String.format("https://res.cloudinary.com/%s/image/upload/%s", TEST_CLOUD_NAME, TEST_PUBLIC_ID);
verify(urlLoader).buildLoadData(captor.capture(), anyInt(), anyInt(), any(Options.class));
Assert.assertEquals(expectedUrl, captor.getValue().toStringUrl());
}

@Test
public void testGeneratedUrlWithTransformation() {
CloudinaryRequest model = new CloudinaryRequest.Builder(TEST_PUBLIC_ID)
.transformation(new Transformation().width(200).height(400))
.build();

sut.buildLoadData(model, SIZE_ORIGINAL, SIZE_ORIGINAL, new Options());

String expectedUrl = String.format("https://res.cloudinary.com/%s/image/upload/h_400,w_200/%s", TEST_CLOUD_NAME, TEST_PUBLIC_ID);
verify(urlLoader).buildLoadData(captor.capture(), anyInt(), anyInt(), any(Options.class));
Assert.assertEquals(expectedUrl, captor.getValue().toStringUrl());
}

@Test
public void testGeneratedUrlWithTransformationOption() {
CloudinaryRequest model = new CloudinaryRequest.Builder(TEST_PUBLIC_ID).build();
Options options = new Options().set(CloudinaryRequestModelLoader.TRANSFORMATION, new Transformation().width(200).height(400));

sut.buildLoadData(model, SIZE_ORIGINAL, SIZE_ORIGINAL, options);

String expectedUrl = String.format("https://res.cloudinary.com/%s/image/upload/h_400,w_200/%s", TEST_CLOUD_NAME, TEST_PUBLIC_ID);
verify(urlLoader).buildLoadData(captor.capture(), anyInt(), anyInt(), any(Options.class));
Assert.assertEquals(expectedUrl, captor.getValue().toStringUrl());
}
}
1 change: 1 addition & 0 deletions glide-integration/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest package="com.cloudinary.android.glide_integration" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.cloudinary.android.glide_integration;

import com.bumptech.glide.annotation.GlideExtension;
import com.bumptech.glide.annotation.GlideOption;
import com.bumptech.glide.request.BaseRequestOptions;
import com.bumptech.glide.request.RequestOptions;
import com.cloudinary.Transformation;
import com.cloudinary.android.ResponsiveUrl;

import androidx.annotation.NonNull;

/**
* Extension class which adds custom cloudinary options when building a request.
*/
@GlideExtension
public class CloudinaryGlideExtension {

private CloudinaryGlideExtension() { }

@NonNull
@GlideOption
public static BaseRequestOptions<?> transformation(BaseRequestOptions<?> requestOptions, Transformation transformation) {
RequestOptions options = new RequestOptions().set(CloudinaryRequestModelLoader.TRANSFORMATION, transformation);
return requestOptions.apply(options);
}

@NonNull
@GlideOption
public static BaseRequestOptions<?> responsive(BaseRequestOptions<?> requestOptions, ResponsiveUrl responsive) {
RequestOptions options = new RequestOptions().set(CloudinaryRequestModelLoader.RESPONSIVE, responsive);
return requestOptions.apply(options);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.cloudinary.android.glide_integration;

import android.content.Context;

import com.bumptech.glide.Glide;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.LibraryGlideModule;
import com.cloudinary.android.CloudinaryRequest;

import java.io.InputStream;

import androidx.annotation.NonNull;

@GlideModule
public class CloudinaryLibraryGlideModule extends LibraryGlideModule {

@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
registry.prepend(CloudinaryRequest.class, InputStream.class, new CloudinaryRequestModelLoader.Factory());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.cloudinary.android.glide_integration;

import com.bumptech.glide.load.Option;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import com.cloudinary.Transformation;
import com.cloudinary.Url;
import com.cloudinary.android.CloudinaryRequest;
import com.cloudinary.android.MediaManager;
import com.cloudinary.android.ResponsiveUrl;

import java.io.InputStream;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
* A {@link ModelLoader} for translating a {@link CloudinaryRequest} into {@link java.io.InputStream} data.
*/
public class CloudinaryRequestModelLoader implements ModelLoader<CloudinaryRequest, InputStream> {

static final Option<Transformation> TRANSFORMATION =
Option.memory("com.cloudinary.android.glide_cloudinary.CloudinaryRequestModelLoader.Transformation");
static final Option<ResponsiveUrl> RESPONSIVE =
Option.memory("com.cloudinary.android.glide_cloudinary.CloudinaryRequestModelLoader.Responsive");;

private ModelLoader<GlideUrl, InputStream> urlLoader;

public CloudinaryRequestModelLoader(ModelLoader<GlideUrl, InputStream> urlLoader) {
this.urlLoader = urlLoader;
}

@Nullable
@Override
public LoadData<InputStream> buildLoadData(@NonNull CloudinaryRequest model, int width, int height, @NonNull Options options) {
Url url = MediaManager.get().url().publicId(model.getPublicId());

Transformation transformation = model.getTransformation();
if (transformation == null) {
transformation = options.get(TRANSFORMATION);
}
if (transformation != null) {
url.transformation(transformation);
}

ResponsiveUrl responsive = model.getResponsive();
if (responsive == null) {
responsive = options.get(RESPONSIVE);
}
if (responsive != null) {
url = responsive.buildUrl(url, width, height);
}

return urlLoader.buildLoadData(new GlideUrl(url.generate()), width, height, options);
}

@Override
public boolean handles(@NonNull CloudinaryRequest model) {
return true;
}

public static class Factory implements ModelLoaderFactory<CloudinaryRequest, InputStream> {

@NonNull
@Override
public ModelLoader<CloudinaryRequest, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
return new CloudinaryRequestModelLoader(multiFactory.build(GlideUrl.class, InputStream.class));
}

@Override
public void teardown() {

}
}
}
Loading