diff --git a/build.gradle b/build.gradle index ee4dceb..716f076 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { organization = 'twocoders' - projectWebsite = 'https://github.com/Two-Coders/android-dynamic-componets/' + projectWebsite = 'https://github.com/Two-Coders/android-dynamic-components/' projectDescription = 'Project containing useful Dynamic components.' projectLicences = ['MIT'] @@ -11,14 +11,21 @@ buildscript { androidTargerSdkVersion = androidCompileSdkVersion libDynamicColorVersion = '1.0.0' + libDynamicImageCoilVersion = '1.0.0' + libDynamicImageGlideVersion = '1.0.0' + libDynamicImagePicassoVersion = '1.0.0' libDynamicTextVersion = '3.0.0' kotlinVersion = '1.4.21' androidXCoreVersion = '1.3.2' annotationVersion = '1.1.0' commonExtensionsVersion = '1.0.0' + coilVersion = '1.1.0' + glideVersion = '4.11.0' + picassoVersion = '2.71828' junitVersion = '4.13.1' + mockitoVersion = '3.7.0' androidTestRunnerVersion = '1.3.0' androidJunitVersion = '1.1.2' } diff --git a/dynamic/color/build.gradle b/dynamic/color/build.gradle index ca88d4c..b0cb286 100644 --- a/dynamic/color/build.gradle +++ b/dynamic/color/build.gradle @@ -17,12 +17,6 @@ android { consumerProguardFiles 'consumer-rules.pro' } - buildTypes { - release { - minifyEnabled false - } - } - buildFeatures { dataBinding = true } diff --git a/dynamic/color/src/main/java/com/twocoders/dynamic/color/DynamicColor.kt b/dynamic/color/src/main/java/com/twocoders/dynamic/color/DynamicColor.kt index 26af110..e3c4014 100644 --- a/dynamic/color/src/main/java/com/twocoders/dynamic/color/DynamicColor.kt +++ b/dynamic/color/src/main/java/com/twocoders/dynamic/color/DynamicColor.kt @@ -91,9 +91,7 @@ open class DynamicColor : Parcelable { parcel.writeInt(attrRes ?: NO_ID) } - override fun describeContents(): Int { - return 0 - } + override fun describeContents() = 0 override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/dynamic/color/src/main/res/values/strings.xml b/dynamic/color/src/main/res/values/strings.xml deleted file mode 100644 index 3b233f0..0000000 --- a/dynamic/color/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Dynamic Color - \ No newline at end of file diff --git a/dynamic/image-base/.gitignore b/dynamic/image-base/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/dynamic/image-base/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/dynamic/image-base/build.gradle b/dynamic/image-base/build.gradle new file mode 100644 index 0000000..0ee0421 --- /dev/null +++ b/dynamic/image-base/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' + +android { + compileSdkVersion androidCompileSdkVersion + + defaultConfig { + minSdkVersion androidMinSdkVersion + targetSdkVersion androidTargerSdkVersion + + consumerProguardFiles 'consumer-rules.pro' + } + + buildFeatures { + dataBinding = true + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" + implementation "androidx.annotation:annotation:$annotationVersion" + implementation "com.twocoders.extensions:common:$commonExtensionsVersion" +} diff --git a/dynamic/image-base/consumer-rules.pro b/dynamic/image-base/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/dynamic/image-base/src/main/AndroidManifest.xml b/dynamic/image-base/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ae67ed8 --- /dev/null +++ b/dynamic/image-base/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dynamic/image-base/src/main/java/com/twocoders/dynamic/image/base/BaseDynamicImage.kt b/dynamic/image-base/src/main/java/com/twocoders/dynamic/image/base/BaseDynamicImage.kt new file mode 100644 index 0000000..60f3e8a --- /dev/null +++ b/dynamic/image-base/src/main/java/com/twocoders/dynamic/image/base/BaseDynamicImage.kt @@ -0,0 +1,139 @@ +package com.twocoders.dynamic.image.base + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.os.Parcel +import android.os.Parcelable +import android.widget.ImageView +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import com.twocoders.dynamic.image.base.component.UriComponent +import com.twocoders.extensions.common.NO_ID +import com.twocoders.extensions.common.getDrawable +import com.twocoders.extensions.common.logd + +/** + * + * Handy class which can be used to bind image data to views. + * Data can be in [DrawableRes], [Drawable], or [UriComponent] format. + */ +@Suppress("unused", "MemberVisibilityCanBePrivate") +abstract class BaseDynamicImage : Parcelable { + + @DrawableRes protected val imageRes: Int? + protected val imageDrawable: Drawable? + protected val imageUri: UriComponent? + + protected constructor( + @ColorInt imageRes: Int? = null, + imageDrawable: Drawable? = null, + imageUri: UriComponent? = null + ) { + this.imageRes = imageRes + this.imageDrawable = imageDrawable + this.imageUri = imageUri + } + + protected constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.readDrawable(), + parcel.readParcelable(UriComponent::class.java.classLoader) + ) + + protected abstract suspend fun getDrawableFromUri( + context: Context, + imageUriComponent: UriComponent + ): Drawable? + + protected abstract fun getDrawableFromUri( + context: Context, + imageUriComponent: UriComponent, + callback: (drawable: Drawable) -> Unit + ) + + open suspend fun getDrawable(context: Context): Drawable? { + imageUri?.let { imageUriComponent -> + return getDrawableFromUri(context, imageUriComponent) + } + + imageDrawable?.let { drawable -> + return drawable + } + + imageRes?.let { res -> + return context.getDrawable(drawableResId = res) + } + + return null + } + + open fun getDrawable(context: Context, callback: (drawable: Drawable?) -> Unit) { + if (isEmpty()) { + callback(null) + return + } + + imageUri?.let { imageUriComponent -> + getDrawableFromUri(context, imageUriComponent, callback) + } + + imageDrawable?.let { drawable -> + callback(drawable) + } + + imageRes?.let { res -> + context.getDrawable(drawableResId = res)?.let { callback(it) } + } + } + + abstract fun loadDrawableInto(imageView: ImageView, withCrossFade: Boolean = false) + + fun isEmpty() = imageRes == null && imageDrawable == null && imageUri == null + + fun isNotEmpty() = !isEmpty() + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(imageRes ?: NO_ID) + parcel.writeParcelable(imageDrawable.getParcelable(), flags) + parcel.writeParcelable(imageUri, flags) + } + + override fun describeContents() = 0 + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as BaseDynamicImage + + if (imageRes != other.imageRes) return false + if (imageDrawable != other.imageDrawable) return false + if (imageUri != other.imageUri) return false + + return true + } + + override fun hashCode(): Int { + var result = imageRes ?: NO_ID + result = 31 * result + imageDrawable.hashCode() + result = 31 * result + imageUri.hashCode() + return result + } +} + +private fun Parcel.readDrawable(): Drawable? = + when (val parcelDrawable = readParcelable(BaseDynamicImage::class.java.classLoader)) { + is Bitmap -> @Suppress("DEPRECATION") BitmapDrawable(parcelDrawable) + else -> null + } + +private fun Drawable?.getParcelable(): Parcelable? = when (this) { + is BitmapDrawable -> bitmap + is Drawable -> { + logd("Unsupported $this in imageDrawable for parcel, only BitmapDrawable is supported now.") + null + } + else -> null +} \ No newline at end of file diff --git a/dynamic/image-base/src/main/java/com/twocoders/dynamic/image/base/DynamicImageBindingAdapter.kt b/dynamic/image-base/src/main/java/com/twocoders/dynamic/image/base/DynamicImageBindingAdapter.kt new file mode 100644 index 0000000..a9bce11 --- /dev/null +++ b/dynamic/image-base/src/main/java/com/twocoders/dynamic/image/base/DynamicImageBindingAdapter.kt @@ -0,0 +1,8 @@ +package com.twocoders.dynamic.image.base + +import android.widget.ImageView +import androidx.databinding.BindingAdapter + +@BindingAdapter(value = ["android:src", "loadWithCrossFade"], requireAll = false) +fun ImageView.loadDynamicImage(image: BaseDynamicImage, withCrossFade: Boolean = false) = + image.loadDrawableInto(this, withCrossFade) \ No newline at end of file diff --git a/dynamic/image-base/src/main/java/com/twocoders/dynamic/image/base/component/UriComponent.kt b/dynamic/image-base/src/main/java/com/twocoders/dynamic/image/base/component/UriComponent.kt new file mode 100755 index 0000000..00a68a7 --- /dev/null +++ b/dynamic/image-base/src/main/java/com/twocoders/dynamic/image/base/component/UriComponent.kt @@ -0,0 +1,33 @@ +package com.twocoders.dynamic.image.base.component + +import android.net.Uri +import android.os.Parcel +import android.os.Parcelable +import androidx.annotation.DrawableRes +import com.twocoders.extensions.common.NO_ID + +data class UriComponent( + val image: Uri, + @DrawableRes val errorImage: Int? = null, + @DrawableRes val placeholderImage: Int? = null +) : Parcelable { + + constructor(parcel: Parcel) : this( + parcel.readParcelable(Uri::class.java.classLoader)!!, + parcel.readInt(), + parcel.readInt() + ) + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = UriComponent(parcel) + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeParcelable(image, flags) + parcel.writeInt(errorImage ?: NO_ID) + parcel.writeInt(placeholderImage ?: NO_ID) + } + + override fun describeContents() = 0 +} \ No newline at end of file diff --git a/dynamic/image-coil/.gitignore b/dynamic/image-coil/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/dynamic/image-coil/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/dynamic/image-coil/build.gradle b/dynamic/image-coil/build.gradle new file mode 100644 index 0000000..509a121 --- /dev/null +++ b/dynamic/image-coil/build.gradle @@ -0,0 +1,58 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' + +ext.bintrayPublishVersion = libDynamicImageCoilVersion +apply from: '../../bintray-publish-config.gradle' + +android { + compileSdkVersion androidCompileSdkVersion + + defaultConfig { + minSdkVersion androidMinSdkVersion + targetSdkVersion androidTargerSdkVersion + versionName libDynamicImageCoilVersion + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildFeatures { + dataBinding = true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + + testOptions { + unitTests { + returnDefaultValues = true + includeAndroidResources = true + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + api project(':dynamic:image-base') + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" + implementation "androidx.annotation:annotation:$annotationVersion" + implementation "androidx.core:core-ktx:$androidXCoreVersion" + implementation "com.twocoders.extensions:common:$commonExtensionsVersion" + + implementation "io.coil-kt:coil:$coilVersion" + + androidTestImplementation "androidx.test:runner:$androidTestRunnerVersion" + androidTestImplementation "androidx.test.ext:junit:$androidJunitVersion" + androidTestImplementation "org.mockito:mockito-android:$mockitoVersion" + + testImplementation "junit:junit:$junitVersion" +} diff --git a/dynamic/image-coil/consumer-rules.pro b/dynamic/image-coil/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/dynamic/image-coil/src/androidTest/java/com/twocoders/dynamic/image/coil/DynamicImageCoilTest.kt b/dynamic/image-coil/src/androidTest/java/com/twocoders/dynamic/image/coil/DynamicImageCoilTest.kt new file mode 100644 index 0000000..e54d2ef --- /dev/null +++ b/dynamic/image-coil/src/androidTest/java/com/twocoders/dynamic/image/coil/DynamicImageCoilTest.kt @@ -0,0 +1,58 @@ +package com.twocoders.dynamic.image.coil + +import android.content.Context +import android.graphics.drawable.BitmapDrawable +import android.widget.ImageView +import androidx.annotation.DrawableRes +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.twocoders.extensions.common.getDrawable +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.* + +@RunWith(AndroidJUnit4::class) +class DynamicImageCoilTest { + + private lateinit var context: Context + + @Before + fun setUp() { + context = InstrumentationRegistry.getInstrumentation().context + } + + @Test + fun emptyDynamicImageReturnsNullDrawable() { + val emptyDynamicImage = DynamicImage.EMPTY + + emptyDynamicImage.getDrawable(context) { assertNull(it) } + + GlobalScope.launch { assertNull(emptyDynamicImage.getDrawable(context)) } + + val imageViewMock = mock(ImageView::class.java) + emptyDynamicImage.loadDrawableInto(imageViewMock) + verify(imageViewMock, never()).setImageDrawable(any()) + } + + @Test + fun dynamicImageFromDrawableRes() { + @DrawableRes val testedImageRes = android.R.drawable.ic_delete + val testedDynamicImage = DynamicImage.from(testedImageRes) + val expectedDrawable = context.getDrawable(drawableResId = testedImageRes) as BitmapDrawable + val expectedBitmap = expectedDrawable.bitmap + + testedDynamicImage.getDrawable(context) { assertEquals(expectedBitmap, (it as BitmapDrawable).bitmap ) } + + GlobalScope.launch { assertEquals(expectedBitmap, (testedDynamicImage.getDrawable(context) as BitmapDrawable).bitmap) } + + val imageViewMock = mock(ImageView::class.java) + `when`(imageViewMock.context).thenReturn(context) + testedDynamicImage.loadDrawableInto(imageViewMock) + verify(imageViewMock).setImageDrawable(expectedDrawable) + } +} \ No newline at end of file diff --git a/dynamic/image-coil/src/main/AndroidManifest.xml b/dynamic/image-coil/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ea98b05 --- /dev/null +++ b/dynamic/image-coil/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dynamic/image-coil/src/main/java/com/twocoders/dynamic/image/coil/DynamicImage.kt b/dynamic/image-coil/src/main/java/com/twocoders/dynamic/image/coil/DynamicImage.kt new file mode 100644 index 0000000..cbff9e2 --- /dev/null +++ b/dynamic/image-coil/src/main/java/com/twocoders/dynamic/image/coil/DynamicImage.kt @@ -0,0 +1,123 @@ +package com.twocoders.dynamic.image.coil + +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.VectorDrawable +import android.os.Parcel +import android.os.Parcelable +import android.widget.ImageView +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import coil.Coil +import coil.ImageLoader +import coil.load +import coil.request.ImageRequest +import com.twocoders.dynamic.image.base.BaseDynamicImage +import com.twocoders.dynamic.image.base.component.UriComponent + +/** + * + * Handy class which can be used to bind image data to views. + * Data can be in [DrawableRes], [Drawable], or [UriComponent] format. + * + * Use one of [DynamicImage.from] creator methods to create the data + * and [DynamicImage.getDrawable] to obtain the final [Drawable] output. Or you can + * provide target [ImageView] class into the [DynamicImage.loadDrawableInto] method. + * + * This [DynamicImage] implementation uses [Coil], for other implementations please visit our GitHub: + * [Android Dynamic Components](https://github.com/Two-Coders/android-dynamic-components) + * + */ +@Suppress("unused", "MemberVisibilityCanBePrivate") +open class DynamicImage : BaseDynamicImage { + + protected constructor( + @ColorInt imageRes: Int? = null, + imageDrawable: Drawable? = null, + imageUri: UriComponent? = null + ) : super(imageRes, imageDrawable, imageUri) + + protected constructor(parcel: Parcel) : super(parcel) + + companion object { + + val EMPTY = DynamicImage() + + @JvmStatic + fun from(@DrawableRes imageRes: Int) = DynamicImage(imageRes = imageRes) + + @JvmStatic + fun from(imageDrawable: Drawable) = DynamicImage(imageDrawable = imageDrawable) + + @JvmStatic + fun from(imageUri: UriComponent) = DynamicImage(imageUri = imageUri) + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = DynamicImage(parcel) + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + } + + override suspend fun getDrawableFromUri( + context: Context, + imageUriComponent: UriComponent + ): Drawable? { + val request = ImageRequest.Builder(context) + .data(imageUriComponent.image) + .build() + return ImageLoader(context).execute(request).drawable + } + + override fun getDrawableFromUri( + context: Context, + imageUriComponent: UriComponent, + callback: (drawable: Drawable) -> Unit + ) { + ImageLoader(context).enqueue( + ImageRequest.Builder(context) + .data(imageUriComponent.image) + .target { callback(it) } + .build() + ) + } + + override fun loadDrawableInto( + imageView: ImageView, + withCrossFade: Boolean + ) { + imageUri?.let { imageUriComponent -> + imageView.load(imageUriComponent.image) { + crossfade(withCrossFade) + imageUriComponent.placeholderImage?.let { placeholder(it) } + imageUriComponent.errorImage?.let { error(it) } + } + } + + imageDrawable?.let { + // Note: Coil has currently issue with loading vectors + if (it is VectorDrawable) { + imageView.setImageDrawable(it) + } else { + imageView.load(it) { + crossfade(withCrossFade) + } + } + } + + imageRes?.let { + // We need to get drawable before setting it to imageView through Coil, + // there is a problem with background tint from theme attribute + getDrawable(imageView.context) { drawable -> + // Note: Coil has currently issue with loading vectors + if (drawable is VectorDrawable) { + imageView.setImageDrawable(drawable) + } else { + imageView.load(drawable) { + crossfade(withCrossFade) + } + } + } + } + } +} \ No newline at end of file diff --git a/dynamic/image-glide/.gitignore b/dynamic/image-glide/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/dynamic/image-glide/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/dynamic/image-glide/build.gradle b/dynamic/image-glide/build.gradle new file mode 100644 index 0000000..51c9bd4 --- /dev/null +++ b/dynamic/image-glide/build.gradle @@ -0,0 +1,50 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' + +ext.bintrayPublishVersion = libDynamicImageGlideVersion +apply from: '../../bintray-publish-config.gradle' + +android { + compileSdkVersion androidCompileSdkVersion + + defaultConfig { + minSdkVersion androidMinSdkVersion + targetSdkVersion androidTargerSdkVersion + versionName libDynamicImageGlideVersion + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildFeatures { + dataBinding = true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + api project(':dynamic:image-base') + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" + implementation "androidx.annotation:annotation:$annotationVersion" + implementation "androidx.core:core-ktx:$androidXCoreVersion" + implementation "com.twocoders.extensions:common:$commonExtensionsVersion" + + implementation "com.github.bumptech.glide:glide:$glideVersion" + + androidTestImplementation "androidx.test:runner:$androidTestRunnerVersion" + androidTestImplementation "androidx.test.ext:junit:$androidJunitVersion" + + testImplementation "junit:junit:$junitVersion" +} diff --git a/dynamic/image-glide/consumer-rules.pro b/dynamic/image-glide/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/dynamic/image-glide/src/main/AndroidManifest.xml b/dynamic/image-glide/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8f373ac --- /dev/null +++ b/dynamic/image-glide/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dynamic/image-glide/src/main/java/com/twocoders/dynamic/image/glide/DynamicImage.kt b/dynamic/image-glide/src/main/java/com/twocoders/dynamic/image/glide/DynamicImage.kt new file mode 100644 index 0000000..dcad5e1 --- /dev/null +++ b/dynamic/image-glide/src/main/java/com/twocoders/dynamic/image/glide/DynamicImage.kt @@ -0,0 +1,124 @@ +package com.twocoders.dynamic.image.glide + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.os.Parcel +import android.os.Parcelable +import android.widget.ImageView +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.twocoders.dynamic.image.base.BaseDynamicImage +import com.twocoders.dynamic.image.base.component.UriComponent + +/** + * + * Handy class which can be used to bind image data to views. + * Data can be in [DrawableRes], [Drawable], or [UriComponent] format. + * + * Use one of [DynamicImage.from] creator methods to create the data + * and [DynamicImage.getDrawable] to obtain the final [Drawable] output. Or you can + * provide target [ImageView] class into the [DynamicImage.loadDrawableInto] method. + * + * This [DynamicImage] implementation uses [Glide], for other implementations please visit our GitHub: + * [Android Dynamic Components](https://github.com/Two-Coders/android-dynamic-components) + * + */ +@Suppress("unused", "MemberVisibilityCanBePrivate") +open class DynamicImage : BaseDynamicImage { + + protected constructor( + @ColorInt imageRes: Int? = null, + imageDrawable: Drawable? = null, + imageUri: UriComponent? = null + ) : super(imageRes, imageDrawable, imageUri) + + protected constructor(parcel: Parcel) : super(parcel) + + companion object { + + val EMPTY = DynamicImage() + + @JvmStatic + fun from(@DrawableRes imageRes: Int) = DynamicImage(imageRes = imageRes) + + @JvmStatic + fun from(imageDrawable: Drawable) = DynamicImage(imageDrawable = imageDrawable) + + @JvmStatic + fun from(imageUri: UriComponent) = DynamicImage(imageUri = imageUri) + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = DynamicImage(parcel) + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + } + + override suspend fun getDrawableFromUri( + context: Context, + imageUriComponent: UriComponent + ) = BitmapDrawable( + context.resources, + Glide.with(context) + .asBitmap() + .load(imageUriComponent.image) + .submit() + .get() + ) + + override fun getDrawableFromUri( + context: Context, + imageUriComponent: UriComponent, + callback: (drawable: Drawable) -> Unit + ) { + Glide.with(context) + .asBitmap() + .load(imageUriComponent.image) + .into(object : CustomTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + callback(BitmapDrawable(context.resources, resource)) + } + override fun onLoadCleared(placeholder: Drawable?) {} + }) + } + + override fun loadDrawableInto( + imageView: ImageView, + withCrossFade: Boolean + ) { + imageUri?.let { imageUriComponent -> + Glide.with(imageView.context) + .load(imageUriComponent.image) + .apply { + if (withCrossFade) transition(DrawableTransitionOptions.withCrossFade()) + imageUriComponent.placeholderImage?.let { placeholder(it) } + imageUriComponent.errorImage?.let { error(it) } + } + .into(imageView) + } + + imageDrawable?.let { + Glide.with(imageView.context) + .load(it) + .apply { + if (withCrossFade) transition(DrawableTransitionOptions.withCrossFade()) + } + .into(imageView) + } + + imageRes?.let { + Glide.with(imageView.context) + .load(it) + .apply { + if (withCrossFade) transition(DrawableTransitionOptions.withCrossFade()) + } + .into(imageView) + } + } +} \ No newline at end of file diff --git a/dynamic/image-picasso/.gitignore b/dynamic/image-picasso/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/dynamic/image-picasso/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/dynamic/image-picasso/build.gradle b/dynamic/image-picasso/build.gradle new file mode 100644 index 0000000..8ed39b7 --- /dev/null +++ b/dynamic/image-picasso/build.gradle @@ -0,0 +1,53 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' + +ext.bintrayPublishVersion = libDynamicImagePicassoVersion +apply from: '../../bintray-publish-config.gradle' + +android { + compileSdkVersion androidCompileSdkVersion + + defaultConfig { + minSdkVersion androidMinSdkVersion + targetSdkVersion androidTargerSdkVersion + versionName libDynamicImagePicassoVersion + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildFeatures { + dataBinding = true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + api project(':dynamic:image-base') + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" + implementation "androidx.annotation:annotation:$annotationVersion" + implementation "androidx.core:core-ktx:$androidXCoreVersion" + implementation "com.twocoders.extensions:common:$commonExtensionsVersion" + + implementation ("com.squareup.picasso:picasso:$picassoVersion") { + exclude group: 'com.android.support' + exclude module: ['exifinterface', 'support-annotations'] + } + + androidTestImplementation "androidx.test:runner:$androidTestRunnerVersion" + androidTestImplementation "androidx.test.ext:junit:$androidJunitVersion" + + testImplementation "junit:junit:$junitVersion" +} diff --git a/dynamic/image-picasso/consumer-rules.pro b/dynamic/image-picasso/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/dynamic/image-picasso/src/main/AndroidManifest.xml b/dynamic/image-picasso/src/main/AndroidManifest.xml new file mode 100644 index 0000000..08f73e3 --- /dev/null +++ b/dynamic/image-picasso/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dynamic/image-picasso/src/main/java/com/twocoders/dynamic/image/picasso/DynamicImage.kt b/dynamic/image-picasso/src/main/java/com/twocoders/dynamic/image/picasso/DynamicImage.kt new file mode 100644 index 0000000..eee0afe --- /dev/null +++ b/dynamic/image-picasso/src/main/java/com/twocoders/dynamic/image/picasso/DynamicImage.kt @@ -0,0 +1,109 @@ +package com.twocoders.dynamic.image.picasso + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.os.Parcel +import android.os.Parcelable +import android.widget.ImageView +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import com.squareup.picasso.Picasso +import com.squareup.picasso.Target +import com.twocoders.dynamic.image.base.BaseDynamicImage +import com.twocoders.dynamic.image.base.component.UriComponent + +/** + * + * Handy class which can be used to bind image data to views. + * Data can be in [DrawableRes], [Drawable], or [UriComponent] format. + * + * Use one of [DynamicImage.from] creator methods to create the data + * and [DynamicImage.getDrawable] to obtain the final [Drawable] output. Or you can + * provide target [ImageView] class into the [DynamicImage.loadDrawableInto] method. + * + * This [DynamicImage] implementation uses [Picasso], for other implementations please visit our GitHub: + * [Android Dynamic Components](https://github.com/Two-Coders/android-dynamic-components) + * + */ +@Suppress("unused", "MemberVisibilityCanBePrivate") +open class DynamicImage : BaseDynamicImage { + + protected constructor( + @ColorInt imageRes: Int? = null, + imageDrawable: Drawable? = null, + imageUri: UriComponent? = null + ) : super(imageRes, imageDrawable, imageUri) + + protected constructor(parcel: Parcel) : super(parcel) + + companion object { + + val EMPTY = DynamicImage() + + @JvmStatic + fun from(@DrawableRes imageRes: Int) = DynamicImage(imageRes = imageRes) + + @JvmStatic + fun from(imageDrawable: Drawable) = DynamicImage(imageDrawable = imageDrawable) + + @JvmStatic + fun from(imageUri: UriComponent) = DynamicImage(imageUri = imageUri) + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = DynamicImage(parcel) + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + } + + override suspend fun getDrawableFromUri( + context: Context, + imageUriComponent: UriComponent + ) = BitmapDrawable( + context.resources, + Picasso.get() + .load(imageUriComponent.image) + .get() + ) + + override fun getDrawableFromUri( + context: Context, + imageUriComponent: UriComponent, + callback: (drawable: Drawable) -> Unit + ) { + Picasso.get() + .load(imageUriComponent.image) + .into(object : Target { + override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) { + callback(BitmapDrawable(context.resources, bitmap)) + } + override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {} + override fun onPrepareLoad(placeHolderDrawable: Drawable?) {} + }) + } + + override fun loadDrawableInto( + imageView: ImageView, + withCrossFade: Boolean + ) { + imageUri?.let { imageUriComponent -> + Picasso.get() + .load(imageUriComponent.image) + .apply { + imageUriComponent.placeholderImage?.let { placeholder(it) } + imageUriComponent.errorImage?.let { error(it) } + } + .into(imageView) + } + + imageDrawable?.let { + imageView.setImageDrawable(it) + } + + imageRes?.let { + Picasso.get().load(it).into(imageView) + } + } +} \ No newline at end of file diff --git a/dynamic/text/build.gradle b/dynamic/text/build.gradle index e2711d8..9d8e019 100644 --- a/dynamic/text/build.gradle +++ b/dynamic/text/build.gradle @@ -17,12 +17,6 @@ android { consumerProguardFiles 'consumer-rules.pro' } - buildTypes { - release { - minifyEnabled false - } - } - buildFeatures { dataBinding = true } diff --git a/dynamic/text/src/main/java/com/twocoders/dynamic/text/Quantity.kt b/dynamic/text/src/main/java/com/twocoders/dynamic/text/Quantity.kt index 5484166..e68c9ee 100644 --- a/dynamic/text/src/main/java/com/twocoders/dynamic/text/Quantity.kt +++ b/dynamic/text/src/main/java/com/twocoders/dynamic/text/Quantity.kt @@ -37,7 +37,7 @@ data class Quantity(val number: Int, val useInText: Boolean = false) : Parcelabl ParcelCompat.writeBoolean(dest, useInText) } - override fun describeContents(): Int = 0 + override fun describeContents() = 0 companion object { @JvmField diff --git a/dynamic/text/src/main/res/values/strings.xml b/dynamic/text/src/main/res/values/strings.xml deleted file mode 100644 index 9cacc32..0000000 --- a/dynamic/text/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Dynamic Text - diff --git a/settings.gradle b/settings.gradle index 996b560..80fd82c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,6 @@ include ':dynamic:text', - ':dynamic:color' + ':dynamic:color', + ':dynamic:image-base', + ':dynamic:image-coil', + ':dynamic:image-glide', + ':dynamic:image-picasso'