mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-06-28 19:59:55 +00:00
Merge branch 'refs/heads/main' into emoji-inline-search
# Conflicts: # app/src/main/java/helium314/keyboard/latin/RichInputConnection.java # app/src/main/java/helium314/keyboard/latin/utils/TextRange.java
This commit is contained in:
commit
f914cb6524
12 changed files with 227 additions and 116 deletions
|
@ -12,8 +12,8 @@ android {
|
|||
applicationId = "helium314.keyboard"
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
versionCode = 3101
|
||||
versionName = "3.1"
|
||||
versionCode = 3200
|
||||
versionName = "3.2-beta1"
|
||||
ndk {
|
||||
abiFilters.clear()
|
||||
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
|
||||
|
|
|
@ -229,13 +229,15 @@ class KeyboardParser(private val params: KeyboardParams, private val context: Co
|
|||
return
|
||||
// replace comma / period if 2 keys in normal bottom row
|
||||
if (baseKeys.last().size == 2) {
|
||||
val newComma = baseKeys.last()[0]
|
||||
functionalKeysBottom.replaceFirst(
|
||||
{ it.label == KeyLabel.COMMA || it.groupId == KeyData.GROUP_COMMA},
|
||||
{ baseKeys.last()[0].copy(newGroupId = 1, newType = baseKeys.last()[0].type ?: it.type) }
|
||||
{ newComma.copy(newGroupId = 1, newType = newComma.type, newLabelFlags = it.labelFlags or newComma.labelFlags) }
|
||||
)
|
||||
val newPeriod = baseKeys.last()[1]
|
||||
functionalKeysBottom.replaceFirst(
|
||||
{ it.label == KeyLabel.PERIOD || it.groupId == KeyData.GROUP_PERIOD},
|
||||
{ baseKeys.last()[1].copy(newGroupId = 2, newType = baseKeys.last()[1].type ?: it.type) }
|
||||
{ newPeriod.copy(newGroupId = 2, newType = newPeriod.type, newLabelFlags = it.labelFlags or newPeriod.labelFlags) }
|
||||
)
|
||||
baseKeys.removeAt(baseKeys.lastIndex)
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ object KeyCode {
|
|||
const val LANGUAGE_SWITCH = -227
|
||||
|
||||
//const val IME_SHOW_UI = -231
|
||||
//const val IME_HIDE_UI = -232
|
||||
const val IME_HIDE_UI = -232
|
||||
const val VOICE_INPUT = -233
|
||||
|
||||
//const val TOGGLE_SMARTBAR_VISIBILITY = -241
|
||||
|
@ -183,7 +183,7 @@ object KeyCode {
|
|||
REDO, ARROW_DOWN, ARROW_UP, ARROW_RIGHT, ARROW_LEFT, CLIPBOARD_COPY, CLIPBOARD_PASTE, CLIPBOARD_SELECT_ALL,
|
||||
CLIPBOARD_SELECT_WORD, TOGGLE_INCOGNITO_MODE, TOGGLE_AUTOCORRECT, MOVE_START_OF_LINE, MOVE_END_OF_LINE,
|
||||
MOVE_START_OF_PAGE, MOVE_END_OF_PAGE, SHIFT, CAPS_LOCK, MULTIPLE_CODE_POINTS, UNSPECIFIED, CTRL, ALT,
|
||||
FN, CLIPBOARD_CLEAR_HISTORY, NUMPAD,
|
||||
FN, CLIPBOARD_CLEAR_HISTORY, NUMPAD, IME_HIDE_UI,
|
||||
|
||||
// heliboard only
|
||||
SYMBOL_ALPHA, TOGGLE_ONE_HANDED_MODE, SWITCH_ONE_HANDED_MODE, SPLIT_LAYOUT, SHIFT_ENTER,
|
||||
|
|
|
@ -40,8 +40,6 @@ import helium314.keyboard.latin.settings.SpacingAndPunctuations;
|
|||
import helium314.keyboard.latin.utils.CapsModeUtils;
|
||||
import helium314.keyboard.latin.utils.DebugLogUtils;
|
||||
import helium314.keyboard.latin.utils.NgramContextUtils;
|
||||
import helium314.keyboard.latin.utils.ScriptUtils;
|
||||
import helium314.keyboard.latin.utils.SpannableStringUtils;
|
||||
import helium314.keyboard.latin.utils.StatsUtils;
|
||||
import helium314.keyboard.latin.utils.TextRange;
|
||||
|
||||
|
@ -825,15 +823,6 @@ public final class RichInputConnection implements PrivateCommandPerformer {
|
|||
return NgramContextUtils.getNgramContextFromNthPreviousWord(prev, spacingAndPunctuations, n);
|
||||
}
|
||||
|
||||
private static boolean isPartOfCompositionForScript(final int codePoint,
|
||||
final SpacingAndPunctuations spacingAndPunctuations, final String script) {
|
||||
// We always consider word connectors part of compositions.
|
||||
return spacingAndPunctuations.isWordConnector(codePoint)
|
||||
// Otherwise, it's part of composition if it's part of script and not a separator.
|
||||
|| (!spacingAndPunctuations.isWordSeparator(codePoint)
|
||||
&& ScriptUtils.isLetterPartOfScript(codePoint, script));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text surrounding the cursor.
|
||||
*
|
||||
|
@ -860,90 +849,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
|
|||
if (before == null || after == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Going backward, find the first breaking point (separator)
|
||||
int startIndexInBefore = before.length();
|
||||
int endIndexInAfter = -1;
|
||||
while (startIndexInBefore > 0) {
|
||||
final int codePoint = Character.codePointBefore(before, startIndexInBefore);
|
||||
if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, script)) {
|
||||
if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces)
|
||||
break;
|
||||
// continue to the next whitespace and see whether this contains a sometimesWordConnector
|
||||
for (int i = startIndexInBefore - 1; i >= 0; i--) {
|
||||
final char c = before.charAt(i);
|
||||
if (spacingAndPunctuations.isSometimesWordConnector(c)) {
|
||||
// if yes -> whitespace is the index
|
||||
startIndexInBefore = Math.max(StringUtils.charIndexOfLastWhitespace(before), 0);
|
||||
final int firstSpaceAfter = StringUtils.charIndexOfFirstWhitespace(after);
|
||||
endIndexInAfter = firstSpaceAfter == -1 ? after.length() : firstSpaceAfter -1;
|
||||
break;
|
||||
} else if (Character.isWhitespace(c)) {
|
||||
// if no, just break normally
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
--startIndexInBefore;
|
||||
if (Character.isSupplementaryCodePoint(codePoint)) {
|
||||
--startIndexInBefore;
|
||||
}
|
||||
}
|
||||
|
||||
// Find last word separator after the cursor
|
||||
if (endIndexInAfter == -1) {
|
||||
while (++endIndexInAfter < after.length()) {
|
||||
final int codePoint = Character.codePointAt(after, endIndexInAfter);
|
||||
if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, script)) {
|
||||
if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces)
|
||||
break;
|
||||
// continue to the next whitespace and see whether this contains a sometimesWordConnector
|
||||
for (int i = endIndexInAfter; i < after.length(); i++) {
|
||||
final char c = after.charAt(i);
|
||||
if (spacingAndPunctuations.isSometimesWordConnector(c)) {
|
||||
// if yes -> whitespace is next to the index
|
||||
startIndexInBefore = Math.max(StringUtils.charIndexOfLastWhitespace(before), 0);
|
||||
final int firstSpaceAfter = StringUtils.charIndexOfFirstWhitespace(after);
|
||||
endIndexInAfter = firstSpaceAfter == -1 ? after.length() : firstSpaceAfter - 1;
|
||||
break;
|
||||
} else if (Character.isWhitespace(c)) {
|
||||
// if no, just break normally
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (Character.isSupplementaryCodePoint(codePoint)) {
|
||||
++endIndexInAfter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// strip stuff before "//" (i.e. ignore http and other protocols)
|
||||
final String beforeConsideringStart = before.subSequence(startIndexInBefore, before.length()).toString();
|
||||
final int protocolEnd = beforeConsideringStart.lastIndexOf("//");
|
||||
if (protocolEnd != -1)
|
||||
startIndexInBefore += protocolEnd + 1;
|
||||
|
||||
// we don't want the end characters to be word separators
|
||||
while (endIndexInAfter > 0 && spacingAndPunctuations.isWordSeparator(after.charAt(endIndexInAfter - 1))) {
|
||||
--endIndexInAfter;
|
||||
}
|
||||
while (startIndexInBefore < before.length() && spacingAndPunctuations.isWordSeparator(before.charAt(startIndexInBefore))) {
|
||||
++startIndexInBefore;
|
||||
}
|
||||
|
||||
final boolean hasUrlSpans =
|
||||
SpannableStringUtils.hasUrlSpans(before, startIndexInBefore, before.length())
|
||||
|| SpannableStringUtils.hasUrlSpans(after, 0, endIndexInAfter);
|
||||
// We don't use TextUtils#concat because it copies all spans without respect to their
|
||||
// nature. If the text includes a PARAGRAPH span and it has been split, then
|
||||
// TextUtils#concat will crash when it tries to concat both sides of it.
|
||||
return new TextRange(
|
||||
SpannableStringUtils.concatWithNonParagraphSuggestionSpansOnly(before, after),
|
||||
startIndexInBefore, before.length() + endIndexInAfter, before.length(),
|
||||
hasUrlSpans);
|
||||
return StringUtilsKt.getTouchedWordRange(before, after, script, spacingAndPunctuations);
|
||||
}
|
||||
|
||||
public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations,
|
||||
|
@ -956,17 +862,7 @@ public final class RichInputConnection implements PrivateCommandPerformer {
|
|||
// a composing region should always count as a word
|
||||
return true;
|
||||
}
|
||||
final String textBeforeCursor = mCommittedTextBeforeComposingText.toString();
|
||||
int indexOfCodePointInJavaChars = textBeforeCursor.length();
|
||||
int consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE
|
||||
: textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars);
|
||||
// Search for the first non word-connector char
|
||||
if (spacingAndPunctuations.isWordConnector(consideredCodePoint)) {
|
||||
indexOfCodePointInJavaChars -= Character.charCount(consideredCodePoint);
|
||||
consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE
|
||||
: textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars);
|
||||
}
|
||||
return Constants.NOT_A_CODE != consideredCodePoint && spacingAndPunctuations.isWordCodePoint(consideredCodePoint);
|
||||
return StringUtilsKt.endsWithWordCodepoint(mCommittedTextBeforeComposingText.toString(), spacingAndPunctuations);
|
||||
}
|
||||
|
||||
public boolean isCursorFollowedByWordCharacter(
|
||||
|
|
|
@ -6,13 +6,18 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
|
|||
import helium314.keyboard.latin.common.StringUtils.mightBeEmoji
|
||||
import helium314.keyboard.latin.common.StringUtils.newSingleCodePointString
|
||||
import helium314.keyboard.latin.settings.SpacingAndPunctuations
|
||||
import helium314.keyboard.latin.utils.ScriptUtils
|
||||
import helium314.keyboard.latin.utils.SpacedTokens
|
||||
import helium314.keyboard.latin.utils.SpannableStringUtils
|
||||
import helium314.keyboard.latin.utils.TextRange
|
||||
import java.math.BigInteger
|
||||
import java.util.Locale
|
||||
import kotlin.math.max
|
||||
|
||||
fun CharSequence.codePointAt(offset: Int) = Character.codePointAt(this, offset)
|
||||
fun CharSequence.codePointBefore(offset: Int) = Character.codePointBefore(this, offset)
|
||||
|
||||
/** Loops over the codepoints in [text]. Exits when [loop] returns true */
|
||||
inline fun loopOverCodePoints(text: CharSequence, loop: (cp: Int, charCount: Int) -> Boolean) {
|
||||
var offset = 0
|
||||
while (offset < text.length) {
|
||||
|
@ -23,6 +28,7 @@ inline fun loopOverCodePoints(text: CharSequence, loop: (cp: Int, charCount: Int
|
|||
}
|
||||
}
|
||||
|
||||
/** Loops backwards over the codepoints in [text]. Exits when [loop] returns true */
|
||||
inline fun loopOverCodePointsBackwards(text: CharSequence, loop: (cp: Int, charCount: Int) -> Boolean) {
|
||||
var offset = text.length
|
||||
while (offset > 0) {
|
||||
|
@ -88,6 +94,111 @@ fun getFullEmojiAtEnd(text: CharSequence): String {
|
|||
return s.substring(offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the [text] does not end with word separator, ignoring all word connectors.
|
||||
* If the [text] is empty (after ignoring word connectors), the method returns false.
|
||||
*/
|
||||
// todo: this returns true on numbers, why isn't Character.isLetter(code) used?
|
||||
fun endsWithWordCodepoint(text: String, spacingAndPunctuations: SpacingAndPunctuations): Boolean {
|
||||
if (text.isEmpty()) return false
|
||||
var codePoint = 0 // initial value irrelevant since length is always > 0
|
||||
loopOverCodePointsBackwards(text) { cp, _ ->
|
||||
codePoint = cp
|
||||
!spacingAndPunctuations.isWordConnector(cp)
|
||||
}
|
||||
// codePoint might still be a wordConnector (if text consists of wordConnectors)
|
||||
return !spacingAndPunctuations.isWordConnector(codePoint) && !spacingAndPunctuations.isWordSeparator(codePoint)
|
||||
}
|
||||
|
||||
// todo: simplify... maybe compare with original code?
|
||||
fun getTouchedWordRange(before: CharSequence, after: CharSequence, script: String, spacingAndPunctuations: SpacingAndPunctuations): TextRange {
|
||||
// Going backward, find the first breaking point (separator)
|
||||
var startIndexInBefore = before.length
|
||||
var endIndexInAfter = -1 // todo: clarify why might we want to set it when checking before
|
||||
loopOverCodePointsBackwards(before) { codePoint, cpLength ->
|
||||
if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, script)) {
|
||||
if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces)
|
||||
return@loopOverCodePointsBackwards true
|
||||
// continue to the next whitespace and see whether this contains a sometimesWordConnector
|
||||
for (i in startIndexInBefore - 1 downTo 0) {
|
||||
val c = before[i]
|
||||
if (spacingAndPunctuations.isSometimesWordConnector(c.code)) {
|
||||
// if yes -> whitespace is the index
|
||||
startIndexInBefore = max(StringUtils.charIndexOfLastWhitespace(before).toDouble(), 0.0).toInt()
|
||||
val firstSpaceAfter = StringUtils.charIndexOfFirstWhitespace(after)
|
||||
endIndexInAfter = if (firstSpaceAfter == -1) after.length else firstSpaceAfter - 1
|
||||
return@loopOverCodePointsBackwards true
|
||||
} else if (Character.isWhitespace(c)) {
|
||||
// if no, just break normally
|
||||
return@loopOverCodePointsBackwards true
|
||||
}
|
||||
}
|
||||
return@loopOverCodePointsBackwards true
|
||||
}
|
||||
startIndexInBefore -= cpLength
|
||||
false
|
||||
}
|
||||
|
||||
// Find last word separator after the cursor
|
||||
if (endIndexInAfter == -1) {
|
||||
endIndexInAfter = 0
|
||||
loopOverCodePoints(after) { codePoint, cpLength ->
|
||||
if (!isPartOfCompositionForScript(codePoint, spacingAndPunctuations, script)) {
|
||||
if (Character.isWhitespace(codePoint) || !spacingAndPunctuations.mCurrentLanguageHasSpaces)
|
||||
return@loopOverCodePoints true
|
||||
// continue to the next whitespace and see whether this contains a sometimesWordConnector
|
||||
for (i in endIndexInAfter..<after.length) {
|
||||
val c = after[i]
|
||||
if (spacingAndPunctuations.isSometimesWordConnector(c.code)) {
|
||||
// if yes -> whitespace is next to the index
|
||||
startIndexInBefore = max(StringUtils.charIndexOfLastWhitespace(before), 0)
|
||||
val firstSpaceAfter = StringUtils.charIndexOfFirstWhitespace(after)
|
||||
endIndexInAfter = if (firstSpaceAfter == -1) after.length else firstSpaceAfter - 1
|
||||
return@loopOverCodePoints true
|
||||
} else if (Character.isWhitespace(c)) {
|
||||
// if no, just break normally
|
||||
return@loopOverCodePoints true
|
||||
}
|
||||
}
|
||||
return@loopOverCodePoints true
|
||||
}
|
||||
endIndexInAfter += cpLength
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// strip text before "//" (i.e. ignore http and other protocols)
|
||||
val beforeConsideringStart = before.substring(startIndexInBefore, before.length)
|
||||
val protocolEnd = beforeConsideringStart.lastIndexOf("//")
|
||||
if (protocolEnd != -1) startIndexInBefore += protocolEnd + 1
|
||||
|
||||
// we don't want the end characters to be word separators
|
||||
while (endIndexInAfter > 0 && spacingAndPunctuations.isWordSeparator(after[endIndexInAfter - 1].code)) {
|
||||
--endIndexInAfter
|
||||
}
|
||||
while (startIndexInBefore < before.length && spacingAndPunctuations.isWordSeparator(before[startIndexInBefore].code)) {
|
||||
++startIndexInBefore
|
||||
}
|
||||
|
||||
val hasUrlSpans = SpannableStringUtils.hasUrlSpans(before, startIndexInBefore, before.length)
|
||||
|| SpannableStringUtils.hasUrlSpans(after, 0, endIndexInAfter)
|
||||
|
||||
// We don't use TextUtils#concat because it copies all spans without respect to their
|
||||
// nature. If the text includes a PARAGRAPH span and it has been split, then
|
||||
// TextUtils#concat will crash when it tries to concat both sides of it.
|
||||
return TextRange(
|
||||
SpannableStringUtils.concatWithNonParagraphSuggestionSpansOnly(before, after),
|
||||
startIndexInBefore, before.length + endIndexInAfter, before.length,
|
||||
hasUrlSpans
|
||||
)
|
||||
}
|
||||
|
||||
// actually this should not be in STRING Utils, but only used for getTouchedWordRange
|
||||
private fun isPartOfCompositionForScript(codePoint: Int, spacingAndPunctuations: SpacingAndPunctuations, script: String) =
|
||||
spacingAndPunctuations.isWordConnector(codePoint) // We always consider word connectors part of compositions.
|
||||
// Otherwise, it's part of composition if it's part of script and not a separator.
|
||||
|| (!spacingAndPunctuations.isWordSeparator(codePoint) && ScriptUtils.isLetterPartOfScript(codePoint, script))
|
||||
|
||||
/** split the string on the first of consecutive space only, further consecutive spaces are added to the next split */
|
||||
fun String.splitOnFirstSpacesOnly(): List<String> {
|
||||
val out = mutableListOf<String>()
|
||||
|
|
|
@ -776,6 +776,9 @@ public final class InputLogic {
|
|||
case KeyCode.TIMESTAMP:
|
||||
mLatinIME.onTextInput(TimestampKt.getTimestamp(mLatinIME));
|
||||
break;
|
||||
case KeyCode.IME_HIDE_UI:
|
||||
mLatinIME.hideWindow();
|
||||
break;
|
||||
case KeyCode.VOICE_INPUT:
|
||||
// switching to shortcut IME, shift state, keyboard,... is handled by LatinIME,
|
||||
// {@link KeyboardSwitcher#onEvent(Event)}, or {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
|
||||
|
|
|
@ -7,9 +7,13 @@
|
|||
package helium314.keyboard.latin.utils;
|
||||
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.SuggestionSpan;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a range of text, relative to the current cursor position.
|
||||
|
@ -95,6 +99,28 @@ public final class TextRange {
|
|||
return writeIndex == readIndex ? spans : Arrays.copyOfRange(spans, 0, writeIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof TextRange textRange)) return false;
|
||||
return mWordAtCursorStartIndex == textRange.mWordAtCursorStartIndex
|
||||
&& mWordAtCursorEndIndex == textRange.mWordAtCursorEndIndex
|
||||
&& mCursorIndex == textRange.mCursorIndex
|
||||
&& mHasUrlSpans == textRange.mHasUrlSpans
|
||||
&& TextUtils.equals(mTextAtCursor, textRange.mTextAtCursor)
|
||||
&& TextUtils.equals(mWord, textRange.mWord);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mTextAtCursor, mWordAtCursorStartIndex, mWordAtCursorEndIndex, mCursorIndex, mWord, mHasUrlSpans);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return mTextAtCursor + ", " + mWord + ", " + mCursorIndex;
|
||||
}
|
||||
|
||||
public TextRange(final CharSequence textAtCursor, final int wordAtCursorStartIndex,
|
||||
final int wordAtCursorEndIndex, final int cursorIndex, final boolean hasUrlSpans) {
|
||||
if (wordAtCursorStartIndex < 0 || cursorIndex < wordAtCursorStartIndex
|
||||
|
|
|
@ -79,7 +79,8 @@ fun AppearanceScreen(
|
|||
SettingsWithoutKey.CUSTOM_FONT,
|
||||
Settings.PREF_FONT_SCALE,
|
||||
Settings.PREF_EMOJI_FONT_SCALE,
|
||||
Settings.PREF_EMOJI_KEY_FIT,
|
||||
if (prefs.getFloat(Settings.PREF_EMOJI_FONT_SCALE, Defaults.PREF_EMOJI_FONT_SCALE) != 1f)
|
||||
Settings.PREF_EMOJI_KEY_FIT else null,
|
||||
if (prefs.getInt(Settings.PREF_EMOJI_MAX_SDK, Defaults.PREF_EMOJI_MAX_SDK) >= 24)
|
||||
Settings.PREF_EMOJI_SKIN_TONE else null,
|
||||
)
|
||||
|
@ -109,6 +110,7 @@ fun createAppearanceSettings(context: Context) = listOf(
|
|||
prefs.edit().remove(Settings.PREF_THEME_COLORS_NIGHT).apply()
|
||||
}
|
||||
KeyboardIconsSet.needsReload = true // only relevant for Settings.PREF_CUSTOM_ICON_NAMES
|
||||
KeyboardSwitcher.getInstance().setThemeNeedsReload()
|
||||
}
|
||||
},
|
||||
Setting(context, Settings.PREF_ICON_STYLE, R.string.icon_style) { setting ->
|
||||
|
|
|
@ -57,8 +57,11 @@ fun DictionaryScreen(
|
|||
val enabledLanguages = SubtypeSettings.getEnabledSubtypes(true).map { it.locale().language }
|
||||
val cachedDictFolders = DictionaryInfoUtils.getCacheDirectories(ctx).map { it.name }
|
||||
val comparer = compareBy<Locale>({ it.language !in enabledLanguages }, { it.toLanguageTag() !in cachedDictFolders}, { it.displayName })
|
||||
val dictionaryLocales = remember { getDictionaryLocales(ctx).sortedWith(comparer).toMutableList() }
|
||||
dictionaryLocales.add(0, Locale(SubtypeLocaleUtils.NO_LANGUAGE))
|
||||
val dictionaryLocales = remember {
|
||||
getDictionaryLocales(ctx).sortedWith(comparer).toMutableList().apply {
|
||||
add(0, Locale(SubtypeLocaleUtils.NO_LANGUAGE))
|
||||
}
|
||||
}
|
||||
var selectedLocale: Locale? by remember { mutableStateOf(null) }
|
||||
var showAddDictDialog by remember { mutableStateOf(false) }
|
||||
val dictPicker = dictionaryFilePicker(selectedLocale)
|
||||
|
|
|
@ -4,9 +4,13 @@ package helium314.keyboard.latin
|
|||
import androidx.test.core.app.ApplicationProvider
|
||||
import helium314.keyboard.ShadowInputMethodManager2
|
||||
import helium314.keyboard.latin.common.StringUtils
|
||||
import helium314.keyboard.latin.common.endsWithWordCodepoint
|
||||
import helium314.keyboard.latin.common.getFullEmojiAtEnd
|
||||
import helium314.keyboard.latin.common.getTouchedWordRange
|
||||
import helium314.keyboard.latin.common.nonWordCodePointAndNoSpaceBeforeCursor
|
||||
import helium314.keyboard.latin.settings.SpacingAndPunctuations
|
||||
import helium314.keyboard.latin.utils.ScriptUtils
|
||||
import helium314.keyboard.latin.utils.TextRange
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
@ -60,6 +64,54 @@ class StringUtilsTest {
|
|||
assert(nonWordCodePointAndNoSpaceBeforeCursor("th.is", sp))
|
||||
}
|
||||
|
||||
@Test fun `is word-like at end`() {
|
||||
val sp = SpacingAndPunctuations(ApplicationProvider.getApplicationContext<App>().resources, false)
|
||||
assert(!endsWithWordCodepoint("", sp))
|
||||
assert(endsWithWordCodepoint("don'", sp))
|
||||
assert(!endsWithWordCodepoint("hello!", sp))
|
||||
assert(!endsWithWordCodepoint("when ", sp))
|
||||
assert(endsWithWordCodepoint("3-", sp)) // todo: this seems wrong
|
||||
assert(endsWithWordCodepoint("5'", sp)) // todo: this seems wrong
|
||||
assert(endsWithWordCodepoint("1", sp)) // todo: this seems wrong
|
||||
assert(endsWithWordCodepoint("a-", sp))
|
||||
assert(!endsWithWordCodepoint("--", sp))
|
||||
}
|
||||
|
||||
@Test fun `get touched text range`() {
|
||||
val sp = SpacingAndPunctuations(ApplicationProvider.getApplicationContext<App>().resources, false)
|
||||
val spUrl = SpacingAndPunctuations(ApplicationProvider.getApplicationContext<App>().resources, true)
|
||||
val script = ScriptUtils.SCRIPT_LATIN
|
||||
checkTextRange("blabla this is v", "ery good", sp, script, 15, 19)
|
||||
checkTextRange(".hel", "lo...", sp, script, 1, 6)
|
||||
checkTextRange("(hi", ")", sp, script, 1, 3)
|
||||
checkTextRange("", "word", sp, script, 0, 4)
|
||||
|
||||
checkTextRange("mail: blorb@", "florb.com or", sp, script, 12, 17)
|
||||
checkTextRange("mail: blorb@", "florb.com or", spUrl, script, 6, 21)
|
||||
checkTextRange("mail: blor", "b@florb.com or", sp, script, 6, 11)
|
||||
checkTextRange("mail: blor", "b@florb.com or", spUrl, script, 6, 21)
|
||||
checkTextRange("mail: blorb@f", "lorb.com or", sp, script, 12, 17)
|
||||
checkTextRange("mail: blorb@f", "lorb.com or", spUrl, script, 6, 21)
|
||||
|
||||
checkTextRange("http://exam", "ple.com", sp, script, 7, 14)
|
||||
checkTextRange("http://exam", "ple.com", spUrl, script, 7, 18)
|
||||
checkTextRange("http://example.", "com", sp, script, 15, 18)
|
||||
checkTextRange("http://example.", "com", spUrl, script, 7, 18)
|
||||
checkTextRange("htt", "p://example.com", sp, script, 0, 4)
|
||||
checkTextRange("htt", "p://example.com", spUrl, script, 0, 18)
|
||||
checkTextRange("http:/", "/example.com", sp, script, 6, 6)
|
||||
checkTextRange("http:/", "/example.com", spUrl, script, 0, 18)
|
||||
|
||||
checkTextRange("..", ".", spUrl, script, 2, 2)
|
||||
checkTextRange("...", "", spUrl, script, 3, 3)
|
||||
|
||||
// todo: these are bad cases of url detection
|
||||
// also: sometimesWordConnectors are for URL and should be named accordingly
|
||||
checkTextRange("@@@", "@@@", spUrl, script, 0, 6)
|
||||
checkTextRange("a...", "", spUrl, script, 0, 4)
|
||||
checkTextRange("@@@", "", spUrl, script, 0, 3)
|
||||
}
|
||||
|
||||
@Test fun detectEmojisAtEnd() {
|
||||
assertEquals("", getFullEmojiAtEnd("\uD83C\uDF83 "))
|
||||
assertEquals("", getFullEmojiAtEnd("a"))
|
||||
|
@ -87,4 +139,10 @@ class StringUtilsTest {
|
|||
// could help towards fully fixing https://github.com/Helium314/HeliBoard/issues/22
|
||||
// though this might be tricky, as some emojis will show as one on new Android versions, and
|
||||
// as two on older versions
|
||||
|
||||
private fun checkTextRange(before: String, after: String, sp: SpacingAndPunctuations, script: String, wordStart: Int, WordEnd: Int) {
|
||||
val got = getTouchedWordRange(before, after, script, sp)
|
||||
val wanted = TextRange(before + after, wordStart, WordEnd, before.length, false)
|
||||
assertEquals(wanted, got)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ buildscript {
|
|||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.9.2")
|
||||
classpath("com.android.tools.build:gradle:8.9.3")
|
||||
classpath(kotlin("gradle-plugin", version = kotlinVersion))
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
|
10
fastlane/metadata/android/en-US/changelogs/3200.txt
Normal file
10
fastlane/metadata/android/en-US/changelogs/3200.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
* add toolbar modes (allows hiding toolbar)
|
||||
* add some missing emoji variants
|
||||
* improve subtype screen and dictionary dialog
|
||||
* fix colors when forcing dark mode
|
||||
* move most of the portrait / landscape scale settings into a dialog
|
||||
* remove translations of strings marked as non-translatable
|
||||
* fix next-screen arrow direction for RTL languages
|
||||
* fix proper loading of Hebrew locale on Android 15
|
||||
* have at least a basic keyboard when library doesn't work at all
|
||||
* minor bug fixes
|
Loading…
Add table
Add a link
Reference in a new issue