Merge pull request #1709 from medici/feature/signature
Fingure Signature on amc kyc flow
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n")
|
||||
.append("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.2\" baseProfile=\"tiny\" ")
|
||||
.append("height=\"")
|
||||
.append(height)
|
||||
.append("\" ")
|
||||
.append("width=\"")
|
||||
.append(width)
|
||||
.append("\">")
|
||||
.append("<g ")
|
||||
.append("stroke-linejoin=\"round\" ")
|
||||
.append("stroke-linecap=\"round\" ")
|
||||
.append("fill=\"none\" ")
|
||||
.append("stroke=\"black\"")
|
||||
.append(">")
|
||||
.append(mSvgPathsBuilder)
|
||||
.append("</g>")
|
||||
.append("</svg>")
|
||||
.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
|
||||
}
|
||||
@@ -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("<path ")
|
||||
.append("stroke-width=\"")
|
||||
.append(strokeWidth)
|
||||
.append("\" ")
|
||||
.append("d=\"")
|
||||
.append(SVG_MOVE)
|
||||
.append(mStartPoint)
|
||||
.append(mStringBuilder)
|
||||
.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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.navi.amc.investorapp.signature.ui
|
||||
|
||||
interface OnSignedListener {
|
||||
fun onStartSigning()
|
||||
|
||||
fun onSigned()
|
||||
|
||||
fun onClear()
|
||||
}
|
||||
@@ -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<TimedPoint>? = 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<TimedPoint?> = 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()
|
||||
}
|
||||
}
|
||||
@@ -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...
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<FragmentSignatureBinding, SignatureVM>(), 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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -2,77 +2,32 @@
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/_16dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/title_tv"
|
||||
style="@style/Textview_22223D_semibold_16sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/_20dp"
|
||||
android:text="@string/upload_your_signature"
|
||||
<com.navi.amc.investorapp.signature.ui.SilkySignaturePad
|
||||
android:id="@+id/signature_pad"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:maxHeight="@dimen/_dp440"
|
||||
android:background="@drawable/bg_white_rectangular"
|
||||
android:layout_marginBottom="@dimen/_40dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/save_tv"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/sub_title_tv"
|
||||
style="@style/TextStyle.tv_size_14sp_color_A3A3AB"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/_8dp"
|
||||
android:text="@string/ensure_the_photo_has_white_background"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title_tv" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/doc_iv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/_126dp"
|
||||
android:layout_marginTop="@dimen/_8dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@drawable/bg_rectangle_gray_outline_white_bg"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/sub_title_tv" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/upload_iv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/ic_upload_svg"
|
||||
app:layout_constraintBottom_toTopOf="@+id/upload_title_tv"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/sub_title_tv"
|
||||
app:layout_constraintTop_toTopOf="@+id/doc_iv"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/upload_title_tv"
|
||||
style="@style/TextStyle.tv_size_14sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginTop="@dimen/_4dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/upload_signature"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/doc_iv"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/upload_iv" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/retake_tv"
|
||||
android:id="@+id/redo_tv"
|
||||
style="@style/ActionButtonStyleWithWhiteBG"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/_48dp"
|
||||
android:layout_marginEnd="@dimen/_8dp"
|
||||
android:background="@drawable/bg_rectangle_black_outline_white_bg"
|
||||
android:gravity="center"
|
||||
android:text="@string/upload_again"
|
||||
android:text="@string/redo"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/save_tv"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
@@ -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" />
|
||||
|
||||
<com.navi.amc.investorapp.common.customview.PermissionDeniedView
|
||||
android:id="@+id/permission_denied_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</layout>
|
||||
@@ -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" />
|
||||
|
||||
|
||||
10
navi-amc/src/main/res/values-night/attrs.xml
Normal file
10
navi-amc/src/main/res/values-night/attrs.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="SilkySignaturePad">
|
||||
<attr name="penMinWidth" format="dimension" />
|
||||
<attr name="penMaxWidth" format="dimension" />
|
||||
<attr name="penColor" format="color" />
|
||||
<attr name="velocityFilterWeight" format="float" />
|
||||
<attr name="clearOnDoubleClick" format="boolean"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -37,6 +37,7 @@
|
||||
<dimen name="_70dp">70dp</dimen>
|
||||
<dimen name="_76dp">76dp</dimen>
|
||||
<dimen name="_80dp">80dp</dimen>
|
||||
<dimen name="_84dp">84dp</dimen>
|
||||
<dimen name="_88dp">88dp</dimen>
|
||||
<dimen name="_92dp">92dp</dimen>
|
||||
<dimen name="_96dp">96dp</dimen>
|
||||
@@ -91,6 +92,7 @@
|
||||
<dimen name="_dp172">172dp</dimen>
|
||||
<dimen name="_dp200">200dp</dimen>
|
||||
<dimen name="_dp205">205dp</dimen>
|
||||
<dimen name="_dp440">440dp</dimen>
|
||||
<dimen name="_dp55">55dp</dimen>
|
||||
<dimen name="_dp33">33dp</dimen>
|
||||
<dimen name="_dp125">125dp</dimen>
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
|
||||
<string name="select_banks">Select Bank</string>
|
||||
<string name="lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum">Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum Lorem ipsum</string>
|
||||
<string name="pincode">Pincode</string>
|
||||
<string name="pincode_text">Pincode</string>
|
||||
<string name="ex_650087">Ex - 650087</string>
|
||||
<string name="house_no_street_name">House no/ Street name</string>
|
||||
<string name="house_no_10">House no 10</string>
|
||||
@@ -542,6 +542,7 @@
|
||||
<string name="ensure_the_photo_has_white_background">Ensure the photo has white background</string>
|
||||
<string name="upload_signature">Upload Signature</string>
|
||||
<string name="upload_again">Upload again</string>
|
||||
<string name="redo">Redo</string>
|
||||
<string name="speak_the_digits_below_while_recording">Speak the digits below while recording</string>
|
||||
<string name="tips_for_great_video">Tips for great video</string>
|
||||
<string name="retake_video">Retake video</string>
|
||||
|
||||
Reference in New Issue
Block a user