Compare commits

..

No commits in common. "main" and "v3.2-beta1" have entirely different histories.

71 changed files with 600 additions and 1139 deletions

View file

@ -12,13 +12,13 @@ android {
applicationId = "helium314.keyboard"
minSdk = 21
targetSdk = 35
versionCode = 3201
versionName = "3.2"
versionCode = 3200
versionName = "3.2-beta1"
ndk {
abiFilters.clear()
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
}
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
buildTypes {

View file

@ -136,6 +136,32 @@
®️
™️
🫟
🇦
🇧
🇨
🇩
🇪
🇫
🇬
🇭
🇮
🇯
🇰
🇱
🇲
🇳
🇴
🇵
🇶
🇷
🇸
🇹
🇺
🇻
🇼
🇽
🇾
🇿
#️⃣
*️⃣
0
@ -221,4 +247,4 @@
💠
🔘
🔳
🔲
🔲

View file

@ -139,16 +139,6 @@ 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,
@ -266,8 +256,10 @@ class Event private constructor(
source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags or FLAG_COMBINING, source.mNextEvent)
}
val notHandledEvent = Event(EVENT_TYPE_NOT_HANDLED, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE, 0,
fun createNotHandledEvent(): Event {
return 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.

View file

@ -24,8 +24,7 @@ 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.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
val codePointAndFlags = keyEvent.unicodeChar
// 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.
@ -49,21 +48,6 @@ 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 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
}
} else Event.createNotHandledEvent()
}
}

View file

@ -6,8 +6,6 @@
package helium314.keyboard.keyboard;
import android.view.KeyEvent;
import helium314.keyboard.latin.common.Constants;
import helium314.keyboard.latin.common.InputPointers;
@ -33,12 +31,6 @@ 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.
*
@ -125,10 +117,6 @@ 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) {}

View file

@ -1,24 +1,16 @@
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
@ -27,11 +19,6 @@ 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<HardwareEventDecoder> = SparseArray(1)
private val keyboardSwitcher = KeyboardSwitcher.getInstance()
private val settings = Settings.getInstance()
@ -71,62 +58,9 @@ 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
// 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)
latinIME.onCodeInput(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat)
}
override fun onTextInput(text: String?) = latinIME.onTextInput(text)
@ -323,13 +257,4 @@ 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
}
}

View file

@ -184,11 +184,6 @@ 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;

View file

@ -138,7 +138,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
return false;
}
private void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues,
public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues,
final int currentAutoCapsState, final int currentRecapitalizeState) {
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
mThemeContext, editorInfo);
@ -527,10 +527,6 @@ 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());
}
@ -601,10 +597,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
if (mKeyboardView == null || !mKeyboardView.isShown()) {
return false;
}
final Keyboard keyboard = mKeyboardView.getKeyboard();
if (keyboard == null) // may happen when using hardware keyboard
return false;
int activeKeyboardId = keyboard.mId.mElementId;
int activeKeyboardId = mKeyboardView.getKeyboard().mId.mElementId;
for (int keyboardId : keyboardIds) {
if (activeKeyboardId == keyboardId) {
return true;

View file

@ -360,21 +360,25 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy
public void onKeyPressed(@NonNull final Key key, final boolean withPreview) {
key.onPressed();
invalidateKey(key);
final Keyboard keyboard = getKeyboard();
if (keyboard == null) {
return;
}
mKeyPreviewDrawParams.setVisibleOffset(-keyboard.mVerticalGap);
if (withPreview && !key.noKeyPreview() && mKeyPreviewDrawParams.isPopupEnabled()) {
if (withPreview && !key.noKeyPreview()) {
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, getKeyboard().mIconsSet, getKeyDrawParams(),
mKeyPreviewChoreographer.placeAndShowKeyPreview(key, keyboard.mIconsSet, getKeyDrawParams(),
KeyboardSwitcher.getInstance().getWrapperView().getWidth(), mOriginCoords, mDrawingPreviewPlacerView);
}

View file

@ -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.EmojiViewCallback;
import helium314.keyboard.keyboard.emoji.OnKeyEventListener;
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 EmojiViewCallback mEmojiViewCallback;
protected OnKeyEventListener mKeyEventListener;
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;
mEmojiViewCallback = null;
mKeyEventListener = 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 EmojiViewCallback emojiViewCallback) {
final int pointX, final int pointY, final OnKeyEventListener listener) {
mListener = null;
mEmojiViewCallback = emojiViewCallback;
mKeyEventListener = listener;
showPopupKeysPanelInternal(parentView, controller, pointX, pointY);
}
@ -157,9 +157,6 @@ 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
@ -225,8 +222,8 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
false /* isKeyRepeat */);
}
}
} else if (mEmojiViewCallback != null) {
mEmojiViewCallback.onReleaseKey(key);
} else if (mKeyEventListener != null) {
mKeyEventListener.onReleaseKey(key);
}
}
@ -317,4 +314,28 @@ 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);
}
}

View file

@ -8,17 +8,10 @@ package helium314.keyboard.keyboard;
import android.view.View;
import android.view.ViewGroup;
import helium314.keyboard.keyboard.emoji.EmojiViewCallback;
import helium314.keyboard.keyboard.emoji.OnKeyEventListener;
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.
@ -66,18 +59,19 @@ 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 EmojiViewCallback}.
* but with a {@link OnKeyEventListener}.
*
* @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 emojiViewCallback to receive keyboard actions from this {@link PopupKeysPanel}.
* @param listener the listener that will receive keyboard action 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, EmojiViewCallback emojiViewCallback);
int pointY, OnKeyEventListener listener);
/**
* Dismisses the popup keys panel and calls the controller's onDismissPopupKeysPanel to remove
@ -133,35 +127,20 @@ 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}.
*/
default void showInParent(ViewGroup parentView) {
removeFromParent();
parentView.addView(getContainerView());
}
void showInParent(ViewGroup parentView);
/**
* Remove this {@link PopupKeysPanel} from the parent view.
*/
default void removeFromParent() {
final View containerView = getContainerView();
final ViewGroup currentParent = (ViewGroup)containerView.getParent();
if (currentParent != null) {
currentParent.removeView(containerView);
}
}
void removeFromParent();
/**
* Return whether the panel is currently being shown.
*/
default boolean isShowingInParent() {
return getContainerView().getParent() != null;
}
boolean isShowingInParent();
}

View file

