Compare commits

...

26 commits
v3.2 ... main

Author SHA1 Message Date
Eran Leshem
d2217f099a
Export DictionaryPackInstallBroadcastReceiver (#1756) 2025-06-29 20:02:29 +02:00
Eran Leshem
4f58e5d013
Support Latin (#1749) 2025-06-28 16:25:15 +02:00
Helium314
10a5eab3bc don't change insets when view is hidden by hardware keyboard
seems to happen only in some apps, e.g. simplemobiletools notes
issue only occurs when keyboard is suppressed after it has been shown and a new input view is created

fixes GH-1455
fixes GH-702
2025-06-28 16:20:54 +02:00
Helium314
8d876a15f0 fix crash in isShowingKeyboardId with hardware keyboard 2025-06-28 15:16:25 +02:00
Helium314
945a700b71 do the correct checks when loading blacklist file
fixes GH-1475
2025-06-28 14:11:00 +02:00
Helium314
53a899794e register OnSharedPreferenceChangeListener in LatinIME.onCreate
because it's unregistered in onDestroy
(registering twice is not an issue)

though maybe not unregistering would be more correct, as it's registered in app.onCreate?

fixes GH-1670
2025-06-24 06:27:51 +02:00
Eran Leshem
c2068224a0
Change settings search keyboard action to Search. (#1734) 2025-06-22 12:23:46 +02:00
Eran Leshem
9193c95c2b
Ignore spacer columns when calculating emoji page size (#1721) 2025-06-22 12:18:37 +02:00
Helium314
76ebf99921 deal with Android refusing to add word to user dictionary
fixes GH-1735
2025-06-22 12:11:20 +02:00
Helium314
77a728e390 don't pre-select unavailable locale when adding dictionary 2025-06-19 09:35:08 +02:00
Helium314
7b0c511857 add logging when hiding window
may help with GH-1710
2025-06-16 19:43:32 +02:00
Eran Leshem
e062efb3d4
Always default popup VisibleOffset to keyboard.mVerticalGap (#1722) 2025-06-16 19:14:47 +02:00
lurebat
d356f9f54b
Add broadcast intent keys (#1675)
This commit introduces three new keycodes: SEND_INTENT_ONE, SEND_INTENT_TWO, and SEND_INTENT_THREE.

When these keys are pressed, a broadcast intent is sent with the action `helium314.keyboard.latin.ACTION_SEND_INTENT`. The intent includes an extra `EXTRA_NUMBER` (integer) indicating which of the three keys was pressed (1, 2, or 3).

This functionality allows external applications to react to these specific key presses.
2025-06-16 18:55:15 +02:00
Helium314
9549389be7 use different method for hiding keyboard
fixes GH-1719
2025-06-15 21:09:53 +02:00
Helium314
2dc838798d use hasLabels for TLD popups
and add documentation
2025-06-15 19:49:32 +02:00
Helium314
24a2eddc1f avoid StringIndexOutOfBoundsException when initializing stringbuilder from other stringbuilder 2025-06-15 17:43:06 +02:00
Helium314
ef3191a2eb prepare for selecting text with cursor movement keys when shift is enabled manually
not enabled, as it's not working reliably
2025-06-15 17:41:05 +02:00
Helium314
e430d13c4a improvements to hardware keyboard handling 2025-06-14 12:53:46 +02:00
Helium314
9c97a6b9bf move some key press logic from LatinIme into KeyboardActionListener 2025-06-14 12:16:29 +02:00
Helium314
a37de668c0 enable hardware keyboard handling again, with improved handling 2025-06-14 11:29:26 +02:00
Helium314
63dad1549e redirect suggestion strip view code input to KeyboardActionListener 2025-06-13 20:57:49 +02:00
Helium314
f06a553d2c make sure selection start is before end
this should not be necessary according to documentation, but apparently there are apps (even by Google!) that can't deal with documented behavior
see comments in GH-1512
2025-06-13 20:49:53 +02:00
Eran Leshem
e9e3bdac17
Display emoji descriptions in popups (#1542) 2025-06-11 22:17:26 +02:00
Eran Leshem
d5cd18ecaa
Remove regional indicator symbol letters from emoji list (#1680) 2025-06-11 22:17:05 +02:00
Eran Leshem
871ac110ad
Move non-com global TLDs to end of list (#1668) 2025-06-11 22:16:39 +02:00
Helium314
49c9d77978 try more aggressive proguard settings 2025-06-11 22:15:49 +02:00
47 changed files with 740 additions and 472 deletions

View file

@ -18,7 +18,7 @@ android {
abiFilters.clear()
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
}
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
buildTypes {

View file

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

View file

@ -139,6 +139,16 @@ class Event private constructor(
null, if (isKeyRepeat) FLAG_REPEAT else FLAG_NONE, null)
}
// A helper method to split the code point and the key code.
// todo: Ultimately, they should not be squashed into the same variable, and this method should be removed.
@JvmStatic
fun createSoftwareKeypressEvent(keyCodeOrCodePoint: Int, metaState: Int, keyX: Int, keyY: Int, isKeyRepeat: Boolean) =
if (keyCodeOrCodePoint <= 0) {
createSoftwareKeypressEvent(NOT_A_CODE_POINT, keyCodeOrCodePoint, metaState, keyX, keyY, isKeyRepeat)
} else {
createSoftwareKeypressEvent(keyCodeOrCodePoint, NOT_A_KEY_CODE, metaState, keyX, keyY, isKeyRepeat)
}
fun createHardwareKeypressEvent(codePoint: Int, keyCode: Int, metaState: Int, next: Event?, isKeyRepeat: Boolean): Event {
return Event(EVENT_TYPE_INPUT_KEYPRESS, null, codePoint, keyCode, metaState,
Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
@ -256,11 +266,9 @@ class Event private constructor(
source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags or FLAG_COMBINING, source.mNextEvent)
}
fun createNotHandledEvent(): Event {
return Event(EVENT_TYPE_NOT_HANDLED, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE, 0,
val notHandledEvent = Event(EVENT_TYPE_NOT_HANDLED, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE, 0,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, null, FLAG_NONE, null)
}
}
// This method is private - to create a new event, use one of the create* utility methods.
init {

View file

@ -24,7 +24,8 @@ class HardwareKeyboardEventDecoder(val mDeviceId: Int) : HardwareEventDecoder {
// KeyEvent#getUnicodeChar() does not exactly returns a unicode char, but rather a value
// that includes both the unicode char in the lower 21 bits and flags in the upper bits,
// hence the name "codePointAndFlags". {@see KeyEvent#getUnicodeChar()} for more info.
val codePointAndFlags = keyEvent.unicodeChar
val codePointAndFlags = keyEvent.unicodeChar.takeIf { it != 0 }
?: Event.NOT_A_CODE_POINT // KeyEvent has 0 if no codePoint, but that's actually valid so we convert it to -1
// The keyCode is the abstraction used by the KeyEvent to represent different keys that
// do not necessarily map to a unicode character. This represents a physical key, like
// the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock.
@ -48,6 +49,21 @@ class HardwareKeyboardEventDecoder(val mDeviceId: Int) : HardwareEventDecoder {
} else Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, metaState, null, isKeyRepeat)
// If not Enter, then this is just a regular keypress event for a normal character
// that can be committed right away, taking into account the current state.
} else Event.createNotHandledEvent()
} else if (isDpadDirection(keyCode)) {
Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, metaState, null, isKeyRepeat)
// } else if (KeyEvent.isModifierKey(keyCode)) {
// todo: we could synchronize meta state across HW and SW keyboard, but that's more work for little benefit (especially with shift & caps lock)
} else {
Event.notHandledEvent
}
}
companion object {
private fun isDpadDirection(keyCode: Int) = when (keyCode) {
KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.KEYCODE_DPAD_DOWN_LEFT, KeyEvent.KEYCODE_DPAD_DOWN_RIGHT, KeyEvent.KEYCODE_DPAD_UP_RIGHT,
KeyEvent.KEYCODE_DPAD_UP_LEFT -> true
else -> false
}
}
}

View file

@ -6,6 +6,8 @@
package helium314.keyboard.keyboard;
import android.view.KeyEvent;
import helium314.keyboard.latin.common.Constants;
import helium314.keyboard.latin.common.InputPointers;
@ -31,6 +33,12 @@ public interface KeyboardActionListener {
*/
void onReleaseKey(int primaryCode, boolean withSliding);
/** For handling hardware key presses. Returns whether the event was handled. */
boolean onKeyDown(int keyCode, KeyEvent keyEvent);
/** For handling hardware key presses. Returns whether the event was handled. */
boolean onKeyUp(int keyCode, KeyEvent keyEvent);
/**
* Send a key code to the listener.
*
@ -117,6 +125,10 @@ public interface KeyboardActionListener {
@Override
public void onReleaseKey(int primaryCode, boolean withSliding) {}
@Override
public boolean onKeyDown(int keyCode, KeyEvent keyEvent) { return false; }
@Override
public boolean onKeyUp(int keyCode, KeyEvent keyEvent) { return false; }
@Override
public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat) {}
@Override
public void onTextInput(String text) {}

View file

@ -1,16 +1,24 @@
package helium314.keyboard.keyboard
import android.text.InputType
import android.util.SparseArray
import android.view.KeyEvent
import android.view.inputmethod.InputMethodSubtype
import helium314.keyboard.event.Event
import helium314.keyboard.event.HangulEventDecoder
import helium314.keyboard.event.HardwareEventDecoder
import helium314.keyboard.event.HardwareKeyboardEventDecoder
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
import helium314.keyboard.latin.EmojiAltPhysicalKeyDetector
import helium314.keyboard.latin.LatinIME
import helium314.keyboard.latin.RichInputMethodManager
import helium314.keyboard.latin.common.Constants
import helium314.keyboard.latin.common.InputPointers
import helium314.keyboard.latin.common.StringUtils
import helium314.keyboard.latin.common.combiningRange
import helium314.keyboard.latin.common.loopOverCodePoints
import helium314.keyboard.latin.common.loopOverCodePointsBackwards
import helium314.keyboard.latin.define.ProductionFlags
import helium314.keyboard.latin.inputlogic.InputLogic
import helium314.keyboard.latin.settings.Settings
import kotlin.math.abs
@ -19,6 +27,11 @@ import kotlin.math.min
class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inputLogic: InputLogic) : KeyboardActionListener {
private val connection = inputLogic.mConnection
private val emojiAltPhysicalKeyDetector by lazy { EmojiAltPhysicalKeyDetector(latinIME.resources) }
// We expect to have only one decoder in almost all cases, hence the default capacity of 1.
// If it turns out we need several, it will get grown seamlessly.
private val hardwareEventDecoders: SparseArray<HardwareEventDecoder> = SparseArray(1)
private val keyboardSwitcher = KeyboardSwitcher.getInstance()
private val settings = Settings.getInstance()
@ -58,9 +71,62 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
keyboardSwitcher.onReleaseKey(primaryCode, withSliding, latinIME.currentAutoCapsState, latinIME.currentRecapitalizeState)
}
override fun onKeyUp(keyCode: Int, keyEvent: KeyEvent): Boolean {
emojiAltPhysicalKeyDetector.onKeyUp(keyEvent)
if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED)
return false
val keyIdentifier = keyEvent.deviceId.toLong() shl 32 + keyEvent.keyCode
return inputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)
}
override fun onKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean {
emojiAltPhysicalKeyDetector.onKeyDown(keyEvent)
if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED)
return false
val event: Event
if (settings.current.mLocale.language == "ko") { // todo: this does not appear to be the right place
val subtype = keyboardSwitcher.keyboard?.mId?.mSubtype ?: RichInputMethodManager.getInstance().currentSubtype
event = HangulEventDecoder.decodeHardwareKeyEvent(subtype, keyEvent) {
getHardwareKeyEventDecoder(keyEvent.deviceId).decodeHardwareKey(keyEvent)
}
} else {
event = getHardwareKeyEventDecoder(keyEvent.deviceId).decodeHardwareKey(keyEvent)
}
if (event.isHandled) {
inputLogic.onCodeInput(
settings.current, event,
keyboardSwitcher.getKeyboardShiftMode(), // TODO: this is not necessarily correct for a hardware keyboard right now
keyboardSwitcher.getCurrentKeyboardScript(),
latinIME.mHandler
)
return true
}
return false
}
override fun onCodeInput(primaryCode: Int, x: Int, y: Int, isKeyRepeat: Boolean) {
when (primaryCode) {
KeyCode.TOGGLE_AUTOCORRECT -> return Settings.getInstance().toggleAutoCorrect()
KeyCode.TOGGLE_INCOGNITO_MODE -> return Settings.getInstance().toggleAlwaysIncognitoMode()
}
val mkv = keyboardSwitcher.mainKeyboardView
latinIME.onCodeInput(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat)
// checking if the character is a combining accent
val event = if (primaryCode in combiningRange) { // todo: should this be done later, maybe in inputLogic?
Event.createSoftwareDeadEvent(primaryCode, 0, metaState, mkv.getKeyX(x), mkv.getKeyY(y), null)
} else {
// todo:
// setting meta shift should only be done for arrow and similar cursor movement keys
// should only be enabled once it works more reliably (currently depends on app for some reason)
// if (mkv.keyboard?.mId?.isAlphabetShiftedManually == true)
// Event.createSoftwareKeypressEvent(primaryCode, metaState or KeyEvent.META_SHIFT_ON, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat)
// else Event.createSoftwareKeypressEvent(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat)
Event.createSoftwareKeypressEvent(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat)
}
latinIME.onEvent(event)
}
override fun onTextInput(text: String?) = latinIME.onTextInput(text)
@ -257,4 +323,13 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
}
return -min(-actualSteps, text.length)
}
private fun getHardwareKeyEventDecoder(deviceId: Int): HardwareEventDecoder {
hardwareEventDecoders.get(deviceId)?.let { return it }
// TODO: create the decoder according to the specification
val newDecoder = HardwareKeyboardEventDecoder(deviceId)
hardwareEventDecoders.put(deviceId, newDecoder)
return newDecoder
}
}

View file

@ -184,6 +184,11 @@ public final class KeyboardId {
|| mElementId == ELEMENT_ALPHABET_AUTOMATIC_SHIFTED || mElementId == ELEMENT_ALPHABET_MANUAL_SHIFTED;
}
public boolean isAlphabetShiftedManually() {
return mElementId == ELEMENT_ALPHABET_SHIFT_LOCKED || mElementId == ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED
|| mElementId == ELEMENT_ALPHABET_MANUAL_SHIFTED;
}
public boolean isNumberLayout() {
return mElementId == ELEMENT_NUMBER || mElementId == ELEMENT_NUMPAD
|| mElementId == ELEMENT_PHONE || mElementId == ELEMENT_PHONE_SYMBOLS;

View file

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

View file

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

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.OnKeyEventListener;
import helium314.keyboard.keyboard.emoji.EmojiViewCallback;
import helium314.keyboard.keyboard.internal.KeyDrawParams;
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
import helium314.keyboard.latin.R;
@ -39,7 +39,7 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
protected final KeyDetector mKeyDetector;
private Controller mController = EMPTY_CONTROLLER;
protected KeyboardActionListener mListener;
protected OnKeyEventListener mKeyEventListener;
protected EmojiViewCallback mEmojiViewCallback;
private int mOriginX;
private int mOriginY;
private Key mCurrentKey;
@ -122,7 +122,7 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
public void showPopupKeysPanel(final View parentView, final Controller controller,
final int pointX, final int pointY, final KeyboardActionListener listener) {
mListener = listener;
mKeyEventListener = null;
mEmojiViewCallback = null;
showPopupKeysPanelInternal(parentView, controller, pointX, pointY);
}
@ -131,9 +131,9 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
*/
@Override
public void showPopupKeysPanel(final View parentView, final Controller controller,
final int pointX, final int pointY, final OnKeyEventListener listener) {
final int pointX, final int pointY, final EmojiViewCallback emojiViewCallback) {
mListener = null;
mKeyEventListener = listener;
mEmojiViewCallback = emojiViewCallback;
showPopupKeysPanelInternal(parentView, controller, pointX, pointY);
}
@ -157,6 +157,9 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
mOriginX = x + container.getPaddingLeft();
mOriginY = y + container.getPaddingTop();
var center = panelX + getMeasuredWidth() / 2;
// This is needed for cases where there's also a long text popup above this keyboard
controller.setLayoutGravity(center < pointX? Gravity.RIGHT : center > pointX? Gravity.LEFT : Gravity.CENTER_HORIZONTAL);
controller.onShowPopupKeysPanel(this);
final PopupKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
if (accessibilityDelegate != null
@ -222,8 +225,8 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
false /* isKeyRepeat */);
}
}
} else if (mKeyEventListener != null) {
mKeyEventListener.onReleaseKey(key);
} else if (mEmojiViewCallback != null) {
mEmojiViewCallback.onReleaseKey(key);
}
}
@ -314,28 +317,4 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane
}
return super.onHoverEvent(event);
}
private View getContainerView() {
return (View)getParent();
}
@Override
public void showInParent(final ViewGroup parentView) {
removeFromParent();
parentView.addView(getContainerView());
}
@Override
public void removeFromParent() {
final View containerView = getContainerView();
final ViewGroup currentParent = (ViewGroup)containerView.getParent();
if (currentParent != null) {
currentParent.removeView(containerView);
}
}
@Override
public boolean isShowingInParent() {
return (getContainerView().getParent() != null);
}
}

