From a7be23a3d251731b65e618938a8bc906a313667e Mon Sep 17 00:00:00 2001 From: Shankar Yadav Date: Thu, 28 Oct 2021 12:53:52 +0530 Subject: [PATCH 1/2] Fingure Signature on amc kyc flow --- .../drawerControllers/ControlTimedPoints.java | 14 + .../drawerControllers/CurveBezier.java | 49 ++ .../drawerControllers/TimedPoint.java | 24 + .../signature/svgUtils/SvgBuilder.java | 76 ++ .../signature/svgUtils/SvgPathBuilder.java | 73 ++ .../signature/svgUtils/SvgPoint.java | 56 ++ .../signature/ui/OnSignedListener.kt | 9 + .../signature/ui/SilkySignaturePad.java | 651 ++++++++++++++++++ .../signature/viewHelper/ViewCompat.java | 15 + .../viewHelper/ViewTreeObserverCompat.java | 20 + .../ui/kyc/fragments/SignatureFragment.kt | 118 +--- ...rounded.xml => progressbar_horizontal.xml} | 0 .../res/layout/activity_verify_pan_card.xml | 2 +- .../src/main/res/layout/fragment_address.xml | 4 +- .../main/res/layout/fragment_signature.xml | 80 +-- .../main/res/layout/view_progress_header.xml | 3 +- navi-amc/src/main/res/values-night/attrs.xml | 10 + navi-amc/src/main/res/values/dimens.xml | 2 + navi-amc/src/main/res/values/strings.xml | 3 +- 19 files changed, 1043 insertions(+), 166 deletions(-) create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/ControlTimedPoints.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/CurveBezier.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/TimedPoint.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgBuilder.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPathBuilder.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPoint.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/OnSignedListener.kt create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/SilkySignaturePad.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewCompat.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewTreeObserverCompat.java rename navi-amc/src/main/res/drawable/{progressbar_horizontal_rounded.xml => progressbar_horizontal.xml} (100%) create mode 100644 navi-amc/src/main/res/values-night/attrs.xml diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/ControlTimedPoints.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/ControlTimedPoints.java new file mode 100644 index 0000000000..1d80f182a4 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/ControlTimedPoints.java @@ -0,0 +1,14 @@ +package com.navi.amc.investorapp.signature.drawerControllers; + +public class ControlTimedPoints { + + public TimedPoint c1; + public TimedPoint c2; + + public ControlTimedPoints set(TimedPoint c1, TimedPoint c2) { + this.c1 = c1; + this.c2 = c2; + return this; + } + +} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/CurveBezier.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/CurveBezier.java new file mode 100644 index 0000000000..594580c264 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/CurveBezier.java @@ -0,0 +1,49 @@ +package com.navi.amc.investorapp.signature.drawerControllers; + +public class CurveBezier { + + public TimedPoint startPoint; + public TimedPoint control1; + public TimedPoint control2; + public TimedPoint endPoint; + + public CurveBezier set(TimedPoint startPoint, TimedPoint control1, + TimedPoint control2, TimedPoint endPoint) { + this.startPoint = startPoint; + this.control1 = control1; + this.control2 = control2; + this.endPoint = endPoint; + return this; + } + + public float length() { + int steps = 10; + float length = 0; + double cx, cy, px = 0, py = 0, xDiff, yDiff; + + for (int i = 0; i <= steps; i++) { + float t = (float) i / steps; + cx = point(t, this.startPoint.x, this.control1.x, + this.control2.x, this.endPoint.x); + cy = point(t, this.startPoint.y, this.control1.y, + this.control2.y, this.endPoint.y); + if (i > 0) { + xDiff = cx - px; + yDiff = cy - py; + length += Math.sqrt(xDiff * xDiff + yDiff * yDiff); + } + px = cx; + py = cy; + } + return length; + + } + + public double point(float t, float start, float c1, float c2, float end) { + return start * (1.0 - t) * (1.0 - t) * (1.0 - t) + + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t + + 3.0 * c2 * (1.0 - t) * t * t + + end * t * t * t; + } + +} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/TimedPoint.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/TimedPoint.java new file mode 100644 index 0000000000..bfeb4d723f --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/TimedPoint.java @@ -0,0 +1,24 @@ +package com.navi.amc.investorapp.signature.drawerControllers; + +public class TimedPoint { + public float x; + public float y; + public long timestamp; + + public TimedPoint set(float x, float y) { + this.x = x; + this.y = y; + this.timestamp = System.currentTimeMillis(); + return this; + } + + public float velocityFrom(TimedPoint start) { + float velocity = distanceTo(start) / (this.timestamp - start.timestamp); + if (velocity != velocity) return 0f; + return velocity; + } + + public float distanceTo(TimedPoint point) { + return (float) Math.sqrt(Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2)); + } +} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgBuilder.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgBuilder.java new file mode 100644 index 0000000000..e4a2885ca9 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgBuilder.java @@ -0,0 +1,76 @@ +package com.navi.amc.investorapp.signature.svgUtils; + +import com.navi.amc.investorapp.signature.drawerControllers.CurveBezier; + +public class SvgBuilder { + + private final StringBuilder mSvgPathsBuilder = new StringBuilder(); + private SvgPathBuilder mCurrentPathBuilder = null; + + public SvgBuilder() { + } + + public void clear() { + mSvgPathsBuilder.setLength(0); + mCurrentPathBuilder = null; + } + + public String build(final int width, final int height) { + if (isPathStarted()) { + appendCurrentPath(); + } + return (new StringBuilder()) + .append("\n") + .append("") + .append("") + .append(mSvgPathsBuilder) + .append("") + .append("") + .toString(); + } + + public SvgBuilder append(final CurveBezier curve, final float strokeWidth) { + final Integer roundedStrokeWidth = Math.round(strokeWidth); + final SvgPoint curveStartSvgPoint = new SvgPoint(curve.startPoint); + final SvgPoint curveControlSvgPoint1 = new SvgPoint(curve.control1); + final SvgPoint curveControlSvgPoint2 = new SvgPoint(curve.control2); + final SvgPoint curveEndSvgPoint = new SvgPoint(curve.endPoint); + + if (!isPathStarted()) { + startNewPath(roundedStrokeWidth, curveStartSvgPoint); + } + + if (!curveStartSvgPoint.equals(mCurrentPathBuilder.getLastPoint()) + || !roundedStrokeWidth.equals(mCurrentPathBuilder.getStrokeWidth())) { + appendCurrentPath(); + startNewPath(roundedStrokeWidth, curveStartSvgPoint); + } + + mCurrentPathBuilder.append(curveControlSvgPoint1, curveControlSvgPoint2, curveEndSvgPoint); + return this; + } + + private void startNewPath(Integer roundedStrokeWidth, SvgPoint curveStartSvgPoint) { + mCurrentPathBuilder = new SvgPathBuilder(curveStartSvgPoint, roundedStrokeWidth); + } + + private void appendCurrentPath() { + mSvgPathsBuilder.append(mCurrentPathBuilder); + } + + private boolean isPathStarted() { + return mCurrentPathBuilder != null; + } + +} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPathBuilder.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPathBuilder.java new file mode 100644 index 0000000000..25bbf8be86 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPathBuilder.java @@ -0,0 +1,73 @@ +package com.navi.amc.investorapp.signature.svgUtils; + +public class SvgPathBuilder { + + public static final Character SVG_RELATIVE_CUBIC_BEZIER_CURVE = 'c'; + public static final Character SVG_MOVE = 'M'; + private final StringBuilder mStringBuilder; + private final Integer mStrokeWidth; + private final SvgPoint mStartPoint; + private SvgPoint mLastPoint; + + public SvgPathBuilder(final SvgPoint startPoint, final Integer strokeWidth) { + mStrokeWidth = strokeWidth; + mStartPoint = startPoint; + mLastPoint = startPoint; + mStringBuilder = new StringBuilder(); + mStringBuilder.append(SVG_RELATIVE_CUBIC_BEZIER_CURVE); + + } + + public final Integer getStrokeWidth() { + return mStrokeWidth; + } + + public final SvgPoint getLastPoint() { + return mLastPoint; + } + + public SvgPathBuilder append(final SvgPoint controlPoint1, final SvgPoint controlPoint2, final SvgPoint endPoint) { + mStringBuilder.append(makeRelativeCubicBezierCurve(controlPoint1, controlPoint2, endPoint)); + mLastPoint = endPoint; + return this; + } + + @Override + public String toString() { + return (new StringBuilder()) + .append("") + .toString(); + } + + private String makeRelativeCubicBezierCurve(final SvgPoint controlPoint1, final SvgPoint controlPoint2, final SvgPoint endPoint) { + final String sControlPoint1 = controlPoint1.toRelativeCoordinates(mLastPoint); + final String sControlPoint2 = controlPoint2.toRelativeCoordinates(mLastPoint); + final String sEndPoint = endPoint.toRelativeCoordinates(mLastPoint); + + final StringBuilder sb = new StringBuilder(); + + sb.append(sControlPoint1); + sb.append(" "); + sb.append(sControlPoint2); + sb.append(" "); + + sb.append(sEndPoint); + sb.append(" "); + + // discard zero curve + final String svg = sb.toString(); + if ("c0 0 0 0 0 0".equals(svg)) { + return ""; + } else { + return svg; + } + } +} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPoint.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPoint.java new file mode 100644 index 0000000000..79dd6cf573 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPoint.java @@ -0,0 +1,56 @@ +package com.navi.amc.investorapp.signature.svgUtils; + +import com.navi.amc.investorapp.signature.drawerControllers.TimedPoint; + +class SvgPoint { + + final Integer x, y; + + public SvgPoint(TimedPoint point) { + // one optimisation is to get rid of decimals as they are mostly non-significant in the + // produced SVG image + x = Math.round(point.x); + y = Math.round(point.y); + } + + public SvgPoint(int x, int y) { + this.x = x; + this.y = y; + } + + public String toAbsoluteCoordinates() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(x); + stringBuilder.append(","); + stringBuilder.append(y); + return stringBuilder.toString(); + } + + public String toRelativeCoordinates(final SvgPoint referencePoint) { + return (new SvgPoint(x - referencePoint.x, y - referencePoint.y)).toString(); + } + + @Override + public String toString() { + return toAbsoluteCoordinates(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SvgPoint svgPoint = (SvgPoint) o; + + if (!x.equals(svgPoint.x)) return false; + return y.equals(svgPoint.y); + + } + + @Override + public int hashCode() { + int result = x.hashCode(); + result = 31 * result + y.hashCode(); + return result; + } +} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/OnSignedListener.kt b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/OnSignedListener.kt new file mode 100644 index 0000000000..f6dc76cc52 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/OnSignedListener.kt @@ -0,0 +1,9 @@ +package com.navi.amc.investorapp.signature.ui + +interface OnSignedListener { + fun onStartSigning() + + fun onSigned() + + fun onClear() +} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/SilkySignaturePad.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/SilkySignaturePad.java new file mode 100644 index 0000000000..a3c5c20f05 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/SilkySignaturePad.java @@ -0,0 +1,651 @@ +package com.navi.amc.investorapp.signature.ui; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewTreeObserver; + +import com.navi.amc.investorapp.R; +import com.navi.amc.investorapp.signature.drawerControllers.ControlTimedPoints; +import com.navi.amc.investorapp.signature.drawerControllers.CurveBezier; +import com.navi.amc.investorapp.signature.drawerControllers.TimedPoint; +import com.navi.amc.investorapp.signature.svgUtils.SvgBuilder; +import com.navi.amc.investorapp.signature.viewHelper.ViewCompat; +import com.navi.amc.investorapp.signature.viewHelper.ViewTreeObserverCompat; + +import java.util.ArrayList; +import java.util.List; + + +public class SilkySignaturePad extends View { + //View state + private List mPoints; + private boolean mIsEmpty; + private float mLastTouchX; + private float mLastTouchY; + private float mLastVelocity; + private float mLastWidth; + private RectF mDirtyRect; + + private final SvgBuilder mSvgBuilder = new SvgBuilder(); + + // Cache + private List mPointsCache = new ArrayList<>(); + private ControlTimedPoints mControlTimedPointsCached = new ControlTimedPoints(); + private CurveBezier mCurveBezierCached = new CurveBezier(); + + //Configurable parameters + private int mMinWidth; + private int mMaxWidth; + private float mVelocityFilterWeight; + private OnSignedListener mOnSignedListener; + private boolean mClearOnDoubleClick; + + //Click values + private long mFirstClick; + private int mCountClick; + private static final int DOUBLE_CLICK_DELAY_MS = 200; + + //Default attribute values + private final int DEFAULT_ATTR_PEN_MIN_WIDTH_PX = 3; + private final int DEFAULT_ATTR_PEN_MAX_WIDTH_PX = 7; + private final int DEFAULT_ATTR_PEN_COLOR = Color.BLACK; + private final float DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT = 0.9f; + private final boolean DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK = false; + + private Paint mPaint = new Paint(); + private Bitmap mSignatureBitmap = null; + private Canvas mSignatureBitmapCanvas = null; + + public SilkySignaturePad(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.getTheme().obtainStyledAttributes( + attrs, + R.styleable.SilkySignaturePad, + 0, 0); + + //Configurable parameters + try { + mMinWidth = a.getDimensionPixelSize(R.styleable.SilkySignaturePad_penMinWidth, convertDpToPx(DEFAULT_ATTR_PEN_MIN_WIDTH_PX)); + mMaxWidth = a.getDimensionPixelSize(R.styleable.SilkySignaturePad_penMaxWidth, convertDpToPx(DEFAULT_ATTR_PEN_MAX_WIDTH_PX)); + mPaint.setColor(a.getColor(R.styleable.SilkySignaturePad_penColor, DEFAULT_ATTR_PEN_COLOR)); + mVelocityFilterWeight = a.getFloat(R.styleable.SilkySignaturePad_velocityFilterWeight, DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT); + mClearOnDoubleClick = a.getBoolean(R.styleable.SilkySignaturePad_clearOnDoubleClick, DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK); + } finally { + a.recycle(); + } + + //Fixed parameters + mPaint.setAntiAlias(true); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeCap(Paint.Cap.ROUND); + mPaint.setStrokeJoin(Paint.Join.ROUND); + + //Dirty rectangle to update only the changed portion of the view + mDirtyRect = new RectF(); + + clear(); + } + + /** + * Set the pen color from a given resource. + * If the resource is not found, {@link Color#BLACK} is assumed. + * + * @param colorRes the color resource. + */ + public void setPenColorRes(int colorRes) { + try { + setPenColor(getResources().getColor(colorRes)); + } catch (Resources.NotFoundException ex) { + setPenColor(Color.parseColor("#000000")); + } + } + + /** + * Set the pen color from a given color. + * + * @param color the color. + */ + public void setPenColor(int color) { + mPaint.setColor(color); + } + + /** + * Set the minimum width of the stroke in pixel. + * + * @param minWidth the width in dp. + */ + public void setMinWidth(float minWidth) { + mMinWidth = convertDpToPx(minWidth); + } + + /** + * Set the maximum width of the stroke in pixel. + * + * @param maxWidth the width in dp. + */ + public void setMaxWidth(float maxWidth) { + mMaxWidth = convertDpToPx(maxWidth); + } + + /** + * Set the velocity filter weight. + * + * @param velocityFilterWeight the weight. + */ + public void setVelocityFilterWeight(float velocityFilterWeight) { + mVelocityFilterWeight = velocityFilterWeight; + } + + public void clear() { + mSvgBuilder.clear(); + mPoints = new ArrayList<>(); + mLastVelocity = 0; + mLastWidth = (mMinWidth + mMaxWidth) / 2; + + if (mSignatureBitmap != null) { + mSignatureBitmap = null; + ensureSignatureBitmap(); + } + + setIsEmpty(true); + + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isEnabled()) + return false; + + float eventX = event.getX(); + float eventY = event.getY(); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + getParent().requestDisallowInterceptTouchEvent(true); + mPoints.clear(); + if (isDoubleClick()) break; + mLastTouchX = eventX; + mLastTouchY = eventY; + addPoint(getNewPoint(eventX, eventY)); + if (mOnSignedListener != null) mOnSignedListener.onStartSigning(); + + case MotionEvent.ACTION_MOVE: + resetDirtyRect(eventX, eventY); + addPoint(getNewPoint(eventX, eventY)); + break; + + case MotionEvent.ACTION_UP: + resetDirtyRect(eventX, eventY); + addPoint(getNewPoint(eventX, eventY)); + getParent().requestDisallowInterceptTouchEvent(true); + setIsEmpty(false); + break; + + default: + return false; + } + + //invalidate(); + invalidate( + (int) (mDirtyRect.left - mMaxWidth), + (int) (mDirtyRect.top - mMaxWidth), + (int) (mDirtyRect.right + mMaxWidth), + (int) (mDirtyRect.bottom + mMaxWidth)); + + return true; + } + + @Override + protected void onDraw(Canvas canvas) { + if (mSignatureBitmap != null) { + canvas.drawBitmap(mSignatureBitmap, 0, 0, mPaint); + } + } + + public void setOnSignedListener(OnSignedListener listener) { + mOnSignedListener = listener; + } + + public boolean isEmpty() { + return mIsEmpty; + } + + public String getSignatureSvg() { + int width = getTransparentSignatureBitmap().getWidth(); + int height = getTransparentSignatureBitmap().getHeight(); + return mSvgBuilder.build(width, height); + } + + public Bitmap getSignatureBitmap() { + Bitmap originalBitmap = getTransparentSignatureBitmap(); + Bitmap whiteBgBitmap = Bitmap.createBitmap(originalBitmap.getWidth(), originalBitmap.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(whiteBgBitmap); + canvas.drawColor(Color.WHITE); + canvas.drawBitmap(originalBitmap, 0, 0, null); + return whiteBgBitmap; + } + + /** + * @param compressPercentage Hint to the compressor, 0-100 percent. 0 meaning compress for + * small size, 100 meaning compress for max quality. Some + * formats, like PNG which is lossless, will ignore the + * quality setting + */ + public Bitmap getCompressedSignatureBitmap(int compressPercentage) { + + if (compressPercentage < 0) { + compressPercentage = 0; + } else if (compressPercentage > 100) { + compressPercentage = 100; + } + Bitmap originalBitmap = getTransparentSignatureBitmap(); + int originalWidth = originalBitmap.getWidth(); + int originalHeight = originalBitmap.getHeight(); + + int targetWidth = originalWidth * compressPercentage / 100; // your arbitrary fixed limit + int targetHeight = (int) (originalHeight * targetWidth / (double) originalWidth); + + Bitmap whiteBgBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(whiteBgBitmap); + canvas.drawColor(Color.WHITE); + canvas.drawBitmap(originalBitmap, 0, 0, null); + whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true); + return whiteBgBitmap; + } + + /** + * @param deiredWidth Desired width of the bitmap + */ + public Bitmap getFixedSizeSignatureBitmap(int deiredWidth) { + + Bitmap originalBitmap = getTransparentSignatureBitmap(); + int originalWidth = originalBitmap.getWidth(); + int originalHeight = originalBitmap.getHeight(); + + int targetWidth = deiredWidth; // your arbitrary fixed limit + int targetHeight = (int) (originalHeight * targetWidth / (double) originalWidth); + + Bitmap whiteBgBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(whiteBgBitmap); + canvas.drawColor(Color.WHITE); + canvas.drawBitmap(originalBitmap, 0, 0, null); + whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true); + return whiteBgBitmap; + } + + /** + * @param deiredWidth Desired width of the bitmap + */ + public Bitmap getFixedSizeSignatureBitmap(int deiredWidth,int desiredHeight) { + + Bitmap originalBitmap = getTransparentSignatureBitmap(); + int originalWidth = originalBitmap.getWidth(); + int originalHeight = originalBitmap.getHeight(); + + int targetWidth = deiredWidth; // your arbitrary fixed limit + int targetHeight = desiredHeight; + + Bitmap whiteBgBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(whiteBgBitmap); + canvas.drawColor(Color.WHITE); + canvas.drawBitmap(originalBitmap, 0, 0, null); + whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true); + return whiteBgBitmap; + } + + public void setSignatureBitmap(final Bitmap signature) { + // View was laid out... + if (ViewCompat.isLaidOut(this)) { + clear(); + ensureSignatureBitmap(); + + RectF tempSrc = new RectF(); + RectF tempDst = new RectF(); + + int dWidth = signature.getWidth(); + int dHeight = signature.getHeight(); + int vWidth = getWidth(); + int vHeight = getHeight(); + + // Generate the required transform. + tempSrc.set(0, 0, dWidth, dHeight); + tempDst.set(0, 0, vWidth, vHeight); + + Matrix drawMatrix = new Matrix(); + drawMatrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.CENTER); + + Canvas canvas = new Canvas(mSignatureBitmap); + canvas.drawBitmap(signature, drawMatrix, null); + setIsEmpty(false); + invalidate(); + } + // View not laid out yet e.g. called from onCreate(), onRestoreInstanceState()... + else { + getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // Remove layout listener... + ViewTreeObserverCompat.removeOnGlobalLayoutListener(getViewTreeObserver(), this); + + // Signature bitmap... + setSignatureBitmap(signature); + } + }); + } + } + + public Bitmap getTransparentSignatureBitmap() { + ensureSignatureBitmap(); + return mSignatureBitmap; + } + + public Bitmap getTransparentSignatureBitmap(boolean trimBlankSpace) { + + if (!trimBlankSpace) { + return getTransparentSignatureBitmap(); + } + + ensureSignatureBitmap(); + + int imgHeight = mSignatureBitmap.getHeight(); + int imgWidth = mSignatureBitmap.getWidth(); + + int backgroundColor = Color.TRANSPARENT; + + int xMin = Integer.MAX_VALUE, + xMax = Integer.MIN_VALUE, + yMin = Integer.MAX_VALUE, + yMax = Integer.MIN_VALUE; + + boolean foundPixel = false; + + // Find xMin + for (int x = 0; x < imgWidth; x++) { + boolean stop = false; + for (int y = 0; y < imgHeight; y++) { + if (mSignatureBitmap.getPixel(x, y) != backgroundColor) { + xMin = x; + stop = true; + foundPixel = true; + break; + } + } + if (stop) + break; + } + + // Image is empty... + if (!foundPixel) + return null; + + // Find yMin + for (int y = 0; y < imgHeight; y++) { + boolean stop = false; + for (int x = xMin; x < imgWidth; x++) { + if (mSignatureBitmap.getPixel(x, y) != backgroundColor) { + yMin = y; + stop = true; + break; + } + } + if (stop) + break; + } + + // Find xMax + for (int x = imgWidth - 1; x >= xMin; x--) { + boolean stop = false; + for (int y = yMin; y < imgHeight; y++) { + if (mSignatureBitmap.getPixel(x, y) != backgroundColor) { + xMax = x; + stop = true; + break; + } + } + if (stop) + break; + } + + // Find yMax + for (int y = imgHeight - 1; y >= yMin; y--) { + boolean stop = false; + for (int x = xMin; x <= xMax; x++) { + if (mSignatureBitmap.getPixel(x, y) != backgroundColor) { + yMax = y; + stop = true; + break; + } + } + if (stop) + break; + } + + return Bitmap.createBitmap(mSignatureBitmap, xMin, yMin, xMax - xMin, yMax - yMin); + } + + private boolean isDoubleClick() { + if (mClearOnDoubleClick) { + if (mFirstClick != 0 && System.currentTimeMillis() - mFirstClick > DOUBLE_CLICK_DELAY_MS) { + mCountClick = 0; + } + mCountClick++; + if (mCountClick == 1) { + mFirstClick = System.currentTimeMillis(); + } else if (mCountClick == 2) { + long lastClick = System.currentTimeMillis(); + if (lastClick - mFirstClick < DOUBLE_CLICK_DELAY_MS) { + this.clear(); + return true; + } + } + } + return false; + } + + private TimedPoint getNewPoint(float x, float y) { + int mCacheSize = mPointsCache.size(); + TimedPoint timedPoint; + if (mCacheSize == 0) { + // Cache is empty, create a new point + timedPoint = new TimedPoint(); + } else { + // Get point from cache + timedPoint = mPointsCache.remove(mCacheSize - 1); + } + + return timedPoint.set(x, y); + } + + private void recyclePoint(TimedPoint point) { + mPointsCache.add(point); + } + + private void addPoint(TimedPoint newPoint) { + mPoints.add(newPoint); + + int pointsCount = mPoints.size(); + if (pointsCount > 3) { + + ControlTimedPoints tmp = calculateCurveControlPoints(mPoints.get(0), mPoints.get(1), mPoints.get(2)); + TimedPoint c2 = tmp.c2; + recyclePoint(tmp.c1); + + tmp = calculateCurveControlPoints(mPoints.get(1), mPoints.get(2), mPoints.get(3)); + TimedPoint c3 = tmp.c1; + recyclePoint(tmp.c2); + + CurveBezier curve = mCurveBezierCached.set(mPoints.get(1), c2, c3, mPoints.get(2)); + + TimedPoint startPoint = curve.startPoint; + TimedPoint endPoint = curve.endPoint; + + float velocity = endPoint.velocityFrom(startPoint); + velocity = Float.isNaN(velocity) ? 0.0f : velocity; + + velocity = mVelocityFilterWeight * velocity + + (1 - mVelocityFilterWeight) * mLastVelocity; + + // The new width is a function of the velocity. Higher velocities + // correspond to thinner strokes. + float newWidth = strokeWidth(velocity); + + // The Bezier's width starts out as last curve's final width, and + // gradually changes to the stroke width just calculated. The new + // width calculation is based on the velocity between the Bezier's + // start and end mPoints. + addBezier(curve, mLastWidth, newWidth); + + mLastVelocity = velocity; + mLastWidth = newWidth; + + // Remove the first element from the list, + // so that we always have no more than 4 mPoints in mPoints array. + recyclePoint(mPoints.remove(0)); + + recyclePoint(c2); + recyclePoint(c3); + + } else if (pointsCount == 1) { + // To reduce the initial lag make it work with 3 mPoints + // by duplicating the first point + TimedPoint firstPoint = mPoints.get(0); + mPoints.add(getNewPoint(firstPoint.x, firstPoint.y)); + } + } + + private void addBezier(CurveBezier curve, float startWidth, float endWidth) { + mSvgBuilder.append(curve, (startWidth + endWidth) / 2); + ensureSignatureBitmap(); + float originalWidth = mPaint.getStrokeWidth(); + float widthDelta = endWidth - startWidth; + float drawSteps = (float) Math.floor(curve.length()); + + for (int i = 0; i < drawSteps; i++) { + // Calculate the Bezier (x, y) coordinate for this step. + float t = ((float) i) / drawSteps; + float tt = t * t; + float ttt = tt * t; + float u = 1 - t; + float uu = u * u; + float uuu = uu * u; + + float x = uuu * curve.startPoint.x; + x += 3 * uu * t * curve.control1.x; + x += 3 * u * tt * curve.control2.x; + x += ttt * curve.endPoint.x; + + float y = uuu * curve.startPoint.y; + y += 3 * uu * t * curve.control1.y; + y += 3 * u * tt * curve.control2.y; + y += ttt * curve.endPoint.y; + + // Set the incremental stroke width and draw. + mPaint.setStrokeWidth(startWidth + ttt * widthDelta); + mSignatureBitmapCanvas.drawPoint(x, y, mPaint); + expandDirtyRect(x, y); + } + + mPaint.setStrokeWidth(originalWidth); + } + + private ControlTimedPoints calculateCurveControlPoints(TimedPoint s1, TimedPoint s2, TimedPoint s3) { + float dx1 = s1.x - s2.x; + float dy1 = s1.y - s2.y; + float dx2 = s2.x - s3.x; + float dy2 = s2.y - s3.y; + + float m1X = (s1.x + s2.x) / 2.0f; + float m1Y = (s1.y + s2.y) / 2.0f; + float m2X = (s2.x + s3.x) / 2.0f; + float m2Y = (s2.y + s3.y) / 2.0f; + + float l1 = (float) Math.sqrt(dx1 * dx1 + dy1 * dy1); + float l2 = (float) Math.sqrt(dx2 * dx2 + dy2 * dy2); + + float dxm = (m1X - m2X); + float dym = (m1Y - m2Y); + float k = l2 / (l1 + l2); + if (Float.isNaN(k)) k = 0.0f; + float cmX = m2X + dxm * k; + float cmY = m2Y + dym * k; + + float tx = s2.x - cmX; + float ty = s2.y - cmY; + + return mControlTimedPointsCached.set(getNewPoint(m1X + tx, m1Y + ty), getNewPoint(m2X + tx, m2Y + ty)); + } + + private float strokeWidth(float velocity) { + return Math.max(mMaxWidth / (velocity + 1), mMinWidth); + } + + /** + * Called when replaying history to ensure the dirty region includes all + * mPoints. + * + * @param historicalX the previous x coordinate. + * @param historicalY the previous y coordinate. + */ + private void expandDirtyRect(float historicalX, float historicalY) { + if (historicalX < mDirtyRect.left) { + mDirtyRect.left = historicalX; + } else if (historicalX > mDirtyRect.right) { + mDirtyRect.right = historicalX; + } + if (historicalY < mDirtyRect.top) { + mDirtyRect.top = historicalY; + } else if (historicalY > mDirtyRect.bottom) { + mDirtyRect.bottom = historicalY; + } + } + + /** + * Resets the dirty region when the motion event occurs. + * + * @param eventX the event x coordinate. + * @param eventY the event y coordinate. + */ + private void resetDirtyRect(float eventX, float eventY) { + + // The mLastTouchX and mLastTouchY were set when the ACTION_DOWN motion event occurred. + mDirtyRect.left = Math.min(mLastTouchX, eventX); + mDirtyRect.right = Math.max(mLastTouchX, eventX); + mDirtyRect.top = Math.min(mLastTouchY, eventY); + mDirtyRect.bottom = Math.max(mLastTouchY, eventY); + } + + private void setIsEmpty(boolean newValue) { + mIsEmpty = newValue; + if (mOnSignedListener != null) { + if (mIsEmpty) { + mOnSignedListener.onClear(); + } else { + mOnSignedListener.onSigned(); + } + } + } + + private void ensureSignatureBitmap() { + if (mSignatureBitmap == null) { + mSignatureBitmap = Bitmap.createBitmap(getWidth(), getHeight(), + Bitmap.Config.ARGB_8888); + mSignatureBitmapCanvas = new Canvas(mSignatureBitmap); + } + } + + private int convertDpToPx(float dp) { + return Math.round(getContext().getResources().getDisplayMetrics().density * dp); + } +} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewCompat.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewCompat.java new file mode 100644 index 0000000000..17e5187940 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewCompat.java @@ -0,0 +1,15 @@ +package com.navi.amc.investorapp.signature.viewHelper; + +import android.os.Build; +import android.view.View; + +public class ViewCompat { + public static boolean isLaidOut(View view) { + // Future (API19+)... + if (Build.VERSION.SDK_INT >= 19) { + return view.isLaidOut(); + } + // Legacy... + return view.getWidth() > 0 && view.getHeight() > 0; + } +} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewTreeObserverCompat.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewTreeObserverCompat.java new file mode 100644 index 0000000000..d08e3d0641 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewTreeObserverCompat.java @@ -0,0 +1,20 @@ +package com.navi.amc.investorapp.signature.viewHelper; + +import android.annotation.SuppressLint; +import android.os.Build; +import android.view.ViewTreeObserver; + +public class ViewTreeObserverCompat { + @SuppressLint("NewApi") + @SuppressWarnings("deprecation") + public static void removeOnGlobalLayoutListener(ViewTreeObserver observer, ViewTreeObserver.OnGlobalLayoutListener victim) { + // Future (API16+)... + if (Build.VERSION.SDK_INT >= 16) { + observer.removeOnGlobalLayoutListener(victim); + } + // Legacy... + else { + observer.removeGlobalOnLayoutListener(victim); + } + } +} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/ui/kyc/fragments/SignatureFragment.kt b/navi-amc/src/main/java/com/navi/amc/investorapp/ui/kyc/fragments/SignatureFragment.kt index 8449679332..9e5428503f 100644 --- a/navi-amc/src/main/java/com/navi/amc/investorapp/ui/kyc/fragments/SignatureFragment.kt +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/ui/kyc/fragments/SignatureFragment.kt @@ -2,15 +2,8 @@ package com.navi.amc.investorapp.ui.kyc.fragments import android.content.Context import android.graphics.Bitmap -import android.graphics.ImageDecoder -import android.net.Uri -import android.os.Build import android.os.Bundle -import android.provider.MediaStore import android.view.View -import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.content.PermissionChecker -import androidx.lifecycle.ViewModelProvider import com.navi.amc.investorapp.R import com.navi.amc.investorapp.base.BaseFragment import com.navi.amc.investorapp.constants.AnalyticsConstant @@ -19,22 +12,16 @@ import com.navi.amc.investorapp.constants.Constant.SIGNATURE_UPLOAD import com.navi.amc.investorapp.databinding.FragmentSignatureBinding import com.navi.amc.investorapp.sharedpreference.SharedPreferencesKeys import com.navi.amc.investorapp.sharedpreference.SharedPreferencesWriter +import com.navi.amc.investorapp.signature.ui.OnSignedListener import com.navi.amc.investorapp.ui.kyc.listeners.FragmentInterchangeListener import com.navi.amc.investorapp.ui.kyc.listeners.HeaderInteractionListener -import com.navi.amc.investorapp.ui.kyc.viewmodel.KycVM import com.navi.amc.investorapp.ui.kyc.viewmodel.SignatureVM import com.navi.amc.investorapp.util.IconUtils -import com.navi.amc.investorapp.util.PermissionsManager -import com.navi.amc.investorapp.util.observeNonNull import java.io.ByteArrayOutputStream class SignatureFragment : BaseFragment(), View.OnClickListener { - private val permissionsManager by lazy { PermissionsManager(requireActivity()) } - private val storagePermission by lazy { arrayOf(PermissionsManager.STORAGE_PERMISSION) } - private var isPermissionAlready = false - private var bitmapImage: Bitmap? = null private val analyticsEventTracker = AnalyticsConstant.KycScreen() companion object { @@ -60,7 +47,6 @@ class SignatureFragment : override val layoutRes: Int get() = R.layout.fragment_signature - override fun bindViewModel() { binding.lifecycleOwner = this } @@ -81,46 +67,28 @@ class SignatureFragment : 85, IconUtils.ICON_SIGNATURE ) - binding.uploadIv.visibility = View.VISIBLE - binding.uploadTitleTv.visibility = View.VISIBLE - binding.saveTv.visibility = View.GONE - binding.retakeTv.visibility = View.GONE - binding.permissionDeniedView.visibility = View.GONE - binding.permissionDeniedView.setTitle(getString(R.string.allow_storage_permission)) - binding.permissionDeniedView.setBgColor(R.color.white) - isPermissionAlready = permissionsManager.hasPermissions(storagePermission) - if (!isPermissionAlready) { - permissionsManager.requestPermissions( - storagePermission, - PermissionsManager.REQUEST_CODE - ) - } + disable(binding.saveTv) + disable(binding.redoTv) } private fun initListeners() { binding.saveTv.setOnClickListener(this) - binding.retakeTv.setOnClickListener(this) - binding.uploadTitleTv.setOnClickListener(this) - binding.uploadIv.setOnClickListener(this) - } + binding.redoTv.setOnClickListener(this) + binding.signaturePad.setOnSignedListener(object : OnSignedListener { + override fun onStartSigning() { + enable(binding.saveTv) + enable(binding.redoTv) + } - private fun initAfterScreenCapture(uri: Uri) { - val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - ImageDecoder.decodeBitmap( - ImageDecoder.createSource( - requireContext().contentResolver, - uri - ) - ) - } else { - MediaStore.Images.Media.getBitmap(requireContext().contentResolver, uri) - } - bitmapImage = bitmap - binding.uploadIv.visibility = View.GONE - binding.uploadTitleTv.visibility = View.GONE - binding.saveTv.visibility = View.VISIBLE - binding.retakeTv.visibility = View.VISIBLE - binding.docIv.setImageURI(uri) + override fun onSigned() { + + } + + override fun onClear() { + disable(binding.saveTv) + disable(binding.redoTv) + } + }) } private fun initObservers() { @@ -136,58 +104,22 @@ class SignatureFragment : val accessToken = sharedPreferences?.getObject(SharedPreferencesKeys.COMMON_RESPONSE_OBJECT)?.accessToken.toString() val outputStream = ByteArrayOutputStream() + val bitmapImage = binding.signaturePad.signatureBitmap bitmapImage?.compress(Bitmap.CompressFormat.JPEG, 70, outputStream) viewModel.submitData(accessToken, SIGNATURE_UPLOAD, outputStream.toByteArray()) } - R.id.retake_tv, R.id.upload_iv, R.id.upload_title_tv -> { - if (permissionsManager.hasPermissions(storagePermission) - ) openStorage() else - permissionsManager.requestPermissions( - storagePermission, - PermissionsManager.REQUEST_CODE - ) + R.id.redo_tv -> { + binding.signaturePad.clear() } } } - private val getContent = - registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> - uri?.let { - initAfterScreenCapture(uri) - } - } - - private fun openStorage() { - getContent.launch("image/*") + private fun enable(view: View) { + view.alpha = 1f } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - activity?.let { - val sharedViewModel = ViewModelProvider(it).get(KycVM::class.java) - observePermissionResult(sharedViewModel) - } - } - - private fun observePermissionResult(sharedViewModel: KycVM) { - sharedViewModel.permissionResult.observeNonNull(this) { - when (it?.third) { - PermissionsManager.REQUEST_CODE -> { - if (it.second.firstOrNull() == PermissionChecker.PERMISSION_GRANTED) { - init() - } else { - binding.permissionDeniedView.visibility = View.VISIBLE - } - } - } - sharedViewModel.permissionResult.value = null - } - } - - override fun onResume() { - super.onResume() - if (permissionsManager.hasPermissions(storagePermission) && !isPermissionAlready) { - init() - } + private fun disable(view: View) { + view.alpha = 0.5f } override val screenName: String diff --git a/navi-amc/src/main/res/drawable/progressbar_horizontal_rounded.xml b/navi-amc/src/main/res/drawable/progressbar_horizontal.xml similarity index 100% rename from navi-amc/src/main/res/drawable/progressbar_horizontal_rounded.xml rename to navi-amc/src/main/res/drawable/progressbar_horizontal.xml diff --git a/navi-amc/src/main/res/layout/activity_verify_pan_card.xml b/navi-amc/src/main/res/layout/activity_verify_pan_card.xml index 3c69bca154..a232663ab5 100644 --- a/navi-amc/src/main/res/layout/activity_verify_pan_card.xml +++ b/navi-amc/src/main/res/layout/activity_verify_pan_card.xml @@ -119,7 +119,7 @@ android:layout_height="@dimen/_4dp" android:layout_marginTop="@dimen/_5dp" android:progress="20" - android:progressDrawable="@drawable/progressbar_horizontal_rounded" + android:progressDrawable="@drawable/progressbar_horizontal" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/tvProgress" /> diff --git a/navi-amc/src/main/res/layout/fragment_address.xml b/navi-amc/src/main/res/layout/fragment_address.xml index a307a31c71..c59a5a807d 100644 --- a/navi-amc/src/main/res/layout/fragment_address.xml +++ b/navi-amc/src/main/res/layout/fragment_address.xml @@ -76,7 +76,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/_24dp" - android:text="@string/pincode" + android:text="@string/pincode_text" app:layout_constraintStart_toStartOf="@+id/pincode_ev" app:layout_constraintTop_toBottomOf="@+id/address_ev" /> @@ -88,7 +88,7 @@ android:layout_marginStart="@dimen/_8dp" android:layout_marginTop="@dimen/_8dp" android:gravity="center_vertical" - android:hint="@string/pincode" + android:hint="@string/pincode_text" android:imeOptions="actionNext" android:inputType="number" android:maxLength="6" diff --git a/navi-amc/src/main/res/layout/fragment_signature.xml b/navi-amc/src/main/res/layout/fragment_signature.xml index 33f38323b1..6350b72a3c 100644 --- a/navi-amc/src/main/res/layout/fragment_signature.xml +++ b/navi-amc/src/main/res/layout/fragment_signature.xml @@ -2,77 +2,32 @@ - - - - - - - - - - @@ -84,20 +39,11 @@ android:layout_height="@dimen/_48dp" android:layout_marginStart="@dimen/_8dp" android:gravity="center" - android:text="@string/continuee" + android:text="@string/save" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@+id/retake_tv" /> + app:layout_constraintStart_toEndOf="@+id/redo_tv" /> - - \ No newline at end of file diff --git a/navi-amc/src/main/res/layout/view_progress_header.xml b/navi-amc/src/main/res/layout/view_progress_header.xml index 089cab0e00..18bf40b90e 100644 --- a/navi-amc/src/main/res/layout/view_progress_header.xml +++ b/navi-amc/src/main/res/layout/view_progress_header.xml @@ -12,7 +12,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/_20dp" - android:paddingStart="@dimen/_4dp" android:paddingTop="@dimen/_10dp" android:paddingEnd="@dimen/_30dp" android:paddingBottom="@dimen/_10dp" @@ -68,7 +67,7 @@ android:layout_height="@dimen/_4dp" android:layout_marginTop="@dimen/_16dp" android:progress="50" - android:progressDrawable="@drawable/progressbar_horizontal_rounded" + android:progressDrawable="@drawable/progressbar_horizontal" android:visibility="gone" app:layout_constraintTop_toBottomOf="@id/subtitle_tv" /> diff --git a/navi-amc/src/main/res/values-night/attrs.xml b/navi-amc/src/main/res/values-night/attrs.xml new file mode 100644 index 0000000000..c1aab882ec --- /dev/null +++ b/navi-amc/src/main/res/values-night/attrs.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/navi-amc/src/main/res/values/dimens.xml b/navi-amc/src/main/res/values/dimens.xml index c771c81a9b..803c3ce546 100644 --- a/navi-amc/src/main/res/values/dimens.xml +++ b/navi-amc/src/main/res/values/dimens.xml @@ -37,6 +37,7 @@ 70dp 76dp 80dp + 84dp 88dp 92dp 96dp @@ -90,6 +91,7 @@ 172dp 200dp 205dp + 440dp 55dp 33dp 125dp diff --git a/navi-amc/src/main/res/values/strings.xml b/navi-amc/src/main/res/values/strings.xml index c4f8a7735c..63ad817b6d 100644 --- a/navi-amc/src/main/res/values/strings.xml +++ b/navi-amc/src/main/res/values/strings.xml @@ -134,7 +134,7 @@ Select Bank Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum - Pincode + Pincode Ex - 650087 House no/ Street name House no 10 @@ -542,6 +542,7 @@ Ensure the photo has white background Upload Signature Upload again + Redo Speak the digits below while recording Tips for great video Retake video From 03322d3a419a9aa93b17753d5968cc45c962cc94 Mon Sep 17 00:00:00 2001 From: Shankar Yadav Date: Fri, 29 Oct 2021 18:49:12 +0530 Subject: [PATCH 2/2] Convert java file into kotlin --- .../drawerControllers/ControlTimedPoints.java | 14 - .../drawerControllers/ControlTimedPoints.kt | 11 + .../drawerControllers/CurveBezier.java | 49 -- .../drawerControllers/CurveBezier.kt | 52 ++ .../drawerControllers/TimedPoint.java | 24 - .../signature/drawerControllers/TimedPoint.kt | 31 + .../signature/svgUtils/SvgBuilder.java | 76 -- .../signature/svgUtils/SvgBuilder.kt | 68 ++ .../signature/svgUtils/SvgPathBuilder.java | 73 -- .../signature/svgUtils/SvgPathBuilder.kt | 69 ++ .../signature/svgUtils/SvgPoint.java | 56 -- .../signature/svgUtils/SvgPoint.kt | 52 ++ .../signature/ui/SilkySignaturePad.java | 651 ------------------ .../signature/ui/SilkySignaturePad.kt | 567 +++++++++++++++ .../signature/viewHelper/ViewCompat.java | 15 - .../signature/viewHelper/ViewCompat.kt | 14 + .../viewHelper/ViewTreeObserverCompat.java | 20 - .../viewHelper/ViewTreeObserverCompat.kt | 20 + 18 files changed, 884 insertions(+), 978 deletions(-) delete mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/ControlTimedPoints.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/ControlTimedPoints.kt delete mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/CurveBezier.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/CurveBezier.kt delete mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/TimedPoint.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/TimedPoint.kt delete mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgBuilder.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgBuilder.kt delete mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPathBuilder.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPathBuilder.kt delete mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPoint.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPoint.kt delete mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/SilkySignaturePad.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/SilkySignaturePad.kt delete mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewCompat.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewCompat.kt delete mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewTreeObserverCompat.java create mode 100644 navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewTreeObserverCompat.kt diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/ControlTimedPoints.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/ControlTimedPoints.java deleted file mode 100644 index 1d80f182a4..0000000000 --- a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/ControlTimedPoints.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.navi.amc.investorapp.signature.drawerControllers; - -public class ControlTimedPoints { - - public TimedPoint c1; - public TimedPoint c2; - - public ControlTimedPoints set(TimedPoint c1, TimedPoint c2) { - this.c1 = c1; - this.c2 = c2; - return this; - } - -} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/ControlTimedPoints.kt b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/ControlTimedPoints.kt new file mode 100644 index 0000000000..7fd8d90429 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/ControlTimedPoints.kt @@ -0,0 +1,11 @@ +package com.navi.amc.investorapp.signature.drawerControllers + +class ControlTimedPoints { + var c1: TimedPoint? = null + var c2: TimedPoint? = null + fun set(c1: TimedPoint?, c2: TimedPoint?): ControlTimedPoints { + this.c1 = c1 + this.c2 = c2 + return this + } +} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/CurveBezier.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/CurveBezier.java deleted file mode 100644 index 594580c264..0000000000 --- a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/CurveBezier.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.navi.amc.investorapp.signature.drawerControllers; - -public class CurveBezier { - - public TimedPoint startPoint; - public TimedPoint control1; - public TimedPoint control2; - public TimedPoint endPoint; - - public CurveBezier set(TimedPoint startPoint, TimedPoint control1, - TimedPoint control2, TimedPoint endPoint) { - this.startPoint = startPoint; - this.control1 = control1; - this.control2 = control2; - this.endPoint = endPoint; - return this; - } - - public float length() { - int steps = 10; - float length = 0; - double cx, cy, px = 0, py = 0, xDiff, yDiff; - - for (int i = 0; i <= steps; i++) { - float t = (float) i / steps; - cx = point(t, this.startPoint.x, this.control1.x, - this.control2.x, this.endPoint.x); - cy = point(t, this.startPoint.y, this.control1.y, - this.control2.y, this.endPoint.y); - if (i > 0) { - xDiff = cx - px; - yDiff = cy - py; - length += Math.sqrt(xDiff * xDiff + yDiff * yDiff); - } - px = cx; - py = cy; - } - return length; - - } - - public double point(float t, float start, float c1, float c2, float end) { - return start * (1.0 - t) * (1.0 - t) * (1.0 - t) - + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t - + 3.0 * c2 * (1.0 - t) * t * t - + end * t * t * t; - } - -} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/CurveBezier.kt b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/CurveBezier.kt new file mode 100644 index 0000000000..b03fd5695f --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/CurveBezier.kt @@ -0,0 +1,52 @@ +package com.navi.amc.investorapp.signature.drawerControllers + +class CurveBezier { + var startPoint: TimedPoint? = null + var control1: TimedPoint? = null + var control2: TimedPoint? = null + var endPoint: TimedPoint? = null + operator fun set( + startPoint: TimedPoint?, control1: TimedPoint?, + control2: TimedPoint?, endPoint: TimedPoint? + ): CurveBezier { + this.startPoint = startPoint + this.control1 = control1 + this.control2 = control2 + this.endPoint = endPoint + return this + } + + fun length(): Float { + val steps = 10 + var length = 0f + var cx: Double + var cy: Double + var px = 0.0 + var py = 0.0 + var xDiff: Double + var yDiff: Double + for (i in 0..steps) { + val t = i.toFloat() / steps + cx = point( + t, startPoint!!.x, control1!!.x, + control2!!.x, endPoint!!.x + ) + cy = point( + t, startPoint!!.y, control1!!.y, + control2!!.y, endPoint!!.y + ) + if (i > 0) { + xDiff = cx - px + yDiff = cy - py + length += Math.sqrt(xDiff * xDiff + yDiff * yDiff).toFloat() + } + px = cx + py = cy + } + return length + } + + fun point(t: Float, start: Float, c1: Float, c2: Float, end: Float): Double { + return start * (1.0 - t) * (1.0 - t) * (1.0 - t) + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t + 3.0 * c2 * (1.0 - t) * t * t + end * t * t * t + } +} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/TimedPoint.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/TimedPoint.java deleted file mode 100644 index bfeb4d723f..0000000000 --- a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/TimedPoint.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.navi.amc.investorapp.signature.drawerControllers; - -public class TimedPoint { - public float x; - public float y; - public long timestamp; - - public TimedPoint set(float x, float y) { - this.x = x; - this.y = y; - this.timestamp = System.currentTimeMillis(); - return this; - } - - public float velocityFrom(TimedPoint start) { - float velocity = distanceTo(start) / (this.timestamp - start.timestamp); - if (velocity != velocity) return 0f; - return velocity; - } - - public float distanceTo(TimedPoint point) { - return (float) Math.sqrt(Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2)); - } -} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/TimedPoint.kt b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/TimedPoint.kt new file mode 100644 index 0000000000..353b0d20b9 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/drawerControllers/TimedPoint.kt @@ -0,0 +1,31 @@ +package com.navi.amc.investorapp.signature.drawerControllers + +import com.navi.amc.investorapp.util.orZero + +class TimedPoint { + @JvmField + var x = 0f + @JvmField + var y = 0f + var timestamp: Long = 0 + operator fun set(x: Float, y: Float): TimedPoint { + this.x = x + this.y = y + timestamp = System.currentTimeMillis() + return this + } + + fun velocityFrom(start: TimedPoint?): Float { + val velocity = distanceTo(start) / (timestamp - start?.timestamp.orZero()) + return if (velocity != velocity) 0f else velocity + } + + fun distanceTo(point: TimedPoint?): Float { + return Math.sqrt( + Math.pow((point?.x.orZero() - x).toDouble(), 2.0) + Math.pow( + (point?.y.orZero() - y).toDouble(), + 2.0 + ) + ).toFloat() + } +} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgBuilder.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgBuilder.java deleted file mode 100644 index e4a2885ca9..0000000000 --- a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgBuilder.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.navi.amc.investorapp.signature.svgUtils; - -import com.navi.amc.investorapp.signature.drawerControllers.CurveBezier; - -public class SvgBuilder { - - private final StringBuilder mSvgPathsBuilder = new StringBuilder(); - private SvgPathBuilder mCurrentPathBuilder = null; - - public SvgBuilder() { - } - - public void clear() { - mSvgPathsBuilder.setLength(0); - mCurrentPathBuilder = null; - } - - public String build(final int width, final int height) { - if (isPathStarted()) { - appendCurrentPath(); - } - return (new StringBuilder()) - .append("\n") - .append("") - .append("") - .append(mSvgPathsBuilder) - .append("") - .append("") - .toString(); - } - - public SvgBuilder append(final CurveBezier curve, final float strokeWidth) { - final Integer roundedStrokeWidth = Math.round(strokeWidth); - final SvgPoint curveStartSvgPoint = new SvgPoint(curve.startPoint); - final SvgPoint curveControlSvgPoint1 = new SvgPoint(curve.control1); - final SvgPoint curveControlSvgPoint2 = new SvgPoint(curve.control2); - final SvgPoint curveEndSvgPoint = new SvgPoint(curve.endPoint); - - if (!isPathStarted()) { - startNewPath(roundedStrokeWidth, curveStartSvgPoint); - } - - if (!curveStartSvgPoint.equals(mCurrentPathBuilder.getLastPoint()) - || !roundedStrokeWidth.equals(mCurrentPathBuilder.getStrokeWidth())) { - appendCurrentPath(); - startNewPath(roundedStrokeWidth, curveStartSvgPoint); - } - - mCurrentPathBuilder.append(curveControlSvgPoint1, curveControlSvgPoint2, curveEndSvgPoint); - return this; - } - - private void startNewPath(Integer roundedStrokeWidth, SvgPoint curveStartSvgPoint) { - mCurrentPathBuilder = new SvgPathBuilder(curveStartSvgPoint, roundedStrokeWidth); - } - - private void appendCurrentPath() { - mSvgPathsBuilder.append(mCurrentPathBuilder); - } - - private boolean isPathStarted() { - return mCurrentPathBuilder != null; - } - -} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgBuilder.kt b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgBuilder.kt new file mode 100644 index 0000000000..b008284d94 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgBuilder.kt @@ -0,0 +1,68 @@ +package com.navi.amc.investorapp.signature.svgUtils + +import com.navi.amc.investorapp.signature.drawerControllers.CurveBezier +import java.lang.StringBuilder + +class SvgBuilder { + private val mSvgPathsBuilder = StringBuilder() + private var mCurrentPathBuilder: SvgPathBuilder? = null + fun clear() { + mSvgPathsBuilder.setLength(0) + mCurrentPathBuilder = null + } + + fun build(width: Int, height: Int): String { + if (isPathStarted) { + appendCurrentPath() + } + return StringBuilder() + .append("\n") + .append("") + .append("") + .append(mSvgPathsBuilder) + .append("") + .append("") + .toString() + } + + fun append(curve: CurveBezier, strokeWidth: Float): SvgBuilder { + val roundedStrokeWidth = Math.round(strokeWidth) + val curveStartSvgPoint = SvgPoint(curve.startPoint) + val curveControlSvgPoint1 = SvgPoint(curve.control1) + val curveControlSvgPoint2 = SvgPoint(curve.control2) + val curveEndSvgPoint = SvgPoint(curve.endPoint) + if (!isPathStarted) { + startNewPath(roundedStrokeWidth, curveStartSvgPoint) + } + if (curveStartSvgPoint != mCurrentPathBuilder!!.lastPoint + || roundedStrokeWidth != mCurrentPathBuilder!!.strokeWidth + ) { + appendCurrentPath() + startNewPath(roundedStrokeWidth, curveStartSvgPoint) + } + mCurrentPathBuilder!!.append(curveControlSvgPoint1, curveControlSvgPoint2, curveEndSvgPoint) + return this + } + + private fun startNewPath(roundedStrokeWidth: Int, curveStartSvgPoint: SvgPoint) { + mCurrentPathBuilder = SvgPathBuilder(curveStartSvgPoint, roundedStrokeWidth) + } + + private fun appendCurrentPath() { + mSvgPathsBuilder.append(mCurrentPathBuilder) + } + + private val isPathStarted: Boolean + private get() = mCurrentPathBuilder != null +} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPathBuilder.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPathBuilder.java deleted file mode 100644 index 25bbf8be86..0000000000 --- a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPathBuilder.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.navi.amc.investorapp.signature.svgUtils; - -public class SvgPathBuilder { - - public static final Character SVG_RELATIVE_CUBIC_BEZIER_CURVE = 'c'; - public static final Character SVG_MOVE = 'M'; - private final StringBuilder mStringBuilder; - private final Integer mStrokeWidth; - private final SvgPoint mStartPoint; - private SvgPoint mLastPoint; - - public SvgPathBuilder(final SvgPoint startPoint, final Integer strokeWidth) { - mStrokeWidth = strokeWidth; - mStartPoint = startPoint; - mLastPoint = startPoint; - mStringBuilder = new StringBuilder(); - mStringBuilder.append(SVG_RELATIVE_CUBIC_BEZIER_CURVE); - - } - - public final Integer getStrokeWidth() { - return mStrokeWidth; - } - - public final SvgPoint getLastPoint() { - return mLastPoint; - } - - public SvgPathBuilder append(final SvgPoint controlPoint1, final SvgPoint controlPoint2, final SvgPoint endPoint) { - mStringBuilder.append(makeRelativeCubicBezierCurve(controlPoint1, controlPoint2, endPoint)); - mLastPoint = endPoint; - return this; - } - - @Override - public String toString() { - return (new StringBuilder()) - .append("") - .toString(); - } - - private String makeRelativeCubicBezierCurve(final SvgPoint controlPoint1, final SvgPoint controlPoint2, final SvgPoint endPoint) { - final String sControlPoint1 = controlPoint1.toRelativeCoordinates(mLastPoint); - final String sControlPoint2 = controlPoint2.toRelativeCoordinates(mLastPoint); - final String sEndPoint = endPoint.toRelativeCoordinates(mLastPoint); - - final StringBuilder sb = new StringBuilder(); - - sb.append(sControlPoint1); - sb.append(" "); - sb.append(sControlPoint2); - sb.append(" "); - - sb.append(sEndPoint); - sb.append(" "); - - // discard zero curve - final String svg = sb.toString(); - if ("c0 0 0 0 0 0".equals(svg)) { - return ""; - } else { - return svg; - } - } -} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPathBuilder.kt b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPathBuilder.kt new file mode 100644 index 0000000000..0b98d9336d --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPathBuilder.kt @@ -0,0 +1,69 @@ +package com.navi.amc.investorapp.signature.svgUtils + +import java.lang.StringBuilder + +class SvgPathBuilder(private val mStartPoint: SvgPoint, val strokeWidth: Int) { + private val mStringBuilder: StringBuilder + var lastPoint: SvgPoint + private set + + fun append( + controlPoint1: SvgPoint, + controlPoint2: SvgPoint, + endPoint: SvgPoint + ): SvgPathBuilder { + mStringBuilder.append(makeRelativeCubicBezierCurve(controlPoint1, controlPoint2, endPoint)) + lastPoint = endPoint + return this + } + + override fun toString(): String { + return StringBuilder() + .append("") + .toString() + } + + private fun makeRelativeCubicBezierCurve( + controlPoint1: SvgPoint, + controlPoint2: SvgPoint, + endPoint: SvgPoint + ): String { + val sControlPoint1 = controlPoint1.toRelativeCoordinates(lastPoint) + val sControlPoint2 = controlPoint2.toRelativeCoordinates(lastPoint) + val sEndPoint = endPoint.toRelativeCoordinates(lastPoint) + val sb = StringBuilder() + sb.append(sControlPoint1) + sb.append(" ") + sb.append(sControlPoint2) + sb.append(" ") + sb.append(sEndPoint) + sb.append(" ") + + // discard zero curve + val svg = sb.toString() + return if ("c0 0 0 0 0 0" == svg) { + "" + } else { + svg + } + } + + companion object { + const val SVG_RELATIVE_CUBIC_BEZIER_CURVE = 'c' + const val SVG_MOVE = 'M' + } + + init { + lastPoint = mStartPoint + mStringBuilder = StringBuilder() + mStringBuilder.append(SVG_RELATIVE_CUBIC_BEZIER_CURVE) + } +} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPoint.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPoint.java deleted file mode 100644 index 79dd6cf573..0000000000 --- a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPoint.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.navi.amc.investorapp.signature.svgUtils; - -import com.navi.amc.investorapp.signature.drawerControllers.TimedPoint; - -class SvgPoint { - - final Integer x, y; - - public SvgPoint(TimedPoint point) { - // one optimisation is to get rid of decimals as they are mostly non-significant in the - // produced SVG image - x = Math.round(point.x); - y = Math.round(point.y); - } - - public SvgPoint(int x, int y) { - this.x = x; - this.y = y; - } - - public String toAbsoluteCoordinates() { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(x); - stringBuilder.append(","); - stringBuilder.append(y); - return stringBuilder.toString(); - } - - public String toRelativeCoordinates(final SvgPoint referencePoint) { - return (new SvgPoint(x - referencePoint.x, y - referencePoint.y)).toString(); - } - - @Override - public String toString() { - return toAbsoluteCoordinates(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - SvgPoint svgPoint = (SvgPoint) o; - - if (!x.equals(svgPoint.x)) return false; - return y.equals(svgPoint.y); - - } - - @Override - public int hashCode() { - int result = x.hashCode(); - result = 31 * result + y.hashCode(); - return result; - } -} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPoint.kt b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPoint.kt new file mode 100644 index 0000000000..15b5907ba1 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/svgUtils/SvgPoint.kt @@ -0,0 +1,52 @@ +package com.navi.amc.investorapp.signature.svgUtils + +import com.navi.amc.investorapp.signature.drawerControllers.TimedPoint +import com.navi.amc.investorapp.signature.svgUtils.SvgPoint +import com.navi.amc.investorapp.util.orZero +import java.lang.StringBuilder + +class SvgPoint { + val x: Int + val y: Int + + constructor(point: TimedPoint?) { + // one optimisation is to get rid of decimals as they are mostly non-significant in the + // produced SVG image + x = Math.round(point?.x.orZero()) + y = Math.round(point?.y.orZero()) + } + + constructor(x: Int, y: Int) { + this.x = x + this.y = y + } + + fun toAbsoluteCoordinates(): String { + val stringBuilder = StringBuilder() + stringBuilder.append(x) + stringBuilder.append(",") + stringBuilder.append(y) + return stringBuilder.toString() + } + + fun toRelativeCoordinates(referencePoint: SvgPoint?): String { + return SvgPoint(x - referencePoint?.x.orZero(), y - referencePoint?.y.orZero()).toString() + } + + override fun toString(): String { + return toAbsoluteCoordinates() + } + + override fun equals(o: Any?): Boolean { + if (this === o) return true + if (o == null || javaClass != o.javaClass) return false + val svgPoint = o as SvgPoint + return if (x != svgPoint.x) false else y == svgPoint.y + } + + override fun hashCode(): Int { + var result = x.hashCode() + result = 31 * result + y.hashCode() + return result + } +} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/SilkySignaturePad.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/SilkySignaturePad.java deleted file mode 100644 index a3c5c20f05..0000000000 --- a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/SilkySignaturePad.java +++ /dev/null @@ -1,651 +0,0 @@ -package com.navi.amc.investorapp.signature.ui; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewTreeObserver; - -import com.navi.amc.investorapp.R; -import com.navi.amc.investorapp.signature.drawerControllers.ControlTimedPoints; -import com.navi.amc.investorapp.signature.drawerControllers.CurveBezier; -import com.navi.amc.investorapp.signature.drawerControllers.TimedPoint; -import com.navi.amc.investorapp.signature.svgUtils.SvgBuilder; -import com.navi.amc.investorapp.signature.viewHelper.ViewCompat; -import com.navi.amc.investorapp.signature.viewHelper.ViewTreeObserverCompat; - -import java.util.ArrayList; -import java.util.List; - - -public class SilkySignaturePad extends View { - //View state - private List mPoints; - private boolean mIsEmpty; - private float mLastTouchX; - private float mLastTouchY; - private float mLastVelocity; - private float mLastWidth; - private RectF mDirtyRect; - - private final SvgBuilder mSvgBuilder = new SvgBuilder(); - - // Cache - private List mPointsCache = new ArrayList<>(); - private ControlTimedPoints mControlTimedPointsCached = new ControlTimedPoints(); - private CurveBezier mCurveBezierCached = new CurveBezier(); - - //Configurable parameters - private int mMinWidth; - private int mMaxWidth; - private float mVelocityFilterWeight; - private OnSignedListener mOnSignedListener; - private boolean mClearOnDoubleClick; - - //Click values - private long mFirstClick; - private int mCountClick; - private static final int DOUBLE_CLICK_DELAY_MS = 200; - - //Default attribute values - private final int DEFAULT_ATTR_PEN_MIN_WIDTH_PX = 3; - private final int DEFAULT_ATTR_PEN_MAX_WIDTH_PX = 7; - private final int DEFAULT_ATTR_PEN_COLOR = Color.BLACK; - private final float DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT = 0.9f; - private final boolean DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK = false; - - private Paint mPaint = new Paint(); - private Bitmap mSignatureBitmap = null; - private Canvas mSignatureBitmapCanvas = null; - - public SilkySignaturePad(Context context, AttributeSet attrs) { - super(context, attrs); - - TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, - R.styleable.SilkySignaturePad, - 0, 0); - - //Configurable parameters - try { - mMinWidth = a.getDimensionPixelSize(R.styleable.SilkySignaturePad_penMinWidth, convertDpToPx(DEFAULT_ATTR_PEN_MIN_WIDTH_PX)); - mMaxWidth = a.getDimensionPixelSize(R.styleable.SilkySignaturePad_penMaxWidth, convertDpToPx(DEFAULT_ATTR_PEN_MAX_WIDTH_PX)); - mPaint.setColor(a.getColor(R.styleable.SilkySignaturePad_penColor, DEFAULT_ATTR_PEN_COLOR)); - mVelocityFilterWeight = a.getFloat(R.styleable.SilkySignaturePad_velocityFilterWeight, DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT); - mClearOnDoubleClick = a.getBoolean(R.styleable.SilkySignaturePad_clearOnDoubleClick, DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK); - } finally { - a.recycle(); - } - - //Fixed parameters - mPaint.setAntiAlias(true); - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeCap(Paint.Cap.ROUND); - mPaint.setStrokeJoin(Paint.Join.ROUND); - - //Dirty rectangle to update only the changed portion of the view - mDirtyRect = new RectF(); - - clear(); - } - - /** - * Set the pen color from a given resource. - * If the resource is not found, {@link Color#BLACK} is assumed. - * - * @param colorRes the color resource. - */ - public void setPenColorRes(int colorRes) { - try { - setPenColor(getResources().getColor(colorRes)); - } catch (Resources.NotFoundException ex) { - setPenColor(Color.parseColor("#000000")); - } - } - - /** - * Set the pen color from a given color. - * - * @param color the color. - */ - public void setPenColor(int color) { - mPaint.setColor(color); - } - - /** - * Set the minimum width of the stroke in pixel. - * - * @param minWidth the width in dp. - */ - public void setMinWidth(float minWidth) { - mMinWidth = convertDpToPx(minWidth); - } - - /** - * Set the maximum width of the stroke in pixel. - * - * @param maxWidth the width in dp. - */ - public void setMaxWidth(float maxWidth) { - mMaxWidth = convertDpToPx(maxWidth); - } - - /** - * Set the velocity filter weight. - * - * @param velocityFilterWeight the weight. - */ - public void setVelocityFilterWeight(float velocityFilterWeight) { - mVelocityFilterWeight = velocityFilterWeight; - } - - public void clear() { - mSvgBuilder.clear(); - mPoints = new ArrayList<>(); - mLastVelocity = 0; - mLastWidth = (mMinWidth + mMaxWidth) / 2; - - if (mSignatureBitmap != null) { - mSignatureBitmap = null; - ensureSignatureBitmap(); - } - - setIsEmpty(true); - - invalidate(); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (!isEnabled()) - return false; - - float eventX = event.getX(); - float eventY = event.getY(); - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - getParent().requestDisallowInterceptTouchEvent(true); - mPoints.clear(); - if (isDoubleClick()) break; - mLastTouchX = eventX; - mLastTouchY = eventY; - addPoint(getNewPoint(eventX, eventY)); - if (mOnSignedListener != null) mOnSignedListener.onStartSigning(); - - case MotionEvent.ACTION_MOVE: - resetDirtyRect(eventX, eventY); - addPoint(getNewPoint(eventX, eventY)); - break; - - case MotionEvent.ACTION_UP: - resetDirtyRect(eventX, eventY); - addPoint(getNewPoint(eventX, eventY)); - getParent().requestDisallowInterceptTouchEvent(true); - setIsEmpty(false); - break; - - default: - return false; - } - - //invalidate(); - invalidate( - (int) (mDirtyRect.left - mMaxWidth), - (int) (mDirtyRect.top - mMaxWidth), - (int) (mDirtyRect.right + mMaxWidth), - (int) (mDirtyRect.bottom + mMaxWidth)); - - return true; - } - - @Override - protected void onDraw(Canvas canvas) { - if (mSignatureBitmap != null) { - canvas.drawBitmap(mSignatureBitmap, 0, 0, mPaint); - } - } - - public void setOnSignedListener(OnSignedListener listener) { - mOnSignedListener = listener; - } - - public boolean isEmpty() { - return mIsEmpty; - } - - public String getSignatureSvg() { - int width = getTransparentSignatureBitmap().getWidth(); - int height = getTransparentSignatureBitmap().getHeight(); - return mSvgBuilder.build(width, height); - } - - public Bitmap getSignatureBitmap() { - Bitmap originalBitmap = getTransparentSignatureBitmap(); - Bitmap whiteBgBitmap = Bitmap.createBitmap(originalBitmap.getWidth(), originalBitmap.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(whiteBgBitmap); - canvas.drawColor(Color.WHITE); - canvas.drawBitmap(originalBitmap, 0, 0, null); - return whiteBgBitmap; - } - - /** - * @param compressPercentage Hint to the compressor, 0-100 percent. 0 meaning compress for - * small size, 100 meaning compress for max quality. Some - * formats, like PNG which is lossless, will ignore the - * quality setting - */ - public Bitmap getCompressedSignatureBitmap(int compressPercentage) { - - if (compressPercentage < 0) { - compressPercentage = 0; - } else if (compressPercentage > 100) { - compressPercentage = 100; - } - Bitmap originalBitmap = getTransparentSignatureBitmap(); - int originalWidth = originalBitmap.getWidth(); - int originalHeight = originalBitmap.getHeight(); - - int targetWidth = originalWidth * compressPercentage / 100; // your arbitrary fixed limit - int targetHeight = (int) (originalHeight * targetWidth / (double) originalWidth); - - Bitmap whiteBgBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(whiteBgBitmap); - canvas.drawColor(Color.WHITE); - canvas.drawBitmap(originalBitmap, 0, 0, null); - whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true); - return whiteBgBitmap; - } - - /** - * @param deiredWidth Desired width of the bitmap - */ - public Bitmap getFixedSizeSignatureBitmap(int deiredWidth) { - - Bitmap originalBitmap = getTransparentSignatureBitmap(); - int originalWidth = originalBitmap.getWidth(); - int originalHeight = originalBitmap.getHeight(); - - int targetWidth = deiredWidth; // your arbitrary fixed limit - int targetHeight = (int) (originalHeight * targetWidth / (double) originalWidth); - - Bitmap whiteBgBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(whiteBgBitmap); - canvas.drawColor(Color.WHITE); - canvas.drawBitmap(originalBitmap, 0, 0, null); - whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true); - return whiteBgBitmap; - } - - /** - * @param deiredWidth Desired width of the bitmap - */ - public Bitmap getFixedSizeSignatureBitmap(int deiredWidth,int desiredHeight) { - - Bitmap originalBitmap = getTransparentSignatureBitmap(); - int originalWidth = originalBitmap.getWidth(); - int originalHeight = originalBitmap.getHeight(); - - int targetWidth = deiredWidth; // your arbitrary fixed limit - int targetHeight = desiredHeight; - - Bitmap whiteBgBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(whiteBgBitmap); - canvas.drawColor(Color.WHITE); - canvas.drawBitmap(originalBitmap, 0, 0, null); - whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true); - return whiteBgBitmap; - } - - public void setSignatureBitmap(final Bitmap signature) { - // View was laid out... - if (ViewCompat.isLaidOut(this)) { - clear(); - ensureSignatureBitmap(); - - RectF tempSrc = new RectF(); - RectF tempDst = new RectF(); - - int dWidth = signature.getWidth(); - int dHeight = signature.getHeight(); - int vWidth = getWidth(); - int vHeight = getHeight(); - - // Generate the required transform. - tempSrc.set(0, 0, dWidth, dHeight); - tempDst.set(0, 0, vWidth, vHeight); - - Matrix drawMatrix = new Matrix(); - drawMatrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.CENTER); - - Canvas canvas = new Canvas(mSignatureBitmap); - canvas.drawBitmap(signature, drawMatrix, null); - setIsEmpty(false); - invalidate(); - } - // View not laid out yet e.g. called from onCreate(), onRestoreInstanceState()... - else { - getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - // Remove layout listener... - ViewTreeObserverCompat.removeOnGlobalLayoutListener(getViewTreeObserver(), this); - - // Signature bitmap... - setSignatureBitmap(signature); - } - }); - } - } - - public Bitmap getTransparentSignatureBitmap() { - ensureSignatureBitmap(); - return mSignatureBitmap; - } - - public Bitmap getTransparentSignatureBitmap(boolean trimBlankSpace) { - - if (!trimBlankSpace) { - return getTransparentSignatureBitmap(); - } - - ensureSignatureBitmap(); - - int imgHeight = mSignatureBitmap.getHeight(); - int imgWidth = mSignatureBitmap.getWidth(); - - int backgroundColor = Color.TRANSPARENT; - - int xMin = Integer.MAX_VALUE, - xMax = Integer.MIN_VALUE, - yMin = Integer.MAX_VALUE, - yMax = Integer.MIN_VALUE; - - boolean foundPixel = false; - - // Find xMin - for (int x = 0; x < imgWidth; x++) { - boolean stop = false; - for (int y = 0; y < imgHeight; y++) { - if (mSignatureBitmap.getPixel(x, y) != backgroundColor) { - xMin = x; - stop = true; - foundPixel = true; - break; - } - } - if (stop) - break; - } - - // Image is empty... - if (!foundPixel) - return null; - - // Find yMin - for (int y = 0; y < imgHeight; y++) { - boolean stop = false; - for (int x = xMin; x < imgWidth; x++) { - if (mSignatureBitmap.getPixel(x, y) != backgroundColor) { - yMin = y; - stop = true; - break; - } - } - if (stop) - break; - } - - // Find xMax - for (int x = imgWidth - 1; x >= xMin; x--) { - boolean stop = false; - for (int y = yMin; y < imgHeight; y++) { - if (mSignatureBitmap.getPixel(x, y) != backgroundColor) { - xMax = x; - stop = true; - break; - } - } - if (stop) - break; - } - - // Find yMax - for (int y = imgHeight - 1; y >= yMin; y--) { - boolean stop = false; - for (int x = xMin; x <= xMax; x++) { - if (mSignatureBitmap.getPixel(x, y) != backgroundColor) { - yMax = y; - stop = true; - break; - } - } - if (stop) - break; - } - - return Bitmap.createBitmap(mSignatureBitmap, xMin, yMin, xMax - xMin, yMax - yMin); - } - - private boolean isDoubleClick() { - if (mClearOnDoubleClick) { - if (mFirstClick != 0 && System.currentTimeMillis() - mFirstClick > DOUBLE_CLICK_DELAY_MS) { - mCountClick = 0; - } - mCountClick++; - if (mCountClick == 1) { - mFirstClick = System.currentTimeMillis(); - } else if (mCountClick == 2) { - long lastClick = System.currentTimeMillis(); - if (lastClick - mFirstClick < DOUBLE_CLICK_DELAY_MS) { - this.clear(); - return true; - } - } - } - return false; - } - - private TimedPoint getNewPoint(float x, float y) { - int mCacheSize = mPointsCache.size(); - TimedPoint timedPoint; - if (mCacheSize == 0) { - // Cache is empty, create a new point - timedPoint = new TimedPoint(); - } else { - // Get point from cache - timedPoint = mPointsCache.remove(mCacheSize - 1); - } - - return timedPoint.set(x, y); - } - - private void recyclePoint(TimedPoint point) { - mPointsCache.add(point); - } - - private void addPoint(TimedPoint newPoint) { - mPoints.add(newPoint); - - int pointsCount = mPoints.size(); - if (pointsCount > 3) { - - ControlTimedPoints tmp = calculateCurveControlPoints(mPoints.get(0), mPoints.get(1), mPoints.get(2)); - TimedPoint c2 = tmp.c2; - recyclePoint(tmp.c1); - - tmp = calculateCurveControlPoints(mPoints.get(1), mPoints.get(2), mPoints.get(3)); - TimedPoint c3 = tmp.c1; - recyclePoint(tmp.c2); - - CurveBezier curve = mCurveBezierCached.set(mPoints.get(1), c2, c3, mPoints.get(2)); - - TimedPoint startPoint = curve.startPoint; - TimedPoint endPoint = curve.endPoint; - - float velocity = endPoint.velocityFrom(startPoint); - velocity = Float.isNaN(velocity) ? 0.0f : velocity; - - velocity = mVelocityFilterWeight * velocity - + (1 - mVelocityFilterWeight) * mLastVelocity; - - // The new width is a function of the velocity. Higher velocities - // correspond to thinner strokes. - float newWidth = strokeWidth(velocity); - - // The Bezier's width starts out as last curve's final width, and - // gradually changes to the stroke width just calculated. The new - // width calculation is based on the velocity between the Bezier's - // start and end mPoints. - addBezier(curve, mLastWidth, newWidth); - - mLastVelocity = velocity; - mLastWidth = newWidth; - - // Remove the first element from the list, - // so that we always have no more than 4 mPoints in mPoints array. - recyclePoint(mPoints.remove(0)); - - recyclePoint(c2); - recyclePoint(c3); - - } else if (pointsCount == 1) { - // To reduce the initial lag make it work with 3 mPoints - // by duplicating the first point - TimedPoint firstPoint = mPoints.get(0); - mPoints.add(getNewPoint(firstPoint.x, firstPoint.y)); - } - } - - private void addBezier(CurveBezier curve, float startWidth, float endWidth) { - mSvgBuilder.append(curve, (startWidth + endWidth) / 2); - ensureSignatureBitmap(); - float originalWidth = mPaint.getStrokeWidth(); - float widthDelta = endWidth - startWidth; - float drawSteps = (float) Math.floor(curve.length()); - - for (int i = 0; i < drawSteps; i++) { - // Calculate the Bezier (x, y) coordinate for this step. - float t = ((float) i) / drawSteps; - float tt = t * t; - float ttt = tt * t; - float u = 1 - t; - float uu = u * u; - float uuu = uu * u; - - float x = uuu * curve.startPoint.x; - x += 3 * uu * t * curve.control1.x; - x += 3 * u * tt * curve.control2.x; - x += ttt * curve.endPoint.x; - - float y = uuu * curve.startPoint.y; - y += 3 * uu * t * curve.control1.y; - y += 3 * u * tt * curve.control2.y; - y += ttt * curve.endPoint.y; - - // Set the incremental stroke width and draw. - mPaint.setStrokeWidth(startWidth + ttt * widthDelta); - mSignatureBitmapCanvas.drawPoint(x, y, mPaint); - expandDirtyRect(x, y); - } - - mPaint.setStrokeWidth(originalWidth); - } - - private ControlTimedPoints calculateCurveControlPoints(TimedPoint s1, TimedPoint s2, TimedPoint s3) { - float dx1 = s1.x - s2.x; - float dy1 = s1.y - s2.y; - float dx2 = s2.x - s3.x; - float dy2 = s2.y - s3.y; - - float m1X = (s1.x + s2.x) / 2.0f; - float m1Y = (s1.y + s2.y) / 2.0f; - float m2X = (s2.x + s3.x) / 2.0f; - float m2Y = (s2.y + s3.y) / 2.0f; - - float l1 = (float) Math.sqrt(dx1 * dx1 + dy1 * dy1); - float l2 = (float) Math.sqrt(dx2 * dx2 + dy2 * dy2); - - float dxm = (m1X - m2X); - float dym = (m1Y - m2Y); - float k = l2 / (l1 + l2); - if (Float.isNaN(k)) k = 0.0f; - float cmX = m2X + dxm * k; - float cmY = m2Y + dym * k; - - float tx = s2.x - cmX; - float ty = s2.y - cmY; - - return mControlTimedPointsCached.set(getNewPoint(m1X + tx, m1Y + ty), getNewPoint(m2X + tx, m2Y + ty)); - } - - private float strokeWidth(float velocity) { - return Math.max(mMaxWidth / (velocity + 1), mMinWidth); - } - - /** - * Called when replaying history to ensure the dirty region includes all - * mPoints. - * - * @param historicalX the previous x coordinate. - * @param historicalY the previous y coordinate. - */ - private void expandDirtyRect(float historicalX, float historicalY) { - if (historicalX < mDirtyRect.left) { - mDirtyRect.left = historicalX; - } else if (historicalX > mDirtyRect.right) { - mDirtyRect.right = historicalX; - } - if (historicalY < mDirtyRect.top) { - mDirtyRect.top = historicalY; - } else if (historicalY > mDirtyRect.bottom) { - mDirtyRect.bottom = historicalY; - } - } - - /** - * Resets the dirty region when the motion event occurs. - * - * @param eventX the event x coordinate. - * @param eventY the event y coordinate. - */ - private void resetDirtyRect(float eventX, float eventY) { - - // The mLastTouchX and mLastTouchY were set when the ACTION_DOWN motion event occurred. - mDirtyRect.left = Math.min(mLastTouchX, eventX); - mDirtyRect.right = Math.max(mLastTouchX, eventX); - mDirtyRect.top = Math.min(mLastTouchY, eventY); - mDirtyRect.bottom = Math.max(mLastTouchY, eventY); - } - - private void setIsEmpty(boolean newValue) { - mIsEmpty = newValue; - if (mOnSignedListener != null) { - if (mIsEmpty) { - mOnSignedListener.onClear(); - } else { - mOnSignedListener.onSigned(); - } - } - } - - private void ensureSignatureBitmap() { - if (mSignatureBitmap == null) { - mSignatureBitmap = Bitmap.createBitmap(getWidth(), getHeight(), - Bitmap.Config.ARGB_8888); - mSignatureBitmapCanvas = new Canvas(mSignatureBitmap); - } - } - - private int convertDpToPx(float dp) { - return Math.round(getContext().getResources().getDisplayMetrics().density * dp); - } -} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/SilkySignaturePad.kt b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/SilkySignaturePad.kt new file mode 100644 index 0000000000..61e1d6fc67 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/ui/SilkySignaturePad.kt @@ -0,0 +1,567 @@ +package com.navi.amc.investorapp.signature.ui + +import android.content.Context +import android.content.res.Resources +import android.graphics.* +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import com.navi.amc.investorapp.R +import com.navi.amc.investorapp.signature.drawerControllers.TimedPoint +import com.navi.amc.investorapp.signature.svgUtils.SvgBuilder +import com.navi.amc.investorapp.signature.drawerControllers.ControlTimedPoints +import com.navi.amc.investorapp.signature.drawerControllers.CurveBezier + +import java.util.ArrayList + +class SilkySignaturePad(context: Context, attrs: AttributeSet?) : View(context, attrs) { + //View state + private var mPoints: MutableList? = null + private var mIsEmpty = false + private var mLastTouchX = 0f + private var mLastTouchY = 0f + private var mLastVelocity = 0f + private var mLastWidth = 0f + private val mDirtyRect: RectF + private val mSvgBuilder = SvgBuilder() + + // Cache + private val mPointsCache: MutableList = ArrayList() + private val mControlTimedPointsCached = ControlTimedPoints() + private val mCurveBezierCached = CurveBezier() + + //Configurable parameters + private var mMinWidth = 0 + private var mMaxWidth = 0 + private var mVelocityFilterWeight = 0f + private var mOnSignedListener: OnSignedListener? = null + private var mClearOnDoubleClick = false + + //Click values + private var mFirstClick: Long = 0 + private var mCountClick = 0 + + //Default attribute values + private val DEFAULT_ATTR_PEN_MIN_WIDTH_PX = 3 + private val DEFAULT_ATTR_PEN_MAX_WIDTH_PX = 7 + private val DEFAULT_ATTR_PEN_COLOR = Color.BLACK + private val DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT = 0.9f + private val DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK = false + private val mPaint = Paint() + private var mSignatureBitmap: Bitmap? = null + private var mSignatureBitmapCanvas: Canvas? = null + + /** + * Set the pen color from a given resource. + * If the resource is not found, [Color.BLACK] is assumed. + * + * @param colorRes the color resource. + */ + fun setPenColorRes(colorRes: Int) { + try { + setPenColor(resources.getColor(colorRes)) + } catch (ex: Resources.NotFoundException) { + setPenColor(Color.parseColor("#000000")) + } + } + + /** + * Set the pen color from a given color. + * + * @param color the color. + */ + fun setPenColor(color: Int) { + mPaint.color = color + } + + /** + * Set the minimum width of the stroke in pixel. + * + * @param minWidth the width in dp. + */ + fun setMinWidth(minWidth: Float) { + mMinWidth = convertDpToPx(minWidth) + } + + /** + * Set the maximum width of the stroke in pixel. + * + * @param maxWidth the width in dp. + */ + fun setMaxWidth(maxWidth: Float) { + mMaxWidth = convertDpToPx(maxWidth) + } + + /** + * Set the velocity filter weight. + * + * @param velocityFilterWeight the weight. + */ + fun setVelocityFilterWeight(velocityFilterWeight: Float) { + mVelocityFilterWeight = velocityFilterWeight + } + + fun clear() { + mSvgBuilder.clear() + mPoints = ArrayList() + mLastVelocity = 0f + mLastWidth = ((mMinWidth + mMaxWidth) / 2).toFloat() + if (mSignatureBitmap != null) { + mSignatureBitmap = null + ensureSignatureBitmap() + } + isEmpty = true + invalidate() + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + if (!isEnabled) return false + val eventX = event.x + val eventY = event.y + when (event.action) { + MotionEvent.ACTION_DOWN -> { + parent.requestDisallowInterceptTouchEvent(true) + mPoints!!.clear() + if (isDoubleClick) return false + mLastTouchX = eventX + mLastTouchY = eventY + addPoint(getNewPoint(eventX, eventY)) + if (mOnSignedListener != null) mOnSignedListener!!.onStartSigning() + resetDirtyRect(eventX, eventY) + addPoint(getNewPoint(eventX, eventY)) + } + MotionEvent.ACTION_MOVE -> { + resetDirtyRect(eventX, eventY) + addPoint(getNewPoint(eventX, eventY)) + } + MotionEvent.ACTION_UP -> { + resetDirtyRect(eventX, eventY) + addPoint(getNewPoint(eventX, eventY)) + parent.requestDisallowInterceptTouchEvent(true) + isEmpty = false + } + else -> return false + } + + //invalidate(); + invalidate( + (mDirtyRect.left - mMaxWidth).toInt(), + (mDirtyRect.top - mMaxWidth).toInt(), + (mDirtyRect.right + mMaxWidth).toInt(), + (mDirtyRect.bottom + mMaxWidth).toInt() + ) + return true + } + + override fun onDraw(canvas: Canvas) { + if (mSignatureBitmap != null) { + canvas.drawBitmap(mSignatureBitmap!!, 0f, 0f, mPaint) + } + } + + fun setOnSignedListener(listener: OnSignedListener?) { + mOnSignedListener = listener + } + + var isEmpty: Boolean + get() = mIsEmpty + private set(newValue) { + mIsEmpty = newValue + if (mOnSignedListener != null) { + if (mIsEmpty) { + mOnSignedListener!!.onClear() + } else { + mOnSignedListener!!.onSigned() + } + } + } + val signatureSvg: String + get() { + val width = transparentSignatureBitmap!!.width + val height = transparentSignatureBitmap!!.height + return mSvgBuilder.build(width, height) + } + val signatureBitmap: Bitmap + get() { + val originalBitmap = transparentSignatureBitmap + val whiteBgBitmap = Bitmap.createBitmap( + originalBitmap!!.width, originalBitmap.height, Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(whiteBgBitmap) + canvas.drawColor(Color.WHITE) + canvas.drawBitmap(originalBitmap, 0f, 0f, null) + return whiteBgBitmap + } + + /** + * @param compressPercentage Hint to the compressor, 0-100 percent. 0 meaning compress for + * small size, 100 meaning compress for max quality. Some + * formats, like PNG which is lossless, will ignore the + * quality setting + */ + fun getCompressedSignatureBitmap(compressPercentage: Int): Bitmap { + var compressPercentage = compressPercentage + if (compressPercentage < 0) { + compressPercentage = 0 + } else if (compressPercentage > 100) { + compressPercentage = 100 + } + val originalBitmap = transparentSignatureBitmap + val originalWidth = originalBitmap!!.width + val originalHeight = originalBitmap.height + val targetWidth = originalWidth * compressPercentage / 100 // your arbitrary fixed limit + val targetHeight = (originalHeight * targetWidth / originalWidth.toDouble()).toInt() + var whiteBgBitmap = + Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(whiteBgBitmap) + canvas.drawColor(Color.WHITE) + canvas.drawBitmap(originalBitmap, 0f, 0f, null) + whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true) + return whiteBgBitmap + } + + /** + * @param deiredWidth Desired width of the bitmap + */ + fun getFixedSizeSignatureBitmap(deiredWidth: Int): Bitmap { + val originalBitmap = transparentSignatureBitmap + val originalWidth = originalBitmap!!.width + val originalHeight = originalBitmap.height + val targetHeight = (originalHeight * deiredWidth // your arbitrary fixed limit + / originalWidth.toDouble()).toInt() + var whiteBgBitmap = + Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(whiteBgBitmap) + canvas.drawColor(Color.WHITE) + canvas.drawBitmap(originalBitmap, 0f, 0f, null) + whiteBgBitmap = Bitmap.createScaledBitmap( + originalBitmap, deiredWidth, targetHeight, true + ) + return whiteBgBitmap + } + + + val transparentSignatureBitmap: Bitmap? + get() { + ensureSignatureBitmap() + return mSignatureBitmap + } + + fun getTransparentSignatureBitmap(trimBlankSpace: Boolean): Bitmap? { + if (!trimBlankSpace) { + return transparentSignatureBitmap + } + ensureSignatureBitmap() + val imgHeight = mSignatureBitmap!!.height + val imgWidth = mSignatureBitmap!!.width + val backgroundColor = Color.TRANSPARENT + var xMin = Int.MAX_VALUE + var xMax = Int.MIN_VALUE + var yMin = Int.MAX_VALUE + var yMax = Int.MIN_VALUE + var foundPixel = false + + // Find xMin + for (x in 0 until imgWidth) { + var stop = false + for (y in 0 until imgHeight) { + if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) { + xMin = x + stop = true + foundPixel = true + break + } + } + if (stop) break + } + + // Image is empty... + if (!foundPixel) return null + + // Find yMin + for (y in 0 until imgHeight) { + var stop = false + for (x in xMin until imgWidth) { + if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) { + yMin = y + stop = true + break + } + } + if (stop) break + } + + // Find xMax + for (x in imgWidth - 1 downTo xMin) { + var stop = false + for (y in yMin until imgHeight) { + if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) { + xMax = x + stop = true + break + } + } + if (stop) break + } + + // Find yMax + for (y in imgHeight - 1 downTo yMin) { + var stop = false + for (x in xMin..xMax) { + if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) { + yMax = y + stop = true + break + } + } + if (stop) break + } + return Bitmap.createBitmap(mSignatureBitmap!!, xMin, yMin, xMax - xMin, yMax - yMin) + } + + private val isDoubleClick: Boolean + private get() { + if (mClearOnDoubleClick) { + if (mFirstClick != 0L && System.currentTimeMillis() - mFirstClick > DOUBLE_CLICK_DELAY_MS) { + mCountClick = 0 + } + mCountClick++ + if (mCountClick == 1) { + mFirstClick = System.currentTimeMillis() + } else if (mCountClick == 2) { + val lastClick = System.currentTimeMillis() + if (lastClick - mFirstClick < DOUBLE_CLICK_DELAY_MS) { + this.clear() + return true + } + } + } + return false + } + + private fun getNewPoint(x: Float, y: Float): TimedPoint { + val mCacheSize = mPointsCache.size + val timedPoint: TimedPoint? + timedPoint = if (mCacheSize == 0) { + // Cache is empty, create a new point + TimedPoint() + } else { + // Get point from cache + mPointsCache.removeAt(mCacheSize - 1) + } + return timedPoint!!.set(x, y) + } + + private fun recyclePoint(point: TimedPoint?) { + mPointsCache.add(point) + } + + private fun addPoint(newPoint: TimedPoint) { + mPoints!!.add(newPoint) + val pointsCount = mPoints!!.size + if (pointsCount > 3) { + var tmp = calculateCurveControlPoints(mPoints!![0], mPoints!![1], mPoints!![2]) + val c2 = tmp.c2 + recyclePoint(tmp.c1) + tmp = calculateCurveControlPoints(mPoints!![1], mPoints!![2], mPoints!![3]) + val c3 = tmp.c1 + recyclePoint(tmp.c2) + val curve = mCurveBezierCached.set(mPoints!![1], c2, c3, mPoints!![2]) + val startPoint = curve.startPoint + val endPoint = curve.endPoint + var velocity = endPoint!!.velocityFrom(startPoint) + velocity = if (java.lang.Float.isNaN(velocity)) 0.0f else velocity + velocity = (mVelocityFilterWeight * velocity + + (1 - mVelocityFilterWeight) * mLastVelocity) + + // The new width is a function of the velocity. Higher velocities + // correspond to thinner strokes. + val newWidth = strokeWidth(velocity) + + // The Bezier's width starts out as last curve's final width, and + // gradually changes to the stroke width just calculated. The new + // width calculation is based on the velocity between the Bezier's + // start and end mPoints. + addBezier(curve, mLastWidth, newWidth) + mLastVelocity = velocity + mLastWidth = newWidth + + // Remove the first element from the list, + // so that we always have no more than 4 mPoints in mPoints array. + recyclePoint(mPoints!!.removeAt(0)) + recyclePoint(c2) + recyclePoint(c3) + } else if (pointsCount == 1) { + // To reduce the initial lag make it work with 3 mPoints + // by duplicating the first point + val firstPoint = mPoints!![0] + mPoints!!.add(getNewPoint(firstPoint.x, firstPoint.y)) + } + } + + private fun addBezier(curve: CurveBezier, startWidth: Float, endWidth: Float) { + mSvgBuilder.append(curve, (startWidth + endWidth) / 2) + ensureSignatureBitmap() + val originalWidth = mPaint.strokeWidth + val widthDelta = endWidth - startWidth + val drawSteps = Math.floor(curve.length().toDouble()).toFloat() + var i = 0 + while (i < drawSteps) { + + // Calculate the Bezier (x, y) coordinate for this step. + val t = i.toFloat() / drawSteps + val tt = t * t + val ttt = tt * t + val u = 1 - t + val uu = u * u + val uuu = uu * u + var x = uuu * curve.startPoint!!.x + x += 3 * uu * t * curve.control1!!.x + x += 3 * u * tt * curve.control2!!.x + x += ttt * curve.endPoint!!.x + var y = uuu * curve.startPoint!!.y + y += 3 * uu * t * curve.control1!!.y + y += 3 * u * tt * curve.control2!!.y + y += ttt * curve.endPoint!!.y + + // Set the incremental stroke width and draw. + mPaint.strokeWidth = startWidth + ttt * widthDelta + mSignatureBitmapCanvas!!.drawPoint(x, y, mPaint) + expandDirtyRect(x, y) + i++ + } + mPaint.strokeWidth = originalWidth + } + + private fun calculateCurveControlPoints( + s1: TimedPoint, + s2: TimedPoint, + s3: TimedPoint + ): ControlTimedPoints { + val dx1 = s1.x - s2.x + val dy1 = s1.y - s2.y + val dx2 = s2.x - s3.x + val dy2 = s2.y - s3.y + val m1X = (s1.x + s2.x) / 2.0f + val m1Y = (s1.y + s2.y) / 2.0f + val m2X = (s2.x + s3.x) / 2.0f + val m2Y = (s2.y + s3.y) / 2.0f + val l1 = Math.sqrt((dx1 * dx1 + dy1 * dy1).toDouble()).toFloat() + val l2 = Math.sqrt((dx2 * dx2 + dy2 * dy2).toDouble()).toFloat() + val dxm = m1X - m2X + val dym = m1Y - m2Y + var k = l2 / (l1 + l2) + if (java.lang.Float.isNaN(k)) k = 0.0f + val cmX = m2X + dxm * k + val cmY = m2Y + dym * k + val tx = s2.x - cmX + val ty = s2.y - cmY + return mControlTimedPointsCached.set( + getNewPoint(m1X + tx, m1Y + ty), + getNewPoint(m2X + tx, m2Y + ty) + ) + } + + private fun strokeWidth(velocity: Float): Float { + return Math.max(mMaxWidth / (velocity + 1), mMinWidth.toFloat()) + } + + /** + * Called when replaying history to ensure the dirty region includes all + * mPoints. + * + * @param historicalX the previous x coordinate. + * @param historicalY the previous y coordinate. + */ + private fun expandDirtyRect(historicalX: Float, historicalY: Float) { + if (historicalX < mDirtyRect.left) { + mDirtyRect.left = historicalX + } else if (historicalX > mDirtyRect.right) { + mDirtyRect.right = historicalX + } + if (historicalY < mDirtyRect.top) { + mDirtyRect.top = historicalY + } else if (historicalY > mDirtyRect.bottom) { + mDirtyRect.bottom = historicalY + } + } + + /** + * Resets the dirty region when the motion event occurs. + * + * @param eventX the event x coordinate. + * @param eventY the event y coordinate. + */ + private fun resetDirtyRect(eventX: Float, eventY: Float) { + + // The mLastTouchX and mLastTouchY were set when the ACTION_DOWN motion event occurred. + mDirtyRect.left = Math.min(mLastTouchX, eventX) + mDirtyRect.right = Math.max(mLastTouchX, eventX) + mDirtyRect.top = Math.min(mLastTouchY, eventY) + mDirtyRect.bottom = Math.max(mLastTouchY, eventY) + } + + private fun ensureSignatureBitmap() { + if (mSignatureBitmap == null) { + mSignatureBitmap = Bitmap.createBitmap( + width, height, + Bitmap.Config.ARGB_8888 + ) + mSignatureBitmap?.let { + mSignatureBitmapCanvas = Canvas(it) + } + } + } + + private fun convertDpToPx(dp: Float): Int { + return Math.round(context.resources.displayMetrics.density * dp) + } + + companion object { + private const val DOUBLE_CLICK_DELAY_MS = 200 + } + + init { + val a = context.theme.obtainStyledAttributes( + attrs, + R.styleable.SilkySignaturePad, + 0, 0 + ) + + //Configurable parameters + try { + mMinWidth = a.getDimensionPixelSize( + R.styleable.SilkySignaturePad_penMinWidth, + convertDpToPx(DEFAULT_ATTR_PEN_MIN_WIDTH_PX.toFloat()) + ) + mMaxWidth = a.getDimensionPixelSize( + R.styleable.SilkySignaturePad_penMaxWidth, + convertDpToPx(DEFAULT_ATTR_PEN_MAX_WIDTH_PX.toFloat()) + ) + mPaint.color = a.getColor( + R.styleable.SilkySignaturePad_penColor, + DEFAULT_ATTR_PEN_COLOR + ) + mVelocityFilterWeight = a.getFloat( + R.styleable.SilkySignaturePad_velocityFilterWeight, + DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT + ) + mClearOnDoubleClick = a.getBoolean( + R.styleable.SilkySignaturePad_clearOnDoubleClick, + DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK + ) + } finally { + a.recycle() + } + + //Fixed parameters + mPaint.isAntiAlias = true + mPaint.style = Paint.Style.STROKE + mPaint.strokeCap = Paint.Cap.ROUND + mPaint.strokeJoin = Paint.Join.ROUND + + //Dirty rectangle to update only the changed portion of the view + mDirtyRect = RectF() + clear() + } +} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewCompat.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewCompat.java deleted file mode 100644 index 17e5187940..0000000000 --- a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewCompat.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.navi.amc.investorapp.signature.viewHelper; - -import android.os.Build; -import android.view.View; - -public class ViewCompat { - public static boolean isLaidOut(View view) { - // Future (API19+)... - if (Build.VERSION.SDK_INT >= 19) { - return view.isLaidOut(); - } - // Legacy... - return view.getWidth() > 0 && view.getHeight() > 0; - } -} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewCompat.kt b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewCompat.kt new file mode 100644 index 0000000000..ac329f64a9 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewCompat.kt @@ -0,0 +1,14 @@ +package com.navi.amc.investorapp.signature.viewHelper + +import android.os.Build +import android.view.View + +object ViewCompat { + fun isLaidOut(view: View): Boolean { + // Future (API19+)... + return if (Build.VERSION.SDK_INT >= 19) { + view.isLaidOut + } else view.width > 0 && view.height > 0 + // Legacy... + } +} \ No newline at end of file diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewTreeObserverCompat.java b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewTreeObserverCompat.java deleted file mode 100644 index d08e3d0641..0000000000 --- a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewTreeObserverCompat.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.navi.amc.investorapp.signature.viewHelper; - -import android.annotation.SuppressLint; -import android.os.Build; -import android.view.ViewTreeObserver; - -public class ViewTreeObserverCompat { - @SuppressLint("NewApi") - @SuppressWarnings("deprecation") - public static void removeOnGlobalLayoutListener(ViewTreeObserver observer, ViewTreeObserver.OnGlobalLayoutListener victim) { - // Future (API16+)... - if (Build.VERSION.SDK_INT >= 16) { - observer.removeOnGlobalLayoutListener(victim); - } - // Legacy... - else { - observer.removeGlobalOnLayoutListener(victim); - } - } -} diff --git a/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewTreeObserverCompat.kt b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewTreeObserverCompat.kt new file mode 100644 index 0000000000..dcc5eaf292 --- /dev/null +++ b/navi-amc/src/main/java/com/navi/amc/investorapp/signature/viewHelper/ViewTreeObserverCompat.kt @@ -0,0 +1,20 @@ +package com.navi.amc.investorapp.signature.viewHelper + +import android.annotation.SuppressLint +import android.os.Build +import android.view.ViewTreeObserver + +object ViewTreeObserverCompat { + @SuppressLint("NewApi") + fun removeOnGlobalLayoutListener( + observer: ViewTreeObserver, + victim: ViewTreeObserver.OnGlobalLayoutListener? + ) { + // Future (API16+)... + if (Build.VERSION.SDK_INT >= 16) { + observer.removeOnGlobalLayoutListener(victim) + } else { + observer.removeGlobalOnLayoutListener(victim) + } + } +} \ No newline at end of file