@ -1,121 +0,0 @@
/*
* 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;
}
}

View file

@ -112,12 +112,12 @@ final class DynamicGridKeyboard extends Keyboard {
}
public int getDynamicOccupiedHeight() {
final int row = (mGridKeys.size() - 1) / getOccupiedColumnCount() + 1;
final int row = (mGridKeys.size() - 1) / mColumnsNum + 1;
return row * mVerticalStep;
}
public int getOccupiedColumnCount() {
return mColumnsNum - mEmptyColumnIndices.size();
public int getColumnsCount() {
return mColumnsNum;
}
public void addPendingKey(final Key usedKey) {

View file

@ -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.getOccupiedColumnCount();
return MAX_LINE_COUNT_PER_PAGE * tempKeyboard.getColumnsCount();
}
private static final Comparator<Key> EMOJI_KEY_COMPARATOR = (lhs, rhs) -> {

View file

@ -13,9 +13,6 @@ 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;
@ -56,18 +53,14 @@ 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 EmojiViewCallback EMPTY_EMOJI_VIEW_CALLBACK = new EmojiViewCallback() {
private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() {
@Override
public void onPressKey(final Key key) {}
@Override
public void onReleaseKey(final Key key) {}
@Override
public String getDescription(String emoji) {
return null;
}
};
private EmojiViewCallback mEmojiViewCallback = EMPTY_EMOJI_VIEW_CALLBACK;
private OnKeyEventListener mListener = EMPTY_LISTENER;
private final KeyDetector mKeyDetector = new KeyDetector();
private KeyboardAccessibilityDelegate<EmojiPageKeyboardView> mAccessibilityDelegate;
@ -81,8 +74,6 @@ 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<Key, Keyboard> mPopupKeysKeyboardCache = new WeakHashMap<>();
private final boolean mConfigShowPopupKeysKeyboardAtTouchedPoint;
private final ViewGroup mPopupKeysPlacerView;
@ -111,8 +102,6 @@ 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
@ -157,8 +146,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
}
}
public void setEmojiViewCallback(final EmojiViewCallback emojiViewCallback) {
mEmojiViewCallback = emojiViewCallback;
public void setOnKeyEventListener(final OnKeyEventListener listener) {
mListener = listener;
}
/**
@ -180,8 +169,7 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
}
@Nullable
private PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key) {
mPopupKeysKeyboardView.setVisibility(GONE);
public PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key, final int lastX, final int lastY) {
final PopupKeySpec[] popupKeys = key.getPopupKeys();
if (popupKeys == null) {
return null;
@ -194,9 +182,21 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
mPopupKeysKeyboardCache.put(key, popupKeysKeyboard);
}
mPopupKeysKeyboardView.setKeyboard(popupKeysKeyboard);
mPopupKeysKeyboardView.setVisibility(VISIBLE);
return mPopupKeysKeyboardView;
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;
}
private void dismissPopupKeysPanel() {
@ -209,17 +209,6 @@ 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
@ -301,11 +290,9 @@ 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);
@ -314,34 +301,6 @@ 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) {
@ -359,7 +318,7 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
releasedKey.onReleased();
invalidateKey(releasedKey);
if (withKeyRegistering) {
mEmojiViewCallback.onReleaseKey(releasedKey);
mListener.onReleaseKey(releasedKey);
}
}
@ -367,7 +326,7 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
mPendingKeyDown = null;
pressedKey.onPressed();
invalidateKey(pressedKey);
mEmojiViewCallback.onPressKey(pressedKey);
mListener.onPressKey(pressedKey);
}
public void releaseCurrentKey(final boolean withKeyRegistering) {

View file

@ -7,6 +7,7 @@
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;
@ -22,13 +23,13 @@ final class EmojiPalettesAdapter extends RecyclerView.Adapter<EmojiPalettesAdapt
private static final boolean DEBUG_PAGER = false;
private final int mCategoryId;
private final EmojiViewCallback mEmojiViewCallback;
private final OnKeyEventListener mListener;
private final EmojiCategory mEmojiCategory;
public EmojiPalettesAdapter(final EmojiCategory emojiCategory, int categoryId, final EmojiViewCallback emojiViewCallback) {
public EmojiPalettesAdapter(final EmojiCategory emojiCategory, int categoryId, final OnKeyEventListener listener) {
mEmojiCategory = emojiCategory;
mCategoryId = categoryId;
mEmojiViewCallback = emojiViewCallback;
mListener = listener;
}
@NonNull
@ -37,7 +38,6 @@ final class EmojiPalettesAdapter extends RecyclerView.Adapter<EmojiPalettesAdapt
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate(
R.layout.emoji_keyboard_page, parent, false);
keyboardView.setEmojiViewCallback(mEmojiViewCallback);
return new ViewHolder(keyboardView);
}
@ -50,6 +50,7 @@ final class EmojiPalettesAdapter extends RecyclerView.Adapter<EmojiPalettesAdapt
final Keyboard keyboard =
mEmojiCategory.getKeyboardFromAdapterPosition(mCategoryId, position);
holder.getKeyboardView().setKeyboard(keyboard);
holder.getKeyboardView().setOnKeyEventListener(mListener);
}
@Override

View file

@ -38,16 +38,12 @@ import helium314.keyboard.keyboard.internal.KeyDrawParams;
import helium314.keyboard.keyboard.internal.KeyVisualAttributes;
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
import helium314.keyboard.latin.AudioAndHapticFeedbackManager;
import helium314.keyboard.latin.DictionaryFactory;
import helium314.keyboard.latin.R;
import helium314.keyboard.latin.RichInputMethodManager;
import helium314.keyboard.latin.RichInputMethodSubtype;
import helium314.keyboard.latin.SingleDictionaryFacilitator;
import helium314.keyboard.latin.common.ColorType;
import helium314.keyboard.latin.common.Colors;
import helium314.keyboard.latin.settings.Settings;
import helium314.keyboard.latin.settings.SettingsValues;
import helium314.keyboard.latin.utils.DictionaryInfoUtils;
import helium314.keyboard.latin.utils.ResourceUtils;
import static helium314.keyboard.latin.common.Constants.NOT_A_COORDINATE;
@ -64,7 +60,7 @@ import static helium314.keyboard.latin.common.Constants.NOT_A_COORDINATE;
* Because of the above reasons, this class doesn't extend {@link KeyboardView}.
*/
public final class EmojiPalettesView extends LinearLayout
implements View.OnClickListener, EmojiViewCallback {
implements View.OnClickListener, OnKeyEventListener {
private static final class PagerViewHolder extends RecyclerView.ViewHolder {
private long mCategoryId;
@ -75,7 +71,7 @@ public final class EmojiPalettesView extends LinearLayout
private final class PagerAdapter extends RecyclerView.Adapter<PagerViewHolder> {
private boolean mInitialized;
private final Map<Integer, RecyclerView> mViews = new HashMap<>(mEmojiCategory.getShownCategories().size());
private Map<Integer, RecyclerView> mViews = new HashMap<>(mEmojiCategory.getShownCategories().size());
private PagerAdapter(ViewPager2 pager) {
setHasStableIds(true);
@ -183,14 +179,15 @@ public final class EmojiPalettesView extends LinearLayout
}
}
private static SingleDictionaryFacilitator sDictionaryFacilitator;
private boolean initialized = false;
private final Colors mColors;
private final EmojiLayoutParams mEmojiLayoutParams;
private LinearLayout mTabStrip;
private EmojiCategoryPageIndicatorView mEmojiCategoryPageIndicatorView;
private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
private final EmojiCategory mEmojiCategory;
private ViewPager2 mPager;
@ -257,7 +254,9 @@ public final class EmojiPalettesView extends LinearLayout
mEmojiLayoutParams.setEmojiListProperties(mPager);
mEmojiCategoryPageIndicatorView = findViewById(R.id.emoji_category_page_id_view);
mEmojiLayoutParams.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView);
setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true);
mEmojiCategoryPageIndicatorView.setColors(mColors.get(ColorType.EMOJI_CATEGORY_SELECTED), mColors.get(ColorType.STRIP_BACKGROUND));
initialized = true;
}
@ -281,7 +280,8 @@ public final class EmojiPalettesView extends LinearLayout
}
/**
* Called from {@link EmojiPageKeyboardView} through {@link EmojiViewCallback}
* Called from {@link EmojiPageKeyboardView} through
* {@link helium314.keyboard.keyboard.emoji.OnKeyEventListener}
* interface to handle touch events from non-View-based elements such as Emoji buttons.
*/
@Override
@ -291,9 +291,10 @@ public final class EmojiPalettesView extends LinearLayout
}
/**
* Called from {@link EmojiPageKeyboardView} through {@link EmojiViewCallback}
* Called from {@link EmojiPageKeyboardView} through
* {@link helium314.keyboard.keyboard.emoji.OnKeyEventListener}
* interface to handle touch events from non-View-based elements such as Emoji buttons.
* This may be called without any prior call to {@link EmojiViewCallback#onPressKey(Key)}.
* This may be called without any prior call to {@link OnKeyEventListener#onPressKey(Key)}.
*/
@Override
public void onReleaseKey(final Key key) {
@ -309,20 +310,6 @@ public final class EmojiPalettesView extends LinearLayout
mKeyboardActionListener.onCodeInput(KeyCode.ALPHA, NOT_A_COORDINATE, NOT_A_COORDINATE, false);
}
@Override
public String getDescription(String emoji) {
if (sDictionaryFacilitator == null) {
return null;
}
var wordProperty = sDictionaryFacilitator.getWordProperty(emoji);
if (wordProperty == null || ! wordProperty.mHasShortcuts) {
return null;
}
return wordProperty.mShortcutTargets.get(0).mWord;
}
public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
if (!enabled) return;
// TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
@ -337,7 +324,6 @@ public final class EmojiPalettesView extends LinearLayout
final KeyDrawParams params = new KeyDrawParams();
params.updateParams(mEmojiLayoutParams.getBottomRowKeyboardHeight(), keyVisualAttr);
setupSidePadding();
initDictionaryFacilitator();
}
private void addRecentKey(final Key key) {
@ -417,7 +403,7 @@ public final class EmojiPalettesView extends LinearLayout
if (mPager.getScrollState() != ViewPager2.SCROLL_STATE_DRAGGING) {
// Not swiping
mPager.setCurrentItem(mEmojiCategory.getTabIdFromCategoryId(
mEmojiCategory.getCurrentCategoryId()), ! initial && ! isAnimationsDisabled());
mEmojiCategory.getCurrentCategoryId()), ! initial);
}
if (Settings.getValues().mSecondaryStripVisible) {
@ -432,11 +418,6 @@ public final class EmojiPalettesView extends LinearLayout
}
}
private boolean isAnimationsDisabled() {
return android.provider.Settings.Global.getFloat(getContext().getContentResolver(),
android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f) == 0.0f;
}
public void clearKeyboardCache() {
if (!initialized) {
return;
@ -444,27 +425,5 @@ public final class EmojiPalettesView extends LinearLayout
mEmojiCategory.clearKeyboardCache();
mPager.getAdapter().notifyDataSetChanged();
closeDictionaryFacilitator();
}
private void initDictionaryFacilitator() {
if (Settings.getValues().mShowEmojiDescriptions) {
var locale = RichInputMethodManager.getInstance().getCurrentSubtype().getLocale();
if (sDictionaryFacilitator == null || ! sDictionaryFacilitator.isForLocale(locale)) {
closeDictionaryFacilitator();
var dictFile = DictionaryInfoUtils.getCachedDictForLocaleAndType(locale, "emoji", getContext());
var dictionary = dictFile != null? DictionaryFactory.getDictionary(dictFile, locale) : null;
sDictionaryFacilitator = dictionary != null? new SingleDictionaryFacilitator(dictionary) : null;
}
} else {
closeDictionaryFacilitator();
}
}
private static void closeDictionaryFacilitator() {
if (sDictionaryFacilitator != null) {
sDictionaryFacilitator.closeDictionaries();
sDictionaryFacilitator = null;
}
}
}

View file