View file

@ -8,10 +8,17 @@ package helium314.keyboard.keyboard;
import android.view.View;
import android.view.ViewGroup;
import helium314.keyboard.keyboard.emoji.OnKeyEventListener;
import helium314.keyboard.keyboard.emoji.EmojiViewCallback;
public interface PopupKeysPanel {
interface Controller {
/**
* Set the layout gravity.
* @param layoutGravity requested by the popup
*/
default void setLayoutGravity(int layoutGravity) {
}
/**
* Add the {@link PopupKeysPanel} to the target view.
* @param panel the panel to be shown.
@ -59,19 +66,18 @@ public interface PopupKeysPanel {
* Initializes the layout and event handling of this {@link PopupKeysPanel} and calls the
* controller's onShowPopupKeysPanel to add the panel's container view.
* Same as {@link PopupKeysPanel#showPopupKeysPanel(View, Controller, int, int, KeyboardActionListener)},
* but with a {@link OnKeyEventListener}.
* but with a {@link EmojiViewCallback}.
*
* @param parentView the parent view of this {@link PopupKeysPanel}
* @param controller the controller that can dismiss this {@link PopupKeysPanel}
* @param pointX x coordinate of this {@link PopupKeysPanel}
* @param pointY y coordinate of this {@link PopupKeysPanel}
* @param listener the listener that will receive keyboard action from this
* {@link PopupKeysPanel}.
* @param emojiViewCallback to receive keyboard actions from this {@link PopupKeysPanel}.
*/
// TODO: Currently the PopupKeysPanel is inside a container view that is added to the parent.
// Consider the simpler approach of placing the PopupKeysPanel itself into the parent view.
void showPopupKeysPanel(View parentView, Controller controller, int pointX,
int pointY, OnKeyEventListener listener);
int pointY, EmojiViewCallback emojiViewCallback);
/**
* Dismisses the popup keys panel and calls the controller's onDismissPopupKeysPanel to remove
@ -127,20 +133,35 @@ public interface PopupKeysPanel {
*/
int translateY(int y);
default View getContainerView() {
return (View) ((View) this).getParent();
}
/**
* Show this {@link PopupKeysPanel} in the parent view.
*
* @param parentView the {@link ViewGroup} that hosts this {@link PopupKeysPanel}.
*/
void showInParent(ViewGroup parentView);
default void showInParent(ViewGroup parentView) {
removeFromParent();
parentView.addView(getContainerView());
}
/**
* Remove this {@link PopupKeysPanel} from the parent view.
*/
void removeFromParent();
default void removeFromParent() {
final View containerView = getContainerView();
final ViewGroup currentParent = (ViewGroup)containerView.getParent();
if (currentParent != null) {
currentParent.removeView(containerView);
}
}
/**
* Return whether the panel is currently being shown.
*/
boolean isShowingInParent();
default boolean isShowingInParent() {
return getContainerView().getParent() != null;
}
}

View file

