mirror of
https://github.com/Julow/Unexpected-Keyboard.git
synced 2025-08-19 10:52:32 +02:00
Compare commits
9 Commits
edge-to-ed
...
1.30.2
Author | SHA1 | Date | |
---|---|---|---|
|
e4695e1ff4 | ||
|
a9b78923c0 | ||
|
d9b5b36c27 | ||
|
5b5d8c692e | ||
|
926b99cbfe | ||
|
52af262e16 | ||
|
0d5954cc3a | ||
|
370f921bc3 | ||
|
57dbf3292f |
@@ -10,11 +10,13 @@
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.view.im" android:resource="@xml/method"/>
|
||||
</service>
|
||||
<activity android:name="juloo.keyboard2.SettingsActivity" android:icon="@mipmap/ic_launcher" android:label="@string/settings_activity_label" android:theme="@style/appTheme" android:exported="true" android:directBootAware="true">
|
||||
|
||||
<activity android:name="juloo.keyboard2.SettingsActivity" android:icon="@mipmap/ic_launcher" android:label="@string/settings_activity_label" android:theme="@style/settingsTheme" android:exported="true" android:directBootAware="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="juloo.keyboard2.LauncherActivity" android:icon="@mipmap/ic_launcher" android:theme="@style/appTheme" android:exported="true" android:directBootAware="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
@@ -12,10 +12,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "juloo.keyboard2"
|
||||
minSdk 11
|
||||
minSdk 21
|
||||
targetSdkVersion 35
|
||||
versionCode 44
|
||||
versionName "1.30.1"
|
||||
versionCode 45
|
||||
versionName "1.30.2"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
1
fastlane/metadata/android/en-US/changelogs/45.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/45.txt
Normal file
@@ -0,0 +1 @@
|
||||
Bug fixes
|
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true">
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" android:fitsSystemWindows="true">
|
||||
<LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical">
|
||||
<TextView style="@style/paragraph" android:text="@string/launcher_description"/>
|
||||
<Button style="@style/paragraph" android:text="@string/launcher_button_imesettings" android:onClick="launch_imesettings" android:layout_width="wrap_content"/>
|
||||
|
@@ -80,4 +80,9 @@
|
||||
<item name="android:orientation">horizontal</item>
|
||||
</style>
|
||||
<style name="appTheme" parent="@android:style/Theme.DeviceDefault.DayNight"/>
|
||||
<style name="settingsTheme" parent="appTheme">
|
||||
<!-- Setting this in the activity theme so it propagate to nested
|
||||
preference screens. -->
|
||||
<item name="android:fitsSystemWindows">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
@@ -6,5 +6,9 @@
|
||||
<dimen name="emoji_text_size">28dp</dimen>
|
||||
<dimen name="clipboard_view_height">300dp</dimen>
|
||||
<dimen name="pref_button_size">28dp</dimen>
|
||||
<bool name="debug_logs">false</bool> <!-- Will be overwritten automatically by Gradle for the debug build variant -->
|
||||
<!-- Margin needed to accomodate the gesture nav bar on Android 15. Found in
|
||||
[core/res/res/values/dimens.xml]. -->
|
||||
<dimen name="bottom_inset_min">48dp</dimen>
|
||||
<!-- Will be overwritten automatically by Gradle for the debug build variant -->
|
||||
<bool name="debug_logs">false</bool>
|
||||
</resources>
|
||||
|
@@ -21,6 +21,7 @@
|
||||
<subtype android:label="%s" android:languageTag="en-IN" android:imeSubtypeLocale="en_IN" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=latin,default_layout=latn_qwerty_us"/>
|
||||
<subtype android:label="%s" android:languageTag="en-US" android:imeSubtypeLocale="en_US" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=latin,default_layout=latn_qwerty_us"/>
|
||||
<subtype android:label="%s" android:languageTag="es" android:imeSubtypeLocale="es_ES" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=latin,default_layout=latn_qwerty_es,extra_keys=accent_aigu:á:é:í:ó:ú@d|accent_tilde:ñ@n|accent_grave@f|accent_trema@u|€"/>
|
||||
<subtype android:label="%s" android:languageTag="et" android:imeSubtypeLocale="et_EE" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=latin,default_layout=latn_qwerty_et,extra_keys=accent_trema:ä:ö:ü@u|accent_tilde:õ@o|accent_caron:š:ž@s|€"/>
|
||||
<subtype android:label="%s" android:languageTag="fa" android:imeSubtypeLocale="fa_IR" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="default_layout=arab_pc_ir"/>
|
||||
<subtype android:label="%s" android:languageTag="fr-BE" android:imeSubtypeLocale="fr_BE" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=latin,default_layout=latn_azerty_be,extra_keys=accent_grave:è@f|accent_aigu:á:é:í:ó:ú:ý:j́@d|accent_circonflexe:ê@f|accent_cedille:ç@c|accent_trema@u|€"/>
|
||||
<subtype android:label="%s" android:languageTag="fr-CA" android:imeSubtypeLocale="fr_CA" android:imeSubtypeMode="keyboard" android:isAsciiCapable="true" android:imeSubtypeExtraValue="script=latin,default_layout=latn_azerty_fr,extra_keys=accent_grave:à:è:ù@f|accent_aigu:é@d|accent_circonflexe:â:ê:ô@f|accent_cedille:ç@c|accent_trema:ë:ï:ü:ÿ@u"/>
|
||||
|
38
shell.nix
38
shell.nix
@@ -11,8 +11,36 @@ let
|
||||
buildToolsVersions = [ build_tools_version ];
|
||||
platformVersions = [ "34" ];
|
||||
abiVersions = [ "armeabi-v7a" ];
|
||||
inherit repoJson;
|
||||
};
|
||||
|
||||
# Ensure we have the needed system images
|
||||
repoJson = pkgs.fetchurl {
|
||||
url =
|
||||
"https://raw.githubusercontent.com/NixOS/nixpkgs/ebc7402410a3ce2d25622137c190d4ab83945c10/pkgs/development/mobile/androidenv/repo.json";
|
||||
hash = "sha256-4/0FMyxM+7d66qfhlY3A10RIe6j6VrW8DIilH2eQyzc=";
|
||||
};
|
||||
|
||||
emulators = let
|
||||
mk_emulator = { platformVersion, device ? "pixel_6", abiVersion ? "x86_64", systemImageType ? "default" }:
|
||||
pkgs.androidenv.emulateApp rec {
|
||||
name = "emulator_api${platformVersion}";
|
||||
inherit platformVersion abiVersion systemImageType;
|
||||
androidAvdFlags = "--device ${device}";
|
||||
sdkExtraArgs = { inherit repoJson; };
|
||||
};
|
||||
# Allow to install several emulators in the same environment
|
||||
link_emulator = version_name: args: {
|
||||
name = "bin/emulate_android_${version_name}";
|
||||
path = "${mk_emulator args}/bin/run-test-emulator";
|
||||
};
|
||||
in pkgs.linkFarm "emulator" [
|
||||
(link_emulator "5" { platformVersion = "21"; })
|
||||
# (link_emulator "14" { platformVersion = "34"; })
|
||||
# There's no 'default' image for Android 15
|
||||
(link_emulator "15" { platformVersion = "35"; systemImageType = "google_apis"; })
|
||||
];
|
||||
|
||||
ANDROID_SDK_ROOT = "${android.androidsdk}/libexec/android-sdk";
|
||||
|
||||
gradle = pkgs.gradle.override { java = jdk; };
|
||||
@@ -27,8 +55,14 @@ let
|
||||
'';
|
||||
|
||||
in pkgs.mkShell {
|
||||
buildInputs =
|
||||
[ pkgs.findutils pkgs.fontforge jdk android.androidsdk gradle_wrapped ];
|
||||
buildInputs = [
|
||||
pkgs.findutils
|
||||
pkgs.fontforge
|
||||
jdk
|
||||
android.androidsdk
|
||||
gradle_wrapped
|
||||
emulators
|
||||
];
|
||||
JAVA_HOME = jdk.home;
|
||||
inherit ANDROID_SDK_ROOT;
|
||||
}
|
||||
|
@@ -5,14 +5,9 @@ import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import juloo.keyboard2.prefs.CustomExtraKeysPreference;
|
||||
import juloo.keyboard2.prefs.ExtraKeysPreference;
|
||||
import juloo.keyboard2.prefs.LayoutsPreference;
|
||||
@@ -28,10 +23,6 @@ public final class Config
|
||||
public final float labelTextSize;
|
||||
public final float sublabelTextSize;
|
||||
|
||||
public final KeyboardData.Row bottom_row;
|
||||
public final KeyboardData.Row number_row;
|
||||
public final KeyboardData num_pad;
|
||||
|
||||
// From preferences
|
||||
/** [null] represent the [system] layout. */
|
||||
public List<KeyboardData> layouts;
|
||||
@@ -84,6 +75,7 @@ public final class Config
|
||||
[get_current_layout()] and [set_current_layout()]. */
|
||||
int current_layout_portrait;
|
||||
int current_layout_landscape;
|
||||
public int bottomInsetMin;
|
||||
|
||||
private Config(SharedPreferences prefs, Resources res, IKeyEventHandler h)
|
||||
{
|
||||
@@ -93,16 +85,6 @@ public final class Config
|
||||
keyPadding = res.getDimension(R.dimen.key_padding);
|
||||
labelTextSize = 0.33f;
|
||||
sublabelTextSize = 0.22f;
|
||||
try
|
||||
{
|
||||
number_row = KeyboardData.load_number_row(res);
|
||||
bottom_row = KeyboardData.load_bottom_row(res);
|
||||
num_pad = KeyboardData.load_num_pad(res);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage()); // Not recoverable
|
||||
}
|
||||
// from prefs
|
||||
refresh(res);
|
||||
// initialized later
|
||||
@@ -187,6 +169,8 @@ public final class Config
|
||||
current_layout_landscape = _prefs.getInt("current_layout_landscape", 0);
|
||||
circle_sensitivity = Integer.valueOf(_prefs.getString("circle_sensitivity", "2"));
|
||||
clipboard_history_enabled = _prefs.getBoolean("clipboard_history_enabled", false);
|
||||
bottomInsetMin = Utils.is_navigation_bar_gestural(res) ?
|
||||
(int)res.getDimension(R.dimen.bottom_inset_min) : 0;
|
||||
}
|
||||
|
||||
public int get_current_layout()
|
||||
@@ -213,190 +197,6 @@ public final class Config
|
||||
_prefs.edit().putBoolean("clipboard_history_enabled", e).commit();
|
||||
}
|
||||
|
||||
KeyValue action_key()
|
||||
{
|
||||
// Update the name to avoid caching in KeyModifier
|
||||
return (actionLabel == null) ? null : KeyValue.makeActionKey(actionLabel);
|
||||
}
|
||||
|
||||
/** Update the layout according to the configuration.
|
||||
* - Remove the switching key if it isn't needed
|
||||
* - Remove "localized" keys from other locales (not in 'extra_keys')
|
||||
* - Replace the action key to show the right label
|
||||
* - Swap the enter and action keys
|
||||
* - Add the optional numpad and number row
|
||||
* - Add the extra keys
|
||||
*/
|
||||
public KeyboardData modify_layout(KeyboardData kw)
|
||||
{
|
||||
final KeyValue action_key = action_key();
|
||||
// Extra keys are removed from the set as they are encountered during the
|
||||
// first iteration then automatically added.
|
||||
final Map<KeyValue, KeyboardData.PreferredPos> extra_keys = new HashMap<KeyValue, KeyboardData.PreferredPos>();
|
||||
final Set<KeyValue> remove_keys = new HashSet<KeyValue>();
|
||||
// Make sure the config key is accessible to avoid being locked in a custom
|
||||
// layout.
|
||||
extra_keys.put(KeyValue.getKeyByName("config"), KeyboardData.PreferredPos.ANYWHERE);
|
||||
extra_keys.putAll(extra_keys_param);
|
||||
extra_keys.putAll(extra_keys_custom);
|
||||
if (extra_keys_subtype != null && kw.locale_extra_keys)
|
||||
{
|
||||
Set<KeyValue> present = new HashSet<KeyValue>();
|
||||
present.addAll(kw.getKeys().keySet());
|
||||
present.addAll(extra_keys_param.keySet());
|
||||
present.addAll(extra_keys_custom.keySet());
|
||||
extra_keys_subtype.compute(extra_keys,
|
||||
new ExtraKeys.Query(kw.script, present));
|
||||
}
|
||||
KeyboardData.Row added_number_row = null;
|
||||
if (add_number_row && !show_numpad)
|
||||
added_number_row = modify_number_row(number_row, kw);
|
||||
if (added_number_row != null)
|
||||
remove_keys.addAll(added_number_row.getKeys(0).keySet());
|
||||
if (kw.bottom_row)
|
||||
kw = kw.insert_row(bottom_row, kw.rows.size());
|
||||
kw = kw.mapKeys(new KeyboardData.MapKeyValues() {
|
||||
public KeyValue apply(KeyValue key, boolean localized)
|
||||
{
|
||||
boolean is_extra_key = extra_keys.containsKey(key);
|
||||
if (is_extra_key)
|
||||
extra_keys.remove(key);
|
||||
if (localized && !is_extra_key)
|
||||
return null;
|
||||
if (remove_keys.contains(key))
|
||||
return null;
|
||||
switch (key.getKind())
|
||||
{
|
||||
case Event:
|
||||
switch (key.getEvent())
|
||||
{
|
||||
case CHANGE_METHOD_PICKER:
|
||||
if (switch_input_immediate)
|
||||
return KeyValue.getKeyByName("change_method_prev");
|
||||
return key;
|
||||
case ACTION:
|
||||
return (swapEnterActionKey && action_key != null) ?
|
||||
KeyValue.getKeyByName("enter") : action_key;
|
||||
case SWITCH_FORWARD:
|
||||
return (layouts.size() > 1) ? key : null;
|
||||
case SWITCH_BACKWARD:
|
||||
return (layouts.size() > 2) ? key : null;
|
||||
case SWITCH_VOICE_TYPING:
|
||||
case SWITCH_VOICE_TYPING_CHOOSER:
|
||||
return shouldOfferVoiceTyping ? key : null;
|
||||
}
|
||||
break;
|
||||
case Keyevent:
|
||||
switch (key.getKeyevent())
|
||||
{
|
||||
case KeyEvent.KEYCODE_ENTER:
|
||||
return (swapEnterActionKey && action_key != null) ? action_key : key;
|
||||
}
|
||||
break;
|
||||
case Modifier:
|
||||
switch (key.getModifier())
|
||||
{
|
||||
case SHIFT:
|
||||
if (double_tap_lock_shift)
|
||||
return key.withFlags(key.getFlags() | KeyValue.FLAG_LOCK);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
});
|
||||
if (show_numpad)
|
||||
kw = kw.addNumPad(modify_numpad(num_pad, kw));
|
||||
if (extra_keys.size() > 0)
|
||||
kw = kw.addExtraKeys(extra_keys.entrySet().iterator());
|
||||
if (added_number_row != null)
|
||||
kw = kw.insert_row(added_number_row, 0);
|
||||
return kw;
|
||||
}
|
||||
|
||||
/** Handle the numpad layout. The [main_kw] is used to adapt the numpad to
|
||||
the main layout's script. */
|
||||
public KeyboardData modify_numpad(KeyboardData kw, KeyboardData main_kw)
|
||||
{
|
||||
final KeyValue action_key = action_key();
|
||||
final int map_digit = KeyModifier.modify_numpad_script(main_kw.numpad_script);
|
||||
return kw.mapKeys(new KeyboardData.MapKeyValues() {
|
||||
public KeyValue apply(KeyValue key, boolean localized)
|
||||
{
|
||||
switch (key.getKind())
|
||||
{
|
||||
case Event:
|
||||
switch (key.getEvent())
|
||||
{
|
||||
case ACTION:
|
||||
return (swapEnterActionKey && action_key != null) ?
|
||||
KeyValue.getKeyByName("enter") : action_key;
|
||||
}
|
||||
break;
|
||||
case Keyevent:
|
||||
switch (key.getKeyevent())
|
||||
{
|
||||
case KeyEvent.KEYCODE_ENTER:
|
||||
return (swapEnterActionKey && action_key != null) ? action_key : key;
|
||||
}
|
||||
break;
|
||||
case Char:
|
||||
char prev_c = key.getChar();
|
||||
char c = prev_c;
|
||||
if (inverse_numpad)
|
||||
c = inverse_numpad_char(c);
|
||||
if (map_digit != -1)
|
||||
{
|
||||
KeyValue modified = ComposeKey.apply(map_digit, c);
|
||||
if (modified != null) // Was modified by script
|
||||
return modified;
|
||||
}
|
||||
if (prev_c != c) // Was inverted
|
||||
return key.withChar(c);
|
||||
break;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static KeyboardData.MapKeyValues numpad_script_map(String numpad_script)
|
||||
{
|
||||
final int map_digit = KeyModifier.modify_numpad_script(numpad_script);
|
||||
if (map_digit == -1)
|
||||
return null;
|
||||
return new KeyboardData.MapKeyValues() {
|
||||
public KeyValue apply(KeyValue key, boolean localized)
|
||||
{
|
||||
switch (key.getKind())
|
||||
{
|
||||
case Char:
|
||||
KeyValue modified = ComposeKey.apply(map_digit, key.getChar());
|
||||
if (modified != null)
|
||||
return modified;
|
||||
break;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Modify the pin entry layout. [main_kw] is used to map the digits into the
|
||||
same script. */
|
||||
public KeyboardData modify_pinentry(KeyboardData kw, KeyboardData main_kw)
|
||||
{
|
||||
KeyboardData.MapKeyValues m = numpad_script_map(main_kw.numpad_script);
|
||||
return m == null ? kw : kw.mapKeys(m);
|
||||
}
|
||||
|
||||
/** Modify the number row according to [main_kw]'s script. */
|
||||
public KeyboardData.Row modify_number_row(KeyboardData.Row row,
|
||||
KeyboardData main_kw)
|
||||
{
|
||||
KeyboardData.MapKeyValues m = numpad_script_map(main_kw.numpad_script);
|
||||
return m == null ? row : row.mapKeys(m);
|
||||
}
|
||||
|
||||
private float get_dip_pref(DisplayMetrics dm, String pref_name, float def)
|
||||
{
|
||||
float value;
|
||||
@@ -443,20 +243,6 @@ public final class Config
|
||||
}
|
||||
}
|
||||
|
||||
char inverse_numpad_char(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '7': return '1';
|
||||
case '8': return '2';
|
||||
case '9': return '3';
|
||||
case '1': return '7';
|
||||
case '2': return '8';
|
||||
case '3': return '9';
|
||||
default: return c;
|
||||
}
|
||||
}
|
||||
|
||||
private static Config _globalConfig = null;
|
||||
|
||||
public static void initGlobalConfig(SharedPreferences prefs, Resources res,
|
||||
@@ -464,6 +250,7 @@ public final class Config
|
||||
{
|
||||
migrate(prefs);
|
||||
_globalConfig = new Config(prefs, res, handler);
|
||||
LayoutModifier.init(_globalConfig, res);
|
||||
}
|
||||
|
||||
public static Config globalConfig()
|
||||
|
@@ -291,6 +291,8 @@ public final class KeyValue implements Comparable<KeyValue>
|
||||
|
||||
private KeyValue(Object p, int kind, int value, int flags)
|
||||
{
|
||||
if (p == null)
|
||||
throw new NullPointerException("KeyValue payload cannot be null");
|
||||
_payload = p;
|
||||
_code = (kind & KIND_BITS) | (flags & FLAGS_BITS) | (value & VALUE_BITS);
|
||||
}
|
||||
|
@@ -62,7 +62,7 @@ public class Keyboard2 extends InputMethodService
|
||||
{
|
||||
if (_currentSpecialLayout != null)
|
||||
return _currentSpecialLayout;
|
||||
return _config.modify_layout(current_layout_unmodified());
|
||||
return LayoutModifier.modify_layout(current_layout_unmodified());
|
||||
}
|
||||
|
||||
void setTextLayout(int l)
|
||||
@@ -92,13 +92,13 @@ public class Keyboard2 extends InputMethodService
|
||||
/** Load a layout that contains a numpad. */
|
||||
KeyboardData loadNumpad(int layout_id)
|
||||
{
|
||||
return _config.modify_numpad(KeyboardData.load(getResources(), layout_id),
|
||||
return LayoutModifier.modify_numpad(KeyboardData.load(getResources(), layout_id),
|
||||
current_layout_unmodified());
|
||||
}
|
||||
|
||||
KeyboardData loadPinentry(int layout_id)
|
||||
{
|
||||
return _config.modify_pinentry(KeyboardData.load(getResources(), layout_id),
|
||||
return LayoutModifier.modify_pinentry(KeyboardData.load(getResources(), layout_id),
|
||||
current_layout_unmodified());
|
||||
}
|
||||
|
||||
@@ -292,6 +292,16 @@ public class Keyboard2 extends InputMethodService
|
||||
|
||||
private void updateSoftInputWindowLayoutParams() {
|
||||
final Window window = getWindow().getWindow();
|
||||
// On API >= 35, Keyboard2View behaves as edge-to-edge
|
||||
// APIs 30 to 34 have visual artifact when edge-to-edge is enabled
|
||||
if (VERSION.SDK_INT >= 35)
|
||||
{
|
||||
WindowManager.LayoutParams wattrs = window.getAttributes();
|
||||
wattrs.layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||
// Allow to draw behind system bars
|
||||
wattrs.setFitInsetsTypes(0);
|
||||
}
|
||||
updateLayoutHeightOf(window, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
final View inputArea = window.findViewById(android.R.id.inputArea);
|
||||
|
||||
|
@@ -42,7 +42,9 @@ public class Keyboard2View extends View
|
||||
private Config _config;
|
||||
|
||||
private float _keyWidth;
|
||||
private float _bottomMargin;
|
||||
private float _marginRight;
|
||||
private float _marginLeft;
|
||||
private float _marginBottom;
|
||||
|
||||
private Theme _theme;
|
||||
|
||||
@@ -232,7 +234,7 @@ public class Keyboard2View extends View
|
||||
private KeyboardData.Key getKeyAtPosition(float tx, float ty)
|
||||
{
|
||||
KeyboardData.Row row = getRowAtPosition(ty);
|
||||
float x = _config.horizontal_margin;
|
||||
float x = _marginLeft;
|
||||
if (row == null || tx < x)
|
||||
return null;
|
||||
for (KeyboardData.Key key : row.keys)
|
||||
@@ -256,28 +258,56 @@ public class Keyboard2View extends View
|
||||
@Override
|
||||
public void onMeasure(int wSpec, int hSpec)
|
||||
{
|
||||
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
|
||||
int width = dm.widthPixels;
|
||||
_bottomMargin = _config.margin_bottom;
|
||||
// Compatibility with display cutouts and navigation on the right
|
||||
if (VERSION.SDK_INT >= 30)
|
||||
int width;
|
||||
int insets_left = 0;
|
||||
int insets_right = 0;
|
||||
int insets_bottom = 0;
|
||||
// LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS is set in [Keyboard2#updateSoftInputWindowLayoutParams].
|
||||
// and keyboard is allowed do draw behind status/navigation bars
|
||||
if (VERSION.SDK_INT >= 35)
|
||||
{
|
||||
WindowMetrics metrics =
|
||||
((WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE))
|
||||
.getCurrentWindowMetrics();
|
||||
Insets insets = metrics.getWindowInsets().getInsetsIgnoringVisibility(
|
||||
WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars()
|
||||
| WindowInsets.Type.displayCutout());
|
||||
width = metrics.getBounds().width() - insets.right - insets.left;
|
||||
// Starting in API 35, keyboard window has LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
|
||||
width = metrics.getBounds().width();
|
||||
WindowInsets wi = metrics.getWindowInsets();
|
||||
int insets_types =
|
||||
WindowInsets.Type.statusBars()
|
||||
| WindowInsets.Type.displayCutout()
|
||||
| WindowInsets.Type.mandatorySystemGestures()
|
||||
| WindowInsets.Type.navigationBars();
|
||||
Insets insets = wi.getInsets(insets_types);
|
||||
insets_left = insets.left;
|
||||
insets_right = insets.right;
|
||||
// On API 35, the keyboard is allowed to draw under the
|
||||
// button-navigation bar but on lower APIs, it must be discounted from
|
||||
// the width.
|
||||
if (VERSION.SDK_INT < 35)
|
||||
{
|
||||
Insets nav_insets = wi.getInsets(WindowInsets.Type.navigationBars());
|
||||
width -= nav_insets.left + nav_insets.right;
|
||||
insets_left -= nav_insets.left;
|
||||
insets_right -= nav_insets.right;
|
||||
}
|
||||
// [insets.bottom] doesn't take into account the buttons that appear in
|
||||
// the gesture navigation bar when the IME is showing so ensure a minimum
|
||||
// of margin is added.
|
||||
if (VERSION.SDK_INT >= 35)
|
||||
_bottomMargin += insets.bottom;
|
||||
insets_bottom = Math.max(insets.bottom, _config.bottomInsetMin);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
|
||||
width = dm.widthPixels;
|
||||
}
|
||||
int height =
|
||||
(int)(_config.keyHeight * _keyboard.keysHeight
|
||||
+ _config.marginTop + _bottomMargin);
|
||||
+ _config.marginTop + _marginBottom);
|
||||
setMeasuredDimension(width, height);
|
||||
_keyWidth = (width - (_config.horizontal_margin * 2)) / _keyboard.keysWidth;
|
||||
_marginLeft = Math.max(_config.horizontal_margin, insets_left);
|
||||
_marginRight = Math.max(_config.horizontal_margin, insets_right);
|
||||
_marginBottom = _config.margin_bottom + insets_bottom;
|
||||
_keyWidth = (width - _marginLeft - _marginRight) / _keyboard.keysWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -289,10 +319,10 @@ public class Keyboard2View extends View
|
||||
{
|
||||
// Disable the back-gesture on the keyboard area
|
||||
Rect keyboard_area = new Rect(
|
||||
left + (int)_config.horizontal_margin,
|
||||
left + (int)_marginLeft,
|
||||
top + (int)_config.marginTop,
|
||||
right - (int)_config.horizontal_margin,
|
||||
bottom - (int)_bottomMargin);
|
||||
right - (int)_marginRight,
|
||||
bottom - (int)_marginBottom);
|
||||
setSystemGestureExclusionRects(Arrays.asList(keyboard_area));
|
||||
}
|
||||
}
|
||||
@@ -327,7 +357,7 @@ public class Keyboard2View extends View
|
||||
for (KeyboardData.Row row : _keyboard.rows)
|
||||
{
|
||||
y += row.shift * _config.keyHeight;
|
||||
float x = _config.horizontal_margin + key_horizontal_margin / 2;
|
||||
float x = _marginLeft + key_horizontal_margin / 2;
|
||||
float keyH = row.height * _config.keyHeight - key_vertical_margin;
|
||||
for (KeyboardData.Key k : row.keys)
|
||||
{
|
||||
@@ -463,32 +493,12 @@ public class Keyboard2View extends View
|
||||
private void drawIndication(Canvas canvas, KeyboardData.Key k, float x,
|
||||
float y, float keyW, float keyH)
|
||||
{
|
||||
boolean special_font = false;
|
||||
String indic;
|
||||
int indic_length;
|
||||
float text_size;
|
||||
if (k.indication != null)
|
||||
{
|
||||
indic = k.indication;
|
||||
indic_length = indic.length();
|
||||
text_size = keyH * _config.sublabelTextSize * _config.characterSize;
|
||||
}
|
||||
else if (k.anticircle != null)
|
||||
{
|
||||
indic = k.anticircle.getString();
|
||||
// 3 character limit like regular labels
|
||||
indic_length = Math.min(indic.length(), 3);
|
||||
special_font = k.anticircle.hasFlagsAny(KeyValue.FLAG_KEY_FONT);
|
||||
text_size = scaleTextSize(k.anticircle, _config.sublabelTextSize, keyH);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (k.indication == null || k.indication.equals(""))
|
||||
return;
|
||||
}
|
||||
Paint p = _theme.indicationPaint(special_font);
|
||||
Paint p = _theme.indicationPaint(false);
|
||||
p.setColor(_theme.subLabelColor);
|
||||
p.setTextSize(text_size);
|
||||
canvas.drawText(indic, 0, indic_length,
|
||||
p.setTextSize(keyH * _config.sublabelTextSize * _config.characterSize);
|
||||
canvas.drawText(k.indication, 0, k.indication.length(),
|
||||
x + keyW / 2f, (keyH - p.ascent() - p.descent()) * 4/5 + y, p);
|
||||
}
|
||||
|
||||
|
217
srcs/juloo.keyboard2/LayoutModifier.java
Normal file
217
srcs/juloo.keyboard2/LayoutModifier.java
Normal file
@@ -0,0 +1,217 @@
|
||||
package juloo.keyboard2;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.view.KeyEvent;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public final class LayoutModifier
|
||||
{
|
||||
static Config globalConfig;
|
||||
static KeyboardData.Row bottom_row;
|
||||
static KeyboardData.Row number_row;
|
||||
static KeyboardData num_pad;
|
||||
|
||||
/** Update the layout according to the configuration.
|
||||
* - Remove the switching key if it isn't needed
|
||||
* - Remove "localized" keys from other locales (not in 'extra_keys')
|
||||
* - Replace the action key to show the right label
|
||||
* - Swap the enter and action keys
|
||||
* - Add the optional numpad and number row
|
||||
* - Add the extra keys
|
||||
*/
|
||||
public static KeyboardData modify_layout(KeyboardData kw)
|
||||
{
|
||||
// Extra keys are removed from the set as they are encountered during the
|
||||
// first iteration then automatically added.
|
||||
final Map<KeyValue, KeyboardData.PreferredPos> extra_keys = new HashMap<KeyValue, KeyboardData.PreferredPos>();
|
||||
final Set<KeyValue> remove_keys = new HashSet<KeyValue>();
|
||||
// Make sure the config key is accessible to avoid being locked in a custom
|
||||
// layout.
|
||||
extra_keys.put(KeyValue.getKeyByName("config"), KeyboardData.PreferredPos.ANYWHERE);
|
||||
extra_keys.putAll(globalConfig.extra_keys_param);
|
||||
extra_keys.putAll(globalConfig.extra_keys_custom);
|
||||
if (globalConfig.extra_keys_subtype != null && kw.locale_extra_keys)
|
||||
{
|
||||
Set<KeyValue> present = new HashSet<KeyValue>();
|
||||
present.addAll(kw.getKeys().keySet());
|
||||
present.addAll(globalConfig.extra_keys_param.keySet());
|
||||
present.addAll(globalConfig.extra_keys_custom.keySet());
|
||||
globalConfig.extra_keys_subtype.compute(extra_keys,
|
||||
new ExtraKeys.Query(kw.script, present));
|
||||
}
|
||||
KeyboardData.Row added_number_row = null;
|
||||
if (globalConfig.add_number_row && !globalConfig.show_numpad)
|
||||
added_number_row = modify_number_row(number_row, kw);
|
||||
if (added_number_row != null)
|
||||
remove_keys.addAll(added_number_row.getKeys(0).keySet());
|
||||
if (kw.bottom_row)
|
||||
kw = kw.insert_row(bottom_row, kw.rows.size());
|
||||
kw = kw.mapKeys(new KeyboardData.MapKeyValues() {
|
||||
public KeyValue apply(KeyValue key, boolean localized)
|
||||
{
|
||||
boolean is_extra_key = extra_keys.containsKey(key);
|
||||
if (is_extra_key)
|
||||
extra_keys.remove(key);
|
||||
if (localized && !is_extra_key)
|
||||
return null;
|
||||
if (remove_keys.contains(key))
|
||||
return null;
|
||||
return modify_key(key);
|
||||
}
|
||||
});
|
||||
if (globalConfig.show_numpad)
|
||||
kw = kw.addNumPad(modify_numpad(num_pad, kw));
|
||||
if (extra_keys.size() > 0)
|
||||
kw = kw.addExtraKeys(extra_keys.entrySet().iterator());
|
||||
if (added_number_row != null)
|
||||
kw = kw.insert_row(added_number_row, 0);
|
||||
return kw;
|
||||
}
|
||||
|
||||
/** Handle the numpad layout. The [main_kw] is used to adapt the numpad to
|
||||
the main layout's script. */
|
||||
public static KeyboardData modify_numpad(KeyboardData kw, KeyboardData main_kw)
|
||||
{
|
||||
final int map_digit = KeyModifier.modify_numpad_script(main_kw.numpad_script);
|
||||
return kw.mapKeys(new KeyboardData.MapKeyValues() {
|
||||
public KeyValue apply(KeyValue key, boolean localized)
|
||||
{
|
||||
switch (key.getKind())
|
||||
{
|
||||
case Char:
|
||||
char prev_c = key.getChar();
|
||||
char c = prev_c;
|
||||
if (globalConfig.inverse_numpad)
|
||||
c = inverse_numpad_char(c);
|
||||
if (map_digit != -1)
|
||||
{
|
||||
KeyValue modified = ComposeKey.apply(map_digit, c);
|
||||
if (modified != null) // Was modified by script
|
||||
return modified;
|
||||
}
|
||||
if (prev_c != c) // Was inverted
|
||||
return key.withChar(c);
|
||||
return key; // Don't fallback into [modify_key]
|
||||
}
|
||||
return modify_key(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Modify the pin entry layout. [main_kw] is used to map the digits into the
|
||||
same script. */
|
||||
public static KeyboardData modify_pinentry(KeyboardData kw, KeyboardData main_kw)
|
||||
{
|
||||
KeyboardData.MapKeyValues m = numpad_script_map(main_kw.numpad_script);
|
||||
return m == null ? kw : kw.mapKeys(m);
|
||||
}
|
||||
|
||||
/** Modify the number row according to [main_kw]'s script. */
|
||||
static KeyboardData.Row modify_number_row(KeyboardData.Row row,
|
||||
KeyboardData main_kw)
|
||||
{
|
||||
KeyboardData.MapKeyValues m = numpad_script_map(main_kw.numpad_script);
|
||||
return m == null ? row : row.mapKeys(m);
|
||||
}
|
||||
|
||||
static KeyboardData.MapKeyValues numpad_script_map(String numpad_script)
|
||||
{
|
||||
final int map_digit = KeyModifier.modify_numpad_script(numpad_script);
|
||||
if (map_digit == -1)
|
||||
return null;
|
||||
return new KeyboardData.MapKeyValues() {
|
||||
public KeyValue apply(KeyValue key, boolean localized)
|
||||
{
|
||||
switch (key.getKind())
|
||||
{
|
||||
case Char:
|
||||
KeyValue modified = ComposeKey.apply(map_digit, key.getChar());
|
||||
if (modified != null)
|
||||
return modified;
|
||||
break;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Modify keys on the main layout and on the numpad according to the config.
|
||||
*/
|
||||
static KeyValue modify_key(KeyValue orig)
|
||||
{
|
||||
switch (orig.getKind())
|
||||
{
|
||||
case Event:
|
||||
switch (orig.getEvent())
|
||||
{
|
||||
case CHANGE_METHOD_PICKER:
|
||||
if (globalConfig.switch_input_immediate)
|
||||
return KeyValue.getKeyByName("change_method_prev");
|
||||
break;
|
||||
case ACTION:
|
||||
if (globalConfig.swapEnterActionKey && globalConfig.actionLabel != null)
|
||||
return KeyValue.getKeyByName("enter");
|
||||
return KeyValue.makeActionKey(globalConfig.actionLabel);
|
||||
case SWITCH_FORWARD:
|
||||
return (globalConfig.layouts.size() > 1) ? orig : null;
|
||||
case SWITCH_BACKWARD:
|
||||
return (globalConfig.layouts.size() > 2) ? orig : null;
|
||||
case SWITCH_VOICE_TYPING:
|
||||
case SWITCH_VOICE_TYPING_CHOOSER:
|
||||
return globalConfig.shouldOfferVoiceTyping ? orig : null;
|
||||
}
|
||||
break;
|
||||
case Keyevent:
|
||||
switch (orig.getKeyevent())
|
||||
{
|
||||
case KeyEvent.KEYCODE_ENTER:
|
||||
if (globalConfig.swapEnterActionKey && globalConfig.actionLabel != null)
|
||||
return KeyValue.makeActionKey(globalConfig.actionLabel);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Modifier:
|
||||
switch (orig.getModifier())
|
||||
{
|
||||
case SHIFT:
|
||||
if (globalConfig.double_tap_lock_shift)
|
||||
return orig.withFlags(orig.getFlags() | KeyValue.FLAG_LOCK);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return orig;
|
||||
}
|
||||
|
||||
static char inverse_numpad_char(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '7': return '1';
|
||||
case '8': return '2';
|
||||
case '9': return '3';
|
||||
case '1': return '7';
|
||||
case '2': return '8';
|
||||
case '3': return '9';
|
||||
default: return c;
|
||||
}
|
||||
}
|
||||
|
||||
public static void init(Config globalConfig_, Resources res)
|
||||
{
|
||||
globalConfig = globalConfig_;
|
||||
try
|
||||
{
|
||||
number_row = KeyboardData.load_number_row(res);
|
||||
bottom_row = KeyboardData.load_bottom_row(res);
|
||||
num_pad = KeyboardData.load_num_pad(res);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e.getMessage()); // Not recoverable
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,8 +1,13 @@
|
||||
package juloo.keyboard2;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Insets;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.IBinder;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -44,4 +49,14 @@ public final class Utils
|
||||
out.append(buff, 0, l);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
/** Whether the thin gesture-navigation bar is used.
|
||||
https://stackoverflow.com/questions/36514167/how-to-really-get-the-navigation-bar-height-in-android
|
||||
*/
|
||||
public static boolean is_navigation_bar_gestural(Resources res)
|
||||
{
|
||||
// core/java/android/view/WindowManagerPolicyConstants.java
|
||||
int res_id = res.getIdentifier("config_navBarInteractionMode", "integer", "android");
|
||||
return (res_id > 0 && res.getInteger(res_id) == 2);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user