diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Cache.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Cache.java index 1c396b993..86dd30481 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Cache.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Cache.java @@ -142,6 +142,12 @@ public class Cache { return bitmapSize == 0 ? 1 : bitmapSize; } }; + } else { + clearCache(); + if (mReusableBitmaps != null) { + mReusableBitmaps.clear(); + mReusableBitmaps = null; + } } } @@ -236,6 +242,7 @@ public class Cache { Log.v(TAG, "Memory cache cleared"); } } + mMemoryCache = null; } /** @@ -246,18 +253,6 @@ public class Cache { public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED; public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED; - /** - * Create a set of image cache parameters that can be provided to - * {@link Cache#getInstance(CacheParams)}. - * - * @param context A context to use. - * @param diskCacheDirectoryName A unique subdirectory name that will be appended to the - * application cache directory. Usually "cache" or "images" - * is sufficient. - */ - public CacheParams(Context context, String diskCacheDirectoryName) { - } - /** * Sets the memory cache size based on a percentage of the max available VM memory. * Eg. setting percent to 0.2 would set the memory cache to one fifth of the available diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java index 459788e8b..7788a7e21 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Fetcher.java @@ -23,11 +23,14 @@ import android.util.Log; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; @@ -47,14 +50,22 @@ public class Fetcher extends Resizer { private static final int DISK_CACHE_INDEX = 0; private final String mPackageName; + private static Fetcher instance; + public static Fetcher getInstance(Context context) { + if (instance == null) { + instance = new Fetcher(context); + } + + return instance; + } /** * Initialize providing a target image width and height for the processing images. * * @param context */ - public Fetcher(Context context) { + private Fetcher(Context context) { super(context); mHttpCacheDir = Cache.getDiskCacheDir(context, HTTP_CACHE_DIR); mPackageName = context.getPackageName(); @@ -184,9 +195,9 @@ public class Fetcher extends Resizer { fileDescriptor = fileInputStream.getFD(); } } catch (IOException e) { - Log.e(TAG, "processBitmap - " + e); + Log.e(TAG, "processHttp - " + e); } catch (IllegalStateException e) { - Log.e(TAG, "processBitmap - " + e); + Log.e(TAG, "processHttp - " + e); } finally { if (fileDescriptor == null && fileInputStream != null) { try { @@ -216,19 +227,17 @@ public class Fetcher extends Resizer { if (debuggable > 0) { Log.v(TAG, "processHttp - " + data); } - final String key = Cache.hashKeyForDisk(data); - FileOutputStream outputStream = null; + + ByteArrayOutputStreamInternal outputStream = null; Bitmap bitmap = null; try { - outputStream = new FileOutputStream(key); + outputStream = new ByteArrayOutputStreamInternal(); if (downloadUrlToStream(data, outputStream)) { - bitmap = decodeSampledBitmapFromDescriptor(outputStream.getFD(), decodeWidth, decodeHeight, getCache()); + bitmap = decodeSampledBitmapFromByteArray(outputStream.getBuffer(), decodeWidth, decodeHeight, getCache()); } - } catch (IOException e) { - Log.e(TAG, "processBitmap - " + e); } catch (IllegalStateException e) { - Log.e(TAG, "processBitmap - " + e); + Log.e(TAG, "processHttpNoCache - " + e); } finally { if (outputStream != null) { try { @@ -263,7 +272,7 @@ public class Fetcher extends Resizer { Log.v(TAG, "Missing Image with resourceID: " + stringData); } } else { - if (useCache) { + if (useCache && mHttpDiskCache != null) { return processHttp(stringData, decodeWidth, decodeHeight); } else { return processHttpNoCache(stringData, decodeWidth, decodeHeight); @@ -328,4 +337,10 @@ public class Fetcher extends Resizer { System.setProperty("http.keepAlive", "false"); } } + + private static class ByteArrayOutputStreamInternal extends ByteArrayOutputStream { + public byte[] getBuffer() { + return buf; + } + } } diff --git a/android/widgets/src/main/java/org/nativescript/widgets/Image/Resizer.java b/android/widgets/src/main/java/org/nativescript/widgets/Image/Resizer.java index f399af214..770303c3a 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/Image/Resizer.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/Image/Resizer.java @@ -23,6 +23,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Build; import java.io.FileDescriptor; +import java.io.InputStream; /** * A simple subclass of {@link Worker} that resize images given a target width @@ -149,6 +150,34 @@ public abstract class Resizer extends Worker { return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); } + public static Bitmap decodeSampledBitmapFromByteArray( + byte[] buffer, int reqWidth, int reqHeight, Cache cache) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options); + + // If requested width/height were not specified - decode in full size. + if (reqWidth > 0 && reqHeight > 0) { + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + } + else { + options.inSampleSize = 1; + } + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + + // If we're running on Honeycomb or newer, try to use inBitmap + if (Utils.hasHoneycomb()) { + addInBitmapOptions(options, cache); + } + + return BitmapFactory.decodeByteArray(buffer, 0, buffer.length, options); + } + /** * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates diff --git a/android/widgets/src/main/java/org/nativescript/widgets/ImageView.java b/android/widgets/src/main/java/org/nativescript/widgets/ImageView.java index bccb40933..fc9a2dd0f 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/ImageView.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/ImageView.java @@ -5,9 +5,11 @@ package org.nativescript.widgets; import android.content.Context; import android.graphics.*; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import org.nativescript.widgets.image.Fetcher; +import org.nativescript.widgets.image.Worker; + /** * @author hhristov * @@ -23,6 +25,16 @@ public class ImageView extends android.widget.ImageView { private float rotationAngle; + private Matrix mMatrix; + private Bitmap mBitmap; + private String mUri; + private int mDecodeWidth; + private int mDecodeHeight; + private boolean mUseCache; + private boolean mAsync; + private Worker.OnImageLoadedListener mListener; + private boolean mAttachedToWindow = false; + public float getRotationAngle() { return rotationAngle; } @@ -38,6 +50,23 @@ public class ImageView extends android.widget.ImageView { this.setScaleType(ScaleType.FIT_CENTER); } + @Override + protected void onAttachedToWindow() { + mAttachedToWindow = true; + super.onAttachedToWindow(); + this.loadImage(); + } + + @Override + protected void onDetachedFromWindow() { + mAttachedToWindow = false; + super.onDetachedFromWindow(); + if (mUri != null) { + // Clear the bitmap as we are not in the visual tree. + this.setImageBitmap(null); + } + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @@ -132,21 +161,30 @@ public class ImageView extends android.widget.ImageView { } } - private Matrix mMatrix; - private Bitmap pBitmap; + public void setUri(String uri, int decodeWidth, int decodeHeight, boolean useCache, boolean async, Worker.OnImageLoadedListener listener) { + mUri = uri; + mDecodeWidth = decodeWidth; + mDecodeHeight = decodeHeight; + mUseCache = useCache; + mAsync = async; + mListener = listener; + if (mAttachedToWindow) { + loadImage(); + } + } + + private void loadImage() { + Fetcher fetcher = Fetcher.getInstance(this.getContext()); + if (mUri != null && fetcher != null) { + // Get the Bitmap from cache. + fetcher.loadImage(mUri, this, mDecodeWidth, mDecodeHeight, mUseCache, mAsync, mListener); + } + } @Override public void setImageBitmap(Bitmap bm) { super.setImageBitmap(bm); - this.pBitmap = bm; - } - - @Override - public void setImageDrawable(Drawable drawable) { - super.setImageDrawable(drawable); - if (drawable instanceof BitmapDrawable) { - this.pBitmap = ((BitmapDrawable)drawable).getBitmap(); - } + this.mBitmap = bm; } @Override @@ -185,8 +223,8 @@ public class ImageView extends android.widget.ImageView { float viewWidth = this.getWidth() - (2 * roundedBorderWidth); float viewHeight = this.getHeight() - (2 * roundedBorderWidth); - float bitmapWidth = (float) pBitmap.getWidth(); - float bitmapHeight = (float) pBitmap.getHeight(); + float bitmapWidth = (float) mBitmap.getWidth(); + float bitmapHeight = (float) mBitmap.getHeight(); float scaleX; float scaleY; @@ -223,7 +261,7 @@ public class ImageView extends android.widget.ImageView { matrix.postRotate(rotationDegree); matrix.postTranslate(viewWidth / 2 + roundedBorderWidth, viewHeight / 2 + roundedBorderWidth); - canvas.drawBitmap(this.pBitmap, matrix, null); + canvas.drawBitmap(this.mBitmap, matrix, null); } else { super.onDraw(canvas); diff --git a/android/widgets/src/main/java/org/nativescript/widgets/ViewHelper.java b/android/widgets/src/main/java/org/nativescript/widgets/ViewHelper.java index 08755eb07..f4c8859f7 100644 --- a/android/widgets/src/main/java/org/nativescript/widgets/ViewHelper.java +++ b/android/widgets/src/main/java/org/nativescript/widgets/ViewHelper.java @@ -373,5 +373,21 @@ public class ViewHelper { view.setZ(value); } } + + @TargetApi(21) + public static float getLetterspacing(android.widget.TextView textView) { + if (ViewHelper.version >= 21) { + return textView.getLetterSpacing(); + } + + return 0; + } + + @TargetApi(21) + public static void setLetterspacing(android.widget.TextView textView, float value) { + if (ViewHelper.version >= 21) { + textView.setLetterSpacing(value); + } + } } \ No newline at end of file diff --git a/build.android.sh b/build.android.sh new file mode 100755 index 000000000..cda334f42 --- /dev/null +++ b/build.android.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +echo "Set exit on simple errors" +set -e + +echo "Use dumb gradle terminal" +export TERM=dumb + +echo "Clean dist" +rm -rf dist +mkdir dist +mkdir dist/package +mkdir dist/package/platforms + +echo "Build android" +mkdir dist/package/platforms/android +cd android +./gradlew assembleRelease +cd .. +cp android/widgets/build/outputs/aar/widgets-release.aar dist/package/platforms/android/widgets-release.aar + +echo "Copy NPM artefacts" +cp LICENSE dist/package/LICENSE +cp LICENSE.md dist/package/LICENSE.md +cp README.md dist/package/README.md +cp package.json dist/package/package.json +if [ "$1" ] +then + echo "Suffix package.json's version with tag: $1" + sed -i.bak 's/\(\"version\"\:[[:space:]]*\"[^\"]*\)\"/\1-'$1'"/g' ./dist/package/package.json +fi + +echo "NPM pack" +cd dist/package +PACKAGE="$(npm pack)" +cd ../.. +mv dist/package/$PACKAGE dist/$PACKAGE +echo "Output: dist/$PACKAGE" + diff --git a/build.ios.sh b/build.ios.sh new file mode 100755 index 000000000..cf0aceade --- /dev/null +++ b/build.ios.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +echo "Set exit on simple errors" +set -e + +echo "Use dumb gradle terminal" +export TERM=dumb + +echo "Clean dist" +rm -rf dist +mkdir dist +mkdir dist/package +mkdir dist/package/platforms + +echo "Build ios" +mkdir dist/package/platforms/ios +cd ios +./build.sh +cd .. +cp -r ios/TNSWidgets/build/TNSWidgets.framework dist/package/platforms/ios/TNSWidgets.framework + +echo "Copy NPM artefacts" +cp LICENSE dist/package/LICENSE +cp LICENSE.md dist/package/LICENSE.md +cp README.md dist/package/README.md +cp package.json dist/package/package.json +if [ "$1" ] +then + echo "Suffix package.json's version with tag: $1" + sed -i.bak 's/\(\"version\"\:[[:space:]]*\"[^\"]*\)\"/\1-'$1'"/g' ./dist/package/package.json +fi + +echo "NPM pack" +cd dist/package +PACKAGE="$(npm pack)" +cd ../.. +mv dist/package/$PACKAGE dist/$PACKAGE +echo "Output: dist/$PACKAGE" +