@ -0,0 +1,121 @@
/*
* Copyright (C) 2011 The Android Open Source Project
* modified
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
*/
package helium314.keyboard.keyboard;
import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
import helium314.keyboard.keyboard.emoji.EmojiViewCallback;
import helium314.keyboard.keyboard.internal.KeyDrawParams;
import helium314.keyboard.latin.R;
import helium314.keyboard.latin.common.ColorType;
import helium314.keyboard.latin.common.CoordinateUtils;
import helium314.keyboard.latin.settings.Settings;
/**
* A view that displays popup text.
*/
public class PopupTextView extends TextView implements PopupKeysPanel {
private final int[] mCoordinates = CoordinateUtils.newInstance();
private final Typeface mTypeface;
private Controller mController = EMPTY_CONTROLLER;
private int mOriginX;
private int mOriginY;
private Key mKey;
private EmojiViewCallback mEmojiViewCallback;
public PopupTextView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.popupKeysKeyboardViewStyle);
}
public PopupTextView(final Context context, final AttributeSet attrs,
final int defStyle) {
super(context, attrs, defStyle);
mTypeface = Settings.getInstance().getCustomTypeface();
}
public void setKeyDrawParams(Key key, KeyDrawParams drawParams) {
mKey = key;
Settings.getValues().mColors.setBackground(this, ColorType.KEY_PREVIEW_BACKGROUND);
setTextColor(drawParams.mPreviewTextColor);
setTextSize(TypedValue.COMPLEX_UNIT_PX, key.selectHintTextSize(drawParams) << 1);
setTypeface(mTypeface == null ? key.selectTypeface(drawParams) : mTypeface);
}
@Override
public void showPopupKeysPanel(final View parentView, final Controller controller,
final int pointX, final int pointY, final KeyboardActionListener listener) {
showPopupKeysPanelInternal(parentView, controller, pointX, pointY);
}
@Override
public void showPopupKeysPanel(final View parentView, final Controller controller,
final int pointX, final int pointY, final EmojiViewCallback emojiViewCallback) {
mEmojiViewCallback = emojiViewCallback;
showPopupKeysPanelInternal(parentView, controller, pointX, pointY);
}
private void showPopupKeysPanelInternal(final View parentView, final Controller controller,
final int pointX, final int pointY) {
mController = controller;
final View container = getContainerView();
// The coordinates of panel's left-top corner in parentView's coordinate system.
// We need to consider background drawable paddings.
final int x = pointX - getMeasuredWidth() / 2 - container.getPaddingLeft() - getPaddingLeft();
final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom()
+ getPaddingBottom();
parentView.getLocationInWindow(mCoordinates);
// Ensure the horizontal position of the panel does not extend past the parentView edges.
final int maxX = parentView.getMeasuredWidth() - container.getMeasuredWidth();
final int panelX = Math.max(0, Math.min(maxX, x)) + CoordinateUtils.x(mCoordinates);
final int panelY = y + CoordinateUtils.y(mCoordinates);
container.setX(panelX);
container.setY(panelY);
mOriginX = x + container.getPaddingLeft();
mOriginY = y + container.getPaddingTop();
controller.setLayoutGravity(Gravity.NO_GRAVITY);
controller.onShowPopupKeysPanel(this);
}
@Override
public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime) {
}
@Override
public void onMoveEvent(final int x, final int y, final int pointerId, final long eventTime) {
}
@Override
public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) {
mEmojiViewCallback.onReleaseKey(mKey);
}
@Override
public void dismissPopupKeysPanel() {
if (!isShowingInParent()) {
return;
}
mController.onDismissPopupKeysPanel();
}
@Override
public int translateX(final int x) {
return x - mOriginX;
}
@Override
public int translateY(final int y) {
return y - mOriginY;
}
}

View file

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

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

View file

