Skip to content

Commit 81c2c58

Browse files
authored
Preprocessing of image/video uploads (#26)
* ADD: base preprocessor class and image preprocessor implementation * ADD: base interface to implement preprocessing functions * ADD: preprocess interface * ADD: preprocess implementations for image crop, rotate and dimensions limiter * ADD: upload preprocessor parameters to uploader methods * ADD: transcoding logic for video preprocessing * ADD: video transformation listener and preprocessing error handlers * REFACTOR: upload preprocessor classes and Gradle build config * ADD: view binding and upload preprocessors usage in the sample app * FIX: Gradle test task runtime issue * ADD: policy parameter to repository's upload method calls
1 parent 55ae95c commit 81c2c58

21 files changed

+485
-78
lines changed

.idea/kotlinc.xml

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.gradle

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
// Top-level build file where you can add configuration options common to all sub-projects/modules.
22

33
buildscript {
4-
ext.kotlin_version = '1.3.50'
4+
ext.kotlin_version = '1.8.0'
55
repositories {
66
google()
77
mavenCentral()
88
maven { url "https://jitpack.io" }
99
jcenter()
1010
}
1111
dependencies {
12-
classpath 'com.android.tools.build:gradle:3.6.4'
12+
classpath 'com.android.tools.build:gradle:4.2.2'
1313
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1414
classpath "org.jetbrains.dokka:dokka-gradle-plugin:0.9.18"
1515
// NOTE: Do not place your application dependencies here; they belong
1616
// in the individual module build.gradle files
1717

18-
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
19-
classpath 'com.dicedmelon.gradle:jacoco-android:0.1.4'
18+
classpath 'org.jacoco:org.jacoco.core:0.8.8'
2019
}
2120
}
2221

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#Thu Jun 08 16:25:53 IST 2023
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
4-
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
54
zipStoreBase=GRADLE_USER_HOME
65
zipStorePath=wrapper/dists
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip

imagekit/build.gradle

+50-24
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ apply plugin: 'kotlin-android'
44

55
apply plugin: 'kotlin-kapt'
66
apply plugin: 'maven-publish'
7+
apply plugin: 'jacoco'
78

89
group = 'com.github.imagekit-developer'
910

@@ -67,7 +68,7 @@ dependencies {
6768

6869
api 'io.reactivex.rxjava2:rxandroid:2.1.1'
6970

70-
api "com.google.dagger:dagger:$dagger_version"
71+
implementation "com.google.dagger:dagger:$dagger_version"
7172
kapt "com.google.dagger:dagger-compiler:$dagger_version"
7273
api "org.jetbrains.kotlin:kotlin-reflect:1.3.50"
7374

@@ -76,34 +77,59 @@ dependencies {
7677
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0'
7778
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
7879
testImplementation 'com.squareup.okhttp3:mockwebserver:3.12.0'
79-
compileOnly 'com.github.pengrad:jdk9-deps:1.0'
80+
// compileOnly 'com.github.pengrad:jdk9-deps:1.0'
81+
82+
implementation "com.linkedin.android.litr:litr:1.4.16"
8083
}
8184

8285
apply plugin: 'org.jetbrains.dokka'
83-
apply plugin: 'jacoco-android'
86+
//
87+
88+
jacoco {
89+
toolVersion "0.8.8"
90+
}
8491

8592
tasks.withType(Test) {
86-
jacoco.includeNoLocationClasses = true
93+
jacoco {
94+
includeNoLocationClasses = true
95+
excludes = ['jdk.internal.*']
96+
}
97+
finalizedBy jacocoTestReport
98+
}
99+
100+
tasks.register('jacocoTestCoverageVerification', JacocoCoverageVerification) {
101+
violationRules {
102+
rule {
103+
enabled = true
104+
element = 'CLASS'
105+
106+
}
107+
}
87108
}
88109

89-
jacocoAndroidUnitTestReport {
90-
csv.enabled false
91-
html.enabled true
92-
xml.enabled true
93-
excludes += ['**/R.class',
94-
'**/BR.class',
95-
'**/R$*.class',
96-
'**/*$ViewInjector*.*',
97-
'**/*$ViewBinder*.*',
98-
'**/BuildConfig.*',
99-
'android/**',
100-
'**/Manifest*.*',
101-
'**/*$Lambda$*.*', // Jacoco can not handle several "$" in class name.
102-
'**/*Module.*', // Modules for Dagger.
103-
'**/*Dagger*.*', // Dagger auto-generated code.
104-
'**/*MembersInjector*.*', // Dagger auto-generated code.
105-
'**/*_Provide*Factory*.*',
106-
'**/*_Factory.*', //Dagger auto-generated code
107-
'**/*$*$*.*' // Anonymous classes generated by kotlin
108-
]
110+
task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {
111+
reports {
112+
csv.enabled false
113+
html.enabled true
114+
xml.enabled true
115+
}
116+
classDirectories.setFrom(files(classDirectories.files.collect {
117+
fileTree(dir: it, excludes: [
118+
'**/R.class',
119+
'**/BR.class',
120+
'**/R$*.class',
121+
'**/*$ViewInjector*.*',
122+
'**/*$ViewBinder*.*',
123+
'**/BuildConfig.*',
124+
'android/**',
125+
'**/Manifest*.*',
126+
'**/*$Lambda$*.*', // Jacoco can not handle several "$" in class name.
127+
'**/*Module.*', // Modules for Dagger.
128+
'**/*Dagger*.*', // Dagger auto-generated code.
129+
'**/*MembersInjector*.*', // Dagger auto-generated code.
130+
'**/*_Provide*Factory*.*',
131+
'**/*_Factory.*', //Dagger auto-generated code
132+
'**/*$*$*.*' // Anonymous classes generated by kotlin
133+
])
134+
}))
109135
}

imagekit/src/main/java/com/imagekit/android/ImagekitUploader.kt

+116-15
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@ import android.os.PowerManager
1111
import com.imagekit.android.data.Repository
1212
import com.imagekit.android.entity.UploadError
1313
import com.imagekit.android.entity.UploadPolicy
14+
import com.imagekit.android.preprocess.ImageUploadPreprocessor
15+
import com.imagekit.android.preprocess.UploadPreprocessor
16+
import com.imagekit.android.preprocess.VideoUploadPreprocessor
1417
import com.imagekit.android.util.BitmapUtil.bitmapToFile
18+
import com.linkedin.android.litr.TransformationListener
19+
import com.linkedin.android.litr.analytics.TrackTransformationInfo
1520
import com.imagekit.android.util.LogUtil
1621
import java.io.File
22+
import java.io.IOException
1723
import javax.inject.Inject
1824

1925
class ImagekitUploader @Inject constructor(
@@ -56,25 +62,35 @@ class ImagekitUploader @Inject constructor(
5662
customCoordinates: String? = null,
5763
policy: UploadPolicy = ImageKit.getInstance().defaultUploadPolicy,
5864
responseFields: String? = null,
65+
preprocessor: ImageUploadPreprocessor<Bitmap>? = null,
5966
imageKitCallback: ImageKitCallback
6067
) {
6168
if (checkUploadPolicy(policy, imageKitCallback)) {
62-
mRepository.upload(
63-
bitmapToFile(
69+
try {
70+
val imageFile = preprocessor?.outputFile(file, fileName, context) ?: bitmapToFile(
6471
context,
6572
fileName,
66-
file
67-
),
68-
fileName,
69-
useUniqueFilename,
70-
tags,
71-
folder,
72-
isPrivateFile,
73-
customCoordinates,
74-
responseFields,
75-
policy,
76-
imageKitCallback
77-
)
73+
file,
74+
Bitmap.CompressFormat.PNG
75+
)
76+
return mRepository.upload(
77+
imageFile,
78+
fileName,
79+
useUniqueFilename,
80+
tags,
81+
folder,
82+
isPrivateFile,
83+
customCoordinates,
84+
responseFields,
85+
policy,
86+
imageKitCallback
87+
)
88+
} catch (e: IOException) {
89+
imageKitCallback.onError(UploadError(
90+
exception = true,
91+
message = context.getString(R.string.error_upload_preprocess)
92+
))
93+
}
7894
} else {
7995
LogUtil.logError("Upload failed! Upload Policy Violation!")
8096
}
@@ -115,6 +131,7 @@ class ImagekitUploader @Inject constructor(
115131
customCoordinates: String? = null,
116132
responseFields: String? = null,
117133
policy: UploadPolicy = ImageKit.getInstance().defaultUploadPolicy,
134+
preprocessor: UploadPreprocessor<File>? = null,
118135
imageKitCallback: ImageKitCallback
119136
) {
120137
if (checkUploadPolicy(policy, imageKitCallback)) {
@@ -127,7 +144,91 @@ class ImagekitUploader @Inject constructor(
127144
)
128145
return
129146
}
130-
return mRepository.upload(
147+
preprocessor?.let {
148+
when (it) {
149+
is ImageUploadPreprocessor -> {
150+
try {
151+
mRepository.upload(
152+
preprocessor.outputFile(file, fileName, context),
153+
fileName,
154+
useUniqueFilename,
155+
tags,
156+
folder,
157+
isPrivateFile,
158+
customCoordinates,
159+
responseFields,
160+
policy,
161+
imageKitCallback
162+
)
163+
} catch (e: Exception) {
164+
imageKitCallback.onError(UploadError(
165+
exception = true,
166+
message = context.getString(R.string.error_upload_preprocess)
167+
))
168+
}
169+
}
170+
is VideoUploadPreprocessor -> {
171+
var outputFile: File? = null
172+
(preprocessor as VideoUploadPreprocessor).listener = object : TransformationListener {
173+
override fun onStarted(id: String) {
174+
}
175+
176+
override fun onProgress(id: String, progress: Float) {
177+
}
178+
179+
override fun onCompleted(
180+
id: String,
181+
trackTransformationInfos: MutableList<TrackTransformationInfo>?
182+
) {
183+
mRepository.upload(
184+
outputFile!!,
185+
fileName,
186+
useUniqueFilename,
187+
tags,
188+
folder,
189+
isPrivateFile,
190+
customCoordinates,
191+
responseFields,
192+
policy,
193+
imageKitCallback
194+
)
195+
}
196+
197+
override fun onCancelled(
198+
id: String,
199+
trackTransformationInfos: MutableList<TrackTransformationInfo>?
200+
) {
201+
println("Process cancelled")
202+
imageKitCallback.onError(UploadError(
203+
exception = true,
204+
message = context.getString(R.string.error_upload_preprocess)
205+
))
206+
}
207+
208+
override fun onError(
209+
id: String,
210+
cause: Throwable?,
211+
trackTransformationInfos: MutableList<TrackTransformationInfo>?
212+
) {
213+
cause?.printStackTrace()
214+
imageKitCallback.onError(UploadError(
215+
exception = true,
216+
message = context.getString(R.string.error_upload_preprocess)
217+
))
218+
}
219+
}
220+
try {
221+
outputFile = preprocessor.outputFile(file, fileName, context)
222+
} catch (e: Exception) {
223+
e.printStackTrace()
224+
imageKitCallback.onError(UploadError(
225+
exception = true,
226+
message = context.getString(R.string.error_upload_preprocess)
227+
))
228+
}
229+
}
230+
}
231+
} ?: mRepository.upload(
131232
file,
132233
fileName,
133234
useUniqueFilename,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.imagekit.android.preprocess
2+
3+
import android.graphics.Bitmap
4+
import android.graphics.Point
5+
6+
internal class ImageCrop(
7+
private val topLeft: Point,
8+
private val bottomRight: Point
9+
) : Preprocess<Bitmap> {
10+
override fun process(source: Bitmap): Bitmap =
11+
Bitmap.createBitmap(
12+
source,
13+
topLeft.x,
14+
topLeft.y,
15+
bottomRight.x - topLeft.x,
16+
bottomRight.y - topLeft.y
17+
)
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.imagekit.android.preprocess
2+
3+
import android.graphics.Bitmap
4+
5+
internal class ImageDimensionsLimiter(
6+
private val maxWidth: Int,
7+
private val maxHeight: Int
8+
) : Preprocess<Bitmap> {
9+
override fun process(source: Bitmap): Bitmap =
10+
if (source.width <= maxWidth && source.height <= maxHeight) source
11+
else Bitmap.createScaledBitmap(
12+
source,
13+
source.width.coerceAtMost(maxWidth),
14+
source.height.coerceAtMost(maxHeight),
15+
true
16+
)
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.imagekit.android.preprocess
2+
3+
import android.graphics.Bitmap
4+
import android.graphics.Matrix
5+
6+
internal class ImageRotation(private val angle: Float) : Preprocess<Bitmap> {
7+
override fun process(source: Bitmap): Bitmap = Matrix().let {
8+
it.setRotate(angle)
9+
Bitmap.createBitmap(source, 0, 0, source.width, source.height, it, true)
10+
}
11+
}

0 commit comments

Comments
 (0)