@ -5,10 +5,10 @@ package helium314.keyboard.keyboard.emoji;
import helium314.keyboard.keyboard.Key;
/**
* Interface to handle callbacks from child elements
* such as Emoji buttons and keyboard views.
* Interface to handle touch events from non-View-based elements
* such as Emoji buttons.
*/
public interface EmojiViewCallback {
public interface OnKeyEventListener {
/**
* Called when a key is pressed by the user
@ -17,13 +17,8 @@ public interface EmojiViewCallback {
/**
* Called when a key is released.
* This may be called without any prior call to {@link EmojiViewCallback#onPressKey(Key)},
* This may be called without any prior call to {@link OnKeyEventListener#onPressKey(Key)},
* for example when a key from a popup keys keyboard is selected by releasing touch on it.
*/
void onReleaseKey(Key key);
/**
* Called from keyboard view to get an emoji description
*/
String getDescription(String emoji);
}

View file

@ -229,15 +229,13 @@ class KeyboardParser(private val params: KeyboardParams, private val context: Co
return
// replace comma / period if 2 keys in normal bottom row
if (baseKeys.last().size == 2) {
val newComma = baseKeys.last()[0]
functionalKeysBottom.replaceFirst(
{ it.label == KeyLabel.COMMA || it.groupId == KeyData.GROUP_COMMA},
{ newComma.copy(newGroupId = 1, newType = newComma.type, newLabelFlags = it.labelFlags or newComma.labelFlags) }
{ baseKeys.last()[0].copy(newGroupId = 1, newType = baseKeys.last()[0].type ?: it.type) }
)
val newPeriod = baseKeys.last()[1]
functionalKeysBottom.replaceFirst(
{ it.label == KeyLabel.PERIOD || it.groupId == KeyData.GROUP_PERIOD},
{ newPeriod.copy(newGroupId = 2, newType = newPeriod.type, newLabelFlags = it.labelFlags or newPeriod.labelFlags) }
{ baseKeys.last()[1].copy(newGroupId = 2, newType = baseKeys.last()[1].type ?: it.type) }
)
baseKeys.removeAt(baseKeys.lastIndex)
}

View file

@ -45,7 +45,7 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) {
"mns" -> Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO
else -> 0
}
val tlds = mutableListOf(Key.POPUP_KEYS_HAS_LABELS)
val tlds = getLocaleTlds(locale)
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 -> tlds.addAll(SpacedTokens(line).map { ".$it" })
READER_MODE_TLD -> SpacedTokens(line).forEach { tlds.add(".$it") }
}
}
}
@ -156,16 +156,6 @@ 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<String>) {
@ -215,7 +205,6 @@ 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
}
@ -231,6 +220,28 @@ private fun getStreamForLocale(locale: Locale, context: Context) =
}
}
private fun getLocaleTlds(locale: Locale): LinkedHashSet<String> {
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<String> {
val tlds = linkedSetOf<String>()
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
@ -330,7 +341,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 = hashMapOf<String, String>(
private val specialCountryTlds = listOf(
"bd" to ".bd .com.bd",
"bq" to ".bq .an .nl",
"bl" to ".bl .gp .fr",
@ -340,5 +351,4 @@ private val specialCountryTlds = hashMapOf<String, String>(
"mf" to ".mf .gp .fr",
"tl" to ".tl .tp",
)
private const val comTld = ".com"
private const val otherDefaultTlds = ".gov .edu .org .net"
private const val defaultTlds = ".com .gov .edu .org .net"

View file

@ -83,7 +83,7 @@ object KeyCode {
const val LANGUAGE_SWITCH = -227
//const val IME_SHOW_UI = -231
const val IME_HIDE_UI = -232
//const val IME_HIDE_UI = -232
const val VOICE_INPUT = -233
//const val TOGGLE_SMARTBAR_VISIBILITY = -241
@ -175,12 +175,6 @@ 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
@ -189,14 +183,14 @@ object KeyCode {
REDO, ARROW_DOWN, ARROW_UP, ARROW_RIGHT, ARROW_LEFT, CLIPBOARD_COPY, CLIPBOARD_PASTE, CLIPBOARD_SELECT_ALL,
CLIPBOARD_SELECT_WORD, TOGGLE_INCOGNITO_MODE, TOGGLE_AUTOCORRECT, MOVE_START_OF_LINE, MOVE_END_OF_LINE,
MOVE_START_OF_PAGE, MOVE_END_OF_PAGE, SHIFT, CAPS_LOCK, MULTIPLE_CODE_POINTS, UNSPECIFIED, CTRL, ALT,
FN, CLIPBOARD_CLEAR_HISTORY, NUMPAD, IME_HIDE_UI,
FN, CLIPBOARD_CLEAR_HISTORY, NUMPAD,
// heliboard only
SYMBOL_ALPHA, TOGGLE_ONE_HANDED_MODE, SWITCH_ONE_HANDED_MODE, SPLIT_LAYOUT, SHIFT_ENTER,
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, SEND_INTENT_ONE, SEND_INTENT_TWO, SEND_INTENT_THREE,
TIMESTAMP, CTRL_LEFT, CTRL_RIGHT, ALT_LEFT, ALT_RIGHT, META_LEFT, META_RIGHT
-> this
// conversion
@ -216,11 +210,66 @@ object KeyCode {
// todo: there are many more keys, see near https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0
/**
* Convert an internal keyCode to a KeyEvent.KEYCODE_<xxx>.
* Positive codes are passed through, unknown negative codes result in KeyEvent.KEYCODE_UNKNOWN.
* Convert a keyCode / codePoint to a KeyEvent.KEYCODE_<xxx>.
* Fallback to KeyEvent.KEYCODE_UNKNOWN.
* To be uses for fake hardware key press.
*/
@JvmStatic fun keyCodeToKeyEventCode(keyCode: Int) = when (keyCode) {
* */
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) {
ARROW_UP -> KeyEvent.KEYCODE_DPAD_UP
ARROW_RIGHT -> KeyEvent.KEYCODE_DPAD_RIGHT
ARROW_DOWN -> KeyEvent.KEYCODE_DPAD_DOWN
@ -254,67 +303,6 @@ 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_<xxx>.
* 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
}
}

View file

@ -11,7 +11,6 @@ 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;
/**
@ -178,10 +177,6 @@ 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.

View file

@ -381,7 +381,6 @@ class DictionaryFacilitatorImpl : DictionaryFacilitator {
}
private fun addToPersonalDictionaryIfInvalidButInHistory(word: String) {
if (word.length <= 1) return
val dictionaryGroup = clearlyPreferredDictionaryGroup ?: return
val userDict = dictionaryGroup.getSubDict(Dictionary.TYPE_USER) ?: return
val userHistoryDict = dictionaryGroup.getSubDict(Dictionary.TYPE_USER_HISTORY) ?: return
@ -397,10 +396,7 @@ 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 {
// 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) }
UserDictionary.Words.addWord(userDict.mContext, word, 250, null, dictionaryGroup.locale)
}
}
}
@ -734,7 +730,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?.exists() == true || file.parentFile?.mkdirs() == true) file
if (file.parentFile?.mkdirs() == true) file
else null
}

View file

@ -64,26 +64,10 @@ object DictionaryFactory {
* if the dictionary type already exists in [dicts], the [file] is skipped
*/
private fun checkAndAddDictionaryToListNewType(file: File, dicts: MutableList<Dictionary>, locale: Locale) {
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
}
if (!file.isFile) return
val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file) ?: return killDictionary(file)
val dictType = header.mIdString.split(":").first()
if (dicts.any { it.mDictType == dictType }) return
val readOnlyBinaryDictionary = ReadOnlyBinaryDictionary(
file.absolutePath, 0, file.length(), false, locale, dictType
)
@ -91,13 +75,14 @@ object DictionaryFactory {
if (readOnlyBinaryDictionary.isValidDictionary) {
if (locale.language == "ko") {
// Use KoreanDictionary for Korean locale
return KoreanDictionary(readOnlyBinaryDictionary)
dicts.add(KoreanDictionary(readOnlyBinaryDictionary))
} else {
dicts.add(readOnlyBinaryDictionary)
}
return readOnlyBinaryDictionary
} else {
readOnlyBinaryDictionary.close()
killDictionary(file)
}
readOnlyBinaryDictionary.close()
killDictionary(file)
return null
}
private fun killDictionary(file: File) {

View file

@ -23,7 +23,7 @@ import java.util.List;
/**
* A class for detecting Emoji-Alt physical key.
*/
public final class EmojiAltPhysicalKeyDetector {
final class EmojiAltPhysicalKeyDetector {
private static final String TAG = "EmojiAltPhysKeyDetector";
private static final boolean DEBUG = false;

View file

@ -4,7 +4,6 @@ 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;
@ -73,11 +72,6 @@ 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();

View file

@ -26,6 +26,7 @@ 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;
@ -50,6 +51,9 @@ 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;
@ -64,6 +68,7 @@ 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;
@ -129,6 +134,9 @@ 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<HardwareEventDecoder> mHardwareEventDecoders = new SparseArray<>(1);
// TODO: Move these {@link View}s to {@link KeyboardSwitcher}.
private View mInputView;
@ -138,6 +146,7 @@ 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()}.
@ -270,7 +279,9 @@ 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.reloadMainKeyboard();
latinIme.mKeyboardSwitcher.loadKeyboard(latinIme.getCurrentInputEditorInfo(),
settingsValues, latinIme.getCurrentAutoCapsState(),
latinIme.getCurrentRecapitalizeState());
}
break;
case MSG_WAIT_FOR_DICTIONARY_LOAD:
@ -626,7 +637,6 @@ public class LatinIME extends InputMethodService implements
@Override
public void onCreate() {
mSettings.startListener();
KeyboardIconsSet.Companion.getInstance().loadIcons(this);
mRichImm = RichInputMethodManager.getInstance();
AudioAndHapticFeedbackManager.init(this);
@ -654,8 +664,7 @@ public class LatinIME extends InputMethodService implements
final IntentFilter newDictFilter = new IntentFilter();
newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
// 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);
ContextCompat.registerReceiver(this, mDictionaryPackInstallReceiver, newDictFilter, ContextCompat.RECEIVER_NOT_EXPORTED);
final IntentFilter dictDumpFilter = new IntentFilter();
dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);
@ -1062,7 +1071,7 @@ public class LatinIME extends InputMethodService implements
if (isDifferentTextField) {
mainKeyboardView.closing();
suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
switcher.reloadMainKeyboard();
switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
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.
@ -1110,7 +1119,6 @@ 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();
@ -1167,12 +1175,8 @@ public class LatinIME extends InputMethodService implements
if (isInputViewShown()
&& mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
composingSpanStart, composingSpanEnd, settingsValues)) {
// 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());
mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
getCurrentRecapitalizeState());
}
}
@ -1213,7 +1217,6 @@ 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();
@ -1226,12 +1229,6 @@ 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) {
@ -1283,8 +1280,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.
// 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
outInsets.contentTopInsets = inputHeight;
outInsets.visibleTopInsets = inputHeight;
mInsetsUpdater.setInsets(outInsets);
return;
}
@ -1389,10 +1386,6 @@ public class LatinIME extends InputMethodService implements
@RequiresApi(api = Build.VERSION_CODES.R)
public boolean onInlineSuggestionsResponse(InlineSuggestionsResponse response) {
Log.d(TAG,"onInlineSuggestionsResponse called");
if (Settings.getValues().mSuggestionStripHiddenPerUserSettings) {
return false;
}
final List<InlineSuggestion> inlineSuggestions = response.getInlineSuggestions();
if (inlineSuggestions.isEmpty()) {
return false;
@ -1542,7 +1535,25 @@ 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) {
mKeyboardActionListener.onCodeInput(codePoint, x, y, 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);
}
// This method is public for testability of LatinIME, but also in the future it should
@ -1559,6 +1570,24 @@ 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);
@ -1730,7 +1759,8 @@ public class LatinIME extends InputMethodService implements
loadSettings();
if (mKeyboardSwitcher.getMainKeyboardView() != null) {
// Reload keyboard because the current language has been changed.
mKeyboardSwitcher.reloadMainKeyboard();
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent(),
getCurrentAutoCapsState(), getCurrentRecapitalizeState());
}
}
@ -1798,18 +1828,63 @@ 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 (mKeyboardActionListener.onKeyDown(keyCode, 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);
return true;
}
return super.onKeyDown(keyCode, keyEvent);
}
@Override
public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) {
if (mKeyboardActionListener.onKeyUp(keyCode, 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)) {
return true;
}
return super.onKeyUp(keyCode, keyEvent);
}

View file

@ -10,7 +10,6 @@ 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;
@ -108,18 +107,6 @@ 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();

View file

@ -40,6 +40,8 @@ import helium314.keyboard.latin.settings.SpacingAndPunctuations;
import helium314.keyboard.latin.utils.CapsModeUtils;
import helium314.keyboard.latin.utils.DebugLogUtils;
import helium314.keyboard.latin.utils.NgramContextUtils;
import helium314.keyboard.latin.utils.ScriptUtils;
import helium314.keyboard.latin.utils.SpannableStringUtils;
import helium314.keyboard.latin.utils.StatsUtils;
import helium314.keyboard.latin.utils.TextRange;
@ -439,7 +441,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.toString());
final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText);
// 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,13 +718,8 @@ public final class RichInputConnection implements PrivateCommandPerformer {
if (start < 0 || end < 0) {
return false;
}
if (start > end) {
mExpectedSelStart = end;
mExpectedSelEnd = start;
} else {
mExpectedSelStart = start;
mExpectedSelEnd = end;
}
mExpectedSelStart = start;
mExpectedSelEnd = end;
if (isConnected()) {
final boolean isIcValid = mIC.setSelection(start, end);
if (!isIcValid) {
@ -828,6 +825,15 @@ public final class RichInputConnection implements PrivateCommandPerformer {
return NgramContextUtils.getNgramContextFromNthPreviousWord(prev, spacingAndPunctuations, n);
}
private static boolean isPartOfCompositionForScript(final int codePoint,
final SpacingAndPunctuations spacingAndPunctuations, final String script) {
// We always consider word connectors part of compositions.
return spacingAndPunctuations.isWordConnector(codePoint)
// Otherwise, it's part of composition if it's part of script and not a separator.
|| (!spacingAndPunctuations.isWordSeparator(codePoint)
&& ScriptUtils.isLetterPartOfScript(codePoint, script));
}
/**
* Returns the text surrounding the cursor.
*
@ -854,7 +860,90 @@ public final class RichInputConnection implements PrivateCommandPerformer {
if (before == null || after == null) {
return null;
}
return StringUtilsKt.getTouchedWordRange(before, after, script, spacingAndPunctuations);
// Going backward, find the first breaking point (separator)
int startIndexInBefore = before.length();
int endIndexInAfter = -1;
while (startIndexInBefore > 0) {
final int codePoint = Character.codePointBefore(before, startIndexInBefore);
if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, script)) {
if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces)
break;
// continue to the next whitespace and see whether this contains a sometimesWordConnector
for (int i = startIndexInBefore - 1; i >= 0; i--) {
final char c = before.charAt(i);
if (spacingAndPunctuations.isSometimesWordConnector(c)) {
// if yes -> whitespace is the index
startIndexInBefore = Math.max(StringUtils.charIndexOfLastWhitespace(before), 0);
final int firstSpaceAfter = StringUtils.charIndexOfFirstWhitespace(after);
endIndexInAfter = firstSpaceAfter == -1 ? after.length() : firstSpaceAfter -1;
break;
} else if (Character.isWhitespace(c)) {
// if no, just break normally
break;
}
}
break;
}
--startIndexInBefore;
if (Character.isSupplementaryCodePoint(codePoint)) {
--startIndexInBefore;
}
}
// Find last word separator after the cursor
if (endIndexInAfter == -1) {
while (++endIndexInAfter < after.length()) {
final int codePoint = Character.codePointAt(after, endIndexInAfter);
if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, script)) {
if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces)
break;
// continue to the next whitespace and see whether this contains a sometimesWordConnector
for (int i = endIndexInAfter; i < after.length(); i++) {
final char c = after.charAt(i);
if (spacingAndPunctuations.isSometimesWordConnector(c)) {
// if yes -> whitespace is next to the index
startIndexInBefore = Math.max(StringUtils.charIndexOfLastWhitespace(before), 0);
final int firstSpaceAfter = StringUtils.charIndexOfFirstWhitespace(after);
endIndexInAfter = firstSpaceAfter == -1 ? after.length() : firstSpaceAfter - 1;
break;
} else if (Character.isWhitespace(c)) {
// if no, just break normally
break;
}
}
break;
}
if (Character.isSupplementaryCodePoint(codePoint)) {
++endIndexInAfter;
}
}
}
// strip stuff before "//" (i.e. ignore http and other protocols)
final String beforeConsideringStart = before.subSequence(startIndexInBefore, before.length()).toString();
final int protocolEnd = beforeConsideringStart.lastIndexOf("//");
if (protocolEnd != -1)
startIndexInBefore += protocolEnd + 1;
// we don't want the end characters to be word separators
while (endIndexInAfter > 0 && spacingAndPunctuations.isWordSeparator(after.charAt(endIndexInAfter - 1))) {
--endIndexInAfter;
}
while (startIndexInBefore < before.length() && spacingAndPunctuations.isWordSeparator(before.charAt(startIndexInBefore))) {
++startIndexInBefore;
}
final boolean hasUrlSpans =
SpannableStringUtils.hasUrlSpans(before, startIndexInBefore, before.length())
|| SpannableStringUtils.hasUrlSpans(after, 0, endIndexInAfter);
// We don't use TextUtils#concat because it copies all spans without respect to their
// nature. If the text includes a PARAGRAPH span and it has been split, then
// TextUtils#concat will crash when it tries to concat both sides of it.
return new TextRange(
SpannableStringUtils.concatWithNonParagraphSuggestionSpansOnly(before, after),
startIndexInBefore, before.length() + endIndexInAfter, before.length(),
hasUrlSpans);
}
public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations,
@ -867,7 +956,19 @@ public final class RichInputConnection implements PrivateCommandPerformer {
// a composing region should always count as a word
return true;
}
return StringUtilsKt.endsWithWordCodepoint(mCommittedTextBeforeComposingText.toString(), spacingAndPunctuations);
final String textBeforeCursor = mCommittedTextBeforeComposingText.toString();
int indexOfCodePointInJavaChars = textBeforeCursor.length();
int consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE
: textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars);
// Search for the first non word-connector char
if (spacingAndPunctuations.isWordConnector(consideredCodePoint)) {
indexOfCodePointInJavaChars -= Character.charCount(consideredCodePoint);
consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE
: textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars);
}
return !(Constants.NOT_A_CODE == consideredCodePoint
|| spacingAndPunctuations.isWordSeparator(consideredCodePoint)
|| spacingAndPunctuations.isWordConnector(consideredCodePoint));
}
public boolean isCursorFollowedByWordCharacter(

View file

@ -7,7 +7,6 @@ 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
@ -48,8 +47,6 @@ 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<String, Boolean>) {}

View file

@ -12,5 +12,3 @@ object Links {
const val CUSTOM_LAYOUTS = "$GITHUB/discussions/categories/custom-layout"
const val CUSTOM_COLORS = "$GITHUB/discussions/categories/custom-colors"
}
val combiningRange = 0x300..0x35b

View file

@ -6,18 +6,13 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
import helium314.keyboard.latin.common.StringUtils.mightBeEmoji
import helium314.keyboard.latin.common.StringUtils.newSingleCodePointString
import helium314.keyboard.latin.settings.SpacingAndPunctuations
import helium314.keyboard.latin.utils.ScriptUtils
import helium314.keyboard.latin.utils.SpacedTokens
import helium314.keyboard.latin.utils.SpannableStringUtils
import helium314.keyboard.latin.utils.TextRange
import java.math.BigInteger
import java.util.Locale
import kotlin.math.max
fun CharSequence.codePointAt(offset: Int) = Character.codePointAt(this, offset)
fun CharSequence.codePointBefore(offset: Int) = Character.codePointBefore(this, offset)
/** Loops over the codepoints in [text]. Exits when [loop] returns true */
inline fun loopOverCodePoints(text: CharSequence, loop: (cp: Int, charCount: Int) -> Boolean) {
var offset = 0
while (offset < text.length) {
@ -28,7 +23,6 @@ inline fun loopOverCodePoints(text: CharSequence, loop: (cp: Int, charCount: Int
}
}
/** Loops backwards over the codepoints in [text]. Exits when [loop] returns true */
inline fun loopOverCodePointsBackwards(text: CharSequence, loop: (cp: Int, charCount: Int) -> Boolean) {
var offset = text.length
while (offset > 0) {
@ -94,111 +88,6 @@ fun getFullEmojiAtEnd(text: CharSequence): String {
return s.substring(offset)
}
/**
* Returns whether the [text] does not end with word separator, ignoring all word connectors.
* If the [text] is empty (after ignoring word connectors), the method returns false.
*/
// todo: this returns true on numbers, why isn't Character.isLetter(code) used?
fun endsWithWordCodepoint(text: String, spacingAndPunctuations: SpacingAndPunctuations): Boolean {
if (text.isEmpty()) return false
var codePoint = 0 // initial value irrelevant since length is always > 0
loopOverCodePointsBackwards(text) { cp, _ ->
codePoint = cp
!spacingAndPunctuations.isWordConnector(cp)
}
// codePoint might still be a wordConnector (if text consists of wordConnectors)
return !spacingAndPunctuations.isWordConnector(codePoint) && !spacingAndPunctuations.isWordSeparator(codePoint)
}
// todo: simplify... maybe compare with original code?
fun getTouchedWordRange(before: CharSequence, after: CharSequence, script: String, spacingAndPunctuations: SpacingAndPunctuations): TextRange {
// Going backward, find the first breaking point (separator)
var startIndexInBefore = before.length
var endIndexInAfter = -1 // todo: clarify why might we want to set it when checking before
loopOverCodePointsBackwards(before) { codePoint, cpLength ->
if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, script)) {
if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces)
return@loopOverCodePointsBackwards true
// continue to the next whitespace and see whether this contains a sometimesWordConnector
for (i in startIndexInBefore - 1 downTo 0) {
val c = before[i]
if (spacingAndPunctuations.isSometimesWordConnector(c.code)) {
// if yes -> whitespace is the index
startIndexInBefore = max(StringUtils.charIndexOfLastWhitespace(before).toDouble(), 0.0).toInt()
val firstSpaceAfter = StringUtils.charIndexOfFirstWhitespace(after)
endIndexInAfter = if (firstSpaceAfter == -1) after.length else firstSpaceAfter - 1
return@loopOverCodePointsBackwards true
} else if (Character.isWhitespace(c)) {
// if no, just break normally
return@loopOverCodePointsBackwards true
}
}
return@loopOverCodePointsBackwards true
}
startIndexInBefore -= cpLength
false
}
// Find last word separator after the cursor
if (endIndexInAfter == -1) {
endIndexInAfter = 0
loopOverCodePoints(after) { codePoint, cpLength ->
if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, script)) {
if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces)
return@loopOverCodePoints true
// continue to the next whitespace and see whether this contains a sometimesWordConnector
for (i in endIndexInAfter..<after.length) {
val c = after[i]
if (spacingAndPunctuations.isSometimesWordConnector(c.code)) {
// if yes -> whitespace is next to the index
startIndexInBefore = max(StringUtils.charIndexOfLastWhitespace(before), 0)
val firstSpaceAfter = StringUtils.charIndexOfFirstWhitespace(after)
endIndexInAfter = if (firstSpaceAfter == -1) after.length else firstSpaceAfter - 1
return@loopOverCodePoints true
} else if (Character.isWhitespace(c)) {
// if no, just break normally
return@loopOverCodePoints true
}
}
return@loopOverCodePoints true
}
endIndexInAfter += cpLength
false
}
}
// strip text before "//" (i.e. ignore http and other protocols)
val beforeConsideringStart = before.substring(startIndexInBefore, before.length)
val protocolEnd = beforeConsideringStart.lastIndexOf("//")
if (protocolEnd != -1) startIndexInBefore += protocolEnd + 1
// we don't want the end characters to be word separators
while (endIndexInAfter > 0 && spacingAndPunctuations.isWordSeparator(after[endIndexInAfter - 1].code)) {
--endIndexInAfter
}
while (startIndexInBefore < before.length && spacingAndPunctuations.isWordSeparator(before[startIndexInBefore].code)) {
++startIndexInBefore
}
val hasUrlSpans = SpannableStringUtils.hasUrlSpans(before, startIndexInBefore, before.length)
|| SpannableStringUtils.hasUrlSpans(after, 0, endIndexInAfter)
// We don't use TextUtils#concat because it copies all spans without respect to their
// nature. If the text includes a PARAGRAPH span and it has been split, then
// TextUtils#concat will crash when it tries to concat both sides of it.
return TextRange(
SpannableStringUtils.concatWithNonParagraphSuggestionSpansOnly(before, after),
startIndexInBefore, before.length + endIndexInAfter, before.length,
hasUrlSpans
)
}
// actually this should not be in STRING Utils, but only used for getTouchedWordRange
private fun isPartOfCompositionForScript(codePoint: Int, spacingAndPunctuations: SpacingAndPunctuations, script: String) =
spacingAndPunctuations.isWordConnector(codePoint) // We always consider word connectors part of compositions.
// Otherwise, it's part of composition if it's part of script and not a separator.
|| (!spacingAndPunctuations.isWordSeparator(codePoint) && ScriptUtils.isLetterPartOfScript(codePoint, script))
/** split the string on the first of consecutive space only, further consecutive spaces are added to the next split */
fun String.splitOnFirstSpacesOnly(): List<String> {
val out = mutableListOf<String>()

View file

@ -7,11 +7,15 @@
package helium314.keyboard.latin.define
object ProductionFlags {
const val IS_HARDWARE_KEYBOARD_SUPPORTED = true
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
/**
* Include all suggestions from all dictionaries in
* [helium314.keyboard.latin.SuggestedWords.mRawSuggestions].
*/
const val INCLUDE_RAW_SUGGESTIONS = false
}
}

