From 495ffe16c35f7711d47bd119fca0c39fd685d46d Mon Sep 17 00:00:00 2001 From: tibbi Date: Thu, 6 Jan 2022 23:31:06 +0100 Subject: [PATCH] copying some resources and assets from the AOSP keyboard --- .../keyboard/views/MyKeyboard.kt | 1420 ----------------- .../keyboard/views/MyKeyboardView.kt | 4 - .../main/res/drawable/btn_keyboard_key.xml | 31 + .../drawable/btn_keyboard_key_normal.9.png | Bin 0 -> 715 bytes .../btn_keyboard_key_normal_off.9.png | Bin 0 -> 1001 bytes .../drawable/btn_keyboard_key_normal_on.9.png | Bin 0 -> 1077 bytes .../drawable/btn_keyboard_key_pressed.9.png | Bin 0 -> 745 bytes .../btn_keyboard_key_pressed_off.9.png | Bin 0 -> 1042 bytes .../btn_keyboard_key_pressed_on.9.png | Bin 0 -> 1105 bytes .../res/drawable/keyboard_background.9.png | Bin 0 -> 206 bytes .../res/drawable/keyboard_key_feedback.xml | 4 + .../keyboard_key_feedback_background.9.png | Bin 0 -> 3971 bytes ...eyboard_key_feedback_more_background.9.png | Bin 0 -> 4663 bytes .../main/res/layout/keyboard_key_preview.xml | 13 +- .../res/layout/keyboard_popup_keyboard.xml | 1 + .../res/layout/keyboard_view_keyboard.xml | 1 + app/src/main/res/values/styles.xml | 100 +- 17 files changed, 143 insertions(+), 1431 deletions(-) delete mode 100644 app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboard.kt create mode 100644 app/src/main/res/drawable/btn_keyboard_key.xml create mode 100644 app/src/main/res/drawable/btn_keyboard_key_normal.9.png create mode 100644 app/src/main/res/drawable/btn_keyboard_key_normal_off.9.png create mode 100644 app/src/main/res/drawable/btn_keyboard_key_normal_on.9.png create mode 100644 app/src/main/res/drawable/btn_keyboard_key_pressed.9.png create mode 100644 app/src/main/res/drawable/btn_keyboard_key_pressed_off.9.png create mode 100644 app/src/main/res/drawable/btn_keyboard_key_pressed_on.9.png create mode 100644 app/src/main/res/drawable/keyboard_background.9.png create mode 100644 app/src/main/res/drawable/keyboard_key_feedback.xml create mode 100644 app/src/main/res/drawable/keyboard_key_feedback_background.9.png create mode 100644 app/src/main/res/drawable/keyboard_key_feedback_more_background.9.png diff --git a/app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboard.kt b/app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboard.kt deleted file mode 100644 index 0d5beac..0000000 --- a/app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboard.kt +++ /dev/null @@ -1,1420 +0,0 @@ -package com.simplemobiletools.keyboard.views - -import android.annotation.SuppressLint -import android.content.Context -import android.content.res.TypedArray -import android.graphics.* -import android.graphics.Paint.Align -import android.graphics.drawable.Drawable -import android.inputmethodservice.Keyboard -import android.inputmethodservice.KeyboardView -import android.media.AudioManager -import android.os.Handler -import android.os.Message -import android.util.AttributeSet -import android.util.TypedValue -import android.view.* -import android.view.GestureDetector.SimpleOnGestureListener -import android.view.accessibility.AccessibilityEvent -import android.view.accessibility.AccessibilityManager -import android.widget.PopupWindow -import android.widget.TextView -import com.simplemobiletools.commons.extensions.getResolution -import com.simplemobiletools.commons.helpers.mydebug -import com.simplemobiletools.keyboard.R -import java.util.* - -/** - * A view that renders a virtual [Keyboard]. It handles rendering of keys and - * detecting key presses and touch movements. - * - * @attr ref android.R.styleable#KeyboardView_keyBackground - * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout - * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset - * @attr ref android.R.styleable#KeyboardView_keyPreviewHeight - * @attr ref android.R.styleable#KeyboardView_labelTextSize - * @attr ref android.R.styleable#KeyboardView_keyTextSize - * @attr ref android.R.styleable#KeyboardView_keyTextColor - * @attr ref android.R.styleable#KeyboardView_verticalCorrection - * @attr ref android.R.styleable#KeyboardView_popupLayout - * - */ -class MyKeyboard @JvmOverloads constructor( - context: Context, - attrs: AttributeSet?, - defStyleAttr: Int = R.attr.keyboardViewStyle, - defStyleRes: Int = 0 -) : - View(context, attrs, defStyleAttr, defStyleRes), View.OnClickListener { - - /** - * Listener for virtual keyboard events. - */ - interface OnKeyboardActionListener { - /** - * Called when the user presses a key. This is sent before the [.onKey] is called. - * For keys that repeat, this is only called once. - * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid - * key, the value will be zero. - */ - fun onPress(primaryCode: Int) - - /** - * Called when the user releases a key. This is sent after the [.onKey] is called. - * For keys that repeat, this is only called once. - * @param primaryCode the code of the key that was released - */ - fun onRelease(primaryCode: Int) - - /** - * Send a key press to the listener. - * @param primaryCode this is the key that was pressed - * @param keyCodes the codes for all the possible alternative keys - * with the primary code being the first. If the primary key code is - * a single character such as an alphabet or number or symbol, the alternatives - * will include other characters that may be on the same key or adjacent keys. - * These codes are useful to correct for accidental presses of a key adjacent to - * the intended key. - */ - fun onKey(primaryCode: Int, keyCodes: IntArray?) - - /** - * Sends a sequence of characters to the listener. - * @param text the sequence of characters to be displayed. - */ - fun onText(text: CharSequence?) - - /** - * Called when the user quickly moves the finger from right to left. - */ - fun swipeLeft() - - /** - * Called when the user quickly moves the finger from left to right. - */ - fun swipeRight() - - /** - * Called when the user quickly moves the finger from up to down. - */ - fun swipeDown() - - /** - * Called when the user quickly moves the finger from down to up. - */ - fun swipeUp() - } - - private var mKeyboard: Keyboard? = null - private var mCurrentKeyIndex: Int = NOT_A_KEY - - private var mLabelTextSize = 0 - private var mKeyTextSize = 0 - private var mKeyTextColor = 0 - private var mShadowRadius = 0f - private var mShadowColor = 0 - private val mBackgroundDimAmount: Float - - private var mPreviewText: TextView? = null - private val mPreviewPopup: PopupWindow - private var mPreviewTextSizeLarge = 0 - private var mPreviewOffset = 0 - private var mPreviewHeight = 0 - - // Working variable - private val mCoordinates = IntArray(2) - private val mPopupKeyboard: PopupWindow - private var mMiniKeyboardContainer: View? = null - private var mMiniKeyboard: MyKeyboard? = null - private var mMiniKeyboardOnScreen = false - private var mPopupParent: View - private var mMiniKeyboardOffsetX = 0 - private var mMiniKeyboardOffsetY = 0 - private val mMiniKeyboardCache: MutableMap - private var mKeys = ArrayList() - /** - * Returns the [OnKeyboardActionListener] object. - * @return the listener attached to this keyboard - */ - /** Listener for [OnKeyboardActionListener]. */ - var onKeyboardActionListener: OnKeyboardActionListener? = null - private var mVerticalCorrection = 0 - private var mProximityThreshold = 0 - private val mPreviewCentered = false - /** - * Returns the enabled state of the key feedback popup. - * @return whether or not the key feedback popup is enabled - * @see .setPreviewEnabled - */ - /** - * Enables or disables the key feedback popup. This is a popup that shows a magnified - * version of the depressed key. By default the preview is enabled. - * @param previewEnabled whether or not to enable the key feedback popup - * @see .isPreviewEnabled - */ - var isPreviewEnabled = true - private val mShowTouchPoints = true - private var mPopupPreviewX = 0 - private var mPopupPreviewY = 0 - private var mLastX = 0 - private var mLastY = 0 - private var mStartX = 0 - private var mStartY = 0 - /** - * Returns true if proximity correction is enabled. - */ - /** - * When enabled, calls to [OnKeyboardActionListener.onKey] will include key - * codes for adjacent keys. When disabled, only the primary key code will be - * reported. - * @param enabled whether or not the proximity correction is enabled - */ - var isProximityCorrectionEnabled = false - private val mPaint: Paint - private val mPadding: Rect - private var mDownTime: Long = 0 - private var mLastMoveTime: Long = 0 - private var mLastKey = 0 - private var mLastCodeX = 0 - private var mLastCodeY = 0 - private var mCurrentKey: Int = NOT_A_KEY - private var mDownKey: Int = NOT_A_KEY - private var mLastKeyTime: Long = 0 - private var mCurrentKeyTime: Long = 0 - private val mKeyIndices = IntArray(12) - private var mGestureDetector: GestureDetector? = null - private var mPopupX = 0 - private var mPopupY = 0 - private var mRepeatKeyIndex: Int = NOT_A_KEY - private var mPopupLayout = 0 - private var mAbortKey = false - private var mInvalidatedKey: Keyboard.Key? = null - private val mClipRegion = Rect(0, 0, 0, 0) - private var mPossiblePoly = false - private val mSwipeTracker: SwipeTracker = SwipeTracker() - private val mSwipeThreshold: Int - private val mDisambiguateSwipe: Boolean - - // Variables for dealing with multiple pointers - private var mOldPointerCount = 1 - private var mOldPointerX = 0f - private var mOldPointerY = 0f - - private var mKeyBackground: Drawable? = null - private val mDistances = IntArray(MAX_NEARBY_KEYS) - - // For multi-tap - private var mLastSentIndex = 0 - private var mTapCount = 0 - private var mLastTapTime: Long = 0 - private var mInMultiTap = false - private val mPreviewLabel = StringBuilder(1) - - /** Whether the keyboard bitmap needs to be redrawn before it's blitted. */ - private var mDrawPending = false - - /** The dirty region in the keyboard bitmap */ - private val mDirtyRect = Rect() - - /** The keyboard bitmap for faster updates */ - private var mBuffer: Bitmap? = null - - /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */ - private var mKeyboardChanged = false - - /** The canvas for the above mutable keyboard bitmap */ - private var mCanvas: Canvas? = null - - /** The accessibility manager for accessibility support */ - private val mAccessibilityManager: AccessibilityManager - - /** The audio manager for accessibility support */ - private val mAudioManager: AudioManager - - var mHandler: Handler? = null - - companion object { - private const val DEBUG = false - private const val NOT_A_KEY = -1 - private val KEY_DELETE = intArrayOf(Keyboard.KEYCODE_DELETE) - private val LONG_PRESSABLE_STATE_SET = intArrayOf(R.attr.state_long_pressable) - private const val MSG_SHOW_PREVIEW = 1 - private const val MSG_REMOVE_PREVIEW = 2 - private const val MSG_REPEAT = 3 - private const val MSG_LONGPRESS = 4 - private const val DELAY_BEFORE_PREVIEW = 0 - private const val DELAY_AFTER_PREVIEW = 70 - private const val DEBOUNCE_TIME = 70 - private const val REPEAT_INTERVAL = 50 // ~20 keys per second - private const val REPEAT_START_DELAY = 400 - private val LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() - private const val MAX_NEARBY_KEYS = 12 - private const val MULTITAP_INTERVAL = 800 // milliseconds - } - - init { - val a = context.obtainStyledAttributes(attrs, R.styleable.KeyboardView, defStyleAttr, defStyleRes) - val inflate = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater - var previewLayout = 0 - val keyTextSize = 0 - val n = a.indexCount - for (i in 0 until n) { - val attr = a.getIndex(i) - when (attr) { - R.styleable.KeyboardView_keyBackground -> mKeyBackground = a.getDrawable(attr) - R.styleable.KeyboardView_verticalCorrection -> mVerticalCorrection = a.getDimensionPixelOffset(attr, 0) - R.styleable.KeyboardView_keyPreviewLayout -> previewLayout = a.getResourceId(attr, 0) - R.styleable.KeyboardView_keyPreviewOffset -> mPreviewOffset = a.getDimensionPixelOffset(attr, 0) - R.styleable.KeyboardView_keyPreviewHeight -> mPreviewHeight = a.getDimensionPixelSize(attr, 80) - R.styleable.KeyboardView_keyTextSize -> mKeyTextSize = a.getDimensionPixelSize(attr, 18) - R.styleable.KeyboardView_keyTextColor -> mKeyTextColor = a.getColor(attr, -0x1000000) - R.styleable.KeyboardView_labelTextSize -> mLabelTextSize = a.getDimensionPixelSize(attr, 14) - R.styleable.KeyboardView_popupLayout -> mPopupLayout = a.getResourceId(attr, 0) - R.styleable.KeyboardView_shadowColor -> mShadowColor = a.getColor(attr, 0) - R.styleable.KeyboardView_shadowRadius -> mShadowRadius = a.getFloat(attr, 0f) - } - } - - mKeyBackground = resources.getDrawable(R.drawable.keyboard_popup_panel_background, context.theme) - mBackgroundDimAmount = 0.5f - mPreviewPopup = PopupWindow(context) - if (previewLayout != 0) { - mPreviewText = inflate.inflate(previewLayout, null) as TextView - mPreviewTextSizeLarge = mPreviewText!!.textSize.toInt() - mPreviewPopup.contentView = mPreviewText - mPreviewPopup.setBackgroundDrawable(null) - } else { - isPreviewEnabled = false - } - mPreviewPopup.isTouchable = false - mPopupKeyboard = PopupWindow(context) - mPopupKeyboard.setBackgroundDrawable(null) - //mPopupKeyboard.setClippingEnabled(false); - mPopupParent = this - //mPredicting = true; - mPaint = Paint() - mPaint.isAntiAlias = true - mPaint.textSize = keyTextSize.toFloat() - mPaint.textAlign = Align.CENTER - mPaint.alpha = 255 - mPadding = Rect(0, 0, 0, 0) - mMiniKeyboardCache = HashMap() - mKeyBackground!!.getPadding(mPadding) - mSwipeThreshold = (500 * resources.displayMetrics.density).toInt() - mDisambiguateSwipe = false//resources.getBoolean(R.bool.config_swipeDisambiguation) - mAccessibilityManager = (context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager) - mAudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - resetMultiTap() - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - initGestureDetector() - if (mHandler == null) { - mHandler = object : Handler() { - @SuppressLint("HandlerLeak") - override fun handleMessage(msg: Message) { - when (msg.what) { - MSG_SHOW_PREVIEW -> showKey(msg.arg1) - MSG_REMOVE_PREVIEW -> mPreviewText!!.visibility = INVISIBLE - MSG_REPEAT -> if (repeatKey()) { - val repeat: Message = Message.obtain(this, MSG_REPEAT) - sendMessageDelayed(repeat, REPEAT_INTERVAL.toLong()) - } - MSG_LONGPRESS -> openPopupIfRequired(msg.obj as MotionEvent) - } - } - } - } - } - - private fun initGestureDetector() { - if (mGestureDetector == null) { - mGestureDetector = GestureDetector(context, object : SimpleOnGestureListener() { - override fun onFling( - me1: MotionEvent, me2: MotionEvent, - velocityX: Float, velocityY: Float - ): Boolean { - if (mPossiblePoly) return false - val absX = Math.abs(velocityX) - val absY = Math.abs(velocityY) - val deltaX = me2.x - me1.x - val deltaY = me2.y - me1.y - val travelX = width / 2 // Half the keyboard width - val travelY = height / 2 // Half the keyboard height - mSwipeTracker.computeCurrentVelocity(1000) - val endingVelocityX: Float = mSwipeTracker.xVelocity - val endingVelocityY: Float = mSwipeTracker.yVelocity - var sendDownKey = false - if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) { - sendDownKey = if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) { - true - } else { - swipeRight() - return true - } - } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) { - sendDownKey = if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) { - true - } else { - swipeLeft() - return true - } - } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) { - sendDownKey = if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) { - true - } else { - swipeUp() - return true - } - } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) { - sendDownKey = if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) { - true - } else { - swipeDown() - return true - } - } - if (sendDownKey) { - detectAndSendKey(mDownKey, mStartX, mStartY, me1.eventTime) - } - return false - } - }) - mGestureDetector!!.setIsLongpressEnabled(false) - } - } - /** - * Returns the current keyboard being displayed by this view. - * @return the currently attached keyboard - * @see .setKeyboard - */// Remove any pending messages - // Hint to reallocate the buffer if the size changed - // Not really necessary to do every time, but will free up views - // Switching to a different keyboard should abort any pending keys so that the key up - // doesn't get delivered to the old or new keyboard - // Until the next ACTION_DOWN - /** - * Attaches a keyboard to this view. The keyboard can be switched at any time and the - * view will re-layout itself to accommodate the keyboard. - * @see Keyboard - * - * @see .getKeyboard - * @param keyboard the keyboard to display in this view - */ - var keyboard: Keyboard? - get() = mKeyboard - set(keyboard) { - if (mKeyboard != null) { - showPreview(NOT_A_KEY) - } - // Remove any pending messages - removeMessages() - mKeyboard = keyboard - val keys = mKeyboard!!.keys - mKeys = keys.toMutableList() as ArrayList - requestLayout() - // Hint to reallocate the buffer if the size changed - mKeyboardChanged = true - invalidateAllKeys() - computeProximityThreshold(keyboard) - mMiniKeyboardCache.clear() // Not really necessary to do every time, but will free up views - // Switching to a different keyboard should abort any pending keys so that the key up - // doesn't get delivered to the old or new keyboard - mAbortKey = true // Until the next ACTION_DOWN - } - - /** - * Sets the state of the shift key of the keyboard, if any. - * @param shifted whether or not to enable the state of the shift key - * @return true if the shift key state changed, false if there was no change - * @see KeyboardView.isShifted - */ - fun setShifted(shifted: Boolean): Boolean { - if (mKeyboard != null) { - if (mKeyboard!!.setShifted(shifted)) { - // The whole keyboard probably needs to be redrawn - invalidateAllKeys() - return true - } - } - return false - } - - /** - * Returns the state of the shift key of the keyboard, if any. - * @return true if the shift is in a pressed state, false otherwise. If there is - * no shift key on the keyboard or there is no keyboard attached, it returns false. - * @see KeyboardView.setShifted - */ - var isShifted: Boolean = false - get() = if (mKeyboard != null) { - mKeyboard!!.isShifted - } else false - - fun setVerticalCorrection(verticalOffset: Int) {} - fun setPopupParent(v: View) { - mPopupParent = v - } - - fun setPopupOffset(x: Int, y: Int) { - mMiniKeyboardOffsetX = x - mMiniKeyboardOffsetY = y - if (mPreviewPopup.isShowing) { - mPreviewPopup.dismiss() - } - } - - /** - * Popup keyboard close button clicked. - * @hide - */ - override fun onClick(v: View) { - dismissPopupKeyboard() - } - - private fun adjustCase(label: CharSequence): CharSequence? { - var label: CharSequence? = label - if (mKeyboard!!.isShifted && label != null && label.length < 3 && Character.isLowerCase(label[0])) { - label = label.toString().toUpperCase() - } - return label - } - - public override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - // Round up a little - if (mKeyboard == null) { - setMeasuredDimension(paddingLeft + paddingRight, paddingTop + paddingBottom) - } else { - var width: Int = mKeyboard!!.minWidth + paddingLeft + paddingRight - if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) { - width = MeasureSpec.getSize(widthMeasureSpec) - } - setMeasuredDimension(width, mKeyboard!!.height + paddingTop + paddingBottom) - } - } - - /** - * Compute the average distance between adjacent keys (horizontally and vertically) - * and square it to get the proximity threshold. We use a square here and in computing - * the touch distance from a key's center to avoid taking a square root. - * @param keyboard - */ - private fun computeProximityThreshold(keyboard: Keyboard?) { - if (keyboard == null) return - val keys = mKeys ?: return - val length = keys.size - var dimensionSum = 0 - for (i in 0 until length) { - val key = keys[i] - dimensionSum += Math.min(key.width, key.height) + key.gap - } - if (dimensionSum < 0 || length == 0) return - mProximityThreshold = (dimensionSum * 1.4f / length).toInt() - mProximityThreshold *= mProximityThreshold // Square it - } - - public override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { - super.onSizeChanged(w, h, oldw, oldh) - if (mKeyboard != null) { - //mKeyboard.resize(w, h) - } - // Release the buffer, if any and it will be reallocated on the next draw - mBuffer = null - } - - public override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - if (mDrawPending || mBuffer == null || mKeyboardChanged) { - onBufferDraw() - } - canvas.drawBitmap(mBuffer!!, 0f, 0f, null) - } - - private fun onBufferDraw() { - if (mBuffer == null || mKeyboardChanged) { - if (mBuffer == null || mKeyboardChanged && - (mBuffer!!.width != width || mBuffer!!.height != height) - ) { - // Make sure our bitmap is at least 1x1 - val width = Math.max(1, width) - val height = Math.max(1, height) - mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - mCanvas = Canvas(mBuffer!!) - } - invalidateAllKeys() - mKeyboardChanged = false - } - if (mKeyboard == null) return - mCanvas!!.save() - val canvas = mCanvas - canvas!!.clipRect(mDirtyRect) - val paint = mPaint - val keyBackground = mKeyBackground - val clipRegion = mClipRegion - val padding = mPadding - val kbdPaddingLeft: Int = paddingLeft - val kbdPaddingTop: Int = paddingTop - val keys = mKeys - val invalidKey = mInvalidatedKey - paint.color = mKeyTextColor - var drawSingleKey = false - if (invalidKey != null && canvas.getClipBounds(clipRegion)) { - // Is clipRegion completely contained within the invalidated key? - if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left && invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top && invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right && invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) { - drawSingleKey = true - } - } - canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR) - val keyCount = keys.size - for (i in 0 until keyCount) { - val key = keys[i] - if (drawSingleKey && invalidKey !== key) { - continue - } - val drawableState = key.currentDrawableState - keyBackground!!.state = drawableState - - // Switch the character to uppercase if shift is pressed - val label = if (key.label == null) null else adjustCase(key.label).toString() - val bounds = keyBackground.bounds - if (key.width != bounds.right || - key.height != bounds.bottom - ) { - keyBackground.setBounds(0, 0, key.width, key.height) - } - canvas.translate((key.x + kbdPaddingLeft).toFloat(), (key.y + kbdPaddingTop).toFloat()) - keyBackground.draw(canvas) - if (label != null) { - // For characters, use large font. For labels like "Done", use small font. - if (label.length > 1 && key.codes.size < 2) { - paint.textSize = mLabelTextSize.toFloat() - paint.typeface = Typeface.DEFAULT_BOLD - } else { - paint.textSize = mKeyTextSize.toFloat() - paint.typeface = Typeface.DEFAULT - } - // Draw a drop shadow for the text - paint.setShadowLayer(mShadowRadius, 0f, 0f, mShadowColor) - // Draw the text - canvas.drawText( - label, ((key.width - padding.left - padding.right) / 2 + padding.left).toFloat(), - (key.height - padding.top - padding.bottom) / 2 + (paint.textSize - paint.descent()) / 2 + padding.top, paint - ) - // Turn off drop shadow - paint.setShadowLayer(0f, 0f, 0f, 0) - } else if (key.icon != null) { - val drawableX = (key.width - padding.left - padding.right - key.icon.intrinsicWidth) / 2 + padding.left - val drawableY = (key.height - padding.top - padding.bottom - key.icon.intrinsicHeight) / 2 + paddingTop - canvas.translate(drawableX.toFloat(), drawableY.toFloat()) - key.icon.setBounds( - 0, 0, key.icon.intrinsicWidth, key.icon.intrinsicHeight - ) - key.icon.draw(canvas) - canvas.translate(-drawableX.toFloat(), -drawableY.toFloat()) - } - canvas.translate((-key.x - kbdPaddingLeft).toFloat(), (-key.y - kbdPaddingTop).toFloat()) - } - mInvalidatedKey = null - // Overlay a dark rectangle to dim the keyboard - if (mMiniKeyboardOnScreen) { - paint.color = (mBackgroundDimAmount * 0xFF).toInt() shl 24 - canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint) - } - if (DEBUG && mShowTouchPoints) { - paint.alpha = 128 - paint.color = -0x10000 - canvas.drawCircle(mStartX.toFloat(), mStartY.toFloat(), 3f, paint) - canvas.drawLine(mStartX.toFloat(), mStartY.toFloat(), mLastX.toFloat(), mLastY.toFloat(), paint) - paint.color = -0xffff01 - canvas.drawCircle(mLastX.toFloat(), mLastY.toFloat(), 3f, paint) - paint.color = -0xff0100 - canvas.drawCircle(((mStartX + mLastX) / 2).toFloat(), ((mStartY + mLastY) / 2).toFloat(), 2f, paint) - } - mCanvas!!.restore() - mDrawPending = false - mDirtyRect.setEmpty() - } - - private fun getKeyIndices(x: Int, y: Int, allKeys: IntArray?): Int { - val keys = mKeys - var primaryIndex: Int = NOT_A_KEY - var closestKey: Int = NOT_A_KEY - var closestKeyDist = mProximityThreshold + 1 - Arrays.fill(mDistances, Int.MAX_VALUE) - val nearestKeyIndices = mKeyboard!!.getNearestKeys(x, y) - val keyCount = nearestKeyIndices.size - for (i in 0 until keyCount) { - val key = keys!![nearestKeyIndices[i]] - var dist = 0 - val isInside = key.isInside(x, y) - if (isInside) { - primaryIndex = nearestKeyIndices[i] - } - if (((isProximityCorrectionEnabled - && key.squaredDistanceFrom(x, y).also { dist = it } < mProximityThreshold) - || isInside) - && key.codes[0] > 32 - ) { - // Find insertion point - val nCodes = key.codes.size - if (dist < closestKeyDist) { - closestKeyDist = dist - closestKey = nearestKeyIndices[i] - } - if (allKeys == null) continue - for (j in mDistances.indices) { - if (mDistances[j] > dist) { - // Make space for nCodes codes - System.arraycopy( - mDistances, j, mDistances, j + nCodes, - mDistances.size - j - nCodes - ) - System.arraycopy( - allKeys, j, allKeys, j + nCodes, - allKeys.size - j - nCodes - ) - for (c in 0 until nCodes) { - allKeys[j + c] = key.codes[c] - mDistances[j + c] = dist - } - break - } - } - } - } - if (primaryIndex == NOT_A_KEY) { - primaryIndex = closestKey - } - return primaryIndex - } - - private fun detectAndSendKey(index: Int, x: Int, y: Int, eventTime: Long) { - if (index != NOT_A_KEY && index < mKeys.size) { - val key = mKeys[index] - if (key.text != null) { - onKeyboardActionListener!!.onText(key.text) - onKeyboardActionListener!!.onRelease(NOT_A_KEY) - } else { - var code = key.codes[0] - //TextEntryState.keyPressedAt(key, x, y); - val codes = IntArray(MAX_NEARBY_KEYS) - Arrays.fill(codes, NOT_A_KEY) - getKeyIndices(x, y, codes) - // Multi-tap - if (mInMultiTap) { - if (mTapCount != -1) { - onKeyboardActionListener!!.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE) - } else { - mTapCount = 0 - } - code = key.codes[mTapCount] - } - onKeyboardActionListener!!.onKey(code, codes) - onKeyboardActionListener!!.onRelease(code) - } - mLastSentIndex = index - mLastTapTime = eventTime - } - } - - /** - * Handle multi-tap keys by producing the key label for the current multi-tap state. - */ - private fun getPreviewText(key: Keyboard.Key): CharSequence? { - return if (mInMultiTap) { - // Multi-tap - mPreviewLabel.setLength(0) - mPreviewLabel.append(key.codes[if (mTapCount < 0) 0 else mTapCount].toChar()) - adjustCase(mPreviewLabel) - } else { - adjustCase(key.label) - } - } - - private fun showPreview(keyIndex: Int) { - val oldKeyIndex = mCurrentKeyIndex - val previewPopup = mPreviewPopup - mCurrentKeyIndex = keyIndex - // Release the old key and press the new key - val keys = mKeys - if (oldKeyIndex != mCurrentKeyIndex) { - if (oldKeyIndex != NOT_A_KEY && keys.size > oldKeyIndex) { - val oldKey = keys[oldKeyIndex] - oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY) - invalidateKey(oldKeyIndex) - val keyCode = oldKey.codes[0] - sendAccessibilityEventForUnicodeCharacter( - AccessibilityEvent.TYPE_VIEW_HOVER_EXIT, - keyCode - ) - // TODO: We need to implement AccessibilityNodeProvider for this view. - sendAccessibilityEventForUnicodeCharacter( - AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode - ) - } - if (mCurrentKeyIndex != NOT_A_KEY && keys!!.size > mCurrentKeyIndex) { - val newKey = keys[mCurrentKeyIndex] - newKey.onPressed() - invalidateKey(mCurrentKeyIndex) - val keyCode = newKey.codes[0] - sendAccessibilityEventForUnicodeCharacter( - AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, - keyCode - ) - // TODO: We need to implement AccessibilityNodeProvider for this view. - sendAccessibilityEventForUnicodeCharacter( - AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, keyCode - ) - } - } - // If key changed and preview is on ... - if (oldKeyIndex != mCurrentKeyIndex && isPreviewEnabled) { - mHandler!!.removeMessages(MSG_SHOW_PREVIEW) - if (previewPopup.isShowing) { - if (keyIndex == NOT_A_KEY) { - mHandler!!.sendMessageDelayed( - mHandler!!.obtainMessage(MSG_REMOVE_PREVIEW), - DELAY_AFTER_PREVIEW.toLong() - ) - } - } - if (keyIndex != NOT_A_KEY) { - if (previewPopup.isShowing && mPreviewText!!.visibility == VISIBLE) { - // Show right away, if it's already visible and finger is moving around - showKey(keyIndex) - } else { - mHandler!!.sendMessageDelayed( - mHandler!!.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0), - DELAY_BEFORE_PREVIEW.toLong() - ) - } - } - } - } - - private fun showKey(keyIndex: Int) { - val previewPopup = mPreviewPopup - val keys = mKeys - if (keyIndex < 0 || keyIndex >= mKeys!!.size) return - val key = keys!![keyIndex] - if (key.icon != null) { - mPreviewText!!.setCompoundDrawables( - null, null, null, - if (key.iconPreview != null) key.iconPreview else key.icon - ) - mPreviewText!!.setText(null) - } else { - mPreviewText!!.setCompoundDrawables(null, null, null, null) - mPreviewText!!.text = getPreviewText(key) - if (key.label.length > 1 && key.codes.size < 2) { - mPreviewText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize.toFloat()) - mPreviewText!!.setTypeface(Typeface.DEFAULT_BOLD) - } else { - mPreviewText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge.toFloat()) - mPreviewText!!.setTypeface(Typeface.DEFAULT) - } - } - mPreviewText!!.measure( - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) - ) - val popupWidth = Math.max( - mPreviewText!!.measuredWidth, key.width - + mPreviewText!!.paddingLeft + mPreviewText!!.paddingRight - ) - val popupHeight = mPreviewHeight - val lp = mPreviewText!!.layoutParams - if (lp != null) { - lp.width = popupWidth - lp.height = popupHeight - } - if (!mPreviewCentered) { - mPopupPreviewX = key.x - mPreviewText!!.paddingLeft + paddingLeft - mPopupPreviewY = key.y - popupHeight + mPreviewOffset - } else { - // TODO: Fix this if centering is brought back - mPopupPreviewX = 160 - mPreviewText!!.measuredWidth / 2 - mPopupPreviewY = -mPreviewText!!.measuredHeight - } - mHandler!!.removeMessages(MSG_REMOVE_PREVIEW) - getLocationInWindow(mCoordinates) - mCoordinates[0] += mMiniKeyboardOffsetX // Offset may be zero - mCoordinates[1] += mMiniKeyboardOffsetY // Offset may be zero - - // Set the preview background state - mPreviewText!!.background.state = if (key.popupResId != 0) LONG_PRESSABLE_STATE_SET else EMPTY_STATE_SET - mPopupPreviewX += mCoordinates[0] - mPopupPreviewY += mCoordinates[1] - - // If the popup cannot be shown above the key, put it on the side - getLocationOnScreen(mCoordinates) - if (mPopupPreviewY + mCoordinates[1] < 0) { - // If the key you're pressing is on the left side of the keyboard, show the popup on - // the right, offset by enough to see at least one key to the left/right. - if (key.x + key.width <= width / 2) { - mPopupPreviewX += (key.width * 2.5).toInt() - } else { - mPopupPreviewX -= (key.width * 2.5).toInt() - } - mPopupPreviewY += popupHeight - } - if (previewPopup.isShowing) { - previewPopup.update( - mPopupPreviewX, mPopupPreviewY, - popupWidth, popupHeight - ) - } else { - previewPopup.width = popupWidth - previewPopup.height = popupHeight - previewPopup.showAtLocation( - mPopupParent, Gravity.NO_GRAVITY, - mPopupPreviewX, mPopupPreviewY - ) - } - mPreviewText!!.visibility = VISIBLE - } - - private fun sendAccessibilityEventForUnicodeCharacter(eventType: Int, code: Int) { - if (mAccessibilityManager.isEnabled) { - val event = AccessibilityEvent.obtain(eventType) - onInitializeAccessibilityEvent(event) - val text: String - when (code) { - Keyboard.KEYCODE_ALT -> text = context.getString(R.string.keyboardview_keycode_alt) - Keyboard.KEYCODE_CANCEL -> text = context.getString(R.string.keyboardview_keycode_cancel) - Keyboard.KEYCODE_DELETE -> text = context.getString(R.string.keyboardview_keycode_delete) - Keyboard.KEYCODE_DONE -> text = context.getString(R.string.keyboardview_keycode_done) - Keyboard.KEYCODE_MODE_CHANGE -> text = context.getString(R.string.keyboardview_keycode_mode_change) - Keyboard.KEYCODE_SHIFT -> text = context.getString(R.string.keyboardview_keycode_shift) - '\n'.toInt() -> text = context.getString(R.string.keyboardview_keycode_enter) - else -> text = code.toChar().toString() - } - event.text.add(text) - mAccessibilityManager.sendAccessibilityEvent(event) - } - } - - /** - * Requests a redraw of the entire keyboard. Calling [.invalidate] is not sufficient - * because the keyboard renders the keys to an off-screen buffer and an invalidate() only - * draws the cached buffer. - * @see .invalidateKey - */ - fun invalidateAllKeys() { - mDirtyRect.union(0, 0, width, height) - mDrawPending = true - invalidate() - } - - /** - * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only - * one key is changing it's content. Any changes that affect the position or size of the key - * may not be honored. - * @param keyIndex the index of the key in the attached [Keyboard]. - * @see .invalidateAllKeys - */ - fun invalidateKey(keyIndex: Int) { - if (keyIndex < 0 || keyIndex >= mKeys.size) { - return - } - val key = mKeys[keyIndex] - mInvalidatedKey = key - mDirtyRect.union( - key.x + paddingLeft, key.y + paddingTop, - key.x + key.width + paddingLeft, key.y + key.height + paddingTop - ) - onBufferDraw() - invalidate( - key.x + paddingLeft, key.y + paddingTop, - key.x + key.width + paddingLeft, key.y + key.height + paddingTop - ) - } - - private fun openPopupIfRequired(me: MotionEvent): Boolean { - // Check if we have a popup layout specified first. - if (mPopupLayout == 0) { - return false - } - if (mCurrentKey < 0 || mCurrentKey >= mKeys.size) { - return false - } - val popupKey = mKeys[mCurrentKey] - val result = onLongPress(popupKey) - if (result) { - mAbortKey = true - showPreview(NOT_A_KEY) - } - return result - } - - /** - * Called when a key is long pressed. By default this will open any popup keyboard associated - * with this key through the attributes popupLayout and popupCharacters. - * @param popupKey the key that was long pressed - * @return true if the long press is handled, false otherwise. Subclasses should call the - * method on the base class if the subclass doesn't wish to handle the call. - */ - protected fun onLongPress(popupKey: Keyboard.Key): Boolean { - val popupKeyboardId = popupKey.popupResId - if (popupKeyboardId != 0) { - mMiniKeyboardContainer = mMiniKeyboardCache[popupKey] - if (mMiniKeyboardContainer == null) { - val inflater = context.getSystemService( - Context.LAYOUT_INFLATER_SERVICE - ) as LayoutInflater - mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null) - mMiniKeyboard = mMiniKeyboardContainer!!.findViewById(R.id.keyboardView) as MyKeyboard - val closeButton = mMiniKeyboardContainer!!.findViewById(R.id.closeButton) - closeButton?.setOnClickListener(this) - - mMiniKeyboard!!.onKeyboardActionListener = object : OnKeyboardActionListener { - override fun onKey(primaryCode: Int, keyCodes: IntArray?) { - onKeyboardActionListener!!.onKey(primaryCode, keyCodes) - dismissPopupKeyboard() - } - - override fun onText(text: CharSequence?) { - onKeyboardActionListener!!.onText(text) - dismissPopupKeyboard() - } - - override fun swipeLeft() {} - override fun swipeRight() {} - override fun swipeUp() {} - override fun swipeDown() {} - override fun onPress(primaryCode: Int) { - onKeyboardActionListener!!.onPress(primaryCode) - } - - override fun onRelease(primaryCode: Int) { - onKeyboardActionListener!!.onRelease(primaryCode) - } - } - //mInputView.setSuggest(mSuggest); - val keyboard: Keyboard - keyboard = if (popupKey.popupCharacters != null) { - Keyboard( - context, popupKeyboardId, - popupKey.popupCharacters, -1, paddingLeft + paddingRight - ) - } else { - Keyboard(context, popupKeyboardId) - } - mMiniKeyboard!!.keyboard = keyboard - mMiniKeyboard!!.setPopupParent(this) - mMiniKeyboardContainer!!.measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST) - ) - mMiniKeyboardCache[popupKey] = mMiniKeyboardContainer - } else { - mMiniKeyboard = mMiniKeyboardContainer!!.findViewById( - R.id.keyboardView - ) as MyKeyboard - } - getLocationInWindow(mCoordinates) - mPopupX = popupKey.x + paddingLeft - mPopupY = popupKey.y + paddingTop - mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer!!.measuredWidth - mPopupY = mPopupY - mMiniKeyboardContainer!!.measuredHeight - val x = mPopupX + mMiniKeyboardContainer!!.paddingRight + mCoordinates[0] - val y = mPopupY + mMiniKeyboardContainer!!.paddingBottom + mCoordinates[1] - mMiniKeyboard!!.setPopupOffset(if (x < 0) 0 else x, y) - mMiniKeyboard!!.isShifted = isShifted - mPopupKeyboard.contentView = mMiniKeyboardContainer - mPopupKeyboard.width = mMiniKeyboardContainer!!.measuredWidth - mPopupKeyboard.height = mMiniKeyboardContainer!!.measuredHeight - mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y) - mMiniKeyboardOnScreen = true - //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me)); - invalidateAllKeys() - return true - } - return false - } - - override fun onHoverEvent(event: MotionEvent): Boolean { - if (mAccessibilityManager.isTouchExplorationEnabled && event.pointerCount == 1) { - val action = event.action - when (action) { - MotionEvent.ACTION_HOVER_ENTER -> { - event.action = MotionEvent.ACTION_DOWN - } - MotionEvent.ACTION_HOVER_MOVE -> { - event.action = MotionEvent.ACTION_MOVE - } - MotionEvent.ACTION_HOVER_EXIT -> { - event.action = MotionEvent.ACTION_UP - } - } - return onTouchEvent(event) - } - return true - } - - override fun onTouchEvent(me: MotionEvent): Boolean { - // Convert multi-pointer up/down events to single up/down events to - // deal with the typical multi-pointer behavior of two-thumb typing - val pointerCount = me.pointerCount - val action = me.action - var result = false - val now = me.eventTime - if (pointerCount != mOldPointerCount) { - if (pointerCount == 1) { - // Send a down event for the latest pointer - val down = MotionEvent.obtain( - now, now, MotionEvent.ACTION_DOWN, - me.x, me.y, me.metaState - ) - result = onModifiedTouchEvent(down, false) - down.recycle() - // If it's an up action, then deliver the up as well. - if (action == MotionEvent.ACTION_UP) { - result = onModifiedTouchEvent(me, true) - } - } else { - // Send an up event for the last pointer - val up = MotionEvent.obtain( - now, now, MotionEvent.ACTION_UP, - mOldPointerX, mOldPointerY, me.metaState - ) - result = onModifiedTouchEvent(up, true) - up.recycle() - } - } else { - if (pointerCount == 1) { - result = onModifiedTouchEvent(me, false) - mOldPointerX = me.x - mOldPointerY = me.y - } else { - // Don't do anything when 2 pointers are down and moving. - result = true - } - } - mOldPointerCount = pointerCount - return result - } - - private fun onModifiedTouchEvent(me: MotionEvent, possiblePoly: Boolean): Boolean { - var touchX: Int = me.x.toInt() - paddingLeft - var touchY: Int = me.y.toInt() - paddingTop - if (touchY >= -mVerticalCorrection) touchY += mVerticalCorrection - val action = me.action - val eventTime = me.eventTime - val keyIndex = getKeyIndices(touchX, touchY, null) - mPossiblePoly = possiblePoly - - // Track the last few movements to look for spurious swipes. - if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear() - mSwipeTracker.addMovement(me) - - // Ignore all motion events until a DOWN. - if (mAbortKey - && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL - ) { - return true - } - if (mGestureDetector!!.onTouchEvent(me)) { - showPreview(NOT_A_KEY) - mHandler!!.removeMessages(MSG_REPEAT) - mHandler!!.removeMessages(MSG_LONGPRESS) - return true - } - - // Needs to be called after the gesture detector gets a turn, as it may have - // displayed the mini keyboard - if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) { - return true - } - when (action) { - MotionEvent.ACTION_DOWN -> { - mAbortKey = false - mStartX = touchX - mStartY = touchY - mLastCodeX = touchX - mLastCodeY = touchY - mLastKeyTime = 0 - mCurrentKeyTime = 0 - mLastKey = NOT_A_KEY - mCurrentKey = keyIndex - mDownKey = keyIndex - mDownTime = me.eventTime - mLastMoveTime = mDownTime - checkMultiTap(eventTime, keyIndex) - onKeyboardActionListener!!.onPress(if (keyIndex != NOT_A_KEY) mKeys[keyIndex].codes[0] else 0) - - var wasHandled = false - if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) { - mRepeatKeyIndex = mCurrentKey - val msg = mHandler!!.obtainMessage(MSG_REPEAT) - mHandler!!.sendMessageDelayed(msg, REPEAT_START_DELAY.toLong()) - repeatKey() - // Delivering the key could have caused an abort - if (mAbortKey) { - mRepeatKeyIndex = NOT_A_KEY - wasHandled = true - } - } - if (!wasHandled && mCurrentKey != NOT_A_KEY) { - val msg = mHandler!!.obtainMessage(MSG_LONGPRESS, me) - mHandler!!.sendMessageDelayed(msg, LONGPRESS_TIMEOUT.toLong()) - } - showPreview(keyIndex) - } - MotionEvent.ACTION_MOVE -> { - var continueLongPress = false - if (keyIndex != NOT_A_KEY) { - if (mCurrentKey == NOT_A_KEY) { - mCurrentKey = keyIndex - mCurrentKeyTime = eventTime - mDownTime - } else { - if (keyIndex == mCurrentKey) { - mCurrentKeyTime += eventTime - mLastMoveTime - continueLongPress = true - } else if (mRepeatKeyIndex == NOT_A_KEY) { - resetMultiTap() - mLastKey = mCurrentKey - mLastCodeX = mLastX - mLastCodeY = mLastY - mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime - mCurrentKey = keyIndex - mCurrentKeyTime = 0 - } - } - } - if (!continueLongPress) { - // Cancel old longpress - mHandler!!.removeMessages(MSG_LONGPRESS) - // Start new longpress if key has changed - if (keyIndex != NOT_A_KEY) { - val msg = mHandler!!.obtainMessage(MSG_LONGPRESS, me) - mHandler!!.sendMessageDelayed(msg, LONGPRESS_TIMEOUT.toLong()) - } - } - showPreview(mCurrentKey) - mLastMoveTime = eventTime - } - MotionEvent.ACTION_UP -> { - removeMessages() - if (keyIndex == mCurrentKey) { - mCurrentKeyTime += eventTime - mLastMoveTime - } else { - resetMultiTap() - mLastKey = mCurrentKey - mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime - mCurrentKey = keyIndex - mCurrentKeyTime = 0 - } - if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME && mLastKey != NOT_A_KEY) { - mCurrentKey = mLastKey - touchX = mLastCodeX - touchY = mLastCodeY - } - showPreview(NOT_A_KEY) - Arrays.fill(mKeyIndices, NOT_A_KEY) - // If we're not on a repeating key (which sends on a DOWN event) - if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) { - detectAndSendKey(mCurrentKey, touchX, touchY, eventTime) - } - invalidateKey(keyIndex) - mRepeatKeyIndex = NOT_A_KEY - } - MotionEvent.ACTION_CANCEL -> { - removeMessages() - dismissPopupKeyboard() - mAbortKey = true - showPreview(NOT_A_KEY) - invalidateKey(mCurrentKey) - } - } - mLastX = touchX - mLastY = touchY - return true - } - - private fun repeatKey(): Boolean { - val key = mKeys[mRepeatKeyIndex] - detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime) - return true - } - - protected fun swipeRight() { - onKeyboardActionListener!!.swipeRight() - } - - protected fun swipeLeft() { - onKeyboardActionListener!!.swipeLeft() - } - - protected fun swipeUp() { - onKeyboardActionListener!!.swipeUp() - } - - protected fun swipeDown() { - onKeyboardActionListener!!.swipeDown() - } - - fun closing() { - if (mPreviewPopup.isShowing) { - mPreviewPopup.dismiss() - } - removeMessages() - dismissPopupKeyboard() - mBuffer = null - mCanvas = null - mMiniKeyboardCache.clear() - } - - private fun removeMessages() { - if (mHandler != null) { - mHandler!!.removeMessages(MSG_REPEAT) - mHandler!!.removeMessages(MSG_LONGPRESS) - mHandler!!.removeMessages(MSG_SHOW_PREVIEW) - } - } - - public override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - closing() - } - - private fun dismissPopupKeyboard() { - if (mPopupKeyboard.isShowing) { - mPopupKeyboard.dismiss() - mMiniKeyboardOnScreen = false - invalidateAllKeys() - } - } - - fun handleBack(): Boolean { - if (mPopupKeyboard.isShowing) { - dismissPopupKeyboard() - return true - } - return false - } - - private fun resetMultiTap() { - mLastSentIndex = NOT_A_KEY - mTapCount = 0 - mLastTapTime = -1 - mInMultiTap = false - } - - private fun checkMultiTap(eventTime: Long, keyIndex: Int) { - if (keyIndex == NOT_A_KEY) return - val key = mKeys[keyIndex] - if (key.codes.size > 1) { - mInMultiTap = true - if (eventTime < mLastTapTime + MULTITAP_INTERVAL - && keyIndex == mLastSentIndex - ) { - mTapCount = (mTapCount + 1) % key.codes.size - return - } else { - mTapCount = -1 - return - } - } - if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) { - resetMultiTap() - } - } - - private class SwipeTracker { - val mPastX = FloatArray(NUM_PAST) - val mPastY = FloatArray(NUM_PAST) - val mPastTime = LongArray(NUM_PAST) - var yVelocity = 0f - var xVelocity = 0f - - fun clear() { - mPastTime[0] = 0 - } - - fun addMovement(ev: MotionEvent) { - val time = ev.eventTime - val N = ev.historySize - for (i in 0 until N) { - addPoint( - ev.getHistoricalX(i), ev.getHistoricalY(i), - ev.getHistoricalEventTime(i) - ) - } - addPoint(ev.x, ev.y, time) - } - - private fun addPoint(x: Float, y: Float, time: Long) { - var drop = -1 - var i: Int - val pastTime = mPastTime - i = 0 - while (i < NUM_PAST) { - if (pastTime[i] == 0L) { - break - } else if (pastTime[i] < time - LONGEST_PAST_TIME) { - drop = i - } - i++ - } - if (i == NUM_PAST && drop < 0) { - drop = 0 - } - if (drop == i) drop-- - val pastX = mPastX - val pastY = mPastY - if (drop >= 0) { - val start = drop + 1 - val count: Int = NUM_PAST - drop - 1 - System.arraycopy(pastX, start, pastX, 0, count) - System.arraycopy(pastY, start, pastY, 0, count) - System.arraycopy(pastTime, start, pastTime, 0, count) - i -= drop + 1 - } - pastX[i] = x - pastY[i] = y - pastTime[i] = time - i++ - if (i < NUM_PAST) { - pastTime[i] = 0 - } - } - - @JvmOverloads - fun computeCurrentVelocity(units: Int, maxVelocity: Float = Float.MAX_VALUE) { - val pastX = mPastX - val pastY = mPastY - val pastTime = mPastTime - val oldestX = pastX[0] - val oldestY = pastY[0] - val oldestTime = pastTime[0] - var accumX = 0f - var accumY = 0f - var N = 0 - while (N < NUM_PAST) { - if (pastTime[N] == 0L) { - break - } - N++ - } - for (i in 1 until N) { - val dur = (pastTime[i] - oldestTime).toInt() - if (dur == 0) continue - var dist = pastX[i] - oldestX - var vel = dist / dur * units // pixels/frame. - accumX = if (accumX == 0f) vel else (accumX + vel) * .5f - dist = pastY[i] - oldestY - vel = dist / dur * units // pixels/frame. - accumY = if (accumY == 0f) vel else (accumY + vel) * .5f - } - xVelocity = if (accumX < 0.0f) Math.max(accumX, -maxVelocity) else Math.min(accumX, maxVelocity) - yVelocity = if (accumY < 0.0f) Math.max(accumY, -maxVelocity) else Math.min(accumY, maxVelocity) - } - - companion object { - const val NUM_PAST = 4 - const val LONGEST_PAST_TIME = 200 - } - } -} diff --git a/app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboardView.kt b/app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboardView.kt index 46bb153..ddba179 100644 --- a/app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboardView.kt +++ b/app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboardView.kt @@ -2,12 +2,10 @@ package com.simplemobiletools.keyboard.views import android.annotation.SuppressLint import android.content.Context -import android.content.res.TypedArray import android.graphics.* import android.graphics.Paint.Align import android.graphics.drawable.Drawable import android.inputmethodservice.Keyboard -import android.inputmethodservice.KeyboardView import android.media.AudioManager import android.os.Handler import android.os.Message @@ -19,8 +17,6 @@ import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityManager import android.widget.PopupWindow import android.widget.TextView -import com.simplemobiletools.commons.extensions.getResolution -import com.simplemobiletools.commons.helpers.mydebug import com.simplemobiletools.keyboard.R import java.util.* diff --git a/app/src/main/res/drawable/btn_keyboard_key.xml b/app/src/main/res/drawable/btn_keyboard_key.xml new file mode 100644 index 0000000..f1ee659 --- /dev/null +++ b/app/src/main/res/drawable/btn_keyboard_key.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/btn_keyboard_key_normal.9.png b/app/src/main/res/drawable/btn_keyboard_key_normal.9.png new file mode 100644 index 0000000000000000000000000000000000000000..42c7c146d67525b6b97af6b4edb42e75ad2851da GIT binary patch literal 715 zcmV;+0yO=JP)!1m1YE1t zDoG`h`VjdLc~U9+7w7X?PN$PxE*DC`jYdP7&88@&{6vn&qYMTE8IQ*@91bPRvV1U9 zk|g<9uh+6%E@iXXNUPQI6PZq@(r&k9x7*3}dZi$vNu+5iy+Y0rqjD;(E2)nit2x)6EWdy^8$w|b_1`84MMJ^>` zPGTiu<1AFf9>FOR4H1{EkQa*gQa>w|i7`k;$}q)A#I_XEJ_0Oy4|%UuE0nNTwibIv z-gDJr3wx2hN)=(uNUh~JnvAgA=K5|gYA?Txic~rda_#Okhj3NH{@F(u_mL>Y7rl~%&swVJ0yZK=n7l-AV!=kF z2vcE3932KPJqoIo5p%L8!xnT`_k${vkS**QTdj<+sG+U(>x?ieQadB;ihME>Tt+Y% z?RMy%!%r4PG!bBOy}%C{(S3txN%eKBy~k=6JtT*(~~OxW-DdGjON&aRs?K?WG{ z+^a~p+m*#)A+y;mZ<%H6GDS--Faqn{-|?Wob^+kq?N$zl1MS@+2*EM^e*dNlzILJP xKw$!t5r6hid{1S;MZ2iFTc%;MOK&R7*CE#o}OSxQ*Y}+;y+3)vse}7NC zUXSYaIysIb8i%kfOSJiXPLGd|^z!mT`F!3?WH1=e)zuZfzP{4?`#TFVl0+txp_`i< z>i7FJ8jZ|E0Qma)TF491LgSPX$O;V@bzGl_APbTNHzMZ>^*1vbF6WckrIbKON{f?6 z(A+S&5>dUug@}5QeTk@@OoP_zwWt^<2Aj=>3WWld%Vnz7YE-FIA}6Bb^vsCfWXxtW znog%eqoq=bs@19}3?TIU{7erI57cNh;?IZ|5o7{HkO5Q;R1XA7Aj4 z2=i{{oVn+`H#6J69g#aCE`_B3L#NZh+1VMo-7c4a z1Azd7!5}0_GAD9+dWx~JG34`kq*5u!vaAjsLJ$OXlu9KO3I&wQWsHoBm=jrATEgh) zDC+e(dc7VOWEcri6cLF;u(-I0m6a89A_O=T3aRoUY0==RA|xvsgi(*{3K6OTBf%Sy zixo<#j1HH4#q3NkWNUtJPBd(`+_XPmxZe(I{fEn5th_Z`e#Q z8A^*yGD@n|Y7i2;n0oaasay^5i589zA}AFTD1V}LXny8;OWe`<%!}mKvkKy{keDZ_fqF8l@{Z*Dw3HCA{-3ao-i}l#nva; z)oKhRt#o!0J+W?k!gMyK_wOt7wk~qqG<)CD(zD+# z9%A)F46onK;K8Kw%pxDFgor>;h~sEaQjTw{^W3h+K+>WptZw3{RL4R-i`5IoWLHAo%+9bm@(3^0VA8s_M|iA=V~_A&kvot0&LcFL+U=-&j(+k| z5k`bC8C~Ej9$|fh)RJn}t>%@qs703H)8P^>5upj~c3W+J`})NhM=ThG=Aqs#JA*nI`(1mVNsN68@M@ry13_ zZ8n=ErG~UfBog^jQ|2FTw;NrrSGwQtkbqmQ7PZ@LQcAgrTrL-yOeQp+&-3rc2&Pr_jo+ga=E0@Xp}3$M5-pClVKvP$l-9H#bQCj z;qaAy-xOicOoHu&N<=W4xITz5cqSo4#9ozWrC$=}vq1j{b^$MhP!tgytc>mDD%%86 zMxId`W3s5>ww6f-B}TZra9I`6$sj4>d%1Id zqGv9o^GKT8+7U2s-N8;sa5U(w7g2d#&Kh2h=OmB221C4a9ZCm436PSS!)Vm_(4Q<-?Tv z2BTeI`>-k3B-?YMv}UYscvoP)t2C&5LUNGC*ox!f5eV|6Oh@AqlFUejzgqi(mGPeM|J`}us%F*4RS zqWmFszZ@6){hm&z6YO?{mFe|*G#-y%s(jS_GGiWqR9$K#Vhea>8)W*g4y|aB$m)Xk bM}PqUY?J9Njve6W00000NkvXXu0mjf+J0A` literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/btn_keyboard_key_pressed_off.9.png b/app/src/main/res/drawable/btn_keyboard_key_pressed_off.9.png new file mode 100644 index 0000000000000000000000000000000000000000..218a2d29ee0ce205540e727471d76629375ba275 GIT binary patch literal 1042 zcmV+t1nv8YP)yItjU(Cg{xsfu#NV$rFO zp!(TmWkd&~cWAX*ba!{BV)*g#F_p_@6|x6|0X3UVs?};#tyZ1N%P#<}losm|3;;+z zpQl_dr@&x@QmK>*gU`>;>OQW)kPByDTUvVMPLGd|bZ~H>qy!LnAXAR-_#Nz^-O0&` z6aUbIQ7CEId<60V6G%w9Orm~i!3%1rOAAik@Aqkce}7>j#{+;FGuSvfI&zYGcmQ>k zgbKf=Qz1tH$Z$BM!^6YxsD=ArVl*0kw{z(up~7!a)B+<&dJhi|D%4^M%|H;Xa9ycX zR67832_#h1(w$uI0ArCKz{bnHMIMU%>LifLV0_8Qz7=^ z;zCIaAr})%URC10>nTd;%#qXwY`W7oI&C|+5+{#wzlOZAA_;(Q z+sZXNchIXOAbvJ^wk0L+4dR;L?nwZ_w(&WbKp#OvRNhCIv}R}9P>sP|Je&#VH-@~ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/btn_keyboard_key_pressed_on.9.png b/app/src/main/res/drawable/btn_keyboard_key_pressed_on.9.png new file mode 100644 index 0000000000000000000000000000000000000000..afe49512e9ec9756f6126b852ea40db9178dea3a GIT binary patch literal 1105 zcmV-X1g`suP)K@|QLmOlZ@5*3iz zNNwUpFWQ@W(c3Yym%$TFq&~q5FzkH zf)QczT6k2tCt)5l=o8M)z%xR;ScKsq%UHc!(P{y+3>zC;qtQUERztmBN2k+4G#W)R znZ(xC77~est7Hh1g*EJ@WhTQ~rBXq)`vrklKal!ZMp$Z~CnwOk%AizV5{{(B^w<@O z$R1oGx)ITTicI{W_%^T>yoB=gHP%P(TygffrDg1FQ8ZgcW=GOTlI_FRcL23gh1S$u z@egA#awIJ)AKA?H5eY~$A4v1s-$t(bh$Cs4t-;>Jc6Jl0Pnr3^?d&>!o{wCUmR)WW z?fk>MNL4ZjZp_|D4z4qDEv-~TG!<+)-K}Md;bRUWE(?G851G` z0bw28dJMlWhWfmgs|nF@)G|7`dBLxI6CGs@FZU8iWwS_1At-VmrBVl11qFY~H?Hn} z|B19%tCrg1zKCtSV`-VgFK_1&qqH3Rh%s{UbRL;2vs>j6UaT^gw9I69BI4ND&DP-2 zB2U_)@Fp!sd1UD=GHtK5=w>`r%`Gk7!ml|sJddblKCkm8ttVeT@FFcoM_nub;L##; zUq0}-*K&OMFzd>6k$1myNz3j!h^IVa7k;%>k(OX2VISLkjK=JCj|UPA8-3685t>My%2DmA)fRPTXV|ElUhd=K zOjf~3RdGauiZppTA=1mGP8bQRtwkb{2+q#VaCmr#P$)EB#FIs+_xt@ZM#B1tD1ETH zU-AoCmeK3=xZSRhWy0YwBuRR(%0%5SF{TMTRhL>3v2u9PYLZ!h%g|;e32j;M{uN*V XADSr z1<%~X^wgl##FWaylc_d9MFpNNjv*Dd-b~-fbwGi~<$ag;3Hu-a?Ppk;%34QiEV)oU zx%tFrrK}kE9lgp{))kUp&1Jw~w?=l&(b*Y1K)V?{UHx3vIVCg! E06}w0n*aa+ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/keyboard_key_feedback.xml b/app/src/main/res/drawable/keyboard_key_feedback.xml new file mode 100644 index 0000000..7ab46a1 --- /dev/null +++ b/app/src/main/res/drawable/keyboard_key_feedback.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/keyboard_key_feedback_background.9.png b/app/src/main/res/drawable/keyboard_key_feedback_background.9.png new file mode 100644 index 0000000000000000000000000000000000000000..6e8584bf71ab300d0e755af689349d34eaf276b0 GIT binary patch literal 3971 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/keyboard_key_feedback_more_background.9.png b/app/src/main/res/drawable/keyboard_key_feedback_more_background.9.png new file mode 100644 index 0000000000000000000000000000000000000000..d983a9521ed4d7940e514b70b45999b67ab4b4e0 GIT binary patch literal 4663 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 + android:layout_width="wrap_content" + android:layout_height="80sp" + android:background="@drawable/keyboard_key_feedback" + android:gravity="center" + android:minWidth="32dip" + android:textColor="?android:attr/textColorPrimaryInverse" + android:textSize="40sp" /> diff --git a/app/src/main/res/layout/keyboard_popup_keyboard.xml b/app/src/main/res/layout/keyboard_popup_keyboard.xml index 1f38ea2..de8cc5c 100644 --- a/app/src/main/res/layout/keyboard_popup_keyboard.xml +++ b/app/src/main/res/layout/keyboard_popup_keyboard.xml @@ -2,6 +2,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="@drawable/keyboard_popup_panel_background" android:orientation="horizontal"> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 6b6da0c..b669511 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -2,6 +2,25 @@ + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +