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.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.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.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("")
+ .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.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.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/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.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.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.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
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 83050a7cea..9a9bda4591 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
@@ -91,6 +92,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 69758d02d4..586b1e5d9b 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