View file

@ -49,7 +49,6 @@ 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;
@ -90,7 +89,6 @@ 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<Long> mCurrentlyPressedHardwareKeys = new TreeSet<>();
// Keeps track of most recently inserted text (multi-character key) for reverting
@ -401,11 +399,7 @@ public final class InputLogic {
// Stop the last recapitalization, if started.
mRecapitalizeStatus.stop();
mWordBeingCorrectedByCursor = null;
// we do not return true if
final boolean oneSidedSelectionMove = hasOrHadSelection
&& ((oldSelEnd == newSelEnd && oldSelStart != newSelStart) || (oldSelEnd != newSelEnd && oldSelStart == newSelStart));
return !oneSidedSelectionMove;
return true;
}
public boolean moveCursorByAndReturnIfInsideComposingWord(int distance) {
@ -649,8 +643,7 @@ public final class InputLogic {
*/
private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction,
final String currentKeyboardScript, final LatinIME.UIHandler handler) {
final int keyCode = event.getMKeyCode();
switch (keyCode) {
switch (event.getMKeyCode()) {
case KeyCode.DELETE:
handleBackspaceEvent(event, inputTransaction, currentKeyboardScript);
// Backspace is a functional key, but it affects the contents of the editor.
@ -693,7 +686,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,
keyCode, 0, event.getMX(), event.getMY(), event.isKeyRepeat());
event.getMKeyCode(), 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.
@ -724,43 +717,37 @@ public final class InputLogic {
if (mConnection.hasSelection()) {
mConnection.copyText(true);
// fake delete keypress to remove the text
final Event backspaceEvent = Event.createSoftwareKeypressEvent(KeyCode.DELETE, 0,
final Event backspaceEvent = LatinIME.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 | event.getMMetaState());
sendDownUpKeyEventWithMetaState(ScriptUtils.isScriptRtl(currentKeyboardScript)?
KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.META_CTRL_ON);
break;
case KeyCode.WORD_RIGHT:
sendDownUpKeyEventWithMetaState(
ScriptUtils.isScriptRtl(currentKeyboardScript) ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.META_CTRL_ON | event.getMMetaState());
sendDownUpKeyEventWithMetaState(ScriptUtils.isScriptRtl(currentKeyboardScript)?
KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.META_CTRL_ON);
break;
case KeyCode.MOVE_START_OF_PAGE:
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);
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);
}
break;
case KeyCode.MOVE_END_OF_PAGE:
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) {
final int selectionStart = mConnection.getExpectedSelectionEnd();
sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_END, KeyEvent.META_CTRL_ON);
if (mConnection.getExpectedSelectionStart() == selectionStart) {
// 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 {
final int newStart = (event.getMMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? selectionStart2 : Integer.MAX_VALUE;
mConnection.setSelection(newStart, Integer.MAX_VALUE);
mConnection.setSelection(Integer.MAX_VALUE, 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);
@ -779,11 +766,6 @@ 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.requestHideSelf(0);
break;
case KeyCode.VOICE_INPUT:
// switching to shortcut IME, shift state, keyboard,... is handled by LatinIME,
// {@link KeyboardSwitcher#onEvent(Event)}, or {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
@ -792,20 +774,23 @@ 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(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;
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_<xxx>
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;
}
}
// unknown event
Log.e(TAG, "unknown event, key code: "+keyCode+", meta: "+event.getMMetaState());
if (DebugFlags.DEBUG_ENABLED)
throw new RuntimeException("Unknown event");
throw new RuntimeException("Unknown key code : " + event.getMKeyCode());
}
}

View file

@ -58,7 +58,6 @@ 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

View file

@ -67,7 +67,6 @@ 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";

View file

@ -54,7 +54,6 @@ 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;
@ -170,7 +169,6 @@ 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);

View file

