diff --git a/build.gradle b/build.gradle index c93b711..0b30bc4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.70' + ext.kotlin_version = '1.5.31' repositories { jcenter() google() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.2' + classpath 'com.android.tools.build:gradle:7.0.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/example/build.gradle b/example/build.gradle index 2d2f895..a270266 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -1,13 +1,12 @@ apply plugin: 'com.android.application' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' android { - compileSdkVersion 28 + compileSdkVersion 31 defaultConfig { applicationId "com.goodiebag.pinview.example" minSdkVersion 15 - targetSdkVersion 28 + targetSdkVersion 31 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -25,8 +24,8 @@ dependencies { androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { exclude group: 'com.android.support', module: 'support-annotations' }) - implementation 'androidx.appcompat:appcompat:1.0.0' - testImplementation 'junit:junit:4.12' + implementation 'androidx.appcompat:appcompat:1.3.1' + testImplementation 'junit:junit:4.13.2' implementation project(':pinview') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/example/src/main/AndroidManifest.xml b/example/src/main/AndroidManifest.xml index c361d8e..bfdc1ea 100644 --- a/example/src/main/AndroidManifest.xml +++ b/example/src/main/AndroidManifest.xml @@ -8,10 +8,11 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> - + - diff --git a/example/src/main/java/com/goodiebag/pinview/example/MainActivity.kt b/example/src/main/java/com/goodiebag/pinview/example/MainActivity.kt index 49bd504..e592176 100644 --- a/example/src/main/java/com/goodiebag/pinview/example/MainActivity.kt +++ b/example/src/main/java/com/goodiebag/pinview/example/MainActivity.kt @@ -1,5 +1,6 @@ package com.goodiebag.pinview.example +import android.annotation.SuppressLint import android.graphics.Color import android.os.Bundle import android.widget.Toast @@ -13,11 +14,10 @@ class MainActivity : AppCompatActivity() { setContentView(R.layout.activity_main) val pinview1 = findViewById(R.id.pinview1) - pinview1.setPinViewEventListener(object : PinViewEventListener { - override fun onDataEntered(pinview: Pinview?, fromUser: Boolean) { - Toast.makeText(this@MainActivity, pinview!!.value, Toast.LENGTH_SHORT).show() - } - }) + pinview1.setPinViewEventListener { pinview: Pinview, fromUser: Boolean -> + Toast.makeText(this@MainActivity, pinview.value, Toast.LENGTH_SHORT).show() + } + setFont(pinview1) // pinView Customize val pinview5 = findViewById(R.id.pinview5) @@ -29,4 +29,11 @@ class MainActivity : AppCompatActivity() { showCursor(true) } } + + @SuppressLint("NewApi") + private fun setFont(pinview1: Pinview) { + val typeface = resources.getFont(R.font.poppins_semibold) + pinview1.setTypeface(typeface) + } + } \ No newline at end of file diff --git a/example/src/main/res/font/poppins_semibold.ttf b/example/src/main/res/font/poppins_semibold.ttf new file mode 100644 index 0000000..3b8622f Binary files /dev/null and b/example/src/main/res/font/poppins_semibold.ttf differ diff --git a/example/src/main/res/layout/activity_main.xml b/example/src/main/res/layout/activity_main.xml index 254eaf0..628c8d0 100644 --- a/example/src/main/res/layout/activity_main.xml +++ b/example/src/main/res/layout/activity_main.xml @@ -83,12 +83,13 @@ app:pinBackground="@drawable/example_drawable" app:pinHeight="40dp" app:pinLength="4" - app:pinWidth="40dp"/> + app:pinWidth="40dp" + app:textSize="9sp"/> + android:text="Custom Pin View (with dp textSize)"/> + app:pinWidth="40dp" + app:textSize="18dp"/> diff --git a/pinview/build.gradle b/pinview/build.gradle index 5c0fe94..663255c 100644 --- a/pinview/build.gradle +++ b/pinview/build.gradle @@ -1,13 +1,12 @@ apply plugin: 'com.android.library' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' android { - compileSdkVersion 29 + compileSdkVersion 31 defaultConfig { minSdkVersion 15 - targetSdkVersion 29 + targetSdkVersion 31 versionCode 3 versionName "1.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -26,8 +25,8 @@ dependencies { androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { exclude group: 'com.android.support', module: 'support-annotations' }) - implementation 'androidx.appcompat:appcompat:1.1.0' - testImplementation 'junit:junit:4.12' + implementation 'androidx.appcompat:appcompat:1.3.1' + testImplementation 'junit:junit:4.13.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } repositories { diff --git a/pinview/src/main/AndroidManifest.xml b/pinview/src/main/AndroidManifest.xml index aa785b8..ba57c8f 100644 --- a/pinview/src/main/AndroidManifest.xml +++ b/pinview/src/main/AndroidManifest.xml @@ -1,10 +1,2 @@ - - - - - - + package="com.goodiebag.pinview"/> \ No newline at end of file diff --git a/pinview/src/main/java/com/goodiebag/pinview/Pinview.kt b/pinview/src/main/java/com/goodiebag/pinview/Pinview.kt index 61b3ee0..baaceea 100644 --- a/pinview/src/main/java/com/goodiebag/pinview/Pinview.kt +++ b/pinview/src/main/java/com/goodiebag/pinview/Pinview.kt @@ -22,6 +22,7 @@ package com.goodiebag.pinview import android.annotation.SuppressLint import android.content.Context import android.graphics.PorterDuff +import android.graphics.Typeface import android.text.Editable import android.text.InputFilter import android.text.TextWatcher @@ -61,13 +62,14 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = * Attributes */ private var mPinLength = 4 - private val editTextList: MutableList? = ArrayList() + private val editTextList: MutableList = ArrayList() private var mPinWidth = 50 private var mTextSize = 12 private var mPinHeight = 50 private var mSplitWidth = 20 private var mCursorVisible = false private var mDelPressed = false + private var mTypeFace: Typeface? = null @get:DrawableRes @DrawableRes @@ -89,13 +91,13 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = * Interface for onDataEntered event. */ interface PinViewEventListener { - fun onDataEntered(pinview: Pinview?, fromUser: Boolean) + fun onDataEntered(pinview: Pinview, fromUser: Boolean) } var mClickListener: OnClickListener? = null - var currentFocus: View? = null + var currentFocus: View? = null // Will be null if there are no pin-views var filters = arrayOfNulls(1) - var params: LayoutParams? = null + lateinit var params: LayoutParams /** * A method to take care of all the initialisations. @@ -116,10 +118,10 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = createEditTexts() super.setOnClickListener { var focused = false - for (editText in editTextList!!) { + for (editText in editTextList) { if (editText.length() == 0) { editText.requestFocus() - openKeyboard() + openKeyboardIfForced() focused = true break } @@ -127,12 +129,10 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = if (!focused && editTextList.size > 0) { // Focus the last view editTextList[editTextList.size - 1].requestFocus() } - if (mClickListener != null) { - mClickListener!!.onClick(this@Pinview) - } + mClickListener?.onClick(this@Pinview) } // Bring up the keyboard - val firstEditText: View? = editTextList?.first() + val firstEditText: View? = editTextList.firstOrNull() // list is empty, if pinLength==0 firstEditText?.postDelayed({ openKeyboard() }, 200) updateEnabledState() } @@ -142,12 +142,13 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = */ private fun createEditTexts() { removeAllViews() - editTextList!!.clear() + editTextList.clear() var editText: EditText for (i in 0 until mPinLength) { editText = EditText(context) editText.textSize = mTextSize.toFloat() + mTypeFace.let { editText.typeface = it } editTextList.add(i, editText) this.addView(editText) generateOneEditText(editText, "" + i) @@ -173,7 +174,9 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = mPinHeight = array.getDimension(R.styleable.Pinview_pinHeight, mPinHeight.toFloat()).toInt() mPinWidth = array.getDimension(R.styleable.Pinview_pinWidth, mPinWidth.toFloat()).toInt() mSplitWidth = array.getDimension(R.styleable.Pinview_splitWidth, mSplitWidth.toFloat()).toInt() - mTextSize = array.getDimension(R.styleable.Pinview_textSize, mTextSize.toFloat()).toInt() + // We expect mTextSize to be sp, but we allow specifying via xml in any dimension resource as standard. Hence the scaling here + val scaledDensity = resources.displayMetrics.scaledDensity + mTextSize = (array.getDimensionPixelSize(R.styleable.Pinview_textSize, (mTextSize * scaledDensity).toInt()) / scaledDensity).toInt() mCursorVisible = array.getBoolean(R.styleable.Pinview_cursorVisible, mCursorVisible) mPassword = array.getBoolean(R.styleable.Pinview_password, mPassword) mForceKeyboard = array.getBoolean(R.styleable.Pinview_forceKeyboard, mForceKeyboard) @@ -241,7 +244,7 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = var value: String get() { val sb = StringBuilder() - for (et in editTextList!!) { + for (et in editTextList) { sb.append(et.text.toString()) } return sb.toString() @@ -279,25 +282,34 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = } /** - * Requsets focus on current pin view and opens keyboard if forceKeyboard is enabled. + * Requests focus on current pin view and opens keyboard if forceKeyboard is enabled. + * If open keyboard is disabled in XML, use openKeyboard() * - * @return the current focused pin view. It can be used to open softkeyboard manually. + * @return the current focused pin view. It can be used to open soft-keyboard manually. */ - fun requestPinEntryFocus(): View? { + fun requestPinEntryFocus(): View { val currentTag = max(0, indexOfCurrentFocus) - val currentEditText = editTextList?.get(currentTag) - currentEditText?.requestFocus() - openKeyboard() + val currentEditText = editTextList[currentTag] + currentEditText.requestFocus() + openKeyboardIfForced() return currentEditText } - private fun openKeyboard() { + private fun openKeyboardIfForced() { if (mForceKeyboard) { - val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager - inputMethodManager?.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY) + openKeyboard() } } + /** + * Request the keyboard to open on the currently focused view + */ + @Suppress("MemberVisibilityCanBePrivate") + fun openKeyboard() { + val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY) + } + /** * Clears the values in the Pinview */ @@ -312,7 +324,7 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = mDelPressed = false return } - for (editText in editTextList!!) { + for (editText in editTextList) { if (editText.length() == 0) { if (editText !== view) { editText.requestFocus() @@ -339,13 +351,13 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = */ private fun setTransformation() { if (mPassword) { - for (editText in editTextList!!) { + for (editText in editTextList) { editText.removeTextChangedListener(this) editText.transformationMethod = PinTransformationMethod() editText.addTextChangedListener(this) } } else { - for (editText in editTextList!!) { + for (editText in editTextList) { editText.removeTextChangedListener(this) editText.transformationMethod = null editText.addTextChangedListener(this) @@ -372,9 +384,9 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = var delay: Long = 1 if (mPassword) delay = 25 postDelayed({ - val nextEditText = editTextList?.get(currentTag + 1) - nextEditText?.isEnabled = true - nextEditText?.requestFocus() + val nextEditText = editTextList[currentTag + 1] + nextEditText.isEnabled = true + nextEditText.requestFocus() }, delay) } @@ -389,12 +401,12 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = this.mDelPressed = true //For the last cell of the non password text fields. Clear the text without changing the focus. - if (!this.editTextList?.get(currentTag)?.text.isNullOrEmpty()) { - this.editTextList?.get(currentTag)?.setText("") + if (!this.editTextList[currentTag].text.isNullOrEmpty()) { + this.editTextList[currentTag].setText("") } } - this.editTextList?.forEach { item -> + this.editTextList.forEach { item -> if (item.text.isNotEmpty()) { val index = this.editTextList.indexOf(item) + 1 if (!this.fromSetValue && index == mPinLength) { @@ -412,7 +424,7 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = private fun updateEnabledState() { val currentTag = max(0, indexOfCurrentFocus) - for (index in editTextList!!.indices) { + for (index in editTextList.indices) { val editText = editTextList[index] editText.isEnabled = index <= currentTag } @@ -432,26 +444,26 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = if (keyEvent.action == KeyEvent.ACTION_UP && i == KeyEvent.KEYCODE_DEL) { // Perform action on Del press val currentTag = indexOfCurrentFocus - val currentEditText = editTextList?.get(currentTag)?.text + val currentEditText = editTextList[currentTag].text //Last tile of the number pad. Clear the edit text without changing the focus. if (inputType == InputType.NUMBER && currentTag == mPinLength - 1 && finalNumberPin || mPassword && currentTag == mPinLength - 1 && finalNumberPin) { if (!currentEditText.isNullOrEmpty()) { - this.editTextList?.get(currentTag)?.setText("") + this.editTextList[currentTag].setText("") } finalNumberPin = false } else if (currentTag > 0) { mDelPressed = true if (currentEditText.isNullOrEmpty()) { //Takes it back one tile - this.editTextList?.get(currentTag - 1)?.requestFocus() + this.editTextList[currentTag - 1].requestFocus() } - this.editTextList?.get(currentTag)?.setText("") + this.editTextList[currentTag].setText("") } else { //For the first cell if (!currentEditText.isNullOrEmpty()) { - editTextList?.get(currentTag)?.setText("") + editTextList[currentTag].setText("") } } return true @@ -463,7 +475,7 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = * Getters and Setters */ private val indexOfCurrentFocus: Int - get() = editTextList!!.indexOf(currentFocus) + get() = editTextList.indexOf(currentFocus) var splitWidth: Int get() = mSplitWidth @@ -471,7 +483,7 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = mSplitWidth = splitWidth val margin = splitWidth / 2 params?.setMargins(margin, margin, margin, margin) - this.editTextList?.forEach { + this.editTextList.forEach { it.layoutParams = params } } @@ -481,7 +493,7 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = set(pinHeight) { mPinHeight = pinHeight params?.height = pinHeight - this.editTextList?.forEach { + this.editTextList.forEach { it.layoutParams = params } } @@ -491,7 +503,7 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = set(pinWidth) { mPinWidth = pinWidth params?.width = pinWidth - this.editTextList?.forEach { + this.editTextList.forEach { it.layoutParams = params } } @@ -514,14 +526,14 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = get() = mHint set(mHint) { this.mHint = mHint - this.editTextList?.forEach { + this.editTextList.forEach { it.hint = mHint } } fun setPinBackgroundRes(@DrawableRes res: Int) { pinBackground = res - this.editTextList?.forEach { + this.editTextList.forEach { it.setBackgroundResource(res) } } @@ -537,7 +549,7 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = fun setInputType(inputType: InputType) { this.inputType = inputType val keyInputType = keyboardInputType - editTextList?.forEach { + editTextList.forEach { it.inputType = keyInputType } } @@ -546,30 +558,55 @@ class Pinview @JvmOverloads constructor(context: Context, attrs: AttributeSet? = mListener = listener } + fun setPinViewEventListener(listener: (Pinview, Boolean) -> Unit) { + mListener = object: PinViewEventListener { + override fun onDataEntered(pinview: Pinview, fromUser: Boolean) { + listener(pinview, fromUser) + } + } + } + + fun clearPinViewEventListener() { + mListener = null + } + fun showCursor(status: Boolean) { mCursorVisible = status - this.editTextList?.forEach { it.isCursorVisible = status } + this.editTextList.forEach { it.isCursorVisible = status } } fun setTextSize(textSize: Int) { mTextSize = textSize - this.editTextList?.forEach { it.textSize = mTextSize.toFloat() } + updateEditTexts() } + fun setTypeface(typeFace: Typeface?) { + mTypeFace = typeFace + updateEditTexts() + } + + private fun updateEditTexts() { + for (edt in editTextList) { + edt.textSize = mTextSize.toFloat() + edt.layoutParams = params + mTypeFace?.let { edt.typeface = it } + } + } + fun setCursorColor(@ColorInt color: Int) { - this.editTextList?.forEach { + this.editTextList.forEach { setCursorColor(it, color) } } fun setTextColor(@ColorInt color: Int) { - this.editTextList?.forEach { + this.editTextList.forEach { it.setTextColor(color) } } fun setCursorShape(@DrawableRes shape: Int) { - editTextList?.forEach { + editTextList.forEach { try { val field = TextView::class.java.getDeclaredField("mCursorDrawableRes") field.isAccessible = true