diff --git a/README.md b/README.md index e4f2eebc..5d5ad95f 100644 --- a/README.md +++ b/README.md @@ -113,9 +113,12 @@ You **MUST** attach it to the PinLockView, otherwise it will be simply ignored. There are several theming options available through XML attributes which you can use to completely change the look-and-feel of this view to match the theme of your app. ```xml + + // In + app:pinLength="6" // Change the pin length - app:keypadTextColor="#E6E6E6" // Change the color of the keypad text - app:keypadTextSize="16dp" // Change the text size in the keypad + app:keypadTextColor="#E6E6E6" // Change the color of the keypad text and numbers + app:keypadTextSize="16dp" // Change the text and numbers size in the keypad app:keypadButtonSize="72dp" // Change the size of individual keys/buttons app:keypadVerticalSpacing="24dp" // Alters the vertical spacing between the keypad buttons app:keypadHorizontalSpacing="36dp" // Alters the horizontal spacing between the keypad buttons @@ -125,6 +128,23 @@ There are several theming options available through XML attributes which you can app:keypadShowDeleteButton="false" // Should show the delete button, default is true app:keypadDeleteButtonPressedColor="#C8C8C8" // Change the pressed/focused state color of the delete button + // New properties for , added for Okta + // Purpose is to add letters under numbers, along with some other small configurations + + app:keypadUseDeprecatedColorOptions // Set to true if you’d like to use keypadTextColor and keypadTextSize, otherwise it will use the new settings + app:keypadNumbersTextColor="#C8C8C8" // Sets the color of the numbers + app:keypadNumbersTextSize // Sets the font size of the numbers + app:keypadLettersTextColor="#C8C8C8" // Sets the color of the letters + app:keypadLettersTextSize // Sets the font size of the letters + app:keypadDeleteButtonColor="#C8C8C8" // Sets the color of the delete button + app:keypadShowLetters="true" // Changes visibility of the letters + app:keypadNumbersBold="true" // Sets the boldness of number text + app:keypadLettersBold="false" // Sets the boldness of letter text + app:keypadDefaultDeleteColor="false" // Set to false if you want a differently coloured delete button + + + // In + app:dotEmptyBackground="@drawable/empty" // Customize the empty state of the dots app:dotFilledBackground"@drawable/filled" // Customize the filled state of the dots app:dotDiameter="12dp" // Change the diameter of the dots diff --git a/app/build.gradle b/app/build.gradle index de9556a1..6165d39a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 25 - buildToolsVersion "25.0.3" + compileSdkVersion 26 + buildToolsVersion "26.0.2" defaultConfig { applicationId "com.andrognito.pinlockviewapp" - minSdkVersion 11 - targetSdkVersion 25 + minSdkVersion 14 + targetSdkVersion 26 versionCode 1 versionName "1.0" } @@ -22,6 +22,6 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:25.3.1' + compile 'com.android.support:appcompat-v7:26.1.0' compile project(':pinlockview') } diff --git a/app/src/main/java/com/andrognito/pinlockviewapp/SampleActivity.java b/app/src/main/java/com/andrognito/pinlockviewapp/SampleActivity.java index f6414d5a..1e846109 100644 --- a/app/src/main/java/com/andrognito/pinlockviewapp/SampleActivity.java +++ b/app/src/main/java/com/andrognito/pinlockviewapp/SampleActivity.java @@ -1,15 +1,22 @@ package com.andrognito.pinlockviewapp; +import android.graphics.Color; import android.os.Bundle; -import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Window; import android.view.WindowManager; +import android.widget.ImageView; +import android.view.View; +import android.view.View.*; +import android.widget.RelativeLayout; +import android.widget.Toast; import com.andrognito.pinlockview.IndicatorDots; +import com.andrognito.pinlockview.InputField; import com.andrognito.pinlockview.PinLockListener; import com.andrognito.pinlockview.PinLockView; +import com.andrognito.pinlockview.SeparateDeleteButton; public class SampleActivity extends AppCompatActivity { @@ -17,11 +24,16 @@ public class SampleActivity extends AppCompatActivity { private PinLockView mPinLockView; private IndicatorDots mIndicatorDots; + private InputField mInputField; + private SeparateDeleteButton mSeparateDeleteButton; + private ImageView logo; + private boolean isEnterButtonEnabled = true; private PinLockListener mPinLockListener = new PinLockListener() { @Override public void onComplete(String pin) { Log.d(TAG, "Pin complete: " + pin); + Toast.makeText(SampleActivity.this, "Pin complete: " + pin, Toast.LENGTH_SHORT).show(); } @Override @@ -42,18 +54,73 @@ protected void onCreate(Bundle savedInstanceState) { getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_sample); - + logo = (ImageView) findViewById(R.id.profile_image); mPinLockView = (PinLockView) findViewById(R.id.pin_lock_view); + mInputField = (InputField) findViewById(R.id.input_field); mIndicatorDots = (IndicatorDots) findViewById(R.id.indicator_dots); + mSeparateDeleteButton = (SeparateDeleteButton) findViewById(R.id.separate_delete_button); + mPinLockView.attachInputField(mInputField); mPinLockView.attachIndicatorDots(mIndicatorDots); + mPinLockView.attachSeparateDeleteButton(mSeparateDeleteButton); + mPinLockView.setPinLockListener(mPinLockListener); - //mPinLockView.setCustomKeySet(new int[]{2, 3, 1, 5, 9, 6, 7, 0, 8, 4}); - //mPinLockView.enableLayoutShuffling(); + mPinLockView.setPinLength(6); + mPinLockView.setShowDeleteButton(false); + ((RelativeLayout.LayoutParams) mPinLockView.getLayoutParams()).addRule(RelativeLayout.BELOW, R.id.input_field); + mInputField.setVisibility(View.VISIBLE); + mInputField.requestFocus(); + + mSeparateDeleteButton.setShowSeparateDeleteButton(true); + mSeparateDeleteButton.setSeparateDeleteButtonColor(Color.TRANSPARENT); + mSeparateDeleteButton.setSeparateDeleteButtonPressedColor(Color.GRAY); + mSeparateDeleteButton.setImageResource(R.drawable.ic_keyboard_backspace); + + mPinLockView.setUseCustomEnterButtonImages(true); + mPinLockView.setEnterButtonEnabledDrawableId(R.drawable.ic_check_box); + mPinLockView.setEnterButtonDisabledDrawableId(R.drawable.ic_check_box_outline); + mPinLockView.setEnterButtonPressedDrawableId(R.drawable.ic_check_box); + mPinLockView.setDeleteButtonDrawable(getResources().getDrawable(R.drawable.ic_cheveron_left)); + + mPinLockView.detachIndicatorDots(); + mIndicatorDots.setVisibility(View.GONE); + mIndicatorDots.setIndicatorType(IndicatorDots.IndicatorType.FIXED); + + logo.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + if (isEnterButtonEnabled) { + + isEnterButtonEnabled = false; + mPinLockView.setShowEnterButton(false); + mPinLockView.setSwapEnterDeleteButtons(false); + mPinLockView.setShowDeleteButton(true); + mSeparateDeleteButton.setShowSeparateDeleteButton(false); + mInputField.setVisibility(View.GONE); + mIndicatorDots.setVisibility(View.VISIBLE); + ((RelativeLayout.LayoutParams) mPinLockView.getLayoutParams()).addRule(RelativeLayout.BELOW, R.id.indicator_dots); + mPinLockView.resetPinLockView(); + + mPinLockView.detachInputField(); + mPinLockView.detachSeparateDeleteButton(); + mPinLockView.attachIndicatorDots(mIndicatorDots); + } else{ - mPinLockView.setPinLength(4); - mPinLockView.setTextColor(ContextCompat.getColor(this, R.color.white)); + isEnterButtonEnabled = true; + mPinLockView.setShowEnterButton(true); + mPinLockView.setSwapEnterDeleteButtons(true); + mPinLockView.setShowDeleteButton(false); + mSeparateDeleteButton.setShowSeparateDeleteButton(true); + mInputField.setVisibility(View.VISIBLE); + mIndicatorDots.setVisibility(View.GONE); + ((RelativeLayout.LayoutParams) mPinLockView.getLayoutParams()).addRule(RelativeLayout.BELOW, R.id.input_field); + mInputField.requestFocus(); + mPinLockView.resetPinLockView(); - mIndicatorDots.setIndicatorType(IndicatorDots.IndicatorType.FILL_WITH_ANIMATION); + mPinLockView.attachInputField(mInputField); + mPinLockView.attachSeparateDeleteButton(mSeparateDeleteButton); + mPinLockView.detachIndicatorDots(); + } + } + }); } } diff --git a/app/src/main/res/drawable-hdpi/ic_check_box.png b/app/src/main/res/drawable-hdpi/ic_check_box.png new file mode 100644 index 00000000..dfde503b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_check_box.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_check_box_outline.png b/app/src/main/res/drawable-hdpi/ic_check_box_outline.png new file mode 100644 index 00000000..ea0e7e63 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_check_box_outline.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_cheveron_left.png b/app/src/main/res/drawable-hdpi/ic_cheveron_left.png new file mode 100644 index 00000000..fb87e635 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_cheveron_left.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_keyboard_backspace.png b/app/src/main/res/drawable-hdpi/ic_keyboard_backspace.png new file mode 100644 index 00000000..8d7c3a7d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_keyboard_backspace.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_check_box.png b/app/src/main/res/drawable-mdpi/ic_check_box.png new file mode 100644 index 00000000..ad595bcc Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_check_box.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_check_box_outline.png b/app/src/main/res/drawable-mdpi/ic_check_box_outline.png new file mode 100644 index 00000000..a7598b03 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_check_box_outline.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_cheveron_left.png b/app/src/main/res/drawable-mdpi/ic_cheveron_left.png new file mode 100644 index 00000000..e301accb Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_cheveron_left.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_keyboard_backspace.png b/app/src/main/res/drawable-mdpi/ic_keyboard_backspace.png new file mode 100644 index 00000000..81678c64 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_keyboard_backspace.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_check_box.png b/app/src/main/res/drawable-xhdpi/ic_check_box.png new file mode 100644 index 00000000..b1bc4430 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_check_box.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_check_box_outline.png b/app/src/main/res/drawable-xhdpi/ic_check_box_outline.png new file mode 100644 index 00000000..20ed7755 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_check_box_outline.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_cheveron_left.png b/app/src/main/res/drawable-xhdpi/ic_cheveron_left.png new file mode 100644 index 00000000..764df638 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_cheveron_left.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_keyboard_backspace.png b/app/src/main/res/drawable-xhdpi/ic_keyboard_backspace.png new file mode 100644 index 00000000..5abe5bcc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_keyboard_backspace.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_check_box.png b/app/src/main/res/drawable-xxhdpi/ic_check_box.png new file mode 100644 index 00000000..5529859e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_check_box.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_check_box_outline.png b/app/src/main/res/drawable-xxhdpi/ic_check_box_outline.png new file mode 100644 index 00000000..8d34740c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_check_box_outline.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_cheveron_left.png b/app/src/main/res/drawable-xxhdpi/ic_cheveron_left.png new file mode 100644 index 00000000..e5f8c0db Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_cheveron_left.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_keyboard_backspace.png b/app/src/main/res/drawable-xxhdpi/ic_keyboard_backspace.png new file mode 100644 index 00000000..58343f19 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_keyboard_backspace.png differ diff --git a/app/src/main/res/layout/activity_sample.xml b/app/src/main/res/layout/activity_sample.xml index 05b65c72..a72f15df 100644 --- a/app/src/main/res/layout/activity_sample.xml +++ b/app/src/main/res/layout/activity_sample.xml @@ -15,6 +15,8 @@ android:layout_marginTop="104dp" android:src="@drawable/img_no_avatar" /> + + + + + + + app:keypadShowLetters="true" + app:keypadDeleteButtonColor="#E1BEE7" + app:keypadDefaultDeleteColor="false" + app:keypadDeleteButtonPressedColor="@color/white" + app:keypadShowEnterButton="true" + app:keypadSwapEnterDeleteButtons="true" + app:keypadEnterButtonColor="@android:color/transparent" + app:keypadEnterButtonDisabledColor="@android:color/transparent" + app:keypadEnterButtonPressedColor="@color/greyish" + app:indicatorType="fixed" + /> diff --git a/build.gradle b/build.gradle index b249cff4..f61856e6 100644 --- a/build.gradle +++ b/build.gradle @@ -2,10 +2,11 @@ buildscript { repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:3.0.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' // NOTE: Do not place your application dependencies here; they belong @@ -15,6 +16,7 @@ buildscript { allprojects { repositories { + google() jcenter() } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 660db70a..568ab3c4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Mar 10 00:08:20 IST 2017 +#Tue Dec 12 09:32:28 EET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip diff --git a/pinlockview/build.gradle b/pinlockview/build.gradle index 2fe6adbf..9b567ad3 100644 --- a/pinlockview/build.gradle +++ b/pinlockview/build.gradle @@ -26,12 +26,12 @@ ext { android { - compileSdkVersion 25 - buildToolsVersion "25.0.3" + compileSdkVersion 26 + buildToolsVersion "26.0.2" defaultConfig { - minSdkVersion 11 - targetSdkVersion 25 + minSdkVersion 14 + targetSdkVersion 26 versionCode 5 versionName "2.1.0" } @@ -46,8 +46,8 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:25.3.1' - compile 'com.android.support:recyclerview-v7:25.3.1' + compile 'com.android.support:appcompat-v7:26.1.0' + compile 'com.android.support:recyclerview-v7:26.1.0' } apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' diff --git a/pinlockview/src/main/java/com/andrognito/pinlockview/CustomizationOptionsBundle.java b/pinlockview/src/main/java/com/andrognito/pinlockview/CustomizationOptionsBundle.java index 63eee73e..1e9f939a 100644 --- a/pinlockview/src/main/java/com/andrognito/pinlockview/CustomizationOptionsBundle.java +++ b/pinlockview/src/main/java/com/andrognito/pinlockview/CustomizationOptionsBundle.java @@ -5,39 +5,142 @@ /** * The customization options for the buttons in {@link PinLockView} * passed to the {@link PinLockAdapter} to decorate the individual views - * + *

* Created by aritraroy on 01/06/16. */ public class CustomizationOptionsBundle { - + private int numberTextColor; + private int lettersTextColor; + private int deleteButtonColor; private int textColor; private int textSize; + private int numberTextSize; + private int lettersTextSize; + private boolean showLetters; + private boolean isNumbersTextBold; + private boolean isLettersTextBold; + private boolean isDeleteDefaultColor; private int buttonSize; private Drawable buttonBackgroundDrawable; private Drawable deleteButtonDrawable; private int deleteButtonSize; private boolean showDeleteButton; + private boolean useDeprecated; private int deleteButtonPressesColor; + private boolean showEnterButton; + private int pinLength; + private boolean swapEnterDeleteButtons; + + private int enterButtonColor; + private int enterButtonDisabledColor; + private int enterButtonPressesColor; + private boolean useCustomEnterButtomImages; + private int enterButtonEnabledDrawableId; + private int enterButtonDisabledDrawableId; + private int enterButtonPressedDrawableId; + + public CustomizationOptionsBundle() { } public int getTextColor() { return textColor; } - + public void setTextColor(int textColor) { this.textColor = textColor; } - + public int getTextSize() { return textSize; } - + public void setTextSize(int textSize) { this.textSize = textSize; } + public int getNumbersTextColor() { + return numberTextColor; + } + + public void setNumbersTextColor(int textColor) { + this.numberTextColor = textColor; + } + + public int getLettersTextColor() { + return lettersTextColor; + } + + public void setLettersTextColor(int textColor) { + this.lettersTextColor = textColor; + } + + public int getNumbersTextSize() { + return numberTextSize; + } + + public void setNumbersTextSize(int textSize) { + this.numberTextSize = textSize; + } + + public int getLettersTextSize() { + return lettersTextSize; + } + + public void setLettersTextSize(int textSize) { + this.lettersTextSize = textSize; + } + + public boolean getShowLetters() { + return showLetters; + } + + public void setShowLetters(boolean showLetters) { + this.showLetters = showLetters; + } + + // Deprecated colour options + public boolean getUseDeprecated() { + return useDeprecated; + } + + public void setUseDeprecated(boolean useDeprecated) { + this.useDeprecated = useDeprecated; + } + + public int getDeleteButtonColor() { + return deleteButtonColor; + } + + public void setDeleteButtonColor(int defaultColor) { + this.deleteButtonColor = defaultColor; + } + + public boolean getDeleteButtonDefault() { + return isDeleteDefaultColor; + } + + public void setDeleteButtonDefault(boolean isDefaultColor) { + this.isDeleteDefaultColor = isDefaultColor; + } + + public boolean getIsNumbersTextBold() { + return isNumbersTextBold; + } + + public void setIsNumbersTextBold(boolean isNumbersTextBold) { + this.isNumbersTextBold = isNumbersTextBold; + } + + public boolean getIsLettersTextBold() { + return isLettersTextBold; + } + + public void setIsLettersTextBold(boolean isLettersTextBold) { + this.isLettersTextBold = isLettersTextBold; + } + public int getButtonSize() { return buttonSize; } @@ -85,4 +188,84 @@ public int getDeleteButtonPressesColor() { public void setDeleteButtonPressesColor(int deleteButtonPressesColor) { this.deleteButtonPressesColor = deleteButtonPressesColor; } + + public boolean isShowEnterButton() { + return showEnterButton; + } + + public void setShowEnterButton(boolean showEnterButton) { + this.showEnterButton = showEnterButton; + } + + public int getEnterButtonColor() { + return this.enterButtonColor; + } + + public void setEnterButtonColor(int enterButtonColor) { + this.enterButtonColor = enterButtonColor; + } + + public int getEnterButtonDisabledColor() { + return this.enterButtonDisabledColor; + } + + public void setEnterButtonDisabledColor(int enterButtonDisabledColor) { + this.enterButtonDisabledColor = enterButtonDisabledColor; + } + + public int getEnterButtonPressesColor() { + return this.enterButtonPressesColor; + } + + public void setEnterButtonPressesColor(int enterButtonPressesColor) { + this.enterButtonPressesColor = enterButtonPressesColor; + } + + public int getPinLength() { + return pinLength; + } + + public void setPinLength(int pinLength) { + this.pinLength = pinLength; + } + + public boolean isSwapEnterDeleteButtons() { + return swapEnterDeleteButtons; + } + + public void setSwapEnterDeleteButtons(boolean swapEnterDeleteButtons) { + this.swapEnterDeleteButtons = swapEnterDeleteButtons; + } + + public boolean isUseCustomEnterButtonImages() { + return useCustomEnterButtomImages; + } + + public void setUseCustomEnterButtonImages(boolean useCustomEnterButtomImages) { + this.useCustomEnterButtomImages = useCustomEnterButtomImages; + } + + public int getEnterButtonEnabledDrawableId() { + return enterButtonEnabledDrawableId; + } + + public void setEnterButtonEnabledDrawableId(int enterButtonEnabledDrawableId) { + this.enterButtonEnabledDrawableId = enterButtonEnabledDrawableId; + } + + public int getEnterButtonDisabledDrawableId() { + return enterButtonDisabledDrawableId; + } + + public void setEnterButtonDisabledDrawableId(int enterButtonDisabledDrawableId) { + this.enterButtonDisabledDrawableId = enterButtonDisabledDrawableId; + } + + public int getEnterButtonPressedDrawableId() { + return enterButtonPressedDrawableId; + } + + public void setEnterButtonPressedDrawableId(int enterButtonPressedDrawableId) { + this.enterButtonPressedDrawableId = enterButtonPressedDrawableId; + } } diff --git a/pinlockview/src/main/java/com/andrognito/pinlockview/InputField.java b/pinlockview/src/main/java/com/andrognito/pinlockview/InputField.java new file mode 100644 index 00000000..aac09dce --- /dev/null +++ b/pinlockview/src/main/java/com/andrognito/pinlockview/InputField.java @@ -0,0 +1,199 @@ +package com.andrognito.pinlockview; + +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.support.v4.view.ViewCompat; +import android.support.v7.widget.AppCompatEditText; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class InputField extends AppCompatEditText { + + private static final char PASSWORD_DOT = '\u2022'; + private static final int DEFAULT_DOT_DELAY_MILLIS = 1500; + + private TextWatcher pinWatcher; + + public InputField(Context context) { + super(context); + + initView(context); + } + + public InputField(Context context, AttributeSet attrs) { + super(context, attrs); + + initView(context); + } + + public InputField(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + initView(context); + } + + private void initView(Context context) { + ViewCompat.setLayoutDirection(this, ViewCompat.LAYOUT_DIRECTION_LTR); + + disableKeyboard(context); + setupPasswordDots(); + } + + private void disableKeyboard(Context context) { + setCursorVisible(true); + ((Activity) context).getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setShowSoftInputOnFocus(false); + } + setTextIsSelectable(true); + setInputType(InputType.TYPE_NULL); + setKeyListener(null); + setRawInputType(InputType.TYPE_CLASS_TEXT); + + setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + /* do nothing */ + } + }); + setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + /* do nothing */ + return true; + } + }); + setOnKeyListener(new OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + /* do nothing */ + return true; + } + }); + setOnEditorActionListener(new OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + /* do nothing */ + return true; + } + }); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + /* do nothing */ + return true; + } + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + /* do nothing */ + return true; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + /* do nothing */ + return true; + } + + private void setupPasswordDots() { + setupPasswordDots(DEFAULT_DOT_DELAY_MILLIS); + } + + private void setupPasswordDots(final int dotDelayMillis) { + if (pinWatcher != null) { + removeTextChangedListener(pinWatcher); + } + + pinWatcher = new TextWatcher() { + private Handler pinHandler = new Handler(); + + private int oldLength = 0; + private AtomicBoolean isCurrentlyEditing = new AtomicBoolean(false); + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + if (isCurrentlyEditing.get()) { + return; // Do nothing if editing field within the watcher, to prevent infinite loops + } + + pinHandler.removeCallbacksAndMessages(null); + + Runnable hideText = new Runnable() { + @Override + public void run() { + isCurrentlyEditing.set(true); + InputField.this.setText(hideString(InputField.this.getText().toString())); + InputField.this.setSelection(InputField.this.getText().length()); + isCurrentlyEditing.set(false); + } + }; + + if (oldLength < s.toString().length()) { // Briefly display last digit if PIN increases + oldLength = s.length(); + + isCurrentlyEditing.set(true); + InputField.this.setText(almostHideString(InputField.this.getText().toString())); + InputField.this.setSelection(InputField.this.getText().length()); + isCurrentlyEditing.set(false); + + pinHandler.postDelayed(hideText, dotDelayMillis); + } else { // Otherwise just hide PIN if deleting + oldLength = s.length(); + + hideText.run(); + } + } + + }; + + addTextChangedListener(pinWatcher); + } + + private String almostHideString(String s) { + if (s == null || s.isEmpty()) { + return ""; + } + + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < s.length() - 1; ++i) { + stringBuilder.append(PASSWORD_DOT); + } + stringBuilder.append(s.charAt(s.length() - 1)); + + return stringBuilder.toString(); + } + + private String hideString(String s) { + if (s == null || s.isEmpty()) { + return ""; + } + + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < s.length(); ++i) { + stringBuilder.append(PASSWORD_DOT); + } + + return stringBuilder.toString(); + } +} diff --git a/pinlockview/src/main/java/com/andrognito/pinlockview/PinLockAdapter.java b/pinlockview/src/main/java/com/andrognito/pinlockview/PinLockAdapter.java index 1df5c3a7..9b48b5a8 100644 --- a/pinlockview/src/main/java/com/andrognito/pinlockview/PinLockAdapter.java +++ b/pinlockview/src/main/java/com/andrognito/pinlockview/PinLockAdapter.java @@ -1,8 +1,10 @@ package com.andrognito.pinlockview; +import android.annotation.SuppressLint; import android.content.Context; import android.graphics.PorterDuff; import android.graphics.Rect; +import android.graphics.Typeface; import android.os.Build; import android.support.v7.widget.RecyclerView; import android.util.TypedValue; @@ -10,9 +12,10 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TextView; /** * Created by aritraroy on 31/05/16. @@ -21,11 +24,18 @@ public class PinLockAdapter extends RecyclerView.Adapter 0) { - holder.mButtonImage.setVisibility(View.VISIBLE); - if (mCustomizationOptionsBundle.getDeleteButtonDrawable() != null) { - holder.mButtonImage.setImageDrawable(mCustomizationOptionsBundle.getDeleteButtonDrawable()); - } - holder.mButtonImage.setColorFilter(mCustomizationOptionsBundle.getTextColor(), - PorterDuff.Mode.SRC_ATOP); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - mCustomizationOptionsBundle.getDeleteButtonSize(), - mCustomizationOptionsBundle.getDeleteButtonSize()); - holder.mButtonImage.setLayoutParams(params); - } else { - holder.mButtonImage.setVisibility(View.GONE); + if (holder == null) { + return; + } + + // If delete button is not shown, disable and make invisible + if (mPinLength < 1 || !mCustomizationOptionsBundle.isShowDeleteButton()) { + holder.mDeleteButton.setEnabled(false); + holder.mDeleteButton.setVisibility(View.GONE); + setButtonSizes(holder.mDeleteButton); + return; + } + + // Otherwise, configure delete button + + // Set image of delete button + holder.mDeleteButton.setEnabled(true); + holder.mDeleteButton.setVisibility(View.VISIBLE); + if (mCustomizationOptionsBundle.getDeleteButtonDrawable() != null) { + holder.mButtonImage.setImageDrawable(mCustomizationOptionsBundle.getDeleteButtonDrawable()); + } + + // Set color of delete button + if (mCustomizationOptionsBundle.getDeleteButtonDefault()) { + holder.mButtonImage.setColorFilter(mCustomizationOptionsBundle.getNumbersTextColor(), + PorterDuff.Mode.SRC_ATOP); + } else { + holder.mButtonImage.setColorFilter(mCustomizationOptionsBundle.getDeleteButtonColor(), + PorterDuff.Mode.SRC_ATOP); + } + + setButtonSizes(holder.mDeleteButton); + } + + private void configureEnterButtonHolder(EnterViewHolder holder) { + if (holder == null) { + return; + } + + // If enter button is not shown, disable and make invisible + if (!mCustomizationOptionsBundle.isShowEnterButton()) { + holder.mEnterButton.setEnabled(false); + holder.mEnterButton.setVisibility(View.GONE); + setButtonSizes(holder.mEnterButton); + return; + } + + // Otherwise, configure enter button + + // Set Enable of Enter Button + holder.mEnterButton.setVisibility(View.VISIBLE); + if (mPinLength >= mCustomizationOptionsBundle.getPinLength()) { + holder.mEnterButton.setEnabled(true); + if (mCustomizationOptionsBundle.isUseCustomEnterButtonImages()) { + holder.mEnterButton.setImageResource(mCustomizationOptionsBundle.getEnterButtonEnabledDrawableId()); } + holder.mEnterButton.setColorFilter(mCustomizationOptionsBundle.getEnterButtonColor(), + PorterDuff.Mode.SRC_ATOP); + } else { + holder.mEnterButton.setEnabled(false); + if (mCustomizationOptionsBundle.isUseCustomEnterButtonImages()) { + holder.mEnterButton.setImageResource(mCustomizationOptionsBundle.getEnterButtonDisabledDrawableId()); + } + holder.mEnterButton.setColorFilter(mCustomizationOptionsBundle.getEnterButtonDisabledColor(), + PorterDuff.Mode.SRC_ATOP); } + + setButtonSizes(holder.mEnterButton); + } + + private void setButtonSizes(View button) { + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + mCustomizationOptionsBundle.getButtonSize(), + mCustomizationOptionsBundle.getButtonSize()); + button.setLayoutParams(params); } @Override @@ -118,7 +268,10 @@ public int getItemCount() { @Override public int getItemViewType(int position) { - if (position == getItemCount() - 1) { + if (position == getEnterButtonPosition()) { + return VIEW_TYPE_ENTER; + } + if (position == getDeleteButtonPosition()) { return VIEW_TYPE_DELETE; } return VIEW_TYPE_NUMBER; @@ -170,20 +323,46 @@ public void setOnDeleteClickListener(OnDeleteClickListener onDeleteClickListener this.mOnDeleteClickListener = onDeleteClickListener; } + public void setOnEnterClickListener(OnEnterClickListener onEnterClickListener) { + this.mOnEnterClickListener = onEnterClickListener; + } + public CustomizationOptionsBundle getCustomizationOptions() { return mCustomizationOptionsBundle; } public void setCustomizationOptions(CustomizationOptionsBundle customizationOptionsBundle) { this.mCustomizationOptionsBundle = customizationOptionsBundle; + setEnterButtonPosition(mCustomizationOptionsBundle.isSwapEnterDeleteButtons() ? 11 : 9); + setDeleteButtonPosition(mCustomizationOptionsBundle.isSwapEnterDeleteButtons() ? 9 : 11); + } + + public int getEnterButtonPosition() { + return enterButtonPosition; + } + + public void setEnterButtonPosition(int enterButtonPosition) { + this.enterButtonPosition = enterButtonPosition; + } + + public int getDeleteButtonPosition() { + return deleteButtonPosition; + } + + public void setDeleteButtonPosition(int deleteButtonPosition) { + this.deleteButtonPosition = deleteButtonPosition; } public class NumberViewHolder extends RecyclerView.ViewHolder { - Button mNumberButton; + LinearLayout mNumberButton; + TextView number; + TextView letters; public NumberViewHolder(final View itemView) { super(itemView); - mNumberButton = (Button) itemView.findViewById(R.id.button); + mNumberButton = (LinearLayout) itemView.findViewById(R.id.keypad_button); + number = (TextView) itemView.findViewById(R.id.number); + letters = (TextView) itemView.findViewById(R.id.letters); mNumberButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -199,54 +378,120 @@ public class DeleteViewHolder extends RecyclerView.ViewHolder { LinearLayout mDeleteButton; ImageView mButtonImage; + @SuppressLint("ClickableViewAccessibility") public DeleteViewHolder(final View itemView) { super(itemView); - mDeleteButton = (LinearLayout) itemView.findViewById(R.id.button); + mDeleteButton = (LinearLayout) itemView.findViewById(R.id.delete_button); mButtonImage = (ImageView) itemView.findViewById(R.id.buttonImage); - if (mCustomizationOptionsBundle.isShowDeleteButton() && mPinLength > 0) { - mDeleteButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mOnDeleteClickListener != null) { - mOnDeleteClickListener.onDeleteClicked(); - } + mDeleteButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mOnDeleteClickListener != null) { + mOnDeleteClickListener.onDeleteClicked(); } - }); + } + }); - mDeleteButton.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (mOnDeleteClickListener != null) { - mOnDeleteClickListener.onDeleteLongClicked(); - } - return true; + mDeleteButton.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (mOnDeleteClickListener != null) { + mOnDeleteClickListener.onDeleteLongClicked(); } - }); + return true; + } + }); - mDeleteButton.setOnTouchListener(new View.OnTouchListener() { - private Rect rect; + mDeleteButton.setOnTouchListener(new View.OnTouchListener() { + private Rect rect; - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - mButtonImage.setColorFilter(mCustomizationOptionsBundle - .getDeleteButtonPressesColor()); - rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + mButtonImage.setColorFilter(mCustomizationOptionsBundle + .getDeleteButtonPressesColor()); + rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); + } + if (event.getAction() == MotionEvent.ACTION_UP) { + if (mCustomizationOptionsBundle.getDeleteButtonDefault()) { + mButtonImage.clearColorFilter(); + } else { + mButtonImage.setColorFilter(mCustomizationOptionsBundle.getDeleteButtonColor(), + PorterDuff.Mode.SRC_ATOP); } - if (event.getAction() == MotionEvent.ACTION_UP) { + } + if (leftButtonArea(v, event)) { + if (mCustomizationOptionsBundle.getDeleteButtonDefault()) { mButtonImage.clearColorFilter(); + } else { + mButtonImage.setColorFilter(mCustomizationOptionsBundle.getDeleteButtonColor(), + PorterDuff.Mode.SRC_ATOP); + } + } + return false; + } + + private boolean leftButtonArea(View v, MotionEvent event) { + return rect != null && !rect.contains(v.getLeft() + (int) event.getX(), + v.getTop() + (int) event.getY()); + } + }); + } + } + + public class EnterViewHolder extends RecyclerView.ViewHolder { + ImageButton mEnterButton; + + @SuppressLint("ClickableViewAccessibility") + public EnterViewHolder(final View itemView) { + super(itemView); + mEnterButton = (ImageButton) itemView.findViewById(R.id.enter_button); + + mEnterButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mOnEnterClickListener != null) { + mOnEnterClickListener.onEnterClicked(); + } + } + }); + + mEnterButton.setOnTouchListener(new View.OnTouchListener() { + private Rect rect; + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (mCustomizationOptionsBundle.isUseCustomEnterButtonImages()) { + mEnterButton.setImageResource(mCustomizationOptionsBundle.getEnterButtonPressedDrawableId()); } - if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!rect.contains(v.getLeft() + (int) event.getX(), - v.getTop() + (int) event.getY())) { - mButtonImage.clearColorFilter(); - } + mEnterButton.setColorFilter(mCustomizationOptionsBundle.getEnterButtonPressesColor(), + PorterDuff.Mode.SRC_ATOP); + rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); + } + if (event.getAction() == MotionEvent.ACTION_UP) { + if (mCustomizationOptionsBundle.isUseCustomEnterButtonImages()) { + mEnterButton.setImageResource(mCustomizationOptionsBundle.getEnterButtonEnabledDrawableId()); } - return false; + mEnterButton.setColorFilter(mCustomizationOptionsBundle.getEnterButtonColor(), + PorterDuff.Mode.SRC_ATOP); } - }); - } + if (leftButtonArea(v, event)) { + if (mCustomizationOptionsBundle.isUseCustomEnterButtonImages()) { + mEnterButton.setImageResource(mCustomizationOptionsBundle.getEnterButtonEnabledDrawableId()); + } + mEnterButton.setColorFilter(mCustomizationOptionsBundle.getEnterButtonColor(), + PorterDuff.Mode.SRC_ATOP); + } + return false; + } + + private boolean leftButtonArea(View v, MotionEvent event) { + return rect != null && !rect.contains(v.getLeft() + (int) event.getX(), + v.getTop() + (int) event.getY()); + } + }); } } @@ -259,4 +504,8 @@ public interface OnDeleteClickListener { void onDeleteLongClicked(); } + + public interface OnEnterClickListener { + void onEnterClicked(); + } } diff --git a/pinlockview/src/main/java/com/andrognito/pinlockview/PinLockView.java b/pinlockview/src/main/java/com/andrognito/pinlockview/PinLockView.java index 72eade71..f60ac08f 100644 --- a/pinlockview/src/main/java/com/andrognito/pinlockview/PinLockView.java +++ b/pinlockview/src/main/java/com/andrognito/pinlockview/PinLockView.java @@ -2,10 +2,12 @@ import android.content.Context; import android.content.res.TypedArray; +import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; +import android.view.View; /** * Represents a numeric lock view which can used to taken numbers as input. @@ -23,13 +25,29 @@ public class PinLockView extends RecyclerView { private String mPin = ""; private int mPinLength; private int mHorizontalSpacing, mVerticalSpacing; - private int mTextColor, mDeleteButtonPressedColor; - private int mTextSize, mButtonSize, mDeleteButtonSize; + private int mTextColor, mNumbersTextColor, mLettersTextColor, mDeleteButtonPressedColor, mDeleteButtonColor; + private int mTextSize, mNumbersTextSize, mLettersTextSize, mButtonSize, mDeleteButtonSize; private Drawable mButtonBackgroundDrawable; private Drawable mDeleteButtonDrawable; - private boolean mShowDeleteButton; + private boolean mShowDeleteButton, mShowLetters; + private boolean mNumbersTextBold, mLettersTextBold; + private boolean mDeleteButtonDefault; + private boolean mDeprecatedColorOptions; + + private boolean mShowEnterButton; + private boolean mSwapEnterDeleteButtons; + + private int mEnterButtonColor; + private int mEnterButtonDisabledColor; + private int mEnterButtonPressedColor; + + private boolean mUseCustomEnterButtonImages; + private int mEnterButtonEnabledDrawableId; + private int mEnterButtonDisabledDrawableId; private IndicatorDots mIndicatorDots; + private InputField mInputField; + private SeparateDeleteButton mSeparateDeleteButton; private PinLockAdapter mAdapter; private PinLockListener mPinLockListener; private CustomizationOptionsBundle mCustomizationOptionsBundle; @@ -45,37 +63,32 @@ public void onNumberClicked(int keyValue) { if (isIndicatorDotsAttached()) { mIndicatorDots.updateDot(mPin.length()); } + if (isInputFieldAttached()) { + mInputField.setText(mPin); + } if (mPin.length() == 1) { mAdapter.setPinLength(mPin.length()); - mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1); + mAdapter.notifyItemChanged(mAdapter.getDeleteButtonPosition()); + if (mSeparateDeleteButton != null && mSeparateDeleteButton.isShowSeparateDeleteButton()) { + mSeparateDeleteButton.setVisibility(View.VISIBLE); + } + } + + if (mPin.length() == mPinLength) { + mAdapter.setPinLength(mPin.length()); + mAdapter.notifyItemChanged(mAdapter.getEnterButtonPosition()); } if (mPinLockListener != null) { - if (mPin.length() == mPinLength) { + if (mPin.length() == mPinLength && !mShowEnterButton) { mPinLockListener.onComplete(mPin); } else { mPinLockListener.onPinChange(mPin.length(), mPin); } } - } else { - if (!isShowDeleteButton()) { - resetPinLockView(); - mPin = mPin.concat(String.valueOf(keyValue)); - - if (isIndicatorDotsAttached()) { - mIndicatorDots.updateDot(mPin.length()); - } - - if (mPinLockListener != null) { - mPinLockListener.onPinChange(mPin.length(), mPin); - } - - } else { - if (mPinLockListener != null) { - mPinLockListener.onComplete(mPin); - } - } + } else if (mPinLockListener != null && !mShowEnterButton) { + mPinLockListener.onComplete(mPin); } } }; @@ -90,10 +103,21 @@ public void onDeleteClicked() { if (isIndicatorDotsAttached()) { mIndicatorDots.updateDot(mPin.length()); } + if (isInputFieldAttached()) { + mInputField.setText(mPin); + } if (mPin.length() == 0) { mAdapter.setPinLength(mPin.length()); - mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1); + mAdapter.notifyItemChanged(mAdapter.getDeleteButtonPosition()); + if (mSeparateDeleteButton != null) { + mSeparateDeleteButton.setVisibility(View.GONE); + } + } + + if (mPin.length() == mPinLength - 1) { + mAdapter.setPinLength(mPin.length()); + mAdapter.notifyItemChanged(mAdapter.getEnterButtonPosition()); } if (mPinLockListener != null) { @@ -104,10 +128,8 @@ public void onDeleteClicked() { mPinLockListener.onPinChange(mPin.length(), mPin); } } - } else { - if (mPinLockListener != null) { - mPinLockListener.onEmpty(); - } + } else if (mPinLockListener != null) { + mPinLockListener.onEmpty(); } } @@ -120,6 +142,16 @@ public void onDeleteLongClicked() { } }; + private PinLockAdapter.OnEnterClickListener mOnEnterClickListener + = new PinLockAdapter.OnEnterClickListener() { + @Override + public void onEnterClicked() { + if (mPin.length() >= mPinLength) { + mPinLockListener.onComplete(mPin); + } + } + }; + public PinLockView(Context context) { super(context); init(null, 0); @@ -143,28 +175,61 @@ private void init(AttributeSet attributeSet, int defStyle) { mPinLength = typedArray.getInt(R.styleable.PinLockView_pinLength, DEFAULT_PIN_LENGTH); mHorizontalSpacing = (int) typedArray.getDimension(R.styleable.PinLockView_keypadHorizontalSpacing, ResourceUtils.getDimensionInPx(getContext(), R.dimen.default_horizontal_spacing)); mVerticalSpacing = (int) typedArray.getDimension(R.styleable.PinLockView_keypadVerticalSpacing, ResourceUtils.getDimensionInPx(getContext(), R.dimen.default_vertical_spacing)); - mTextColor = typedArray.getColor(R.styleable.PinLockView_keypadTextColor, ResourceUtils.getColor(getContext(), R.color.white)); - mTextSize = (int) typedArray.getDimension(R.styleable.PinLockView_keypadTextSize, ResourceUtils.getDimensionInPx(getContext(), R.dimen.default_text_size)); + mNumbersTextColor = typedArray.getColor(R.styleable.PinLockView_keypadNumbersTextColor, ResourceUtils.getColor(getContext(), R.color.white)); + mLettersTextColor = typedArray.getColor(R.styleable.PinLockView_keypadLettersTextColor, ResourceUtils.getColor(getContext(), R.color.white)); + mDeleteButtonColor = typedArray.getColor(R.styleable.PinLockView_keypadDeleteButtonColor, ResourceUtils.getColor(getContext(), R.color.white)); + mNumbersTextSize = (int) typedArray.getDimension(R.styleable.PinLockView_keypadNumbersTextSize, ResourceUtils.getDimensionInPx(getContext(), R.dimen.default_text_size)); + mLettersTextSize = (int) typedArray.getDimension(R.styleable.PinLockView_keypadLettersTextSize, ResourceUtils.getDimensionInPx(getContext(), R.dimen.default_text_size)); mButtonSize = (int) typedArray.getDimension(R.styleable.PinLockView_keypadButtonSize, ResourceUtils.getDimensionInPx(getContext(), R.dimen.default_button_size)); mDeleteButtonSize = (int) typedArray.getDimension(R.styleable.PinLockView_keypadDeleteButtonSize, ResourceUtils.getDimensionInPx(getContext(), R.dimen.default_delete_button_size)); mButtonBackgroundDrawable = typedArray.getDrawable(R.styleable.PinLockView_keypadButtonBackgroundDrawable); mDeleteButtonDrawable = typedArray.getDrawable(R.styleable.PinLockView_keypadDeleteButtonDrawable); mShowDeleteButton = typedArray.getBoolean(R.styleable.PinLockView_keypadShowDeleteButton, true); + mNumbersTextBold = typedArray.getBoolean(R.styleable.PinLockView_keypadNumbersBold, false); + mLettersTextBold = typedArray.getBoolean(R.styleable.PinLockView_keypadLettersBold, false); + mDeleteButtonDefault = typedArray.getBoolean(R.styleable.PinLockView_keypadDefaultDeleteColor, true); + mShowLetters = typedArray.getBoolean(R.styleable.PinLockView_keypadShowLetters, false); mDeleteButtonPressedColor = typedArray.getColor(R.styleable.PinLockView_keypadDeleteButtonPressedColor, ResourceUtils.getColor(getContext(), R.color.greyish)); + mDeprecatedColorOptions = typedArray.getBoolean(R.styleable.PinLockView_keypadUseDeprecatedColorOptions, true); + mTextColor = typedArray.getColor(R.styleable.PinLockView_keypadTextColor, ResourceUtils.getColor(getContext(), R.color.white)); + mTextSize = (int) typedArray.getDimension(R.styleable.PinLockView_keypadTextSize, ResourceUtils.getDimensionInPx(getContext(), R.dimen.default_text_size)); + mShowEnterButton = typedArray.getBoolean(R.styleable.PinLockView_keypadShowEnterButton, false); + mSwapEnterDeleteButtons = typedArray.getBoolean(R.styleable.PinLockView_keypadSwapEnterDeleteButtons, false); + + mEnterButtonColor = typedArray.getColor(R.styleable.PinLockView_keypadEnterButtonColor, ResourceUtils.getColor(getContext(), R.color.white)); + mEnterButtonDisabledColor = typedArray.getColor(R.styleable.PinLockView_keypadEnterButtonDisabledColor, ResourceUtils.getColor(getContext(), R.color.greyish)); + mEnterButtonPressedColor = typedArray.getColor(R.styleable.PinLockView_keypadEnterButtonPressedColor, ResourceUtils.getColor(getContext(), R.color.greyish)); } finally { typedArray.recycle(); } mCustomizationOptionsBundle = new CustomizationOptionsBundle(); + mCustomizationOptionsBundle.setNumbersTextColor(mNumbersTextColor); + mCustomizationOptionsBundle.setNumbersTextSize(mNumbersTextSize); + mCustomizationOptionsBundle.setLettersTextColor(mLettersTextColor); + mCustomizationOptionsBundle.setLettersTextSize(mLettersTextSize); mCustomizationOptionsBundle.setTextColor(mTextColor); mCustomizationOptionsBundle.setTextSize(mTextSize); mCustomizationOptionsBundle.setButtonSize(mButtonSize); + mCustomizationOptionsBundle.setShowLetters(mShowLetters); + mCustomizationOptionsBundle.setDeleteButtonColor(mDeleteButtonColor); + mCustomizationOptionsBundle.setIsNumbersTextBold(mNumbersTextBold); + mCustomizationOptionsBundle.setIsLettersTextBold(mLettersTextBold); mCustomizationOptionsBundle.setButtonBackgroundDrawable(mButtonBackgroundDrawable); mCustomizationOptionsBundle.setDeleteButtonDrawable(mDeleteButtonDrawable); mCustomizationOptionsBundle.setDeleteButtonSize(mDeleteButtonSize); mCustomizationOptionsBundle.setShowDeleteButton(mShowDeleteButton); mCustomizationOptionsBundle.setDeleteButtonPressesColor(mDeleteButtonPressedColor); + mCustomizationOptionsBundle.setDeleteButtonDefault(mDeleteButtonDefault); + mCustomizationOptionsBundle.setUseDeprecated(mDeprecatedColorOptions); + + mCustomizationOptionsBundle.setShowEnterButton(mShowEnterButton); + mCustomizationOptionsBundle.setPinLength(mPinLength); + mCustomizationOptionsBundle.setSwapEnterDeleteButtons(mSwapEnterDeleteButtons); + mCustomizationOptionsBundle.setEnterButtonColor(mEnterButtonColor); + mCustomizationOptionsBundle.setEnterButtonDisabledColor(mEnterButtonDisabledColor); + mCustomizationOptionsBundle.setEnterButtonPressesColor(mEnterButtonPressedColor); initView(); } @@ -174,6 +239,9 @@ private void initView() { mAdapter = new PinLockAdapter(getContext()); mAdapter.setOnItemClickListener(mOnNumberClickListener); mAdapter.setOnDeleteClickListener(mOnDeleteClickListener); + + mAdapter.setOnEnterClickListener(mOnEnterClickListener); + mAdapter.setCustomizationOptions(mCustomizationOptionsBundle); setAdapter(mAdapter); @@ -206,10 +274,14 @@ public int getPinLength() { */ public void setPinLength(int pinLength) { this.mPinLength = pinLength; + mCustomizationOptionsBundle.setPinLength(mPinLength); if (isIndicatorDotsAttached()) { mIndicatorDots.setPinLength(pinLength); } + if (isInputFieldAttached()) { + mInputField.setText(mPin); + } } /** @@ -252,6 +324,207 @@ public void setTextSize(int textSize) { mAdapter.notifyDataSetChanged(); } + /** + * Get the text color in the buttons + * + * @return the text color of the numbers + */ + public int getNumbersTextColor() { + return mNumbersTextColor; + } + + /** + * Set the text color of the buttons dynamically + * + * @param textColor the text color for the numbers + */ + public void setNumbersTextColor(int textColor) { + this.mNumbersTextColor = textColor; + mCustomizationOptionsBundle.setNumbersTextColor(textColor); + mAdapter.notifyDataSetChanged(); + } + + /** + * Get the text color in the buttons + * + * @return the text color of the letters + */ + public int getLettersTextColor() { + return mLettersTextColor; + } + + /** + * Set the text color of the buttons dynamically + * + * @param textColor the text color for the letters + */ + public void setLettersTextColor(int textColor) { + this.mLettersTextColor = textColor; + mCustomizationOptionsBundle.setLettersTextColor(textColor); + mAdapter.notifyDataSetChanged(); + } + + /** + * Get the size of the numerical text in the buttons + * + * @return the size of the text in pixels + */ + public int getNumbersTextSize() { + return mNumbersTextSize; + } + + /** + * Set the size of numerical text in pixels + * + * @param textSize the text size in pixels + */ + public void setNumbersTextSize(int textSize) { + this.mNumbersTextSize = textSize; + mCustomizationOptionsBundle.setNumbersTextSize(textSize); + mAdapter.notifyDataSetChanged(); + } + + /** + * Get the size of the alphabetical text in the buttons + * + * @return the size of the text in pixels + */ + public int getLettersTextSize() { + return mLettersTextSize; + } + + /** + * Set the size of the alphabetical text in pixels + * + * @param textSize the text size in pixels + */ + public void setLettersTextSize(int textSize) { + this.mLettersTextSize = textSize; + mCustomizationOptionsBundle.setLettersTextSize(textSize); + mAdapter.notifyDataSetChanged(); + } + + /** + * Get the color of the delete button + * + * @return the delete button color + */ + public int getDeleteButtonColor() { + return mDeleteButtonColor; + } + + /** + * Set the color fo the delete button + * + * @param buttonColor the color of the delete button + */ + public void setDeleteButtonColor(int buttonColor) { + this.mDeleteButtonColor = buttonColor; + mCustomizationOptionsBundle.setDeleteButtonColor(buttonColor); + mAdapter.notifyDataSetChanged(); + } + + /** + * Checks if the numbers text is bold + * + * @return returns true if numbers are bolded, false otherwise + */ + public boolean isNumbersTextBold() { + return mNumbersTextBold; + } + + /** + * Dynamically set the boldness of numbers text + * + * @param isNumbersBold true if the numbers text should be bold, false otherwise + */ + public void setNumbersTextBold(boolean isNumbersBold) { + this.mNumbersTextBold = isNumbersBold; + mCustomizationOptionsBundle.setIsNumbersTextBold(isNumbersBold); + mAdapter.notifyDataSetChanged(); + } + + /** + * Checks if the letters text is bold + * + * @return returns true if letters are bolded, false otherwise + */ + public boolean isLettersTextBold() { + return mLettersTextBold; + } + + /** + * Dynamically set the boldness of letters text + * + * @param isLettersBold true if the letters text should be bold, false otherwise + */ + public void setLettersTextBold(boolean isLettersBold) { + this.mLettersTextBold = isLettersBold; + mCustomizationOptionsBundle.setIsLettersTextBold(isLettersBold); + mAdapter.notifyDataSetChanged(); + } + + /** + * Are the letters in the keypad buttons shown + * + * @return returns true if shown, false otherwise + */ + public boolean isShownLetters() { + return mShowLetters; + } + + /** + * Dynamically set if the letters in buttons should be shown + * + * @param showLetters true if the letters in buttons should be shown, false otherwise + */ + public void setShownLetters(boolean showLetters) { + this.mShowLetters = showLetters; + mCustomizationOptionsBundle.setShowLetters(showLetters); + mAdapter.notifyDataSetChanged(); + } + + + /** + * Is the delete button of default color (white) + * + * @return returns true if it is default color, false otherwise + */ + public boolean isDefaultDeleteColor() { + return mDeleteButtonDefault; + } + + /** + * Dynamically set if the delete button is of default color + * + * @param showLetters true if the letters in buttons should be shown, false otherwise + */ + public void setDefaultDeleteColor(boolean isDefaultColor) { + this.mDeleteButtonDefault = isDefaultColor; + mCustomizationOptionsBundle.setDeleteButtonDefault(isDefaultColor); + mAdapter.notifyDataSetChanged(); + } + + /** + * Are we using deprecated color options for color of numbers and letters + * + * @return returns true if it we are using the deprecated options, false otherwise + */ + public boolean isUsingDeprecatedColorOptions() { + return mDeprecatedColorOptions; + } + + /** + * Dynamically set if the delete button is of default color + * + * @param showLetters true if the letters in buttons should be shown, false otherwise + */ + public void setDeprecatedColorOptions(boolean deprecatedColorOption) { + this.mDeprecatedColorOptions = deprecatedColorOption; + mCustomizationOptionsBundle.setUseDeprecated(deprecatedColorOption); + mAdapter.notifyDataSetChanged(); + } + /** * Get the size of the pin buttons * @@ -349,6 +622,7 @@ public boolean isShowDeleteButton() { public void setShowDeleteButton(boolean showDeleteButton) { this.mShowDeleteButton = showDeleteButton; mCustomizationOptionsBundle.setShowDeleteButton(showDeleteButton); + mAdapter.notifyItemChanged(mAdapter.getDeleteButtonPosition()); mAdapter.notifyDataSetChanged(); } @@ -372,6 +646,44 @@ public void setDeleteButtonPressedColor(int deleteButtonPressedColor) { mAdapter.notifyDataSetChanged(); } + public boolean isShowEnterButton() { + return mShowEnterButton; + } + + /** + * Sets if the enter button should be shown + * + * @param showEnterButton true if the enter button should be shown + */ + public void setShowEnterButton(boolean showEnterButton) { + this.mShowEnterButton = showEnterButton; + mCustomizationOptionsBundle.setShowEnterButton(showEnterButton); + mAdapter.notifyItemChanged(mAdapter.getEnterButtonPosition()); + mAdapter.notifyDataSetChanged(); + } + + /** + * Returns if enter and delete buttons are swapped + * + * @return true if enter and delete buttons are swapped + */ + public boolean isSwapEnterDeleteButtons() { + return mSwapEnterDeleteButtons; + } + + /** + * Swaps the placement of the enter and delete buttons + * + * @param swapEnterDeleteButtons true if enter and delete buttons should be swapped + */ + public void setSwapEnterDeleteButtons(boolean swapEnterDeleteButtons) { + this.mSwapEnterDeleteButtons = swapEnterDeleteButtons; + mCustomizationOptionsBundle.setSwapEnterDeleteButtons(swapEnterDeleteButtons); + mAdapter.setEnterButtonPosition(mCustomizationOptionsBundle.isSwapEnterDeleteButtons() ? 11 : 9); + mAdapter.setDeleteButtonPosition(mCustomizationOptionsBundle.isSwapEnterDeleteButtons() ? 9 : 11); + mAdapter.notifyDataSetChanged(); + } + public int[] getCustomKeySet() { return mCustomKeySet; } @@ -405,11 +717,20 @@ public void resetPinLockView() { clearInternalPin(); mAdapter.setPinLength(mPin.length()); - mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1); + mAdapter.notifyItemChanged(mAdapter.getEnterButtonPosition()); + mAdapter.notifyItemChanged(mAdapter.getDeleteButtonPosition()); if (mIndicatorDots != null) { mIndicatorDots.updateDot(mPin.length()); } + if (mInputField != null) { + mInputField.setText(""); + } + if (mSeparateDeleteButton != null) { + mSeparateDeleteButton.setVisibility(View.GONE); + mSeparateDeleteButton.setColorFilter(mSeparateDeleteButton.getSeparateDeleteButtonColor(), + PorterDuff.Mode.SRC_ATOP); + } } /** @@ -429,4 +750,102 @@ public boolean isIndicatorDotsAttached() { public void attachIndicatorDots(IndicatorDots mIndicatorDots) { this.mIndicatorDots = mIndicatorDots; } + + public void detachIndicatorDots() { + this.mIndicatorDots = null; + } + + /** + * Returns true if {@link SeparateDeleteButton} is attached to {@link PinLockView} + * + * @return true if attached, false otherwise + */ + public boolean isSeparateDeleteButtonAttached() { + return mSeparateDeleteButton != null; + } + + /** + * Attaches {@link SeparateDeleteButton} to {@link PinLockView} + * + * @param separateDeleteButton the SeparateDeleteButton to attach + */ + public void attachSeparateDeleteButton(SeparateDeleteButton separateDeleteButton) { + this.mSeparateDeleteButton = separateDeleteButton; + this.mSeparateDeleteButton.setOnDeleteClickListener(mOnDeleteClickListener); + if (mPin.length() == 0) { + this.mSeparateDeleteButton.setVisibility(View.GONE); + } else { + this.mSeparateDeleteButton.setVisibility(View.VISIBLE); + } + } + + public void detachSeparateDeleteButton() { + this.mSeparateDeleteButton = null; + } + + /** + * Returns true if {@link InputField} is attached to {@link PinLockView} + * + * @return true if attached, false otherwise + */ + public boolean isInputFieldAttached() { + return mInputField != null; + } + + /** + * Attaches {@link InputField} to {@link PinLockView} + * + * @param inputField the InputField to attach + */ + public void attachInputField(InputField inputField) { + this.mInputField = inputField; + } + + public void detachInputField() { + this.mInputField = null; + } + + /** + * Sets whether custom enter button images should be used. + * + * @param useCustomEnterButtonImages true if custom enter buttons should be used + */ + public void setUseCustomEnterButtonImages(boolean useCustomEnterButtonImages) { + this.mUseCustomEnterButtonImages = useCustomEnterButtonImages; + mCustomizationOptionsBundle.setUseCustomEnterButtonImages(useCustomEnterButtonImages); + mAdapter.notifyDataSetChanged(); + } + + /** + * Sets the drawable ID of the enter button when enabled + * + * @param id the drawable ID + */ + public void setEnterButtonEnabledDrawableId(int id) { + this.mEnterButtonEnabledDrawableId = id; + mCustomizationOptionsBundle.setEnterButtonEnabledDrawableId(id); + mAdapter.notifyDataSetChanged(); + } + + /** + * Sets the drawable ID of the enter button when disabled + * + * @param id the drawable ID + */ + public void setEnterButtonDisabledDrawableId(int id) { + this.mEnterButtonEnabledDrawableId = id; + mCustomizationOptionsBundle.setEnterButtonDisabledDrawableId(id); + mAdapter.notifyDataSetChanged(); + } + + /** + * Sets the drawable ID of the enter button when pressed + * + * @param id the drawable ID + */ + public void setEnterButtonPressedDrawableId(int id) { + this.mEnterButtonEnabledDrawableId = id; + mCustomizationOptionsBundle.setEnterButtonPressedDrawableId(id); + mAdapter.notifyDataSetChanged(); + } } diff --git a/pinlockview/src/main/java/com/andrognito/pinlockview/SeparateDeleteButton.java b/pinlockview/src/main/java/com/andrognito/pinlockview/SeparateDeleteButton.java new file mode 100644 index 00000000..dca9eaee --- /dev/null +++ b/pinlockview/src/main/java/com/andrognito/pinlockview/SeparateDeleteButton.java @@ -0,0 +1,132 @@ +package com.andrognito.pinlockview; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.support.v4.view.ViewCompat; +import android.support.v7.widget.AppCompatImageButton; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +@SuppressLint("ClickableViewAccessibility") +public class SeparateDeleteButton extends AppCompatImageButton { + + private PinLockAdapter.OnDeleteClickListener mOnDeleteClickListener; + + private int mSeparateDeleteButtonColor; + private int mSeparateDeleteButtonPressedColor; + private boolean mShowSeparateDeleteButton = true; + + public SeparateDeleteButton(Context context) { + super(context); + initView(context, null); + } + + public SeparateDeleteButton(Context context, AttributeSet attrs) { + super(context, attrs); + initView(context, attrs); + } + + public SeparateDeleteButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(context, attrs); + } + + public void initView(Context context, AttributeSet attrs) { + TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.PinLockView); + + try { + mSeparateDeleteButtonColor = typedArray.getColor(R.styleable.PinLockView_separateDeleteButtonColor, ResourceUtils.getColor(getContext(), R.color.white)); + mSeparateDeleteButtonPressedColor = typedArray.getColor(R.styleable.PinLockView_separateDeleteButtonPressedColor, ResourceUtils.getColor(getContext(), R.color.greyish)); + } finally { + typedArray.recycle(); + } + + ViewCompat.setLayoutDirection(this, ViewCompat.LAYOUT_DIRECTION_LTR); + + setImageResource(R.drawable.ic_backspace); + setBackgroundColor(Color.TRANSPARENT); + setScaleType(ScaleType.FIT_CENTER); + setColorFilter(getSeparateDeleteButtonColor(), PorterDuff.Mode.SRC_ATOP); + + setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mOnDeleteClickListener != null) { + mOnDeleteClickListener.onDeleteClicked(); + } + } + }); + + setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (mOnDeleteClickListener != null) { + mOnDeleteClickListener.onDeleteLongClicked(); + } + return true; + } + }); + + setOnTouchListener(new OnTouchListener() { + private Rect rect; + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + SeparateDeleteButton.this.setColorFilter( + getSeparateDeleteButtonPressedColor(), PorterDuff.Mode.SRC_ATOP); + rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); + } + if (event.getAction() == MotionEvent.ACTION_UP) { + SeparateDeleteButton.this.setColorFilter( + getSeparateDeleteButtonColor(), PorterDuff.Mode.SRC_ATOP); + } + if (leftButtonArea(v, event)) { + SeparateDeleteButton.this.setColorFilter( + getSeparateDeleteButtonColor(), PorterDuff.Mode.SRC_ATOP); + } + return false; + } + + private boolean leftButtonArea(View v, MotionEvent event) { + return rect != null && !rect.contains(v.getLeft() + (int) event.getX(), + v.getTop() + (int) event.getY()); + } + }); + + } + + public void setOnDeleteClickListener(PinLockAdapter.OnDeleteClickListener mOnDeleteClickListener) { + this.mOnDeleteClickListener = mOnDeleteClickListener; + } + + public int getSeparateDeleteButtonColor() { + return mSeparateDeleteButtonColor; + } + + public void setSeparateDeleteButtonColor(int mSeparateDeleteButtonColor) { + this.mSeparateDeleteButtonColor = mSeparateDeleteButtonColor; + setColorFilter(getSeparateDeleteButtonColor(), PorterDuff.Mode.SRC_ATOP); + } + + public int getSeparateDeleteButtonPressedColor() { + return mSeparateDeleteButtonPressedColor; + } + + public void setSeparateDeleteButtonPressedColor(int mSeparateDeleteButtonPressedColor) { + this.mSeparateDeleteButtonPressedColor = mSeparateDeleteButtonPressedColor; + } + + public boolean isShowSeparateDeleteButton() { + return mShowSeparateDeleteButton; + } + + public void setShowSeparateDeleteButton(boolean mShowSeparateDeleteButton) { + this.mShowSeparateDeleteButton = mShowSeparateDeleteButton; + } +} diff --git a/pinlockview/src/main/res/drawable/ic_check_circle.png b/pinlockview/src/main/res/drawable/ic_check_circle.png new file mode 100644 index 00000000..f88c7828 Binary files /dev/null and b/pinlockview/src/main/res/drawable/ic_check_circle.png differ diff --git a/pinlockview/src/main/res/layout-v21/layout_number_item.xml b/pinlockview/src/main/res/layout-v21/layout_number_item.xml index e96e7fe8..f96f5bc0 100644 --- a/pinlockview/src/main/res/layout-v21/layout_number_item.xml +++ b/pinlockview/src/main/res/layout-v21/layout_number_item.xml @@ -6,10 +6,26 @@ android:clipToPadding="false" android:orientation="vertical"> -