Skip to content

Commit 8608d49

Browse files
authored
Add Download module (#114)
1 parent 6ced3a0 commit 8608d49

28 files changed

+985
-17
lines changed

all/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
api project(path: ':core')
3030
api project(path: ':preprocess')
3131
api project(path: ':ui')
32+
api project(path: ':download')
3233

3334
implementation 'androidx.appcompat:appcompat:1.1.0'
3435
testImplementation 'junit:junit:4.12'

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import com.cloudinary.android.callback.UploadCallback;
1616
import com.cloudinary.android.callback.UploadResult;
1717
import com.cloudinary.android.callback.UploadStatus;
18+
import com.cloudinary.android.download.DownloadRequestBuilder;
19+
import com.cloudinary.android.download.DownloadRequestBuilderFactory;
1820
import com.cloudinary.android.payload.ByteArrayPayload;
1921
import com.cloudinary.android.payload.FilePayload;
2022
import com.cloudinary.android.payload.LocalUriPayload;
@@ -57,6 +59,7 @@ public class MediaManager {
5759
private final ExecutorService executor;
5860

5961
private GlobalUploadPolicy globalUploadPolicy = GlobalUploadPolicy.defaultPolicy();
62+
private DownloadRequestBuilderFactory downloadRequestBuilderFactory;
6063

6164
private MediaManager(@NonNull Context context, @Nullable SignatureProvider signatureProvider, @Nullable Map config) {
6265
executor = new ThreadPoolExecutor(4, 4,
@@ -442,4 +445,26 @@ SignatureProvider getSignatureProvider() {
442445
void execute(Runnable runnable) {
443446
executor.execute(runnable);
444447
}
448+
449+
/**
450+
* Set a {@link DownloadRequestBuilderFactory} factory that will construct the
451+
* {@link DownloadRequestBuilder} instance, to be used when creating download requests
452+
* using {@link #download(Context)}.
453+
*/
454+
public void setDownloadRequestBuilderFactory(DownloadRequestBuilderFactory factory) {
455+
downloadRequestBuilderFactory = factory;
456+
}
457+
458+
/**
459+
* Create a new {@link DownloadRequestBuilder} to be used to create a download request.
460+
* @param context Android context
461+
* @return The {@link DownloadRequestBuilder} that will create the download request.
462+
*/
463+
public DownloadRequestBuilder download(@NonNull Context context) {
464+
if (downloadRequestBuilderFactory == null) {
465+
throw new IllegalStateException("Must set a factory before downloading.");
466+
}
467+
468+
return downloadRequestBuilderFactory.createDownloadRequestBuilder(context);
469+
}
445470
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.cloudinary.android.download;
2+
3+
/**
4+
* Represents an active download request (in progress).
5+
*/
6+
public interface DownloadRequest {
7+
8+
/**
9+
* Cancel the download request.
10+
*/
11+
void cancel();
12+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.cloudinary.android.download;
2+
3+
import android.graphics.drawable.Drawable;
4+
import android.widget.ImageView;
5+
6+
import com.cloudinary.Transformation;
7+
import com.cloudinary.android.ResponsiveUrl;
8+
9+
import androidx.annotation.DrawableRes;
10+
import androidx.annotation.IdRes;
11+
12+
/**
13+
* Builds a download request.
14+
*/
15+
public interface DownloadRequestBuilder {
16+
17+
/**
18+
* Load the request with a resource id.
19+
* @return Itself for chaining.
20+
*/
21+
DownloadRequestBuilder load(@IdRes int resource);
22+
23+
/**
24+
* Load the request with a String source. The source can either be a remote url, or a cloudinary publicId.
25+
* In the case of a remote url, all cloudinary related builder options will not take place.
26+
* @return Itself for chaining.
27+
*/
28+
DownloadRequestBuilder load(String source);
29+
30+
/**
31+
* Set a {@link Transformation} that will be used to generate the url with.
32+
* Only applies if {@link #load(String)} was called with a cloudinary publicId.
33+
* @return Itself for chaining.
34+
*/
35+
DownloadRequestBuilder transformation(Transformation transformation);
36+
37+
/**
38+
* Set a {@link ResponsiveUrl} that will be used to generate the url with.
39+
* Only applies if {@link #load(String)} was called with a cloudinary publicId.
40+
* @return Itself for chaining
41+
*/
42+
DownloadRequestBuilder responsive(ResponsiveUrl responsiveUrl);
43+
44+
/**
45+
* Set a {@link ResponsiveUrl.Preset} that will be used to generate the url with.
46+
* Only applies if {@link #load(String)} was called with a cloudinary publicId.
47+
* @return Itself for chaining
48+
*/
49+
DownloadRequestBuilder responsive(ResponsiveUrl.Preset responsivePreset);
50+
51+
/**
52+
* Sets an Android resource id for a {@link Drawable} resource to display while a resource is
53+
* loading.
54+
* @param resourceId The id of the resource to use as a placeholder
55+
* @return Itself for chaining.
56+
*/
57+
DownloadRequestBuilder placeholder(@DrawableRes int resourceId);
58+
59+
/**
60+
* Set a callback to be called for the result of the download request.
61+
* @param callback The callback to be called for the result of the download request.
62+
* @return Itself for chaining.
63+
*/
64+
DownloadRequestBuilder callback(DownloadRequestCallback callback);
65+
66+
/**
67+
* Set the target {@link ImageView} to load the resource into and start the operation.
68+
* @param imageView The {@link ImageView} the resource will be loaded into.
69+
* @return The dispatched request.
70+
*/
71+
DownloadRequest into(ImageView imageView);
72+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.cloudinary.android.download;
2+
3+
import android.content.Context;
4+
5+
/**
6+
* Constructs a {@link DownloadRequestBuilder} instance to be used for creating download requests.
7+
*/
8+
public interface DownloadRequestBuilderFactory {
9+
10+
/**
11+
* Create a {@link DownloadRequestBuilder} that will create the download requests.
12+
* @param context Android context.
13+
* @return The created {@link DownloadRequestBuilder}
14+
*/
15+
DownloadRequestBuilder createDownloadRequestBuilder(Context context);
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.cloudinary.android.download;
2+
3+
/**
4+
* A callback for the result of the download request.
5+
*/
6+
public interface DownloadRequestCallback {
7+
8+
/**
9+
* Called when a request completes successfully.
10+
*/
11+
void onSuccess();
12+
13+
/**
14+
* Called when a request failed.
15+
* @param t The error containing the information about why the request failed.
16+
*/
17+
void onFailure(Throwable t);
18+
}

download/build.gradle

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
multiDexEnabled true
16+
17+
manifestPlaceholders = [cloudinaryUrl: getCloudinaryUrl() ?: ""]
18+
}
19+
20+
buildTypes {
21+
release {
22+
minifyEnabled false
23+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24+
}
25+
}
26+
27+
}
28+
29+
dependencies {
30+
implementation project(':core')
31+
32+
compileOnly 'com.squareup.picasso:picasso:2.71828'
33+
compileOnly 'com.facebook.fresco:fresco:2.2.0'
34+
compileOnly 'com.github.bumptech.glide:glide:4.11.0'
35+
36+
testImplementation 'junit:junit:4.12'
37+
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
38+
androidTestImplementation 'androidx.test:runner:1.2.0'
39+
androidTestImplementation 'org.mockito:mockito-android:2.24.0'
40+
}
41+
42+
ext {
43+
publishArtifactId = 'cloudinary-android-download'
44+
publishArtifactName = 'Cloudinary Android Download Library'
45+
jarFileName = "download"
46+
}
47+
48+
apply from: '../publish.gradle'
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="com.cloudinary.android.download.test"
4+
android:versionCode="1"
5+
android:versionName="1.0">
6+
7+
<application>
8+
<uses-library android:name="android.test.runner" />
9+
<meta-data
10+
android:name="CLOUDINARY_URL"
11+
android:value="${cloudinaryUrl}" />
12+
</application>
13+
14+
</manifest>
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package com.cloudinary.android.download;
2+
3+
import android.content.Context;
4+
import android.widget.ImageView;
5+
import android.widget.LinearLayout;
6+
7+
import com.cloudinary.Transformation;
8+
import com.cloudinary.android.MediaManager;
9+
import com.cloudinary.android.ResponsiveUrl;
10+
import com.cloudinary.android.download.test.R;
11+
12+
import org.junit.Before;
13+
import org.junit.BeforeClass;
14+
import org.junit.Test;
15+
import org.junit.runner.RunWith;
16+
import org.mockito.Mock;
17+
import org.mockito.junit.MockitoJUnitRunner;
18+
19+
import androidx.test.platform.app.InstrumentationRegistry;
20+
21+
import static org.mockito.ArgumentMatchers.eq;
22+
import static org.mockito.Mockito.times;
23+
import static org.mockito.Mockito.verify;
24+
25+
@RunWith(MockitoJUnitRunner.class)
26+
public class DownloadRequestBuilderImplTest {
27+
28+
private static final String TEST_PUBLIC_ID = "sample";
29+
private static String cloudName;
30+
private static boolean initialized;
31+
32+
private DownloadRequestBuilderImpl sut;
33+
34+
@Mock
35+
private DownloadRequestBuilderStrategy downloadRequestBuilderStrategy;
36+
37+
@Mock
38+
private ImageView imageView;
39+
40+
@BeforeClass
41+
public synchronized static void setup() {
42+
if (!initialized) {
43+
MediaManager.init(InstrumentationRegistry.getInstrumentation().getTargetContext());
44+
cloudName = MediaManager.get().getCloudinary().config.cloudName;
45+
initialized = true;
46+
}
47+
}
48+
49+
@Before
50+
public void initSut() {
51+
Context context = InstrumentationRegistry.getInstrumentation().getContext();
52+
sut = new DownloadRequestBuilderImpl(context, downloadRequestBuilderStrategy);
53+
}
54+
55+
@Test
56+
public void testLoadResource() {
57+
int resource = R.drawable.old_logo;
58+
59+
sut.load(resource);
60+
sut.into(imageView);
61+
62+
verify(downloadRequestBuilderStrategy, times(1)).load(eq(resource));
63+
verify(downloadRequestBuilderStrategy, times(1)).into(imageView);
64+
}
65+
66+
@Test
67+
public void testLoadWithRemoteUrl() {
68+
String remoteUrl = String.format("https://res.cloudinary.com/%s/image/upload/%s", cloudName, TEST_PUBLIC_ID);
69+
70+
sut.load(remoteUrl);
71+
sut.into(imageView);
72+
73+
verify(downloadRequestBuilderStrategy, times(1)).load(eq(remoteUrl));
74+
verify(downloadRequestBuilderStrategy, times(1)).into(imageView);
75+
}
76+
77+
@Test
78+
public void testLoadWithGeneratedCloudinaryUrlSource() {
79+
sut.load(TEST_PUBLIC_ID);
80+
sut.into(imageView);
81+
82+
String expectedUrl = String.format("https://res.cloudinary.com/%s/image/upload/%s", cloudName, TEST_PUBLIC_ID);
83+
verify(downloadRequestBuilderStrategy, times(1)).load(eq(expectedUrl));
84+
verify(downloadRequestBuilderStrategy, times(1)).into(imageView);
85+
}
86+
87+
@Test
88+
public void testLoadWithGeneratedCloudinaryUrlSourceWithTransformation() {
89+
sut.load(TEST_PUBLIC_ID);
90+
sut.transformation(new Transformation().width(200).height(400));
91+
sut.into(imageView);
92+
93+
String expectedUrl = String.format("https://res.cloudinary.com/%s/image/upload/h_400,w_200/%s", cloudName, TEST_PUBLIC_ID);
94+
verify(downloadRequestBuilderStrategy, times(1)).load(eq(expectedUrl));
95+
verify(downloadRequestBuilderStrategy, times(1)).into(imageView);
96+
}
97+
98+
@Test
99+
public void testLoadWithResponsive() {
100+
Context context = InstrumentationRegistry.getInstrumentation().getContext();
101+
LinearLayout linearLayout = new LinearLayout(context);
102+
ImageView imageView = new ImageView(context);
103+
int width = 200;
104+
int height = 400;
105+
linearLayout.layout(0, 0, width, height);
106+
imageView.layout(0, 0, width, height);
107+
linearLayout.addView(imageView);
108+
109+
sut.load(TEST_PUBLIC_ID);
110+
sut.responsive(ResponsiveUrl.Preset.AUTO_FILL);
111+
sut.into(imageView);
112+
113+
String expectedUrl = String.format("https://res.cloudinary.com/%s/image/upload/c_fill,g_auto,h_%d,w_%d/%s", cloudName, height, width, TEST_PUBLIC_ID);
114+
verify(downloadRequestBuilderStrategy, times(1)).load(eq(expectedUrl));
115+
verify(downloadRequestBuilderStrategy, times(1)).into(imageView);
116+
}
117+
118+
@Test
119+
public void testRequestBuiltWithPlaceholder() {
120+
int placeholder = R.drawable.old_logo;
121+
122+
sut.load(TEST_PUBLIC_ID);
123+
sut.placeholder(placeholder);
124+
sut.into(imageView);
125+
126+
verify(downloadRequestBuilderStrategy, times(1)).placeholder(eq(placeholder));
127+
verify(downloadRequestBuilderStrategy, times(1)).into(imageView);
128+
}
129+
}

0 commit comments

Comments
 (0)