@ -116,7 +116,6 @@ class SuggestionStripView(context: Context, attrs: AttributeSet?, defStyle: Int)
private val toolbarArrowIcon = KeyboardIconsSet.instance.getNewDrawable(KeyboardIconsSet.NAME_TOOLBAR_KEY, context)
private val defaultToolbarBackground: Drawable = toolbarExpandKey.background
private val enabledToolKeyBackground = GradientDrawable()
private var direction = 1 // 1 if LTR, -1 if RTL
init {
val colors = Settings.getValues().mColors
@ -172,6 +171,7 @@ class SuggestionStripView(context: Context, attrs: AttributeSet?, defStyle: Int)
private lateinit var listener: Listener
private var suggestedWords = SuggestedWords.getEmptyInstance()
private var startIndexOfMoreSuggestions = 0
private var direction = 1 // 1 if LTR, -1 if RTL
private var isExternalSuggestionVisible = false // Required to disable the more suggestions if other suggestions are visible
private val layoutHelper = SuggestionStripLayoutHelper(context, attrs, defStyle, wordViews, dividerViews, debugInfoViews)
private val moreSuggestionsView = moreSuggestionsContainer.findViewById<MoreSuggestionsView>(R.id.more_suggestions_view).apply {
@ -234,7 +234,6 @@ class SuggestionStripView(context: Context, attrs: AttributeSet?, defStyle: Int)
context, suggestedWords, suggestionsStrip, this
)
isExternalSuggestionVisible = false
updateKeys();
}
fun setExternalSuggestionView(view: View?) {

View file

@ -101,10 +101,6 @@ 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()

View file

@ -1,26 +0,0 @@
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")
}
}

View file