@ -13,6 +13,9 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.LinearLayout;
import helium314.keyboard.keyboard.PopupTextView;
import helium314.keyboard.latin.utils.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@ -53,14 +56,18 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
private static final long KEY_PRESS_DELAY_TIME = 250; // msec
private static final long KEY_RELEASE_DELAY_TIME = 30; // msec
private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() {
private static final EmojiViewCallback EMPTY_EMOJI_VIEW_CALLBACK = new EmojiViewCallback() {
@Override
public void onPressKey(final Key key) {}
@Override
public void onReleaseKey(final Key key) {}
@Override
public String getDescription(String emoji) {
return null;
}
};
private OnKeyEventListener mListener = EMPTY_LISTENER;
private EmojiViewCallback mEmojiViewCallback = EMPTY_EMOJI_VIEW_CALLBACK;
private final KeyDetector mKeyDetector = new KeyDetector();
private KeyboardAccessibilityDelegate<EmojiPageKeyboardView> mAccessibilityDelegate;
@ -74,6 +81,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
// More keys keyboard
private final View mPopupKeysKeyboardContainer;
private final PopupTextView mDescriptionView;
private final PopupKeysKeyboardView mPopupKeysKeyboardView;
private final WeakHashMap<Key, Keyboard> mPopupKeysKeyboardCache = new WeakHashMap<>();
private final boolean mConfigShowPopupKeysKeyboardAtTouchedPoint;
private final ViewGroup mPopupKeysPlacerView;
@ -102,6 +111,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
final LayoutInflater inflater = LayoutInflater.from(getContext());
mPopupKeysKeyboardContainer = inflater.inflate(popupKeysKeyboardLayoutId, null);
mDescriptionView = mPopupKeysKeyboardContainer.findViewById(R.id.description_view);
mPopupKeysKeyboardView = mPopupKeysKeyboardContainer.findViewById(R.id.popup_keys_keyboard_view);
}
@Override
@ -146,8 +157,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
}
}
public void setOnKeyEventListener(final OnKeyEventListener listener) {
mListener = listener;
public void setEmojiViewCallback(final EmojiViewCallback emojiViewCallback) {
mEmojiViewCallback = emojiViewCallback;
}
/**
@ -169,7 +180,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
}
@Nullable
public PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key, final int lastX, final int lastY) {
private PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key) {
mPopupKeysKeyboardView.setVisibility(GONE);
final PopupKeySpec[] popupKeys = key.getPopupKeys();
if (popupKeys == null) {
return null;
@ -182,21 +194,9 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
mPopupKeysKeyboardCache.put(key, popupKeysKeyboard);
}
final View container = mPopupKeysKeyboardContainer;
final PopupKeysKeyboardView popupKeysKeyboardView = container.findViewById(R.id.popup_keys_keyboard_view);
popupKeysKeyboardView.setKeyboard(popupKeysKeyboard);
container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
final int[] lastCoords = CoordinateUtils.newCoordinateArray(1, lastX, lastY);
// The popup keys keyboard is usually horizontally aligned with the center of the parent key.
// If showPopupKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
// keys keyboard is placed at the touch point of the parent key.
final int pointX = mConfigShowPopupKeysKeyboardAtTouchedPoint
? CoordinateUtils.x(lastCoords)
: key.getX() + key.getWidth() / 2;
final int pointY = key.getY();
popupKeysKeyboardView.showPopupKeysPanel(this, this, pointX, pointY, mListener);
return popupKeysKeyboardView;
mPopupKeysKeyboardView.setKeyboard(popupKeysKeyboard);
mPopupKeysKeyboardView.setVisibility(VISIBLE);
return mPopupKeysKeyboardView;
}
private void dismissPopupKeysPanel() {
@ -209,6 +209,17 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
return mPopupKeysPanel != null;
}
@Override
public void setLayoutGravity(int layoutGravity) {
var layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = mDescriptionView.getMeasuredWidth() > mPopupKeysKeyboardView.getMeasuredWidth()?
layoutGravity : Gravity.CENTER_HORIZONTAL;
mPopupKeysKeyboardContainer.setLayoutParams(layoutParams);
mDescriptionView.setLayoutParams(layoutParams);
mPopupKeysKeyboardView.setLayoutParams(layoutParams);
}
@Override
public void onShowPopupKeysPanel(final PopupKeysPanel panel) {
// install placer view only when needed instead of when this
@ -290,9 +301,11 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
return;
}
var descriptionPanel = showDescription(key);
final PopupKeysPanel popupKeysPanel = showPopupKeysKeyboard(key);
final int x = mLastX;
final int y = mLastY;
final PopupKeysPanel popupKeysPanel = showPopupKeysKeyboard(key, x, y);
if (popupKeysPanel != null) {
final int translatedX = popupKeysPanel.translateX(x);
final int translatedY = popupKeysPanel.translateY(y);
@ -301,6 +314,34 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
// want any scroll to append during this entire input.
disallowParentInterceptTouchEvent(true);
}
if (popupKeysPanel != null || descriptionPanel != null) {
mPopupKeysKeyboardContainer.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
final int[] lastCoords = CoordinateUtils.newCoordinateArray(1, x, y);
// The popup keys keyboard is usually horizontally aligned with the center of the parent key.
// If showPopupKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
// keys keyboard is placed at the touch point of the parent key.
final int pointX = mConfigShowPopupKeysKeyboardAtTouchedPoint
? CoordinateUtils.x(lastCoords)
: key.getX() + key.getWidth() / 2;
final int pointY = key.getY() - getKeyboard().mVerticalGap;
(popupKeysPanel != null? popupKeysPanel : descriptionPanel)
.showPopupKeysPanel(this, this, pointX, pointY, mEmojiViewCallback);
}
}
private PopupKeysPanel showDescription(Key key) {
mDescriptionView.setVisibility(GONE);
var description = mEmojiViewCallback.getDescription(key.getLabel());
if (description == null) {
return null;
}
mDescriptionView.setText(description);
mDescriptionView.setKeyDrawParams(key, getKeyDrawParams());
mDescriptionView.setVisibility(VISIBLE);
return mDescriptionView;
}
private void registerPress(final Key key) {
@ -318,7 +359,7 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
releasedKey.onReleased();
invalidateKey(releasedKey);
if (withKeyRegistering) {
mListener.onReleaseKey(releasedKey);
mEmojiViewCallback.onReleaseKey(releasedKey);
}
}
@ -326,7 +367,7 @@ public final class EmojiPageKeyboardView extends KeyboardView implements
mPendingKeyDown = null;
pressedKey.onPressed();
invalidateKey(pressedKey);
mListener.onPressKey(pressedKey);
mEmojiViewCallback.onPressKey(pressedKey);
}
public void releaseCurrentKey(final boolean withKeyRegistering) {

View file

@ -7,7 +7,6 @@
package helium314.keyboard.keyboard.emoji;
import helium314.keyboard.latin.utils.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -23,13 +22,13 @@ final class EmojiPalettesAdapter extends RecyclerView.Adapter<EmojiPalettesAdapt
private static final boolean DEBUG_PAGER = false;
private final int mCategoryId;
private final OnKeyEventListener mListener;
private final EmojiViewCallback mEmojiViewCallback;
private final EmojiCategory mEmojiCategory;
public EmojiPalettesAdapter(final EmojiCategory emojiCategory, int categoryId, final OnKeyEventListener listener) {
public EmojiPalettesAdapter(final EmojiCategory emojiCategory, int categoryId, final EmojiViewCallback emojiViewCallback) {
mEmojiCategory = emojiCategory;
mCategoryId = categoryId;
mListener = listener;
mEmojiViewCallback = emojiViewCallback;
}
@NonNull
@ -38,6 +37,7 @@ 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,7 +50,6 @@ 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,12 +38,16 @@ 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;
@ -60,7 +64,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, OnKeyEventListener {
implements View.OnClickListener, EmojiViewCallback {
private static final class PagerViewHolder extends RecyclerView.ViewHolder {
private long mCategoryId;
@ -179,15 +183,14 @@ 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;
@ -254,9 +257,7 @@ 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;
}
@ -280,8 +281,7 @@ public final class EmojiPalettesView extends LinearLayout
}
/**
* Called from {@link EmojiPageKeyboardView} through
* {@link helium314.keyboard.keyboard.emoji.OnKeyEventListener}
* Called from {@link EmojiPageKeyboardView} through {@link EmojiViewCallback}
* interface to handle touch events from non-View-based elements such as Emoji buttons.
*/
@Override
@ -291,10 +291,9 @@ public final class EmojiPalettesView extends LinearLayout
}
/**
* Called from {@link EmojiPageKeyboardView} through
* {@link helium314.keyboard.keyboard.emoji.OnKeyEventListener}
* Called from {@link EmojiPageKeyboardView} through {@link EmojiViewCallback}
* interface to handle touch events from non-View-based elements such as Emoji buttons.
* This may be called without any prior call to {@link OnKeyEventListener#onPressKey(Key)}.
* This may be called without any prior call to {@link EmojiViewCallback#onPressKey(Key)}.
*/
@Override
public void onReleaseKey(final Key key) {
@ -310,6 +309,20 @@ 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?
@ -324,6 +337,7 @@ public final class EmojiPalettesView extends LinearLayout
final KeyDrawParams params = new KeyDrawParams();
params.updateParams(mEmojiLayoutParams.getBottomRowKeyboardHeight(), keyVisualAttr);
setupSidePadding();
initDictionaryFacilitator();
}
private void addRecentKey(final Key key) {
@ -430,5 +444,27 @@ 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 touch events from non-View-based elements
* such as Emoji buttons.
* Interface to handle callbacks from child elements
* such as Emoji buttons and keyboard views.
*/
public interface OnKeyEventListener {
public interface EmojiViewCallback {
/**
* Called when a key is pressed by the user
@ -17,8 +17,13 @@ public interface OnKeyEventListener {
/**
* Called when a key is released.
* This may be called without any prior call to {@link OnKeyEventListener#onPressKey(Key)},
* This may be called without any prior call to {@link EmojiViewCallback#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

@ -45,7 +45,7 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) {
"mns" -> Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO
else -> 0
}
val tlds = getLocaleTlds(locale)
val tlds = mutableListOf(Key.POPUP_KEYS_HAS_LABELS)
init {
readStream(dataStream, false, true)
@ -84,7 +84,7 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) {
READER_MODE_EXTRA_KEYS -> if (!onlyPopupKeys) addExtraKey(line.split(colonSpaceRegex, 2))
READER_MODE_LABELS -> if (!onlyPopupKeys) addLabel(line.split(colonSpaceRegex, 2))
READER_MODE_NUMBER_ROW -> localizedNumberKeys = line.splitOnWhitespace()
READER_MODE_TLD -> SpacedTokens(line).forEach { tlds.add(".$it") }
READER_MODE_TLD -> tlds.addAll(SpacedTokens(line).map { ".$it" })
}
}
}
@ -156,6 +156,16 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) {
}
}
fun addLocaleTlds(locale: Locale) {
tlds.add(0, comTld)
val ccLower = locale.country.lowercase()
if (ccLower.isNotEmpty() && locale.language != SubtypeLocaleUtils.NO_LANGUAGE) {
specialCountryTlds[ccLower]?.let { tlds.addAll(SpacedTokens(it)) } ?: tlds.add(".$ccLower")
}
if ((locale.language != "en" && euroLocales.matches(locale.language)) || euroCountries.matches(locale.country))
tlds.add(".eu")
tlds.addAll(SpacedTokens(otherDefaultTlds))
}
}
private fun addFixedColumnOrder(popupKeys: MutableCollection<String>) {
@ -205,6 +215,7 @@ private fun createLocaleKeyTexts(context: Context, params: KeyboardParams, popup
POPUP_KEYS_MORE -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_more.txt"), false)
POPUP_KEYS_ALL -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_all.txt"), false)
}
lkt.addLocaleTlds(params.mId.locale)
return lkt
}
@ -220,28 +231,6 @@ private fun getStreamForLocale(locale: Locale, context: Context) =
}
}
private fun getLocaleTlds(locale: Locale): LinkedHashSet<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
@ -341,7 +330,7 @@ const val POPUP_KEYS_NORMAL = "normal"
private const val LOCALE_TEXTS_FOLDER = "locale_key_texts"
// either tld is not simply lowercase ISO 3166-1 code, or there are multiple according to some list
private val specialCountryTlds = listOf(
private val specialCountryTlds = hashMapOf<String, String>(
"bd" to ".bd .com.bd",
"bq" to ".bq .an .nl",
"bl" to ".bl .gp .fr",
@ -351,4 +340,5 @@ private val specialCountryTlds = listOf(
"mf" to ".mf .gp .fr",
"tl" to ".tl .tp",
)
private const val defaultTlds = ".com .gov .edu .org .net"
private const val comTld = ".com"
private const val otherDefaultTlds = ".gov .edu .org .net"

View file

@ -175,6 +175,12 @@ object KeyCode {
const val META_LEFT = -10048
const val META_RIGHT = -10049
// Intents
const val SEND_INTENT_ONE = -20000
const val SEND_INTENT_TWO = -20001
const val SEND_INTENT_THREE = -20002
/** to make sure a FlorisBoard code works when reading a JSON layout */
fun Int.checkAndConvertCode(): Int = if (this > 0) this else when (this) {
// working
@ -190,7 +196,7 @@ object KeyCode {
ACTION_NEXT, ACTION_PREVIOUS, NOT_SPECIFIED, CLIPBOARD_COPY_ALL, WORD_LEFT, WORD_RIGHT, PAGE_UP,
PAGE_DOWN, META, TAB, ESCAPE, INSERT, SLEEP, MEDIA_PLAY, MEDIA_PAUSE, MEDIA_PLAY_PAUSE, MEDIA_NEXT,
MEDIA_PREVIOUS, VOL_UP, VOL_DOWN, MUTE, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, BACK,
TIMESTAMP, CTRL_LEFT, CTRL_RIGHT, ALT_LEFT, ALT_RIGHT, META_LEFT, META_RIGHT
TIMESTAMP, CTRL_LEFT, CTRL_RIGHT, ALT_LEFT, ALT_RIGHT, META_LEFT, META_RIGHT, SEND_INTENT_ONE, SEND_INTENT_TWO, SEND_INTENT_THREE,
-> this
// conversion
@ -210,12 +216,54 @@ object KeyCode {
// todo: there are many more keys, see near https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0
/**
* Convert a keyCode / codePoint to a KeyEvent.KEYCODE_<xxx>.
* Convert an internal keyCode to a KeyEvent.KEYCODE_<xxx>.
* Positive codes are passed through, unknown negative codes result in KeyEvent.KEYCODE_UNKNOWN.
* To be uses for fake hardware key press.
*/
@JvmStatic fun keyCodeToKeyEventCode(keyCode: Int) = when (keyCode) {
ARROW_UP -> KeyEvent.KEYCODE_DPAD_UP
ARROW_RIGHT -> KeyEvent.KEYCODE_DPAD_RIGHT
ARROW_DOWN -> KeyEvent.KEYCODE_DPAD_DOWN
ARROW_LEFT -> KeyEvent.KEYCODE_DPAD_LEFT
MOVE_START_OF_LINE -> KeyEvent.KEYCODE_MOVE_HOME
MOVE_END_OF_LINE -> KeyEvent.KEYCODE_MOVE_END
TAB -> KeyEvent.KEYCODE_TAB
PAGE_UP -> KeyEvent.KEYCODE_PAGE_UP
PAGE_DOWN -> KeyEvent.KEYCODE_PAGE_DOWN
ESCAPE -> KeyEvent.KEYCODE_ESCAPE
INSERT -> KeyEvent.KEYCODE_INSERT
SLEEP -> KeyEvent.KEYCODE_SLEEP
MEDIA_PLAY -> KeyEvent.KEYCODE_MEDIA_PLAY
MEDIA_PAUSE -> KeyEvent.KEYCODE_MEDIA_PAUSE
MEDIA_PLAY_PAUSE -> KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
MEDIA_NEXT -> KeyEvent.KEYCODE_MEDIA_NEXT
MEDIA_PREVIOUS -> KeyEvent.KEYCODE_MEDIA_PREVIOUS
VOL_UP -> KeyEvent.KEYCODE_VOLUME_UP
VOL_DOWN -> KeyEvent.KEYCODE_VOLUME_DOWN
MUTE -> KeyEvent.KEYCODE_VOLUME_MUTE
BACK -> KeyEvent.KEYCODE_BACK
F1 -> KeyEvent.KEYCODE_F1
F2 -> KeyEvent.KEYCODE_F2
F3 -> KeyEvent.KEYCODE_F3
F4 -> KeyEvent.KEYCODE_F4
F5 -> KeyEvent.KEYCODE_F5
F6 -> KeyEvent.KEYCODE_F6
F7 -> KeyEvent.KEYCODE_F7
F8 -> KeyEvent.KEYCODE_F8
F9 -> KeyEvent.KEYCODE_F9
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.
* */
fun Int.toKeyEventCode(): Int = if (this > 0)
when (this.toChar().uppercaseChar()) {
*/
@JvmStatic fun codePointToKeyEventCode(codePoint: Int): Int = when (codePoint.toChar().uppercaseChar()) {
'/' -> KeyEvent.KEYCODE_SLASH
'\\' -> KeyEvent.KEYCODE_BACKSLASH
';' -> KeyEvent.KEYCODE_SEMICOLON
@ -269,40 +317,4 @@ object KeyCode {
'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
ARROW_LEFT -> KeyEvent.KEYCODE_DPAD_LEFT
MOVE_START_OF_LINE -> KeyEvent.KEYCODE_MOVE_HOME
MOVE_END_OF_LINE -> KeyEvent.KEYCODE_MOVE_END
TAB -> KeyEvent.KEYCODE_TAB
PAGE_UP -> KeyEvent.KEYCODE_PAGE_UP
PAGE_DOWN -> KeyEvent.KEYCODE_PAGE_DOWN
ESCAPE -> KeyEvent.KEYCODE_ESCAPE
INSERT -> KeyEvent.KEYCODE_INSERT
SLEEP -> KeyEvent.KEYCODE_SLEEP
MEDIA_PLAY -> KeyEvent.KEYCODE_MEDIA_PLAY
MEDIA_PAUSE -> KeyEvent.KEYCODE_MEDIA_PAUSE
MEDIA_PLAY_PAUSE -> KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
MEDIA_NEXT -> KeyEvent.KEYCODE_MEDIA_NEXT
MEDIA_PREVIOUS -> KeyEvent.KEYCODE_MEDIA_PREVIOUS
VOL_UP -> KeyEvent.KEYCODE_VOLUME_UP
VOL_DOWN -> KeyEvent.KEYCODE_VOLUME_DOWN
MUTE -> KeyEvent.KEYCODE_VOLUME_MUTE
BACK -> KeyEvent.KEYCODE_BACK
F1 -> KeyEvent.KEYCODE_F1
F2 -> KeyEvent.KEYCODE_F2
F3 -> KeyEvent.KEYCODE_F3
F4 -> KeyEvent.KEYCODE_F4
F5 -> KeyEvent.KEYCODE_F5
F6 -> KeyEvent.KEYCODE_F6
F7 -> KeyEvent.KEYCODE_F7
F8 -> KeyEvent.KEYCODE_F8
F9 -> KeyEvent.KEYCODE_F9
F10 -> KeyEvent.KEYCODE_F10
F11 -> KeyEvent.KEYCODE_F11
F12 -> KeyEvent.KEYCODE_F12
else -> KeyEvent.KEYCODE_UNKNOWN
}
}

View file

@ -11,6 +11,7 @@ import java.util.Locale;
import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo;
import helium314.keyboard.latin.common.ComposedData;
import helium314.keyboard.latin.makedict.WordProperty;
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion;
/**
@ -177,6 +178,10 @@ public abstract class Dictionary {
};
}
public WordProperty getWordProperty(final String word, final boolean isBeginningOfSentence) {
return null;
}
/**
* Not a true dictionary. A placeholder used to indicate suggestions that don't come from any
* real dictionary.

View file

@ -397,7 +397,10 @@ class DictionaryFacilitatorImpl : DictionaryFacilitator {
// This is not too bad, but it delays adding in case a user wants to fill a dictionary using this functionality
if (userHistoryDict.getFrequency(word) > 120) {
scope.launch {
UserDictionary.Words.addWord(userDict.mContext, word, 250, null, dictionaryGroup.locale)
// adding can throw IllegalArgumentException: Unknown URL content://user_dictionary/words
// https://stackoverflow.com/q/41474623 https://github.com/AnySoftKeyboard/AnySoftKeyboard/issues/490
// apparently some devices don't have a dictionary? or it's just sporadic hiccups?
runCatching { UserDictionary.Words.addWord(userDict.mContext, word, 250, null, dictionaryGroup.locale) }
}
}
}
@ -731,7 +734,7 @@ private class DictionaryGroup(
else {
val file = File(context.filesDir.absolutePath + File.separator + "blacklists" + File.separator + locale.toLanguageTag() + ".txt")
if (file.isDirectory) file.delete() // this apparently was an issue in some versions
if (file.parentFile?.mkdirs() == true) file
if (file.parentFile?.exists() == true || file.parentFile?.mkdirs() == true) file
else null
}

View file

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

View file

@ -4,6 +4,7 @@ package helium314.keyboard.latin;
import helium314.keyboard.event.HangulCombiner;
import helium314.keyboard.latin.common.ComposedData;
import helium314.keyboard.latin.makedict.WordProperty;
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion;
import java.text.Normalizer;
@ -72,6 +73,11 @@ public class KoreanDictionary extends Dictionary {
return mDictionary.getMaxFrequencyOfExactMatches(processInput(word));
}
@Override
public WordProperty getWordProperty(String word, boolean isBeginningOfSentence) {
return mDictionary.getWordProperty(processInput(word), isBeginningOfSentence);
}
@Override
protected boolean same(char[] word, int length, String typedWord) {
word = processInput(new String(word)).toCharArray();

View file

@ -26,7 +26,6 @@ import android.os.Process;
import android.text.InputType;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
@ -51,9 +50,6 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
import helium314.keyboard.latin.common.InsetsOutlineProvider;
import helium314.keyboard.dictionarypack.DictionaryPackConstants;
import helium314.keyboard.event.Event;
import helium314.keyboard.event.HangulEventDecoder;
import helium314.keyboard.event.HardwareEventDecoder;
import helium314.keyboard.event.HardwareKeyboardEventDecoder;
import helium314.keyboard.event.InputTransaction;
import helium314.keyboard.keyboard.Keyboard;
import helium314.keyboard.keyboard.KeyboardId;
@ -68,7 +64,6 @@ import helium314.keyboard.latin.common.InputPointers;
import helium314.keyboard.latin.common.LocaleUtils;
import helium314.keyboard.latin.common.ViewOutlineProviderUtilsKt;
import helium314.keyboard.latin.define.DebugFlags;
import helium314.keyboard.latin.define.ProductionFlags;
import helium314.keyboard.latin.inputlogic.InputLogic;
import helium314.keyboard.latin.personalization.PersonalizationHelper;
import helium314.keyboard.latin.settings.Settings;
@ -134,9 +129,6 @@ public class LatinIME extends InputMethodService implements
private final DictionaryFacilitator mDictionaryFacilitator =
DictionaryFacilitatorProvider.getDictionaryFacilitator(false);
final InputLogic mInputLogic = new InputLogic(this, this, mDictionaryFacilitator);
// We expect to have only one decoder in almost all cases, hence the default capacity of 1.
// If it turns out we need several, it will get grown seamlessly.
final SparseArray<HardwareEventDecoder> mHardwareEventDecoders = new SparseArray<>(1);
// TODO: Move these {@link View}s to {@link KeyboardSwitcher}.
private View mInputView;
@ -146,7 +138,6 @@ public class LatinIME extends InputMethodService implements
private RichInputMethodManager mRichImm;
final KeyboardSwitcher mKeyboardSwitcher;
private final SubtypeState mSubtypeState = new SubtypeState();
private EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector;
private final StatsUtilsManager mStatsUtilsManager;
// Working variable for {@link #startShowingInputView()} and
// {@link #onEvaluateInputViewShown()}.
@ -279,9 +270,7 @@ public class LatinIME extends InputMethodService implements
msg.arg2 /* remainingTries */, this /* handler */)) {
// If we were able to reset the caches, then we can reload the keyboard.
// Otherwise, we'll do it when we can.
latinIme.mKeyboardSwitcher.loadKeyboard(latinIme.getCurrentInputEditorInfo(),
settingsValues, latinIme.getCurrentAutoCapsState(),
latinIme.getCurrentRecapitalizeState());
latinIme.mKeyboardSwitcher.reloadMainKeyboard();
}
break;
case MSG_WAIT_FOR_DICTIONARY_LOAD:
@ -637,6 +626,7 @@ public class LatinIME extends InputMethodService implements
@Override
public void onCreate() {
mSettings.startListener();
KeyboardIconsSet.Companion.getInstance().loadIcons(this);
mRichImm = RichInputMethodManager.getInstance();
AudioAndHapticFeedbackManager.init(this);
@ -664,7 +654,8 @@ public class LatinIME extends InputMethodService implements
final IntentFilter newDictFilter = new IntentFilter();
newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
ContextCompat.registerReceiver(this, mDictionaryPackInstallReceiver, newDictFilter, ContextCompat.RECEIVER_NOT_EXPORTED);
// RECEIVER_EXPORTED is necessary because apparently Android 15 (and others?) don't recognize if the sender and receiver are the same app, see https://github.com/Helium314/HeliBoard/pull/1756
ContextCompat.registerReceiver(this, mDictionaryPackInstallReceiver, newDictFilter, ContextCompat.RECEIVER_EXPORTED);
final IntentFilter dictDumpFilter = new IntentFilter();
dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);
@ -1071,7 +1062,7 @@ public class LatinIME extends InputMethodService implements
if (isDifferentTextField) {
mainKeyboardView.closing();
suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
switcher.reloadMainKeyboard();
if (needToCallLoadKeyboardLater) {
// If we need to call loadKeyboard again later, we need to save its state now. The
// later call will be done in #retryResetCaches.
@ -1119,6 +1110,7 @@ public class LatinIME extends InputMethodService implements
@Override
public void onWindowHidden() {
super.onWindowHidden();
Log.i(TAG, "onWindowHidden");
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
mainKeyboardView.closing();
@ -1175,8 +1167,12 @@ public class LatinIME extends InputMethodService implements
if (isInputViewShown()
&& mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
composingSpanStart, composingSpanEnd, settingsValues)) {
mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(),
getCurrentRecapitalizeState());
// we don't want to update a manually set shift state if selection changed towards one side
// because this may end the manual shift, which is unwanted in case of shift + arrow keys for changing selection
// todo: this is not fully implemented yet, and maybe should be behind a setting
if (mKeyboardSwitcher.getKeyboard() != null && mKeyboardSwitcher.getKeyboard().mId.isAlphabetShiftedManually()
&& !((oldSelEnd == newSelEnd && oldSelStart != newSelStart) || (oldSelEnd != newSelEnd && oldSelStart == newSelStart)))
mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), getCurrentRecapitalizeState());
}
}
@ -1217,6 +1213,7 @@ public class LatinIME extends InputMethodService implements
@Override
public void hideWindow() {
Log.i(TAG, "hideWindow");
if (hasSuggestionStripView() && mSettings.getCurrent().mToolbarMode == ToolbarMode.EXPANDABLE)
mSuggestionStripView.setToolbarVisibility(false);
mKeyboardSwitcher.onHideWindow();
@ -1229,6 +1226,12 @@ public class LatinIME extends InputMethodService implements
super.hideWindow();
}
@Override
public void requestHideSelf(int flags) {
super.requestHideSelf(flags);
Log.i(TAG, "requestHideSelf: " + flags);
}
@Override
public void onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions) {
if (DebugFlags.DEBUG_ENABLED) {
@ -1280,8 +1283,8 @@ public class LatinIME extends InputMethodService implements
if (isImeSuppressedByHardwareKeyboard() && !visibleKeyboardView.isShown()) {
// If there is a hardware keyboard and a visible software keyboard view has been hidden,
// no visual element will be shown on the screen.
outInsets.contentTopInsets = inputHeight;
outInsets.visibleTopInsets = inputHeight;
// for some reason setting contentTopInsets and visibleTopInsets broke somewhere along the
// way from OpenBoard to HeliBoard (GH-702, GH-1455), but not setting anything seems to work
mInsetsUpdater.setInsets(outInsets);
return;
}
@ -1539,25 +1542,7 @@ public class LatinIME extends InputMethodService implements
// Implementation of {@link SuggestionStripView.Listener}.
@Override
public void onCodeInput(final int codePoint, final int x, final int y, final boolean isKeyRepeat) {
onCodeInput(codePoint, 0, x, y, isKeyRepeat);
}
public void onCodeInput(final int codePoint, final int metaState, final int x, final int y, final boolean isKeyRepeat) {
if (codePoint < 0) {
switch (codePoint) {
case KeyCode.TOGGLE_AUTOCORRECT -> {mSettings.toggleAutoCorrect(); return; }
case KeyCode.TOGGLE_INCOGNITO_MODE -> {mSettings.toggleAlwaysIncognitoMode(); return; }
}
}
final Event event;
// checking if the character is a combining accent
if (0x300 <= codePoint && codePoint <= 0x35b) {
event = Event.createSoftwareDeadEvent(codePoint, 0, metaState, x, y, null);
} else {
event = createSoftwareKeypressEvent(codePoint, metaState, x, y, isKeyRepeat);
}
onEvent(event);
mKeyboardActionListener.onCodeInput(codePoint, x, y, isKeyRepeat);
}
// This method is public for testability of LatinIME, but also in the future it should
@ -1574,24 +1559,6 @@ public class LatinIME extends InputMethodService implements
mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
}
// A helper method to split the code point and the key code. Ultimately, they should not be
// squashed into the same variable, and this method should be removed.
// public for testing, as we don't want to copy the same logic into test code
@NonNull
public static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int metaState,
final int keyX, final int keyY, final boolean isKeyRepeat) {
final int keyCode;
final int codePoint;
if (keyCodeOrCodePoint <= 0) {
keyCode = keyCodeOrCodePoint;
codePoint = Event.NOT_A_CODE_POINT;
} else {
keyCode = Event.NOT_A_KEY_CODE;
codePoint = keyCodeOrCodePoint;
}
return Event.createSoftwareKeypressEvent(codePoint, keyCode, metaState, keyX, keyY, isKeyRepeat);
}
public void onTextInput(final String rawText) {
// TODO: have the keyboard pass the correct key code when we need it.
final Event event = Event.createSoftwareTextEvent(rawText, KeyCode.MULTIPLE_CODE_POINTS);
@ -1763,8 +1730,7 @@ public class LatinIME extends InputMethodService implements
loadSettings();
if (mKeyboardSwitcher.getMainKeyboardView() != null) {
// Reload keyboard because the current language has been changed.
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent(),
getCurrentAutoCapsState(), getCurrentRecapitalizeState());
mKeyboardSwitcher.reloadMainKeyboard();
}
}
@ -1832,63 +1798,18 @@ public class LatinIME extends InputMethodService implements
feedbackManager.performAudioFeedback(code);
}
private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) {
final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId);
if (null != decoder) return decoder;
// TODO: create the decoder according to the specification
final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId);
mHardwareEventDecoders.put(deviceId, newDecoder);
return newDecoder;
}
// Hooks for hardware keyboard
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
if (mEmojiAltPhysicalKeyDetector == null) {
mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector(
getApplicationContext().getResources());
}
mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent);
if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
return super.onKeyDown(keyCode, keyEvent);
}
final Event event;
if (mRichImm.getCurrentSubtypeLocale().getLanguage().equals("ko")) {
final RichInputMethodSubtype subtype = mKeyboardSwitcher.getKeyboard() == null
? mRichImm.getCurrentSubtype()
: mKeyboardSwitcher.getKeyboard().mId.mSubtype;
event = HangulEventDecoder.decodeHardwareKeyEvent(subtype, keyEvent,
() -> getHardwareKeyEventDecoder(keyEvent.getDeviceId()).decodeHardwareKey(keyEvent));
} else {
event = getHardwareKeyEventDecoder(keyEvent.getDeviceId()).decodeHardwareKey(keyEvent);
}
// If the event is not handled by LatinIME, we just pass it to the parent implementation.
// If it's handled, we return true because we did handle it.
if (event.isHandled()) {
mInputLogic.onCodeInput(mSettings.getCurrent(), event,
mKeyboardSwitcher.getKeyboardShiftMode(),
// TODO: this is not necessarily correct for a hardware keyboard right now
mKeyboardSwitcher.getCurrentKeyboardScript(),
mHandler);
if (mKeyboardActionListener.onKeyDown(keyCode, keyEvent))
return true;
}
return super.onKeyDown(keyCode, keyEvent);
}
@Override
public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) {
if (mEmojiAltPhysicalKeyDetector == null) {
mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector(
getApplicationContext().getResources());
}
mEmojiAltPhysicalKeyDetector.onKeyUp(keyEvent);
if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
return super.onKeyUp(keyCode, keyEvent);
}
final long keyIdentifier = (long) keyEvent.getDeviceId() << 32 + keyEvent.getKeyCode();
if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) {
if (mKeyboardActionListener.onKeyUp(keyCode, keyEvent))
return true;
}
return super.onKeyUp(keyCode, keyEvent);
}

