diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 93bb05765..97bad787c 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -18,7 +18,7 @@ android { abiFilters.clear() abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")) } - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } buildTypes { diff --git a/app/src/main/assets/emoji/SYMBOLS.txt b/app/src/main/assets/emoji/SYMBOLS.txt index f85cc13bb..02effebc7 100644 --- a/app/src/main/assets/emoji/SYMBOLS.txt +++ b/app/src/main/assets/emoji/SYMBOLS.txt @@ -136,32 +136,6 @@ ®️ ™️ 🫟 -🇦 -🇧 -🇨 -🇩 -🇪 -🇫 -🇬 -🇭 -🇮 -🇯 -🇰 -🇱 -🇲 -🇳 -🇴 -🇵 -🇶 -🇷 -🇸 -🇹 -🇺 -🇻 -🇼 -🇽 -🇾 -🇿 #️⃣ *️⃣ 0️⃣ @@ -247,4 +221,4 @@ 💠 🔘 🔳 -🔲 \ No newline at end of file +🔲 diff --git a/app/src/main/java/helium314/keyboard/event/Event.kt b/app/src/main/java/helium314/keyboard/event/Event.kt index 75fda0535..37dc1ea79 100644 --- a/app/src/main/java/helium314/keyboard/event/Event.kt +++ b/app/src/main/java/helium314/keyboard/event/Event.kt @@ -139,6 +139,16 @@ class Event private constructor( null, if (isKeyRepeat) FLAG_REPEAT else FLAG_NONE, null) } + // A helper method to split the code point and the key code. + // todo: Ultimately, they should not be squashed into the same variable, and this method should be removed. + @JvmStatic + fun createSoftwareKeypressEvent(keyCodeOrCodePoint: Int, metaState: Int, keyX: Int, keyY: Int, isKeyRepeat: Boolean) = + if (keyCodeOrCodePoint <= 0) { + createSoftwareKeypressEvent(NOT_A_CODE_POINT, keyCodeOrCodePoint, metaState, keyX, keyY, isKeyRepeat) + } else { + createSoftwareKeypressEvent(keyCodeOrCodePoint, NOT_A_KEY_CODE, metaState, keyX, keyY, isKeyRepeat) + } + fun createHardwareKeypressEvent(codePoint: Int, keyCode: Int, metaState: Int, next: Event?, isKeyRepeat: Boolean): Event { return Event(EVENT_TYPE_INPUT_KEYPRESS, null, codePoint, keyCode, metaState, Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE, @@ -256,10 +266,8 @@ class Event private constructor( source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags or FLAG_COMBINING, source.mNextEvent) } - fun createNotHandledEvent(): Event { - return Event(EVENT_TYPE_NOT_HANDLED, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE, 0, + val notHandledEvent = Event(EVENT_TYPE_NOT_HANDLED, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE, 0, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, null, FLAG_NONE, null) - } } // This method is private - to create a new event, use one of the create* utility methods. diff --git a/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt b/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt index 2742ee193..a8d2d7f32 100644 --- a/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt +++ b/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt @@ -24,7 +24,8 @@ class HardwareKeyboardEventDecoder(val mDeviceId: Int) : HardwareEventDecoder { // KeyEvent#getUnicodeChar() does not exactly returns a unicode char, but rather a value // that includes both the unicode char in the lower 21 bits and flags in the upper bits, // hence the name "codePointAndFlags". {@see KeyEvent#getUnicodeChar()} for more info. - val codePointAndFlags = keyEvent.unicodeChar + val codePointAndFlags = keyEvent.unicodeChar.takeIf { it != 0 } + ?: Event.NOT_A_CODE_POINT // KeyEvent has 0 if no codePoint, but that's actually valid so we convert it to -1 // The keyCode is the abstraction used by the KeyEvent to represent different keys that // do not necessarily map to a unicode character. This represents a physical key, like // the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock. @@ -48,6 +49,21 @@ class HardwareKeyboardEventDecoder(val mDeviceId: Int) : HardwareEventDecoder { } else Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, metaState, null, isKeyRepeat) // If not Enter, then this is just a regular keypress event for a normal character // that can be committed right away, taking into account the current state. - } else Event.createNotHandledEvent() + } else if (isDpadDirection(keyCode)) { + Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, metaState, null, isKeyRepeat) +// } else if (KeyEvent.isModifierKey(keyCode)) { +// todo: we could synchronize meta state across HW and SW keyboard, but that's more work for little benefit (especially with shift & caps lock) + } else { + Event.notHandledEvent + } + } + + companion object { + private fun isDpadDirection(keyCode: Int) = when (keyCode) { + KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.KEYCODE_DPAD_DOWN_LEFT, KeyEvent.KEYCODE_DPAD_DOWN_RIGHT, KeyEvent.KEYCODE_DPAD_UP_RIGHT, + KeyEvent.KEYCODE_DPAD_UP_LEFT -> true + else -> false + } } } diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java index fb177e806..20fb20839 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java @@ -6,6 +6,8 @@ package helium314.keyboard.keyboard; +import android.view.KeyEvent; + import helium314.keyboard.latin.common.Constants; import helium314.keyboard.latin.common.InputPointers; @@ -31,6 +33,12 @@ public interface KeyboardActionListener { */ void onReleaseKey(int primaryCode, boolean withSliding); + /** For handling hardware key presses. Returns whether the event was handled. */ + boolean onKeyDown(int keyCode, KeyEvent keyEvent); + + /** For handling hardware key presses. Returns whether the event was handled. */ + boolean onKeyUp(int keyCode, KeyEvent keyEvent); + /** * Send a key code to the listener. * @@ -117,6 +125,10 @@ public interface KeyboardActionListener { @Override public void onReleaseKey(int primaryCode, boolean withSliding) {} @Override + public boolean onKeyDown(int keyCode, KeyEvent keyEvent) { return false; } + @Override + public boolean onKeyUp(int keyCode, KeyEvent keyEvent) { return false; } + @Override public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat) {} @Override public void onTextInput(String text) {} diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt index fc002a187..f75e2825e 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt @@ -1,16 +1,24 @@ package helium314.keyboard.keyboard import android.text.InputType +import android.util.SparseArray import android.view.KeyEvent import android.view.inputmethod.InputMethodSubtype +import helium314.keyboard.event.Event +import helium314.keyboard.event.HangulEventDecoder +import helium314.keyboard.event.HardwareEventDecoder +import helium314.keyboard.event.HardwareKeyboardEventDecoder import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode +import helium314.keyboard.latin.EmojiAltPhysicalKeyDetector import helium314.keyboard.latin.LatinIME import helium314.keyboard.latin.RichInputMethodManager import helium314.keyboard.latin.common.Constants import helium314.keyboard.latin.common.InputPointers import helium314.keyboard.latin.common.StringUtils +import helium314.keyboard.latin.common.combiningRange import helium314.keyboard.latin.common.loopOverCodePoints import helium314.keyboard.latin.common.loopOverCodePointsBackwards +import helium314.keyboard.latin.define.ProductionFlags import helium314.keyboard.latin.inputlogic.InputLogic import helium314.keyboard.latin.settings.Settings import kotlin.math.abs @@ -19,6 +27,11 @@ import kotlin.math.min class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inputLogic: InputLogic) : KeyboardActionListener { private val connection = inputLogic.mConnection + private val emojiAltPhysicalKeyDetector by lazy { EmojiAltPhysicalKeyDetector(latinIME.resources) } + + // We expect to have only one decoder in almost all cases, hence the default capacity of 1. + // If it turns out we need several, it will get grown seamlessly. + private val hardwareEventDecoders: SparseArray = SparseArray(1) private val keyboardSwitcher = KeyboardSwitcher.getInstance() private val settings = Settings.getInstance() @@ -58,9 +71,62 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp keyboardSwitcher.onReleaseKey(primaryCode, withSliding, latinIME.currentAutoCapsState, latinIME.currentRecapitalizeState) } + override fun onKeyUp(keyCode: Int, keyEvent: KeyEvent): Boolean { + emojiAltPhysicalKeyDetector.onKeyUp(keyEvent) + if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) + return false + + val keyIdentifier = keyEvent.deviceId.toLong() shl 32 + keyEvent.keyCode + return inputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier) + } + + override fun onKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean { + emojiAltPhysicalKeyDetector.onKeyDown(keyEvent) + if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) + return false + + val event: Event + if (settings.current.mLocale.language == "ko") { // todo: this does not appear to be the right place + val subtype = keyboardSwitcher.keyboard?.mId?.mSubtype ?: RichInputMethodManager.getInstance().currentSubtype + event = HangulEventDecoder.decodeHardwareKeyEvent(subtype, keyEvent) { + getHardwareKeyEventDecoder(keyEvent.deviceId).decodeHardwareKey(keyEvent) + } + } else { + event = getHardwareKeyEventDecoder(keyEvent.deviceId).decodeHardwareKey(keyEvent) + } + + if (event.isHandled) { + inputLogic.onCodeInput( + settings.current, event, + keyboardSwitcher.getKeyboardShiftMode(), // TODO: this is not necessarily correct for a hardware keyboard right now + keyboardSwitcher.getCurrentKeyboardScript(), + latinIME.mHandler + ) + return true + } + return false + } + override fun onCodeInput(primaryCode: Int, x: Int, y: Int, isKeyRepeat: Boolean) { + when (primaryCode) { + KeyCode.TOGGLE_AUTOCORRECT -> return Settings.getInstance().toggleAutoCorrect() + KeyCode.TOGGLE_INCOGNITO_MODE -> return Settings.getInstance().toggleAlwaysIncognitoMode() + } val mkv = keyboardSwitcher.mainKeyboardView - latinIME.onCodeInput(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat) + + // checking if the character is a combining accent + val event = if (primaryCode in combiningRange) { // todo: should this be done later, maybe in inputLogic? + Event.createSoftwareDeadEvent(primaryCode, 0, metaState, mkv.getKeyX(x), mkv.getKeyY(y), null) + } else { + // todo: + // setting meta shift should only be done for arrow and similar cursor movement keys + // should only be enabled once it works more reliably (currently depends on app for some reason) +// if (mkv.keyboard?.mId?.isAlphabetShiftedManually == true) +// Event.createSoftwareKeypressEvent(primaryCode, metaState or KeyEvent.META_SHIFT_ON, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat) +// else Event.createSoftwareKeypressEvent(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat) + Event.createSoftwareKeypressEvent(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat) + } + latinIME.onEvent(event) } override fun onTextInput(text: String?) = latinIME.onTextInput(text) @@ -257,4 +323,13 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp } return -min(-actualSteps, text.length) } + + private fun getHardwareKeyEventDecoder(deviceId: Int): HardwareEventDecoder { + hardwareEventDecoders.get(deviceId)?.let { return it } + + // TODO: create the decoder according to the specification + val newDecoder = HardwareKeyboardEventDecoder(deviceId) + hardwareEventDecoders.put(deviceId, newDecoder) + return newDecoder + } } diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardId.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardId.java index ac39680a8..62523f488 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardId.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardId.java @@ -184,6 +184,11 @@ public final class KeyboardId { || mElementId == ELEMENT_ALPHABET_AUTOMATIC_SHIFTED || mElementId == ELEMENT_ALPHABET_MANUAL_SHIFTED; } + public boolean isAlphabetShiftedManually() { + return mElementId == ELEMENT_ALPHABET_SHIFT_LOCKED || mElementId == ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED + || mElementId == ELEMENT_ALPHABET_MANUAL_SHIFTED; + } + public boolean isNumberLayout() { return mElementId == ELEMENT_NUMBER || mElementId == ELEMENT_NUMPAD || mElementId == ELEMENT_PHONE || mElementId == ELEMENT_PHONE_SYMBOLS; diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java index e4849c259..34330ad48 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java @@ -138,7 +138,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { return false; } - public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues, + private void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues, final int currentAutoCapsState, final int currentRecapitalizeState) { final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( mThemeContext, editorInfo); @@ -527,6 +527,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (mCurrentInputView == null) return; mEmojiPalettesView.clearKeyboardCache(); + reloadMainKeyboard(); + } + + public void reloadMainKeyboard() { loadKeyboard(mLatinIME.getCurrentInputEditorInfo(), Settings.getValues(), mLatinIME.getCurrentAutoCapsState(), mLatinIME.getCurrentRecapitalizeState()); } @@ -597,7 +601,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (mKeyboardView == null || !mKeyboardView.isShown()) { return false; } - int activeKeyboardId = mKeyboardView.getKeyboard().mId.mElementId; + final Keyboard keyboard = mKeyboardView.getKeyboard(); + if (keyboard == null) // may happen when using hardware keyboard + return false; + int activeKeyboardId = keyboard.mId.mElementId; for (int keyboardId : keyboardIds) { if (activeKeyboardId == keyboardId) { return true; diff --git a/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java b/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java index 3a3fa3e16..76a01e8e5 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java @@ -360,25 +360,21 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy public void onKeyPressed(@NonNull final Key key, final boolean withPreview) { key.onPressed(); invalidateKey(key); - if (withPreview && !key.noKeyPreview()) { + + final Keyboard keyboard = getKeyboard(); + if (keyboard == null) { + return; + } + mKeyPreviewDrawParams.setVisibleOffset(-keyboard.mVerticalGap); + if (withPreview && !key.noKeyPreview() && mKeyPreviewDrawParams.isPopupEnabled()) { showKeyPreview(key); } } private void showKeyPreview(@NonNull final Key key) { - final Keyboard keyboard = getKeyboard(); - if (keyboard == null) { - return; - } - final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams; - if (!previewParams.isPopupEnabled()) { - previewParams.setVisibleOffset(-keyboard.mVerticalGap); - return; - } - locatePreviewPlacerView(); getLocationInWindow(mOriginCoords); - mKeyPreviewChoreographer.placeAndShowKeyPreview(key, keyboard.mIconsSet, getKeyDrawParams(), + mKeyPreviewChoreographer.placeAndShowKeyPreview(key, getKeyboard().mIconsSet, getKeyDrawParams(), KeyboardSwitcher.getInstance().getWrapperView().getWidth(), mOriginCoords, mDrawingPreviewPlacerView); } diff --git a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java index 7d1735c08..4aa543201 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java @@ -12,15 +12,15 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import androidx.annotation.NonNull; import helium314.keyboard.accessibility.AccessibilityUtils; import helium314.keyboard.accessibility.PopupKeysKeyboardAccessibilityDelegate; -import helium314.keyboard.keyboard.emoji.OnKeyEventListener; +import helium314.keyboard.keyboard.emoji.EmojiViewCallback; import helium314.keyboard.keyboard.internal.KeyDrawParams; import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode; import helium314.keyboard.latin.R; @@ -39,7 +39,7 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane protected final KeyDetector mKeyDetector; private Controller mController = EMPTY_CONTROLLER; protected KeyboardActionListener mListener; - protected OnKeyEventListener mKeyEventListener; + protected EmojiViewCallback mEmojiViewCallback; private int mOriginX; private int mOriginY; private Key mCurrentKey; @@ -122,7 +122,7 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane public void showPopupKeysPanel(final View parentView, final Controller controller, final int pointX, final int pointY, final KeyboardActionListener listener) { mListener = listener; - mKeyEventListener = null; + mEmojiViewCallback = null; showPopupKeysPanelInternal(parentView, controller, pointX, pointY); } @@ -131,9 +131,9 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane */ @Override public void showPopupKeysPanel(final View parentView, final Controller controller, - final int pointX, final int pointY, final OnKeyEventListener listener) { + final int pointX, final int pointY, final EmojiViewCallback emojiViewCallback) { mListener = null; - mKeyEventListener = listener; + mEmojiViewCallback = emojiViewCallback; showPopupKeysPanelInternal(parentView, controller, pointX, pointY); } @@ -157,6 +157,9 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane mOriginX = x + container.getPaddingLeft(); mOriginY = y + container.getPaddingTop(); + var center = panelX + getMeasuredWidth() / 2; + // This is needed for cases where there's also a long text popup above this keyboard + controller.setLayoutGravity(center < pointX? Gravity.RIGHT : center > pointX? Gravity.LEFT : Gravity.CENTER_HORIZONTAL); controller.onShowPopupKeysPanel(this); final PopupKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; if (accessibilityDelegate != null @@ -222,8 +225,8 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane false /* isKeyRepeat */); } } - } else if (mKeyEventListener != null) { - mKeyEventListener.onReleaseKey(key); + } else if (mEmojiViewCallback != null) { + mEmojiViewCallback.onReleaseKey(key); } } @@ -314,28 +317,4 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane } return super.onHoverEvent(event); } - - private View getContainerView() { - return (View)getParent(); - } - - @Override - public void showInParent(final ViewGroup parentView) { - removeFromParent(); - parentView.addView(getContainerView()); - } - - @Override - public void removeFromParent() { - final View containerView = getContainerView(); - final ViewGroup currentParent = (ViewGroup)containerView.getParent(); - if (currentParent != null) { - currentParent.removeView(containerView); - } - } - - @Override - public boolean isShowingInParent() { - return (getContainerView().getParent() != null); - } } diff --git a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysPanel.java b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysPanel.java index 5a7e60e30..b511db9d3 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysPanel.java +++ b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysPanel.java @@ -8,10 +8,17 @@ package helium314.keyboard.keyboard; import android.view.View; import android.view.ViewGroup; -import helium314.keyboard.keyboard.emoji.OnKeyEventListener; +import helium314.keyboard.keyboard.emoji.EmojiViewCallback; public interface PopupKeysPanel { interface Controller { + /** + * Set the layout gravity. + * @param layoutGravity requested by the popup + */ + default void setLayoutGravity(int layoutGravity) { + } + /** * Add the {@link PopupKeysPanel} to the target view. * @param panel the panel to be shown. @@ -59,19 +66,18 @@ public interface PopupKeysPanel { * Initializes the layout and event handling of this {@link PopupKeysPanel} and calls the * controller's onShowPopupKeysPanel to add the panel's container view. * Same as {@link PopupKeysPanel#showPopupKeysPanel(View, Controller, int, int, KeyboardActionListener)}, - * but with a {@link OnKeyEventListener}. + * but with a {@link EmojiViewCallback}. * * @param parentView the parent view of this {@link PopupKeysPanel} * @param controller the controller that can dismiss this {@link PopupKeysPanel} * @param pointX x coordinate of this {@link PopupKeysPanel} * @param pointY y coordinate of this {@link PopupKeysPanel} - * @param listener the listener that will receive keyboard action from this - * {@link PopupKeysPanel}. + * @param emojiViewCallback to receive keyboard actions from this {@link PopupKeysPanel}. */ // TODO: Currently the PopupKeysPanel is inside a container view that is added to the parent. // Consider the simpler approach of placing the PopupKeysPanel itself into the parent view. void showPopupKeysPanel(View parentView, Controller controller, int pointX, - int pointY, OnKeyEventListener listener); + int pointY, EmojiViewCallback emojiViewCallback); /** * Dismisses the popup keys panel and calls the controller's onDismissPopupKeysPanel to remove @@ -127,20 +133,35 @@ public interface PopupKeysPanel { */ int translateY(int y); + default View getContainerView() { + return (View) ((View) this).getParent(); + } + /** * Show this {@link PopupKeysPanel} in the parent view. * * @param parentView the {@link ViewGroup} that hosts this {@link PopupKeysPanel}. */ - void showInParent(ViewGroup parentView); + default void showInParent(ViewGroup parentView) { + removeFromParent(); + parentView.addView(getContainerView()); + } /** * Remove this {@link PopupKeysPanel} from the parent view. */ - void removeFromParent(); + default void removeFromParent() { + final View containerView = getContainerView(); + final ViewGroup currentParent = (ViewGroup)containerView.getParent(); + if (currentParent != null) { + currentParent.removeView(containerView); + } + } /** * Return whether the panel is currently being shown. */ - boolean isShowingInParent(); + default boolean isShowingInParent() { + return getContainerView().getParent() != null; + } } diff --git a/app/src/main/java/helium314/keyboard/keyboard/PopupTextView.java b/app/src/main/java/helium314/keyboard/keyboard/PopupTextView.java new file mode 100644 index 000000000..d20f62559 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/keyboard/PopupTextView.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * modified + * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only + */ + +package helium314.keyboard.keyboard; + +import android.content.Context; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.widget.TextView; +import helium314.keyboard.keyboard.emoji.EmojiViewCallback; +import helium314.keyboard.keyboard.internal.KeyDrawParams; +import helium314.keyboard.latin.R; +import helium314.keyboard.latin.common.ColorType; +import helium314.keyboard.latin.common.CoordinateUtils; +import helium314.keyboard.latin.settings.Settings; + + +/** + * A view that displays popup text. + */ +public class PopupTextView extends TextView implements PopupKeysPanel { + private final int[] mCoordinates = CoordinateUtils.newInstance(); + private final Typeface mTypeface; + private Controller mController = EMPTY_CONTROLLER; + private int mOriginX; + private int mOriginY; + private Key mKey; + private EmojiViewCallback mEmojiViewCallback; + + public PopupTextView(final Context context, final AttributeSet attrs) { + this(context, attrs, R.attr.popupKeysKeyboardViewStyle); + } + + public PopupTextView(final Context context, final AttributeSet attrs, + final int defStyle) { + super(context, attrs, defStyle); + mTypeface = Settings.getInstance().getCustomTypeface(); + } + + public void setKeyDrawParams(Key key, KeyDrawParams drawParams) { + mKey = key; + Settings.getValues().mColors.setBackground(this, ColorType.KEY_PREVIEW_BACKGROUND); + setTextColor(drawParams.mPreviewTextColor); + setTextSize(TypedValue.COMPLEX_UNIT_PX, key.selectHintTextSize(drawParams) << 1); + setTypeface(mTypeface == null ? key.selectTypeface(drawParams) : mTypeface); + } + + @Override + public void showPopupKeysPanel(final View parentView, final Controller controller, + final int pointX, final int pointY, final KeyboardActionListener listener) { + showPopupKeysPanelInternal(parentView, controller, pointX, pointY); + } + + @Override + public void showPopupKeysPanel(final View parentView, final Controller controller, + final int pointX, final int pointY, final EmojiViewCallback emojiViewCallback) { + mEmojiViewCallback = emojiViewCallback; + showPopupKeysPanelInternal(parentView, controller, pointX, pointY); + } + + private void showPopupKeysPanelInternal(final View parentView, final Controller controller, + final int pointX, final int pointY) { + mController = controller; + final View container = getContainerView(); + // The coordinates of panel's left-top corner in parentView's coordinate system. + // We need to consider background drawable paddings. + final int x = pointX - getMeasuredWidth() / 2 - container.getPaddingLeft() - getPaddingLeft(); + final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom() + + getPaddingBottom(); + + parentView.getLocationInWindow(mCoordinates); + // Ensure the horizontal position of the panel does not extend past the parentView edges. + final int maxX = parentView.getMeasuredWidth() - container.getMeasuredWidth(); + final int panelX = Math.max(0, Math.min(maxX, x)) + CoordinateUtils.x(mCoordinates); + final int panelY = y + CoordinateUtils.y(mCoordinates); + container.setX(panelX); + container.setY(panelY); + + mOriginX = x + container.getPaddingLeft(); + mOriginY = y + container.getPaddingTop(); + controller.setLayoutGravity(Gravity.NO_GRAVITY); + controller.onShowPopupKeysPanel(this); + } + + @Override + public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime) { + } + + @Override + public void onMoveEvent(final int x, final int y, final int pointerId, final long eventTime) { + } + + @Override + public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) { + mEmojiViewCallback.onReleaseKey(mKey); + } + + @Override + public void dismissPopupKeysPanel() { + if (!isShowingInParent()) { + return; + } + mController.onDismissPopupKeysPanel(); + } + + @Override + public int translateX(final int x) { + return x - mOriginX; + } + + @Override + public int translateY(final int y) { + return y - mOriginY; + } +} diff --git a/app/src/main/java/helium314/keyboard/keyboard/emoji/DynamicGridKeyboard.java b/app/src/main/java/helium314/keyboard/keyboard/emoji/DynamicGridKeyboard.java index 6e9c4a36c..eea566a72 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/emoji/DynamicGridKeyboard.java +++ b/app/src/main/java/helium314/keyboard/keyboard/emoji/DynamicGridKeyboard.java @@ -112,12 +112,12 @@ final class DynamicGridKeyboard extends Keyboard { } public int getDynamicOccupiedHeight() { - final int row = (mGridKeys.size() - 1) / mColumnsNum + 1; + final int row = (mGridKeys.size() - 1) / getOccupiedColumnCount() + 1; return row * mVerticalStep; } - public int getColumnsCount() { - return mColumnsNum; + public int getOccupiedColumnCount() { + return mColumnsNum - mEmptyColumnIndices.size(); } public void addPendingKey(final Key usedKey) { diff --git a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiCategory.java b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiCategory.java index d2a7213fd..413a6b8f6 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiCategory.java +++ b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiCategory.java @@ -324,7 +324,7 @@ final class EmojiCategory { final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs, mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS), 0, 0, ResourceUtils.getKeyboardWidth(mContext, Settings.getValues())); - return MAX_LINE_COUNT_PER_PAGE * tempKeyboard.getColumnsCount(); + return MAX_LINE_COUNT_PER_PAGE * tempKeyboard.getOccupiedColumnCount(); } private static final Comparator EMOJI_KEY_COMPARATOR = (lhs, rhs) -> { diff --git a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPageKeyboardView.java b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPageKeyboardView.java index 47e0d3b83..cc1e0a359 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPageKeyboardView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPageKeyboardView.java @@ -13,6 +13,9 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.os.Handler; import android.util.AttributeSet; +import android.view.Gravity; +import android.widget.LinearLayout; +import helium314.keyboard.keyboard.PopupTextView; import helium314.keyboard.latin.utils.Log; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -53,14 +56,18 @@ public final class EmojiPageKeyboardView extends KeyboardView implements private static final long KEY_PRESS_DELAY_TIME = 250; // msec private static final long KEY_RELEASE_DELAY_TIME = 30; // msec - private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() { + private static final EmojiViewCallback EMPTY_EMOJI_VIEW_CALLBACK = new EmojiViewCallback() { @Override public void onPressKey(final Key key) {} @Override public void onReleaseKey(final Key key) {} + @Override + public String getDescription(String emoji) { + return null; + } }; - private OnKeyEventListener mListener = EMPTY_LISTENER; + private EmojiViewCallback mEmojiViewCallback = EMPTY_EMOJI_VIEW_CALLBACK; private final KeyDetector mKeyDetector = new KeyDetector(); private KeyboardAccessibilityDelegate mAccessibilityDelegate; @@ -74,6 +81,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements // More keys keyboard private final View mPopupKeysKeyboardContainer; + private final PopupTextView mDescriptionView; + private final PopupKeysKeyboardView mPopupKeysKeyboardView; private final WeakHashMap mPopupKeysKeyboardCache = new WeakHashMap<>(); private final boolean mConfigShowPopupKeysKeyboardAtTouchedPoint; private final ViewGroup mPopupKeysPlacerView; @@ -102,6 +111,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements final LayoutInflater inflater = LayoutInflater.from(getContext()); mPopupKeysKeyboardContainer = inflater.inflate(popupKeysKeyboardLayoutId, null); + mDescriptionView = mPopupKeysKeyboardContainer.findViewById(R.id.description_view); + mPopupKeysKeyboardView = mPopupKeysKeyboardContainer.findViewById(R.id.popup_keys_keyboard_view); } @Override @@ -146,8 +157,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements } } - public void setOnKeyEventListener(final OnKeyEventListener listener) { - mListener = listener; + public void setEmojiViewCallback(final EmojiViewCallback emojiViewCallback) { + mEmojiViewCallback = emojiViewCallback; } /** @@ -169,7 +180,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements } @Nullable - public PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key, final int lastX, final int lastY) { + private PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key) { + mPopupKeysKeyboardView.setVisibility(GONE); final PopupKeySpec[] popupKeys = key.getPopupKeys(); if (popupKeys == null) { return null; @@ -182,21 +194,9 @@ public final class EmojiPageKeyboardView extends KeyboardView implements mPopupKeysKeyboardCache.put(key, popupKeysKeyboard); } - final View container = mPopupKeysKeyboardContainer; - final PopupKeysKeyboardView popupKeysKeyboardView = container.findViewById(R.id.popup_keys_keyboard_view); - popupKeysKeyboardView.setKeyboard(popupKeysKeyboard); - container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - final int[] lastCoords = CoordinateUtils.newCoordinateArray(1, lastX, lastY); - // The popup keys keyboard is usually horizontally aligned with the center of the parent key. - // If showPopupKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more - // keys keyboard is placed at the touch point of the parent key. - final int pointX = mConfigShowPopupKeysKeyboardAtTouchedPoint - ? CoordinateUtils.x(lastCoords) - : key.getX() + key.getWidth() / 2; - final int pointY = key.getY(); - popupKeysKeyboardView.showPopupKeysPanel(this, this, pointX, pointY, mListener); - return popupKeysKeyboardView; + mPopupKeysKeyboardView.setKeyboard(popupKeysKeyboard); + mPopupKeysKeyboardView.setVisibility(VISIBLE); + return mPopupKeysKeyboardView; } private void dismissPopupKeysPanel() { @@ -209,6 +209,17 @@ public final class EmojiPageKeyboardView extends KeyboardView implements return mPopupKeysPanel != null; } + @Override + public void setLayoutGravity(int layoutGravity) { + var layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + layoutParams.gravity = mDescriptionView.getMeasuredWidth() > mPopupKeysKeyboardView.getMeasuredWidth()? + layoutGravity : Gravity.CENTER_HORIZONTAL; + mPopupKeysKeyboardContainer.setLayoutParams(layoutParams); + mDescriptionView.setLayoutParams(layoutParams); + mPopupKeysKeyboardView.setLayoutParams(layoutParams); + } + @Override public void onShowPopupKeysPanel(final PopupKeysPanel panel) { // install placer view only when needed instead of when this @@ -290,9 +301,11 @@ public final class EmojiPageKeyboardView extends KeyboardView implements return; } + var descriptionPanel = showDescription(key); + final PopupKeysPanel popupKeysPanel = showPopupKeysKeyboard(key); + final int x = mLastX; final int y = mLastY; - final PopupKeysPanel popupKeysPanel = showPopupKeysKeyboard(key, x, y); if (popupKeysPanel != null) { final int translatedX = popupKeysPanel.translateX(x); final int translatedY = popupKeysPanel.translateY(y); @@ -301,6 +314,34 @@ public final class EmojiPageKeyboardView extends KeyboardView implements // want any scroll to append during this entire input. disallowParentInterceptTouchEvent(true); } + + if (popupKeysPanel != null || descriptionPanel != null) { + mPopupKeysKeyboardContainer.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + final int[] lastCoords = CoordinateUtils.newCoordinateArray(1, x, y); + // The popup keys keyboard is usually horizontally aligned with the center of the parent key. + // If showPopupKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more + // keys keyboard is placed at the touch point of the parent key. + final int pointX = mConfigShowPopupKeysKeyboardAtTouchedPoint + ? CoordinateUtils.x(lastCoords) + : key.getX() + key.getWidth() / 2; + final int pointY = key.getY() - getKeyboard().mVerticalGap; + (popupKeysPanel != null? popupKeysPanel : descriptionPanel) + .showPopupKeysPanel(this, this, pointX, pointY, mEmojiViewCallback); + } + } + + private PopupKeysPanel showDescription(Key key) { + mDescriptionView.setVisibility(GONE); + var description = mEmojiViewCallback.getDescription(key.getLabel()); + if (description == null) { + return null; + } + + mDescriptionView.setText(description); + mDescriptionView.setKeyDrawParams(key, getKeyDrawParams()); + mDescriptionView.setVisibility(VISIBLE); + return mDescriptionView; } private void registerPress(final Key key) { @@ -318,7 +359,7 @@ public final class EmojiPageKeyboardView extends KeyboardView implements releasedKey.onReleased(); invalidateKey(releasedKey); if (withKeyRegistering) { - mListener.onReleaseKey(releasedKey); + mEmojiViewCallback.onReleaseKey(releasedKey); } } @@ -326,7 +367,7 @@ public final class EmojiPageKeyboardView extends KeyboardView implements mPendingKeyDown = null; pressedKey.onPressed(); invalidateKey(pressedKey); - mListener.onPressKey(pressedKey); + mEmojiViewCallback.onPressKey(pressedKey); } public void releaseCurrentKey(final boolean withKeyRegistering) { diff --git a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPalettesAdapter.java b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPalettesAdapter.java index 7f7b63614..4172d3db9 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPalettesAdapter.java +++ b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPalettesAdapter.java @@ -7,7 +7,6 @@ package helium314.keyboard.keyboard.emoji; import helium314.keyboard.latin.utils.Log; -import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -23,13 +22,13 @@ final class EmojiPalettesAdapter extends RecyclerView.Adapter Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO else -> 0 } - val tlds = getLocaleTlds(locale) + val tlds = mutableListOf(Key.POPUP_KEYS_HAS_LABELS) init { readStream(dataStream, false, true) @@ -84,7 +84,7 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) { READER_MODE_EXTRA_KEYS -> if (!onlyPopupKeys) addExtraKey(line.split(colonSpaceRegex, 2)) READER_MODE_LABELS -> if (!onlyPopupKeys) addLabel(line.split(colonSpaceRegex, 2)) READER_MODE_NUMBER_ROW -> localizedNumberKeys = line.splitOnWhitespace() - READER_MODE_TLD -> SpacedTokens(line).forEach { tlds.add(".$it") } + READER_MODE_TLD -> tlds.addAll(SpacedTokens(line).map { ".$it" }) } } } @@ -156,6 +156,16 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) { } } + fun addLocaleTlds(locale: Locale) { + tlds.add(0, comTld) + val ccLower = locale.country.lowercase() + if (ccLower.isNotEmpty() && locale.language != SubtypeLocaleUtils.NO_LANGUAGE) { + specialCountryTlds[ccLower]?.let { tlds.addAll(SpacedTokens(it)) } ?: tlds.add(".$ccLower") + } + if ((locale.language != "en" && euroLocales.matches(locale.language)) || euroCountries.matches(locale.country)) + tlds.add(".eu") + tlds.addAll(SpacedTokens(otherDefaultTlds)) + } } private fun addFixedColumnOrder(popupKeys: MutableCollection) { @@ -205,6 +215,7 @@ private fun createLocaleKeyTexts(context: Context, params: KeyboardParams, popup POPUP_KEYS_MORE -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_more.txt"), false) POPUP_KEYS_ALL -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_all.txt"), false) } + lkt.addLocaleTlds(params.mId.locale) return lkt } @@ -220,28 +231,6 @@ private fun getStreamForLocale(locale: Locale, context: Context) = } } -private fun getLocaleTlds(locale: Locale): LinkedHashSet { - val tlds = getDefaultTlds(locale) - val ccLower = locale.country.lowercase() - if (ccLower.isEmpty() || locale.language == SubtypeLocaleUtils.NO_LANGUAGE) - return tlds - specialCountryTlds.forEach { - if (ccLower != it.first) return@forEach - tlds.addAll(SpacedTokens(it.second)) - return@getLocaleTlds tlds - } - tlds.add(".$ccLower") - return tlds -} - -private fun getDefaultTlds(locale: Locale): LinkedHashSet { - val tlds = linkedSetOf() - tlds.addAll(SpacedTokens(defaultTlds)) - if ((locale.language != "en" && euroLocales.matches(locale.language)) || euroCountries.matches(locale.country)) - tlds.add(".eu") - return tlds -} - fun clearCache() = localeKeyboardInfosCache.clear() // cache the texts, so they don't need to be read over and over @@ -341,7 +330,7 @@ const val POPUP_KEYS_NORMAL = "normal" private const val LOCALE_TEXTS_FOLDER = "locale_key_texts" // either tld is not simply lowercase ISO 3166-1 code, or there are multiple according to some list -private val specialCountryTlds = listOf( +private val specialCountryTlds = hashMapOf( "bd" to ".bd .com.bd", "bq" to ".bq .an .nl", "bl" to ".bl .gp .fr", @@ -351,4 +340,5 @@ private val specialCountryTlds = listOf( "mf" to ".mf .gp .fr", "tl" to ".tl .tp", ) -private const val defaultTlds = ".com .gov .edu .org .net" +private const val comTld = ".com" +private const val otherDefaultTlds = ".gov .edu .org .net" diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt index 1488faaf5..eccf62ebb 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt @@ -175,6 +175,12 @@ object KeyCode { const val META_LEFT = -10048 const val META_RIGHT = -10049 + + // Intents + const val SEND_INTENT_ONE = -20000 + const val SEND_INTENT_TWO = -20001 + const val SEND_INTENT_THREE = -20002 + /** to make sure a FlorisBoard code works when reading a JSON layout */ fun Int.checkAndConvertCode(): Int = if (this > 0) this else when (this) { // working @@ -190,7 +196,7 @@ object KeyCode { ACTION_NEXT, ACTION_PREVIOUS, NOT_SPECIFIED, CLIPBOARD_COPY_ALL, WORD_LEFT, WORD_RIGHT, PAGE_UP, PAGE_DOWN, META, TAB, ESCAPE, INSERT, SLEEP, MEDIA_PLAY, MEDIA_PAUSE, MEDIA_PLAY_PAUSE, MEDIA_NEXT, MEDIA_PREVIOUS, VOL_UP, VOL_DOWN, MUTE, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, BACK, - TIMESTAMP, CTRL_LEFT, CTRL_RIGHT, ALT_LEFT, ALT_RIGHT, META_LEFT, META_RIGHT + TIMESTAMP, CTRL_LEFT, CTRL_RIGHT, ALT_LEFT, ALT_RIGHT, META_LEFT, META_RIGHT, SEND_INTENT_ONE, SEND_INTENT_TWO, SEND_INTENT_THREE, -> this // conversion @@ -210,66 +216,11 @@ object KeyCode { // todo: there are many more keys, see near https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0 /** - * Convert a keyCode / codePoint to a KeyEvent.KEYCODE_. - * Fallback to KeyEvent.KEYCODE_UNKNOWN. + * Convert an internal keyCode to a KeyEvent.KEYCODE_. + * Positive codes are passed through, unknown negative codes result in KeyEvent.KEYCODE_UNKNOWN. * To be uses for fake hardware key press. - * */ - fun Int.toKeyEventCode(): Int = if (this > 0) - when (this.toChar().uppercaseChar()) { - '/' -> KeyEvent.KEYCODE_SLASH - '\\' -> KeyEvent.KEYCODE_BACKSLASH - ';' -> KeyEvent.KEYCODE_SEMICOLON - ',' -> KeyEvent.KEYCODE_COMMA - '.' -> KeyEvent.KEYCODE_PERIOD - '\'' -> KeyEvent.KEYCODE_APOSTROPHE - '`' -> KeyEvent.KEYCODE_GRAVE - '*' -> KeyEvent.KEYCODE_STAR - ']' -> KeyEvent.KEYCODE_RIGHT_BRACKET - '[' -> KeyEvent.KEYCODE_LEFT_BRACKET - '+' -> KeyEvent.KEYCODE_PLUS - '-' -> KeyEvent.KEYCODE_MINUS - '=' -> KeyEvent.KEYCODE_EQUALS - '\n' -> KeyEvent.KEYCODE_ENTER - '\t' -> KeyEvent.KEYCODE_TAB - '0' -> KeyEvent.KEYCODE_0 - '1' -> KeyEvent.KEYCODE_1 - '2' -> KeyEvent.KEYCODE_2 - '3' -> KeyEvent.KEYCODE_3 - '4' -> KeyEvent.KEYCODE_4 - '5' -> KeyEvent.KEYCODE_5 - '6' -> KeyEvent.KEYCODE_6 - '7' -> KeyEvent.KEYCODE_7 - '8' -> KeyEvent.KEYCODE_8 - '9' -> KeyEvent.KEYCODE_9 - 'A' -> KeyEvent.KEYCODE_A - 'B' -> KeyEvent.KEYCODE_B - 'C' -> KeyEvent.KEYCODE_C - 'D' -> KeyEvent.KEYCODE_D - 'E' -> KeyEvent.KEYCODE_E - 'F' -> KeyEvent.KEYCODE_F - 'G' -> KeyEvent.KEYCODE_G - 'H' -> KeyEvent.KEYCODE_H - 'I' -> KeyEvent.KEYCODE_I - 'J' -> KeyEvent.KEYCODE_J - 'K' -> KeyEvent.KEYCODE_K - 'L' -> KeyEvent.KEYCODE_L - 'M' -> KeyEvent.KEYCODE_M - 'N' -> KeyEvent.KEYCODE_N - 'O' -> KeyEvent.KEYCODE_O - 'P' -> KeyEvent.KEYCODE_P - 'Q' -> KeyEvent.KEYCODE_Q - 'R' -> KeyEvent.KEYCODE_R - 'S' -> KeyEvent.KEYCODE_S - 'T' -> KeyEvent.KEYCODE_T - 'U' -> KeyEvent.KEYCODE_U - 'V' -> KeyEvent.KEYCODE_V - 'W' -> KeyEvent.KEYCODE_W - 'X' -> KeyEvent.KEYCODE_X - 'Y' -> KeyEvent.KEYCODE_Y - 'Z' -> KeyEvent.KEYCODE_Z - else -> KeyEvent.KEYCODE_UNKNOWN - } - else when (this) { + */ + @JvmStatic fun keyCodeToKeyEventCode(keyCode: Int) = when (keyCode) { ARROW_UP -> KeyEvent.KEYCODE_DPAD_UP ARROW_RIGHT -> KeyEvent.KEYCODE_DPAD_RIGHT ARROW_DOWN -> KeyEvent.KEYCODE_DPAD_DOWN @@ -303,6 +254,67 @@ object KeyCode { F10 -> KeyEvent.KEYCODE_F10 F11 -> KeyEvent.KEYCODE_F11 F12 -> KeyEvent.KEYCODE_F12 + else -> if (keyCode < 0) KeyEvent.KEYCODE_UNKNOWN else keyCode + } + + // todo: there are many more keys, see near https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0 + /** + * Convert a codePoint to a KeyEvent.KEYCODE_. + * Fallback to KeyEvent.KEYCODE_UNKNOWN. + * To be uses for fake hardware key press. + */ + @JvmStatic fun codePointToKeyEventCode(codePoint: Int): Int = when (codePoint.toChar().uppercaseChar()) { + '/' -> KeyEvent.KEYCODE_SLASH + '\\' -> KeyEvent.KEYCODE_BACKSLASH + ';' -> KeyEvent.KEYCODE_SEMICOLON + ',' -> KeyEvent.KEYCODE_COMMA + '.' -> KeyEvent.KEYCODE_PERIOD + '\'' -> KeyEvent.KEYCODE_APOSTROPHE + '`' -> KeyEvent.KEYCODE_GRAVE + '*' -> KeyEvent.KEYCODE_STAR + ']' -> KeyEvent.KEYCODE_RIGHT_BRACKET + '[' -> KeyEvent.KEYCODE_LEFT_BRACKET + '+' -> KeyEvent.KEYCODE_PLUS + '-' -> KeyEvent.KEYCODE_MINUS + '=' -> KeyEvent.KEYCODE_EQUALS + '\n' -> KeyEvent.KEYCODE_ENTER + '\t' -> KeyEvent.KEYCODE_TAB + '0' -> KeyEvent.KEYCODE_0 + '1' -> KeyEvent.KEYCODE_1 + '2' -> KeyEvent.KEYCODE_2 + '3' -> KeyEvent.KEYCODE_3 + '4' -> KeyEvent.KEYCODE_4 + '5' -> KeyEvent.KEYCODE_5 + '6' -> KeyEvent.KEYCODE_6 + '7' -> KeyEvent.KEYCODE_7 + '8' -> KeyEvent.KEYCODE_8 + '9' -> KeyEvent.KEYCODE_9 + 'A' -> KeyEvent.KEYCODE_A + 'B' -> KeyEvent.KEYCODE_B + 'C' -> KeyEvent.KEYCODE_C + 'D' -> KeyEvent.KEYCODE_D + 'E' -> KeyEvent.KEYCODE_E + 'F' -> KeyEvent.KEYCODE_F + 'G' -> KeyEvent.KEYCODE_G + 'H' -> KeyEvent.KEYCODE_H + 'I' -> KeyEvent.KEYCODE_I + 'J' -> KeyEvent.KEYCODE_J + 'K' -> KeyEvent.KEYCODE_K + 'L' -> KeyEvent.KEYCODE_L + 'M' -> KeyEvent.KEYCODE_M + 'N' -> KeyEvent.KEYCODE_N + 'O' -> KeyEvent.KEYCODE_O + 'P' -> KeyEvent.KEYCODE_P + 'Q' -> KeyEvent.KEYCODE_Q + 'R' -> KeyEvent.KEYCODE_R + 'S' -> KeyEvent.KEYCODE_S + 'T' -> KeyEvent.KEYCODE_T + 'U' -> KeyEvent.KEYCODE_U + 'V' -> KeyEvent.KEYCODE_V + 'W' -> KeyEvent.KEYCODE_W + 'X' -> KeyEvent.KEYCODE_X + 'Y' -> KeyEvent.KEYCODE_Y + 'Z' -> KeyEvent.KEYCODE_Z else -> KeyEvent.KEYCODE_UNKNOWN } } diff --git a/app/src/main/java/helium314/keyboard/latin/Dictionary.java b/app/src/main/java/helium314/keyboard/latin/Dictionary.java index b9e9f0344..f65893edf 100644 --- a/app/src/main/java/helium314/keyboard/latin/Dictionary.java +++ b/app/src/main/java/helium314/keyboard/latin/Dictionary.java @@ -11,6 +11,7 @@ import java.util.Locale; import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo; import helium314.keyboard.latin.common.ComposedData; +import helium314.keyboard.latin.makedict.WordProperty; import helium314.keyboard.latin.settings.SettingsValuesForSuggestion; /** @@ -177,6 +178,10 @@ public abstract class Dictionary { }; } + public WordProperty getWordProperty(final String word, final boolean isBeginningOfSentence) { + return null; + } + /** * Not a true dictionary. A placeholder used to indicate suggestions that don't come from any * real dictionary. diff --git a/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt b/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt index 325ee3595..c64158b01 100644 --- a/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt +++ b/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt @@ -397,7 +397,10 @@ class DictionaryFacilitatorImpl : DictionaryFacilitator { // This is not too bad, but it delays adding in case a user wants to fill a dictionary using this functionality if (userHistoryDict.getFrequency(word) > 120) { scope.launch { - UserDictionary.Words.addWord(userDict.mContext, word, 250, null, dictionaryGroup.locale) + // adding can throw IllegalArgumentException: Unknown URL content://user_dictionary/words + // https://stackoverflow.com/q/41474623 https://github.com/AnySoftKeyboard/AnySoftKeyboard/issues/490 + // apparently some devices don't have a dictionary? or it's just sporadic hiccups? + runCatching { UserDictionary.Words.addWord(userDict.mContext, word, 250, null, dictionaryGroup.locale) } } } } @@ -731,7 +734,7 @@ private class DictionaryGroup( else { val file = File(context.filesDir.absolutePath + File.separator + "blacklists" + File.separator + locale.toLanguageTag() + ".txt") if (file.isDirectory) file.delete() // this apparently was an issue in some versions - if (file.parentFile?.mkdirs() == true) file + if (file.parentFile?.exists() == true || file.parentFile?.mkdirs() == true) file else null } diff --git a/app/src/main/java/helium314/keyboard/latin/DictionaryFactory.kt b/app/src/main/java/helium314/keyboard/latin/DictionaryFactory.kt index bd86960b3..576bd90a4 100644 --- a/app/src/main/java/helium314/keyboard/latin/DictionaryFactory.kt +++ b/app/src/main/java/helium314/keyboard/latin/DictionaryFactory.kt @@ -64,10 +64,26 @@ object DictionaryFactory { * if the dictionary type already exists in [dicts], the [file] is skipped */ private fun checkAndAddDictionaryToListNewType(file: File, dicts: MutableList, locale: Locale) { - if (!file.isFile) return - val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file) ?: return killDictionary(file) + val dictionary = getDictionary(file, locale) ?: return + if (dicts.any { it.mDictType == dictionary.mDictType }) { + dictionary.close() + return + } + dicts.add(dictionary) + } + + @JvmStatic + fun getDictionary( + file: File, + locale: Locale + ): Dictionary? { + if (!file.isFile) return null + val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file) + if (header == null) { + killDictionary(file) + return null + } val dictType = header.mIdString.split(":").first() - if (dicts.any { it.mDictType == dictType }) return val readOnlyBinaryDictionary = ReadOnlyBinaryDictionary( file.absolutePath, 0, file.length(), false, locale, dictType ) @@ -75,14 +91,13 @@ object DictionaryFactory { if (readOnlyBinaryDictionary.isValidDictionary) { if (locale.language == "ko") { // Use KoreanDictionary for Korean locale - dicts.add(KoreanDictionary(readOnlyBinaryDictionary)) - } else { - dicts.add(readOnlyBinaryDictionary) + return KoreanDictionary(readOnlyBinaryDictionary) } - } else { - readOnlyBinaryDictionary.close() - killDictionary(file) + return readOnlyBinaryDictionary } + readOnlyBinaryDictionary.close() + killDictionary(file) + return null } private fun killDictionary(file: File) { diff --git a/app/src/main/java/helium314/keyboard/latin/EmojiAltPhysicalKeyDetector.java b/app/src/main/java/helium314/keyboard/latin/EmojiAltPhysicalKeyDetector.java index 79bfeeb24..9b06420ee 100644 --- a/app/src/main/java/helium314/keyboard/latin/EmojiAltPhysicalKeyDetector.java +++ b/app/src/main/java/helium314/keyboard/latin/EmojiAltPhysicalKeyDetector.java @@ -23,7 +23,7 @@ import java.util.List; /** * A class for detecting Emoji-Alt physical key. */ -final class EmojiAltPhysicalKeyDetector { +public final class EmojiAltPhysicalKeyDetector { private static final String TAG = "EmojiAltPhysKeyDetector"; private static final boolean DEBUG = false; diff --git a/app/src/main/java/helium314/keyboard/latin/KoreanDictionary.java b/app/src/main/java/helium314/keyboard/latin/KoreanDictionary.java index 9b791148c..2b6722777 100644 --- a/app/src/main/java/helium314/keyboard/latin/KoreanDictionary.java +++ b/app/src/main/java/helium314/keyboard/latin/KoreanDictionary.java @@ -4,6 +4,7 @@ package helium314.keyboard.latin; import helium314.keyboard.event.HangulCombiner; import helium314.keyboard.latin.common.ComposedData; +import helium314.keyboard.latin.makedict.WordProperty; import helium314.keyboard.latin.settings.SettingsValuesForSuggestion; import java.text.Normalizer; @@ -72,6 +73,11 @@ public class KoreanDictionary extends Dictionary { return mDictionary.getMaxFrequencyOfExactMatches(processInput(word)); } + @Override + public WordProperty getWordProperty(String word, boolean isBeginningOfSentence) { + return mDictionary.getWordProperty(processInput(word), isBeginningOfSentence); + } + @Override protected boolean same(char[] word, int length, String typedWord) { word = processInput(new String(word)).toCharArray(); diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index 670e820bd..9e2ed3c72 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -26,7 +26,6 @@ import android.os.Process; import android.text.InputType; import android.util.PrintWriterPrinter; import android.util.Printer; -import android.util.SparseArray; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; @@ -51,9 +50,6 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode; import helium314.keyboard.latin.common.InsetsOutlineProvider; import helium314.keyboard.dictionarypack.DictionaryPackConstants; import helium314.keyboard.event.Event; -import helium314.keyboard.event.HangulEventDecoder; -import helium314.keyboard.event.HardwareEventDecoder; -import helium314.keyboard.event.HardwareKeyboardEventDecoder; import helium314.keyboard.event.InputTransaction; import helium314.keyboard.keyboard.Keyboard; import helium314.keyboard.keyboard.KeyboardId; @@ -68,7 +64,6 @@ import helium314.keyboard.latin.common.InputPointers; import helium314.keyboard.latin.common.LocaleUtils; import helium314.keyboard.latin.common.ViewOutlineProviderUtilsKt; import helium314.keyboard.latin.define.DebugFlags; -import helium314.keyboard.latin.define.ProductionFlags; import helium314.keyboard.latin.inputlogic.InputLogic; import helium314.keyboard.latin.personalization.PersonalizationHelper; import helium314.keyboard.latin.settings.Settings; @@ -134,9 +129,6 @@ public class LatinIME extends InputMethodService implements private final DictionaryFacilitator mDictionaryFacilitator = DictionaryFacilitatorProvider.getDictionaryFacilitator(false); final InputLogic mInputLogic = new InputLogic(this, this, mDictionaryFacilitator); - // We expect to have only one decoder in almost all cases, hence the default capacity of 1. - // If it turns out we need several, it will get grown seamlessly. - final SparseArray mHardwareEventDecoders = new SparseArray<>(1); // TODO: Move these {@link View}s to {@link KeyboardSwitcher}. private View mInputView; @@ -146,7 +138,6 @@ public class LatinIME extends InputMethodService implements private RichInputMethodManager mRichImm; final KeyboardSwitcher mKeyboardSwitcher; private final SubtypeState mSubtypeState = new SubtypeState(); - private EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector; private final StatsUtilsManager mStatsUtilsManager; // Working variable for {@link #startShowingInputView()} and // {@link #onEvaluateInputViewShown()}. @@ -279,9 +270,7 @@ public class LatinIME extends InputMethodService implements msg.arg2 /* remainingTries */, this /* handler */)) { // If we were able to reset the caches, then we can reload the keyboard. // Otherwise, we'll do it when we can. - latinIme.mKeyboardSwitcher.loadKeyboard(latinIme.getCurrentInputEditorInfo(), - settingsValues, latinIme.getCurrentAutoCapsState(), - latinIme.getCurrentRecapitalizeState()); + latinIme.mKeyboardSwitcher.reloadMainKeyboard(); } break; case MSG_WAIT_FOR_DICTIONARY_LOAD: @@ -637,6 +626,7 @@ public class LatinIME extends InputMethodService implements @Override public void onCreate() { + mSettings.startListener(); KeyboardIconsSet.Companion.getInstance().loadIcons(this); mRichImm = RichInputMethodManager.getInstance(); AudioAndHapticFeedbackManager.init(this); @@ -664,7 +654,8 @@ public class LatinIME extends InputMethodService implements final IntentFilter newDictFilter = new IntentFilter(); newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION); - ContextCompat.registerReceiver(this, mDictionaryPackInstallReceiver, newDictFilter, ContextCompat.RECEIVER_NOT_EXPORTED); + // RECEIVER_EXPORTED is necessary because apparently Android 15 (and others?) don't recognize if the sender and receiver are the same app, see https://github.com/Helium314/HeliBoard/pull/1756 + ContextCompat.registerReceiver(this, mDictionaryPackInstallReceiver, newDictFilter, ContextCompat.RECEIVER_EXPORTED); final IntentFilter dictDumpFilter = new IntentFilter(); dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION); @@ -1071,7 +1062,7 @@ public class LatinIME extends InputMethodService implements if (isDifferentTextField) { mainKeyboardView.closing(); suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold); - switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); + switcher.reloadMainKeyboard(); if (needToCallLoadKeyboardLater) { // If we need to call loadKeyboard again later, we need to save its state now. The // later call will be done in #retryResetCaches. @@ -1119,6 +1110,7 @@ public class LatinIME extends InputMethodService implements @Override public void onWindowHidden() { super.onWindowHidden(); + Log.i(TAG, "onWindowHidden"); final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); if (mainKeyboardView != null) { mainKeyboardView.closing(); @@ -1175,8 +1167,12 @@ public class LatinIME extends InputMethodService implements if (isInputViewShown() && mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart, composingSpanEnd, settingsValues)) { - mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), - getCurrentRecapitalizeState()); + // we don't want to update a manually set shift state if selection changed towards one side + // because this may end the manual shift, which is unwanted in case of shift + arrow keys for changing selection + // todo: this is not fully implemented yet, and maybe should be behind a setting + if (mKeyboardSwitcher.getKeyboard() != null && mKeyboardSwitcher.getKeyboard().mId.isAlphabetShiftedManually() + && !((oldSelEnd == newSelEnd && oldSelStart != newSelStart) || (oldSelEnd != newSelEnd && oldSelStart == newSelStart))) + mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), getCurrentRecapitalizeState()); } } @@ -1217,6 +1213,7 @@ public class LatinIME extends InputMethodService implements @Override public void hideWindow() { + Log.i(TAG, "hideWindow"); if (hasSuggestionStripView() && mSettings.getCurrent().mToolbarMode == ToolbarMode.EXPANDABLE) mSuggestionStripView.setToolbarVisibility(false); mKeyboardSwitcher.onHideWindow(); @@ -1229,6 +1226,12 @@ public class LatinIME extends InputMethodService implements super.hideWindow(); } + @Override + public void requestHideSelf(int flags) { + super.requestHideSelf(flags); + Log.i(TAG, "requestHideSelf: " + flags); + } + @Override public void onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions) { if (DebugFlags.DEBUG_ENABLED) { @@ -1280,8 +1283,8 @@ public class LatinIME extends InputMethodService implements if (isImeSuppressedByHardwareKeyboard() && !visibleKeyboardView.isShown()) { // If there is a hardware keyboard and a visible software keyboard view has been hidden, // no visual element will be shown on the screen. - outInsets.contentTopInsets = inputHeight; - outInsets.visibleTopInsets = inputHeight; + // for some reason setting contentTopInsets and visibleTopInsets broke somewhere along the + // way from OpenBoard to HeliBoard (GH-702, GH-1455), but not setting anything seems to work mInsetsUpdater.setInsets(outInsets); return; } @@ -1539,25 +1542,7 @@ public class LatinIME extends InputMethodService implements // Implementation of {@link SuggestionStripView.Listener}. @Override public void onCodeInput(final int codePoint, final int x, final int y, final boolean isKeyRepeat) { - onCodeInput(codePoint, 0, x, y, isKeyRepeat); - } - - public void onCodeInput(final int codePoint, final int metaState, final int x, final int y, final boolean isKeyRepeat) { - if (codePoint < 0) { - switch (codePoint) { - case KeyCode.TOGGLE_AUTOCORRECT -> {mSettings.toggleAutoCorrect(); return; } - case KeyCode.TOGGLE_INCOGNITO_MODE -> {mSettings.toggleAlwaysIncognitoMode(); return; } - } - } - final Event event; - // checking if the character is a combining accent - if (0x300 <= codePoint && codePoint <= 0x35b) { - event = Event.createSoftwareDeadEvent(codePoint, 0, metaState, x, y, null); - } else { - event = createSoftwareKeypressEvent(codePoint, metaState, x, y, isKeyRepeat); - } - - onEvent(event); + mKeyboardActionListener.onCodeInput(codePoint, x, y, isKeyRepeat); } // This method is public for testability of LatinIME, but also in the future it should @@ -1574,24 +1559,6 @@ public class LatinIME extends InputMethodService implements mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); } - // A helper method to split the code point and the key code. Ultimately, they should not be - // squashed into the same variable, and this method should be removed. - // public for testing, as we don't want to copy the same logic into test code - @NonNull - public static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int metaState, - final int keyX, final int keyY, final boolean isKeyRepeat) { - final int keyCode; - final int codePoint; - if (keyCodeOrCodePoint <= 0) { - keyCode = keyCodeOrCodePoint; - codePoint = Event.NOT_A_CODE_POINT; - } else { - keyCode = Event.NOT_A_KEY_CODE; - codePoint = keyCodeOrCodePoint; - } - return Event.createSoftwareKeypressEvent(codePoint, keyCode, metaState, keyX, keyY, isKeyRepeat); - } - public void onTextInput(final String rawText) { // TODO: have the keyboard pass the correct key code when we need it. final Event event = Event.createSoftwareTextEvent(rawText, KeyCode.MULTIPLE_CODE_POINTS); @@ -1763,8 +1730,7 @@ public class LatinIME extends InputMethodService implements loadSettings(); if (mKeyboardSwitcher.getMainKeyboardView() != null) { // Reload keyboard because the current language has been changed. - mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent(), - getCurrentAutoCapsState(), getCurrentRecapitalizeState()); + mKeyboardSwitcher.reloadMainKeyboard(); } } @@ -1832,63 +1798,18 @@ public class LatinIME extends InputMethodService implements feedbackManager.performAudioFeedback(code); } - private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) { - final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId); - if (null != decoder) return decoder; - // TODO: create the decoder according to the specification - final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId); - mHardwareEventDecoders.put(deviceId, newDecoder); - return newDecoder; - } - // Hooks for hardware keyboard @Override public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { - if (mEmojiAltPhysicalKeyDetector == null) { - mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector( - getApplicationContext().getResources()); - } - mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent); - if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { - return super.onKeyDown(keyCode, keyEvent); - } - final Event event; - if (mRichImm.getCurrentSubtypeLocale().getLanguage().equals("ko")) { - final RichInputMethodSubtype subtype = mKeyboardSwitcher.getKeyboard() == null - ? mRichImm.getCurrentSubtype() - : mKeyboardSwitcher.getKeyboard().mId.mSubtype; - event = HangulEventDecoder.decodeHardwareKeyEvent(subtype, keyEvent, - () -> getHardwareKeyEventDecoder(keyEvent.getDeviceId()).decodeHardwareKey(keyEvent)); - } else { - event = getHardwareKeyEventDecoder(keyEvent.getDeviceId()).decodeHardwareKey(keyEvent); - } - // If the event is not handled by LatinIME, we just pass it to the parent implementation. - // If it's handled, we return true because we did handle it. - if (event.isHandled()) { - mInputLogic.onCodeInput(mSettings.getCurrent(), event, - mKeyboardSwitcher.getKeyboardShiftMode(), - // TODO: this is not necessarily correct for a hardware keyboard right now - mKeyboardSwitcher.getCurrentKeyboardScript(), - mHandler); + if (mKeyboardActionListener.onKeyDown(keyCode, keyEvent)) return true; - } return super.onKeyDown(keyCode, keyEvent); } @Override public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) { - if (mEmojiAltPhysicalKeyDetector == null) { - mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector( - getApplicationContext().getResources()); - } - mEmojiAltPhysicalKeyDetector.onKeyUp(keyEvent); - if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { - return super.onKeyUp(keyCode, keyEvent); - } - final long keyIdentifier = (long) keyEvent.getDeviceId() << 32 + keyEvent.getKeyCode(); - if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) { + if (mKeyboardActionListener.onKeyUp(keyCode, keyEvent)) return true; - } return super.onKeyUp(keyCode, keyEvent); } diff --git a/app/src/main/java/helium314/keyboard/latin/ReadOnlyBinaryDictionary.java b/app/src/main/java/helium314/keyboard/latin/ReadOnlyBinaryDictionary.java index b4cfe5f3a..f757e87eb 100644 --- a/app/src/main/java/helium314/keyboard/latin/ReadOnlyBinaryDictionary.java +++ b/app/src/main/java/helium314/keyboard/latin/ReadOnlyBinaryDictionary.java @@ -10,6 +10,7 @@ import com.android.inputmethod.latin.BinaryDictionary; import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo; import helium314.keyboard.latin.common.ComposedData; +import helium314.keyboard.latin.makedict.WordProperty; import helium314.keyboard.latin.settings.SettingsValuesForSuggestion; import java.util.ArrayList; @@ -107,6 +108,18 @@ public final class ReadOnlyBinaryDictionary extends Dictionary { return NOT_A_PROBABILITY; } + @Override + public WordProperty getWordProperty(String word, boolean isBeginningOfSentence) { + if (mLock.readLock().tryLock()) { + try { + return mBinaryDictionary.getWordProperty(word, isBeginningOfSentence); + } finally { + mLock.readLock().unlock(); + } + } + return null; + } + @Override public void close() { mLock.writeLock().lock(); diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java index 95beb8683..f7e8bcc03 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java @@ -439,7 +439,7 @@ public final class RichInputConnection implements PrivateCommandPerformer { // test for this explicitly) if (INVALID_CURSOR_POSITION != mExpectedSelStart && (cachedLength >= n || cachedLength >= mExpectedSelStart)) { - final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText); + final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText.toString()); // We call #toString() here to create a temporary object. // In some situations, this method is called on a worker thread, and it's possible // the main thread touches the contents of mComposingText while this worker thread @@ -716,8 +716,13 @@ public final class RichInputConnection implements PrivateCommandPerformer { if (start < 0 || end < 0) { return false; } - mExpectedSelStart = start; - mExpectedSelEnd = end; + if (start > end) { + mExpectedSelStart = end; + mExpectedSelEnd = start; + } else { + mExpectedSelStart = start; + mExpectedSelEnd = end; + } if (isConnected()) { final boolean isIcValid = mIC.setSelection(start, end); if (!isIcValid) { diff --git a/app/src/main/java/helium314/keyboard/latin/SingleDictionaryFacilitator.kt b/app/src/main/java/helium314/keyboard/latin/SingleDictionaryFacilitator.kt index e8941c459..4e2d62486 100644 --- a/app/src/main/java/helium314/keyboard/latin/SingleDictionaryFacilitator.kt +++ b/app/src/main/java/helium314/keyboard/latin/SingleDictionaryFacilitator.kt @@ -7,6 +7,7 @@ import helium314.keyboard.keyboard.Keyboard import helium314.keyboard.keyboard.KeyboardSwitcher import helium314.keyboard.latin.DictionaryFacilitator.DictionaryInitializationListener import helium314.keyboard.latin.common.ComposedData +import helium314.keyboard.latin.makedict.WordProperty import helium314.keyboard.latin.settings.SettingsValuesForSuggestion import helium314.keyboard.latin.utils.SuggestionResults import java.util.Locale @@ -47,6 +48,8 @@ class SingleDictionaryFacilitator(private val dict: Dictionary) : DictionaryFaci return suggestionResults } + fun getWordProperty(word: String): WordProperty? = dict.getWordProperty(word, false) + // ------------ dummy functionality ---------------- override fun setValidSpellingWordReadCache(cache: LruCache) {} diff --git a/app/src/main/java/helium314/keyboard/latin/common/Constants.kt b/app/src/main/java/helium314/keyboard/latin/common/Constants.kt index 17b36d1da..43b9e66dd 100644 --- a/app/src/main/java/helium314/keyboard/latin/common/Constants.kt +++ b/app/src/main/java/helium314/keyboard/latin/common/Constants.kt @@ -12,3 +12,5 @@ object Links { const val CUSTOM_LAYOUTS = "$GITHUB/discussions/categories/custom-layout" const val CUSTOM_COLORS = "$GITHUB/discussions/categories/custom-colors" } + +val combiningRange = 0x300..0x35b diff --git a/app/src/main/java/helium314/keyboard/latin/define/ProductionFlags.kt b/app/src/main/java/helium314/keyboard/latin/define/ProductionFlags.kt index d476a0426..9aeb7cc3d 100644 --- a/app/src/main/java/helium314/keyboard/latin/define/ProductionFlags.kt +++ b/app/src/main/java/helium314/keyboard/latin/define/ProductionFlags.kt @@ -7,15 +7,11 @@ package helium314.keyboard.latin.define object ProductionFlags { - const val IS_HARDWARE_KEYBOARD_SUPPORTED = false - // todo: make it work - // was set to true in hangul branch (and there is the hangul hardware event decoder in latinIme) - // but disabled again because this breaks ctrl+c / ctrl+v, and most likely other things - // so it looks like the HardwareKeyboardEventDecoder needs some work before it's ready + const val IS_HARDWARE_KEYBOARD_SUPPORTED = true /** * Include all suggestions from all dictionaries in * [helium314.keyboard.latin.SuggestedWords.mRawSuggestions]. */ const val INCLUDE_RAW_SUGGESTIONS = false -} \ No newline at end of file +} diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index b1f8b5bca..28572fa2f 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -49,6 +49,7 @@ import helium314.keyboard.latin.settings.SpacingAndPunctuations; import helium314.keyboard.latin.suggestions.SuggestionStripViewAccessor; import helium314.keyboard.latin.utils.AsyncResultHolder; import helium314.keyboard.latin.utils.InputTypeUtils; +import helium314.keyboard.latin.utils.IntentUtils; import helium314.keyboard.latin.utils.Log; import helium314.keyboard.latin.utils.RecapitalizeStatus; import helium314.keyboard.latin.utils.ScriptUtils; @@ -89,6 +90,7 @@ public final class InputLogic { private int mDeleteCount; private long mLastKeyTime; + // todo: this is not used, so either remove it or do something with it public final TreeSet mCurrentlyPressedHardwareKeys = new TreeSet<>(); // Keeps track of most recently inserted text (multi-character key) for reverting @@ -399,7 +401,11 @@ public final class InputLogic { // Stop the last recapitalization, if started. mRecapitalizeStatus.stop(); mWordBeingCorrectedByCursor = null; - return true; + + // we do not return true if + final boolean oneSidedSelectionMove = hasOrHadSelection + && ((oldSelEnd == newSelEnd && oldSelStart != newSelStart) || (oldSelEnd != newSelEnd && oldSelStart == newSelStart)); + return !oneSidedSelectionMove; } public boolean moveCursorByAndReturnIfInsideComposingWord(int distance) { @@ -643,7 +649,8 @@ public final class InputLogic { */ private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction, final String currentKeyboardScript, final LatinIME.UIHandler handler) { - switch (event.getMKeyCode()) { + final int keyCode = event.getMKeyCode(); + switch (keyCode) { case KeyCode.DELETE: handleBackspaceEvent(event, inputTransaction, currentKeyboardScript); // Backspace is a functional key, but it affects the contents of the editor. @@ -686,7 +693,7 @@ public final class InputLogic { case KeyCode.SHIFT_ENTER: // todo: try using sendDownUpKeyEventWithMetaState() and remove the key code maybe final Event tmpEvent = Event.createSoftwareKeypressEvent(Constants.CODE_ENTER, - event.getMKeyCode(), 0, event.getMX(), event.getMY(), event.isKeyRepeat()); + keyCode, 0, event.getMX(), event.getMY(), event.isKeyRepeat()); handleNonSpecialCharacterEvent(tmpEvent, inputTransaction, handler); // Shift + Enter is treated as a functional key but it results in adding a new // line, so that does affect the contents of the editor. @@ -717,37 +724,43 @@ public final class InputLogic { if (mConnection.hasSelection()) { mConnection.copyText(true); // fake delete keypress to remove the text - final Event backspaceEvent = LatinIME.createSoftwareKeypressEvent(KeyCode.DELETE, 0, + final Event backspaceEvent = Event.createSoftwareKeypressEvent(KeyCode.DELETE, 0, event.getMX(), event.getMY(), event.isKeyRepeat()); handleBackspaceEvent(backspaceEvent, inputTransaction, currentKeyboardScript); inputTransaction.setDidAffectContents(); } break; case KeyCode.WORD_LEFT: - sendDownUpKeyEventWithMetaState(ScriptUtils.isScriptRtl(currentKeyboardScript)? - KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.META_CTRL_ON); + sendDownUpKeyEventWithMetaState( + ScriptUtils.isScriptRtl(currentKeyboardScript) ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT, + KeyEvent.META_CTRL_ON | event.getMMetaState()); break; case KeyCode.WORD_RIGHT: - sendDownUpKeyEventWithMetaState(ScriptUtils.isScriptRtl(currentKeyboardScript)? - KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.META_CTRL_ON); + sendDownUpKeyEventWithMetaState( + ScriptUtils.isScriptRtl(currentKeyboardScript) ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.META_CTRL_ON | event.getMMetaState()); break; case KeyCode.MOVE_START_OF_PAGE: - final int selectionEnd = mConnection.getExpectedSelectionEnd(); - sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_HOME, KeyEvent.META_CTRL_ON); - if (mConnection.getExpectedSelectionStart() > 0 && mConnection.getExpectedSelectionEnd() == selectionEnd) { - // unchanged, and we're not at the top -> try a different method (necessary for compose fields) - mConnection.setSelection(0, 0); + final int selectionEnd1 = mConnection.getExpectedSelectionEnd(); + final int selectionStart1 = mConnection.getExpectedSelectionStart(); + sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_HOME, KeyEvent.META_CTRL_ON | event.getMMetaState()); + if (mConnection.getExpectedSelectionStart() == selectionStart1 && mConnection.getExpectedSelectionEnd() == selectionEnd1) { + // unchanged -> try a different method (necessary for compose fields) + final int newEnd = (event.getMMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? selectionEnd1 : 0; + mConnection.setSelection(0, newEnd); } break; case KeyCode.MOVE_END_OF_PAGE: - final int selectionStart = mConnection.getExpectedSelectionEnd(); - sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_END, KeyEvent.META_CTRL_ON); - if (mConnection.getExpectedSelectionStart() == selectionStart) { + final int selectionStart2 = mConnection.getExpectedSelectionStart(); + final int selectionEnd2 = mConnection.getExpectedSelectionEnd(); + sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_END, KeyEvent.META_CTRL_ON | event.getMMetaState()); + if (mConnection.getExpectedSelectionStart() == selectionStart2 && mConnection.getExpectedSelectionEnd() == selectionEnd2) { // unchanged, try fallback e.g. for compose fields that don't care about ctrl + end // we just move to a very large index, and hope the field is prepared to deal with this // getting the actual length of the text for setting the correct position can be tricky for some apps... try { - mConnection.setSelection(Integer.MAX_VALUE, Integer.MAX_VALUE); + final int newStart = (event.getMMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? selectionStart2 : Integer.MAX_VALUE; + mConnection.setSelection(newStart, Integer.MAX_VALUE); } catch (Exception e) { // better catch potential errors and just do nothing in this case Log.i(TAG, "error when trying to move cursor to last position: " + e); @@ -766,8 +779,10 @@ public final class InputLogic { case KeyCode.TIMESTAMP: mLatinIME.onTextInput(TimestampKt.getTimestamp(mLatinIME)); break; + case KeyCode.SEND_INTENT_ONE, KeyCode.SEND_INTENT_TWO, KeyCode.SEND_INTENT_THREE: + IntentUtils.handleSendIntentKey(mLatinIME, event.getMKeyCode()); case KeyCode.IME_HIDE_UI: - mLatinIME.hideWindow(); + mLatinIME.requestHideSelf(0); break; case KeyCode.VOICE_INPUT: // switching to shortcut IME, shift state, keyboard,... is handled by LatinIME, @@ -777,23 +792,20 @@ public final class InputLogic { case KeyCode.CAPS_LOCK, KeyCode.EMOJI, KeyCode.TOGGLE_ONE_HANDED_MODE, KeyCode.SWITCH_ONE_HANDED_MODE: break; default: - if (KeyCode.INSTANCE.isModifier(event.getMKeyCode())) - return; // continuation of previous switch case, but modifiers are in a separate place - if (event.getMMetaState() != 0) { - // need to convert codepoint to KeyEvent.KEYCODE_ - final int codeToConvert = event.getMKeyCode() < 0 ? event.getMKeyCode() : event.getMCodePoint(); - int keyEventCode = KeyCode.INSTANCE.toKeyEventCode(codeToConvert); - if (keyEventCode != KeyEvent.KEYCODE_UNKNOWN) - sendDownUpKeyEventWithMetaState(keyEventCode, event.getMMetaState()); - return; // never crash if user inputs sth we don't have a KeyEvent.KEYCODE for - } else if (event.getMKeyCode() < 0) { - int keyEventCode = KeyCode.INSTANCE.toKeyEventCode(event.getMKeyCode()); - if (keyEventCode != KeyEvent.KEYCODE_UNKNOWN) { - sendDownUpKeyEvent(keyEventCode); - return; - } + if (KeyCode.INSTANCE.isModifier(keyCode)) + return; // continuation of previous switch case above, but modifiers are held in a separate place + final int keyEventCode = keyCode > 0 + ? keyCode + : event.getMCodePoint() >= 0 ? KeyCode.codePointToKeyEventCode(event.getMCodePoint()) + : KeyCode.keyCodeToKeyEventCode(keyCode); + if (keyEventCode != KeyEvent.KEYCODE_UNKNOWN) { + sendDownUpKeyEventWithMetaState(keyEventCode, event.getMMetaState()); + return; } - throw new RuntimeException("Unknown key code : " + event.getMKeyCode()); + // unknown event + Log.e(TAG, "unknown event, key code: "+keyCode+", meta: "+event.getMMetaState()); + if (DebugFlags.DEBUG_ENABLED) + throw new RuntimeException("Unknown event"); } } diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt index 98e93a64f..a73760205 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt @@ -58,6 +58,7 @@ object Defaults { const val PREF_VIBRATE_ON = false const val PREF_VIBRATE_IN_DND_MODE = false const val PREF_SOUND_ON = false + const val PREF_SHOW_EMOJI_DESCRIPTIONS = true @JvmField var PREF_POPUP_ON = true const val PREF_AUTO_CORRECTION = true diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java index ea6e8f59f..cc72c893c 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -67,6 +67,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_VIBRATE_ON = "vibrate_on"; public static final String PREF_VIBRATE_IN_DND_MODE = "vibrate_in_dnd_mode"; public static final String PREF_SOUND_ON = "sound_on"; + public static final String PREF_SHOW_EMOJI_DESCRIPTIONS = "show_emoji_descriptions"; public static final String PREF_POPUP_ON = "popup_on"; public static final String PREF_AUTO_CORRECTION = "auto_correction"; public static final String PREF_MORE_AUTO_CORRECTION = "more_auto_correction"; diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java index 689a63700..f750f1072 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java @@ -54,6 +54,7 @@ public class SettingsValues { public final boolean mVibrateOn; public final boolean mVibrateInDndMode; public final boolean mSoundOn; + public final boolean mShowEmojiDescriptions; public final boolean mKeyPreviewPopupOn; public final boolean mShowsVoiceInputKey; public final boolean mLanguageSwitchKeyToOtherImes; @@ -169,6 +170,7 @@ public class SettingsValues { mVibrateOn = Settings.readVibrationEnabled(prefs); mVibrateInDndMode = prefs.getBoolean(Settings.PREF_VIBRATE_IN_DND_MODE, Defaults.PREF_VIBRATE_IN_DND_MODE); mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, Defaults.PREF_SOUND_ON); + mShowEmojiDescriptions = prefs.getBoolean(Settings.PREF_SHOW_EMOJI_DESCRIPTIONS, Defaults.PREF_SHOW_EMOJI_DESCRIPTIONS); mKeyPreviewPopupOn = prefs.getBoolean(Settings.PREF_POPUP_ON, Defaults.PREF_POPUP_ON); mSlidingKeyInputPreviewEnabled = prefs.getBoolean( DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, Defaults.PREF_SLIDING_KEY_INPUT_PREVIEW); diff --git a/app/src/main/java/helium314/keyboard/latin/utils/DictionaryInfoUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/DictionaryInfoUtils.kt index 8715d2d4c..05417fdb3 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/DictionaryInfoUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/DictionaryInfoUtils.kt @@ -101,6 +101,10 @@ object DictionaryInfoUtils { return absoluteDirectoryName } + @JvmStatic + fun getCachedDictForLocaleAndType(locale: Locale, type: String, context: Context): File? = + getCachedDictsForLocale(locale, context).firstOrNull { it.name.substringBefore("_") == type } + fun getCachedDictsForLocale(locale: Locale, context: Context) = getCacheDirectoryForLocale(locale, context)?.let { File(it).listFiles() }.orEmpty() diff --git a/app/src/main/java/helium314/keyboard/latin/utils/IntentUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/IntentUtils.kt new file mode 100644 index 000000000..da1b06bd8 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/latin/utils/IntentUtils.kt @@ -0,0 +1,26 @@ +package helium314.keyboard.latin.utils + +import android.content.Context +import android.content.Intent +import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode +import helium314.keyboard.latin.inputlogic.InputLogic +import helium314.keyboard.latin.utils.Log.i + +object IntentUtils { + val TAG: String = InputLogic::class.java.simpleName + private val ACTION_SEND_INTENT = "helium314.keyboard.latin.ACTION_SEND_INTENT" + private val EXTRA_NUMBER = "EXTRA_NUMBER" + + @JvmStatic + fun handleSendIntentKey(context: Context, mKeyCode: Int) { + val intentNumber = (KeyCode.SEND_INTENT_ONE + 1) - mKeyCode; + + val intent: Intent = Intent(ACTION_SEND_INTENT).apply { + putExtra(EXTRA_NUMBER, intentNumber) + } + + context.sendBroadcast(intent) + i(TAG, "Sent broadcast for intent number: $intentNumber") + } +} + diff --git a/app/src/main/java/helium314/keyboard/settings/SearchScreen.kt b/app/src/main/java/helium314/keyboard/settings/SearchScreen.kt index 66ba49f64..16b2e2d92 100644 --- a/app/src/main/java/helium314/keyboard/settings/SearchScreen.kt +++ b/app/src/main/java/helium314/keyboard/settings/SearchScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -45,6 +46,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import helium314.keyboard.latin.R @@ -241,7 +243,8 @@ fun ExpandableSearchField( }) { CloseIcon(android.R.string.cancel) } }, singleLine = true, colors = colors, - textStyle = contentTextDirectionStyle + textStyle = contentTextDirectionStyle, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search) ) } } diff --git a/app/src/main/java/helium314/keyboard/settings/dialogs/NewDictionaryDialog.kt b/app/src/main/java/helium314/keyboard/settings/dialogs/NewDictionaryDialog.kt index 1405c71ae..a82a07217 100644 --- a/app/src/main/java/helium314/keyboard/settings/dialogs/NewDictionaryDialog.kt +++ b/app/src/main/java/helium314/keyboard/settings/dialogs/NewDictionaryDialog.kt @@ -47,12 +47,12 @@ fun NewDictionaryDialog( } else if (header != null) { val ctx = LocalContext.current val dictLocale = header.mLocaleString.constructLocale() - var locale by remember { mutableStateOf(mainLocale ?: dictLocale) } val enabledLanguages = SubtypeSettings.getEnabledSubtypes().map { it.locale().language } val comparer = compareBy({ it != mainLocale }, { it != dictLocale }, { it.language !in enabledLanguages }, { it.script() != dictLocale.script() }) val locales = SubtypeSettings.getAvailableSubtypeLocales() .filter { it.script() == dictLocale.script() || it.script() == mainLocale?.script() } .sortedWith(comparer) + var locale by remember { mutableStateOf(mainLocale ?: dictLocale.takeIf { it in locales } ?: locales.first()) } val cacheDir = DictionaryInfoUtils.getCacheDirectoryForLocale(locale, ctx) val dictFile = File(cacheDir, header.mIdString.substringBefore(":") + "_" + DictionaryInfoUtils.USER_DICTIONARY_SUFFIX) val type = header.mIdString.substringBefore(":") diff --git a/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt index 272757201..c41696059 100644 --- a/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt +++ b/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt @@ -57,6 +57,7 @@ fun PreferencesScreen( Settings.PREF_SOUND_ON, if (prefs.getBoolean(Settings.PREF_SOUND_ON, Defaults.PREF_SOUND_ON)) Settings.PREF_KEYPRESS_SOUND_VOLUME else null, + Settings.PREF_SHOW_EMOJI_DESCRIPTIONS, R.string.settings_category_additional_keys, Settings.PREF_SHOW_NUMBER_ROW, if (SubtypeSettings.getEnabledSubtypes(true).any { it.locale().language in localesWithLocalizedNumberRow }) @@ -111,6 +112,14 @@ fun createPreferencesSettings(context: Context) = listOf( Setting(context, Settings.PREF_SOUND_ON, R.string.sound_on_keypress) { SwitchPreference(it, Defaults.PREF_SOUND_ON) }, + Setting( + context, Settings.PREF_SHOW_EMOJI_DESCRIPTIONS, R.string.show_emoji_descriptions, + R.string.show_emoji_descriptions_summary + ) { + SwitchPreference(it, Defaults.PREF_SHOW_EMOJI_DESCRIPTIONS) { + KeyboardSwitcher.getInstance().reloadKeyboard() + } + }, Setting(context, Settings.PREF_ENABLE_CLIPBOARD_HISTORY, R.string.enable_clipboard_history, R.string.enable_clipboard_history_summary) { diff --git a/app/src/main/res/layout/popup_keys_keyboard.xml b/app/src/main/res/layout/popup_keys_keyboard.xml index a8bea57c4..e9e249482 100644 --- a/app/src/main/res/layout/popup_keys_keyboard.xml +++ b/app/src/main/res/layout/popup_keys_keyboard.xml @@ -10,6 +10,15 @@ android:layout_height="wrap_content" android:orientation="vertical" > + Vibrate in do not disturb mode Sound on keypress + + Show emoji description on long press + + Requires an emoji dictionary Popup on keypress diff --git a/app/src/main/res/xml/method.xml b/app/src/main/res/xml/method.xml index 0a8e99fc9..69994344c 100644 --- a/app/src/main/res/xml/method.xml +++ b/app/src/main/res/xml/method.xml @@ -79,6 +79,7 @@ kn_IN: Kannada (India)/kannada kn_IN: Kannada Extended (India)/kannada ky: Kyrgyz/russian + la: Latin lg: Luganda/luganda # This is a preliminary keyboard layout. ln: Lingala/lingala # This is a preliminary keyboard layout. lo_LA: Lao (Laos)/lao @@ -828,6 +829,15 @@ android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=MAIN:qwertz,AsciiCapable,EmojiCapable" /> + + diff --git a/layouts.md b/layouts.md index d03b20f98..59b092a9a 100644 --- a/layouts.md +++ b/layouts.md @@ -73,7 +73,7 @@ If the layout has exactly 2 keys in the bottom row, these keys will replace comm * `0.1` for phones * `0.09` for tablets * If the sum of widths in a row is greater than 1, keys are rescaled to fit on the screen -* `labelFlags`: allows specific effects, see [here](app/src/main/res/values/attrs.xml#L251-L287) in the section _keyLabelFlags_ for names and numeric values +* `labelFlags`: allows specific effects, see [here](app/src/main/res/values/attrs.xml#L250-L282) in the section _keyLabelFlags_ for names and numeric values * Since json does not support hexadecimal-values, you have to use the decimal values in the comments in the same line. * In case you want to apply multiple flags, you will need to combine them using [bitwise OR](https://en.wikipedia.org/wiki/Bitwise_operation#OR). In most cases this means you can just add the individual values, only exceptions are `fontDefault`, `followKeyLabelRatio`, `followKeyHintLabelRatio`, and `autoScale`. @@ -106,6 +106,12 @@ Usually the label is what is displayed on the key. However, there are some speci You can also specify special key codes like `a|!code/key_action_previous` or `abc|!code/-10043`, but it's cleaner to use a json layout and specify the code explicitly. Note that when specifying a code in the label, and a code in a json layout, the code in the label will be ignored. * It's also possible to specify an icon, like `!icon/previous_key|!code/key_action_previous`. * You can find available icon names in [KeyboardIconsSet](/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt). You can also use toolbar key icons using the uppercase name of the [toolbar key](/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt#L109), e.g. `!icon/redo` +* There are some further special labels to be used in popup keys (i.e. one of the popup keys should have the label) + * `!noPanelAutoPopupKey!`: no popups are shown, a long press will result in the first normal popup of the key being selected + * `!needsDividers!`: dividers are shown between popup keys + * `!hasLabels!`: reduces text size in popup keys for nicer display of labels instead of letters + * `!autoColumnOrder!`: use with a number, e.g. _!autoColumnOrder!4_ will result in 4 popup columns + * `!fixedColumnOrder!`: use with a number, e.g. _!fixedColumnOrder!4_ will result in 4 popup columns. Keys will not be re-ordered if the result is a single line. ## Adding new layouts / languages * You need a layout file in one of the formats above, and add it to [layouts](app/src/main/assets/layouts) diff --git a/tools/make-emoji-keys/src/main/kotlin/com/majeur/inputmethod/tools/emoji/model/EmojiData.kt b/tools/make-emoji-keys/src/main/kotlin/com/majeur/inputmethod/tools/emoji/model/EmojiData.kt index e8fe8f868..d4f73733a 100644 --- a/tools/make-emoji-keys/src/main/kotlin/com/majeur/inputmethod/tools/emoji/model/EmojiData.kt +++ b/tools/make-emoji-keys/src/main/kotlin/com/majeur/inputmethod/tools/emoji/model/EmojiData.kt @@ -33,36 +33,6 @@ class EmojiData { } private fun onEmojiInserted(group: EmojiGroup, emoji: EmojiSpec): Boolean { - // Unicode RGI does not include letter symbols but Android supports them, so we inject them manually. - if (emoji.codes contentEquals RAW_CPS_KEYCAP_HASH) { - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_A), 2.0f, "regional indicator symbol letter a") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_B), 2.0f, "regional indicator symbol letter b") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_C), 2.0f, "regional indicator symbol letter c") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_D), 2.0f, "regional indicator symbol letter d") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_E), 2.0f, "regional indicator symbol letter e") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_F), 2.0f, "regional indicator symbol letter f") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_G), 2.0f, "regional indicator symbol letter g") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_H), 2.0f, "regional indicator symbol letter h") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_I), 2.0f, "regional indicator symbol letter i") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_J), 2.0f, "regional indicator symbol letter j") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_K), 2.0f, "regional indicator symbol letter k") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_L), 2.0f, "regional indicator symbol letter l") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_M), 2.0f, "regional indicator symbol letter m") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_N), 2.0f, "regional indicator symbol letter n") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_O), 2.0f, "regional indicator symbol letter o") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_P), 2.0f, "regional indicator symbol letter p") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Q), 2.0f, "regional indicator symbol letter q") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_R), 2.0f, "regional indicator symbol letter r") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_S), 2.0f, "regional indicator symbol letter s") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_T), 2.0f, "regional indicator symbol letter t") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_U), 2.0f, "regional indicator symbol letter u") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_V), 2.0f, "regional indicator symbol letter v") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_W), 2.0f, "regional indicator symbol letter w") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_X), 2.0f, "regional indicator symbol letter x") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Y), 2.0f, "regional indicator symbol letter y") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Z), 2.0f, "regional indicator symbol letter z") - } - // Some multi-skin-tone variants use a different base code than their non-multi-skin-tone counterparts, // so they don't get grouped. We drop them here, to prevent each variant from being displayed separately. return ! hasMultipleSkinModifiers(emoji.codes) @@ -118,9 +88,6 @@ class EmojiData { } companion object { - - private val RAW_CPS_KEYCAP_HASH = intArrayOf(0x0023, 0xFE0F, 0x20E3) - const val CP_NUL = 0x0000 private const val CP_ZWJ = 0x200D @@ -136,34 +103,5 @@ class EmojiData { private const val CP_WHITE_HAIR = 0x1F9B3 private const val CP_BARLD = 0x1F9B2 private const val CP_VARIANT_SELECTOR = 0xFE0F - - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_A = 0x1F1E6 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_B = 0x1F1E7 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_C = 0x1F1E8 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_D = 0x1F1E9 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_E = 0x1F1EA - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_F = 0x1F1EB - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_G = 0x1F1EC - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_H = 0x1F1ED - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_I = 0x1F1EE - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_J = 0x1F1EF - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_K = 0x1F1F0 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_L = 0x1F1F1 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_M = 0x1F1F2 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_N = 0x1F1F3 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_O = 0x1F1F4 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_P = 0x1F1F5 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Q = 0x1F1F6 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_R = 0x1F1F7 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_S = 0x1F1F8 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_T = 0x1F1F9 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_U = 0x1F1FA - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_V = 0x1F1FB - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_W = 0x1F1FC - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_X = 0x1F1FD - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Y = 0x1F1FE - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Z = 0x1F1FF } - - } diff --git a/tools/make-emoji-keys/src/main/resources/emoji/android-emoji-support.txt b/tools/make-emoji-keys/src/main/resources/emoji/android-emoji-support.txt index 15aff89ab..baaf17988 100644 --- a/tools/make-emoji-keys/src/main/resources/emoji/android-emoji-support.txt +++ b/tools/make-emoji-keys/src/main/resources/emoji/android-emoji-support.txt @@ -3866,31 +3866,5 @@ U+1F532 # black square button U+1F3C1 # chequered flag U+1F6A9 # triangular flag U+1F38C # crossed flags -U+1F1E6 # regional indicator symbol letter a -U+1F1E7 # regional indicator symbol letter b -U+1F1E8 # regional indicator symbol letter c -U+1F1E9 # regional indicator symbol letter d -U+1F1EA # regional indicator symbol letter e -U+1F1EB # regional indicator symbol letter f -U+1F1EC # regional indicator symbol letter g -U+1F1ED # regional indicator symbol letter h -U+1F1EE # regional indicator symbol letter i -U+1F1EF # regional indicator symbol letter j -U+1F1F0 # regional indicator symbol letter k -U+1F1F1 # regional indicator symbol letter l -U+1F1F2 # regional indicator symbol letter m -U+1F1F3 # regional indicator symbol letter n -U+1F1F4 # regional indicator symbol letter o -U+1F1F5 # regional indicator symbol letter p -U+1F1F6 # regional indicator symbol letter q -U+1F1F7 # regional indicator symbol letter r -U+1F1F8 # regional indicator symbol letter s -U+1F1F9 # regional indicator symbol letter t -U+1F1FA # regional indicator symbol letter u -U+1F1FB # regional indicator symbol letter v -U+1F1FC # regional indicator symbol letter w -U+1F1FD # regional indicator symbol letter x -U+1F1FE # regional indicator symbol letter y -U+1F1FF # regional indicator symbol letter z # Above emojis are supported from Android 4.4 (API level 19)