@ -7,13 +7,9 @@
package helium314.keyboard.latin.utils;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import androidx.annotation.NonNull;
import java.util.Arrays;
import java.util.Objects;
/**
* Represents a range of text, relative to the current cursor position.
@ -99,28 +95,6 @@ public final class TextRange {
return writeIndex == readIndex ? spans : Arrays.copyOfRange(spans, 0, writeIndex);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof TextRange textRange)) return false;
return mWordAtCursorStartIndex == textRange.mWordAtCursorStartIndex
&& mWordAtCursorEndIndex == textRange.mWordAtCursorEndIndex
&& mCursorIndex == textRange.mCursorIndex
&& mHasUrlSpans == textRange.mHasUrlSpans
&& TextUtils.equals(mTextAtCursor, textRange.mTextAtCursor)
&& TextUtils.equals(mWord, textRange.mWord);
}
@Override
public int hashCode() {
return Objects.hash(mTextAtCursor, mWordAtCursorStartIndex, mWordAtCursorEndIndex, mCursorIndex, mWord, mHasUrlSpans);
}
@NonNull
@Override
public String toString() {
return mTextAtCursor + ", " + mWord + ", " + mCursorIndex;
}
public TextRange(final CharSequence textAtCursor, final int wordAtCursorStartIndex,
final int wordAtCursorEndIndex, final int cursorIndex, final boolean hasUrlSpans) {
if (wordAtCursorStartIndex < 0 || cursorIndex < wordAtCursorStartIndex
@ -135,4 +109,4 @@ public final class TextRange {
mHasUrlSpans = hasUrlSpans;
mWord = mTextAtCursor.subSequence(mWordAtCursorStartIndex, mWordAtCursorEndIndex);
}
}
}

View file

@ -16,7 +16,6 @@ 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
@ -46,7 +45,6 @@ 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
@ -243,8 +241,7 @@ fun ExpandableSearchField(
}) { CloseIcon(android.R.string.cancel) } },
singleLine = true,
colors = colors,
textStyle = contentTextDirectionStyle,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search)
textStyle = contentTextDirectionStyle
)
}
}

View file

@ -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<Locale>({ 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(":")

View file

@ -79,8 +79,7 @@ fun AppearanceScreen(
SettingsWithoutKey.CUSTOM_FONT,
Settings.PREF_FONT_SCALE,
Settings.PREF_EMOJI_FONT_SCALE,
if (prefs.getFloat(Settings.PREF_EMOJI_FONT_SCALE, Defaults.PREF_EMOJI_FONT_SCALE) != 1f)
Settings.PREF_EMOJI_KEY_FIT else null,
Settings.PREF_EMOJI_KEY_FIT,
if (prefs.getInt(Settings.PREF_EMOJI_MAX_SDK, Defaults.PREF_EMOJI_MAX_SDK) >= 24)
Settings.PREF_EMOJI_SKIN_TONE else null,
)
@ -110,7 +109,6 @@ fun createAppearanceSettings(context: Context) = listOf(
prefs.edit().remove(Settings.PREF_THEME_COLORS_NIGHT).apply()
}
KeyboardIconsSet.needsReload = true // only relevant for Settings.PREF_CUSTOM_ICON_NAMES
KeyboardSwitcher.getInstance().setThemeNeedsReload()
}
},
Setting(context, Settings.PREF_ICON_STYLE, R.string.icon_style) { setting ->

View file

@ -57,11 +57,8 @@ fun DictionaryScreen(
val enabledLanguages = SubtypeSettings.getEnabledSubtypes(true).map { it.locale().language }
val cachedDictFolders = DictionaryInfoUtils.getCacheDirectories(ctx).map { it.name }
val comparer = compareBy<Locale>({ it.language !in enabledLanguages }, { it.toLanguageTag() !in cachedDictFolders}, { it.displayName })
val dictionaryLocales = remember {
getDictionaryLocales(ctx).sortedWith(comparer).toMutableList().apply {
add(0, Locale(SubtypeLocaleUtils.NO_LANGUAGE))
}
}
val dictionaryLocales = remember { getDictionaryLocales(ctx).sortedWith(comparer).toMutableList() }
dictionaryLocales.add(0, Locale(SubtypeLocaleUtils.NO_LANGUAGE))
var selectedLocale: Locale? by remember { mutableStateOf(null) }
var showAddDictDialog by remember { mutableStateOf(false) }
val dictPicker = dictionaryFilePicker(selectedLocale)

View file

@ -20,15 +20,12 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -86,11 +83,6 @@ fun PersonalDictionaryScreen(
)
if (selectedWord != null) {
val selWord = selectedWord!!
val focusRequester = remember { FocusRequester() }
LaunchedEffect(selectedWord) {
if (selWord.word == "" && selWord.weight == null && selWord.shortcut == null)
focusRequester.requestFocus() // user clicked add word
}
var newWord by remember { mutableStateOf(selWord) }
var newLocale by remember { mutableStateOf(locale) }
val wordValid = (newWord.word == selWord.word && locale == newLocale) || !doesWordExist(newWord.word, newLocale, ctx)
@ -127,7 +119,7 @@ fun PersonalDictionaryScreen(
TextField(
value = newWord.word,
onValueChange = { newWord = newWord.copy(word = it) },
modifier = Modifier.fillMaxWidth().focusRequester(focusRequester),
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
Row(

View file

@ -57,7 +57,6 @@ 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 })
@ -112,14 +111,6 @@ 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)
{

View file

@ -24,7 +24,6 @@ import helium314.keyboard.latin.settings.Defaults
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.JniUtils
import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.ToolbarMode
import helium314.keyboard.latin.utils.getActivity
import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.settings.NextScreenIcon
@ -50,8 +49,7 @@ fun TextCorrectionScreen(
if ((b?.value ?: 0) < 0)
Log.v("irrelevant", "stupid way to trigger recomposition on preference change")
val autocorrectEnabled = prefs.getBoolean(Settings.PREF_AUTO_CORRECTION, Defaults.PREF_AUTO_CORRECTION)
val suggestionsVisible = Settings.readToolbarMode(prefs) in setOf(ToolbarMode.SUGGESTION_STRIP, ToolbarMode.EXPANDABLE)
val suggestionsEnabled = suggestionsVisible && prefs.getBoolean(Settings.PREF_SHOW_SUGGESTIONS, Defaults.PREF_SHOW_SUGGESTIONS)
val suggestionsEnabled = prefs.getBoolean(Settings.PREF_SHOW_SUGGESTIONS, Defaults.PREF_SHOW_SUGGESTIONS)
val gestureEnabled = JniUtils.sHaveGestureLib && prefs.getBoolean(Settings.PREF_GESTURE_INPUT, Defaults.PREF_GESTURE_INPUT)
val items = listOf(
SettingsWithoutKey.EDIT_PERSONAL_DICTIONARY,
@ -71,7 +69,7 @@ fun TextCorrectionScreen(
if (gestureEnabled) Settings.PREF_AUTOSPACE_AFTER_GESTURE_TYPING else null,
Settings.PREF_SHIFT_REMOVES_AUTOSPACE,
R.string.settings_category_suggestions,
if (suggestionsVisible) Settings.PREF_SHOW_SUGGESTIONS else null,
Settings.PREF_SHOW_SUGGESTIONS,
if (suggestionsEnabled) Settings.PREF_ALWAYS_SHOW_SUGGESTIONS else null,
if (suggestionsEnabled && prefs.getBoolean(Settings.PREF_ALWAYS_SHOW_SUGGESTIONS, Defaults.PREF_ALWAYS_SHOW_SUGGESTIONS))
Settings.PREF_ALWAYS_SHOW_SUGGESTIONS_EXCEPT_WEB_TEXT else null,

View file

@ -10,15 +10,6 @@
android:layout_height="wrap_content"
android:orientation="vertical"
>
<helium314.keyboard.keyboard.PopupTextView
android:id="@+id/description_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="8dp"
android:paddingBottom="4dp"
android:visibility="gone"
android:maxLines="1" android:ellipsize="end"
style="?attr/popupKeysKeyboardViewStyle" />
<helium314.keyboard.keyboard.PopupKeysKeyboardView
android:id="@+id/popup_keys_keyboard_view"
android:layout_width="wrap_content"

View file

@ -426,13 +426,4 @@
<string name="use_apps_dict_summary">Namen der installierten Apps für Vorschläge und Korrekturen verwenden</string>
<string name="prefs_emoji_skin_tone">Standard-Emoji-Hautton</string>
<string name="prefs_emoji_skin_tone_neutral">Neutral</string>
<string name="toolbar_mode_expandable">Symbolleiste Tasten und Vorschläge</string>
<string name="toolbar_mode_toolbar_keys">Symbolleiste nur Tasten</string>
<string name="popup_order_and_hint_source">Popup Tasten Reihenfolge und Vorschlag Quelle</string>
<string name="toolbar_mode_hidden">Versteckt</string>
<string name="toolbar_hiding_global">Auch Zwischenablage und Emoji Symbolleisten verstecken</string>
<string name="subtype_with_layout_generic">%1$s (%2$s)</string>
<string name="landscape">Querformat</string>
<string name="toolbar_mode_suggestion_strip">Nur Vorschläge</string>
<string name="toolbar_mode">Symbolleiste Modus</string>
</resources>

View file

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2008 The Android Open Source Project
modified
SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
--><resources>
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="use_contacts_dict">"Look up contact names"</string>
<string name="vibrate_on_keypress">"Vibrate on keypress"</string>
<string name="sound_on_keypress">"Sound on keypress"</string>
@ -108,29 +109,4 @@
<string name="label_search_key">"Search"</string>
<string name="label_pause_key">"Pause"</string>
<string name="label_wait_key">"Wait"</string>
<string name="split_spacer_scale_landscape">Split distance (landscape)</string>
<string name="spell_checker_service_name">HeliBoard Spell Checker</string>
<string name="ime_settings">HeliBoard Settings</string>
<string name="android_spell_checker_settings">HeliBoard Spell Checker Settings</string>
<string name="split_spacer_scale">Split distance</string>
<string name="show_emoji_key">Emoji key</string>
<string name="switch_language">Switch Language</string>
<string name="settings_category_input">Input</string>
<string name="settings_category_additional_keys">Additional keys</string>
<string name="settings_category_suggestions">Suggestions</string>
<string name="vibrate_in_dnd_mode">Vibrate in do not disturb mode</string>
<string name="settings_category_correction">Corrections</string>
<string name="enable_split_keyboard_landscape">Enable split keyboard (landscape)</string>
<string name="disable_personalized_dicts_message">Warning: Disabling this setting will clear learned data</string>
<string name="settings_category_clipboard_history">Clipboard history</string>
<string name="settings_category_space">Space</string>
<string name="settings_category_experimental">Experimental</string>
<string name="settings_category_miscellaneous">Uncategorised</string>
<string name="language_switch_key_switch_both">Switch both</string>
<string name="language_switch_key_behavior">Language switch key behavior</string>
<string name="abbreviation_unit_minutes">%s min</string>
<string name="settings_no_limit">No limit</string>
<string name="add_to_personal_dictionary">Add learnt words to personal dictionary</string>
<string name="use_apps_dict">Look up app names</string>
<string name="use_apps_dict_summary">Use names of installed apps for suggestions and corrections</string>
</resources>

View file

@ -124,7 +124,7 @@ Nouveau dictionnaire:
<string name="dictionary_file_wrong_script">Erreur: script non compatible avec ce clavier</string>
<string name="dictionary_file_wrong_locale_ok">"Utiliser quand même"</string>
<string name="dictionary_load_error">"Erreur de chargement du fichier dictionnaire"</string>
<string name="dictionary_available">Dictionnaires disponibles</string>
<string name="dictionary_available">"Dictionnaire disponible"</string>
<string name="last_update">"Dernière mise à jour"</string>
<string name="delete">"Supprimer"</string>
<string name="version_text">Version %s</string>

View file

@ -428,19 +428,11 @@
<string name="timestamp_format_title">Formato per il tasto data/ora</string>
<string name="subtype_dag">Dagbani</string>
<string name="subtype_st">Sesotho</string>
<string name="prefs_emoji_key_fit">Ridimensiona il tasto emoji in base alla dimensione dei caratteri</string>
<string name="prefs_emoji_key_fit">Scala la dimensione dei tasti con la dimensione dei caratteri</string>
<string name="subtype_with_layout_generic">%1$s (%2$s)</string>
<string name="backup_restored">Backup ripristinato</string>
<string name="use_apps_dict">Suggerisci nomi delle app installate</string>
<string name="use_apps_dict_summary">Aggiunge i nomi delle app installate alla lista di suggerimenti e correzioni</string>
<string name="prefs_emoji_skin_tone">Tonalità di pelle predefinita per le emoji</string>
<string name="prefs_emoji_skin_tone_neutral">Neutro (giallo)</string>
<string name="toolbar_mode_expandable">Tasti funzione e suggerimenti</string>
<string name="toolbar_mode_toolbar_keys">Solo tasti funzione</string>
<string name="toolbar_mode_hidden">Nascosta</string>
<string name="toolbar_hiding_global">Nascondi anche le barre degli appunti e delle emoji</string>
<string name="landscape">Orizzontale</string>
<string name="popup_order_and_hint_source">Ordine dei caratteri popup e fonti dei suggerimenti</string>
<string name="toolbar_mode_suggestion_strip">Solo suggerimenti</string>
<string name="toolbar_mode">Modalità</string>
</resources>

View file

@ -17,10 +17,6 @@
<string name="vibrate_in_dnd_mode">Vibrate in do not disturb mode</string>
<!-- Option to play back sound on keypress in soft keyboard -->
<string name="sound_on_keypress">Sound on keypress</string>
<!-- Option to show emoji descriptions on long press -->
<string name="show_emoji_descriptions">Show emoji description on long press</string>
<!-- Description for option to show emoji descriptions on long press -->
<string name="show_emoji_descriptions_summary">Requires an emoji dictionary</string>
<!-- Option to control whether or not to show a popup with a larger font on each key press. -->
<string name="popup_on_keypress">Popup on keypress</string>
<!-- Settings screen title for preferences-->

View file

@ -79,7 +79,6 @@
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
@ -829,15 +828,6 @@
android:imeSubtypeMode="keyboard"
android:imeSubtypeExtraValue="KeyboardLayoutSet=MAIN:qwertz,AsciiCapable,EmojiCapable"
/>
<subtype android:icon="@drawable/ic_ime_switcher"
android:label="@string/subtype_generic"
android:subtypeId="0x37885a0c"
android:imeSubtypeLocale="la"
android:languageTag="la"
android:imeSubtypeMode="keyboard"
android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection,EmojiCapable"
android:isAsciiCapable="true"
/>
<subtype android:icon="@drawable/ic_ime_switcher"
android:label="@string/subtype_generic"
android:subtypeId="0x1ec2b4c9"
@ -1053,7 +1043,7 @@
android:imeSubtypeMode="keyboard"
android:imeSubtypeExtraValue="KeyboardLayoutSet=MAIN:russian_extended,SupportTouchPositionCorrection,EmojiCapable"
android:isAsciiCapable="false"
/>
<subtype android:icon="@drawable/ic_ime_switcher"
android:label="@string/subtype_generic_student"

View file

@ -92,9 +92,6 @@
<subtype
android:label="@string/subtype_generic"
android:subtypeLocale="kn" />
<subtype
android:label="@string/subtype_generic"
android:subtypeLocale="la" />
<subtype
android:label="@string/subtype_generic"
android:subtypeLocale="lb" />

View file

@ -4,13 +4,9 @@ package helium314.keyboard.latin
import androidx.test.core.app.ApplicationProvider
import helium314.keyboard.ShadowInputMethodManager2
import helium314.keyboard.latin.common.StringUtils
import helium314.keyboard.latin.common.endsWithWordCodepoint
import helium314.keyboard.latin.common.getFullEmojiAtEnd
import helium314.keyboard.latin.common.getTouchedWordRange
import helium314.keyboard.latin.common.nonWordCodePointAndNoSpaceBeforeCursor
import helium314.keyboard.latin.settings.SpacingAndPunctuations
import helium314.keyboard.latin.utils.ScriptUtils
import helium314.keyboard.latin.utils.TextRange
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@ -64,54 +60,6 @@ class StringUtilsTest {
assert(nonWordCodePointAndNoSpaceBeforeCursor("th.is", sp))
}
@Test fun `is word-like at end`() {
val sp = SpacingAndPunctuations(ApplicationProvider.getApplicationContext<App>().resources, false)
assert(!endsWithWordCodepoint("", sp))
assert(endsWithWordCodepoint("don'", sp))
assert(!endsWithWordCodepoint("hello!", sp))
assert(!endsWithWordCodepoint("when ", sp))
assert(endsWithWordCodepoint("3-", sp)) // todo: this seems wrong
assert(endsWithWordCodepoint("5'", sp)) // todo: this seems wrong
assert(endsWithWordCodepoint("1", sp)) // todo: this seems wrong
assert(endsWithWordCodepoint("a-", sp))
assert(!endsWithWordCodepoint("--", sp))
}
@Test fun `get touched text range`() {
val sp = SpacingAndPunctuations(ApplicationProvider.getApplicationContext<App>().resources, false)
val spUrl = SpacingAndPunctuations(ApplicationProvider.getApplicationContext<App>().resources, true)
val script = ScriptUtils.SCRIPT_LATIN
checkTextRange("blabla this is v", "ery good", sp, script, 15, 19)
checkTextRange(".hel", "lo...", sp, script, 1, 6)
checkTextRange("(hi", ")", sp, script, 1, 3)
checkTextRange("", "word", sp, script, 0, 4)
checkTextRange("mail: blorb@", "florb.com or", sp, script, 12, 17)
checkTextRange("mail: blorb@", "florb.com or", spUrl, script, 6, 21)
checkTextRange("mail: blor", "b@florb.com or", sp, script, 6, 11)
checkTextRange("mail: blor", "b@florb.com or", spUrl, script, 6, 21)
checkTextRange("mail: blorb@f", "lorb.com or", sp, script, 12, 17)
checkTextRange("mail: blorb@f", "lorb.com or", spUrl, script, 6, 21)
checkTextRange("http://exam", "ple.com", sp, script, 7, 14)
checkTextRange("http://exam", "ple.com", spUrl, script, 7, 18)
checkTextRange("http://example.", "com", sp, script, 15, 18)
checkTextRange("http://example.", "com", spUrl, script, 7, 18)
checkTextRange("htt", "p://example.com", sp, script, 0, 4)
checkTextRange("htt", "p://example.com", spUrl, script, 0, 18)
checkTextRange("http:/", "/example.com", sp, script, 6, 6)
checkTextRange("http:/", "/example.com", spUrl, script, 0, 18)
checkTextRange("..", ".", spUrl, script, 2, 2)
checkTextRange("...", "", spUrl, script, 3, 3)
// todo: these are bad cases of url detection
// also: sometimesWordConnectors are for URL and should be named accordingly
checkTextRange("@@@", "@@@", spUrl, script, 0, 6)
checkTextRange("a...", "", spUrl, script, 0, 4)
checkTextRange("@@@", "", spUrl, script, 0, 3)
}
@Test fun detectEmojisAtEnd() {
assertEquals("", getFullEmojiAtEnd("\uD83C\uDF83 "))
assertEquals("", getFullEmojiAtEnd("a"))
@ -139,10 +87,4 @@ class StringUtilsTest {
// could help towards fully fixing https://github.com/Helium314/HeliBoard/issues/22
// though this might be tricky, as some emojis will show as one on new Android versions, and
// as two on older versions
private fun checkTextRange(before: String, after: String, sp: SpacingAndPunctuations, script: String, wordStart: Int, WordEnd: Int) {
val got = getTouchedWordRange(before, after, script, sp)
val wanted = TextRange(before + after, wordStart, WordEnd, before.length, false)
assertEquals(wanted, got)
}
}

View file

@ -1,10 +0,0 @@
* إضافة أوضاع شريط الأدوات (تسمح بإخفاء شريط الأدوات)
* إضافة بعض متغيرات الرموز التعبيرية المفقودة
* تحسين شاشة النوع الفرعي وحوار القاموس
* إصلاح الألوان عند فرض الوضع الداكن
* نقل معظم إعدادات مقياس الوضع الرأسي/الأفقي إلى حوار
* إزالة ترجمات السلاسل الموسومة على أنها غير قابلة للترجمة
* إصلاح اتجاه سهم الشاشة التالية للغات اليمين إلى اليسار
* إصلاح التحميل الصحيح للغة العبرية على أندرويد 15
* توفير لوحة مفاتيح أساسية على الأقل عندما لا تعمل المكتبة على الإطلاق
* إصلاحات أخطاء طفيفة

View file

@ -1,10 +0,0 @@
* إضافة أوضاع شريط الأدوات (تسمح بإخفاء شريط الأدوات)
* إضافة بعض متغيرات الرموز التعبيرية المفقودة
* تحسين شاشة النوع الفرعي وحوار القاموس
* إصلاح الألوان عند فرض الوضع الداكن
* نقل معظم إعدادات مقياس الوضع الرأسي/الأفقي إلى حوار
* إزالة ترجمات السلاسل الموسومة على أنها غير قابلة للترجمة
* إصلاح اتجاه سهم الشاشة التالية للغات اليمين إلى اليسار
* إصلاح التحميل الصحيح للغة العبرية على أندرويد 15
* توفير لوحة مفاتيح أساسية على الأقل عندما لا تعمل المكتبة على الإطلاق
* إصلاحات أخطاء طفيفة

View file

@ -1,10 +0,0 @@
* af. modes de barra d'eines (permet ocultar-la)
* af. alguns emojis
* millorar pantalla de subtipus i el diàleg del dicc.
* corr. colors en forçar el mode fosc
* moure la majoria de la configuració d'escala vert/horiz a un diàleg
* elim. les trads. de les cadenes marcades com a no traduïbles
* corr. direcció de fletxa de la pantalla següent a idiomes RTL
* corr. la càrrega correcta de la config. regional hebrea a Android 15
* tenir almenys un teclat bàsic quan la biblio. no funciona
* corr. etc

View file

@ -1,10 +0,0 @@
* af. modes de barra d'eines (permet ocultar-la)
* af. alguns emojis
* millorar pantalla de subtipus i el diàleg del dicc.
* corr. colors en forçar el mode fosc
* moure la majoria de la configuració d'escala vert/horiz a un diàleg
* elim. les trads. de les cadenes marcades com a no traduïbles
* corr. direcció de fletxa de la pantalla següent a idiomes RTL
* corr. la càrrega correcta de la config. regional hebrea a Android 15
* tenir almenys un teclat bàsic quan la biblio. no funciona
* corr. etc

View file

@ -1,10 +0,0 @@
* přidány režimy panelu nástrojů (umožňuje skrýt p. n.)
* přidány chybějící varianty emodži
* vylepšena obrazovka podtypů a dialog slovníku
* opraveny barvy při vynuceném tmavém režimu
* přesun většiny nastavení měřítka do dialogu
* odstraněny překlady řetězců označených jako nepřekládatelné
* opraven směr šipky „dále“ pro jazyky RTL
* opraveno správné načítání hebrejského jazyka na Androidu 15
* zajištění alespoň základní klávesnice, když knihovna vůbec nefunguje
* drobné opravy chyb

View file

@ -1,10 +0,0 @@
* přidány režimy panelu nástrojů (umožňuje skrýt p. n.)
* přidány chybějící varianty emodži
* vylepšena obrazovka podtypů a dialog slovníku
* opraveny barvy při vynuceném tmavém režimu
* přesun většiny nastavení měřítka do dialogu
* odstraněny překlady řetězců označených jako nepřekládatelné
* opraven směr šipky „dále“ pro jazyky RTL
* opraveno správné načítání hebrejského jazyka na Androidu 15
* zajištění alespoň základní klávesnice, když knihovna vůbec nefunguje
* drobné opravy chyb

View file

@ -1,10 +0,0 @@
* add toolbar modes (allows hiding toolbar)
* add some missing emoji variants
* improve subtype screen and dictionary dialog
* fix colors when forcing dark mode
* move most of the portrait / landscape scale settings into a dialog
* remove translations of strings marked as non-translatable
* fix next-screen arrow direction for RTL languages
* fix proper loading of Hebrew locale on Android 15
* have at least a basic keyboard when library doesn't work at all
* minor bug fixes

View file

@ -1,10 +0,0 @@
* הוספת מצבי סרגל כלים (מאפשר הסתרת סרגלי כלים)
* הוספת גווני עור חסרים לחלק מהאמוג'ים
* שיפור מסך הכתב התחתי ודו-שיח המילון
* תיקון הצבעים בעת כפיית מצב כהה
* העברת מרבית הגדרות קנה-המידה של מצב אנכי / אופקי לדו-שיח
* הסרת תרגומי מחרוזות המסומנות כבלתי ניתנות לתרגום
* תיקון כיווני החץ למעבר למסך הבא עבור שםות הנכתבות מימין לשמאל
* תיקון טעינה נכונה של הגדרות השפה העברית באנדרואיד 15
* הצגת מקלדת בסיסית לכל הפחות כאשר ספריה איננה עובדת כלל
* תיקוני שגיאות שוליות

View file

@ -1,10 +0,0 @@
* הוספת מצבי סרגל כלים (מאפשר הסתרת סרגלי כלים)
* הוספת גווני עור חסרים לחלק מהאמוג'ים
* שיפור מסך הכתב התחתי ודו-שיח המילון
* תיקון הצבעים בעת כפיית מצב כהה
* העברת מרבית הגדרות קנה-המידה של מצב אנכי / אופקי לדו-שיח
* הסרת תרגומי מחרוזות המסומנות כבלתי ניתנות לתרגום
* תיקון כיווני החץ למעבר למסך הבא עבור שםות הנכתבות מימין לשמאל
* תיקון טעינה נכונה של הגדרות השפה העברית באנדרואיד 15
* הצגת מקלדת בסיסית לכל הפחות כאשר ספריה איננה עובדת כלל
* תיקוני שגיאות שוליות

View file

@ -1,10 +0,0 @@
* добавлены режимы панели инструментов (появилась возможность скрывать панель)
* добавлены некоторые недостающие варианты эмодзи
* улучшены экран выбора подтипа и диалог словаря
* исправлены цвета при принудительном тёмном режиме
* большинство настроек масштабирования для портретного и ландшафтного режимов перенесены в отдельный диалог
* удалены переводы строк, отмеченных как не подлежащие переводу
* исправлено направление стрелки перехода на следующий экран для языков с письмом справа налево (RTL)
* исправлена корректная загрузка иврита на Android 15
* обеспечена работа хотя бы базовой клавиатуры, если библиотека не функционирует вовсе
* мелкие исправления ошибок

View file

@ -1,10 +0,0 @@
* добавлены режимы панели инструментов (появилась возможность скрывать панель)
* добавлены некоторые недостающие варианты эмодзи
* улучшены экран выбора подтипа и диалог словаря
* исправлены цвета при принудительном тёмном режиме
* большинство настроек масштабирования для портретного и ландшафтного режимов перенесены в отдельный диалог
* удалены переводы строк, отмеченных как не подлежащие переводу
* исправлено направление стрелки перехода на следующий экран для языков с письмом справа налево (RTL)
* исправлена корректная загрузка иврита на Android 15
* обеспечена работа хотя бы базовой клавиатуры, если библиотека не функционирует вовсе
* мелкие исправления ошибок

View file

@ -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#L250-L282) in the section _keyLabelFlags_ for names and numeric values
* `labelFlags`: allows specific effects, see [here](app/src/main/res/values/attrs.xml#L251-L287) 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,12 +106,6 @@ 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)

View file

@ -33,6 +33,36 @@ 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)
@ -88,6 +118,9 @@ 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
@ -103,5 +136,34 @@ 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
}
}

View file

@ -3866,5 +3866,31 @@ 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)