View file

@ -10,6 +10,7 @@ import com.android.inputmethod.latin.BinaryDictionary;
import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo;
import helium314.keyboard.latin.common.ComposedData;
import helium314.keyboard.latin.makedict.WordProperty;
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion;
import java.util.ArrayList;
@ -107,6 +108,18 @@ public final class ReadOnlyBinaryDictionary extends Dictionary {
return NOT_A_PROBABILITY;
}
@Override
public WordProperty getWordProperty(String word, boolean isBeginningOfSentence) {
if (mLock.readLock().tryLock()) {
try {
return mBinaryDictionary.getWordProperty(word, isBeginningOfSentence);
} finally {
mLock.readLock().unlock();
}
}
return null;
}
@Override
public void close() {
mLock.writeLock().lock();

View file

@ -439,7 +439,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
// test for this explicitly)
if (INVALID_CURSOR_POSITION != mExpectedSelStart
&& (cachedLength >= n || cachedLength >= mExpectedSelStart)) {
final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText);
final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText.toString());
// We call #toString() here to create a temporary object.
// In some situations, this method is called on a worker thread, and it's possible
// the main thread touches the contents of mComposingText while this worker thread
@ -716,8 +716,13 @@ public final class RichInputConnection implements PrivateCommandPerformer {
if (start < 0 || end < 0) {
return false;
}
if (start > end) {
mExpectedSelStart = end;
mExpectedSelEnd = start;
} else {
mExpectedSelStart = start;
mExpectedSelEnd = end;
}
if (isConnected()) {
final boolean isIcValid = mIC.setSelection(start, end);
if (!isIcValid) {

View file

@ -7,6 +7,7 @@ import helium314.keyboard.keyboard.Keyboard
import helium314.keyboard.keyboard.KeyboardSwitcher
import helium314.keyboard.latin.DictionaryFacilitator.DictionaryInitializationListener
import helium314.keyboard.latin.common.ComposedData
import helium314.keyboard.latin.makedict.WordProperty
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion
import helium314.keyboard.latin.utils.SuggestionResults
import java.util.Locale
@ -47,6 +48,8 @@ class SingleDictionaryFacilitator(private val dict: Dictionary) : DictionaryFaci
return suggestionResults
}
fun getWordProperty(word: String): WordProperty? = dict.getWordProperty(word, false)
// ------------ dummy functionality ----------------
override fun setValidSpellingWordReadCache(cache: LruCache<String, Boolean>) {}

View file

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

View file

@ -7,11 +7,7 @@
package helium314.keyboard.latin.define
object ProductionFlags {
const val IS_HARDWARE_KEYBOARD_SUPPORTED = false
// todo: make it work
// was set to true in hangul branch (and there is the hangul hardware event decoder in latinIme)
// but disabled again because this breaks ctrl+c / ctrl+v, and most likely other things
// so it looks like the HardwareKeyboardEventDecoder needs some work before it's ready
const val IS_HARDWARE_KEYBOARD_SUPPORTED = true
/**
* Include all suggestions from all dictionaries in

View file

@ -49,6 +49,7 @@ import helium314.keyboard.latin.settings.SpacingAndPunctuations;
import helium314.keyboard.latin.suggestions.SuggestionStripViewAccessor;
import helium314.keyboard.latin.utils.AsyncResultHolder;
import helium314.keyboard.latin.utils.InputTypeUtils;
import helium314.keyboard.latin.utils.IntentUtils;
import helium314.keyboard.latin.utils.Log;
import helium314.keyboard.latin.utils.RecapitalizeStatus;
import helium314.keyboard.latin.utils.ScriptUtils;
@ -89,6 +90,7 @@ public final class InputLogic {
private int mDeleteCount;
private long mLastKeyTime;
// todo: this is not used, so either remove it or do something with it
public final TreeSet<Long> mCurrentlyPressedHardwareKeys = new TreeSet<>();
// Keeps track of most recently inserted text (multi-character key) for reverting
@ -399,7 +401,11 @@ public final class InputLogic {
// Stop the last recapitalization, if started.
mRecapitalizeStatus.stop();
mWordBeingCorrectedByCursor = null;
return true;
// we do not return true if
final boolean oneSidedSelectionMove = hasOrHadSelection
&& ((oldSelEnd == newSelEnd && oldSelStart != newSelStart) || (oldSelEnd != newSelEnd && oldSelStart == newSelStart));
return !oneSidedSelectionMove;
}
public boolean moveCursorByAndReturnIfInsideComposingWord(int distance) {
@ -643,7 +649,8 @@ public final class InputLogic {
*/
private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction,
final String currentKeyboardScript, final LatinIME.UIHandler handler) {
switch (event.getMKeyCode()) {
final int keyCode = event.getMKeyCode();
switch (keyCode) {
case KeyCode.DELETE:
handleBackspaceEvent(event, inputTransaction, currentKeyboardScript);
// Backspace is a functional key, but it affects the contents of the editor.
@ -686,7 +693,7 @@ public final class InputLogic {
case KeyCode.SHIFT_ENTER:
// todo: try using sendDownUpKeyEventWithMetaState() and remove the key code maybe
final Event tmpEvent = Event.createSoftwareKeypressEvent(Constants.CODE_ENTER,
event.getMKeyCode(), 0, event.getMX(), event.getMY(), event.isKeyRepeat());
keyCode, 0, event.getMX(), event.getMY(), event.isKeyRepeat());
handleNonSpecialCharacterEvent(tmpEvent, inputTransaction, handler);
// Shift + Enter is treated as a functional key but it results in adding a new
// line, so that does affect the contents of the editor.
@ -717,37 +724,43 @@ public final class InputLogic {
if (mConnection.hasSelection()) {
mConnection.copyText(true);
// fake delete keypress to remove the text
final Event backspaceEvent = LatinIME.createSoftwareKeypressEvent(KeyCode.DELETE, 0,
final Event backspaceEvent = Event.createSoftwareKeypressEvent(KeyCode.DELETE, 0,
event.getMX(), event.getMY(), event.isKeyRepeat());
handleBackspaceEvent(backspaceEvent, inputTransaction, currentKeyboardScript);
inputTransaction.setDidAffectContents();
}
break;
case KeyCode.WORD_LEFT:
sendDownUpKeyEventWithMetaState(ScriptUtils.isScriptRtl(currentKeyboardScript)?
KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.META_CTRL_ON);
sendDownUpKeyEventWithMetaState(
ScriptUtils.isScriptRtl(currentKeyboardScript) ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.META_CTRL_ON | event.getMMetaState());
break;
case KeyCode.WORD_RIGHT:
sendDownUpKeyEventWithMetaState(ScriptUtils.isScriptRtl(currentKeyboardScript)?
KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.META_CTRL_ON);
sendDownUpKeyEventWithMetaState(
ScriptUtils.isScriptRtl(currentKeyboardScript) ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.META_CTRL_ON | event.getMMetaState());
break;
case KeyCode.MOVE_START_OF_PAGE:
final int selectionEnd = mConnection.getExpectedSelectionEnd();
sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_HOME, KeyEvent.META_CTRL_ON);
if (mConnection.getExpectedSelectionStart() > 0 && mConnection.getExpectedSelectionEnd() == selectionEnd) {
// unchanged, and we're not at the top -> try a different method (necessary for compose fields)
mConnection.setSelection(0, 0);
final int selectionEnd1 = mConnection.getExpectedSelectionEnd();
final int selectionStart1 = mConnection.getExpectedSelectionStart();
sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_HOME, KeyEvent.META_CTRL_ON | event.getMMetaState());
if (mConnection.getExpectedSelectionStart() == selectionStart1 && mConnection.getExpectedSelectionEnd() == selectionEnd1) {
// unchanged -> try a different method (necessary for compose fields)
final int newEnd = (event.getMMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? selectionEnd1 : 0;
mConnection.setSelection(0, newEnd);
}
break;
case KeyCode.MOVE_END_OF_PAGE:
final int selectionStart = mConnection.getExpectedSelectionEnd();
sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_END, KeyEvent.META_CTRL_ON);
if (mConnection.getExpectedSelectionStart() == selectionStart) {
final int selectionStart2 = mConnection.getExpectedSelectionStart();
final int selectionEnd2 = mConnection.getExpectedSelectionEnd();
sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_END, KeyEvent.META_CTRL_ON | event.getMMetaState());
if (mConnection.getExpectedSelectionStart() == selectionStart2 && mConnection.getExpectedSelectionEnd() == selectionEnd2) {
// unchanged, try fallback e.g. for compose fields that don't care about ctrl + end
// we just move to a very large index, and hope the field is prepared to deal with this
// getting the actual length of the text for setting the correct position can be tricky for some apps...
try {
mConnection.setSelection(Integer.MAX_VALUE, Integer.MAX_VALUE);
final int newStart = (event.getMMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? selectionStart2 : Integer.MAX_VALUE;
mConnection.setSelection(newStart, Integer.MAX_VALUE);
} catch (Exception e) {
// better catch potential errors and just do nothing in this case
Log.i(TAG, "error when trying to move cursor to last position: " + e);
@ -766,8 +779,10 @@ public final class InputLogic {
case KeyCode.TIMESTAMP:
mLatinIME.onTextInput(TimestampKt.getTimestamp(mLatinIME));
break;
case KeyCode.SEND_INTENT_ONE, KeyCode.SEND_INTENT_TWO, KeyCode.SEND_INTENT_THREE:
IntentUtils.handleSendIntentKey(mLatinIME, event.getMKeyCode());
case KeyCode.IME_HIDE_UI:
mLatinIME.hideWindow();
mLatinIME.requestHideSelf(0);
break;
case KeyCode.VOICE_INPUT:
// switching to shortcut IME, shift state, keyboard,... is handled by LatinIME,
@ -777,23 +792,20 @@ public final class InputLogic {
case KeyCode.CAPS_LOCK, KeyCode.EMOJI, KeyCode.TOGGLE_ONE_HANDED_MODE, KeyCode.SWITCH_ONE_HANDED_MODE:
break;
default:
if (KeyCode.INSTANCE.isModifier(event.getMKeyCode()))
return; // continuation of previous switch case, but modifiers are in a separate place
if (event.getMMetaState() != 0) {
// need to convert codepoint to KeyEvent.KEYCODE_<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 (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) {
sendDownUpKeyEvent(keyEventCode);
sendDownUpKeyEventWithMetaState(keyEventCode, event.getMMetaState());
return;
}
}
throw new RuntimeException("Unknown key code : " + event.getMKeyCode());
// unknown event
Log.e(TAG, "unknown event, key code: "+keyCode+", meta: "+event.getMMetaState());
if (DebugFlags.DEBUG_ENABLED)
throw new RuntimeException("Unknown event");
}
}

View file

@ -58,6 +58,7 @@ object Defaults {
const val PREF_VIBRATE_ON = false
const val PREF_VIBRATE_IN_DND_MODE = false
const val PREF_SOUND_ON = false
const val PREF_SHOW_EMOJI_DESCRIPTIONS = true
@JvmField
var PREF_POPUP_ON = true
const val PREF_AUTO_CORRECTION = true

View file

@ -67,6 +67,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_VIBRATE_ON = "vibrate_on";
public static final String PREF_VIBRATE_IN_DND_MODE = "vibrate_in_dnd_mode";
public static final String PREF_SOUND_ON = "sound_on";
public static final String PREF_SHOW_EMOJI_DESCRIPTIONS = "show_emoji_descriptions";
public static final String PREF_POPUP_ON = "popup_on";
public static final String PREF_AUTO_CORRECTION = "auto_correction";
public static final String PREF_MORE_AUTO_CORRECTION = "more_auto_correction";

View file

@ -54,6 +54,7 @@ public class SettingsValues {
public final boolean mVibrateOn;
public final boolean mVibrateInDndMode;
public final boolean mSoundOn;
public final boolean mShowEmojiDescriptions;
public final boolean mKeyPreviewPopupOn;
public final boolean mShowsVoiceInputKey;
public final boolean mLanguageSwitchKeyToOtherImes;
@ -169,6 +170,7 @@ public class SettingsValues {
mVibrateOn = Settings.readVibrationEnabled(prefs);
mVibrateInDndMode = prefs.getBoolean(Settings.PREF_VIBRATE_IN_DND_MODE, Defaults.PREF_VIBRATE_IN_DND_MODE);
mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, Defaults.PREF_SOUND_ON);
mShowEmojiDescriptions = prefs.getBoolean(Settings.PREF_SHOW_EMOJI_DESCRIPTIONS, Defaults.PREF_SHOW_EMOJI_DESCRIPTIONS);
mKeyPreviewPopupOn = prefs.getBoolean(Settings.PREF_POPUP_ON, Defaults.PREF_POPUP_ON);
mSlidingKeyInputPreviewEnabled = prefs.getBoolean(
DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, Defaults.PREF_SLIDING_KEY_INPUT_PREVIEW);

View file

@ -101,6 +101,10 @@ object DictionaryInfoUtils {
return absoluteDirectoryName
}
@JvmStatic
fun getCachedDictForLocaleAndType(locale: Locale, type: String, context: Context): File? =
getCachedDictsForLocale(locale, context).firstOrNull { it.name.substringBefore("_") == type }
fun getCachedDictsForLocale(locale: Locale, context: Context) =
getCacheDirectoryForLocale(locale, context)?.let { File(it).listFiles() }.orEmpty()

View file

@ -0,0 +1,26 @@
package helium314.keyboard.latin.utils
import android.content.Context
import android.content.Intent
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
import helium314.keyboard.latin.inputlogic.InputLogic
import helium314.keyboard.latin.utils.Log.i
object IntentUtils {
val TAG: String = InputLogic::class.java.simpleName
private val ACTION_SEND_INTENT = "helium314.keyboard.latin.ACTION_SEND_INTENT"
private val EXTRA_NUMBER = "EXTRA_NUMBER"
@JvmStatic
fun handleSendIntentKey(context: Context, mKeyCode: Int) {
val intentNumber = (KeyCode.SEND_INTENT_ONE + 1) - mKeyCode;
val intent: Intent = Intent(ACTION_SEND_INTENT).apply {
putExtra(EXTRA_NUMBER, intentNumber)
}
context.sendBroadcast(intent)
i(TAG, "Sent broadcast for intent number: $intentNumber")
}
}

View file

@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
@ -45,6 +46,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import helium314.keyboard.latin.R
@ -241,7 +243,8 @@ fun ExpandableSearchField(
}) { CloseIcon(android.R.string.cancel) } },
singleLine = true,
colors = colors,
textStyle = contentTextDirectionStyle
textStyle = contentTextDirectionStyle,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search)
)
}
}

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

@ -57,6 +57,7 @@ fun PreferencesScreen(
Settings.PREF_SOUND_ON,
if (prefs.getBoolean(Settings.PREF_SOUND_ON, Defaults.PREF_SOUND_ON))
Settings.PREF_KEYPRESS_SOUND_VOLUME else null,
Settings.PREF_SHOW_EMOJI_DESCRIPTIONS,
R.string.settings_category_additional_keys,
Settings.PREF_SHOW_NUMBER_ROW,
if (SubtypeSettings.getEnabledSubtypes(true).any { it.locale().language in localesWithLocalizedNumberRow })
@ -111,6 +112,14 @@ fun createPreferencesSettings(context: Context) = listOf(
Setting(context, Settings.PREF_SOUND_ON, R.string.sound_on_keypress) {
SwitchPreference(it, Defaults.PREF_SOUND_ON)
},
Setting(
context, Settings.PREF_SHOW_EMOJI_DESCRIPTIONS, R.string.show_emoji_descriptions,
R.string.show_emoji_descriptions_summary
) {
SwitchPreference(it, Defaults.PREF_SHOW_EMOJI_DESCRIPTIONS) {
KeyboardSwitcher.getInstance().reloadKeyboard()
}
},
Setting(context, Settings.PREF_ENABLE_CLIPBOARD_HISTORY,
R.string.enable_clipboard_history, R.string.enable_clipboard_history_summary)
{

View file

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

@ -17,6 +17,10 @@
<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,6 +79,7 @@
kn_IN: Kannada (India)/kannada
kn_IN: Kannada Extended (India)/kannada
ky: Kyrgyz/russian
la: Latin
lg: Luganda/luganda # This is a preliminary keyboard layout.
ln: Lingala/lingala # This is a preliminary keyboard layout.
lo_LA: Lao (Laos)/lao
@ -828,6 +829,15 @@
android:imeSubtypeMode="keyboard"
android:imeSubtypeExtraValue="KeyboardLayoutSet=MAIN:qwertz,AsciiCapable,EmojiCapable"
/>
<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"

View file

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

@ -73,7 +73,7 @@ If the layout has exactly 2 keys in the bottom row, these keys will replace comm
* `0.1` for phones
* `0.09` for tablets
* If the sum of widths in a row is greater than 1, keys are rescaled to fit on the screen
* `labelFlags`: allows specific effects, see [here](app/src/main/res/values/attrs.xml#L251-L287) in the section _keyLabelFlags_ for names and numeric values
* `labelFlags`: allows specific effects, see [here](app/src/main/res/values/attrs.xml#L250-L282) in the section _keyLabelFlags_ for names and numeric values
* Since json does not support hexadecimal-values, you have to use the decimal values in the comments in the same line.
* In case you want to apply multiple flags, you will need to combine them using [bitwise OR](https://en.wikipedia.org/wiki/Bitwise_operation#OR). In most cases this means you can just add the individual values, only exceptions are `fontDefault`, `followKeyLabelRatio`, `followKeyHintLabelRatio`, and `autoScale`.
@ -106,6 +106,12 @@ Usually the label is what is displayed on the key. However, there are some speci
You can also specify special key codes like `a|!code/key_action_previous` or `abc|!code/-10043`, but it's cleaner to use a json layout and specify the code explicitly. Note that when specifying a code in the label, and a code in a json layout, the code in the label will be ignored.
* It's also possible to specify an icon, like `!icon/previous_key|!code/key_action_previous`.
* You can find available icon names in [KeyboardIconsSet](/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt). You can also use toolbar key icons using the uppercase name of the [toolbar key](/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt#L109), e.g. `!icon/redo`
* There are some further special labels to be used in popup keys (i.e. one of the popup keys should have the label)
* `!noPanelAutoPopupKey!`: no popups are shown, a long press will result in the first normal popup of the key being selected
* `!needsDividers!`: dividers are shown between popup keys
* `!hasLabels!`: reduces text size in popup keys for nicer display of labels instead of letters
* `!autoColumnOrder!`: use with a number, e.g. _!autoColumnOrder!4_ will result in 4 popup columns
* `!fixedColumnOrder!`: use with a number, e.g. _!fixedColumnOrder!4_ will result in 4 popup columns. Keys will not be re-ordered if the result is a single line.
## Adding new layouts / languages
* You need a layout file in one of the formats above, and add it to [layouts](app/src/main/assets/layouts)

View file

@ -33,36 +33,6 @@ class EmojiData {
}
private fun onEmojiInserted(group: EmojiGroup, emoji: EmojiSpec): Boolean {
// Unicode RGI does not include letter symbols but Android supports them, so we inject them manually.
if (emoji.codes contentEquals RAW_CPS_KEYCAP_HASH) {
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_A), 2.0f, "regional indicator symbol letter a")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_B), 2.0f, "regional indicator symbol letter b")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_C), 2.0f, "regional indicator symbol letter c")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_D), 2.0f, "regional indicator symbol letter d")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_E), 2.0f, "regional indicator symbol letter e")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_F), 2.0f, "regional indicator symbol letter f")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_G), 2.0f, "regional indicator symbol letter g")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_H), 2.0f, "regional indicator symbol letter h")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_I), 2.0f, "regional indicator symbol letter i")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_J), 2.0f, "regional indicator symbol letter j")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_K), 2.0f, "regional indicator symbol letter k")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_L), 2.0f, "regional indicator symbol letter l")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_M), 2.0f, "regional indicator symbol letter m")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_N), 2.0f, "regional indicator symbol letter n")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_O), 2.0f, "regional indicator symbol letter o")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_P), 2.0f, "regional indicator symbol letter p")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Q), 2.0f, "regional indicator symbol letter q")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_R), 2.0f, "regional indicator symbol letter r")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_S), 2.0f, "regional indicator symbol letter s")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_T), 2.0f, "regional indicator symbol letter t")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_U), 2.0f, "regional indicator symbol letter u")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_V), 2.0f, "regional indicator symbol letter v")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_W), 2.0f, "regional indicator symbol letter w")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_X), 2.0f, "regional indicator symbol letter x")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Y), 2.0f, "regional indicator symbol letter y")
insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Z), 2.0f, "regional indicator symbol letter z")
}
// Some multi-skin-tone variants use a different base code than their non-multi-skin-tone counterparts,
// so they don't get grouped. We drop them here, to prevent each variant from being displayed separately.
return ! hasMultipleSkinModifiers(emoji.codes)
@ -118,9 +88,6 @@ class EmojiData {
}
companion object {
private val RAW_CPS_KEYCAP_HASH = intArrayOf(0x0023, 0xFE0F, 0x20E3)
const val CP_NUL = 0x0000
private const val CP_ZWJ = 0x200D
@ -136,34 +103,5 @@ class EmojiData {
private const val CP_WHITE_HAIR = 0x1F9B3
private const val CP_BARLD = 0x1F9B2
private const val CP_VARIANT_SELECTOR = 0xFE0F
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_A = 0x1F1E6
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_B = 0x1F1E7
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_C = 0x1F1E8
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_D = 0x1F1E9
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_E = 0x1F1EA
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_F = 0x1F1EB
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_G = 0x1F1EC
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_H = 0x1F1ED
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_I = 0x1F1EE
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_J = 0x1F1EF
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_K = 0x1F1F0
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_L = 0x1F1F1
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_M = 0x1F1F2
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_N = 0x1F1F3
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_O = 0x1F1F4
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_P = 0x1F1F5
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Q = 0x1F1F6
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_R = 0x1F1F7
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_S = 0x1F1F8
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_T = 0x1F1F9
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_U = 0x1F1FA
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_V = 0x1F1FB
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_W = 0x1F1FC
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_X = 0x1F1FD
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Y = 0x1F1FE
private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Z = 0x1F1FF
}
}

View file

@ -3866,31 +3866,5 @@ U+1F532 # black square button
U+1F3C1 # chequered flag
U+1F6A9 # triangular flag
U+1F38C # crossed flags
U+1F1E6 # regional indicator symbol letter a
U+1F1E7 # regional indicator symbol letter b
U+1F1E8 # regional indicator symbol letter c
U+1F1E9 # regional indicator symbol letter d
U+1F1EA # regional indicator symbol letter e
U+1F1EB # regional indicator symbol letter f
U+1F1EC # regional indicator symbol letter g
U+1F1ED # regional indicator symbol letter h
U+1F1EE # regional indicator symbol letter i
U+1F1EF # regional indicator symbol letter j
U+1F1F0 # regional indicator symbol letter k
U+1F1F1 # regional indicator symbol letter l
U+1F1F2 # regional indicator symbol letter m
U+1F1F3 # regional indicator symbol letter n
U+1F1F4 # regional indicator symbol letter o
U+1F1F5 # regional indicator symbol letter p
U+1F1F6 # regional indicator symbol letter q
U+1F1F7 # regional indicator symbol letter r
U+1F1F8 # regional indicator symbol letter s
U+1F1F9 # regional indicator symbol letter t
U+1F1FA # regional indicator symbol letter u
U+1F1FB # regional indicator symbol letter v
U+1F1FC # regional indicator symbol letter w
U+1F1FD # regional indicator symbol letter x
U+1F1FE # regional indicator symbol letter y
U+1F1FF # regional indicator symbol letter z
# Above emojis are supported from Android 4.4 (API level 19)